aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/sway/config.h14
-rw-r--r--include/sway/tree/container.h7
-rw-r--r--include/sway/tree/workspace.h4
-rw-r--r--sway/commands/gaps.c151
-rw-r--r--sway/commands/resize.c47
-rw-r--r--sway/commands/swap.c2
-rw-r--r--sway/commands/workspace.c124
-rw-r--r--sway/config.c5
-rw-r--r--sway/sway.5.scd46
-rw-r--r--sway/tree/container.c34
-rw-r--r--sway/tree/view.c14
-rw-r--r--sway/tree/workspace.c65
12 files changed, 363 insertions, 150 deletions
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
@@ -168,6 +168,16 @@ struct output_config {
168}; 168};
169 169
170/** 170/**
171 * Stores size of gaps for each side
172 */
173struct side_gaps {
174 int top;
175 int right;
176 int bottom;
177 int left;
178};
179
180/**
171 * Stores configuration for a workspace, regardless of whether the workspace 181 * Stores configuration for a workspace, regardless of whether the workspace
172 * exists. 182 * exists.
173 */ 183 */
@@ -175,7 +185,7 @@ struct workspace_config {
175 char *workspace; 185 char *workspace;
176 char *output; 186 char *output;
177 int gaps_inner; 187 int gaps_inner;
178 int gaps_outer; 188 struct side_gaps gaps_outer;
179}; 189};
180 190
181struct bar_config { 191struct bar_config {
@@ -398,7 +408,7 @@ struct sway_config {
398 408
399 bool smart_gaps; 409 bool smart_gaps;
400 int gaps_inner; 410 int gaps_inner;
401 int gaps_outer; 411 struct side_gaps gaps_outer;
402 412
403 list_t *config_chain; 413 list_t *config_chain;
404 const char *current_config_path; 414 const char *current_config_path;
diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h
index 4366a010..d3155eb3 100644
--- a/include/sway/tree/container.h
+++ b/include/sway/tree/container.h
@@ -104,7 +104,12 @@ struct sway_container {
104 bool border_right; 104 bool border_right;
105 105
106 // The gaps currently applied to the container. 106 // The gaps currently applied to the container.
107 double current_gaps; 107 struct {
108 int top;
109 int right;
110 int bottom;
111 int left;
112 } current_gaps;
108 113
109 struct sway_workspace *workspace; // NULL when hidden in the scratchpad 114 struct sway_workspace *workspace; // NULL when hidden in the scratchpad
110 struct sway_container *parent; // NULL if container in root of workspace 115 struct sway_container *parent; // NULL if container in root of workspace
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 {
32 enum sway_container_layout layout; 32 enum sway_container_layout layout;
33 enum sway_container_layout prev_split_layout; 33 enum sway_container_layout prev_split_layout;
34 34
35 int current_gaps; 35 struct side_gaps current_gaps;
36 int gaps_inner; 36 int gaps_inner;
37 int gaps_outer; 37 struct side_gaps gaps_outer;
38 38
39 struct sway_output *output; // NULL if no outputs are connected 39 struct sway_output *output; // NULL if no outputs are connected
40 list_t *floating; // struct sway_container 40 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 {
16 16
17struct gaps_data { 17struct gaps_data {
18 bool inner; 18 bool inner;
19 struct {
20 bool top;
21 bool right;
22 bool bottom;
23 bool left;
24 } outer;
19 enum gaps_op operation; 25 enum gaps_op operation;
20 int amount; 26 int amount;
21}; 27};
22 28
23// gaps inner|outer <px> 29// Prevent negative outer gaps from moving windows out of the workspace.
30static void prevent_invalid_outer_gaps(void) {
31 if (config->gaps_outer.top < -config->gaps_inner) {
32 config->gaps_outer.top = -config->gaps_inner;
33 }
34 if (config->gaps_outer.right < -config->gaps_inner) {
35 config->gaps_outer.right = -config->gaps_inner;
36 }
37 if (config->gaps_outer.bottom < -config->gaps_inner) {
38 config->gaps_outer.bottom = -config->gaps_inner;
39 }
40 if (config->gaps_outer.left < -config->gaps_inner) {
41 config->gaps_outer.left = -config->gaps_inner;
42 }
43}
44
45// gaps inner|outer|horizontal|vertical|top|right|bottom|left <px>
46static const char *expected_defaults =
47 "'gaps inner|outer|horizontal|vertical|top|right|bottom|left <px>'";
24static struct cmd_results *gaps_set_defaults(int argc, char **argv) { 48static struct cmd_results *gaps_set_defaults(int argc, char **argv) {
25 struct cmd_results *error = checkarg(argc, "gaps", EXPECTED_EQUAL_TO, 2); 49 struct cmd_results *error = checkarg(argc, "gaps", EXPECTED_EQUAL_TO, 2);
26 if (error) { 50 if (error) {
27 return error; 51 return error;
28 } 52 }
29 53
30 bool inner;
31 if (strcasecmp(argv[0], "inner") == 0) {
32 inner = true;
33 } else if (strcasecmp(argv[0], "outer") == 0) {
34 inner = false;
35 } else {
36 return cmd_results_new(CMD_INVALID, "gaps",
37 "Expected 'gaps inner|outer <px>'");
38 }
39
40 char *end; 54 char *end;
41 int amount = strtol(argv[1], &end, 10); 55 int amount = strtol(argv[1], &end, 10);
42 if (strlen(end) && strcasecmp(end, "px") != 0) { 56 if (strlen(end) && strcasecmp(end, "px") != 0) {
43 return cmd_results_new(CMD_INVALID, "gaps", 57 return cmd_results_new(CMD_INVALID, "gaps",
44 "Expected 'gaps inner|outer <px>'"); 58 "Expected %s", expected_defaults);
45 } 59 }
46 if (inner) { 60
61 bool valid = false;
62 if (!strcasecmp(argv[0], "inner")) {
63 valid = true;
47 config->gaps_inner = (amount >= 0) ? amount : 0; 64 config->gaps_inner = (amount >= 0) ? amount : 0;
48 } else { 65 } else {
49 config->gaps_outer = amount; 66 if (!strcasecmp(argv[0], "outer") || !strcasecmp(argv[0], "vertical")
50 } 67 || !strcasecmp(argv[0], "top")) {
51 68 valid = true;
52 // Prevent negative outer gaps from moving windows out of the workspace. 69 config->gaps_outer.top = amount;
53 if (config->gaps_outer < -config->gaps_inner) { 70 }
54 config->gaps_outer = -config->gaps_inner; 71 if (!strcasecmp(argv[0], "outer") || !strcasecmp(argv[0], "horizontal")
72 || !strcasecmp(argv[0], "right")) {
73 valid = true;
74 config->gaps_outer.right = amount;
75 }
76 if (!strcasecmp(argv[0], "outer") || !strcasecmp(argv[0], "vertical")
77 || !strcasecmp(argv[0], "bottom")) {
78 valid = true;
79 config->gaps_outer.bottom = amount;
80 }
81 if (!strcasecmp(argv[0], "outer") || !strcasecmp(argv[0], "horizontal")
82 || !strcasecmp(argv[0], "left")) {
83 valid = true;
84 config->gaps_outer.left = amount;
85 }
86 }
87 if (!valid) {
88 return cmd_results_new(CMD_INVALID, "gaps",
89 "Expected %s", expected_defaults);
55 } 90 }
56 91
92 prevent_invalid_outer_gaps();
57 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 93 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
58} 94}
59 95
60static void configure_gaps(struct sway_workspace *ws, void *_data) { 96static void apply_gaps_op(int *prop, enum gaps_op op, int amount) {
61 struct gaps_data *data = _data; 97 switch (op) {
62 int *prop = data->inner ? &ws->gaps_inner : &ws->gaps_outer;
63
64 switch (data->operation) {
65 case GAPS_OP_SET: 98 case GAPS_OP_SET:
66 *prop = data->amount; 99 *prop = amount;
67 break; 100 break;
68 case GAPS_OP_ADD: 101 case GAPS_OP_ADD:
69 *prop += data->amount; 102 *prop += amount;
70 break; 103 break;
71 case GAPS_OP_SUBTRACT: 104 case GAPS_OP_SUBTRACT:
72 *prop -= data->amount; 105 *prop -= amount;
73 break; 106 break;
74 } 107 }
108}
109
110static void configure_gaps(struct sway_workspace *ws, void *_data) {
111 // Apply operation to gaps
112 struct gaps_data *data = _data;
113 if (data->inner) {
114 apply_gaps_op(&ws->gaps_inner, data->operation, data->amount);
115 }
116 if (data->outer.top) {
117 apply_gaps_op(&(ws->gaps_outer.top), data->operation, data->amount);
118 }
119 if (data->outer.right) {
120 apply_gaps_op(&(ws->gaps_outer.right), data->operation, data->amount);
121 }
122 if (data->outer.bottom) {
123 apply_gaps_op(&(ws->gaps_outer.bottom), data->operation, data->amount);
124 }
125 if (data->outer.left) {
126 apply_gaps_op(&(ws->gaps_outer.left), data->operation, data->amount);
127 }
128
75 // Prevent invalid gaps configurations. 129 // Prevent invalid gaps configurations.
76 if (ws->gaps_inner < 0) { 130 if (ws->gaps_inner < 0) {
77 ws->gaps_inner = 0; 131 ws->gaps_inner = 0;
78 } 132 }
79 if (ws->gaps_outer < -ws->gaps_inner) { 133 prevent_invalid_outer_gaps();
80 ws->gaps_outer = -ws->gaps_inner;
81 }
82 arrange_workspace(ws); 134 arrange_workspace(ws);
83} 135}
84 136
85// gaps inner|outer current|all set|plus|minus <px> 137// gaps inner|outer|horizontal|vertical|top|right|bottom|left current|all
138// set|plus|minus <px>
139static const char *expected_runtime = "'gaps inner|outer|horizontal|vertical|"
140 "top|right|bottom|left current|all set|plus|minus <px>'";
86static struct cmd_results *gaps_set_runtime(int argc, char **argv) { 141static struct cmd_results *gaps_set_runtime(int argc, char **argv) {
87 struct cmd_results *error = checkarg(argc, "gaps", EXPECTED_EQUAL_TO, 4); 142 struct cmd_results *error = checkarg(argc, "gaps", EXPECTED_EQUAL_TO, 4);
88 if (error) { 143 if (error) {
@@ -93,15 +148,24 @@ static struct cmd_results *gaps_set_runtime(int argc, char **argv) {
93 "Can't run this command while there's no outputs connected."); 148 "Can't run this command while there's no outputs connected.");
94 } 149 }
95 150
96 struct gaps_data data; 151 struct gaps_data data = {0};
97 152
98 if (strcasecmp(argv[0], "inner") == 0) { 153 if (strcasecmp(argv[0], "inner") == 0) {
99 data.inner = true; 154 data.inner = true;
100 } else if (strcasecmp(argv[0], "outer") == 0) {
101 data.inner = false;
102 } else { 155 } else {
156 data.outer.top = !strcasecmp(argv[0], "outer") ||
157 !strcasecmp(argv[0], "vertical") || !strcasecmp(argv[0], "top");
158 data.outer.right = !strcasecmp(argv[0], "outer") ||
159 !strcasecmp(argv[0], "horizontal") || !strcasecmp(argv[0], "right");
160 data.outer.bottom = !strcasecmp(argv[0], "outer") ||
161 !strcasecmp(argv[0], "vertical") || !strcasecmp(argv[0], "bottom");
162 data.outer.left = !strcasecmp(argv[0], "outer") ||
163 !strcasecmp(argv[0], "horizontal") || !strcasecmp(argv[0], "left");
164 }
165 if (!data.inner && !data.outer.top && !data.outer.right &&
166 !data.outer.bottom && !data.outer.left) {
103 return cmd_results_new(CMD_INVALID, "gaps", 167 return cmd_results_new(CMD_INVALID, "gaps",
104 "Expected 'gaps inner|outer current|all set|plus|minus <px>'"); 168 "Expected %s", expected_runtime);
105 } 169 }
106 170
107 bool all; 171 bool all;
@@ -111,7 +175,7 @@ static struct cmd_results *gaps_set_runtime(int argc, char **argv) {
111 all = true; 175 all = true;
112 } else { 176 } else {
113 return cmd_results_new(CMD_INVALID, "gaps", 177 return cmd_results_new(CMD_INVALID, "gaps",
114 "Expected 'gaps inner|outer current|all set|plus|minus <px>'"); 178 "Expected %s", expected_runtime);
115 } 179 }
116 180
117 if (strcasecmp(argv[2], "set") == 0) { 181 if (strcasecmp(argv[2], "set") == 0) {
@@ -122,14 +186,14 @@ static struct cmd_results *gaps_set_runtime(int argc, char **argv) {
122 data.operation = GAPS_OP_SUBTRACT; 186 data.operation = GAPS_OP_SUBTRACT;
123 } else { 187 } else {
124 return cmd_results_new(CMD_INVALID, "gaps", 188 return cmd_results_new(CMD_INVALID, "gaps",
125 "Expected 'gaps inner|outer current|all set|plus|minus <px>'"); 189 "Expected %s", expected_runtime);
126 } 190 }
127 191
128 char *end; 192 char *end;
129 data.amount = strtol(argv[3], &end, 10); 193 data.amount = strtol(argv[3], &end, 10);
130 if (strlen(end) && strcasecmp(end, "px") != 0) { 194 if (strlen(end) && strcasecmp(end, "px") != 0) {
131 return cmd_results_new(CMD_INVALID, "gaps", 195 return cmd_results_new(CMD_INVALID, "gaps",
132 "Expected 'gaps inner|outer current|all set|plus|minus <px>'"); 196 "Expected %s", expected_runtime);
133 } 197 }
134 198
135 if (all) { 199 if (all) {
@@ -141,8 +205,10 @@ static struct cmd_results *gaps_set_runtime(int argc, char **argv) {
141 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 205 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
142} 206}
143 207
144// gaps inner|outer <px> - sets defaults for workspaces 208// gaps inner|outer|<dir>|<side> <px> - sets defaults for workspaces
145// gaps inner|outer current|all set|plus|minus <px> - runtime only 209// gaps inner|outer|<dir>|<side> current|all set|plus|minus <px> - runtime only
210// <dir> = horizontal|vertical
211// <side> = top|right|bottom|left
146struct cmd_results *cmd_gaps(int argc, char **argv) { 212struct cmd_results *cmd_gaps(int argc, char **argv) {
147 struct cmd_results *error = checkarg(argc, "gaps", EXPECTED_AT_LEAST, 2); 213 struct cmd_results *error = checkarg(argc, "gaps", EXPECTED_AT_LEAST, 2);
148 if (error) { 214 if (error) {
@@ -159,9 +225,8 @@ struct cmd_results *cmd_gaps(int argc, char **argv) {
159 } 225 }
160 if (config_loading) { 226 if (config_loading) {
161 return cmd_results_new(CMD_INVALID, "gaps", 227 return cmd_results_new(CMD_INVALID, "gaps",
162 "Expected 'gaps inner|outer <px>'"); 228 "Expected %s", expected_defaults);
163 } 229 }
164 return cmd_results_new(CMD_INVALID, "gaps", 230 return cmd_results_new(CMD_INVALID, "gaps",
165 "Expected 'gaps inner|outer <px>' or " 231 "Expected %s or %s", expected_runtime, expected_defaults);
166 "'gaps inner|outer current|all set|plus|minus <px>'");
167} 232}
diff --git a/sway/commands/resize.c b/sway/commands/resize.c
index 8635b309..a82bec20 100644
--- a/sway/commands/resize.c
+++ b/sway/commands/resize.c
@@ -499,7 +499,7 @@ static struct cmd_results *resize_set_tiled(struct sway_container *con,
499 } 499 }
500 if (height->unit == RESIZE_UNIT_PX) { 500 if (height->unit == RESIZE_UNIT_PX) {
501 resize_tiled(con, height->amount - con->height, 501 resize_tiled(con, height->amount - con->height,
502 RESIZE_AXIS_HORIZONTAL); 502 RESIZE_AXIS_VERTICAL);
503 } 503 }
504 } 504 }
505 505
@@ -538,34 +538,45 @@ static struct cmd_results *resize_set_floating(struct sway_container *con,
538/** 538/**
539 * resize set <args> 539 * resize set <args>
540 * 540 *
541 * args: <width> [px|ppt] <height> [px|ppt] 541 * args: [width] <width> [px|ppt]
542 * : height <height> [px|ppt]
543 * : [width] <width> [px|ppt] [height] <height> [px|ppt]
542 */ 544 */
543static struct cmd_results *cmd_resize_set(int argc, char **argv) { 545static struct cmd_results *cmd_resize_set(int argc, char **argv) {
544 struct cmd_results *error; 546 struct cmd_results *error;
545 if ((error = checkarg(argc, "resize", EXPECTED_AT_LEAST, 2))) { 547 if ((error = checkarg(argc, "resize", EXPECTED_AT_LEAST, 1))) {
546 return error; 548 return error;
547 } 549 }
548 const char *usage = "Expected 'resize set <width> <height>'"; 550 const char *usage = "Expected 'resize set [width] <width> [px|ppt]' or "
551 "'resize set height <height> [px|ppt]' or "
552 "'resize set [width] <width> [px|ppt] [height] <height> [px|ppt]'";
549 553
550 // Width 554 // Width
551 struct resize_amount width; 555 struct resize_amount width = {0};
552 int num_consumed_args = parse_resize_amount(argc, argv, &width); 556 if (argc >= 2 && !strcmp(argv[0], "width") && strcmp(argv[1], "height")) {
553 argc -= num_consumed_args; 557 argc--; argv++;
554 argv += num_consumed_args;
555 if (width.unit == RESIZE_UNIT_INVALID) {
556 return cmd_results_new(CMD_INVALID, "resize", usage);
557 } 558 }
558 if (!argc) { 559 if (strcmp(argv[0], "height")) {
559 return cmd_results_new(CMD_INVALID, "resize", usage); 560 int num_consumed_args = parse_resize_amount(argc, argv, &width);
561 argc -= num_consumed_args;
562 argv += num_consumed_args;
563 if (width.unit == RESIZE_UNIT_INVALID) {
564 return cmd_results_new(CMD_INVALID, "resize set", usage);
565 }
560 } 566 }
561 567
562 // Height 568 // Height
563 struct resize_amount height; 569 struct resize_amount height = {0};
564 num_consumed_args = parse_resize_amount(argc, argv, &height); 570 if (argc) {
565 argc -= num_consumed_args; 571 if (argc >= 2 && !strcmp(argv[0], "height")) {
566 argv += num_consumed_args; 572 argc--; argv++;
567 if (height.unit == RESIZE_UNIT_INVALID) { 573 }
568 return cmd_results_new(CMD_INVALID, "resize", usage); 574 int num_consumed_args = parse_resize_amount(argc, argv, &height);
575 argc -= num_consumed_args;
576 argv += num_consumed_args;
577 if (width.unit == RESIZE_UNIT_INVALID) {
578 return cmd_results_new(CMD_INVALID, "resize set", usage);
579 }
569 } 580 }
570 581
571 // If 0, don't resize that dimension 582 // If 0, don't resize that dimension
diff --git a/sway/commands/swap.c b/sway/commands/swap.c
index 23e8d583..99051395 100644
--- a/sway/commands/swap.c
+++ b/sway/commands/swap.c
@@ -181,7 +181,7 @@ struct cmd_results *cmd_swap(int argc, char **argv) {
181 } 181 }
182 182
183 struct sway_container *current = config->handler_context.container; 183 struct sway_container *current = config->handler_context.container;
184 struct sway_container *other; 184 struct sway_container *other = NULL;
185 185
186 char *value = join_args(argv + 3, argc - 3); 186 char *value = join_args(argv + 3, argc - 3);
187 if (strcasecmp(argv[2], "id") == 0) { 187 if (strcasecmp(argv[2], "id") == 0) {
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) {
22 } 22 }
23 wsc->workspace = strdup(ws_name); 23 wsc->workspace = strdup(ws_name);
24 wsc->gaps_inner = INT_MIN; 24 wsc->gaps_inner = INT_MIN;
25 wsc->gaps_outer = INT_MIN; 25 wsc->gaps_outer.top = INT_MIN;
26 wsc->gaps_outer.right = INT_MIN;
27 wsc->gaps_outer.bottom = INT_MIN;
28 wsc->gaps_outer.left = INT_MIN;
26 list_add(config->workspace_configs, wsc); 29 list_add(config->workspace_configs, wsc);
27 return wsc; 30 return wsc;
28} 31}
@@ -33,6 +36,89 @@ void free_workspace_config(struct workspace_config *wsc) {
33 free(wsc); 36 free(wsc);
34} 37}
35 38
39static void prevent_invalid_outer_gaps(struct workspace_config *wsc) {
40 if (wsc->gaps_outer.top != INT_MIN &&
41 wsc->gaps_outer.top < -wsc->gaps_inner) {
42 wsc->gaps_outer.top = -wsc->gaps_inner;
43 }
44 if (wsc->gaps_outer.right != INT_MIN &&
45 wsc->gaps_outer.right < -wsc->gaps_inner) {
46 wsc->gaps_outer.right = -wsc->gaps_inner;
47 }
48 if (wsc->gaps_outer.bottom != INT_MIN &&
49 wsc->gaps_outer.bottom < -wsc->gaps_inner) {
50 wsc->gaps_outer.bottom = -wsc->gaps_inner;
51 }
52 if (wsc->gaps_outer.left != INT_MIN &&
53 wsc->gaps_outer.left < -wsc->gaps_inner) {
54 wsc->gaps_outer.left = -wsc->gaps_inner;
55 }
56}
57
58static struct cmd_results *cmd_workspace_gaps(int argc, char **argv,
59 int gaps_location) {
60 const char *expected = "Expected 'workspace <name> gaps "
61 "inner|outer|horizontal|vertical|top|right|bottom|left <px>'";
62 struct cmd_results *error = NULL;
63 if ((error = checkarg(argc, "workspace", EXPECTED_EQUAL_TO,
64 gaps_location + 3))) {
65 return error;
66 }
67 char *ws_name = join_args(argv, argc - 3);
68 struct workspace_config *wsc = workspace_config_find_or_create(ws_name);
69 free(ws_name);
70 if (!wsc) {
71 return cmd_results_new(CMD_FAILURE, "workspace gaps",
72 "Unable to allocate workspace output");
73 }
74
75 char *end;
76 int amount = strtol(argv[gaps_location + 2], &end, 10);
77 if (strlen(end)) {
78 free(end);
79 return cmd_results_new(CMD_FAILURE, "workspace gaps", expected);
80 }
81
82 bool valid = false;
83 char *type = argv[gaps_location + 1];
84 if (!strcasecmp(type, "inner")) {
85 valid = true;
86 wsc->gaps_inner = (amount >= 0) ? amount : 0;
87 } else {
88 if (!strcasecmp(type, "outer") || !strcasecmp(type, "vertical")
89 || !strcasecmp(type, "top")) {
90 valid = true;
91 wsc->gaps_outer.top = amount;
92 }
93 if (!strcasecmp(type, "outer") || !strcasecmp(type, "horizontal")
94 || !strcasecmp(type, "right")) {
95 valid = true;
96 wsc->gaps_outer.right = amount;
97 }
98 if (!strcasecmp(type, "outer") || !strcasecmp(type, "vertical")
99 || !strcasecmp(type, "bottom")) {
100 valid = true;
101 wsc->gaps_outer.bottom = amount;
102 }
103 if (!strcasecmp(type, "outer") || !strcasecmp(type, "horizontal")
104 || !strcasecmp(type, "left")) {
105 valid = true;
106 wsc->gaps_outer.left = amount;
107 }
108 }
109 if (!valid) {
110 return cmd_results_new(CMD_INVALID, "workspace gaps", expected);
111 }
112
113 // Prevent invalid gaps configurations.
114 if (wsc->gaps_inner != INT_MIN && wsc->gaps_inner < 0) {
115 wsc->gaps_inner = 0;
116 }
117 prevent_invalid_outer_gaps(wsc);
118
119 return error;
120}
121
36struct cmd_results *cmd_workspace(int argc, char **argv) { 122struct cmd_results *cmd_workspace(int argc, char **argv) {
37 struct cmd_results *error = NULL; 123 struct cmd_results *error = NULL;
38 if ((error = checkarg(argc, "workspace", EXPECTED_AT_LEAST, 1))) { 124 if ((error = checkarg(argc, "workspace", EXPECTED_AT_LEAST, 1))) {
@@ -68,43 +154,9 @@ struct cmd_results *cmd_workspace(int argc, char **argv) {
68 free(wsc->output); 154 free(wsc->output);
69 wsc->output = strdup(argv[output_location + 1]); 155 wsc->output = strdup(argv[output_location + 1]);
70 } else if (gaps_location >= 0) { 156 } else if (gaps_location >= 0) {
71 if ((error = checkarg(argc, "workspace", EXPECTED_EQUAL_TO, gaps_location + 3))) { 157 if ((error = cmd_workspace_gaps(argc, argv, gaps_location))) {
72 return error; 158 return error;
73 } 159 }
74 char *ws_name = join_args(argv, argc - 3);
75 struct workspace_config *wsc = workspace_config_find_or_create(ws_name);
76 free(ws_name);
77 if (!wsc) {
78 return cmd_results_new(CMD_FAILURE, "workspace gaps",
79 "Unable to allocate workspace output");
80 }
81 int *prop = NULL;
82 if (strcasecmp(argv[gaps_location + 1], "inner") == 0) {
83 prop = &wsc->gaps_inner;
84 } else if (strcasecmp(argv[gaps_location + 1], "outer") == 0) {
85 prop = &wsc->gaps_outer;
86 } else {
87 return cmd_results_new(CMD_FAILURE, "workspace gaps",
88 "Expected 'workspace <ws> gaps inner|outer <px>'");
89 }
90 char *end;
91 int val = strtol(argv[gaps_location + 2], &end, 10);
92
93 if (strlen(end)) {
94 free(end);
95 return cmd_results_new(CMD_FAILURE, "workspace gaps",
96 "Expected 'workspace <ws> gaps inner|outer <px>'");
97 }
98 *prop = val;
99
100 // Prevent invalid gaps configurations.
101 if (wsc->gaps_inner < 0) {
102 wsc->gaps_inner = 0;
103 }
104 if (wsc->gaps_outer < -wsc->gaps_inner) {
105 wsc->gaps_outer = -wsc->gaps_inner;
106 }
107
108 } else { 160 } else {
109 if (config->reading || !config->active) { 161 if (config->reading || !config->active) {
110 return cmd_results_new(CMD_DEFER, "workspace", NULL); 162 return cmd_results_new(CMD_DEFER, "workspace", NULL);
diff --git a/sway/config.c b/sway/config.c
index 2a2841a7..c1320acf 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -234,7 +234,10 @@ static void config_defaults(struct sway_config *config) {
234 234
235 config->smart_gaps = false; 235 config->smart_gaps = false;
236 config->gaps_inner = 0; 236 config->gaps_inner = 0;
237 config->gaps_outer = 0; 237 config->gaps_outer.top = 0;
238 config->gaps_outer.right = 0;
239 config->gaps_outer.bottom = 0;
240 config->gaps_outer.left = 0;
238 241
239 if (!(config->active_bar_modifiers = create_list())) goto cleanup; 242 if (!(config->active_bar_modifiers = create_list())) goto cleanup;
240 243
diff --git a/sway/sway.5.scd b/sway/sway.5.scd
index 4a645837..8f6b35f1 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).
133*fullscreen* 133*fullscreen*
134 Toggles fullscreen for the focused view. 134 Toggles fullscreen for the focused view.
135 135
136*gaps* inner|outer all|current set|plus|minus <amount> 136*gaps* inner|outer|horizontal|vertical|top|right|bottom|left all|current
137set|plus|minus <amount>
137 Changes the _inner_ or _outer_ gaps for either _all_ workspaces or the 138 Changes the _inner_ or _outer_ gaps for either _all_ workspaces or the
138 _current_ workspace. 139 _current_ workspace. _outer_ gaps can be altered per side with _top_,
140 _right_, _bottom_, and _left_ or per direction with _horizontal_ and
141 _vertical_.
139 142
140*layout* default|splith|splitv|stacking|tabbed 143*layout* default|splith|splitv|stacking|tabbed
141 Sets the layout mode of the focused container. 144 Sets the layout mode of the focused container.
@@ -207,11 +210,23 @@ They are expected to be used with *bindsym* or at runtime through *swaymsg*(1).
207 percentage points. If the units are omitted, floating containers are resized 210 percentage points. If the units are omitted, floating containers are resized
208 in px and tiled containers by ppt. _amount_ will default to 10 if omitted. 211 in px and tiled containers by ppt. _amount_ will default to 10 if omitted.
209 212
210*resize set* <width> [px|ppt] <height> [px|ppt] 213*resize set* height <height> [px|ppt]
211 Sets the width and height of the currently focused container to _width_ and 214 Sets the height of the container to _height_, specified in pixels or
212 _height_, specified in pixels or percentage points. If the units are 215 percentage points. If the units are omitted, floating containers are
213 omitted, floating containers are resized in px and tiled containers by ppt. 216 resized in px and tiled containers by ppt. If _height_ is 0, the container
214 If _width_ or _height_ is 0, no resize is done on that axis. 217 will not be resized.
218
219*resize set* [width] <width> [px|ppt]
220 Sets the width of the container to _width_, specified in pixels or
221 percentage points. If the units are omitted, floating containers are
222 resized in px and tiled containers by ppt. If _width_ is 0, the container
223 will not be resized.
224
225*resize set* [width] <width> [px|ppt] [height] <height> [px|ppt]
226 Sets the width and height of the container to _width_ and _height_,
227 specified in pixels or percentage points. If the units are omitted,
228 floating containers are resized in px and tiled containers by ppt. If
229 _width_ or _height_ is 0, the container will not be resized on that axis.
215 230
216*scratchpad show* 231*scratchpad show*
217 Shows a window from the scratchpad. Repeatedly using this command will 232 Shows a window from the scratchpad. Repeatedly using this command will
@@ -404,8 +419,10 @@ The default colors are:
404 specified direction while holding the floating modifier. Resets the 419 specified direction while holding the floating modifier. Resets the
405 command, when given no arguments. 420 command, when given no arguments.
406 421
407*focus\_follows\_mouse* yes|no 422*focus\_follows\_mouse* yes|no|always
408 If set to _yes_, moving your mouse over a window will focus that window. 423 If set to _yes_, moving your mouse over a window will focus that window. If
424 set to _always_, the window under the cursor will always be focused, even
425 after switching between workspaces.
409 426
410*focus\_wrapping* yes|no|force 427*focus\_wrapping* yes|no|force
411 This option determines what to do when attempting to focus over the edge 428 This option determines what to do when attempting to focus over the edge
@@ -429,14 +446,16 @@ The default colors are:
429 _focus\_wrapping force_. This is only available for convenience. Please 446 _focus\_wrapping force_. This is only available for convenience. Please
430 use _focus\_wrapping_ instead when possible. 447 use _focus\_wrapping_ instead when possible.
431 448
432*gaps* inner|outer <amount> 449*gaps* inner|outer|horizontal|vertical|top|right|bottom|left <amount>
433 Sets default _amount_ pixels of _inner_ or _outer_ gap, where the inner 450 Sets default _amount_ pixels of _inner_ or _outer_ gap, where the inner
434 affects spacing around each view and outer affects the spacing around each 451 affects spacing around each view and outer affects the spacing around each
435 workspace. Outer gaps are in addition to inner gaps. To reduce or remove 452 workspace. Outer gaps are in addition to inner gaps. To reduce or remove
436 outer gaps, outer gaps can be set to a negative value. 453 outer gaps, outer gaps can be set to a negative value. _outer_ gaps can
454 also be specified per side with _top_, _right_, _bottom_, and _left_ or
455 per direction with _horizontal_ and _vertical_.
437 456
438 This affects new workspaces only, and is used when the workspace doesn't 457 This affects new workspaces only, and is used when the workspace doesn't
439 have its own gaps settings (see: workspace <ws> gaps inner|outer <amount>). 458 have its own gaps settings (see: workspace <ws> gaps ...).
440 459
441*hide\_edge\_borders* none|vertical|horizontal|both|smart|smart\_no\_gaps 460*hide\_edge\_borders* none|vertical|horizontal|both|smart|smart\_no\_gaps
442 Hides window borders adjacent to the screen edges. Default is _none_. 461 Hides window borders adjacent to the screen edges. Default is _none_.
@@ -549,7 +568,8 @@ The default colors are:
549*workspace* back\_and\_forth 568*workspace* back\_and\_forth
550 Switches to the previously focused workspace. 569 Switches to the previously focused workspace.
551 570
552*workspace* <name> gaps inner|outer <amount> 571*workspace* <name> gaps inner|outer|horizontal|vertical|top|right|bottom|left
572<amount>
553 Specifies that workspace _name_ should have the given gaps settings when it 573 Specifies that workspace _name_ should have the given gaps settings when it
554 is created. 574 is created.
555 575
diff --git a/sway/tree/container.c b/sway/tree/container.c
index 458ed7ff..3740cb53 100644
--- a/sway/tree/container.c
+++ b/sway/tree/container.c
@@ -1009,19 +1009,25 @@ void container_discover_outputs(struct sway_container *con) {
1009} 1009}
1010 1010
1011void container_remove_gaps(struct sway_container *c) { 1011void container_remove_gaps(struct sway_container *c) {
1012 if (c->current_gaps == 0) { 1012 if (c->current_gaps.top == 0 && c->current_gaps.right == 0 &&
1013 c->current_gaps.bottom == 0 && c->current_gaps.left == 0) {
1013 return; 1014 return;
1014 } 1015 }
1015 1016
1016 c->width += c->current_gaps * 2; 1017 c->width += c->current_gaps.left + c->current_gaps.right;
1017 c->height += c->current_gaps * 2; 1018 c->height += c->current_gaps.top + c->current_gaps.bottom;
1018 c->x -= c->current_gaps; 1019 c->x -= c->current_gaps.left;
1019 c->y -= c->current_gaps; 1020 c->y -= c->current_gaps.top;
1020 c->current_gaps = 0; 1021
1022 c->current_gaps.top = 0;
1023 c->current_gaps.right = 0;
1024 c->current_gaps.bottom = 0;
1025 c->current_gaps.left = 0;
1021} 1026}
1022 1027
1023void container_add_gaps(struct sway_container *c) { 1028void container_add_gaps(struct sway_container *c) {
1024 if (c->current_gaps > 0) { 1029 if (c->current_gaps.top > 0 || c->current_gaps.right > 0 ||
1030 c->current_gaps.bottom > 0 || c->current_gaps.left > 0) {
1025 return; 1031 return;
1026 } 1032 }
1027 // Linear containers don't have gaps because it'd create double gaps 1033 // Linear containers don't have gaps because it'd create double gaps
@@ -1054,11 +1060,15 @@ void container_add_gaps(struct sway_container *c) {
1054 1060
1055 struct sway_workspace *ws = c->workspace; 1061 struct sway_workspace *ws = c->workspace;
1056 1062
1057 c->current_gaps = ws->gaps_inner; 1063 c->current_gaps.top = c->y == ws->y ? ws->gaps_inner : 0;
1058 c->x += c->current_gaps; 1064 c->current_gaps.right = ws->gaps_inner;
1059 c->y += c->current_gaps; 1065 c->current_gaps.bottom = ws->gaps_inner;
1060 c->width -= 2 * c->current_gaps; 1066 c->current_gaps.left = c->x == ws->x ? ws->gaps_inner : 0;
1061 c->height -= 2 * c->current_gaps; 1067
1068 c->x += c->current_gaps.left;
1069 c->y += c->current_gaps.top;
1070 c->width -= c->current_gaps.left + c->current_gaps.right;
1071 c->height -= c->current_gaps.top + c->current_gaps.bottom;
1062} 1072}
1063 1073
1064enum sway_container_layout container_parent_layout(struct sway_container *con) { 1074enum sway_container_layout container_parent_layout(struct sway_container *con) {
diff --git a/sway/tree/view.c b/sway/tree/view.c
index 1aa59e68..21b32649 100644
--- a/sway/tree/view.c
+++ b/sway/tree/view.c
@@ -185,12 +185,14 @@ bool view_is_only_visible(struct sway_view *view) {
185static bool gaps_to_edge(struct sway_view *view) { 185static bool gaps_to_edge(struct sway_view *view) {
186 struct sway_container *con = view->container; 186 struct sway_container *con = view->container;
187 while (con) { 187 while (con) {
188 if (con->current_gaps > 0) { 188 if (con->current_gaps.top > 0 || con->current_gaps.right > 0 ||
189 con->current_gaps.bottom > 0 || con->current_gaps.left > 0) {
189 return true; 190 return true;
190 } 191 }
191 con = con->parent; 192 con = con->parent;
192 } 193 }
193 return view->container->workspace->current_gaps > 0; 194 struct side_gaps gaps = view->container->workspace->current_gaps;
195 return gaps.top > 0 || gaps.right > 0 || gaps.bottom > 0 || gaps.left > 0;
194} 196}
195 197
196void view_autoconfigure(struct sway_view *view) { 198void view_autoconfigure(struct sway_view *view) {
@@ -222,15 +224,15 @@ void view_autoconfigure(struct sway_view *view) {
222 if (config->hide_edge_borders == E_BOTH 224 if (config->hide_edge_borders == E_BOTH
223 || config->hide_edge_borders == E_VERTICAL 225 || config->hide_edge_borders == E_VERTICAL
224 || (smart && !other_views && no_gaps)) { 226 || (smart && !other_views && no_gaps)) {
225 con->border_left = con->x - con->current_gaps != ws->x; 227 con->border_left = con->x - con->current_gaps.left != ws->x;
226 int right_x = con->x + con->width + con->current_gaps; 228 int right_x = con->x + con->width + con->current_gaps.right;
227 con->border_right = right_x != ws->x + ws->width; 229 con->border_right = right_x != ws->x + ws->width;
228 } 230 }
229 if (config->hide_edge_borders == E_BOTH 231 if (config->hide_edge_borders == E_BOTH
230 || config->hide_edge_borders == E_HORIZONTAL 232 || config->hide_edge_borders == E_HORIZONTAL
231 || (smart && !other_views && no_gaps)) { 233 || (smart && !other_views && no_gaps)) {
232 con->border_top = con->y - con->current_gaps != ws->y; 234 con->border_top = con->y - con->current_gaps.top != ws->y;
233 int bottom_y = con->y + con->height + con->current_gaps; 235 int bottom_y = con->y + con->height + con->current_gaps.bottom;
234 con->border_bottom = bottom_y != ws->y + ws->height; 236 con->border_bottom = bottom_y != ws->y + ws->height;
235 } 237 }
236 238
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) {
49 return focus->output; 49 return focus->output;
50} 50}
51 51
52static void prevent_invalid_outer_gaps(struct sway_workspace *ws) {
53 if (ws->gaps_outer.top < -ws->gaps_inner) {
54 ws->gaps_outer.top = -ws->gaps_inner;
55 }
56 if (ws->gaps_outer.right < -ws->gaps_inner) {
57 ws->gaps_outer.right = -ws->gaps_inner;
58 }
59 if (ws->gaps_outer.bottom < -ws->gaps_inner) {
60 ws->gaps_outer.bottom = -ws->gaps_inner;
61 }
62 if (ws->gaps_outer.left < -ws->gaps_inner) {
63 ws->gaps_outer.left = -ws->gaps_inner;
64 }
65}
66
52struct sway_workspace *workspace_create(struct sway_output *output, 67struct sway_workspace *workspace_create(struct sway_output *output,
53 const char *name) { 68 const char *name) {
54 if (output == NULL) { 69 if (output == NULL) {
@@ -77,12 +92,24 @@ struct sway_workspace *workspace_create(struct sway_output *output,
77 if (name) { 92 if (name) {
78 struct workspace_config *wsc = workspace_find_config(name); 93 struct workspace_config *wsc = workspace_find_config(name);
79 if (wsc) { 94 if (wsc) {
80 if (wsc->gaps_outer != INT_MIN) { 95 if (wsc->gaps_outer.top != INT_MIN) {
81 ws->gaps_outer = wsc->gaps_outer; 96 ws->gaps_outer.top = wsc->gaps_outer.top;
97 }
98 if (wsc->gaps_outer.right != INT_MIN) {
99 ws->gaps_outer.right = wsc->gaps_outer.right;
100 }
101 if (wsc->gaps_outer.bottom != INT_MIN) {
102 ws->gaps_outer.bottom = wsc->gaps_outer.bottom;
103 }
104 if (wsc->gaps_outer.left != INT_MIN) {
105 ws->gaps_outer.left = wsc->gaps_outer.left;
82 } 106 }
83 if (wsc->gaps_inner != INT_MIN) { 107 if (wsc->gaps_inner != INT_MIN) {
84 ws->gaps_inner = wsc->gaps_inner; 108 ws->gaps_inner = wsc->gaps_inner;
85 } 109 }
110 // Since default outer gaps can be smaller than the negation of
111 // workspace specific inner gaps, check outer gaps again
112 prevent_invalid_outer_gaps(ws);
86 } 113 }
87 } 114 }
88 115
@@ -615,19 +642,25 @@ void workspace_insert_tiling(struct sway_workspace *workspace,
615} 642}
616 643
617void workspace_remove_gaps(struct sway_workspace *ws) { 644void workspace_remove_gaps(struct sway_workspace *ws) {
618 if (ws->current_gaps == 0) { 645 if (ws->current_gaps.top == 0 && ws->current_gaps.right == 0 &&
646 ws->current_gaps.bottom == 0 && ws->current_gaps.left == 0) {
619 return; 647 return;
620 } 648 }
621 649
622 ws->width += ws->current_gaps * 2; 650 ws->width += ws->current_gaps.left + ws->current_gaps.right;
623 ws->height += ws->current_gaps * 2; 651 ws->height += ws->current_gaps.top + ws->current_gaps.bottom;
624 ws->x -= ws->current_gaps; 652 ws->x -= ws->current_gaps.left;
625 ws->y -= ws->current_gaps; 653 ws->y -= ws->current_gaps.top;
626 ws->current_gaps = 0; 654
655 ws->current_gaps.top = 0;
656 ws->current_gaps.right = 0;
657 ws->current_gaps.bottom = 0;
658 ws->current_gaps.left = 0;
627} 659}
628 660
629void workspace_add_gaps(struct sway_workspace *ws) { 661void workspace_add_gaps(struct sway_workspace *ws) {
630 if (ws->current_gaps > 0) { 662 if (ws->current_gaps.top > 0 || ws->current_gaps.right > 0 ||
663 ws->current_gaps.bottom > 0 || ws->current_gaps.left > 0) {
631 return; 664 return;
632 } 665 }
633 if (config->smart_gaps) { 666 if (config->smart_gaps) {
@@ -643,18 +676,20 @@ void workspace_add_gaps(struct sway_workspace *ws) {
643 } 676 }
644 677
645 ws->current_gaps = ws->gaps_outer; 678 ws->current_gaps = ws->gaps_outer;
646
647 if (ws->layout == L_TABBED || ws->layout == L_STACKED) { 679 if (ws->layout == L_TABBED || ws->layout == L_STACKED) {
648 // We have to add inner gaps for this, because children of tabbed and 680 // We have to add inner gaps for this, because children of tabbed and
649 // stacked containers don't apply their own gaps - they assume the 681 // stacked containers don't apply their own gaps - they assume the
650 // tabbed/stacked container is using gaps. 682 // tabbed/stacked container is using gaps.
651 ws->current_gaps += ws->gaps_inner; 683 ws->current_gaps.top += ws->gaps_inner;
684 ws->current_gaps.right += ws->gaps_inner;
685 ws->current_gaps.bottom += ws->gaps_inner;
686 ws->current_gaps.left += ws->gaps_inner;
652 } 687 }
653 688
654 ws->x += ws->current_gaps; 689 ws->x += ws->current_gaps.left;
655 ws->y += ws->current_gaps; 690 ws->y += ws->current_gaps.top;
656 ws->width -= 2 * ws->current_gaps; 691 ws->width -= ws->current_gaps.left + ws->current_gaps.right;
657 ws->height -= 2 * ws->current_gaps; 692 ws->height -= ws->current_gaps.top + ws->current_gaps.bottom;
658} 693}
659 694
660struct sway_container *workspace_split(struct sway_workspace *workspace, 695struct sway_container *workspace_split(struct sway_workspace *workspace,