i3
drag.c
Go to the documentation of this file.
1/*
2 * vim:ts=4:sw=4:expandtab
3 *
4 * i3 - an improved tiling window manager
5 * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
6 *
7 * drag.c: click and drag.
8 *
9 */
10#include "all.h"
11
12/* Custom data structure used to track dragging-related events. */
14 ev_prepare prepare;
15
16 /* Whether this modal event loop should be exited and with which result. */
18
19 /* The container that is being dragged or resized, or NULL if this is a
20 * drag of the resize handle. */
22
23 /* The original event that initiated the drag. */
24 const xcb_button_press_event_t *event;
25
26 /* The dimensions of con when the loop was started. */
28
29 /* The callback to invoke after every pointer movement. */
31
32 /* Drag distance threshold exceeded. If use_threshold is not set, then
33 * threshold_exceeded is always true. */
35
36 /* Cursor to set after the threshold is exceeded. */
37 xcb_cursor_t xcursor;
38
39 /* User data pointer for callback. */
40 const void *extra;
41};
42
43static bool threshold_exceeded(uint32_t x1, uint32_t y1,
44 uint32_t x2, uint32_t y2) {
45 /* The threshold is about the height of one window decoration. */
46 const uint32_t threshold = logical_px(15);
47 return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) > threshold * threshold;
48}
49
50static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) {
51 xcb_motion_notify_event_t *last_motion_notify = NULL;
52 xcb_generic_event_t *event;
53
54 while ((event = xcb_poll_for_event(conn)) != NULL) {
55 if (event->response_type == 0) {
56 xcb_generic_error_t *error = (xcb_generic_error_t *)event;
57 DLOG("X11 Error received (probably harmless)! sequence 0x%x, error_code = %d\n",
58 error->sequence, error->error_code);
59 free(event);
60 continue;
61 }
62
63 /* Strip off the highest bit (set if the event is generated) */
64 int type = (event->response_type & 0x7F);
65
66 switch (type) {
67 case XCB_BUTTON_RELEASE:
68 dragloop->result = DRAG_SUCCESS;
69 break;
70
71 case XCB_KEY_PRESS:
72 DLOG("A key was pressed during drag, reverting changes.\n");
73 dragloop->result = DRAG_REVERT;
74 handle_event(type, event);
75 break;
76
77 case XCB_UNMAP_NOTIFY: {
78 xcb_unmap_notify_event_t *unmap_event = (xcb_unmap_notify_event_t *)event;
79 Con *con = con_by_window_id(unmap_event->window);
80
81 if (con != NULL) {
82 DLOG("UnmapNotify for window 0x%08x (container %p)\n", unmap_event->window, con);
83
85 DLOG("UnmapNotify for a managed window on the current workspace, aborting\n");
86 dragloop->result = DRAG_ABORT;
87 }
88 }
89
90 handle_event(type, event);
91 break;
92 }
93
94 case XCB_MOTION_NOTIFY:
95 /* motion_notify events are saved for later */
96 FREE(last_motion_notify);
97 last_motion_notify = (xcb_motion_notify_event_t *)event;
98 break;
99
100 default:
101 DLOG("Passing to original handler\n");
102 handle_event(type, event);
103 break;
104 }
105
106 if (last_motion_notify != (xcb_motion_notify_event_t *)event) {
107 free(event);
108 }
109
110 if (dragloop->result != DRAGGING) {
111 ev_break(EV_A_ EVBREAK_ONE);
112 if (dragloop->result == DRAG_SUCCESS) {
113 /* Ensure motion notify events are handled. */
114 break;
115 } else {
116 free(last_motion_notify);
117 return true;
118 }
119 }
120 }
121
122 if (last_motion_notify == NULL) {
123 return true;
124 }
125
126 if (!dragloop->threshold_exceeded &&
127 threshold_exceeded(last_motion_notify->root_x, last_motion_notify->root_y,
128 dragloop->event->root_x, dragloop->event->root_y)) {
129 if (dragloop->xcursor != XCB_NONE) {
130 xcb_change_active_pointer_grab(
131 conn,
132 dragloop->xcursor,
133 XCB_CURRENT_TIME,
134 XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION);
135 }
136 dragloop->threshold_exceeded = true;
137 }
138
139 /* Ensure that we are either dragging the resize handle (con is NULL) or that the
140 * container still exists. The latter might not be true, e.g., if the window closed
141 * for any reason while the user was dragging it. */
142 if (dragloop->threshold_exceeded && (!dragloop->con || con_exists(dragloop->con))) {
143 dragloop->callback(
144 dragloop->con,
145 &(dragloop->old_rect),
146 last_motion_notify->root_x,
147 last_motion_notify->root_y,
148 dragloop->event,
149 dragloop->extra);
150 }
151 FREE(last_motion_notify);
152
153 xcb_flush(conn);
154 return dragloop->result != DRAGGING;
155}
156
157static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
158 struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
159 while (!drain_drag_events(EV_A, dragloop)) {
160 /* repeatedly drain events: draining might produce additional ones */
161 }
162}
163
164/*
165 * This function grabs your pointer and keyboard and lets you drag stuff around
166 * (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
167 * be received and the given callback will be called with the parameters
168 * specified (client, the original event), the original rect of the client,
169 * and the new coordinates (x, y).
170 *
171 * If use_threshold is set, dragging only starts after the user moves the
172 * pointer past a certain threshold. That is, the cursor will not be set and the
173 * callback will not be called until then.
174 *
175 */
176drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event,
177 xcb_window_t confine_to, int cursor,
178 bool use_threshold, callback_t callback,
179 const void *extra) {
180 xcb_cursor_t xcursor = cursor ? xcursor_get_cursor(cursor) : XCB_NONE;
181
182 /* Grab the pointer */
183 xcb_grab_pointer_cookie_t cookie;
184 xcb_grab_pointer_reply_t *reply;
185 xcb_generic_error_t *error;
186
187 cookie = xcb_grab_pointer(conn,
188 false, /* get all pointer events specified by the following mask */
189 root, /* grab the root window */
190 XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */
191 XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
192 XCB_GRAB_MODE_ASYNC, /* keyboard mode */
193 confine_to, /* confine_to = in which window should the cursor stay */
194 use_threshold ? XCB_NONE : xcursor, /* possibly display a special cursor */
195 XCB_CURRENT_TIME);
196
197 if ((reply = xcb_grab_pointer_reply(conn, cookie, &error)) == NULL) {
198 ELOG("Could not grab pointer (error_code = %d)\n", error->error_code);
199 free(error);
200 return DRAG_ABORT;
201 }
202
203 free(reply);
204
205 /* Grab the keyboard */
206 xcb_grab_keyboard_cookie_t keyb_cookie;
207 xcb_grab_keyboard_reply_t *keyb_reply;
208
209 keyb_cookie = xcb_grab_keyboard(conn,
210 false, /* get all keyboard events */
211 root, /* grab the root window */
212 XCB_CURRENT_TIME,
213 XCB_GRAB_MODE_ASYNC, /* continue processing pointer events as normal */
214 XCB_GRAB_MODE_ASYNC /* keyboard mode */
215 );
216
217 if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, &error)) == NULL) {
218 ELOG("Could not grab keyboard (error_code = %d)\n", error->error_code);
219 free(error);
220 xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
221 return DRAG_ABORT;
222 }
223
224 free(keyb_reply);
225
226 /* Go into our own event loop */
227 struct drag_x11_cb loop = {
228 .result = DRAGGING,
229 .con = con,
230 .event = event,
231 .callback = callback,
232 .threshold_exceeded = !use_threshold,
233 .xcursor = xcursor,
234 .extra = extra,
235 };
236 ev_prepare *prepare = &loop.prepare;
237 if (con) {
238 loop.old_rect = con->rect;
239 }
240 ev_prepare_init(prepare, xcb_drag_prepare_cb);
241 prepare->data = &loop;
242 main_set_x11_cb(false);
243 ev_prepare_start(main_loop, prepare);
244
245 ev_loop(main_loop, 0);
246
247 ev_prepare_stop(main_loop, prepare);
248 main_set_x11_cb(true);
249
250 xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
251 xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
252 xcb_flush(conn);
253
254 return loop.result;
255}
Con * con_get_workspace(Con *con)
Gets the workspace container this node is on.
Definition con.c:547
Con * con_by_window_id(xcb_window_t window)
Returns the container with the given client window ID or NULL if no such container exists.
Definition con.c:752
bool con_exists(Con *con)
Returns true if the given container (still) exists.
Definition con.c:783
static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents)
Definition drag.c:157
static bool threshold_exceeded(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2)
Definition drag.c:43
static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop)
Definition drag.c:50
drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t confine_to, int cursor, bool use_threshold, callback_t callback, const void *extra)
This function grabs your pointer and keyboard and lets you drag stuff around (borders).
Definition drag.c:176
void handle_event(int type, xcb_generic_event_t *event)
Takes an xcb_generic_event_t and calls the appropriate handler, based on the event type.
Definition handlers.c:1431
struct Con * focused
Definition tree.c:13
xcb_cursor_t xcursor_get_cursor(enum xcursor_cursor_t c)
Definition xcursor.c:54
xcb_connection_t * conn
XCB connection and root screen.
Definition main.c:54
xcb_window_t root
Definition main.c:67
void main_set_x11_cb(bool enable)
Enable or disable the main X11 event handling function.
Definition main.c:166
struct ev_loop * main_loop
Definition main.c:79
void(* callback_t)(Con *, Rect *, uint32_t, uint32_t, const xcb_button_press_event_t *, const void *)
Callback for dragging.
Definition drag.h:15
drag_result_t
This is the return value of a drag operation like drag_pointer.
Definition drag.h:39
@ DRAG_SUCCESS
Definition drag.h:41
@ DRAG_ABORT
Definition drag.h:43
@ DRAGGING
Definition drag.h:40
@ DRAG_REVERT
Definition drag.h:42
int logical_px(const int logical)
Convert a logical amount of pixels (e.g.
#define DLOG(fmt,...)
Definition libi3.h:105
#define ELOG(fmt,...)
Definition libi3.h:100
#define FREE(pointer)
Definition util.h:47
Rect old_rect
Definition drag.c:27
drag_result_t result
Definition drag.c:17
xcb_cursor_t xcursor
Definition drag.c:37
ev_prepare prepare
Definition drag.c:14
Con * con
Definition drag.c:21
const xcb_button_press_event_t * event
Definition drag.c:24
const void * extra
Definition drag.c:40
callback_t callback
Definition drag.c:30
bool threshold_exceeded
Definition drag.c:34
Stores a rectangle, for example the size of a window, the child window etc.
Definition data.h:185
A 'Con' represents everything from the X11 root window down to a single X11 window.
Definition data.h:643
struct Rect rect
Definition data.h:682