+/****************
+ * XCB Wrappers *
+ ****************/
+
+static int do_query_tree(xcb_window_t win, xcb_window_t **kids)
+{
+ xcb_query_tree_cookie_t cookie =
+ xcb_query_tree(conn, win);
+ if (!cookie.sequence)
+ return warn("do_query_tree: %d - bad cookie", win);
+
+ xcb_query_tree_reply_t *reply =
+ xcb_query_tree_reply(conn, cookie, NULL);
+ if (!reply)
+ return warn("do_query_tree: %d - no reply", win);
+
+ int nkids = xcb_query_tree_children_length(reply);
+ *kids = xcb_query_tree_children(reply);
+ printf("do_query_tree: %d - n=%d\n", win, nkids);
+ return nkids;
+}
+
+static int do_get_geometry(xcb_window_t win,
+ int *x, int *y, int *w, int *h)
+{
+ xcb_get_geometry_cookie_t cookie =
+ xcb_get_geometry(conn, win);
+ if (!cookie.sequence)
+ return warn("do_get_geometry: %d - bad cookie", win);
+
+ xcb_get_geometry_reply_t *reply =
+ xcb_get_geometry_reply(conn, cookie, NULL);
+ if (!reply)
+ return warn("do_get_geometry: %d - no reply", win);
+
+ printf("do_get_geometry: %d - %dx%d @ %d,%d\n",
+ win, reply->width, reply->height, reply->x, reply->y);
+ *x = reply->x;
+ *y = reply->y;
+ *w = reply->width;
+ *h = reply->height;
+ return 1;
+}
+
+static int do_get_window_attributes(xcb_window_t win,
+ int *override, int *mapped)
+{
+ xcb_get_window_attributes_cookie_t cookie =
+ xcb_get_window_attributes(conn, win);
+ if (!cookie.sequence)
+ return warn("do_get_window_attributes: %d - bad cookie", win);
+
+ xcb_get_window_attributes_reply_t *reply =
+ xcb_get_window_attributes_reply(conn, cookie, NULL);
+ if (!reply)
+ return warn("do_get_window_attributes: %d - no reply ", win);
+
+ printf("do_get_window_attributes: %d - %d\n",
+ win, reply->override_redirect);
+ *override = reply->override_redirect;
+ *mapped = reply->map_state != XCB_MAP_STATE_UNMAPPED;
+ return 1;
+}
+
+static int do_xinerama_check(void)
+{
+ const xcb_query_extension_reply_t *data =
+ xcb_get_extension_data(conn, &xcb_xinerama_id);
+ if (!data || !data->present)
+ return warn("do_xinerama_check: no ext");
+
+ xcb_xinerama_is_active_cookie_t cookie =
+ xcb_xinerama_is_active(conn);
+ if (!cookie.sequence)
+ return warn("do_xinerama_check: no cookie");
+
+ xcb_xinerama_is_active_reply_t *reply =
+ xcb_xinerama_is_active_reply(conn, cookie, NULL);
+ if (!reply)
+ warn("do_xinerama_check: no reply");
+
+ printf("do_xinerama_check: %d\n", reply->state);
+ return reply && reply->state;
+}
+
+static int do_query_screens(xcb_xinerama_screen_info_t **info)
+{
+ xcb_xinerama_query_screens_cookie_t cookie =
+ xcb_xinerama_query_screens(conn);
+ if (!cookie.sequence)
+ return warn("do_query_screens: bad cookie");
+
+ xcb_xinerama_query_screens_reply_t *reply =
+ xcb_xinerama_query_screens_reply(conn, cookie, NULL);
+ if (!reply)
+ return warn("do_query_screens: no reply");
+
+ int ninfo = xcb_xinerama_query_screens_screen_info_length(reply);
+ *info = xcb_xinerama_query_screens_screen_info(reply);
+ printf("do_query_screens: %d screens\n", ninfo);
+ return ninfo;
+}
+
+static int do_get_input_focus(void)
+{
+ xcb_get_input_focus_cookie_t cookie =
+ xcb_get_input_focus(conn);
+ if (!cookie.sequence)
+ return warn("do_get_input_focus: bad cookie");
+
+ xcb_get_input_focus_reply_t *reply =
+ xcb_get_input_focus_reply(conn, cookie, NULL);
+ if (!reply)
+ return warn("do_get_input_focus: no reply");
+
+ return reply->focus;
+}
+
+static xcb_atom_t do_intern_atom(const char *name)
+{
+ xcb_intern_atom_cookie_t cookie =
+ xcb_intern_atom(conn, 0, strlen(name), name);
+ if (!cookie.sequence)
+ return warn("do_intern_atom: bad cookie");
+
+ xcb_intern_atom_reply_t *reply =
+ xcb_intern_atom_reply(conn, cookie, NULL);
+ if (!reply)
+ return warn("do_intern_atom: no reply");
+
+ xcb_atom_t atom = reply->atom;
+ free(reply);
+ return atom;
+}
+
+static int do_ewmh_init_atoms(void)
+{
+ xcb_intern_atom_cookie_t *cookies =
+ xcb_ewmh_init_atoms(conn, &ewmh);
+ if (!cookies)
+ return warn("do_ewmh_init_atoms: no cookies");
+
+ int status =
+ xcb_ewmh_init_atoms_replies(&ewmh, cookies, NULL);
+ if (!status)
+ return warn("do_ewmh_init_atoms: no status");
+ return status;
+}
+
+static int do_get_strut(xcb_window_t win, strut_t *strut)
+{
+ xcb_get_property_cookie_t cookie =
+ xcb_ewmh_get_wm_strut(&ewmh, win);
+ if (!cookie.sequence)
+ return warn("do_get_strut: bad cookie");
+
+ xcb_ewmh_get_extents_reply_t ext = {};
+ int status =
+ xcb_ewmh_get_wm_strut_reply(&ewmh, cookie, &ext, NULL);
+ if (!status)
+ return warn("do_get_strut: no status");
+
+ strut->left = ext.left;
+ strut->right = ext.right;
+ strut->top = ext.top;
+ strut->bottom = ext.bottom;
+
+ return ext.left || ext.right || ext.top || ext.bottom;
+}
+
+static xcb_pixmap_t do_alloc_color(uint32_t rgb)
+{
+ uint16_t r = (rgb & 0xFF0000) >> 8;
+ uint16_t g = (rgb & 0x00FF00);
+ uint16_t b = (rgb & 0x0000FF) << 8;
+ xcb_alloc_color_cookie_t cookie =
+ xcb_alloc_color(conn, colormap, r, g, b);
+ if (!cookie.sequence)
+ return warn("do_alloc_color: bad cookie");
+
+ xcb_alloc_color_reply_t *reply =
+ xcb_alloc_color_reply(conn, cookie, NULL);
+ if (!reply)
+ return warn("do_alloc_color: no reply");
+
+ printf("do_alloc_color: %06x -> %06x\n", rgb, reply->pixel);
+ xcb_pixmap_t pixel = reply->pixel;
+ free(reply);
+ return pixel;
+}
+
+static void do_grab_pointer(xcb_event_mask_t mask)
+{
+ if (!grabbed)
+ xcb_grab_pointer(conn, 0, root, mask,
+ XCB_GRAB_MODE_ASYNC,
+ XCB_GRAB_MODE_ASYNC,
+ 0, 0, XCB_CURRENT_TIME);
+ grabbed++;
+}
+
+static void do_ungrab_pointer(void)
+{
+ grabbed--;
+ if (!grabbed)
+ xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
+}
+
+static void do_configure_window(xcb_window_t win,
+ int x, int y, int w, int h,
+ int b, int s, int r)
+{
+ int table[][2] = {
+ { x, XCB_CONFIG_WINDOW_X },
+ { y, XCB_CONFIG_WINDOW_Y },
+ { w, XCB_CONFIG_WINDOW_WIDTH },
+ { h, XCB_CONFIG_WINDOW_HEIGHT },
+ { b, XCB_CONFIG_WINDOW_BORDER_WIDTH },
+ { s, XCB_CONFIG_WINDOW_SIBLING },
+ { r, XCB_CONFIG_WINDOW_STACK_MODE },
+ };
+
+ uint16_t mask = 0;
+ uint32_t list[7] = {};
+ for (int i = 0; i < 7; i++) {
+ if (table[i][0] >= 0) {
+ list[i] = table[i][0];
+ mask |= table[i][1];
+ }
+ }
+
+ xcb_configure_window(conn, win, mask, list);
+}
+
+static int do_client_message(xcb_window_t win, xcb_atom_t atom)
+{
+ /* Get protocols */
+ xcb_get_property_cookie_t cookie =
+ xcb_icccm_get_wm_protocols(conn, win, wm_protos);
+ if (!cookie.sequence)
+ return warn("do_client_message: %d - bad cookie", win);
+
+ xcb_icccm_get_wm_protocols_reply_t protos = {};
+ if (!xcb_icccm_get_wm_protocols_reply(conn, cookie, &protos, NULL))
+ return warn("do_client_message: %d - no reply", win);
+
+ /* Search for the atom */
+ int found = 0;
+ for (int i = 0; i < protos.atoms_len; i++)
+ if (protos.atoms[i] == atom)
+ found = 1;
+ if (!found)
+ return warn("do_client_message: %d - no atom", win);
+
+ /* Send the message */
+ xcb_client_message_event_t msg = {
+ .response_type = XCB_CLIENT_MESSAGE,
+ .format = 32,
+ .window = win,
+ .type = wm_protos,
+ .data.data32[0] = atom,
+ .data.data32[1] = XCB_CURRENT_TIME,
+ };
+ xcb_send_event(conn, 0, win, XCB_EVENT_MASK_NO_EVENT,
+ (const char *)&msg);
+ return 1;
+}
+
+/**************************
+ * Window Manager Helpers *
+ **************************/
+
+/* Send event info */
+static int send_event(event_t ev, xcb_window_t ewin)
+{
+ win_t *win = win_get(ewin);
+ do_grab_pointer(0);
+ int status = wm_handle_event(win, ev, MOD(), PTR());
+ do_ungrab_pointer();
+ return status;
+}
+
+/* Send event info */
+static int send_event_info(event_t ev, xcb_mod_mask_t mask, int up, int16_t *pos,
+ xcb_window_t rwin, xcb_window_t ewin, xcb_window_t cwin)
+{
+ xcb_window_t xcb = ewin == rwin ? cwin : ewin;
+ win_t *win = win_get(xcb);
+ mod_t mod = mask_to_mod(mask, up);
+ ptr_t ptr = list_to_ptr(pos);
+ do_grab_pointer(0);
+ int status = wm_handle_event(win, ev, mod, ptr);
+ do_ungrab_pointer();
+ return status;
+}
+
+/* Send pointer motion info */
+static int send_pointer(int16_t *pos,
+ xcb_window_t rwin, xcb_window_t ewin, xcb_window_t cwin)
+{
+ xcb_window_t xcb = ewin == rwin ? cwin : ewin;
+ win_t *win = win_get(xcb);
+ ptr_t ptr = list_to_ptr(pos);
+ do_grab_pointer(0);
+ int status = wm_handle_ptr(win, ptr);
+ do_ungrab_pointer();
+ return status;
+}
+
+/* Send window state info */
+static void send_manage(win_t *win, int managed)
+{
+ if (win->sys->managed == managed)
+ return;
+ if (managed)
+ wm_insert(win);
+ else
+ wm_remove(win);
+ win->sys->managed = managed;
+}
+
+/* Send window state info */
+static void send_state(win_t *win, state_t next)
+{
+ if (!win->sys->managed)
+ return;
+ if (win->state == next)
+ return;
+ state_t prev = win->state;
+ win->state = next;
+ wm_handle_state(win, prev, next);
+}
+