i3
drag.c
Go to the documentation of this file.
1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3 - an improved dynamic 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. */
13 struct drag_x11_cb {
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. */
21  Con *con;
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 
43 static bool threshold_exceeded(uint32_t x1, uint32_t y1,
44  uint32_t x2, uint32_t y2) {
45  const uint32_t threshold = 9;
46  return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) > threshold * threshold;
47 }
48 
49 static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) {
50  xcb_motion_notify_event_t *last_motion_notify = NULL;
51  xcb_generic_event_t *event;
52 
53  while ((event = xcb_poll_for_event(conn)) != NULL) {
54  if (event->response_type == 0) {
55  xcb_generic_error_t *error = (xcb_generic_error_t *)event;
56  DLOG("X11 Error received (probably harmless)! sequence 0x%x, error_code = %d\n",
57  error->sequence, error->error_code);
58  free(event);
59  continue;
60  }
61 
62  /* Strip off the highest bit (set if the event is generated) */
63  int type = (event->response_type & 0x7F);
64 
65  switch (type) {
66  case XCB_BUTTON_RELEASE:
67  dragloop->result = DRAG_SUCCESS;
68  break;
69 
70  case XCB_KEY_PRESS:
71  DLOG("A key was pressed during drag, reverting changes.\n");
72  dragloop->result = DRAG_REVERT;
73  handle_event(type, event);
74  break;
75 
76  case XCB_UNMAP_NOTIFY: {
77  xcb_unmap_notify_event_t *unmap_event = (xcb_unmap_notify_event_t *)event;
78  Con *con = con_by_window_id(unmap_event->window);
79 
80  if (con != NULL) {
81  DLOG("UnmapNotify for window 0x%08x (container %p)\n", unmap_event->window, con);
82 
84  DLOG("UnmapNotify for a managed window on the current workspace, aborting\n");
85  dragloop->result = DRAG_ABORT;
86  }
87  }
88 
89  handle_event(type, event);
90  break;
91  }
92 
93  case XCB_MOTION_NOTIFY:
94  /* motion_notify events are saved for later */
95  FREE(last_motion_notify);
96  last_motion_notify = (xcb_motion_notify_event_t *)event;
97  break;
98 
99  default:
100  DLOG("Passing to original handler\n");
101  handle_event(type, event);
102  break;
103  }
104 
105  if (last_motion_notify != (xcb_motion_notify_event_t *)event)
106  free(event);
107 
108  if (dragloop->result != DRAGGING) {
109  ev_break(EV_A_ EVBREAK_ONE);
110  if (dragloop->result == DRAG_SUCCESS) {
111  /* Ensure motion notify events are handled. */
112  break;
113  } else {
114  free(last_motion_notify);
115  return true;
116  }
117  }
118  }
119 
120  if (last_motion_notify == NULL) {
121  return true;
122  }
123 
124  if (!dragloop->threshold_exceeded &&
125  threshold_exceeded(last_motion_notify->root_x, last_motion_notify->root_y,
126  dragloop->event->root_x, dragloop->event->root_y)) {
127  if (dragloop->xcursor != XCB_NONE) {
128  xcb_change_active_pointer_grab(
129  conn,
130  dragloop->xcursor,
131  XCB_CURRENT_TIME,
132  XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION);
133  }
134  dragloop->threshold_exceeded = true;
135  }
136 
137  /* Ensure that we are either dragging the resize handle (con is NULL) or that the
138  * container still exists. The latter might not be true, e.g., if the window closed
139  * for any reason while the user was dragging it. */
140  if (dragloop->threshold_exceeded && (!dragloop->con || con_exists(dragloop->con))) {
141  dragloop->callback(
142  dragloop->con,
143  &(dragloop->old_rect),
144  last_motion_notify->root_x,
145  last_motion_notify->root_y,
146  dragloop->event,
147  dragloop->extra);
148  }
149  FREE(last_motion_notify);
150 
151  xcb_flush(conn);
152  return dragloop->result != DRAGGING;
153 }
154 
155 static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
156  struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
157  while (!drain_drag_events(EV_A, dragloop)) {
158  /* repeatedly drain events: draining might produce additional ones */
159  }
160 }
161 
162 /*
163  * This function grabs your pointer and keyboard and lets you drag stuff around
164  * (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
165  * be received and the given callback will be called with the parameters
166  * specified (client, the original event), the original rect of the client,
167  * and the new coordinates (x, y).
168  *
169  * If use_threshold is set, dragging only starts after the user moves the
170  * pointer past a certain threshold. That is, the cursor will not be set and the
171  * callback will not be called until then.
172  *
173  */
174 drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event,
175  xcb_window_t confine_to, int cursor,
176  bool use_threshold, callback_t callback,
177  const void *extra) {
178  xcb_cursor_t xcursor = cursor ? xcursor_get_cursor(cursor) : XCB_NONE;
179 
180  /* Grab the pointer */
181  xcb_grab_pointer_cookie_t cookie;
182  xcb_grab_pointer_reply_t *reply;
183  xcb_generic_error_t *error;
184 
185  cookie = xcb_grab_pointer(conn,
186  false, /* get all pointer events specified by the following mask */
187  root, /* grab the root window */
188  XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */
189  XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
190  XCB_GRAB_MODE_ASYNC, /* keyboard mode */
191  confine_to, /* confine_to = in which window should the cursor stay */
192  use_threshold ? XCB_NONE : xcursor, /* possibly display a special cursor */
193  XCB_CURRENT_TIME);
194 
195  if ((reply = xcb_grab_pointer_reply(conn, cookie, &error)) == NULL) {
196  ELOG("Could not grab pointer (error_code = %d)\n", error->error_code);
197  free(error);
198  return DRAG_ABORT;
199  }
200 
201  free(reply);
202 
203  /* Grab the keyboard */
204  xcb_grab_keyboard_cookie_t keyb_cookie;
205  xcb_grab_keyboard_reply_t *keyb_reply;
206 
207  keyb_cookie = xcb_grab_keyboard(conn,
208  false, /* get all keyboard events */
209  root, /* grab the root window */
210  XCB_CURRENT_TIME,
211  XCB_GRAB_MODE_ASYNC, /* continue processing pointer events as normal */
212  XCB_GRAB_MODE_ASYNC /* keyboard mode */
213  );
214 
215  if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, &error)) == NULL) {
216  ELOG("Could not grab keyboard (error_code = %d)\n", error->error_code);
217  free(error);
218  xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
219  return DRAG_ABORT;
220  }
221 
222  free(keyb_reply);
223 
224  /* Go into our own event loop */
225  struct drag_x11_cb loop = {
226  .result = DRAGGING,
227  .con = con,
228  .event = event,
229  .callback = callback,
230  .threshold_exceeded = !use_threshold,
231  .xcursor = xcursor,
232  .extra = extra,
233  };
234  ev_prepare *prepare = &loop.prepare;
235  if (con)
236  loop.old_rect = con->rect;
237  ev_prepare_init(prepare, xcb_drag_prepare_cb);
238  prepare->data = &loop;
239  main_set_x11_cb(false);
240  ev_prepare_start(main_loop, prepare);
241 
242  ev_loop(main_loop, 0);
243 
244  ev_prepare_stop(main_loop, prepare);
245  main_set_x11_cb(true);
246 
247  xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
248  xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
249  xcb_flush(conn);
250 
251  return loop.result;
252 }
Con * con_get_workspace(Con *con)
Gets the workspace container this node is on.
Definition: con.c:477
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:668
bool con_exists(Con *con)
Returns true if the given container (still) exists.
Definition: con.c:699
static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents)
Definition: drag.c:155
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:49
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:174
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:1378
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
struct Con * focused
Definition: tree.c:13
xcb_cursor_t xcursor_get_cursor(enum xcursor_cursor_t c)
Definition: xcursor.c:54
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
#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:156
A 'Con' represents everything from the X11 root window down to a single X11 window.
Definition: data.h:613
struct Rect rect
Definition: data.h:649