wm

My window manager
git clone git://pcardenasb.com/wm
Log | Files | Refs

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++
AMakefile | 31+++++++++++++++++++++++++++++++
Aconfig.mk | 20++++++++++++++++++++
Apwm.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"); + } + } +}