From e567cf436c31b6d91f4e69e80b3af2a278aa70fa Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 15 Dec 2013 17:30:06 +0100 Subject: [PATCH] dragging: instead of using a custom event loop, use libev This is done by installing a new check watcher that replaces the main X11 event handler and calling ev_run with EVRUN_ONCE until the dragging loop left state DRAGGING. With this commit, other handlers, most notably the redraw handler for placeholder windows, get a chance to run when dragging (placeholder!) windows around. --- include/all.h | 1 + include/main.h | 21 +++++ src/floating.c | 194 ++++++++++++++++++++++++++----------------- src/main.c | 23 ++++- src/restore_layout.c | 1 - 5 files changed, 160 insertions(+), 80 deletions(-) create mode 100644 include/main.h diff --git a/include/all.h b/include/all.h index 840ca443..9ba4c1d9 100644 --- a/include/all.h +++ b/include/all.h @@ -84,5 +84,6 @@ #include "fake_outputs.h" #include "display_version.h" #include "restore_layout.h" +#include "main.h" #endif diff --git a/include/main.h b/include/main.h new file mode 100644 index 00000000..9261a5df --- /dev/null +++ b/include/main.h @@ -0,0 +1,21 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE) + * + * main.c: Initialization, main loop + * + */ +#ifndef I3_MAIN_H +#define I3_MAIN_H + +/** + * Enable or disable the main X11 event handling function. + * This is used by drag_pointer() which has its own, modal event handler, which + * takes precedence over the normal event handler. + * + */ +void main_set_x11_cb(bool enable); + +#endif diff --git a/src/floating.c b/src/floating.c index 10962403..166e054c 100644 --- a/src/floating.c +++ b/src/floating.c @@ -567,6 +567,104 @@ void floating_resize_window(Con *con, const bool proportional, con->scratchpad_state = SCRATCHPAD_CHANGED; } +/* As endorsed by “ASSOCIATING CUSTOM DATA WITH A WATCHER” in ev(3) */ +struct drag_x11_cb { + ev_check check; + + /* Whether this modal event loop should be exited and with which result. */ + drag_result_t result; + + /* The container that is being dragged or resized, or NULL if this is a + * drag of the resize handle. */ + Con *con; + + /* The dimensions of con when the loop was started. */ + Rect old_rect; + + /* The callback to invoke after every pointer movement. */ + callback_t callback; + + /* User data pointer for callback. */ + const void *extra; +}; + +static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) { + struct drag_x11_cb *dragloop = (struct drag_x11_cb*)w; + xcb_motion_notify_event_t *last_motion_notify = NULL; + xcb_generic_event_t *event; + + while ((event = xcb_poll_for_event(conn)) != NULL) { + if (event->response_type == 0) { + xcb_generic_error_t *error = (xcb_generic_error_t*)event; + DLOG("X11 Error received (probably harmless)! sequence 0x%x, error_code = %d\n", + error->sequence, error->error_code); + free(event); + continue; + } + + /* Strip off the highest bit (set if the event is generated) */ + int type = (event->response_type & 0x7F); + + switch (type) { + case XCB_BUTTON_RELEASE: + dragloop->result = DRAG_SUCCESS; + break; + + case XCB_KEY_PRESS: + DLOG("A key was pressed during drag, reverting changes."); + dragloop->result = DRAG_REVERT; + handle_event(type, event); + break; + + case XCB_UNMAP_NOTIFY: { + xcb_unmap_notify_event_t *unmap_event = (xcb_unmap_notify_event_t*)event; + Con *con = con_by_window_id(unmap_event->window); + + if (con != NULL) { + DLOG("UnmapNotify for window 0x%08x (container %p)\n", unmap_event->window, con); + + if (con_get_workspace(con) == con_get_workspace(focused)) { + DLOG("UnmapNotify for a managed window on the current workspace, aborting\n"); + dragloop->result = DRAG_ABORT; + } + } + + handle_event(type, event); + break; + } + + case XCB_MOTION_NOTIFY: + /* motion_notify events are saved for later */ + FREE(last_motion_notify); + last_motion_notify = (xcb_motion_notify_event_t*)event; + break; + + default: + DLOG("Passing to original handler\n"); + handle_event(type, event); + break; + } + + if (last_motion_notify != (xcb_motion_notify_event_t*)event) + free(event); + + if (dragloop->result != DRAGGING) + return; + } + + if (last_motion_notify == NULL) + return; + + dragloop->callback( + dragloop->con, + &(dragloop->old_rect), + last_motion_notify->root_x, + last_motion_notify->root_y, + dragloop->extra); + free(last_motion_notify); +} + + /* * This function grabs your pointer and keyboard and lets you drag stuff around * (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will @@ -578,11 +676,6 @@ void floating_resize_window(Con *con, const bool proportional, drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t confine_to, border_t border, int cursor, callback_t callback, const void *extra) { - uint32_t new_x, new_y; - Rect old_rect = { 0, 0, 0, 0 }; - if (con != NULL) - memcpy(&old_rect, &(con->rect), sizeof(Rect)); - xcb_cursor_t xcursor = (cursor && xcursor_supported) ? xcursor_get_cursor(cursor) : XCB_NONE; @@ -626,84 +719,29 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ free(keyb_reply); /* Go into our own event loop */ - xcb_flush(conn); - - xcb_generic_event_t *inside_event, *last_motion_notify = NULL; - Con *inside_con = NULL; - - drag_result_t drag_result = DRAGGING; - /* I’ve always wanted to have my own eventhandler… */ - while (drag_result == DRAGGING && (inside_event = xcb_wait_for_event(conn))) { - /* We now handle all events we can get using xcb_poll_for_event */ - do { - /* skip x11 errors */ - if (inside_event->response_type == 0) { - free(inside_event); - continue; - } - /* Strip off the highest bit (set if the event is generated) */ - int type = (inside_event->response_type & 0x7F); - - switch (type) { - case XCB_BUTTON_RELEASE: - drag_result = DRAG_SUCCESS; - break; - - case XCB_MOTION_NOTIFY: - /* motion_notify events are saved for later */ - FREE(last_motion_notify); - last_motion_notify = inside_event; - break; - - case XCB_UNMAP_NOTIFY: - inside_con = con_by_window_id(((xcb_unmap_notify_event_t*)inside_event)->window); - - if (inside_con != NULL) { - DLOG("UnmapNotify for window 0x%08x (container %p)\n", ((xcb_unmap_notify_event_t*)inside_event)->window, inside_con); - - if (con_get_workspace(inside_con) == con_get_workspace(focused)) { - DLOG("UnmapNotify for a managed window on the current workspace, aborting\n"); - drag_result = DRAG_ABORT; - } - } - - handle_event(type, inside_event); - break; - - case XCB_KEY_PRESS: - /* Cancel the drag if a key was pressed */ - DLOG("A key was pressed during drag, reverting changes."); - drag_result = DRAG_REVERT; - - handle_event(type, inside_event); - break; - - default: - DLOG("Passing to original handler\n"); - /* Use original handler */ - handle_event(type, inside_event); - break; - } - if (last_motion_notify != inside_event) - free(inside_event); - } while ((inside_event = xcb_poll_for_event(conn)) != NULL); - - if (last_motion_notify == NULL || drag_result != DRAGGING) - continue; - - new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x; - new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y; - - callback(con, &old_rect, new_x, new_y, extra); - FREE(last_motion_notify); - } + struct drag_x11_cb loop = { + .result = DRAGGING, + .con = con, + .callback = callback, + .extra = extra, + }; + if (con) + loop.old_rect = con->rect; + ev_check_init(&loop.check, xcb_drag_check_cb); + main_set_x11_cb(false); + ev_check_start(main_loop, &loop.check); + + while (loop.result == DRAGGING) + ev_run(main_loop, EVRUN_ONCE); + + ev_check_stop(main_loop, &loop.check); + main_set_x11_cb(true); xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME); xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); - xcb_flush(conn); - return drag_result; + return loop.result; } /* diff --git a/src/main.c b/src/main.c index c8cfedd7..ca2617c6 100644 --- a/src/main.c +++ b/src/main.c @@ -31,6 +31,10 @@ struct rlimit original_rlimit_core; /** The number of file descriptors passed via socket activation. */ int listen_fds; +/* We keep the xcb_check watcher around to be able to enable and disable it + * temporarily for drag_pointer(). */ +static struct ev_check *xcb_check; + static int xkb_event_base; int xkb_current_group; @@ -143,6 +147,23 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { } } +/* + * Enable or disable the main X11 event handling function. + * This is used by drag_pointer() which has its own, modal event handler, which + * takes precedence over the normal event handler. + * + */ +void main_set_x11_cb(bool enable) { + DLOG("Setting main X11 callback to enabled=%d\n", enable); + if (enable) { + ev_check_start(main_loop, xcb_check); + /* Trigger the watcher explicitly to handle all remaining X11 events. + * drag_pointer()’s event handler exits in the middle of the loop. */ + ev_feed_event(main_loop, xcb_check, 0); + } else { + ev_check_stop(main_loop, xcb_check); + } +} /* * When using xmodmap to change the keyboard mapping, this event @@ -742,7 +763,7 @@ int main(int argc, char *argv[]) { struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io)); struct ev_io *xkb = scalloc(sizeof(struct ev_io)); - struct ev_check *xcb_check = scalloc(sizeof(struct ev_check)); + xcb_check = scalloc(sizeof(struct ev_check)); struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare)); ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); diff --git a/src/restore_layout.c b/src/restore_layout.c index 96e60840..cdf15517 100644 --- a/src/restore_layout.c +++ b/src/restore_layout.c @@ -335,7 +335,6 @@ static void configure_notify(xcb_configure_notify_event_t *event) { ELOG("Received ConfigureNotify for unknown window 0x%08x\n", event->window); } -// TODO: this event loop is not taken care of in the floating event handler static void restore_handle_event(int type, xcb_generic_event_t *event) { switch (type) { case XCB_EXPOSE: -- 2.39.5