Compare commits
5 Commits
ddc587933d
...
fa9481cdc6
Author | SHA1 | Date | |
---|---|---|---|
fa9481cdc6 | |||
8220ff251c | |||
|
ebcd1d43ea | ||
|
807e972330 | ||
|
103dc7b55d |
@ -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.
|
||||
|
||||
=== 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
|
||||
|
||||
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
|
||||
---------------------------
|
||||
|
||||
[[client_colors]]
|
||||
=== Changing colors
|
||||
|
||||
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
|
||||
-------------------------
|
||||
|
||||
[[move_direction]]
|
||||
=== Moving containers
|
||||
|
||||
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
|
||||
-------------------------
|
||||
|
||||
[[move_to_mark]]
|
||||
=== Moving containers/windows to marks
|
||||
|
||||
To move a container to another container with a specific mark (see <<vim_like_marks>>),
|
||||
|
@ -58,6 +58,7 @@
|
||||
#include "match.h"
|
||||
#include "xcursor.h"
|
||||
#include "resize.h"
|
||||
#include "tiling_drag.h"
|
||||
#include "sighandler.h"
|
||||
#include "move.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_target(Con *con, Con *target);
|
||||
/**
|
||||
* Moves the given container to the given mark.
|
||||
*
|
||||
|
@ -48,6 +48,7 @@ CFGFUN(floating_maximum_size, const long width, const long height);
|
||||
CFGFUN(default_orientation, const char *orientation);
|
||||
CFGFUN(workspace_layout, const char *layout);
|
||||
CFGFUN(workspace_back_and_forth, const char *value);
|
||||
CFGFUN(empty_workspaces, const char *value);
|
||||
CFGFUN(focus_follows_mouse, const char *value);
|
||||
CFGFUN(mouse_warping, const char *value);
|
||||
CFGFUN(focus_wrapping, const char *value);
|
||||
|
@ -118,6 +118,9 @@ struct Config {
|
||||
/** Default orientation for new containers */
|
||||
int default_orientation;
|
||||
|
||||
/** Init empty workspaces on startup */
|
||||
bool empty_workspaces;
|
||||
|
||||
/** By default, focus follows mouse. If the user explicitly wants to
|
||||
* turn this off (and instead rely only on the keyboard for changing
|
||||
* focus), we allow them to do this with this relatively special option.
|
||||
|
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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
@ -186,3 +186,9 @@ Con *workspace_encapsulate(Con *ws);
|
||||
*
|
||||
*/
|
||||
void workspace_move_to_output(Con *ws, Output *output);
|
||||
|
||||
/*
|
||||
* Create all workspaces specified in the config bindings.
|
||||
*
|
||||
*/
|
||||
void workspace_init(void);
|
||||
|
@ -409,6 +409,7 @@ i3srcs = [
|
||||
'src/sighandler.c',
|
||||
'src/startup.c',
|
||||
'src/sync.c',
|
||||
'src/tiling_drag.c',
|
||||
'src/tree.c',
|
||||
'src/util.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;
|
||||
}
|
||||
|
||||
if (ws != focused_workspace)
|
||||
workspace_show(ws);
|
||||
|
||||
/* get the floating con */
|
||||
Con *floatingcon = con_inside_floating(con);
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
if (in_stacked && dest == CLICK_DECORATION) {
|
||||
/* 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);
|
||||
|
||||
/* 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 */
|
||||
Con *fs = con_get_fullscreen_covering_ws(ws);
|
||||
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) {
|
||||
floating_drag_window(floatingcon, event, false);
|
||||
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.
|
||||
* also try resizing (tiling) if possible */
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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) */
|
||||
if (dest == CLICK_DECORATION && is_left_click) {
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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 (floating_mod_on_tiled_client(con, event)) {
|
||||
return;
|
||||
@ -293,7 +305,7 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
||||
xcb_flush(conn);
|
||||
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) &&
|
||||
is_left_or_right_click) {
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
bool con_move_to_target(Con *con, Con *target) {
|
||||
/* For target containers in the scratchpad, we just send the window to the scratchpad. */
|
||||
if (con_get_workspace(target) == workspace_get("__i3_scratch")) {
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
* workspace.
|
||||
|
@ -209,6 +209,8 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
|
||||
|
||||
config.show_marks = true;
|
||||
|
||||
config.empty_workspaces = false;
|
||||
|
||||
config.default_border = BS_NORMAL;
|
||||
config.default_floating_border = BS_NORMAL;
|
||||
config.default_border_width = logical_px(2);
|
||||
|
@ -314,6 +314,10 @@ CFGFUN(hide_edge_borders, const char *borders) {
|
||||
config.hide_edge_borders = HEBM_NONE;
|
||||
}
|
||||
|
||||
CFGFUN(empty_workspaces, const char *value) {
|
||||
config.empty_workspaces = boolstr(value);
|
||||
}
|
||||
|
||||
CFGFUN(focus_follows_mouse, const char *value) {
|
||||
config.disable_focus_follows_mouse = !boolstr(value);
|
||||
}
|
||||
|
@ -1197,5 +1197,10 @@ int main(int argc, char *argv[]) {
|
||||
atexit(i3_exit);
|
||||
|
||||
sd_notify(1, "READY=1");
|
||||
|
||||
if (config.empty_workspaces) {
|
||||
workspace_init();
|
||||
}
|
||||
|
||||
ev_loop(main_loop, 0);
|
||||
}
|
||||
|
26
src/randr.c
26
src/randr.c
@ -665,11 +665,12 @@ static bool randr_query_outputs_15(void) {
|
||||
|
||||
new->primary = monitor_info->primary;
|
||||
|
||||
new->changed =
|
||||
update_if_necessary(&(new->rect.x), monitor_info->x) |
|
||||
update_if_necessary(&(new->rect.y), monitor_info->y) |
|
||||
update_if_necessary(&(new->rect.width), monitor_info->width) |
|
||||
update_if_necessary(&(new->rect.height), monitor_info->height);
|
||||
const bool update_x = update_if_necessary(&(new->rect.x), monitor_info->x);
|
||||
const bool update_y = update_if_necessary(&(new->rect.y), monitor_info->y);
|
||||
const bool update_w = update_if_necessary(&(new->rect.width), monitor_info->width);
|
||||
const bool update_h = update_if_necessary(&(new->rect.height), monitor_info->height);
|
||||
|
||||
new->changed = update_x || update_y || update_w || update_h;
|
||||
|
||||
DLOG("name %s, x %d, y %d, width %d px, height %d px, width %d mm, height %d mm, primary %d, automatic %d\n",
|
||||
name,
|
||||
@ -743,10 +744,11 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
|
||||
return;
|
||||
}
|
||||
|
||||
bool updated = update_if_necessary(&(new->rect.x), crtc->x) |
|
||||
update_if_necessary(&(new->rect.y), crtc->y) |
|
||||
update_if_necessary(&(new->rect.width), crtc->width) |
|
||||
update_if_necessary(&(new->rect.height), crtc->height);
|
||||
const bool update_x = update_if_necessary(&(new->rect.x), crtc->x);
|
||||
const bool update_y = update_if_necessary(&(new->rect.y), crtc->y);
|
||||
const bool update_w = update_if_necessary(&(new->rect.width), crtc->width);
|
||||
const bool update_h = update_if_necessary(&(new->rect.height), crtc->height);
|
||||
const bool updated = update_x || update_y || update_w || update_h;
|
||||
free(crtc);
|
||||
new->active = (new->rect.width != 0 && new->rect.height != 0);
|
||||
if (!new->active) {
|
||||
@ -943,9 +945,11 @@ void randr_query_outputs(void) {
|
||||
uint32_t width = min(other->rect.width, output->rect.width);
|
||||
uint32_t height = min(other->rect.height, output->rect.height);
|
||||
|
||||
if (update_if_necessary(&(output->rect.width), width) |
|
||||
update_if_necessary(&(output->rect.height), height))
|
||||
const bool update_w = update_if_necessary(&(output->rect.width), width);
|
||||
const bool update_h = update_if_necessary(&(output->rect.height), height);
|
||||
if (update_w || update_h) {
|
||||
output->changed = true;
|
||||
}
|
||||
|
||||
update_if_necessary(&(other->rect.width), width);
|
||||
update_if_necessary(&(other->rect.height), height);
|
||||
|
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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
Con *output = con_get_output(ws);
|
||||
if (output == NULL)
|
||||
if (output == NULL) {
|
||||
return false;
|
||||
}
|
||||
Con *fs = con_get_fullscreen_con(output, CF_OUTPUT);
|
||||
LOG("workspace visible? fs = %p, ws = %p\n", fs, ws);
|
||||
return (fs == ws);
|
||||
}
|
||||
|
||||
@ -506,8 +506,9 @@ void workspace_show(Con *workspace) {
|
||||
* client, which will clear the urgency flag too early. Also, there is no
|
||||
* way for con_focus() to know about when to clear urgency immediately and
|
||||
* when to defer it. */
|
||||
if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
|
||||
/* check if this workspace is currently visible */
|
||||
|
||||
if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))
|
||||
&& !config.empty_workspaces) {
|
||||
if (!workspace_is_visible(old)) {
|
||||
LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
|
||||
yajl_gen gen = ipc_marshal_workspace_event("empty", old, NULL);
|
||||
@ -520,7 +521,6 @@ void workspace_show(Con *workspace) {
|
||||
|
||||
y(free);
|
||||
|
||||
/* Avoid calling output_push_sticky_windows later with a freed container. */
|
||||
if (old == old_focus) {
|
||||
old_focus = NULL;
|
||||
}
|
||||
@ -1065,3 +1065,19 @@ void workspace_move_to_output(Con *ws, Output *output) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void workspace_init(void)
|
||||
{
|
||||
int i = 0;
|
||||
char *c = binding_workspace_names[0];
|
||||
|
||||
|
||||
while (c != NULL) {
|
||||
DLOG("bwn: %s\n", c);
|
||||
workspace_get(c);
|
||||
i++;
|
||||
c = binding_workspace_names[i];
|
||||
}
|
||||
|
||||
//workspace_show_by_name(binding_workspace_names[0]);
|
||||
}
|
||||
|
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;
|
@ -26,11 +26,12 @@ my $exitcode = 0;
|
||||
|
||||
# Whitelist for spelling errors in manpages, in case the spell checker has
|
||||
# false-positives.
|
||||
my $binary_spelling_exceptions = {
|
||||
#'exmaple' => 1, # Example for how to add entries to this whitelist.
|
||||
'betwen' => 1, # asan_flags.inc contains this spelling error.
|
||||
'dissassemble' => 1, # https://reviews.llvm.org/D93902
|
||||
};
|
||||
my $binary_spelling_exceptions = [
|
||||
#'exmaple', # Example for how to add entries to this whitelist.
|
||||
'betwen', # asan_flags.inc contains this spelling error.
|
||||
'dissassemble', # https://reviews.llvm.org/D93902
|
||||
'oT', # lintian finds this in build/i3bar when built with clang?!
|
||||
];
|
||||
my @binaries = qw(
|
||||
build/i3
|
||||
build/i3-config-wizard
|
||||
@ -41,7 +42,7 @@ my @binaries = qw(
|
||||
build/i3bar
|
||||
);
|
||||
for my $binary (@binaries) {
|
||||
check_spelling($profile, slurp($binary), $binary_spelling_exceptions, sub {
|
||||
check_spelling($profile->data, slurp($binary), $binary_spelling_exceptions, sub {
|
||||
my ($current, $fixed) = @_;
|
||||
say STDERR qq|Binary "$binary" contains a spelling error: "$current" should be "$fixed"|;
|
||||
$exitcode = 1;
|
||||
@ -50,13 +51,13 @@ for my $binary (@binaries) {
|
||||
|
||||
# Whitelist for spelling errors in manpages, in case the spell checker has
|
||||
# false-positives.
|
||||
my $manpage_spelling_exceptions = {
|
||||
};
|
||||
my $manpage_spelling_exceptions = [
|
||||
];
|
||||
|
||||
for my $name (glob('build/man/*.1')) {
|
||||
for my $line (split(/\n/, slurp($name))) {
|
||||
next if $line =~ /^\.\\\"/o;
|
||||
check_spelling($profile, $line, $manpage_spelling_exceptions, sub {
|
||||
check_spelling($profile->data, $line, $manpage_spelling_exceptions, sub {
|
||||
my ($current, $fixed) = @_;
|
||||
say STDERR qq|Manpage "$name" contains a spelling error: "$current" should be "$fixed"|;
|
||||
$exitcode = 1;
|
||||
|
Loading…
x
Reference in New Issue
Block a user