1 #define _XOPEN_SOURCE 700 // for realpath
2 #include <stdio.h>
3 #include <stdbool.h>
4 #include <stdlib.h>
5 #include <unistd.h>
6 #include <libgen.h>
7 #include <wordexp.h>
8 #include <sys/types.h>
9 #include <sys/wait.h>
10 #include <sys/stat.h>
11 #include <signal.h>
12 #include <libinput.h>
13 #include <limits.h>
14 #include <dirent.h>
15 #include <strings.h>
16 #include <linux/input-event-codes.h>
17 #include <wlr/types/wlr_output.h>
18 #include "sway/input/input-manager.h"
19 #include "sway/input/seat.h"
20 #include "sway/input/switch.h"
21 #include "sway/commands.h"
22 #include "sway/config.h"
23 #include "sway/criteria.h"
24 #include "sway/desktop/transaction.h"
25 #include "sway/swaynag.h"
26 #include "sway/tree/arrange.h"
27 #include "sway/tree/root.h"
28 #include "sway/tree/workspace.h"
29 #include "cairo.h"
30 #include "pango.h"
31 #include "stringop.h"
32 #include "list.h"
33 #include "log.h"
34 #include "util.h"
35
36 struct sway_config *config = NULL;
37
keysym_translation_state_create(struct xkb_rule_names rules)38 static struct xkb_state *keysym_translation_state_create(
39 struct xkb_rule_names rules) {
40 struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
41 struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_names(
42 context,
43 &rules,
44 XKB_KEYMAP_COMPILE_NO_FLAGS);
45
46 xkb_context_unref(context);
47 return xkb_state_new(xkb_keymap);
48 }
49
keysym_translation_state_destroy(struct xkb_state * state)50 static void keysym_translation_state_destroy(
51 struct xkb_state *state) {
52 xkb_keymap_unref(xkb_state_get_keymap(state));
53 xkb_state_unref(state);
54 }
55
free_mode(struct sway_mode * mode)56 static void free_mode(struct sway_mode *mode) {
57 if (!mode) {
58 return;
59 }
60 free(mode->name);
61 if (mode->keysym_bindings) {
62 for (int i = 0; i < mode->keysym_bindings->length; i++) {
63 free_sway_binding(mode->keysym_bindings->items[i]);
64 }
65 list_free(mode->keysym_bindings);
66 }
67 if (mode->keycode_bindings) {
68 for (int i = 0; i < mode->keycode_bindings->length; i++) {
69 free_sway_binding(mode->keycode_bindings->items[i]);
70 }
71 list_free(mode->keycode_bindings);
72 }
73 if (mode->mouse_bindings) {
74 for (int i = 0; i < mode->mouse_bindings->length; i++) {
75 free_sway_binding(mode->mouse_bindings->items[i]);
76 }
77 list_free(mode->mouse_bindings);
78 }
79 if (mode->switch_bindings) {
80 for (int i = 0; i < mode->switch_bindings->length; i++) {
81 free_switch_binding(mode->switch_bindings->items[i]);
82 }
83 list_free(mode->switch_bindings);
84 }
85 free(mode);
86 }
87
free_config(struct sway_config * config)88 void free_config(struct sway_config *config) {
89 if (!config) {
90 return;
91 }
92
93 memset(&config->handler_context, 0, sizeof(config->handler_context));
94
95 // TODO: handle all currently unhandled lists as we add implementations
96 if (config->symbols) {
97 for (int i = 0; i < config->symbols->length; ++i) {
98 free_sway_variable(config->symbols->items[i]);
99 }
100 list_free(config->symbols);
101 }
102 if (config->modes) {
103 for (int i = 0; i < config->modes->length; ++i) {
104 free_mode(config->modes->items[i]);
105 }
106 list_free(config->modes);
107 }
108 if (config->bars) {
109 for (int i = 0; i < config->bars->length; ++i) {
110 free_bar_config(config->bars->items[i]);
111 }
112 list_free(config->bars);
113 }
114 list_free(config->cmd_queue);
115 if (config->workspace_configs) {
116 for (int i = 0; i < config->workspace_configs->length; i++) {
117 free_workspace_config(config->workspace_configs->items[i]);
118 }
119 list_free(config->workspace_configs);
120 }
121 if (config->output_configs) {
122 for (int i = 0; i < config->output_configs->length; i++) {
123 free_output_config(config->output_configs->items[i]);
124 }
125 list_free(config->output_configs);
126 }
127 if (config->swaybg_client != NULL) {
128 wl_client_destroy(config->swaybg_client);
129 }
130 if (config->input_configs) {
131 for (int i = 0; i < config->input_configs->length; i++) {
132 free_input_config(config->input_configs->items[i]);
133 }
134 list_free(config->input_configs);
135 }
136 if (config->input_type_configs) {
137 for (int i = 0; i < config->input_type_configs->length; i++) {
138 free_input_config(config->input_type_configs->items[i]);
139 }
140 list_free(config->input_type_configs);
141 }
142 if (config->seat_configs) {
143 for (int i = 0; i < config->seat_configs->length; i++) {
144 free_seat_config(config->seat_configs->items[i]);
145 }
146 list_free(config->seat_configs);
147 }
148 if (config->criteria) {
149 for (int i = 0; i < config->criteria->length; ++i) {
150 criteria_destroy(config->criteria->items[i]);
151 }
152 list_free(config->criteria);
153 }
154 list_free(config->no_focus);
155 list_free(config->active_bar_modifiers);
156 list_free_items_and_destroy(config->config_chain);
157 free(config->floating_scroll_up_cmd);
158 free(config->floating_scroll_down_cmd);
159 free(config->floating_scroll_left_cmd);
160 free(config->floating_scroll_right_cmd);
161 free(config->font);
162 free(config->swaybg_command);
163 free(config->swaynag_command);
164 free((char *)config->current_config_path);
165 free((char *)config->current_config);
166 keysym_translation_state_destroy(config->keysym_translation_state);
167 free(config);
168 }
169
destroy_removed_seats(struct sway_config * old_config,struct sway_config * new_config)170 static void destroy_removed_seats(struct sway_config *old_config,
171 struct sway_config *new_config) {
172 struct seat_config *seat_config;
173 struct sway_seat *seat;
174 int i;
175 for (i = 0; i < old_config->seat_configs->length; i++) {
176 seat_config = old_config->seat_configs->items[i];
177 // Skip the wildcard seat config, it won't have a matching real seat.
178 if (strcmp(seat_config->name, "*") == 0) {
179 continue;
180 }
181
182 /* Also destroy seats that aren't present in new config */
183 if (new_config && list_seq_find(new_config->seat_configs,
184 seat_name_cmp, seat_config->name) < 0) {
185 seat = input_manager_get_seat(seat_config->name, false);
186 if (seat) {
187 seat_destroy(seat);
188 }
189 }
190 }
191 }
192
config_defaults(struct sway_config * config)193 static void config_defaults(struct sway_config *config) {
194 if (!(config->swaynag_command = strdup("swaynag"))) goto cleanup;
195 config->swaynag_config_errors = (struct swaynag_instance){0};
196 config->swaynag_config_errors.args = "--type error "
197 "--message 'There are errors in your config file' "
198 "--detailed-message "
199 "--button-no-terminal 'Exit sway' 'swaymsg exit' "
200 "--button-no-terminal 'Reload sway' 'swaymsg reload'";
201 config->swaynag_config_errors.detailed = true;
202
203 if (!(config->symbols = create_list())) goto cleanup;
204 if (!(config->modes = create_list())) goto cleanup;
205 if (!(config->bars = create_list())) goto cleanup;
206 if (!(config->workspace_configs = create_list())) goto cleanup;
207 if (!(config->criteria = create_list())) goto cleanup;
208 if (!(config->no_focus = create_list())) goto cleanup;
209 if (!(config->seat_configs = create_list())) goto cleanup;
210 if (!(config->output_configs = create_list())) goto cleanup;
211
212 if (!(config->input_type_configs = create_list())) goto cleanup;
213 if (!(config->input_configs = create_list())) goto cleanup;
214
215 if (!(config->cmd_queue = create_list())) goto cleanup;
216
217 if (!(config->current_mode = malloc(sizeof(struct sway_mode))))
218 goto cleanup;
219 if (!(config->current_mode->name = malloc(sizeof("default")))) goto cleanup;
220 strcpy(config->current_mode->name, "default");
221 if (!(config->current_mode->keysym_bindings = create_list())) goto cleanup;
222 if (!(config->current_mode->keycode_bindings = create_list())) goto cleanup;
223 if (!(config->current_mode->mouse_bindings = create_list())) goto cleanup;
224 if (!(config->current_mode->switch_bindings = create_list())) goto cleanup;
225 list_add(config->modes, config->current_mode);
226
227 config->floating_mod = 0;
228 config->floating_mod_inverse = false;
229 config->dragging_key = BTN_LEFT;
230 config->resizing_key = BTN_RIGHT;
231
232 if (!(config->floating_scroll_up_cmd = strdup(""))) goto cleanup;
233 if (!(config->floating_scroll_down_cmd = strdup(""))) goto cleanup;
234 if (!(config->floating_scroll_left_cmd = strdup(""))) goto cleanup;
235 if (!(config->floating_scroll_right_cmd = strdup(""))) goto cleanup;
236 config->default_layout = L_NONE;
237 config->default_orientation = L_NONE;
238 if (!(config->font = strdup("monospace 10"))) goto cleanup;
239 config->font_height = 17; // height of monospace 10
240 config->urgent_timeout = 500;
241 config->focus_on_window_activation = FOWA_URGENT;
242 config->popup_during_fullscreen = POPUP_SMART;
243 config->xwayland = XWAYLAND_MODE_LAZY;
244 config->xwayland_scale = 1;
245
246 config->titlebar_border_thickness = 1;
247 config->titlebar_h_padding = 5;
248 config->titlebar_v_padding = 4;
249
250 // floating view
251 config->floating_maximum_width = 0;
252 config->floating_maximum_height = 0;
253 config->floating_minimum_width = 75;
254 config->floating_minimum_height = 50;
255
256 // Flags
257 config->focus_follows_mouse = FOLLOWS_YES;
258 config->mouse_warping = WARP_OUTPUT;
259 config->focus_wrapping = WRAP_YES;
260 config->validating = false;
261 config->reloading = false;
262 config->active = false;
263 config->failed = false;
264 config->auto_back_and_forth = false;
265 config->reading = false;
266 config->show_marks = true;
267 config->title_align = ALIGN_LEFT;
268 config->tiling_drag = true;
269 config->tiling_drag_threshold = 9;
270
271 config->smart_gaps = false;
272 config->gaps_inner = 0;
273 config->gaps_outer.top = 0;
274 config->gaps_outer.right = 0;
275 config->gaps_outer.bottom = 0;
276 config->gaps_outer.left = 0;
277
278 if (!(config->active_bar_modifiers = create_list())) goto cleanup;
279
280 if (!(config->swaybg_command = strdup("swaybg"))) goto cleanup;
281
282 if (!(config->config_chain = create_list())) goto cleanup;
283 config->current_config_path = NULL;
284 config->current_config = NULL;
285
286 // borders
287 config->border = B_NORMAL;
288 config->floating_border = B_NORMAL;
289 config->border_thickness = 2;
290 config->floating_border_thickness = 2;
291 config->hide_edge_borders = E_NONE;
292 config->hide_edge_borders_smart = ESMART_OFF;
293 config->hide_lone_tab = false;
294
295 // border colors
296 color_to_rgba(config->border_colors.focused.border, 0x4C7899FF);
297 color_to_rgba(config->border_colors.focused.background, 0x285577FF);
298 color_to_rgba(config->border_colors.focused.text, 0xFFFFFFFF);
299 color_to_rgba(config->border_colors.focused.indicator, 0x2E9EF4FF);
300 color_to_rgba(config->border_colors.focused.child_border, 0x285577FF);
301
302 color_to_rgba(config->border_colors.focused_inactive.border, 0x333333FF);
303 color_to_rgba(config->border_colors.focused_inactive.background, 0x5F676AFF);
304 color_to_rgba(config->border_colors.focused_inactive.text, 0xFFFFFFFF);
305 color_to_rgba(config->border_colors.focused_inactive.indicator, 0x484E50FF);
306 color_to_rgba(config->border_colors.focused_inactive.child_border, 0x5F676AFF);
307
308 color_to_rgba(config->border_colors.unfocused.border, 0x333333FF);
309 color_to_rgba(config->border_colors.unfocused.background, 0x222222FF);
310 color_to_rgba(config->border_colors.unfocused.text, 0x888888FF);
311 color_to_rgba(config->border_colors.unfocused.indicator, 0x292D2EFF);
312 color_to_rgba(config->border_colors.unfocused.child_border, 0x222222FF);
313
314 color_to_rgba(config->border_colors.urgent.border, 0x2F343AFF);
315 color_to_rgba(config->border_colors.urgent.background, 0x900000FF);
316 color_to_rgba(config->border_colors.urgent.text, 0xFFFFFFFF);
317 color_to_rgba(config->border_colors.urgent.indicator, 0x900000FF);
318 color_to_rgba(config->border_colors.urgent.child_border, 0x900000FF);
319
320 color_to_rgba(config->border_colors.placeholder.border, 0x000000FF);
321 color_to_rgba(config->border_colors.placeholder.background, 0x0C0C0CFF);
322 color_to_rgba(config->border_colors.placeholder.text, 0xFFFFFFFF);
323 color_to_rgba(config->border_colors.placeholder.indicator, 0x000000FF);
324 color_to_rgba(config->border_colors.placeholder.child_border, 0x0C0C0CFF);
325
326 color_to_rgba(config->border_colors.background, 0xFFFFFFFF);
327
328 // The keysym to keycode translation
329 struct xkb_rule_names rules = {0};
330 config->keysym_translation_state =
331 keysym_translation_state_create(rules);
332
333 return;
334 cleanup:
335 sway_abort("Unable to allocate config structures");
336 }
337
file_exists(const char * path)338 static bool file_exists(const char *path) {
339 return path && access(path, R_OK) != -1;
340 }
341
get_config_path(void)342 static char *get_config_path(void) {
343 static const char *config_paths[] = {
344 "$HOME/.sway/config",
345 "$XDG_CONFIG_HOME/sway/config",
346 "$HOME/.i3/config",
347 "$XDG_CONFIG_HOME/i3/config",
348 SYSCONFDIR "/sway/config",
349 SYSCONFDIR "/i3/config",
350 };
351
352 char *config_home = getenv("XDG_CONFIG_HOME");
353 if (!config_home || !*config_home) {
354 config_paths[1] = "$HOME/.config/sway/config";
355 config_paths[3] = "$HOME/.config/i3/config";
356 }
357
358 for (size_t i = 0; i < sizeof(config_paths) / sizeof(char *); ++i) {
359 wordexp_t p;
360 if (wordexp(config_paths[i], &p, WRDE_UNDEF) == 0) {
361 char *path = strdup(p.we_wordv[0]);
362 wordfree(&p);
363 if (file_exists(path)) {
364 return path;
365 }
366 free(path);
367 }
368 }
369
370 return NULL;
371 }
372
load_config(const char * path,struct sway_config * config,struct swaynag_instance * swaynag)373 static bool load_config(const char *path, struct sway_config *config,
374 struct swaynag_instance *swaynag) {
375 if (path == NULL) {
376 sway_log(SWAY_ERROR, "Unable to find a config file!");
377 return false;
378 }
379
380 sway_log(SWAY_INFO, "Loading config from %s", path);
381
382 struct stat sb;
383 if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
384 sway_log(SWAY_ERROR, "%s is a directory not a config file", path);
385 return false;
386 }
387
388 FILE *f = fopen(path, "r");
389 if (!f) {
390 sway_log(SWAY_ERROR, "Unable to open %s for reading", path);
391 return false;
392 }
393
394 bool config_load_success = read_config(f, config, swaynag);
395 fclose(f);
396
397 if (!config_load_success) {
398 sway_log(SWAY_ERROR, "Error(s) loading config!");
399 }
400
401 return config->active || !config->validating || config_load_success;
402 }
403
load_main_config(const char * file,bool is_active,bool validating)404 bool load_main_config(const char *file, bool is_active, bool validating) {
405 char *path;
406 if (file != NULL) {
407 path = strdup(file);
408 } else {
409 path = get_config_path();
410 }
411
412 char *real_path = realpath(path, NULL);
413 if (real_path == NULL) {
414 sway_log(SWAY_DEBUG, "%s not found.", path);
415 free(path);
416 return false;
417 }
418
419 struct sway_config *old_config = config;
420 config = calloc(1, sizeof(struct sway_config));
421 if (!config) {
422 sway_abort("Unable to allocate config");
423 }
424
425 config_defaults(config);
426 config->validating = validating;
427 if (is_active) {
428 sway_log(SWAY_DEBUG, "Performing configuration file %s",
429 validating ? "validation" : "reload");
430 config->reloading = true;
431 config->active = true;
432
433 // xwayland can only be enabled/disabled at launch
434 sway_log(SWAY_DEBUG, "xwayland will remain %s",
435 old_config->xwayland ? "enabled" : "disabled");
436 config->xwayland = old_config->xwayland;
437
438 if (!config->validating) {
439 if (old_config->swaybg_client != NULL) {
440 wl_client_destroy(old_config->swaybg_client);
441 }
442
443 if (old_config->swaynag_config_errors.client != NULL) {
444 wl_client_destroy(old_config->swaynag_config_errors.client);
445 }
446
447 input_manager_reset_all_inputs();
448 }
449 }
450
451 config->user_config_path = file ? true : false;
452 config->current_config_path = path;
453 list_add(config->config_chain, real_path);
454
455 config->reading = true;
456
457 // Read security configs
458 // TODO: Security
459 bool success = true;
460 /*
461 DIR *dir = opendir(SYSCONFDIR "/sway/security.d");
462 if (!dir) {
463 sway_log(SWAY_ERROR,
464 "%s does not exist, sway will have no security configuration"
465 " and will probably be broken", SYSCONFDIR "/sway/security.d");
466 } else {
467 list_t *secconfigs = create_list();
468 char *base = SYSCONFDIR "/sway/security.d/";
469 struct dirent *ent = readdir(dir);
470 struct stat s;
471 while (ent != NULL) {
472 char *_path = malloc(strlen(ent->d_name) + strlen(base) + 1);
473 strcpy(_path, base);
474 strcat(_path, ent->d_name);
475 lstat(_path, &s);
476 if (S_ISREG(s.st_mode) && ent->d_name[0] != '.') {
477 list_add(secconfigs, _path);
478 }
479 else {
480 free(_path);
481 }
482 ent = readdir(dir);
483 }
484 closedir(dir);
485
486 list_qsort(secconfigs, qstrcmp);
487 for (int i = 0; i < secconfigs->length; ++i) {
488 char *_path = secconfigs->items[i];
489 if (stat(_path, &s) || s.st_uid != 0 || s.st_gid != 0 ||
490 (((s.st_mode & 0777) != 0644) &&
491 (s.st_mode & 0777) != 0444)) {
492 sway_log(SWAY_ERROR,
493 "Refusing to load %s - it must be owned by root "
494 "and mode 644 or 444", _path);
495 success = false;
496 } else {
497 success = success && load_config(_path, config);
498 }
499 }
500
501 list_free_items_and_destroy(secconfigs);
502 }
503 */
504
505 success = success && load_config(path, config,
506 &config->swaynag_config_errors);
507
508 if (validating) {
509 free_config(config);
510 config = old_config;
511 return success;
512 }
513
514 if (is_active && !validating) {
515 input_manager_verify_fallback_seat();
516
517 for (int i = 0; i < config->input_configs->length; i++) {
518 input_manager_apply_input_config(config->input_configs->items[i]);
519 }
520
521 for (int i = 0; i < config->input_type_configs->length; i++) {
522 input_manager_apply_input_config(
523 config->input_type_configs->items[i]);
524 }
525
526 for (int i = 0; i < config->seat_configs->length; i++) {
527 input_manager_apply_seat_config(config->seat_configs->items[i]);
528 }
529 sway_switch_retrigger_bindings_for_all();
530
531 reset_outputs();
532 spawn_swaybg();
533
534 config->reloading = false;
535 if (config->swaynag_config_errors.client != NULL) {
536 swaynag_show(&config->swaynag_config_errors);
537 }
538 }
539
540 if (old_config) {
541 destroy_removed_seats(old_config, config);
542 free_config(old_config);
543 }
544 config->reading = false;
545 return success;
546 }
547
load_include_config(const char * path,const char * parent_dir,struct sway_config * config,struct swaynag_instance * swaynag)548 static bool load_include_config(const char *path, const char *parent_dir,
549 struct sway_config *config, struct swaynag_instance *swaynag) {
550 // save parent config
551 const char *parent_config = config->current_config_path;
552
553 char *full_path;
554 int len = strlen(path);
555 if (len >= 1 && path[0] != '/') {
556 len = len + strlen(parent_dir) + 2;
557 full_path = malloc(len * sizeof(char));
558 if (!full_path) {
559 sway_log(SWAY_ERROR,
560 "Unable to allocate full path to included config");
561 return false;
562 }
563 snprintf(full_path, len, "%s/%s", parent_dir, path);
564 } else {
565 full_path = strdup(path);
566 }
567
568 char *real_path = realpath(full_path, NULL);
569 free(full_path);
570
571 if (real_path == NULL) {
572 sway_log(SWAY_DEBUG, "%s not found.", path);
573 return false;
574 }
575
576 // check if config has already been included
577 int j;
578 for (j = 0; j < config->config_chain->length; ++j) {
579 char *old_path = config->config_chain->items[j];
580 if (strcmp(real_path, old_path) == 0) {
581 sway_log(SWAY_DEBUG,
582 "%s already included once, won't be included again.",
583 real_path);
584 free(real_path);
585 return false;
586 }
587 }
588
589 config->current_config_path = real_path;
590 list_add(config->config_chain, real_path);
591 int index = config->config_chain->length - 1;
592
593 if (!load_config(real_path, config, swaynag)) {
594 free(real_path);
595 config->current_config_path = parent_config;
596 list_del(config->config_chain, index);
597 return false;
598 }
599
600 // restore current_config_path
601 config->current_config_path = parent_config;
602 return true;
603 }
604
load_include_configs(const char * path,struct sway_config * config,struct swaynag_instance * swaynag)605 void load_include_configs(const char *path, struct sway_config *config,
606 struct swaynag_instance *swaynag) {
607 char *wd = getcwd(NULL, 0);
608 char *parent_path = strdup(config->current_config_path);
609 const char *parent_dir = dirname(parent_path);
610
611 if (chdir(parent_dir) < 0) {
612 sway_log(SWAY_ERROR, "failed to change working directory");
613 goto cleanup;
614 }
615
616 wordexp_t p;
617 if (wordexp(path, &p, 0) == 0) {
618 char **w = p.we_wordv;
619 size_t i;
620 for (i = 0; i < p.we_wordc; ++i) {
621 load_include_config(w[i], parent_dir, config, swaynag);
622 }
623 wordfree(&p);
624 }
625
626 // Attempt to restore working directory before returning.
627 if (chdir(wd) < 0) {
628 sway_log(SWAY_ERROR, "failed to change working directory");
629 }
630 cleanup:
631 free(parent_path);
632 free(wd);
633 }
634
run_deferred_commands(void)635 void run_deferred_commands(void) {
636 if (!config->cmd_queue->length) {
637 return;
638 }
639 sway_log(SWAY_DEBUG, "Running deferred commands");
640 while (config->cmd_queue->length) {
641 char *line = config->cmd_queue->items[0];
642 list_t *res_list = execute_command(line, NULL, NULL);
643 for (int i = 0; i < res_list->length; ++i) {
644 struct cmd_results *res = res_list->items[i];
645 if (res->status != CMD_SUCCESS) {
646 sway_log(SWAY_ERROR, "Error on line '%s': %s",
647 line, res->error);
648 }
649 free_cmd_results(res);
650 }
651 list_del(config->cmd_queue, 0);
652 list_free(res_list);
653 free(line);
654 }
655 }
656
run_deferred_bindings(void)657 void run_deferred_bindings(void) {
658 struct sway_seat *seat;
659 wl_list_for_each(seat, &(server.input->seats), link) {
660 if (!seat->deferred_bindings->length) {
661 continue;
662 }
663 sway_log(SWAY_DEBUG, "Running deferred bindings for seat %s",
664 seat->wlr_seat->name);
665 while (seat->deferred_bindings->length) {
666 struct sway_binding *binding = seat->deferred_bindings->items[0];
667 seat_execute_command(seat, binding);
668 list_del(seat->deferred_bindings, 0);
669 free_sway_binding(binding);
670 }
671 }
672 }
673
674 // get line, with backslash continuation
getline_with_cont(char ** lineptr,size_t * line_size,FILE * file,int * nlines)675 static ssize_t getline_with_cont(char **lineptr, size_t *line_size, FILE *file,
676 int *nlines) {
677 char *next_line = NULL;
678 size_t next_line_size = 0;
679 ssize_t nread = getline(lineptr, line_size, file);
680 *nlines = nread == -1 ? 0 : 1;
681 while (nread >= 2 && strcmp(&(*lineptr)[nread - 2], "\\\n") == 0 && (*lineptr)[0] != '#') {
682 ssize_t next_nread = getline(&next_line, &next_line_size, file);
683 if (next_nread == -1) {
684 break;
685 }
686 (*nlines)++;
687
688 nread += next_nread - 2;
689 if ((ssize_t) *line_size < nread + 1) {
690 *line_size = nread + 1;
691 char *old_ptr = *lineptr;
692 *lineptr = realloc(*lineptr, *line_size);
693 if (!*lineptr) {
694 free(old_ptr);
695 nread = -1;
696 break;
697 }
698 }
699 strcpy(&(*lineptr)[nread - next_nread], next_line);
700 }
701 free(next_line);
702 return nread;
703 }
704
detect_brace(FILE * file)705 static int detect_brace(FILE *file) {
706 int ret = 0;
707 int lines = 0;
708 long pos = ftell(file);
709 char *line = NULL;
710 size_t line_size = 0;
711 while ((getline(&line, &line_size, file)) != -1) {
712 lines++;
713 strip_whitespace(line);
714 if (*line) {
715 if (strcmp(line, "{") == 0) {
716 ret = lines;
717 }
718 break;
719 }
720 }
721 free(line);
722 if (ret == 0) {
723 fseek(file, pos, SEEK_SET);
724 }
725 return ret;
726 }
727
expand_line(const char * block,const char * line,bool add_brace)728 static char *expand_line(const char *block, const char *line, bool add_brace) {
729 int size = (block ? strlen(block) + 1 : 0) + strlen(line)
730 + (add_brace ? 2 : 0) + 1;
731 char *expanded = calloc(1, size);
732 if (!expanded) {
733 sway_log(SWAY_ERROR, "Cannot allocate expanded line buffer");
734 return NULL;
735 }
736 snprintf(expanded, size, "%s%s%s%s", block ? block : "",
737 block ? " " : "", line, add_brace ? " {" : "");
738 return expanded;
739 }
740
read_config(FILE * file,struct sway_config * config,struct swaynag_instance * swaynag)741 bool read_config(FILE *file, struct sway_config *config,
742 struct swaynag_instance *swaynag) {
743 bool reading_main_config = false;
744 char *this_config = NULL;
745 size_t config_size = 0;
746 if (config->current_config == NULL) {
747 reading_main_config = true;
748
749 int ret_seek = fseek(file, 0, SEEK_END);
750 long ret_tell = ftell(file);
751 if (ret_seek == -1 || ret_tell == -1) {
752 sway_log(SWAY_ERROR, "Unable to get size of config file");
753 return false;
754 }
755 config_size = ret_tell;
756 rewind(file);
757
758 config->current_config = this_config = calloc(1, config_size + 1);
759 if (this_config == NULL) {
760 sway_log(SWAY_ERROR, "Unable to allocate buffer for config contents");
761 return false;
762 }
763 }
764
765 bool success = true;
766 int line_number = 0;
767 char *line = NULL;
768 size_t line_size = 0;
769 ssize_t nread;
770 list_t *stack = create_list();
771 size_t read = 0;
772 int nlines = 0;
773 while ((nread = getline_with_cont(&line, &line_size, file, &nlines)) != -1) {
774 if (reading_main_config) {
775 if (read + nread > config_size) {
776 sway_log(SWAY_ERROR, "Config file changed during reading");
777 success = false;
778 break;
779 }
780
781 strcpy(&this_config[read], line);
782 read += nread;
783 }
784
785 if (line[nread - 1] == '\n') {
786 line[nread - 1] = '\0';
787 }
788
789 line_number += nlines;
790 sway_log(SWAY_DEBUG, "Read line %d: %s", line_number, line);
791
792 strip_whitespace(line);
793 if (!*line || line[0] == '#') {
794 continue;
795 }
796 int brace_detected = 0;
797 if (line[strlen(line) - 1] != '{' && line[strlen(line) - 1] != '}') {
798 brace_detected = detect_brace(file);
799 if (brace_detected > 0) {
800 line_number += brace_detected;
801 sway_log(SWAY_DEBUG, "Detected open brace on line %d", line_number);
802 }
803 }
804 char *block = stack->length ? stack->items[0] : NULL;
805 char *expanded = expand_line(block, line, brace_detected > 0);
806 if (!expanded) {
807 success = false;
808 break;
809 }
810 config->current_config_line_number = line_number;
811 config->current_config_line = line;
812 struct cmd_results *res;
813 char *new_block = NULL;
814 if (block && strcmp(block, "<commands>") == 0) {
815 // Special case
816 res = config_commands_command(expanded);
817 } else {
818 res = config_command(expanded, &new_block);
819 }
820 switch(res->status) {
821 case CMD_FAILURE:
822 case CMD_INVALID:
823 sway_log(SWAY_ERROR, "Error on line %i '%s': %s (%s)", line_number,
824 line, res->error, config->current_config_path);
825 if (!config->validating) {
826 swaynag_log(config->swaynag_command, swaynag,
827 "Error on line %i (%s) '%s': %s", line_number,
828 config->current_config_path, line, res->error);
829 }
830 success = false;
831 break;
832
833 case CMD_DEFER:
834 sway_log(SWAY_DEBUG, "Deferring command `%s'", line);
835 list_add(config->cmd_queue, strdup(expanded));
836 break;
837
838 case CMD_BLOCK_COMMANDS:
839 sway_log(SWAY_DEBUG, "Entering commands block");
840 list_insert(stack, 0, "<commands>");
841 break;
842
843 case CMD_BLOCK:
844 sway_log(SWAY_DEBUG, "Entering block '%s'", new_block);
845 list_insert(stack, 0, strdup(new_block));
846 if (strcmp(new_block, "bar") == 0) {
847 config->current_bar = NULL;
848 }
849 break;
850
851 case CMD_BLOCK_END:
852 if (!block) {
853 sway_log(SWAY_DEBUG, "Unmatched '}' on line %i", line_number);
854 success = false;
855 break;
856 }
857 if (strcmp(block, "bar") == 0) {
858 config->current_bar = NULL;
859 }
860
861 sway_log(SWAY_DEBUG, "Exiting block '%s'", block);
862 list_del(stack, 0);
863 free(block);
864 memset(&config->handler_context, 0,
865 sizeof(config->handler_context));
866 default:;
867 }
868 free(new_block);
869 free(expanded);
870 free_cmd_results(res);
871 }
872 free(line);
873 list_free_items_and_destroy(stack);
874 config->current_config_line_number = 0;
875 config->current_config_line = NULL;
876
877 return success;
878 }
879
config_add_swaynag_warning(char * fmt,...)880 void config_add_swaynag_warning(char *fmt, ...) {
881 if (config->reading && !config->validating) {
882 va_list args;
883 va_start(args, fmt);
884 size_t length = vsnprintf(NULL, 0, fmt, args) + 1;
885 va_end(args);
886
887 char *temp = malloc(length + 1);
888 if (!temp) {
889 sway_log(SWAY_ERROR, "Failed to allocate buffer for warning.");
890 return;
891 }
892
893 va_start(args, fmt);
894 vsnprintf(temp, length, fmt, args);
895 va_end(args);
896
897 swaynag_log(config->swaynag_command, &config->swaynag_config_errors,
898 "Warning on line %i (%s) '%s': %s",
899 config->current_config_line_number, config->current_config_path,
900 config->current_config_line, temp);
901 }
902 }
903
do_var_replacement(char * str)904 char *do_var_replacement(char *str) {
905 int i;
906 char *find = str;
907 while ((find = strchr(find, '$'))) {
908 // Skip if escaped.
909 if (find > str && find[-1] == '\\') {
910 if (find == str + 1 || !(find > str + 1 && find[-2] == '\\')) {
911 ++find;
912 continue;
913 }
914 }
915 // Unescape double $ and move on
916 if (find[1] == '$') {
917 size_t length = strlen(find + 1);
918 memmove(find, find + 1, length);
919 find[length] = '\0';
920 ++find;
921 continue;
922 }
923 // Find matching variable
924 for (i = 0; i < config->symbols->length; ++i) {
925 struct sway_variable *var = config->symbols->items[i];
926 int vnlen = strlen(var->name);
927 if (strncmp(find, var->name, vnlen) == 0) {
928 int vvlen = strlen(var->value);
929 char *newstr = malloc(strlen(str) - vnlen + vvlen + 1);
930 if (!newstr) {
931 sway_log(SWAY_ERROR,
932 "Unable to allocate replacement "
933 "during variable expansion");
934 break;
935 }
936 char *newptr = newstr;
937 int offset = find - str;
938 strncpy(newptr, str, offset);
939 newptr += offset;
940 strncpy(newptr, var->value, vvlen);
941 newptr += vvlen;
942 strcpy(newptr, find + vnlen);
943 free(str);
944 str = newstr;
945 find = str + offset + vvlen;
946 break;
947 }
948 }
949 if (i == config->symbols->length) {
950 ++find;
951 }
952 }
953 return str;
954 }
955
956 // the naming is intentional (albeit long): a workspace_output_cmp function
957 // would compare two structs in full, while this method only compares the
958 // workspace.
workspace_output_cmp_workspace(const void * a,const void * b)959 int workspace_output_cmp_workspace(const void *a, const void *b) {
960 const struct workspace_config *wsa = a, *wsb = b;
961 return lenient_strcmp(wsa->workspace, wsb->workspace);
962 }
963
find_font_height_iterator(struct sway_container * con,void * data)964 static void find_font_height_iterator(struct sway_container *con, void *data) {
965 size_t amount_below_baseline = con->title_height - con->title_baseline;
966 size_t extended_height = config->font_baseline + amount_below_baseline;
967 if (extended_height > config->font_height) {
968 config->font_height = extended_height;
969 }
970 }
971
find_baseline_iterator(struct sway_container * con,void * data)972 static void find_baseline_iterator(struct sway_container *con, void *data) {
973 bool *recalculate = data;
974 if (*recalculate) {
975 container_calculate_title_height(con);
976 }
977 if (con->title_baseline > config->font_baseline) {
978 config->font_baseline = con->title_baseline;
979 }
980 }
981
config_update_font_height(bool recalculate)982 void config_update_font_height(bool recalculate) {
983 size_t prev_max_height = config->font_height;
984 config->font_height = 0;
985 config->font_baseline = 0;
986
987 root_for_each_container(find_baseline_iterator, &recalculate);
988 root_for_each_container(find_font_height_iterator, NULL);
989
990 if (config->font_height != prev_max_height) {
991 arrange_root();
992 }
993 }
994
translate_binding_list(list_t * bindings,list_t * bindsyms,list_t * bindcodes)995 static void translate_binding_list(list_t *bindings, list_t *bindsyms,
996 list_t *bindcodes) {
997 for (int i = 0; i < bindings->length; ++i) {
998 struct sway_binding *binding = bindings->items[i];
999 translate_binding(binding);
1000
1001 switch (binding->type) {
1002 case BINDING_KEYSYM:
1003 binding_add_translated(binding, bindsyms);
1004 break;
1005 case BINDING_KEYCODE:
1006 binding_add_translated(binding, bindcodes);
1007 break;
1008 default:
1009 sway_assert(false, "unexpected translated binding type: %d",
1010 binding->type);
1011 break;
1012 }
1013
1014 }
1015 }
1016
translate_keysyms(struct input_config * input_config)1017 void translate_keysyms(struct input_config *input_config) {
1018 keysym_translation_state_destroy(config->keysym_translation_state);
1019
1020 struct xkb_rule_names rules = {0};
1021 input_config_fill_rule_names(input_config, &rules);
1022 config->keysym_translation_state =
1023 keysym_translation_state_create(rules);
1024
1025 for (int i = 0; i < config->modes->length; ++i) {
1026 struct sway_mode *mode = config->modes->items[i];
1027
1028 list_t *bindsyms = create_list();
1029 list_t *bindcodes = create_list();
1030
1031 translate_binding_list(mode->keysym_bindings, bindsyms, bindcodes);
1032 translate_binding_list(mode->keycode_bindings, bindsyms, bindcodes);
1033
1034 list_free(mode->keysym_bindings);
1035 list_free(mode->keycode_bindings);
1036
1037 mode->keysym_bindings = bindsyms;
1038 mode->keycode_bindings = bindcodes;
1039 }
1040
1041 sway_log(SWAY_DEBUG, "Translated keysyms using config for device '%s'",
1042 input_config->identifier);
1043 }
1044