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"
11static 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 */
34static Con *find_drop_target(uint32_t x, uint32_t y) {
35 Con *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
62typedef enum { DT_SIBLING,
66
67struct callback_params {
68 xcb_window_t *indicator;
72};
73
74static 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
94static 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 */
124DRAGGING_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) {
169 } else if (d_top == d_min) {
170 direction = D_UP;
171 } else if (d_right == d_min) {
173 } else if (d_bottom == d_min) {
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)) {
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
212create_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 */
237static 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 */
270void 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
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
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
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
Con * con_descend_direction(Con *con, direction_t direction)
Returns the leftmost, rightmost, etc.
Definition: con.c:1628
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
Con * con_get_workspace(Con *con)
Gets the workspace container this node is on.
Definition: con.c:477
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
void con_disable_fullscreen(Con *con)
Disables fullscreen mode for the given container, if necessary.
Definition: con.c:1192
Con * con_get_fullscreen_covering_ws(Con *ws)
Returns the fullscreen node that covers the given workspace if it exists.
Definition: con.c:573
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 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
static Con * find_drop_target(uint32_t x, uint32_t y)
Definition: tiling_drag.c:34
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
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
const char * direction_to_string(direction_t direction)
Converts direction to a string representation.
Definition: util.c:484
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:1472
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:601
@ CF_NONE
Definition: data.h:600
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
struct Colortriple focused
Stores a rectangle, for example the size of a window, the child window etc.
Definition: data.h:158
uint32_t height
Definition: data.h:162
uint32_t x
Definition: data.h:159
uint32_t y
Definition: data.h:160
uint32_t width
Definition: data.h:161
An Output is a physical output on your graphics driver.
Definition: data.h:362
Con * con
Pointer to the Con which represents this output.
Definition: data.h:382
A 'Con' represents everything from the X11 root window down to a single X11 window.
Definition: data.h:614
struct Con * parent
Definition: data.h:646
struct Rect deco_rect
Definition: data.h:656
enum Con::@18 type
struct Rect rect
Definition: data.h:650
layout_t layout
Definition: data.h:723
fullscreen_mode_t fullscreen_mode
Definition: data.h:702
uint32_t colorpixel
Definition: libi3.h:433