glazier.c (23074B)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <xcb/xcb.h> 4 #include <xcb/xcb_cursor.h> 5 #include <xcb/xcb_image.h> 6 #include <xcb/randr.h> 7 8 #include "arg.h" 9 #include "wm.h" 10 11 #define LEN(x) (sizeof(x)/sizeof(x[0])) 12 #define XEV(x) (evname[(x)->response_type & ~0x80]) 13 #define MIN(x,y) ((x)>(y)?(y):(x)) 14 #define MAX(x,y) ((x)>(y)?(x):(y)) 15 16 17 struct ev_callback_t { 18 uint32_t type; 19 int (*handle)(xcb_generic_event_t *); 20 }; 21 22 struct cursor_t { 23 int x, y, b; 24 int mode; 25 }; 26 27 enum { 28 XHAIR_DFLT, 29 XHAIR_MOVE, 30 XHAIR_SIZE, 31 XHAIR_TELE, 32 }; 33 34 enum { 35 GRAB_NONE = 0, 36 GRAB_MOVE, 37 GRAB_SIZE, 38 GRAB_TELE, 39 }; 40 41 #include "config.h" 42 43 void usage(char *); 44 static int takeover(); 45 static int adopt(xcb_window_t); 46 static uint32_t backpixel(xcb_window_t); 47 static int paint(xcb_window_t); 48 static int inflate(xcb_window_t, int); 49 static int outline(xcb_drawable_t, int, int, int, int); 50 static int ev_callback(xcb_generic_event_t *); 51 52 /* XRandR specific functions */ 53 static int crossedge(xcb_window_t); 54 static int snaptoedge(xcb_window_t); 55 56 /* XCB events callbacks */ 57 static int cb_default(xcb_generic_event_t *); 58 static int cb_create(xcb_generic_event_t *); 59 static int cb_mapreq(xcb_generic_event_t *); 60 static int cb_mouse_press(xcb_generic_event_t *); 61 static int cb_mouse_release(xcb_generic_event_t *); 62 static int cb_motion(xcb_generic_event_t *); 63 static int cb_enter(xcb_generic_event_t *); 64 static int cb_focus(xcb_generic_event_t *); 65 static int cb_configreq(xcb_generic_event_t *); 66 static int cb_configure(xcb_generic_event_t *); 67 68 int verbose = 0; 69 xcb_connection_t *conn; 70 xcb_screen_t *scrn; 71 xcb_window_t curwid; 72 struct cursor_t cursor; 73 74 static const char *evname[] = { 75 [0] = "EVENT_ERROR", 76 [XCB_CREATE_NOTIFY] = "CREATE_NOTIFY", 77 [XCB_DESTROY_NOTIFY] = "DESTROY_NOTIFY", 78 [XCB_BUTTON_PRESS] = "BUTTON_PRESS", 79 [XCB_BUTTON_RELEASE] = "BUTTON_RELEASE", 80 [XCB_MOTION_NOTIFY] = "MOTION_NOTIFY", 81 [XCB_ENTER_NOTIFY] = "ENTER_NOTIFY", 82 [XCB_CONFIGURE_NOTIFY] = "CONFIGURE_NOTIFY", 83 [XCB_KEY_PRESS] = "KEY_PRESS", 84 [XCB_FOCUS_IN] = "FOCUS_IN", 85 [XCB_FOCUS_OUT] = "FOCUS_OUT", 86 [XCB_KEYMAP_NOTIFY] = "KEYMAP_NOTIFY", 87 [XCB_EXPOSE] = "EXPOSE", 88 [XCB_GRAPHICS_EXPOSURE] = "GRAPHICS_EXPOSURE", 89 [XCB_NO_EXPOSURE] = "NO_EXPOSURE", 90 [XCB_VISIBILITY_NOTIFY] = "VISIBILITY_NOTIFY", 91 [XCB_UNMAP_NOTIFY] = "UNMAP_NOTIFY", 92 [XCB_MAP_NOTIFY] = "MAP_NOTIFY", 93 [XCB_MAP_REQUEST] = "MAP_REQUEST", 94 [XCB_REPARENT_NOTIFY] = "REPARENT_NOTIFY", 95 [XCB_CONFIGURE_REQUEST] = "CONFIGURE_REQUEST", 96 [XCB_GRAVITY_NOTIFY] = "GRAVITY_NOTIFY", 97 [XCB_RESIZE_REQUEST] = "RESIZE_REQUEST", 98 [XCB_CIRCULATE_NOTIFY] = "CIRCULATE_NOTIFY", 99 [XCB_PROPERTY_NOTIFY] = "PROPERTY_NOTIFY", 100 [XCB_SELECTION_CLEAR] = "SELECTION_CLEAR", 101 [XCB_SELECTION_REQUEST] = "SELECTION_REQUEST", 102 [XCB_SELECTION_NOTIFY] = "SELECTION_NOTIFY", 103 [XCB_COLORMAP_NOTIFY] = "COLORMAP_NOTIFY", 104 [XCB_CLIENT_MESSAGE] = "CLIENT_MESSAGE", 105 [XCB_MAPPING_NOTIFY] = "MAPPING_NOTIFY" 106 }; 107 108 static const struct ev_callback_t cb[] = { 109 /* event, function */ 110 { XCB_CREATE_NOTIFY, cb_create }, 111 { XCB_MAP_REQUEST, cb_mapreq }, 112 { XCB_BUTTON_PRESS, cb_mouse_press }, 113 { XCB_BUTTON_RELEASE, cb_mouse_release }, 114 { XCB_MOTION_NOTIFY, cb_motion }, 115 { XCB_ENTER_NOTIFY, cb_enter }, 116 { XCB_FOCUS_IN, cb_focus }, 117 { XCB_FOCUS_OUT, cb_focus }, 118 { XCB_CONFIGURE_REQUEST, cb_configreq }, 119 { XCB_CONFIGURE_NOTIFY, cb_configure }, 120 }; 121 122 void 123 usage(char *name) 124 { 125 fprintf(stderr, "usage: %s [-vh]\n", name); 126 } 127 128 /* 129 * Every window that shouldn't be ignored (override_redirect) is adoped 130 * by the WM when it is created, or when the WM is started. 131 * When a window is created, it is centered on the cursor, before it 132 * gets mapped on screen. Windows that are already visible are not moved. 133 * Some events are also registered by the WM for these windows. 134 */ 135 int 136 adopt(xcb_window_t wid) 137 { 138 if (wm_is_ignored(wid)) 139 return -1; 140 141 return wm_reg_window_event(wid, XCB_EVENT_MASK_ENTER_WINDOW 142 | XCB_EVENT_MASK_FOCUS_CHANGE 143 | XCB_EVENT_MASK_STRUCTURE_NOTIFY); 144 } 145 146 /* 147 * Return the color of the pixel in one of the window corners. 148 * Each corner is tested in a clockwise fashion until an uncovered region 149 * is found. When such pixel is found, the color is returned. 150 * If no color is found a default of border_color is returned. 151 */ 152 uint32_t 153 backpixel(xcb_window_t wid) 154 { 155 int w, h; 156 uint32_t color; 157 xcb_image_t *px; 158 159 w = wm_get_attribute(wid, ATTR_W); 160 h = wm_get_attribute(wid, ATTR_H); 161 162 px = xcb_image_get(conn, wid, 0, 0, 1, 1, 0xffffffff, XCB_IMAGE_FORMAT_Z_PIXMAP); 163 if (px) color = xcb_image_get_pixel(px, 0, 0); 164 165 if (!color) { 166 px = xcb_image_get(conn, wid, w - 1, 0, 1, 1, 0xffffffff, XCB_IMAGE_FORMAT_Z_PIXMAP); 167 if (px) color = xcb_image_get_pixel(px, 0, 0); 168 } 169 170 if (!color) { 171 px = xcb_image_get(conn, wid, 0, h - 1, 1, 1, 0xffffffff, XCB_IMAGE_FORMAT_Z_PIXMAP); 172 if (px) color = xcb_image_get_pixel(px, 0, 0); 173 } 174 175 if (!color) { 176 px = xcb_image_get(conn, wid, w, h, 1, 1, 0xffffffff, XCB_IMAGE_FORMAT_Z_PIXMAP); 177 if (px) color = xcb_image_get_pixel(px, 0, 0); 178 } 179 180 return color ? color : border_color; 181 } 182 183 /* 184 * Paint double borders around the window. The background is taken from 185 * the window content via backpixel(), and the border line is drawn on 186 * top of it using the colors defined in config.h. 187 * 188 * Note: drawing on the borders require specifying regions from position 189 * the top-left corner of the window itself. Drawing on the border pixmap 190 * is done by drawing outside the window, and then wrapping over to the 191 * left side. For example, assuming a window of 200x100, with a 10px 192 * border, drawing a 5px square in the top left of the border means drawing 193 * a 5x5 rectangle at position 210,110. The area does not wrap around 194 * indefinitely though, so drawing a rectangle of 10x10 or 200x10 at 195 * position 210,110 would have the same effect: draw a 10x10 square in 196 * the top right. uugh… 197 */ 198 int 199 paint(xcb_window_t wid) 200 { 201 int val[2], w, h, d, b, i; 202 xcb_pixmap_t px; 203 xcb_gcontext_t gc; 204 205 w = wm_get_attribute(wid, ATTR_W); 206 h = wm_get_attribute(wid, ATTR_H); 207 d = wm_get_attribute(wid, ATTR_D); 208 b = wm_get_attribute(wid, ATTR_B); 209 i = inner_border; 210 211 if (i > b) 212 return -1; 213 214 px = xcb_generate_id(conn); 215 gc = xcb_generate_id(conn); 216 217 val[0] = backpixel(wid); 218 xcb_create_gc(conn, gc, wid, XCB_GC_FOREGROUND, val); 219 xcb_create_pixmap(conn, d, px, wid, w + 2*b, h + 2*b); 220 221 /* background color */ 222 xcb_rectangle_t bg = { 0, 0, w + 2*b, h + 2*b }; 223 224 xcb_poly_fill_rectangle(conn, px, gc, 1, &bg); 225 226 /* abandon all hopes already */ 227 xcb_rectangle_t r[] = { 228 {w+(b-i)/2,0,i,h+(b+i)/2}, /* right */ 229 {w+b+(b-i)/2,0,i,h+(b+i)/2}, /* left */ 230 {0,h+(b-i)/2,w+(b-i)/2+i,i}, /* bottom; bottom-right */ 231 {0,h+b+(b-i)/2,w+(b+i)/2,i}, /* top; top-right */ 232 {w+b+(b-i)/2,h+b+(b-i)/2,i+(b-i/2),i}, /* top-left corner; top-part */ 233 {w+b+(b-i)/2,h+b+(b-i)/2,i,i+(b-i/2)}, /* top-left corner; left-part */ 234 {w+b+(b-i)/2,h+(b-i)/2,i+(b-i)/2,i}, /* top-right corner; right-part */ 235 {w+(b-i)/2,h+b+(b-i)/2,i,i+(b-i)/2} /* bottom-left corner; bottom-part */ 236 }; 237 238 val[0] = (wid == wm_get_focus()) ? border_color_active : border_color; 239 xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, val); 240 xcb_poly_fill_rectangle(conn, px, gc, 8, r); 241 242 xcb_change_window_attributes(conn, wid, XCB_CW_BORDER_PIXMAP, &px); 243 244 xcb_free_pixmap(conn, px); 245 xcb_free_gc(conn, gc); 246 247 return 0; 248 } 249 250 /* 251 * Inflating a window will grow it both vertically and horizontally in 252 * all 4 directions, thus making it look like it is inflating. 253 * The window can be "deflated" by providing a negative `step` value. 254 */ 255 int 256 inflate(xcb_window_t wid, int step) 257 { 258 int x, y, w, h; 259 260 x = wm_get_attribute(wid, ATTR_X) - step/2; 261 y = wm_get_attribute(wid, ATTR_Y) - step/2; 262 w = wm_get_attribute(wid, ATTR_W) + step; 263 h = wm_get_attribute(wid, ATTR_H) + step; 264 265 wm_teleport(wid, x, y, w, h); 266 paint(wid); 267 268 return 0; 269 } 270 271 /* 272 * When the WM is started, it will take control of the existing windows. 273 * This means registering events on them and setting the borders if they 274 * are mapped. This function is only supposed to run once at startup, 275 * as the callback functions will take control of new windows 276 */ 277 int 278 takeover() 279 { 280 int i, n; 281 xcb_window_t *orphans, wid; 282 283 n = wm_get_windows(scrn->root, &orphans); 284 285 for (i = 0; i < n; i++) { 286 wid = orphans[i]; 287 if (wm_is_ignored(wid)) 288 continue; 289 290 if (verbose) 291 fprintf(stderr, "Adopting 0x%08x\n", wid); 292 293 adopt(wid); 294 if (wm_is_mapped(wid)) { 295 wm_set_border(border, 0, wid); 296 paint(wid); 297 } 298 } 299 300 wid = wm_get_focus(); 301 if (wid != scrn->root) { 302 curwid = wid; 303 paint(wid); 304 } 305 306 return n; 307 } 308 309 /* 310 * Draws a rectangle selection on the screen. 311 * The trick here is to invert the color on the selection, so that 312 * redrawing the same rectangle will "clear" it. 313 * This function is used to dynamically draw a region for moving/resizing 314 * a window using the cursor. As such, we need to make sure that whenever 315 * we draw a rectangle, we clear out the last drawn one by redrawing 316 * the latest coordinates again, so we have to save them from one call to 317 * the other. 318 */ 319 int 320 outline(xcb_drawable_t wid, int x, int y, int w, int h) 321 { 322 int mask, val[3]; 323 static int X = 0, Y = 0, W = 0, H = 0; 324 xcb_gcontext_t gc; 325 xcb_rectangle_t r; 326 327 gc = xcb_generate_id(conn); 328 mask = XCB_GC_FUNCTION | XCB_GC_SUBWINDOW_MODE | XCB_GC_GRAPHICS_EXPOSURES; 329 val[0] = XCB_GX_INVERT; 330 val[1] = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS, 331 val[2] = 0; 332 xcb_create_gc(conn, gc, wid, mask, val); 333 334 /* redraw last rectangle to clear it */ 335 r.x = X; 336 r.y = Y; 337 r.width = W; 338 r.height = H; 339 xcb_poly_rectangle(conn, wid, gc, 1, &r); 340 341 /* draw rectangle and save its coordinates for later removal */ 342 X = r.x = x; 343 Y = r.y = y; 344 W = r.width = w; 345 H = r.height = h; 346 xcb_poly_rectangle(conn, wid, gc, 1, &r); 347 348 return 0; 349 } 350 351 /* 352 * Callback used for all events that are not explicitely registered. 353 * This is not at all necessary, and used for debugging purposes. 354 */ 355 int 356 cb_default(xcb_generic_event_t *ev) 357 { 358 if (verbose < 2) 359 return 0; 360 361 if (XEV(ev)) { 362 fprintf(stderr, "%s not handled\n", XEV(ev)); 363 } else { 364 fprintf(stderr, "EVENT %d not handled\n", ev->response_type); 365 } 366 367 return 0; 368 } 369 370 /* 371 * XCB_CREATE_NOTIFY is the first event triggered by new windows, and 372 * is used to prepare the window for use by the WM. 373 * The attribute `override_redirect` allow windows to specify that they 374 * shouldn't be handled by the WM. 375 */ 376 int 377 cb_create(xcb_generic_event_t *ev) 378 { 379 int x, y, w, h; 380 xcb_randr_monitor_info_t *m; 381 xcb_create_notify_event_t *e; 382 383 e = (xcb_create_notify_event_t *)ev; 384 385 if (e->override_redirect) 386 return 0; 387 388 if (verbose) 389 fprintf(stderr, "%s 0x%08x\n", XEV(e), e->window); 390 391 x = wm_get_attribute(e->window, ATTR_X); 392 y = wm_get_attribute(e->window, ATTR_Y); 393 394 if (!wm_is_mapped(e->window) && !x && !y) { 395 wm_get_cursor(0, scrn->root, &x, &y); 396 397 /* move window under the cursor */ 398 if ((m = wm_get_monitor(wm_find_monitor(x, y)))) { 399 w = wm_get_attribute(e->window, ATTR_W); 400 h = wm_get_attribute(e->window, ATTR_H); 401 x = MAX(m->x, x - w/2); 402 y = MAX(m->y, y - h/2); 403 404 wm_teleport(e->window, x, y, w, h); 405 } 406 } 407 408 adopt(e->window); 409 410 return 0; 411 } 412 413 /* 414 * XCB_MAP_REQUEST is triggered by a window that wants to be mapped on 415 * screen. This is then the responsibility of the WM to map it on screen 416 * and eventually decorate it. This event require that the WM register 417 * XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT on the root window to intercept 418 * map requests. 419 */ 420 int 421 cb_mapreq(xcb_generic_event_t *ev) 422 { 423 xcb_map_request_event_t *e; 424 425 e = (xcb_map_request_event_t *)ev; 426 427 if (verbose) 428 fprintf(stderr, "%s 0x%08x\n", XEV(e), e->window); 429 430 wm_remap(e->window, MAP); 431 wm_set_border(border, 0, e->window); 432 wm_set_focus(e->window); 433 paint(e->window); 434 435 /* prevent window to pop outside the screen */ 436 if (crossedge(e->window)) 437 snaptoedge(e->window); 438 439 return 0; 440 } 441 442 /* 443 * The WM grabs XCB_BUTTON_PRESS events when the modifier is held. 444 * Once pressed, we'll grab the pointer entirely (without modifiers) 445 * and wait for motion/release events. 446 * The special mouse buttons 4/5 (scroll up/down) are treated especially, 447 * as they do not trigger any "release" event. 448 * 449 * This function must also save the window ID where the mouse press 450 * occured so we know which window to move/resize, even if the focus 451 * changes to another window. 452 * For similar reasons, we must save the cursor position. 453 */ 454 int 455 cb_mouse_press(xcb_generic_event_t *ev) 456 { 457 int mask; 458 static xcb_timestamp_t lasttime = 0; 459 xcb_button_press_event_t *e; 460 xcb_window_t wid; 461 462 e = (xcb_button_press_event_t *)ev; 463 464 /* ignore some motion events if they happen too often */ 465 if (e->time - lasttime < 8) 466 return -1; 467 468 wid = e->child ? e->child : e->event; 469 470 if (verbose) 471 fprintf(stderr, "%s 0x%08x %d\n", XEV(e), wid, e->detail); 472 473 cursor.x = e->root_x - wm_get_attribute(wid, ATTR_X); 474 cursor.y = e->root_y - wm_get_attribute(wid, ATTR_Y); 475 cursor.b = e->detail; 476 lasttime = e->time; 477 478 mask = XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION; 479 480 switch(e->detail) { 481 case 1: 482 curwid = wid; 483 cursor.mode = GRAB_MOVE; 484 wm_reg_cursor_event(scrn->root, mask, xhair[XHAIR_MOVE]); 485 break; 486 case 2: 487 /* teleport acts on the last focused window */ 488 cursor.x = e->root_x; 489 cursor.y = e->root_y; 490 cursor.mode = GRAB_TELE; 491 wm_reg_cursor_event(scrn->root, mask, xhair[XHAIR_TELE]); 492 break; 493 case 3: 494 curwid = wid; 495 cursor.mode = GRAB_SIZE; 496 wm_reg_cursor_event(scrn->root, mask, xhair[XHAIR_SIZE]); 497 break; 498 case 4: 499 inflate(wid, move_step); 500 wm_restack(e->child, XCB_STACK_MODE_ABOVE); 501 break; 502 case 5: 503 inflate(wid, - move_step); 504 wm_restack(wid, XCB_STACK_MODE_ABOVE); 505 break; 506 default: 507 return -1; 508 } 509 510 return 0; 511 } 512 513 /* 514 * When XCB_BUTTON_RELEASE is triggered, this will "commit" any 515 * move/resize initiated on a previous mouse press. 516 * This will also ungrab the mouse pointer. 517 */ 518 int 519 cb_mouse_release(xcb_generic_event_t *ev) 520 { 521 int x, y, w, h; 522 xcb_cursor_t p; 523 xcb_cursor_context_t *cx; 524 xcb_button_release_event_t *e; 525 526 e = (xcb_button_release_event_t *)ev; 527 if (verbose) 528 fprintf(stderr, "%s 0x%08x %d\n", XEV(e), e->event, e->detail); 529 530 /* only respond to release events for the current grab mode */ 531 if (cursor.mode != GRAB_NONE && e->detail != cursor.b) 532 return -1; 533 534 if (xcb_cursor_context_new(conn, scrn, &cx) < 0) { 535 fprintf(stderr, "cannot instantiate cursor\n"); 536 exit(1); 537 } 538 539 p = xcb_cursor_load_cursor(cx, xhair[XHAIR_DFLT]); 540 xcb_change_window_attributes(conn, e->event, XCB_CW_CURSOR, &p); 541 xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); 542 543 xcb_cursor_context_free(cx); 544 545 switch (e->detail) { 546 case 1: 547 w = wm_get_attribute(curwid, ATTR_W); 548 h = wm_get_attribute(curwid, ATTR_H); 549 wm_teleport(curwid, e->root_x - cursor.x, e->root_y - cursor.y, w, h); 550 break; 551 case 2: 552 x = MIN(e->root_x,cursor.x); 553 y = MIN(e->root_y,cursor.y); 554 w = MAX(e->root_x,cursor.x) - x; 555 h = MAX(e->root_y,cursor.y) - y; 556 wm_teleport(curwid, x, y, w, h); 557 break; 558 case 3: 559 x = wm_get_attribute(curwid, ATTR_X); 560 y = wm_get_attribute(curwid, ATTR_Y); 561 wm_teleport(curwid, x, y, e->root_x - x, e->root_y - y); 562 break; 563 } 564 565 cursor.x = 0; 566 cursor.y = 0; 567 cursor.b = 0; 568 cursor.mode = GRAB_NONE; 569 570 wm_restack(curwid, XCB_STACK_MODE_ABOVE); 571 572 /* clear last drawn rectangle to avoid leaving artefacts */ 573 outline(scrn->root, 0, 0, 0, 0); 574 xcb_clear_area(conn, 0, scrn->root, 0, 0, 0, 0); 575 576 w = wm_get_attribute(curwid, ATTR_W); 577 h = wm_get_attribute(curwid, ATTR_H); 578 xcb_clear_area(conn, 1, curwid, 0, 0, w, h); 579 paint(curwid); 580 581 return 0; 582 } 583 584 /* 585 * When the pointer is grabbed, every move triggers a XCB_MOTION_NOTIFY. 586 * Events are reported for every single move by 1 pixel. 587 * 588 * This can spam a huge lot of events, and treating them all can be 589 * resource hungry and make the interface feels laggy. 590 * To get around this, we must ignore some of these events. This is done 591 * by using the `time` attribute, and only processing new events every 592 * X milliseconds. 593 * 594 * This callback is different from the others because it does not uses 595 * the ID of the window that reported the event, but an ID previously 596 * saved in cb_mouse_press(). 597 * This makes sense as we want to move the last window we clicked on, 598 * and not the window we are moving over. 599 */ 600 int 601 cb_motion(xcb_generic_event_t *ev) 602 { 603 int x, y, w, h; 604 static xcb_timestamp_t lasttime = 0; 605 xcb_motion_notify_event_t *e; 606 607 e = (xcb_motion_notify_event_t *)ev; 608 609 /* ignore some motion events if they happen too often */ 610 if (e->time - lasttime < 32) 611 return 0; 612 613 if (curwid == scrn->root) 614 return -1; 615 616 if (verbose) 617 fprintf(stderr, "%s 0x%08x %d,%d\n", XEV(e), curwid, 618 e->root_x, e->root_y); 619 620 lasttime = e->time; 621 622 switch (e->state & (XCB_BUTTON_MASK_1|XCB_BUTTON_MASK_2|XCB_BUTTON_MASK_3)) { 623 case XCB_BUTTON_MASK_1: 624 x = e->root_x - cursor.x; 625 y = e->root_y - cursor.y; 626 w = wm_get_attribute(curwid, ATTR_W); 627 h = wm_get_attribute(curwid, ATTR_H); 628 outline(scrn->root, x, y, w, h); 629 break; 630 case XCB_BUTTON_MASK_2: 631 x = MIN(cursor.x, e->root_x); 632 y = MIN(cursor.y, e->root_y); 633 w = MAX(cursor.x - e->root_x, e->root_x - cursor.x); 634 h = MAX(cursor.y - e->root_y, e->root_y - cursor.y); 635 outline(scrn->root, x, y, w, h); 636 break; 637 case XCB_BUTTON_MASK_3: 638 x = wm_get_attribute(curwid, ATTR_X); 639 y = wm_get_attribute(curwid, ATTR_Y); 640 w = e->root_x - x; 641 h = e->root_y - y; 642 outline(scrn->root, x, y, w, h); 643 break; 644 default: 645 return -1; 646 } 647 648 return 0; 649 } 650 651 /* 652 * Each time the pointer moves from one window to another, an 653 * XCB_ENTER_NOTIFY event is fired. This is used to switch input focus 654 * between windows to follow where the pointer is. 655 */ 656 int 657 cb_enter(xcb_generic_event_t *ev) 658 { 659 xcb_enter_notify_event_t *e; 660 661 e = (xcb_enter_notify_event_t *)ev; 662 663 if (wm_is_ignored(e->event)) 664 return 0; 665 666 if (cursor.mode != GRAB_NONE) 667 return 0; 668 669 if (verbose) 670 fprintf(stderr, "%s 0x%08x\n", XEV(e), e->event); 671 672 return wm_set_focus(e->event); 673 } 674 675 /* 676 * Whenever the input focus change from one window to another, both an 677 * XCB_FOCUS_OUT and XCB_FOCUS_IN are fired. 678 * This is the occasion to change the border color to represent focus. 679 */ 680 int 681 cb_focus(xcb_generic_event_t *ev) 682 { 683 xcb_focus_in_event_t *e; 684 685 e = (xcb_focus_in_event_t *)ev; 686 687 if (verbose) 688 fprintf(stderr, "%s 0x%08x\n", XEV(e), e->event); 689 690 switch(e->response_type & ~0x80) { 691 case XCB_FOCUS_IN: 692 curwid = e->event; 693 return paint(e->event); 694 break; /* NOTREACHED */ 695 case XCB_FOCUS_OUT: 696 return paint(e->event); 697 break; /* NOTREACHED */ 698 } 699 700 return -1; 701 } 702 703 /* 704 * XCB_CONFIGURE_REQUEST is triggered by every window that wants to 705 * change its attributes like size, stacking order or border. 706 * These must now be handled by the WM because of the 707 * XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT registration. 708 */ 709 int 710 cb_configreq(xcb_generic_event_t *ev) 711 { 712 int x, y, w, h; 713 xcb_configure_request_event_t *e; 714 715 e = (xcb_configure_request_event_t *)ev; 716 717 if (verbose) 718 fprintf(stderr, "%s 0x%08x 0x%08x:%dx%d+%d+%d\n", 719 XEV(e), e->parent, e->window, 720 e->width, e->height, 721 e->x, e->y); 722 723 if (e->value_mask & 724 ( XCB_CONFIG_WINDOW_X 725 | XCB_CONFIG_WINDOW_Y 726 | XCB_CONFIG_WINDOW_WIDTH 727 | XCB_CONFIG_WINDOW_HEIGHT)) { 728 x = wm_get_attribute(e->window, ATTR_X); 729 y = wm_get_attribute(e->window, ATTR_Y); 730 w = wm_get_attribute(e->window, ATTR_W); 731 h = wm_get_attribute(e->window, ATTR_H); 732 733 if (e->value_mask & XCB_CONFIG_WINDOW_X) x = e->x; 734 if (e->value_mask & XCB_CONFIG_WINDOW_Y) y = e->y; 735 if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH) w = e->width; 736 if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT) h = e->height; 737 738 wm_teleport(e->window, x, y, w, h); 739 740 /* redraw border pixmap after move/resize */ 741 paint(e->window); 742 } 743 744 if (e->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) 745 wm_set_border(e->border_width, border_color, e->window); 746 747 if (e->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) 748 wm_restack(e->window, e->stack_mode); 749 750 return 0; 751 } 752 753 int 754 cb_configure(xcb_generic_event_t *ev) 755 { 756 xcb_configure_notify_event_t *e; 757 758 e = (xcb_configure_notify_event_t *)ev; 759 760 if (verbose) 761 fprintf(stderr, "%s 0x%08x %dx%d+%d+%d\n", 762 XEV(e), e->window, 763 e->width, e->height, 764 e->x, e->y); 765 766 /* update screen size when root window's size change */ 767 if (e->window == scrn->root) { 768 scrn->width_in_pixels = e->width; 769 scrn->height_in_pixels = e->height; 770 } 771 772 return 0; 773 } 774 775 /* 776 * This functions uses the ev_callback_t structure to call out a specific 777 * callback function for each EVENT fired. 778 */ 779 int 780 ev_callback(xcb_generic_event_t *ev) 781 { 782 uint8_t i; 783 uint32_t type; 784 785 if (!ev) 786 return -1; 787 788 type = ev->response_type & ~0x80; 789 for (i=0; i<LEN(cb); i++) 790 if (type == cb[i].type) 791 return cb[i].handle(ev); 792 793 return cb_default(ev); 794 } 795 796 /* 797 * Returns 1 is the given window's geometry crosses the monitor's edge, 798 * and 0 otherwise 799 */ 800 int 801 crossedge(xcb_window_t wid) 802 { 803 int r = 0; 804 int x, y, w, h, b; 805 xcb_randr_monitor_info_t *m; 806 807 b = wm_get_attribute(wid, ATTR_B); 808 x = wm_get_attribute(wid, ATTR_X); 809 y = wm_get_attribute(wid, ATTR_Y); 810 w = wm_get_attribute(wid, ATTR_W); 811 h = wm_get_attribute(wid, ATTR_H); 812 m = wm_get_monitor(wm_find_monitor(x, y)); 813 814 if (!m) 815 return -1; 816 817 if ((x + w + 2*b > m->x + m->width) 818 || (y + h + 2*b > m->y + m->height)) 819 r = 1; 820 821 free(m); 822 return r; 823 } 824 825 /* 826 * Moves a window so that its border doesn't cross the monitor's edge 827 */ 828 int 829 snaptoedge(xcb_window_t wid) 830 { 831 int x, y, w, h, b; 832 xcb_randr_monitor_info_t *m; 833 834 b = wm_get_attribute(wid, ATTR_B); 835 x = wm_get_attribute(wid, ATTR_X); 836 y = wm_get_attribute(wid, ATTR_Y); 837 w = wm_get_attribute(wid, ATTR_W); 838 h = wm_get_attribute(wid, ATTR_H); 839 m = wm_get_monitor(wm_find_monitor(x, y)); 840 841 if (!m) 842 return -1; 843 844 if (w + 2*b > m->width) w = m->width - 2*b; 845 if (h + 2*b > m->height) h = m->height - 2*b; 846 847 if (x + w + 2*b > m->x + m->width) x = MAX(m->x, m->x + m->width - w - 2*b); 848 if (y + h + 2*b > m->y + m->height) y = MAX(m->y, m->y + m->height - h - 2*b); 849 850 wm_teleport(wid, x, y, w, h); 851 852 return 0; 853 } 854 855 int 856 main (int argc, char *argv[]) 857 { 858 int mask; 859 char *argv0; 860 xcb_generic_event_t *ev = NULL; 861 862 ARGBEGIN { 863 case 'v': 864 verbose++; 865 break; 866 case 'h': 867 usage(argv0); 868 return 0; 869 break; /* NOTREACHED */ 870 default: 871 usage(argv0); 872 return -1; 873 break; /* NOTREACHED */ 874 } ARGEND; 875 876 wm_init_xcb(); 877 wm_get_screen(); 878 879 curwid = scrn->root; 880 881 /* needed to get notified of windows creation */ 882 mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY 883 | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY 884 | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; 885 886 if (!wm_reg_window_event(scrn->root, mask)) { 887 fprintf(stderr, "Cannot redirect root window event.\n"); 888 return -1; 889 } 890 891 xcb_grab_button(conn, 0, scrn->root, XCB_EVENT_MASK_BUTTON_PRESS, 892 XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, scrn->root, 893 XCB_NONE, XCB_BUTTON_INDEX_ANY, modifier); 894 895 takeover(); 896 897 for (;;) { 898 xcb_flush(conn); 899 ev = xcb_wait_for_event(conn); 900 if (!ev) 901 break; 902 903 ev_callback(ev); 904 free(ev); 905 } 906 907 return wm_kill_xcb(); 908 }