# defaults to 10 pixels.
move <left|right|down|up> [<px> px]
-# Moves the container either to a specific location
-# or to the center of the screen. If 'absolute' is
-# used, it is moved to the center of all outputs.
-move [absolute] position <pos_x> [px] <pos_y> [px]
+# Moves the container to the specified pos_x and pos_y
+# coordinates on the screen.
+move position <pos_x> [px] <pos_y> [px]
+
+# Moves the container to the center of the screen.
+# If 'absolute' is used, it is moved to the center of
+# all outputs.
move [absolute] position center
# Moves the container to the current position of the
RandR output.
[[move_to_outputs]]
-[[_moving_containers_workspaces_to_randr_outputs]]
-=== Moving containers/workspaces to RandR outputs
+=== [[_moving_containers_workspaces_to_randr_outputs]]Moving containers/workspaces to RandR outputs
To move a container to another RandR output (addressed by names like +LVDS1+ or
+VGA1+) or to a RandR output identified by a specific direction (like +left+,
static int reply_end_map_cb(void *params) {
if (!last_reply.success) {
- fprintf(stderr, "ERROR: Your command: %s\n", last_reply.input);
- fprintf(stderr, "ERROR: %s\n", last_reply.errorposition);
+ if (last_reply.input) {
+ fprintf(stderr, "ERROR: Your command: %s\n", last_reply.input);
+ fprintf(stderr, "ERROR: %s\n", last_reply.errorposition);
+ }
fprintf(stderr, "ERROR: %s\n", last_reply.error);
}
return 1;
# We welcome patches that add distribution-specific mechanisms to find the
# preferred terminal emulator. On Debian, there is the x-terminal-emulator
# symlink for example.
-for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda; do
+for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty; do
if command -v "$terminal" > /dev/null 2>&1; then
exec "$terminal" "$@"
fi
* Implementation of 'move [window|container] [to] [absolute] position <px> [px] <px> [px]
*
*/
-void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y);
+void cmd_move_window_to_position(I3_CMD, long x, long y);
/**
* Implementation of 'move [window|container] [to] [absolute] position center
/** the criteria to check if a window matches */
Match match;
- /** destination workspace/command, depending on the type */
+ /** destination workspace/command/output, depending on the type */
union {
char *command;
char *workspace;
* outputs.
*
*/
-void floating_reposition(Con *con, Rect newrect);
+bool floating_reposition(Con *con, Rect newrect);
/**
* Sets size of the CT_FLOATING_CON to specified dimensions. Might limit the
* kitty
* guake
* tilda
+* alacritty
Please don’t complain about the order: If the user has any preference, they will
have $TERMINAL set or modified their i3 configuration file.
state MOVE_TO_POSITION_Y:
'px', end
- -> call cmd_move_window_to_position($method, &coord_x, &coord_y)
+ -> call cmd_move_window_to_position(&coord_x, &coord_y)
# mode <string>
state MODE:
/* Check if any assignments match */
Assignment *current;
TAILQ_FOREACH(current, &assignments, assignments) {
- if (!match_matches_window(&(current->match), window))
+ if (current->type != A_COMMAND || !match_matches_window(&(current->match), window))
continue;
bool skip = false;
window->ran_assignments = srealloc(window->ran_assignments, sizeof(Assignment *) * window->nr_assignments);
window->ran_assignments[window->nr_assignments - 1] = current;
- DLOG("matching assignment, would do:\n");
- if (current->type == A_COMMAND) {
- DLOG("execute command %s\n", current->dest.command);
- char *full_command;
- sasprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command);
- CommandResult *result = parse_command(full_command, NULL);
- free(full_command);
+ DLOG("matching assignment, execute command %s\n", current->dest.command);
+ char *full_command;
+ sasprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command);
+ CommandResult *result = parse_command(full_command, NULL);
+ free(full_command);
- if (result->needs_tree_render)
- needs_tree_render = true;
+ if (result->needs_tree_render)
+ needs_tree_render = true;
- command_result_free(result);
- }
+ command_result_free(result);
}
/* If any of the commands required re-rendering, we will do that now. */
* Implementation of 'move [window|container] [to] [absolute] position <px> [px] <px> [px]
*
*/
-void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y) {
+void cmd_move_window_to_position(I3_CMD, long x, long y) {
bool has_error = false;
owindow *current;
continue;
}
- if (strcmp(method, "absolute") == 0) {
- current->con->parent->rect.x = x;
- current->con->parent->rect.y = y;
+ Rect newrect = current->con->parent->rect;
- DLOG("moving to absolute position %ld %ld\n", x, y);
- floating_maybe_reassign_ws(current->con->parent);
- cmd_output->needs_tree_render = true;
- }
+ DLOG("moving to position %ld %ld\n", x, y);
+ newrect.x = x;
+ newrect.y = y;
- if (strcmp(method, "position") == 0) {
- Rect newrect = current->con->parent->rect;
-
- DLOG("moving to position %ld %ld\n", x, y);
- newrect.x = x;
- newrect.y = y;
-
- floating_reposition(current->con->parent, newrect);
+ if (!floating_reposition(current->con->parent, newrect)) {
+ yerror("Cannot move window/container out of bounds.");
+ has_error = true;
}
}
- // XXX: default reply for now, make this a better reply
if (!has_error)
ysuccess(true);
}
FREE(assign->dest.workspace);
else if (assign->type == A_COMMAND)
FREE(assign->dest.command);
+ else if (assign->type == A_TO_OUTPUT)
+ FREE(assign->dest.output);
match_free(&(assign->match));
TAILQ_REMOVE(&assignments, assign, assignments);
FREE(assign);
FREE(barconfig);
}
- /* Invalidate pixmap caches in case font or colors changed */
Con *con;
- TAILQ_FOREACH(con, &all_cons, all_cons)
- FREE(con->deco_render_params);
+ TAILQ_FOREACH(con, &all_cons, all_cons) {
+ /* Assignments changed, previously ran assignments are invalid. */
+ if (con->window) {
+ con->window->nr_assignments = 0;
+ FREE(con->window->ran_assignments);
+ }
+ /* Invalidate pixmap caches in case font or colors changed. */
+ FREE(con->deco_render_params);
+ }
/* Get rid of the current font */
free_font();
* outputs.
*
*/
-void floating_reposition(Con *con, Rect newrect) {
+bool floating_reposition(Con *con, Rect newrect) {
/* Sanity check: Are the new coordinates on any output? If not, we
* ignore that request. */
if (!contained_by_output(newrect)) {
ELOG("No output found at destination coordinates. Not repositioning.\n");
- return;
+ return false;
}
con->rect = newrect;
con->scratchpad_state = SCRATCHPAD_CHANGED;
tree_render();
+ return true;
}
/*
typedef enum { BEFORE,
AFTER } position_t;
+/*
+ * Returns the lowest container in the tree that has both a and b as descendants.
+ *
+ */
+static Con *lowest_common_ancestor(Con *a, Con *b) {
+ Con *parent_a = a;
+ while (parent_a) {
+ Con *parent_b = b;
+ while (parent_b) {
+ if (parent_a == parent_b) {
+ return parent_a;
+ }
+ parent_b = parent_b->parent;
+ }
+ parent_a = parent_a->parent;
+ }
+ assert(false);
+}
+
+/*
+ * Returns the direct child of ancestor that contains con.
+ *
+ */
+static Con *child_containing_con_recursively(Con *ancestor, Con *con) {
+ Con *child = con;
+ while (child && child->parent != ancestor) {
+ child = child->parent;
+ assert(child->parent);
+ }
+ return child;
+}
+
+/*
+ * Returns true if the given container is the focused descendant of ancestor, recursively.
+ *
+ */
+static bool is_focused_descendant(Con *con, Con *ancestor) {
+ Con *current = con;
+ while (current != ancestor) {
+ if (TAILQ_FIRST(&(current->parent->focus_head)) != current) {
+ return false;
+ }
+ current = current->parent;
+ assert(current->parent);
+ }
+ return true;
+}
+
/*
* This function detaches 'con' from its parent and inserts it either before or
* after 'target'.
* afterwards which might then close the con if it is empty. */
Con *old_parent = con->parent;
+ /* We compare the focus order of the children of the lowest common ancestor. If con or
+ * its ancestor is before target's ancestor then con should be placed before the target
+ * in the focus stack. */
+ Con *lca = lowest_common_ancestor(con, parent);
+ if (lca == con) {
+ ELOG("Container is being inserted into one of its descendants.\n");
+ return;
+ }
+
+ Con *con_ancestor = child_containing_con_recursively(lca, con);
+ Con *target_ancestor = child_containing_con_recursively(lca, target);
+ bool moves_focus_from_ancestor = is_focused_descendant(con, con_ancestor);
+ bool focus_before;
+
+ /* Determine if con is going to be placed before or after target in the parent's focus stack. */
+ if (con_ancestor == target_ancestor) {
+ /* Happens when the target is con's old parent. Eg with layout V [ A H [ B C ] ],
+ * if we move C up. Target will be H. */
+ focus_before = moves_focus_from_ancestor;
+ } else {
+ /* Look at the focus stack order of the children of the lowest common ancestor. */
+ Con *current;
+ TAILQ_FOREACH(current, &(lca->focus_head), focused) {
+ if (current == con_ancestor || current == target_ancestor) {
+ break;
+ }
+ }
+ focus_before = (current == con_ancestor);
+ }
+
+ /* If con is the focused container in our old ancestor we place the new ancestor
+ * before the old ancestor in the focus stack. Example:
+ * Consider the layout [ H [ V1 [ A* B ] V2 [ C ] ] ] where A is focused. We move to
+ * a second workspace and from there we move A to the right and switch back to the
+ * original workspace. Without the change focus would move to B instead of staying
+ * with A. */
+ if (moves_focus_from_ancestor && focus_before) {
+ Con *place = TAILQ_PREV(con_ancestor, focus_head, focused);
+ TAILQ_REMOVE(&(lca->focus_head), target_ancestor, focused);
+ if (place) {
+ TAILQ_INSERT_AFTER(&(lca->focus_head), place, target_ancestor, focused);
+ } else {
+ TAILQ_INSERT_HEAD(&(lca->focus_head), target_ancestor, focused);
+ }
+ }
+
con_detach(con);
con_fix_percent(con->parent);
con->parent = parent;
+ if (parent == lca) {
+ if (focus_before) {
+ /* Example layout: H [ A B* ], we move A up/down. 'target' will be H. */
+ TAILQ_INSERT_BEFORE(target, con, focused);
+ } else {
+ /* Example layout: H [ A B* ], we move A up/down. 'target' will be H. */
+ TAILQ_INSERT_AFTER(&(parent->focus_head), target, con, focused);
+ }
+ } else {
+ if (focus_before) {
+ /* Example layout: V [ H [ A B ] C* ], we move C up. 'target' will be A. */
+ TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
+ } else {
+ /* Example layout: V [ H [ A* B ] C ], we move C up. 'target' will be A. */
+ TAILQ_INSERT_TAIL(&(parent->focus_head), con, focused);
+ }
+ }
+
if (position == BEFORE) {
TAILQ_INSERT_BEFORE(target, con, nodes);
- TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
} else if (position == AFTER) {
TAILQ_INSERT_AFTER(&(parent->nodes_head), target, con, nodes);
- TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
}
/* Pretend the con was just opened with regards to size percent values.
}
end:
- /* We need to call con_focus() to fix the focus stack "above" the container
- * we just inserted the focused container into (otherwise, the parent
- * container(s) would still point to the old container(s)). */
- con_focus(con);
-
/* force re-painting the indicators */
FREE(con->deco_render_params);
ok(!$source_ws->{urgent}, 'Source workspace is no longer marked urgent');
is($target_ws->{urgent}, 1, 'Target workspace is now marked urgent');
+##############################################################################
+# Test that moving an unfocused container doesn't reset its urgency hint.
+##############################################################################
+ $tmp = fresh_workspace;
+ $win1 = open_window;
+ $win2 = open_window;
+ cmd 'split v';
+ $win3 = open_window;
+ set_urgency($win1, 1, $type);
+ sync_with_i3;
+
+ my $win1_info;
+
+ @content = @{get_ws_content($tmp)};
+ $win1_info = first { $_->{window} == $win1->id } @content;
+ ok($win1_info->{urgent}, 'win1 window is marked urgent');
+
+ cmd '[id="' . $win1->id . '"] move right';
+ cmd '[id="' . $win1->id . '"] move right';
+ @content = @{get_ws_content($tmp)};
+ $win1_info = first { $_->{window} == $win1->id } @content;
+ ok($win1_info->{urgent}, 'win1 window is still marked urgent after moving');
+
+ cmd '[id="' . $win1->id . '"] focus';
+ @content = @{get_ws_content($tmp)};
+ $win1_info = first { $_->{window} == $win1->id } @content;
+ ok(!$win1_info->{urgent}, 'win1 window is not marked urgent after focusing');
+
##############################################################################
exit_gracefully($pid);
cmd 'move left';
confirm_focus('split-v + move');
+######################################################################
+# Test that moving an unfocused container maintains the correct focus
+# order.
+# Layout: H [ A V1 [ B C D ] ]
+######################################################################
+
+fresh_workspace;
+$windows[3] = open_window;
+$windows[2] = open_window;
+cmd 'split v';
+$windows[1] = open_window;
+$windows[0] = open_window;
+
+cmd '[id=' . $windows[3]->id . '] move right';
+confirm_focus('split-v + unfocused move');
+
done_testing;