Allow multiple outputs for workspace output

`i3 4.16` allows users to list multiple outputs for a workspace and the
first available will be used. The syntax is as follows:
`workspace <workspace> output <outputs...>`

Additionally when the workspace is created, the outputs get added to the
output priority list in the order specified. This ensures that if a higher
output gets connected, the workspace will move to the higher output. This
works the same way as if the user had a workspace on an output, disconnected
the output, and then later reconnected the output.
This commit is contained in:
Brian Ashworth 2018-11-11 11:22:38 -05:00
parent 80a1c340a9
commit 12876932a9
5 changed files with 66 additions and 25 deletions

View File

@ -183,7 +183,7 @@ struct side_gaps {
*/ */
struct workspace_config { struct workspace_config {
char *workspace; char *workspace;
char *output; list_t *outputs;
int gaps_inner; int gaps_inner;
struct side_gaps gaps_outer; struct side_gaps gaps_outer;
}; };

View File

@ -21,6 +21,7 @@ static struct workspace_config *workspace_config_find_or_create(char *ws_name) {
return NULL; return NULL;
} }
wsc->workspace = strdup(ws_name); wsc->workspace = strdup(ws_name);
wsc->outputs = create_list();
wsc->gaps_inner = INT_MIN; wsc->gaps_inner = INT_MIN;
wsc->gaps_outer.top = INT_MIN; wsc->gaps_outer.top = INT_MIN;
wsc->gaps_outer.right = INT_MIN; wsc->gaps_outer.right = INT_MIN;
@ -32,7 +33,7 @@ static struct workspace_config *workspace_config_find_or_create(char *ws_name) {
void free_workspace_config(struct workspace_config *wsc) { void free_workspace_config(struct workspace_config *wsc) {
free(wsc->workspace); free(wsc->workspace);
free(wsc->output); free_flat_list(wsc->outputs);
free(wsc); free(wsc);
} }
@ -141,18 +142,20 @@ struct cmd_results *cmd_workspace(int argc, char **argv) {
} }
} }
if (output_location >= 0) { if (output_location >= 0) {
if ((error = checkarg(argc, "workspace", EXPECTED_EQUAL_TO, output_location + 2))) { if ((error = checkarg(argc, "workspace", EXPECTED_AT_LEAST,
output_location + 2))) {
return error; return error;
} }
char *ws_name = join_args(argv, argc - 2); char *ws_name = join_args(argv, output_location);
struct workspace_config *wsc = workspace_config_find_or_create(ws_name); struct workspace_config *wsc = workspace_config_find_or_create(ws_name);
free(ws_name); free(ws_name);
if (!wsc) { if (!wsc) {
return cmd_results_new(CMD_FAILURE, "workspace output", return cmd_results_new(CMD_FAILURE, "workspace output",
"Unable to allocate workspace output"); "Unable to allocate workspace output");
} }
free(wsc->output); for (int i = output_location + 1; i < argc; ++i) {
wsc->output = strdup(argv[output_location + 1]); list_add(wsc->outputs, strdup(argv[i]));
}
} else if (gaps_location >= 0) { } else if (gaps_location >= 0) {
if ((error = cmd_workspace_gaps(argc, argv, gaps_location))) { if ((error = cmd_workspace_gaps(argc, argv, gaps_location))) {
return error; return error;

View File

@ -573,8 +573,12 @@ The default colors are:
Specifies that workspace _name_ should have the given gaps settings when it Specifies that workspace _name_ should have the given gaps settings when it
is created. is created.
*workspace* <name> output <output> *workspace* <name> output <outputs...>
Specifies that workspace _name_ should be shown on the specified _output_. Specifies that workspace _name_ should be shown on the specified _outputs_.
Multiple outputs can be listed and the first available will be used. If the
workspace gets placed on an output further down the list and an output that
is higher on the list becomes available, the workspace will be move to the
higher priority output.
*workspace\_auto\_back\_and\_forth* yes|no *workspace\_auto\_back\_and\_forth* yes|no
When _yes_, repeating a workspace switch command will switch back to the When _yes_, repeating a workspace switch command will switch back to the

View File

@ -31,6 +31,13 @@ static void restore_workspaces(struct sway_output *output) {
j--; j--;
} }
} }
if (other->workspaces->length == 0) {
char *next = workspace_next_name(other->wlr_output->name);
struct sway_workspace *ws = workspace_create(other, next);
free(next);
ipc_event_workspace(NULL, ws, "init");
}
} }
// Saved workspaces // Saved workspaces

View File

@ -33,16 +33,17 @@ struct workspace_config *workspace_find_config(const char *ws_name) {
struct sway_output *workspace_get_initial_output(const char *name) { struct sway_output *workspace_get_initial_output(const char *name) {
// Check workspace configs for a workspace<->output pair // Check workspace configs for a workspace<->output pair
struct workspace_config *wsc = workspace_find_config(name); struct workspace_config *wsc = workspace_find_config(name);
if (wsc && wsc->output) { if (wsc) {
struct sway_output *output = output_by_name(wsc->output); for (int i = 0; i < wsc->outputs->length; i++) {
struct sway_output *output = output_by_name(wsc->outputs->items[i]);
if (!output) { if (!output) {
output = output_by_identifier(wsc->output); output = output_by_identifier(wsc->outputs->items[i]);
} }
if (output) { if (output) {
return output; return output;
} }
} }
}
// Otherwise put it on the focused output // Otherwise put it on the focused output
struct sway_seat *seat = input_manager_current_seat(); struct sway_seat *seat = input_manager_current_seat();
struct sway_workspace *focus = seat_get_focused_workspace(seat); struct sway_workspace *focus = seat_get_focused_workspace(seat);
@ -85,7 +86,6 @@ struct sway_workspace *workspace_create(struct sway_output *output,
ws->floating = create_list(); ws->floating = create_list();
ws->tiling = create_list(); ws->tiling = create_list();
ws->output_priority = create_list(); ws->output_priority = create_list();
workspace_output_add_priority(ws, output);
ws->gaps_outer = config->gaps_outer; ws->gaps_outer = config->gaps_outer;
ws->gaps_inner = config->gaps_inner; ws->gaps_inner = config->gaps_inner;
@ -110,8 +110,16 @@ struct sway_workspace *workspace_create(struct sway_output *output,
// Since default outer gaps can be smaller than the negation of // Since default outer gaps can be smaller than the negation of
// workspace specific inner gaps, check outer gaps again // workspace specific inner gaps, check outer gaps again
prevent_invalid_outer_gaps(ws); prevent_invalid_outer_gaps(ws);
// Add output priorities
for (int i = 0; i < wsc->outputs->length; ++i) {
list_add(ws->output_priority, strdup(wsc->outputs->items[i]));
} }
} }
}
// If not already added, add the output to the lowest priority
workspace_output_add_priority(ws, output);
output_add_workspace(output, ws); output_add_workspace(output, ws);
output_sort_workspaces(output); output_sort_workspaces(output);
@ -134,8 +142,7 @@ void workspace_destroy(struct sway_workspace *workspace) {
free(workspace->name); free(workspace->name);
free(workspace->representation); free(workspace->representation);
list_foreach(workspace->output_priority, free); free_flat_list(workspace->output_priority);
list_free(workspace->output_priority);
list_free(workspace->floating); list_free(workspace->floating);
list_free(workspace->tiling); list_free(workspace->tiling);
list_free(workspace->current.floating); list_free(workspace->current.floating);
@ -178,7 +185,18 @@ static bool workspace_valid_on_output(const char *output_name,
struct sway_output *output = output_by_name(output_name); struct sway_output *output = output_by_name(output_name);
output_get_identifier(identifier, sizeof(identifier), output); output_get_identifier(identifier, sizeof(identifier), output);
return !wsc || !wsc->output || strcmp(wsc->output, output_name) == 0 || strcasecmp(identifier, output_name) == 0; if (!wsc) {
return true;
}
for (int i = 0; i < wsc->outputs->length; i++) {
if (strcmp(wsc->outputs->items[i], output_name) == 0 ||
strcmp(wsc->outputs->items[i], identifier) == 0) {
return true;
}
}
return false;
} }
static void workspace_name_from_binding(const struct sway_binding * binding, static void workspace_name_from_binding(const struct sway_binding * binding,
@ -281,13 +299,22 @@ char *workspace_next_name(const char *output_name) {
for (int i = 0; i < config->workspace_configs->length; ++i) { for (int i = 0; i < config->workspace_configs->length; ++i) {
// Unlike with bindings, this does not guarantee order // Unlike with bindings, this does not guarantee order
const struct workspace_config *wsc = config->workspace_configs->items[i]; const struct workspace_config *wsc = config->workspace_configs->items[i];
if (wsc->output && strcmp(wsc->output, output_name) == 0 if (workspace_by_name(wsc->workspace)) {
&& workspace_by_name(wsc->workspace) == NULL) { continue;
}
bool found = false;
for (int j = 0; j < wsc->outputs->length; ++j) {
if (strcmp(wsc->outputs->items[j], output_name) == 0) {
found = true;
free(target); free(target);
target = strdup(wsc->workspace); target = strdup(wsc->workspace);
break; break;
} }
} }
if (found) {
break;
}
}
if (target != NULL) { if (target != NULL) {
return target; return target;
} }