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 }