summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Kaivo <jkk@ung.org>2021-05-14 20:31:12 -0400
committerJakob Kaivo <jkk@ung.org>2021-05-14 20:31:12 -0400
commit0cfec94c0284ddd0eeeb99667ce8e30ec99e15be (patch)
tree9f960ada8173e41ad8713d92d377aef4be592b04
initial commit
-rw-r--r--.gitignore2
-rw-r--r--LICENSE21
-rw-r--r--Makefile26
-rw-r--r--xnext.c162
4 files changed, 211 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7794b6d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+xnext
+*.o
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..778e0b5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Jakob Kaivo
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..c5931bf
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,26 @@
+.POSIX:
+
+# This Makefile was generated by maje
+# See https://gitlab.com/jkaivo/maje/ for more information
+# Do not edit this Makefile by hand
+
+CC=c99
+LD=$(CC)
+CFLAGS=-Wall -Wextra -Wpedantic -Werror -g
+LDFLAGS=
+LDLIBS=-lX11
+SRCDIR=.
+OBJDIR=.
+BINDIR=$(OBJDIR)
+
+all: $(BINDIR)/xnext
+
+clean:
+ rm -f $(BINDIR)/xnext $(OBJDIR)/*.o
+
+$(BINDIR)/xnext: $(OBJDIR)/xnext.o
+$(OBJDIR)/xnext.o: $(SRCDIR)/xnext.c
+ $(CC) $(CFLAGS) -o $@ -c $(SRCDIR)/xnext.c
+
+$(BINDIR)/xnext:
+ $(LD) $(LDFLAGS) -o $@ $(OBJDIR)/*.o $(LDLIBS)
diff --git a/xnext.c b/xnext.c
new file mode 100644
index 0000000..d02230d
--- /dev/null
+++ b/xnext.c
@@ -0,0 +1,162 @@
+#define _POSIX_C_SOURCE 200809L
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <X11/Xlib.h>
+
+#define WM_STATE_FORMAT 32
+
+static Bool is_normal(Display *dpy, Window win)
+{
+ static Atom state = None;
+ if (state == None) {
+ state = XInternAtom(dpy, "WM_STATE", True);
+ if (state == None) {
+ return False;
+ }
+ }
+
+ Atom type = AnyPropertyType;
+ int format = WM_STATE_FORMAT;
+ unsigned long nitems = 0;
+ unsigned long bytes = 0;
+ unsigned char *prop = NULL;
+
+ int r = XGetWindowProperty(dpy,
+ win,
+ state,
+ 0,
+ 0,
+ False,
+ type,
+ &type,
+ &format,
+ &nitems,
+ &bytes,
+ &prop);
+
+ if (r == Success && prop != NULL) {
+ XFree(prop);
+ return True;
+ }
+
+ return False;
+}
+
+static int compar(const void *a, const void *b)
+{
+ const Window *wina = a;
+ const Window *winb = b;
+ if (*wina > *winb) {
+ return 1;
+ }
+ if (*wina < *winb) {
+ return -1;
+ }
+ return 0;
+}
+
+static Window *walk_tree(Display *dpy, Window win, Window *list, size_t *nwin)
+{
+ Window root = win, parent = win, *children = NULL;
+ unsigned int nchildren = 0;
+ if (!XQueryTree(dpy, win, &root, &parent, &children, &nchildren)) {
+ return NULL;
+ }
+
+ for (unsigned int i = 0; i < nchildren; i++) {
+ list = walk_tree(dpy, children[i], list, nwin);
+
+ if (is_normal(dpy, children[i])) {
+ list = realloc(list, sizeof(*list) * (*nwin + 1));
+ if (list == NULL) {
+ return NULL;
+ }
+ list[*nwin] = children[i];
+ (*nwin)++;
+ }
+ }
+
+ return list;
+}
+
+int switch_to(Display *dpy, Window root, Window prev, Window win)
+{
+ Atom ActiveWindowAtom = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
+ XEvent xev = {
+ .xclient.type = ClientMessage,
+ .xclient.window = win,
+ .xclient.message_type = ActiveWindowAtom,
+ .xclient.format = 32,
+ .xclient.data.l = { 1, 1, 0, 0 },
+ };
+ XSendEvent(dpy, root, False, SubstructureRedirectMask, &xev);
+ XMapRaised(dpy, win);
+ XRaiseWindow(dpy, win);
+ XWarpPointer(dpy, prev, win, 0, 0, 0, 0, 0, 0);
+ XSetInputFocus(dpy, win, RevertToNone, CurrentTime);
+ XCloseDisplay(dpy);
+ return 0;
+}
+
+Window find_normal_parent(Display *dpy, Window win)
+{
+ if (win == PointerRoot || win == None || is_normal(dpy, win)) {
+ return win;
+ }
+
+ Window root = win, parent = win, *children = NULL;
+ unsigned int nchildren = 0;
+ if (!XQueryTree(dpy, win, &root, &parent, &children, &nchildren)) {
+ return None;
+ }
+
+ if (parent == win || is_normal(dpy, parent)) {
+ return parent;
+ }
+
+ return find_normal_parent(dpy, win);
+}
+
+int main(int argc, char *argv[])
+{
+ int previous = 0;
+
+ int c = 0;
+ while ((c = getopt(argc, argv, "p")) != -1) {
+ switch (c) {
+ case 'p':
+ previous = 1;
+ break;
+ default:
+ return 1;
+ }
+ }
+
+ Display *dpy = XOpenDisplay("");
+ Window root = XDefaultRootWindow(dpy);
+
+ Window focused = root;
+ int revert = RevertToNone;
+ XGetInputFocus(dpy, &focused, &revert);
+ focused = find_normal_parent(dpy, focused);
+
+ size_t nwin = 0;
+ Window *list = walk_tree(dpy, root, NULL, &nwin);
+ if (list == NULL) {
+ return 1;
+ }
+
+ qsort(list, nwin, sizeof(*list), compar);
+ if (focused == PointerRoot || focused == None) {
+ return switch_to(dpy, root, focused, list[previous ? nwin - 1 : 0]);
+ }
+
+ for (size_t i = 0; i < nwin; i++) {
+ if (list[i] == focused) {
+ return switch_to(dpy, root, focused, list[(i + (previous ? -1 : 1)) % nwin]);
+ }
+ }
+
+ return 1;
+}