URI: 
       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