diff options
Diffstat (limited to 'sway/config.c')
-rw-r--r-- | sway/config.c | 504 |
1 files changed, 504 insertions, 0 deletions
diff --git a/sway/config.c b/sway/config.c new file mode 100644 index 00000000..475e8b04 --- /dev/null +++ b/sway/config.c | |||
@@ -0,0 +1,504 @@ | |||
1 | #define _POSIX_C_SOURCE 200809L | ||
2 | #define _XOPEN_SOURCE 700 | ||
3 | #include <stdio.h> | ||
4 | #include <stdbool.h> | ||
5 | #include <stdlib.h> | ||
6 | #include <unistd.h> | ||
7 | #include <libgen.h> | ||
8 | #include <wordexp.h> | ||
9 | #include <sys/types.h> | ||
10 | #include <sys/wait.h> | ||
11 | #include <sys/stat.h> | ||
12 | #include <signal.h> | ||
13 | #include <libinput.h> | ||
14 | #include <limits.h> | ||
15 | #include <float.h> | ||
16 | #include <dirent.h> | ||
17 | #include <strings.h> | ||
18 | #ifdef __linux__ | ||
19 | #include <linux/input-event-codes.h> | ||
20 | #elif __FreeBSD__ | ||
21 | #include <dev/evdev/input-event-codes.h> | ||
22 | #endif | ||
23 | #include <wlr/types/wlr_output.h> | ||
24 | #include "sway/commands.h" | ||
25 | #include "sway/config.h" | ||
26 | #include "sway/layout.h" | ||
27 | #include "readline.h" | ||
28 | #include "stringop.h" | ||
29 | #include "list.h" | ||
30 | #include "log.h" | ||
31 | |||
32 | struct sway_config *config = NULL; | ||
33 | |||
34 | void free_config(struct sway_config *config) { | ||
35 | // TODO | ||
36 | } | ||
37 | |||
38 | static void config_defaults(struct sway_config *config) { | ||
39 | if (!(config->symbols = create_list())) goto cleanup; | ||
40 | if (!(config->modes = create_list())) goto cleanup; | ||
41 | if (!(config->bars = create_list())) goto cleanup; | ||
42 | if (!(config->workspace_outputs = create_list())) goto cleanup; | ||
43 | if (!(config->pid_workspaces = create_list())) goto cleanup; | ||
44 | if (!(config->criteria = create_list())) goto cleanup; | ||
45 | if (!(config->no_focus = create_list())) goto cleanup; | ||
46 | if (!(config->input_configs = create_list())) goto cleanup; | ||
47 | if (!(config->output_configs = create_list())) goto cleanup; | ||
48 | |||
49 | if (!(config->cmd_queue = create_list())) goto cleanup; | ||
50 | |||
51 | if (!(config->current_mode = malloc(sizeof(struct sway_mode)))) goto cleanup; | ||
52 | if (!(config->current_mode->name = malloc(sizeof("default")))) goto cleanup; | ||
53 | strcpy(config->current_mode->name, "default"); | ||
54 | if (!(config->current_mode->bindings = create_list())) goto cleanup; | ||
55 | list_add(config->modes, config->current_mode); | ||
56 | |||
57 | config->floating_mod = 0; | ||
58 | config->dragging_key = BTN_LEFT; | ||
59 | config->resizing_key = BTN_RIGHT; | ||
60 | if (!(config->floating_scroll_up_cmd = strdup(""))) goto cleanup; | ||
61 | if (!(config->floating_scroll_down_cmd = strdup(""))) goto cleanup; | ||
62 | if (!(config->floating_scroll_left_cmd = strdup(""))) goto cleanup; | ||
63 | if (!(config->floating_scroll_right_cmd = strdup(""))) goto cleanup; | ||
64 | config->default_layout = L_NONE; | ||
65 | config->default_orientation = L_NONE; | ||
66 | if (!(config->font = strdup("monospace 10"))) goto cleanup; | ||
67 | // TODO: border | ||
68 | //config->font_height = get_font_text_height(config->font); | ||
69 | |||
70 | // floating view | ||
71 | config->floating_maximum_width = 0; | ||
72 | config->floating_maximum_height = 0; | ||
73 | config->floating_minimum_width = 75; | ||
74 | config->floating_minimum_height = 50; | ||
75 | |||
76 | // Flags | ||
77 | config->focus_follows_mouse = true; | ||
78 | config->mouse_warping = true; | ||
79 | config->reloading = false; | ||
80 | config->active = false; | ||
81 | config->failed = false; | ||
82 | config->auto_back_and_forth = false; | ||
83 | config->seamless_mouse = true; | ||
84 | config->reading = false; | ||
85 | config->show_marks = true; | ||
86 | |||
87 | config->edge_gaps = true; | ||
88 | config->smart_gaps = false; | ||
89 | config->gaps_inner = 0; | ||
90 | config->gaps_outer = 0; | ||
91 | |||
92 | if (!(config->active_bar_modifiers = create_list())) goto cleanup; | ||
93 | |||
94 | if (!(config->config_chain = create_list())) goto cleanup; | ||
95 | config->current_config = NULL; | ||
96 | |||
97 | // borders | ||
98 | config->border = B_NORMAL; | ||
99 | config->floating_border = B_NORMAL; | ||
100 | config->border_thickness = 2; | ||
101 | config->floating_border_thickness = 2; | ||
102 | config->hide_edge_borders = E_NONE; | ||
103 | |||
104 | // border colors | ||
105 | config->border_colors.focused.border = 0x4C7899FF; | ||
106 | config->border_colors.focused.background = 0x285577FF; | ||
107 | config->border_colors.focused.text = 0xFFFFFFFF; | ||
108 | config->border_colors.focused.indicator = 0x2E9EF4FF; | ||
109 | config->border_colors.focused.child_border = 0x285577FF; | ||
110 | |||
111 | config->border_colors.focused_inactive.border = 0x333333FF; | ||
112 | config->border_colors.focused_inactive.background = 0x5F676AFF; | ||
113 | config->border_colors.focused_inactive.text = 0xFFFFFFFF; | ||
114 | config->border_colors.focused_inactive.indicator = 0x484E50FF; | ||
115 | config->border_colors.focused_inactive.child_border = 0x5F676AFF; | ||
116 | |||
117 | config->border_colors.unfocused.border = 0x333333FF; | ||
118 | config->border_colors.unfocused.background = 0x222222FF; | ||
119 | config->border_colors.unfocused.text = 0x888888FF; | ||
120 | config->border_colors.unfocused.indicator = 0x292D2EFF; | ||
121 | config->border_colors.unfocused.child_border = 0x222222FF; | ||
122 | |||
123 | config->border_colors.urgent.border = 0x2F343AFF; | ||
124 | config->border_colors.urgent.background = 0x900000FF; | ||
125 | config->border_colors.urgent.text = 0xFFFFFFFF; | ||
126 | config->border_colors.urgent.indicator = 0x900000FF; | ||
127 | config->border_colors.urgent.child_border = 0x900000FF; | ||
128 | |||
129 | config->border_colors.placeholder.border = 0x000000FF; | ||
130 | config->border_colors.placeholder.background = 0x0C0C0CFF; | ||
131 | config->border_colors.placeholder.text = 0xFFFFFFFF; | ||
132 | config->border_colors.placeholder.indicator = 0x000000FF; | ||
133 | config->border_colors.placeholder.child_border = 0x0C0C0CFF; | ||
134 | |||
135 | config->border_colors.background = 0xFFFFFFFF; | ||
136 | |||
137 | // Security | ||
138 | if (!(config->command_policies = create_list())) goto cleanup; | ||
139 | if (!(config->feature_policies = create_list())) goto cleanup; | ||
140 | if (!(config->ipc_policies = create_list())) goto cleanup; | ||
141 | |||
142 | return; | ||
143 | cleanup: | ||
144 | sway_abort("Unable to allocate config structures"); | ||
145 | } | ||
146 | |||
147 | static bool file_exists(const char *path) { | ||
148 | return path && access(path, R_OK) != -1; | ||
149 | } | ||
150 | |||
151 | static char *get_config_path(void) { | ||
152 | static const char *config_paths[] = { | ||
153 | "$HOME/.sway/config", | ||
154 | "$XDG_CONFIG_HOME/sway/config", | ||
155 | "$HOME/.i3/config", | ||
156 | "$XDG_CONFIG_HOME/i3/config", | ||
157 | SYSCONFDIR "/sway/config", | ||
158 | SYSCONFDIR "/i3/config", | ||
159 | }; | ||
160 | |||
161 | if (!getenv("XDG_CONFIG_HOME")) { | ||
162 | char *home = getenv("HOME"); | ||
163 | char *config_home = malloc(strlen(home) + strlen("/.config") + 1); | ||
164 | if (!config_home) { | ||
165 | sway_log(L_ERROR, "Unable to allocate $HOME/.config"); | ||
166 | } else { | ||
167 | strcpy(config_home, home); | ||
168 | strcat(config_home, "/.config"); | ||
169 | setenv("XDG_CONFIG_HOME", config_home, 1); | ||
170 | sway_log(L_DEBUG, "Set XDG_CONFIG_HOME to %s", config_home); | ||
171 | free(config_home); | ||
172 | } | ||
173 | } | ||
174 | |||
175 | wordexp_t p; | ||
176 | char *path; | ||
177 | |||
178 | int i; | ||
179 | for (i = 0; i < (int)(sizeof(config_paths) / sizeof(char *)); ++i) { | ||
180 | if (wordexp(config_paths[i], &p, 0) == 0) { | ||
181 | path = strdup(p.we_wordv[0]); | ||
182 | wordfree(&p); | ||
183 | if (file_exists(path)) { | ||
184 | return path; | ||
185 | } | ||
186 | } | ||
187 | } | ||
188 | |||
189 | return NULL; // Not reached | ||
190 | } | ||
191 | |||
192 | const char *current_config_path; | ||
193 | |||
194 | static bool load_config(const char *path, struct sway_config *config) { | ||
195 | sway_log(L_INFO, "Loading config from %s", path); | ||
196 | current_config_path = path; | ||
197 | |||
198 | struct stat sb; | ||
199 | if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { | ||
200 | return false; | ||
201 | } | ||
202 | |||
203 | if (path == NULL) { | ||
204 | sway_log(L_ERROR, "Unable to find a config file!"); | ||
205 | return false; | ||
206 | } | ||
207 | |||
208 | FILE *f = fopen(path, "r"); | ||
209 | if (!f) { | ||
210 | sway_log(L_ERROR, "Unable to open %s for reading", path); | ||
211 | return false; | ||
212 | } | ||
213 | |||
214 | bool config_load_success = read_config(f, config); | ||
215 | fclose(f); | ||
216 | |||
217 | if (!config_load_success) { | ||
218 | sway_log(L_ERROR, "Error(s) loading config!"); | ||
219 | } | ||
220 | |||
221 | current_config_path = NULL; | ||
222 | return true; | ||
223 | } | ||
224 | |||
225 | static int qstrcmp(const void* a, const void* b) { | ||
226 | return strcmp(*((char**) a), *((char**) b)); | ||
227 | } | ||
228 | |||
229 | bool load_main_config(const char *file, bool is_active) { | ||
230 | char *path; | ||
231 | if (file != NULL) { | ||
232 | path = strdup(file); | ||
233 | } else { | ||
234 | path = get_config_path(); | ||
235 | } | ||
236 | |||
237 | struct sway_config *old_config = config; | ||
238 | config = calloc(1, sizeof(struct sway_config)); | ||
239 | if (!config) { | ||
240 | sway_abort("Unable to allocate config"); | ||
241 | } | ||
242 | |||
243 | config_defaults(config); | ||
244 | if (is_active) { | ||
245 | sway_log(L_DEBUG, "Performing configuration file reload"); | ||
246 | config->reloading = true; | ||
247 | config->active = true; | ||
248 | } | ||
249 | |||
250 | config->current_config = path; | ||
251 | list_add(config->config_chain, path); | ||
252 | |||
253 | config->reading = true; | ||
254 | |||
255 | // Read security configs | ||
256 | bool success = true; | ||
257 | DIR *dir = opendir(SYSCONFDIR "/sway/security.d"); | ||
258 | if (!dir) { | ||
259 | sway_log(L_ERROR, "%s does not exist, sway will have no security configuration" | ||
260 | " and will probably be broken", SYSCONFDIR "/sway/security.d"); | ||
261 | } else { | ||
262 | list_t *secconfigs = create_list(); | ||
263 | char *base = SYSCONFDIR "/sway/security.d/"; | ||
264 | struct dirent *ent = readdir(dir); | ||
265 | struct stat s; | ||
266 | while (ent != NULL) { | ||
267 | char *_path = malloc(strlen(ent->d_name) + strlen(base) + 1); | ||
268 | strcpy(_path, base); | ||
269 | strcat(_path, ent->d_name); | ||
270 | lstat(_path, &s); | ||
271 | if (S_ISREG(s.st_mode) && ent->d_name[0] != '.') { | ||
272 | list_add(secconfigs, _path); | ||
273 | } | ||
274 | else { | ||
275 | free(_path); | ||
276 | } | ||
277 | ent = readdir(dir); | ||
278 | } | ||
279 | closedir(dir); | ||
280 | |||
281 | list_qsort(secconfigs, qstrcmp); | ||
282 | for (int i = 0; i < secconfigs->length; ++i) { | ||
283 | char *_path = secconfigs->items[i]; | ||
284 | if (stat(_path, &s) || s.st_uid != 0 || s.st_gid != 0 || (((s.st_mode & 0777) != 0644) && (s.st_mode & 0777) != 0444)) { | ||
285 | sway_log(L_ERROR, "Refusing to load %s - it must be owned by root and mode 644 or 444", _path); | ||
286 | success = false; | ||
287 | } else { | ||
288 | success = success && load_config(_path, config); | ||
289 | } | ||
290 | } | ||
291 | |||
292 | free_flat_list(secconfigs); | ||
293 | } | ||
294 | |||
295 | success = success && load_config(path, config); | ||
296 | |||
297 | if (is_active) { | ||
298 | config->reloading = false; | ||
299 | } | ||
300 | |||
301 | if (old_config) { | ||
302 | free_config(old_config); | ||
303 | } | ||
304 | config->reading = false; | ||
305 | |||
306 | if (success) { | ||
307 | // TODO: bar | ||
308 | //update_active_bar_modifiers(); | ||
309 | } | ||
310 | |||
311 | return success; | ||
312 | } | ||
313 | |||
314 | bool read_config(FILE *file, struct sway_config *config) { | ||
315 | bool success = true; | ||
316 | enum cmd_status block = CMD_BLOCK_END; | ||
317 | |||
318 | int line_number = 0; | ||
319 | char *line; | ||
320 | while (!feof(file)) { | ||
321 | line = read_line(file); | ||
322 | if (!line) { | ||
323 | continue; | ||
324 | } | ||
325 | line_number++; | ||
326 | line = strip_whitespace(line); | ||
327 | if (line[0] == '#') { | ||
328 | free(line); | ||
329 | continue; | ||
330 | } | ||
331 | struct cmd_results *res; | ||
332 | if (block == CMD_BLOCK_COMMANDS) { | ||
333 | // Special case | ||
334 | res = config_commands_command(line); | ||
335 | } else { | ||
336 | res = config_command(line, block); | ||
337 | } | ||
338 | switch(res->status) { | ||
339 | case CMD_FAILURE: | ||
340 | case CMD_INVALID: | ||
341 | sway_log(L_ERROR, "Error on line %i '%s': %s (%s)", line_number, line, | ||
342 | res->error, config->current_config); | ||
343 | success = false; | ||
344 | break; | ||
345 | |||
346 | case CMD_DEFER: | ||
347 | sway_log(L_DEBUG, "Defferring command `%s'", line); | ||
348 | list_add(config->cmd_queue, strdup(line)); | ||
349 | break; | ||
350 | |||
351 | case CMD_BLOCK_MODE: | ||
352 | if (block == CMD_BLOCK_END) { | ||
353 | block = CMD_BLOCK_MODE; | ||
354 | } else { | ||
355 | sway_log(L_ERROR, "Invalid block '%s'", line); | ||
356 | } | ||
357 | break; | ||
358 | |||
359 | case CMD_BLOCK_INPUT: | ||
360 | if (block == CMD_BLOCK_END) { | ||
361 | block = CMD_BLOCK_INPUT; | ||
362 | } else { | ||
363 | sway_log(L_ERROR, "Invalid block '%s'", line); | ||
364 | } | ||
365 | break; | ||
366 | |||
367 | case CMD_BLOCK_BAR: | ||
368 | if (block == CMD_BLOCK_END) { | ||
369 | block = CMD_BLOCK_BAR; | ||
370 | } else { | ||
371 | sway_log(L_ERROR, "Invalid block '%s'", line); | ||
372 | } | ||
373 | break; | ||
374 | |||
375 | case CMD_BLOCK_BAR_COLORS: | ||
376 | if (block == CMD_BLOCK_BAR) { | ||
377 | block = CMD_BLOCK_BAR_COLORS; | ||
378 | } else { | ||
379 | sway_log(L_ERROR, "Invalid block '%s'", line); | ||
380 | } | ||
381 | break; | ||
382 | |||
383 | case CMD_BLOCK_COMMANDS: | ||
384 | if (block == CMD_BLOCK_END) { | ||
385 | block = CMD_BLOCK_COMMANDS; | ||
386 | } else { | ||
387 | sway_log(L_ERROR, "Invalid block '%s'", line); | ||
388 | } | ||
389 | break; | ||
390 | |||
391 | case CMD_BLOCK_IPC: | ||
392 | if (block == CMD_BLOCK_END) { | ||
393 | block = CMD_BLOCK_IPC; | ||
394 | } else { | ||
395 | sway_log(L_ERROR, "Invalid block '%s'", line); | ||
396 | } | ||
397 | break; | ||
398 | |||
399 | case CMD_BLOCK_IPC_EVENTS: | ||
400 | if (block == CMD_BLOCK_IPC) { | ||
401 | block = CMD_BLOCK_IPC_EVENTS; | ||
402 | } else { | ||
403 | sway_log(L_ERROR, "Invalid block '%s'", line); | ||
404 | } | ||
405 | break; | ||
406 | |||
407 | case CMD_BLOCK_END: | ||
408 | switch(block) { | ||
409 | case CMD_BLOCK_MODE: | ||
410 | sway_log(L_DEBUG, "End of mode block"); | ||
411 | config->current_mode = config->modes->items[0]; | ||
412 | block = CMD_BLOCK_END; | ||
413 | break; | ||
414 | |||
415 | case CMD_BLOCK_INPUT: | ||
416 | sway_log(L_DEBUG, "End of input block"); | ||
417 | // TODO: input | ||
418 | //current_input_config = NULL; | ||
419 | block = CMD_BLOCK_END; | ||
420 | break; | ||
421 | |||
422 | case CMD_BLOCK_BAR: | ||
423 | sway_log(L_DEBUG, "End of bar block"); | ||
424 | config->current_bar = NULL; | ||
425 | block = CMD_BLOCK_END; | ||
426 | break; | ||
427 | |||
428 | case CMD_BLOCK_BAR_COLORS: | ||
429 | sway_log(L_DEBUG, "End of bar colors block"); | ||
430 | block = CMD_BLOCK_BAR; | ||
431 | break; | ||
432 | |||
433 | case CMD_BLOCK_COMMANDS: | ||
434 | sway_log(L_DEBUG, "End of commands block"); | ||
435 | block = CMD_BLOCK_END; | ||
436 | break; | ||
437 | |||
438 | case CMD_BLOCK_IPC: | ||
439 | sway_log(L_DEBUG, "End of IPC block"); | ||
440 | block = CMD_BLOCK_END; | ||
441 | break; | ||
442 | |||
443 | case CMD_BLOCK_IPC_EVENTS: | ||
444 | sway_log(L_DEBUG, "End of IPC events block"); | ||
445 | block = CMD_BLOCK_IPC; | ||
446 | break; | ||
447 | |||
448 | case CMD_BLOCK_END: | ||
449 | sway_log(L_ERROR, "Unmatched }"); | ||
450 | break; | ||
451 | |||
452 | default:; | ||
453 | } | ||
454 | default:; | ||
455 | } | ||
456 | free(line); | ||
457 | free_cmd_results(res); | ||
458 | } | ||
459 | |||
460 | return success; | ||
461 | } | ||
462 | |||
463 | char *do_var_replacement(char *str) { | ||
464 | int i; | ||
465 | char *find = str; | ||
466 | while ((find = strchr(find, '$'))) { | ||
467 | // Skip if escaped. | ||
468 | if (find > str && find[-1] == '\\') { | ||
469 | if (find == str + 1 || !(find > str + 1 && find[-2] == '\\')) { | ||
470 | ++find; | ||
471 | continue; | ||
472 | } | ||
473 | } | ||
474 | // Find matching variable | ||
475 | for (i = 0; i < config->symbols->length; ++i) { | ||
476 | struct sway_variable *var = config->symbols->items[i]; | ||
477 | int vnlen = strlen(var->name); | ||
478 | if (strncmp(find, var->name, vnlen) == 0) { | ||
479 | int vvlen = strlen(var->value); | ||
480 | char *newstr = malloc(strlen(str) - vnlen + vvlen + 1); | ||
481 | if (!newstr) { | ||
482 | sway_log(L_ERROR, | ||
483 | "Unable to allocate replacement during variable expansion"); | ||
484 | break; | ||
485 | } | ||
486 | char *newptr = newstr; | ||
487 | int offset = find - str; | ||
488 | strncpy(newptr, str, offset); | ||
489 | newptr += offset; | ||
490 | strncpy(newptr, var->value, vvlen); | ||
491 | newptr += vvlen; | ||
492 | strcpy(newptr, find + vnlen); | ||
493 | free(str); | ||
494 | str = newstr; | ||
495 | find = str + offset + vvlen; | ||
496 | break; | ||
497 | } | ||
498 | } | ||
499 | if (i == config->symbols->length) { | ||
500 | ++find; | ||
501 | } | ||
502 | } | ||
503 | return str; | ||
504 | } | ||