From ec2ecf9085d1c001b803d27261ecf7ae3fe6d298 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Sun, 18 Dec 2022 12:10:47 +0100 Subject: More robust main loop and all event handlers. --- schewm.c | 261 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 249 insertions(+), 12 deletions(-) (limited to 'schewm.c') diff --git a/schewm.c b/schewm.c index 44bb841..aff4ad9 100644 --- a/schewm.c +++ b/schewm.c @@ -631,7 +631,7 @@ static struct { * 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 *); +typedef void (*EventHandler)(xcb_generic_event_t *); static struct { uint32_t cur_workspace, num_workspaces; @@ -642,7 +642,7 @@ static struct { struct Workspace *workspaces; struct CallbacksMap *keys; struct CallbacksMap *buttons; - generic_event_handler_t events[XCB_NO_OPERATION]; + EventHandler events[XCB_NO_OPERATION]; } wm; static xcb_screen_t *get_screen(); @@ -2102,6 +2102,40 @@ wm_fit_client(struct Client *client) { return should_move || should_resize; } +void +wm_handle_state(struct Client *client, xcb_atom_t atom, uint32_t action) { + // `state' holds what the atom wants to tell us about what to do + enum WindowState state = WS_NORMAL; + if (atom == dpy.ewmh->_NET_WM_STATE_FULLSCREEN) { + state = WS_FULLSCREEN; + } else if (atom == dpy.ewmh->_NET_WM_STATE_HIDDEN) { + state = WS_ICONIFIED; + } else { + fprintf(stderr, + "handle_state(): Unknown state=%u, action=%u\n", + state, + action); + } + + // `new_state' is what the new state of our window should be + enum WindowState new_state = state; + switch (action) { + case XCB_EWMH_WM_STATE_ADD: + // This case is assumed to be the default as well + new_state = state; + break; + case XCB_EWMH_WM_STATE_REMOVE: + new_state = client->prev_state; + break; + case XCB_EWMH_WM_STATE_TOGGLE: + new_state = client->state != state ? state : client->prev_state; + break; + default: + fprintf(stderr, "handle_state(): Unknown action=%u\n", action); + } + wm_set_client_state(client, new_state); +} + void wm_set_mod_key(uint16_t mod) { wm.mod_key = mod; @@ -2132,7 +2166,6 @@ wm_grab_key(xcb_keysym_t keysym, bool with_shift, Callback callback) { * mod/key combo */ dpy_grab_key(mod, keysym); - dpy_flush(); } void @@ -2623,9 +2656,25 @@ ev_type(const xcb_generic_event_t *ev) { void wm_run() { while (wm.is_running) { + /* + * Flush everything and check connection health before trying + * to process further events + */ + dpy_flush(); + if (dpy_has_error()) { + fprintf(stderr, "display has an error\n"); + break; + } + + // We usually get ev == NULL when the display server is killed xcb_generic_event_t *ev = xcb_wait_for_event(dpy.conn); + if (ev == NULL) { + fprintf(stderr, "cannot get display events, aborting\n"); + break; + } + fprintf(stderr, "ev: %u\n", ev_type(ev)); - generic_event_handler_t handler = wm.events[ev_type(ev)]; + EventHandler handler = wm.events[ev_type(ev)]; if (handler) { handler(ev); } @@ -2656,10 +2705,76 @@ wm_set_configure_request_handler(configure_request_handler_t handler) { static void ev_configure_request(xcb_generic_event_t *generic_ev) { + xcb_configure_request_event_t *ev = (xcb_configure_request_event_t *) generic_ev; if (configure_request_handler) { - xcb_configure_request_event_t *ev = (xcb_configure_request_event_t *) generic_ev; configure_request_handler(); } + + struct Client *client = wm_find_client(ev->window); + if (client == NULL) { + uint16_t mask = 0; + uint32_t data[7]; + uint8_t i = 0; + if (ev->value_mask & XCB_CONFIG_WINDOW_X) { + mask |= XCB_CONFIG_WINDOW_X; + data[i++] = ev->x; + } + if (ev->value_mask & XCB_CONFIG_WINDOW_Y) { + mask |= XCB_CONFIG_WINDOW_Y; + data[i++] = ev->y; + } + if (ev->value_mask & XCB_CONFIG_WINDOW_WIDTH) { + mask |= XCB_CONFIG_WINDOW_WIDTH; + data[i++] = ev->width; + } + if (ev->value_mask & XCB_CONFIG_WINDOW_HEIGHT) { + mask |= XCB_CONFIG_WINDOW_HEIGHT; + data[i++] = ev->height; + } + if (ev->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) { + mask |= XCB_CONFIG_WINDOW_BORDER_WIDTH; + data[i++] = ev->border_width; + } + if (ev->value_mask & XCB_CONFIG_WINDOW_SIBLING) { + mask |= XCB_CONFIG_WINDOW_SIBLING; + data[i++] = ev->sibling; + } + if (ev->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) { + mask |= XCB_CONFIG_WINDOW_STACK_MODE; + data[i++] = ev->stack_mode; + } + if (i > 0) { + // TODO: should be a dpy_something() function + xcb_configure_window(dpy.conn, ev->window, mask, data); + } + } else if (client->state == WS_NORMAL) { + // TODO: do we need to handle XCB_CONFIG_WINDOW_SIBLING and + // XCB_CONFIG_WINDOW_STACK_MODE? + if (ev->value_mask & XCB_CONFIG_WINDOW_X) { + client->x = ev->x; + } + if (ev->value_mask & XCB_CONFIG_WINDOW_Y) { + client->y = ev->y; + } + if (ev->value_mask & XCB_CONFIG_WINDOW_WIDTH) { + client->width = ev->width; + } + if (ev->value_mask & XCB_CONFIG_WINDOW_HEIGHT) { + client->height = ev->height; + } + wm_apply_size_hints(client); + dpy_update_window_geometry(client); + + // Update the client monitor accordingly + struct Monitor *monitor = find_monitor(client); + if (monitor != NULL && client->monitor != monitor) { + client->monitor = monitor; + } + wm_fit_client(client); + + // TODO: do we need this? + // dpy_draw_borders(client, wm.focus->id == client->id); + } } // XCB_DESTROY_NOTIFY @@ -2673,10 +2788,19 @@ wm_set_destroy_notify_handler(destroy_notify_handler_t handler) { static void ev_destroy_notify(xcb_generic_event_t *generic_ev) { + xcb_destroy_notify_event_t *ev = (xcb_destroy_notify_event_t *) generic_ev; if (destroy_notify_handler) { - xcb_destroy_notify_event_t *ev = (xcb_destroy_notify_event_t *) generic_ev; destroy_notify_handler(); } + + struct Client *client = wm_find_client(ev->window); + if (client == NULL) { + return; + } + + wm_erase_client(client->id); + dpy_update_window_list(wm.clients); + dpy_flush(); } // XCB_ENTER_NOTIFY @@ -2690,10 +2814,20 @@ wm_set_enter_notify_handler(enter_notify_handler_t handler) { static void ev_enter_notify(xcb_generic_event_t *generic_ev) { + xcb_enter_notify_event_t *ev = (xcb_enter_notify_event_t *) generic_ev; if (enter_notify_handler) { - xcb_enter_notify_event_t *ev = (xcb_enter_notify_event_t *) generic_ev; enter_notify_handler(); } + + if ((ev->mode != XCB_NOTIFY_MODE_NORMAL && ev->mode != XCB_NOTIFY_MODE_UNGRAB) + || (wm.focus != NULL && wm.focus->id == ev->event)) { + return; + } + + struct Client *client = wm_find_client(ev->event); + if (client != NULL) { + wm_set_focus(client); + } } // XCB_KEY_PRESS @@ -2733,10 +2867,28 @@ wm_set_map_request_handler(map_request_handler_t handler) { static void ev_map_request(xcb_generic_event_t *generic_ev) { + xcb_map_request_event_t *ev = (xcb_map_request_event_t *) generic_ev; if (map_request_handler) { - xcb_map_request_event_t *ev = (xcb_map_request_event_t *) generic_ev; map_request_handler(); } + + struct Client *client = wm_find_client(ev->window); + if (client != NULL) { + // Client already known, ignore + return; + } + + client = manage_client_maybe_reposition(ev->window, true); + if (client == NULL) { + dpy_map_window(ev->window); + return; + } + + dpy_map_window(client->id); + dpy_set_window_state(client->id, WS_NORMAL); + dpy_center_cursor_client(client); + dpy_update_window_list(wm.clients); + dpy_flush(); } // XCB_UNMAP_NOTIFY @@ -2750,12 +2902,44 @@ wm_set_unmap_notify_handler(unmap_notify_handler_t handler) { static void ev_unmap_notify(xcb_generic_event_t *generic_ev) { + xcb_unmap_notify_event_t *ev = (xcb_unmap_notify_event_t *) generic_ev; if (unmap_notify_handler) { - xcb_unmap_notify_event_t *ev = (xcb_unmap_notify_event_t *) generic_ev; unmap_notify_handler(); } + + struct Client *client = wm_find_client(ev->window); + if (client == NULL + || wm.workspaces[wm.cur_workspace].ring == NULL + || wm.workspaces[wm.cur_workspace].ring->id == ev->window + || client->state == WS_ICONIFIED) { + /* + * For unknown clients, clients not in our current workspace, + * or when a client was unmmaped and new state is iconified, + * we don't have to do anything + */ + return; + } + + bool should_ignore = true; + struct Client *ring_ptr = wm.workspaces[wm.cur_workspace].ring->next; + while (ring_ptr != wm.workspaces[wm.cur_workspace].ring) { + if (ring_ptr->id == ev->window) { + // We found the client, now we must handle the unmapping + should_ignore = false; + break; + } + } + + if (should_ignore) { + return; + } + + wm_erase_client(client->id); + dpy_update_window_list(wm.clients); + dpy_flush(); } +// XXX: do we need this? // XCB_MAPPING_NOTIFY typedef void (*mapping_notify_handler_t)(); static mapping_notify_handler_t mapping_notify_handler = NULL; @@ -2784,10 +2968,25 @@ wm_set_configure_notify_handler(configure_notify_handler_t handler) { static void ev_configure_notify(xcb_generic_event_t *generic_ev) { + xcb_configure_notify_event_t *ev = (xcb_configure_notify_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); } + + if (ev->window != dpy.screen->root) { + return; + } + + if (ev->width != dpy.screen->width_in_pixels || + ev->height != dpy.screen->height_in_pixels) { + dpy.screen->width_in_pixels = ev->width; + dpy.screen->height_in_pixels = ev->height; + + if (!dpy.has_randr) { + // TODO: loop over all clients and call fit_client() + dpy_flush(); + } + } } // XCB_CIRCULATE_REQUEST @@ -2801,10 +3000,20 @@ wm_set_circulate_request_handler(circulate_request_handler_t handler) { static void ev_circulate_request(xcb_generic_event_t *generic_ev) { + xcb_circulate_request_event_t *ev = (xcb_circulate_request_event_t *) generic_ev; if (circulate_request_handler) { - xcb_circulate_request_event_t *ev = (xcb_circulate_request_event_t *) generic_ev; circulate_request_handler(); } + + /* + * XXX: + * For proper abstraction, we should be calling + * + * dpy_circulate_window(ev->window, ev->place); + * + * instead. But this direct call below makes our code smaller. + */ + xcb_circulate_window(dpy.conn, ev->window, ev->place); } // XCB_BUTTON_PRESS @@ -2835,8 +3044,36 @@ wm_set_client_message_handler(client_message_handler_t handler) { static void ev_client_message(xcb_generic_event_t *generic_ev) { + xcb_client_message_event_t *ev = (xcb_client_message_event_t *) generic_ev; if (client_message_handler) { - xcb_client_message_event_t *ev = (xcb_client_message_event_t *) generic_ev; client_message_handler(); } + + if (ev->type == dpy.ewmh->_NET_CURRENT_DESKTOP) { + wm_set_workspace(ev->data.data32[0]); + return; + } + + struct Client *client = wm_find_client(ev->window); + if (client == NULL) { + return; + } + + if (ev->type == dpy.ewmh->_NET_ACTIVE_WINDOW) { + wm_set_focus(client); + } else if (ev->type == dpy.ewmh->_NET_WM_DESKTOP) { + wm_set_client_workspace(client, ev->data.data32[0]); + } else if (ev->type == dpy.ewmh->_NET_CLOSE_WINDOW) { + wm_close(client); + } else if (ev->type == dpy.ewmh->_NET_WM_STATE) { + wm_handle_state(client, ev->data.data32[1], ev->data.data32[0]); + wm_handle_state(client, ev->data.data32[2], ev->data.data32[0]); + } else if (ev->type == dpy.change_state_atom + && ev->format == 32 + && ev->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) { + wm_handle_state( + client, + dpy.ewmh->_NET_WM_STATE_HIDDEN, + XCB_EWMH_WM_STATE_TOGGLE); + } } -- cgit v1.2.3