commit 14ab6805e8690f4f6bd67102ad754b2b107747eb
Author: Pablo Cárdenas <pablo.cardenas@imca.edu.pe>
Date: Mon, 30 Dec 2024 03:33:25 -0500
Initial Commit
Diffstat:
| A | .clang-format | | | 14 | ++++++++++++++ |
| A | .gitignore | | | 2 | ++ |
| A | Makefile | | | 31 | +++++++++++++++++++++++++++++++ |
| A | config.mk | | | 20 | ++++++++++++++++++++ |
| A | pwm.c | | | 403 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
5 files changed, 470 insertions(+), 0 deletions(-)
diff --git a/.clang-format b/.clang-format
@@ -0,0 +1,14 @@
+BasedOnStyle: LLVM
+IndentWidth: 8
+UseTab: Always
+BreakBeforeBraces: Linux
+AllowShortIfStatementsOnASingleLine: false
+IndentCaseLabels: false
+
+ContinuationIndentWidth: 8
+BinPackArguments: false
+BinPackParameters: false
+AlignAfterOpenBracket: AlwaysBreak
+AllowAllParametersOfDeclarationOnNextLine: false
+
+ColumnLimit: 200
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,2 @@
+pwm
+pwm.o
diff --git a/Makefile b/Makefile
@@ -0,0 +1,31 @@
+# pwm - pablo window manager
+# See LICENSE file for copyright and license details.
+
+include config.mk
+
+SRC = pwm.c
+OBJ = ${SRC:.c=.o}
+
+all: pwm
+
+.c.o:
+ ${CC} -c ${CFLAGS} $<
+
+${OBJ}: config.mk
+
+
+pwm: ${OBJ}
+ ${CC} -o $@ ${OBJ} ${LDFLAGS}
+
+clean:
+ rm -f pwm ${OBJ} pwm-${VERSION}.tar.gz
+
+install: all
+ mkdir -p ${DESTDIR}${PREFIX}/bin
+ cp -f pwm ${DESTDIR}${PREFIX}/bin
+ chmod 755 ${DESTDIR}${PREFIX}/bin/pwm
+
+uninstall:
+ rm -f ${DESTDIR}${PREFIX}/bin/pwm
+
+.PHONY: all clean dist install uninstall
diff --git a/config.mk b/config.mk
@@ -0,0 +1,20 @@
+# pwm version
+VERSION = 0.1
+
+# Customize below to fit your system
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = ${PREFIX}/share/man
+
+# includes and libs
+INCS =
+LIBS = -lX11
+
+# flags
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
+CFLAGS = -g -std=c99 -pedantic -Wall -Wno-deprecated-declarations -O0 ${INCS} ${CPPFLAGS}
+LDFLAGS = ${LIBS}
+
+# compiler and linker
+CC = cc
diff --git a/pwm.c b/pwm.c
@@ -0,0 +1,403 @@
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/keysymdef.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define MAX_WINDOWS 100
+
+#define BORDER_WIDTH 2
+#define BORDER_COLOR 0x444444
+
+Window windows[MAX_WINDOWS];
+unsigned int num_windows = 0;
+unsigned int current_index;
+
+void spawn(char *const argv[], Display *dpy)
+{
+ if (fork() == 0) {
+ if (dpy) {
+ close(ConnectionNumber(dpy));
+ }
+
+ setsid();
+
+ struct sigaction sa;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = SIG_DFL;
+ sigaction(SIGCHLD, &sa, NULL);
+
+ execvp(argv[0], argv);
+
+ fprintf(stderr, "pwm: execvp '%s' failed: ", argv[0]);
+ perror(NULL);
+
+ exit(1);
+ }
+}
+
+int main(void)
+{
+ Display *dpy = XOpenDisplay(NULL);
+
+ if (!dpy) {
+ fprintf(stderr, "Cannot open display\n");
+ return 1;
+ }
+
+ Window root = DefaultRootWindow(dpy);
+ printf("root = %lx\n", root);
+
+ XSelectInput(dpy, root, SubstructureRedirectMask | SubstructureNotifyMask | ButtonPressMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask | StructureNotifyMask | PropertyChangeMask);
+ XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("j")), Mod4Mask, root, True, GrabModeAsync, GrabModeAsync);
+ XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("k")), Mod4Mask, root, True, GrabModeAsync, GrabModeAsync);
+ XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("q")), Mod4Mask, root, True, GrabModeAsync, GrabModeAsync);
+ XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("space")), Mod4Mask, root, True, GrabModeAsync, GrabModeAsync);
+ XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("Return")), Mod4Mask, root, True, GrabModeAsync, GrabModeAsync);
+ XGrabButton(dpy, 1, Mod4Mask, root, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None);
+ XGrabButton(dpy, 3, Mod4Mask, root, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None);
+
+ Window wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0);
+ printf("wmcheckwin = %lx\n", wmcheckwin);
+
+ Atom NetWMCheck = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False);
+ Atom utf8string = XInternAtom(dpy, "UTF8_STRING", False);
+ Atom NetWMName = XInternAtom(dpy, "_NET_WM_NAME", False);
+
+ /* This sets the _NET_SUPPORTING_WM_CHECK property on the supporting window referring to
+ * its own window ID. */
+ XChangeProperty(dpy, wmcheckwin, NetWMCheck, XA_WINDOW, 32, PropModeReplace, (unsigned char *)&wmcheckwin, 1);
+
+ /* This sets the _NET_WM_NAME property on the supporting window with the name of the window
+ * manager. */
+ XChangeProperty(dpy, wmcheckwin, NetWMName, utf8string, 8, PropModeReplace, (unsigned char *)"pwm", 3);
+
+ /* This sets the _NET_SUPPORTING_WM_CHECK on the root window referring to the supporting
+ * window's window ID. */
+ XChangeProperty(dpy, root, NetWMCheck, XA_WINDOW, 32, PropModeReplace, (unsigned char *)&wmcheckwin, 1);
+
+ /* This represents the various extended window manager hint atoms that pwm supports. */
+ Atom netatom[9] = {
+ XInternAtom(dpy, "_NET_SUPPORTED", False),
+ XInternAtom(dpy, "_NET_WM_NAME", False),
+ XInternAtom(dpy, "_NET_WM_STATE", False),
+ XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False),
+ XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False),
+ XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False),
+ XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False),
+ XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False),
+ XInternAtom(dpy, "_NET_CLIENT_LIST", False),
+ };
+ XChangeProperty(dpy, root, netatom[0], XA_ATOM, 32, PropModeReplace, (unsigned char *)netatom, sizeof(netatom) / sizeof(Atom));
+
+ ///////////////////////////
+ // Scan existing windows //
+ ///////////////////////////
+
+ Atom NetClientList = XInternAtom(dpy, "_NET_CLIENT_LIST", False);
+ XDeleteProperty(dpy, root, NetClientList);
+ {
+ Window d1, d2;
+ Window *wins;
+ unsigned int num_wins;
+
+ if (XQueryTree(dpy, root, &d1, &d2, &wins, &num_wins)) {
+ for (unsigned int i = 0; i < num_wins; i++) {
+ XWindowAttributes wa;
+ if (!XGetTransientForHint(dpy, wins[i], &d1) && XGetWindowAttributes(dpy, wins[i], &wa) && !wa.override_redirect && wa.map_state == IsViewable) {
+ windows[num_windows] = wins[i];
+ num_windows++;
+
+ XSetWindowBorderWidth(dpy, wins[i], BORDER_WIDTH);
+ XSetWindowBorder(dpy, wins[i], BORDER_COLOR);
+ XSelectInput(dpy, wins[i], EnterWindowMask | FocusChangeMask | PropertyChangeMask | StructureNotifyMask);
+ XChangeProperty(dpy, root, NetClientList, XA_WINDOW, 32, PropModeAppend, (unsigned char *)&wins[i], 1);
+ }
+ }
+ }
+ XFree(wins);
+ }
+
+ ///////////////
+ // Main Loop //
+ ///////////////
+ XWindowAttributes pressed_wa;
+ XButtonEvent start = {.subwindow = None};
+ Atom NetActiveWindow = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
+
+ XSync(dpy, False);
+ for (;;) {
+ XEvent ev;
+ XNextEvent(dpy, &ev);
+
+ switch (ev.type) {
+ case MapRequest:
+ bool exists = 0;
+ for (int i = 0; i < num_windows; i++) {
+ if (windows[i] == ev.xmaprequest.window) {
+ exists = 1;
+ break;
+ }
+ }
+ if (!exists) {
+ windows[num_windows] = ev.xmaprequest.window;
+ num_windows++;
+ XSetWindowBorderWidth(dpy, ev.xmaprequest.window, BORDER_WIDTH);
+ XSetWindowBorder(dpy, ev.xmaprequest.window, BORDER_COLOR);
+ XSelectInput(dpy, ev.xmaprequest.window, EnterWindowMask | FocusChangeMask | PropertyChangeMask | StructureNotifyMask);
+ XChangeProperty(dpy, root, NetClientList, XA_WINDOW, 32, PropModeAppend, (unsigned char *)&ev.xmaprequest.window, 1);
+ }
+ XMapWindow(dpy, ev.xmaprequest.window);
+ fprintf(stderr, "%d: XMapRequestEvent %lu, num_windows: %d\n", ev.type, ev.xmaprequest.window, num_windows);
+ break;
+ case UnmapNotify:
+ for (int i = 0; i < num_windows; i++) {
+ if (windows[i] == ev.xunmap.window) {
+ memmove(&windows[i], &windows[i + 1], (num_windows - i - 1) * sizeof(Window));
+ num_windows--;
+ break;
+ }
+ }
+ XDeleteProperty(dpy, root, NetClientList);
+ for (int i = 0; i < num_windows; i++) {
+ XChangeProperty(dpy, root, NetClientList, XA_WINDOW, 32, PropModeAppend, (unsigned char *)&windows[i], 1);
+ }
+ current_index = current_index % num_windows;
+ fprintf(stderr, "%d: XUnmapEvent %lu, num_windows = %d\n", ev.type, ev.xunmap.window, num_windows);
+ break;
+ case ClientMessage:
+ fprintf(stderr, "%d: XClientMessageEvent %lu\n", ev.type, ev.xclient.window);
+ Atom net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
+ Atom net_wm_state_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
+
+ fprintf(stderr, "ev.xclient.message_type = %lu = %s\n", ev.xclient.message_type, XGetAtomName(dpy, ev.xclient.message_type));
+ if (ev.xclient.message_type == net_wm_state) {
+ Atom action = ev.xclient.data.l[0];
+ Atom property = (Atom)ev.xclient.data.l[1];
+ fprintf(stderr, "ev.xclient.data.l[0] = %lu\n", ev.xclient.data.l[0]);
+ fprintf(stderr, "ev.xclient.data.l[1] = %lu = %s\n", ev.xclient.data.l[1], XGetAtomName(dpy, ev.xclient.data.l[1]));
+ if (property == net_wm_state_fullscreen) {
+ if (action == 1) { // _NET_WM_STATE_ADD
+ fprintf(stderr, "Setting window %lu to fullscreen\n", ev.xclient.window);
+ XChangeProperty(dpy, ev.xclient.window, net_wm_state, XA_ATOM, 32, PropModeReplace, (unsigned char *)&net_wm_state_fullscreen, 1);
+ XWindowChanges wc = {
+ .x = 0,
+ .y = 0,
+ .height = DisplayHeight(dpy, DefaultScreen(dpy)),
+ .width = DisplayWidth(dpy, DefaultScreen(dpy)),
+ .border_width = 0,
+ };
+ XConfigureWindow(dpy, ev.xclient.window, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &wc);
+ XSync(dpy, False);
+ } else if (action == 0) { // _NET_WM_STATE_REMOVE
+ fprintf(stderr, "Exiting fullscreen for window %lu\n", ev.xclient.window);
+ XSetWindowBorderWidth(dpy, ev.xclient.window, BORDER_WIDTH);
+ XDeleteProperty(dpy, ev.xclient.window, net_wm_state);
+ // Restore window to its previous size and position here
+ }
+ }
+ }
+ break;
+ case KeyPress:
+ fprintf(stderr, "%d: XKeyEvent KeyPress %d\n", ev.type, ev.xkey.keycode);
+
+ for (int i = 0; i < num_windows; i++) {
+ XWindowAttributes attr;
+ char *window_name = NULL;
+ XClassHint class_hint;
+ Window d1;
+
+ int status = XGetTransientForHint(dpy, windows[i], &d1);
+ XGetWindowAttributes(dpy, windows[i], &attr);
+ XFetchName(dpy, windows[i], &window_name);
+ XGetClassHint(dpy, windows[i], &class_hint);
+
+ fprintf(stderr,
+ "%lu: %s - %s - %s - %d - (%d, %d, %d, %d) - %d(%lu)\n",
+ windows[i],
+ window_name,
+ class_hint.res_class,
+ class_hint.res_name,
+ attr.map_state,
+ attr.x,
+ attr.y,
+ attr.width,
+ attr.height,
+ status,
+ d1);
+ fprintf(stderr, "override_redirect = %d\n", attr.override_redirect);
+ }
+
+ if (ev.xkey.keycode == XKeysymToKeycode(dpy, XStringToKeysym("space"))) {
+ char *const cmd_argv[] = {"dmenu_run", NULL};
+ spawn(cmd_argv, dpy);
+ } else if (ev.xkey.keycode == XKeysymToKeycode(dpy, XStringToKeysym("j"))) {
+ XSetWindowBorder(dpy, windows[current_index], BORDER_COLOR);
+ current_index = (current_index + num_windows - 1) % num_windows;
+ XSetInputFocus(dpy, windows[current_index], RevertToParent, CurrentTime);
+ XRaiseWindow(dpy, windows[current_index]);
+ XSetWindowBorder(dpy, windows[current_index], 0xAAAAAA);
+ XChangeProperty(dpy, root, NetActiveWindow, XA_WINDOW, 32, PropModeReplace, (unsigned char *)&windows[current_index], 1);
+ XClientMessageEvent send_ev = {
+ .type = ClientMessage,
+ .window = windows[current_index],
+ .message_type = XInternAtom(dpy, "WM_PROTOCOLS", False),
+ .format = 32,
+ .data.l[0] = XInternAtom(dpy, "WM_TAKE_FOCUS", False),
+ .data.l[1] = CurrentTime,
+ };
+ XSendEvent(dpy, windows[current_index], False, NoEventMask, (XEvent *)&send_ev);
+ } else if (ev.xkey.keycode == XKeysymToKeycode(dpy, XStringToKeysym("k"))) {
+ XSetWindowBorder(dpy, windows[current_index], BORDER_COLOR);
+ current_index = (current_index + 1) % num_windows;
+ XSetInputFocus(dpy, windows[current_index], RevertToParent, CurrentTime);
+ XRaiseWindow(dpy, windows[current_index]);
+ XSetWindowBorder(dpy, windows[current_index], 0xAAAAAA);
+ XChangeProperty(dpy, root, NetActiveWindow, XA_WINDOW, 32, PropModeReplace, (unsigned char *)&windows[current_index], 1);
+ XClientMessageEvent send_ev = {
+ .type = ClientMessage,
+ .window = windows[current_index],
+ .message_type = XInternAtom(dpy, "WM_PROTOCOLS", False),
+ .format = 32,
+ .data.l[0] = XInternAtom(dpy, "WM_TAKE_FOCUS", False),
+ .data.l[1] = CurrentTime,
+ };
+ XSendEvent(dpy, windows[current_index], False, NoEventMask, (XEvent *)&send_ev);
+ } else if (ev.xkey.keycode == XKeysymToKeycode(dpy, XStringToKeysym("q"))) {
+ // TODO: Only send event If the window supports (XGetWMProtocols)
+ Atom *protocols;
+ int n;
+ int exists = 0;
+ if (XGetWMProtocols(dpy, windows[current_index], &protocols, &n)) {
+ while (!exists && n--) {
+ printf("protocols[%d] = %s\n", n, XGetAtomName(dpy, protocols[n]));
+ exists = protocols[n] == XInternAtom(dpy, "WM_DELETE_WINDOW", False);
+ }
+ XFree(protocols);
+ }
+ printf("exists = %d\n", exists);
+
+ XClientMessageEvent send_ev = {
+ .type = ClientMessage,
+ .window = windows[current_index],
+ .message_type = XInternAtom(dpy, "WM_PROTOCOLS", False),
+ .format = 32,
+ .data.l[0] = XInternAtom(dpy, "WM_DELETE_WINDOW", False),
+ .data.l[1] = CurrentTime,
+ };
+ XSendEvent(dpy, windows[current_index], False, NoEventMask, (XEvent *)&send_ev);
+ } else if (ev.xkey.keycode == XKeysymToKeycode(dpy, XStringToKeysym("Return"))) {
+ char *const cmd_argv[] = {"st", NULL};
+ spawn(cmd_argv, dpy);
+ }
+ break;
+ case ButtonPress:
+ fprintf(stderr, "%d: ButtonPress\n", ev.type);
+ if (ev.xbutton.subwindow != None) {
+ XGetWindowAttributes(dpy, ev.xbutton.subwindow, &pressed_wa);
+ start = ev.xbutton;
+ }
+ break;
+ case MotionNotify:
+ if (start.subwindow != None) {
+ int xdiff = ev.xbutton.x_root - start.x_root;
+ int ydiff = ev.xbutton.y_root - start.y_root;
+ XMoveResizeWindow(
+ dpy,
+ start.subwindow,
+ pressed_wa.x + (start.button == 1 ? xdiff : 0),
+ pressed_wa.y + (start.button == 1 ? ydiff : 0),
+ MAX(1, pressed_wa.width + (start.button == 3 ? xdiff : 0)),
+ MAX(1, pressed_wa.height + (start.button == 3 ? ydiff : 0)));
+ /* fprintf(stderr, "XMoveResizeWindow\n"); */
+ }
+ break;
+ case ButtonRelease:
+ start.subwindow = None;
+ fprintf(stderr, "%d: ButtonRelease\n", ev.type);
+ break;
+ case PropertyNotify:
+ fprintf(stderr, "%d: PropertyNotify : ev.xproperty.atom = %ld: %s\n", ev.type, ev.xproperty.atom, XGetAtomName(dpy, ev.xproperty.atom));
+ break;
+ default:
+ fprintf(stderr, "ev.type = %2d ", ev.type);
+ if (ev.type == 2)
+ fprintf(stderr, "= KeyPress\n");
+ if (ev.type == 3)
+ fprintf(stderr, "= KeyRelease\n");
+ if (ev.type == 4)
+ fprintf(stderr, "= ButtonPress\n");
+ if (ev.type == 5)
+ fprintf(stderr, "= ButtonRelease\n");
+ if (ev.type == 6)
+ fprintf(stderr, "= MotionNotify\n");
+ if (ev.type == 7)
+ fprintf(stderr, "= EnterNotify\n");
+ if (ev.type == 8)
+ fprintf(stderr, "= LeaveNotify\n");
+ if (ev.type == 9)
+ fprintf(stderr, "= FocusIn\n");
+ if (ev.type == 10)
+ fprintf(stderr, "= FocusOut\n");
+ if (ev.type == 11)
+ fprintf(stderr, "= KeymapNotify\n");
+ if (ev.type == 12)
+ fprintf(stderr, "= Expose\n");
+ if (ev.type == 13)
+ fprintf(stderr, "= GraphicsExpose\n");
+ if (ev.type == 14)
+ fprintf(stderr, "= NoExpose\n");
+ if (ev.type == 15)
+ fprintf(stderr, "= VisibilityNotify\n");
+ if (ev.type == 16)
+ fprintf(stderr, "= CreateNotify\n");
+ if (ev.type == 17)
+ fprintf(stderr, "= DestroyNotify\n");
+ if (ev.type == 18)
+ fprintf(stderr, "= UnmapNotify\n");
+ if (ev.type == 19)
+ fprintf(stderr, "= MapNotify\n");
+ if (ev.type == 20)
+ fprintf(stderr, "= MapRequest\n");
+ if (ev.type == 21)
+ fprintf(stderr, "= ReparentNotify\n");
+ if (ev.type == 22)
+ fprintf(stderr, "= ConfigureNotify\n");
+ if (ev.type == 23)
+ fprintf(stderr, "= ConfigureRequest\n");
+ if (ev.type == 24)
+ fprintf(stderr, "= GravityNotify\n");
+ if (ev.type == 25)
+ fprintf(stderr, "= ResizeRequest\n");
+ if (ev.type == 26)
+ fprintf(stderr, "= CirculateNotify\n");
+ if (ev.type == 27)
+ fprintf(stderr, "= CirculateRequest\n");
+ if (ev.type == 28)
+ fprintf(stderr, "= PropertyNotify\n");
+ if (ev.type == 29)
+ fprintf(stderr, "= SelectionClear\n");
+ if (ev.type == 30)
+ fprintf(stderr, "= SelectionRequest\n");
+ if (ev.type == 31)
+ fprintf(stderr, "= SelectionNotify\n");
+ if (ev.type == 32)
+ fprintf(stderr, "= ColormapNotify\n");
+ if (ev.type == 33)
+ fprintf(stderr, "= ClientMessage\n");
+ if (ev.type == 34)
+ fprintf(stderr, "= MappingNotify\n");
+ if (ev.type == 35)
+ fprintf(stderr, "= GenericEvent\n");
+ if (ev.type == 36)
+ fprintf(stderr, "= LASTEvent\n");
+ }
+ }
+}