progman.exe^H^H^H^H
at master 608 lines 16 kB view raw
1/* 2 * Copyright 2020 joshua stein <jcs@jcs.org> 3 * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>. 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining a copy 6 * of this software and associated documentation files (the "Software"), to 7 * deal in the Software without restriction, including without limitation the 8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 * sell copies of the Software, and to permit persons to whom the Software is 10 * furnished to do so, subject to the following conditions: 11 * 12 * The above copyright notice and this permission notice shall be included in 13 * all copies or substantial portions of the Software. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 19 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 */ 22 23#include <stdlib.h> 24#include <stdio.h> 25#include <poll.h> 26#include <X11/Xatom.h> 27#include <X11/extensions/shape.h> 28#include "progman.h" 29#include "atom.h" 30 31#ifndef INFTIM 32#define INFTIM (-1) 33#endif 34 35static void handle_button_press(XButtonEvent *); 36static void handle_button_release(XButtonEvent *); 37static void handle_configure_request(XConfigureRequestEvent *); 38static void handle_circulate_request(XCirculateRequestEvent *); 39static void handle_map_request(XMapRequestEvent *); 40static void handle_destroy_event(XDestroyWindowEvent *); 41static void handle_client_message(XClientMessageEvent *); 42static void handle_property_change(XPropertyEvent *); 43static void handle_enter_event(XCrossingEvent *); 44static void handle_cmap_change(XColormapEvent *); 45static void handle_expose_event(XExposeEvent *); 46static void handle_shape_change(XShapeEvent *); 47 48static XEvent ev; 49 50void 51event_loop(void) 52{ 53 struct pollfd pfd[2]; 54 55 memset(&pfd, 0, sizeof(pfd)); 56 pfd[0].fd = ConnectionNumber(dpy); 57 pfd[0].events = POLLIN; 58 pfd[1].fd = exitmsg[0]; 59 pfd[1].events = POLLIN; 60 61 for (;;) { 62 if (!XPending(dpy)) { 63 poll(pfd, 2, INFTIM); 64 if (pfd[1].revents) 65 /* exitmsg */ 66 break; 67 68 if (!XPending(dpy)) 69 continue; 70 } 71 72 XNextEvent(dpy, &ev); 73#ifdef DEBUG 74 show_event(ev); 75#endif 76 switch (ev.type) { 77 case ButtonPress: 78 handle_button_press(&ev.xbutton); 79 break; 80 case ButtonRelease: 81 handle_button_release(&ev.xbutton); 82 break; 83 case ConfigureRequest: 84 handle_configure_request(&ev.xconfigurerequest); 85 break; 86 case CirculateRequest: 87 handle_circulate_request(&ev.xcirculaterequest); 88 break; 89 case MapRequest: 90 handle_map_request(&ev.xmaprequest); 91 break; 92 case UnmapNotify: 93 handle_unmap_event(&ev.xunmap); 94 break; 95 case DestroyNotify: 96 handle_destroy_event(&ev.xdestroywindow); 97 break; 98 case ClientMessage: 99 handle_client_message(&ev.xclient); 100 break; 101 case ColormapNotify: 102 handle_cmap_change(&ev.xcolormap); 103 break; 104 case PropertyNotify: 105 handle_property_change(&ev.xproperty); 106 break; 107 case EnterNotify: 108 handle_enter_event(&ev.xcrossing); 109 break; 110 case Expose: 111 handle_expose_event(&ev.xexpose); 112 break; 113 case KeyPress: 114 case KeyRelease: 115 handle_key_event(&ev.xkey); 116 break; 117 default: 118 if (shape_support && ev.type == shape_event) 119 handle_shape_change((XShapeEvent *)&ev); 120 } 121 } 122} 123 124/* 125 * Someone clicked a button. If they clicked on a window, we want the button 126 * press, but if they clicked on the root, we're only interested in the button 127 * release. Thus, two functions. 128 * 129 * If it was on the root, we get the click by default. If it's on a window 130 * frame, we get it as well. 131 * 132 * If it's on a client window, it may still fall through to us if the client 133 * doesn't select for mouse-click events. The upshot of this is that you should 134 * be able to click on the blank part of a GTK window with Button2 to move 135 * it. 136 */ 137static void 138handle_button_press(XButtonEvent *e) 139{ 140 client_t *c = find_client(e->window, MATCH_ANY); 141 int i; 142 143 if (e->window == root) { 144 client_t *fc; 145 /* 146 * Clicking inside transparent icons may fall through to the 147 * root, so check for an iconified client here 148 */ 149 if ((fc = find_client_at_coords(e->window, e->x, e->y)) && 150 (fc->state & STATE_ICONIFIED)) { 151 c = fc; 152 e->window = c->icon; 153 } 154 } 155 156 if (c && (c->state & STATE_DOCK)) { 157 /* pass button event through */ 158 XAllowEvents(dpy, ReplayPointer, CurrentTime); 159 } else if (c) { 160 if (opt_drag_button && e->button == opt_drag_button && 161 (e->state & opt_drag_mod) && 162 !(c->state & (STATE_FULLSCREEN | STATE_ZOOMED | 163 STATE_ICONIFIED))) { 164 /* alt+click, begin moving */ 165 focus_client(c, FOCUS_NORMAL); 166 move_client(c); 167 } else if (find_client(e->window, MATCH_FRAME)) { 168 /* raising our frame will also raise the window */ 169 focus_client(c, FOCUS_NORMAL); 170 user_action(c, e->window, e->x, e->y, e->button, 1); 171 } else { 172 if (e->button == 1) 173 focus_client(c, FOCUS_NORMAL); 174 175 /* pass button event through */ 176 XAllowEvents(dpy, ReplayPointer, CurrentTime); 177 } 178 } else if (e->window == root) { 179 for (i = 0; i < nkey_actions; i++) { 180 if (key_actions[i].type == BINDING_TYPE_DESKTOP && 181 key_actions[i].mod == e->state && 182 key_actions[i].button == e->button) { 183 take_action(&key_actions[i]); 184 break; 185 } 186 } 187 } 188} 189 190static void 191handle_button_release(XButtonEvent *e) 192{ 193 client_t *c = find_client(e->window, MATCH_ANY); 194 195 if (e->window == root) { 196 client_t *fc; 197 if ((fc = find_client_at_coords(e->window, e->x, e->y)) && 198 (fc->state & STATE_ICONIFIED)) { 199 c = fc; 200 e->window = c->icon; 201 } 202 } 203 204 if (c) { 205 if (find_client(e->window, MATCH_FRAME)) 206 user_action(c, e->window, e->x, e->y, e->button, 0); 207 208 XAllowEvents(dpy, ReplayPointer, CurrentTime); 209 } 210} 211 212/* 213 * Because we are redirecting the root window, we get ConfigureRequest events 214 * from both clients we're handling and ones that we aren't. For clients we 215 * manage, we need to adjust the frame and the client window, and for unmanaged 216 * windows we have to pass along everything unchanged. 217 * 218 * Most of the assignments here are going to be garbage, but only the ones that 219 * are masked in by e->value_mask will be looked at by the X server. 220 */ 221static void 222handle_configure_request(XConfigureRequestEvent *e) 223{ 224 client_t *c = NULL; 225 XWindowChanges wc; 226 227 if ((c = find_client(e->window, MATCH_WINDOW))) { 228 recalc_frame(c); 229 230 if (e->value_mask & CWX) 231 c->geom.x = e->x + (c->geom.x - c->frame_geom.x); 232 if (e->value_mask & CWY) 233 c->geom.y = e->y + (c->geom.y - c->frame_geom.y); 234 if (e->value_mask & CWWidth) 235 c->geom.w = e->width; 236 if (e->value_mask & CWHeight) 237 c->geom.h = e->height; 238 239 constrain_frame(c); 240 241 wc.x = c->frame_geom.x; 242 wc.y = c->frame_geom.y; 243 wc.width = c->frame_geom.w; 244 wc.height = c->frame_geom.h; 245 wc.border_width = 0; 246 wc.sibling = e->above; 247 wc.stack_mode = e->detail; 248#ifdef DEBUG 249 dump_geom(c, c->frame_geom, "moving frame to"); 250#endif 251 XConfigureWindow(dpy, c->frame, e->value_mask, &wc); 252 if (e->value_mask & (CWWidth | CWHeight)) 253 set_shape(c); 254 if ((c->state & STATE_ZOOMED) && 255 (e->value_mask & (CWX | CWY | CWWidth | CWHeight))) { 256#ifdef DEBUG 257 dump_name(c, __func__, NULL, 258 "unzooming from XConfigureRequest"); 259#endif 260 unzoom_client(c); 261 } else { 262 redraw_frame(c, None); 263 send_config(c); 264 } 265 } 266 267 if (c) { 268 wc.x = c->geom.x - c->frame_geom.x; 269 wc.y = c->geom.y - c->frame_geom.y; 270 wc.width = c->geom.w; 271 wc.height = c->geom.h; 272 } else { 273 wc.x = e->x; 274 wc.y = e->y; 275 wc.width = e->width; 276 wc.height = e->height; 277 } 278 wc.sibling = e->above; 279 wc.stack_mode = e->detail; 280 XConfigureWindow(dpy, e->window, e->value_mask, &wc); 281 282 /* top client may not be the focused one now */ 283 if ((c = top_client()) && IS_ON_CUR_DESK(c)) 284 focus_client(c, FOCUS_FORCE); 285} 286 287/* 288 * The only window that we will circulate children for is the root (because 289 * nothing else would make sense). After a client requests that the root's 290 * children be circulated, the server will determine which window needs to be 291 * raised or lowered, and so all we have to do is make it so. 292 */ 293static void 294handle_circulate_request(XCirculateRequestEvent *e) 295{ 296 client_t *c; 297 298 if (e->parent == root) { 299 c = find_client(e->window, MATCH_ANY); 300 301 if (e->place == PlaceOnBottom) { 302 if (c) { 303 adjust_client_order(c, ORDER_BOTTOM); 304 if (focused) 305 focus_client(focused, FOCUS_FORCE); 306 } else 307 XLowerWindow(dpy, e->window); 308 } else { 309 if (c && IS_ON_CUR_DESK(c)) 310 focus_client(c, FOCUS_FORCE); 311 else if (c) 312 adjust_client_order(c, ORDER_TOP); 313 else 314 XRaiseWindow(dpy, e->window); 315 } 316 } 317} 318 319/* 320 * Two possibilities if a client is asking to be mapped. One is that it's a new 321 * window, so we handle that if it isn't in our clients list anywhere. The 322 * other is that it already exists and wants to de-iconify, which is simple to 323 * take care of. Since we iconify all of a window's transients when iconifying 324 * that window, de-iconify them here. 325 */ 326static void 327handle_map_request(XMapRequestEvent *e) 328{ 329 client_t *c, *p; 330 331 if ((c = find_client(e->window, MATCH_WINDOW))) { 332 uniconify_client(c); 333 for (p = focused; p; p = p->next) 334 if (p->trans == c->win) 335 uniconify_client(p); 336 } else { 337 c = new_client(e->window); 338 map_client(c); 339 } 340} 341 342/* 343 * We don't get to intercept Unmap events, so this is post mortem. If we caused 344 * the unmap ourselves earlier (explictly or by remapping), we will have 345 * incremented c->ignore_unmap. If not, time to destroy the client. 346 * 347 * Because most clients unmap and destroy themselves at once, they're gone 348 * before we even get the Unmap event, never mind the Destroy one. Therefore we 349 * must be extra careful in del_client. 350 */ 351void 352handle_unmap_event(XUnmapEvent *e) 353{ 354 client_t *c; 355 356 if ((c = find_client(e->window, MATCH_WINDOW))) { 357 if (c->ignore_unmap) 358 c->ignore_unmap--; 359 else 360 del_client(c, DEL_WITHDRAW); 361 } 362} 363 364/* 365 * But a window can also go away when it's not mapped, in which case there is 366 * no Unmap event. 367 */ 368static void 369handle_destroy_event(XDestroyWindowEvent *e) 370{ 371 client_t *c; 372 373 if ((c = find_client(e->window, MATCH_WINDOW))) 374 del_client(c, DEL_WITHDRAW); 375} 376 377static void 378handle_client_message(XClientMessageEvent *e) 379{ 380 client_t *c; 381 382 if (e->window == root) { 383 if (e->message_type == net_cur_desk && e->format == 32) 384 goto_desk(e->data.l[0]); 385 else if (e->message_type == net_num_desks && e->format == 32) { 386 if (e->data.l[0] < ndesks) 387 /* TODO: move clients from deleted desks */ 388 return; 389 ndesks = e->data.l[0]; 390 } 391 return; 392 } 393 394 c = find_client(e->window, MATCH_WINDOW); 395 if (!c) 396 return; 397 if (e->format != 32) 398 return; 399 400 if (e->message_type == wm_change_state && e->data.l[0] == IconicState) 401 iconify_client(c); 402 else if (e->message_type == net_close_window) 403 send_wm_delete(c); 404 else if (e->message_type == net_active_window) { 405 c->desk = cur_desk; 406 map_if_desk(c); 407 if (c->state == STATE_ICONIFIED) 408 uniconify_client(c); 409 focus_client(c, FOCUS_NORMAL); 410 } else if (e->message_type == net_wm_state && 411 e->data.l[1] == net_wm_state_fs) { 412 if (e->data.l[0] == net_wm_state_add || 413 (e->data.l[0] == net_wm_state_toggle && 414 c->state != STATE_FULLSCREEN)) 415 fullscreen_client(c); 416 else 417 unfullscreen_client(c); 418 } 419} 420 421/* 422 * If we have something copied to a variable, or displayed on the screen, make 423 * sure it is up to date. If redrawing the name is necessary, clear the window 424 * because Xft uses alpha rendering. 425 */ 426static void 427handle_property_change(XPropertyEvent *e) 428{ 429 client_t *c; 430#ifdef DEBUG 431 char *atom; 432#endif 433 434 if (!(c = find_client(e->window, MATCH_WINDOW))) 435 return; 436 437#ifdef DEBUG 438 atom = XGetAtomName(dpy, e->atom); 439 dump_name(c, __func__, "", atom); 440 XFree(atom); 441#endif 442 443 if (e->atom == XA_WM_NAME || e->atom == net_wm_name) { 444 if (c->name) 445 XFree(c->name); 446 c->name = get_wm_name(c->win); 447 if (c->frame_style & FRAME_TITLEBAR) 448 redraw_frame(c, c->titlebar); 449 } else if (e->atom == XA_WM_ICON_NAME || e->atom == net_wm_icon_name) { 450 if (c->icon_name) 451 XFree(c->icon_name); 452 c->icon_name = get_wm_icon_name(c->win); 453 if (c->state & STATE_ICONIFIED) 454 redraw_icon(c, c->icon_label); 455 } else if (e->atom == XA_WM_NORMAL_HINTS) { 456 update_size_hints(c); 457 fix_size(c); 458 redraw_frame(c, None); 459 send_config(c); 460 } else if (e->atom == XA_WM_HINTS) { 461 if (c->wm_hints) 462 XFree(c->wm_hints); 463 c->wm_hints = XGetWMHints(dpy, c->win); 464 if (c->wm_hints && 465 c->wm_hints->flags & (IconPixmapHint | IconMaskHint)) { 466 get_client_icon(c); 467 if (c->state & STATE_ICONIFIED) 468 redraw_icon(c, c->icon); 469 } 470 } else if (e->atom == net_wm_state || e->atom == wm_state) { 471 int was_state = c->state; 472 check_states(c); 473 if (was_state != c->state) { 474 if (c->state & STATE_ICONIFIED) 475 iconify_client(c); 476 else if (c->state & STATE_ZOOMED) 477 zoom_client(c); 478 else if (c->state & STATE_FULLSCREEN) 479 fullscreen_client(c); 480 else { 481 if (was_state & STATE_ZOOMED) 482 unzoom_client(c); 483 else if (was_state & STATE_ICONIFIED) 484 uniconify_client(c); 485 else if (was_state & STATE_FULLSCREEN) 486 unfullscreen_client(c); 487 } 488 } 489 } else if (e->atom == net_wm_desk) { 490 if (get_atoms(c->win, net_wm_desk, XA_CARDINAL, 0, 491 &c->desk, 1, NULL)) { 492 if (c->desk == -1) 493 c->desk = DESK_ALL; /* FIXME */ 494 map_if_desk(c); 495 } 496 } 497#ifdef DEBUG 498 else { 499 printf("%s: unknown atom %ld (%s)\n", __func__, (long)e->atom, 500 XGetAtomName(dpy, e->atom)); 501 } 502#endif 503} 504 505/* Support click-to-focus policy. */ 506static void 507handle_enter_event(XCrossingEvent *e) 508{ 509 client_t *c; 510 511 if ((c = find_client(e->window, MATCH_FRAME))) 512 XGrabButton(dpy, Button1, AnyModifier, c->win, False, 513 ButtonMask, GrabModeSync, GrabModeSync, None, None); 514} 515 516/* 517 * Colormap policy: when a client installs a new colormap on itself, set the 518 * display's colormap to that. We do this even if it's not focused. 519 */ 520static void 521handle_cmap_change(XColormapEvent *e) 522{ 523 client_t *c; 524 525 if ((c = find_client(e->window, MATCH_WINDOW)) && e->new) { 526 c->cmap = e->colormap; 527 XInstallColormap(dpy, c->cmap); 528 } 529} 530 531/* 532 * If we were covered by multiple windows, we will usually get multiple expose 533 * events, so ignore them unless e->count (the number of outstanding exposes) 534 * is zero. 535 */ 536static void 537handle_expose_event(XExposeEvent *e) 538{ 539 client_t *c; 540 541 if ((c = find_client(e->window, MATCH_FRAME)) && e->count == 0) 542 redraw_frame(c, e->window); 543} 544 545static void 546handle_shape_change(XShapeEvent *e) 547{ 548 client_t *c; 549 550 if ((c = find_client(e->window, MATCH_WINDOW))) 551 set_shape(c); 552} 553 554#ifdef DEBUG 555void 556show_event(XEvent e) 557{ 558 char ev_type[128]; 559 Window w; 560 client_t *c; 561 562 switch (e.type) { 563 SHOW_EV(ButtonPress, xbutton) 564 SHOW_EV(ButtonRelease, xbutton) 565 SHOW_EV(ClientMessage, xclient) 566 SHOW_EV(ColormapNotify, xcolormap) 567 SHOW_EV(ConfigureNotify, xconfigure) 568 SHOW_EV(ConfigureRequest, xconfigurerequest) 569 SHOW_EV(CirculateRequest, xcirculaterequest) 570 SHOW_EV(CreateNotify, xcreatewindow) 571 SHOW_EV(DestroyNotify, xdestroywindow) 572 SHOW_EV(EnterNotify, xcrossing) 573 SHOW_EV(Expose, xexpose) 574 SHOW_EV(KeyPress, xkey) 575 SHOW_EV(KeyRelease, xkey) 576 SHOW_EV(NoExpose, xexpose) 577 SHOW_EV(MapNotify, xmap) 578 SHOW_EV(MapRequest, xmaprequest) 579 SHOW_EV(MappingNotify, xmapping) 580 SHOW_EV(PropertyNotify, xproperty) 581 SHOW_EV(ReparentNotify, xreparent) 582 SHOW_EV(ResizeRequest, xresizerequest) 583 SHOW_EV(UnmapNotify, xunmap) 584 SHOW_EV(MotionNotify, xmotion) 585 default: 586 if (shape_support && e.type == shape_event) { 587 snprintf(ev_type, sizeof(ev_type), "ShapeNotify"); 588 w = ((XShapeEvent *) & e)->window; 589 break; 590 } 591 snprintf(ev_type, sizeof(ev_type), "unknown event %d", e.type); 592 w = None; 593 break; 594 } 595 596 if ((c = find_client(w, MATCH_WINDOW))) 597 dump_name(c, ev_type, "window", c->name); 598 else if ((c = find_client(w, MATCH_FRAME))) { 599 /* 600 * ConfigureNotify can only come from us (otherwise it'd be a 601 * ConfigureRequest) and NoExpose events are just not useful 602 */ 603 if (e.type != ConfigureNotify && e.type != NoExpose) 604 dump_name(c, ev_type, frame_name(c, w), c->name); 605 } else if (w == root) 606 dump_name(NULL, ev_type, "root", "(root)"); 607} 608#endif