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