diff --git a/include/sway/config.h b/include/sway/config.h index 0912bc73..cd56c3dc 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -167,6 +167,16 @@ struct output_config { enum config_dpms dpms_state; }; +/** + * Stores size of gaps for each side + */ +struct side_gaps { + int top; + int right; + int bottom; + int left; +}; + /** * Stores configuration for a workspace, regardless of whether the workspace * exists. @@ -175,7 +185,7 @@ struct workspace_config { char *workspace; char *output; int gaps_inner; - int gaps_outer; + struct side_gaps gaps_outer; }; struct bar_config { @@ -398,7 +408,7 @@ struct sway_config { bool smart_gaps; int gaps_inner; - int gaps_outer; + struct side_gaps gaps_outer; list_t *config_chain; const char *current_config_path; diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h index b5ae92f3..7abfbff1 100644 --- a/include/sway/tree/workspace.h +++ b/include/sway/tree/workspace.h @@ -32,9 +32,9 @@ struct sway_workspace { enum sway_container_layout layout; enum sway_container_layout prev_split_layout; - int current_gaps; + struct side_gaps current_gaps; int gaps_inner; - int gaps_outer; + struct side_gaps gaps_outer; struct sway_output *output; // NULL if no outputs are connected list_t *floating; // struct sway_container diff --git a/sway/commands/gaps.c b/sway/commands/gaps.c index 3f0ef155..faaeab37 100644 --- a/sway/commands/gaps.c +++ b/sway/commands/gaps.c @@ -16,73 +16,128 @@ enum gaps_op { struct gaps_data { bool inner; + struct { + bool top; + bool right; + bool bottom; + bool left; + } outer; enum gaps_op operation; int amount; }; -// gaps inner|outer +// Prevent negative outer gaps from moving windows out of the workspace. +static void prevent_invalid_outer_gaps(void) { + if (config->gaps_outer.top < -config->gaps_inner) { + config->gaps_outer.top = -config->gaps_inner; + } + if (config->gaps_outer.right < -config->gaps_inner) { + config->gaps_outer.right = -config->gaps_inner; + } + if (config->gaps_outer.bottom < -config->gaps_inner) { + config->gaps_outer.bottom = -config->gaps_inner; + } + if (config->gaps_outer.left < -config->gaps_inner) { + config->gaps_outer.left = -config->gaps_inner; + } +} + +// gaps inner|outer|horizontal|vertical|top|right|bottom|left +static const char *expected_defaults = + "'gaps inner|outer|horizontal|vertical|top|right|bottom|left '"; static struct cmd_results *gaps_set_defaults(int argc, char **argv) { struct cmd_results *error = checkarg(argc, "gaps", EXPECTED_EQUAL_TO, 2); if (error) { return error; } - bool inner; - if (strcasecmp(argv[0], "inner") == 0) { - inner = true; - } else if (strcasecmp(argv[0], "outer") == 0) { - inner = false; - } else { - return cmd_results_new(CMD_INVALID, "gaps", - "Expected 'gaps inner|outer '"); - } - char *end; int amount = strtol(argv[1], &end, 10); if (strlen(end) && strcasecmp(end, "px") != 0) { return cmd_results_new(CMD_INVALID, "gaps", - "Expected 'gaps inner|outer '"); + "Expected %s", expected_defaults); } - if (inner) { + + bool valid = false; + if (!strcasecmp(argv[0], "inner")) { + valid = true; config->gaps_inner = (amount >= 0) ? amount : 0; } else { - config->gaps_outer = amount; - } - - // Prevent negative outer gaps from moving windows out of the workspace. - if (config->gaps_outer < -config->gaps_inner) { - config->gaps_outer = -config->gaps_inner; + if (!strcasecmp(argv[0], "outer") || !strcasecmp(argv[0], "vertical") + || !strcasecmp(argv[0], "top")) { + valid = true; + config->gaps_outer.top = amount; + } + if (!strcasecmp(argv[0], "outer") || !strcasecmp(argv[0], "horizontal") + || !strcasecmp(argv[0], "right")) { + valid = true; + config->gaps_outer.right = amount; + } + if (!strcasecmp(argv[0], "outer") || !strcasecmp(argv[0], "vertical") + || !strcasecmp(argv[0], "bottom")) { + valid = true; + config->gaps_outer.bottom = amount; + } + if (!strcasecmp(argv[0], "outer") || !strcasecmp(argv[0], "horizontal") + || !strcasecmp(argv[0], "left")) { + valid = true; + config->gaps_outer.left = amount; + } + } + if (!valid) { + return cmd_results_new(CMD_INVALID, "gaps", + "Expected %s", expected_defaults); } + prevent_invalid_outer_gaps(); return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static void configure_gaps(struct sway_workspace *ws, void *_data) { - struct gaps_data *data = _data; - int *prop = data->inner ? &ws->gaps_inner : &ws->gaps_outer; - - switch (data->operation) { +static void apply_gaps_op(int *prop, enum gaps_op op, int amount) { + switch (op) { case GAPS_OP_SET: - *prop = data->amount; + *prop = amount; break; case GAPS_OP_ADD: - *prop += data->amount; + *prop += amount; break; case GAPS_OP_SUBTRACT: - *prop -= data->amount; + *prop -= amount; break; } +} + +static void configure_gaps(struct sway_workspace *ws, void *_data) { + // Apply operation to gaps + struct gaps_data *data = _data; + if (data->inner) { + apply_gaps_op(&ws->gaps_inner, data->operation, data->amount); + } + if (data->outer.top) { + apply_gaps_op(&(ws->gaps_outer.top), data->operation, data->amount); + } + if (data->outer.right) { + apply_gaps_op(&(ws->gaps_outer.right), data->operation, data->amount); + } + if (data->outer.bottom) { + apply_gaps_op(&(ws->gaps_outer.bottom), data->operation, data->amount); + } + if (data->outer.left) { + apply_gaps_op(&(ws->gaps_outer.left), data->operation, data->amount); + } + // Prevent invalid gaps configurations. if (ws->gaps_inner < 0) { ws->gaps_inner = 0; } - if (ws->gaps_outer < -ws->gaps_inner) { - ws->gaps_outer = -ws->gaps_inner; - } + prevent_invalid_outer_gaps(); arrange_workspace(ws); } -// gaps inner|outer current|all set|plus|minus +// gaps inner|outer|horizontal|vertical|top|right|bottom|left current|all +// set|plus|minus +static const char *expected_runtime = "'gaps inner|outer|horizontal|vertical|" + "top|right|bottom|left current|all set|plus|minus '"; static struct cmd_results *gaps_set_runtime(int argc, char **argv) { struct cmd_results *error = checkarg(argc, "gaps", EXPECTED_EQUAL_TO, 4); if (error) { @@ -93,15 +148,24 @@ static struct cmd_results *gaps_set_runtime(int argc, char **argv) { "Can't run this command while there's no outputs connected."); } - struct gaps_data data; + struct gaps_data data = {0}; if (strcasecmp(argv[0], "inner") == 0) { data.inner = true; - } else if (strcasecmp(argv[0], "outer") == 0) { - data.inner = false; } else { + data.outer.top = !strcasecmp(argv[0], "outer") || + !strcasecmp(argv[0], "vertical") || !strcasecmp(argv[0], "top"); + data.outer.right = !strcasecmp(argv[0], "outer") || + !strcasecmp(argv[0], "horizontal") || !strcasecmp(argv[0], "right"); + data.outer.bottom = !strcasecmp(argv[0], "outer") || + !strcasecmp(argv[0], "vertical") || !strcasecmp(argv[0], "bottom"); + data.outer.left = !strcasecmp(argv[0], "outer") || + !strcasecmp(argv[0], "horizontal") || !strcasecmp(argv[0], "left"); + } + if (!data.inner && !data.outer.top && !data.outer.right && + !data.outer.bottom && !data.outer.left) { return cmd_results_new(CMD_INVALID, "gaps", - "Expected 'gaps inner|outer current|all set|plus|minus '"); + "Expected %s", expected_runtime); } bool all; @@ -111,7 +175,7 @@ static struct cmd_results *gaps_set_runtime(int argc, char **argv) { all = true; } else { return cmd_results_new(CMD_INVALID, "gaps", - "Expected 'gaps inner|outer current|all set|plus|minus '"); + "Expected %s", expected_runtime); } if (strcasecmp(argv[2], "set") == 0) { @@ -122,14 +186,14 @@ static struct cmd_results *gaps_set_runtime(int argc, char **argv) { data.operation = GAPS_OP_SUBTRACT; } else { return cmd_results_new(CMD_INVALID, "gaps", - "Expected 'gaps inner|outer current|all set|plus|minus '"); + "Expected %s", expected_runtime); } char *end; data.amount = strtol(argv[3], &end, 10); if (strlen(end) && strcasecmp(end, "px") != 0) { return cmd_results_new(CMD_INVALID, "gaps", - "Expected 'gaps inner|outer current|all set|plus|minus '"); + "Expected %s", expected_runtime); } if (all) { @@ -141,8 +205,10 @@ static struct cmd_results *gaps_set_runtime(int argc, char **argv) { return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -// gaps inner|outer - sets defaults for workspaces -// gaps inner|outer current|all set|plus|minus - runtime only +// gaps inner|outer|| - sets defaults for workspaces +// gaps inner|outer|| current|all set|plus|minus - runtime only +// = horizontal|vertical +// = top|right|bottom|left struct cmd_results *cmd_gaps(int argc, char **argv) { struct cmd_results *error = checkarg(argc, "gaps", EXPECTED_AT_LEAST, 2); if (error) { @@ -159,9 +225,8 @@ struct cmd_results *cmd_gaps(int argc, char **argv) { } if (config_loading) { return cmd_results_new(CMD_INVALID, "gaps", - "Expected 'gaps inner|outer '"); + "Expected %s", expected_defaults); } return cmd_results_new(CMD_INVALID, "gaps", - "Expected 'gaps inner|outer ' or " - "'gaps inner|outer current|all set|plus|minus '"); + "Expected %s or %s", expected_runtime, expected_defaults); } diff --git a/sway/commands/workspace.c b/sway/commands/workspace.c index 5abbb676..168494d2 100644 --- a/sway/commands/workspace.c +++ b/sway/commands/workspace.c @@ -22,7 +22,10 @@ static struct workspace_config *workspace_config_find_or_create(char *ws_name) { } wsc->workspace = strdup(ws_name); wsc->gaps_inner = INT_MIN; - wsc->gaps_outer = 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; } @@ -33,6 +36,89 @@ void free_workspace_config(struct workspace_config *wsc) { 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 gaps " + "inner|outer|horizontal|vertical|top|right|bottom|left '"; + 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))) { @@ -68,43 +154,9 @@ struct cmd_results *cmd_workspace(int argc, char **argv) { free(wsc->output); wsc->output = strdup(argv[output_location + 1]); } else if (gaps_location >= 0) { - if ((error = checkarg(argc, "workspace", EXPECTED_EQUAL_TO, gaps_location + 3))) { + if ((error = cmd_workspace_gaps(argc, argv, gaps_location))) { 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"); - } - int *prop = NULL; - if (strcasecmp(argv[gaps_location + 1], "inner") == 0) { - prop = &wsc->gaps_inner; - } else if (strcasecmp(argv[gaps_location + 1], "outer") == 0) { - prop = &wsc->gaps_outer; - } else { - return cmd_results_new(CMD_FAILURE, "workspace gaps", - "Expected 'workspace gaps inner|outer '"); - } - char *end; - int val = strtol(argv[gaps_location + 2], &end, 10); - - if (strlen(end)) { - free(end); - return cmd_results_new(CMD_FAILURE, "workspace gaps", - "Expected 'workspace gaps inner|outer '"); - } - *prop = val; - - // Prevent invalid gaps configurations. - if (wsc->gaps_inner < 0) { - wsc->gaps_inner = 0; - } - if (wsc->gaps_outer < -wsc->gaps_inner) { - wsc->gaps_outer = -wsc->gaps_inner; - } - } else { if (config->reading || !config->active) { return cmd_results_new(CMD_DEFER, "workspace", NULL); diff --git a/sway/config.c b/sway/config.c index 64653024..0fdd1723 100644 --- a/sway/config.c +++ b/sway/config.c @@ -234,7 +234,10 @@ static void config_defaults(struct sway_config *config) { config->smart_gaps = false; config->gaps_inner = 0; - config->gaps_outer = 0; + config->gaps_outer.top = 0; + config->gaps_outer.right = 0; + config->gaps_outer.bottom = 0; + config->gaps_outer.left = 0; if (!(config->active_bar_modifiers = create_list())) goto cleanup; diff --git a/sway/sway.5.scd b/sway/sway.5.scd index 4a645837..3bf6233d 100644 --- a/sway/sway.5.scd +++ b/sway/sway.5.scd @@ -133,9 +133,12 @@ They are expected to be used with *bindsym* or at runtime through *swaymsg*(1). *fullscreen* Toggles fullscreen for the focused view. -*gaps* inner|outer all|current set|plus|minus +*gaps* inner|outer|horizontal|vertical|top|right|bottom|left all|current +set|plus|minus Changes the _inner_ or _outer_ gaps for either _all_ workspaces or the - _current_ workspace. + _current_ workspace. _outer_ gaps can be altered per side with _top_, + _right_, _bottom_, and _left_ or per direction with _horizontal_ and + _vertical_. *layout* default|splith|splitv|stacking|tabbed Sets the layout mode of the focused container. @@ -429,14 +432,16 @@ The default colors are: _focus\_wrapping force_. This is only available for convenience. Please use _focus\_wrapping_ instead when possible. -*gaps* inner|outer +*gaps* inner|outer|horizontal|vertical|top|right|bottom|left Sets default _amount_ pixels of _inner_ or _outer_ gap, where the inner affects spacing around each view and outer affects the spacing around each workspace. Outer gaps are in addition to inner gaps. To reduce or remove - outer gaps, outer gaps can be set to a negative value. + outer gaps, outer gaps can be set to a negative value. _outer_ gaps can + also be specified per side with _top_, _right_, _bottom_, and _left_ or + per direction with _horizontal_ and _vertical_. This affects new workspaces only, and is used when the workspace doesn't - have its own gaps settings (see: workspace gaps inner|outer ). + have its own gaps settings (see: workspace gaps ...). *hide\_edge\_borders* none|vertical|horizontal|both|smart|smart\_no\_gaps Hides window borders adjacent to the screen edges. Default is _none_. @@ -549,7 +554,8 @@ The default colors are: *workspace* back\_and\_forth Switches to the previously focused workspace. -*workspace* gaps inner|outer +*workspace* gaps inner|outer|horizontal|vertical|top|right|bottom|left + Specifies that workspace _name_ should have the given gaps settings when it is created. diff --git a/sway/tree/view.c b/sway/tree/view.c index 1aa59e68..7ae8efaa 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -190,7 +190,8 @@ static bool gaps_to_edge(struct sway_view *view) { } con = con->parent; } - return view->container->workspace->current_gaps > 0; + struct side_gaps gaps = view->container->workspace->current_gaps; + return gaps.top > 0 || gaps.right > 0 || gaps.bottom > 0 || gaps.left > 0; } void view_autoconfigure(struct sway_view *view) { diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c index 05cda5c0..93ce50de 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c @@ -49,6 +49,21 @@ struct sway_output *workspace_get_initial_output(const char *name) { return focus->output; } +static void prevent_invalid_outer_gaps(struct sway_workspace *ws) { + if (ws->gaps_outer.top < -ws->gaps_inner) { + ws->gaps_outer.top = -ws->gaps_inner; + } + if (ws->gaps_outer.right < -ws->gaps_inner) { + ws->gaps_outer.right = -ws->gaps_inner; + } + if (ws->gaps_outer.bottom < -ws->gaps_inner) { + ws->gaps_outer.bottom = -ws->gaps_inner; + } + if (ws->gaps_outer.left < -ws->gaps_inner) { + ws->gaps_outer.left = -ws->gaps_inner; + } +} + struct sway_workspace *workspace_create(struct sway_output *output, const char *name) { if (output == NULL) { @@ -77,12 +92,24 @@ struct sway_workspace *workspace_create(struct sway_output *output, if (name) { struct workspace_config *wsc = workspace_find_config(name); if (wsc) { - if (wsc->gaps_outer != INT_MIN) { - ws->gaps_outer = wsc->gaps_outer; + if (wsc->gaps_outer.top != INT_MIN) { + ws->gaps_outer.top = wsc->gaps_outer.top; + } + if (wsc->gaps_outer.right != INT_MIN) { + ws->gaps_outer.right = wsc->gaps_outer.right; + } + if (wsc->gaps_outer.bottom != INT_MIN) { + ws->gaps_outer.bottom = wsc->gaps_outer.bottom; + } + if (wsc->gaps_outer.left != INT_MIN) { + ws->gaps_outer.left = wsc->gaps_outer.left; } if (wsc->gaps_inner != INT_MIN) { ws->gaps_inner = wsc->gaps_inner; } + // Since default outer gaps can be smaller than the negation of + // workspace specific inner gaps, check outer gaps again + prevent_invalid_outer_gaps(ws); } } @@ -615,19 +642,25 @@ void workspace_insert_tiling(struct sway_workspace *workspace, } void workspace_remove_gaps(struct sway_workspace *ws) { - if (ws->current_gaps == 0) { + if (ws->current_gaps.top == 0 && ws->current_gaps.right == 0 && + ws->current_gaps.bottom == 0 && ws->current_gaps.left == 0) { return; } - ws->width += ws->current_gaps * 2; - ws->height += ws->current_gaps * 2; - ws->x -= ws->current_gaps; - ws->y -= ws->current_gaps; - ws->current_gaps = 0; + ws->width += ws->current_gaps.left + ws->current_gaps.right; + ws->height += ws->current_gaps.top + ws->current_gaps.bottom; + ws->x -= ws->current_gaps.left; + ws->y -= ws->current_gaps.top; + + ws->current_gaps.top = 0; + ws->current_gaps.right = 0; + ws->current_gaps.bottom = 0; + ws->current_gaps.left = 0; } void workspace_add_gaps(struct sway_workspace *ws) { - if (ws->current_gaps > 0) { + if (ws->current_gaps.top > 0 || ws->current_gaps.right > 0 || + ws->current_gaps.bottom > 0 || ws->current_gaps.left > 0) { return; } if (config->smart_gaps) { @@ -643,18 +676,20 @@ void workspace_add_gaps(struct sway_workspace *ws) { } ws->current_gaps = ws->gaps_outer; - if (ws->layout == L_TABBED || ws->layout == L_STACKED) { // We have to add inner gaps for this, because children of tabbed and // stacked containers don't apply their own gaps - they assume the // tabbed/stacked container is using gaps. - ws->current_gaps += ws->gaps_inner; + ws->current_gaps.top += ws->gaps_inner; + ws->current_gaps.right += ws->gaps_inner; + ws->current_gaps.bottom += ws->gaps_inner; + ws->current_gaps.left += ws->gaps_inner; } - ws->x += ws->current_gaps; - ws->y += ws->current_gaps; - ws->width -= 2 * ws->current_gaps; - ws->height -= 2 * ws->current_gaps; + ws->x += ws->current_gaps.left; + ws->y += ws->current_gaps.top; + ws->width -= ws->current_gaps.left + ws->current_gaps.right; + ws->height -= ws->current_gaps.top + ws->current_gaps.bottom; } struct sway_container *workspace_split(struct sway_workspace *workspace,