// Copyright 2020 - 2025, project-repo and the NEDM contributors
// SPDX-License-Identifier: MIT

#define _POSIX_C_SOURCE 200809L

#include <signal.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend/multi.h>
#include <wlr/backend/session.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_keyboard_group.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/util/log.h>

#include "input.h"
#include "input_manager.h"
#include "keybinding.h"
#include "message.h"
#include "output.h"
#include "seat.h"
#include "server.h"
#include "util.h"
#include "view.h"
#include "wallpaper.h"
#include "workspace.h"

char *keybinding_action_string[] = {FOREACH_KEYBINDING(GENERATE_STRING)};

int
keybinding_resize(struct keybinding_list *list) {
	if(list->length == list->capacity) {
		list->capacity *= 2;
		struct keybinding **new_list =
		    realloc(list->keybindings, sizeof(void *) * list->capacity);
		if(new_list == NULL) {
			return -1;
		} else {
			list->keybindings = new_list;
			return 0;
		}
	}
	return 0;
}

struct keybinding **
find_keybinding(const struct keybinding_list *list,
                const struct keybinding *keybinding) {
	struct keybinding **it = list->keybindings;
	for(size_t i = 0; i < list->length; ++i, ++it) {
		if(!(keybinding->modifiers ^ (*it)->modifiers) &&
		   keybinding->mode == (*it)->mode && keybinding->key == (*it)->key) {
			return it;
		}
	}
	return NULL;
}

void
keybinding_free(struct keybinding *keybinding, bool recursive) {
	switch(keybinding->action) {
	case KEYBINDING_DEFINEMODE:
	case KEYBINDING_RUN_COMMAND:
		if(keybinding->data.c != NULL) {
			free(keybinding->data.c);
		}
		break;
	case KEYBINDING_DEFINEKEY:
		if(keybinding->data.kb != NULL && recursive) {
			keybinding_free(keybinding->data.kb, true);
		}
		break;
	case KEYBINDING_CONFIGURE_OUTPUT:
		free(keybinding->data.o_cfg->output_name);
		free(keybinding->data.o_cfg);
		break;
	case KEYBINDING_CONFIGURE_INPUT:
		free(keybinding->data.i_cfg->identifier);
		if(keybinding->data.i_cfg->mapped_from_region) {
			free(keybinding->data.i_cfg->mapped_from_region);
		}
		if(keybinding->data.i_cfg->mapped_to_output) {
			free(keybinding->data.i_cfg->mapped_to_output);
		}
		free(keybinding->data.o_cfg);
		break;
	case KEYBINDING_CONFIGURE_MESSAGE:
		if(keybinding->data.m_cfg->font != NULL) {
			free(keybinding->data.m_cfg->font);
		}
		free(keybinding->data.m_cfg);
		break;
	case KEYBINDING_DISPLAY_MESSAGE:
		if(keybinding->data.c != NULL) {
			free(keybinding->data.c);
		}
		break;
	case KEYBINDING_SEND_CUSTOM_EVENT:
		if(keybinding->data.c != NULL) {
			free(keybinding->data.c);
		}
		break;
	case KEYBINDING_SETMODECURSOR:
		if(keybinding->data.cs[0] != NULL) {
			free(keybinding->data.cs[0]);
		}
		if(keybinding->data.cs[1] != NULL) {
			free(keybinding->data.cs[1]);
		}
	default:
		break;
	}
	free(keybinding);
}

int
keybinding_list_push(struct keybinding_list *list,
                     struct keybinding *keybinding) {

	/* Error resizing list */
	if(keybinding_resize(list) != 0) {
		return -1;
	}

	/*Maintain that only a single keybinding for a key, modifier and mode may
	 * exist*/
	struct keybinding **found_keybinding = find_keybinding(list, keybinding);
	if(found_keybinding != NULL) {
		keybinding_free(*found_keybinding, true);
		*found_keybinding = keybinding;
		wlr_log(WLR_DEBUG, "A keybinding was found twice in the config file.");
	} else {
		list->keybindings[list->length] = keybinding;
		++list->length;
	}
	return 0;
}

struct keybinding_list *
keybinding_list_init(void) {
	struct keybinding_list *list = malloc(sizeof(struct keybinding_list));
	list->keybindings = malloc(sizeof(struct keybinding *));
	list->capacity = 1;
	list->length = 0;
	return list;
}

void
keybinding_list_free(struct keybinding_list *list) {
	if(!list) {
		return;
	}
	for(unsigned int i = 0; i < list->length; ++i) {
		keybinding_free(list->keybindings[i], true);
	}
	free(list->keybindings);
	free(list);
}

// Returns whether x is between a and b (a exclusive, b exclusive) where
// a<b
bool
is_between_strict(int a, int b, int x) {
	return a < x && x < b;
}

struct nedm_tile *
tile_from_id(struct nedm_server *server, uint32_t id) {
	struct nedm_tile *tile = NULL;
	struct nedm_output *outp_it;
	wl_list_for_each(outp_it, &server->outputs, link) {
		for(unsigned int i = 0; i < server->nws && tile == NULL; ++i) {
			bool first = true;
			for(struct nedm_tile *tile_it = outp_it->workspaces[i]->focused_tile;
			    first || tile_it != outp_it->workspaces[i]->focused_tile;
			    tile_it = tile_it->next) {
				first = false;
				if(id == tile_it->id) {
					tile = tile_it;
					break;
				}
			}
		}
		if(tile != NULL) {
			break;
		}
	}
	return tile;
}

struct nedm_view *
view_from_id(struct nedm_server *server, uint32_t id) {
	struct nedm_view *view = NULL;
	struct nedm_output *outp_it;
	wl_list_for_each(outp_it, &server->outputs, link) {
		for(unsigned int i = 0; i < server->nws && view == NULL; ++i) {
			struct nedm_view *view_it;
			wl_list_for_each(view_it, &outp_it->workspaces[i]->views, link) {
				if(id == view_it->id) {
					view = view_it;
					break;
				}
			}
		}
		if(view != NULL) {
			break;
		}
	}
	return view;
}

struct nedm_output *
output_from_num(struct nedm_server *server, int num) {
	struct nedm_output *it;
	int count = 1;
	wl_list_for_each(it, &server->outputs, link) {
		if(count == num) {
			return it;
		}
		++count;
	}
	return NULL;
}

struct nedm_tile *
find_right_tile(const struct nedm_tile *tile) {
	struct nedm_tile *it = tile->next;
	int center = tile->tile.y + tile->tile.height / 2;
	while(it != tile) {
		if(it->tile.x == tile->tile.x + tile->tile.width) {
			if(is_between_strict(it->tile.y, it->tile.y + it->tile.height,
			                     center) ||
			   it->tile.y + it->tile.height == center) {
				return it;
			}
		}
		it = it->next;
	}
	return NULL;
}

struct nedm_tile *
find_left_tile(const struct nedm_tile *tile) {
	struct nedm_tile *it = tile->next;
	int center = tile->tile.y + tile->tile.height / 2;
	while(it != tile) {
		if(it->tile.x + it->tile.width == tile->tile.x) {
			if(is_between_strict(it->tile.y, it->tile.y + it->tile.height,
			                     center) ||
			   it->tile.y + it->tile.height == center) {
				return it;
			}
		}
		it = it->next;
	}
	return NULL;
}

struct nedm_tile *
find_top_tile(const struct nedm_tile *tile) {
	struct nedm_tile *it = tile->next;
	int center = tile->tile.x + tile->tile.width / 2;
	while(it != tile) {
		if(it->tile.y + it->tile.height == tile->tile.y) {
			if(is_between_strict(it->tile.x, it->tile.x + it->tile.width,
			                     center) ||
			   it->tile.x + it->tile.width == center) {
				return it;
			}
		}
		it = it->next;
	}
	return NULL;
}

struct nedm_tile *
find_bottom_tile(const struct nedm_tile *tile) {
	struct nedm_tile *it = tile->next;
	int center = tile->tile.x + tile->tile.width / 2;
	while(it != tile) {
		if(it->tile.y == tile->tile.y + tile->tile.height) {
			if(is_between_strict(it->tile.x, it->tile.x + it->tile.width,
			                     center) ||
			   it->tile.x + it->tile.width == center) {
				return it;
			}
		}
		it = it->next;
	}
	return NULL;
}

int *
get_compl_coord(struct nedm_tile *tile, int *(*get_coord)(struct nedm_tile *tile)) {
	if(get_coord(tile) == &tile->tile.x) {
		return &tile->tile.y;
	} else {
		return &tile->tile.x;
	}
}

int *
get_compl_dim(struct nedm_tile *tile, int *(*get_dim)(struct nedm_tile *tile)) {
	if(get_dim(tile) == &tile->tile.height) {
		return &tile->tile.width;
	} else {
		return &tile->tile.height;
	}
}

/* find_tile is the direction in which to search, get_dim returns the dimension
 * which must be equal for merging to be possible and get_coord returns the
 * coordinate which must be equal for merging to be possible. */
void
merge_tile(struct nedm_tile *tile,
           struct nedm_tile *(*find_tile)(const struct nedm_tile *),
           int *(*get_dim)(struct nedm_tile *tile),
           int *(*get_coord)(struct nedm_tile *tile)) {
	struct nedm_tile *merge_tile = find_tile(tile);
	if(merge_tile == NULL || merge_tile == tile ||
	   *get_dim(tile) != *get_dim(merge_tile) ||
	   *get_coord(tile) != *get_coord(merge_tile)) {
		return;
	}
	// There are at least two tiles once we reach this point, since merge_tile
	// != tile
	int merge_tile_id = merge_tile->id;
	workspace_tile_update_view(merge_tile, NULL);
	merge_tile->prev->next = merge_tile->next;
	merge_tile->next->prev = merge_tile->prev;
	if(merge_tile->workspace->focused_tile == merge_tile) {
		merge_tile->workspace->focused_tile = tile;
	}
	*get_compl_dim(tile, get_dim) =
	    *get_compl_dim(merge_tile, get_dim) + *get_compl_dim(tile, get_dim);
	*get_compl_coord(tile, get_coord) =
	    fmin(*get_compl_coord(merge_tile, get_coord),
	         *get_compl_coord(tile, get_coord));
	if(tile->workspace->server->seat->cursor_tile == merge_tile) {
		tile->workspace->server->seat->cursor_tile = tile;
	}
	free(merge_tile);
	if(tile->view != NULL) {
		view_maximize(tile->view, tile);
	}
	ipc_send_event(
	    tile->workspace->output->server,
	    "{\"event_name\":\"merge_tile\",\"tile_id\":%d,\"merge_tile_id\":%d,"
	    "\"workspace\":%d,\"output\":\"%s\",\"output_id\":%d}",
	    tile->id, merge_tile_id, tile->workspace->num + 1,
	    tile->workspace->output->name, output_get_num(tile->workspace->output));
}

void
keybinding_focus_tile(struct nedm_server *server, uint32_t tile_id);

void
swap_tiles(struct nedm_tile *tile, struct nedm_tile *swap_tile, bool follow) {
	if(swap_tile == NULL || tile == NULL || swap_tile == tile) {
		return;
	}
	struct nedm_server *server = tile->workspace->server;
	struct nedm_view *tmp_view = tile->view;
	struct nedm_view *tmp_swap_view = swap_tile->view;
	workspace_tile_update_view(tile, NULL);
	workspace_tile_update_view(swap_tile, tmp_view);
	workspace_tile_update_view(tile, tmp_swap_view);
	if(follow) {
		keybinding_focus_tile(server, swap_tile->id);
	} else {
		seat_set_focus(
		    server->seat,
		    server->curr_output->workspaces[server->curr_output->curr_workspace]
		        ->focused_tile->view);
	}
	ipc_send_event(
	    tile->workspace->output->server,
	    "{\"event_name\":\"swap_tile\",\"tile_id\":%d,\"swap_"
	    "tile_id\":%d,\"workspace\":%d,\"output\":\"%s\",\"output_id\":%d}",
	    tile->id, swap_tile->id, tile->workspace->num + 1,
	    tile->workspace->output->name, output_get_num(tile->workspace->output));
}

void
swap_tile(struct nedm_server *server, uint32_t tile_id,
          struct nedm_tile *(*find_tile)(const struct nedm_tile *), bool follow) {
	struct nedm_tile *tile = NULL;
	if(tile_id == 0) {
		tile =
		    server->curr_output->workspaces[server->curr_output->curr_workspace]
		        ->focused_tile;
	} else {
		tile = tile_from_id(server, tile_id);
	}
	if(tile != NULL) {
		struct nedm_tile *swap_tile = find_tile(tile);
		swap_tiles(tile, swap_tile, follow);
	}
}

int *
get_width(struct nedm_tile *tile) {
	return &tile->tile.width;
}

int *
get_height(struct nedm_tile *tile) {
	return &tile->tile.height;
}

int *
get_x(struct nedm_tile *tile) {
	return &tile->tile.x;
}

int *
get_y(struct nedm_tile *tile) {
	return &tile->tile.y;
}

void
merge_tile_left(struct nedm_tile *tile) {
	merge_tile(tile, find_left_tile, get_height, get_y);
}

void
merge_tile_right(struct nedm_tile *tile) {
	merge_tile(tile, find_right_tile, get_height, get_y);
}

void
merge_tile_top(struct nedm_tile *tile) {
	merge_tile(tile, find_top_tile, get_width, get_x);
}

void
merge_tile_bottom(struct nedm_tile *tile) {
	merge_tile(tile, find_bottom_tile, get_width, get_x);
}

void
swap_tile_left(struct nedm_server *server, uint32_t tile_id, bool follow) {
	swap_tile(server, tile_id, find_left_tile, follow);
}

void
swap_tile_right(struct nedm_server *server, uint32_t tile_id, bool follow) {
	swap_tile(server, tile_id, find_right_tile, follow);
}

void
swap_tile_top(struct nedm_server *server, uint32_t tile_id, bool follow) {
	swap_tile(server, tile_id, find_top_tile, follow);
}

void
swap_tile_bottom(struct nedm_server *server, uint32_t tile_id, bool follow) {
	swap_tile(server, tile_id, find_bottom_tile, follow);
}

void
focus_tile(struct nedm_tile *tile,
           struct nedm_tile *(*find_tile)(const struct nedm_tile *tile)) {
	struct nedm_tile *new_tile = find_tile(tile);
	if(new_tile == NULL) {
		return;
	}
	struct nedm_server *server = new_tile->workspace->server;
	workspace_focus_tile(tile->workspace, new_tile);
	seat_set_focus(server->seat, new_tile->view);
}

void
focus_tile_left(struct nedm_tile *tile) {
	focus_tile(tile, find_left_tile);
}

void
focus_tile_right(struct nedm_tile *tile) {
	focus_tile(tile, find_right_tile);
}

void
focus_tile_top(struct nedm_tile *tile) {
	focus_tile(tile, find_top_tile);
}

void
focus_tile_bottom(struct nedm_tile *tile) {
	focus_tile(tile, find_bottom_tile);
}

bool
intervalls_intersect(int x1, int x2, int y1, int y2) {
	return y2 > x1 && y1 < x2;
}

bool
resize_allowed(struct nedm_tile *tile, const struct nedm_tile *parent,
               int coord_offset, int dim_offset,
               int *(*get_coord)(struct nedm_tile *tile),
               int *(*get_dim)(struct nedm_tile *tile), struct nedm_tile *orig) {

	if(coord_offset == 0 && dim_offset == 0) {
		return true;
	} else if(*get_dim(tile) + dim_offset <= 0) {
		return false;
	}

	for(struct nedm_tile *it = tile->next; it != tile && it != NULL;
	    it = it->next) {
		if(it == parent || it == orig) {
			continue;
		}
		if(intervalls_intersect(*get_compl_coord(tile, get_coord),
		                        *get_compl_coord(tile, get_coord) +
		                            *get_compl_dim(tile, get_dim),
		                        *get_compl_coord(it, get_coord),
		                        *get_compl_coord(it, get_coord) +
		                            *get_compl_dim(it, get_dim))) {
			if(*get_coord(it) == *get_coord(tile) + *get_dim(tile)) {
				if(!resize_allowed(it, tile, dim_offset + coord_offset,
				                   -dim_offset - coord_offset, get_coord,
				                   get_dim, orig)) {
					return false;
				}
			} else if(*get_coord(it) + *get_dim(it) == *get_coord(tile)) {
				if(!resize_allowed(it, tile, 0, coord_offset, get_coord,
				                   get_dim, orig)) {
					return false;
				}
			}
		}
	}
	return true;
}

void
resize(struct nedm_tile *tile, const struct nedm_tile *parent, int coord_offset,
       int dim_offset, int *(*get_coord)(struct nedm_tile *tile),
       int *(*get_dim)(struct nedm_tile *tile), struct nedm_tile *orig) {
	if(coord_offset == 0 && dim_offset == 0) {
		return;
	}

	for(struct nedm_tile *it = tile->next; it != tile && it != NULL;
	    it = it->next) {
		if(it == parent || it == orig) {
			continue;
		}
		if(intervalls_intersect(*get_compl_coord(tile, get_coord),
		                        *get_compl_coord(tile, get_coord) +
		                            *get_compl_dim(tile, get_dim),
		                        *get_compl_coord(it, get_coord),
		                        *get_compl_coord(it, get_coord) +
		                            *get_compl_dim(it, get_dim))) {
			if(*get_coord(it) == *get_coord(tile) + *get_dim(tile)) {
				resize(it, tile, dim_offset + coord_offset,
				       -dim_offset - coord_offset, get_coord, get_dim, orig);
			} else if(*get_coord(it) + *get_dim(it) == *get_coord(tile)) {
				resize(it, tile, 0, coord_offset, get_coord, get_dim, orig);
			}
		}
	}

	int old_x = tile->tile.x, old_y = tile->tile.y,
	    old_height = tile->tile.height, old_width = tile->tile.width;
	*get_coord(tile) += coord_offset;
	*get_dim(tile) += dim_offset;

	if(tile->view != NULL) {
		view_maximize(tile->view, tile);
	}
	ipc_send_event(tile->workspace->output->server,
	               "{\"event_name\":\"resize_tile\",\"tile_id\":%d,\"old_"
	               "dims\":\"[%d,%d,%d,%d]\",\"new_dims\":\"[%d,%d,%d,%d]\","
	               "\"workspace\":%d,\"output\":\"%s\",\"output_id\":%d}",
	               tile->id, old_x, old_y, old_height, old_width, tile->tile.x,
	               tile->tile.y, tile->tile.height, tile->tile.width,
	               tile->workspace->num + 1, tile->workspace->output->name,
	               output_get_num(tile->workspace->output));
}

bool
resize_allowed_horizontal(struct nedm_tile *tile, struct nedm_tile *parent,
                          int x_offset, int width_offset) {
	return resize_allowed(tile, parent, x_offset, width_offset, get_x,
	                      get_width, tile);
}

bool
resize_allowed_vertical(struct nedm_tile *tile, struct nedm_tile *parent,
                        int y_offset, int height_offset) {
	return resize_allowed(tile, parent, y_offset, height_offset, get_y,
	                      get_height, tile);
}

void
resize_horizontal(struct nedm_tile *tile, struct nedm_tile *parent, int x_offset,
                  int width_offset) {
	resize(tile, parent, x_offset, width_offset, get_x, get_width, tile);
}

void
resize_vertical(struct nedm_tile *tile, struct nedm_tile *parent, int y_offset,
                int height_offset) {
	resize(tile, parent, y_offset, height_offset, get_y, get_height, tile);
}

/* hpixs: positiv -> right, negative -> left; vpixs: positiv -> down, negative
 * -> up */
void
resize_tile(struct nedm_server *server, int hpixs, int vpixs, int tile_id) {
	struct nedm_output *output = server->curr_output;

	struct nedm_tile *tile =
	    output->workspaces[output->curr_workspace]->focused_tile;
	if(tile_id != 0) {
		tile = tile_from_id(server, tile_id);
	}
	if(tile == NULL) {
		return;
	}
	/* First do the horizontal adjustment */
	if(hpixs != 0 && tile->tile.width < output_get_layout_box(output).width &&
	   is_between_strict(0, output_get_layout_box(output).width,
	                     tile->tile.width + hpixs)) {
		int x_offset = 0;
		/* In case we are on the total right, move the left edge of the tile */
		if(tile->tile.x + tile->tile.width ==
		   output_get_layout_box(output).width) {
			x_offset = -hpixs;
		}
		bool resize_allowed =
		    resize_allowed_horizontal(tile, NULL, x_offset, hpixs);
		if(resize_allowed) {
			resize_horizontal(tile, NULL, x_offset, hpixs);
		}
	}
	/* Repeat for vertical */
	if(vpixs != 0 && tile->tile.height < output_get_layout_box(output).height &&
	   is_between_strict(0, output_get_layout_box(output).height,
	                     tile->tile.height + vpixs)) {
		int y_offset = 0;
		if(tile->tile.y + tile->tile.height ==
		   output_get_layout_box(output).height) {
			y_offset = -vpixs;
		}
		bool resize_allowed =
		    resize_allowed_vertical(tile, NULL, y_offset, vpixs);
		if(resize_allowed) {
			resize_vertical(tile, NULL, y_offset, vpixs);
		}
	}
}

void
keybinding_workspace_fullscreen(struct nedm_server *server, uint32_t screen,
                                uint32_t workspace) {
	struct nedm_output *output = server->curr_output;
	uint32_t ws = output->curr_workspace;
	if(screen != 0) {
		output = output_from_num(server, screen);
		ws = workspace;
	}
	if(output == NULL || ws >= server->nws) {
		return;
	}
	output_make_workspace_fullscreen(output, ws);
	ipc_send_event(server,
	               "{\"event_name\":\"fullscreen\",\"tile_id\":%d,"
	               "\"workspace\":%d,\"output\":\"%s\",\"output_id\":%d}",
	               output->workspaces[ws]->focused_tile->id,
	               output->workspaces[ws]->num + 1, output->name,
	               output_get_num(output));
}

// Switch to a differerent virtual terminal
static int
keybinding_switch_vt(struct nedm_server *server, unsigned int vt) {
	if(wlr_backend_is_multi(server->backend)) {
		if(server->session) {
			wlr_session_change_vt(server->session, vt);
		}
		return 0;
	}
	return -1;
}

/* Split screen (vertical or horizontal)
 * Important: Do not attempt to perform mathematical simplifications in this
 * function without taking rounding errors into account. */
static void
keybinding_split_output(struct nedm_output *output, bool vertical,
                        float percentage) {
	struct nedm_view *original_view = seat_get_focus(output->server->seat);
	struct nedm_workspace *curr_workspace =
	    output->workspaces[output->curr_workspace];
	int32_t width = curr_workspace->focused_tile->tile.width;
	int32_t height = curr_workspace->focused_tile->tile.height;
	int32_t x = curr_workspace->focused_tile->tile.x;
	int32_t y = curr_workspace->focused_tile->tile.y;
	int32_t new_width, new_height;

	if(vertical) {
		new_width = (int)(((float)width) * percentage);
		new_height = height;
	} else {
		new_width = width;
		new_height = (int)(((float)height) * percentage);
	}
	if(new_width < 1 || new_height < 1) {
		return;
	}

	struct nedm_view *next_view = NULL;
	struct nedm_view *it;
	wl_list_for_each(it, &output->workspaces[output->curr_workspace]->views,
	                 link) {
		if(!view_is_visible(it) && original_view != it) {
			next_view = it;
			break;
		}
	}

	int32_t new_x, new_y;
	if(vertical) {
		new_x = x + new_width;
		new_y = y;
	} else {
		new_x = x;
		new_y = y + new_height;
	}

	struct nedm_tile *new_tile = calloc(1, sizeof(struct nedm_tile));
	if(!new_tile) {
		wlr_log(WLR_ERROR, "Failed to allocate new tile for splitting");
		return;
	}
	new_tile->id = output->server->tiles_curr_id;
	++output->server->tiles_curr_id;
	new_tile->tile.x = new_x;
	new_tile->tile.y = new_y;
	new_tile->tile.width = x + width - new_x;
	new_tile->tile.height = y + height - new_y;
	new_tile->prev = curr_workspace->focused_tile;
	new_tile->next = curr_workspace->focused_tile->next;
	workspace_tile_update_view(new_tile, next_view);
	new_tile->workspace = curr_workspace;
	curr_workspace->focused_tile->next->prev = new_tile;
	curr_workspace->focused_tile->next = new_tile;

	curr_workspace->focused_tile->tile.width = new_width;
	curr_workspace->focused_tile->tile.height = new_height;
	workspace_focus_tile(curr_workspace, curr_workspace->focused_tile);

	if(next_view != NULL) {
		view_maximize(next_view, new_tile);
	}

	if(original_view != NULL) {
		view_maximize(original_view, curr_workspace->focused_tile);
	}
	ipc_send_event(
	    output->server,
	    "{\"event_name\":\"split\",\"tile_id\":%d,\"new_tile_id\":%d,"
	    "\"workspace\":%d,\"output\":\"%s\",\"output_id\":%d,\"vertical\":%d}",
	    curr_workspace->focused_tile->id, new_tile->id, curr_workspace->num + 1,
	    curr_workspace->output->name, output_get_num(curr_workspace->output),
	    vertical);
}

static void
keybinding_close_view(struct nedm_view *view) {
	if(view == NULL) {
		return;
	}
	struct nedm_output *outp = view->workspace->output;
	uint32_t view_id = view->id;
	uint32_t view_pid = view->impl->get_pid(view);
	uint32_t tile_id = view->id;
	uint32_t ws = view->workspace->num;
	view->impl->close(view);
	ipc_send_event(
	    outp->server,
	    "{\"event_name\":\"close\",\"view_id\":%d,\"view_pid\":%d,\"tile_id\":"
	    "%d,\"workspace\":%d,\"output\":\"%s\",\"output_id\":%d}",
	    view_id, view_pid, tile_id, ws + 1, outp->name, output_get_num(outp));
}

static void
keybinding_split_vertical(struct nedm_server *server, float percentage) {
	keybinding_split_output(server->curr_output, true, percentage);
}

static void
keybinding_split_horizontal(struct nedm_server *server, float percentage) {
	keybinding_split_output(server->curr_output, false, percentage);
}

static void
into_process(const char *command) {
	execlp("sh", "sh", "-c", command, (char *)NULL);
	_exit(1);
}

void
set_output(struct nedm_server *server, struct nedm_output *output) {
	server->curr_output = output;
	seat_set_focus(
	    server->seat,
	    server->curr_output->workspaces[server->curr_output->curr_workspace]
	        ->focused_tile->view);
	message_printf(server->curr_output, "Current Output");
}

void
keybinding_cycle_outputs(struct nedm_server *server, bool reverse,
                         bool trigger_event) {
	struct nedm_output *output = NULL;
	struct nedm_output *old_output = server->curr_output;
	if(reverse) {
		output = wl_container_of(server->curr_output->link.prev,
		                         server->curr_output, link);
	} else {
		output = wl_container_of(server->curr_output->link.next,
		                         server->curr_output, link);
	}
	if(&output->link == &server->outputs) {
		if(reverse) {
			output = wl_container_of(output->link.prev, output, link);
		} else {
			output = wl_container_of(output->link.next, output, link);
		}
	}
	set_output(server, output);
	if(trigger_event) {
		ipc_send_event(
		    output->server,
		    "{\"event_name\":\"cycle_outputs\",\"old_output\":\"%s\",\"old_"
		    "output_id\":%d,"
		    "\"new_output\":\"%s\",\"new_output_id\":%d,\"reverse\":%d}",
		    old_output->name, output_get_num(old_output), output->name,
		    output_get_num(output), reverse);
	}
}

/* Cycle through views, whereby the workspace does not change */
void
keybinding_cycle_views(struct nedm_server *server, struct nedm_tile *tile,
                       uint32_t view_id, bool reverse, bool ipc) {
	if(tile == NULL) {
		tile =
		    server->curr_output->workspaces[server->curr_output->curr_workspace]
		        ->focused_tile;
	}
	struct nedm_view *current_view = tile->view;
	struct nedm_workspace *ws = tile->workspace;

	struct nedm_view *it_view, *next_view = NULL;
	if(view_id == 0) {
		if(reverse) {
			wl_list_for_each(it_view, &ws->views, link) {
				if(!view_is_visible(it_view)) {
					next_view = it_view;
					break;
				}
			}
		} else {
			wl_list_for_each_reverse(it_view, &ws->views, link) {
				if(!view_is_visible(it_view)) {
					next_view = it_view;
					break;
				}
			}
		}
	} else {
		next_view = view_from_id(server, view_id);
		if(next_view == NULL || view_is_visible(next_view)) {
			return;
		}
	}

	if(next_view == NULL) {
		return;
	}

	workspace_tile_update_view(tile, next_view);
	if(tile ==
	   server->curr_output->workspaces[server->curr_output->curr_workspace]
	       ->focused_tile) {
		seat_set_focus(server->seat, next_view);
	}
	if(ipc) {
		int curr_id = -1;
		int curr_pid = -1;
		if(current_view != NULL && current_view->link.next != ws->views.next) {
			curr_id = current_view->id;
			curr_pid = current_view->impl->get_pid(current_view);
		}
		ipc_send_event(
		    ws->output->server,
		    "{\"event_name\":\"cycle_views\",\"old_view_id\":%d,\"old_view_"
		    "pid\":%d,"
		    "\"new_view_id\":%d,\"new_view_pid\":%d,\"tile_id\":%d,"
		    "\"workspace\":%d,\"output\":\"%s\",\"output_id\":%d}",
		    curr_id, curr_pid, next_view == NULL ? -1 : (int)next_view->id,
		    next_view == NULL ? -1 : (int)next_view->impl->get_pid(next_view),
		    next_view->tile->id, ws->num + 1, ws->output->name,
		    output_get_num(ws->output));
	}
}

int
keybinding_switch_ws(struct nedm_server *server, uint32_t ws) {
	if(ws >= server->nws) {
		wlr_log(WLR_ERROR,
		        "Requested workspace %u, but only have %u workspaces.", ws + 1,
		        server->nws);
		return -1;
	}
	struct nedm_output *output = server->curr_output;
	uint32_t old_ws = server->curr_output->curr_workspace;
	workspace_focus(output, ws);
	seat_set_focus(server->seat,
	               server->curr_output->workspaces[ws]->focused_tile->view);
	message_printf(server->curr_output, "Workspace %d", ws + 1);
	ipc_send_event(output->server,
	               "{\"event_name\":\"switch_ws\",\"old_workspace\":%d,"
	               "\"new_workspace\":%d,\"output\":\"%s\",\"output_id\":%d}",
	               old_ws + 1, ws + 1, output->name, output_get_num(output));
	return 0;
}

void
keybinding_focus_tile(struct nedm_server *server, uint32_t tile_id) {
	struct nedm_output *output = server->curr_output;
	struct nedm_workspace *workspace = output->workspaces[output->curr_workspace];
	struct nedm_tile *old_tile = workspace->focused_tile;
	struct nedm_tile *tile = tile_from_id(server, tile_id);
	if(tile == NULL) {
		return;
	}
	if(server->curr_output != tile->workspace->output) {
		set_output(server, tile->workspace->output);
	}
	if(server->curr_output->curr_workspace != (int)tile->workspace->num) {
		keybinding_switch_ws(server, tile->workspace->num);
	}
	workspace_focus_tile(tile->workspace, tile);
	struct nedm_view *next_view = tile->workspace->focused_tile->view;
	seat_set_focus(server->seat, next_view);
	
	char *view_title = "";
	if (next_view && next_view->impl && next_view->impl->get_title) {
		char *title = next_view->impl->get_title(next_view);
		view_title = title ? title : "";
	}
	
	ipc_send_event(
	    output->server,
	    "{\"event_name\":\"focus_tile\",\"old_tile_id\":%d,\"new_tile_"
	    "id\":%d,\"old_workspace\":%d,\"new_workspace\":%d,\"old_output\":\"%"
	    "s\",\"old_output_id\":%d,\"output\":\"%s\",\"output_id\":%d,\"view_title\":\"%s\"}",
	    old_tile->id, tile->workspace->focused_tile->id, workspace->num + 1,
	    tile->workspace->num + 1, output->name, output_get_num(output),
	    tile->workspace->output->name, output_get_num(tile->workspace->output), view_title);
}

void
keybinding_cycle_tiles(struct nedm_server *server, bool reverse) {
	struct nedm_output *output = server->curr_output;
	struct nedm_workspace *workspace = output->workspaces[output->curr_workspace];
	if(reverse) {
		keybinding_focus_tile(server, workspace->focused_tile->prev->id);
	} else {
		keybinding_focus_tile(server, workspace->focused_tile->next->id);
	}
}

void
keybinding_show_time(struct nedm_server *server) {
	char *msg, *tmp;
	time_t timep;

	timep = time(NULL);
	tmp = ctime(&timep);
	msg = strdup(tmp);
	msg[strcspn(msg, "\n")] = '\0'; /* Remove the newline */

	message_printf(server->curr_output, "%s", msg);
	free(msg);
}

struct dyn_str {
	uint32_t len;
	uint32_t cur_pos;
	char **str_arr;
};

/*Note that inp is in an invalid state after calling the function * and should
 * no longer be used.*/
char *
dyn_str_to_str(struct dyn_str *inp) {
	char *outp = calloc(inp->len + 1, sizeof(char));
	if(outp == NULL) {
		return NULL;
	}
	outp[inp->len] = '\0';
	uint32_t tmp_pos = 0;
	for(uint32_t i = 0; i < inp->cur_pos; ++i) {
		strcat(outp + tmp_pos, inp->str_arr[i]);
		tmp_pos += strlen(inp->str_arr[i]);
		free(inp->str_arr[i]);
	}
	free(inp->str_arr);
	return outp;
}

int
print_str(struct dyn_str *outp, const char *fmt, ...) {
	va_list args;
	va_start(args, fmt);
	char *ret = malloc_vsprintf_va_list(fmt, args);
	if(ret == NULL) {
		return -1;
	}
	outp->str_arr[outp->cur_pos] = ret;
	++outp->cur_pos;
	outp->len += strlen(ret);
	return 0;
}

char *
print_message_conf(struct nedm_message_config *config) {
	struct dyn_str outp_str;
	outp_str.len = 0;
	outp_str.cur_pos = 0;
	uint32_t nmemb = 8;
	outp_str.str_arr = calloc(nmemb, sizeof(char *));
	print_str(&outp_str, "\"message_config\": {");
	print_str(&outp_str, "\"font\": \"%s\",\n", config->font);
	print_str(&outp_str, "\"display_time\": %d,\n", config->display_time);
	print_str(&outp_str, "\"bg_color\": [%f,%f,%f,%f],\n", config->bg_color[0],
	          config->bg_color[1], config->bg_color[2], config->bg_color[3]);
	print_str(&outp_str, "\"fg_color\": [%f,%f,%f,%f],\n", config->fg_color[0],
	          config->fg_color[1], config->fg_color[2], config->fg_color[3]);
	print_str(&outp_str, "\"enabled\": %d,\n", config->enabled == 1);
	switch(config->anchor) {
	case NEDM_MESSAGE_TOP_LEFT:
		print_str(&outp_str, "\"anchor\": \"top_left\"\n", config->font);
		break;
	case NEDM_MESSAGE_TOP_CENTER:
		print_str(&outp_str, "\"anchor\": \"top_center\"\n", config->font);
		break;
	case NEDM_MESSAGE_TOP_RIGHT:
		print_str(&outp_str, "\"anchor\": \"top_right\"\n", config->font);
		break;
	case NEDM_MESSAGE_BOTTOM_LEFT:
		print_str(&outp_str, "\"anchor\": \"bottom_left\"\n", config->font);
		break;
	case NEDM_MESSAGE_BOTTOM_CENTER:
		print_str(&outp_str, "\"anchor\": \"bottom_center\"\n", config->font);
		break;
	case NEDM_MESSAGE_BOTTOM_RIGHT:
		print_str(&outp_str, "\"anchor\": \"bottom_right\"\n", config->font);
		break;
	case NEDM_MESSAGE_CENTER:
		print_str(&outp_str, "\"anchor\": \"center\"\n", config->font);
		break;
	case NEDM_MESSAGE_NOPT: // This should actually never occur
		print_str(&outp_str, "\"anchor\": \"no_op\"\n", config->font);
		break;
	}
	print_str(&outp_str, "}");
	return dyn_str_to_str(&outp_str);
}

void
print_modes(struct dyn_str *str, char **modes) {
	uint32_t len = 0;
	char **tmp = modes;
	uint32_t nmemb = 0;
	while(*tmp != NULL) {
		len += strlen(*tmp);
		++nmemb;
		++tmp;
	}
	if(nmemb == 0) {
		wlr_log(WLR_ERROR,
		        "This is a bug: Cagebreak has no valid modes. This should not "
		        "occur, since default modes are defined on startup.");
		return;
	}
	/* We are assuming here that we have at least one mode, which is given by
	 * the initialization of cagebreak */
	char *modes_str = calloc(len + 3 * (nmemb - 1) + 1, sizeof(char));
	modes_str[len + nmemb - 1] = '\0';
	uint32_t tmp_pos = 0;
	for(uint32_t i = 0; i < nmemb; ++i) {
		if(i != 0) {
			strcat(modes_str + tmp_pos, "\",\"");
			tmp_pos += 3;
		}
		strcat(modes_str + tmp_pos, modes[i]);
		tmp_pos += strlen(modes[i]);
	}
	print_str(str, "\"modes\":[\"%s\"],\n", modes_str);
	free(modes_str);
}

char *
print_view(struct nedm_view *view) {
	struct dyn_str outp_str;
	outp_str.len = 0;
	outp_str.cur_pos = 0;
	uint32_t nmemb = 5;
	outp_str.str_arr = calloc(nmemb, sizeof(char *));
	print_str(&outp_str, "\"id\": %d,\n", view->id);
	print_str(&outp_str, "\"pid\": %d,\n", view->impl->get_pid(view));
	if(view->server->bs == true) {
		char *title_str = view->impl->get_title(view);
		print_str(&outp_str, "\"title\": \"%s\",\n",
		          title_str == NULL ? "" : title_str);
	}
	print_str(&outp_str, "\"coords\": {\"x\":%d,\"y\":%d},\n", view->ox,
	          view->oy);
#if NEDM_HAS_XWAYLAND
	print_str(&outp_str, "\"type\": \"%s\"\n",
	          view->type == NEDM_XWAYLAND_VIEW ? "xwayland" : "xdg");
#else
	print_str(&outp_str, "\"type\": \"xdg\"\n");
#endif
	return dyn_str_to_str(&outp_str);
}

char *
print_tile(struct nedm_tile *tile) {
	struct dyn_str outp_str;
	outp_str.len = 0;
	outp_str.cur_pos = 0;
	uint32_t nmemb = 4;
	outp_str.str_arr = calloc(nmemb, sizeof(char *));
	print_str(&outp_str, "\"id\": %d,\n", tile->id);
	print_str(&outp_str, "\"coords\": {\"x\":%d,\"y\":%d},\n", tile->tile.x,
	          tile->tile.y);
	print_str(&outp_str, "\"size\": {\"width\":%d,\"height\":%d},\n",
	          tile->tile.width, tile->tile.height);
	print_str(&outp_str, "\"view_id\": %d\n",
	          tile->view == NULL ? -1 : (int)tile->view->id);
	return dyn_str_to_str(&outp_str);
}

char *
print_views(struct nedm_workspace *ws) {
	struct dyn_str outp_str;
	outp_str.len = 0;
	outp_str.cur_pos = 0;
	uint32_t nviews = wl_list_length(&ws->views);
	if(nviews == 0) {
		return strdup("{}");
	}
	outp_str.str_arr = calloc(2 * nviews - 1, sizeof(char *));
	struct nedm_view *it;
	uint32_t count = 0;
	wl_list_for_each(it, &ws->views, link) {
		if(count != 0) {
			print_str(&outp_str, ",");
		}
		++count;
		char *view_str = print_view(it);
		if(view_str != NULL) {
			print_str(&outp_str, "{\n%s\n}", view_str);
			free(view_str);
		}
	}
	return dyn_str_to_str(&outp_str);
}

char *
print_tiles(struct nedm_workspace *ws) {
	struct dyn_str outp_str;
	outp_str.len = 0;
	outp_str.cur_pos = 0;
	uint32_t ntiles = 0;
	bool first = true;
	for(struct nedm_tile *tile = ws->focused_tile;
	    first || tile != ws->focused_tile; tile = tile->next) {
		first = false;
		++ntiles;
	}
	if(ntiles == 0) {
		return strdup("{}");
	}
	outp_str.str_arr = calloc(2 * ntiles - 1, sizeof(char *));
	first = true;
	for(struct nedm_tile *tile = ws->focused_tile;
	    first || tile != ws->focused_tile; tile = tile->next) {
		if(first == false) {
			print_str(&outp_str, ",");
		}
		first = false;
		char *tile_str = print_tile(tile);
		if(tile_str != NULL) {
			print_str(&outp_str, "{\n%s\n}", tile_str);
			free(tile_str);
		}
	}
	return dyn_str_to_str(&outp_str);
}

char *
print_workspace(struct nedm_workspace *ws) {
	struct dyn_str outp_str;
	outp_str.len = 0;
	outp_str.cur_pos = 0;
	uint32_t nmemb = 6;
	outp_str.str_arr = calloc(nmemb, sizeof(char *));
	print_str(&outp_str, "\"views\": [");
	char *views_str = print_views(ws);
	if(views_str != NULL) {
		print_str(&outp_str, "%s", views_str);
		free(views_str);
	}
	print_str(&outp_str, "],");
	print_str(&outp_str, "\"tiles\": [");
	char *tiles_str = print_tiles(ws);
	if(tiles_str != NULL) {
		print_str(&outp_str, "%s", tiles_str);
		free(tiles_str);
	}
	print_str(&outp_str, "]");
	return dyn_str_to_str(&outp_str);
}

char *
print_workspaces(struct nedm_output *outp) {
	struct dyn_str outp_str;
	outp_str.len = 0;
	outp_str.cur_pos = 0;
	outp_str.str_arr = calloc((2 * outp->server->nws - 1) + 2, sizeof(char *));
	print_str(&outp_str, "\"workspaces\": [");
	for(int i = 0; i < outp->server->nws; ++i) {
		if(i != 0) {
			print_str(&outp_str, ",");
		}
		char *ws = print_workspace(outp->workspaces[i]);
		if(ws != NULL) {
			print_str(&outp_str, "{%s}", ws);
			free(ws);
		}
	}
	print_str(&outp_str, "]\n");
	return dyn_str_to_str(&outp_str);
}

char *
print_output(struct nedm_output *outp) {
	struct dyn_str outp_str;
	outp_str.len = 0;
	outp_str.cur_pos = 0;
	uint32_t nmemb = 10;
	outp_str.str_arr = calloc(nmemb, sizeof(char *));
	print_str(&outp_str, "\"%s\": {\n", outp->name);
	print_str(&outp_str, "\"priority\": %d,\n", outp->priority);
	print_str(&outp_str, "\"coords\": {\"x\":%d,\"y\":%d},\n",
	          output_get_layout_box(outp).x, output_get_layout_box(outp).y);
	print_str(&outp_str, "\"size\": {\"width\":%d,\"height\":%d},\n",
	          outp->wlr_output->width, outp->wlr_output->height);
	print_str(&outp_str, "\"refresh_rate\": %f,\n",
	          (float)outp->wlr_output->refresh / 1000.0);
	print_str(&outp_str, "\"permanent\": %d,\n",
	          outp->role == OUTPUT_ROLE_PERMANENT);
	print_str(&outp_str, "\"active\": %d,\n", !outp->destroyed);
	print_str(&outp_str, "\"curr_workspace\": %d,\n", outp->curr_workspace + 1);
	char *workspaces_str = print_workspaces(outp);
	if(workspaces_str != NULL) {
		print_str(&outp_str, "%s", workspaces_str);
		free(workspaces_str);
	}
	print_str(&outp_str, "}");
	return dyn_str_to_str(&outp_str);
}

char *
print_outputs(struct nedm_server *server) {
	uint32_t noutps = wl_list_length(&server->outputs);
	struct dyn_str outp_str;
	outp_str.len = 0;
	outp_str.cur_pos = 0;
	outp_str.str_arr = calloc((2 * noutps - 1) + 2, sizeof(char *));
	print_str(&outp_str, "\"outputs\": {");
	struct nedm_output *it;
	uint32_t count = 0;
	wl_list_for_each(it, &server->outputs, link) {
		if(count != 0) {
			print_str(&outp_str, ",");
		}
		++count;
		char *outp = print_output(it);
		if(outp == NULL) {
			continue;
		}
		print_str(&outp_str, "%s", outp);
		free(outp);
	}
	print_str(&outp_str, "}\n");
	return dyn_str_to_str(&outp_str);
}

char *
print_keyboard_group(struct nedm_keyboard_group *grp) {
	struct dyn_str outp_str;
	outp_str.len = 0;
	outp_str.cur_pos = 0;
	uint32_t nmemb = 5;
	outp_str.str_arr = calloc(nmemb, sizeof(char *));
	if(grp->identifier != NULL) {
		print_str(&outp_str, "\"%s\": {\n", grp->identifier);
	} else {
		print_str(&outp_str, "\"NULL\": {\n");
	}
	print_str(&outp_str, "\"commands_enabled\": %d,\n",
	          grp->enable_keybindings);
	print_str(&outp_str, "\"repeat_delay\": %d,\n",
	          grp->wlr_group->keyboard.repeat_info.delay);
	print_str(&outp_str, "\"repeat_rate\": %d\n",
	          grp->wlr_group->keyboard.repeat_info.rate);
	print_str(&outp_str, "}");
	return dyn_str_to_str(&outp_str);
}

char *
print_keyboard_groups(struct nedm_server *server) {
	uint32_t ninps = wl_list_length(&server->seat->keyboard_groups);
	struct dyn_str outp_str;
	outp_str.len = 0;
	outp_str.cur_pos = 0;
	outp_str.str_arr = calloc((2 * ninps - 1) + 3, sizeof(char *));
	print_str(&outp_str, "\"keyboards\": {");
	struct nedm_keyboard_group *it;
	uint32_t count = 0;
	wl_list_for_each(it, &server->seat->keyboard_groups, link) {
		if(count != 0) {
			print_str(&outp_str, ",");
		}
		++count;
		char *outp = print_keyboard_group(it);
		if(outp == NULL) {
			continue;
		}
		print_str(&outp_str, "%s", outp);
		free(outp);
	}
	print_str(&outp_str, "}\n");
	return dyn_str_to_str(&outp_str);
}

char *
print_input_device(struct nedm_input_device *dev) {
	struct dyn_str outp_str;
	outp_str.len = 0;
	outp_str.cur_pos = 0;
	uint32_t nmemb = 4;
	outp_str.str_arr = calloc(nmemb, sizeof(char *));
	if(dev->identifier != NULL) {
		print_str(&outp_str, "\"%s\": {\n", dev->identifier);
	} else {
		print_str(&outp_str, "\"NULL\": {\n");
	}
	print_str(&outp_str, "\"is_virtual\": %d,\n", dev->is_virtual);
	print_str(&outp_str, "\"type\": \"%s\"\n",
	          dev->wlr_device->type == WLR_INPUT_DEVICE_POINTER  ? "pointer"
	          : dev->wlr_device->type == WLR_INPUT_DEVICE_SWITCH ? "switch"
	          : dev->wlr_device->type == WLR_INPUT_DEVICE_TABLET_PAD
	              ? "tablet pad"
	          : dev->wlr_device->type == WLR_INPUT_DEVICE_TABLET   ? "tablet"
	          : dev->wlr_device->type == WLR_INPUT_DEVICE_TOUCH    ? "touch"
	          : dev->wlr_device->type == WLR_INPUT_DEVICE_KEYBOARD ? "keyboard"
	                                                               : "unknown");
	print_str(&outp_str, "}");
	return dyn_str_to_str(&outp_str);
}

char *
print_input_devices(struct nedm_server *server) {
	uint32_t ninps = wl_list_length(&server->input->devices);
	struct dyn_str outp_str;
	outp_str.len = 0;
	outp_str.cur_pos = 0;
	outp_str.str_arr = calloc((2 * ninps - 1) + 3, sizeof(char *));
	print_str(&outp_str, "\"input_devices\": {");
	struct nedm_input_device *it;
	uint32_t count = 0;
	wl_list_for_each(it, &server->input->devices, link) {
		if(count != 0) {
			print_str(&outp_str, ",");
		}
		++count;
		char *outp = print_input_device(it);
		if(outp == NULL) {
			continue;
		}
		print_str(&outp_str, "%s", outp);
		free(outp);
	}
	print_str(&outp_str, "}\n");
	return dyn_str_to_str(&outp_str);
}

char *
get_mode_name(char **modes, unsigned int mode_nr) {
	unsigned int i;
	for(i = 0; i < mode_nr && modes[i] != NULL; ++i) {
	}
	if(modes[i] != NULL) {
		return modes[i];
	} else {
		return "NULL";
	}
}

void
keybinding_dump(struct nedm_server *server) {
	struct dyn_str str;
	str.len = 0;
	str.cur_pos = 0;
	uint32_t nmemb = 14;
	str.str_arr = calloc(nmemb, sizeof(char *));

	print_str(&str, "{\"event_name\":\"dump\",");
	print_str(&str, "\"nws\":%d,\n", server->nws);
	print_str(&str, "\"bg_color\":[%f,%f,%f],\n", server->bg_color[0],
	          server->bg_color[1], server->bg_color[2]);
	struct nedm_view *focused_view = seat_get_focus(server->seat);
	int curr_view_id = -1, curr_tile_id = -1;
	if(focused_view != NULL) {
		curr_view_id = focused_view->id;
		if(focused_view->tile != NULL) {
			curr_tile_id = focused_view->tile->id;
		}
	}
	print_str(&str, "\"views_curr_id\":%d,\n", curr_view_id);
	print_str(&str, "\"tiles_curr_id\":%d,\n", curr_tile_id);
	print_str(&str, "\"curr_output\":\"%s\",\n", server->curr_output->name);
	print_str(&str, "\"default_mode\":\"%s\",\n",
	          get_mode_name(server->modes, server->seat->default_mode));
	print_modes(&str, server->modes);
	char *message_string = print_message_conf(&server->message_config);
	if(message_string != NULL) {
		print_str(&str, "%s,", message_string);
		free(message_string);
	}
	char *outps_str = print_outputs(server);
	if(outps_str != NULL) {
		print_str(&str, "%s,", outps_str);
		free(outps_str);
	}
	char *keyboards_str = print_keyboard_groups(server);
	if(keyboards_str != NULL) {
		print_str(&str, "%s,", keyboards_str);
		free(keyboards_str);
	}
	char *input_dev_str = print_input_devices(server);
	if(input_dev_str != NULL) {
		print_str(&str, "%s,", input_dev_str);
		free(input_dev_str);
	}
	print_str(&str, "\"cursor_coords\":{\"x\":%f,\"y\":%f}\n",
	          server->seat->cursor->x, server->seat->cursor->y);
	print_str(&str, "}");

	char *send_str = dyn_str_to_str(&str);
	if(send_str == NULL) {
		wlr_log(WLR_ERROR, "Unable to create output string for \"dump\".");
	}
	ipc_send_event(server, send_str);
	free(send_str);
}

void
keybinding_show_info(struct nedm_server *server) {
	char *msg = server_show_info(server);

	if(!msg) {
		return;
	}

	message_printf(server->curr_output, "%s", msg);
	free(msg);
}

void
keybinding_display_message(struct nedm_server *server, char *msg) {
	message_printf(server->curr_output, "%s", msg);
}

void
keybinding_send_custom_event(struct nedm_server *server, char *msg) {
	ipc_send_event(server,
	               "{\"event_name\":\"custom_event\",\"message\":\"%s\"}", msg);
}

void
keybinding_move_view_to_cycle_output(struct nedm_server *server, bool reverse) {
	if(wl_list_length(&server->outputs) <= 1) {
		return;
	}
	struct nedm_output *old_outp = server->curr_output;
	struct nedm_view *view =
	    server->curr_output->workspaces[server->curr_output->curr_workspace]
	        ->focused_tile->view;
	if(view != NULL) {
		wl_list_remove(&view->link);
		server->curr_output->workspaces[server->curr_output->curr_workspace]
		    ->focused_tile->view = NULL;
		keybinding_cycle_views(server, NULL, 0, false, false);
		if(server->curr_output->workspaces[server->curr_output->curr_workspace]
		       ->focused_tile->view == NULL) {
			seat_set_focus(server->seat, NULL);
		}
	}
	keybinding_cycle_outputs(server, reverse, false);
	if(view != NULL) {
		struct nedm_workspace *ws =
		    server->curr_output
		        ->workspaces[server->curr_output->curr_workspace];
		wl_list_insert(&ws->views, &view->link);
		wlr_scene_node_reparent(&view->scene_tree->node, ws->scene);
		workspace_tile_update_view(ws->focused_tile, view);
		view->workspace = ws;
		seat_set_focus(server->seat, view);
	}
	int id = -1;
	int pid = -1;
	if(view != NULL) {
		id = view->id;
		pid = view->impl->get_pid(view);
	}
	ipc_send_event(
	    server,
	    "{\"event_name\":\"move_view_to_cycle_output\",\"view_id\":%d,\"view_"
	    "pid\":%d,\"old_output\":\"%s\",\"old_output_id\":%d,\"new_output\":\"%"
	    "s\",\"new_output_id\":%d,\"old_tile_id\":%d,\"new_tile_id\":%d}",
	    id, pid, old_outp->name, output_get_num(old_outp),
	    server->curr_output->name, output_get_num(server->curr_output),
	    old_outp->workspaces[old_outp->curr_workspace]->focused_tile->id,
	    server->curr_output->workspaces[server->curr_output->curr_workspace]
	        ->focused_tile->id);
}

void
keybinding_set_nws(struct nedm_server *server, int nws) {
	struct nedm_output *output;
	unsigned int old_nws = server->nws;
	server->nws = nws;
	wl_list_for_each(output, &server->outputs, link) {
		for(unsigned int i = nws; i < old_nws; ++i) {
			struct nedm_view *view, *tmp;
			wl_list_for_each_safe(view, tmp, &output->workspaces[i]->views,
			                      link) {
				wl_list_remove(&view->link);
				wl_list_insert(&output->workspaces[nws - 1]->views,
				               &view->link);
				view->workspace = output->workspaces[nws - 1];
			}
			wl_list_for_each_safe(
			    view, tmp, &output->workspaces[i]->unmanaged_views, link) {
				wl_list_remove(&view->link);
				wl_list_insert(&output->workspaces[nws - 1]->unmanaged_views,
				               &view->link);
				view->workspace = output->workspaces[nws - 1];
			}
			workspace_free(output->workspaces[i]);
		}
		struct nedm_workspace **new_workspaces =
		    realloc(output->workspaces, nws * sizeof(struct nedm_workspace *));
		if(new_workspaces == NULL) {
			wlr_log(WLR_ERROR, "Error reallocating memory for workspaces.");
			return;
		}
		output->workspaces = new_workspaces;
		for(int i = old_nws; i < nws; ++i) {
			output->workspaces[i] = full_screen_workspace(output);
			output->workspaces[i]->num = i;
			if(!output->workspaces[i]) {
				wlr_log(WLR_ERROR, "Failed to allocate additional workspaces");
				return;
			}

			wl_list_init(&output->workspaces[i]->views);
			wl_list_init(&output->workspaces[i]->unmanaged_views);
		}

		if(output->curr_workspace >= nws) {
			output->curr_workspace = 0;
			workspace_focus(output, nws - 1);
		}
	}
	seat_set_focus(
	    server->seat,
	    server->curr_output->workspaces[server->curr_output->curr_workspace]
	        ->focused_tile->view);
	ipc_send_event(server,
	               "{\"event_name\":\"set_nws\",\"old_nws\":%d,\"new_nws\":%d}",
	               old_nws, server->nws);
}

void
keybinding_set_gap(struct nedm_server *server, int gap_size) {
	uint32_t old_gap = server->gap_size;
	server->gap_size = gap_size >= 0 ? gap_size : 0;
	
	// Re-position all windows with new gap settings
	struct nedm_output *output;
	wl_list_for_each(output, &server->outputs, link) {
		for(unsigned int i = 0; i < server->nws; ++i) {
			struct nedm_tile *tile = output->workspaces[i]->focused_tile;
			if(tile) {
				struct nedm_tile *curr_tile = tile;
				do {
					if(curr_tile->view) {
						view_maximize(curr_tile->view, curr_tile);
					}
					curr_tile = curr_tile->next;
				} while(curr_tile != tile);
			}
		}
	}
	
	ipc_send_event(server,
	               "{\"event_name\":\"set_gap\",\"old_gap\":%d,\"new_gap\":%d}",
	               old_gap, server->gap_size);
}

void
keybinding_set_smart_gaps(struct nedm_server *server, int enable) {
	bool old_smart_gaps = server->smart_gaps;
	server->smart_gaps = enable ? true : false;
	
	// Re-position all windows with new smart gaps settings
	struct nedm_output *output;
	wl_list_for_each(output, &server->outputs, link) {
		for(unsigned int i = 0; i < server->nws; ++i) {
			struct nedm_tile *tile = output->workspaces[i]->focused_tile;
			if(tile) {
				struct nedm_tile *curr_tile = tile;
				do {
					if(curr_tile->view) {
						view_maximize(curr_tile->view, curr_tile);
					}
					curr_tile = curr_tile->next;
				} while(curr_tile != tile);
			}
		}
	}
	
	ipc_send_event(server,
	               "{\"event_name\":\"set_smart_gaps\",\"old_smart_gaps\":%s,\"new_smart_gaps\":%s}",
	               old_smart_gaps ? "true" : "false", server->smart_gaps ? "true" : "false");
}

void
keybinding_definemode(struct nedm_server *server, char *mode) {
	int length = 0;
	while(server->modes[length++] != NULL)
		;
	char **tmp = realloc(server->modes, (length + 1) * sizeof(char *));
	char **tmp2 = realloc(server->modecursors, (length + 1) * sizeof(char *));
	if(tmp == NULL || tmp2 == NULL) {
		if(tmp != NULL) {
			free(tmp);
		}
		if(tmp2 != NULL) {
			free(tmp2);
		}
		wlr_log(WLR_ERROR, "Could not allocate memory for storing modes.");
		return;
	}
	server->modes = tmp;
	server->modecursors = tmp2;
	server->modes[length] = NULL;
	server->modecursors[length] = NULL;

	server->modes[length - 1] = strdup(mode);
	ipc_send_event(server, "{\"event_name\":\"definemode\",\"mode\":\"%s\"}",
	               mode);
}

void
keybinding_definekey(struct nedm_server *server, struct keybinding *kb) {
	keybinding_list_push(server->keybindings, kb);
	ipc_send_event(server,
	               "{\"event_name\":\"definekey\",\"modifiers\":%d,\"key\":"
	               "%d,\"command\":\"%s\"}",
	               kb->modifiers, kb->key,
	               keybinding_action_string[kb->action]);
}

void
keybinding_set_background(struct nedm_server *server, float *bg) {
	ipc_send_event(server,
	               "{\"event_name\":\"background\",\"old_bg\":[%f,%f,%f],"
	               "\"new_bg\":[%f,%f,%f]}",
	               server->bg_color[0], server->bg_color[1],
	               server->bg_color[2], bg[0], bg[1], bg[2]);
	server->bg_color[0] = bg[0];
	server->bg_color[1] = bg[1];
	server->bg_color[2] = bg[2];
	struct nedm_output *it = NULL;
	wl_list_for_each(it, &server->outputs, link) {
		wlr_scene_rect_set_color(it->bg, server->bg_color);
	}
	wl_list_for_each(it, &server->disabled_outputs, link) {
		wlr_scene_rect_set_color(it->bg, server->bg_color);
	}
}

void
keybinding_switch_output(struct nedm_server *server, int output) {
	struct nedm_output *old_outp = server->curr_output;
	struct nedm_output *new_outp = output_from_num(server, output);
	if(new_outp != NULL) {
		set_output(server, new_outp);
		ipc_send_event(server,
		               "{\"event_name\":\"switch_output\",\"old_output\":"
		               "\"%s\",\"old_output_id\":%d,\"new_output\":\"%s\","
		               "\"new_output_id\":%d}",
		               old_outp->name, output_get_num(old_outp), new_outp->name,
		               output_get_num(new_outp));
		return;
	}
	message_printf(server->curr_output, "Output %d does not exist", output);
	return;
}

void
keybinding_move_view_to_tile(struct nedm_server *server, uint32_t view_id,
                             uint32_t tile_id, bool follow) {
	struct nedm_view *view = view_from_id(server, view_id);
	struct nedm_tile *tile = tile_from_id(server, tile_id);
	struct nedm_tile *old_tile = view ? view->tile : NULL;
	struct nedm_output *old_outp = view ? view->workspace->output : NULL;
	int old_workspace = view ? (int)view->workspace->num : -1;
	if(tile == NULL) {
		return;
	}
	if(view != NULL) {
		if(old_tile != NULL) {
			workspace_tile_update_view(old_tile, NULL);
			wl_list_remove(&view->link);
			keybinding_cycle_views(server, old_tile, 0, false, false);
			if(old_tile->view == NULL &&
			   old_tile == server->curr_output
			                   ->workspaces[server->curr_output->curr_workspace]
			                   ->focused_tile) {
				seat_set_focus(server->seat, NULL);
			}
		} else {
			wl_list_remove(&view->link);
		}
	}

	if(view != NULL) {
		struct nedm_workspace *ws = tile->workspace;
		view->workspace = ws;
		wl_list_insert(&ws->views, &view->link);
		wlr_scene_node_reparent(&view->scene_tree->node, ws->scene);
		view->tile = tile;
		tile->view = view;
		if(tile ==
		   server->curr_output->workspaces[server->curr_output->curr_workspace]
		       ->focused_tile) {
			seat_set_focus(server->seat, view);
		}
	} else {
		if(tile->view) {
			tile->view->tile = NULL;
			tile->view = NULL;
		}
	}
	if(follow) {
		keybinding_focus_tile(server, tile->id);
	} else {
		if(tile->view != NULL) {
			workspace_tile_update_view(tile, tile->view);
		}
	}
	ipc_send_event(
	    server,
	    "{\"event_name\":\"move_view\",\"view_id\":%d,\"old_output\":\"%s\","
	    "\"old_workspace\":\"%d\",\"old_tile\":\"%d\",\"new_output\":\"%s\","
	    "\"new_workspace\":\"%d\",\"new_tile\":\"%d\"}",
	    view_id, old_outp ? old_outp->name : "", old_workspace,
	    old_tile ? (int)old_tile->id : -1, server->curr_output->name,
	    tile->workspace->num, tile->id);
}

void
keybinding_move_view_to_output(struct nedm_server *server, int view_id,
                               int output_num, bool follow) {
	struct nedm_output *outp = output_from_num(server, output_num);
	if(outp != NULL) {
		keybinding_move_view_to_tile(
		    server, view_id,
		    outp->workspaces[outp->curr_workspace]->focused_tile->id, follow);
	} else {
		message_printf(server->curr_output, "Output number %d not found.",
		               output_num);
	}
}

void
keybinding_move_view_to_workspace(struct nedm_server *server, int view_id,
                                  uint32_t ws, bool follow) {
	if(ws >= server->nws) {
		message_printf(server->curr_output,
		               "Attempting to move view to workspace %d, but there are "
		               "only %d workspaces.",
		               ws + 1, server->nws);
		return;
	}
	keybinding_move_view_to_tile(
	    server, view_id, server->curr_output->workspaces[ws]->focused_tile->id,
	    follow);
}

void
merge_config(struct nedm_output_config *config_new,
             struct nedm_output_config *config_old) {
	if(config_new->status == OUTPUT_DEFAULT) {
		config_new->status = config_old->status;
	}
	if(config_new->pos.x == -1) {
		config_new->pos = config_old->pos;
	}
	if(config_new->refresh_rate == 0) {
		config_new->refresh_rate = config_old->refresh_rate;
	}
	if(config_new->priority == -1) {
		config_new->priority = config_old->priority;
	}
}

void
keybinding_configure_output(struct nedm_server *server,
                            struct nedm_output_config *cfg) {
	struct nedm_output_config *config;
	config = malloc(sizeof(struct nedm_output_config));
	if(config == NULL) {
		wlr_log(WLR_ERROR,
		        "Could not allocate memory for server configuration.");
		return;
	}

	*config = *cfg;
	config->output_name = strdup(cfg->output_name);

	struct nedm_output_config *it, *tmp;
	wl_list_for_each_safe(it, tmp, &server->output_config, link) {
		if(strcmp(config->output_name, it->output_name) == 0) {
			wl_list_remove(&it->link);
			merge_config(config, it);
			free(it->output_name);
			free(it);
		}
	}
	wl_list_insert(&server->output_config, &config->link);

	struct nedm_output *output, *tmp_output;
	wl_list_for_each_safe(output, tmp_output, &server->outputs, link) {
		if(strcmp(config->output_name, output->name) == 0) {
			int output_num = output_get_num(output);
			output_configure(server, output);
			ipc_send_event(server,
			               "{\"event_name\":\"configure_output\",\"output\":\"%"
			               "s\",\"output_id\":%d}",
			               cfg->output_name, output_num);
			return;
		}
	}
	wl_list_for_each_safe(output, tmp_output, &server->disabled_outputs, link) {
		if(strcmp(config->output_name, output->name) == 0) {
			output_configure(server, output);
			ipc_send_event(
			    output->server,
			    "{\"event_name\":\"configure_output\",\"output\":\"%s\"}",
			    cfg->output_name);
			return;
		}
	}
}

void
keybinding_configure_input(struct nedm_server *server,
                           struct nedm_input_config *cfg) {
	struct nedm_input_config *tcfg = input_manager_create_empty_input_config();
	if(tcfg == NULL) {
		wlr_log(WLR_ERROR,
		        "Could not allocate temporary empty input configuration.");
		return;
	}
	struct nedm_input_config *ocfg = input_manager_merge_input_configs(cfg, tcfg);
	free(tcfg);
	if(ocfg == NULL) {
		wlr_log(WLR_ERROR,
		        "Could not allocate input configuration for merging.");
		return;
	}
	wl_list_insert(&server->input_config, &ocfg->link);
	nedm_input_manager_configure(server);
	ipc_send_event(server,
	               "{\"event_name\":\"configure_input\",\"input\":\"%s\"}",
	               cfg->identifier);
}

void
keybinding_configure_message(struct nedm_server *server,
                             struct nedm_message_config *config) {
	if(config->font != NULL) {
		free(server->message_config.font);
		server->message_config.font = strdup(config->font);
	}
	if(config->display_time != -1) {
		server->message_config.display_time = config->display_time;
	}
	if(config->bg_color[0] != -1) {
		server->message_config.bg_color[0] = config->bg_color[0];
		server->message_config.bg_color[1] = config->bg_color[1];
		server->message_config.bg_color[2] = config->bg_color[2];
		server->message_config.bg_color[3] = config->bg_color[3];
	}
	if(config->fg_color[0] != -1) {
		server->message_config.fg_color[0] = config->fg_color[0];
		server->message_config.fg_color[1] = config->fg_color[1];
		server->message_config.fg_color[2] = config->fg_color[2];
		server->message_config.fg_color[3] = config->fg_color[3];
	}
	if(config->anchor != NEDM_MESSAGE_NOPT) {
		server->message_config.anchor = config->anchor;
	}
	if(config->enabled != -1) {
		server->message_config.enabled = config->enabled;
	}
	ipc_send_event(server, "{\"event_name\":\"configure_message\"}");
}

void
keybinding_configure_wallpaper(struct nedm_server *server,
                               struct nedm_wallpaper_config *config) {
	if(config->image_path != NULL && strcmp(config->image_path, "assets/nedm.png") != 0) {
		free(server->wallpaper_config.image_path);
		server->wallpaper_config.image_path = strdup(config->image_path);
	}
	server->wallpaper_config.mode = config->mode;
	server->wallpaper_config.bg_color[0] = config->bg_color[0];
	server->wallpaper_config.bg_color[1] = config->bg_color[1];
	server->wallpaper_config.bg_color[2] = config->bg_color[2];
	server->wallpaper_config.bg_color[3] = config->bg_color[3];
	
	// Refresh wallpapers on all outputs with new configuration
	nedm_wallpaper_refresh_all_outputs(server);
	
	ipc_send_event(server, "{\"event_name\":\"configure_wallpaper\"}");
}

void
set_cursor(bool enabled, struct nedm_seat *seat) {
	if(enabled == true) {
		seat->enable_cursor = true;
		wlr_cursor_set_xcursor(seat->cursor, seat->xcursor_manager,
		                       DEFAULT_XCURSOR);
	} else {
		seat->enable_cursor = false;
		wlr_cursor_unset_image(seat->cursor);
	}
}

/* Hint: see keybinding.h for details on "data" */
int
run_action(enum keybinding_action action, struct nedm_server *server,
           union keybinding_params data) {
	switch(action) {
	case KEYBINDING_QUIT:
		display_terminate(server);
		server->running = false;
		break;
	case KEYBINDING_CHANGE_TTY:
		return keybinding_switch_vt(server, data.u);
	case KEYBINDING_CURSOR:
		set_cursor(data.i, server->seat);
		break;
	case KEYBINDING_LAYOUT_FULLSCREEN:
		keybinding_workspace_fullscreen(server, data.us[0], data.us[1]);
		break;
	case KEYBINDING_SPLIT_HORIZONTAL:
		keybinding_split_horizontal(server, data.f);
		break;
	case KEYBINDING_SPLIT_VERTICAL:
		keybinding_split_vertical(server, data.f);
		break;
	case KEYBINDING_RUN_COMMAND: {
		int pid;
		if((pid = fork()) == 0) {
			setsid();
			sigset_t set;
			sigemptyset(&set);
			sigprocmask(SIG_SETMASK, &set, NULL);
			if(fork() == 0) {
				into_process(data.c);
			}
			_exit(0);
		} else if(pid > 0) {
			waitpid(pid, NULL, 0);
		}
	} break;
	case KEYBINDING_CYCLE_VIEWS:
		keybinding_cycle_views(server, NULL, data.us[1], data.us[0], true);
		break;
	case KEYBINDING_CYCLE_TILES:
		if(data.us[1] == 0) {
			keybinding_cycle_tiles(server, data.us[0]);
		} else {
			keybinding_focus_tile(server, data.us[1]);
		}
		break;
	case KEYBINDING_CYCLE_OUTPUT:
		keybinding_cycle_outputs(server, data.b, true);
		break;
	case KEYBINDING_SWITCH_WORKSPACE:
		keybinding_switch_ws(server, data.u);
		break;
	case KEYBINDING_SWITCH_OUTPUT:
		keybinding_switch_output(server, data.u);
		break;
	case KEYBINDING_SWITCH_MODE:
		uint32_t n_modes = 0;
		while(server->modes[n_modes] != NULL) {
			++n_modes;
		}
		if(data.u != server->seat->default_mode && data.u < n_modes &&
		   server->seat->num_pointers > 0) {
			wlr_seat_pointer_notify_clear_focus(server->seat->seat);
			if(server->seat->enable_cursor == true &&
			   server->modecursors[data.u] != NULL) {
				wlr_cursor_set_xcursor(server->seat->cursor,
				                       server->seat->xcursor_manager,
				                       server->modecursors[data.u]);
			}
		}
		server->seat->mode = data.u;
		break;
	case KEYBINDING_SWITCH_DEFAULT_MODE:
		ipc_send_event(server,
		               "{\"event_name\":\"switch_default_mode\",\"old_mode\":"
		               "\"%s\",\"mode\":\"%s\"}",
		               get_mode_name(server->modes, server->seat->default_mode),
		               get_mode_name(server->modes, data.u));
		uint32_t n_modes2 = 0;
		while(server->modes[n_modes2] != NULL) {
			++n_modes2;
		}
		if(data.u != server->seat->default_mode && data.u < n_modes2) {
			wlr_seat_pointer_notify_clear_focus(server->seat->seat);
			if(server->seat->enable_cursor == true &&
			   server->seat->num_pointers > 0) {
				if(server->modecursors[data.u] != NULL) {
					wlr_cursor_set_xcursor(server->seat->cursor,
					                       server->seat->xcursor_manager,
					                       server->modecursors[data.u]);
				} else {
					wlr_cursor_set_xcursor(server->seat->cursor,
					                       server->seat->xcursor_manager,
					                       DEFAULT_XCURSOR);
				}
			}
		}
		server->seat->mode = data.u;
		server->seat->default_mode = data.u;
		break;
	case KEYBINDING_NOOP:
		break;
	case KEYBINDING_SHOW_TIME:
		keybinding_show_time(server);
		break;
	case KEYBINDING_DUMP:
		keybinding_dump(server);
		break;
	case KEYBINDING_SHOW_INFO:
		keybinding_show_info(server);
		break;
	case KEYBINDING_DISPLAY_MESSAGE:
		keybinding_display_message(server, data.c);
		break;
	case KEYBINDING_SEND_CUSTOM_EVENT:
		keybinding_send_custom_event(server, data.c);
		break;
	case KEYBINDING_RESIZE_TILE_HORIZONTAL:
		resize_tile(server, data.is[0], 0, data.is[1]);
		break;
	case KEYBINDING_RESIZE_TILE_VERTICAL:
		resize_tile(server, 0, data.is[0], data.is[1]);
		break;
	case KEYBINDING_MOVE_TO_TILE: {
		struct nedm_view *view =
		    server->curr_output->workspaces[server->curr_output->curr_workspace]
		        ->focused_tile->view;
		keybinding_move_view_to_tile(server, view ? (int)view->id : -1,
		                             data.us[0], data.us[1] > 0);
		break;
	}
	case KEYBINDING_MOVE_TO_WORKSPACE: {
		struct nedm_view *view =
		    server->curr_output->workspaces[server->curr_output->curr_workspace]
		        ->focused_tile->view;
		keybinding_move_view_to_workspace(server, view ? (int)view->id : -1,
		                                  data.us[0], data.us[1] > 0);
		break;
	}
	case KEYBINDING_MOVE_TO_OUTPUT: {
		struct nedm_view *view =
		    server->curr_output->workspaces[server->curr_output->curr_workspace]
		        ->focused_tile->view;
		keybinding_move_view_to_output(server, view ? (int)view->id : -1,
		                               data.us[0], data.us[1] > 0);
		break;
	}
	case KEYBINDING_MOVE_VIEW_TO_TILE: {
		keybinding_move_view_to_tile(server, data.us[0], data.us[1],
		                             data.us[2] > 0);
		break;
	}
	case KEYBINDING_MOVE_VIEW_TO_WORKSPACE: {
		keybinding_move_view_to_workspace(server, data.us[0], data.us[1],
		                                  data.us[2] > 0);
		break;
	}
	case KEYBINDING_MOVE_VIEW_TO_OUTPUT: {
		keybinding_move_view_to_output(server, data.us[0], data.us[1],
		                               data.us[2] > 0);
		break;
	}
	case KEYBINDING_MERGE_LEFT: {
		struct nedm_tile *tile =
		    server->curr_output->workspaces[server->curr_output->curr_workspace]
		        ->focused_tile;
		if(data.u != 0) {
			tile = tile_from_id(server, data.u);
		}
		if(tile != NULL) {
			merge_tile_left(tile);
		}
		break;
	}
	case KEYBINDING_MERGE_RIGHT: {
		struct nedm_tile *tile =
		    server->curr_output->workspaces[server->curr_output->curr_workspace]
		        ->focused_tile;
		if(data.u != 0) {
			tile = tile_from_id(server, data.u);
		}
		if(tile != NULL) {
			merge_tile_right(tile);
		}
		break;
	}
	case KEYBINDING_MERGE_TOP: {
		struct nedm_tile *tile =
		    server->curr_output->workspaces[server->curr_output->curr_workspace]
		        ->focused_tile;
		if(data.u != 0) {
			tile = tile_from_id(server, data.u);
		}
		if(tile != NULL) {
			merge_tile_top(tile);
		}
		break;
	}
	case KEYBINDING_MERGE_BOTTOM: {
		struct nedm_tile *tile =
		    server->curr_output->workspaces[server->curr_output->curr_workspace]
		        ->focused_tile;
		if(data.u != 0) {
			tile = tile_from_id(server, data.u);
		}
		if(tile != NULL) {
			merge_tile_bottom(tile);
		}
		break;
	}
	case KEYBINDING_SWAP_LEFT: {
		swap_tile_left(server, data.us[0], data.us[1] == 1);
		break;
	}
	case KEYBINDING_SWAP_RIGHT: {
		swap_tile_right(server, data.us[0], data.us[1] == 1);
		break;
	}
	case KEYBINDING_SWAP_TOP: {
		swap_tile_top(server, data.us[0], data.us[1] == 1);
		break;
	}
	case KEYBINDING_SWAP_BOTTOM: {
		swap_tile_bottom(server, data.us[0], data.us[1] == 1);
		break;
	}
	case KEYBINDING_SWAP: {
		swap_tiles(tile_from_id(server, data.us[0]),
		           tile_from_id(server, data.us[1]), data.us[2] == 1);
		break;
	}
	case KEYBINDING_FOCUS_LEFT: {
		focus_tile_left(
		    server->curr_output->workspaces[server->curr_output->curr_workspace]
		        ->focused_tile);
		break;
	}
	case KEYBINDING_FOCUS_RIGHT: {
		focus_tile_right(
		    server->curr_output->workspaces[server->curr_output->curr_workspace]
		        ->focused_tile);
		break;
	}
	case KEYBINDING_FOCUS_TOP: {
		focus_tile_top(
		    server->curr_output->workspaces[server->curr_output->curr_workspace]
		        ->focused_tile);
		break;
	}
	case KEYBINDING_FOCUS_BOTTOM: {
		focus_tile_bottom(
		    server->curr_output->workspaces[server->curr_output->curr_workspace]
		        ->focused_tile);
		break;
	}
	case KEYBINDING_MOVE_VIEW_TO_CYCLE_OUTPUT: {
		keybinding_move_view_to_cycle_output(server, data.b);
		break;
	}
	case KEYBINDING_DEFINEKEY:
		keybinding_definekey(server, data.kb);
		break;
	case KEYBINDING_BACKGROUND:
		keybinding_set_background(server, data.color);
		break;
	case KEYBINDING_DEFINEMODE:
		keybinding_definemode(server, data.c);
		break;
	case KEYBINDING_WORKSPACES:
		keybinding_set_nws(server, data.i);
		break;
	case KEYBINDING_GAP:
		keybinding_set_gap(server, data.i);
		break;
	case KEYBINDING_SMART_GAPS:
		keybinding_set_smart_gaps(server, data.i);
		break;
	case KEYBINDING_CONFIGURE_OUTPUT:
		keybinding_configure_output(server, data.o_cfg);
		break;
	case KEYBINDING_CONFIGURE_MESSAGE:
		keybinding_configure_message(server, data.m_cfg);
		break;
	case KEYBINDING_CONFIGURE_INPUT:
		keybinding_configure_input(server, data.i_cfg);
		break;
	case KEYBINDING_CONFIGURE_WALLPAPER:
		keybinding_configure_wallpaper(server, data.wp_cfg);
		break;
	case KEYBINDING_CLOSE_VIEW:
		keybinding_close_view(
		    server->curr_output->workspaces[server->curr_output->curr_workspace]
		        ->focused_tile->view);
		break;
	case KEYBINDING_SETMODECURSOR:
		for(int i = 0; server->modes[i] != NULL; ++i) {
			if(strcmp(server->modes[i], data.cs[0]) == 0) {
				if(server->modecursors[i] != NULL) {
					free(server->modecursors[i]);
				}
				server->modecursors[i] = strdup(data.cs[1]);
			}
		}
		break;
	default: {
		wlr_log(WLR_ERROR,
		        "run_action was called with a value not present in \"enum "
		        "keybinding_action\". This should not happen.");
		return -1;
	}
	}
	return 0;
}
