From fec1a9511e75f83b8707758c04d5a201edbc652d Mon Sep 17 00:00:00 2001 From: wentasah Date: Wed, 6 Apr 2016 21:19:10 +0200 Subject: [PATCH] Make fullscreen windows open on the output which is indicated by their geometry With this change, multi-monitor presentations (e.g. as implemented by LibreOffice Impress) work out of the box. Previously, one had to move the presentation windows to the right outputs oneself. --- include/con.h | 7 ++ include/randr.h | 7 ++ src/con.c | 13 ++++ src/manage.c | 10 ++- src/randr.c | 21 ++++++ testcases/t/531-fullscreen-on-given-output.t | 75 ++++++++++++++++++++ 6 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 testcases/t/531-fullscreen-on-given-output.t diff --git a/include/con.h b/include/con.h index 5b48120b..130dd83b 100644 --- a/include/con.h +++ b/include/con.h @@ -268,6 +268,13 @@ void con_disable_fullscreen(Con *con); void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp, bool ignore_focus); +/** + * Moves the given container to the currently focused container on the + * visible workspace on the given output. + * + */ +void con_move_to_output(Con *con, Output *output); + /** * Moves the given container to the given mark. * diff --git a/include/randr.h b/include/randr.h index 998f0d59..8d4b4def 100644 --- a/include/randr.h +++ b/include/randr.h @@ -79,6 +79,13 @@ Output *get_output_by_name(const char *name); */ Output *get_output_containing(unsigned int x, unsigned int y); +/** + * 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. * We do this by checking if the output rect is intersected by the Rect. diff --git a/src/con.c b/src/con.c index cd17f9e5..17cdd2d6 100644 --- a/src/con.c +++ b/src/con.c @@ -1157,6 +1157,19 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool _con_move_to_con(con, target, true, fix_coordinates, dont_warp, ignore_focus); } +/* + * Moves the given container to the currently focused container on the + * visible workspace on the given output. + * + */ +void con_move_to_output(Con *con, Output *output) { + Con *ws = NULL; + GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child)); + assert(ws != NULL); + DLOG("Moving con %p to output %s\n", con, output->name); + con_move_to_workspace(con, ws, false, false, false); +} + /* * Returns the orientation of the given container (for stacked containers, * vertical orientation is used regardless of the actual orientation of the diff --git a/src/manage.c b/src/manage.c index 93272f1b..81a62ab8 100644 --- a/src/manage.c +++ b/src/manage.c @@ -359,8 +359,16 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if (xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_FULLSCREEN)) { /* If this window is already fullscreen (after restarting!), skip * toggling fullscreen, that would drop it out of fullscreen mode. */ - if (fs != nc) + if (fs != nc) { + Output *output = get_output_with_dimensions((Rect){geom->x, geom->y, geom->width, geom->height}); + /* If the requested window geometry spans the whole area + * of an output, move the window to that output. This is + * needed e.g. for LibreOffice Impress multi-monitor + * presentations to work out of the box. */ + if (output != NULL) + con_move_to_output(nc, output); con_toggle_fullscreen(nc, CF_OUTPUT); + } fs = NULL; } diff --git a/src/randr.c b/src/randr.c index f0234c5c..6aed40cf 100644 --- a/src/randr.c +++ b/src/randr.c @@ -108,6 +108,27 @@ Output *get_output_containing(unsigned int x, unsigned int y) { return NULL; } +/* + * 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) { + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active) + continue; + DLOG("comparing x=%d y=%d %dx%d with x=%d and y=%d %dx%d\n", + rect.x, rect.y, rect.width, rect.height, + output->rect.x, output->rect.y, output->rect.width, output->rect.height); + if (rect.x == output->rect.x && rect.width == output->rect.width && + rect.y == output->rect.y && rect.height == output->rect.height) + return output; + } + + return NULL; +} + /* * In contained_by_output, 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. diff --git a/testcases/t/531-fullscreen-on-given-output.t b/testcases/t/531-fullscreen-on-given-output.t new file mode 100644 index 00000000..fd328653 --- /dev/null +++ b/testcases/t/531-fullscreen-on-given-output.t @@ -0,0 +1,75 @@ +#!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 that fullscreen windows appear on the output indicated by +# their geometry +use i3test i3_autostart => 0; +use List::Util qw(first); + +my $config = <fullscreen(1); +} + +sub find_window { + my ($nodes, $id) = @_; + + foreach (@{$nodes}) { + return $_ if ($_->{window} // 0) == $id; + my $node = find_window($_->{nodes}, $id); + return $node if $node; + }; + return undef; +} + +# Create two fullscreen windows, each on different output +my $orig_rect1 = X11::XCB::Rect->new(x => 0, y => 0, width => 1024, height => 768); +my $orig_rect2 = X11::XCB::Rect->new(x => 1024, y => 0, width => 1024, height => 768); + +my $win_on_first_output = open_window(rect => $orig_rect1, + before_map => \&fullscreen); + +my $win_on_second_output = open_window(rect => $orig_rect2, + before_map => \&fullscreen); + +sync_with_i3; + +# Check that the windows are on the correct output +is_deeply(scalar $win_on_first_output->rect, $orig_rect1, "first window spans the first output"); +is_deeply(scalar $win_on_second_output->rect, $orig_rect2, "second window spans the sencond output"); + +# Check that both windows remained fullscreen +my $tree = i3(get_socket_path())->get_tree->recv; + +my $node1 = find_window($tree->{nodes}, $win_on_first_output->{id}); +my $node2 = find_window($tree->{nodes}, $win_on_second_output->{id}); + +is($node1->{fullscreen_mode}, 1, "first window is fullscreen"); +is($node2->{fullscreen_mode}, 1, "second window is fullscreen"); + +exit_gracefully($pid); + +done_testing;