Merge pull request #1662 from Airblader/feature-move-to-mark
Allow moving windows to marks
This commit is contained in:
commit
116294cc41
|
@ -1837,6 +1837,26 @@ bindsym $mod+x move workspace to output right
|
||||||
bindsym $mod+x move container to output VGA1
|
bindsym $mod+x move container to output VGA1
|
||||||
--------------------------------------------------------
|
--------------------------------------------------------
|
||||||
|
|
||||||
|
=== Moving containers/workspaces to marks
|
||||||
|
|
||||||
|
To move a container to another container with a specific mark (see <<vim_like_marks>>),
|
||||||
|
you can use the following command.
|
||||||
|
|
||||||
|
The window will be moved right after the marked container in the tree, i.e., it ends up
|
||||||
|
in the same position as if you had opened a new window when the marked container was
|
||||||
|
focused. If the mark is on a split container, the window will appear as a new child
|
||||||
|
after the currently focused child within that container.
|
||||||
|
|
||||||
|
*Syntax*:
|
||||||
|
------------------------------------
|
||||||
|
move window|container to mark <mark>
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
*Example*:
|
||||||
|
--------------------------------------------------------
|
||||||
|
for_window [instance="tabme"] move window to mark target
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
[[resizingconfig]]
|
[[resizingconfig]]
|
||||||
|
|
||||||
=== Resizing containers/windows
|
=== Resizing containers/windows
|
||||||
|
|
|
@ -132,6 +132,12 @@ void cmd_mode(I3_CMD, char *mode);
|
||||||
*/
|
*/
|
||||||
void cmd_move_con_to_output(I3_CMD, char *name);
|
void cmd_move_con_to_output(I3_CMD, char *name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of 'move [window|container] [to] mark <str>'.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void cmd_move_con_to_mark(I3_CMD, char *mark);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of 'floating enable|disable|toggle'
|
* Implementation of 'floating enable|disable|toggle'
|
||||||
*
|
*
|
||||||
|
|
|
@ -126,6 +126,13 @@ Con *con_by_window_id(xcb_window_t window);
|
||||||
*/
|
*/
|
||||||
Con *con_by_frame_id(xcb_window_t frame);
|
Con *con_by_frame_id(xcb_window_t frame);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the container with the given mark or NULL if no such container
|
||||||
|
* exists.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Con *con_by_mark(const char *mark);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the first container below 'con' which wants to swallow this window
|
* Returns the first container below 'con' which wants to swallow this window
|
||||||
* TODO: priority
|
* TODO: priority
|
||||||
|
@ -203,6 +210,12 @@ void con_disable_fullscreen(Con *con);
|
||||||
*/
|
*/
|
||||||
void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp);
|
void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the given container to the given mark.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
bool con_move_to_mark(Con *con, const char *mark);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the orientation of the given container (for stacked containers,
|
* Returns the orientation of the given container (for stacked containers,
|
||||||
* vertical orientation is used regardless of the actual orientation of the
|
* vertical orientation is used regardless of the actual orientation of the
|
||||||
|
|
|
@ -265,6 +265,7 @@ state RENAME_WORKSPACE_NEW_NAME:
|
||||||
# move <direction> [<pixels> [px]]
|
# move <direction> [<pixels> [px]]
|
||||||
# move [window|container] [to] workspace [<str>|next|prev|next_on_output|prev_on_output|current]
|
# move [window|container] [to] workspace [<str>|next|prev|next_on_output|prev_on_output|current]
|
||||||
# move [window|container] [to] output <str>
|
# move [window|container] [to] output <str>
|
||||||
|
# move [window|container] [to] mark <str>
|
||||||
# move [window|container] [to] scratchpad
|
# move [window|container] [to] scratchpad
|
||||||
# move workspace to [output] <str>
|
# move workspace to [output] <str>
|
||||||
# move scratchpad
|
# move scratchpad
|
||||||
|
@ -280,6 +281,8 @@ state MOVE:
|
||||||
-> MOVE_WORKSPACE
|
-> MOVE_WORKSPACE
|
||||||
'output'
|
'output'
|
||||||
-> MOVE_TO_OUTPUT
|
-> MOVE_TO_OUTPUT
|
||||||
|
'mark'
|
||||||
|
-> MOVE_TO_MARK
|
||||||
'scratchpad'
|
'scratchpad'
|
||||||
-> call cmd_move_scratchpad()
|
-> call cmd_move_scratchpad()
|
||||||
direction = 'left', 'right', 'up', 'down'
|
direction = 'left', 'right', 'up', 'down'
|
||||||
|
@ -321,6 +324,10 @@ state MOVE_TO_OUTPUT:
|
||||||
output = string
|
output = string
|
||||||
-> call cmd_move_con_to_output($output)
|
-> call cmd_move_con_to_output($output)
|
||||||
|
|
||||||
|
state MOVE_TO_MARK:
|
||||||
|
mark = string
|
||||||
|
-> call cmd_move_con_to_mark($mark)
|
||||||
|
|
||||||
state MOVE_WORKSPACE_TO_OUTPUT:
|
state MOVE_WORKSPACE_TO_OUTPUT:
|
||||||
'output'
|
'output'
|
||||||
->
|
->
|
||||||
|
|
|
@ -1100,13 +1100,11 @@ void cmd_unmark(I3_CMD, char *mark) {
|
||||||
}
|
}
|
||||||
DLOG("removed all window marks");
|
DLOG("removed all window marks");
|
||||||
} else {
|
} else {
|
||||||
Con *con;
|
Con *con = con_by_mark(mark);
|
||||||
TAILQ_FOREACH(con, &all_cons, all_cons) {
|
if (con != NULL) {
|
||||||
if (con->mark && strcmp(con->mark, mark) == 0) {
|
|
||||||
FREE(con->mark);
|
FREE(con->mark);
|
||||||
con->mark_changed = true;
|
con->mark_changed = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
DLOG("removed window mark %s\n", mark);
|
DLOG("removed window mark %s\n", mark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1169,6 +1167,26 @@ void cmd_move_con_to_output(I3_CMD, char *name) {
|
||||||
ysuccess(true);
|
ysuccess(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Implementation of 'move [container|window] [to] mark <str>'.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void cmd_move_con_to_mark(I3_CMD, char *mark) {
|
||||||
|
DLOG("moving window to mark \"%s\"\n", mark);
|
||||||
|
|
||||||
|
HANDLE_EMPTY_MATCH;
|
||||||
|
|
||||||
|
bool result = true;
|
||||||
|
owindow *current;
|
||||||
|
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||||
|
DLOG("moving matched window %p / %s to mark \"%s\"\n", current->con, current->con->name, mark);
|
||||||
|
result &= con_move_to_mark(current->con, mark);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_output->needs_tree_render = true;
|
||||||
|
ysuccess(result);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Implementation of 'floating enable|disable|toggle'
|
* Implementation of 'floating enable|disable|toggle'
|
||||||
*
|
*
|
||||||
|
|
196
src/con.c
196
src/con.c
|
@ -70,20 +70,10 @@ Con *con_new(Con *parent, i3Window *window) {
|
||||||
return new;
|
return new;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static void _con_attach(Con *con, Con *parent, Con *previous, bool ignore_focus) {
|
||||||
* Attaches the given container to the given parent. This happens when moving
|
|
||||||
* a container or when inserting a new container at a specific place in the
|
|
||||||
* tree.
|
|
||||||
*
|
|
||||||
* ignore_focus is to just insert the Con at the end (useful when creating a
|
|
||||||
* new split container *around* some containers, that is, detaching and
|
|
||||||
* attaching them in order without wanting to mess with the focus in between).
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
void con_attach(Con *con, Con *parent, bool ignore_focus) {
|
|
||||||
con->parent = parent;
|
con->parent = parent;
|
||||||
Con *loop;
|
Con *loop;
|
||||||
Con *current = NULL;
|
Con *current = previous;
|
||||||
struct nodes_head *nodes_head = &(parent->nodes_head);
|
struct nodes_head *nodes_head = &(parent->nodes_head);
|
||||||
struct focus_head *focus_head = &(parent->focus_head);
|
struct focus_head *focus_head = &(parent->focus_head);
|
||||||
|
|
||||||
|
@ -155,8 +145,7 @@ void con_attach(Con *con, Con *parent, bool ignore_focus) {
|
||||||
/* Insert the container after the tiling container, if found.
|
/* Insert the container after the tiling container, if found.
|
||||||
* When adding to a CT_OUTPUT, just append one after another. */
|
* When adding to a CT_OUTPUT, just append one after another. */
|
||||||
if (current && parent->type != CT_OUTPUT) {
|
if (current && parent->type != CT_OUTPUT) {
|
||||||
DLOG("Inserting con = %p after last focused tiling con %p\n",
|
DLOG("Inserting con = %p after con %p\n", con, current);
|
||||||
con, current);
|
|
||||||
TAILQ_INSERT_AFTER(nodes_head, current, con, nodes);
|
TAILQ_INSERT_AFTER(nodes_head, current, con, nodes);
|
||||||
} else
|
} else
|
||||||
TAILQ_INSERT_TAIL(nodes_head, con, nodes);
|
TAILQ_INSERT_TAIL(nodes_head, con, nodes);
|
||||||
|
@ -170,6 +159,20 @@ add_to_focus_head:
|
||||||
con_force_split_parents_redraw(con);
|
con_force_split_parents_redraw(con);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Attaches the given container to the given parent. This happens when moving
|
||||||
|
* a container or when inserting a new container at a specific place in the
|
||||||
|
* tree.
|
||||||
|
*
|
||||||
|
* ignore_focus is to just insert the Con at the end (useful when creating a
|
||||||
|
* new split container *around* some containers, that is, detaching and
|
||||||
|
* attaching them in order without wanting to mess with the focus in between).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void con_attach(Con *con, Con *parent, bool ignore_focus) {
|
||||||
|
_con_attach(con, parent, NULL, ignore_focus);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Detaches the given container from its current parent
|
* Detaches the given container from its current parent
|
||||||
*
|
*
|
||||||
|
@ -460,6 +463,21 @@ Con *con_by_frame_id(xcb_window_t frame) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the container with the given mark or NULL if no such container
|
||||||
|
* exists.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Con *con_by_mark(const char *mark) {
|
||||||
|
Con *con;
|
||||||
|
TAILQ_FOREACH(con, &all_cons, all_cons) {
|
||||||
|
if (con->mark != NULL && strcmp(con->mark, mark) == 0)
|
||||||
|
return con;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns the first container below 'con' which wants to swallow this window
|
* Returns the first container below 'con' which wants to swallow this window
|
||||||
* TODO: priority
|
* TODO: priority
|
||||||
|
@ -682,28 +700,14 @@ void con_disable_fullscreen(Con *con) {
|
||||||
con_set_fullscreen_mode(con, CF_NONE);
|
con_set_fullscreen_mode(con, CF_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fix_coordinates, bool dont_warp) {
|
||||||
* Moves the given container to the currently focused container on the given
|
Con *orig_target = target;
|
||||||
* workspace.
|
|
||||||
*
|
|
||||||
* The fix_coordinates flag will translate the current coordinates (offset from
|
|
||||||
* the monitor position basically) to appropriate coordinates on the
|
|
||||||
* destination workspace.
|
|
||||||
* Not enabling this behaviour comes in handy when this function gets called by
|
|
||||||
* floating_maybe_reassign_ws, which will only "move" a floating window when it
|
|
||||||
* *already* changed its coordinates to a different output.
|
|
||||||
*
|
|
||||||
* The dont_warp flag disables pointer warping and will be set when this
|
|
||||||
* function is called while dragging a floating window.
|
|
||||||
*
|
|
||||||
* TODO: is there a better place for this function?
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp) {
|
|
||||||
/* Prevent moving if this would violate the fullscreen focus restrictions. */
|
/* Prevent moving if this would violate the fullscreen focus restrictions. */
|
||||||
if (!con_fullscreen_permits_focusing(workspace)) {
|
Con *target_ws = con_get_workspace(target);
|
||||||
|
if (!con_fullscreen_permits_focusing(target_ws)) {
|
||||||
LOG("Cannot move out of a fullscreen container");
|
LOG("Cannot move out of a fullscreen container");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (con_is_floating(con)) {
|
if (con_is_floating(con)) {
|
||||||
|
@ -712,27 +716,23 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
|
||||||
}
|
}
|
||||||
|
|
||||||
Con *source_ws = con_get_workspace(con);
|
Con *source_ws = con_get_workspace(con);
|
||||||
if (workspace == source_ws) {
|
|
||||||
DLOG("Not moving, already there\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (con->type == CT_WORKSPACE) {
|
if (con->type == CT_WORKSPACE) {
|
||||||
/* Re-parent all of the old workspace's floating windows. */
|
/* Re-parent all of the old workspace's floating windows. */
|
||||||
Con *child;
|
Con *child;
|
||||||
while (!TAILQ_EMPTY(&(source_ws->floating_head))) {
|
while (!TAILQ_EMPTY(&(source_ws->floating_head))) {
|
||||||
child = TAILQ_FIRST(&(source_ws->floating_head));
|
child = TAILQ_FIRST(&(source_ws->floating_head));
|
||||||
con_move_to_workspace(child, workspace, true, true);
|
con_move_to_workspace(child, target_ws, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If there are no non-floating children, ignore the workspace. */
|
/* If there are no non-floating children, ignore the workspace. */
|
||||||
if (con_is_leaf(con))
|
if (con_is_leaf(con))
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
con = workspace_encapsulate(con);
|
con = workspace_encapsulate(con);
|
||||||
if (con == NULL) {
|
if (con == NULL) {
|
||||||
ELOG("Workspace failed to move its contents into a container!\n");
|
ELOG("Workspace failed to move its contents into a container!\n");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -744,34 +744,31 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
|
||||||
Con *current_ws = con_get_workspace(focused);
|
Con *current_ws = con_get_workspace(focused);
|
||||||
|
|
||||||
Con *source_output = con_get_output(con),
|
Con *source_output = con_get_output(con),
|
||||||
*dest_output = con_get_output(workspace);
|
*dest_output = con_get_output(target_ws);
|
||||||
|
|
||||||
/* 1: save the container which is going to be focused after the current
|
/* 1: save the container which is going to be focused after the current
|
||||||
* container is moved away */
|
* container is moved away */
|
||||||
Con *focus_next = con_next_focused(con);
|
Con *focus_next = con_next_focused(con);
|
||||||
|
|
||||||
/* 2: get the focused container of this workspace */
|
/* 2: we go up one level, but only when target is a normal container */
|
||||||
Con *next = con_descend_focused(workspace);
|
if (target->type != CT_WORKSPACE) {
|
||||||
|
DLOG("target originally = %p / %s / type %d\n", target, target->name, target->type);
|
||||||
/* 3: we go up one level, but only when next is a normal container */
|
target = target->parent;
|
||||||
if (next->type != CT_WORKSPACE) {
|
|
||||||
DLOG("next originally = %p / %s / type %d\n", next, next->name, next->type);
|
|
||||||
next = next->parent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 4: if the target container is floating, we get the workspace instead.
|
/* 3: if the target container is floating, we get the workspace instead.
|
||||||
* Only tiling windows need to get inserted next to the current container.
|
* Only tiling windows need to get inserted next to the current container.
|
||||||
* */
|
* */
|
||||||
Con *floatingcon = con_inside_floating(next);
|
Con *floatingcon = con_inside_floating(target);
|
||||||
if (floatingcon != NULL) {
|
if (floatingcon != NULL) {
|
||||||
DLOG("floatingcon, going up even further\n");
|
DLOG("floatingcon, going up even further\n");
|
||||||
next = floatingcon->parent;
|
target = floatingcon->parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (con->type == CT_FLOATING_CON) {
|
if (con->type == CT_FLOATING_CON) {
|
||||||
Con *ws = con_get_workspace(next);
|
Con *ws = con_get_workspace(target);
|
||||||
DLOG("This is a floating window, using workspace %p / %s\n", ws, ws->name);
|
DLOG("This is a floating window, using workspace %p / %s\n", ws, ws->name);
|
||||||
next = ws;
|
target = ws;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source_output != dest_output) {
|
if (source_output != dest_output) {
|
||||||
|
@ -785,8 +782,8 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
|
||||||
/* If moving to a visible workspace, call show so it can be considered
|
/* If moving to a visible workspace, call show so it can be considered
|
||||||
* focused. Must do before attaching because workspace_show checks to see
|
* focused. Must do before attaching because workspace_show checks to see
|
||||||
* if focused container is in its area. */
|
* if focused container is in its area. */
|
||||||
if (workspace_is_visible(workspace)) {
|
if (workspace_is_visible(target_ws)) {
|
||||||
workspace_show(workspace);
|
workspace_show(target_ws);
|
||||||
|
|
||||||
/* Don’t warp if told so (when dragging floating windows with the
|
/* Don’t warp if told so (when dragging floating windows with the
|
||||||
* mouse for example) */
|
* mouse for example) */
|
||||||
|
@ -799,29 +796,29 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
|
||||||
|
|
||||||
/* If moving a fullscreen container and the destination already has a
|
/* If moving a fullscreen container and the destination already has a
|
||||||
* fullscreen window on it, un-fullscreen the target's fullscreen con. */
|
* fullscreen window on it, un-fullscreen the target's fullscreen con. */
|
||||||
Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
|
Con *fullscreen = con_get_fullscreen_con(target_ws, CF_OUTPUT);
|
||||||
if (con->fullscreen_mode != CF_NONE && fullscreen != NULL) {
|
if (con->fullscreen_mode != CF_NONE && fullscreen != NULL) {
|
||||||
con_toggle_fullscreen(fullscreen, CF_OUTPUT);
|
con_toggle_fullscreen(fullscreen, CF_OUTPUT);
|
||||||
fullscreen = NULL;
|
fullscreen = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
DLOG("Re-attaching container to %p / %s\n", next, next->name);
|
DLOG("Re-attaching container to %p / %s\n", target, target->name);
|
||||||
/* 5: re-attach the con to the parent of this focused container */
|
/* 4: re-attach the con to the parent of this focused container */
|
||||||
Con *parent = con->parent;
|
Con *parent = con->parent;
|
||||||
con_detach(con);
|
con_detach(con);
|
||||||
con_attach(con, next, false);
|
_con_attach(con, target, behind_focused ? NULL : orig_target, !behind_focused);
|
||||||
|
|
||||||
/* 6: fix the percentages */
|
/* 5: fix the percentages */
|
||||||
con_fix_percent(parent);
|
con_fix_percent(parent);
|
||||||
con->percent = 0.0;
|
con->percent = 0.0;
|
||||||
con_fix_percent(next);
|
con_fix_percent(target);
|
||||||
|
|
||||||
/* 7: focus the con on the target workspace, but only within that
|
/* 6: focus the con on the target workspace, but only within that
|
||||||
* workspace, that is, don’t move focus away if the target workspace is
|
* workspace, that is, don’t move focus away if the target workspace is
|
||||||
* invisible.
|
* invisible.
|
||||||
* We don’t focus the con for i3 pseudo workspaces like __i3_scratch and
|
* We don’t focus the con for i3 pseudo workspaces like __i3_scratch and
|
||||||
* we don’t focus when there is a fullscreen con on that workspace. */
|
* we don’t focus when there is a fullscreen con on that workspace. */
|
||||||
if (!con_is_internal(workspace) && !fullscreen) {
|
if (!con_is_internal(target_ws) && !fullscreen) {
|
||||||
/* We need to save the focused workspace on the output in case the
|
/* We need to save the focused workspace on the output in case the
|
||||||
* new workspace is hidden and it's necessary to immediately switch
|
* new workspace is hidden and it's necessary to immediately switch
|
||||||
* back to the originally-focused workspace. */
|
* back to the originally-focused workspace. */
|
||||||
|
@ -833,7 +830,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
|
||||||
con_focus(old_focus);
|
con_focus(old_focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 8: when moving to another workspace, we leave the focus on the current
|
/* 7: when moving to another workspace, we leave the focus on the current
|
||||||
* workspace. (see also #809) */
|
* workspace. (see also #809) */
|
||||||
|
|
||||||
/* Descend focus stack in case focus_next is a workspace which can
|
/* Descend focus stack in case focus_next is a workspace which can
|
||||||
|
@ -846,7 +843,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
|
||||||
if (source_ws == current_ws)
|
if (source_ws == current_ws)
|
||||||
con_focus(con_descend_focused(focus_next));
|
con_focus(con_descend_focused(focus_next));
|
||||||
|
|
||||||
/* 9. If anything within the container is associated with a startup sequence,
|
/* 8. If anything within the container is associated with a startup sequence,
|
||||||
* delete it so child windows won't be created on the old workspace. */
|
* delete it so child windows won't be created on the old workspace. */
|
||||||
struct Startup_Sequence *sequence;
|
struct Startup_Sequence *sequence;
|
||||||
xcb_get_property_cookie_t cookie;
|
xcb_get_property_cookie_t cookie;
|
||||||
|
@ -880,13 +877,78 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
|
||||||
|
|
||||||
CALL(parent, on_remove_child);
|
CALL(parent, on_remove_child);
|
||||||
|
|
||||||
/* 10. If the container was marked urgent, move the urgency hint. */
|
/* 9. If the container was marked urgent, move the urgency hint. */
|
||||||
if (urgent) {
|
if (urgent) {
|
||||||
workspace_update_urgent_flag(source_ws);
|
workspace_update_urgent_flag(source_ws);
|
||||||
con_set_urgency(con, true);
|
con_set_urgency(con, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
ipc_send_window_event("move", con);
|
ipc_send_window_event("move", con);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For floating target containers, we just send the window to the same workspace. */
|
||||||
|
if (con_is_floating(target)) {
|
||||||
|
DLOG("target container is floating, moving container to target's workspace.\n");
|
||||||
|
con_move_to_workspace(con, con_get_workspace(target), true, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For split containers, we use the currently focused container within it.
|
||||||
|
* This allows setting marks on, e.g., tabbed containers which will move
|
||||||
|
* con to a new tab behind the focused tab. */
|
||||||
|
if (con_is_split(target)) {
|
||||||
|
DLOG("target is a split container, descending to the currently focused child.\n");
|
||||||
|
target = TAILQ_FIRST(&(target->focus_head));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (con == target) {
|
||||||
|
DLOG("cannot move the container to itself, aborting.\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _con_move_to_con(con, target, false, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Moves the given container to the currently focused container on the given
|
||||||
|
* workspace.
|
||||||
|
*
|
||||||
|
* The fix_coordinates flag will translate the current coordinates (offset from
|
||||||
|
* the monitor position basically) to appropriate coordinates on the
|
||||||
|
* destination workspace.
|
||||||
|
* Not enabling this behaviour comes in handy when this function gets called by
|
||||||
|
* floating_maybe_reassign_ws, which will only "move" a floating window when it
|
||||||
|
* *already* changed its coordinates to a different output.
|
||||||
|
*
|
||||||
|
* The dont_warp flag disables pointer warping and will be set when this
|
||||||
|
* function is called while dragging a floating window.
|
||||||
|
*
|
||||||
|
* TODO: is there a better place for this function?
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp) {
|
||||||
|
assert(workspace->type == CT_WORKSPACE);
|
||||||
|
|
||||||
|
Con *source_ws = con_get_workspace(con);
|
||||||
|
if (workspace == source_ws) {
|
||||||
|
DLOG("Not moving, already there\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Con *target = con_descend_focused(workspace);
|
||||||
|
_con_move_to_con(con, target, true, fix_coordinates, dont_warp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -150,7 +150,7 @@ is(parser_calls('unknown_literal'),
|
||||||
'error for unknown literal ok');
|
'error for unknown literal ok');
|
||||||
|
|
||||||
is(parser_calls('move something to somewhere'),
|
is(parser_calls('move something to somewhere'),
|
||||||
"ERROR: Expected one of these tokens: 'window', 'container', 'to', 'workspace', 'output', 'scratchpad', 'left', 'right', 'up', 'down', 'position', 'absolute'\n" .
|
"ERROR: Expected one of these tokens: 'window', 'container', 'to', 'workspace', 'output', 'mark', 'scratchpad', 'left', 'right', 'up', 'down', 'position', 'absolute'\n" .
|
||||||
"ERROR: Your command: move something to somewhere\n" .
|
"ERROR: Your command: move something to somewhere\n" .
|
||||||
"ERROR: ^^^^^^^^^^^^^^^^^^^^^^",
|
"ERROR: ^^^^^^^^^^^^^^^^^^^^^^",
|
||||||
'error for unknown literal ok');
|
'error for unknown literal ok');
|
||||||
|
|
|
@ -0,0 +1,341 @@
|
||||||
|
#!perl
|
||||||
|
# vim:ts=4:sw=4:expandtab
|
||||||
|
#
|
||||||
|
# Please read the following documents before working on tests:
|
||||||
|
# • http://build.i3wm.org/docs/testsuite.html
|
||||||
|
# (or docs/testsuite)
|
||||||
|
#
|
||||||
|
# • http://build.i3wm.org/docs/lib-i3test.html
|
||||||
|
# (alternatively: perldoc ./testcases/lib/i3test.pm)
|
||||||
|
#
|
||||||
|
# • http://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)
|
||||||
|
#
|
||||||
|
# Tests for the 'move [window|container] to mark' command
|
||||||
|
# Ticket: #1643
|
||||||
|
use i3test;
|
||||||
|
|
||||||
|
# In the following tests descriptions, we will always use the following names:
|
||||||
|
# * 'S' for the source container which is going to be moved,
|
||||||
|
# * 'M' for the marked target container to which 'S' will be moved.
|
||||||
|
|
||||||
|
my ($A, $B, $S, $M, $F, $source_ws, $target_ws, $ws);
|
||||||
|
my ($nodes, $focus);
|
||||||
|
my $cmd_result;
|
||||||
|
|
||||||
|
my $_NET_WM_STATE_REMOVE = 0;
|
||||||
|
my $_NET_WM_STATE_ADD = 1;
|
||||||
|
my $_NET_WM_STATE_TOGGLE = 2;
|
||||||
|
|
||||||
|
sub set_urgency {
|
||||||
|
my ($win, $urgent_flag) = @_;
|
||||||
|
my $msg = pack "CCSLLLLLL",
|
||||||
|
X11::XCB::CLIENT_MESSAGE, # response_type
|
||||||
|
32, # format
|
||||||
|
0, # sequence
|
||||||
|
$win->id, # window
|
||||||
|
$x->atom(name => '_NET_WM_STATE')->id, # message type
|
||||||
|
($urgent_flag ? $_NET_WM_STATE_ADD : $_NET_WM_STATE_REMOVE), # data32[0]
|
||||||
|
$x->atom(name => '_NET_WM_STATE_DEMANDS_ATTENTION')->id, # data32[1]
|
||||||
|
0, # data32[2]
|
||||||
|
0, # data32[3]
|
||||||
|
0; # data32[4]
|
||||||
|
|
||||||
|
$x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Given 'M' and 'S' in a horizontal split, when 'S' is moved to 'M', then
|
||||||
|
# verify that nothing changed.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$ws = fresh_workspace;
|
||||||
|
$M = open_window;
|
||||||
|
cmd 'mark target';
|
||||||
|
$S = open_window;
|
||||||
|
|
||||||
|
cmd 'move container to mark target';
|
||||||
|
sync_with_i3;
|
||||||
|
|
||||||
|
($nodes, $focus) = get_ws_content($ws);
|
||||||
|
is(@{$nodes}, 2, 'there are two containers');
|
||||||
|
is($nodes->[0]->{window}, $M->{id}, 'M is left of S');
|
||||||
|
is($nodes->[1]->{window}, $S->{id}, 'S is right of M');
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Given 'S' and 'M' in a horizontal split, when 'S' is moved to 'M', then
|
||||||
|
# both containers switch places.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$ws = fresh_workspace;
|
||||||
|
$S = open_window;
|
||||||
|
$M = open_window;
|
||||||
|
cmd 'mark target';
|
||||||
|
cmd 'focus left';
|
||||||
|
|
||||||
|
cmd 'move container to mark target';
|
||||||
|
sync_with_i3;
|
||||||
|
|
||||||
|
($nodes, $focus) = get_ws_content($ws);
|
||||||
|
is(@{$nodes}, 2, 'there are two containers');
|
||||||
|
is($nodes->[0]->{window}, $M->{id}, 'M is left of S');
|
||||||
|
is($nodes->[1]->{window}, $S->{id}, 'S is right of M');
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Given 'S' and no container 'M' exists, when 'S' is moved to 'M', then
|
||||||
|
# the command is unsuccessful.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$ws = fresh_workspace;
|
||||||
|
$S = open_window;
|
||||||
|
|
||||||
|
$cmd_result = cmd 'move container to mark absent';
|
||||||
|
|
||||||
|
is($cmd_result->[0]->{success}, 0, 'command was unsuccessful');
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Given 'S' and 'M' on different workspaces, when 'S' is moved to 'M', then
|
||||||
|
# 'S' ends up on the same workspace as 'M'.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$source_ws = fresh_workspace;
|
||||||
|
$S = open_window;
|
||||||
|
$target_ws = fresh_workspace;
|
||||||
|
$M = open_window;
|
||||||
|
cmd 'mark target';
|
||||||
|
|
||||||
|
cmd '[id="' . $S->{id} . '"] move container to mark target';
|
||||||
|
sync_with_i3;
|
||||||
|
|
||||||
|
($nodes, $focus) = get_ws_content($source_ws);
|
||||||
|
is(@{$nodes}, 0, 'source workspace is empty');
|
||||||
|
|
||||||
|
($nodes, $focus) = get_ws_content($target_ws);
|
||||||
|
is(@{$nodes}, 2, 'both containers are on the target workspace');
|
||||||
|
is($nodes->[0]->{window}, $M->{id}, 'M is left of S');
|
||||||
|
is($nodes->[1]->{window}, $S->{id}, 'S is right of M');
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Given 'S' and 'M' on different workspaces and 'S' is urgent, when 'S' is
|
||||||
|
# moved to 'M', then the urgency flag is transferred to the target workspace.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$source_ws = fresh_workspace;
|
||||||
|
$S = open_window;
|
||||||
|
$F = open_window;
|
||||||
|
$target_ws = fresh_workspace;
|
||||||
|
$M = open_window;
|
||||||
|
cmd 'mark target';
|
||||||
|
cmd 'workspace ' . $source_ws;
|
||||||
|
set_urgency($S, 1);
|
||||||
|
|
||||||
|
cmd '[id="' . $S->{id} . '"] move container to mark target';
|
||||||
|
sync_with_i3;
|
||||||
|
|
||||||
|
$source_ws = get_ws($source_ws);
|
||||||
|
$target_ws = get_ws($target_ws);
|
||||||
|
ok(!$source_ws->{urgent}, 'source workspace is no longer urgent');
|
||||||
|
ok($target_ws->{urgent}, 'target workspace is urgent');
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Given 'S' and 'M' where 'M' is inside a tabbed container, when 'S' is moved
|
||||||
|
# to 'M', then 'S' ends up as a new tab.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$source_ws = fresh_workspace;
|
||||||
|
$S = open_window;
|
||||||
|
|
||||||
|
# open tabbed container ['A' 'M' 'B']
|
||||||
|
$target_ws = fresh_workspace;
|
||||||
|
$A = open_window;
|
||||||
|
cmd 'layout tabbed';
|
||||||
|
$M = open_window;
|
||||||
|
cmd 'mark target';
|
||||||
|
$B = open_window;
|
||||||
|
|
||||||
|
cmd '[id="' . $S->{id} . '"] move container to mark target';
|
||||||
|
sync_with_i3;
|
||||||
|
|
||||||
|
($nodes, $focus) = get_ws_content($target_ws);
|
||||||
|
is(@{$nodes}, 1, 'there is a tabbed container');
|
||||||
|
|
||||||
|
$nodes = $nodes->[0]->{nodes};
|
||||||
|
is(@{$nodes}, 4, 'all four containers are on the target workspace');
|
||||||
|
is($nodes->[0]->{window}, $A->{id}, 'A is the first tab');
|
||||||
|
is($nodes->[1]->{window}, $M->{id}, 'M is the second tab');
|
||||||
|
is($nodes->[2]->{window}, $S->{id}, 'S is the third tab');
|
||||||
|
is($nodes->[3]->{window}, $B->{id}, 'B is the fourth tab');
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Given 'S' and 'M' where 'M' is a tabbed container where the currently focused
|
||||||
|
# tab is a nested layout, when 'S' is moved to 'M', then 'S' is a new tab
|
||||||
|
# within 'M'.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$source_ws = fresh_workspace;
|
||||||
|
$S = open_window;
|
||||||
|
|
||||||
|
$target_ws = fresh_workspace;
|
||||||
|
$A = open_window;
|
||||||
|
cmd 'layout tabbed';
|
||||||
|
cmd 'focus parent';
|
||||||
|
cmd 'mark target';
|
||||||
|
cmd 'focus child';
|
||||||
|
$B = open_window;
|
||||||
|
cmd 'split h';
|
||||||
|
$F = open_window;
|
||||||
|
|
||||||
|
cmd '[id="' . $S->{id} . '"] move container to mark target';
|
||||||
|
sync_with_i3;
|
||||||
|
|
||||||
|
($nodes, $focus) = get_ws_content($target_ws);
|
||||||
|
is(@{$nodes}, 1, 'there is a tabbed container');
|
||||||
|
|
||||||
|
$nodes = $nodes->[0]->{nodes};
|
||||||
|
is(@{$nodes}, 3, 'there are three tabs');
|
||||||
|
|
||||||
|
is($nodes->[0]->{window}, $A->{id}, 'A is the first tab');
|
||||||
|
is($nodes->[2]->{window}, $S->{id}, 'S is the third tab');
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Given 'S' and 'M' where 'M' is inside a split container inside a tabbed
|
||||||
|
# container, when 'S' is moved to 'M', then 'S' ends up as a container
|
||||||
|
# within the same tab as 'M'.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$source_ws = fresh_workspace;
|
||||||
|
$S = open_window;
|
||||||
|
|
||||||
|
# open tabbed container ['A'['B' 'M']]
|
||||||
|
$target_ws = fresh_workspace;
|
||||||
|
$A = open_window;
|
||||||
|
cmd 'layout tabbed';
|
||||||
|
$B = open_window;
|
||||||
|
cmd 'split h';
|
||||||
|
$M = open_window;
|
||||||
|
cmd 'mark target';
|
||||||
|
|
||||||
|
cmd '[id="' . $S->{id} . '"] move container to mark target';
|
||||||
|
sync_with_i3;
|
||||||
|
|
||||||
|
($nodes, $focus) = get_ws_content($target_ws);
|
||||||
|
is(@{$nodes}, 1, 'there is a tabbed container');
|
||||||
|
|
||||||
|
$nodes = $nodes->[0]->{nodes};
|
||||||
|
is(@{$nodes}, 2, 'there are two tabs');
|
||||||
|
|
||||||
|
$nodes = $nodes->[1]->{nodes};
|
||||||
|
is(@{$nodes}, 3, 'the tab with the marked children has three children');
|
||||||
|
is($nodes->[0]->{window}, $B->{id}, 'B is the first tab');
|
||||||
|
is($nodes->[1]->{window}, $M->{id}, 'M is the second tab');
|
||||||
|
is($nodes->[2]->{window}, $S->{id}, 'S is the third tab');
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Given 'S', 'A' and 'B' where 'A' and 'B' are inside the tabbed container 'M',
|
||||||
|
# when 'S' is moved to 'M', then 'S' ends up as a new tab in 'M'.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$source_ws = fresh_workspace;
|
||||||
|
$S = open_window;
|
||||||
|
$target_ws = fresh_workspace;
|
||||||
|
$A = open_window;
|
||||||
|
cmd 'layout tabbed';
|
||||||
|
$B = open_window;
|
||||||
|
cmd 'focus parent';
|
||||||
|
cmd 'mark target';
|
||||||
|
cmd 'focus child';
|
||||||
|
|
||||||
|
cmd '[id="' . $S->{id} . '"] move container to mark target';
|
||||||
|
sync_with_i3;
|
||||||
|
|
||||||
|
($nodes, $focus) = get_ws_content($target_ws);
|
||||||
|
is(@{$nodes}, 1, 'there is a tabbed container');
|
||||||
|
|
||||||
|
$nodes = $nodes->[0]->{nodes};
|
||||||
|
is(@{$nodes}, 3, 'there are three tabs');
|
||||||
|
|
||||||
|
is($nodes->[0]->{window}, $A->{id}, 'A is the first tab');
|
||||||
|
is($nodes->[1]->{window}, $B->{id}, 'B is the second tab');
|
||||||
|
is($nodes->[2]->{window}, $S->{id}, 'S is the third tab');
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Given 'S', 'A', 'F' and 'M', where 'M' is a workspace containing a tabbed
|
||||||
|
# container, when 'S' is moved to 'M', then 'S' does not end up as a tab, but
|
||||||
|
# rather as a new window next to the tabbed container.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$source_ws = fresh_workspace;
|
||||||
|
$S = open_window;
|
||||||
|
$target_ws = fresh_workspace;
|
||||||
|
$A = open_window;
|
||||||
|
cmd 'layout tabbed';
|
||||||
|
$F = open_window;
|
||||||
|
$M = $target_ws;
|
||||||
|
cmd 'focus parent';
|
||||||
|
cmd 'focus parent';
|
||||||
|
cmd 'mark target';
|
||||||
|
cmd 'focus ' . $source_ws;
|
||||||
|
|
||||||
|
cmd '[id="' . $S->{id} . '"] move container to mark target';
|
||||||
|
sync_with_i3;
|
||||||
|
|
||||||
|
($nodes, $focus) = get_ws_content($target_ws);
|
||||||
|
is(@{$nodes}, 2, 'there is a tabbed container and a window');
|
||||||
|
is($nodes->[1]->{window}, $S->{id}, 'S is the second window');
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Given 'S' and 'M' where 'S' is floating and 'M' on a different workspace,
|
||||||
|
# when 'S' is moved to 'M', then 'S' is a floating container on the same
|
||||||
|
# workspaces as 'M'.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$source_ws = fresh_workspace;
|
||||||
|
$S = open_floating_window;
|
||||||
|
$target_ws = fresh_workspace;
|
||||||
|
$M = open_window;
|
||||||
|
cmd 'mark target';
|
||||||
|
|
||||||
|
cmd '[id="' . $S->{id} . '"] move container to mark target';
|
||||||
|
sync_with_i3;
|
||||||
|
|
||||||
|
is(@{get_ws($target_ws)->{floating_nodes}}, 1, 'target workspace has the container now');
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Given 'S' and 'M' where 'M' is floating and on a different workspace,
|
||||||
|
# when 'S' is moved to 'M', then 'S' ends up as a tiling container on the
|
||||||
|
# same workspace as 'M'.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$source_ws = fresh_workspace;
|
||||||
|
$S = open_window;
|
||||||
|
$target_ws = fresh_workspace;
|
||||||
|
$M = open_floating_window;
|
||||||
|
cmd 'mark target';
|
||||||
|
|
||||||
|
cmd '[id="' . $S->{id} . '"] move container to mark target';
|
||||||
|
sync_with_i3;
|
||||||
|
|
||||||
|
($nodes, $focus) = get_ws_content($target_ws);
|
||||||
|
is(@{$nodes}, 1, 'tiling container moved to the target workspace');
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Given 'S' and 'M' are the same container, when 'S' is moved to 'M', then
|
||||||
|
# the command is ignored.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
$ws = fresh_workspace;
|
||||||
|
$S = open_window;
|
||||||
|
$M = $S;
|
||||||
|
cmd 'mark target';
|
||||||
|
|
||||||
|
cmd '[id="' . $S->{id} . '"] move container to mark target';
|
||||||
|
sync_with_i3;
|
||||||
|
|
||||||
|
does_i3_live;
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
done_testing;
|
Loading…
Reference in New Issue