i3
tiling_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 * tiling_drag.c: Reposition tiled windows by dragging.
8 *
9 */
10#include "all.h"
11static xcb_window_t create_drop_indicator(Rect rect);
12
13static 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 */
42bool 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 */
73static 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) ||
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
100
101struct callback_params {
102 xcb_window_t *indicator;
106};
107
108static 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
128static 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 */
158DRAGGING_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) {
203 } else if (d_top == d_min) {
204 direction = D_UP;
205 } else if (d_right == d_min) {
207 } else if (d_bottom == d_min) {
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)) {
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
246create_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 */
271static 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 */
304void 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
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);
341 const uint32_t mod = (config.swap_modifier & 0xFFFF);
342 const bool swap_pressed = (mod != 0 && (event->state & mod) == mod);
343 if (swap_pressed) {
344 if (!con_swap(con, target)) {
345 return;
346 }
347 } else {
349 }
350 break;
351 case DT_SIBLING:
352 DLOG("drop %s %p\n", position_to_string(position), target);
354 /* If con and target are the only children of the same parent, we can just change
355 * the parent's layout manually and then move con to the correct position.
356 * tree_split checks for a parent with only one child so it would create a new
357 * parent with the new layout. */
358 if (con->parent == target->parent && con_num_children(target->parent) == 2) {
359 target->parent->layout = layout;
360 } else {
362 }
363 }
364
365 insert_con_into(con, target, position);
366
367 ipc_send_window_event("move", con);
368 break;
369 case DT_PARENT: {
370 const bool parent_tabbed_or_stacked = (target->parent->layout == L_TABBED || target->parent->layout == L_STACKED);
371 DLOG("drop %s (%s) of %s%p\n",
373 position_to_string(position),
374 parent_tabbed_or_stacked ? "tabbed/stacked " : "",
375 target);
376 if (parent_tabbed_or_stacked) {
377 /* When dealing with tabbed/stacked the target can be in the
378 * middle of the container. Thus, after a directional move, con
379 * will still be bound to the tabbed/stacked parent. */
380 if (position == BEFORE) {
381 target = TAILQ_FIRST(&(target->parent->nodes_head));
382 } else {
383 target = TAILQ_LAST(&(target->parent->nodes_head), nodes_head);
384 }
385 }
386 if (con != target) {
387 insert_con_into(con, target, position);
388 }
389 /* tree_move can change the focus */
390 Con *old_focus = focused;
391 tree_move(con, direction);
392 if (focused != old_focus) {
393 con_activate(old_focus);
394 }
395 break;
396 }
397 }
398 /* Warning: target might not exist anymore */
399 target = NULL;
400
401 /* Manage fullscreen status. */
402 if (set_focus || set_fs) {
404 if (fs == con) {
405 ELOG("dragged container somehow got fullscreen again.\n");
406 assert(false);
407 } else if (fs && set_focus && set_fs) {
408 /* con was focused & fullscreen, disable other fullscreen container. */
410 } else if (fs) {
411 /* con was not focused, prefer other fullscreen container. */
412 set_fs = set_focus = false;
413 } else if (!set_focus) {
414 /* con was not focused. If it was fullscreen and we are moving it to the focused
415 * workspace we must focus it. */
416 set_focus = (set_fs && con_get_workspace(focused) == con_get_workspace(con));
417 }
418 }
419 if (set_fs) {
421 }
422 if (set_focus) {
424 con_focus(con);
425 }
426 tree_render();
427}
#define y(x,...)
Definition commands.c:18
bool con_move_to_target(Con *con, Con *target)
Definition con.c:1503
bool con_is_floating(Con *con)
Returns true if the node is floating.
Definition con.c:670
orientation_t con_orientation(Con *con)
Returns the orientation of the given container (for stacked containers, vertical orientation is used ...
Definition con.c:1625
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:374
Con * con_descend_direction(Con *con, direction_t direction)
Returns the leftmost, rightmost, etc.
Definition con.c:1742
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:410
Con * con_get_workspace(Con *con)
Gets the workspace container this node is on.
Definition con.c:547
Con * con_descend_tiling_focused(Con *con)
Returns the focused con inside this client, descending the tree as far as possible.
Definition con.c:1714
void con_disable_fullscreen(Con *con)
Disables fullscreen mode for the given container, if necessary.
Definition con.c:1299
Con * con_get_fullscreen_covering_ws(Con *ws)
Returns the fullscreen node that covers the given workspace if it exists.
Definition con.c:647
bool con_swap(Con *first, Con *second)
Swaps the two containers.
Definition con.c:2587
bool con_is_internal(Con *con)
Returns true if the container is internal, such as __i3_scratch.
Definition con.c:662
bool con_exists(Con *con)
Returns true if the given container (still) exists.
Definition con.c:783
int con_num_children(Con *con)
Returns the number of children of this container.
Definition con.c:1075
void con_activate(Con *con)
Sets input focus to the given container and raises it to the top.
Definition con.c:292
void con_enable_fullscreen(Con *con, fullscreen_mode_t fullscreen_mode)
Enables fullscreen mode for the given container, if necessary.
Definition con.c:1248
void con_focus(Con *con)
Sets input focus to the given container.
Definition con.c:250
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:176
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:259
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:122
int render_deco_height(void)
Returns the height for the decorations.
Definition render.c:27
static bool con_on_side_of_parent(Con *con, direction_t direction)
static bool is_tiling_drop_target(Con *con)
Definition tiling_drag.c:13
static xcb_window_t create_drop_indicator(Rect rect)
void tiling_drag(Con *con, xcb_button_press_event_t *event, bool use_threshold)
Initiates a mouse drag operation on a tiled window.
static Rect adjust_rect(Rect rect, direction_t direction, uint32_t threshold)
drop_type_t
Definition tiling_drag.c:96
@ DT_PARENT
Definition tiling_drag.c:98
@ DT_SIBLING
Definition tiling_drag.c:96
@ DT_CENTER
Definition tiling_drag.c:97
static Con * find_drop_target(uint32_t x, uint32_t y)
Definition tiling_drag.c:73
bool has_drop_targets(void)
Returns whether there currently are any drop targets.
Definition tiling_drag.c:42
struct Con * focused
Definition tree.c:13
struct Con * croot
Definition tree.c:12
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:455
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:330
orientation_t orientation_from_direction(direction_t direction)
Convert a direction to its corresponding orientation.
Definition util.c:466
position_t position_from_direction(direction_t direction)
Convert a direction to its corresponding position.
Definition util.c:474
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:512
const char * direction_to_string(direction_t direction)
Converts direction to a string representation.
Definition util.c:494
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:438
bool workspace_is_visible(Con *ws)
Returns true if the workspace is currently visible.
Definition workspace.c:320
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:1557
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
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:1650
xcb_connection_t * conn
XCB connection and root screen.
Definition main.c:54
position_t
Definition data.h:63
@ BEFORE
Definition data.h:63
layout_t
Container layouts.
Definition data.h:101
@ L_STACKED
Definition data.h:103
@ L_TABBED
Definition data.h:104
@ L_SPLITH
Definition data.h:108
@ L_SPLITV
Definition data.h:107
orientation_t
Definition data.h:60
@ VERT
Definition data.h:62
@ CF_OUTPUT
Definition data.h:630
@ CF_NONE
Definition data.h:629
direction_t
Definition data.h:56
@ D_RIGHT
Definition data.h:57
@ D_LEFT
Definition data.h:56
@ D_UP
Definition data.h:58
@ D_DOWN
Definition data.h:59
#define DRAGGING_CB(name)
Macro to create a callback function for dragging.
Definition drag.h:19
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
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
#define GREP_FIRST(dest, head, condition)
Definition util.h:38
@ XCURSOR_CURSOR_MOVE
Definition xcursor.h:25
orientation_t orientation
Definition resize.c:20
drop_type_t * drop_type
direction_t * direction
xcb_window_t * indicator
color_t indicator
uint32_t swap_modifier
The modifier which needs to be pressed in combination with the floating modifier and your mouse butto...
struct Config::config_client client
struct Colortriple focused
Stores a rectangle, for example the size of a window, the child window etc.
Definition data.h:185
uint32_t height
Definition data.h:189
uint32_t x
Definition data.h:186
uint32_t y
Definition data.h:187
uint32_t width
Definition data.h:188
An Output is a physical output on your graphics driver.
Definition data.h:391
Con * con
Pointer to the Con which represents this output.
Definition data.h:411
A 'Con' represents everything from the X11 root window down to a single X11 window.
Definition data.h:643
struct Con * parent
Definition data.h:678
enum Con::@18 type
struct Rect rect
Definition data.h:682
layout_t layout
Definition data.h:755
fullscreen_mode_t fullscreen_mode
Definition data.h:734
uint32_t colorpixel
Definition libi3.h:433