From e2bacc7df8ba09a552ddd3555f3c730ed5205664 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 16 Sep 2017 20:14:35 +0300 Subject: [PATCH 1/5] Add con_move_to_output_name function --- include/con.h | 11 ++++++++++- src/commands.c | 20 +------------------- src/con.c | 25 +++++++++++++++++++++++-- src/manage.c | 2 +- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/include/con.h b/include/con.h index 6cd1ef3e..69292411 100644 --- a/include/con.h +++ b/include/con.h @@ -307,7 +307,16 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, * visible workspace on the given output. * */ -void con_move_to_output(Con *con, Output *output); +void con_move_to_output(Con *con, Output *output, bool fix_coordinates); + +/** + * Moves the given container to the currently focused container on the + * visible workspace on the output specified by the given name. + * The current output for the container is used to resolve relative names + * such as left, right, up, down. + * + */ +bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates); /** * Moves the given container to the given mark. diff --git a/src/commands.c b/src/commands.c index 2697d6e1..d7cdf219 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1044,25 +1044,7 @@ void cmd_move_con_to_output(I3_CMD, const char *name) { TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); - Output *current_output = get_output_for_con(current->con); - assert(current_output != NULL); - - Output *output = get_output_from_string(current_output, name); - if (output == NULL) { - ELOG("Could not find output \"%s\", skipping.\n", name); - had_error = true; - continue; - } - - Con *ws = NULL; - GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child)); - if (ws == NULL) { - ELOG("Could not find a visible workspace on output %p.\n", output); - had_error = true; - continue; - } - - con_move_to_workspace(current->con, ws, true, false, false); + had_error |= !con_move_to_output_name(current->con, name, true); } cmd_output->needs_tree_render = true; diff --git a/src/con.c b/src/con.c index a663db31..8254672f 100644 --- a/src/con.c +++ b/src/con.c @@ -1284,12 +1284,33 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool * visible workspace on the given output. * */ -void con_move_to_output(Con *con, Output *output) { +void con_move_to_output(Con *con, Output *output, bool fix_coordinates) { 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_primary_name(output)); - con_move_to_workspace(con, ws, false, false, false); + con_move_to_workspace(con, ws, fix_coordinates, false, false); +} + +/* + * Moves the given container to the currently focused container on the + * visible workspace on the output specified by the given name. + * The current output for the container is used to resolve relative names + * such as left, right, up, down. + * + */ +bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates) { + Output *current_output = get_output_for_con(con); + assert(current_output != NULL); + + Output *output = get_output_from_string(current_output, name); + if (output == NULL) { + ELOG("Could not find output \"%s\"\n", name); + return false; + } + + con_move_to_output(con, output, fix_coordinates); + return true; } /* diff --git a/src/manage.c b/src/manage.c index f155603d..8087d563 100644 --- a/src/manage.c +++ b/src/manage.c @@ -384,7 +384,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki * 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_move_to_output(nc, output, false); con_toggle_fullscreen(nc, CF_OUTPUT); } fs = NULL; From 1c975a1b8ca69a199d1d994c3bcfe07fb368a6cf Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 17 Sep 2017 14:45:37 +0300 Subject: [PATCH 2/5] 166-assign.t: fix typo --- testcases/t/166-assign.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcases/t/166-assign.t b/testcases/t/166-assign.t index 68548831..8ca62d1e 100644 --- a/testcases/t/166-assign.t +++ b/testcases/t/166-assign.t @@ -242,7 +242,7 @@ $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); my @docked = get_dock_clients; -is(@docked, 0, 'one dock client yet'); +is(@docked, 0, 'no dock client yet'); $window = open_special( window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), From 716a5b366065717be20432cb80a21802282ec823 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 17 Sep 2017 13:10:58 +0300 Subject: [PATCH 3/5] 166-assign.t: improve open_special call --- testcases/t/166-assign.t | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/testcases/t/166-assign.t b/testcases/t/166-assign.t index 8ca62d1e..07bddfff 100644 --- a/testcases/t/166-assign.t +++ b/testcases/t/166-assign.t @@ -21,12 +21,12 @@ use i3test i3_autostart => 0; sub open_special { my %args = @_; $args{name} //= 'special window'; + $args{wm_class} //= 'special'; # We use dont_map because i3 will not map the window on the current # workspace. Thus, open_window would time out in wait_for_map (2 seconds). my $window = open_window( %args, - wm_class => 'special', dont_map => 1, ); $window->map; @@ -50,8 +50,7 @@ sub test_workspace_assignment { # We use sync_with_i3 instead of wait_for_map here because i3 will not actually # map the window -- it will be assigned to a different workspace and will only # be mapped once you switch to that workspace - my $window = open_special(dont_map => 1); - $window->map; + my $window = open_special; sync_with_i3; ok(@{get_ws_content($tmp)} == 0, 'still no containers'); From 45d1e51857ef69d7012ce71222be32a323b3101b Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 17 Sep 2017 15:20:47 +0300 Subject: [PATCH 4/5] Fix userguide formatting error --- docs/userguide | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index cc9b5a01..38b16e5c 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2238,7 +2238,6 @@ bindsym $mod+x move container to output VGA1 bindsym $mod+x move container to output primary -------------------------------------------------------- -------------------------------- Note that you might not have a primary output configured yet. To do so, run: ------------------------- xrandr --output --primary From a35854ddf4c39de84660471652c6a90c6a221535 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 16 Sep 2017 20:54:44 +0300 Subject: [PATCH 5/5] Allow assign to output Implements the "assign" part of issue #2764. --- docs/userguide | 18 +++++- include/config_directives.h | 1 + include/data.h | 4 +- parser-specs/config.spec | 8 ++- src/config_directives.c | 14 +++++ src/manage.c | 4 ++ testcases/t/166-assign.t | 121 +++++++++++++++++++++++++++++++++++- 7 files changed, 166 insertions(+), 4 deletions(-) diff --git a/docs/userguide b/docs/userguide index 38b16e5c..a911b08c 100644 --- a/docs/userguide +++ b/docs/userguide @@ -760,6 +760,10 @@ title change. As i3 will get the title as soon as the application maps the window (mapping means actually displaying it on the screen), you’d need to have to match on 'Firefox' in this case. +You can also assign a window to show up on a specific output. You can use RandR +names such as +VGA1+ or names relative to the output with the currently focused +workspace such as +left+ and +down+. + Assignments are processed by i3 in the order in which they appear in the config file. The first one which matches the window wins and later assignments are not considered. @@ -767,6 +771,7 @@ considered. *Syntax*: ------------------------------------------------------------ assign [→] [workspace] [number] +assign [→] output left|right|up|down|primary| ------------------------------------------------------------ *Examples*: @@ -791,9 +796,20 @@ assign [class="^URxvt$"] → number "2: work" # Start urxvt -name irssi assign [class="^URxvt$" instance="^irssi$"] → 3 + +# Assign urxvt to the output right of the current one +assign [class="^URxvt$"] → output right + +# Assign urxvt to the primary output +assign [class="^URxvt$"] → output primary ---------------------- -Note that the arrow is not required, it just looks good :-). If you decide to +Note that you might not have a primary output configured yet. To do so, run: +------------------------- +xrandr --output --primary +------------------------- + +Also, the arrow is not required, it just looks good :-). If you decide to use it, it has to be a UTF-8 encoded arrow, not `->` or something like that. To get the class and instance, you can use +xprop+. After clicking on the diff --git a/include/config_directives.h b/include/config_directives.h index b729e728..5318bae8 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -57,6 +57,7 @@ CFGFUN(force_display_urgency_hint, 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); +CFGFUN(assign_output, const char *output); CFGFUN(assign, const char *workspace, bool is_number); CFGFUN(no_focus); CFGFUN(ipc_socket, const char *path); diff --git a/include/data.h b/include/data.h index 7411ac20..632dfb4f 100644 --- a/include/data.h +++ b/include/data.h @@ -557,7 +557,8 @@ struct Assignment { A_COMMAND = (1 << 0), A_TO_WORKSPACE = (1 << 1), A_NO_FOCUS = (1 << 2), - A_TO_WORKSPACE_NUMBER = (1 << 3) + A_TO_WORKSPACE_NUMBER = (1 << 3), + A_TO_OUTPUT = (1 << 4) } type; /** the criteria to check if a window matches */ @@ -567,6 +568,7 @@ struct Assignment { union { char *command; char *workspace; + char *output; } dest; TAILQ_ENTRY(Assignment) diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 665b046a..8c89b3f9 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -141,7 +141,7 @@ state FOR_WINDOW_COMMAND: command = string -> call cfg_for_window($command) -# assign [→] workspace +# assign [→] [workspace | output] state ASSIGN: '[' -> call cfg_criteria_init(ASSIGN_WORKSPACE); CRITERIA @@ -149,6 +149,8 @@ state ASSIGN: state ASSIGN_WORKSPACE: '→' -> + 'output' + -> ASSIGN_OUTPUT 'workspace' -> 'number' @@ -156,6 +158,10 @@ state ASSIGN_WORKSPACE: workspace = string -> call cfg_assign($workspace, 0) +state ASSIGN_OUTPUT: + output = string + -> call cfg_assign_output($output) + state ASSIGN_WORKSPACE_NUMBER: number = string -> call cfg_assign($number, 1) diff --git a/src/config_directives.c b/src/config_directives.c index 376397e8..c981ff60 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -377,6 +377,20 @@ CFGFUN(color, const char *colorclass, const char *border, const char *background #undef APPLY_COLORS } +CFGFUN(assign_output, const char *output) { + if (match_is_empty(current_match)) { + ELOG("Match is empty, ignoring this assignment\n"); + return; + } + + DLOG("New assignment, using above criteria, to output \"%s\".\n", output); + Assignment *assignment = scalloc(1, sizeof(Assignment)); + match_copy(&(assignment->match), current_match); + assignment->type = A_TO_OUTPUT; + assignment->dest.output = sstrdup(output); + TAILQ_INSERT_TAIL(&assignments, assignment, assignments); +} + CFGFUN(assign, const char *workspace, bool is_number) { if (match_is_empty(current_match)) { ELOG("Match is empty, ignoring this assignment\n"); diff --git a/src/manage.c b/src/manage.c index 8087d563..d12ce8d6 100644 --- a/src/manage.c +++ b/src/manage.c @@ -322,6 +322,10 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki } else nc = tree_open_con(NULL, cwindow); } + + if ((assignment = assignment_for(cwindow, A_TO_OUTPUT))) { + con_move_to_output_name(nc, assignment->dest.output, true); + } } else { /* M_BELOW inserts the new window as a child of the one which was * matched (e.g. dock areas) */ diff --git a/testcases/t/166-assign.t b/testcases/t/166-assign.t index 07bddfff..3c425467 100644 --- a/testcases/t/166-assign.t +++ b/testcases/t/166-assign.t @@ -203,8 +203,127 @@ my $content = get_ws($tmp); ok(@{$content->{nodes}} == 0, 'no tiling cons'); ok(@{$content->{floating_nodes}} == 1, 'one floating con'); -$window->destroy; +kill_all_windows; +exit_gracefully($pid); +##################################################################### +# test assignments to named outputs +##################################################################### +$config = < $class); + sync_with_i3; + is_num_children($ws, $expected_count, + "after: $expected_count containers on output $output"); +} + +cmd "workspace ws-0"; +open_in_output(0, 1); +my $focused = $x->input_focus; + +open_in_output(1, 1); +is($x->input_focus, $focused, 'focus remains on output fake-0'); + +open_in_output(2, 1); +is($x->input_focus, $focused, 'focus remains on output fake-0'); + +for my $i (1 .. 5){ + open_in_output(3, $i); + is($x->input_focus, $focused, 'focus remains on output fake-0'); +} + +# Check invalid output +$tmp = fresh_workspace; +open_special(wm_class => "special-4"); +sync_with_i3; +is_num_children($tmp, 1, 'window assigned to invalid output opened in current workspace'); +open_special(wm_class => "special-3"); +sync_with_i3; +is_num_children($tmp, 1, 'but window assigned to valid output did not'); + +kill_all_windows; +exit_gracefully($pid); + +##################################################################### +# Test assignments to outputs with relative names +##################################################################### +$config = < 'current'); +} +sync_with_i3; +is_num_children('left-top', 5, 'windows opened in current workspace'); + +is_num_children('right-top', 0, 'no children on right-top'); +open_special(wm_class => 'right'); +sync_with_i3; +is_num_children('right-top', 1, 'one child on right-top'); + +is_num_children('left-bottom', 0, 'no children on left-bottom'); +open_special(wm_class => 'down'); +sync_with_i3; +is_num_children('left-bottom', 1, 'one child on left-bottom'); + +cmd 'workspace right-bottom'; + +open_special(wm_class => 'up'); +sync_with_i3; +is_num_children('right-top', 2, 'two children on right-top'); + +open_special(wm_class => 'left'); +sync_with_i3; +is_num_children('left-bottom', 2, 'two children on left-bottom'); + +kill_all_windows; exit_gracefully($pid); #####################################################################