i3-msg 'debuglog on; shmlog on; reload'
---------------------------------------
+== Reproducing the problem
+
+Before submitting an issue, please make sure to close down on the problem as
+much as you can yourself. Here are some steps you should consider:
+
+* Find a deterministic, reliable way to reproduce the problem and provide it
+ with your bug report.
+* Try using the default i3 config to reproduce the problem. If the issue does
+ not appear with the default config, gradually adapt it to track down what
+ change(s) to the config introduce the problem.
+* Reproduce the problem with a minimal setup, i.e., only use as few applications,
+ windows and steps as necessary.
+* In addition, try to stick to applications that are common and, even more
+ importantly, free / open source.
+* Before obtaining the log file, restart i3 in-place, execute the steps to
+ reproduce the problem and then save the logs. This keeps the log file as
+ small as possible and necessary.
+
+Please be aware that we cannot support compatibility issues with closed-source
+software, as digging into compatibility problems without having access to the
+source code is too time-consuming. Additionally, experience has shown that
+often, the software in question is responsible for the issue. Please raise an
+issue with the software in question, not i3.
+
== Obtaining the debug logfile
Please note that log files may contain sensitive data such as window titles.
keyword. These commands will be run in order.
See <<command_chaining>> for details on the special meaning of +;+ (semicolon)
-and +,+ (comma): they chain commands together in i3 and need to be escaped if
-you want to use them in your command.
+and +,+ (comma): they chain commands together in i3, so you need to use quoted
+strings if they appear in your command.
*Syntax*:
---------------------------------------
force_display_urgency_hint 500 ms
---------------------------------
+=== Delaying exiting on zero displays
+
+Outputs may disappear momentarily and come back later. For example,
+using a docking station that does not announce the undock (e.g. ACPI Undock
+event triggered through manually pushing a button before actually ejecting
+the notebook). During the removal of the notebook from the docking station,
+all outputs disappear momentarily.
+
+To prevent i3 from exiting when no output is available momentarily, you can
+tell i3 to delay a certain time first and check available outputs again using
+the +delay_exit_on_zero_displays+ directive. Setting the value to 0 disables
+this feature.
+
+The default is 500ms.
+
+*Syntax*:
+----------------------------------------
+delay_exit_on_zero_displays <timeout> ms
+----------------------------------------
+
+*Example*:
+----------------------------------
+delay_exit_on_zero_displays 500 ms
+----------------------------------
+
=== Focus on window activation
[[focus_on_window_activation]]
searched in your +$PATH+.
See <<command_chaining>> for details on the special meaning of +;+ (semicolon)
-and +,+ (comma): they chain commands together in i3 and need to be escaped if
-you want to use them in your command.
+and +,+ (comma): they chain commands together in i3, so you need to use quoted
+strings if they appear in your command.
*Syntax*:
--------------------------------
bindsym $mod+t floating toggle
--------------
-=== Focusing/Moving containers
+[[_focusing_moving_containers]]
-To change the focus, use the focus command: +focus left+, +focus right+, +focus
-down+ and +focus up+.
+=== Focusing containers
-There are a few special parameters you can use for the focus command:
+To change focus, you can use the +focus+ command. The following options are
+available:
+left|right|up|down::
+ Sets focus to the nearest container in the given direction.
parent::
- Sets focus to the +Parent Container+ of the current +Container+.
+ Sets focus to the parent container of the current container.
child::
The opposite of +focus parent+, sets the focus to the last focused
child container.
Followed by a direction or an output name, this will focus the
corresponding output.
-For moving, use +move left+, +move right+, +move down+ and +move up+.
-
*Syntax*:
------------------------------------
-focus <left|right|down|up>
-focus <parent|child|floating|tiling|mode_toggle>
-focus output <<left|right|down|up>|output>
-move <left|right|down|up> [<px> px]
-move [absolute] position [[<px> px] [<px> px]|center]
------------------------------------
-
-Note that the amount of pixels you can specify for the +move+ command is only
-relevant for floating containers. The default amount is 10 pixels.
+----------------------------------------------
+focus left|right|down|up
+focus parent|child|floating|tiling|mode_toggle
+focus output left|right|up|down|<output>
+----------------------------------------------
*Examples*:
-----------------------
-# Focus container on the left, bottom, top, right:
+-------------------------------------------------
+# Focus container on the left, bottom, top, right
bindsym $mod+j focus left
bindsym $mod+k focus down
bindsym $mod+l focus up
# Focus the big output
bindsym $mod+x focus output HDMI-2
+-------------------------------------------------
+
+=== Moving containers
+
+Use the +move+ command to move a container.
+
+*Syntax*:
+-----------------------------------------------------
+# Moves the container into the given direction.
+# The optional pixel argument specifies how far the
+# container should be moved if it is floating and
+# defaults to 10 pixels.
+move <left|right|down|up> [<px> px]
-# Move container to the left, bottom, top, right:
+# 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 [[<px> px] [<px> px]|center]
+
+# Moves the container to the current position of the
+# mouse cursor. Only affects floating containers.
+move position mouse
+-----------------------------------------------------
+
+*Examples*:
+-------------------------------------------------------
+# Move container to the left, bottom, top, right
bindsym $mod+j move left
bindsym $mod+k move down
bindsym $mod+l move up
# move more than the default
bindsym $mod+j move left 20 px
-# Move floating container to the center
-# of all outputs
+# Move floating container to the center of all outputs
bindsym $mod+c move absolute position center
-----------------------
+
+# Move container to the current position of the cursor
+bindsym $mod+m move position mouse
+-------------------------------------------------------
=== Changing (named) workspaces/moving to workspaces
if (!strcmp(params->cur_key, "name")) {
const char *ws_name = (const char *)val;
- params->workspaces_walk->canonical_name = strndup(ws_name, len);
+ params->workspaces_walk->canonical_name = sstrndup(ws_name, len);
if (config.strip_ws_numbers && params->workspaces_walk->num >= 0) {
/* Special case: strip off the workspace number */
/* The font we'll use */
static i3Font font;
+/* Icon size (based on font size) */
+int icon_size;
+
/* Overall height of the bar (based on font size) */
int bar_height;
/* Additional offset between the tray and the statusline, if the tray is not empty */
static const int tray_loff_px = 2;
+/* Padding around the tray icons */
+static const int tray_spacing_px = 2;
+
/* Vertical offset between the bar and a separator */
static const int sep_voff_px = 4;
TAILQ_FOREACH_REVERSE(trayclient, trayclients, tc_head, tailq) {
if (!trayclient->mapped)
continue;
- tray_width += font.height + logical_px(2);
+ tray_width += icon_size + logical_px(tray_spacing_px);
}
if (tray_width > 0)
tray_width += logical_px(tray_loff_px);
clients++;
DLOG("Configuring tray window %08x to x=%d\n",
- trayclient->win, output->rect.w - (clients * (font.height + logical_px(2))));
- uint32_t x = output->rect.w - (clients * (font.height + logical_px(2)));
+ trayclient->win, output->rect.w - (clients * (icon_size + logical_px(tray_spacing_px))));
+ uint32_t x = output->rect.w - (clients * (icon_size + logical_px(tray_spacing_px)));
xcb_configure_window(xcb_connection,
trayclient->win,
XCB_CONFIG_WINDOW_X,
xcb_reparent_window(xcb_connection,
client,
output->bar,
- output->rect.w - font.height - 2,
- 2);
+ output->rect.w - icon_size - logical_px(tray_spacing_px),
+ logical_px(tray_spacing_px));
/* We reconfigure the window to use a reasonable size. The systray
* specification explicitly says:
* Tray icons may be assigned any size by the system tray, and
* should do their best to cope with any size effectively
*/
mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
- values[0] = font.height;
- values[1] = font.height;
+ values[0] = icon_size;
+ values[1] = icon_size;
xcb_configure_window(xcb_connection,
client,
mask,
continue;
xcb_rectangle_t rect;
- rect.x = output->rect.w - (clients * (font.height + 2));
- rect.y = 2;
- rect.width = font.height;
- rect.height = font.height;
+ rect.x = output->rect.w - (clients * (icon_size + logical_px(tray_spacing_px)));
+ rect.y = logical_px(tray_spacing_px);
+ rect.width = icon_size;
+ rect.height = icon_size;
DLOG("This is a tray window. x = %d\n", rect.x);
fake_configure_notify(xcb_connection, rect, event->window, 0);
set_font(&font);
DLOG("Calculated font height: %d\n", font.height);
bar_height = font.height + 2 * logical_px(ws_voff_px);
+ icon_size = bar_height - 2 * logical_px(tray_spacing_px);
if (config.separator_symbol)
separator_symbol_width = predict_text_width(config.separator_symbol);
*/
void cmd_move_window_to_center(I3_CMD, char *method);
+/**
+ * Implementation of 'move [window|container] [to] position mouse'
+ *
+ */
+void cmd_move_window_to_mouse(I3_CMD);
+
/**
* Implementation of 'move scratchpad'.
*
* flag can be delayed using an urgency timer. */
float workspace_urgency_timer;
+ /** Use a timer to delay exiting when no output is available.
+ * This can prevent i3 from exiting when all outputs disappear momentarily. */
+ float zero_disp_exit_timer_ms;
+
/** Behavior when a window sends a NET_ACTIVE_WINDOW message. */
enum {
/* Focus if the target workspace is visible, set urgency hint otherwise. */
CFGFUN(force_xinerama, const char *value);
CFGFUN(fake_outputs, const char *outputs);
CFGFUN(force_display_urgency_hint, const long duration_ms);
+CFGFUN(delay_exit_on_zero_displays, const long duration_ms);
CFGFUN(focus_on_window_activation, const char *mode);
CFGFUN(show_marks, const char *value);
CFGFUN(hide_edge_borders, const char *borders);
*/
void floating_center(Con *con, Rect rect);
+/**
+ * Moves the given floating con to the current pointer position.
+ *
+ */
+void floating_move_to_pointer(Con *con);
+
#if 0
/**
* Removes the floating client from its workspace and attaches it to the new
*/
char *sstrdup(const char *str);
+/**
+ * Safe-wrapper around strndup which exits if strndup returns NULL (meaning that
+ * there is no more memory available)
+ *
+ */
+char *sstrndup(const char *str, size_t size);
+
/**
* Safe-wrapper around asprintf which exits if it returns -1 (meaning that
* there is no more memory available)
char *head, *tail, *result;
tail = strchr(path, '/');
- head = strndup(path, tail ? (size_t)(tail - path) : strlen(path));
+ head = sstrndup(path, tail ? (size_t)(tail - path) : strlen(path));
int res = glob(head, GLOB_TILDE, NULL, &globbuf);
free(head);
return result;
}
+char *sstrndup(const char *str, size_t size) {
+ char *result = strndup(str, size);
+ if (result == NULL)
+ err(EXIT_FAILURE, "strndup()");
+ return result;
+}
+
int sasprintf(char **strp, const char *fmt, ...) {
va_list args;
int result;
# move workspace to [output] <str>
# move scratchpad
# move [window|container] [to] [absolute] position [ [<pixels> [px] <pixels> [px]] | center ]
+# move [window|container] [to] position mouse|cursor|pointer
state MOVE:
'window'
->
state MOVE_TO_POSITION:
'center'
-> call cmd_move_window_to_center($method)
+ 'mouse', 'cursor', 'pointer'
+ -> call cmd_move_window_to_mouse()
coord_x = word
-> MOVE_TO_POSITION_X
'workspace_auto_back_and_forth' -> WORKSPACE_BACK_AND_FORTH
'fake_outputs', 'fake-outputs' -> FAKE_OUTPUTS
'force_display_urgency_hint' -> FORCE_DISPLAY_URGENCY_HINT
+ 'delay_exit_on_zero_displays' -> DELAY_EXIT_ON_ZERO_DISPLAYS
'focus_on_window_activation' -> FOCUS_ON_WINDOW_ACTIVATION
'show_marks' -> SHOW_MARKS
'workspace' -> WORKSPACE
end
-> call cfg_force_display_urgency_hint(&duration_ms)
+# delay_exit_on_zero_displays <delay> ms
+state DELAY_EXIT_ON_ZERO_DISPLAYS:
+ duration_ms = number
+ -> DELAY_EXIT_ON_ZERO_DISPLAYS_MS
+
+state DELAY_EXIT_ON_ZERO_DISPLAYS_MS:
+ 'ms'
+ ->
+ end
+ -> call cfg_delay_exit_on_zero_displays(&duration_ms)
+
# focus_on_window_activation <smart|urgent|focus|none>
state FOCUS_ON_WINDOW_ACTIVATION:
mode = word
if (strcmp(ctype, "con_id") == 0) {
char *end;
- long parsed = strtol(cvalue, &end, 10);
+ long parsed = strtol(cvalue, &end, 0);
if (parsed == LONG_MIN ||
parsed == LONG_MAX ||
parsed < 0 ||
if (strcmp(ctype, "id") == 0) {
char *end;
- long parsed = strtol(cvalue, &end, 10);
+ long parsed = strtol(cvalue, &end, 0);
if (parsed == LONG_MIN ||
parsed == LONG_MAX ||
parsed < 0 ||
ysuccess(true);
}
+/*
+ * Implementation of 'move [window|container] [to] position mouse'
+ *
+ */
+void cmd_move_window_to_mouse(I3_CMD) {
+ HANDLE_EMPTY_MATCH;
+
+ owindow *current;
+ TAILQ_FOREACH(current, &owindows, owindows) {
+ Con *floating_con = con_inside_floating(current->con);
+ if (floating_con == NULL) {
+ DLOG("con %p / %s is not floating, cannot move it to the mouse position.\n",
+ current->con, current->con->name);
+ continue;
+ }
+
+ DLOG("moving floating container %p / %s to cursor position\n", floating_con, floating_con->name);
+ floating_move_to_pointer(floating_con);
+ }
+
+ cmd_output->needs_tree_render = true;
+ ysuccess(true);
+}
+
/*
* Implementation of 'move scratchpad'.
*
if (config.workspace_urgency_timer == 0)
config.workspace_urgency_timer = 0.5;
+ /* Set default zero displays exit delay to 500ms */
+ if (config.zero_disp_exit_timer_ms == 0)
+ config.zero_disp_exit_timer_ms = 500;
+
parse_configuration(override_configpath, true);
if (reload) {
config.workspace_urgency_timer = duration_ms / 1000.0;
}
+CFGFUN(delay_exit_on_zero_displays, const long duration_ms) {
+ config.zero_disp_exit_timer_ms = duration_ms;
+}
+
CFGFUN(focus_on_window_activation, const char *mode) {
if (strcmp(mode, "smart") == 0)
config.focus_on_window_activation = FOWA_SMART;
con->rect.y = rect.y + (rect.height / 2) - (con->rect.height / 2);
}
+/*
+ * Moves the given floating con to the current pointer position.
+ *
+ */
+void floating_move_to_pointer(Con *con) {
+ assert(con->type == CT_FLOATING_CON);
+
+ xcb_query_pointer_reply_t *reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL);
+ if (reply == NULL) {
+ ELOG("could not query pointer position, not moving this container\n");
+ return;
+ }
+
+ Output *output = get_output_containing(reply->root_x, reply->root_y);
+ if (output == NULL) {
+ ELOG("The pointer is not on any output, cannot move the container here.");
+ return;
+ }
+
+ /* Determine where to put the window. */
+ int32_t x = reply->root_x - con->rect.width / 2;
+ int32_t y = reply->root_y - con->rect.height / 2;
+ FREE(reply);
+
+ /* Correct target coordinates to be in-bounds. */
+ x = MAX(x, (int32_t)output->rect.x);
+ y = MAX(y, (int32_t)output->rect.y);
+ if (x + con->rect.width > output->rect.x + output->rect.width)
+ x = output->rect.x + output->rect.width - con->rect.width;
+ if (y + con->rect.height > output->rect.y + output->rect.height)
+ y = output->rect.y + output->rect.height - con->rect.height;
+
+ /* Update container's coordinates to position it correctly. */
+ floating_reposition(con, (Rect){x, y, con->rect.width, con->rect.height});
+}
+
DRAGGING_CB(drag_window_callback) {
const struct xcb_button_press_event_t *event = extra;
ELOG("ERROR: No screen at (%d, %d), starting on the first screen\n",
pointerreply->root_x, pointerreply->root_y);
output = get_first_output();
+ if (!output)
+ die("No usable outputs available.\n");
}
con_focus(con_descend_focused(output_get_content(output->con)));
if (output->active)
return output;
- die("No usable outputs available.\n");
+ return NULL;
}
/*
if (!new->active) {
DLOG("width/height 0/0, disabling output\n");
return;
+ } else {
+ new->to_be_disabled = false;
}
DLOG("mode: %dx%d+%d+%d\n", new->rect.width, new->rect.height,
new->changed = true;
}
-/*
- * (Re-)queries the outputs via RandR and stores them in the list of outputs.
- *
- */
-void randr_query_outputs(void) {
+static bool __randr_query_outputs(void) {
Output *output, *other, *first;
xcb_randr_get_output_primary_cookie_t pcookie;
xcb_randr_get_screen_resources_current_cookie_t rcookie;
xcb_randr_output_t *randr_outputs;
if (randr_disabled)
- return;
+ return true;
/* Get screen resources (primary output, crtcs, outputs, modes) */
rcookie = xcb_randr_get_screen_resources_current(conn, root);
DLOG("primary output is %08x\n", primary->output);
if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) {
disable_randr(conn);
- return;
+ return true;
}
cts = res->config_timestamp;
DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name);
first = get_first_output();
+ if (!first) {
+ FREE(res);
+ FREE(primary);
+ return false;
+ }
/* TODO: refactor the following code into a nice function. maybe
* use an on_destroy callback which is implement differently for
FREE(res);
FREE(primary);
+
+ return true;
+}
+
+/*
+ * (Re-)queries the outputs via RandR and stores them in the list of outputs.
+ *
+ */
+void randr_query_outputs(void) {
+ static bool first_query = true;
+
+ if (first_query) {
+ /* find monitors at least once via RandR */
+ if (!__randr_query_outputs())
+ die("No usable outputs available.\n");
+ first_query = false;
+ } else {
+ /* requery */
+ if (!__randr_query_outputs()) {
+ DLOG("sleep %f ms due to zero displays\n", config.zero_disp_exit_timer_ms);
+ usleep(config.zero_disp_exit_timer_ms * 1000);
+
+ if (!__randr_query_outputs())
+ die("No usable outputs available.\n");
+ }
+ }
}
/*
* null-terminated strings (for compatibility reasons). Instead, we
* use strdup() on both strings */
const size_t prop_length = xcb_get_property_value_length(prop);
- char *new_class = smalloc(prop_length + 1);
- memcpy(new_class, xcb_get_property_value(prop), prop_length);
- new_class[prop_length] = '\0';
+ char *new_class = xcb_get_property_value(prop);
+ const size_t class_class_index = strnlen(new_class, prop_length) + 1;
FREE(win->class_instance);
FREE(win->class_class);
- win->class_instance = sstrdup(new_class);
- if ((strlen(new_class) + 1) < prop_length)
- win->class_class = sstrdup(new_class + strlen(new_class) + 1);
+ win->class_instance = sstrndup(new_class, prop_length);
+ if (class_class_index < prop_length)
+ win->class_class = sstrndup(new_class + class_class_index, prop_length - class_class_index);
else
win->class_class = NULL;
LOG("WM_CLASS changed to %s (instance), %s (class)\n",
win->class_instance, win->class_class);
if (before_mgmt) {
- free(new_class);
free(prop);
return;
}
run_assignments(win);
- free(new_class);
free(prop);
}
$expected,
'force_display_urgency_hint ok');
+################################################################################
+# delay_exit_on_zero_displays
+################################################################################
+
+is(parser_calls('delay_exit_on_zero_displays 300'),
+ "cfg_delay_exit_on_zero_displays(300)\n",
+ 'delay_exit_on_zero_displays ok');
+
+is(parser_calls('delay_exit_on_zero_displays 500 ms'),
+ "cfg_delay_exit_on_zero_displays(500)\n",
+ 'delay_exit_on_zero_displays ok');
+
+is(parser_calls('delay_exit_on_zero_displays 700ms'),
+ "cfg_delay_exit_on_zero_displays(700)\n",
+ 'delay_exit_on_zero_displays ok');
+
+$config = <<'EOT';
+delay_exit_on_zero_displays 300
+delay_exit_on_zero_displays 500 ms
+delay_exit_on_zero_displays 700ms
+delay_exit_on_zero_displays 700
+EOT
+
+$expected = <<'EOT';
+cfg_delay_exit_on_zero_displays(300)
+cfg_delay_exit_on_zero_displays(500)
+cfg_delay_exit_on_zero_displays(700)
+cfg_delay_exit_on_zero_displays(700)
+EOT
+
+is(parser_calls($config),
+ $expected,
+ 'delay_exit_on_zero_displays ok');
+
################################################################################
# workspace
################################################################################
EOT
my $expected_all_tokens = <<'EOT';
-ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'no_focus', 'focus_follows_mouse', 'mouse_warping', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'focus_on_window_activation', 'show_marks', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent', 'client.placeholder'
+ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'no_focus', 'focus_follows_mouse', 'mouse_warping', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'delay_exit_on_zero_displays', 'focus_on_window_activation', 'show_marks', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent', 'client.placeholder'
EOT
my $expected_end = <<'EOT';
--- /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 the 'move [window|container] [to] position mouse|cursor|pointer command.
+# Ticket: #1696
+use i3test i3_autostart => 0;
+use POSIX qw(floor);
+
+sub warp_pointer {
+ my ($pos_x, $pos_y) = @_;
+ $x->root->warp_pointer($pos_x, $pos_y);
+ sync_with_i3;
+}
+
+my ($pid, $config, $ws, $rect, @cons);
+my $root_rect = $x->root->rect;
+
+##########################################################################
+
+##########################################################################
+# Given a floating container and the cursor far from any edges, when
+# moving the container to the mouse, then the container is moved such
+# that the cursor is centered inside it.
+##########################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font pango:monospace 8
+EOT
+$pid = launch_with_config($config);
+
+$ws = fresh_workspace;
+open_floating_window;
+warp_pointer(100, 100);
+
+cmd 'move position mouse';
+
+@cons = @{get_ws($ws)->{floating_nodes}};
+$rect = $cons[0]->{rect};
+
+is(floor($rect->{x} + $rect->{width} / 2), 100, "con is centered around cursor horizontally");
+is(floor($rect->{y} + $rect->{height} / 2), 100, "con is centered around cursor vertically");
+
+exit_gracefully($pid);
+
+##########################################################################
+# Given a floating container and the cursor is in the left upper edge
+# of the output, when moving the container to the mouse, then the
+# container is moved but adjusted to stay in-bounds.
+##########################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font pango:monospace 8
+EOT
+$pid = launch_with_config($config);
+
+$ws = fresh_workspace;
+open_floating_window;
+warp_pointer(5, 5);
+
+cmd 'move position mouse';
+
+@cons = @{get_ws($ws)->{floating_nodes}};
+$rect = $cons[0]->{rect};
+
+is($rect->{x}, 0, "con is on the left edge");
+is($rect->{y}, 0, "con is on the upper edge");
+
+exit_gracefully($pid);
+
+##########################################################################
+# Given a floating container and the cursor is in the left right lower
+# edge of the output, when moving the container to the mouse, then the
+# container is moved but adjusted to stay in-bounds.
+##########################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font pango:monospace 8
+EOT
+$pid = launch_with_config($config);
+
+$ws = fresh_workspace;
+open_floating_window;
+warp_pointer($root_rect->width - 5, $root_rect->height - 5);
+
+cmd 'move position mouse';
+
+@cons = @{get_ws($ws)->{floating_nodes}};
+$rect = $cons[0]->{rect};
+
+is($rect->{x} + $rect->{width}, $root_rect->width, "con is on the right edge");
+is($rect->{y} + $rect->{height}, $root_rect->height, "con is on the lower edge");
+
+exit_gracefully($pid);
+
+##########################################################################
+# Given a floating container and the cursor being close to the corner of
+# an output, when the container is moved to the mouse, then the container
+# is moved but adjusted to the output boundaries.
+# This test verifies that boundaries of individual outputs are respected
+# and not just boundaries of the entire X root screen.
+##########################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font pango:monospace 8
+fake-outputs 500x500+0+0,500x500+500+0,500x500+0+500,500x500+500+500
+EOT
+$pid = launch_with_config($config);
+
+$ws = fresh_workspace;
+open_floating_window;
+warp_pointer(495, 495);
+
+cmd 'move position mouse';
+
+@cons = @{get_ws($ws)->{floating_nodes}};
+$rect = $cons[0]->{rect};
+
+is($rect->{x} + $rect->{width}, 500, "con is on the right edge");
+is($rect->{y} + $rect->{height}, 500, "con is on the lower edge");
+
+exit_gracefully($pid);
+
+##########################################################################
+
+done_testing;