summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Fadel <samuel@nihil.ws>2022-12-18 12:10:47 +0100
committerSamuel Fadel <samuel@nihil.ws>2022-12-18 12:10:47 +0100
commitec2ecf9085d1c001b803d27261ecf7ae3fe6d298 (patch)
treefa8db1b92df3130dd6b9d98b1d11fc2b5f8d91b9
parent8028b2b02ae5a302c7781fdd958be6fbf3bc2445 (diff)
More robust main loop and all event handlers.
-rw-r--r--schewm.c261
1 files changed, 249 insertions, 12 deletions
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();
@@ -2103,6 +2103,40 @@ wm_fit_client(struct Client *client) {
}
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);
+ }
}