wm

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

pwm.c (16644B)


      1 #include <X11/Xatom.h>
      2 #include <X11/Xlib.h>
      3 #include <X11/Xutil.h>
      4 #include <X11/keysymdef.h>
      5 #include <signal.h>
      6 #include <stdbool.h>
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <string.h>
     10 #include <unistd.h>
     11 
     12 #define MAX(a, b) ((a) > (b) ? (a) : (b))
     13 #define MAX_WINDOWS 100
     14 
     15 #define BORDER_WIDTH 2
     16 #define BORDER_COLOR 0x444444
     17 
     18 Window windows[MAX_WINDOWS];
     19 unsigned int num_windows = 0;
     20 unsigned int current_index;
     21 
     22 void spawn(char *const argv[], Display *dpy)
     23 {
     24 	if (fork() == 0) {
     25 		if (dpy) {
     26 			close(ConnectionNumber(dpy));
     27 		}
     28 
     29 		setsid();
     30 
     31 		struct sigaction sa;
     32 		sigemptyset(&sa.sa_mask);
     33 		sa.sa_flags = 0;
     34 		sa.sa_handler = SIG_DFL;
     35 		sigaction(SIGCHLD, &sa, NULL);
     36 
     37 		execvp(argv[0], argv);
     38 
     39 		fprintf(stderr, "pwm: execvp '%s' failed: ", argv[0]);
     40 		perror(NULL);
     41 
     42 		exit(1);
     43 	}
     44 }
     45 
     46 int main(void)
     47 {
     48 	Display *dpy = XOpenDisplay(NULL);
     49 
     50 	if (!dpy) {
     51 		fprintf(stderr, "Cannot open display\n");
     52 		return 1;
     53 	}
     54 
     55 	Window root = DefaultRootWindow(dpy);
     56 	printf("root = 0x%lx\n", root);
     57 
     58 	XSelectInput(dpy, root, SubstructureRedirectMask | SubstructureNotifyMask | ButtonPressMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask | StructureNotifyMask | PropertyChangeMask);
     59 	XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("j")), Mod4Mask, root, True, GrabModeAsync, GrabModeAsync);
     60 	XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("k")), Mod4Mask, root, True, GrabModeAsync, GrabModeAsync);
     61 	XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("c")), Mod4Mask, root, True, GrabModeAsync, GrabModeAsync);
     62 	XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("q")), Mod4Mask, root, True, GrabModeAsync, GrabModeAsync);
     63 	XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("m")), Mod4Mask, root, True, GrabModeAsync, GrabModeAsync);
     64 	XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("space")), Mod4Mask, root, True, GrabModeAsync, GrabModeAsync);
     65 	XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("Return")), Mod4Mask, root, True, GrabModeAsync, GrabModeAsync);
     66 	XGrabButton(dpy, 1, Mod4Mask, root, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None);
     67 	XGrabButton(dpy, 3, Mod4Mask, root, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None);
     68 
     69 	// https://specifications.freedesktop.org/wm-spec/1.3/ar01s03.html#id-1.4.12
     70 	Window wmcheckwin = None;
     71 
     72 	///////////////////////////
     73 	// Scan existing windows //
     74 	///////////////////////////
     75 	{
     76 		Window d1, d2;
     77 		Window *wins;
     78 		unsigned int num_wins;
     79 
     80 		if (XQueryTree(dpy, root, &d1, &d2, &wins, &num_wins)) {
     81 			for (unsigned int i = 0; i < num_wins; i++) {
     82 				XWindowAttributes wa;
     83 				if (!XGetTransientForHint(dpy, wins[i], &d1) && XGetWindowAttributes(dpy, wins[i], &wa) && !wa.override_redirect && wa.map_state == IsViewable) {
     84 					windows[num_windows] = wins[i];
     85 					num_windows++;
     86 
     87 					XSetWindowBorderWidth(dpy, wins[i], BORDER_WIDTH);
     88 					XSetWindowBorder(dpy, wins[i], BORDER_COLOR);
     89 					XSelectInput(dpy, wins[i], EnterWindowMask | FocusChangeMask | PropertyChangeMask | StructureNotifyMask);
     90 				}
     91 			}
     92 		}
     93 		XFree(wins);
     94 	}
     95 
     96 	///////////////
     97 	// Main Loop //
     98 	///////////////
     99 	XWindowAttributes pressed_wa;
    100 	XWindowAttributes fullscreen_wa;
    101 	bool is_fullscreen = 0;
    102 	XButtonEvent start = {.subwindow = None};
    103 
    104 	XSync(dpy, False);
    105 	for (;;) {
    106 		XEvent ev;
    107 		XNextEvent(dpy, &ev);
    108 
    109 		switch (ev.type) {
    110 		case MapRequest:
    111 			bool exists = 0;
    112 			for (int i = 0; i < num_windows; i++) {
    113 				if (windows[i] == ev.xmaprequest.window) {
    114 					exists = 1;
    115 					break;
    116 				}
    117 			}
    118 			if (!exists) {
    119 				windows[num_windows] = ev.xmaprequest.window;
    120 				num_windows++;
    121 				XSetWindowBorderWidth(dpy, ev.xmaprequest.window, BORDER_WIDTH);
    122 				XSetWindowBorder(dpy, ev.xmaprequest.window, BORDER_COLOR);
    123 				XSelectInput(dpy, ev.xmaprequest.window, EnterWindowMask | FocusChangeMask | PropertyChangeMask | StructureNotifyMask);
    124 			}
    125 			XMapWindow(dpy, ev.xmaprequest.window);
    126 			fprintf(stderr, "%d: XMapRequestEvent 0x%lx, num_windows: %d\n", ev.type, ev.xmaprequest.window, num_windows);
    127 			break;
    128 		case UnmapNotify:
    129 			for (int i = 0; i < num_windows; i++) {
    130 				if (windows[i] == ev.xunmap.window) {
    131 					memmove(&windows[i], &windows[i + 1], (num_windows - i - 1) * sizeof(Window));
    132 					num_windows--;
    133 					break;
    134 				}
    135 			}
    136 			current_index = current_index % num_windows;
    137 			fprintf(stderr, "%d: XUnmapEvent 0x%lx, num_windows = %d\n", ev.type, ev.xunmap.window, num_windows);
    138 			XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
    139 			break;
    140 		case ClientMessage:
    141 			fprintf(stderr, "%d: XClientMessageEvent 0x%lx\n", ev.type, ev.xclient.window);
    142 			Atom net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
    143 			Atom net_wm_state_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
    144 
    145 			fprintf(stderr, "ev.xclient.message_type = %lu =  %s\n", ev.xclient.message_type, XGetAtomName(dpy, ev.xclient.message_type));
    146 			// https://specifications.freedesktop.org/wm-spec/1.3/ar01s05.html#id-1.6.8
    147 			if (ev.xclient.message_type == net_wm_state) {
    148 				Atom action = ev.xclient.data.l[0];
    149 				Atom property = (Atom)ev.xclient.data.l[1];
    150 				fprintf(stderr, "ev.xclient.data.l[0] = %lu\n", ev.xclient.data.l[0]);
    151 				fprintf(stderr, "ev.xclient.data.l[1] = %lu = %s\n", ev.xclient.data.l[1], XGetAtomName(dpy, ev.xclient.data.l[1]));
    152 				if (property == net_wm_state_fullscreen) {
    153 					if (action == 1 || (action == 2 && !is_fullscreen)) { // _NET_WM_STATE_ADD || _NET_WM_STATE_TOGGLE
    154 						fprintf(stderr, "Setting window 0x%lx to fullscreen\n", ev.xclient.window);
    155 						XChangeProperty(dpy, ev.xclient.window, net_wm_state, XA_ATOM, 32, PropModeReplace, (unsigned char *)&net_wm_state_fullscreen, 1);
    156 						XGetWindowAttributes(dpy, ev.xclient.window, &fullscreen_wa);
    157 						XWindowChanges wc = {
    158 							.x = 0,
    159 							.y = 0,
    160 							.height = DisplayHeight(dpy, DefaultScreen(dpy)),
    161 							.width = DisplayWidth(dpy, DefaultScreen(dpy)),
    162 							.border_width = 0,
    163 						};
    164 						XConfigureWindow(dpy, ev.xclient.window, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &wc);
    165 						is_fullscreen = 1;
    166 					} else if (action == 0 || (action == 2 && is_fullscreen)) { // _NET_WM_STATE_REMOVE || _NET_WM_STATE_TOGGLE
    167 						fprintf(stderr, "Exiting fullscreen for window 0x%lx\n", ev.xclient.window);
    168 						XSetWindowBorderWidth(dpy, ev.xclient.window, BORDER_WIDTH);
    169 						XDeleteProperty(dpy, ev.xclient.window, net_wm_state);
    170 						XWindowChanges wc = {
    171 							.x = fullscreen_wa.x,
    172 							.y = fullscreen_wa.y,
    173 							.height = fullscreen_wa.height,
    174 							.width = fullscreen_wa.width,
    175 							.border_width = fullscreen_wa.border_width,
    176 						};
    177 						XConfigureWindow(dpy, ev.xclient.window, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &wc);
    178 						is_fullscreen = 0;
    179 					}
    180 				}
    181 			}
    182 			break;
    183 		case KeyPress:
    184 			fprintf(stderr, "%d: XKeyEvent KeyPress %d\n", ev.type, ev.xkey.keycode);
    185 
    186 			if (ev.xkey.keycode == XKeysymToKeycode(dpy, XStringToKeysym("space"))) {
    187 				char *const cmd_argv[] = {"dmenu_run", NULL};
    188 				spawn(cmd_argv, dpy);
    189 			} else if (ev.xkey.keycode == XKeysymToKeycode(dpy, XStringToKeysym("j"))) {
    190 				XSetWindowBorder(dpy, windows[current_index], BORDER_COLOR);
    191 				current_index = (current_index + num_windows - 1) % num_windows;
    192 				XRaiseWindow(dpy, windows[current_index]);
    193 				XSetWindowBorder(dpy, windows[current_index], 0xAAAAAA);
    194 
    195 				// Send WM_TAKE_FOCUS if supported
    196 				Atom *protocols;
    197 				int n;
    198 				int supported = 0;
    199 				if (XGetWMProtocols(dpy, windows[current_index], &protocols, &n)) {
    200 					while (!supported && n--) {
    201 						supported = XInternAtom(dpy, "WM_TAKE_FOCUS", False) == protocols[n];
    202 					}
    203 					XFree(protocols);
    204 				}
    205 				printf("supported WM_TAKE_FOCUS = %d\n", supported);
    206 				if (supported) {
    207 					XClientMessageEvent send_ev = {
    208 						.type = ClientMessage,
    209 						.window = windows[current_index],
    210 						.message_type = XInternAtom(dpy, "WM_PROTOCOLS", False),
    211 						.format = 32,
    212 						.data.l[0] = XInternAtom(dpy, "WM_TAKE_FOCUS", False),
    213 						.data.l[1] = CurrentTime,
    214 					};
    215 					XSendEvent(dpy, windows[current_index], False, NoEventMask, (XEvent *)&send_ev);
    216 				} else {
    217 					XSetInputFocus(dpy, windows[current_index], RevertToPointerRoot, CurrentTime);
    218 				}
    219 			} else if (ev.xkey.keycode == XKeysymToKeycode(dpy, XStringToKeysym("k"))) {
    220 				XSetWindowBorder(dpy, windows[current_index], BORDER_COLOR);
    221 				current_index = (current_index + 1) % num_windows;
    222 				XRaiseWindow(dpy, windows[current_index]);
    223 				XSetWindowBorder(dpy, windows[current_index], 0xAAAAAA);
    224 
    225 				// Send WM_TAKE_FOCUS if supported
    226 				Atom *protocols;
    227 				int n;
    228 				int supported = 0;
    229 				if (XGetWMProtocols(dpy, windows[current_index], &protocols, &n)) {
    230 					while (!supported && n--) {
    231 						supported = XInternAtom(dpy, "WM_TAKE_FOCUS", False) == protocols[n];
    232 					}
    233 					XFree(protocols);
    234 				}
    235 				printf("supported WM_TAKE_FOCUS = %d\n", supported);
    236 				if (supported) {
    237 					XClientMessageEvent send_ev = {
    238 						.type = ClientMessage,
    239 						.window = windows[current_index],
    240 						.message_type = XInternAtom(dpy, "WM_PROTOCOLS", False),
    241 						.format = 32,
    242 						.data.l[0] = XInternAtom(dpy, "WM_TAKE_FOCUS", False),
    243 						.data.l[1] = CurrentTime,
    244 					};
    245 					XSendEvent(dpy, windows[current_index], False, NoEventMask, (XEvent *)&send_ev);
    246 				} else {
    247 					XSetInputFocus(dpy, windows[current_index], RevertToPointerRoot, CurrentTime);
    248 				}
    249 			} else if (ev.xkey.keycode == XKeysymToKeycode(dpy, XStringToKeysym("c"))) {
    250 				if (wmcheckwin) {
    251 					XDestroyWindow(dpy, wmcheckwin);
    252 					XDeleteProperty(dpy, root, XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False));
    253 					XDeleteProperty(dpy, root, XInternAtom(dpy, "_NET_SUPPORTED", False));
    254 					wmcheckwin = None;
    255 				} else {
    256 					wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0);
    257 					fprintf(stderr, "wmcheckwin = 0x%lx\n", wmcheckwin);
    258 					XChangeProperty(dpy, wmcheckwin, XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False), XA_WINDOW, 32, PropModeReplace, (unsigned char *)&wmcheckwin, 1);
    259 					XChangeProperty(
    260 						dpy, wmcheckwin, XInternAtom(dpy, "_NET_WM_NAME", False), XInternAtom(dpy, "UTF8_STRING", False), 8, PropModeReplace, (unsigned char *)"pwm", 3);
    261 					XChangeProperty(dpy, root, XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False), XA_WINDOW, 32, PropModeReplace, (unsigned char *)&wmcheckwin, 1);
    262 					Atom netatom[] = {
    263 						XInternAtom(dpy, "_NET_SUPPORTED", False),
    264 						XInternAtom(dpy, "_NET_WM_NAME", False),
    265 						XInternAtom(dpy, "_NET_WM_STATE", False),
    266 						XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False),
    267 						XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False),
    268 						XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False),
    269 						XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False),
    270 					};
    271 					XChangeProperty(dpy, root, netatom[0], XA_ATOM, 32, PropModeReplace, (unsigned char *)netatom, sizeof(netatom) / sizeof(Atom));
    272 				}
    273 			} else if (ev.xkey.keycode == XKeysymToKeycode(dpy, XStringToKeysym("m"))) {
    274 				for (int i = 0; i < num_windows; i++) {
    275 					XWindowAttributes attr;
    276 					char *window_name = NULL;
    277 					XClassHint class_hint;
    278 					Window d1;
    279 
    280 					int status = XGetTransientForHint(dpy, windows[i], &d1);
    281 					XGetWindowAttributes(dpy, windows[i], &attr);
    282 					XFetchName(dpy, windows[i], &window_name);
    283 					XGetClassHint(dpy, windows[i], &class_hint);
    284 
    285 					fprintf(stderr,
    286 						"0x%lx: %s - %s - %s - %d - (%d, %d, %d, %d) - "
    287 						"%d(%lu)\n",
    288 						windows[i],
    289 						window_name,
    290 						class_hint.res_class,
    291 						class_hint.res_name,
    292 						attr.map_state,
    293 						attr.x,
    294 						attr.y,
    295 						attr.width,
    296 						attr.height,
    297 						status,
    298 						d1);
    299 					fprintf(stderr, "override_redirect = %d\n", attr.override_redirect);
    300 				}
    301 
    302 			} else if (ev.xkey.keycode == XKeysymToKeycode(dpy, XStringToKeysym("q"))) {
    303 				// Send WM_TAKE_DELETE if supported
    304 				Atom *protocols;
    305 				int n;
    306 				int supported = 0;
    307 				if (XGetWMProtocols(dpy, windows[current_index], &protocols, &n)) {
    308 					while (!supported && n--) {
    309 						supported = XInternAtom(dpy, "WM_DELETE_WINDOW", False) == protocols[n];
    310 					}
    311 					XFree(protocols);
    312 				}
    313 				printf("supported WM_DELETE_WINDOW = %d\n", supported);
    314 
    315 				if (supported) {
    316 					XClientMessageEvent send_ev = {
    317 						.type = ClientMessage,
    318 						.window = windows[current_index],
    319 						.message_type = XInternAtom(dpy, "WM_PROTOCOLS", False),
    320 						.format = 32,
    321 						.data.l[0] = XInternAtom(dpy, "WM_DELETE_WINDOW", False),
    322 						.data.l[1] = CurrentTime,
    323 					};
    324 					XSendEvent(dpy, windows[current_index], False, NoEventMask, (XEvent *)&send_ev);
    325 				}
    326 			} else if (ev.xkey.keycode == XKeysymToKeycode(dpy, XStringToKeysym("Return"))) {
    327 				char *const cmd_argv[] = {"st", NULL};
    328 				spawn(cmd_argv, dpy);
    329 			}
    330 			break;
    331 		case ButtonPress:
    332 			fprintf(stderr, "%d: ButtonPress\n", ev.type);
    333 			if (ev.xbutton.subwindow != None) {
    334 				XGetWindowAttributes(dpy, ev.xbutton.subwindow, &pressed_wa);
    335 				start = ev.xbutton;
    336 			}
    337 			break;
    338 		case MotionNotify:
    339 			if (start.subwindow != None) {
    340 				int xdiff = ev.xbutton.x_root - start.x_root;
    341 				int ydiff = ev.xbutton.y_root - start.y_root;
    342 				XMoveResizeWindow(
    343 					dpy,
    344 					start.subwindow,
    345 					pressed_wa.x + (start.button == 1 ? xdiff : 0),
    346 					pressed_wa.y + (start.button == 1 ? ydiff : 0),
    347 					MAX(1, pressed_wa.width + (start.button == 3 ? xdiff : 0)),
    348 					MAX(1, pressed_wa.height + (start.button == 3 ? ydiff : 0)));
    349 				/* fprintf(stderr, "XMoveResizeWindow\n"); */
    350 			}
    351 			break;
    352 		case ButtonRelease:
    353 			start.subwindow = None;
    354 			fprintf(stderr, "%d: ButtonRelease\n", ev.type);
    355 			break;
    356 		case PropertyNotify:
    357 			fprintf(stderr,
    358 				"%d: PropertyNotify : window = 0x%lx, ev.xproperty.atom = %ld: %s : ev.xproperty.state = %d\n",
    359 				ev.type,
    360 				ev.xproperty.window,
    361 				ev.xproperty.atom,
    362 				XGetAtomName(dpy, ev.xproperty.atom),
    363 				ev.xproperty.state);
    364 			break;
    365 		case ConfigureRequest:;
    366 			XWindowChanges wc = {
    367 				.x = ev.xconfigurerequest.x,
    368 				.y = ev.xconfigurerequest.y,
    369 				.width = ev.xconfigurerequest.width,
    370 				.height = ev.xconfigurerequest.height,
    371 				.border_width = ev.xconfigurerequest.border_width,
    372 				.sibling = ev.xconfigurerequest.above,
    373 				.stack_mode = ev.xconfigurerequest.detail,
    374 			};
    375 			XConfigureWindow(dpy, ev.xconfigurerequest.window, ev.xconfigurerequest.value_mask, &wc);
    376 			break;
    377 		case CreateNotify:
    378 			fprintf(stderr, "ev.type = %2d = CreateNotify, window = 0x%lx\n", ev.type, ev.xcreatewindow.window);
    379 			break;
    380 		case DestroyNotify:
    381 			fprintf(stderr, "ev.type = %2d = DestroyNotify, window = 0x%lx\n", ev.type, ev.xdestroywindow.window);
    382 			Window focus_destroy;
    383 			int revert_to_destroy;
    384 			XGetInputFocus(dpy, &focus_destroy, &revert_to_destroy);
    385 			fprintf(stderr, "focus = %lx, revert_to = %d\n", focus_destroy, revert_to_destroy);
    386 			break;
    387 		case FocusIn:
    388 			fprintf(stderr, "ev.type = %2d ", ev.type);
    389 			fprintf(stderr, "= FocusIn ");
    390 			fprintf(stderr, "serial = %lu, send_event = %d, window = 0x%lx, mode = %d, detail = %d\n", ev.xfocus.serial, ev.xfocus.send_event, ev.xfocus.window, ev.xfocus.mode, ev.xfocus.detail);
    391 			Window focus_in;
    392 			int revert_to_in;
    393 			XGetInputFocus(dpy, &focus_in, &revert_to_in);
    394 			fprintf(stderr, "focus = %lx, revert_to = %d\n", focus_in, revert_to_in);
    395 			break;
    396 		case FocusOut:
    397 			fprintf(stderr, "ev.type = %2d ", ev.type);
    398 			fprintf(stderr, "= FocusOut ");
    399 			fprintf(stderr, "serial = %lu, send_event = %d, window = 0x%lx, mode = %d, detail = %d\n", ev.xfocus.serial, ev.xfocus.send_event, ev.xfocus.window, ev.xfocus.mode, ev.xfocus.detail);
    400 			Window focus_out;
    401 			int revert_to_out;
    402 			XGetInputFocus(dpy, &focus_out, &revert_to_out);
    403 			fprintf(stderr, "focus = %lx, revert_to = %d\n", focus_out, revert_to_out);
    404 			break;
    405 		default:
    406 			fprintf(stderr, "ev.type = %2d ", ev.type);
    407 			char events[LASTEvent + 1][19] = {
    408 				"",
    409 				"",
    410 				"KeyPress",
    411 				"KeyRelease",
    412 				"ButtonPress",
    413 				"ButtonRelease",
    414 				"MotionNotify",
    415 				"EnterNotify",
    416 				"LeaveNotify",
    417 				"FocusIn",
    418 				"FocusOut",
    419 				"KeymapNotify",
    420 				"Expose",
    421 				"GraphicsExpose",
    422 				"NoExpose",
    423 				"VisibilityNotify",
    424 				"CreateNotify",
    425 				"DestroyNotify",
    426 				"UnmapNotify",
    427 				"MapNotify",
    428 				"MapRequest",
    429 				"ReparentNotify",
    430 				"ConfigureNotify",
    431 				"ConfigureRequest",
    432 				"GravityNotify",
    433 				"ResizeRequest",
    434 				"CirculateNotify",
    435 				"CirculateRequest",
    436 				"PropertyNotify",
    437 				"SelectionClear",
    438 				"SelectionRequest",
    439 				"SelectionNotify",
    440 				"ColormapNotify",
    441 				"ClientMessage",
    442 				"MappingNotify",
    443 				"GenericEvent",
    444 				"LASTEvent",
    445 			};
    446 			fprintf(stderr, "= %s\n", events[ev.type]);
    447 		}
    448 	}
    449 }