Allow multiple assignments of workspaces to outputs

Also makes get_assigned_output work with the primary output:
    workspace X output primary
will now work.

Fixes #555.
next
Orestis Floros 2018-03-28 00:55:20 +03:00
parent d525eb80ae
commit bce088679a
No known key found for this signature in database
GPG Key ID: E9AD9F32E401E38F
10 changed files with 130 additions and 18 deletions

View File

@ -888,7 +888,7 @@ the second screen and so on).
*Syntax*:
-------------------------------------
workspace <workspace> output <output>
workspace <workspace> output <output1> [output2]…
-------------------------------------
The 'output' is the name of the RandR output you attach your screen to. On a
@ -907,12 +907,15 @@ monitor name is “Dell UP2414Q”.
entire monitor, i3 will still use the entire area of the containing monitor
rather than that of just the output's.)
You can specify multiple outputs. The first available will be used.
If you use named workspaces, they must be quoted:
*Examples*:
---------------------------
workspace 1 output LVDS1
workspace 5 output VGA1
workspace 2 output primary
workspace 5 output VGA1 LVDS1
workspace "2: vim" output VGA1
---------------------------

View File

@ -38,6 +38,13 @@ Con *get_existing_workspace_by_name(const char *name);
*/
Con *get_existing_workspace_by_num(int num);
/**
* Returns true if the first output assigned to a workspace with the given
* workspace assignment is the same as the given output.
*
*/
bool output_triggers_assignment(Output *output, struct Workspace_Assignment *assignment);
/**
* Returns a pointer to the workspace with the given number (starting at 0),
* creating the workspace if necessary (by allocating the necessary amount of

View File

@ -273,7 +273,7 @@ state WORKSPACE_OUTPUT:
-> WORKSPACE_OUTPUT_STR
state WORKSPACE_OUTPUT_STR:
output = word
output = string
-> call cfg_workspace($workspace, $output)
# ipc-socket <path>

View File

@ -2019,6 +2019,9 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
LOG("Could not get output named \"%s\"\n", assignment->output);
continue;
}
if (!output_triggers_assignment(target_output, assignment)) {
continue;
}
workspace_move_to_output(workspace, target_output);
bool can_restore_focus = previously_focused != NULL;

View File

@ -322,8 +322,8 @@ CFGFUN(show_marks, const char *value) {
config.show_marks = eval_boolstr(value);
}
CFGFUN(workspace, const char *workspace, const char *output) {
DLOG("Assigning workspace \"%s\" to output \"%s\"\n", workspace, output);
CFGFUN(workspace, const char *workspace, const char *outputs) {
DLOG("Assigning workspace \"%s\" to outputs \"%s\"\n", workspace, outputs);
/* Check for earlier assignments of the same workspace so that we
* dont have assignments of a single workspace to different
* outputs */
@ -336,10 +336,16 @@ CFGFUN(workspace, const char *workspace, const char *output) {
}
}
assignment = scalloc(1, sizeof(struct Workspace_Assignment));
assignment->name = sstrdup(workspace);
assignment->output = sstrdup(output);
TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
char *buf = sstrdup(outputs);
char *output = strtok(buf, " ");
while (output != NULL) {
assignment = scalloc(1, sizeof(struct Workspace_Assignment));
assignment->name = sstrdup(workspace);
assignment->output = sstrdup(output);
TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
output = strtok(NULL, " ");
}
free(buf);
}
CFGFUN(ipc_socket, const char *path) {

View File

@ -424,9 +424,9 @@ void init_ws_for_output(Output *output, Con *content) {
/* go through all assignments and move the existing workspaces to this output */
struct Workspace_Assignment *assignment;
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
if (strcmp(assignment->output, output_primary_name(output)) != 0)
if (!output_triggers_assignment(output, assignment)) {
continue;
}
Con *workspace = get_existing_workspace_by_name(assignment->name);
if (workspace == NULL)
continue;
@ -501,8 +501,9 @@ void init_ws_for_output(Output *output, Con *content) {
/* otherwise, we create the first assigned ws for this output */
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
if (strcmp(assignment->output, output_primary_name(output)) != 0)
if (!output_triggers_assignment(output, assignment)) {
continue;
}
LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n",
assignment->name, assignment->output);

View File

@ -72,28 +72,47 @@ static void _workspace_apply_default_orientation(Con *ws) {
* given name or number or NULL if no such output exists. If there is a
* workspace with a matching name and another workspace with a matching number,
* the output assigned to the first one is returned.
* The order of the 'ws_assignments' queue is respected: if multiple assignments
* match the specified workspace, the first one is returned.
* If 'name' is NULL it will be ignored.
* If 'parsed_num' is -1 it will be ignored.
*
*/
static Con *get_assigned_output(const char *name, long parsed_num) {
Con *output = NULL;
struct Workspace_Assignment *assignment;
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
if (name && strcmp(assignment->name, name) == 0) {
DLOG("Found workspace name assignment to output \"%s\"\n", assignment->output);
GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
break;
} else if (parsed_num != -1 && name_is_digits(assignment->name) && ws_name_to_number(assignment->name) == parsed_num) {
Output *assigned_by_name = get_output_by_name(assignment->output, true);
if (assigned_by_name) {
/* When the name matches exactly, skip numbered assignments. */
return assigned_by_name->con;
}
} else if (!output && /* Only keep the first numbered assignment. */
parsed_num != -1 &&
name_is_digits(assignment->name) &&
ws_name_to_number(assignment->name) == parsed_num) {
DLOG("Found workspace number assignment to output \"%s\"\n", assignment->output);
GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
Output *assigned_by_num = get_output_by_name(assignment->output, true);
if (assigned_by_num) {
output = assigned_by_num->con;
}
}
}
return output;
}
/*
* Returns true if the first output assigned to a workspace with the given
* workspace assignment is the same as the given output.
*/
bool output_triggers_assignment(Output *output, struct Workspace_Assignment *assignment) {
Con *assigned = get_assigned_output(assignment->name, -1);
return assigned && assigned == output->con;
}
/*
* Returns a pointer to the workspace with the given number (starting at 0),
* creating the workspace if necessary (by allocating the necessary amount of
@ -957,8 +976,9 @@ bool workspace_move_to_output(Con *ws, Output *output) {
bool used_assignment = false;
struct Workspace_Assignment *assignment;
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
if (assignment->output == NULL || strcmp(assignment->output, output_primary_name(current_output)) != 0)
if (!output_triggers_assignment(current_output, assignment)) {
continue;
}
/* check if this workspace is already attached to the tree */
if (get_existing_workspace_by_name(assignment->name) != NULL) {
continue;

View File

@ -54,7 +54,49 @@ check_output('4', 'fake-3', 'First available number that is not assigned to exis
check_output('dontusethisname', '', 'Named workspace that is assigned to output that does not exist is not used');
check_output('donotoverride', '', 'Named workspace assigned to already occupied output is not used');
exit_gracefully($pid);
################################################################################
# Test multiple assignments
################################################################################
sub do_test {
my ($workspace, $output, $msg) = @_;
cmd 'focus output ' . ($output eq 'fake-3' ? 'fake-0' : 'fake-3');
cmd "workspace $workspace";
check_output($workspace, $output, $msg)
}
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
fake-outputs 1024x768+0+0P,1024x768+1024+0,1024x768+1024+768,1024x768+0+768
workspace 1 output fake-0
workspace 1 output fake-1 fake-2
workspace 2 output fake-3 fake-4 fake-0 fake-1
workspace 3 output these outputs do not exist but these do: fake-0 fake-3
workspace 4 output whitespace fake-0
workspace special output doesnotexist1 doesnotexist2 doesnotexist3
EOT
$pid = launch_with_config($config);
do_test('1', 'fake-0', 'Multiple assignments do not override a single one');
do_test('2', 'fake-3', 'First output out of multiple assignments is used');
do_test('3', 'fake-0', 'First existing output is used');
do_test('4', 'fake-0', 'Excessive whitespace is ok');
do_test('5', 'fake-1', 'Numbered initialization for fake-1');
do_test('6', 'fake-2', 'Numbered initialization for fake-2');
cmd 'focus output fake-0, workspace special';
check_output('special', 'fake-0', 'Workspace with only non-existing assigned outputs opened in current output.');
# Moving assigned workspaces.
cmd 'workspace 2, move workspace to output left';
check_output('2', 'fake-2', 'Moved assigned workspace up');
exit_gracefully($pid);
done_testing;

View File

@ -26,6 +26,8 @@ workspace 1:override output fake-0
workspace 2 output fake-0
workspace 1 output fake-1
workspace 2:override output fake-1
workspace 3 output fake-0
workspace 3:override output doesnotexist fake-1
fake-outputs 1024x768+0+0,1024x768+1024+0
EOT
@ -54,4 +56,10 @@ is(get_output_for_workspace('1:override'), 'fake-0',
'Assignment rules should not be affected by the order assignments are declared')
or diag 'Since workspace "1:override" is assigned by name to fake-0, it should open on fake-0';
cmd 'focus output fake-1';
cmd 'workspace "3:override"';
is(get_output_for_workspace('3:override'), 'fake-1',
'Assignment rules should not be affected by multiple output assignments')
or diag 'Since workspace "3:override" is assigned by name to fake-1, it should open on fake-1';
done_testing;

View File

@ -29,6 +29,8 @@ workspace 2 output fake-1
workspace 3:foo output fake-1
workspace baz output fake-1
workspace 5 output left
workspace 6 output doesnotexist fake-0
workspace 7 output fake-1 fake-0
EOT
##########################################################################
@ -91,4 +93,24 @@ cmd 'rename workspace 5 to 2';
is(get_output_for_workspace('2'), 'fake-1',
'Renaming a workspace so that it moves to the focused output which contains only an empty workspace should replace the empty workspace');
##########################################################################
# Renaming a workspace with multiple assignments, where the first output
# doesn't exist.
##########################################################################
cmd 'focus output fake-1';
cmd 'rename workspace to 6';
is(get_output_for_workspace('6'), 'fake-0',
'Renaming the workspace while first target output doesn\'t exist moves it to the second assigned output');
##########################################################################
# Renaming a workspace with multiple assignments, where both outputs exist
# moves it to the first output.
##########################################################################
cmd 'focus output fake-0';
cmd 'rename workspace to 7';
is(get_output_for_workspace('7'), 'fake-1',
'Renaming a workspace with multiple assignments, where both outputs exist moves it to the first output.');
done_testing;