#include #include #include #include #include #include #include #include #include #include // Workspace values which are special for us const uint32_t ALL_WORKSPACES = 0xffffffff; const uint32_t NULL_WORKSPACE = 0xfffffffe; // Glyphs (mouse cursor types) const uint16_t MOVE_GLYPH = 52; const uint16_t RESIZE_GLYPH = 120; // Key modifiers const uint16_t META = XCB_MOD_MASK_4; const uint16_t SHIFT = XCB_MOD_MASK_SHIFT; struct Point { int16_t x, y; }; struct Rect { int16_t x, y; uint16_t width, height; }; int16_t rect_center_x(const struct Rect *rect) { return rect->x + rect->width / 2; } int16_t rect_center_y(const struct Rect *rect) { return rect->y + rect->height / 2; } void rect_center(const struct Rect *rect, struct Point *p) { p->x = rect_center_x(rect); p->y = rect_center_y(rect); } int16_t max(int16_t a, int16_t b) { return a > b ? a : b; } int16_t min(int16_t a, int16_t b) { return a < b ? a : b; } uint16_t rect_intersect_area(const struct Rect *r1, const struct Rect *r2) { int16_t x1 = max(r1->x, r2->x); int16_t y1 = max(r1->y, r2->y); int16_t x2 = min(r1->x + r1->width, r2->x + r2->width); int16_t y2 = min(r1->y + r1->height, r2->y + r2->height); return (x1 > x2 || y1 > y2) ? 0 : (x2 - x1) * (y2 - y1); } enum WindowState { WS_NORMAL = 0, WS_ICONIFIED, WS_MAXIMIZED, WS_FULLSCREEN, }; struct Client; struct Client { xcb_window_t id; bool user_coord; int16_t x, y; uint16_t width, height; uint16_t min_width, min_height, max_width, max_height; uint16_t width_inc, height_inc, base_width, base_height; uint32_t workspace; enum WindowState state, prev_state; bool is_unkillable; bool should_ignore_borders; struct Monitor *monitor; bool has_old_size; int16_t old_x, old_y; uint16_t old_width, old_height; struct Client *prev, *next; }; struct ClientList { struct Client *client; struct ClientList *next; }; struct Client *client_new(xcb_window_t id) { struct Client *client = (struct Client *) calloc(1, sizeof(struct Client)); if (client == NULL) { return NULL; } client->id = id; client->width_inc = 1; client->height_inc = 1; client->workspace = NULL_WORKSPACE; /* * Supposed to be 0 anyway: * * client->state = WS_NORMAL; * client->prev_state = WS_NORMAL; */ return client; } void client_free(struct Client *client) { free(client); } struct Client *client_ring_add(struct Client *ring, struct Client *client) { if (ring == NULL || ring->next == NULL || ring->prev == NULL) { ring = client; } client->prev = ring; client->next = ring->next; ring->next->prev = client; ring->next = client; return client; } struct Client *client_ring_erase(struct Client *ring, struct Client *client) { if (ring == NULL) { return NULL; } if (ring == client && client->next == client) { client_free(ring); return NULL; } client->next->prev = client->prev; client->prev->next = client->next; if (ring == client) { ring = client->next; client_free(client); } return ring; } void client_store_size(struct Client *client) { client->old_x = client->x; client->old_y = client->y; client->old_width = client->width; client->old_height = client->height; client->has_old_size = true; } void client_restore_size(struct Client *client) { if (!client->has_old_size) { return; } client->x = client->old_x; client->y = client->old_y; client->width = client->old_width; client->height = client->old_height; } struct Monitor { xcb_randr_output_t id; const char *name; int16_t x, y; uint16_t width, height; struct Monitor *prev, *next; }; void monitor_rect(const struct Monitor *monitor, struct Rect *rect) { rect->x = monitor->x; rect->y = monitor->y; rect->width = monitor->width; rect->height = monitor->height; } struct Workspace { struct Client *ring; }; struct WorkspaceList { struct Workspace *workspace; struct WorkspaceList *next; }; static struct { uint16_t inner_border_width, outer_border_width, magnet_border_width; int16_t offset_x, offset_y; uint16_t offset_width, offset_height; uint32_t pointer_motion_interval; struct { uint32_t focused, unfocused, unkillable, empty, outer; } colors; } config; static struct { xcb_connection_t *conn; xcb_ewmh_connection_t *ewmh; bool has_error; uint16_t num_lock, caps_lock, scroll_lock; xcb_font_t cursor_font; xcb_cursor_t fleur_cursor, sizing_cursor; xcb_screen_t *screen; bool has_randr; int randr_base, screen_num; struct Monitor monitors; } display_server; /* * Function pointer type for event handlers that take a generic event * and know what to do with it. */ typedef void (*generic_event_handler_t)(xcb_generic_event_t *); static struct { struct ClientList clients; struct WorkspaceList workspaces; struct xcb_connection_t *conn; uint32_t cur_workspace; bool is_running; /* * Array of (internal) event handlers. They cast the event pointer * to the proper type and handle them. */ generic_event_handler_t events[XCB_NO_OPERATION]; } wm; static xcb_screen_t *get_screen(); static bool request_has_error(xcb_void_cookie_t); static bool conn_init(); static void conn_flush(); static xcb_screen_t *get_screen() { const xcb_setup_t* setup = xcb_get_setup(display_server.conn); xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup); for (int i = display_server.screen_num; iter.rem; --i) { if (i == 0) { return iter.data; } xcb_screen_next(&iter); } return NULL; } static bool request_has_error(xcb_void_cookie_t cookie) { xcb_generic_error_t *error = xcb_request_check(display_server.conn, cookie); if (error) { free(error); return true; } return false; } struct WindowsQueryReply { xcb_window_t *data; int length; }; void windows_query(xcb_window_t root, struct WindowsQueryReply *reply) { xcb_query_tree_cookie_t cookie = xcb_query_tree(display_server.conn, root); xcb_query_tree_reply_t *data = xcb_query_tree_reply(display_server.conn, cookie, NULL); reply->length = 0; if (!data) { return; } int length = xcb_query_tree_children_length(data); // Assume length is OK, but reply->length hold the length of // clients we are really interested in reply->data = (xcb_window_t *) calloc(length, sizeof(xcb_window_t)); xcb_window_t *children = xcb_query_tree_children(data); for (int i = 0; i < length; i++) { xcb_get_window_attributes_cookie_t attr_cookie = xcb_get_window_attributes( display_server.conn, children[i]); xcb_get_window_attributes_reply_t *attr_reply = xcb_get_window_attributes_reply( display_server.conn, attr_cookie, NULL); /* * From the 2bwm source: * Ignore windows in override redirect mode. This mode means * they wouldn't have been reported to us with a * XCB_MAP_REQUEST if we had been running, so in the normal * case we wouldn't have seen them. Only handle visible * windows. */ if (attr_reply && !attr_reply->override_redirect && attr_reply->map_state == XCB_MAP_STATE_VIEWABLE) { reply->data[reply->length++] = children[i]; } free(attr_reply); } } void windows_query_reply_free(struct WindowsQueryReply *reply) { if (reply->length > 0) { free(reply->data); } } bool wm_has_error(); bool wm_init(); void wm_quit(); void wm_close_client(); void wm_begin_move_client(); void wm_begin_resize_client(); void wm_focus_prev(); void wm_focus_next(); void wm_toggle_maximize(); void wm_toggle_half_left(); void wm_toggle_half_right(); void wm_toggle_half_top(); void wm_toggle_half_bottom(); void wm_toggle_top_left(); void wm_toggle_top_right(); void wm_toggle_bottom_left(); void wm_toggle_bottom_right(); void wm_pack_left(); void wm_pack_right(); void wm_pack_top(); void wm_pack_bottom(); void wm_set_workspace(uint32_t workspace); void wm_set_client_workspace(uint32_t workspace); void wm_client_monitor_prev(); void wm_client_monitor_next(); bool wm_has_error() { return display_server.has_error || xcb_connection_has_error(display_server.conn) > 0; } static bool conn_init(const char *wm_name) { // TODO: init keysyms display_server.has_error = false; display_server.has_randr = false; display_server.randr_base = -1; display_server.conn = xcb_connect(NULL, &display_server.screen_num); if (wm_has_error()) { return false; } display_server.screen = get_screen(); if (!display_server.screen) { display_server.has_error = true; return false; } display_server.ewmh = (xcb_ewmh_connection_t *) calloc(1, sizeof(xcb_ewmh_connection_t)); if (!display_server.ewmh) { display_server.has_error = true; return false; } xcb_intern_atom_cookie_t *ewmh_cookie = xcb_ewmh_init_atoms( display_server.conn, display_server.ewmh); if (xcb_ewmh_init_atoms_replies(display_server.ewmh, ewmh_cookie, NULL) == 0) { display_server.has_error = true; return false; } xcb_atom_t net_atoms[] = { display_server.ewmh->_NET_SUPPORTED, display_server.ewmh->_NET_WM_DESKTOP, display_server.ewmh->_NET_NUMBER_OF_DESKTOPS, display_server.ewmh->_NET_CURRENT_DESKTOP, display_server.ewmh->_NET_ACTIVE_WINDOW, display_server.ewmh->_NET_WM_ICON, display_server.ewmh->_NET_WM_STATE, display_server.ewmh->_NET_WM_NAME, display_server.ewmh->_NET_SUPPORTING_WM_CHECK, display_server.ewmh->_NET_WM_STATE_HIDDEN, display_server.ewmh->_NET_WM_ICON_NAME, display_server.ewmh->_NET_WM_WINDOW_TYPE, display_server.ewmh->_NET_WM_WINDOW_TYPE_DOCK, display_server.ewmh->_NET_WM_WINDOW_TYPE_DESKTOP, display_server.ewmh->_NET_WM_WINDOW_TYPE_TOOLBAR, display_server.ewmh->_NET_WM_PID, display_server.ewmh->_NET_CLIENT_LIST, display_server.ewmh->WM_PROTOCOLS, display_server.ewmh->_NET_WM_STATE, display_server.ewmh->_NET_WM_STATE_DEMANDS_ATTENTION, display_server.ewmh->_NET_WM_STATE_FULLSCREEN, }; xcb_ewmh_set_supported( display_server.ewmh, display_server.screen_num, sizeof(net_atoms) / sizeof(net_atoms[0]), net_atoms); // set_focus(XCB_NONE); // setup_randr(); // setup_keys(); // setup_cursors(); xcb_event_mask_t event_mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_BUTTON_PRESS; xcb_void_cookie_t cookie = xcb_change_window_attributes_checked( display_server.conn, display_server.screen->root, XCB_CW_EVENT_MASK, &event_mask); if (request_has_error(cookie)) { display_server.has_error = true; return false; } xcb_ewmh_set_wm_name(display_server.ewmh, display_server.screen->root, strlen(wm_name), wm_name); xcb_ewmh_set_wm_pid(display_server.ewmh, display_server.screen->root, getpid()); conn_flush(); return !display_server.has_error; } static void conn_flush() { xcb_flush(display_server.conn); } /* * Event handlers which take a generic event and know what to cast it into. */ static void ev_configure_request(xcb_generic_event_t *); static void ev_destroy_notify(xcb_generic_event_t *); static void ev_enter_notify(xcb_generic_event_t *); static void ev_key_press(xcb_generic_event_t *); static void ev_map_request(xcb_generic_event_t *); static void ev_unmap_notify(xcb_generic_event_t *); static void ev_mapping_notify(xcb_generic_event_t *); static void ev_configure_notify(xcb_generic_event_t *); static void ev_circulate_request(xcb_generic_event_t *); static void ev_button_press(xcb_generic_event_t *); static void ev_client_message(xcb_generic_event_t *); bool wm_init() { if (!conn_init("schewm")) { return false; } wm.is_running = true; wm.cur_workspace = 0; // conn_set_num_workspaces(); // TODO: load config // m_delete_window_atom = conn.get_atom("WM_DELETE_WINDOW"); // m_change_state_atom = conn.get_atom("WM_CHANGE_STATE"); // Look into all existing windows and set them up struct WindowsQueryReply reply; windows_query(display_server.screen->root, &reply); for (int i = 0; i < reply.length; i++) { xcb_window_t window = reply.data[i]; if (!setup_client(window)) { /* * Could not be setup, just map it on screen because we * probably should not own it. */ conn_map_window(window); } else { /* * Unmap everything we grab initially because they may be * mapped currently, but belong to another workspace; this * way, we can map again only the correct windows at the * set_workspace() call below. */ conn_unmap_window(window); } } windows_query_reply_free(&reply); update_client_list(); set_workspace(wm.cur_workspace); grab_keys(); conn_flush(); memset(wm.events, 0, sizeof(wm.events)); wm.events[XCB_CONFIGURE_REQUEST] = ev_configure_request; wm.events[XCB_DESTROY_NOTIFY] = ev_destroy_notify; wm.events[XCB_ENTER_NOTIFY] = ev_enter_notify; wm.events[XCB_KEY_PRESS] = ev_key_press; wm.events[XCB_MAP_REQUEST] = ev_map_request; wm.events[XCB_UNMAP_NOTIFY] = ev_unmap_notify; wm.events[XCB_MAPPING_NOTIFY] = ev_mapping_notify; wm.events[XCB_CONFIGURE_NOTIFY] = ev_configure_notify; wm.events[XCB_CIRCULATE_REQUEST] = ev_circulate_request; wm.events[XCB_BUTTON_PRESS] = ev_button_press; wm.events[XCB_CLIENT_MESSAGE] = ev_client_message; return !display_server.has_error; } void wm_quit() { wm.is_running = false; } void wm_destroy() { if (display_server.ewmh) { xcb_ewmh_connection_wipe(display_server.ewmh); free(display_server.ewmh); } if (!wm_has_error()) { xcb_free_cursor(display_server.conn, display_server.fleur_cursor); xcb_free_cursor(display_server.conn, display_server.sizing_cursor); xcb_close_font(display_server.conn, display_server.cursor_font); xcb_set_input_focus( display_server.conn, XCB_NONE, XCB_INPUT_FOCUS_POINTER_ROOT, XCB_CURRENT_TIME); conn_flush(); } xcb_disconnect(display_server.conn); } static int ev_type(const xcb_generic_event_t *ev) { return ev->response_type & 0x80; } void wm_run() { while (wm.is_running) { xcb_generic_event_t *ev = xcb_wait_for_event(display_server.conn); generic_event_handler_t handler = wm.events[ev_type(ev)]; if (handler) { handler(ev); } free(ev); } wm_destroy(); } static uint16_t mod_key = META; void wm_set_mod_key(uint16_t mod) { mod_key = mod; } uint16_t wm_get_mod_key(bool with_shift) { if (with_shift) { return mod_key | SHIFT; } return mod_key; } /* * NOTE: * Don't do the thing below. Create hooks for relevant stuff the user * might care about, call them at the appropriate places. Design should be: * - Event handlers: the WMs job * - Hooks: user customization * * Current sketch is too complex and we don't need to customize * behavior that much anyway. */ // XCB_CONFIGURE_REQUEST typedef void (*configure_request_handler_t)(); static configure_request_handler_t configure_request_handler = NULL; void wm_set_configure_request_handler(configure_request_handler_t handler) { configure_request_handler = handler; } static void ev_configure_request(xcb_generic_event_t *generic_ev) { if (configure_request_handler) { xcb_configure_request_event_t *ev = (xcb_configure_request_event_t *) generic_ev; configure_request_handler(); } } // XCB_DESTROY_NOTIFY typedef void (*destroy_notify_handler_t)(); static destroy_notify_handler_t destroy_notify_handler = NULL; void wm_set_destroy_notify_handler(destroy_notify_handler_t handler) { destroy_notify_handler = handler; } static void ev_destroy_notify(xcb_generic_event_t *generic_ev) { if (destroy_notify_handler) { xcb_destroy_notify_event_t *ev = (xcb_destroy_notify_event_t *) generic_ev; destroy_notify_handler(); } } // XCB_ENTER_NOTIFY typedef void (*enter_notify_handler_t)(); static enter_notify_handler_t enter_notify_handler = NULL; void wm_set_enter_notify_handler(enter_notify_handler_t handler) { enter_notify_handler = handler; } static void ev_enter_notify(xcb_generic_event_t *generic_ev) { if (enter_notify_handler) { xcb_enter_notify_event_t *ev = (xcb_enter_notify_event_t *) generic_ev; enter_notify_handler(); } } // XCB_KEY_PRESS typedef void (*key_press_handler_t)(); static key_press_handler_t key_press_handler = NULL; void wm_set_key_press_handler(key_press_handler_t handler) { key_press_handler = handler; } static void ev_key_press(xcb_generic_event_t *generic_ev) { if (key_press_handler) { xcb_key_press_event_t *ev = (xcb_key_press_event_t *) generic_ev; key_press_handler(); } } // XCB_MAP_REQUEST typedef void (*map_request_handler_t)(); static map_request_handler_t map_request_handler = NULL; void wm_set_map_request_handler(map_request_handler_t handler) { map_request_handler = handler; } static void ev_map_request(xcb_generic_event_t *generic_ev) { if (map_request_handler) { xcb_map_request_event_t *ev = (xcb_map_request_event_t *) generic_ev; map_request_handler(); } } // XCB_UNMAP_NOTIFY typedef void (*unmap_notify_handler_t)(); static unmap_notify_handler_t unmap_notify_handler = NULL; void wm_set_unmap_notify_handler(unmap_notify_handler_t handler) { unmap_notify_handler = handler; } static void ev_unmap_notify(xcb_generic_event_t *generic_ev) { if (unmap_notify_handler) { xcb_unmap_notify_event_t *ev = (xcb_unmap_notify_event_t *) generic_ev; unmap_notify_handler(); } } // XCB_MAPPING_NOTIFY typedef void (*mapping_notify_handler_t)(); static mapping_notify_handler_t mapping_notify_handler = NULL; void wm_set_mapping_notify_handler(mapping_notify_handler_t handler) { mapping_notify_handler = handler; } static void ev_mapping_notify(xcb_generic_event_t *generic_ev) { if (mapping_notify_handler) { xcb_mapping_notify_event_t *ev = (xcb_mapping_notify_event_t *) generic_ev; mapping_notify_handler(); } } // XCB_CONFIGURE_NOTIFY typedef void (*configure_notify_handler_t)(int16_t, int16_t, uint16_t, uint16_t); static configure_notify_handler_t configure_notify_handler = NULL; void wm_set_configure_notify_handler(configure_notify_handler_t handler) { configure_notify_handler = handler; } static void ev_configure_notify(xcb_generic_event_t *generic_ev) { if (configure_notify_handler) { xcb_configure_notify_event_t *ev = (xcb_configure_notify_event_t *) generic_ev; configure_notify_handler(ev->x, ev->y, ev->width, ev->height); } } // XCB_CIRCULATE_REQUEST typedef void (*circulate_request_handler_t)(); static circulate_request_handler_t circulate_request_handler = NULL; void wm_set_circulate_request_handler(circulate_request_handler_t handler) { circulate_request_handler = handler; } static void ev_circulate_request(xcb_generic_event_t *generic_ev) { if (circulate_request_handler) { xcb_circulate_request_event_t *ev = (xcb_circulate_request_event_t *) generic_ev; circulate_request_handler(); } } // XCB_BUTTON_PRESS typedef void (*button_press_handler_t)(); static button_press_handler_t button_press_handler = NULL; void wm_set_button_press_handler(button_press_handler_t handler) { button_press_handler = handler; } static void ev_button_press(xcb_generic_event_t *generic_ev) { if (button_press_handler) { xcb_button_press_event_t *ev = (xcb_button_press_event_t *) generic_ev; button_press_handler(); } } // XCB_CLIENT_MESSAGE typedef void (*client_message_handler_t)(); static client_message_handler_t client_message_handler = NULL; void wm_set_client_message_handler(client_message_handler_t handler) { client_message_handler = handler; } static void ev_client_message(xcb_generic_event_t *generic_ev) { if (client_message_handler) { xcb_client_message_event_t *ev = (xcb_client_message_event_t *) generic_ev; client_message_handler(); } }