From eaf7a49e28b7ff8e1820a1b23350647574a0ed5a Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 28 Aug 2017 05:14:03 +0300 Subject: [PATCH] Allow assign to workspace by number Makes "assign [] workspace number " work in the same manner as "move to workspace number " instead of assigning the window to a workspace named "number ". config.spec is modified to expect a 'number' string and an extra argument is used in cfg_assign. For workspaces that don't exist yet, workspace_get is used as a fallback. This also allows the user to assign to " " eg "2: work" and the full name will be used if workspace number 2 doesn't exist yet. Fixes #2590. --- docs/userguide | 8 ++- include/config_directives.h | 2 +- include/data.h | 3 +- parser-specs/config.spec | 8 ++- src/config_directives.c | 10 +++- src/manage.c | 21 +++++++- testcases/t/166-assign.t | 95 +++++++++++++++++++++++++++------ testcases/t/201-config-parser.t | 11 ++-- 8 files changed, 129 insertions(+), 29 deletions(-) diff --git a/docs/userguide b/docs/userguide index 0d5de3b9..52165e2d 100644 --- a/docs/userguide +++ b/docs/userguide @@ -766,7 +766,7 @@ considered. *Syntax*: ------------------------------------------------------------ -assign [→] [workspace] +assign [→] [workspace] [number] ------------------------------------------------------------ *Examples*: @@ -783,6 +783,12 @@ assign [class="^URxvt$"] → 2 # Assignment to a named workspace assign [class="^URxvt$"] → work +# Assign to the workspace with number 2, regardless of name +assign [class="^URxvt$"] → number 2 + +# You can also specify a number + name. If the workspace with number 2 exists, assign will skip the text part. +assign [class="^URxvt$"] → number "2: work" + # Start urxvt -name irssi assign [class="^URxvt$" instance="^irssi$"] → 3 ---------------------- diff --git a/include/config_directives.h b/include/config_directives.h index f35666f3..b729e728 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -57,7 +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, const char *workspace); +CFGFUN(assign, const char *workspace, bool is_number); CFGFUN(no_focus); CFGFUN(ipc_socket, const char *path); CFGFUN(restart_state, const char *path); diff --git a/include/data.h b/include/data.h index 69a79ade..54333e04 100644 --- a/include/data.h +++ b/include/data.h @@ -546,7 +546,8 @@ struct Assignment { A_ANY = 0, A_COMMAND = (1 << 0), A_TO_WORKSPACE = (1 << 1), - A_NO_FOCUS = (1 << 2) + A_NO_FOCUS = (1 << 2), + A_TO_WORKSPACE_NUMBER = (1 << 3) } type; /** the criteria to check if a window matches */ diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 4aa320bf..665b046a 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -151,8 +151,14 @@ state ASSIGN_WORKSPACE: -> 'workspace' -> + 'number' + -> ASSIGN_WORKSPACE_NUMBER workspace = string - -> call cfg_assign($workspace) + -> call cfg_assign($workspace, 0) + +state ASSIGN_WORKSPACE_NUMBER: + number = string + -> call cfg_assign($number, 1) # no_focus state NO_FOCUS: diff --git a/src/config_directives.c b/src/config_directives.c index 7ca6e102..376397e8 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -377,15 +377,21 @@ CFGFUN(color, const char *colorclass, const char *border, const char *background #undef APPLY_COLORS } -CFGFUN(assign, const char *workspace) { +CFGFUN(assign, const char *workspace, bool is_number) { if (match_is_empty(current_match)) { ELOG("Match is empty, ignoring this assignment\n"); return; } + + if (is_number && ws_name_to_number(workspace) == -1) { + ELOG("Could not parse initial part of \"%s\" as a number.\n", workspace); + return; + } + DLOG("New assignment, using above criteria, to workspace \"%s\".\n", workspace); Assignment *assignment = scalloc(1, sizeof(Assignment)); match_copy(&(assignment->match), current_match); - assignment->type = A_TO_WORKSPACE; + assignment->type = is_number ? A_TO_WORKSPACE_NUMBER : A_TO_WORKSPACE; assignment->dest.workspace = sstrdup(workspace); TAILQ_INSERT_TAIL(&assignments, assignment, assignments); } diff --git a/src/manage.c b/src/manage.c index 86a361c3..e3596548 100644 --- a/src/manage.c +++ b/src/manage.c @@ -259,9 +259,26 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki Con *wm_desktop_ws = NULL; /* If not, check if it is assigned to a specific workspace */ - if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE))) { + if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE)) || + (assignment = assignment_for(cwindow, A_TO_WORKSPACE_NUMBER))) { DLOG("Assignment matches (%p)\n", match); - Con *assigned_ws = workspace_get(assignment->dest.workspace, NULL); + + Con *assigned_ws = NULL; + if (assignment->type == A_TO_WORKSPACE_NUMBER) { + Con *output = NULL; + long parsed_num = ws_name_to_number(assignment->dest.workspace); + + /* This will only work for workspaces that already exist. */ + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + GREP_FIRST(assigned_ws, output_get_content(output), child->num == parsed_num); + } + } + /* A_TO_WORKSPACE type assignment or fallback from A_TO_WORKSPACE_NUMBER + * when the target workspace number does not exist yet. */ + if (!assigned_ws) { + assigned_ws = workspace_get(assignment->dest.workspace, NULL); + } + nc = con_descend_tiling_focused(assigned_ws); DLOG("focused on ws %s: %p / %s\n", assigned_ws->name, nc, nc->name); if (nc->type == CT_WORKSPACE) diff --git a/testcases/t/166-assign.t b/testcases/t/166-assign.t index 355058dd..68548831 100644 --- a/testcases/t/166-assign.t +++ b/testcases/t/166-assign.t @@ -33,6 +33,33 @@ sub open_special { return $window; } +sub test_workspace_assignment { + my $target_ws = "@_"; + + # initialize the target workspace, then go to a fresh one + ok(!($target_ws ~~ @{get_workspace_names()}), "$target_ws does not exist yet"); + cmd "workspace $target_ws"; + cmp_ok(@{get_ws_content($target_ws)}, '==', 0, "no containers on $target_ws yet"); + cmd 'open'; + cmp_ok(@{get_ws_content($target_ws)}, '==', 1, "one container on $target_ws"); + my $tmp = fresh_workspace; + + ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + ok($target_ws ~~ @{get_workspace_names()}, "$target_ws does not exist yet"); + + # 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; + sync_with_i3; + + ok(@{get_ws_content($tmp)} == 0, 'still no containers'); + ok(@{get_ws_content($target_ws)} == 2, "two containers on $target_ws"); + + return $window +} + ##################################################################### # start a window and see that it does not get assigned with an empty config ##################################################################### @@ -87,33 +114,67 @@ $window->destroy; exit_gracefully($pid); ##################################################################### -# start a window and see that it gets assigned to a workspace which has content -# already, next to the existing node. +# start a window and see that it gets assigned to a formerly unused +# numbered workspace ##################################################################### -$pid = launch_with_config($config); +my $config_numbered = < 1); -$window->map; +$window = open_special; sync_with_i3; ok(@{get_ws_content($tmp)} == 0, 'still no containers'); -ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws'); +ok("2" ~~ @{get_workspace_names()}, 'workspace number 2 exists'); + +$window->destroy; + +exit_gracefully($pid); + +##################################################################### +# start a window and see that it gets assigned to a numbered +# workspace which has content already, next to the existing node. +##################################################################### + +$pid = launch_with_config($config_numbered); + +$window = test_workspace_assignment("2"); +$window->destroy; + +exit_gracefully($pid); + +##################################################################### +# start a window and see that it gets assigned to a numbered workspace with +# a name which has content already, next to the existing node. +##################################################################### + +$pid = launch_with_config($config_numbered); + +cmd 'workspace 2'; # Make sure that we are not testing for "2" again. +$window = test_workspace_assignment("2: targetws"); +$window->destroy; + +exit_gracefully($pid); + +##################################################################### +# start a window and see that it gets assigned to a workspace which +# has content already, next to the existing node. +##################################################################### + +$pid = launch_with_config($config); + +test_workspace_assignment("targetws"); exit_gracefully($pid); diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index fb3130d5..e8080a73 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -116,6 +116,7 @@ is(parser_calls($config), $config = <<'EOT'; assign [class="^Chrome"] 4 +assign [class="^Chrome"] workspace number 3 assign [class="^Chrome"] named workspace assign [class="^Chrome"] "quoted named workspace" assign [class="^Chrome"] → "quoted named workspace" @@ -123,13 +124,15 @@ EOT $expected = <<'EOT'; cfg_criteria_add(class, ^Chrome) -cfg_assign(4) +cfg_assign(4, 0) cfg_criteria_add(class, ^Chrome) -cfg_assign(named workspace) +cfg_assign(3, 1) cfg_criteria_add(class, ^Chrome) -cfg_assign(quoted named workspace) +cfg_assign(named workspace, 0) cfg_criteria_add(class, ^Chrome) -cfg_assign(quoted named workspace) +cfg_assign(quoted named workspace, 0) +cfg_criteria_add(class, ^Chrome) +cfg_assign(quoted named workspace, 0) EOT is(parser_calls($config), -- 2.39.2