From 459490b67b6e2a1cf772360894e28aa54144ed51 Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Wed, 25 Sep 2013 20:52:59 -0400 Subject: [PATCH] Add ability to escape out of a mouse-resize operation Implement #1074. drag_cancel grabs the keyboard and returns DRAG_CANCEL when the user presses any key during the grab. --- include/floating.h | 20 +++++++++---- src/floating.c | 70 ++++++++++++++++++++++++++++++++++++++-------- src/resize.c | 6 +++- 3 files changed, 78 insertions(+), 18 deletions(-) diff --git a/include/floating.h b/include/floating.h index c8586527..04db1589 100644 --- a/include/floating.h +++ b/include/floating.h @@ -134,14 +134,22 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace); #endif /** - * This function grabs your pointer and lets you drag stuff around (borders). - * Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received - * and the given callback will be called with the parameters specified (client, - * border on which the click originally was), the original rect of the client, - * the event and the new coordinates (x, y). + * This is the return value of a drag operation like drag_pointer. DRAG_CANCEL + * will indicate the intention of the drag should not be carried out, or that + * the drag actions should be undone. * */ -void drag_pointer(Con *con, const xcb_button_press_event_t *event, +typedef enum { DRAG_SUCCESS = 0, DRAG_CANCEL } drag_result_t; + +/** + * 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 + * be received and the given callback will be called with the parameters + * specified (client, border on which the click originally was), the original + * rect of the client, the event and the new coordinates (x, y). + * + */ +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); diff --git a/src/floating.c b/src/floating.c index 97b7d884..a08b7b0f 100644 --- a/src/floating.c +++ b/src/floating.c @@ -441,8 +441,15 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) { * after the user releases the mouse button */ tree_render(); + /* Store the initial rect in case of user cancel */ + Rect initial_rect = con->rect; + /* Drag the window */ - drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event); + drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event); + + /* If the user cancelled, undo the changes. */ + if (drag_result == DRAG_CANCEL) + floating_reposition(con, initial_rect); /* If this is a scratchpad window, don't auto center it from now on. */ if (con->scratchpad_state == SCRATCHPAD_FRESH) @@ -546,7 +553,14 @@ void floating_resize_window(Con *con, const bool proportional, struct resize_window_callback_params params = { corner, proportional, event }; - drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, ¶ms); + /* get the initial rect in case of cancel */ + Rect initial_rect = con->rect; + + drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, ¶ms); + + /* If the user cancels, undo the resize */ + if (drag_result == DRAG_CANCEL) + floating_reposition(con, initial_rect); /* If this is a scratchpad window, don't auto center it from now on. */ if (con->scratchpad_state == SCRATCHPAD_FRESH) @@ -554,14 +568,14 @@ void floating_resize_window(Con *con, const bool proportional, } /* - * This function grabs your pointer and lets you drag stuff around (borders). - * Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received - * and the given callback will be called with the parameters specified (client, - * border on which the click originally was), the original rect of the client, - * the event and the new coordinates (x, y). + * 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 + * be received and the given callback will be called with the parameters + * specified (client, border on which the click originally was), the original + * rect of the client, the event and the new coordinates (x, y). * */ -void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t +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; @@ -587,16 +601,37 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t if ((reply = xcb_grab_pointer_reply(conn, cookie, NULL)) == NULL) { ELOG("Could not grab pointer\n"); - return; + return DRAG_CANCEL; } free(reply); + /* Grab the keyboard */ + xcb_grab_keyboard_cookie_t keyb_cookie; + xcb_grab_keyboard_reply_t *keyb_reply; + + keyb_cookie = xcb_grab_keyboard(conn, + false, /* get all keyboard events */ + root, /* grab the root window */ + XCB_CURRENT_TIME, + XCB_GRAB_MODE_ASYNC, /* continue processing pointer events as normal */ + XCB_GRAB_MODE_ASYNC /* keyboard mode */ + ); + + if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, NULL)) == NULL) { + ELOG("Could not grab keyboard\n"); + return DRAG_CANCEL; + } + + free(keyb_reply); + /* Go into our own event loop */ xcb_flush(conn); xcb_generic_event_t *inside_event, *last_motion_notify = NULL; bool loop_done = false; + /* The return value, set to DRAG_CANCEL on user cancel */ + drag_result_t drag_result = DRAG_SUCCESS; /* I’ve always wanted to have my own eventhandler… */ while (!loop_done && (inside_event = xcb_wait_for_event(conn))) { /* We now handle all events we can get using xcb_poll_for_event */ @@ -621,11 +656,20 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t break; case XCB_UNMAP_NOTIFY: - case XCB_KEY_PRESS: - case XCB_KEY_RELEASE: DLOG("Unmap-notify, aborting\n"); handle_event(type, inside_event); loop_done = true; + drag_result = DRAG_CANCEL; + break; + + case XCB_KEY_PRESS: + case XCB_KEY_RELEASE: + /* Cancel the drag if a key was pressed */ + DLOG("A key was pressed during drag, canceling."); + loop_done = true; + drag_result = DRAG_CANCEL; + + handle_event(type, inside_event); break; default: @@ -648,8 +692,12 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t FREE(last_motion_notify); } + xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME); xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); + xcb_flush(conn); + + return drag_result; } /* diff --git a/src/resize.c b/src/resize.c index f65ce88e..764a485c 100644 --- a/src/resize.c +++ b/src/resize.c @@ -154,12 +154,16 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const struct callback_params params = { orientation, output, helpwin, &new_position }; - drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, ¶ms); + drag_result_t drag_result = drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, ¶ms); xcb_destroy_window(conn, helpwin); xcb_destroy_window(conn, grabwin); xcb_flush(conn); + /* User cancelled the drag so no action should be taken. */ + if (drag_result == DRAG_CANCEL) + return 0; + int pixels; if (orientation == HORIZ) pixels = (new_position - event->root_x);