From 8028b2b02ae5a302c7781fdd958be6fbf3bc2445 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Fri, 16 Dec 2022 22:46:23 +0100 Subject: A working callback mechanism between C and Scheme code. Callbacks can be fully setup via Scheme code; the C code assumes callbacks have no args and return no values. Within Scheme we setup callbacks via some lambda functions that capture args when needed (see make-callback example). Key press handler operates on the C side using a hash map to find the callback to be called. Everything is set up on the Scheme side, which is less verbose and more convenient to read. * main.scm: Setup callbacks for keypresses * schewm.c: Callbacks hash maps for keys and buttons, handlers for keys * wm.scm: Minor reorganization (key grab also now takes the callback directly) --- schewm.c | 296 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 254 insertions(+), 42 deletions(-) (limited to 'schewm.c') diff --git a/schewm.c b/schewm.c index 49f2e9d..44bb841 100644 --- a/schewm.c +++ b/schewm.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -260,9 +261,18 @@ client_list_free(struct ClientList *list, bool deep) { } } -// xcb_window_t is uint32_t +/* + * The following hash functions come from [1]. + * + * Apparently supposed to be overall decent, no independent tests were + * made here. We have 32-bit and 64-bit versions, as we need hash + * tables for both data types. + * + * [1] https://stackoverflow.com/a/12996028 + */ + uint32_t -wid_hash(uint32_t x) { +u32_hash(uint32_t x) { x = ((x >> 16) ^ x) * 0x45d9f3b; x = ((x >> 16) ^ x) * 0x45d9f3b; x = (x >> 16) ^ x; @@ -270,59 +280,76 @@ wid_hash(uint32_t x) { } uint32_t -wid_unhash(uint32_t x) { +u32_unhash(uint32_t x) { x = ((x >> 16) ^ x) * 0x119de1f3; x = ((x >> 16) ^ x) * 0x119de1f3; x = (x >> 16) ^ x; return x; } +// We assume this works as well as for 32 bits, but who knows? +uint64_t +u64_hash(uint64_t x) { + x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9); + x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb); + x = x ^ (x >> 31); + return x; +} + +uint64_t +u64_unhash(uint64_t x) { + x = (x ^ (x >> 31) ^ (x >> 62)) * UINT64_C(0x319642b2d24d8ec3); + x = (x ^ (x >> 27) ^ (x >> 54)) * UINT64_C(0x96de1b173f119089); + x = x ^ (x >> 30) ^ (x >> 60); + return x; +} + struct ClientsMap { - struct ClientList **table; - size_t table_size; + struct ClientList **buckets; + size_t num_buckets; size_t size; }; struct ClientsMap * -clients_map_new(size_t size) { +clients_map_new(size_t num_buckets) { struct ClientsMap *map = calloc(1, sizeof(struct ClientsMap)); if (!map) { return NULL; } - map->table = calloc(size, sizeof(struct ClientList *)); - if (!map->table) { + map->buckets = calloc(num_buckets, sizeof(struct ClientList *)); + if (!map->buckets) { free(map); return NULL; } - map->table_size = size; + map->num_buckets = num_buckets; 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++; +clients_map_add(struct ClientsMap *map, struct Client *client) { + uint32_t hash = u32_hash(client->id); + uint32_t i = hash % map->num_buckets; + // collision if (map->buckets[i] != NULL) + map->buckets[i] = client_list_add(map->buckets[i], client); + map->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; +clients_map_erase(struct ClientsMap *map, struct Client *client) { + uint32_t hash = u32_hash(client->id); + uint32_t i = hash % map->num_buckets; struct ClientList *prev = NULL; - for (struct ClientList *list = clients->table[i]; + for (struct ClientList *list = map->buckets[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; + map->buckets[i] = list->next; } if (prev != NULL) { // We are not at the first, make prev skip the node we @@ -330,7 +357,7 @@ clients_map_erase(struct ClientsMap *clients, struct Client *client) { prev->next = list->next; } free(list); - clients->size--; + map->size--; return client; } prev = list; @@ -340,10 +367,10 @@ clients_map_erase(struct ClientsMap *clients, struct Client *client) { } 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]; +clients_map_find(const struct ClientsMap *map, xcb_window_t id) { + uint32_t hash = u32_hash(id); + uint32_t i = hash % map->num_buckets; + for (struct ClientList *list = map->buckets[i]; list != NULL; list = list->next) { if (list->client->id == id) { @@ -358,12 +385,12 @@ clients_map_find(const struct ClientsMap *clients, xcb_window_t id) { * 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); +clients_map_free(struct ClientsMap *map) { + for (size_t i = 0; i < map->num_buckets; i++) { + client_list_free(map->buckets[i], true); } - free(clients->table); - free(clients); + free(map->buckets); + free(map); } struct Monitor { @@ -428,6 +455,151 @@ struct Workspace { struct Client *ring; }; +typedef void (*Callback)(); + +struct CallbackList { + uint64_t key; + Callback callback; + struct CallbackList *next; +}; + +/* + * Prepends a new callback to list of callbacks. + */ +struct CallbackList * +callback_list_add(struct CallbackList *list, uint64_t key, Callback callback) { + struct CallbackList *node = calloc(1, sizeof(struct CallbackList)); + if (node == NULL) { + // Cannot alloc node, just abort. If list was NULL, stays so. + return list; + } + node->key = key; + node->callback = callback; + // Also works when list is NULL + node->next = list; + list = node; + return list; +} + +void +callback_list_free(struct CallbackList *list) { + struct CallbackList *next; + while (list != NULL) { + next = list->next; + free(list); + list = next; + } +} + +struct CallbacksMap { + struct CallbackList **buckets; + size_t num_buckets; + size_t size; +}; + +struct CallbacksMap * +callbacks_map_new(size_t num_buckets) { + struct CallbacksMap *map = calloc(1, sizeof(struct CallbacksMap)); + if (!map) { + return NULL; + } + + map->buckets = calloc(num_buckets, sizeof(struct CallbackList *)); + if (!map->buckets) { + free(map); + return NULL; + } + + map->num_buckets = num_buckets; + map->size = 0; + return map; +} + +/* + * For internal use only. Call callbacks_map_get() for regular usage. + * + * Returns a pointer to the callback so we can modify stuff and reuse + * the search in multiple other functions. + */ +Callback * +callbacks_map_find(const struct CallbacksMap *map, uint64_t key) { + uint64_t hash = u64_hash(key); + uint64_t i = hash % map->num_buckets; + for (struct CallbackList *list = map->buckets[i]; + list != NULL; + list = list->next) { + if (list->key == key) { + return &list->callback; + } + } + // Not found + return NULL; +} + +Callback +callbacks_map_get(struct CallbacksMap *map, uint64_t key) { + Callback *callback_ptr = callbacks_map_find(map, key); + if (callback_ptr == NULL) { + return NULL; + } + return *callback_ptr; +} + +/* + * The semantics with set/unset is that we simply replace whatever + * callback was there, if something already exists with that key. + * + * This function also increases the size of the map if a new callback + * is added and not simply replaced. + */ +void +callbacks_map_set(struct CallbacksMap *map, uint64_t key, Callback callback) { + Callback *callback_ptr = callbacks_map_find(map, key); + if (callback_ptr != NULL) { + if (*callback_ptr == NULL) { + // Increase size since there was no callback set, even + // though the node exists + map->size++; + } + *callback_ptr = callback; + } else { + uint64_t hash = u64_hash(key); + uint64_t i = hash % map->num_buckets; + map->buckets[i] = callback_list_add(map->buckets[i], key, callback); + map->size++; + } +} + +/* + * This function replaces the existing stored callback with NULL, + * returning the old value, if found. It also reduces the size of the + * map. + * + * That means that once the map grows, it is only truly using less + * memory when callbacks_map_free() is called. + */ +Callback +callbacks_map_unset(struct CallbacksMap *map, uint64_t key) { + Callback *callback_ptr = callbacks_map_find(map, key); + if (callback_ptr != NULL) { + Callback callback = *callback_ptr; + *callback_ptr = NULL; + map->size--; + return callback; + } + // Not found + return NULL; +} + +void +callbacks_map_free(struct CallbacksMap *map) { + for (size_t i = 0; i < map->num_buckets; i++) { + callback_list_free(map->buckets[i]); + } + free(map->buckets); + free(map); +} + static struct { uint16_t inner_border_width, outer_border_width, magnet_border_width; int16_t offset_x, offset_y; @@ -468,6 +640,8 @@ static struct { struct Client *focus; struct ClientsMap *clients; struct Workspace *workspaces; + struct CallbacksMap *keys; + struct CallbacksMap *buttons; generic_event_handler_t events[XCB_NO_OPERATION]; } wm; @@ -1054,6 +1228,7 @@ static void dpy_grab_key(uint16_t mod, xcb_keysym_t keysym) { const xcb_setup_t *setup = xcb_get_setup(dpy.conn); if (!setup) { + assert(setup); return; } @@ -1489,8 +1664,8 @@ dpy_update_window_list(const struct ClientsMap *clients) { size_t num_windows = 0; if (clients->size != 0) { windows = malloc(clients->size * sizeof(xcb_window_t)); - for (size_t i = 0; i < clients->table_size; i++) { - for (struct ClientList *list = clients->table[i]; + for (size_t i = 0; i < clients->num_buckets; i++) { + for (struct ClientList *list = clients->buckets[i]; list != NULL; list = list->next) { windows[num_windows++] = list->client->id; @@ -1566,8 +1741,8 @@ bool wm_update_outputs(); bool wm_has_error(); bool wm_init(); void wm_quit(); -void wm_grab_key_with_mod(xcb_keysym_t keysym); -void wm_grab_key_with_mod_shift(xcb_keysym_t keysym); +void wm_grab_key_with_mod(xcb_keysym_t keysym, Callback callback); +void wm_grab_key_with_mod_shift(xcb_keysym_t keysym, Callback callback); void wm_begin_move_client(); void wm_begin_resize_client(); void wm_focus_close(); @@ -1941,17 +2116,37 @@ wm_get_mod_key(bool with_shift) { } void -wm_grab_key_with_mod(xcb_keysym_t keysym) { - dpy_grab_key(wm_get_mod_key(false), keysym); +wm_grab_key(xcb_keysym_t keysym, bool with_shift, Callback callback) { + /* + * Key grabbing is done in two stages: + * + * 1. Register a function to be called when that key combo is + * pressed + */ + uint16_t mod = wm_get_mod_key(with_shift); + uint64_t map_key = make_key_chord(mod, keysym); + callbacks_map_set(wm.keys, map_key, callback); + + /* + * 2. Tell the display server we are interested in a certain + * mod/key combo + */ + dpy_grab_key(mod, keysym); + dpy_flush(); +} + +void +wm_grab_key_with_mod(xcb_keysym_t keysym, Callback callback) { + wm_grab_key(keysym, false, callback); } void -wm_grab_key_with_mod_shift(xcb_keysym_t keysym) { - dpy_grab_key(wm_get_mod_key(true), keysym); +wm_grab_key_with_mod_shift(xcb_keysym_t keysym, Callback callback) { + wm_grab_key(keysym, true, callback); } /* - * event handlers which take a generic event and know what to cast it into. + * Event handlers that know what to cast the generic event into. */ static void ev_configure_request(xcb_generic_event_t *); static void ev_destroy_notify(xcb_generic_event_t *); @@ -2017,6 +2212,14 @@ wm_init() { if (wm.clients == NULL) { return false; } + wm.keys = callbacks_map_new(64); + if (wm.keys == NULL) { + return false; + } + wm.buttons = callbacks_map_new(16); + if (wm.buttons == NULL) { + return false; + } wm.cur_workspace = 0; wm.num_workspaces = 10; @@ -2079,6 +2282,8 @@ void wm_destroy() { free(wm.workspaces); clients_map_free(wm.clients); + callbacks_map_free(wm.keys); + callbacks_map_free(wm.buttons); dpy_destroy(); } @@ -2502,12 +2707,19 @@ wm_set_key_press_handler(key_press_handler_t handler) { static void ev_key_press(xcb_generic_event_t *generic_ev) { + xcb_key_press_event_t *ev = (xcb_key_press_event_t *) generic_ev; + uint16_t mod = dpy_clean_mod(ev->state); + xcb_keysym_t keysym = dpy_keysym_from_keycode(ev->detail); + if (key_press_handler) { - xcb_key_press_event_t *ev = (xcb_key_press_event_t *) generic_ev; - uint16_t mod = dpy_clean_mod(ev->state); - xcb_keysym_t keysym = dpy_keysym_from_keycode(ev->detail); key_press_handler(mod, keysym); } + + uint64_t map_key = make_key_chord(mod, keysym); + Callback callback = callbacks_map_get(wm.keys, map_key); + if (callback != NULL) { + callback(); + } } // XCB_MAP_REQUEST -- cgit v1.2.3