swayfx/sway/commands/workspace.c
Brian Ashworth 9e8aa39530 Implement per side and per direction outer gaps
This introduces the following command extensions from `i3-gaps`:
* `gaps horizontal|vertical|top|right|bottom|left <amount>`
* `gaps horizontal|vertical|top|right|bottom|left all|current
set|plus|minus <amount>`
* `workspace <ws> gaps horizontal|vertical|top|right|bottom|left
<amount>`

`inner` and `outer` are also still available as options for all three
of the above commands. `outer` now acts as a shorthand to set/alter
all sides.

Additionally, this fixes two bugs with the prevention of invalid gap
configurations for workspace configs:
1. If outer gaps were not set and inner gaps were, the outer gaps
would be snapped to the negation of the inner gaps due to `INT_MIN`
being less than the negation. This took precedence over the default
outer gaps.
2. Similarly, if inner gaps were not set and outer gaps were, inner
gaps would be set to zero, which would take precedence over the
default inner gaps.

Fixing both of the above items also requires checking the gaps again
when creating a workspace since the default outer gaps can be smaller
than the negation of the workspace specific inner gaps.
2018-11-07 22:44:11 -05:00

220 lines
6.2 KiB
C

#define _XOPEN_SOURCE 500
#include <ctype.h>
#include <limits.h>
#include <string.h>
#include <strings.h>
#include "sway/commands.h"
#include "sway/config.h"
#include "sway/input/seat.h"
#include "sway/tree/workspace.h"
#include "list.h"
#include "log.h"
#include "stringop.h"
static struct workspace_config *workspace_config_find_or_create(char *ws_name) {
struct workspace_config *wsc = workspace_find_config(ws_name);
if (wsc) {
return wsc;
}
wsc = calloc(1, sizeof(struct workspace_config));
if (!wsc) {
return NULL;
}
wsc->workspace = strdup(ws_name);
wsc->gaps_inner = INT_MIN;
wsc->gaps_outer.top = INT_MIN;
wsc->gaps_outer.right = INT_MIN;
wsc->gaps_outer.bottom = INT_MIN;
wsc->gaps_outer.left = INT_MIN;
list_add(config->workspace_configs, wsc);
return wsc;
}
void free_workspace_config(struct workspace_config *wsc) {
free(wsc->workspace);
free(wsc->output);
free(wsc);
}
static void prevent_invalid_outer_gaps(struct workspace_config *wsc) {
if (wsc->gaps_outer.top != INT_MIN &&
wsc->gaps_outer.top < -wsc->gaps_inner) {
wsc->gaps_outer.top = -wsc->gaps_inner;
}
if (wsc->gaps_outer.right != INT_MIN &&
wsc->gaps_outer.right < -wsc->gaps_inner) {
wsc->gaps_outer.right = -wsc->gaps_inner;
}
if (wsc->gaps_outer.bottom != INT_MIN &&
wsc->gaps_outer.bottom < -wsc->gaps_inner) {
wsc->gaps_outer.bottom = -wsc->gaps_inner;
}
if (wsc->gaps_outer.left != INT_MIN &&
wsc->gaps_outer.left < -wsc->gaps_inner) {
wsc->gaps_outer.left = -wsc->gaps_inner;
}
}
static struct cmd_results *cmd_workspace_gaps(int argc, char **argv,
int gaps_location) {
const char *expected = "Expected 'workspace <name> gaps "
"inner|outer|horizontal|vertical|top|right|bottom|left <px>'";
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "workspace", EXPECTED_EQUAL_TO,
gaps_location + 3))) {
return error;
}
char *ws_name = join_args(argv, argc - 3);
struct workspace_config *wsc = workspace_config_find_or_create(ws_name);
free(ws_name);
if (!wsc) {
return cmd_results_new(CMD_FAILURE, "workspace gaps",
"Unable to allocate workspace output");
}
char *end;
int amount = strtol(argv[gaps_location + 2], &end, 10);
if (strlen(end)) {
free(end);
return cmd_results_new(CMD_FAILURE, "workspace gaps", expected);
}
bool valid = false;
char *type = argv[gaps_location + 1];
if (!strcasecmp(type, "inner")) {
valid = true;
wsc->gaps_inner = (amount >= 0) ? amount : 0;
} else {
if (!strcasecmp(type, "outer") || !strcasecmp(type, "vertical")
|| !strcasecmp(type, "top")) {
valid = true;
wsc->gaps_outer.top = amount;
}
if (!strcasecmp(type, "outer") || !strcasecmp(type, "horizontal")
|| !strcasecmp(type, "right")) {
valid = true;
wsc->gaps_outer.right = amount;
}
if (!strcasecmp(type, "outer") || !strcasecmp(type, "vertical")
|| !strcasecmp(type, "bottom")) {
valid = true;
wsc->gaps_outer.bottom = amount;
}
if (!strcasecmp(type, "outer") || !strcasecmp(type, "horizontal")
|| !strcasecmp(type, "left")) {
valid = true;
wsc->gaps_outer.left = amount;
}
}
if (!valid) {
return cmd_results_new(CMD_INVALID, "workspace gaps", expected);
}
// Prevent invalid gaps configurations.
if (wsc->gaps_inner != INT_MIN && wsc->gaps_inner < 0) {
wsc->gaps_inner = 0;
}
prevent_invalid_outer_gaps(wsc);
return error;
}
struct cmd_results *cmd_workspace(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "workspace", EXPECTED_AT_LEAST, 1))) {
return error;
}
int output_location = -1;
int gaps_location = -1;
for (int i = 0; i < argc; ++i) {
if (strcasecmp(argv[i], "output") == 0) {
output_location = i;
break;
}
}
for (int i = 0; i < argc; ++i) {
if (strcasecmp(argv[i], "gaps") == 0) {
gaps_location = i;
break;
}
}
if (output_location >= 0) {
if ((error = checkarg(argc, "workspace", EXPECTED_EQUAL_TO, output_location + 2))) {
return error;
}
char *ws_name = join_args(argv, argc - 2);
struct workspace_config *wsc = workspace_config_find_or_create(ws_name);
free(ws_name);
if (!wsc) {
return cmd_results_new(CMD_FAILURE, "workspace output",
"Unable to allocate workspace output");
}
free(wsc->output);
wsc->output = strdup(argv[output_location + 1]);
} else if (gaps_location >= 0) {
if ((error = cmd_workspace_gaps(argc, argv, gaps_location))) {
return error;
}
} else {
if (config->reading || !config->active) {
return cmd_results_new(CMD_DEFER, "workspace", NULL);
} else if (!root->outputs->length) {
return cmd_results_new(CMD_INVALID, "workspace",
"Can't run this command while there's no outputs connected.");
}
bool no_auto_back_and_forth = false;
while (strcasecmp(argv[0], "--no-auto-back-and-forth") == 0) {
no_auto_back_and_forth = true;
if ((error = checkarg(--argc, "workspace", EXPECTED_AT_LEAST, 1))) {
return error;
}
++argv;
}
struct sway_workspace *ws = NULL;
if (strcasecmp(argv[0], "number") == 0) {
if (argc < 2) {
return cmd_results_new(CMD_INVALID, "workspace",
"Expected workspace number");
}
if (!isdigit(argv[1][0])) {
return cmd_results_new(CMD_INVALID, "workspace",
"Invalid workspace number '%s'", argv[1]);
}
if (!(ws = workspace_by_number(argv[1]))) {
char *name = join_args(argv + 1, argc - 1);
ws = workspace_create(NULL, name);
free(name);
}
} else if (strcasecmp(argv[0], "next") == 0 ||
strcasecmp(argv[0], "prev") == 0 ||
strcasecmp(argv[0], "next_on_output") == 0 ||
strcasecmp(argv[0], "prev_on_output") == 0 ||
strcasecmp(argv[0], "current") == 0) {
ws = workspace_by_name(argv[0]);
} else if (strcasecmp(argv[0], "back_and_forth") == 0) {
struct sway_seat *seat = config->handler_context.seat;
if (!seat->prev_workspace_name) {
return cmd_results_new(CMD_INVALID, "workspace",
"There is no previous workspace");
}
if (!(ws = workspace_by_name(argv[0]))) {
ws = workspace_create(NULL, seat->prev_workspace_name);
}
} else {
char *name = join_args(argv, argc);
if (!(ws = workspace_by_name(name))) {
ws = workspace_create(NULL, name);
}
free(name);
}
workspace_switch(ws, no_auto_back_and_forth);
seat_consider_warp_to_focus(config->handler_context.seat);
}
return cmd_results_new(CMD_SUCCESS, NULL, NULL);
}