diff options
-rw-r--r-- | schewm.c | 596 |
1 files changed, 530 insertions, 66 deletions
@@ -1,15 +1,542 @@ -#include <stdio.h> #include <stdbool.h> +#include <stdint.h> #include <stdlib.h> +#include <unistd.h> #include <string.h> #include <xcb/randr.h> #include <xcb/xcb_ewmh.h> +#include <xcb/xcb_icccm.h> #include <xcb/xcb_keysyms.h> #include <X11/keysym.h> -static uint16_t mod_key = XCB_MOD_MASK_4; -static const uint16_t SHIFT = XCB_MOD_MASK_SHIFT; +// 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; @@ -23,13 +550,6 @@ uint16_t wm_get_mod_key(bool with_shift) { } /* - * Array of (internal) event handlers. They cast the event pointer to - * the proper type and call the custom handlers defined in Scheme. - */ -typedef void (*generic_event_handler_t)(xcb_generic_event_t *); -static generic_event_handler_t events[XCB_NO_OPERATION]; - -/* * 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: @@ -41,7 +561,6 @@ static generic_event_handler_t events[XCB_NO_OPERATION]; */ // XCB_CONFIGURE_REQUEST -static void ev_configure_request(xcb_generic_event_t *); typedef void (*configure_request_handler_t)(); static configure_request_handler_t configure_request_handler = NULL; @@ -57,7 +576,6 @@ static void ev_configure_request(xcb_generic_event_t *generic_ev) { } // XCB_DESTROY_NOTIFY -static void ev_destroy_notify(xcb_generic_event_t *); typedef void (*destroy_notify_handler_t)(); static destroy_notify_handler_t destroy_notify_handler = NULL; @@ -73,7 +591,6 @@ static void ev_destroy_notify(xcb_generic_event_t *generic_ev) { } // XCB_ENTER_NOTIFY -static void ev_enter_notify(xcb_generic_event_t *); typedef void (*enter_notify_handler_t)(); static enter_notify_handler_t enter_notify_handler = NULL; @@ -89,7 +606,6 @@ static void ev_enter_notify(xcb_generic_event_t *generic_ev) { } // XCB_KEY_PRESS -static void ev_key_press(xcb_generic_event_t *); typedef void (*key_press_handler_t)(); static key_press_handler_t key_press_handler = NULL; @@ -105,7 +621,6 @@ static void ev_key_press(xcb_generic_event_t *generic_ev) { } // XCB_MAP_REQUEST -static void ev_map_request(xcb_generic_event_t *); typedef void (*map_request_handler_t)(); static map_request_handler_t map_request_handler = NULL; @@ -121,7 +636,6 @@ static void ev_map_request(xcb_generic_event_t *generic_ev) { } // XCB_UNMAP_NOTIFY -static void ev_unmap_notify(xcb_generic_event_t *); typedef void (*unmap_notify_handler_t)(); static unmap_notify_handler_t unmap_notify_handler = NULL; @@ -137,7 +651,6 @@ static void ev_unmap_notify(xcb_generic_event_t *generic_ev) { } // XCB_MAPPING_NOTIFY -static void ev_mapping_notify(xcb_generic_event_t *); typedef void (*mapping_notify_handler_t)(); static mapping_notify_handler_t mapping_notify_handler = NULL; @@ -153,7 +666,6 @@ static void ev_mapping_notify(xcb_generic_event_t *generic_ev) { } // XCB_CONFIGURE_NOTIFY -static void ev_configure_notify(xcb_generic_event_t *); typedef void (*configure_notify_handler_t)(int16_t, int16_t, uint16_t, uint16_t); static configure_notify_handler_t configure_notify_handler = NULL; @@ -169,7 +681,6 @@ static void ev_configure_notify(xcb_generic_event_t *generic_ev) { } // XCB_CIRCULATE_REQUEST -static void ev_circulate_request(xcb_generic_event_t *); typedef void (*circulate_request_handler_t)(); static circulate_request_handler_t circulate_request_handler = NULL; @@ -185,7 +696,6 @@ static void ev_circulate_request(xcb_generic_event_t *generic_ev) { } // XCB_BUTTON_PRESS -static void ev_button_press(xcb_generic_event_t *); typedef void (*button_press_handler_t)(); static button_press_handler_t button_press_handler = NULL; @@ -201,7 +711,6 @@ static void ev_button_press(xcb_generic_event_t *generic_ev) { } // XCB_CLIENT_MESSAGE -static void ev_client_message(xcb_generic_event_t *); typedef void (*client_message_handler_t)(); static client_message_handler_t client_message_handler = NULL; @@ -215,48 +724,3 @@ static void ev_client_message(xcb_generic_event_t *generic_ev) { client_message_handler(); } } - -bool wm_init() { - memset(events, 0, sizeof(events)); - events[XCB_CONFIGURE_REQUEST] = ev_configure_request; - events[XCB_DESTROY_NOTIFY] = ev_destroy_notify; - events[XCB_ENTER_NOTIFY] = ev_enter_notify; - events[XCB_KEY_PRESS] = ev_key_press; - events[XCB_MAP_REQUEST] = ev_map_request; - events[XCB_UNMAP_NOTIFY] = ev_unmap_notify; - events[XCB_MAPPING_NOTIFY] = ev_mapping_notify; - events[XCB_CONFIGURE_NOTIFY] = ev_configure_notify; - events[XCB_CIRCULATE_REQUEST] = ev_circulate_request; - events[XCB_BUTTON_PRESS] = ev_button_press; - events[XCB_CLIENT_MESSAGE] = ev_client_message; - return true; -} - -void wm_destroy(); - -void wm_run() { - /* - while (wm.is_running) { - xcb_generic_event_t *ev = xcb_wait_for_event(display_server.conn); - int ev_type = ev->response_type & 0x80; - generic_event_handler_t handler = wm.events[ev_type]; - if (handler) { - handler(ev); - } - free(ev); - } - - wm_destroy(); - */ -} - -void wm_quit() { - if (configure_notify_handler) { - // xcb_configure_notify_event_t ev; - // ev.x = 0; - // ev.y = 1; - // ev.width = 2; - // ev.height = 3; - configure_notify_handler(0, 1, 2, 3); - } -} |