From 90f7f1a0e61fa20ed1b74b9df057aa70abc791ed Mon Sep 17 00:00:00 2001
From: emersion <contact@emersion.fr>
Date: Tue, 5 Dec 2017 10:40:55 +0100
Subject: Add minimal config subsystem

---
 include/sway/commands.h     |  10 +
 include/sway/config.h       | 405 +++++++++++++++++++++++++++++++++++
 meson.build                 |   2 +
 sway/commands.c             | 132 ++++++++++++
 sway/commands/exec.c        |   6 +-
 sway/commands/exec_always.c |   6 +-
 sway/commands/exit.c        |   4 +-
 sway/config.c               | 504 ++++++++++++++++++++++++++++++++++++++++++++
 sway/main.c                 |  21 +-
 sway/meson.build            |   2 +
 sway/security.c             |  18 ++
 11 files changed, 1091 insertions(+), 19 deletions(-)
 create mode 100644 include/sway/config.h
 create mode 100644 sway/config.c
 create mode 100644 sway/security.c

diff --git a/include/sway/commands.h b/include/sway/commands.h
index df5c6859..b1f0423d 100644
--- a/include/sway/commands.h
+++ b/include/sway/commands.h
@@ -46,6 +46,16 @@ struct cmd_results *checkarg(int argc, const char *name,
  * Parse and handles a command.
  */
 struct cmd_results *handle_command(char *command);
+/**
+ * Parse and handles a command during config file loading.
+ *
+ * Do not use this under normal conditions.
+ */
+struct cmd_results *config_command(char *command, enum cmd_status block);
+/*
+ * Parses a command policy rule.
+ */
+struct cmd_results *config_commands_command(char *exec);
 /**
  * Allocates a cmd_results object.
  */
diff --git a/include/sway/config.h b/include/sway/config.h
new file mode 100644
index 00000000..56b4e637
--- /dev/null
+++ b/include/sway/config.h
@@ -0,0 +1,405 @@
+#ifndef _SWAY_CONFIG_H
+#define _SWAY_CONFIG_H
+
+#define PID_WORKSPACE_TIMEOUT 60
+
+#include <libinput.h>
+#include <stdint.h>
+#include <wlc/geometry.h>
+#include <wlc/wlc.h>
+#include <xkbcommon/xkbcommon.h>
+#include <time.h>
+#include "list.h"
+#include "layout.h"
+#include "container.h"
+
+/**
+ * Describes a variable created via the `set` command.
+ */
+struct sway_variable {
+	char *name;
+	char *value;
+};
+
+/**
+ * A key binding and an associated command.
+ */
+struct sway_binding {
+	int order;
+	bool release;
+	bool bindcode;
+	list_t *keys;
+	uint32_t modifiers;
+	char *command;
+};
+
+/**
+ * A mouse binding and an associated command.
+ */
+struct sway_mouse_binding {
+	uint32_t button;
+	char *command;
+};
+
+/**
+ * A "mode" of keybindings created via the `mode` command.
+ */
+struct sway_mode {
+	char *name;
+	list_t *bindings;
+};
+
+/**
+ * libinput options for input devices
+ */
+struct input_config {
+	char *identifier;
+
+	int accel_profile;
+	int click_method;
+	int drag_lock;
+	int dwt;
+	int left_handed;
+	int middle_emulation;
+	int natural_scroll;
+	float pointer_accel;
+	int scroll_method;
+	int send_events;
+	int tap;
+
+	bool capturable;
+	struct wlc_geometry region;
+};
+
+/**
+ * Size and position configuration for a particular output.
+ *
+ * This is set via the `output` command.
+ */
+struct output_config {
+	char *name;
+	int enabled;
+	int width, height;
+	int x, y;
+	int scale;
+	char *background;
+	char *background_option;
+};
+
+/**
+ * Maps a workspace name to an output name.
+ *
+ * Set via `workspace <x> output <y>`
+ */
+struct workspace_output {
+	char *output;
+	char *workspace;
+};
+
+struct pid_workspace {
+	pid_t *pid;
+	char *workspace;
+	time_t *time_added;
+};
+
+struct bar_config {
+	/**
+	 * One of "dock", "hide", "invisible"
+	 *
+	 * Always visible in dock mode. Visible only when modifier key is held in hide mode.
+	 * Never visible in invisible mode.
+	 */
+	char *mode;
+	/**
+	 * One of "show" or "hide".
+	 *
+	 * In "show" mode, it will always be shown on top of the active workspace.
+	 */
+	char *hidden_state;
+	/**
+	 * Id name used to identify the bar through IPC.
+	 *
+	 * Defaults to bar-x, where x corresponds to the position of the
+	 * embedding bar block in the config file (bar-0, bar-1, ...).
+	 */
+	char *id;
+	uint32_t modifier;
+	list_t *outputs;
+	//enum desktop_shell_panel_position position; // TODO
+	list_t *bindings;
+	char *status_command;
+	bool pango_markup;
+	char *swaybar_command;
+	char *font;
+	int height; // -1 not defined
+
+#ifdef ENABLE_TRAY
+	// Tray
+	char *tray_output;
+	char *icon_theme;
+	uint32_t tray_padding;
+	uint32_t activate_button;
+	uint32_t context_button;
+	uint32_t secondary_button;
+#endif
+
+	bool workspace_buttons;
+	bool wrap_scroll;
+	char *separator_symbol;
+	bool strip_workspace_numbers;
+	bool binding_mode_indicator;
+	bool verbose;
+	pid_t pid;
+	struct {
+		char *background;
+		char *statusline;
+		char *separator;
+		char *focused_background;
+		char *focused_statusline;
+		char *focused_separator;
+		char *focused_workspace_border;
+		char *focused_workspace_bg;
+		char *focused_workspace_text;
+		char *active_workspace_border;
+		char *active_workspace_bg;
+		char *active_workspace_text;
+		char *inactive_workspace_border;
+		char *inactive_workspace_bg;
+		char *inactive_workspace_text;
+		char *urgent_workspace_border;
+		char *urgent_workspace_bg;
+		char *urgent_workspace_text;
+		char *binding_mode_border;
+		char *binding_mode_bg;
+		char *binding_mode_text;
+	} colors;
+};
+
+struct border_colors {
+	uint32_t border;
+	uint32_t background;
+	uint32_t text;
+	uint32_t indicator;
+	uint32_t child_border;
+};
+
+enum edge_border_types {
+	E_NONE,         /**< Don't hide edge borders */
+	E_VERTICAL,     /**< hide vertical edge borders */
+	E_HORIZONTAL,   /**< hide horizontal edge borders */
+	E_BOTH,		/**< hide vertical and horizontal edge borders */
+	E_SMART		/**< hide both if precisely one window is present in workspace */
+};
+
+enum command_context {
+	CONTEXT_CONFIG = 1,
+	CONTEXT_BINDING = 2,
+	CONTEXT_IPC = 4,
+	CONTEXT_CRITERIA = 8,
+	CONTEXT_ALL = 0xFFFFFFFF,
+};
+
+struct command_policy {
+	char *command;
+	uint32_t context;
+};
+
+enum secure_feature {
+	FEATURE_LOCK = 1,
+	FEATURE_PANEL = 2,
+	FEATURE_BACKGROUND = 4,
+	FEATURE_SCREENSHOT = 8,
+	FEATURE_FULLSCREEN = 16,
+	FEATURE_KEYBOARD = 32,
+	FEATURE_MOUSE = 64,
+};
+
+struct feature_policy {
+	char *program;
+	uint32_t features;
+};
+
+enum ipc_feature {
+	IPC_FEATURE_COMMAND = 1,
+	IPC_FEATURE_GET_WORKSPACES = 2,
+	IPC_FEATURE_GET_OUTPUTS = 4,
+	IPC_FEATURE_GET_TREE = 8,
+	IPC_FEATURE_GET_MARKS = 16,
+	IPC_FEATURE_GET_BAR_CONFIG = 32,
+	IPC_FEATURE_GET_VERSION = 64,
+	IPC_FEATURE_GET_INPUTS = 128,
+	IPC_FEATURE_EVENT_WORKSPACE = 256,
+	IPC_FEATURE_EVENT_OUTPUT = 512,
+	IPC_FEATURE_EVENT_MODE = 1024,
+	IPC_FEATURE_EVENT_WINDOW = 2048,
+	IPC_FEATURE_EVENT_BINDING = 4096,
+	IPC_FEATURE_EVENT_INPUT = 8192,
+	IPC_FEATURE_GET_CLIPBOARD = 16384,
+
+	IPC_FEATURE_ALL_COMMANDS = 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 16384,
+	IPC_FEATURE_ALL_EVENTS = 256 | 512 | 1024 | 2048 | 4096 | 8192,
+
+	IPC_FEATURE_ALL = IPC_FEATURE_ALL_COMMANDS | IPC_FEATURE_ALL_EVENTS,
+};
+
+struct ipc_policy {
+	char *program;
+	uint32_t features;
+};
+
+/**
+ * The configuration struct. The result of loading a config file.
+ */
+struct sway_config {
+	list_t *symbols;
+	list_t *modes;
+	list_t *bars;
+	list_t *cmd_queue;
+	list_t *workspace_outputs;
+	list_t *pid_workspaces;
+	list_t *output_configs;
+	list_t *input_configs;
+	list_t *criteria;
+	list_t *no_focus;
+	list_t *active_bar_modifiers;
+	struct sway_mode *current_mode;
+	struct bar_config *current_bar;
+	uint32_t floating_mod;
+	uint32_t dragging_key;
+	uint32_t resizing_key;
+	char *floating_scroll_up_cmd;
+	char *floating_scroll_down_cmd;
+	char *floating_scroll_left_cmd;
+	char *floating_scroll_right_cmd;
+	enum swayc_layouts default_orientation;
+	enum swayc_layouts default_layout;
+	char *font;
+	int font_height;
+
+	// Flags
+	bool focus_follows_mouse;
+	bool mouse_warping;
+	bool force_focus_wrapping;
+	bool active;
+	bool failed;
+	bool reloading;
+	bool reading;
+	bool auto_back_and_forth;
+	bool seamless_mouse;
+	bool show_marks;
+
+	bool edge_gaps;
+	bool smart_gaps;
+	int gaps_inner;
+	int gaps_outer;
+
+	list_t *config_chain;
+	const char *current_config;
+
+	enum swayc_border_types border;
+	enum swayc_border_types floating_border;
+	int border_thickness;
+	int floating_border_thickness;
+	enum edge_border_types hide_edge_borders;
+
+	// border colors
+	struct {
+		struct border_colors focused;
+		struct border_colors focused_inactive;
+		struct border_colors unfocused;
+		struct border_colors urgent;
+		struct border_colors placeholder;
+		uint32_t background;
+	} border_colors;
+
+	// floating view
+	int32_t floating_maximum_width;
+	int32_t floating_maximum_height;
+	int32_t floating_minimum_width;
+	int32_t floating_minimum_height;
+
+	// Security
+	list_t *command_policies;
+	list_t *feature_policies;
+	list_t *ipc_policies;
+};
+
+void pid_workspace_add(struct pid_workspace *pw);
+void free_pid_workspace(struct pid_workspace *pw);
+
+/**
+ * Loads the main config from the given path. is_active should be true when
+ * reloading the config.
+ */
+bool load_main_config(const char *path, bool is_active);
+
+/**
+ * Loads an included config. Can only be used after load_main_config.
+ */
+bool load_include_configs(const char *path, struct sway_config *config);
+
+/**
+ * Reads the config from the given FILE.
+ */
+bool read_config(FILE *file, struct sway_config *config);
+
+/**
+ * Free config struct
+ */
+void free_config(struct sway_config *config);
+/**
+ * Does variable replacement for a string based on the config's currently loaded variables.
+ */
+char *do_var_replacement(char *str);
+
+struct cmd_results *check_security_config();
+
+int input_identifier_cmp(const void *item, const void *data);
+void merge_input_config(struct input_config *dst, struct input_config *src);
+void apply_input_config(struct input_config *ic, struct libinput_device *dev);
+void free_input_config(struct input_config *ic);
+
+int output_name_cmp(const void *item, const void *data);
+void merge_output_config(struct output_config *dst, struct output_config *src);
+/** Sets up a WLC output handle based on a given output_config.
+ */
+void apply_output_config(struct output_config *oc, swayc_t *output);
+void free_output_config(struct output_config *oc);
+
+/**
+ * Updates the list of active bar modifiers
+ */
+void update_active_bar_modifiers(void);
+
+int workspace_output_cmp_workspace(const void *a, const void *b);
+
+int sway_binding_cmp(const void *a, const void *b);
+int sway_binding_cmp_qsort(const void *a, const void *b);
+int sway_binding_cmp_keys(const void *a, const void *b);
+void free_sway_binding(struct sway_binding *sb);
+struct sway_binding *sway_binding_dup(struct sway_binding *sb);
+
+int sway_mouse_binding_cmp(const void *a, const void *b);
+int sway_mouse_binding_cmp_qsort(const void *a, const void *b);
+int sway_mouse_binding_cmp_buttons(const void *a, const void *b);
+void free_sway_mouse_binding(struct sway_mouse_binding *smb);
+
+void load_swaybars();
+void terminate_swaybg(pid_t pid);
+
+/**
+ * Allocate and initialize default bar configuration.
+ */
+struct bar_config *default_bar_config(void);
+
+/**
+ * Global config singleton.
+ */
+extern struct sway_config *config;
+
+/**
+ * Config file currently being read.
+ */
+extern const char *current_config_path;
+
+#endif
diff --git a/meson.build b/meson.build
index 695e237e..8e7b98ed 100644
--- a/meson.build
+++ b/meson.build
@@ -65,6 +65,8 @@ if a2x.found()
 	endforeach
 endif
 
+add_project_arguments('-DSYSCONFDIR="/@0@"'.format(sysconfdir), language : 'c')
+
 version = get_option('sway_version')
 if version != ''
 	version = '"@0@"'.format(version)
diff --git a/sway/commands.c b/sway/commands.c
index 5fdcdbb6..17638129 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -7,6 +7,8 @@
 #include <stdio.h>
 #include <json-c/json.h>
 #include "sway/commands.h"
+#include "sway/config.h"
+#include "sway/security.h"
 #include "stringop.h"
 #include "log.h"
 
@@ -200,6 +202,136 @@ cleanup:
 	return results;
 }
 
+// this is like handle_command above, except:
+// 1) it ignores empty commands (empty lines)
+// 2) it does variable substitution
+// 3) it doesn't split commands (because the multiple commands are supposed to
+//	  be chained together)
+// 4) handle_command handles all state internally while config_command has some
+//	  state handled outside (notably the block mode, in read_config)
+struct cmd_results *config_command(char *exec, enum cmd_status block) {
+	struct cmd_results *results = NULL;
+	int argc;
+	char **argv = split_args(exec, &argc);
+	if (!argc) {
+		results = cmd_results_new(CMD_SUCCESS, NULL, NULL);
+		goto cleanup;
+	}
+
+	sway_log(L_INFO, "handling config command '%s'", exec);
+	// Endblock
+	if (**argv == '}') {
+		results = cmd_results_new(CMD_BLOCK_END, NULL, NULL);
+		goto cleanup;
+	}
+	struct cmd_handler *handler = find_handler(argv[0], block);
+	if (!handler) {
+		char *input = argv[0] ? argv[0] : "(empty)";
+		results = cmd_results_new(CMD_INVALID, input, "Unknown/invalid command");
+		goto cleanup;
+	}
+	int i;
+	// Var replacement, for all but first argument of set
+	// TODO commands
+	for (i = /*handler->handle == cmd_set ? 2 :*/ 1; i < argc; ++i) {
+		argv[i] = do_var_replacement(argv[i]);
+		unescape_string(argv[i]);
+	}
+	/* Strip quotes for first argument.
+	 * TODO This part needs to be handled much better */
+	if (argc>1 && (*argv[1] == '\"' || *argv[1] == '\'')) {
+		strip_quotes(argv[1]);
+	}
+	if (handler->handle) {
+		results = handler->handle(argc-1, argv+1);
+	} else {
+		results = cmd_results_new(CMD_INVALID, argv[0], "This command is shimmed, but unimplemented");
+	}
+
+cleanup:
+	free_argv(argc, argv);
+	return results;
+}
+
+struct cmd_results *config_commands_command(char *exec) {
+	struct cmd_results *results = NULL;
+	int argc;
+	char **argv = split_args(exec, &argc);
+	if (!argc) {
+		results = cmd_results_new(CMD_SUCCESS, NULL, NULL);
+		goto cleanup;
+	}
+
+	// Find handler for the command this is setting a policy for
+	char *cmd = argv[0];
+
+	if (strcmp(cmd, "}") == 0) {
+		results = cmd_results_new(CMD_BLOCK_END, NULL, NULL);
+		goto cleanup;
+	}
+
+	struct cmd_handler *handler = find_handler(cmd, CMD_BLOCK_END);
+	if (!handler && strcmp(cmd, "*") != 0) {
+		char *input = cmd ? cmd : "(empty)";
+		results = cmd_results_new(CMD_INVALID, input, "Unknown/invalid command");
+		goto cleanup;
+	}
+
+	enum command_context context = 0;
+
+	struct {
+		char *name;
+		enum command_context context;
+	} context_names[] = {
+		{ "config", CONTEXT_CONFIG },
+		{ "binding", CONTEXT_BINDING },
+		{ "ipc", CONTEXT_IPC },
+		{ "criteria", CONTEXT_CRITERIA },
+		{ "all", CONTEXT_ALL },
+	};
+
+	for (int i = 1; i < argc; ++i) {
+		size_t j;
+		for (j = 0; j < sizeof(context_names) / sizeof(context_names[0]); ++j) {
+			if (strcmp(context_names[j].name, argv[i]) == 0) {
+				break;
+			}
+		}
+		if (j == sizeof(context_names) / sizeof(context_names[0])) {
+			results = cmd_results_new(CMD_INVALID, cmd,
+					"Invalid command context %s", argv[i]);
+			goto cleanup;
+		}
+		context |= context_names[j].context;
+	}
+
+	struct command_policy *policy = NULL;
+	for (int i = 0; i < config->command_policies->length; ++i) {
+		struct command_policy *p = config->command_policies->items[i];
+		if (strcmp(p->command, cmd) == 0) {
+			policy = p;
+			break;
+		}
+	}
+	if (!policy) {
+		policy = alloc_command_policy(cmd);
+		sway_assert(policy, "Unable to allocate security policy");
+		if (policy) {
+			list_add(config->command_policies, policy);
+		}
+	}
+	policy->context = context;
+
+	sway_log(L_INFO, "Set command policy for %s to %d",
+			policy->command, policy->context);
+
+	results = cmd_results_new(CMD_SUCCESS, NULL, NULL);
+
+cleanup:
+	free_argv(argc, argv);
+	return results;
+}
+
 struct cmd_results *cmd_results_new(enum cmd_status status,
 		const char *input, const char *format, ...) {
 	struct cmd_results *results = malloc(sizeof(struct cmd_results));
diff --git a/sway/commands/exec.c b/sway/commands/exec.c
index dd71500a..fbbc4941 100644
--- a/sway/commands/exec.c
+++ b/sway/commands/exec.c
@@ -1,16 +1,16 @@
 #include <string.h>
 #include "sway/commands.h"
+#include "sway/config.h"
 #include "log.h"
 #include "stringop.h"
 
 struct cmd_results *cmd_exec(int argc, char **argv) {
-	// TODO: config
-	/*if (!config->active) return cmd_results_new(CMD_DEFER, "exec", NULL);
+	if (!config->active) return cmd_results_new(CMD_DEFER, "exec", NULL);
 	if (config->reloading) {
 		char *args = join_args(argv, argc);
 		sway_log(L_DEBUG, "Ignoring 'exec %s' due to reload", args);
 		free(args);
 		return cmd_results_new(CMD_SUCCESS, NULL, NULL);
-	}*/
+	}
 	return cmd_exec_always(argc, argv);
 }
diff --git a/sway/commands/exec_always.c b/sway/commands/exec_always.c
index 0a252e7b..9527a487 100644
--- a/sway/commands/exec_always.c
+++ b/sway/commands/exec_always.c
@@ -5,15 +5,15 @@
 #include <sys/wait.h>
 #include <unistd.h>
 #include "sway/commands.h"
-#include "sway/workspace.h"
+#include "sway/config.h"
 #include "sway/container.h"
+#include "sway/workspace.h"
 #include "log.h"
 #include "stringop.h"
 
 struct cmd_results *cmd_exec_always(int argc, char **argv) {
 	struct cmd_results *error = NULL;
-	// TODO: config
-	//if (!config->active) return cmd_results_new(CMD_DEFER, NULL, NULL);
+	if (!config->active) return cmd_results_new(CMD_DEFER, NULL, NULL);
 	if ((error = checkarg(argc, "exec_always", EXPECTED_MORE_THAN, 0))) {
 		return error;
 	}
diff --git a/sway/commands/exit.c b/sway/commands/exit.c
index d294e344..4bb6a97b 100644
--- a/sway/commands/exit.c
+++ b/sway/commands/exit.c
@@ -1,19 +1,17 @@
 #include <stddef.h>
 #include "sway/commands.h"
+#include "sway/config.h"
 
 void sway_terminate(int exit_code);
 
 struct cmd_results *cmd_exit(int argc, char **argv) {
 	struct cmd_results *error = NULL;
-	/* TODO
 	if (config->reading) {
 		return cmd_results_new(CMD_FAILURE, "exit", "Can't be used in config file.");
 	}
-	*/
 	if ((error = checkarg(argc, "exit", EXPECTED_EQUAL_TO, 0))) {
 		return error;
 	}
 	sway_terminate(0);
 	return cmd_results_new(CMD_SUCCESS, NULL, NULL);
 }
-
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 @@
+#define _POSIX_C_SOURCE 200809L
+#define _XOPEN_SOURCE 700
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <wordexp.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <libinput.h>
+#include <limits.h>
+#include <float.h>
+#include <dirent.h>
+#include <strings.h>
+#ifdef __linux__
+#include <linux/input-event-codes.h>
+#elif __FreeBSD__
+#include <dev/evdev/input-event-codes.h>
+#endif
+#include <wlr/types/wlr_output.h>
+#include "sway/commands.h"
+#include "sway/config.h"
+#include "sway/layout.h"
+#include "readline.h"
+#include "stringop.h"
+#include "list.h"
+#include "log.h"
+
+struct sway_config *config = NULL;
+
+void free_config(struct sway_config *config) {
+	// TODO
+}
+
+static void config_defaults(struct sway_config *config) {
+	if (!(config->symbols = create_list())) goto cleanup;
+	if (!(config->modes = create_list())) goto cleanup;
+	if (!(config->bars = create_list())) goto cleanup;
+	if (!(config->workspace_outputs = create_list())) goto cleanup;
+	if (!(config->pid_workspaces = create_list())) goto cleanup;
+	if (!(config->criteria = create_list())) goto cleanup;
+	if (!(config->no_focus = create_list())) goto cleanup;
+	if (!(config->input_configs = create_list())) goto cleanup;
+	if (!(config->output_configs = create_list())) goto cleanup;
+
+	if (!(config->cmd_queue = create_list())) goto cleanup;
+
+	if (!(config->current_mode = malloc(sizeof(struct sway_mode)))) goto cleanup;
+	if (!(config->current_mode->name = malloc(sizeof("default")))) goto cleanup;
+	strcpy(config->current_mode->name, "default");
+	if (!(config->current_mode->bindings = create_list())) goto cleanup;
+	list_add(config->modes, config->current_mode);
+
+	config->floating_mod = 0;
+	config->dragging_key = BTN_LEFT;
+	config->resizing_key = BTN_RIGHT;
+	if (!(config->floating_scroll_up_cmd = strdup(""))) goto cleanup;
+	if (!(config->floating_scroll_down_cmd = strdup(""))) goto cleanup;
+	if (!(config->floating_scroll_left_cmd = strdup(""))) goto cleanup;
+	if (!(config->floating_scroll_right_cmd = strdup(""))) goto cleanup;
+	config->default_layout = L_NONE;
+	config->default_orientation = L_NONE;
+	if (!(config->font = strdup("monospace 10"))) goto cleanup;
+	// TODO: border
+	//config->font_height = get_font_text_height(config->font);
+
+	// floating view
+	config->floating_maximum_width = 0;
+	config->floating_maximum_height = 0;
+	config->floating_minimum_width = 75;
+	config->floating_minimum_height = 50;
+
+	// Flags
+	config->focus_follows_mouse = true;
+	config->mouse_warping = true;
+	config->reloading = false;
+	config->active = false;
+	config->failed = false;
+	config->auto_back_and_forth = false;
+	config->seamless_mouse = true;
+	config->reading = false;
+	config->show_marks = true;
+
+	config->edge_gaps = true;
+	config->smart_gaps = false;
+	config->gaps_inner = 0;
+	config->gaps_outer = 0;
+
+	if (!(config->active_bar_modifiers = create_list())) goto cleanup;
+
+	if (!(config->config_chain = create_list())) goto cleanup;
+	config->current_config = NULL;
+
+	// borders
+	config->border = B_NORMAL;
+	config->floating_border = B_NORMAL;
+	config->border_thickness = 2;
+	config->floating_border_thickness = 2;
+	config->hide_edge_borders = E_NONE;
+
+	// border colors
+	config->border_colors.focused.border = 0x4C7899FF;
+	config->border_colors.focused.background = 0x285577FF;
+	config->border_colors.focused.text = 0xFFFFFFFF;
+	config->border_colors.focused.indicator = 0x2E9EF4FF;
+	config->border_colors.focused.child_border = 0x285577FF;
+
+	config->border_colors.focused_inactive.border = 0x333333FF;
+	config->border_colors.focused_inactive.background = 0x5F676AFF;
+	config->border_colors.focused_inactive.text = 0xFFFFFFFF;
+	config->border_colors.focused_inactive.indicator = 0x484E50FF;
+	config->border_colors.focused_inactive.child_border = 0x5F676AFF;
+
+	config->border_colors.unfocused.border = 0x333333FF;
+	config->border_colors.unfocused.background = 0x222222FF;
+	config->border_colors.unfocused.text = 0x888888FF;
+	config->border_colors.unfocused.indicator = 0x292D2EFF;
+	config->border_colors.unfocused.child_border = 0x222222FF;
+
+	config->border_colors.urgent.border = 0x2F343AFF;
+	config->border_colors.urgent.background = 0x900000FF;
+	config->border_colors.urgent.text = 0xFFFFFFFF;
+	config->border_colors.urgent.indicator = 0x900000FF;
+	config->border_colors.urgent.child_border = 0x900000FF;
+
+	config->border_colors.placeholder.border = 0x000000FF;
+	config->border_colors.placeholder.background = 0x0C0C0CFF;
+	config->border_colors.placeholder.text = 0xFFFFFFFF;
+	config->border_colors.placeholder.indicator = 0x000000FF;
+	config->border_colors.placeholder.child_border = 0x0C0C0CFF;
+
+	config->border_colors.background = 0xFFFFFFFF;
+
+	// Security
+	if (!(config->command_policies = create_list())) goto cleanup;
+	if (!(config->feature_policies = create_list())) goto cleanup;
+	if (!(config->ipc_policies = create_list())) goto cleanup;
+
+	return;
+cleanup:
+	sway_abort("Unable to allocate config structures");
+}
+
+static bool file_exists(const char *path) {
+	return path && access(path, R_OK) != -1;
+}
+
+static char *get_config_path(void) {
+	static const char *config_paths[] = {
+		"$HOME/.sway/config",
+		"$XDG_CONFIG_HOME/sway/config",
+		"$HOME/.i3/config",
+		"$XDG_CONFIG_HOME/i3/config",
+		SYSCONFDIR "/sway/config",
+		SYSCONFDIR "/i3/config",
+	};
+
+	if (!getenv("XDG_CONFIG_HOME")) {
+		char *home = getenv("HOME");
+		char *config_home = malloc(strlen(home) + strlen("/.config") + 1);
+		if (!config_home) {
+			sway_log(L_ERROR, "Unable to allocate $HOME/.config");
+		} else {
+			strcpy(config_home, home);
+			strcat(config_home, "/.config");
+			setenv("XDG_CONFIG_HOME", config_home, 1);
+			sway_log(L_DEBUG, "Set XDG_CONFIG_HOME to %s", config_home);
+			free(config_home);
+		}
+	}
+
+	wordexp_t p;
+	char *path;
+
+	int i;
+	for (i = 0; i < (int)(sizeof(config_paths) / sizeof(char *)); ++i) {
+		if (wordexp(config_paths[i], &p, 0) == 0) {
+			path = strdup(p.we_wordv[0]);
+			wordfree(&p);
+			if (file_exists(path)) {
+				return path;
+			}
+		}
+	}
+
+	return NULL; // Not reached
+}
+
+const char *current_config_path;
+
+static bool load_config(const char *path, struct sway_config *config) {
+	sway_log(L_INFO, "Loading config from %s", path);
+	current_config_path = path;
+
+	struct stat sb;
+	if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
+		return false;
+	}
+
+	if (path == NULL) {
+		sway_log(L_ERROR, "Unable to find a config file!");
+		return false;
+	}
+
+	FILE *f = fopen(path, "r");
+	if (!f) {
+		sway_log(L_ERROR, "Unable to open %s for reading", path);
+		return false;
+	}
+
+	bool config_load_success = read_config(f, config);
+	fclose(f);
+
+	if (!config_load_success) {
+		sway_log(L_ERROR, "Error(s) loading config!");
+	}
+
+	current_config_path = NULL;
+	return true;
+}
+
+static int qstrcmp(const void* a, const void* b) {
+	return strcmp(*((char**) a), *((char**) b));
+}
+
+bool load_main_config(const char *file, bool is_active) {
+	char *path;
+	if (file != NULL) {
+		path = strdup(file);
+	} else {
+		path = get_config_path();
+	}
+
+	struct sway_config *old_config = config;
+	config = calloc(1, sizeof(struct sway_config));
+	if (!config) {
+		sway_abort("Unable to allocate config");
+	}
+
+	config_defaults(config);
+	if (is_active) {
+		sway_log(L_DEBUG, "Performing configuration file reload");
+		config->reloading = true;
+		config->active = true;
+	}
+
+	config->current_config = path;
+	list_add(config->config_chain, path);
+
+	config->reading = true;
+
+	// Read security configs
+	bool success = true;
+	DIR *dir = opendir(SYSCONFDIR "/sway/security.d");
+	if (!dir) {
+		sway_log(L_ERROR, "%s does not exist, sway will have no security configuration"
+				" and will probably be broken", SYSCONFDIR "/sway/security.d");
+	} else {
+		list_t *secconfigs = create_list();
+		char *base = SYSCONFDIR "/sway/security.d/";
+		struct dirent *ent = readdir(dir);
+		struct stat s;
+		while (ent != NULL) {
+			char *_path = malloc(strlen(ent->d_name) + strlen(base) + 1);
+			strcpy(_path, base);
+			strcat(_path, ent->d_name);
+			lstat(_path, &s);
+			if (S_ISREG(s.st_mode) && ent->d_name[0] != '.') {
+				list_add(secconfigs, _path);
+			}
+			else {
+				free(_path);
+			}
+			ent = readdir(dir);
+		}
+		closedir(dir);
+
+		list_qsort(secconfigs, qstrcmp);
+		for (int i = 0; i < secconfigs->length; ++i) {
+			char *_path = secconfigs->items[i];
+			if (stat(_path, &s) || s.st_uid != 0 || s.st_gid != 0 || (((s.st_mode & 0777) != 0644) && (s.st_mode & 0777) != 0444)) {
+				sway_log(L_ERROR, "Refusing to load %s - it must be owned by root and mode 644 or 444", _path);
+				success = false;
+			} else {
+				success = success && load_config(_path, config);
+			}
+		}
+
+		free_flat_list(secconfigs);
+	}
+
+	success = success && load_config(path, config);
+
+	if (is_active) {
+		config->reloading = false;
+	}
+
+	if (old_config) {
+		free_config(old_config);
+	}
+	config->reading = false;
+
+	if (success) {
+		// TODO: bar
+		//update_active_bar_modifiers();
+	}
+
+	return success;
+}
+
+bool read_config(FILE *file, struct sway_config *config) {
+	bool success = true;
+	enum cmd_status block = CMD_BLOCK_END;
+
+	int line_number = 0;
+	char *line;
+	while (!feof(file)) {
+		line = read_line(file);
+		if (!line) {
+			continue;
+		}
+		line_number++;
+		line = strip_whitespace(line);
+		if (line[0] == '#') {
+			free(line);
+			continue;
+		}
+		struct cmd_results *res;
+		if (block == CMD_BLOCK_COMMANDS) {
+			// Special case
+			res = config_commands_command(line);
+		} else {
+			res = config_command(line, block);
+		}
+		switch(res->status) {
+		case CMD_FAILURE:
+		case CMD_INVALID:
+			sway_log(L_ERROR, "Error on line %i '%s': %s (%s)", line_number, line,
+				res->error, config->current_config);
+			success = false;
+			break;
+
+		case CMD_DEFER:
+			sway_log(L_DEBUG, "Defferring command `%s'", line);
+			list_add(config->cmd_queue, strdup(line));
+			break;
+
+		case CMD_BLOCK_MODE:
+			if (block == CMD_BLOCK_END) {
+				block = CMD_BLOCK_MODE;
+			} else {
+				sway_log(L_ERROR, "Invalid block '%s'", line);
+			}
+			break;
+
+		case CMD_BLOCK_INPUT:
+			if (block == CMD_BLOCK_END) {
+				block = CMD_BLOCK_INPUT;
+			} else {
+				sway_log(L_ERROR, "Invalid block '%s'", line);
+			}
+			break;
+
+		case CMD_BLOCK_BAR:
+			if (block == CMD_BLOCK_END) {
+				block = CMD_BLOCK_BAR;
+			} else {
+				sway_log(L_ERROR, "Invalid block '%s'", line);
+			}
+			break;
+
+		case CMD_BLOCK_BAR_COLORS:
+			if (block == CMD_BLOCK_BAR) {
+				block = CMD_BLOCK_BAR_COLORS;
+			} else {
+				sway_log(L_ERROR, "Invalid block '%s'", line);
+			}
+			break;
+
+		case CMD_BLOCK_COMMANDS:
+			if (block == CMD_BLOCK_END) {
+				block = CMD_BLOCK_COMMANDS;
+			} else {
+				sway_log(L_ERROR, "Invalid block '%s'", line);
+			}
+			break;
+
+		case CMD_BLOCK_IPC:
+			if (block == CMD_BLOCK_END) {
+				block = CMD_BLOCK_IPC;
+			} else {
+				sway_log(L_ERROR, "Invalid block '%s'", line);
+			}
+			break;
+
+		case CMD_BLOCK_IPC_EVENTS:
+			if (block == CMD_BLOCK_IPC) {
+				block = CMD_BLOCK_IPC_EVENTS;
+			} else {
+				sway_log(L_ERROR, "Invalid block '%s'", line);
+			}
+			break;
+
+		case CMD_BLOCK_END:
+			switch(block) {
+			case CMD_BLOCK_MODE:
+				sway_log(L_DEBUG, "End of mode block");
+				config->current_mode = config->modes->items[0];
+				block = CMD_BLOCK_END;
+				break;
+
+			case CMD_BLOCK_INPUT:
+				sway_log(L_DEBUG, "End of input block");
+				// TODO: input
+				//current_input_config = NULL;
+				block = CMD_BLOCK_END;
+				break;
+
+			case CMD_BLOCK_BAR:
+				sway_log(L_DEBUG, "End of bar block");
+				config->current_bar = NULL;
+				block = CMD_BLOCK_END;
+				break;
+
+			case CMD_BLOCK_BAR_COLORS:
+				sway_log(L_DEBUG, "End of bar colors block");
+				block = CMD_BLOCK_BAR;
+				break;
+
+			case CMD_BLOCK_COMMANDS:
+				sway_log(L_DEBUG, "End of commands block");
+				block = CMD_BLOCK_END;
+				break;
+
+			case CMD_BLOCK_IPC:
+				sway_log(L_DEBUG, "End of IPC block");
+				block = CMD_BLOCK_END;
+				break;
+
+			case CMD_BLOCK_IPC_EVENTS:
+				sway_log(L_DEBUG, "End of IPC events block");
+				block = CMD_BLOCK_IPC;
+				break;
+
+			case CMD_BLOCK_END:
+				sway_log(L_ERROR, "Unmatched }");
+				break;
+
+			default:;
+			}
+		default:;
+		}
+		free(line);
+		free_cmd_results(res);
+	}
+
+	return success;
+}
+
+char *do_var_replacement(char *str) {
+	int i;
+	char *find = str;
+	while ((find = strchr(find, '$'))) {
+		// Skip if escaped.
+		if (find > str && find[-1] == '\\') {
+			if (find == str + 1 || !(find > str + 1 && find[-2] == '\\')) {
+				++find;
+				continue;
+			}
+		}
+		// Find matching variable
+		for (i = 0; i < config->symbols->length; ++i) {
+			struct sway_variable *var = config->symbols->items[i];
+			int vnlen = strlen(var->name);
+			if (strncmp(find, var->name, vnlen) == 0) {
+				int vvlen = strlen(var->value);
+				char *newstr = malloc(strlen(str) - vnlen + vvlen + 1);
+				if (!newstr) {
+					sway_log(L_ERROR,
+							"Unable to allocate replacement during variable expansion");
+					break;
+				}
+				char *newptr = newstr;
+				int offset = find - str;
+				strncpy(newptr, str, offset);
+				newptr += offset;
+				strncpy(newptr, var->value, vvlen);
+				newptr += vvlen;
+				strcpy(newptr, find + vnlen);
+				free(str);
+				str = newstr;
+				find = str + offset + vvlen;
+				break;
+			}
+		}
+		if (i == config->symbols->length) {
+			++find;
+		}
+	}
+	return str;
+}
diff --git a/sway/main.c b/sway/main.c
index bc107309..bc843591 100644
--- a/sway/main.c
+++ b/sway/main.c
@@ -15,6 +15,7 @@
 #include <sys/capability.h>
 #include <sys/prctl.h>
 #endif
+#include "sway/config.h"
 #include "sway/server.h"
 #include "sway/layout.h"
 #include "sway/ipc-server.h"
@@ -388,14 +389,14 @@ int main(int argc, char **argv) {
 	ipc_init(&server);
 	log_env();
 
-	//if (validate) {
-	//	bool valid = load_main_config(config_path, false);
-	//	return valid ? 0 : 1;
-	//}
+	if (validate) {
+		bool valid = load_main_config(config_path, false);
+		return valid ? 0 : 1;
+	}
 
-	//if (!load_main_config(config_path, false)) {
-	//	sway_terminate(EXIT_FAILURE);
-	//}
+	if (!load_main_config(config_path, false)) {
+		sway_terminate(EXIT_FAILURE);
+	}
 
 	if (config_path) {
 		free(config_path);
@@ -411,9 +412,9 @@ int main(int argc, char **argv) {
 
 	ipc_terminate();
 
-	//if (config) {
-	//	free_config(config);
-	//}
+	if (config) {
+		free_config(config);
+	}
 
 	return exit_value;
 }
diff --git a/sway/meson.build b/sway/meson.build
index b224f15f..84f48137 100644
--- a/sway/meson.build
+++ b/sway/meson.build
@@ -5,12 +5,14 @@ sway_sources = files(
 	'commands/exit.c',
 	'commands/exec.c',
 	'commands/exec_always.c',
+	'config.c',
 	'ipc-json.c',
 	'ipc-server.c',
 	'desktop/output.c',
 	'desktop/wl_shell.c',
 	'desktop/xdg_shell_v6.c',
 	'desktop/xwayland.c',
+	'security.c',
 	'tree/container.c',
 	'tree/layout.c',
 	'tree/workspace.c',
diff --git a/sway/security.c b/sway/security.c
new file mode 100644
index 00000000..cc0d3f66
--- /dev/null
+++ b/sway/security.c
@@ -0,0 +1,18 @@
+#define _XOPEN_SOURCE 700
+#include <stdlib.h>
+#include <string.h>
+#include "sway/security.h"
+
+struct command_policy *alloc_command_policy(const char *command) {
+	struct command_policy *policy = malloc(sizeof(struct command_policy));
+	if (!policy) {
+		return NULL;
+	}
+	policy->command = strdup(command);
+	if (!policy->command) {
+		free(policy);
+		return NULL;
+	}
+	policy->context = 0;
+	return policy;
+}
-- 
cgit v1.2.3