i3
tiling_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  * tiling_drag.c: Reposition tiled windows by dragging.
8  *
9  */
10 #include "all.h"
11 static xcb_window_t create_drop_indicator(Rect rect);
12 
13 static bool is_tiling_drop_target(Con *con) {
14  if (!con_has_managed_window(con) ||
15  con_is_floating(con) ||
16  con_is_hidden(con)) {
17  return false;
18  }
19  Con *ws = con_get_workspace(con);
20  if (con_is_internal(ws)) {
21  /* Skip containers on i3-internal containers like the scratchpad, which are
22  technically visible on their pseudo-output. */
23  return false;
24  }
25  if (!workspace_is_visible(ws)) {
26  return false;
27  }
29  if (fs != NULL && fs != con) {
30  /* Workspace is visible, but con is not visible because some other
31  container is in fullscreen. */
32  return false;
33  }
34  return true;
35 }
36 
37 /*
38  * Returns whether there currently are any drop targets.
39  * Used to only initiate a drag when there is something to drop onto.
40  *
41  */
42 bool has_drop_targets(void) {
43  int drop_targets = 0;
44  Con *con;
46  if (!is_tiling_drop_target(con)) {
47  continue;
48  }
49  drop_targets++;
50  }
51 
52  /* In addition to tiling containers themselves, an visible but empty
53  * workspace (in a multi-monitor scenario) also is a drop target. */
54  Con *output;
55  TAILQ_FOREACH (output, &(croot->focus_head), focused) {
56  if (con_is_internal(output)) {
57  continue;
58  }
59  Con *visible_ws = NULL;
60  GREP_FIRST(visible_ws, output_get_content(output), workspace_is_visible(child));
61  if (visible_ws != NULL && con_num_children(visible_ws) == 0) {
62  drop_targets++;
63  }
64  }
65 
66  return drop_targets > 1;
67 }
68 
69 /*
70  * Return an appropriate target at given coordinates.
71  *
72  */
73 static Con *find_drop_target(uint32_t x, uint32_t y) {
74  Con *con;
76  Rect rect = con->rect;
77  if (!rect_contains(rect, x, y) ||
78  !is_tiling_drop_target(con)) {
79  continue;
80  }
81  Con *ws = con_get_workspace(con);
83  return fs ? fs : con;
84  }
85 
86  /* Couldn't find leaf container, get a workspace. */
87  Output *output = get_output_containing(x, y);
88  if (!output) {
89  return NULL;
90  }
91  Con *content = output_get_content(output->con);
92  /* Still descend because you can drag to the bar on an non-empty workspace. */
93  return con_descend_tiling_focused(content);
94 }
95 
96 typedef enum { DT_SIBLING,
99 } drop_type_t;
100 
101 struct callback_params {
102  xcb_window_t *indicator;
106 };
107 
108 static Rect adjust_rect(Rect rect, direction_t direction, uint32_t threshold) {
109  switch (direction) {
110  case D_LEFT:
111  rect.width = threshold;
112  break;
113  case D_UP:
114  rect.height = threshold;
115  break;
116  case D_RIGHT:
117  rect.x += (rect.width - threshold);
118  rect.width = threshold;
119  break;
120  case D_DOWN:
121  rect.y += (rect.height - threshold);
122  rect.height = threshold;
123  break;
124  }
125  return rect;
126 }
127 
128 static bool con_on_side_of_parent(Con *con, direction_t direction) {
129  const orientation_t orientation = orientation_from_direction(direction);
130  direction_t reverse_direction;
131  switch (direction) {
132  case D_LEFT:
133  reverse_direction = D_RIGHT;
134  break;
135  case D_RIGHT:
136  reverse_direction = D_LEFT;
137  break;
138  case D_UP:
139  reverse_direction = D_DOWN;
140  break;
141  case D_DOWN:
142  reverse_direction = D_UP;
143  break;
144  }
145  return (con_orientation(con->parent) != orientation ||
146  con->parent->layout == L_STACKED || con->parent->layout == L_TABBED ||
147  con_descend_direction(con->parent, reverse_direction) == con);
148 }
149 
150 /*
151  * The callback that is executed on every mouse move while dragging. On each
152  * invocation we determine the drop target and the direction in which to insert
153  * the dragged container. The indicator window is updated to show the new
154  * position of the dragged container. The target container and direction are
155  * passed out using the callback params.
156  *
157  */
158 DRAGGING_CB(drag_callback) {
159  /* 30% of the container (minus the parent indicator) is used to drop the
160  * dragged container as a sibling to the target */
161  const double sibling_indicator_percent_of_rect = 0.3;
162  /* Use the base decoration height and add a few pixels. This makes the
163  * outer indicator generally thin but at least thick enough to cover
164  * container titles */
165  const double parent_indicator_max_size = render_deco_height() + logical_px(5);
166 
167  Con *target = find_drop_target(new_x, new_y);
168  if (target == NULL) {
169  return;
170  }
171 
172  Rect rect = target->rect;
173 
174  direction_t direction = 0;
175  drop_type_t drop_type = DT_CENTER;
176  bool draw_window = true;
177  const struct callback_params *params = extra;
178 
179  if (target->type == CT_WORKSPACE) {
180  goto create_indicator;
181  }
182 
183  /* Define the thresholds in pixels. The drop type depends on the cursor
184  * position. */
185  const uint32_t min_rect_dimension = min(rect.width, rect.height);
186  const uint32_t sibling_indicator_size = max(logical_px(2), (uint32_t)(sibling_indicator_percent_of_rect * min_rect_dimension));
187  const uint32_t parent_indicator_size = min(
188  parent_indicator_max_size,
189  /* For small containers, start where the sibling indicator finishes.
190  * This is always at least 1 pixel. We use min() to not override the
191  * sibling indicator: */
192  sibling_indicator_size - 1);
193 
194  /* Find which edge the cursor is closer to. */
195  const uint32_t d_left = new_x - rect.x;
196  const uint32_t d_top = new_y - rect.y;
197  const uint32_t d_right = rect.x + rect.width - new_x;
198  const uint32_t d_bottom = rect.y + rect.height - new_y;
199  const uint32_t d_min = min(min(d_left, d_right), min(d_top, d_bottom));
200  /* And move the container towards that direction. */
201  if (d_left == d_min) {
202  direction = D_LEFT;
203  } else if (d_top == d_min) {
204  direction = D_UP;
205  } else if (d_right == d_min) {
206  direction = D_RIGHT;
207  } else if (d_bottom == d_min) {
208  direction = D_DOWN;
209  } else {
210  /* Keep the compiler happy */
211  ELOG("min() is broken\n");
212  assert(false);
213  }
214  const bool target_parent = (d_min < parent_indicator_size &&
216  const bool target_sibling = (d_min < sibling_indicator_size);
217  drop_type = target_parent ? DT_PARENT : (target_sibling ? DT_SIBLING : DT_CENTER);
218 
219  /* target == con makes sense only when we are moving away from target's parent. */
220  if (drop_type != DT_PARENT && target == con) {
221  draw_window = false;
222  xcb_destroy_window(conn, *(params->indicator));
223  *(params->indicator) = 0;
224  goto create_indicator;
225  }
226 
227  switch (drop_type) {
228  case DT_PARENT:
229  while (target->parent->type != CT_WORKSPACE && con_on_side_of_parent(target->parent, direction)) {
230  target = target->parent;
231  }
232  rect = adjust_rect(target->parent->rect, direction, parent_indicator_size);
233  break;
234  case DT_CENTER:
235  rect = target->rect;
236  rect.x += sibling_indicator_size;
237  rect.y += sibling_indicator_size;
238  rect.width -= sibling_indicator_size * 2;
239  rect.height -= sibling_indicator_size * 2;
240  break;
241  case DT_SIBLING:
242  rect = adjust_rect(target->rect, direction, sibling_indicator_size);
243  break;
244  }
245 
246 create_indicator:
247  if (draw_window) {
248  if (*(params->indicator) == 0) {
249  *(params->indicator) = create_drop_indicator(rect);
250  } else {
251  const uint32_t values[4] = {rect.x, rect.y, rect.width, rect.height};
252  const uint32_t mask = XCB_CONFIG_WINDOW_X |
253  XCB_CONFIG_WINDOW_Y |
254  XCB_CONFIG_WINDOW_WIDTH |
255  XCB_CONFIG_WINDOW_HEIGHT;
256  xcb_configure_window(conn, *(params->indicator), mask, values);
257  }
258  }
259  x_mask_event_mask(~XCB_EVENT_MASK_ENTER_WINDOW);
260  xcb_flush(conn);
261 
262  *(params->target) = target;
263  *(params->direction) = direction;
264  *(params->drop_type) = drop_type;
265 }
266 
267 /*
268  * Returns a new drop indicator window with the given initial coordinates.
269  *
270  */
271 static xcb_window_t create_drop_indicator(Rect rect) {
272  uint32_t mask = 0;
273  uint32_t values[2];
274 
275  mask = XCB_CW_BACK_PIXEL;
277 
278  mask |= XCB_CW_OVERRIDE_REDIRECT;
279  values[1] = 1;
280 
281  xcb_window_t indicator = create_window(conn, rect, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
282  XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_MOVE, false, mask, values);
283  /* Change the window class to "i3-drag", so that it can be matched in a
284  * compositor configuration. Note that the class needs to be changed before
285  * mapping the window. */
286  xcb_change_property(conn,
287  XCB_PROP_MODE_REPLACE,
288  indicator,
289  XCB_ATOM_WM_CLASS,
290  XCB_ATOM_STRING,
291  8,
292  (strlen("i3-drag") + 1) * 2,
293  "i3-drag\0i3-drag\0");
294  xcb_map_window(conn, indicator);
295  xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, indicator);
296 
297  return indicator;
298 }
299 
300 /*
301  * Initiates a mouse drag operation on a tiled window.
302  *
303  */
304 void tiling_drag(Con *con, xcb_button_press_event_t *event, bool use_threshold) {
305  DLOG("Start dragging tiled container: con = %p\n", con);
306  bool set_focus = (con == focused);
307  bool set_fs = con->fullscreen_mode != CF_NONE;
308 
309  /* Don't change focus while dragging. */
310  x_mask_event_mask(~XCB_EVENT_MASK_ENTER_WINDOW);
311  xcb_flush(conn);
312 
313  /* Indicate drop location while dragging. This blocks until the drag is completed. */
314  Con *target = NULL;
317  xcb_window_t indicator = 0;
318  const struct callback_params params = {&indicator, &target, &direction, &drop_type};
319 
320  drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, XCURSOR_CURSOR_MOVE, use_threshold, drag_callback, &params);
321 
322  /* Dragging is done. We don't need the indicator window any more. */
323  xcb_destroy_window(conn, indicator);
324 
325  if (drag_result == DRAG_REVERT ||
326  target == NULL ||
327  (target == con && drop_type != DT_PARENT) ||
328  !con_exists(target)) {
329  DLOG("drop aborted\n");
330  return;
331  }
332 
334  const position_t position = position_from_direction(direction);
335  const layout_t layout = orientation == VERT ? L_SPLITV : L_SPLITH;
337  switch (drop_type) {
338  case DT_CENTER:
339  /* Also handles workspaces.*/
340  DLOG("drop to center of %p\n", target);
342  break;
343  case DT_SIBLING:
344  DLOG("drop %s %p\n", position_to_string(position), target);
346  /* If con and target are the only children of the same parent, we can just change
347  * the parent's layout manually and then move con to the correct position.
348  * tree_split checks for a parent with only one child so it would create a new
349  * parent with the new layout. */
350  if (con->parent == target->parent && con_num_children(target->parent) == 2) {
351  target->parent->layout = layout;
352  } else {
354  }
355  }
356 
357  insert_con_into(con, target, position);
358 
359  ipc_send_window_event("move", con);
360  break;
361  case DT_PARENT: {
362  const bool parent_tabbed_or_stacked = (target->parent->layout == L_TABBED || target->parent->layout == L_STACKED);
363  DLOG("drop %s (%s) of %s%p\n",
365  position_to_string(position),
366  parent_tabbed_or_stacked ? "tabbed/stacked " : "",
367  target);
368  if (parent_tabbed_or_stacked) {
369  /* When dealing with tabbed/stacked the target can be in the
370  * middle of the container. Thus, after a directional move, con
371  * will still be bound to the tabbed/stacked parent. */
372  if (position == BEFORE) {
373  target = TAILQ_FIRST(&(target->parent->nodes_head));
374  } else {
375  target = TAILQ_LAST(&(target->parent->nodes_head), nodes_head);
376  }
377  }
378  if (con != target) {
379  insert_con_into(con, target, position);
380  }
381  /* tree_move can change the focus */
382  Con *old_focus = focused;
383  tree_move(con, direction);
384  if (focused != old_focus) {
385  con_activate(old_focus);
386  }
387  break;
388  }
389  }
390  /* Warning: target might not exist anymore */
391  target = NULL;
392 
393  /* Manage fullscreen status. */
394  if (set_focus || set_fs) {
396  if (fs == con) {
397  ELOG("dragged container somehow got fullscreen again.\n");
398  assert(false);
399  } else if (fs && set_focus && set_fs) {
400  /* con was focused & fullscreen, disable other fullscreen container. */
402  } else if (fs) {
403  /* con was not focused, prefer other fullscreen container. */
404  set_fs = set_focus = false;
405  } else if (!set_focus) {
406  /* con was not focused. If it was fullscreen and we are moving it to the focused
407  * workspace we must focus it. */
408  set_focus = (set_fs && con_get_workspace(focused) == con_get_workspace(con));
409  }
410  }
411  if (set_fs) {
413  }
414  if (set_focus) {
416  con_focus(con);
417  }
418  tree_render();
419 }
DRAGGING_CB(drag_callback)
Definition: tiling_drag.c:158
An Output is a physical output on your graphics driver.
Definition: data.h:413
bool has_drop_targets(void)
Returns whether there currently are any drop targets.
Definition: tiling_drag.c:42
#define ELOG(fmt,...)
Definition: libi3.h:100
struct Colortriple focused
bool con_exists(Con *con)
Returns true if the given container (still) exists.
Definition: con.c:702
uint32_t y
Definition: data.h:209
Definition: data.h:112
void tiling_drag(Con *con, xcb_button_press_event_t *event, bool use_threshold)
Initiates a mouse drag operation on a tiled window.
Definition: tiling_drag.c:304
#define TAILQ_FIRST(head)
Definition: queue.h:336
Con * output_get_content(Con *output)
Returns the output container below the given output container.
Definition: output.c:16
const char * direction_to_string(direction_t direction)
Converts direction to a string representation.
Definition: util.c:484
Definition: data.h:58
Stores a rectangle, for example the size of a window, the child window etc.
Definition: data.h:207
int render_deco_height(void)
Returns the height for the decorations.
Definition: render.c:27
layout_t
Container layouts.
Definition: data.h:105
#define y(x,...)
Definition: commands.c:18
static Con * find_drop_target(uint32_t x, uint32_t y)
Definition: tiling_drag.c:73
bool workspace_is_visible(Con *ws)
Returns true if the workspace is currently visible.
Definition: workspace.c:314
struct all_cons_head all_cons
Definition: tree.c:15
uint32_t height
Definition: data.h:211
void con_focus(Con *con)
Sets input focus to the given container.
Definition: con.c:246
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:175
direction_t * direction
Definition: tiling_drag.c:104
enum Con::@18 type
int logical_px(const int logical)
Convert a logical amount of pixels (e.g.
#define GREP_FIRST(dest, head, condition)
Definition: util.h:38
Definition: data.h:56
bool con_move_to_target(Con *con, Con *target)
Definition: con.c:1395
drop_type_t * drop_type
Definition: tiling_drag.c:105
int max(int a, int b)
Definition: util.c:28
orientation_t
Definition: data.h:60
Output * get_output_containing(unsigned int x, unsigned int y)
Returns the active (!) output which contains the coordinates x, y or NULL if there is no output which...
Definition: randr.c:121
orientation_t orientation
Definition: resize.c:20
struct Config::config_client client[QUBE_NUM_LABELS]
orientation_t orientation_from_direction(direction_t direction)
Convert a direction to its corresponding orientation.
Definition: util.c:456
static bool is_tiling_drop_target(Con *con)
Definition: tiling_drag.c:13
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:347
Con * con_get_workspace(Con *con)
Gets the workspace container this node is on.
Definition: con.c:477
int con_num_children(Con *con)
Returns the number of children of this container.
Definition: con.c:983
void tree_split(Con *con, orientation_t orientation)
Splits (horizontally or vertically) the given container by creating a new container which contains th...
Definition: tree.c:327
bool con_is_floating(Con *con)
Returns true if the node is floating.
Definition: con.c:596
void tree_move(Con *con, direction_t direction)
Moves the given container in the given direction.
Definition: move.c:258
bool rect_contains(Rect rect, uint32_t x, uint32_t y)
Definition: util.c:32
orientation_t con_orientation(Con *con)
Returns the orientation of the given container (for stacked containers, vertical orientation is used ...
Definition: con.c:1517
fullscreen_mode_t fullscreen_mode
Definition: data.h:762
int min(int a, int b)
Definition: util.c:24
Definition: data.h:108
Definition: data.h:657
bool con_has_managed_window(Con *con)
Returns true when this con is a leaf node with a managed X11 window (e.g., excluding dock containers)...
Definition: con.c:369
void insert_con_into(Con *con, Con *target, position_t position)
This function detaches &#39;con&#39; from its parent and inserts it either before or after &#39;target&#39;...
Definition: move.c:65
void x_mask_event_mask(uint32_t mask)
Applies the given mask to the event mask of every i3 window decoration X11 window.
Definition: x.c:1505
bool con_is_internal(Con *con)
Returns true if the container is internal, such as __i3_scratch.
Definition: con.c:588
struct Con * croot
Definition: tree.c:12
void workspace_show(Con *workspace)
Switches to the given workspace.
Definition: workspace.c:428
color_t indicator
Definition: configuration.h:58
Definition: data.h:57
Definition: data.h:59
struct Rect rect
Definition: data.h:710
xcb_connection_t * conn
XCB connection and root screen.
Definition: main.c:54
void con_disable_fullscreen(Con *con)
Disables fullscreen mode for the given container, if necessary.
Definition: con.c:1195
const char * position_to_string(position_t position)
Converts position to a string representation.
Definition: util.c:502
uint32_t x
Definition: data.h:208
void tree_render(void)
Renders the tree, that is rendering all outputs using render_con() and pushing the changes to X11 usi...
Definition: tree.c:451
xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t depth, xcb_visualid_t visual, uint16_t window_class, enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values)
Convenience wrapper around xcb_create_window which takes care of depth, generating an ID and checking...
Definition: xcb.c:19
static xcb_window_t create_drop_indicator(Rect rect)
Definition: tiling_drag.c:271
direction_t
Definition: data.h:56
#define TAILQ_LAST(head, headname)
Definition: queue.h:339
Con * con_descend_direction(Con *con, direction_t direction)
Returns the leftmost, rightmost, etc.
Definition: con.c:1631
Definition: data.h:63
xcb_window_t * indicator
Definition: tiling_drag.c:102
void con_enable_fullscreen(Con *con, fullscreen_mode_t fullscreen_mode)
Enables fullscreen mode for the given container, if necessary.
Definition: con.c:1149
static bool con_on_side_of_parent(Con *con, direction_t direction)
Definition: tiling_drag.c:128
position_t
Definition: data.h:63
layout_t layout
Definition: data.h:783
uint32_t width
Definition: data.h:210
static Rect adjust_rect(Rect rect, direction_t direction, uint32_t threshold)
Definition: tiling_drag.c:108
drop_type_t
Definition: tiling_drag.c:96
A &#39;Con&#39; represents everything from the X11 root window down to a single X11 window.
Definition: data.h:671
#define DLOG(fmt,...)
Definition: libi3.h:105
Definition: data.h:62
Con * con_descend_tiling_focused(Con *con)
Returns the focused con inside this client, descending the tree as far as possible.
Definition: con.c:1605
void con_activate(Con *con)
Sets input focus to the given container and raises it to the top.
Definition: con.c:287
struct Con * focused
Definition: tree.c:13
Config config
Definition: config.c:19
Con * con
Pointer to the Con which represents this output.
Definition: data.h:433
drag_result_t
This is the return value of a drag operation like drag_pointer.
Definition: drag.h:39
Definition: data.h:111
struct Con * parent
Definition: data.h:706
void ipc_send_window_event(const char *property, Con *con)
For the window events we send, along the usual "change" field, also the window container, in "container".
Definition: ipc.c:1633
Con * con_get_fullscreen_covering_ws(Con *ws)
Returns the fullscreen node that covers the given workspace if it exists.
Definition: con.c:573
uint32_t colorpixel
Definition: libi3.h:433
position_t position_from_direction(direction_t direction)
Convert a direction to its corresponding position.
Definition: util.c:464
bool con_is_hidden(Con *con)
This will only return true for containers which have some parent with a tabbed / stacked parent of wh...
Definition: con.c:404