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