i3 - improved tiling WM


Add ability to escape out of a mouse-resize operation

Patch status: merged

Patch by Tony Crisci

Long description:

Implement #1074. drag_cancel grabs the keyboard and returns DRAG_CANCEL
when the user presses any key during the grab.

To apply this patch, use:
curl http://cr.i3wm.org/patch/242/raw.patch | git am

b/include/floating.h

17
@@ -134,14 +134,22 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace);
18
 
19
 #endif
20
 /**
21
- * This function grabs your pointer and lets you drag stuff around (borders).
22
- * Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
23
- * and the given callback will be called with the parameters specified (client,
24
- * border on which the click originally was), the original rect of the client,
25
- * the event and the new coordinates (x, y).
26
+ * This is the return value of a drag operation like drag_pointer. DRAG_CANCEL
27
+ * will indicate the intention of the drag should not be carried out, or that
28
+ * the drag actions should be undone.
29
  *
30
  */
31
-void drag_pointer(Con *con, const xcb_button_press_event_t *event,
32
+typedef enum { DRAG_SUCCESS = 0, DRAG_CANCEL } drag_result_t;
33
+
34
+/**
35
+ * This function grabs your pointer and keyboard and lets you drag stuff around
36
+ * (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
37
+ * be received and the given callback will be called with the parameters
38
+ * specified (client, border on which the click originally was), the original
39
+ * rect of the client, the event and the new coordinates (x, y).
40
+ *
41
+ */
42
+drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event,
43
                   xcb_window_t confine_to, border_t border, int cursor,
44
                   callback_t callback, const void *extra);
45
 

b/src/floating.c

50
@@ -441,8 +441,15 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
51
      * after the user releases the mouse button */
52
     tree_render();
53
 
54
+    /* Store the initial rect in case of user cancel */
55
+    Rect initial_rect = con->rect;
56
+
57
     /* Drag the window */
58
-    drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event);
59
+    drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event);
60
+
61
+    /* If the user cancelled, undo the changes. */
62
+    if (drag_result == DRAG_CANCEL)
63
+        floating_reposition(con, initial_rect);
64
 
65
     /* If this is a scratchpad window, don't auto center it from now on. */
66
     if (con->scratchpad_state == SCRATCHPAD_FRESH)
67
@@ -546,7 +553,14 @@ void floating_resize_window(Con *con, const bool proportional,
68
 
69
     struct resize_window_callback_params params = { corner, proportional, event };
70
 
71
-    drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, &params);
72
+    /* get the initial rect in case of cancel */
73
+    Rect initial_rect = con->rect;
74
+
75
+    drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, &params);
76
+
77
+    /* If the user cancels, undo the resize */
78
+    if (drag_result == DRAG_CANCEL)
79
+        floating_reposition(con, initial_rect);
80
 
81
     /* If this is a scratchpad window, don't auto center it from now on. */
82
     if (con->scratchpad_state == SCRATCHPAD_FRESH)
83
@@ -554,14 +568,14 @@ void floating_resize_window(Con *con, const bool proportional,
84
 }
85
 
86
 /*
87
- * This function grabs your pointer and lets you drag stuff around (borders).
88
- * Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
89
- * and the given callback will be called with the parameters specified (client,
90
- * border on which the click originally was), the original rect of the client,
91
- * the event and the new coordinates (x, y).
92
+ * This function grabs your pointer and keyboard and lets you drag stuff around
93
+ * (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
94
+ * be received and the given callback will be called with the parameters
95
+ * specified (client, border on which the click originally was), the original
96
+ * rect of the client, the event and the new coordinates (x, y).
97
  *
98
  */
99
-void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
100
+drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
101
                 confine_to, border_t border, int cursor, callback_t callback, const void *extra)
102
 {
103
     uint32_t new_x, new_y;
104
@@ -587,16 +601,37 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
105
 
106
     if ((reply = xcb_grab_pointer_reply(conn, cookie, NULL)) == NULL) {
107
         ELOG("Could not grab pointer\n");
108
-        return;
109
+        return DRAG_CANCEL;
110
     }
111
 
112
     free(reply);
113
 
114
+    /* Grab the keyboard */
115
+    xcb_grab_keyboard_cookie_t keyb_cookie;
116
+    xcb_grab_keyboard_reply_t *keyb_reply;
117
+
118
+    keyb_cookie = xcb_grab_keyboard(conn,
119
+            false, /* get all keyboard events */
120
+            root, /* grab the root window */
121
+            XCB_CURRENT_TIME,
122
+            XCB_GRAB_MODE_ASYNC, /* continue processing pointer events as normal */
123
+            XCB_GRAB_MODE_ASYNC /* keyboard mode */
124
+            );
125
+
126
+    if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, NULL)) == NULL) {
127
+        ELOG("Could not grab keyboard\n");
128
+        return DRAG_CANCEL;
129
+    }
130
+
131
+    free(keyb_reply);
132
+
133
     /* Go into our own event loop */
134
     xcb_flush(conn);
135
 
136
     xcb_generic_event_t *inside_event, *last_motion_notify = NULL;
137
     bool loop_done = false;
138
+    /* The return value, set to DRAG_CANCEL on user cancel */
139
+    drag_result_t drag_result = DRAG_SUCCESS;
140
     /* I’ve always wanted to have my own eventhandler… */
141
     while (!loop_done && (inside_event = xcb_wait_for_event(conn))) {
142
         /* We now handle all events we can get using xcb_poll_for_event */
143
@@ -621,11 +656,20 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
144
                     break;
145
 
146
                 case XCB_UNMAP_NOTIFY:
147
-                case XCB_KEY_PRESS:
148
-                case XCB_KEY_RELEASE:
149
                     DLOG("Unmap-notify, aborting\n");
150
                     handle_event(type, inside_event);
151
                     loop_done = true;
152
+                    drag_result = DRAG_CANCEL;
153
+                    break;
154
+
155
+                case XCB_KEY_PRESS:
156
+                case XCB_KEY_RELEASE:
157
+                    /* Cancel the drag if a key was pressed */
158
+                    DLOG("A key was pressed during drag, canceling.");
159
+                    loop_done = true;
160
+                    drag_result = DRAG_CANCEL;
161
+
162
+                    handle_event(type, inside_event);
163
                     break;
164
 
165
                 default:
166
@@ -648,8 +692,12 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
167
         FREE(last_motion_notify);
168
     }
169
 
170
+    xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
171
     xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
172
+
173
     xcb_flush(conn);
174
+
175
+    return drag_result;
176
 }
177
 
178
 /*

b/src/resize.c

183
@@ -154,12 +154,16 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
184
 
185
     const struct callback_params params = { orientation, output, helpwin, &new_position };
186
 
187
-    drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, &params);
188
+    drag_result_t drag_result = drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, &params);
189
 
190
     xcb_destroy_window(conn, helpwin);
191
     xcb_destroy_window(conn, grabwin);
192
     xcb_flush(conn);
193
 
194
+    /* User cancelled the drag so no action should be taken. */
195
+    if (drag_result == DRAG_CANCEL)
196
+        return 0;
197
+
198
     int pixels;
199
     if (orientation == HORIZ)
200
         pixels = (new_position - event->root_x);