From b28602aa7425cf435150e6008624429737e037d3 Mon Sep 17 00:00:00 2001
From: Drew DeVault <sir@cmpwn.com>
Date: Tue, 30 Jan 2018 23:09:21 -0500
Subject: Implement workspaces

---
 include/sway/container.h           |  19 +++-
 include/sway/input/input-manager.h |   4 +
 include/sway/workspace.h           |  14 +++
 sway/commands.c                    |   1 +
 sway/commands/kill.c               |   7 +-
 sway/commands/workspace.c          | 101 ++++++++++++++++++
 sway/config.c                      |  15 ++-
 sway/desktop/output.c              |   4 +-
 sway/desktop/xdg_shell_v6.c        |   9 +-
 sway/input/input-manager.c         |   8 ++
 sway/input/seat.c                  |  15 ++-
 sway/meson.build                   |   1 +
 sway/tree/container.c              |  41 ++++++-
 sway/tree/workspace.c              | 211 +++++++++++++++++++++++++++++++++++++
 14 files changed, 420 insertions(+), 30 deletions(-)
 create mode 100644 sway/commands/workspace.c

diff --git a/include/sway/container.h b/include/sway/container.h
index a99e2694..0c66932d 100644
--- a/include/sway/container.h
+++ b/include/sway/container.h
@@ -11,6 +11,7 @@ typedef struct sway_container swayc_t;
 extern swayc_t root_container;
 
 struct sway_view;
+struct sway_seat;
 
 /**
  * Different kinds of containers.
@@ -140,11 +141,25 @@ swayc_t *new_view(swayc_t *sibling, struct sway_view *sway_view);
 swayc_t *destroy_output(swayc_t *output);
 swayc_t *destroy_view(swayc_t *view);
 
+swayc_t *next_view_sibling(struct sway_seat *seat);
+
+/**
+ * Finds a container based on test criteria. Returns the first container that
+ * passes the test.
+ */
+swayc_t *swayc_by_test(swayc_t *container,
+		bool (*test)(swayc_t *view, void *data), void *data);
+/**
+ * Finds a parent container with the given swayc_type.
+ */
 swayc_t *swayc_parent_by_type(swayc_t *container, enum swayc_types type);
+/**
+ * Maps a container's children over a function.
+ */
+void container_map(swayc_t *container,
+		void (*f)(swayc_t *view, void *data), void *data);
 
 swayc_t *swayc_at(swayc_t *parent, double lx, double ly,
 		struct wlr_surface **surface, double *sx, double *sy);
 
-void container_map(swayc_t *container, void (*f)(swayc_t *view, void *data), void *data);
-
 #endif
diff --git a/include/sway/input/input-manager.h b/include/sway/input/input-manager.h
index 63806b8e..66ace262 100644
--- a/include/sway/input/input-manager.h
+++ b/include/sway/input/input-manager.h
@@ -48,4 +48,8 @@ struct sway_seat *sway_input_manager_get_default_seat(
 
 struct sway_seat *input_manager_get_seat(struct sway_input_manager *input,
 		const char *seat_name);
+
+/** Gets the last seat the user interacted with */
+struct sway_seat *input_manager_current_seat(struct sway_input_manager *input);
+
 #endif
diff --git a/include/sway/workspace.h b/include/sway/workspace.h
index 04b2ea4e..30bbdaa8 100644
--- a/include/sway/workspace.h
+++ b/include/sway/workspace.h
@@ -1,6 +1,20 @@
 #ifndef _SWAY_WORKSPACE_H
 #define _SWAY_WORKSPACE_H
 
+struct sway_container;
+
+extern char *prev_workspace_name;
+
 char *workspace_next_name(const char *output_name);
+swayc_t *workspace_create(const char *name);
+bool workspace_switch(swayc_t *workspace);
+
+struct sway_container *workspace_by_number(const char* name);
+swayc_t *workspace_by_name(const char*);
+
+struct sway_container *workspace_output_next(struct sway_container *current);
+struct sway_container *workspace_next(struct sway_container *current);
+struct sway_container *workspace_output_prev(struct sway_container *current);
+struct sway_container *workspace_prev(struct sway_container *current);
 
 #endif
diff --git a/sway/commands.c b/sway/commands.c
index d4262c08..0d4aa104 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -139,6 +139,7 @@ static struct cmd_handler handlers[] = {
 	{ "reload", cmd_reload },
 	{ "seat", cmd_seat },
 	{ "set", cmd_set },
+	{ "workspace", cmd_workspace },
 };
 
 static int handler_compare(const void *_a, const void *_b) {
diff --git a/sway/commands/kill.c b/sway/commands/kill.c
index 3804f0b0..cebf7f3c 100644
--- a/sway/commands/kill.c
+++ b/sway/commands/kill.c
@@ -10,11 +10,10 @@ struct cmd_results *cmd_kill(int argc, char **argv) {
 		return cmd_results_new(CMD_FAILURE, "kill",
 			"Command 'kill' cannot be used in the config file");
 	}
-	if (!sway_assert(config->handler_context.current_container,
-				"cmd_kill called without container context")) {
+	enum swayc_types type = config->handler_context.current_container->type;
+	if (type != C_VIEW || type != C_CONTAINER) {
 		return cmd_results_new(CMD_INVALID, NULL,
-				"cmd_kill called without container context "
-				"(this is a bug in sway)");
+				"Can only kill views and containers with this command");
 	}
 	// TODO close arbitrary containers without a view
 	struct sway_view *view =
diff --git a/sway/commands/workspace.c b/sway/commands/workspace.c
new file mode 100644
index 00000000..12984ed4
--- /dev/null
+++ b/sway/commands/workspace.c
@@ -0,0 +1,101 @@
+#define _XOPEN_SOURCE 500
+#include <string.h>
+#include <strings.h>
+#include "sway/commands.h"
+#include "sway/config.h"
+#include "sway/input/seat.h"
+#include "sway/workspace.h"
+#include "list.h"
+#include "log.h"
+#include "stringop.h"
+
+struct cmd_results *cmd_workspace(int argc, char **argv) {
+	struct cmd_results *error = NULL;
+	if ((error = checkarg(argc, "workspace", EXPECTED_AT_LEAST, 1))) {
+		return error;
+	}
+
+	int output_location = -1;
+
+	swayc_t *current_container = config->handler_context.current_container;
+	swayc_t *old_workspace = NULL, *old_output = NULL;
+	if (current_container) {
+		if (current_container->type == C_WORKSPACE) {
+			old_workspace = current_container;
+		} else {
+			old_workspace = swayc_parent_by_type(current_container, C_WORKSPACE);
+		}
+		old_output = swayc_parent_by_type(current_container, C_OUTPUT);
+	}
+
+	for (int i = 0; i < argc; ++i) {
+		if (strcasecmp(argv[i], "output") == 0) {
+			output_location = i;
+			break;
+		}
+	}
+	if (output_location >= 0) {
+		if ((error = checkarg(argc, "workspace", EXPECTED_EQUAL_TO, output_location + 2))) {
+			return error;
+		}
+		struct workspace_output *wso = calloc(1, sizeof(struct workspace_output));
+		if (!wso) {
+			return cmd_results_new(CMD_FAILURE, "workspace output",
+					"Unable to allocate workspace output");
+		}
+		wso->workspace = join_args(argv, argc - 2);
+		wso->output = strdup(argv[output_location + 1]);
+		int i = -1;
+		if ((i = list_seq_find(config->workspace_outputs, workspace_output_cmp_workspace, wso)) != -1) {
+			struct workspace_output *old = config->workspace_outputs->items[i];
+			free(old); // workspaces can only be assigned to a single output
+			list_del(config->workspace_outputs, i);
+		}
+		wlr_log(L_DEBUG, "Assigning workspace %s to output %s", wso->workspace, wso->output);
+		list_add(config->workspace_outputs, wso);
+	} else {
+		if (config->reading || !config->active) {
+			return cmd_results_new(CMD_DEFER, "workspace", NULL);
+		}
+		swayc_t *ws = NULL;
+		if (strcasecmp(argv[0], "number") == 0) {
+			if (!(ws = workspace_by_number(argv[1]))) {
+				char *name = join_args(argv + 1, argc - 1);
+				ws = workspace_create(name);
+				free(name);
+			}
+		} else if (strcasecmp(argv[0], "next") == 0) {
+			ws = workspace_next(old_workspace);
+		} else if (strcasecmp(argv[0], "prev") == 0) {
+			ws = workspace_prev(old_workspace);
+		} else if (strcasecmp(argv[0], "next_on_output") == 0) {
+			ws = workspace_output_next(old_output);
+		} else if (strcasecmp(argv[0], "prev_on_output") == 0) {
+			ws = workspace_output_prev(old_output);
+		} else if (strcasecmp(argv[0], "back_and_forth") == 0) {
+			// if auto_back_and_forth is enabled, workspace_switch will swap
+			// the workspaces. If we created prev_workspace here, workspace_switch
+			// would put us back on original workspace.
+			if (config->auto_back_and_forth) {
+				ws = old_workspace;
+			} else if (prev_workspace_name
+					&& !(ws = workspace_by_name(prev_workspace_name))) {
+				ws = workspace_create(prev_workspace_name);
+			}
+		} else {
+			char *name = join_args(argv, argc);
+			if (!(ws = workspace_by_name(name))) {
+				ws = workspace_create(name);
+			}
+			free(name);
+		}
+		workspace_switch(ws);
+		current_container = config->handler_context.seat->focus;
+		swayc_t *new_output = swayc_parent_by_type(current_container, C_OUTPUT);
+
+		if (config->mouse_warping && old_output != new_output) {
+			// TODO: Warp mouse
+		}
+	}
+	return cmd_results_new(CMD_SUCCESS, NULL, NULL);
+}
diff --git a/sway/config.c b/sway/config.c
index a67322d1..213e7680 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -318,10 +318,6 @@ static bool load_config(const char *path, struct sway_config *config) {
 	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) {
@@ -349,7 +345,9 @@ bool load_main_config(const char *file, bool is_active) {
 	config->reading = true;
 
 	// Read security configs
+	// TODO: Security
 	bool success = true;
+	/*
 	DIR *dir = opendir(SYSCONFDIR "/sway/security.d");
 	if (!dir) {
 		wlr_log(L_ERROR,
@@ -392,6 +390,7 @@ bool load_main_config(const char *file, bool is_active) {
 
 		free_flat_list(secconfigs);
 	}
+	*/
 
 	success = success && load_config(path, config);
 
@@ -717,3 +716,11 @@ char *do_var_replacement(char *str) {
 	}
 	return str;
 }
+
+// the naming is intentional (albeit long): a workspace_output_cmp function
+// would compare two structs in full, while this method only compares the
+// workspace.
+int workspace_output_cmp_workspace(const void *a, const void *b) {
+	const struct workspace_output *wsa = a, *wsb = b;
+	return lenient_strcmp(wsa->workspace, wsb->workspace);
+}
diff --git a/sway/desktop/output.c b/sway/desktop/output.c
index 0f00222b..a650665f 100644
--- a/sway/desktop/output.c
+++ b/sway/desktop/output.c
@@ -219,8 +219,8 @@ static void output_frame_notify(struct wl_listener *listener, void *data) {
 	wlr_output_make_current(wlr_output);
 	wlr_renderer_begin(server->renderer, wlr_output);
 
-	swayc_descendants_of_type(
-			&root_container, C_VIEW, output_frame_view, soutput);
+	swayc_t *workspace = soutput->swayc->focused;
+	swayc_descendants_of_type(workspace, C_VIEW, output_frame_view, soutput);
 
 	// render unmanaged views on top
 	struct sway_view *view;
diff --git a/sway/desktop/xdg_shell_v6.c b/sway/desktop/xdg_shell_v6.c
index 4b50093f..ca56a9c0 100644
--- a/sway/desktop/xdg_shell_v6.c
+++ b/sway/desktop/xdg_shell_v6.c
@@ -124,8 +124,6 @@ void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data) {
 	sway_surface->view = sway_view;
 	
 	// TODO:
-	// - Wire up listeners
-	// - Handle popups
 	// - Look up pid and open on appropriate workspace
 	// - Set new view to maximized so it behaves nicely
 	// - Criteria
@@ -136,11 +134,8 @@ void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data) {
 	sway_surface->destroy.notify = handle_destroy;
 	wl_signal_add(&xdg_surface->events.destroy, &sway_surface->destroy);
 
-	// TODO: actual focus semantics
-	swayc_t *parent = root_container.children->items[0];
-	parent = parent->children->items[0]; // workspace
-
-	swayc_t *cont = new_view(parent, sway_view);
+	struct sway_seat *seat = input_manager_current_seat(input_manager);
+	swayc_t *cont = new_view(seat->focus, sway_view);
 	sway_view->swayc = cont;
 
 	arrange_windows(cont->parent, -1, -1);
diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c
index 12b3a430..d789c7eb 100644
--- a/sway/input/input-manager.c
+++ b/sway/input/input-manager.c
@@ -23,6 +23,14 @@ struct sway_input_manager *input_manager;
 struct input_config *current_input_config = NULL;
 struct seat_config *current_seat_config = NULL;
 
+struct sway_seat *input_manager_current_seat(struct sway_input_manager *input) {
+	struct sway_seat *seat = config->handler_context.seat;
+	if (!seat) {
+		seat = sway_input_manager_get_default_seat(input_manager);
+	}
+	return seat;
+}
+
 struct sway_seat *input_manager_get_seat(
 		struct sway_input_manager *input, const char *seat_name) {
 	struct sway_seat *seat = NULL;
diff --git a/sway/input/seat.c b/sway/input/seat.c
index 9ea08eec..5e87986d 100644
--- a/sway/input/seat.c
+++ b/sway/input/seat.c
@@ -1,6 +1,7 @@
 #define _XOPEN_SOURCE 700
 #include <wlr/types/wlr_cursor.h>
 #include <wlr/types/wlr_xcursor_manager.h>
+#include "sway/container.h"
 #include "sway/input/seat.h"
 #include "sway/input/cursor.h"
 #include "sway/input/input-manager.h"
@@ -81,7 +82,7 @@ static void seat_configure_keyboard(struct sway_seat *seat,
 	sway_keyboard_configure(seat_device->keyboard);
 	wlr_seat_set_keyboard(seat->wlr_seat,
 		seat_device->input_device->wlr_device);
-	if (seat->focus) {
+	if (seat->focus && seat->focus->type == C_VIEW) {
 		// force notify reenter to pick up the new configuration
 		wlr_seat_keyboard_clear_focus(seat->wlr_seat);
 		wlr_seat_keyboard_notify_enter(seat->wlr_seat,
@@ -205,10 +206,8 @@ void sway_seat_configure_xcursor(struct sway_seat *seat) {
 
 static void handle_focus_destroy(struct wl_listener *listener, void *data) {
 	struct sway_seat *seat = wl_container_of(listener, seat, focus_destroy);
-	//swayc_t *container = data;
-
-	// TODO set new focus based on the state of the tree
-	sway_seat_set_focus(seat, NULL);
+	swayc_t *container = data;
+	sway_seat_set_focus(seat, container->parent);
 }
 
 void sway_seat_set_focus(struct sway_seat *seat, swayc_t *container) {
@@ -218,11 +217,11 @@ void sway_seat_set_focus(struct sway_seat *seat, swayc_t *container) {
 		return;
 	}
 
-	if (last_focus) {
+	if (last_focus && last_focus->type == C_VIEW) {
 		wl_list_remove(&seat->focus_destroy.link);
 	}
 
-	if (container) {
+	if (container && container->type == C_VIEW) {
 		struct sway_view *view = container->sway_view;
 		view_set_activated(view, true);
 		wl_signal_add(&container->events.destroy, &seat->focus_destroy);
@@ -241,7 +240,7 @@ void sway_seat_set_focus(struct sway_seat *seat, swayc_t *container) {
 
 	seat->focus = container;
 
-	if (last_focus &&
+	if (last_focus && last_focus->type == C_VIEW &&
 			!sway_input_manager_has_focus(seat->input, last_focus)) {
 		struct sway_view *view = last_focus->sway_view;
 		view_set_activated(view, false);
diff --git a/sway/meson.build b/sway/meson.build
index 51e9e4db..271d4a99 100644
--- a/sway/meson.build
+++ b/sway/meson.build
@@ -35,6 +35,7 @@ sway_sources = files(
 	'commands/input/xkb_variant.c',
 	'commands/output.c',
 	'commands/reload.c',
+	'commands/workspace.c',
 	'config.c',
 	'config/output.c',
 	'config/seat.c',
diff --git a/sway/tree/container.c b/sway/tree/container.c
index b7b9bc68..48aabd86 100644
--- a/sway/tree/container.c
+++ b/sway/tree/container.c
@@ -3,10 +3,13 @@
 #include <stdlib.h>
 #include <string.h>
 #include <strings.h>
+#include <wayland-server.h>
 #include <wlr/types/wlr_output_layout.h>
 #include <wlr/types/wlr_wl_shell.h>
 #include "sway/config.h"
 #include "sway/container.h"
+#include "sway/input/input-manager.h"
+#include "sway/input/seat.h"
 #include "sway/layout.h"
 #include "sway/output.h"
 #include "sway/server.h"
@@ -14,6 +17,26 @@
 #include "sway/workspace.h"
 #include "log.h"
 
+swayc_t *swayc_by_test(swayc_t *container,
+		bool (*test)(swayc_t *view, void *data), void *data) {
+	if (!container->children) {
+		return NULL;
+	}
+	// TODO: floating windows
+	for (int i = 0; i < container->children->length; ++i) {
+		swayc_t *child = container->children->items[i];
+		if (test(child, data)) {
+			return child;
+		} else {
+			swayc_t *res = swayc_by_test(child, test, data);
+			if (res) {
+				return res;
+			}
+		}
+	}
+	return NULL;
+}
+
 void swayc_descendants_of_type(swayc_t *root, enum swayc_types type,
 		void (*func)(swayc_t *item, void *data), void *data) {
 	for (int i = 0; i < root->children->length; ++i) {
@@ -127,7 +150,19 @@ swayc_t *new_output(struct sway_output *sway_output) {
 	// Create workspace
 	char *ws_name = workspace_next_name(output->name);
 	wlr_log(L_DEBUG, "Creating default workspace %s", ws_name);
-	new_workspace(output, ws_name);
+	swayc_t *ws = new_workspace(output, ws_name);
+	output->focused = ws;
+	// Set each seat's focus if not already set
+	// TODO FOCUS: this is probably stupid, we shouldn't define focus in two
+	// places. We should probably put the active workspace on the sway_output
+	// struct instead of trying to do focus semantics like this
+	struct sway_seat *seat = NULL;
+	wl_list_for_each(seat, &input_manager->seats, link) {
+		if (!seat->focus) {
+			seat->focus = ws;
+		}
+	}
+
 	free(ws_name);
 	return output;
 }
@@ -159,8 +194,8 @@ swayc_t *new_view(swayc_t *sibling, struct sway_view *sway_view) {
 	}
 	const char *title = view_get_title(sway_view);
 	swayc_t *swayc = new_swayc(C_VIEW);
-	wlr_log(L_DEBUG, "Adding new view %p:%s to container %p %d",
-		swayc, title, sibling, sibling ? sibling->type : 0);
+	wlr_log(L_DEBUG, "Adding new view %p:%s to container %p %d %s",
+		swayc, title, sibling, sibling ? sibling->type : 0, sibling->name);
 	// Setup values
 	swayc->sway_view = sway_view;
 	swayc->name = title ? strdup(title) : NULL;
diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c
index c37a873c..23c630b6 100644
--- a/sway/tree/workspace.c
+++ b/sway/tree/workspace.c
@@ -2,8 +2,20 @@
 #include <stdbool.h>
 #include <stdlib.h>
 #include <stdio.h>
+#include <strings.h>
 #include "sway/container.h"
+#include "sway/input/input-manager.h"
+#include "sway/input/seat.h"
+#include "sway/workspace.h"
 #include "log.h"
+#include "util.h"
+
+char *prev_workspace_name = NULL;
+struct workspace_by_number_data {
+	int len;
+	const char *cset;
+	const char *name;
+};
 
 void next_name_map(swayc_t *ws, void *data) {
 	int *count = data;
@@ -24,3 +36,202 @@ char *workspace_next_name(const char *output_name) {
 	snprintf(name, len + 1, "%d", count);
 	return name;
 }
+
+static bool _workspace_by_number(swayc_t *view, void *data) {
+	if (view->type != C_WORKSPACE) {
+		return false;
+	}
+	struct workspace_by_number_data *wbnd = data;
+	int a = strspn(view->name, wbnd->cset);
+	return a == wbnd->len && strncmp(view->name, wbnd->name, a) == 0;
+}
+
+swayc_t *workspace_by_number(const char* name) {
+	struct workspace_by_number_data wbnd = {0, "1234567890", name};
+	wbnd.len = strspn(name, wbnd.cset);
+	if (wbnd.len <= 0) {
+		return NULL;
+	}
+	return swayc_by_test(&root_container, _workspace_by_number, (void *) &wbnd);
+}
+
+static bool _workspace_by_name(swayc_t *view, void *data) {
+	return (view->type == C_WORKSPACE) &&
+		   (strcasecmp(view->name, (char *) data) == 0);
+}
+
+swayc_t *workspace_by_name(const char *name) {
+	struct sway_seat *seat = input_manager_current_seat(input_manager);
+	swayc_t *current_workspace = NULL, *current_output = NULL;
+	if (seat->focus) {
+		current_workspace = swayc_parent_by_type(seat->focus, C_WORKSPACE);
+		current_output = swayc_parent_by_type(seat->focus, C_OUTPUT);
+	}
+	if (strcmp(name, "prev") == 0) {
+		return workspace_prev(current_workspace);
+	} else if (strcmp(name, "prev_on_output") == 0) {
+		return workspace_output_prev(current_output);
+	} else if (strcmp(name, "next") == 0) {
+		return workspace_next(current_workspace);
+	} else if (strcmp(name, "next_on_output") == 0) {
+		return workspace_output_next(current_output);
+	} else if (strcmp(name, "current") == 0) {
+		return current_workspace;
+	} else {
+		return swayc_by_test(&root_container, _workspace_by_name, (void *) name);
+	}
+}
+
+swayc_t *workspace_create(const char *name) {
+	swayc_t *parent;
+	// Search for workspace<->output pair
+	int i, e = config->workspace_outputs->length;
+	for (i = 0; i < e; ++i) {
+		struct workspace_output *wso = config->workspace_outputs->items[i];
+		if (strcasecmp(wso->workspace, name) == 0) {
+			// Find output to use if it exists
+			e = root_container.children->length;
+			for (i = 0; i < e; ++i) {
+				parent = root_container.children->items[i];
+				if (strcmp(parent->name, wso->output) == 0) {
+					return new_workspace(parent, name);
+				}
+			}
+			break;
+		}
+	}
+	// Otherwise create a new one
+	struct sway_seat *seat = input_manager_current_seat(input_manager);
+	parent = seat->focus;
+	parent = swayc_parent_by_type(parent, C_OUTPUT);
+	return new_workspace(parent, name);
+}
+
+/**
+ * Get the previous or next workspace on the specified output. Wraps around at
+ * the end and beginning.  If next is false, the previous workspace is returned,
+ * otherwise the next one is returned.
+ */
+swayc_t *workspace_output_prev_next_impl(swayc_t *output, bool next) {
+	if (!sway_assert(output->type == C_OUTPUT,
+				"Argument must be an output, is %d", output->type)) {
+		return NULL;
+	}
+
+	int i;
+	for (i = 0; i < output->children->length; i++) {
+		if (output->children->items[i] == output->focused) {
+			return output->children->items[
+				wrap(i + (next ? 1 : -1), output->children->length)];
+		}
+	}
+
+	// Doesn't happen, at worst the for loop returns the previously active workspace
+	return NULL;
+}
+
+/**
+ * Get the previous or next workspace. If the first/last workspace on an output
+ * is active, proceed to the previous/next output's previous/next workspace. If
+ * next is false, the previous workspace is returned, otherwise the next one is
+ * returned.
+ */
+swayc_t *workspace_prev_next_impl(swayc_t *workspace, bool next) {
+	if (!sway_assert(workspace->type == C_WORKSPACE,
+				"Argument must be a workspace, is %d", workspace->type)) {
+		return NULL;
+	}
+
+	swayc_t *current_output = workspace->parent;
+	int offset = next ? 1 : -1;
+	int start = next ? 0 : 1;
+	int end;
+	if (next) {
+		end = current_output->children->length - 1;
+	} else {
+		end = current_output->children->length;
+	}
+	int i;
+	for (i = start; i < end; i++) {
+		if (current_output->children->items[i] == workspace) {
+			return current_output->children->items[i + offset];
+		}
+	}
+
+	// Given workspace is the first/last on the output, jump to the previous/next output
+	int num_outputs = root_container.children->length;
+	for (i = 0; i < num_outputs; i++) {
+		if (root_container.children->items[i] == current_output) {
+			swayc_t *next_output = root_container.children->items[
+				wrap(i + offset, num_outputs)];
+			return workspace_output_prev_next_impl(next_output, next);
+		}
+	}
+
+	// Doesn't happen, at worst the for loop returns the previously active workspace on the active output
+	return NULL;
+}
+
+swayc_t *workspace_output_next(swayc_t *current) {
+	return workspace_output_prev_next_impl(current, true);
+}
+
+swayc_t *workspace_next(swayc_t *current) {
+	return workspace_prev_next_impl(current, true);
+}
+
+swayc_t *workspace_output_prev(swayc_t *current) {
+	return workspace_output_prev_next_impl(current, false);
+}
+
+swayc_t *workspace_prev(swayc_t *current) {
+	return workspace_prev_next_impl(current, false);
+}
+
+bool workspace_switch(swayc_t *workspace) {
+	if (!workspace) {
+		return false;
+	}
+	struct sway_seat *seat = input_manager_current_seat(input_manager);
+	if (!seat || !seat->focus) {
+		return false;
+	}
+	swayc_t *active_ws = seat->focus;
+	if (active_ws->type != C_WORKSPACE) {
+		swayc_parent_by_type(seat->focus, C_WORKSPACE);
+	}
+
+	if (config->auto_back_and_forth
+			&& active_ws == workspace
+			&& prev_workspace_name) {
+		swayc_t *new_ws = workspace_by_name(prev_workspace_name);
+		workspace = new_ws ? new_ws : workspace_create(prev_workspace_name);
+	}
+
+	if (!prev_workspace_name || (strcmp(prev_workspace_name, active_ws->name)
+				&& active_ws != workspace)) {
+		free(prev_workspace_name);
+		prev_workspace_name = malloc(strlen(active_ws->name) + 1);
+		if (!prev_workspace_name) {
+			wlr_log(L_ERROR, "Unable to allocate previous workspace name");
+			return false;
+		}
+		strcpy(prev_workspace_name, active_ws->name);
+	}
+
+	// TODO: Deal with sticky containers
+
+	wlr_log(L_DEBUG, "Switching to workspace %p:%s", workspace, workspace->name);
+	// TODO FOCUS: Focus the last view this seat had focused on this workspace
+	if (workspace->children->length) {
+		// TODO FOCUS: This is really fucking stupid
+		sway_seat_set_focus(seat, workspace->children->items[0]);
+	} else {
+		sway_seat_set_focus(seat, workspace);
+	}
+	swayc_t *output = swayc_parent_by_type(workspace, C_OUTPUT);
+	// TODO FOCUS: take a look at this
+	output->focused = workspace;
+	arrange_windows(output, -1, -1);
+	return true;
+}
-- 
cgit v1.2.3