diff options
Diffstat (limited to 'sway/commands/output.c')
-rw-r--r-- | sway/commands/output.c | 429 |
1 files changed, 266 insertions, 163 deletions
diff --git a/sway/commands/output.c b/sway/commands/output.c index 911391d2..f7e3372c 100644 --- a/sway/commands/output.c +++ b/sway/commands/output.c | |||
@@ -17,196 +17,299 @@ static char *bg_options[] = { | |||
17 | "center", | 17 | "center", |
18 | "fill", | 18 | "fill", |
19 | "fit", | 19 | "fit", |
20 | "tile" | 20 | "tile", |
21 | }; | 21 | }; |
22 | 22 | ||
23 | struct cmd_results *cmd_output(int argc, char **argv) { | 23 | static struct cmd_results *cmd_output_mode(struct output_config *output, |
24 | struct cmd_results *error = NULL; | 24 | int *i, int argc, char **argv) { |
25 | if ((error = checkarg(argc, "output", EXPECTED_AT_LEAST, 1))) { | 25 | if (++*i >= argc) { |
26 | return error; | 26 | return cmd_results_new(CMD_INVALID, "output", "Missing mode argument."); |
27 | } | 27 | } |
28 | const char *name = argv[0]; | ||
29 | 28 | ||
30 | struct output_config *output = calloc(1, sizeof(struct output_config)); | 29 | char *end; |
31 | if (!output) { | 30 | output->width = strtol(argv[*i], &end, 10); |
32 | return cmd_results_new(CMD_FAILURE, "output", "Unable to allocate output config"); | 31 | if (*end) { |
32 | // Format is 1234x4321 | ||
33 | if (*end != 'x') { | ||
34 | return cmd_results_new(CMD_INVALID, "output", | ||
35 | "Invalid mode width."); | ||
36 | } | ||
37 | ++end; | ||
38 | output->height = strtol(end, &end, 10); | ||
39 | if (*end) { | ||
40 | if (*end != '@') { | ||
41 | return cmd_results_new(CMD_INVALID, "output", | ||
42 | "Invalid mode height."); | ||
43 | } | ||
44 | ++end; | ||
45 | output->refresh_rate = strtof(end, &end); | ||
46 | if (strcasecmp("Hz", end) != 0) { | ||
47 | return cmd_results_new(CMD_INVALID, "output", | ||
48 | "Invalid mode refresh rate."); | ||
49 | } | ||
50 | } | ||
51 | } else { | ||
52 | // Format is 1234 4321 | ||
53 | if (++*i >= argc) { | ||
54 | return cmd_results_new(CMD_INVALID, "output", | ||
55 | "Missing mode argument (height)."); | ||
56 | } | ||
57 | output->height = strtol(argv[*i], &end, 10); | ||
58 | if (*end) { | ||
59 | return cmd_results_new(CMD_INVALID, "output", | ||
60 | "Invalid mode height."); | ||
61 | } | ||
33 | } | 62 | } |
34 | output->x = output->y = output->width = output->height = -1; | ||
35 | output->name = strdup(name); | ||
36 | output->enabled = -1; | ||
37 | output->scale = 1; | ||
38 | 63 | ||
39 | // TODO: atoi doesn't handle invalid numbers | 64 | return NULL; |
65 | } | ||
40 | 66 | ||
41 | int i; | 67 | static struct cmd_results *cmd_output_position(struct output_config *output, |
42 | for (i = 1; i < argc; ++i) { | 68 | int *i, int argc, char **argv) { |
43 | const char *command = argv[i]; | 69 | if (++*i >= argc) { |
70 | return cmd_results_new(CMD_INVALID, "output", | ||
71 | "Missing position argument."); | ||
72 | } | ||
44 | 73 | ||
45 | if (strcasecmp(command, "disable") == 0) { | 74 | char *end; |
46 | output->enabled = 0; | 75 | output->x = strtol(argv[*i], &end, 10); |
47 | } else if (strcasecmp(command, "resolution") == 0 || strcasecmp(command, "res") == 0) { | 76 | if (*end) { |
48 | if (++i >= argc) { | 77 | // Format is 1234,4321 |
49 | error = cmd_results_new(CMD_INVALID, "output", "Missing resolution argument."); | 78 | if (*end != ',') { |
50 | goto fail; | 79 | return cmd_results_new(CMD_INVALID, "output", |
51 | } | 80 | "Invalid position x."); |
52 | char *res = argv[i]; | 81 | } |
53 | char *x = strchr(res, 'x'); | 82 | ++end; |
54 | int width = -1, height = -1; | 83 | output->y = strtol(end, &end, 10); |
55 | if (x != NULL) { | 84 | if (*end) { |
56 | // Format is 1234x4321 | 85 | return cmd_results_new(CMD_INVALID, "output", |
57 | *x = '\0'; | 86 | "Invalid position y."); |
58 | width = atoi(res); | 87 | } |
59 | height = atoi(x + 1); | 88 | } else { |
60 | *x = 'x'; | 89 | // Format is 1234 4321 (legacy) |
61 | } else { | 90 | if (++*i >= argc) { |
62 | // Format is 1234 4321 | 91 | return cmd_results_new(CMD_INVALID, "output", |
63 | width = atoi(res); | 92 | "Missing position argument (y)."); |
64 | if (++i >= argc) { | 93 | } |
65 | error = cmd_results_new(CMD_INVALID, "output", "Missing resolution argument (height)."); | 94 | output->y = strtol(argv[*i], &end, 10); |
66 | goto fail; | 95 | if (*end) { |
96 | return cmd_results_new(CMD_INVALID, "output", | ||
97 | "Invalid position y."); | ||
98 | } | ||
99 | } | ||
100 | |||
101 | return NULL; | ||
102 | } | ||
103 | |||
104 | static struct cmd_results *cmd_output_scale(struct output_config *output, | ||
105 | int *i, int argc, char **argv) { | ||
106 | if (++*i >= argc) { | ||
107 | return cmd_results_new(CMD_INVALID, "output", | ||
108 | "Missing scale argument."); | ||
109 | } | ||
110 | |||
111 | char *end; | ||
112 | output->scale = strtof(argv[*i], &end); | ||
113 | if (*end) { | ||
114 | return cmd_results_new(CMD_INVALID, "output", "Invalid scale."); | ||
115 | } | ||
116 | |||
117 | return NULL; | ||
118 | } | ||
119 | |||
120 | static struct cmd_results *cmd_output_transform(struct output_config *output, | ||
121 | int *i, int argc, char **argv) { | ||
122 | if (++*i >= argc) { | ||
123 | return cmd_results_new(CMD_INVALID, "output", | ||
124 | "Missing transform argument."); | ||
125 | } | ||
126 | |||
127 | char *value = argv[*i]; | ||
128 | if (strcmp(value, "normal") == 0) { | ||
129 | output->transform = WL_OUTPUT_TRANSFORM_NORMAL; | ||
130 | } else if (strcmp(value, "90") == 0) { | ||
131 | output->transform = WL_OUTPUT_TRANSFORM_90; | ||
132 | } else if (strcmp(value, "180") == 0) { | ||
133 | output->transform = WL_OUTPUT_TRANSFORM_180; | ||
134 | } else if (strcmp(value, "270") == 0) { | ||
135 | output->transform = WL_OUTPUT_TRANSFORM_270; | ||
136 | } else if (strcmp(value, "flipped") == 0) { | ||
137 | output->transform = WL_OUTPUT_TRANSFORM_FLIPPED; | ||
138 | } else if (strcmp(value, "flipped-90") == 0) { | ||
139 | output->transform = WL_OUTPUT_TRANSFORM_FLIPPED_90; | ||
140 | } else if (strcmp(value, "flipped-180") == 0) { | ||
141 | output->transform = WL_OUTPUT_TRANSFORM_FLIPPED_180; | ||
142 | } else if (strcmp(value, "flipped-270") == 0) { | ||
143 | output->transform = WL_OUTPUT_TRANSFORM_FLIPPED_270; | ||
144 | } else { | ||
145 | return cmd_results_new(CMD_INVALID, "output", | ||
146 | "Invalid output transform."); | ||
147 | } | ||
148 | |||
149 | return NULL; | ||
150 | } | ||
151 | |||
152 | static struct cmd_results *cmd_output_background(struct output_config *output, | ||
153 | int *i, int argc, char **argv) { | ||
154 | if (++*i >= argc) { | ||
155 | return cmd_results_new(CMD_INVALID, "output", | ||
156 | "Missing background file or color specification."); | ||
157 | } | ||
158 | const char *background = argv[*i]; | ||
159 | if (*i + 1 >= argc) { | ||
160 | return cmd_results_new(CMD_INVALID, "output", | ||
161 | "Missing background scaling mode or `solid_color`."); | ||
162 | } | ||
163 | const char *background_option = argv[*i]; | ||
164 | |||
165 | if (strcasecmp(background_option, "solid_color") == 0) { | ||
166 | output->background = strdup(background); | ||
167 | output->background_option = strdup("solid_color"); | ||
168 | } else { | ||
169 | bool valid = false; | ||
170 | char *mode; | ||
171 | size_t j; | ||
172 | for (j = 0; j < (size_t)(argc - *i); ++j) { | ||
173 | mode = argv[*i + j]; | ||
174 | size_t n = sizeof(bg_options) / sizeof(char *); | ||
175 | for (size_t k = 0; k < n; ++k) { | ||
176 | if (strcasecmp(mode, bg_options[k]) == 0) { | ||
177 | valid = true; | ||
178 | break; | ||
67 | } | 179 | } |
68 | res = argv[i]; | ||
69 | height = atoi(res); | ||
70 | } | 180 | } |
71 | output->width = width; | 181 | if (valid) { |
72 | output->height = height; | 182 | break; |
73 | } else if (strcasecmp(command, "position") == 0 || strcasecmp(command, "pos") == 0) { | ||
74 | if (++i >= argc) { | ||
75 | error = cmd_results_new(CMD_INVALID, "output", "Missing position argument."); | ||
76 | goto fail; | ||
77 | } | 183 | } |
78 | char *res = argv[i]; | 184 | } |
79 | char *c = strchr(res, ','); | 185 | if (!valid) { |
80 | int x = -1, y = -1; | 186 | return cmd_results_new(CMD_INVALID, "output", |
81 | if (c != NULL) { | 187 | "Missing background scaling mode."); |
82 | // Format is 1234,4321 | 188 | } |
83 | *c = '\0'; | 189 | |
84 | x = atoi(res); | 190 | wordexp_t p; |
85 | y = atoi(c + 1); | 191 | char *src = join_args(argv + *i, j); |
86 | *c = ','; | 192 | if (wordexp(src, &p, 0) != 0 || p.we_wordv[0] == NULL) { |
87 | } else { | 193 | return cmd_results_new(CMD_INVALID, "output", |
88 | // Format is 1234 4321 | 194 | "Invalid syntax (%s).", src); |
89 | x = atoi(res); | 195 | } |
90 | if (++i >= argc) { | 196 | free(src); |
91 | error = cmd_results_new(CMD_INVALID, "output", "Missing position argument (y)."); | 197 | src = p.we_wordv[0]; |
92 | goto fail; | 198 | if (config->reading && *src != '/') { |
199 | char *conf = strdup(config->current_config); | ||
200 | if (conf) { | ||
201 | char *conf_path = dirname(conf); | ||
202 | src = malloc(strlen(conf_path) + strlen(src) + 2); | ||
203 | if (src) { | ||
204 | sprintf(src, "%s/%s", conf_path, p.we_wordv[0]); | ||
205 | } else { | ||
206 | wlr_log(L_ERROR, | ||
207 | "Unable to allocate background source"); | ||
93 | } | 208 | } |
94 | res = argv[i]; | 209 | free(conf); |
95 | y = atoi(res); | ||
96 | } | ||
97 | output->x = x; | ||
98 | output->y = y; | ||
99 | } else if (strcasecmp(command, "scale") == 0) { | ||
100 | if (++i >= argc) { | ||
101 | error = cmd_results_new(CMD_INVALID, "output", "Missing scale parameter."); | ||
102 | goto fail; | ||
103 | } | ||
104 | output->scale = atoi(argv[i]); | ||
105 | } else if (strcasecmp(command, "background") == 0 || strcasecmp(command, "bg") == 0) { | ||
106 | wordexp_t p; | ||
107 | if (++i >= argc) { | ||
108 | error = cmd_results_new(CMD_INVALID, "output", "Missing background file or color specification."); | ||
109 | goto fail; | ||
110 | } | ||
111 | if (i + 1 >= argc) { | ||
112 | error = cmd_results_new(CMD_INVALID, "output", "Missing background scaling mode or `solid_color`."); | ||
113 | goto fail; | ||
114 | } | ||
115 | if (strcasecmp(argv[i + 1], "solid_color") == 0) { | ||
116 | output->background = strdup(argv[argc - 2]); | ||
117 | output->background_option = strdup("solid_color"); | ||
118 | } else { | 210 | } else { |
119 | // argv[i+j]=bg_option | 211 | wlr_log(L_ERROR, "Unable to allocate background source"); |
120 | bool valid = false; | 212 | } |
121 | char *mode; | 213 | } |
122 | size_t j; | 214 | if (!src || access(src, F_OK) == -1) { |
123 | for (j = 0; j < (size_t) (argc - i); ++j) { | 215 | wordfree(&p); |
124 | mode = argv[i + j]; | 216 | return cmd_results_new(CMD_INVALID, "output", |
125 | for (size_t k = 0; k < sizeof(bg_options) / sizeof(char *); ++k) { | 217 | "Background file unreadable (%s).", src); |
126 | if (strcasecmp(mode, bg_options[k]) == 0) { | 218 | } |
127 | valid = true; | ||
128 | break; | ||
129 | } | ||
130 | } | ||
131 | if (valid) { | ||
132 | break; | ||
133 | } | ||
134 | } | ||
135 | if (!valid) { | ||
136 | error = cmd_results_new(CMD_INVALID, "output", "Missing background scaling mode."); | ||
137 | goto fail; | ||
138 | } | ||
139 | 219 | ||
140 | char *src = join_args(argv + i, j); | 220 | output->background = strdup(src); |
141 | if (wordexp(src, &p, 0) != 0 || p.we_wordv[0] == NULL) { | 221 | output->background_option = strdup(mode); |
142 | error = cmd_results_new(CMD_INVALID, "output", "Invalid syntax (%s)", src); | 222 | if (src != p.we_wordv[0]) { |
143 | goto fail; | 223 | free(src); |
144 | } | 224 | } |
145 | free(src); | 225 | wordfree(&p); |
146 | src = p.we_wordv[0]; | ||
147 | if (config->reading && *src != '/') { | ||
148 | char *conf = strdup(config->current_config); | ||
149 | if (conf) { | ||
150 | char *conf_path = dirname(conf); | ||
151 | src = malloc(strlen(conf_path) + strlen(src) + 2); | ||
152 | if (src) { | ||
153 | sprintf(src, "%s/%s", conf_path, p.we_wordv[0]); | ||
154 | } else { | ||
155 | sway_log(L_ERROR, "Unable to allocate background source"); | ||
156 | } | ||
157 | free(conf); | ||
158 | } else { | ||
159 | sway_log(L_ERROR, "Unable to allocate background source"); | ||
160 | } | ||
161 | } | ||
162 | if (!src || access(src, F_OK) == -1) { | ||
163 | error = cmd_results_new(CMD_INVALID, "output", "Background file unreadable (%s)", src); | ||
164 | wordfree(&p); | ||
165 | goto fail; | ||
166 | } | ||
167 | 226 | ||
168 | output->background = strdup(src); | 227 | *i += j; |
169 | output->background_option = strdup(mode); | 228 | } |
170 | if (src != p.we_wordv[0]) { | ||
171 | free(src); | ||
172 | } | ||
173 | wordfree(&p); | ||
174 | 229 | ||
175 | i += j; | 230 | return NULL; |
176 | } | 231 | } |
232 | |||
233 | struct cmd_results *cmd_output(int argc, char **argv) { | ||
234 | struct cmd_results *error = checkarg(argc, "output", EXPECTED_AT_LEAST, 1); | ||
235 | if (error != NULL) { | ||
236 | return error; | ||
237 | } | ||
238 | |||
239 | struct output_config *output = new_output_config(argv[0]); | ||
240 | if (!output) { | ||
241 | wlr_log(L_ERROR, "Failed to allocate output config"); | ||
242 | return NULL; | ||
243 | } | ||
244 | |||
245 | for (int i = 1; i < argc; ++i) { | ||
246 | const char *command = argv[i]; | ||
247 | |||
248 | if (strcasecmp(command, "enable") == 0) { | ||
249 | output->enabled = 1; | ||
250 | } else if (strcasecmp(command, "disable") == 0) { | ||
251 | output->enabled = 0; | ||
252 | } else if (strcasecmp(command, "mode") == 0 || | ||
253 | strcasecmp(command, "resolution") == 0 || | ||
254 | strcasecmp(command, "res") == 0) { | ||
255 | error = cmd_output_mode(output, &i, argc, argv); | ||
256 | } else if (strcasecmp(command, "position") == 0 || | ||
257 | strcasecmp(command, "pos") == 0) { | ||
258 | error = cmd_output_position(output, &i, argc, argv); | ||
259 | } else if (strcasecmp(command, "scale") == 0) { | ||
260 | error = cmd_output_scale(output, &i, argc, argv); | ||
261 | } else if (strcasecmp(command, "transform") == 0) { | ||
262 | error = cmd_output_transform(output, &i, argc, argv); | ||
263 | } else if (strcasecmp(command, "background") == 0 || | ||
264 | strcasecmp(command, "bg") == 0) { | ||
265 | error = cmd_output_background(output, &i, argc, argv); | ||
266 | } else { | ||
267 | error = cmd_results_new(CMD_INVALID, "output", | ||
268 | "Invalid output subcommand: %s.", command); | ||
269 | } | ||
270 | |||
271 | if (error != NULL) { | ||
272 | goto fail; | ||
177 | } | 273 | } |
178 | } | 274 | } |
179 | 275 | ||
180 | i = list_seq_find(config->output_configs, output_name_cmp, name); | 276 | int i = list_seq_find(config->output_configs, output_name_cmp, output->name); |
181 | if (i >= 0) { | 277 | if (i >= 0) { |
182 | // merge existing config | 278 | // Merge existing config |
183 | struct output_config *oc = config->output_configs->items[i]; | 279 | struct output_config *current = config->output_configs->items[i]; |
184 | merge_output_config(oc, output); | 280 | merge_output_config(current, output); |
185 | free_output_config(output); | 281 | free_output_config(output); |
186 | output = oc; | 282 | output = current; |
187 | } else { | 283 | } else { |
188 | list_add(config->output_configs, output); | 284 | list_add(config->output_configs, output); |
189 | } | 285 | } |
190 | 286 | ||
191 | sway_log(L_DEBUG, "Config stored for output %s (enabled:%d) (%d x %d @ %d, %d scale %d) (bg %s %s)", | 287 | wlr_log(L_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz " |
192 | output->name, output->enabled, output->width, | 288 | "position %d,%d scale %f transform %d) (bg %s %s)", |
193 | output->height, output->x, output->y, output->scale, | 289 | output->name, output->enabled, output->width, output->height, |
194 | output->background, output->background_option); | 290 | output->refresh_rate, output->x, output->y, output->scale, |
291 | output->transform, output->background, output->background_option); | ||
195 | 292 | ||
196 | if (output->name) { | 293 | // Try to find the output container and apply configuration now. If |
197 | // Try to find the output container and apply configuration now. If | 294 | // this is during startup then there will be no container and config |
198 | // this is during startup then there will be no container and config | 295 | // will be applied during normal "new output" event from wlroots. |
199 | // will be applied during normal "new output" event from wlc. | 296 | char identifier[128]; |
200 | swayc_t *cont = NULL; | 297 | bool all = strcmp(output->name, "*") == 0; |
201 | for (int i = 0; i < root_container.children->length; ++i) { | 298 | for (int i = 0; i < root_container.children->length; ++i) { |
202 | cont = root_container.children->items[i]; | 299 | struct sway_container *cont = root_container.children->items[i]; |
203 | if (cont->name && ((strcmp(cont->name, output->name) == 0) || (strcmp(output->name, "*") == 0))) { | 300 | if (cont->type != C_OUTPUT) { |
204 | apply_output_config(output, cont); | 301 | continue; |
302 | } | ||
205 | 303 | ||
206 | if (strcmp(output->name, "*") != 0) { | 304 | output_get_identifier(identifier, sizeof(identifier), cont->sway_output); |
207 | // stop looking if the output config isn't applicable to all outputs | 305 | if (all || strcmp(cont->name, output->name) == 0 || |
208 | break; | 306 | strcmp(identifier, output->name) == 0) { |
209 | } | 307 | apply_output_config(output, cont); |
308 | |||
309 | if (!all) { | ||
310 | // Stop looking if the output config isn't applicable to all | ||
311 | // outputs | ||
312 | break; | ||
210 | } | 313 | } |
211 | } | 314 | } |
212 | } | 315 | } |