glazier

X windows manipulator.
git clone git://git.zepp.club/glazier.git
Log | Files | Refs | README | LICENSE

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 }