From 0cfec94c0284ddd0eeeb99667ce8e30ec99e15be Mon Sep 17 00:00:00 2001 From: Jakob Kaivo Date: Fri, 14 May 2021 20:31:12 -0400 Subject: initial commit --- .gitignore | 2 + LICENSE | 21 ++++++++ Makefile | 26 ++++++++++ xnext.c | 162 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 xnext.c 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 +#include +#include +#include + +#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; +} -- cgit v1.2.1