i3 - improved tiling WM


Return DRAG_ABORT on UnmapNotify from drag_pointer

Patch status: needinfo

Patch by Tony Crisci

Long description:

Add DRAG_ABORT to enum drag_result_t. DRAG_ABORT will indicate the drag
operation cannot be completed.

Return DRAG_ABORT on UnmapNotify, or when the keyboard or pointer cannot
be grabbed.

Add DRAGGING to return value for drag_result_t. DRAGGING is used
internally by drag_pointer to indicate the drag is in progress.

Change DRAG_CANCEL to DRAG_REVERT to clarify the distinction between
"abort" and "revert/cancel" actions.

Fixes an issue that caused i3 to crash when a user is dragging or
resizing a floating window that becomes destroyed.

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

b/include/floating.h

29
@@ -134,12 +134,24 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace);
30
 
31
 #endif
32
 /**
33
- * This is the return value of a drag operation like drag_pointer. DRAG_CANCEL
34
- * will indicate the intention of the drag should not be carried out, or that
35
- * the drag actions should be undone.
36
+ * This is the return value of a drag operation like drag_pointer.
37
+ *
38
+ * DRAG_SUCCESS will indicate the intention of the drag action should be
39
+ * carried out.
40
+ *
41
+ * DRAG_REVERT will indicate an attempt should be made to restore the state of
42
+ * the involved windows to their condition before the drag.
43
+ *
44
+ * DRAG_ABORT will indicate that the intention of the drag action cannot be
45
+ * carried out (e.g. because the window has been unmapped).
46
  *
47
  */
48
-typedef enum { DRAG_SUCCESS = 0, DRAG_CANCEL } drag_result_t;
49
+typedef enum {
50
+    DRAGGING = 0,
51
+    DRAG_SUCCESS,
52
+    DRAG_REVERT,
53
+    DRAG_ABORT
54
+} drag_result_t;
55
 
56
 /**
57
  * This function grabs your pointer and keyboard and lets you drag stuff around

b/src/floating.c

62
@@ -441,14 +441,14 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
63
      * after the user releases the mouse button */
64
     tree_render();
65
 
66
-    /* Store the initial rect in case of user cancel */
67
+    /* Store the initial rect in case of user revert/cancel */
68
     Rect initial_rect = con->rect;
69
 
70
     /* Drag the window */
71
     drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event);
72
 
73
     /* If the user cancelled, undo the changes. */
74
-    if (drag_result == DRAG_CANCEL)
75
+    if (drag_result == DRAG_REVERT)
76
         floating_reposition(con, initial_rect);
77
 
78
     /* If this is a scratchpad window, don't auto center it from now on. */
79
@@ -553,13 +553,13 @@ void floating_resize_window(Con *con, const bool proportional,
80
 
81
     struct resize_window_callback_params params = { corner, proportional, event };
82
 
83
-    /* get the initial rect in case of cancel */
84
+    /* get the initial rect in case of revert/cancel */
85
     Rect initial_rect = con->rect;
86
 
87
     drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, &params);
88
 
89
     /* If the user cancels, undo the resize */
90
-    if (drag_result == DRAG_CANCEL)
91
+    if (drag_result == DRAG_REVERT)
92
         floating_reposition(con, initial_rect);
93
 
94
     /* If this is a scratchpad window, don't auto center it from now on. */
95
@@ -601,7 +601,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
96
 
97
     if ((reply = xcb_grab_pointer_reply(conn, cookie, NULL)) == NULL) {
98
         ELOG("Could not grab pointer\n");
99
-        return DRAG_CANCEL;
100
+        return DRAG_ABORT;
101
     }
102
 
103
     free(reply);
104
@@ -620,7 +620,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
105
 
106
     if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, NULL)) == NULL) {
107
         ELOG("Could not grab keyboard\n");
108
-        return DRAG_CANCEL;
109
+        return DRAG_ABORT;
110
     }
111
 
112
     free(keyb_reply);
113
@@ -629,11 +629,11 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
114
     xcb_flush(conn);
115
 
116
     xcb_generic_event_t *inside_event, *last_motion_notify = NULL;
117
-    bool loop_done = false;
118
-    /* The return value, set to DRAG_CANCEL on user cancel */
119
-    drag_result_t drag_result = DRAG_SUCCESS;
120
+    Con *inside_con = NULL;
121
+    /* The return value, set to exit the event loop */
122
+    drag_result_t drag_result = DRAGGING;
123
     /* I’ve always wanted to have my own eventhandler… */
124
-    while (!loop_done && (inside_event = xcb_wait_for_event(conn))) {
125
+    while (drag_result == DRAGGING && (inside_event = xcb_wait_for_event(conn))) {
126
         /* We now handle all events we can get using xcb_poll_for_event */
127
         do {
128
             /* skip x11 errors */
129
@@ -646,7 +646,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
130
 
131
             switch (type) {
132
                 case XCB_BUTTON_RELEASE:
133
-                    loop_done = true;
134
+                    drag_result = DRAG_SUCCESS;
135
                     break;
136
 
137
                 case XCB_MOTION_NOTIFY:
138
@@ -656,17 +656,18 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
139
                     break;
140
 
141
                 case XCB_UNMAP_NOTIFY:
142
-                    DLOG("Unmap-notify, aborting\n");
143
+                    inside_con = con_by_window_id(((xcb_unmap_notify_event_t*)inside_event)->window);
144
+
145
+                    DLOG("UnmapNotify for window 0x%08x (container %p)\n", ((xcb_unmap_notify_event_t*)inside_event)->window, inside_con);
146
+                    drag_result = DRAG_ABORT;
147
+
148
                     handle_event(type, inside_event);
149
-                    loop_done = true;
150
-                    drag_result = DRAG_CANCEL;
151
                     break;
152
 
153
                 case XCB_KEY_PRESS:
154
                     /* Cancel the drag if a key was pressed */
155
-                    DLOG("A key was pressed during drag, canceling.");
156
-                    loop_done = true;
157
-                    drag_result = DRAG_CANCEL;
158
+                    DLOG("A key was pressed during drag, reverting changes.");
159
+                    drag_result = DRAG_REVERT;
160
 
161
                     handle_event(type, inside_event);
162
                     break;
163
@@ -681,7 +682,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
164
                 free(inside_event);
165
         } while ((inside_event = xcb_poll_for_event(conn)) != NULL);
166
 
167
-        if (last_motion_notify == NULL || loop_done)
168
+        if (last_motion_notify == NULL || drag_result != DRAGGING)
169
             continue;
170
 
171
         new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x;

b/src/resize.c

176
@@ -161,7 +161,7 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
177
     xcb_flush(conn);
178
 
179
     /* User cancelled the drag so no action should be taken. */
180
-    if (drag_result == DRAG_CANCEL)
181
+    if (drag_result == DRAG_REVERT)
182
         return 0;
183
 
184
     int pixels;