Allow dragging tiled windows with the mouse. (#3085)
Fixes #2643 Inner drop region behaves like move to mark. The outer region is close to the edge (currently 30px from the edge). This will place the container as a sibling in the given direction within the parent container. If the move direction goes against the orientation of the parent container, tree_move() is called. Contributors: Co-authored-by: Orestis Floros <orestisflo@gmail.com> See #3085 - Inner drop region behaves like move to mark - Handle workspaces - Fix crash when target closes - Initiate tiling drag from titlebar - Hide indicator until container is dragged outside of original position - Calculate outer_threshold using percentages instead of fixed pixel values - Emit 'move' event properly - Don't focus previously unfocused containers - Use tree_split() on different orientation - Fix redundant split containers - DT_PARENT - Readability & optimizations - Limit parent threshold by render_deco_height() - Tests - Fullscreen container handling - Initiate drag from title bar - Fix issue of EnterNotify events still triggering after drag_callback is called - Include decorations for drop target calculation Co-authored-by: Michael Forster <email@michael-forster.de> See #2178 - Original implementation of tiling drag + indicator window > A container can be dragged by the title bar to one of the four sides > of another container. That container will then be split either > horizontally or vertically. Co-authored-by: Tony Crisci <tony@dubstepdish.com> See #2653 - Original implementation of outer/inner drop region indicator: > There are two drop regions per direction. > > The inner region is closer to the center of the window. Dropping on > this region will split the target container and put the container > within the split at the given direction beside the target container. > > The outer region is close to the edge (currently 30px from the edge). > This will place the container as a sibling in the given direction within > the parent container. > > Dropping into the outer region moves the con beside the target. If the > move direction goes against the orientation of the parent container, the > con moves out of the row. - Fix crash: Ignore containers without a managed window (eg i3bar)
This commit is contained in:
parent
807e972330
commit
ebcd1d43ea
@ -196,6 +196,48 @@ provided by the i3 https://github.com/i3/i3/blob/next/etc/config.keycodes[defaul
|
|||||||
|
|
||||||
Floating windows are always on top of tiling windows.
|
Floating windows are always on top of tiling windows.
|
||||||
|
|
||||||
|
=== Moving tiling containers with the mouse
|
||||||
|
|
||||||
|
Since i3 4.21, it's possible to drag tiling containers using the mouse. The
|
||||||
|
drag can be initiated either by dragging the window's titlebar or by pressing
|
||||||
|
the <<floating_modifier>> and dragging the container while holding the
|
||||||
|
left-click button.
|
||||||
|
|
||||||
|
Once the drag is initiated and the cursor has left the original container, drop
|
||||||
|
indicators are created according to the position of the cursor relatively to
|
||||||
|
the target container. These indicators help you understand what the resulting
|
||||||
|
<<tree>> layout is going to be after you release the mouse button.
|
||||||
|
|
||||||
|
The possible drop positions are:
|
||||||
|
|
||||||
|
Drop on container::
|
||||||
|
This happens when the mouse is relatively near the center of a container.
|
||||||
|
If the mouse is released, the result is exactly as if you had run the
|
||||||
|
+move container to mark+ command. See <<move_to_mark>>.
|
||||||
|
Drop as sibling::
|
||||||
|
This happens when the mouse is relatively near the edge of a container. If
|
||||||
|
the mouse is released, the dragged container will become a sibling of the
|
||||||
|
target container, placed left/right/up/down according to the position of
|
||||||
|
the indicator.
|
||||||
|
This might or might not create a new v-split or h-split according to the
|
||||||
|
previous layout of the target container. For example, if the target
|
||||||
|
container is in an h-split and you drop the dragged container below it, the
|
||||||
|
new layout will have to be a v-split.
|
||||||
|
Drop to parent::
|
||||||
|
This happens when the mouse is relatively near the edge of a container (but
|
||||||
|
even closer to the border in comparison to the sibling case above) *and* if
|
||||||
|
that edge is also the parent container's edge. For example, if three
|
||||||
|
containers are in a horizontal layout then edges where this can happen is
|
||||||
|
the left edge of the left container, the right edge of the right container
|
||||||
|
and all bottom and top edges of all three containers.
|
||||||
|
If the mouse is released, the container is first dropped as a sibling to
|
||||||
|
the target container, like in the case above, and then is moved
|
||||||
|
directionally like with the +move left|right|down|up+ command. See
|
||||||
|
<<move_direction>>.
|
||||||
|
|
||||||
|
The color of the indicator matches the +client.focused+ setting. See <<client_colors>>.
|
||||||
|
|
||||||
|
[[tree]]
|
||||||
== Tree
|
== Tree
|
||||||
|
|
||||||
i3 stores all information about the X11 outputs, workspaces and layout of the
|
i3 stores all information about the X11 outputs, workspaces and layout of the
|
||||||
@ -1041,6 +1083,7 @@ workspace 5 output VGA1 LVDS1
|
|||||||
workspace "2: vim" output VGA1
|
workspace "2: vim" output VGA1
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
|
[[client_colors]]
|
||||||
=== Changing colors
|
=== Changing colors
|
||||||
|
|
||||||
You can change all colors which i3 uses to draw the window decorations.
|
You can change all colors which i3 uses to draw the window decorations.
|
||||||
@ -2253,6 +2296,7 @@ Note that you might not have a primary output configured yet. To do so, run:
|
|||||||
xrandr --output <output> --primary
|
xrandr --output <output> --primary
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
[[move_direction]]
|
||||||
=== Moving containers
|
=== Moving containers
|
||||||
|
|
||||||
Use the +move+ command to move a container.
|
Use the +move+ command to move a container.
|
||||||
@ -2537,6 +2581,7 @@ Note that you might not have a primary output configured yet. To do so, run:
|
|||||||
xrandr --output <output> --primary
|
xrandr --output <output> --primary
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
[[move_to_mark]]
|
||||||
=== Moving containers/windows to marks
|
=== Moving containers/windows to marks
|
||||||
|
|
||||||
To move a container to another container with a specific mark (see <<vim_like_marks>>),
|
To move a container to another container with a specific mark (see <<vim_like_marks>>),
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
#include "match.h"
|
#include "match.h"
|
||||||
#include "xcursor.h"
|
#include "xcursor.h"
|
||||||
#include "resize.h"
|
#include "resize.h"
|
||||||
|
#include "tiling_drag.h"
|
||||||
#include "sighandler.h"
|
#include "sighandler.h"
|
||||||
#include "move.h"
|
#include "move.h"
|
||||||
#include "output.h"
|
#include "output.h"
|
||||||
|
@ -371,6 +371,7 @@ void con_move_to_output(Con *con, Output *output, bool fix_coordinates);
|
|||||||
*/
|
*/
|
||||||
bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates);
|
bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates);
|
||||||
|
|
||||||
|
bool con_move_to_target(Con *con, Con *target);
|
||||||
/**
|
/**
|
||||||
* Moves the given container to the given mark.
|
* Moves the given container to the given mark.
|
||||||
*
|
*
|
||||||
|
16
include/tiling_drag.h
Normal file
16
include/tiling_drag.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* vim:ts=4:sw=4:expandtab
|
||||||
|
*
|
||||||
|
* i3 - an improved dynamic tiling window manager
|
||||||
|
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||||
|
*
|
||||||
|
* tiling_drag.h: Reposition tiled windows by dragging.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiates a mouse drag operation on a tiled window.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void tiling_drag(Con *con, xcb_button_press_event_t *event);
|
@ -183,3 +183,15 @@ position_t position_from_direction(direction_t direction);
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
direction_t direction_from_orientation_position(orientation_t orientation, position_t position);
|
direction_t direction_from_orientation_position(orientation_t orientation, position_t position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts direction to a string representation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const char *direction_to_string(direction_t direction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts position to a string representation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const char *position_to_string(position_t position);
|
||||||
|
@ -409,6 +409,7 @@ i3srcs = [
|
|||||||
'src/sighandler.c',
|
'src/sighandler.c',
|
||||||
'src/startup.c',
|
'src/startup.c',
|
||||||
'src/sync.c',
|
'src/sync.c',
|
||||||
|
'src/tiling_drag.c',
|
||||||
'src/tree.c',
|
'src/tree.c',
|
||||||
'src/util.c',
|
'src/util.c',
|
||||||
'src/version.c',
|
'src/version.c',
|
||||||
|
1
release-notes/changes/3-tiling-drag
Normal file
1
release-notes/changes/3-tiling-drag
Normal file
@ -0,0 +1 @@
|
|||||||
|
Allow moving tiling windows with the mouse
|
32
src/click.c
32
src/click.c
@ -188,9 +188,6 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
|||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ws != focused_workspace)
|
|
||||||
workspace_show(ws);
|
|
||||||
|
|
||||||
/* get the floating con */
|
/* get the floating con */
|
||||||
Con *floatingcon = con_inside_floating(con);
|
Con *floatingcon = con_inside_floating(con);
|
||||||
const bool proportional = (event->state & XCB_KEY_BUT_MASK_SHIFT) == XCB_KEY_BUT_MASK_SHIFT;
|
const bool proportional = (event->state & XCB_KEY_BUT_MASK_SHIFT) == XCB_KEY_BUT_MASK_SHIFT;
|
||||||
@ -218,7 +215,13 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
|||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 2: focus this con or one of its children. */
|
/* 2: floating modifier pressed, initiate a drag */
|
||||||
|
if (mod_pressed && event->detail == XCB_BUTTON_INDEX_1 && !floatingcon) {
|
||||||
|
tiling_drag(con, event);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3: focus this con or one of its children. */
|
||||||
Con *con_to_focus = con;
|
Con *con_to_focus = con;
|
||||||
if (in_stacked && dest == CLICK_DECORATION) {
|
if (in_stacked && dest == CLICK_DECORATION) {
|
||||||
/* If the container is a tab/stacked container and the click happened
|
/* If the container is a tab/stacked container and the click happened
|
||||||
@ -231,19 +234,22 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ws != focused_workspace) {
|
||||||
|
workspace_show(ws);
|
||||||
|
}
|
||||||
con_activate(con_to_focus);
|
con_activate(con_to_focus);
|
||||||
|
|
||||||
/* 3: For floating containers, we also want to raise them on click.
|
/* 4: For floating containers, we also want to raise them on click.
|
||||||
* We will skip handling events on floating cons in fullscreen mode */
|
* We will skip handling events on floating cons in fullscreen mode */
|
||||||
Con *fs = con_get_fullscreen_covering_ws(ws);
|
Con *fs = con_get_fullscreen_covering_ws(ws);
|
||||||
if (floatingcon != NULL && fs != con) {
|
if (floatingcon != NULL && fs != con) {
|
||||||
/* 4: floating_modifier plus left mouse button drags */
|
/* 5: floating_modifier plus left mouse button drags */
|
||||||
if (mod_pressed && is_left_click) {
|
if (mod_pressed && is_left_click) {
|
||||||
floating_drag_window(floatingcon, event, false);
|
floating_drag_window(floatingcon, event, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 5: resize (floating) if this was a (left or right) click on the
|
/* 6: resize (floating) if this was a (left or right) click on the
|
||||||
* left/right/bottom border, or a right click on the decoration.
|
* left/right/bottom border, or a right click on the decoration.
|
||||||
* also try resizing (tiling) if possible */
|
* also try resizing (tiling) if possible */
|
||||||
if (mod_pressed && is_right_click) {
|
if (mod_pressed && is_right_click) {
|
||||||
@ -272,7 +278,7 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 6: dragging, if this was a click on a decoration (which did not lead
|
/* 7: dragging, if this was a click on a decoration (which did not lead
|
||||||
* to a resize) */
|
* to a resize) */
|
||||||
if (dest == CLICK_DECORATION && is_left_click) {
|
if (dest == CLICK_DECORATION && is_left_click) {
|
||||||
floating_drag_window(floatingcon, event, !was_focused);
|
floating_drag_window(floatingcon, event, !was_focused);
|
||||||
@ -282,7 +288,13 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
|||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 7: floating modifier pressed, initiate a resize */
|
/* 8: floating modifier pressed, initiate a drag */
|
||||||
|
if ((mod_pressed || dest == CLICK_DECORATION) && event->detail == XCB_BUTTON_INDEX_1) {
|
||||||
|
tiling_drag(con, event);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 9: floating modifier pressed, initiate a resize */
|
||||||
if (dest == CLICK_INSIDE && mod_pressed && is_right_click) {
|
if (dest == CLICK_INSIDE && mod_pressed && is_right_click) {
|
||||||
if (floating_mod_on_tiled_client(con, event)) {
|
if (floating_mod_on_tiled_client(con, event)) {
|
||||||
return;
|
return;
|
||||||
@ -293,7 +305,7 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
|||||||
xcb_flush(conn);
|
xcb_flush(conn);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* 8: otherwise, check for border/decoration clicks and resize */
|
/* 10: otherwise, check for border/decoration clicks and resize */
|
||||||
if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) &&
|
if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) &&
|
||||||
is_left_or_right_click) {
|
is_left_or_right_click) {
|
||||||
DLOG("Trying to resize (tiling)\n");
|
DLOG("Trying to resize (tiling)\n");
|
||||||
|
26
src/con.c
26
src/con.c
@ -1389,17 +1389,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
bool con_move_to_target(Con *con, Con *target) {
|
||||||
* Moves the given container to the given mark.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
bool con_move_to_mark(Con *con, const char *mark) {
|
|
||||||
Con *target = con_by_mark(mark);
|
|
||||||
if (target == NULL) {
|
|
||||||
DLOG("found no container with mark \"%s\"\n", mark);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* For target containers in the scratchpad, we just send the window to the scratchpad. */
|
/* For target containers in the scratchpad, we just send the window to the scratchpad. */
|
||||||
if (con_get_workspace(target) == workspace_get("__i3_scratch")) {
|
if (con_get_workspace(target) == workspace_get("__i3_scratch")) {
|
||||||
DLOG("target container is in the scratchpad, moving container to scratchpad.\n");
|
DLOG("target container is in the scratchpad, moving container to scratchpad.\n");
|
||||||
@ -1436,6 +1426,20 @@ bool con_move_to_mark(Con *con, const char *mark) {
|
|||||||
return _con_move_to_con(con, target, false, true, false, false, true);
|
return _con_move_to_con(con, target, false, true, false, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Moves the given container to the given mark.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
bool con_move_to_mark(Con *con, const char *mark) {
|
||||||
|
Con *target = con_by_mark(mark);
|
||||||
|
if (target == NULL) {
|
||||||
|
DLOG("found no container with mark \"%s\"\n", mark);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return con_move_to_target(con, target);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Moves the given container to the currently focused container on the given
|
* Moves the given container to the currently focused container on the given
|
||||||
* workspace.
|
* workspace.
|
||||||
|
380
src/tiling_drag.c
Normal file
380
src/tiling_drag.c
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
/*
|
||||||
|
* vim:ts=4:sw=4:expandtab
|
||||||
|
*
|
||||||
|
* i3 - an improved dynamic tiling window manager
|
||||||
|
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||||
|
*
|
||||||
|
* tiling_drag.c: Reposition tiled windows by dragging.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "all.h"
|
||||||
|
static xcb_window_t create_drop_indicator(Rect rect);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Includes decoration (container title) to the container's rect. This way we
|
||||||
|
* can find the correct drop target if the mouse is on a container's
|
||||||
|
* decoration.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static Rect con_rect_plus_deco_height(Con *con) {
|
||||||
|
Rect rect = con->rect;
|
||||||
|
rect.height += con->deco_rect.height;
|
||||||
|
rect.y -= con->deco_rect.height;
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return an appropriate target at given coordinates.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static Con *find_drop_target(uint32_t x, uint32_t y) {
|
||||||
|
Con *con;
|
||||||
|
TAILQ_FOREACH (con, &all_cons, all_cons) {
|
||||||
|
Rect rect = con_rect_plus_deco_height(con);
|
||||||
|
|
||||||
|
if (rect_contains(rect, x, y) &&
|
||||||
|
con_has_managed_window(con) &&
|
||||||
|
!con_is_floating(con) &&
|
||||||
|
!con_is_hidden(con)) {
|
||||||
|
Con *ws = con_get_workspace(con);
|
||||||
|
if (!workspace_is_visible(ws)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Con *fs = con_get_fullscreen_covering_ws(ws);
|
||||||
|
return fs ? fs : con;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Couldn't find leaf container, get a workspace. */
|
||||||
|
Output *output = get_output_containing(x, y);
|
||||||
|
if (!output) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Con *content = output_get_content(output->con);
|
||||||
|
/* Still descend because you can drag to the bar on an non-empty workspace. */
|
||||||
|
return con_descend_tiling_focused(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef enum { DT_SIBLING,
|
||||||
|
DT_CENTER,
|
||||||
|
DT_PARENT
|
||||||
|
} drop_type_t;
|
||||||
|
|
||||||
|
struct callback_params {
|
||||||
|
xcb_window_t *indicator;
|
||||||
|
Con **target;
|
||||||
|
direction_t *direction;
|
||||||
|
drop_type_t *drop_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
static Rect adjust_rect(Rect rect, direction_t direction, uint32_t threshold) {
|
||||||
|
switch (direction) {
|
||||||
|
case D_LEFT:
|
||||||
|
rect.width = threshold;
|
||||||
|
break;
|
||||||
|
case D_UP:
|
||||||
|
rect.height = threshold;
|
||||||
|
break;
|
||||||
|
case D_RIGHT:
|
||||||
|
rect.x += (rect.width - threshold);
|
||||||
|
rect.width = threshold;
|
||||||
|
break;
|
||||||
|
case D_DOWN:
|
||||||
|
rect.y += (rect.height - threshold);
|
||||||
|
rect.height = threshold;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool con_on_side_of_parent(Con *con, direction_t direction) {
|
||||||
|
const orientation_t orientation = orientation_from_direction(direction);
|
||||||
|
direction_t reverse_direction;
|
||||||
|
switch (direction) {
|
||||||
|
case D_LEFT:
|
||||||
|
reverse_direction = D_RIGHT;
|
||||||
|
break;
|
||||||
|
case D_RIGHT:
|
||||||
|
reverse_direction = D_LEFT;
|
||||||
|
break;
|
||||||
|
case D_UP:
|
||||||
|
reverse_direction = D_DOWN;
|
||||||
|
break;
|
||||||
|
case D_DOWN:
|
||||||
|
reverse_direction = D_UP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (con_orientation(con->parent) != orientation ||
|
||||||
|
con->parent->layout == L_STACKED || con->parent->layout == L_TABBED ||
|
||||||
|
con_descend_direction(con->parent, reverse_direction) == con);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The callback that is executed on every mouse move while dragging. On each
|
||||||
|
* invocation we determine the drop target and the direction in which to insert
|
||||||
|
* the dragged container. The indicator window is updated to show the new
|
||||||
|
* position of the dragged container. The target container and direction are
|
||||||
|
* passed out using the callback params.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
DRAGGING_CB(drag_callback) {
|
||||||
|
/* 30% of the container (minus the parent indicator) is used to drop the
|
||||||
|
* dragged container as a sibling to the target */
|
||||||
|
const double sibling_indicator_percent_of_rect = 0.3;
|
||||||
|
/* Use the base decoration height and add a few pixels. This makes the
|
||||||
|
* outer indicator generally thin but at least thick enough to cover
|
||||||
|
* container titles */
|
||||||
|
const double parent_indicator_max_size = render_deco_height() + logical_px(5);
|
||||||
|
|
||||||
|
Con *target = find_drop_target(new_x, new_y);
|
||||||
|
if (target == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect rect = con_rect_plus_deco_height(target);
|
||||||
|
|
||||||
|
direction_t direction = 0;
|
||||||
|
drop_type_t drop_type = DT_CENTER;
|
||||||
|
bool draw_window = true;
|
||||||
|
const struct callback_params *params = extra;
|
||||||
|
|
||||||
|
if (target->type == CT_WORKSPACE) {
|
||||||
|
goto create_indicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Define the thresholds in pixels. The drop type depends on the cursor
|
||||||
|
* position. */
|
||||||
|
const uint32_t min_rect_dimension = min(rect.width, rect.height);
|
||||||
|
const uint32_t sibling_indicator_size = max(logical_px(2), (uint32_t)(sibling_indicator_percent_of_rect * min_rect_dimension));
|
||||||
|
const uint32_t parent_indicator_size = min(
|
||||||
|
parent_indicator_max_size,
|
||||||
|
/* For small containers, start where the sibling indicator finishes.
|
||||||
|
* This is always at least 1 pixel. We use min() to not override the
|
||||||
|
* sibling indicator: */
|
||||||
|
sibling_indicator_size - 1);
|
||||||
|
|
||||||
|
/* Find which edge the cursor is closer to. */
|
||||||
|
const uint32_t d_left = new_x - rect.x;
|
||||||
|
const uint32_t d_top = new_y - rect.y;
|
||||||
|
const uint32_t d_right = rect.x + rect.width - new_x;
|
||||||
|
const uint32_t d_bottom = rect.y + rect.height - new_y;
|
||||||
|
const uint32_t d_min = min(min(d_left, d_right), min(d_top, d_bottom));
|
||||||
|
/* And move the container towards that direction. */
|
||||||
|
if (d_left == d_min) {
|
||||||
|
direction = D_LEFT;
|
||||||
|
} else if (d_top == d_min) {
|
||||||
|
direction = D_UP;
|
||||||
|
} else if (d_right == d_min) {
|
||||||
|
direction = D_RIGHT;
|
||||||
|
} else if (d_bottom == d_min) {
|
||||||
|
direction = D_DOWN;
|
||||||
|
} else {
|
||||||
|
/* Keep the compiler happy */
|
||||||
|
ELOG("min() is broken\n");
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
const bool target_parent = (d_min < parent_indicator_size &&
|
||||||
|
con_on_side_of_parent(target, direction));
|
||||||
|
const bool target_sibling = (d_min < sibling_indicator_size);
|
||||||
|
drop_type = target_parent ? DT_PARENT : (target_sibling ? DT_SIBLING : DT_CENTER);
|
||||||
|
|
||||||
|
/* target == con makes sense only when we are moving away from target's parent. */
|
||||||
|
if (drop_type != DT_PARENT && target == con) {
|
||||||
|
draw_window = false;
|
||||||
|
xcb_destroy_window(conn, *(params->indicator));
|
||||||
|
*(params->indicator) = 0;
|
||||||
|
goto create_indicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (drop_type) {
|
||||||
|
case DT_PARENT:
|
||||||
|
while (target->parent->type != CT_WORKSPACE && con_on_side_of_parent(target->parent, direction)) {
|
||||||
|
target = target->parent;
|
||||||
|
}
|
||||||
|
rect = adjust_rect(target->parent->rect, direction, parent_indicator_size);
|
||||||
|
break;
|
||||||
|
case DT_CENTER:
|
||||||
|
rect = target->rect;
|
||||||
|
rect.x += sibling_indicator_size;
|
||||||
|
rect.y += sibling_indicator_size;
|
||||||
|
rect.width -= sibling_indicator_size * 2;
|
||||||
|
rect.height -= sibling_indicator_size * 2;
|
||||||
|
break;
|
||||||
|
case DT_SIBLING:
|
||||||
|
rect = adjust_rect(target->rect, direction, sibling_indicator_size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
create_indicator:
|
||||||
|
if (draw_window) {
|
||||||
|
if (*(params->indicator) == 0) {
|
||||||
|
*(params->indicator) = create_drop_indicator(rect);
|
||||||
|
} else {
|
||||||
|
const uint32_t values[4] = {rect.x, rect.y, rect.width, rect.height};
|
||||||
|
const uint32_t mask = XCB_CONFIG_WINDOW_X |
|
||||||
|
XCB_CONFIG_WINDOW_Y |
|
||||||
|
XCB_CONFIG_WINDOW_WIDTH |
|
||||||
|
XCB_CONFIG_WINDOW_HEIGHT;
|
||||||
|
xcb_configure_window(conn, *(params->indicator), mask, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x_mask_event_mask(~XCB_EVENT_MASK_ENTER_WINDOW);
|
||||||
|
xcb_flush(conn);
|
||||||
|
|
||||||
|
*(params->target) = target;
|
||||||
|
*(params->direction) = direction;
|
||||||
|
*(params->drop_type) = drop_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a new drop indicator window with the given initial coordinates.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static xcb_window_t create_drop_indicator(Rect rect) {
|
||||||
|
uint32_t mask = 0;
|
||||||
|
uint32_t values[2];
|
||||||
|
|
||||||
|
mask = XCB_CW_BACK_PIXEL;
|
||||||
|
values[0] = config.client.focused.indicator.colorpixel;
|
||||||
|
|
||||||
|
mask |= XCB_CW_OVERRIDE_REDIRECT;
|
||||||
|
values[1] = 1;
|
||||||
|
|
||||||
|
xcb_window_t indicator = create_window(conn, rect, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
|
||||||
|
XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_MOVE, false, mask, values);
|
||||||
|
/* Change the window class to "i3-drag", so that it can be matched in a
|
||||||
|
* compositor configuration. Note that the class needs to be changed before
|
||||||
|
* mapping the window. */
|
||||||
|
xcb_change_property(conn,
|
||||||
|
XCB_PROP_MODE_REPLACE,
|
||||||
|
indicator,
|
||||||
|
XCB_ATOM_WM_CLASS,
|
||||||
|
XCB_ATOM_STRING,
|
||||||
|
8,
|
||||||
|
(strlen("i3-drag") + 1) * 2,
|
||||||
|
"i3-drag\0i3-drag\0");
|
||||||
|
xcb_map_window(conn, indicator);
|
||||||
|
xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, indicator);
|
||||||
|
|
||||||
|
return indicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initiates a mouse drag operation on a tiled window.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void tiling_drag(Con *con, xcb_button_press_event_t *event) {
|
||||||
|
DLOG("Start dragging tiled container: con = %p\n", con);
|
||||||
|
bool set_focus = (con == focused);
|
||||||
|
bool set_fs = con->fullscreen_mode != CF_NONE;
|
||||||
|
|
||||||
|
/* Don't change focus while dragging. */
|
||||||
|
x_mask_event_mask(~XCB_EVENT_MASK_ENTER_WINDOW);
|
||||||
|
xcb_flush(conn);
|
||||||
|
|
||||||
|
/* Indicate drop location while dragging. This blocks until the drag is completed. */
|
||||||
|
Con *target = NULL;
|
||||||
|
direction_t direction;
|
||||||
|
drop_type_t drop_type;
|
||||||
|
xcb_window_t indicator = 0;
|
||||||
|
const struct callback_params params = {&indicator, &target, &direction, &drop_type};
|
||||||
|
|
||||||
|
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP, XCURSOR_CURSOR_MOVE, drag_callback, ¶ms);
|
||||||
|
|
||||||
|
/* Dragging is done. We don't need the indicator window any more. */
|
||||||
|
xcb_destroy_window(conn, indicator);
|
||||||
|
|
||||||
|
if (drag_result == DRAG_REVERT ||
|
||||||
|
target == NULL ||
|
||||||
|
(target == con && drop_type != DT_PARENT) ||
|
||||||
|
!con_exists(target)) {
|
||||||
|
DLOG("drop aborted\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const orientation_t orientation = orientation_from_direction(direction);
|
||||||
|
const position_t position = position_from_direction(direction);
|
||||||
|
const layout_t layout = orientation == VERT ? L_SPLITV : L_SPLITH;
|
||||||
|
con_disable_fullscreen(con);
|
||||||
|
switch (drop_type) {
|
||||||
|
case DT_CENTER:
|
||||||
|
/* Also handles workspaces.*/
|
||||||
|
DLOG("drop to center of %p\n", target);
|
||||||
|
con_move_to_target(con, target);
|
||||||
|
break;
|
||||||
|
case DT_SIBLING:
|
||||||
|
DLOG("drop %s %p\n", position_to_string(position), target);
|
||||||
|
if (con_orientation(target->parent) != orientation) {
|
||||||
|
/* If con and target are the only children of the same parent, we can just change
|
||||||
|
* the parent's layout manually and then move con to the correct position.
|
||||||
|
* tree_split checks for a parent with only one child so it would create a new
|
||||||
|
* parent with the new layout. */
|
||||||
|
if (con->parent == target->parent && con_num_children(target->parent) == 2) {
|
||||||
|
target->parent->layout = layout;
|
||||||
|
} else {
|
||||||
|
tree_split(target, orientation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_con_into(con, target, position);
|
||||||
|
|
||||||
|
ipc_send_window_event("move", con);
|
||||||
|
break;
|
||||||
|
case DT_PARENT: {
|
||||||
|
const bool parent_tabbed_or_stacked = (target->parent->layout == L_TABBED || target->parent->layout == L_STACKED);
|
||||||
|
DLOG("drop %s (%s) of %s%p\n",
|
||||||
|
direction_to_string(direction),
|
||||||
|
position_to_string(position),
|
||||||
|
parent_tabbed_or_stacked ? "tabbed/stacked " : "",
|
||||||
|
target);
|
||||||
|
if (parent_tabbed_or_stacked) {
|
||||||
|
/* When dealing with tabbed/stacked the target can be in the
|
||||||
|
* middle of the container. Thus, after a directional move, con
|
||||||
|
* will still be bound to the tabbed/stacked parent. */
|
||||||
|
if (position == BEFORE) {
|
||||||
|
target = TAILQ_FIRST(&(target->parent->nodes_head));
|
||||||
|
} else {
|
||||||
|
target = TAILQ_LAST(&(target->parent->nodes_head), nodes_head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (con != target) {
|
||||||
|
insert_con_into(con, target, position);
|
||||||
|
}
|
||||||
|
/* tree_move can change the focus */
|
||||||
|
Con *old_focus = focused;
|
||||||
|
tree_move(con, direction);
|
||||||
|
if (focused != old_focus) {
|
||||||
|
con_activate(old_focus);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Warning: target might not exist anymore */
|
||||||
|
target = NULL;
|
||||||
|
|
||||||
|
/* Manage fullscreen status. */
|
||||||
|
if (set_focus || set_fs) {
|
||||||
|
Con *fs = con_get_fullscreen_covering_ws(con_get_workspace(con));
|
||||||
|
if (fs == con) {
|
||||||
|
ELOG("dragged container somehow got fullscreen again.\n");
|
||||||
|
assert(false);
|
||||||
|
} else if (fs && set_focus && set_fs) {
|
||||||
|
/* con was focused & fullscreen, disable other fullscreen container. */
|
||||||
|
con_disable_fullscreen(fs);
|
||||||
|
} else if (fs) {
|
||||||
|
/* con was not focused, prefer other fullscreen container. */
|
||||||
|
set_fs = set_focus = false;
|
||||||
|
} else if (!set_focus) {
|
||||||
|
/* con was not focused. If it was fullscreen and we are moving it to the focused
|
||||||
|
* workspace we must focus it. */
|
||||||
|
set_focus = (set_fs && con_get_workspace(focused) == con_get_workspace(con));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (set_fs) {
|
||||||
|
con_enable_fullscreen(con, CF_OUTPUT);
|
||||||
|
}
|
||||||
|
if (set_focus) {
|
||||||
|
con_focus(con);
|
||||||
|
}
|
||||||
|
tree_render();
|
||||||
|
}
|
32
src/util.c
32
src/util.c
@ -476,3 +476,35 @@ direction_t direction_from_orientation_position(orientation_t orientation, posit
|
|||||||
return position == BEFORE ? D_UP : D_DOWN;
|
return position == BEFORE ? D_UP : D_DOWN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Converts direction to a string representation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const char *direction_to_string(direction_t direction) {
|
||||||
|
switch (direction) {
|
||||||
|
case D_LEFT:
|
||||||
|
return "left";
|
||||||
|
case D_RIGHT:
|
||||||
|
return "right";
|
||||||
|
case D_UP:
|
||||||
|
return "up";
|
||||||
|
case D_DOWN:
|
||||||
|
return "down";
|
||||||
|
}
|
||||||
|
return "invalid";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Converts position to a string representation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const char *position_to_string(position_t position) {
|
||||||
|
switch (position) {
|
||||||
|
case BEFORE:
|
||||||
|
return "before";
|
||||||
|
case AFTER:
|
||||||
|
return "after";
|
||||||
|
}
|
||||||
|
return "invalid";
|
||||||
|
}
|
||||||
|
@ -305,10 +305,10 @@ Con *create_workspace_on_output(Output *output, Con *content) {
|
|||||||
*/
|
*/
|
||||||
bool workspace_is_visible(Con *ws) {
|
bool workspace_is_visible(Con *ws) {
|
||||||
Con *output = con_get_output(ws);
|
Con *output = con_get_output(ws);
|
||||||
if (output == NULL)
|
if (output == NULL) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
Con *fs = con_get_fullscreen_con(output, CF_OUTPUT);
|
Con *fs = con_get_fullscreen_con(output, CF_OUTPUT);
|
||||||
LOG("workspace visible? fs = %p, ws = %p\n", fs, ws);
|
|
||||||
return (fs == ws);
|
return (fs == ws);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
321
testcases/t/316-drag-container.t
Normal file
321
testcases/t/316-drag-container.t
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
#!perl
|
||||||
|
# vim:ts=4:sw=4:expandtab
|
||||||
|
#
|
||||||
|
# Please read the following documents before working on tests:
|
||||||
|
# • https://build.i3wm.org/docs/testsuite.html
|
||||||
|
# (or docs/testsuite)
|
||||||
|
#
|
||||||
|
# • https://build.i3wm.org/docs/lib-i3test.html
|
||||||
|
# (alternatively: perldoc ./testcases/lib/i3test.pm)
|
||||||
|
#
|
||||||
|
# • https://build.i3wm.org/docs/ipc.html
|
||||||
|
# (or docs/ipc)
|
||||||
|
#
|
||||||
|
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||||
|
# (unless you are already familiar with Perl)
|
||||||
|
#
|
||||||
|
# Test dragging containers.
|
||||||
|
|
||||||
|
my ($width, $height) = (1000, 500);
|
||||||
|
|
||||||
|
my $config = <<"EOT";
|
||||||
|
# i3 config file (v4)
|
||||||
|
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||||
|
|
||||||
|
focus_follows_mouse no
|
||||||
|
floating_modifier Mod1
|
||||||
|
|
||||||
|
# 2 side by side outputs
|
||||||
|
fake-outputs ${width}x${height}+0+0P,${width}x${height}+${width}+0
|
||||||
|
|
||||||
|
bar {
|
||||||
|
output primary
|
||||||
|
}
|
||||||
|
EOT
|
||||||
|
use i3test i3_autostart => 0;
|
||||||
|
use i3test::XTEST;
|
||||||
|
my $pid = launch_with_config($config);
|
||||||
|
|
||||||
|
sub start_drag {
|
||||||
|
my ($pos_x, $pos_y) = @_;
|
||||||
|
die "Drag outside of bounds!" unless $pos_x < $width * 2 && $pos_y < $height;
|
||||||
|
|
||||||
|
$x->root->warp_pointer($pos_x, $pos_y);
|
||||||
|
sync_with_i3;
|
||||||
|
|
||||||
|
xtest_key_press(64); # Alt_L
|
||||||
|
xtest_button_press(1, $pos_x, $pos_y);
|
||||||
|
xtest_sync_with_i3;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub end_drag {
|
||||||
|
my ($pos_x, $pos_y) = @_;
|
||||||
|
die "Drag outside of bounds!" unless $pos_x < $width * 2 && $pos_y < $height;
|
||||||
|
|
||||||
|
$x->root->warp_pointer($pos_x, $pos_y);
|
||||||
|
sync_with_i3;
|
||||||
|
|
||||||
|
xtest_button_release(1, $pos_x, $pos_y);
|
||||||
|
xtest_key_release(64); # Alt_L
|
||||||
|
xtest_sync_with_i3;
|
||||||
|
}
|
||||||
|
|
||||||
|
my ($ws1, $ws2);
|
||||||
|
my ($A, $B, $tmp);
|
||||||
|
my ($A_id, $B_id);
|
||||||
|
|
||||||
|
sub move_subtest {
|
||||||
|
my ($cb, $win) = @_;
|
||||||
|
|
||||||
|
my @events = events_for($cb, 'window');
|
||||||
|
my @move = grep { $_->{change} eq 'move' } @events;
|
||||||
|
|
||||||
|
is(scalar @move, 1, 'Received 1 window::move event');
|
||||||
|
is($move[0]->{container}->{window}, $A->{id}, "window id matches");
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Drag floating container onto an empty workspace.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$ws2 = fresh_workspace(output => 1);
|
||||||
|
$ws1 = fresh_workspace(output => 0);
|
||||||
|
$A = open_floating_window(rect => [ 30, 30, 50, 50 ]);
|
||||||
|
|
||||||
|
start_drag(40, 40);
|
||||||
|
end_drag(1050, 50);
|
||||||
|
|
||||||
|
is($x->input_focus, $A->id, 'Floating window moved to the right workspace');
|
||||||
|
is($ws2, focused_ws, 'Empty workspace focused after floating window dragged to it');
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Drag tiling container onto an empty workspace.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
subtest "Draging tiling container onto an empty workspace produces move event", \&move_subtest,
|
||||||
|
sub {
|
||||||
|
|
||||||
|
$ws2 = fresh_workspace(output => 1);
|
||||||
|
$ws1 = fresh_workspace(output => 0);
|
||||||
|
$A = open_window;
|
||||||
|
|
||||||
|
start_drag(50, 50);
|
||||||
|
end_drag(1050, 50);
|
||||||
|
|
||||||
|
is($x->input_focus, $A->id, 'Tiling window moved to the right workspace');
|
||||||
|
is($ws2, focused_ws, 'Empty workspace focused after tiling window dragged to it');
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Drag tiling container onto a container that closes before the drag is
|
||||||
|
# complete.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$ws1 = fresh_workspace(output => 0);
|
||||||
|
$A = open_window;
|
||||||
|
open_window;
|
||||||
|
|
||||||
|
start_drag(600, 300); # Start dragging the second window.
|
||||||
|
|
||||||
|
# Try to place it on the first window.
|
||||||
|
$x->root->warp_pointer(50, 50);
|
||||||
|
sync_with_i3;
|
||||||
|
|
||||||
|
cmd '[id=' . $A->id . '] kill';
|
||||||
|
sync_with_i3;
|
||||||
|
end_drag(50, 50);
|
||||||
|
|
||||||
|
is(@{get_ws_content($ws1)}, 1, 'One container left in ws1');
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Drag tiling container onto a tiling container on an other workspace.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
subtest "Draging tiling container onto a tiling container on an other workspace produces move event", \&move_subtest,
|
||||||
|
sub {
|
||||||
|
|
||||||
|
$ws2 = fresh_workspace(output => 1);
|
||||||
|
open_window;
|
||||||
|
$B_id = get_focused($ws2);
|
||||||
|
$ws1 = fresh_workspace(output => 0);
|
||||||
|
$A = open_window;
|
||||||
|
$A_id = get_focused($ws1);
|
||||||
|
|
||||||
|
start_drag(50, 50);
|
||||||
|
end_drag(1500, 250); # Center of right output, inner region.
|
||||||
|
|
||||||
|
is($ws2, focused_ws, 'Workspace focused after tiling window dragged to it');
|
||||||
|
$ws2 = get_ws($ws2);
|
||||||
|
is($ws2->{focus}[0], $A_id, 'A focused first, dragged container kept focus');
|
||||||
|
is($ws2->{focus}[1], $B_id, 'B focused second');
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Drag tiling container onto a floating container on an other workspace.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
subtest "Draging tiling container onto a floating container on an other workspace produces move event", \&move_subtest,
|
||||||
|
sub {
|
||||||
|
|
||||||
|
$ws2 = fresh_workspace(output => 1);
|
||||||
|
open_floating_window;
|
||||||
|
$B_id = get_focused($ws2);
|
||||||
|
$ws1 = fresh_workspace(output => 0);
|
||||||
|
$A = open_window;
|
||||||
|
$A_id = get_focused($ws1);
|
||||||
|
|
||||||
|
start_drag(50, 50);
|
||||||
|
end_drag(1500, 250);
|
||||||
|
|
||||||
|
is($ws2, focused_ws, 'Workspace with one floating container focused after tiling window dragged to it');
|
||||||
|
$ws2 = get_ws($ws2);
|
||||||
|
is($ws2->{focus}[0], $A_id, 'A focused first, dragged container kept focus');
|
||||||
|
is($ws2->{floating_nodes}[0]->{nodes}[0]->{id}, $B_id, 'B exists & floating');
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Drag tiling container onto a bar.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
subtest "Draging tiling container onto a bar produces move event", \&move_subtest,
|
||||||
|
sub {
|
||||||
|
|
||||||
|
$ws1 = fresh_workspace(output => 0);
|
||||||
|
open_window;
|
||||||
|
$B_id = get_focused($ws1);
|
||||||
|
$ws2 = fresh_workspace(output => 1);
|
||||||
|
$A = open_window;
|
||||||
|
$A_id = get_focused($ws2);
|
||||||
|
|
||||||
|
start_drag(1500, 250);
|
||||||
|
end_drag(1, 498); # Bar on bottom of left output.
|
||||||
|
|
||||||
|
is($ws1, focused_ws, 'Workspace focused after tiling window dragged to its bar');
|
||||||
|
$ws1 = get_ws($ws1);
|
||||||
|
is($ws1->{focus}[0], $A_id, 'B focused first, dragged container kept focus');
|
||||||
|
is($ws1->{focus}[1], $B_id, 'A focused second');
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Drag an unfocused tiling container onto it's self.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$ws1 = fresh_workspace(output => 0);
|
||||||
|
open_window;
|
||||||
|
$A_id = get_focused($ws1);
|
||||||
|
open_window;
|
||||||
|
$B_id = get_focused($ws1);
|
||||||
|
|
||||||
|
start_drag(50, 50);
|
||||||
|
end_drag(450, 450);
|
||||||
|
|
||||||
|
$ws1 = get_ws($ws1);
|
||||||
|
is($ws1->{focus}[0], $B_id, 'B focused first, kept focus');
|
||||||
|
is($ws1->{focus}[1], $A_id, 'A focused second, unfocused dragged container didn\'t gain focus');
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Drag an unfocused tiling container onto an occupied workspace.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
subtest "Draging unfocused tiling container onto an occupied workspace produces move event", \&move_subtest,
|
||||||
|
sub {
|
||||||
|
|
||||||
|
$ws1 = fresh_workspace(output => 0);
|
||||||
|
$A = open_window;
|
||||||
|
$A_id = get_focused($ws1);
|
||||||
|
$ws2 = fresh_workspace(output => 1);
|
||||||
|
open_window;
|
||||||
|
$B_id = get_focused($ws2);
|
||||||
|
|
||||||
|
start_drag(50, 50);
|
||||||
|
end_drag(1500, 250); # Center of right output, inner region.
|
||||||
|
|
||||||
|
is($ws2, focused_ws, 'Workspace remained focused after dragging unfocused container');
|
||||||
|
$ws2 = get_ws($ws2);
|
||||||
|
is($ws2->{focus}[0], $B_id, 'B focused first, kept focus');
|
||||||
|
is($ws2->{focus}[1], $A_id, 'A focused second, unfocused container didn\'t steal focus');
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Drag fullscreen container onto window in same workspace.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$ws1 = fresh_workspace(output => 0);
|
||||||
|
open_window;
|
||||||
|
$A = open_window;
|
||||||
|
cmd 'fullscreen enable';
|
||||||
|
|
||||||
|
start_drag(900, 100); # Second window
|
||||||
|
end_drag(50, 50); # To first window
|
||||||
|
|
||||||
|
is($ws1, focused_ws, 'Workspace remained focused after dragging fullscreen container');
|
||||||
|
is_num_fullscreen($ws1, 1, 'Container still fullscreened');
|
||||||
|
is($x->input_focus, $A->id, 'Fullscreen container still focused');
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Drag unfocused fullscreen container onto window in other workspace.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
subtest "Draging unfocused fullscreen container onto window in other workspace produces move event", \&move_subtest,
|
||||||
|
sub {
|
||||||
|
|
||||||
|
$ws1 = fresh_workspace(output => 0);
|
||||||
|
$A = open_window;
|
||||||
|
cmd 'fullscreen enable';
|
||||||
|
$ws2 = fresh_workspace(output => 1);
|
||||||
|
open_window;
|
||||||
|
open_window;
|
||||||
|
|
||||||
|
start_drag(900, 100);
|
||||||
|
end_drag(1000 + 500 * 0.15 + 10, 200); # left of leftmost window
|
||||||
|
|
||||||
|
is($ws2, focused_ws, 'Workspace still focused after dragging fullscreen container to it');
|
||||||
|
is_num_fullscreen($ws1, 0, 'No fullscreen container in first workspace');
|
||||||
|
is_num_fullscreen($ws2, 1, 'Moved container still fullscreened');
|
||||||
|
is($x->input_focus, $A->id, 'Fullscreen container now focused');
|
||||||
|
$ws2 = get_ws($ws2);
|
||||||
|
is($ws2->{nodes}->[0]->{window}, $A->id, 'Fullscreen container now leftmost window in second workspace');
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Drag unfocused fullscreen container onto left outter region of window in
|
||||||
|
# other workspace. The container shouldn't end up in $ws2 because it was
|
||||||
|
# dragged onto the outter region of the leftmost window. We must also check
|
||||||
|
# that the focus remains on the other window.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
subtest "Draging unfocused fullscreen container onto left outter region of window in other workspace produces move event", \&move_subtest,
|
||||||
|
sub {
|
||||||
|
|
||||||
|
$ws1 = fresh_workspace(output => 0);
|
||||||
|
open_window for (1..3);
|
||||||
|
$A = open_window;
|
||||||
|
$tmp = get_focused($ws1);
|
||||||
|
cmd 'fullscreen enable';
|
||||||
|
$ws2 = fresh_workspace(output => 1);
|
||||||
|
$B = open_window;
|
||||||
|
|
||||||
|
start_drag(990, 100); # rightmost of $ws1
|
||||||
|
end_drag(1004, 100); # outter region of window of $ws2
|
||||||
|
|
||||||
|
is($ws2, focused_ws, 'Workspace still focused after dragging fullscreen container to it');
|
||||||
|
is_num_fullscreen($ws1, 1, 'Fullscreen container still in first workspace');
|
||||||
|
is_num_fullscreen($ws2, 0, 'No fullscreen container in second workspace');
|
||||||
|
is($x->input_focus, $B->id, 'Window of second workspace still has focus');
|
||||||
|
is(get_focused($ws1), $tmp, 'Fullscreen container still focused in first workspace');
|
||||||
|
$ws1 = get_ws($ws1);
|
||||||
|
is($ws1->{nodes}->[3]->{window}, $A->id, 'Fullscreen container still rightmost window in first workspace');
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
exit_gracefully($pid);
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
done_testing;
|
Loading…
x
Reference in New Issue
Block a user