From da3523b11bcb174dac02fbfabfa58ed6b78a8622 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Sat, 3 Dec 2022 15:59:48 +0100 Subject: It's alive! * Makefile: Add pkg-config flags to properly link some symbols * wm.scm: Added wm-run and cleanup * schewm.c: Ported a lot more stuff, still missing key features * main.scm: Call wm-run --- Makefile | 5 +- main.scm | 1 + schewm.c | 982 ++++++++++++++++++++++++++++++++++++++++++++++++++++----------- wm.scm | 6 +- 4 files changed, 831 insertions(+), 163 deletions(-) diff --git a/Makefile b/Makefile index 3d127d2..2d8ba0f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,7 @@ +CFLAGS=`pkg-config --cflags xcb xcb-ewmh xcb-icccm xcb-keysyms xcb-randr` +LDFLAGS=`pkg-config --libs xcb xcb-ewmh xcb-icccm xcb-keysyms xcb-randr` + all: libschewm.so libschewm.so: schewm.c - gcc -fPIC -Wall -shared -o libschewm.so schewm.c + gcc -fPIC -Wall ${CFLAGS} ${LDFLAGS} -shared -o libschewm.so schewm.c diff --git a/main.scm b/main.scm index d048e8b..f8ac169 100644 --- a/main.scm +++ b/main.scm @@ -17,4 +17,5 @@ (display "\n"))) (display config) (display "\n") + (wm-run) (wm-quit)) diff --git a/schewm.c b/schewm.c index 9eafd2e..a515e1b 100644 --- a/schewm.c +++ b/schewm.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -31,28 +32,34 @@ struct Rect { uint16_t width, height; }; -int16_t rect_center_x(const struct Rect *rect) { +int16_t +rect_center_x(const struct Rect *rect) { return rect->x + rect->width / 2; } -int16_t rect_center_y(const struct Rect *rect) { +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) { +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) { +int16_t +max(int16_t a, int16_t b) { return a > b ? a : b; } -int16_t min(int16_t a, int16_t 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) { +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); @@ -67,33 +74,27 @@ enum WindowState { 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; + bool user_coord; 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 Monitor *monitor; 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)); +struct Client * +client_new(xcb_window_t id) { + struct Client *client = calloc(1, sizeof(struct Client)); if (client == NULL) { return NULL; } @@ -111,11 +112,15 @@ struct Client *client_new(xcb_window_t id) { return client; } -void client_free(struct Client *client) { +void +client_free(struct Client *client) { + // NOTE: We ignore monitor, next, and prev on purpose. The client + // does not own that memory. free(client); } -struct Client *client_ring_add(struct Client *ring, struct Client *client) { +struct Client * +client_ring_add(struct Client *ring, struct Client *client) { if (ring == NULL || ring->next == NULL || ring->prev == NULL) { ring = client; } @@ -127,7 +132,8 @@ struct Client *client_ring_add(struct Client *ring, struct Client *client) { return client; } -struct Client *client_ring_erase(struct Client *ring, struct Client *client) { +struct Client * +client_ring_erase(struct Client *ring, struct Client *client) { if (ring == NULL) { return NULL; } @@ -145,7 +151,8 @@ struct Client *client_ring_erase(struct Client *ring, struct Client *client) { return ring; } -void client_store_size(struct Client *client) { +void +client_store_size(struct Client *client) { client->old_x = client->x; client->old_y = client->y; client->old_width = client->width; @@ -153,7 +160,8 @@ void client_store_size(struct Client *client) { client->has_old_size = true; } -void client_restore_size(struct Client *client) { +void +client_restore_size(struct Client *client) { if (!client->has_old_size) { return; } @@ -164,6 +172,152 @@ void client_restore_size(struct Client *client) { client->height = client->old_height; } +struct ClientList { + struct Client *client; + struct ClientList *next; +}; + +/* + * Prepends a new client to list of clients. + */ +struct ClientList * +client_list_add(struct ClientList *list, struct Client *client) { + struct ClientList *node = calloc(1, sizeof(struct ClientList)); + if (node == NULL) { + // Cannot alloc node, just abort. If list was NULL, stays so. + return list; + } + node->client = client; + // Also works when list is NULL + node->next = list; + list = node; + return list; +} + +/* + * `deep' indicates whether we should also free the clients referenced + * by the list elements. + */ +void +client_list_free(struct ClientList *list, bool deep) { + struct ClientList *next; + while (list != NULL) { + next = list->next; + if (deep) { + client_free(list->client); + } + free(list); + list = next; + } +} + +// xcb_window_t is uint32_t +uint32_t +wid_hash(uint32_t x) { + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = (x >> 16) ^ x; + return x; +} + +uint32_t +wid_unhash(uint32_t x) { + x = ((x >> 16) ^ x) * 0x119de1f3; + x = ((x >> 16) ^ x) * 0x119de1f3; + x = (x >> 16) ^ x; + return x; +} + +struct ClientsMap { + struct ClientList **table; + // Please never modify this + size_t table_size; + size_t size; +}; + +struct ClientsMap * +clients_map_new(size_t size) { + struct ClientsMap *map = calloc(1, sizeof(struct ClientsMap)); + if (!map) { + return NULL; + } + + map->table = calloc(size, sizeof(struct ClientList *)); + if (!map->table) { + free(map); + return NULL; + } + + map->table_size = size; + map->size = 0; + return map; +} + +void +clients_map_add(struct ClientsMap *clients, struct Client *client) { + uint32_t hash = wid_hash(client->id); + uint32_t i = hash % clients->table_size; + // collision if (clients->table[i] != NULL) + clients->table[i] = client_list_add(clients->table[i], client); + clients->size++; +} + +struct Client * +clients_map_erase(struct ClientsMap *clients, struct Client *client) { + uint32_t hash = wid_hash(client->id); + uint32_t i = hash % clients->table_size; + struct ClientList *prev = NULL; + for (struct ClientList *list = clients->table[i]; + list != NULL; + list = list->next) { + if (list->client == client) { + if (prev == NULL) { + // It matched the first, update the starting node of + // the list + clients->table[i] = list->next; + } + if (prev != NULL) { + // We are not at the first, make prev skip the node we + // just found + prev->next = list->next; + } + free(list); + clients->size--; + return client; + } + prev = list; + } + // Not found + return NULL; +} + +struct Client * +clients_map_find(const struct ClientsMap *clients, xcb_window_t id) { + uint32_t hash = wid_hash(id); + uint32_t i = hash % clients->table_size; + for (struct ClientList *list = clients->table[i]; + list != NULL; + list = list->next) { + if (list->client->id == id) { + return list->client; + } + } + // Not found + return NULL; +} + +/* + * This function frees the memory of clients as well. + */ +void +clients_map_free(struct ClientsMap *clients) { + for (size_t i = 0; i < clients->table_size; i++) { + client_list_free(clients->table[i], true); + } + free(clients->table); + free(clients); +} + struct Monitor { xcb_randr_output_t id; const char *name; @@ -172,7 +326,8 @@ struct Monitor { struct Monitor *prev, *next; }; -void monitor_rect(const struct Monitor *monitor, struct Rect *rect) { +void +monitor_rect(const struct Monitor *monitor, struct Rect *rect) { rect->x = monitor->x; rect->y = monitor->y; rect->width = monitor->width; @@ -183,11 +338,6 @@ 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; @@ -196,12 +346,17 @@ static struct { struct { uint32_t focused, unfocused, unkillable, empty, outer; } colors; -} config; +} cfg; +/* + * Display server-specific information. + */ static struct { xcb_connection_t *conn; xcb_ewmh_connection_t *ewmh; bool has_error; + xcb_atom_t delete_window_atom, change_state_atom; + xcb_key_symbols_t *keysyms; uint16_t num_lock, caps_lock, scroll_lock; xcb_font_t cursor_font; xcb_cursor_t fleur_cursor, sizing_cursor; @@ -209,7 +364,7 @@ static struct { bool has_randr; int randr_base, screen_num; struct Monitor monitors; -} display_server; +} dpy; /* * Function pointer type for event handlers that take a generic event @@ -218,27 +373,25 @@ static struct { 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; + uint32_t cur_workspace, num_workspaces; bool is_running; - /* - * Array of (internal) event handlers. They cast the event pointer - * to the proper type and handle them. - */ + struct Client *focus; + struct ClientsMap *clients; + struct Workspace *workspaces; + struct xcb_connection_t *conn; 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 bool dpy_init(); +static void dpy_flush(); -static xcb_screen_t *get_screen() { - const xcb_setup_t* setup = xcb_get_setup(display_server.conn); +static xcb_screen_t * +get_screen() { + const xcb_setup_t* setup = xcb_get_setup(dpy.conn); xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup); - for (int i = display_server.screen_num; iter.rem; --i) { + for (int i = dpy.screen_num; iter.rem; --i) { if (i == 0) { return iter.data; } @@ -247,8 +400,9 @@ static xcb_screen_t *get_screen() { return NULL; } -static bool request_has_error(xcb_void_cookie_t cookie) { - xcb_generic_error_t *error = xcb_request_check(display_server.conn, cookie); +static bool +request_has_error(xcb_void_cookie_t cookie) { + xcb_generic_error_t *error = xcb_request_check(dpy.conn, cookie); if (error) { free(error); return true; @@ -261,24 +415,25 @@ struct WindowsQueryReply { 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; +void +windows_query(xcb_window_t root, struct WindowsQueryReply *reply) { + xcb_query_tree_cookie_t cookie = xcb_query_tree(dpy.conn, root); + xcb_query_tree_reply_t *data = xcb_query_tree_reply(dpy.conn, cookie, NULL); if (!data) { return; } + reply->length = 0; 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)); + reply->data = 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, + dpy.conn, children[i]); xcb_get_window_attributes_reply_t *attr_reply = xcb_get_window_attributes_reply( - display_server.conn, + dpy.conn, attr_cookie, NULL); /* @@ -298,12 +453,19 @@ void windows_query(xcb_window_t root, struct WindowsQueryReply *reply) { } } -void windows_query_reply_free(struct WindowsQueryReply *reply) { +void +windows_query_reply_free(struct WindowsQueryReply *reply) { if (reply->length > 0) { free(reply->data); } } +/* + * Window Manager API. + */ +struct Client *wm_find_client(xcb_window_t id); +void wm_erase_client(xcb_window_t id); +bool wm_update_outputs(); bool wm_has_error(); bool wm_init(); void wm_quit(); @@ -330,97 +492,542 @@ 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; +struct Client * +wm_find_client(xcb_window_t id) { + return clients_map_find(wm.clients, id); +} + +static void +dpy_set_window_state(xcb_window_t window, enum WindowState state) { + if (state == WS_NORMAL) { + long data[] = { XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE }; + xcb_change_property( + dpy.conn, + XCB_PROP_MODE_REPLACE, + window, + dpy.ewmh->_NET_WM_STATE, + dpy.ewmh->_NET_WM_STATE, + 32, + 2, + data); + } else if (state == WS_ICONIFIED) { + xcb_atom_t data[] = { dpy.ewmh->_NET_WM_STATE_HIDDEN }; + xcb_ewmh_set_wm_state(dpy.ewmh, window, 1, data); + } else if (state == WS_MAXIMIZED) { + xcb_atom_t data[] = { + dpy.ewmh->_NET_WM_STATE_MAXIMIZED_VERT, + dpy.ewmh->_NET_WM_STATE_MAXIMIZED_HORZ, + }; + xcb_ewmh_set_wm_state(dpy.ewmh, window, 2, data); + } else if (state == WS_FULLSCREEN) { + xcb_atom_t data[] = { dpy.ewmh->_NET_WM_STATE_FULLSCREEN }; + xcb_ewmh_set_wm_state(dpy.ewmh, window, 1, data); + } +} + +static void +dpy_set_focus(xcb_window_t window) { + xcb_window_t focus_window = window; + uint8_t revert_to = XCB_INPUT_FOCUS_POINTER_ROOT; + if (window == XCB_NONE) { + focus_window = XCB_INPUT_FOCUS_POINTER_ROOT; + revert_to = XCB_INPUT_FOCUS_NONE; + } else { + dpy_set_window_state(window, WS_NORMAL); + } + xcb_set_input_focus(dpy.conn, revert_to, focus_window, XCB_CURRENT_TIME); + xcb_ewmh_set_active_window(dpy.ewmh, dpy.screen_num, window); +} + +void +wm_erase_client(xcb_window_t id) { + struct Client *client = wm_find_client(id); + if (client == NULL) { + return; + } + + struct Workspace *workspace = &wm.workspaces[client->workspace]; + // Workspace being NULL is a bug! + workspace->ring = client_ring_erase(workspace->ring, client); + clients_map_erase(wm.clients, client); + if (!wm.focus) { + dpy_set_focus(XCB_NONE); + } +} + +bool +wm_has_error() { + return dpy.has_error || xcb_connection_has_error(dpy.conn) > 0; +} + +static void +dpy_update_outputs() { +} + +static void +dpy_flush() { + xcb_flush(dpy.conn); +} + +static uint16_t +mod_from_keycode(xcb_keycode_t keycode) { + uint16_t mod = 0; + xcb_get_modifier_mapping_cookie_t cookie = + xcb_get_modifier_mapping(dpy.conn); + xcb_get_modifier_mapping_reply_t *reply = + xcb_get_modifier_mapping_reply(dpy.conn, cookie, NULL); + if (!reply || reply->keycodes_per_modifier == 0) { + goto mod_from_keycode_end; + } + + xcb_keycode_t *mod_keycodes = xcb_get_modifier_mapping_keycodes(reply); + if (!mod_keycodes) { + goto mod_from_keycode_end; + } + + int length = xcb_get_modifier_mapping_keycodes_length(reply) / reply->keycodes_per_modifier; + for (int i = 0; i < length; i++) { + for (int j = 0; j < reply->keycodes_per_modifier; j++) { + xcb_keycode_t mod_keycode = mod_keycodes[i * reply->keycodes_per_modifier + j]; + if (mod_keycode != XCB_NO_SYMBOL && mod_keycode == keycode) { + mod |= 1 << i; + } + } + } + +mod_from_keycode_end: + free(reply); + return mod; } -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); +static uint16_t +mod_from_keysym(xcb_keysym_t keysym) { + uint16_t mod = 0; + const xcb_setup_t *setup = xcb_get_setup(dpy.conn); + if (!setup) { + return mod; + } + + /* + * We go through every keycode in the setup, looking for the ones + * matching our keysym request. Then, we transform those into a + * mod we can use and return. + * + * Also, don't use xcb_keycode_t for kc. It overflows when + * max_keycode coincides with the maximum value of xcb_keycode_t. + */ + unsigned int kc; + for (kc = setup->min_keycode; kc <= setup->max_keycode; kc++) { + for (unsigned int col = 0; col < 4 /* KEYSYMS_PER_KEYCODE */; col++) { + xcb_keysym_t ks = xcb_key_symbols_get_keysym(dpy.keysyms, kc, col); + if (ks == keysym) { + mod |= mod_from_keycode(kc); + } + } + } + return mod; +} + +static xcb_atom_t +dpy_get_atom(const char *name) { + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(dpy.conn, 0, strlen(name), name); + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(dpy.conn, cookie, NULL); + if (!reply) { + return 0; + } + + xcb_atom_t atom = reply->atom; + free(reply); + return atom; +} + +static bool +dpy_init(const char *wm_name) { + // This is initially NULL on purpose: we always try to dealloc + // later and it might happen that we dealloc before we even tried + // to setup the keysym stuff. This way we ensure we know whether + // keysyms is a valid ptr or not. + dpy.keysyms = NULL; + dpy.has_error = false; + dpy.has_randr = false; + dpy.randr_base = -1; + + dpy.conn = xcb_connect(NULL, &dpy.screen_num); if (wm_has_error()) { return false; } - display_server.screen = get_screen(); - if (!display_server.screen) { - display_server.has_error = true; + dpy.screen = get_screen(); + if (!dpy.screen) { + dpy.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; + dpy.ewmh = calloc(1, sizeof(xcb_ewmh_connection_t)); + if (!dpy.ewmh) { + dpy.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; + dpy.conn, + dpy.ewmh); + if (xcb_ewmh_init_atoms_replies(dpy.ewmh, ewmh_cookie, NULL) == 0) { + dpy.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, + dpy.ewmh->_NET_SUPPORTED, + dpy.ewmh->_NET_WM_DESKTOP, + dpy.ewmh->_NET_NUMBER_OF_DESKTOPS, + dpy.ewmh->_NET_CURRENT_DESKTOP, + dpy.ewmh->_NET_ACTIVE_WINDOW, + dpy.ewmh->_NET_WM_ICON, + dpy.ewmh->_NET_WM_STATE, + dpy.ewmh->_NET_WM_NAME, + dpy.ewmh->_NET_SUPPORTING_WM_CHECK, + dpy.ewmh->_NET_WM_STATE_HIDDEN, + dpy.ewmh->_NET_WM_ICON_NAME, + dpy.ewmh->_NET_WM_WINDOW_TYPE, + dpy.ewmh->_NET_WM_WINDOW_TYPE_DOCK, + dpy.ewmh->_NET_WM_WINDOW_TYPE_DESKTOP, + dpy.ewmh->_NET_WM_WINDOW_TYPE_TOOLBAR, + dpy.ewmh->_NET_WM_PID, + dpy.ewmh->_NET_CLIENT_LIST, + dpy.ewmh->WM_PROTOCOLS, + dpy.ewmh->_NET_WM_STATE, + dpy.ewmh->_NET_WM_STATE_DEMANDS_ATTENTION, + dpy.ewmh->_NET_WM_STATE_FULLSCREEN, }; xcb_ewmh_set_supported( - display_server.ewmh, - display_server.screen_num, + dpy.ewmh, + dpy.screen_num, sizeof(net_atoms) / sizeof(net_atoms[0]), net_atoms); - // set_focus(XCB_NONE); - // setup_randr(); - // setup_keys(); - // setup_cursors(); + // Setup RandR + const struct xcb_query_extension_reply_t *reply = xcb_get_extension_data(dpy.conn, &xcb_randr_id); + if (!reply || !reply->present) { + dpy.has_randr = false; + } else { + dpy.has_randr = true; + dpy_update_outputs(); + dpy.randr_base = reply->first_event; + xcb_randr_select_input( + dpy.conn, + dpy.screen->root, + XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE + | XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE + | XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE + | XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY); + } + + // Ask display server for window manager capabilities + dpy.delete_window_atom = dpy_get_atom("WM_DELETE_WINDOW"); + dpy.change_state_atom = dpy_get_atom("WM_CHANGE_STATE"); + + // Setup keys + xcb_ungrab_key(dpy.conn, XCB_GRAB_ANY, dpy.screen->root, XCB_MOD_MASK_ANY); + dpy.keysyms = xcb_key_symbols_alloc(dpy.conn); + if (!dpy.keysyms) { + dpy.has_error = true; + return false; + } + dpy.num_lock = mod_from_keysym(XK_Num_Lock); + dpy.caps_lock = mod_from_keysym(XK_Caps_Lock); + dpy.scroll_lock = mod_from_keysym(XK_Scroll_Lock); + + // Setup cursors + const char *cursor_font_name = "cursor"; + dpy.cursor_font = xcb_generate_id(dpy.conn); + xcb_open_font(dpy.conn, dpy.cursor_font, strlen(cursor_font_name), cursor_font_name); + dpy.fleur_cursor = xcb_generate_id(dpy.conn); + xcb_create_glyph_cursor( + dpy.conn, + dpy.fleur_cursor, + dpy.cursor_font, + dpy.cursor_font, + MOVE_GLYPH, + MOVE_GLYPH + 1, + 0x3232, + 0x3232, + 0x3232, + 0xeeee, + 0xeeee, + 0xeeec); + dpy.sizing_cursor = xcb_generate_id(dpy.conn); + xcb_create_glyph_cursor( + dpy.conn, + dpy.sizing_cursor, + dpy.cursor_font, + dpy.cursor_font, + RESIZE_GLYPH, + RESIZE_GLYPH + 1, + 0x3232, + 0x3232, + 0x3232, + 0xeeee, + 0xeeee, + 0xeeec); 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, + dpy.conn, + dpy.screen->root, XCB_CW_EVENT_MASK, &event_mask); if (request_has_error(cookie)) { - display_server.has_error = true; + dpy.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()); + xcb_ewmh_set_wm_name(dpy.ewmh, dpy.screen->root, strlen(wm_name), wm_name); + xcb_ewmh_set_wm_pid(dpy.ewmh, dpy.screen->root, getpid()); + dpy_flush(); + return !dpy.has_error; +} + + +static void +dpy_map_window(xcb_window_t window) { + xcb_map_window(dpy.conn, window); +} + +static void +dpy_unmap_window(xcb_window_t window) { + xcb_unmap_window(dpy.conn, window); +} + +static void +dpy_raise_window(xcb_window_t window) { + if (window == dpy.screen->root || window == XCB_NONE) { + return; + } - conn_flush(); - return !display_server.has_error; + uint32_t mode = XCB_STACK_MODE_ABOVE; + xcb_configure_window(dpy.conn, window, XCB_CONFIG_WINDOW_STACK_MODE, &mode); } -static void conn_flush() { - xcb_flush(display_server.conn); +static void +dpy_set_workspace(uint32_t workspace) { + xcb_ewmh_set_current_desktop(dpy.ewmh, dpy.screen_num, workspace); +} + +void +dpy_set_window_border_width(xcb_window_t window) { + xcb_configure_window(dpy.conn, window, XCB_CONFIG_WINDOW_BORDER_WIDTH, &cfg.inner_border_width); +} + +void +dpy_set_window_workspace(xcb_window_t window, uint32_t workspace) { + xcb_ewmh_set_wm_desktop(dpy.ewmh, window, workspace); +} + +#define RECT_INIT(x, y, w, h) { \ + (int16_t) (x), \ + (int16_t) (y), \ + (uint16_t) (w), \ + (uint16_t) (h), \ +} + +static void +dpy_draw_borders(struct Client *client, bool is_focused) { + if (client->state != WS_NORMAL || client->should_ignore_borders) { + return; + } + + dpy_set_window_border_width(client->id); + + xcb_rectangle_t inner_rect[] = { + RECT_INIT( + client->width, + 0, + cfg.inner_border_width - cfg.outer_border_width, + client->height + cfg.inner_border_width - cfg.outer_border_width + ), + RECT_INIT( + client->width + cfg.inner_border_width + cfg.outer_border_width, + 0, + cfg.inner_border_width - cfg.outer_border_width, + client->height + cfg.inner_border_width - cfg.outer_border_width + ), + RECT_INIT( + 0, + client->height, + client->width + cfg.inner_border_width - cfg.outer_border_width, + cfg.inner_border_width - cfg.outer_border_width + ), + RECT_INIT( + 0, + client->height + cfg.inner_border_width + cfg.outer_border_width, + client->width + cfg.inner_border_width - cfg.outer_border_width, + cfg.inner_border_width - cfg.outer_border_width + ), + RECT_INIT( + client->width + cfg.inner_border_width + cfg.outer_border_width, + client->height + cfg.inner_border_width + cfg.outer_border_width, + cfg.inner_border_width, + cfg.inner_border_width + ), + }; + xcb_rectangle_t outer_rect[] = { + RECT_INIT( + client->width + cfg.inner_border_width - cfg.outer_border_width, + 0, + cfg.outer_border_width, + client->height + cfg.inner_border_width * 2 + ), + RECT_INIT( + client->width + cfg.inner_border_width, + 0, + cfg.outer_border_width, + client->height + cfg.inner_border_width * 2 + ), + RECT_INIT( + 0, + client->height + cfg.inner_border_width - cfg.outer_border_width, + client->width + cfg.inner_border_width * 2, + cfg.outer_border_width + ), + RECT_INIT( + 0, + client->height + cfg.inner_border_width, + client->width + cfg.inner_border_width * 2, + cfg.outer_border_width + ), + RECT_INIT( + 1, + 1, + 1, + 1 + ), + }; + + xcb_pixmap_t pixmap = xcb_generate_id(dpy.conn); + xcb_create_pixmap( + dpy.conn, + dpy.screen->root_depth, + pixmap, + dpy.screen->root, + client->width + cfg.inner_border_width * 2, + client->height + cfg.inner_border_width * 2); + + xcb_gcontext_t gc = xcb_generate_id(dpy.conn); + xcb_create_gc(dpy.conn, gc, pixmap, 0, NULL); + + uint32_t color = client->is_unkillable + ? cfg.colors.unkillable : cfg.colors.outer; + xcb_change_gc(dpy.conn, gc, XCB_GC_FOREGROUND, &color); + xcb_poly_fill_rectangle(dpy.conn, pixmap, gc, 5, outer_rect); + + color = is_focused ? cfg.colors.focused : cfg.colors.unfocused; + xcb_change_gc(dpy.conn, gc, XCB_GC_FOREGROUND, &color); + xcb_poly_fill_rectangle(dpy.conn, pixmap, gc, 5, inner_rect); + xcb_change_window_attributes(dpy.conn, client->id, XCB_CW_BORDER_PIXMAP, &pixmap); + xcb_free_gc(dpy.conn, gc); + xcb_free_pixmap(dpy.conn, pixmap); +} + +#undef RECT_INIT + +void +dpy_draw_focused_borders(struct Client *client) { + dpy_draw_borders(client, true); +} + +void +dpy_draw_unfocused_borders(struct Client *client) { + dpy_draw_borders(client, false); +} + +xcb_window_t +dpy_window_under_cursor() { + xcb_query_pointer_cookie_t cookie = + xcb_query_pointer(dpy.conn, dpy.screen->root); + xcb_query_pointer_reply_t *reply = + xcb_query_pointer_reply(dpy.conn, cookie, NULL); + if (!reply) { + return XCB_NONE; + } + + xcb_window_t window = reply->child; + free(reply); + return window; +} + +void +dpy_grab_buttons(struct Client *client) { +} + +void +wm_set_focus(struct Client *client) { + if (wm.focus != NULL) { + if (wm.focus == client) { + return; + } + dpy_draw_unfocused_borders(wm.focus); + } + + wm.focus = client; + dpy_set_focus(client->id); + dpy_grab_buttons(client); + dpy_draw_focused_borders(client); + dpy_flush(); +} + +void +wm_set_workspace(uint32_t workspace) { + struct Client *client; + if (workspace > wm.num_workspaces) { + return; + } + + if (workspace != wm.cur_workspace) { + // Unmap all clients in current workspace + struct Workspace *ws_ptr = &wm.workspaces[wm.cur_workspace]; + client = ws_ptr->ring; + while (client != NULL) { + if (client->state != WS_ICONIFIED) { + dpy_unmap_window(client->id); + } + client = client->next; + + if (client == ws_ptr->ring) { + break; + } + } + + wm.cur_workspace = workspace; + } + + // Update current workspace and map all clients at new current workspace + dpy_set_workspace(wm.cur_workspace); + struct Workspace *ws_ptr = &wm.workspaces[wm.cur_workspace]; + client = ws_ptr->ring; + while (client != NULL) { + if (client->state != WS_ICONIFIED) { + dpy_map_window(client->id); + } + client = client->next; + + if (client == ws_ptr->ring) { + break; + } + } + + xcb_window_t window = dpy_window_under_cursor(); + client = clients_map_find(wm.clients, window); + if (client == NULL) { + wm.focus = NULL; + dpy_set_focus(XCB_NONE); + dpy_flush(); + } else { + wm_set_focus(client); + } } /* @@ -438,23 +1045,31 @@ 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")) { +static bool +setup_client(xcb_window_t window) { + return false; +} + +bool +wm_init() { + if (!dpy_init("schewm")) { return false; } wm.is_running = true; wm.cur_workspace = 0; + wm.num_workspaces = 10; + wm.workspaces = calloc(wm.num_workspaces, sizeof(struct Workspace)); + wm.focus = NULL; + wm.clients = clients_map_new(128); - // 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"); + // TODO + // wm.workspaces = INIT; + // dpy_set_num_workspaces(); // Look into all existing windows and set them up struct WindowsQueryReply reply; - windows_query(display_server.screen->root, &reply); + windows_query(dpy.screen->root, &reply); for (int i = 0; i < reply.length; i++) { xcb_window_t window = reply.data[i]; if (!setup_client(window)) { @@ -462,7 +1077,7 @@ bool wm_init() { * Could not be setup, just map it on screen because we * probably should not own it. */ - conn_map_window(window); + dpy_map_window(window); } else { /* * Unmap everything we grab initially because they may be @@ -470,14 +1085,15 @@ bool wm_init() { * way, we can map again only the correct windows at the * set_workspace() call below. */ - conn_unmap_window(window); + dpy_unmap_window(window); } } windows_query_reply_free(&reply); - update_client_list(); - set_workspace(wm.cur_workspace); - grab_keys(); - conn_flush(); + + // update_client_list(); + wm_set_workspace(wm.cur_workspace); + // grab_keys(); + dpy_flush(); memset(wm.events, 0, sizeof(wm.events)); wm.events[XCB_CONFIGURE_REQUEST] = ev_configure_request; @@ -492,40 +1108,55 @@ bool wm_init() { wm.events[XCB_BUTTON_PRESS] = ev_button_press; wm.events[XCB_CLIENT_MESSAGE] = ev_client_message; - return !display_server.has_error; + return !dpy.has_error; } -void wm_quit() { +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); +void +dpy_destroy() { + if (dpy.ewmh) { + xcb_ewmh_connection_wipe(dpy.ewmh); + free(dpy.ewmh); + } + + if (dpy.keysyms) { + xcb_key_symbols_free(dpy.keysyms); } 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_free_cursor(dpy.conn, dpy.fleur_cursor); + xcb_free_cursor(dpy.conn, dpy.sizing_cursor); + xcb_close_font(dpy.conn, dpy.cursor_font); xcb_set_input_focus( - display_server.conn, + dpy.conn, XCB_NONE, XCB_INPUT_FOCUS_POINTER_ROOT, XCB_CURRENT_TIME); - conn_flush(); + dpy_flush(); } - xcb_disconnect(display_server.conn); + xcb_disconnect(dpy.conn); +} + +void +wm_destroy() { + free(wm.workspaces); + clients_map_free(wm.clients); + dpy_destroy(); } -static int ev_type(const xcb_generic_event_t *ev) { +static int +ev_type(const xcb_generic_event_t *ev) { return ev->response_type & 0x80; } -void wm_run() { +void +wm_run() { while (wm.is_running) { - xcb_generic_event_t *ev = xcb_wait_for_event(display_server.conn); + xcb_generic_event_t *ev = xcb_wait_for_event(dpy.conn); generic_event_handler_t handler = wm.events[ev_type(ev)]; if (handler) { handler(ev); @@ -538,11 +1169,13 @@ void wm_run() { static uint16_t mod_key = META; -void wm_set_mod_key(uint16_t mod) { +void +wm_set_mod_key(uint16_t mod) { mod_key = mod; } -uint16_t wm_get_mod_key(bool with_shift) { +uint16_t +wm_get_mod_key(bool with_shift) { if (with_shift) { return mod_key | SHIFT; } @@ -564,11 +1197,14 @@ uint16_t wm_get_mod_key(bool with_shift) { 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) { +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) { +static void +ev_configure_request(xcb_generic_event_t *generic_ev) { + fprintf(stderr, "configure_request\n"); if (configure_request_handler) { xcb_configure_request_event_t *ev = (xcb_configure_request_event_t *) generic_ev; configure_request_handler(); @@ -579,11 +1215,14 @@ static void ev_configure_request(xcb_generic_event_t *generic_ev) { 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) { +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) { +static void +ev_destroy_notify(xcb_generic_event_t *generic_ev) { + fprintf(stderr, "destroy_notify\n"); if (destroy_notify_handler) { xcb_destroy_notify_event_t *ev = (xcb_destroy_notify_event_t *) generic_ev; destroy_notify_handler(); @@ -594,11 +1233,14 @@ static void ev_destroy_notify(xcb_generic_event_t *generic_ev) { 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) { +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) { +static void +ev_enter_notify(xcb_generic_event_t *generic_ev) { + fprintf(stderr, "enter_notify\n"); if (enter_notify_handler) { xcb_enter_notify_event_t *ev = (xcb_enter_notify_event_t *) generic_ev; enter_notify_handler(); @@ -609,11 +1251,14 @@ static void ev_enter_notify(xcb_generic_event_t *generic_ev) { 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) { +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) { +static void +ev_key_press(xcb_generic_event_t *generic_ev) { + fprintf(stderr, "key_press\n"); if (key_press_handler) { xcb_key_press_event_t *ev = (xcb_key_press_event_t *) generic_ev; key_press_handler(); @@ -624,11 +1269,14 @@ static void ev_key_press(xcb_generic_event_t *generic_ev) { 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) { +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) { +static void +ev_map_request(xcb_generic_event_t *generic_ev) { + fprintf(stderr, "map_resquest\n"); if (map_request_handler) { xcb_map_request_event_t *ev = (xcb_map_request_event_t *) generic_ev; map_request_handler(); @@ -639,11 +1287,14 @@ static void ev_map_request(xcb_generic_event_t *generic_ev) { 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) { +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) { +static void +ev_unmap_notify(xcb_generic_event_t *generic_ev) { + fprintf(stderr, "unmap_notify\n"); if (unmap_notify_handler) { xcb_unmap_notify_event_t *ev = (xcb_unmap_notify_event_t *) generic_ev; unmap_notify_handler(); @@ -654,11 +1305,14 @@ static void ev_unmap_notify(xcb_generic_event_t *generic_ev) { 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) { +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) { +static void +ev_mapping_notify(xcb_generic_event_t *generic_ev) { + fprintf(stderr, "mapping_notify\n"); if (mapping_notify_handler) { xcb_mapping_notify_event_t *ev = (xcb_mapping_notify_event_t *) generic_ev; mapping_notify_handler(); @@ -669,11 +1323,13 @@ static void ev_mapping_notify(xcb_generic_event_t *generic_ev) { 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) { +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) { +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); @@ -684,11 +1340,13 @@ static void ev_configure_notify(xcb_generic_event_t *generic_ev) { 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) { +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) { +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(); @@ -699,11 +1357,13 @@ static void ev_circulate_request(xcb_generic_event_t *generic_ev) { 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) { +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) { +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(); @@ -714,11 +1374,13 @@ static void ev_button_press(xcb_generic_event_t *generic_ev) { 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) { +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) { +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(); diff --git a/wm.scm b/wm.scm index fcd0bc9..06d1581 100644 --- a/wm.scm +++ b/wm.scm @@ -2,6 +2,7 @@ #:use-module (system foreign) #:export (wm-init wm-set-configure-notify-handler! + wm-run wm-quit)) (define libschewm (dynamic-link "libschewm")) @@ -24,6 +25,9 @@ (define wm-quit (schewm-func void "wm_quit" '())) +(define wm-run + (schewm-func void "wm_run" '())) + (define c/wm-set-configure-notify-handler (schewm-func void "wm_set_configure_notify_handler" @@ -35,5 +39,3 @@ handler (list int16 int16 uint32 uint32)))) -(define (wm-set-keybind mod keysym handler) - ) -- cgit v1.2.3