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