# 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
xmacro(_NET_WM_STATE_DEMANDS_ATTENTION)
xmacro(_NET_WM_STATE_MODAL)
xmacro(_NET_WM_STATE_HIDDEN)
+xmacro(_NET_WM_STATE_FOCUSED)
xmacro(_NET_WM_STATE)
xmacro(_NET_WM_WINDOW_TYPE)
xmacro(_NET_WM_WINDOW_TYPE_NORMAL)
* 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
*/
void ewmh_update_sticky(xcb_window_t window, bool sticky);
+/**
+ * Set or remove _NEW_WM_STATE_FOCUSED on the window.
+ *
+ */
+void ewmh_update_focused(xcb_window_t window, bool is_focused);
+
/**
* Set up the EWMH hints on the root window.
*
* 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
*/
Output *get_output_containing(unsigned int x, unsigned int y);
+/**
+ * Returns the active output which contains the midpoint of the given rect. If
+ * such an output doesn't exist, returns the output which contains most of the
+ * rectangle or NULL if there is no output which intersects with it.
+ *
+ */
+Output *get_output_from_rect(Rect rect);
+
/**
* Returns the active output which spans exactly the area specified by
* rect or NULL if there is no output like this.
*/
Output *get_output_with_dimensions(Rect rect);
-/*
- * In contained_by_output, we check if any active output contains part of the container.
+/**
+ * In output_containing_rect, we check if any active output contains part of the container.
* We do this by checking if the output rect is intersected by the Rect.
* This is the 2-dimensional counterpart of get_output_containing.
- * Since we don't actually need the outputs intersected by the given Rect (There could
- * be many), we just return true or false for convenience.
+ * Returns the output with the maximum intersecting area.
*
*/
-bool contained_by_output(Rect rect);
+Output *output_containing_rect(Rect rect);
/**
* Gets the output which is the next one in the given direction.
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:
*
*/
void cmd_append_layout(I3_CMD, const char *cpath) {
- char *path = sstrdup(cpath);
- LOG("Appending layout \"%s\"\n", path);
+ LOG("Appending layout \"%s\"\n", cpath);
/* Make sure we allow paths like '~/.i3/layout.json' */
- path = resolve_tilde(path);
+ char *path = resolve_tilde(cpath);
char *buf = NULL;
ssize_t len;
* 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);
}
/* 1: save the container which is going to be focused after the current
* container is moved away */
- Con *focus_next = con_next_focused(con);
+ Con *focus_next = NULL;
+ if (!ignore_focus && source_ws == current_ws) {
+ focus_next = con_descend_focused(source_ws);
+ if (focus_next == con || con_has_parent(focus_next, con)) {
+ focus_next = con_next_focused(con);
+ }
+ }
/* 2: we go up one level, but only when target is a normal container */
if (target->type != CT_WORKSPACE) {
floating_fix_coordinates(con, &(source_output->rect), &(dest_output->rect));
} else
DLOG("Not fixing coordinates, fix_coordinates flag = %d\n", fix_coordinates);
-
- /* If moving to a visible workspace, call show so it can be considered
- * focused. Must do before attaching because workspace_show checks to see
- * if focused container is in its area. */
- if (!ignore_focus && workspace_is_visible(target_ws)) {
- workspace_show(target_ws);
-
- /* Don’t warp if told so (when dragging floating windows with the
- * mouse for example) */
- if (dont_warp)
- x_set_warp_to(NULL);
- else
- x_set_warp_to(&(con->rect));
- }
}
/* If moving a fullscreen container and the destination already has a
/* Restore focus if the output's focused workspace has changed. */
if (con_get_workspace(focused) != old_focus)
- con_activate(old_focus);
+ con_focus(old_focus);
}
/* 7: when moving to another workspace, we leave the focus on the current
* workspace. (see also #809) */
-
- /* Descend focus stack in case focus_next is a workspace which can
- * occur if we move to the same workspace. Also show current workspace
- * to ensure it is focused. */
if (!ignore_focus) {
workspace_show(current_ws);
if (dont_warp) {
/* Set focus only if con was on current workspace before moving.
* Otherwise we would give focus to some window on different workspace. */
- if (!ignore_focus && source_ws == current_ws)
+ if (focus_next)
con_activate(con_descend_focused(focus_next));
/* 8. If anything within the container is associated with a startup sequence,
* now let's activate the current layout (next in list) */
if (current_layout_found) {
new_layout = layout;
- free(tm_dup);
break;
}
current_layout_found = true;
}
}
+ free(tm_dup);
if (new_layout != L_DEFAULT) {
con_set_layout(con, new_layout);
struct Assignment *assign;
while (!TAILQ_EMPTY(&assignments)) {
assign = TAILQ_FIRST(&assignments);
- if (assign->type == A_TO_WORKSPACE)
+ if (assign->type == A_TO_WORKSPACE || assign->type == A_TO_WORKSPACE_NUMBER)
FREE(assign->dest.workspace);
else if (assign->type == A_COMMAND)
FREE(assign->dest.command);
* don’t have assignments of a single workspace to different
* outputs */
struct Workspace_Assignment *assignment;
- bool duplicate = false;
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
if (strcasecmp(assignment->name, workspace) == 0) {
ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
workspace);
- assignment->output = sstrdup(output);
- duplicate = true;
+ return;
}
}
- if (!duplicate) {
- assignment = scalloc(1, sizeof(struct Workspace_Assignment));
- assignment->name = sstrdup(workspace);
- assignment->output = sstrdup(output);
- TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
- }
+
+ assignment = scalloc(1, sizeof(struct Workspace_Assignment));
+ assignment->name = sstrdup(workspace);
+ assignment->output = sstrdup(output);
+ TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
}
CFGFUN(ipc_socket, const char *path) {
}
}
+/*
+ * Set or remove _NEW_WM_STATE_FOCUSED on the window.
+ *
+ */
+void ewmh_update_focused(xcb_window_t window, bool is_focused) {
+ if (is_focused) {
+ DLOG("Setting _NET_WM_STATE_FOCUSED for window = %d.\n", window);
+ xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
+ } else {
+ DLOG("Removing _NET_WM_STATE_FOCUSED for window = %d.\n", window);
+ xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
+ }
+}
+
/*
* Set up the EWMH hints on the root window.
*
/* Sanity check: Are the coordinates on the appropriate output? If not, we
* need to change them */
- Output *current_output = get_output_containing(nc->rect.x +
- (nc->rect.width / 2),
- nc->rect.y + (nc->rect.height / 2));
-
+ Output *current_output = get_output_from_rect(nc->rect);
Con *correct_output = con_get_output(ws);
if (!current_output || current_output->con != correct_output) {
DLOG("This floating window is on the wrong output, fixing coordinates (currently (%d, %d))\n",
/* If moving from one output to another, keep the relative position
* consistent (e.g. a centered dialog will remain centered). */
- if (current_output)
+ if (current_output) {
floating_fix_coordinates(nc, ¤t_output->con->rect, &correct_output->rect);
- else {
- nc->rect.x = correct_output->rect.x;
- nc->rect.y = correct_output->rect.y;
+ /* Make sure that the result is in the correct output. */
+ current_output = get_output_from_rect(nc->rect);
+ }
+ if (!current_output || current_output->con != correct_output) {
+ floating_center(nc, ws->rect);
}
}
if (set_focus)
con_activate(con);
- /* Check if we need to re-assign it to a different workspace because of its
- * coordinates and exit if that was done successfully. */
- if (floating_maybe_reassign_ws(nc)) {
- goto done;
- }
-
- /* Sanitize coordinates: Check if they are on any output */
- if (get_output_containing(nc->rect.x, nc->rect.y) != NULL) {
- goto done;
- }
-
- ELOG("No output found at destination coordinates, centering floating window on current ws\n");
- floating_center(nc, ws->rect);
-
-done:
floating_set_hint_atom(nc, true);
ipc_send_window_event("floating", con);
}
*
*/
bool floating_maybe_reassign_ws(Con *con) {
- Output *output = get_output_containing(
- con->rect.x + (con->rect.width / 2),
- con->rect.y + (con->rect.height / 2));
+ Output *output = get_output_from_rect(con->rect);
if (!output) {
ELOG("No output found at destination coordinates?\n");
const void *extra;
};
-static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
- struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
+static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) {
xcb_motion_notify_event_t *last_motion_notify = NULL;
xcb_generic_event_t *event;
if (dragloop->result != DRAGGING) {
free(last_motion_notify);
- return;
+ ev_break(EV_A_ EVBREAK_ONE);
+ return true;
}
}
- if (last_motion_notify == NULL)
- return;
+ if (last_motion_notify == NULL) {
+ return true;
+ }
/* Ensure that we are either dragging the resize handle (con is NULL) or that the
* container still exists. The latter might not be true, e.g., if the window closed
last_motion_notify->root_y,
dragloop->extra);
}
- free(last_motion_notify);
+ FREE(last_motion_notify);
xcb_flush(conn);
+ return false;
+}
+
+static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
+ struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
+ while (!drain_drag_events(EV_A, dragloop)) {
+ /* repeatedly drain events: draining might produce additional ones */
+ }
}
/*
main_set_x11_cb(false);
ev_prepare_start(main_loop, prepare);
- while (loop.result == DRAGGING)
- ev_run(main_loop, EVRUN_ONCE);
+ ev_loop(main_loop, 0);
ev_prepare_stop(main_loop, prepare);
main_set_x11_cb(true);
* 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)) {
+ if (!output_containing_rect(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;
}
/*
return;
}
- if (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1) {
+ if (con->fullscreen_mode == CF_GLOBAL) {
+ DLOG("Not moving fullscreen global container\n");
+ return;
+ }
+
+ if ((con->fullscreen_mode == CF_OUTPUT) ||
+ (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1)) {
/* This is the only con on this workspace */
move_to_output_directed(con, direction);
return;
return NULL;
}
+/*
+ * Returns the active output which contains the midpoint of the given rect. If
+ * such an output doesn't exist, returns the output which contains most of the
+ * rectangle or NULL if there is no output which intersects with it.
+ *
+ */
+Output *get_output_from_rect(Rect rect) {
+ unsigned int mid_x = rect.x + rect.width / 2;
+ unsigned int mid_y = rect.y + rect.height / 2;
+ Output *output = get_output_containing(mid_x, mid_y);
+
+ return output ? output : output_containing_rect(rect);
+}
+
/*
* Returns the active output which spans exactly the area specified by
* rect or NULL if there is no output like this.
}
/*
- * In contained_by_output, we check if any active output contains part of the container.
+ * In output_containing_rect, we check if any active output contains part of the container.
* We do this by checking if the output rect is intersected by the Rect.
* This is the 2-dimensional counterpart of get_output_containing.
- * Since we don't actually need the outputs intersected by the given Rect (There could
- * be many), we just return true or false for convenience.
+ * Returns the output with the maximum intersecting area.
*
*/
-bool contained_by_output(Rect rect) {
+Output *output_containing_rect(Rect rect) {
Output *output;
int lx = rect.x, uy = rect.y;
int rx = rect.x + rect.width, by = rect.y + rect.height;
+ long max_area = 0;
+ Output *result = NULL;
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
continue;
+ int lx_o = (int)output->rect.x, uy_o = (int)output->rect.y;
+ int rx_o = (int)(output->rect.x + output->rect.width), by_o = (int)(output->rect.y + output->rect.height);
DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
rect.x, rect.y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
- if (rx >= (int)output->rect.x && lx <= (int)(output->rect.x + output->rect.width) &&
- by >= (int)output->rect.y && uy <= (int)(output->rect.y + output->rect.height))
- return true;
+ int left = max(lx, lx_o);
+ int right = min(rx, rx_o);
+ int bottom = min(by, by_o);
+ int top = max(uy, uy_o);
+ if (left < right && bottom > top) {
+ long area = (right - left) * (bottom - top);
+ if (area > max_area) {
+ result = output;
+ }
+ }
}
- return false;
+ return result;
}
/*
if (!sequence) {
DLOG("Sequence already deleted, nevermind.\n");
+ free(w);
return;
}
if (!workspace)
return false;
- Con *focus = con_descend_tiling_focused(workspace);
- if (focus == workspace) {
- focus = con_descend_focused(workspace);
+ /* Use descend_focused first to give higher priority to floating or
+ * tiling fullscreen containers. */
+ Con *focus = con_descend_focused(workspace);
+ if (focus->fullscreen_mode == CF_NONE) {
+ Con *focus_tiling = con_descend_tiling_focused(workspace);
+ /* If descend_tiling returned a workspace then focus is either a
+ * floating container or the same workspace. */
+ if (focus_tiling != workspace) {
+ focus = focus_tiling;
+ }
}
workspace_show(workspace);
/*
* Callback to reset the urgent flag of the given con to false. May be started by
- * _workspace_show to avoid urgency hints being lost by switching to a workspace
+ * workspace_show to avoid urgency hints being lost by switching to a workspace
* focusing the con.
*
*/
}
}
-static void _workspace_show(Con *workspace) {
+/*
+ * Switches to the given workspace
+ *
+ */
+void workspace_show(Con *workspace) {
Con *current, *old = NULL;
Con *old_focus = focused;
* focused) are skipped, see bug #868. */
if (current && !con_is_internal(current)) {
FREE(previous_workspace_name);
- if (current) {
- previous_workspace_name = sstrdup(current->name);
- DLOG("Setting previous_workspace_name = %s\n", previous_workspace_name);
- }
+ previous_workspace_name = sstrdup(current->name);
+ DLOG("Setting previous_workspace_name = %s\n", previous_workspace_name);
}
workspace_reassign_sticky(workspace);
output_push_sticky_windows(old_focus);
}
-/*
- * Switches to the given workspace
- *
- */
-void workspace_show(Con *workspace) {
- _workspace_show(workspace);
-}
-
/*
* Looks up the workspace by name and switches to it.
*
void workspace_show_by_name(const char *num) {
Con *workspace;
workspace = workspace_get(num, NULL);
- _workspace_show(workspace);
+ workspace_show(workspace);
}
/*
return NULL;
}
+/*
+ * Changes the atoms on the root window and the windows themselves to properly
+ * reflect the current focus for ewmh compliance.
+ *
+ */
+static void change_ewmh_focus(xcb_window_t new_focus, xcb_window_t old_focus) {
+ ewmh_update_active_window(new_focus);
+
+ if (new_focus != XCB_WINDOW_NONE) {
+ ewmh_update_focused(new_focus, true);
+ }
+
+ if (old_focus != XCB_WINDOW_NONE) {
+ ewmh_update_focused(old_focus, false);
+ }
+}
+
/*
* Initializes the X11 part for the given container. Called exactly once for
* every container from con_new().
to_focus, focused, focused->name);
send_take_focus(to_focus, last_timestamp);
- ewmh_update_active_window((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE));
+ change_ewmh_focus((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE), last_focused);
if (to_focus != last_focused && is_con_attached(focused))
ipc_send_window_event("focus", focused);
xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values);
}
- ewmh_update_active_window((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE));
+ change_ewmh_focus((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE), last_focused);
if (to_focus != XCB_NONE && to_focus != last_focused && focused->window != NULL && is_con_attached(focused))
ipc_send_window_event("focus", focused);
* root window in order to avoid an X11 fallback mechanism causing a ghosting effect (see #1378). */
DLOG("Still no window focused, better set focus to the EWMH support window (%d)\n", ewmh_window);
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, ewmh_window, last_timestamp);
- ewmh_update_active_window(XCB_WINDOW_NONE);
+ change_ewmh_focus(XCB_WINDOW_NONE, last_focused);
+
focused_id = ewmh_window;
}
kill_all_windows
events_for
listen_for_binding
+ is_net_wm_state_focused
);
=head1 NAME
return $command;
}
+=head2 is_net_wm_state_focused
+
+Returns true if the given window has the _NET_WM_STATE_FOCUSED atom.
+
+ ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
+
+=cut
+sub is_net_wm_state_focused {
+ my ($window) = @_;
+
+ sync_with_i3;
+ my $atom = $x->atom(name => '_NET_WM_STATE_FOCUSED');
+ my $cookie = $x->get_property(
+ 0,
+ $window->{id},
+ $x->atom(name => '_NET_WM_STATE')->id,
+ GET_PROPERTY_TYPE_ANY,
+ 0,
+ 4096
+ );
+
+ my $reply = $x->get_property_reply($cookie->{sequence});
+ my $len = $reply->{length};
+ return 0 if $len == 0;
+
+ my @atoms = unpack("L$len", $reply->{value});
+ for (my $i = 0; $i < $len; $i++) {
+ return 1 if $atoms[$i] == $atom->id;
+ }
+
+ return 0;
+}
+
+
=head1 AUTHOR
Michael Stapelberg <michael@i3wm.org>
my $window = open_window;
ok(!recv_take_focus($window), 'did not receive ClientMessage');
+ ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
my ($nodes) = get_ws_content($ws);
my $con = shift @$nodes;
$window->map;
ok(!recv_take_focus($window), 'did not receive ClientMessage');
+ ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
my ($nodes) = get_ws_content($ws);
my $con = shift @$nodes;
my $window = open_window({ protocols => [ $take_focus ] });
ok(!recv_take_focus($window), 'did not receive ClientMessage');
+ ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
my ($nodes) = get_ws_content($ws);
my $con = shift @$nodes;
return undef if $len == 0;
my @atoms = unpack("L$len", $reply->{value});
- return \@atoms;
+ return @atoms;
}
my $wm_state_sticky = $x->atom(name => '_NET_WM_STATE_STICKY')->id;
fresh_workspace;
my $window = open_window;
cmd 'sticky enable';
-is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'sanity check: _NET_WM_STATE_STICKY is set');
+my @state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, 'sanity check: _NET_WM_STATE_STICKY is set');
cmd 'fullscreen enable';
-is_deeply(get_wm_state($window), [ $wm_state_sticky, $wm_state_fullscreen ],
- 'both _NET_WM_STATE_FULLSCREEN and _NET_WM_STATE_STICKY are set');
+@state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set');
+ok((scalar grep { $_ == $wm_state_fullscreen } @state) > 0, '_NET_WM_STATE_FULLSCREEN is set');
cmd 'sticky disable';
-is_deeply(get_wm_state($window), [ $wm_state_fullscreen ], 'only _NET_WM_STATE_FULLSCREEN is set');
+@state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) == 0, '_NET_WM_STATE_STICKY is not set');
+ok((scalar grep { $_ == $wm_state_fullscreen } @state) > 0, '_NET_WM_STATE_FULLSCREEN is set');
cmd 'sticky enable';
cmd 'fullscreen disable';
-is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'only _NET_WM_STATE_STICKY is set');
+@state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set');
+ok((scalar grep { $_ == $wm_state_fullscreen } @state) == 0, '_NET_WM_STATE_FULLSCREEN is not set');
###############################################################################
# _NET_WM_STATE is removed when the window is withdrawn.
fresh_workspace;
$window = open_window;
cmd 'sticky enable';
-is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'sanity check: _NET_WM_STATE_STICKY is set');
+@state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set');
$window->unmap;
wait_for_unmap($window);
}
my @windows;
+my $ws;
sub focus_windows {
for (my $i = $#windows; $i >= 0; $i--) {
cmd '[id=' . $windows[3]->id . '] move right';
confirm_focus('split-v + unfocused move');
+######################################################################
+# Test that moving an unfocused container from inside a split
+# container to another workspace doesn't focus sibling.
+######################################################################
+
+$ws = fresh_workspace;
+$windows[0] = open_window;
+$windows[1] = open_window;
+cmd 'split v';
+open_window;
+cmd 'mark a';
+
+cmd '[id=' . $windows[0]->id . '] focus';
+cmd '[con_mark=a] move to workspace ' . get_unused_workspace;
+
+is(@{get_ws_content($ws)}, 2, 'Sanity check: marked window moved');
+confirm_focus('Move unfocused window from split container');
+
done_testing;
--- /dev/null
+#!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 setting and removing the _NET_WM_STATE_FOCUSED atom properly.
+# Ticket: #2273
+use i3test;
+use X11::XCB qw(:all);
+
+my ($windowA, $windowB);
+
+fresh_workspace;
+$windowA = open_window;
+
+ok(is_net_wm_state_focused($windowA), 'a newly opened window that is focused should have _NET_WM_STATE_FOCUSED set');
+
+$windowB = open_window;
+
+ok(!is_net_wm_state_focused($windowA), 'when a another window is focused, the old window should not have _NET_WM_STATE_FOCUSED set');
+
+fresh_workspace;
+
+ok(!is_net_wm_state_focused($windowB), 'when focus moves to the ewmh support window, neither window should have _NET_WM_STATE_FOCUSED set');
+
+done_testing;
--- /dev/null
+#!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 that directional focus gives focus to floating fullscreen containers when
+# switching workspaces.
+# Ticket: #3201
+# Bug still in: 4.15-59-gb849fe3e
+use i3test i3_config => <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0
+EOT
+
+fresh_workspace(output => 0);
+my $ws = fresh_workspace(output => 1);
+open_window;
+open_floating_window;
+cmd 'fullscreen enable';
+my $expected_focus = get_focused($ws);
+
+cmd 'focus left';
+cmd 'focus right';
+
+is (get_focused($ws), $expected_focus, 'floating fullscreen window focused after directional focus');
+
+done_testing;
$compare_window = shift @{get_ws_content('left-top')};
is($social_window->name, $compare_window->{name}, 'moved correct window to left-bottom workspace');
+#####################################################################
+# Moving a fullscreen container should change its output.
+#####################################################################
+
+kill_all_windows;
+
+cmd 'workspace left-top';
+open_window;
+my $fs_window = open_window;
+open_window;
+
+cmd '[id=' . $fs_window->id . '] fullscreen enable, move right';
+is(scalar @{get_ws_content('right-top')}, 1, 'moved fullscreen window to right-top workspace');
+
+#####################################################################
+# Moving a global fullscreen container should not change its output.
+#####################################################################
+
+kill_all_windows;
+
+cmd 'workspace left-top';
+open_window;
+open_window;
+open_window;
+
+cmd 'fullscreen global, move right, fullscreen disable';
+is(scalar @{get_ws_content('right-top')}, 0, 'global fullscreen window didn\'t change workspace with move');
+
done_testing;