i3 - improved tiling WM


Add ability to escape out of a mouse-resize operation

Patch status: needinfo

Patch by Tony Crisci

Long description:

Implement #1074. drag_cancel grabs the keyboard and returns DRAG_CANCEL
when the user presses the ESC key.

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

b/include/floating.h

17
@@ -134,14 +134,23 @@ 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
+typedef enum { DRAG_SUCCESS = 0, DRAG_CANCEL } drag_result_t;
32
+
33
+/**
34
+ * This function grabs your pointer and keyboard and lets you drag stuff around
35
+ * (borders).  Every time you move your mouse, an XCB_MOTION_NOTIFY event will
36
+ * be received and the given callback will be called with the parameters
37
+ * specified (client, border on which the click originally was), the original
38
+ * rect of the client, the event and the new coordinates (x, y). A return value
39
+ * of DRAG_CANCEL indicates the action should be undone.
40
  *
41
  */
42
-void drag_pointer(Con *con, const xcb_button_press_event_t *event,
43
+drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event,
44
                   xcb_window_t confine_to, border_t border, int cursor,
45
                   callback_t callback, const void *extra);
46
 

b/src/floating.c

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

b/src/resize.c

192
@@ -106,12 +106,16 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
193
 
194
     const struct callback_params params = { orientation, output, helpwin, &new_position };
195
 
196
-    drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, &params);
197
+    drag_result_t drag_result = drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, &params);
198
 
199
     xcb_destroy_window(conn, helpwin);
200
     xcb_destroy_window(conn, grabwin);
201
     xcb_flush(conn);
202
 
203
+    /* User cancelled the drag so no action should be taken. */
204
+    if (drag_result == DRAG_CANCEL)
205
+        return 0;
206
+
207
     int pixels;
208
     if (orientation == HORIZ)
209
         pixels = (new_position - event->root_x);