Compare commits

...

5 Commits

Author SHA1 Message Date
fa9481cdc6 add config variable for emtpy workspaces 2022-10-22 21:34:14 +02:00
8220ff251c show empty workspaces 2022-09-08 13:37:13 +02:00
Orestis Floros
ebcd1d43ea
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)
2022-07-28 12:03:16 +02:00
Orestis Floros
807e972330 Fix Wbitwise-instead-of-logical warnings
> error: use of bitwise '|' with boolean operands
2022-07-28 09:25:55 +02:00
Michael Stapelberg
103dc7b55d fix travis/check-spelling.pl for recent Lintian changes 2022-07-28 09:25:55 +02:00
21 changed files with 914 additions and 46 deletions

View File

@ -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>>),

View File

@ -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"

View File

@ -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.
*

View File

@ -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);

View File

@ -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
View 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);

View File

@ -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);

View File

@ -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);

View File

@ -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',

View File

@ -0,0 +1 @@
Allow moving tiling windows with the mouse

View File

@ -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");

View File

@ -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.

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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
View 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, &params);
/* 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();
}

View File

@ -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";
}

View File

@ -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]);
}

View 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;

View File

@ -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;