aboutsummaryrefslogtreecommitdiff
path: root/rootston
diff options
context:
space:
mode:
Diffstat (limited to 'rootston')
-rw-r--r--rootston/bindings.c107
-rw-r--r--rootston/config.c670
-rw-r--r--rootston/cursor.c615
-rw-r--r--rootston/desktop.c1101
-rw-r--r--rootston/ini.c195
-rw-r--r--rootston/input.c145
-rw-r--r--rootston/keyboard.c349
-rw-r--r--rootston/layer_shell.c506
-rw-r--r--rootston/main.c81
-rw-r--r--rootston/meson.build30
-rw-r--r--rootston/output.c918
-rw-r--r--rootston/rootston.ini.example63
-rw-r--r--rootston/seat.c1473
-rw-r--r--rootston/switch.c26
-rw-r--r--rootston/text_input.c310
-rw-r--r--rootston/virtual_keyboard.c21
-rw-r--r--rootston/wl_shell.c295
-rw-r--r--rootston/xdg_shell.c565
-rw-r--r--rootston/xdg_shell_v6.c495
-rw-r--r--rootston/xwayland.c351
20 files changed, 8316 insertions, 0 deletions
diff --git a/rootston/bindings.c b/rootston/bindings.c
new file mode 100644
index 00000000..9fdbb33b
--- /dev/null
+++ b/rootston/bindings.c
@@ -0,0 +1,107 @@
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <wlr/util/log.h>
+#include "rootston/bindings.h"
+
+static bool outputs_enabled = true;
+
+static const char *exec_prefix = "exec ";
+
+static void double_fork_shell_cmd(const char *shell_cmd) {
+ pid_t pid = fork();
+ if (pid < 0) {
+ wlr_log(WLR_ERROR, "cannot execute binding command: fork() failed");
+ return;
+ }
+
+ if (pid == 0) {
+ pid = fork();
+ if (pid == 0) {
+ execl("/bin/sh", "/bin/sh", "-c", shell_cmd, NULL);
+ _exit(EXIT_FAILURE);
+ } else {
+ _exit(pid == -1);
+ }
+ }
+
+ int status;
+ while (waitpid(pid, &status, 0) < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ wlr_log_errno(WLR_ERROR, "waitpid() on first child failed");
+ return;
+ }
+
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+ return;
+ }
+
+ wlr_log(WLR_ERROR, "first child failed to fork command");
+}
+
+void execute_binding_command(struct roots_seat *seat,
+ struct roots_input *input, const char *command) {
+ if (strcmp(command, "exit") == 0) {
+ wl_display_terminate(input->server->wl_display);
+ } else if (strcmp(command, "close") == 0) {
+ struct roots_view *focus = roots_seat_get_focus(seat);
+ if (focus != NULL) {
+ view_close(focus);
+ }
+ } else if (strcmp(command, "fullscreen") == 0) {
+ struct roots_view *focus = roots_seat_get_focus(seat);
+ if (focus != NULL) {
+ bool is_fullscreen = focus->fullscreen_output != NULL;
+ view_set_fullscreen(focus, !is_fullscreen, NULL);
+ }
+ } else if (strcmp(command, "next_window") == 0) {
+ roots_seat_cycle_focus(seat);
+ } else if (strcmp(command, "alpha") == 0) {
+ struct roots_view *focus = roots_seat_get_focus(seat);
+ if (focus != NULL) {
+ view_cycle_alpha(focus);
+ }
+ } else if (strncmp(exec_prefix, command, strlen(exec_prefix)) == 0) {
+ const char *shell_cmd = command + strlen(exec_prefix);
+ double_fork_shell_cmd(shell_cmd);
+ } else if (strcmp(command, "maximize") == 0) {
+ struct roots_view *focus = roots_seat_get_focus(seat);
+ if (focus != NULL) {
+ view_maximize(focus, !focus->maximized);
+ }
+ } else if (strcmp(command, "nop") == 0) {
+ wlr_log(WLR_DEBUG, "nop command");
+ } else if (strcmp(command, "toggle_outputs") == 0) {
+ outputs_enabled = !outputs_enabled;
+ struct roots_output *output;
+ wl_list_for_each(output, &input->server->desktop->outputs, link) {
+ wlr_output_enable(output->wlr_output, outputs_enabled);
+ }
+ } else if (strcmp(command, "toggle_decoration_mode") == 0) {
+ struct roots_view *focus = roots_seat_get_focus(seat);
+ if (focus != NULL && focus->type == ROOTS_XDG_SHELL_VIEW) {
+ struct roots_xdg_toplevel_decoration *decoration =
+ focus->roots_xdg_surface->xdg_toplevel_decoration;
+ if (decoration != NULL) {
+ enum wlr_xdg_toplevel_decoration_v1_mode mode =
+ decoration->wlr_decoration->current_mode;
+ mode = mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE
+ ? WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE
+ : WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
+ wlr_xdg_toplevel_decoration_v1_set_mode(
+ decoration->wlr_decoration, mode);
+ }
+ }
+ } else if (strcmp(command, "break_pointer_constraint") == 0) {
+ struct wl_list *list = &input->seats;
+ struct roots_seat *seat;
+ wl_list_for_each(seat, list, link) {
+ roots_cursor_constrain(seat->cursor, NULL, NAN, NAN);
+ }
+ } else {
+ wlr_log(WLR_ERROR, "unknown binding command: %s", command);
+ }
+}
diff --git a/rootston/config.c b/rootston/config.c
new file mode 100644
index 00000000..198aaba6
--- /dev/null
+++ b/rootston/config.c
@@ -0,0 +1,670 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#include <assert.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/util/log.h>
+#include "rootston/config.h"
+#include "rootston/ini.h"
+#include "rootston/input.h"
+#include "rootston/keyboard.h"
+
+static void usage(const char *name, int ret) {
+ fprintf(stderr,
+ "usage: %s [-C <FILE>] [-E <COMMAND>]\n"
+ "\n"
+ " -C <FILE> Path to the configuration file\n"
+ " (default: rootston.ini).\n"
+ " See `rootston.ini.example` for config\n"
+ " file documentation.\n"
+ " -E <COMMAND> Command that will be ran at startup.\n"
+ " -D Enable damage tracking debugging.\n"
+ " -l <LEVEL> Set log verbosity, where,\n"
+ " 0:SILENT, 1:ERROR, 2:INFO, 3+:DEBUG\n"
+ " (default: DEBUG)\n",
+ name);
+
+ exit(ret);
+}
+
+static struct wlr_box *parse_geometry(const char *str) {
+ // format: {width}x{height}+{x}+{y}
+ if (strlen(str) > 255) {
+ wlr_log(WLR_ERROR, "cannot parse geometry string, too long");
+ return NULL;
+ }
+
+ char *buf = strdup(str);
+ struct wlr_box *box = calloc(1, sizeof(struct wlr_box));
+
+ bool has_width = false;
+ bool has_height = false;
+ bool has_x = false;
+ bool has_y = false;
+
+ char *pch = strtok(buf, "x+");
+ while (pch != NULL) {
+ errno = 0;
+ char *endptr;
+ long val = strtol(pch, &endptr, 0);
+
+ if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) ||
+ (errno != 0 && val == 0)) {
+ goto invalid_input;
+ }
+
+ if (endptr == pch) {
+ goto invalid_input;
+ }
+
+ if (!has_width) {
+ box->width = val;
+ has_width = true;
+ } else if (!has_height) {
+ box->height = val;
+ has_height = true;
+ } else if (!has_x) {
+ box->x = val;
+ has_x = true;
+ } else if (!has_y) {
+ box->y = val;
+ has_y = true;
+ } else {
+ break;
+ }
+ pch = strtok(NULL, "x+");
+ }
+
+ if (!has_width || !has_height) {
+ goto invalid_input;
+ }
+
+ free(buf);
+ return box;
+
+invalid_input:
+ wlr_log(WLR_ERROR, "could not parse geometry string: %s", str);
+ free(buf);
+ free(box);
+ return NULL;
+}
+
+static uint32_t parse_modifier(const char *symname) {
+ if (strcmp(symname, "Shift") == 0) {
+ return WLR_MODIFIER_SHIFT;
+ } else if (strcmp(symname, "Caps") == 0) {
+ return WLR_MODIFIER_CAPS;
+ } else if (strcmp(symname, "Ctrl") == 0) {
+ return WLR_MODIFIER_CTRL;
+ } else if (strcmp(symname, "Alt") == 0) {
+ return WLR_MODIFIER_ALT;
+ } else if (strcmp(symname, "Mod2") == 0) {
+ return WLR_MODIFIER_MOD2;
+ } else if (strcmp(symname, "Mod3") == 0) {
+ return WLR_MODIFIER_MOD3;
+ } else if (strcmp(symname, "Logo") == 0) {
+ return WLR_MODIFIER_LOGO;
+ } else if (strcmp(symname, "Mod5") == 0) {
+ return WLR_MODIFIER_MOD5;
+ } else {
+ return 0;
+ }
+}
+
+static bool parse_modeline(const char *s, drmModeModeInfo *mode) {
+ char hsync[16];
+ char vsync[16];
+ float fclock;
+
+ mode->type = DRM_MODE_TYPE_USERDEF;
+
+ if (sscanf(s, "%f %hd %hd %hd %hd %hd %hd %hd %hd %15s %15s",
+ &fclock,
+ &mode->hdisplay,
+ &mode->hsync_start,
+ &mode->hsync_end,
+ &mode->htotal,
+ &mode->vdisplay,
+ &mode->vsync_start,
+ &mode->vsync_end,
+ &mode->vtotal, hsync, vsync) != 11) {
+ return false;
+ }
+
+ mode->clock = fclock * 1000;
+ mode->vrefresh = mode->clock * 1000.0 * 1000.0
+ / mode->htotal / mode->vtotal;
+ if (strcasecmp(hsync, "+hsync") == 0) {
+ mode->flags |= DRM_MODE_FLAG_PHSYNC;
+ } else if (strcasecmp(hsync, "-hsync") == 0) {
+ mode->flags |= DRM_MODE_FLAG_NHSYNC;
+ } else {
+ return false;
+ }
+
+ if (strcasecmp(vsync, "+vsync") == 0) {
+ mode->flags |= DRM_MODE_FLAG_PVSYNC;
+ } else if (strcasecmp(vsync, "-vsync") == 0) {
+ mode->flags |= DRM_MODE_FLAG_NVSYNC;
+ } else {
+ return false;
+ }
+
+ snprintf(mode->name, sizeof(mode->name), "%dx%d@%d",
+ mode->hdisplay, mode->vdisplay, mode->vrefresh / 1000);
+
+ return true;
+}
+
+void add_binding_config(struct wl_list *bindings, const char* combination,
+ const char* command) {
+ struct roots_binding_config *bc =
+ calloc(1, sizeof(struct roots_binding_config));
+
+ xkb_keysym_t keysyms[ROOTS_KEYBOARD_PRESSED_KEYSYMS_CAP];
+ char *symnames = strdup(combination);
+ char *symname = strtok(symnames, "+");
+ while (symname) {
+ uint32_t modifier = parse_modifier(symname);
+ if (modifier != 0) {
+ bc->modifiers |= modifier;
+ } else {
+ xkb_keysym_t sym = xkb_keysym_from_name(symname,
+ XKB_KEYSYM_NO_FLAGS);
+ if (sym == XKB_KEY_NoSymbol) {
+ wlr_log(WLR_ERROR, "got unknown key binding symbol: %s",
+ symname);
+ free(bc);
+ bc = NULL;
+ break;
+ }
+ keysyms[bc->keysyms_len] = sym;
+ bc->keysyms_len++;
+ }
+ symname = strtok(NULL, "+");
+ }
+ free(symnames);
+
+ if (bc) {
+ wl_list_insert(bindings, &bc->link);
+ bc->command = strdup(command);
+ bc->keysyms = malloc(bc->keysyms_len * sizeof(xkb_keysym_t));
+ memcpy(bc->keysyms, keysyms, bc->keysyms_len * sizeof(xkb_keysym_t));
+ }
+}
+
+void add_switch_config(struct wl_list *switches, const char *switch_name, const char *action,
+ const char* command) {
+ struct roots_switch_config *sc = calloc(1, sizeof(struct roots_switch_config));
+
+ if (strcmp(switch_name, "tablet") == 0) {
+ sc->switch_type = WLR_SWITCH_TYPE_TABLET_MODE;
+ } else if (strcmp(switch_name, "lid") == 0) {
+ sc->switch_type = WLR_SWITCH_TYPE_LID;
+ } else {
+ sc->switch_type = -1;
+ sc->name = strdup(switch_name);
+ }
+ if (strcmp(action, "on") == 0) {
+ sc->switch_state = WLR_SWITCH_STATE_ON;
+ } else if (strcmp(action, "off") == 0) {
+ sc->switch_state = WLR_SWITCH_STATE_OFF;
+ } else if (strcmp(action, "toggle") == 0) {
+ sc->switch_state = WLR_SWITCH_STATE_TOGGLE;
+ } else {
+ wlr_log(WLR_ERROR, "Invalid switch action %s/n for switch %s:%s",
+ action, switch_name, action);
+ return;
+ }
+ sc->command = strdup(command);
+ wl_list_insert(switches, &sc->link);
+}
+
+static void config_handle_cursor(struct roots_config *config,
+ const char *seat_name, const char *name, const char *value) {
+ struct roots_cursor_config *cc;
+ bool found = false;
+ wl_list_for_each(cc, &config->cursors, link) {
+ if (strcmp(cc->seat, seat_name) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ cc = calloc(1, sizeof(struct roots_cursor_config));
+ cc->seat = strdup(seat_name);
+ wl_list_insert(&config->cursors, &cc->link);
+ }
+
+ if (strcmp(name, "map-to-output") == 0) {
+ free(cc->mapped_output);
+ cc->mapped_output = strdup(value);
+ } else if (strcmp(name, "geometry") == 0) {
+ free(cc->mapped_box);
+ cc->mapped_box = parse_geometry(value);
+ } else if (strcmp(name, "theme") == 0) {
+ free(cc->theme);
+ cc->theme = strdup(value);
+ } else if (strcmp(name, "default-image") == 0) {
+ free(cc->default_image);
+ cc->default_image = strdup(value);
+ } else {
+ wlr_log(WLR_ERROR, "got unknown cursor config: %s", name);
+ }
+}
+
+static void config_handle_keyboard(struct roots_config *config,
+ const char *device_name, const char *name, const char *value) {
+ struct roots_keyboard_config *kc;
+ bool found = false;
+ wl_list_for_each(kc, &config->keyboards, link) {
+ if (strcmp(kc->name, device_name) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ kc = calloc(1, sizeof(struct roots_keyboard_config));
+ kc->name = strdup(device_name);
+ wl_list_insert(&config->keyboards, &kc->link);
+ }
+
+ if (strcmp(name, "meta-key") == 0) {
+ kc->meta_key = parse_modifier(value);
+ if (kc->meta_key == 0) {
+ wlr_log(WLR_ERROR, "got unknown meta key: %s", name);
+ }
+ } else if (strcmp(name, "rules") == 0) {
+ kc->rules = strdup(value);
+ } else if (strcmp(name, "model") == 0) {
+ kc->model = strdup(value);
+ } else if (strcmp(name, "layout") == 0) {
+ kc->layout = strdup(value);
+ } else if (strcmp(name, "variant") == 0) {
+ kc->variant = strdup(value);
+ } else if (strcmp(name, "options") == 0) {
+ kc->options = strdup(value);
+ } else if (strcmp(name, "repeat-rate") == 0) {
+ kc->repeat_rate = strtol(value, NULL, 10);
+ } else if (strcmp(name, "repeat-delay") == 0) {
+ kc->repeat_delay = strtol(value, NULL, 10);
+ } else {
+ wlr_log(WLR_ERROR, "got unknown keyboard config: %s", name);
+ }
+}
+
+static const char *output_prefix = "output:";
+static const char *device_prefix = "device:";
+static const char *keyboard_prefix = "keyboard:";
+static const char *cursor_prefix = "cursor:";
+static const char *switch_prefix = "switch:";
+
+static int config_ini_handler(void *user, const char *section, const char *name,
+ const char *value) {
+ struct roots_config *config = user;
+ if (strcmp(section, "core") == 0) {
+ if (strcmp(name, "xwayland") == 0) {
+ if (strcasecmp(value, "true") == 0) {
+ config->xwayland = true;
+ } else if (strcasecmp(value, "immediate") == 0) {
+ config->xwayland = true;
+ config->xwayland_lazy = false;
+ } else if (strcasecmp(value, "false") == 0) {
+ config->xwayland = false;
+ } else {
+ wlr_log(WLR_ERROR, "got unknown xwayland value: %s", value);
+ }
+ } else {
+ wlr_log(WLR_ERROR, "got unknown core config: %s", name);
+ }
+ } else if (strncmp(output_prefix, section, strlen(output_prefix)) == 0) {
+ const char *output_name = section + strlen(output_prefix);
+ struct roots_output_config *oc;
+ bool found = false;
+
+ wl_list_for_each(oc, &config->outputs, link) {
+ if (strcmp(oc->name, output_name) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ oc = calloc(1, sizeof(struct roots_output_config));
+ oc->name = strdup(output_name);
+ oc->transform = WL_OUTPUT_TRANSFORM_NORMAL;
+ oc->scale = 1;
+ oc->enable = true;
+ wl_list_init(&oc->modes);
+ wl_list_insert(&config->outputs, &oc->link);
+ }
+
+ if (strcmp(name, "enable") == 0) {
+ if (strcasecmp(value, "true") == 0) {
+ oc->enable = true;
+ } else if (strcasecmp(value, "false") == 0) {
+ oc->enable = false;
+ } else {
+ wlr_log(WLR_ERROR, "got invalid output enable value: %s", value);
+ }
+ } else if (strcmp(name, "x") == 0) {
+ oc->x = strtol(value, NULL, 10);
+ } else if (strcmp(name, "y") == 0) {
+ oc->y = strtol(value, NULL, 10);
+ } else if (strcmp(name, "scale") == 0) {
+ oc->scale = strtof(value, NULL);
+ assert(oc->scale > 0);
+ } else if (strcmp(name, "rotate") == 0) {
+ if (strcmp(value, "normal") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_NORMAL;
+ } else if (strcmp(value, "90") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_90;
+ } else if (strcmp(value, "180") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_180;
+ } else if (strcmp(value, "270") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_270;
+ } else if (strcmp(value, "flipped") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED;
+ } else if (strcmp(value, "flipped-90") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED_90;
+ } else if (strcmp(value, "flipped-180") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED_180;
+ } else if (strcmp(value, "flipped-270") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED_270;
+ } else {
+ wlr_log(WLR_ERROR, "got unknown transform value: %s", value);
+ }
+ } else if (strcmp(name, "mode") == 0) {
+ char *end;
+ oc->mode.width = strtol(value, &end, 10);
+ assert(*end == 'x');
+ ++end;
+ oc->mode.height = strtol(end, &end, 10);
+ if (*end) {
+ assert(*end == '@');
+ ++end;
+ oc->mode.refresh_rate = strtof(end, &end);
+ assert(strcmp("Hz", end) == 0);
+ }
+ wlr_log(WLR_DEBUG, "Configured output %s with mode %dx%d@%f",
+ oc->name, oc->mode.width, oc->mode.height,
+ oc->mode.refresh_rate);
+ } else if (strcmp(name, "modeline") == 0) {
+ struct roots_output_mode_config *mode = calloc(1, sizeof(*mode));
+
+ if (parse_modeline(value, &mode->info)) {
+ wl_list_insert(&oc->modes, &mode->link);
+ } else {
+ free(mode);
+ wlr_log(WLR_ERROR, "Invalid modeline: %s", value);
+ }
+ }
+ } else if (strncmp(cursor_prefix, section, strlen(cursor_prefix)) == 0) {
+ const char *seat_name = section + strlen(cursor_prefix);
+ config_handle_cursor(config, seat_name, name, value);
+ } else if (strcmp(section, "cursor") == 0) {
+ config_handle_cursor(config, ROOTS_CONFIG_DEFAULT_SEAT_NAME, name,
+ value);
+ } else if (strncmp(device_prefix, section, strlen(device_prefix)) == 0) {
+ const char *device_name = section + strlen(device_prefix);
+
+ struct roots_device_config *dc;
+ bool found = false;
+ wl_list_for_each(dc, &config->devices, link) {
+ if (strcmp(dc->name, device_name) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ dc = calloc(1, sizeof(struct roots_device_config));
+ dc->name = strdup(device_name);
+ dc->seat = strdup(ROOTS_CONFIG_DEFAULT_SEAT_NAME);
+ wl_list_insert(&config->devices, &dc->link);
+ }
+
+ if (strcmp(name, "map-to-output") == 0) {
+ free(dc->mapped_output);
+ dc->mapped_output = strdup(value);
+ } else if (strcmp(name, "geometry") == 0) {
+ free(dc->mapped_box);
+ dc->mapped_box = parse_geometry(value);
+ } else if (strcmp(name, "seat") == 0) {
+ free(dc->seat);
+ dc->seat = strdup(value);
+ } else if (strcmp(name, "tap_enabled") == 0) {
+ if (strcasecmp(value, "true") == 0) {
+ dc->tap_enabled = true;
+ } else if (strcasecmp(value, "false") == 0) {
+ dc->tap_enabled = false;
+ } else {
+ wlr_log(WLR_ERROR,
+ "got unknown tap_enabled value: %s",
+ value);
+ }
+ } else {
+ wlr_log(WLR_ERROR, "got unknown device config: %s", name);
+ }
+ } else if (strcmp(section, "keyboard") == 0) {
+ config_handle_keyboard(config, "", name, value);
+ } else if (strncmp(keyboard_prefix,
+ section, strlen(keyboard_prefix)) == 0) {
+ const char *device_name = section + strlen(keyboard_prefix);
+ config_handle_keyboard(config, device_name, name, value);
+ } else if (strcmp(section, "bindings") == 0) {
+ add_binding_config(&config->bindings, name, value);
+ } else if (strncmp(switch_prefix, section, strlen(switch_prefix)) == 0) {
+ const char *switch_name = section + strlen(switch_prefix);
+ add_switch_config(&config->switches, switch_name, name, value);
+ } else {
+ wlr_log(WLR_ERROR, "got unknown config section: %s", section);
+ }
+
+ return 1;
+}
+
+struct roots_config *roots_config_create_from_args(int argc, char *argv[]) {
+ struct roots_config *config = calloc(1, sizeof(struct roots_config));
+ if (config == NULL) {
+ return NULL;
+ }
+
+ config->xwayland = true;
+ config->xwayland_lazy = true;
+ wl_list_init(&config->outputs);
+ wl_list_init(&config->devices);
+ wl_list_init(&config->keyboards);
+ wl_list_init(&config->cursors);
+ wl_list_init(&config->bindings);
+ wl_list_init(&config->switches);
+
+ int c;
+ unsigned int log_verbosity = WLR_DEBUG;
+ while ((c = getopt(argc, argv, "C:E:hDl:")) != -1) {
+ switch (c) {
+ case 'C':
+ config->config_path = strdup(optarg);
+ break;
+ case 'E':
+ config->startup_cmd = strdup(optarg);
+ break;
+ case 'D':
+ config->debug_damage_tracking = true;
+ break;
+ case 'l':
+ log_verbosity = strtoul(optarg, NULL, 10);
+ if (log_verbosity >= WLR_LOG_IMPORTANCE_LAST) {
+ log_verbosity = WLR_LOG_IMPORTANCE_LAST - 1;
+ }
+ break;
+ case 'h':
+ case '?':
+ usage(argv[0], c != 'h');
+ }
+ }
+ wlr_log_init(log_verbosity, NULL);
+
+ if (!config->config_path) {
+ // get the config path from the current directory
+ char cwd[MAXPATHLEN];
+ if (getcwd(cwd, sizeof(cwd)) != NULL) {
+ char buf[MAXPATHLEN];
+ if (snprintf(buf, MAXPATHLEN, "%s/%s", cwd, "rootston.ini") >= MAXPATHLEN) {
+ wlr_log(WLR_ERROR, "config path too long");
+ exit(1);
+ }
+ config->config_path = strdup(buf);
+ } else {
+ wlr_log(WLR_ERROR, "could not get cwd");
+ exit(1);
+ }
+ }
+
+ int result = ini_parse(config->config_path, config_ini_handler, config);
+
+ if (result == -1) {
+ wlr_log(WLR_DEBUG, "No config file found. Using sensible defaults.");
+ add_binding_config(&config->bindings, "Logo+Shift+E", "exit");
+ add_binding_config(&config->bindings, "Ctrl+q", "close");
+ add_binding_config(&config->bindings, "Alt+Tab", "next_window");
+ add_binding_config(&config->bindings, "Logo+Escape", "break_pointer_constraint");
+ struct roots_keyboard_config *kc =
+ calloc(1, sizeof(struct roots_keyboard_config));
+ kc->meta_key = WLR_MODIFIER_LOGO;
+ kc->name = strdup("");
+ wl_list_insert(&config->keyboards, &kc->link);
+ } else if (result == -2) {
+ wlr_log(WLR_ERROR, "Could not allocate memory to parse config file");
+ exit(1);
+ } else if (result != 0) {
+ wlr_log(WLR_ERROR, "Could not parse config file");
+ exit(1);
+ }
+
+ return config;
+}
+
+void roots_config_destroy(struct roots_config *config) {
+ struct roots_output_config *oc, *otmp = NULL;
+ wl_list_for_each_safe(oc, otmp, &config->outputs, link) {
+ struct roots_output_mode_config *omc, *omctmp = NULL;
+ wl_list_for_each_safe(omc, omctmp, &oc->modes, link) {
+ free(omc);
+ }
+ free(oc->name);
+ free(oc);
+ }
+
+ struct roots_device_config *dc, *dtmp = NULL;
+ wl_list_for_each_safe(dc, dtmp, &config->devices, link) {
+ free(dc->name);
+ free(dc->seat);
+ free(dc->mapped_output);
+ free(dc->mapped_box);
+ free(dc);
+ }
+
+ struct roots_keyboard_config *kc, *ktmp = NULL;
+ wl_list_for_each_safe(kc, ktmp, &config->keyboards, link) {
+ free(kc->name);
+ free(kc->rules);
+ free(kc->model);
+ free(kc->layout);
+ free(kc->variant);
+ free(kc->options);
+ free(kc);
+ }
+
+ struct roots_cursor_config *cc, *ctmp = NULL;
+ wl_list_for_each_safe(cc, ctmp, &config->cursors, link) {
+ free(cc->seat);
+ free(cc->mapped_output);
+ free(cc->mapped_box);
+ free(cc->theme);
+ free(cc->default_image);
+ free(cc);
+ }
+
+ struct roots_binding_config *bc, *btmp = NULL;
+ wl_list_for_each_safe(bc, btmp, &config->bindings, link) {
+ free(bc->keysyms);
+ free(bc->command);
+ free(bc);
+ }
+
+ free(config->config_path);
+ free(config);
+}
+
+struct roots_output_config *roots_config_get_output(struct roots_config *config,
+ struct wlr_output *output) {
+ char name[88];
+ snprintf(name, sizeof(name), "%s %s %s", output->make, output->model,
+ output->serial);
+
+ struct roots_output_config *oc;
+ wl_list_for_each(oc, &config->outputs, link) {
+ if (strcmp(oc->name, output->name) == 0 ||
+ strcmp(oc->name, name) == 0) {
+ return oc;
+ }
+ }
+
+ return NULL;
+}
+
+struct roots_device_config *roots_config_get_device(struct roots_config *config,
+ struct wlr_input_device *device) {
+ struct roots_device_config *d_config;
+ wl_list_for_each(d_config, &config->devices, link) {
+ if (strcmp(d_config->name, device->name) == 0) {
+ return d_config;
+ }
+ }
+
+ return NULL;
+}
+
+struct roots_keyboard_config *roots_config_get_keyboard(
+ struct roots_config *config, struct wlr_input_device *device) {
+ const char *device_name = "";
+ if (device != NULL) {
+ device_name = device->name;
+ }
+
+ struct roots_keyboard_config *kc;
+ wl_list_for_each(kc, &config->keyboards, link) {
+ if (strcmp(kc->name, device_name) == 0) {
+ return kc;
+ }
+ }
+
+ return NULL;
+}
+
+struct roots_cursor_config *roots_config_get_cursor(struct roots_config *config,
+ const char *seat_name) {
+ if (seat_name == NULL) {
+ seat_name = ROOTS_CONFIG_DEFAULT_SEAT_NAME;
+ }
+
+ struct roots_cursor_config *cc;
+ wl_list_for_each(cc, &config->cursors, link) {
+ if (strcmp(cc->seat, seat_name) == 0) {
+ return cc;
+ }
+ }
+
+ return NULL;
+}
diff --git a/rootston/cursor.c b/rootston/cursor.c
new file mode 100644
index 00000000..b9ded30e
--- /dev/null
+++ b/rootston/cursor.c
@@ -0,0 +1,615 @@
+#define _XOPEN_SOURCE 700
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <wlr/types/wlr_region.h>
+#include <wlr/types/wlr_xcursor_manager.h>
+#include <wlr/util/edges.h>
+#include <wlr/util/log.h>
+#include <wlr/util/region.h>
+#ifdef __linux__
+#include <linux/input-event-codes.h>
+#elif __FreeBSD__
+#include <dev/evdev/input-event-codes.h>
+#endif
+#include "rootston/cursor.h"
+#include "rootston/desktop.h"
+#include "rootston/view.h"
+#include "rootston/xcursor.h"
+
+struct roots_cursor *roots_cursor_create(struct roots_seat *seat) {
+ struct roots_cursor *cursor = calloc(1, sizeof(struct roots_cursor));
+ if (!cursor) {
+ return NULL;
+ }
+ cursor->cursor = wlr_cursor_create();
+ if (!cursor->cursor) {
+ free(cursor);
+ return NULL;
+ }
+ cursor->default_xcursor = ROOTS_XCURSOR_DEFAULT;
+ return cursor;
+}
+
+void roots_cursor_destroy(struct roots_cursor *cursor) {
+ // TODO
+}
+
+static void seat_view_deco_motion(struct roots_seat_view *view, double deco_sx, double deco_sy) {
+ struct roots_cursor *cursor = view->seat->cursor;
+
+ double sx = deco_sx;
+ double sy = deco_sy;
+ if (view->has_button_grab) {
+ sx = view->grab_sx;
+ sy = view->grab_sy;
+ }
+
+ enum roots_deco_part parts = view_get_deco_part(view->view, sx, sy);
+
+ bool is_titlebar = (parts & ROOTS_DECO_PART_TITLEBAR);
+ uint32_t edges = 0;
+ if (parts & ROOTS_DECO_PART_LEFT_BORDER) {
+ edges |= WLR_EDGE_LEFT;
+ } else if (parts & ROOTS_DECO_PART_RIGHT_BORDER) {
+ edges |= WLR_EDGE_RIGHT;
+ } else if (parts & ROOTS_DECO_PART_BOTTOM_BORDER) {
+ edges |= WLR_EDGE_BOTTOM;
+ } else if (parts & ROOTS_DECO_PART_TOP_BORDER) {
+ edges |= WLR_EDGE_TOP;
+ }
+
+ if (view->has_button_grab) {
+ if (is_titlebar) {
+ roots_seat_begin_move(view->seat, view->view);
+ } else if (edges) {
+ roots_seat_begin_resize(view->seat, view->view, edges);
+ }
+ view->has_button_grab = false;
+ } else {
+ if (is_titlebar) {
+ wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager,
+ cursor->default_xcursor, cursor->cursor);
+ } else if (edges) {
+ const char *resize_name = wlr_xcursor_get_resize_name(edges);
+ wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager,
+ resize_name, cursor->cursor);
+ }
+ }
+}
+
+static void seat_view_deco_leave(struct roots_seat_view *view) {
+ struct roots_cursor *cursor = view->seat->cursor;
+ wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager,
+ cursor->default_xcursor, cursor->cursor);
+ view->has_button_grab = false;
+}
+
+static void seat_view_deco_button(struct roots_seat_view *view, double sx,
+ double sy, uint32_t button, uint32_t state) {
+ if (button == BTN_LEFT && state == WLR_BUTTON_PRESSED) {
+ view->has_button_grab = true;
+ view->grab_sx = sx;
+ view->grab_sy = sy;
+ } else {
+ view->has_button_grab = false;
+ }
+
+ enum roots_deco_part parts = view_get_deco_part(view->view, sx, sy);
+ if (state == WLR_BUTTON_RELEASED && (parts & ROOTS_DECO_PART_TITLEBAR)) {
+ struct roots_cursor *cursor = view->seat->cursor;
+ wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager,
+ cursor->default_xcursor, cursor->cursor);
+ }
+}
+
+static void roots_passthrough_cursor(struct roots_cursor *cursor,
+ int64_t time) {
+ bool focus_changed;
+ double sx, sy;
+ struct roots_view *view = NULL;
+ struct roots_seat *seat = cursor->seat;
+ struct roots_desktop *desktop = seat->input->server->desktop;
+ struct wlr_surface *surface = desktop_surface_at(desktop,
+ cursor->cursor->x, cursor->cursor->y, &sx, &sy, &view);
+
+ struct wl_client *client = NULL;
+ if (surface) {
+ client = wl_resource_get_client(surface->resource);
+ }
+
+ if (surface && !roots_seat_allow_input(seat, surface->resource)) {
+ return;
+ }
+
+ if (cursor->cursor_client != client) {
+ wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager,
+ cursor->default_xcursor, cursor->cursor);
+ cursor->cursor_client = client;
+ }
+
+ if (view) {
+ struct roots_seat_view *seat_view =
+ roots_seat_view_from_view(seat, view);
+
+ if (cursor->pointer_view &&
+ !cursor->wlr_surface && (surface || seat_view != cursor->pointer_view)) {
+ seat_view_deco_leave(cursor->pointer_view);
+ }
+
+ cursor->pointer_view = seat_view;
+
+ if (!surface) {
+ seat_view_deco_motion(seat_view, sx, sy);
+ }
+ } else {
+ cursor->pointer_view = NULL;
+ }
+
+ cursor->wlr_surface = surface;
+
+ if (surface) {
+ focus_changed = (seat->seat->pointer_state.focused_surface != surface);
+ wlr_seat_pointer_notify_enter(seat->seat, surface, sx, sy);
+ if (!focus_changed && time > 0) {
+ wlr_seat_pointer_notify_motion(seat->seat, time, sx, sy);
+ }
+ } else {
+ wlr_seat_pointer_clear_focus(seat->seat);
+ }
+
+ struct roots_drag_icon *drag_icon;
+ wl_list_for_each(drag_icon, &seat->drag_icons, link) {
+ roots_drag_icon_update_position(drag_icon);
+ }
+}
+
+void roots_cursor_update_focus(struct roots_cursor *cursor) {
+ roots_passthrough_cursor(cursor, -1);
+}
+
+void roots_cursor_update_position(struct roots_cursor *cursor,
+ uint32_t time) {
+ struct roots_seat *seat = cursor->seat;
+ struct roots_view *view;
+ switch (cursor->mode) {
+ case ROOTS_CURSOR_PASSTHROUGH:
+ roots_passthrough_cursor(cursor, time);
+ break;
+ case ROOTS_CURSOR_MOVE:
+ view = roots_seat_get_focus(seat);
+ if (view != NULL) {
+ double dx = cursor->cursor->x - cursor->offs_x;
+ double dy = cursor->cursor->y - cursor->offs_y;
+ view_move(view, cursor->view_x + dx,
+ cursor->view_y + dy);
+ }
+ break;
+ case ROOTS_CURSOR_RESIZE:
+ view = roots_seat_get_focus(seat);
+ if (view != NULL) {
+ double dx = cursor->cursor->x - cursor->offs_x;
+ double dy = cursor->cursor->y - cursor->offs_y;
+ double x = view->box.x;
+ double y = view->box.y;
+ int width = cursor->view_width;
+ int height = cursor->view_height;
+ if (cursor->resize_edges & WLR_EDGE_TOP) {
+ y = cursor->view_y + dy;
+ height -= dy;
+ if (height < 1) {
+ y += height;
+ }
+ } else if (cursor->resize_edges & WLR_EDGE_BOTTOM) {
+ height += dy;
+ }
+ if (cursor->resize_edges & WLR_EDGE_LEFT) {
+ x = cursor->view_x + dx;
+ width -= dx;
+ if (width < 1) {
+ x += width;
+ }
+ } else if (cursor->resize_edges & WLR_EDGE_RIGHT) {
+ width += dx;
+ }
+ view_move_resize(view, x, y,
+ width < 1 ? 1 : width,
+ height < 1 ? 1 : height);
+ }
+ break;
+ case ROOTS_CURSOR_ROTATE:
+ view = roots_seat_get_focus(seat);
+ if (view != NULL) {
+ int ox = view->box.x + view->wlr_surface->current.width/2,
+ oy = view->box.y + view->wlr_surface->current.height/2;
+ int ux = cursor->offs_x - ox,
+ uy = cursor->offs_y - oy;
+ int vx = cursor->cursor->x - ox,
+ vy = cursor->cursor->y - oy;
+ float angle = atan2(ux*vy - uy*vx, vx*ux + vy*uy);
+ int steps = 12;
+ angle = round(angle/M_PI*steps) / (steps/M_PI);
+ view_rotate(view, cursor->view_rotation + angle);
+ }
+ break;
+ }
+}
+
+static void roots_cursor_press_button(struct roots_cursor *cursor,
+ struct wlr_input_device *device, uint32_t time, uint32_t button,
+ uint32_t state, double lx, double ly) {
+ struct roots_seat *seat = cursor->seat;
+ struct roots_desktop *desktop = seat->input->server->desktop;
+
+ bool is_touch = device->type == WLR_INPUT_DEVICE_TOUCH;
+
+ double sx, sy;
+ struct roots_view *view;
+ struct wlr_surface *surface = desktop_surface_at(desktop,
+ lx, ly, &sx, &sy, &view);
+
+ if (state == WLR_BUTTON_PRESSED && view &&
+ roots_seat_has_meta_pressed(seat)) {
+ roots_seat_set_focus(seat, view);
+
+ uint32_t edges;
+ switch (button) {
+ case BTN_LEFT:
+ roots_seat_begin_move(seat, view);
+ break;
+ case BTN_RIGHT:
+ edges = 0;
+ if (sx < view->wlr_surface->current.width/2) {
+ edges |= WLR_EDGE_LEFT;
+ } else {
+ edges |= WLR_EDGE_RIGHT;
+ }
+ if (sy < view->wlr_surface->current.height/2) {
+ edges |= WLR_EDGE_TOP;
+ } else {
+ edges |= WLR_EDGE_BOTTOM;
+ }
+ roots_seat_begin_resize(seat, view, edges);
+ break;
+ case BTN_MIDDLE:
+ roots_seat_begin_rotate(seat, view);
+ break;
+ }
+ } else {
+ if (view && !surface && cursor->pointer_view) {
+ seat_view_deco_button(cursor->pointer_view,
+ sx, sy, button, state);
+ }
+
+ if (state == WLR_BUTTON_RELEASED &&
+ cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ cursor->mode = ROOTS_CURSOR_PASSTHROUGH;
+ }
+
+ if (state == WLR_BUTTON_PRESSED) {
+ if (view) {
+ roots_seat_set_focus(seat, view);
+ }
+ if (surface && wlr_surface_is_layer_surface(surface)) {
+ struct wlr_layer_surface_v1 *layer =
+ wlr_layer_surface_v1_from_wlr_surface(surface);
+ if (layer->current.keyboard_interactive) {
+ roots_seat_set_focus_layer(seat, layer);
+ }
+ }
+ }
+ }
+
+ if (!is_touch) {
+ wlr_seat_pointer_notify_button(seat->seat, time, button, state);
+ }
+}
+
+void roots_cursor_handle_motion(struct roots_cursor *cursor,
+ struct wlr_event_pointer_motion *event) {
+ double dx = event->delta_x;
+ double dy = event->delta_y;
+
+ if (cursor->active_constraint) {
+ struct roots_view *view = cursor->pointer_view->view;
+ assert(view);
+
+ // TODO: handle rotated views
+ if (view->rotation == 0.0) {
+ double lx1 = cursor->cursor->x;
+ double ly1 = cursor->cursor->y;
+
+ double lx2 = lx1 + dx;
+ double ly2 = ly1 + dy;
+
+ double sx1 = lx1 - view->box.x;
+ double sy1 = ly1 - view->box.y;
+
+ double sx2 = lx2 - view->box.x;
+ double sy2 = ly2 - view->box.y;
+
+ double sx2_confined, sy2_confined;
+ if (!wlr_region_confine(&cursor->confine, sx1, sy1, sx2, sy2,
+ &sx2_confined, &sy2_confined)) {
+ return;
+ }
+
+ dx = sx2_confined - sx1;
+ dy = sy2_confined - sy1;
+ }
+ }
+
+ wlr_cursor_move(cursor->cursor, event->device, dx, dy);
+ roots_cursor_update_position(cursor, event->time_msec);
+}
+
+void roots_cursor_handle_motion_absolute(struct roots_cursor *cursor,
+ struct wlr_event_pointer_motion_absolute *event) {
+ double lx, ly;
+ wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device, event->x,
+ event->y, &lx, &ly);
+
+ if (cursor->pointer_view) {
+ struct roots_view *view = cursor->pointer_view->view;
+
+ if (cursor->active_constraint &&
+ !pixman_region32_contains_point(&cursor->confine,
+ floor(lx - view->box.x), floor(ly - view->box.y), NULL)) {
+ return;
+ }
+ }
+
+ wlr_cursor_warp_closest(cursor->cursor, event->device, lx, ly);
+ roots_cursor_update_position(cursor, event->time_msec);
+}
+
+void roots_cursor_handle_button(struct roots_cursor *cursor,
+ struct wlr_event_pointer_button *event) {
+ roots_cursor_press_button(cursor, event->device, event->time_msec,
+ event->button, event->state, cursor->cursor->x, cursor->cursor->y);
+}
+
+void roots_cursor_handle_axis(struct roots_cursor *cursor,
+ struct wlr_event_pointer_axis *event) {
+ wlr_seat_pointer_notify_axis(cursor->seat->seat, event->time_msec,
+ event->orientation, event->delta, event->delta_discrete, event->source);
+}
+
+void roots_cursor_handle_touch_down(struct roots_cursor *cursor,
+ struct wlr_event_touch_down *event) {
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ double lx, ly;
+ wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device,
+ event->x, event->y, &lx, &ly);
+
+ double sx, sy;
+ struct wlr_surface *surface = desktop_surface_at(
+ desktop, lx, ly, &sx, &sy, NULL);
+
+ uint32_t serial = 0;
+ if (surface && roots_seat_allow_input(cursor->seat, surface->resource)) {
+ serial = wlr_seat_touch_notify_down(cursor->seat->seat, surface,
+ event->time_msec, event->touch_id, sx, sy);
+ }
+
+ if (serial && wlr_seat_touch_num_points(cursor->seat->seat) == 1) {
+ cursor->seat->touch_id = event->touch_id;
+ cursor->seat->touch_x = lx;
+ cursor->seat->touch_y = ly;
+ roots_cursor_press_button(cursor, event->device, event->time_msec,
+ BTN_LEFT, 1, lx, ly);
+ }
+}
+
+void roots_cursor_handle_touch_up(struct roots_cursor *cursor,
+ struct wlr_event_touch_up *event) {
+ struct wlr_touch_point *point =
+ wlr_seat_touch_get_point(cursor->seat->seat, event->touch_id);
+ if (!point) {
+ return;
+ }
+
+ if (wlr_seat_touch_num_points(cursor->seat->seat) == 1) {
+ roots_cursor_press_button(cursor, event->device, event->time_msec,
+ BTN_LEFT, 0, cursor->seat->touch_x, cursor->seat->touch_y);
+ }
+
+ wlr_seat_touch_notify_up(cursor->seat->seat, event->time_msec,
+ event->touch_id);
+}
+
+void roots_cursor_handle_touch_motion(struct roots_cursor *cursor,
+ struct wlr_event_touch_motion *event) {
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ struct wlr_touch_point *point =
+ wlr_seat_touch_get_point(cursor->seat->seat, event->touch_id);
+ if (!point) {
+ return;
+ }
+
+ double lx, ly;
+ wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device,
+ event->x, event->y, &lx, &ly);
+
+ double sx, sy;
+ struct wlr_surface *surface = desktop_surface_at(
+ desktop, lx, ly, &sx, &sy, NULL);
+
+ if (surface && roots_seat_allow_input(cursor->seat, surface->resource)) {
+ wlr_seat_touch_point_focus(cursor->seat->seat, surface,
+ event->time_msec, event->touch_id, sx, sy);
+ wlr_seat_touch_notify_motion(cursor->seat->seat, event->time_msec,
+ event->touch_id, sx, sy);
+ } else {
+ wlr_seat_touch_point_clear_focus(cursor->seat->seat, event->time_msec,
+ event->touch_id);
+ }
+
+ if (event->touch_id == cursor->seat->touch_id) {
+ cursor->seat->touch_x = lx;
+ cursor->seat->touch_y = ly;
+ }
+}
+
+void roots_cursor_handle_tool_axis(struct roots_cursor *cursor,
+ struct wlr_event_tablet_tool_axis *event) {
+ double x = NAN, y = NAN;
+ if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X) &&
+ (event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) {
+ x = event->x;
+ y = event->y;
+ } else if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X)) {
+ x = event->x;
+ } else if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) {
+ y = event->y;
+ }
+
+ double lx, ly;
+ wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device,
+ x, y, &lx, &ly);
+
+
+ if (cursor->pointer_view) {
+ struct roots_view *view = cursor->pointer_view->view;
+
+ if (cursor->active_constraint &&
+ !pixman_region32_contains_point(&cursor->confine,
+ floor(lx - view->box.x), floor(ly - view->box.y), NULL)) {
+ return;
+ }
+ }
+
+ wlr_cursor_warp_closest(cursor->cursor, event->device, lx, ly);
+ roots_cursor_update_position(cursor, event->time_msec);
+}
+
+void roots_cursor_handle_tool_tip(struct roots_cursor *cursor,
+ struct wlr_event_tablet_tool_tip *event) {
+ roots_cursor_press_button(cursor, event->device,
+ event->time_msec, BTN_LEFT, event->state, cursor->cursor->x,
+ cursor->cursor->y);
+}
+
+void roots_cursor_handle_request_set_cursor(struct roots_cursor *cursor,
+ struct wlr_seat_pointer_request_set_cursor_event *event) {
+ struct wlr_surface *focused_surface =
+ event->seat_client->seat->pointer_state.focused_surface;
+ bool has_focused =
+ focused_surface != NULL && focused_surface->resource != NULL;
+ struct wl_client *focused_client = NULL;
+ if (has_focused) {
+ focused_client = wl_resource_get_client(focused_surface->resource);
+ }
+ if (event->seat_client->client != focused_client ||
+ cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ wlr_log(WLR_DEBUG, "Denying request to set cursor from unfocused client");
+ return;
+ }
+
+ wlr_cursor_set_surface(cursor->cursor, event->surface, event->hotspot_x,
+ event->hotspot_y);
+ cursor->cursor_client = event->seat_client->client;
+}
+
+void roots_cursor_handle_focus_change(struct roots_cursor *cursor,
+ struct wlr_seat_pointer_focus_change_event *event) {
+ double sx = event->sx;
+ double sy = event->sy;
+
+ double lx = cursor->cursor->x;
+ double ly = cursor->cursor->y;
+
+ wlr_log(WLR_DEBUG, "entered surface %p, lx: %f, ly: %f, sx: %f, sy: %f",
+ event->new_surface, lx, ly, sx, sy);
+
+ roots_cursor_constrain(cursor,
+ wlr_pointer_constraints_v1_constraint_for_surface(
+ cursor->seat->input->server->desktop->pointer_constraints,
+ event->new_surface, cursor->seat->seat),
+ sx, sy);
+}
+
+void roots_cursor_handle_constraint_commit(struct roots_cursor *cursor) {
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+
+ struct roots_view *view;
+ double sx, sy;
+ struct wlr_surface *surface = desktop_surface_at(desktop,
+ cursor->cursor->x, cursor->cursor->y, &sx, &sy, &view);
+ // This should never happen but views move around right when they're
+ // created from (0, 0) to their actual coordinates.
+ if (surface != cursor->active_constraint->surface) {
+ roots_cursor_update_focus(cursor);
+ } else {
+ roots_cursor_constrain(cursor, cursor->active_constraint, sx, sy);
+ }
+}
+
+static void handle_constraint_commit(struct wl_listener *listener,
+ void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, constraint_commit);
+ assert(cursor->active_constraint->surface == data);
+ roots_cursor_handle_constraint_commit(cursor);
+}
+
+void roots_cursor_constrain(struct roots_cursor *cursor,
+ struct wlr_pointer_constraint_v1 *constraint, double sx, double sy) {
+ if (cursor->active_constraint == constraint) {
+ return;
+ }
+
+ wlr_log(WLR_DEBUG, "roots_cursor_constrain(%p, %p)",
+ cursor, constraint);
+ wlr_log(WLR_DEBUG, "cursor->active_constraint: %p",
+ cursor->active_constraint);
+
+ wl_list_remove(&cursor->constraint_commit.link);
+ wl_list_init(&cursor->constraint_commit.link);
+ if (cursor->active_constraint) {
+ wlr_pointer_constraint_v1_send_deactivated(
+ cursor->active_constraint);
+ }
+
+ cursor->active_constraint = constraint;
+
+ if (constraint == NULL) {
+ return;
+ }
+
+ wlr_pointer_constraint_v1_send_activated(constraint);
+
+ wl_list_remove(&cursor->constraint_commit.link);
+ wl_signal_add(&constraint->surface->events.commit,
+ &cursor->constraint_commit);
+ cursor->constraint_commit.notify = handle_constraint_commit;
+
+ pixman_region32_clear(&cursor->confine);
+
+ pixman_region32_t *region = &constraint->region;
+
+ if (!pixman_region32_contains_point(region, floor(sx), floor(sy), NULL)) {
+ // Warp into region if possible
+ int nboxes;
+ pixman_box32_t *boxes = pixman_region32_rectangles(region, &nboxes);
+ if (nboxes > 0) {
+ struct roots_view *view = cursor->pointer_view->view;
+
+ double sx = (boxes[0].x1 + boxes[0].x2) / 2.;
+ double sy = (boxes[0].y1 + boxes[0].y2) / 2.;
+
+ rotate_child_position(&sx, &sy, 0, 0, view->box.width, view->box.height,
+ view->rotation);
+
+ double lx = view->box.x + sx;
+ double ly = view->box.y + sy;
+
+ wlr_cursor_warp_closest(cursor->cursor, NULL, lx, ly);
+ }
+ }
+
+ // A locked pointer will result in an empty region, thus disallowing all movement
+ if (constraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED) {
+ pixman_region32_copy(&cursor->confine, region);
+ }
+}
diff --git a/rootston/desktop.c b/rootston/desktop.c
new file mode 100644
index 00000000..48e2635c
--- /dev/null
+++ b/rootston/desktop.c
@@ -0,0 +1,1101 @@
+#define _POSIX_C_SOURCE 200112L
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <time.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/types/wlr_export_dmabuf_v1.h>
+#include <wlr/types/wlr_gamma_control.h>
+#include <wlr/types/wlr_gamma_control_v1.h>
+#include <wlr/types/wlr_idle_inhibit_v1.h>
+#include <wlr/types/wlr_idle.h>
+#include <wlr/types/wlr_input_inhibitor.h>
+#include <wlr/types/wlr_layer_shell_v1.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_pointer_constraints_v1.h>
+#include <wlr/types/wlr_gtk_primary_selection.h>
+#include <wlr/types/wlr_server_decoration.h>
+#include <wlr/types/wlr_wl_shell.h>
+#include <wlr/types/wlr_xcursor_manager.h>
+#include <wlr/types/wlr_xdg_output_v1.h>
+#include <wlr/types/wlr_xdg_shell_v6.h>
+#include <wlr/types/wlr_xdg_shell.h>
+#include <wlr/types/wlr_xdg_output_v1.h>
+#include <wlr/types/wlr_tablet_v2.h>
+#include <wlr/util/log.h>
+#include "rootston/layers.h"
+#include "rootston/seat.h"
+#include "rootston/server.h"
+#include "rootston/view.h"
+#include "rootston/virtual_keyboard.h"
+#include "rootston/xcursor.h"
+#include "wlr-layer-shell-unstable-v1-protocol.h"
+
+struct roots_view *view_create(struct roots_desktop *desktop) {
+ struct roots_view *view = calloc(1, sizeof(struct roots_view));
+ if (!view) {
+ return NULL;
+ }
+ view->desktop = desktop;
+ view->alpha = 1.0f;
+ wl_signal_init(&view->events.unmap);
+ wl_signal_init(&view->events.destroy);
+ wl_list_init(&view->children);
+ return view;
+}
+
+void view_get_box(const struct roots_view *view, struct wlr_box *box) {
+ box->x = view->box.x;
+ box->y = view->box.y;
+ box->width = view->box.width;
+ box->height = view->box.height;
+}
+
+void view_get_deco_box(const struct roots_view *view, struct wlr_box *box) {
+ view_get_box(view, box);
+ if (!view->decorated) {
+ return;
+ }
+
+ box->x -= view->border_width;
+ box->y -= (view->border_width + view->titlebar_height);
+ box->width += view->border_width * 2;
+ box->height += (view->border_width * 2 + view->titlebar_height);
+}
+
+enum roots_deco_part view_get_deco_part(struct roots_view *view, double sx,
+ double sy) {
+ if (!view->decorated) {
+ return ROOTS_DECO_PART_NONE;
+ }
+
+ int sw = view->wlr_surface->current.width;
+ int sh = view->wlr_surface->current.height;
+ int bw = view->border_width;
+ int titlebar_h = view->titlebar_height;
+
+ if (sx > 0 && sx < sw && sy < 0 && sy > -view->titlebar_height) {
+ return ROOTS_DECO_PART_TITLEBAR;
+ }
+
+ enum roots_deco_part parts = 0;
+ if (sy >= -(titlebar_h + bw) &&
+ sy <= sh + bw) {
+ if (sx < 0 && sx > -bw) {
+ parts |= ROOTS_DECO_PART_LEFT_BORDER;
+ } else if (sx > sw && sx < sw + bw) {
+ parts |= ROOTS_DECO_PART_RIGHT_BORDER;
+ }
+ }
+
+ if (sx >= -bw && sx <= sw + bw) {
+ if (sy > sh && sy <= sh + bw) {
+ parts |= ROOTS_DECO_PART_BOTTOM_BORDER;
+ } else if (sy >= -(titlebar_h + bw) && sy < 0) {
+ parts |= ROOTS_DECO_PART_TOP_BORDER;
+ }
+ }
+
+ // TODO corners
+
+ return parts;
+}
+
+static void view_update_output(const struct roots_view *view,
+ const struct wlr_box *before) {
+ struct roots_desktop *desktop = view->desktop;
+
+ if (view->wlr_surface == NULL) {
+ return;
+ }
+
+ struct wlr_box box;
+ view_get_box(view, &box);
+
+ struct roots_output *output;
+ wl_list_for_each(output, &desktop->outputs, link) {
+ bool intersected = before != NULL && wlr_output_layout_intersects(
+ desktop->layout, output->wlr_output, before);
+ bool intersects = wlr_output_layout_intersects(desktop->layout,
+ output->wlr_output, &box);
+ if (intersected && !intersects) {
+ wlr_surface_send_leave(view->wlr_surface, output->wlr_output);
+ if (view->toplevel_handle) {
+ wlr_foreign_toplevel_handle_v1_output_leave(
+ view->toplevel_handle, output->wlr_output);
+ }
+ }
+ if (!intersected && intersects) {
+ wlr_surface_send_enter(view->wlr_surface, output->wlr_output);
+ if (view->toplevel_handle) {
+ wlr_foreign_toplevel_handle_v1_output_enter(
+ view->toplevel_handle, output->wlr_output);
+ }
+ }
+ }
+}
+
+void view_move(struct roots_view *view, double x, double y) {
+ if (view->box.x == x && view->box.y == y) {
+ return;
+ }
+
+ struct wlr_box before;
+ view_get_box(view, &before);
+ if (view->move) {
+ view->move(view, x, y);
+ } else {
+ view_update_position(view, x, y);
+ }
+ view_update_output(view, &before);
+}
+
+void view_activate(struct roots_view *view, bool activate) {
+ if (view->activate) {
+ view->activate(view, activate);
+ }
+
+ if (view->toplevel_handle) {
+ wlr_foreign_toplevel_handle_v1_set_activated(view->toplevel_handle,
+ activate);
+ }
+}
+
+void view_resize(struct roots_view *view, uint32_t width, uint32_t height) {
+ struct wlr_box before;
+ view_get_box(view, &before);
+ if (view->resize) {
+ view->resize(view, width, height);
+ }
+ view_update_output(view, &before);
+}
+
+void view_move_resize(struct roots_view *view, double x, double y,
+ uint32_t width, uint32_t height) {
+ bool update_x = x != view->box.x;
+ bool update_y = y != view->box.y;
+ if (!update_x && !update_y) {
+ view_resize(view, width, height);
+ return;
+ }
+
+ if (view->move_resize) {
+ view->move_resize(view, x, y, width, height);
+ return;
+ }
+
+ view->pending_move_resize.update_x = update_x;
+ view->pending_move_resize.update_y = update_y;
+ view->pending_move_resize.x = x;
+ view->pending_move_resize.y = y;
+ view->pending_move_resize.width = width;
+ view->pending_move_resize.height = height;
+
+ view_resize(view, width, height);
+}
+
+static struct wlr_output *view_get_output(struct roots_view *view) {
+ struct wlr_box view_box;
+ view_get_box(view, &view_box);
+
+ double output_x, output_y;
+ wlr_output_layout_closest_point(view->desktop->layout, NULL,
+ view->box.x + (double)view_box.width/2,
+ view->box.y + (double)view_box.height/2,
+ &output_x, &output_y);
+ return wlr_output_layout_output_at(view->desktop->layout, output_x,
+ output_y);
+}
+
+void view_arrange_maximized(struct roots_view *view) {
+ struct wlr_box view_box;
+ view_get_box(view, &view_box);
+
+ struct wlr_output *output = view_get_output(view);
+ struct roots_output *roots_output = output->data;
+ struct wlr_box *output_box =
+ wlr_output_layout_get_box(view->desktop->layout, output);
+ struct wlr_box usable_area;
+ memcpy(&usable_area, &roots_output->usable_area,
+ sizeof(struct wlr_box));
+ usable_area.x += output_box->x;
+ usable_area.y += output_box->y;
+
+ view_move_resize(view, usable_area.x, usable_area.y,
+ usable_area.width, usable_area.height);
+ view_rotate(view, 0);
+}
+
+void view_maximize(struct roots_view *view, bool maximized) {
+ if (view->maximized == maximized) {
+ return;
+ }
+
+ if (view->maximize) {
+ view->maximize(view, maximized);
+ }
+
+ if (view->toplevel_handle) {
+ wlr_foreign_toplevel_handle_v1_set_maximized(view->toplevel_handle,
+ maximized);
+ }
+
+ if (!view->maximized && maximized) {
+ view->maximized = true;
+ view->saved.x = view->box.x;
+ view->saved.y = view->box.y;
+ view->saved.rotation = view->rotation;
+ view->saved.width = view->box.width;
+ view->saved.height = view->box.height;
+
+ view_arrange_maximized(view);
+ }
+
+ if (view->maximized && !maximized) {
+ view->maximized = false;
+
+ view_move_resize(view, view->saved.x, view->saved.y, view->saved.width,
+ view->saved.height);
+ view_rotate(view, view->saved.rotation);
+ }
+}
+
+void view_set_fullscreen(struct roots_view *view, bool fullscreen,
+ struct wlr_output *output) {
+ bool was_fullscreen = view->fullscreen_output != NULL;
+ if (was_fullscreen == fullscreen) {
+ // TODO: support changing the output?
+ return;
+ }
+
+ // TODO: check if client is focused?
+
+ if (view->set_fullscreen) {
+ view->set_fullscreen(view, fullscreen);
+ }
+
+ if (!was_fullscreen && fullscreen) {
+ if (output == NULL) {
+ output = view_get_output(view);
+ }
+ struct roots_output *roots_output =
+ desktop_output_from_wlr_output(view->desktop, output);
+ if (roots_output == NULL) {
+ return;
+ }
+
+ struct wlr_box view_box;
+ view_get_box(view, &view_box);
+
+ view->saved.x = view->box.x;
+ view->saved.y = view->box.y;
+ view->saved.rotation = view->rotation;
+ view->saved.width = view_box.width;
+ view->saved.height = view_box.height;
+
+ struct wlr_box *output_box =
+ wlr_output_layout_get_box(view->desktop->layout, output);
+ view_move_resize(view, output_box->x, output_box->y, output_box->width,
+ output_box->height);
+ view_rotate(view, 0);
+
+ roots_output->fullscreen_view = view;
+ view->fullscreen_output = roots_output;
+ output_damage_whole(roots_output);
+ }
+
+ if (was_fullscreen && !fullscreen) {
+ view_move_resize(view, view->saved.x, view->saved.y, view->saved.width,
+ view->saved.height);
+ view_rotate(view, view->saved.rotation);
+
+ output_damage_whole(view->fullscreen_output);
+ view->fullscreen_output->fullscreen_view = NULL;
+ view->fullscreen_output = NULL;
+ }
+}
+
+void view_rotate(struct roots_view *view, float rotation) {
+ if (view->rotation == rotation) {
+ return;
+ }
+
+ view_damage_whole(view);
+ view->rotation = rotation;
+ view_damage_whole(view);
+}
+
+void view_cycle_alpha(struct roots_view *view) {
+ view->alpha -= 0.05;
+ /* Don't go completely transparent */
+ if (view->alpha < 0.1) {
+ view->alpha = 1.0;
+ }
+ view_damage_whole(view);
+}
+
+void view_close(struct roots_view *view) {
+ if (view->close) {
+ view->close(view);
+ }
+}
+
+bool view_center(struct roots_view *view) {
+ struct wlr_box box;
+ view_get_box(view, &box);
+
+ struct roots_desktop *desktop = view->desktop;
+ struct roots_input *input = desktop->server->input;
+ struct roots_seat *seat = input_last_active_seat(input);
+ if (!seat) {
+ return false;
+ }
+
+ struct wlr_output *output =
+ wlr_output_layout_output_at(desktop->layout,
+ seat->cursor->cursor->x,
+ seat->cursor->cursor->y);
+ if (!output) {
+ // empty layout
+ return false;
+ }
+
+ const struct wlr_output_layout_output *l_output =
+ wlr_output_layout_get(desktop->layout, output);
+
+ int width, height;
+ wlr_output_effective_resolution(output, &width, &height);
+
+ double view_x = (double)(width - box.width) / 2 + l_output->x;
+ double view_y = (double)(height - box.height) / 2 + l_output->y;
+ view_move(view, view_x, view_y);
+
+ return true;
+}
+
+void view_child_finish(struct roots_view_child *child) {
+ if (child == NULL) {
+ return;
+ }
+ view_damage_whole(child->view);
+ wl_list_remove(&child->link);
+ wl_list_remove(&child->commit.link);
+ wl_list_remove(&child->new_subsurface.link);
+}
+
+static void view_child_handle_commit(struct wl_listener *listener,
+ void *data) {
+ struct roots_view_child *child = wl_container_of(listener, child, commit);
+ view_apply_damage(child->view);
+}
+
+static void view_child_handle_new_subsurface(struct wl_listener *listener,
+ void *data) {
+ struct roots_view_child *child =
+ wl_container_of(listener, child, new_subsurface);
+ struct wlr_subsurface *wlr_subsurface = data;
+ subsurface_create(child->view, wlr_subsurface);
+}
+
+void view_child_init(struct roots_view_child *child, struct roots_view *view,
+ struct wlr_surface *wlr_surface) {
+ assert(child->destroy);
+ child->view = view;
+ child->wlr_surface = wlr_surface;
+ child->commit.notify = view_child_handle_commit;
+ wl_signal_add(&wlr_surface->events.commit, &child->commit);
+ child->new_subsurface.notify = view_child_handle_new_subsurface;
+ wl_signal_add(&wlr_surface->events.new_subsurface, &child->new_subsurface);
+ wl_list_insert(&view->children, &child->link);
+}
+
+static void subsurface_destroy(struct roots_view_child *child) {
+ assert(child->destroy == subsurface_destroy);
+ struct roots_subsurface *subsurface = (struct roots_subsurface *)child;
+ if (subsurface == NULL) {
+ return;
+ }
+ wl_list_remove(&subsurface->destroy.link);
+ view_child_finish(&subsurface->view_child);
+ free(subsurface);
+}
+
+static void subsurface_handle_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_subsurface *subsurface =
+ wl_container_of(listener, subsurface, destroy);
+ subsurface_destroy((struct roots_view_child *)subsurface);
+}
+
+struct roots_subsurface *subsurface_create(struct roots_view *view,
+ struct wlr_subsurface *wlr_subsurface) {
+ struct roots_subsurface *subsurface =
+ calloc(1, sizeof(struct roots_subsurface));
+ if (subsurface == NULL) {
+ return NULL;
+ }
+ subsurface->wlr_subsurface = wlr_subsurface;
+ subsurface->view_child.destroy = subsurface_destroy;
+ view_child_init(&subsurface->view_child, view, wlr_subsurface->surface);
+ subsurface->destroy.notify = subsurface_handle_destroy;
+ wl_signal_add(&wlr_subsurface->events.destroy, &subsurface->destroy);
+ input_update_cursor_focus(view->desktop->server->input);
+ return subsurface;
+}
+
+void view_destroy(struct roots_view *view) {
+ if (view == NULL) {
+ return;
+ }
+
+ wl_signal_emit(&view->events.destroy, view);
+
+ if (view->wlr_surface != NULL) {
+ view_unmap(view);
+ }
+
+ // Can happen if fullscreened while unmapped, and hasn't been mapped
+ if (view->fullscreen_output != NULL) {
+ view->fullscreen_output->fullscreen_view = NULL;
+ }
+
+ if (view->destroy) {
+ view->destroy(view);
+ }
+
+ free(view);
+}
+
+static void view_handle_new_subsurface(struct wl_listener *listener,
+ void *data) {
+ struct roots_view *view = wl_container_of(listener, view, new_subsurface);
+ struct wlr_subsurface *wlr_subsurface = data;
+ subsurface_create(view, wlr_subsurface);
+}
+
+void view_map(struct roots_view *view, struct wlr_surface *surface) {
+ assert(view->wlr_surface == NULL);
+
+ view->wlr_surface = surface;
+
+ struct wlr_subsurface *subsurface;
+ wl_list_for_each(subsurface, &view->wlr_surface->subsurfaces,
+ parent_link) {
+ subsurface_create(view, subsurface);
+ }
+
+ view->new_subsurface.notify = view_handle_new_subsurface;
+ wl_signal_add(&view->wlr_surface->events.new_subsurface,
+ &view->new_subsurface);
+
+ wl_list_insert(&view->desktop->views, &view->link);
+ view_damage_whole(view);
+ input_update_cursor_focus(view->desktop->server->input);
+}
+
+void view_unmap(struct roots_view *view) {
+ assert(view->wlr_surface != NULL);
+
+ wl_signal_emit(&view->events.unmap, view);
+
+ view_damage_whole(view);
+ wl_list_remove(&view->link);
+
+ wl_list_remove(&view->new_subsurface.link);
+
+ struct roots_view_child *child, *tmp;
+ wl_list_for_each_safe(child, tmp, &view->children, link) {
+ child->destroy(child);
+ }
+
+ if (view->fullscreen_output != NULL) {
+ output_damage_whole(view->fullscreen_output);
+ view->fullscreen_output->fullscreen_view = NULL;
+ view->fullscreen_output = NULL;
+ }
+
+ view->wlr_surface = NULL;
+ view->box.width = view->box.height = 0;
+
+ if (view->toplevel_handle) {
+ wlr_foreign_toplevel_handle_v1_destroy(view->toplevel_handle);
+ view->toplevel_handle = NULL;
+ }
+}
+
+void view_initial_focus(struct roots_view *view) {
+ struct roots_input *input = view->desktop->server->input;
+ // TODO what seat gets focus? the one with the last input event?
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &input->seats, link) {
+ roots_seat_set_focus(seat, view);
+ }
+}
+
+void view_setup(struct roots_view *view) {
+ view_initial_focus(view);
+
+ if (view->fullscreen_output == NULL && !view->maximized) {
+ view_center(view);
+ }
+
+ view_create_foreign_toplevel_handle(view);
+ view_update_output(view, NULL);
+}
+
+void view_apply_damage(struct roots_view *view) {
+ struct roots_output *output;
+ wl_list_for_each(output, &view->desktop->outputs, link) {
+ output_damage_from_view(output, view);
+ }
+}
+
+void view_damage_whole(struct roots_view *view) {
+ struct roots_output *output;
+ wl_list_for_each(output, &view->desktop->outputs, link) {
+ output_damage_whole_view(output, view);
+ }
+}
+
+void view_update_position(struct roots_view *view, int x, int y) {
+ if (view->box.x == x && view->box.y == y) {
+ return;
+ }
+
+ view_damage_whole(view);
+ view->box.x = x;
+ view->box.y = y;
+ view_damage_whole(view);
+}
+
+void view_update_size(struct roots_view *view, int width, int height) {
+ if (view->box.width == width && view->box.height == height) {
+ return;
+ }
+
+ view_damage_whole(view);
+ view->box.width = width;
+ view->box.height = height;
+ view_damage_whole(view);
+}
+
+void view_update_decorated(struct roots_view *view, bool decorated) {
+ if (view->decorated == decorated) {
+ return;
+ }
+
+ view_damage_whole(view);
+ view->decorated = decorated;
+ if (decorated) {
+ view->border_width = 4;
+ view->titlebar_height = 12;
+ } else {
+ view->border_width = 0;
+ view->titlebar_height = 0;
+ }
+ view_damage_whole(view);
+}
+
+void view_set_title(struct roots_view *view, const char *title) {
+ if (view->toplevel_handle) {
+ wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle, title);
+ }
+}
+
+void view_set_app_id(struct roots_view *view, const char *app_id) {
+ if (view->toplevel_handle) {
+ wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle, app_id);
+ }
+}
+
+static void handle_toplevel_handle_request_maximize(struct wl_listener *listener,
+ void *data) {
+ struct roots_view *view = wl_container_of(listener, view,
+ toplevel_handle_request_maximize);
+ struct wlr_foreign_toplevel_handle_v1_maximized_event *event = data;
+ view_maximize(view, event->maximized);
+}
+
+static void handle_toplevel_handle_request_activate(struct wl_listener *listener,
+ void *data) {
+ struct roots_view *view =
+ wl_container_of(listener, view, toplevel_handle_request_activate);
+ struct wlr_foreign_toplevel_handle_v1_activated_event *event = data;
+
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &view->desktop->server->input->seats, link) {
+ if (event->seat == seat->seat) {
+ roots_seat_set_focus(seat, view);
+ }
+ }
+}
+
+static void handle_toplevel_handle_request_close(struct wl_listener *listener,
+ void *data) {
+ struct roots_view *view =
+ wl_container_of(listener, view, toplevel_handle_request_close);
+ view_close(view);
+}
+
+void view_create_foreign_toplevel_handle(struct roots_view *view) {
+ view->toplevel_handle =
+ wlr_foreign_toplevel_handle_v1_create(
+ view->desktop->foreign_toplevel_manager_v1);
+
+ view->toplevel_handle_request_maximize.notify =
+ handle_toplevel_handle_request_maximize;
+ wl_signal_add(&view->toplevel_handle->events.request_maximize,
+ &view->toplevel_handle_request_maximize);
+ view->toplevel_handle_request_activate.notify =
+ handle_toplevel_handle_request_activate;
+ wl_signal_add(&view->toplevel_handle->events.request_activate,
+ &view->toplevel_handle_request_activate);
+ view->toplevel_handle_request_close.notify =
+ handle_toplevel_handle_request_close;
+ wl_signal_add(&view->toplevel_handle->events.request_close,
+ &view->toplevel_handle_request_close);
+}
+
+static bool view_at(struct roots_view *view, double lx, double ly,
+ struct wlr_surface **surface, double *sx, double *sy) {
+ if (view->type == ROOTS_WL_SHELL_VIEW &&
+ view->wl_shell_surface->state == WLR_WL_SHELL_SURFACE_STATE_POPUP) {
+ return false;
+ }
+ if (view->wlr_surface == NULL) {
+ return false;
+ }
+
+ double view_sx = lx - view->box.x;
+ double view_sy = ly - view->box.y;
+
+ struct wlr_surface_state *state = &view->wlr_surface->current;
+ struct wlr_box box = {
+ .x = 0, .y = 0,
+ .width = state->width, .height = state->height,
+ };
+ if (view->rotation != 0.0) {
+ // Coordinates relative to the center of the view
+ double ox = view_sx - (double)box.width/2,
+ oy = view_sy - (double)box.height/2;
+ // Rotated coordinates
+ double rx = cos(view->rotation)*ox + sin(view->rotation)*oy,
+ ry = cos(view->rotation)*oy - sin(view->rotation)*ox;
+ view_sx = rx + (double)box.width/2;
+ view_sy = ry + (double)box.height/2;
+ }
+
+ double _sx, _sy;
+ struct wlr_surface *_surface = NULL;
+ switch (view->type) {
+ case ROOTS_XDG_SHELL_V6_VIEW:
+ _surface = wlr_xdg_surface_v6_surface_at(view->xdg_surface_v6,
+ view_sx, view_sy, &_sx, &_sy);
+ break;
+ case ROOTS_XDG_SHELL_VIEW:
+ _surface = wlr_xdg_surface_surface_at(view->xdg_surface,
+ view_sx, view_sy, &_sx, &_sy);
+ break;
+ case ROOTS_WL_SHELL_VIEW:
+ _surface = wlr_wl_shell_surface_surface_at(view->wl_shell_surface,
+ view_sx, view_sy, &_sx, &_sy);
+ break;
+#if WLR_HAS_XWAYLAND
+ case ROOTS_XWAYLAND_VIEW:
+ _surface = wlr_surface_surface_at(view->wlr_surface,
+ view_sx, view_sy, &_sx, &_sy);
+ break;
+#endif
+ }
+ if (_surface != NULL) {
+ *sx = _sx;
+ *sy = _sy;
+ *surface = _surface;
+ return true;
+ }
+
+ if (view_get_deco_part(view, view_sx, view_sy)) {
+ *sx = view_sx;
+ *sy = view_sy;
+ *surface = NULL;
+ return true;
+ }
+
+ return false;
+}
+
+static struct roots_view *desktop_view_at(struct roots_desktop *desktop,
+ double lx, double ly, struct wlr_surface **surface,
+ double *sx, double *sy) {
+ struct wlr_output *wlr_output =
+ wlr_output_layout_output_at(desktop->layout, lx, ly);
+ if (wlr_output != NULL) {
+ struct roots_output *output =
+ desktop_output_from_wlr_output(desktop, wlr_output);
+ if (output != NULL && output->fullscreen_view != NULL) {
+ if (view_at(output->fullscreen_view, lx, ly, surface, sx, sy)) {
+ return output->fullscreen_view;
+ } else {
+ return NULL;
+ }
+ }
+ }
+
+ struct roots_view *view;
+ wl_list_for_each(view, &desktop->views, link) {
+ if (view_at(view, lx, ly, surface, sx, sy)) {
+ return view;
+ }
+ }
+ return NULL;
+}
+
+static struct wlr_surface *layer_surface_at(struct roots_output *output,
+ struct wl_list *layer, double ox, double oy, double *sx, double *sy) {
+ struct roots_layer_surface *roots_surface;
+ wl_list_for_each_reverse(roots_surface, layer, link) {
+ double _sx = ox - roots_surface->geo.x;
+ double _sy = oy - roots_surface->geo.y;
+
+ struct wlr_surface *sub = wlr_layer_surface_v1_surface_at(
+ roots_surface->layer_surface, _sx, _sy, sx, sy);
+
+ if (sub) {
+ return sub;
+ }
+ }
+
+ return NULL;
+}
+
+struct wlr_surface *desktop_surface_at(struct roots_desktop *desktop,
+ double lx, double ly, double *sx, double *sy,
+ struct roots_view **view) {
+ struct wlr_surface *surface = NULL;
+ struct wlr_output *wlr_output =
+ wlr_output_layout_output_at(desktop->layout, lx, ly);
+ struct roots_output *roots_output = NULL;
+ double ox = lx, oy = ly;
+ if (view) {
+ *view = NULL;
+ }
+
+ if (wlr_output) {
+ roots_output = wlr_output->data;
+ wlr_output_layout_output_coords(desktop->layout, wlr_output, &ox, &oy);
+
+ if ((surface = layer_surface_at(roots_output,
+ &roots_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],
+ ox, oy, sx, sy))) {
+ return surface;
+ }
+ if ((surface = layer_surface_at(roots_output,
+ &roots_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
+ ox, oy, sx, sy))) {
+ return surface;
+ }
+ }
+
+ struct roots_view *_view;
+ if ((_view = desktop_view_at(desktop, lx, ly, &surface, sx, sy))) {
+ if (view) {
+ *view = _view;
+ }
+ return surface;
+ }
+
+ if (wlr_output) {
+ if ((surface = layer_surface_at(roots_output,
+ &roots_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
+ ox, oy, sx, sy))) {
+ return surface;
+ }
+ if ((surface = layer_surface_at(roots_output,
+ &roots_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
+ ox, oy, sx, sy))) {
+ return surface;
+ }
+ }
+ return NULL;
+}
+
+static void handle_layout_change(struct wl_listener *listener, void *data) {
+ struct roots_desktop *desktop =
+ wl_container_of(listener, desktop, layout_change);
+
+ struct wlr_output *center_output =
+ wlr_output_layout_get_center_output(desktop->layout);
+ if (center_output == NULL) {
+ return;
+ }
+
+ struct wlr_box *center_output_box =
+ wlr_output_layout_get_box(desktop->layout, center_output);
+ double center_x = center_output_box->x + center_output_box->width/2;
+ double center_y = center_output_box->y + center_output_box->height/2;
+
+ struct roots_view *view;
+ wl_list_for_each(view, &desktop->views, link) {
+ struct wlr_box box;
+ view_get_box(view, &box);
+
+ if (wlr_output_layout_intersects(desktop->layout, NULL, &box)) {
+ continue;
+ }
+
+ view_move(view, center_x - box.width/2, center_y - box.height/2);
+ }
+}
+
+static void input_inhibit_activate(struct wl_listener *listener, void *data) {
+ struct roots_desktop *desktop = wl_container_of(
+ listener, desktop, input_inhibit_activate);
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &desktop->server->input->seats, link) {
+ roots_seat_set_exclusive_client(seat,
+ desktop->input_inhibit->active_client);
+ }
+}
+
+static void input_inhibit_deactivate(struct wl_listener *listener, void *data) {
+ struct roots_desktop *desktop = wl_container_of(
+ listener, desktop, input_inhibit_deactivate);
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &desktop->server->input->seats, link) {
+ roots_seat_set_exclusive_client(seat, NULL);
+ }
+}
+
+static void handle_constraint_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_pointer_constraint *constraint =
+ wl_container_of(listener, constraint, destroy);
+ struct wlr_pointer_constraint_v1 *wlr_constraint = data;
+ struct roots_seat *seat = wlr_constraint->seat->data;
+
+ wl_list_remove(&constraint->destroy.link);
+
+ if (seat->cursor->active_constraint == wlr_constraint) {
+ wl_list_remove(&seat->cursor->constraint_commit.link);
+ wl_list_init(&seat->cursor->constraint_commit.link);
+ seat->cursor->active_constraint = NULL;
+
+ if (wlr_constraint->current.committed &
+ WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT &&
+ seat->cursor->pointer_view) {
+ double sx = wlr_constraint->current.cursor_hint.x;
+ double sy = wlr_constraint->current.cursor_hint.y;
+
+ struct roots_view *view = seat->cursor->pointer_view->view;
+ rotate_child_position(&sx, &sy, 0, 0, view->box.width, view->box.height,
+ view->rotation);
+ double lx = view->box.x + sx;
+ double ly = view->box.y + sy;
+
+ wlr_cursor_warp(seat->cursor->cursor, NULL, lx, ly);
+ }
+ }
+
+ free(constraint);
+}
+
+static void handle_pointer_constraint(struct wl_listener *listener,
+ void *data) {
+ struct wlr_pointer_constraint_v1 *wlr_constraint = data;
+ struct roots_seat *seat = wlr_constraint->seat->data;
+
+ struct roots_pointer_constraint *constraint =
+ calloc(1, sizeof(struct roots_pointer_constraint));
+ constraint->constraint = wlr_constraint;
+
+ constraint->destroy.notify = handle_constraint_destroy;
+ wl_signal_add(&wlr_constraint->events.destroy, &constraint->destroy);
+
+ double sx, sy;
+ struct wlr_surface *surface = desktop_surface_at(
+ seat->input->server->desktop,
+ seat->cursor->cursor->x, seat->cursor->cursor->y, &sx, &sy, NULL);
+
+ if (surface == wlr_constraint->surface) {
+ assert(!seat->cursor->active_constraint);
+ roots_cursor_constrain(seat->cursor, wlr_constraint, sx, sy);
+ }
+}
+
+struct roots_desktop *desktop_create(struct roots_server *server,
+ struct roots_config *config) {
+ wlr_log(WLR_DEBUG, "Initializing roots desktop");
+
+ struct roots_desktop *desktop = calloc(1, sizeof(struct roots_desktop));
+ if (desktop == NULL) {
+ return NULL;
+ }
+
+ wl_list_init(&desktop->views);
+ wl_list_init(&desktop->outputs);
+
+ desktop->new_output.notify = handle_new_output;
+ wl_signal_add(&server->backend->events.new_output, &desktop->new_output);
+
+ desktop->server = server;
+ desktop->config = config;
+
+ desktop->layout = wlr_output_layout_create();
+ wlr_xdg_output_manager_v1_create(server->wl_display, desktop->layout);
+ desktop->layout_change.notify = handle_layout_change;
+ wl_signal_add(&desktop->layout->events.change, &desktop->layout_change);
+
+ desktop->compositor = wlr_compositor_create(server->wl_display,
+ server->renderer);
+
+ desktop->xdg_shell_v6 = wlr_xdg_shell_v6_create(server->wl_display);
+ wl_signal_add(&desktop->xdg_shell_v6->events.new_surface,
+ &desktop->xdg_shell_v6_surface);
+ desktop->xdg_shell_v6_surface.notify = handle_xdg_shell_v6_surface;
+
+ desktop->xdg_shell = wlr_xdg_shell_create(server->wl_display);
+ wl_signal_add(&desktop->xdg_shell->events.new_surface,
+ &desktop->xdg_shell_surface);
+ desktop->xdg_shell_surface.notify = handle_xdg_shell_surface;
+
+ desktop->wl_shell = wlr_wl_shell_create(server->wl_display);
+ wl_signal_add(&desktop->wl_shell->events.new_surface,
+ &desktop->wl_shell_surface);
+ desktop->wl_shell_surface.notify = handle_wl_shell_surface;
+
+ desktop->layer_shell = wlr_layer_shell_v1_create(server->wl_display);
+ wl_signal_add(&desktop->layer_shell->events.new_surface,
+ &desktop->layer_shell_surface);
+ desktop->layer_shell_surface.notify = handle_layer_shell_surface;
+
+ desktop->tablet_v2 = wlr_tablet_v2_create(server->wl_display);
+
+ const char *cursor_theme = NULL;
+#if WLR_HAS_XWAYLAND
+ const char *cursor_default = ROOTS_XCURSOR_DEFAULT;
+#endif
+ struct roots_cursor_config *cc =
+ roots_config_get_cursor(config, ROOTS_CONFIG_DEFAULT_SEAT_NAME);
+ if (cc != NULL) {
+ cursor_theme = cc->theme;
+#if WLR_HAS_XWAYLAND
+ if (cc->default_image != NULL) {
+ cursor_default = cc->default_image;
+ }
+#endif
+ }
+
+ char cursor_size_fmt[16];
+ snprintf(cursor_size_fmt, sizeof(cursor_size_fmt),
+ "%d", ROOTS_XCURSOR_SIZE);
+ setenv("XCURSOR_SIZE", cursor_size_fmt, 1);
+ if (cursor_theme != NULL) {
+ setenv("XCURSOR_THEME", cursor_theme, 1);
+ }
+
+#if WLR_HAS_XWAYLAND
+ desktop->xcursor_manager = wlr_xcursor_manager_create(cursor_theme,
+ ROOTS_XCURSOR_SIZE);
+ if (desktop->xcursor_manager == NULL) {
+ wlr_log(WLR_ERROR, "Cannot create XCursor manager for theme %s",
+ cursor_theme);
+ free(desktop);
+ return NULL;
+ }
+
+ if (config->xwayland) {
+ desktop->xwayland = wlr_xwayland_create(server->wl_display,
+ desktop->compositor, config->xwayland_lazy);
+ wl_signal_add(&desktop->xwayland->events.new_surface,
+ &desktop->xwayland_surface);
+ desktop->xwayland_surface.notify = handle_xwayland_surface;
+
+ if (wlr_xcursor_manager_load(desktop->xcursor_manager, 1)) {
+ wlr_log(WLR_ERROR, "Cannot load XWayland XCursor theme");
+ }
+ struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(
+ desktop->xcursor_manager, cursor_default, 1);
+ if (xcursor != NULL) {
+ struct wlr_xcursor_image *image = xcursor->images[0];
+ wlr_xwayland_set_cursor(desktop->xwayland, image->buffer,
+ image->width * 4, image->width, image->height, image->hotspot_x,
+ image->hotspot_y);
+ }
+ }
+#endif
+
+ desktop->gamma_control_manager = wlr_gamma_control_manager_create(
+ server->wl_display);
+ desktop->gamma_control_manager_v1 = wlr_gamma_control_manager_v1_create(
+ server->wl_display);
+ desktop->screenshooter = wlr_screenshooter_create(server->wl_display);
+ desktop->export_dmabuf_manager_v1 =
+ wlr_export_dmabuf_manager_v1_create(server->wl_display);
+ desktop->server_decoration_manager =
+ wlr_server_decoration_manager_create(server->wl_display);
+ wlr_server_decoration_manager_set_default_mode(
+ desktop->server_decoration_manager,
+ WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT);
+ desktop->idle = wlr_idle_create(server->wl_display);
+ desktop->idle_inhibit = wlr_idle_inhibit_v1_create(server->wl_display);
+ desktop->primary_selection_device_manager =
+ wlr_gtk_primary_selection_device_manager_create(server->wl_display);
+ desktop->input_inhibit =
+ wlr_input_inhibit_manager_create(server->wl_display);
+ desktop->input_inhibit_activate.notify = input_inhibit_activate;
+ wl_signal_add(&desktop->input_inhibit->events.activate,
+ &desktop->input_inhibit_activate);
+ desktop->input_inhibit_deactivate.notify = input_inhibit_deactivate;
+ wl_signal_add(&desktop->input_inhibit->events.deactivate,
+ &desktop->input_inhibit_deactivate);
+
+ desktop->input_method =
+ wlr_input_method_manager_v2_create(server->wl_display);
+
+ desktop->text_input = wlr_text_input_manager_v3_create(server->wl_display);
+
+ desktop->virtual_keyboard = wlr_virtual_keyboard_manager_v1_create(
+ server->wl_display);
+ wl_signal_add(&desktop->virtual_keyboard->events.new_virtual_keyboard,
+ &desktop->virtual_keyboard_new);
+ desktop->virtual_keyboard_new.notify = handle_virtual_keyboard;
+
+ desktop->screencopy = wlr_screencopy_manager_v1_create(server->wl_display);
+
+ desktop->xdg_decoration_manager =
+ wlr_xdg_decoration_manager_v1_create(server->wl_display);
+ wl_signal_add(&desktop->xdg_decoration_manager->events.new_toplevel_decoration,
+ &desktop->xdg_toplevel_decoration);
+ desktop->xdg_toplevel_decoration.notify = handle_xdg_toplevel_decoration;
+
+ desktop->pointer_constraints =
+ wlr_pointer_constraints_v1_create(server->wl_display);
+ desktop->pointer_constraint.notify = handle_pointer_constraint;
+ wl_signal_add(&desktop->pointer_constraints->events.new_constraint,
+ &desktop->pointer_constraint);
+
+ desktop->presentation =
+ wlr_presentation_create(server->wl_display, server->backend);
+ desktop->foreign_toplevel_manager_v1 =
+ wlr_foreign_toplevel_manager_v1_create(server->wl_display);
+
+ return desktop;
+}
+
+void desktop_destroy(struct roots_desktop *desktop) {
+ // TODO
+}
+
+struct roots_output *desktop_output_from_wlr_output(
+ struct roots_desktop *desktop, struct wlr_output *wlr_output) {
+ struct roots_output *output;
+ wl_list_for_each(output, &desktop->outputs, link) {
+ if (output->wlr_output == wlr_output) {
+ return output;
+ }
+ }
+ return NULL;
+}
diff --git a/rootston/ini.c b/rootston/ini.c
new file mode 100644
index 00000000..f515dd38
--- /dev/null
+++ b/rootston/ini.c
@@ -0,0 +1,195 @@
+/* inih -- simple .INI file parser
+
+inih is released under the New BSD license (see LICENSE.txt). Go to the project
+home page for more info:
+
+https://github.com/benhoyt/inih
+
+*/
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "rootston/ini.h"
+
+#if !INI_USE_STACK
+#include <stdlib.h>
+#endif
+
+#define MAX_SECTION 50
+#define MAX_NAME 50
+
+/* Strip whitespace chars off end of given string, in place. Return s. */
+static char* rstrip(char* s)
+{
+ char* p = s + strlen(s);
+ while (p > s && isspace((unsigned char)(*--p)))
+ *p = '\0';
+ return s;
+}
+
+/* Return pointer to first non-whitespace char in given string. */
+static char* lskip(const char* s)
+{
+ while (*s && isspace((unsigned char)(*s)))
+ s++;
+ return (char*)s;
+}
+
+/* Return pointer to first char (of chars) or inline comment in given string,
+ or pointer to null at end of string if neither found. Inline comment must
+ be prefixed by a whitespace character to register as a comment. */
+static char* find_chars_or_comment(const char* s, const char* chars)
+{
+#if INI_ALLOW_INLINE_COMMENTS
+ int was_space = 0;
+ while (*s && (!chars || !strchr(chars, *s)) &&
+ !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
+ was_space = isspace((unsigned char)(*s));
+ s++;
+ }
+#else
+ while (*s && (!chars || !strchr(chars, *s))) {
+ s++;
+ }
+#endif
+ return (char*)s;
+}
+
+/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
+static char* strncpy0(char* dest, const char* src, size_t size)
+{
+ strncpy(dest, src, size-1);
+ dest[size - 1] = '\0';
+ return dest;
+}
+
+/* See documentation in header file. */
+int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
+ void* user)
+{
+ /* Uses a fair bit of stack (use heap instead if you need to) */
+#if INI_USE_STACK
+ char line[INI_MAX_LINE];
+#else
+ char* line;
+#endif
+ char section[MAX_SECTION] = "";
+ char prev_name[MAX_NAME] = "";
+
+ char* start;
+ char* end;
+ char* name;
+ char* value;
+ int lineno = 0;
+ int error = 0;
+
+#if !INI_USE_STACK
+ line = (char*)malloc(INI_MAX_LINE);
+ if (!line) {
+ return -2;
+ }
+#endif
+
+ /* Scan through stream line by line */
+ while (reader(line, INI_MAX_LINE, stream) != NULL) {
+ lineno++;
+
+ start = line;
+#if INI_ALLOW_BOM
+ if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
+ (unsigned char)start[1] == 0xBB &&
+ (unsigned char)start[2] == 0xBF) {
+ start += 3;
+ }
+#endif
+ start = lskip(rstrip(start));
+
+ if (*start == ';' || *start == '#') {
+ /* Per Python configparser, allow both ; and # comments at the
+ start of a line */
+ }
+#if INI_ALLOW_MULTILINE
+ else if (*prev_name && *start && start > line) {
+ /* Non-blank line with leading whitespace, treat as continuation
+ of previous name's value (as per Python configparser). */
+ if (!handler(user, section, prev_name, start) && !error)
+ error = lineno;
+ }
+#endif
+ else if (*start == '[') {
+ /* A "[section]" line */
+ end = find_chars_or_comment(start + 1, "]");
+ if (*end == ']') {
+ *end = '\0';
+ strncpy0(section, start + 1, sizeof(section));
+ *prev_name = '\0';
+ }
+ else if (!error) {
+ /* No ']' found on section line */
+ error = lineno;
+ }
+ }
+ else if (*start) {
+ /* Not a comment, must be a name[=:]value pair */
+ end = find_chars_or_comment(start, "=:");
+ if (*end == '=' || *end == ':') {
+ *end = '\0';
+ name = rstrip(start);
+ value = lskip(end + 1);
+#if INI_ALLOW_INLINE_COMMENTS
+ end = find_chars_or_comment(value, NULL);
+ if (*end)
+ *end = '\0';
+#endif
+ rstrip(value);
+
+ /* Valid name[=:]value pair found, call handler */
+ strncpy0(prev_name, name, sizeof(prev_name));
+ if (!handler(user, section, name, value) && !error)
+ error = lineno;
+ memset(value, 0, strlen(value));
+ }
+ else if (!error) {
+ /* No '=' or ':' found on name[=:]value line */
+ error = lineno;
+ }
+ }
+
+#if INI_STOP_ON_FIRST_ERROR
+ if (error)
+ break;
+#endif
+ }
+
+#if !INI_USE_STACK
+ free(line);
+#endif
+
+ return error;
+}
+
+/* See documentation in header file. */
+int ini_parse_file(FILE* file, ini_handler handler, void* user)
+{
+ return ini_parse_stream((ini_reader)fgets, file, handler, user);
+}
+
+/* See documentation in header file. */
+int ini_parse(const char* filename, ini_handler handler, void* user)
+{
+ FILE* file;
+ int error;
+
+ file = fopen(filename, "r");
+ if (!file)
+ return -1;
+ error = ini_parse_file(file, handler, user);
+ fclose(file);
+ return error;
+}
diff --git a/rootston/input.c b/rootston/input.c
new file mode 100644
index 00000000..a863b919
--- /dev/null
+++ b/rootston/input.c
@@ -0,0 +1,145 @@
+#define _POSIX_C_SOURCE 200112L
+#include <assert.h>
+#include <stdlib.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wlr/backend/libinput.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/util/log.h>
+#include <wlr/xcursor.h>
+#if WLR_HAS_XWAYLAND
+#include <wlr/xwayland.h>
+#endif
+#include "rootston/config.h"
+#include "rootston/input.h"
+#include "rootston/keyboard.h"
+#include "rootston/seat.h"
+#include "rootston/server.h"
+
+static const char *device_type(enum wlr_input_device_type type) {
+ switch (type) {
+ case WLR_INPUT_DEVICE_KEYBOARD:
+ return "keyboard";
+ case WLR_INPUT_DEVICE_POINTER:
+ return "pointer";
+ case WLR_INPUT_DEVICE_SWITCH:
+ return "switch";
+ case WLR_INPUT_DEVICE_TOUCH:
+ return "touch";
+ case WLR_INPUT_DEVICE_TABLET_TOOL:
+ return "tablet tool";
+ case WLR_INPUT_DEVICE_TABLET_PAD:
+ return "tablet pad";
+ }
+ return NULL;
+}
+
+struct roots_seat *input_get_seat(struct roots_input *input, char *name) {
+ struct roots_seat *seat = NULL;
+ wl_list_for_each(seat, &input->seats, link) {
+ if (strcmp(seat->seat->name, name) == 0) {
+ return seat;
+ }
+ }
+
+ seat = roots_seat_create(input, name);
+ return seat;
+}
+
+static void handle_new_input(struct wl_listener *listener, void *data) {
+ struct wlr_input_device *device = data;
+ struct roots_input *input = wl_container_of(listener, input, new_input);
+
+ char *seat_name = ROOTS_CONFIG_DEFAULT_SEAT_NAME;
+ struct roots_device_config *dc =
+ roots_config_get_device(input->config, device);
+ if (dc) {
+ seat_name = dc->seat;
+ }
+
+ struct roots_seat *seat = input_get_seat(input, seat_name);
+ if (!seat) {
+ wlr_log(WLR_ERROR, "could not create roots seat");
+ return;
+ }
+
+ wlr_log(WLR_DEBUG, "New input device: %s (%d:%d) %s seat:%s", device->name,
+ device->vendor, device->product, device_type(device->type), seat_name);
+
+ roots_seat_add_device(seat, device);
+
+ if (dc && wlr_input_device_is_libinput(device)) {
+ struct libinput_device *libinput_dev =
+ wlr_libinput_get_device_handle(device);
+
+ wlr_log(WLR_DEBUG, "input has config, tap_enabled: %d\n", dc->tap_enabled);
+ if (dc->tap_enabled) {
+ libinput_device_config_tap_set_enabled(libinput_dev,
+ LIBINPUT_CONFIG_TAP_ENABLED);
+ }
+ }
+}
+
+struct roots_input *input_create(struct roots_server *server,
+ struct roots_config *config) {
+ wlr_log(WLR_DEBUG, "Initializing roots input");
+ assert(server->desktop);
+
+ struct roots_input *input = calloc(1, sizeof(struct roots_input));
+ if (input == NULL) {
+ return NULL;
+ }
+
+ input->config = config;
+ input->server = server;
+
+ wl_list_init(&input->seats);
+
+ input->new_input.notify = handle_new_input;
+ wl_signal_add(&server->backend->events.new_input, &input->new_input);
+
+ return input;
+}
+
+void input_destroy(struct roots_input *input) {
+ // TODO
+}
+
+struct roots_seat *input_seat_from_wlr_seat(struct roots_input *input,
+ struct wlr_seat *wlr_seat) {
+ struct roots_seat *seat = NULL;
+ wl_list_for_each(seat, &input->seats, link) {
+ if (seat->seat == wlr_seat) {
+ return seat;
+ }
+ }
+ return seat;
+}
+
+bool input_view_has_focus(struct roots_input *input, struct roots_view *view) {
+ if (!view) {
+ return false;
+ }
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &input->seats, link) {
+ if (view == roots_seat_get_focus(seat)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static inline int64_t timespec_to_msec(const struct timespec *a) {
+ return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000;
+}
+
+void input_update_cursor_focus(struct roots_input *input) {
+ struct roots_seat *seat;
+ struct timespec now;
+ wl_list_for_each(seat, &input->seats, link) {
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ roots_cursor_update_position(seat->cursor, timespec_to_msec(&now));
+ }
+}
diff --git a/rootston/keyboard.c b/rootston/keyboard.c
new file mode 100644
index 00000000..9d3fadf6
--- /dev/null
+++ b/rootston/keyboard.c
@@ -0,0 +1,349 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/backend/session.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_pointer_constraints_v1.h>
+#include <wlr/types/wlr_pointer.h>
+#include <wlr/util/log.h>
+#include <xkbcommon/xkbcommon.h>
+#include "rootston/bindings.h"
+#include "rootston/input.h"
+#include "rootston/keyboard.h"
+#include "rootston/seat.h"
+
+static ssize_t pressed_keysyms_index(xkb_keysym_t *pressed_keysyms,
+ xkb_keysym_t keysym) {
+ for (size_t i = 0; i < ROOTS_KEYBOARD_PRESSED_KEYSYMS_CAP; ++i) {
+ if (pressed_keysyms[i] == keysym) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static size_t pressed_keysyms_length(xkb_keysym_t *pressed_keysyms) {
+ size_t n = 0;
+ for (size_t i = 0; i < ROOTS_KEYBOARD_PRESSED_KEYSYMS_CAP; ++i) {
+ if (pressed_keysyms[i] != XKB_KEY_NoSymbol) {
+ ++n;
+ }
+ }
+ return n;
+}
+
+static void pressed_keysyms_add(xkb_keysym_t *pressed_keysyms,
+ xkb_keysym_t keysym) {
+ ssize_t i = pressed_keysyms_index(pressed_keysyms, keysym);
+ if (i < 0) {
+ i = pressed_keysyms_index(pressed_keysyms, XKB_KEY_NoSymbol);
+ if (i >= 0) {
+ pressed_keysyms[i] = keysym;
+ }
+ }
+}
+
+static void pressed_keysyms_remove(xkb_keysym_t *pressed_keysyms,
+ xkb_keysym_t keysym) {
+ ssize_t i = pressed_keysyms_index(pressed_keysyms, keysym);
+ if (i >= 0) {
+ pressed_keysyms[i] = XKB_KEY_NoSymbol;
+ }
+}
+
+static bool keysym_is_modifier(xkb_keysym_t keysym) {
+ switch (keysym) {
+ case XKB_KEY_Shift_L: case XKB_KEY_Shift_R:
+ case XKB_KEY_Control_L: case XKB_KEY_Control_R:
+ case XKB_KEY_Caps_Lock:
+ case XKB_KEY_Shift_Lock:
+ case XKB_KEY_Meta_L: case XKB_KEY_Meta_R:
+ case XKB_KEY_Alt_L: case XKB_KEY_Alt_R:
+ case XKB_KEY_Super_L: case XKB_KEY_Super_R:
+ case XKB_KEY_Hyper_L: case XKB_KEY_Hyper_R:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void pressed_keysyms_update(xkb_keysym_t *pressed_keysyms,
+ const xkb_keysym_t *keysyms, size_t keysyms_len,
+ enum wlr_key_state state) {
+ for (size_t i = 0; i < keysyms_len; ++i) {
+ if (keysym_is_modifier(keysyms[i])) {
+ continue;
+ }
+ if (state == WLR_KEY_PRESSED) {
+ pressed_keysyms_add(pressed_keysyms, keysyms[i]);
+ } else { // WLR_KEY_RELEASED
+ pressed_keysyms_remove(pressed_keysyms, keysyms[i]);
+ }
+ }
+}
+
+static void keyboard_binding_execute(struct roots_keyboard *keyboard,
+ const char *command) {
+ execute_binding_command(keyboard->seat, keyboard->input, command);
+}
+
+/**
+ * Execute a built-in, hardcoded compositor binding. These are triggered from a
+ * single keysym.
+ *
+ * Returns true if the keysym was handled by a binding and false if the event
+ * should be propagated to clients.
+ */
+static bool keyboard_execute_compositor_binding(struct roots_keyboard *keyboard,
+ xkb_keysym_t keysym) {
+ if (keysym >= XKB_KEY_XF86Switch_VT_1 &&
+ keysym <= XKB_KEY_XF86Switch_VT_12) {
+ struct roots_server *server = keyboard->input->server;
+
+ struct wlr_session *session = wlr_backend_get_session(server->backend);
+ if (session) {
+ unsigned vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1;
+ wlr_session_change_vt(session, vt);
+ }
+
+ return true;
+ }
+
+ if (keysym == XKB_KEY_Escape) {
+ wlr_seat_pointer_end_grab(keyboard->seat->seat);
+ wlr_seat_keyboard_end_grab(keyboard->seat->seat);
+ roots_seat_end_compositor_grab(keyboard->seat);
+ }
+
+ return false;
+}
+
+/**
+ * Execute keyboard bindings. These include compositor bindings and user-defined
+ * bindings.
+ *
+ * Returns true if the keysym was handled by a binding and false if the event
+ * should be propagated to clients.
+ */
+static bool keyboard_execute_binding(struct roots_keyboard *keyboard,
+ xkb_keysym_t *pressed_keysyms, uint32_t modifiers,
+ const xkb_keysym_t *keysyms, size_t keysyms_len) {
+ for (size_t i = 0; i < keysyms_len; ++i) {
+ if (keyboard_execute_compositor_binding(keyboard, keysyms[i])) {
+ return true;
+ }
+ }
+
+ // User-defined bindings
+ size_t n = pressed_keysyms_length(pressed_keysyms);
+ struct wl_list *bindings = &keyboard->input->server->config->bindings;
+ struct roots_binding_config *bc;
+ wl_list_for_each(bc, bindings, link) {
+ if (modifiers ^ bc->modifiers || n != bc->keysyms_len) {
+ continue;
+ }
+
+ bool ok = true;
+ for (size_t i = 0; i < bc->keysyms_len; i++) {
+ ssize_t j = pressed_keysyms_index(pressed_keysyms, bc->keysyms[i]);
+ if (j < 0) {
+ ok = false;
+ break;
+ }
+ }
+
+ if (ok) {
+ keyboard_binding_execute(keyboard, bc->command);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Get keysyms and modifiers from the keyboard as xkb sees them.
+ *
+ * This uses the xkb keysyms translation based on pressed modifiers and clears
+ * the consumed modifiers from the list of modifiers passed to keybind
+ * detection.
+ *
+ * On US layout, pressing Alt+Shift+2 will trigger Alt+@.
+ */
+static size_t keyboard_keysyms_translated(struct roots_keyboard *keyboard,
+ xkb_keycode_t keycode, const xkb_keysym_t **keysyms,
+ uint32_t *modifiers) {
+ *modifiers = wlr_keyboard_get_modifiers(keyboard->device->keyboard);
+ xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods2(
+ keyboard->device->keyboard->xkb_state, keycode, XKB_CONSUMED_MODE_XKB);
+ *modifiers = *modifiers & ~consumed;
+
+ return xkb_state_key_get_syms(keyboard->device->keyboard->xkb_state,
+ keycode, keysyms);
+}
+
+/*
+ * Get keysyms and modifiers from the keyboard as if modifiers didn't change
+ * keysyms.
+ *
+ * This avoids the xkb keysym translation based on modifiers considered pressed
+ * in the state.
+ *
+ * This will trigger keybinds such as Alt+Shift+2.
+ */
+static size_t keyboard_keysyms_raw(struct roots_keyboard *keyboard,
+ xkb_keycode_t keycode, const xkb_keysym_t **keysyms,
+ uint32_t *modifiers) {
+ *modifiers = wlr_keyboard_get_modifiers(keyboard->device->keyboard);
+
+ xkb_layout_index_t layout_index = xkb_state_key_get_layout(
+ keyboard->device->keyboard->xkb_state, keycode);
+ return xkb_keymap_key_get_syms_by_level(keyboard->device->keyboard->keymap,
+ keycode, layout_index, 0, keysyms);
+}
+
+void roots_keyboard_handle_key(struct roots_keyboard *keyboard,
+ struct wlr_event_keyboard_key *event) {
+ xkb_keycode_t keycode = event->keycode + 8;
+
+ bool handled = false;
+ uint32_t modifiers;
+ const xkb_keysym_t *keysyms;
+ size_t keysyms_len;
+
+ // Handle translated keysyms
+
+ keysyms_len = keyboard_keysyms_translated(keyboard, keycode, &keysyms,
+ &modifiers);
+ pressed_keysyms_update(keyboard->pressed_keysyms_translated, keysyms,
+ keysyms_len, event->state);
+ if (event->state == WLR_KEY_PRESSED) {
+ handled = keyboard_execute_binding(keyboard,
+ keyboard->pressed_keysyms_translated, modifiers, keysyms,
+ keysyms_len);
+ }
+
+ // Handle raw keysyms
+ keysyms_len = keyboard_keysyms_raw(keyboard, keycode, &keysyms, &modifiers);
+ pressed_keysyms_update(keyboard->pressed_keysyms_raw, keysyms, keysyms_len,
+ event->state);
+ if (event->state == WLR_KEY_PRESSED && !handled) {
+ handled = keyboard_execute_binding(keyboard,
+ keyboard->pressed_keysyms_raw, modifiers, keysyms, keysyms_len);
+ }
+
+ if (!handled) {
+ wlr_seat_set_keyboard(keyboard->seat->seat, keyboard->device);
+ wlr_seat_keyboard_notify_key(keyboard->seat->seat, event->time_msec,
+ event->keycode, event->state);
+ }
+}
+
+void roots_keyboard_handle_modifiers(struct roots_keyboard *r_keyboard) {
+ struct wlr_seat *seat = r_keyboard->seat->seat;
+ wlr_seat_set_keyboard(seat, r_keyboard->device);
+ wlr_seat_keyboard_notify_modifiers(seat,
+ &r_keyboard->device->keyboard->modifiers);
+}
+
+static void keyboard_config_merge(struct roots_keyboard_config *config,
+ struct roots_keyboard_config *fallback) {
+ if (fallback == NULL) {
+ return;
+ }
+ if (config->rules == NULL) {
+ config->rules = fallback->rules;
+ }
+ if (config->model == NULL) {
+ config->model = fallback->model;
+ }
+ if (config->layout == NULL) {
+ config->layout = fallback->layout;
+ }
+ if (config->variant == NULL) {
+ config->variant = fallback->variant;
+ }
+ if (config->options == NULL) {
+ config->options = fallback->options;
+ }
+ if (config->meta_key == 0) {
+ config->meta_key = fallback->meta_key;
+ }
+ if (config->name == NULL) {
+ config->name = fallback->name;
+ }
+ if (config->repeat_rate <= 0) {
+ config->repeat_rate = fallback->repeat_rate;
+ }
+ if (config->repeat_delay <= 0) {
+ config->repeat_delay = fallback->repeat_delay;
+ }
+}
+
+struct roots_keyboard *roots_keyboard_create(struct wlr_input_device *device,
+ struct roots_input *input) {
+ struct roots_keyboard *keyboard = calloc(sizeof(struct roots_keyboard), 1);
+ if (keyboard == NULL) {
+ return NULL;
+ }
+ device->data = keyboard;
+ keyboard->device = device;
+ keyboard->input = input;
+
+ struct roots_keyboard_config *config =
+ calloc(1, sizeof(struct roots_keyboard_config));
+ if (config == NULL) {
+ free(keyboard);
+ return NULL;
+ }
+ keyboard_config_merge(config, roots_config_get_keyboard(input->config, device));
+ keyboard_config_merge(config, roots_config_get_keyboard(input->config, NULL));
+
+ struct roots_keyboard_config env_config = {
+ .rules = getenv("XKB_DEFAULT_RULES"),
+ .model = getenv("XKB_DEFAULT_MODEL"),
+ .layout = getenv("XKB_DEFAULT_LAYOUT"),
+ .variant = getenv("XKB_DEFAULT_VARIANT"),
+ .options = getenv("XKB_DEFAULT_OPTIONS"),
+ };
+ keyboard_config_merge(config, &env_config);
+ keyboard->config = config;
+
+ struct xkb_rule_names rules = { 0 };
+ rules.rules = config->rules;
+ rules.model = config->model;
+ rules.layout = config->layout;
+ rules.variant = config->variant;
+ rules.options = config->options;
+ struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ if (context == NULL) {
+ wlr_log(WLR_ERROR, "Cannot create XKB context");
+ return NULL;
+ }
+
+ struct xkb_keymap *keymap = xkb_map_new_from_names(context, &rules,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+ if (keymap == NULL) {
+ xkb_context_unref(context);
+ wlr_log(WLR_ERROR, "Cannot create XKB keymap");
+ return NULL;
+ }
+
+ wlr_keyboard_set_keymap(device->keyboard, keymap);
+ xkb_keymap_unref(keymap);
+ xkb_context_unref(context);
+
+ int repeat_rate = (config->repeat_rate > 0) ? config->repeat_rate : 25;
+ int repeat_delay = (config->repeat_delay > 0) ? config->repeat_delay : 600;
+ wlr_keyboard_set_repeat_info(device->keyboard, repeat_rate, repeat_delay);
+
+ return keyboard;
+}
+
+void roots_keyboard_destroy(struct roots_keyboard *keyboard) {
+ wl_list_remove(&keyboard->link);
+ free(keyboard->config);
+ free(keyboard);
+}
diff --git a/rootston/layer_shell.c b/rootston/layer_shell.c
new file mode 100644
index 00000000..4daa20d1
--- /dev/null
+++ b/rootston/layer_shell.c
@@ -0,0 +1,506 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200112L
+#endif
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/types/wlr_layer_shell_v1.h>
+#include <wlr/util/log.h>
+#include "rootston/desktop.h"
+#include "rootston/layers.h"
+#include "rootston/output.h"
+#include "rootston/server.h"
+
+static void apply_exclusive(struct wlr_box *usable_area,
+ uint32_t anchor, int32_t exclusive,
+ int32_t margin_top, int32_t margin_right,
+ int32_t margin_bottom, int32_t margin_left) {
+ if (exclusive <= 0) {
+ return;
+ }
+ struct {
+ uint32_t anchors;
+ int *positive_axis;
+ int *negative_axis;
+ int margin;
+ } edges[] = {
+ {
+ .anchors =
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP,
+ .positive_axis = &usable_area->y,
+ .negative_axis = &usable_area->height,
+ .margin = margin_top,
+ },
+ {
+ .anchors =
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
+ .positive_axis = NULL,
+ .negative_axis = &usable_area->height,
+ .margin = margin_bottom,
+ },
+ {
+ .anchors =
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
+ .positive_axis = &usable_area->x,
+ .negative_axis = &usable_area->width,
+ .margin = margin_left,
+ },
+ {
+ .anchors =
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
+ .positive_axis = NULL,
+ .negative_axis = &usable_area->width,
+ .margin = margin_right,
+ },
+ };
+ for (size_t i = 0; i < sizeof(edges) / sizeof(edges[0]); ++i) {
+ if ((anchor & edges[i].anchors) == edges[i].anchors) {
+ if (edges[i].positive_axis) {
+ *edges[i].positive_axis += exclusive + edges[i].margin;
+ }
+ if (edges[i].negative_axis) {
+ *edges[i].negative_axis -= exclusive + edges[i].margin;
+ }
+ }
+ }
+}
+
+static void update_cursors(struct roots_layer_surface *roots_surface,
+ struct wl_list *seats /* struct roots_seat */) {
+ struct roots_seat *seat;
+ wl_list_for_each(seat, seats, link) {
+ double sx, sy;
+
+ struct wlr_surface *surface = desktop_surface_at(
+ seat->input->server->desktop,
+ seat->cursor->cursor->x, seat->cursor->cursor->y, &sx, &sy, NULL);
+
+ if (surface == roots_surface->layer_surface->surface) {
+ struct timespec time;
+ if (clock_gettime(CLOCK_MONOTONIC, &time) == 0) {
+ roots_cursor_update_position(seat->cursor,
+ time.tv_sec * 1000 + time.tv_nsec / 1000000);
+ } else {
+ wlr_log(WLR_ERROR, "Failed to get time, not updating"
+ "position. Errno: %s\n", strerror(errno));
+ }
+ }
+ }
+}
+
+static void arrange_layer(struct wlr_output *output,
+ struct wl_list *seats /* struct *roots_seat */,
+ struct wl_list *list /* struct *roots_layer_surface */,
+ struct wlr_box *usable_area, bool exclusive) {
+ struct roots_layer_surface *roots_surface;
+ struct wlr_box full_area = { 0 };
+ wlr_output_effective_resolution(output,
+ &full_area.width, &full_area.height);
+ wl_list_for_each_reverse(roots_surface, list, link) {
+ struct wlr_layer_surface_v1 *layer = roots_surface->layer_surface;
+ struct wlr_layer_surface_v1_state *state = &layer->current;
+ if (exclusive != (state->exclusive_zone > 0)) {
+ continue;
+ }
+ struct wlr_box bounds;
+ if (state->exclusive_zone == -1) {
+ bounds = full_area;
+ } else {
+ bounds = *usable_area;
+ }
+ struct wlr_box box = {
+ .width = state->desired_width,
+ .height = state->desired_height
+ };
+ // Horizontal axis
+ const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
+ | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
+ if ((state->anchor & both_horiz) && box.width == 0) {
+ box.x = bounds.x;
+ box.width = bounds.width;
+ } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) {
+ box.x = bounds.x;
+ } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) {
+ box.x = bounds.x + (bounds.width - box.width);
+ } else {
+ box.x = bounds.x + ((bounds.width / 2) - (box.width / 2));
+ }
+ // Vertical axis
+ const uint32_t both_vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
+ | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
+ if ((state->anchor & both_vert) && box.height == 0) {
+ box.y = bounds.y;
+ box.height = bounds.height;
+ } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) {
+ box.y = bounds.y;
+ } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) {
+ box.y = bounds.y + (bounds.height - box.height);
+ } else {
+ box.y = bounds.y + ((bounds.height / 2) - (box.height / 2));
+ }
+ // Margin
+ if ((state->anchor & both_horiz) == both_horiz) {
+ box.x += state->margin.left;
+ box.width -= state->margin.left + state->margin.right;
+ } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) {
+ box.x += state->margin.left;
+ } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) {
+ box.x -= state->margin.right;
+ }
+ if ((state->anchor & both_vert) == both_vert) {
+ box.y += state->margin.top;
+ box.height -= state->margin.top + state->margin.bottom;
+ } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) {
+ box.y += state->margin.top;
+ } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) {
+ box.y -= state->margin.bottom;
+ }
+ if (box.width < 0 || box.height < 0) {
+ // TODO: Bubble up a protocol error?
+ wlr_layer_surface_v1_close(layer);
+ continue;
+ }
+
+ // Apply
+ struct wlr_box old_geo = roots_surface->geo;
+ roots_surface->geo = box;
+ apply_exclusive(usable_area, state->anchor, state->exclusive_zone,
+ state->margin.top, state->margin.right,
+ state->margin.bottom, state->margin.left);
+ wlr_layer_surface_v1_configure(layer, box.width, box.height);
+
+ // Having a cursor newly end up over the moved layer will not
+ // automatically send a motion event to the surface. The event needs to
+ // be synthesized.
+ // Only update layer surfaces which kept their size (and so buffers) the
+ // same, because those with resized buffers will be handled separately.
+
+ if (roots_surface->geo.x != old_geo.x
+ || roots_surface->geo.y != old_geo.y) {
+ update_cursors(roots_surface, seats);
+ }
+ }
+}
+
+void arrange_layers(struct roots_output *output) {
+ struct wlr_box usable_area = { 0 };
+ wlr_output_effective_resolution(output->wlr_output,
+ &usable_area.width, &usable_area.height);
+
+ // Arrange exclusive surfaces from top->bottom
+ arrange_layer(output->wlr_output, &output->desktop->server->input->seats,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],
+ &usable_area, true);
+ arrange_layer(output->wlr_output, &output->desktop->server->input->seats,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
+ &usable_area, true);
+ arrange_layer(output->wlr_output, &output->desktop->server->input->seats,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
+ &usable_area, true);
+ arrange_layer(output->wlr_output, &output->desktop->server->input->seats,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
+ &usable_area, true);
+ memcpy(&output->usable_area, &usable_area, sizeof(struct wlr_box));
+
+ struct roots_view *view;
+ wl_list_for_each(view, &output->desktop->views, link) {
+ if (view->maximized) {
+ view_arrange_maximized(view);
+ }
+ }
+
+ // Arrange non-exlusive surfaces from top->bottom
+ arrange_layer(output->wlr_output, &output->desktop->server->input->seats,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],
+ &usable_area, false);
+ arrange_layer(output->wlr_output, &output->desktop->server->input->seats,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
+ &usable_area, false);
+ arrange_layer(output->wlr_output, &output->desktop->server->input->seats,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
+ &usable_area, false);
+ arrange_layer(output->wlr_output, &output->desktop->server->input->seats,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
+ &usable_area, false);
+
+ // Find topmost keyboard interactive layer, if such a layer exists
+ uint32_t layers_above_shell[] = {
+ ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY,
+ ZWLR_LAYER_SHELL_V1_LAYER_TOP,
+ };
+ size_t nlayers = sizeof(layers_above_shell) / sizeof(layers_above_shell[0]);
+ struct roots_layer_surface *layer, *topmost = NULL;
+ for (size_t i = 0; i < nlayers; ++i) {
+ wl_list_for_each_reverse(layer,
+ &output->layers[layers_above_shell[i]], link) {
+ if (layer->layer_surface->current.keyboard_interactive) {
+ topmost = layer;
+ break;
+ }
+ }
+ if (topmost != NULL) {
+ break;
+ }
+ }
+
+ struct roots_input *input = output->desktop->server->input;
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &input->seats, link) {
+ roots_seat_set_focus_layer(seat,
+ topmost ? topmost->layer_surface : NULL);
+ }
+}
+
+static void handle_output_destroy(struct wl_listener *listener, void *data) {
+ struct roots_layer_surface *layer =
+ wl_container_of(listener, layer, output_destroy);
+ layer->layer_surface->output = NULL;
+ wl_list_remove(&layer->output_destroy.link);
+ wlr_layer_surface_v1_close(layer->layer_surface);
+}
+
+static void handle_surface_commit(struct wl_listener *listener, void *data) {
+ struct roots_layer_surface *layer =
+ wl_container_of(listener, layer, surface_commit);
+ struct wlr_layer_surface_v1 *layer_surface = layer->layer_surface;
+ struct wlr_output *wlr_output = layer_surface->output;
+ if (wlr_output != NULL) {
+ struct roots_output *output = wlr_output->data;
+ struct wlr_box old_geo = layer->geo;
+ arrange_layers(output);
+
+ // Cursor changes which happen as a consequence of resizing a layer
+ // surface are applied in arrange_layers. Because the resize happens
+ // before the underlying surface changes, it will only receive a cursor
+ // update if the new cursor position crosses the *old* sized surface in
+ // the *new* layer surface.
+ // Another cursor move event is needed when the surface actually
+ // changes.
+ struct wlr_surface *surface = layer_surface->surface;
+ if (surface->previous.width != surface->current.width ||
+ surface->previous.height != surface->current.height) {
+ update_cursors(layer, &output->desktop->server->input->seats);
+ }
+
+ if (memcmp(&old_geo, &layer->geo, sizeof(struct wlr_box)) != 0) {
+ output_damage_whole_local_surface(output, layer_surface->surface,
+ old_geo.x, old_geo.y, 0);
+ output_damage_whole_local_surface(output, layer_surface->surface,
+ layer->geo.x, layer->geo.y, 0);
+ } else {
+ output_damage_from_local_surface(output, layer_surface->surface,
+ layer->geo.x, layer->geo.y, 0);
+ }
+ }
+}
+
+static void unmap(struct wlr_layer_surface_v1 *layer_surface) {
+ struct roots_layer_surface *layer = layer_surface->data;
+ struct wlr_output *wlr_output = layer_surface->output;
+ if (wlr_output != NULL) {
+ struct roots_output *output = wlr_output->data;
+ output_damage_whole_local_surface(output, layer_surface->surface,
+ layer->geo.x, layer->geo.y, 0);
+ }
+}
+
+static void handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_layer_surface *layer = wl_container_of(
+ listener, layer, destroy);
+ if (layer->layer_surface->mapped) {
+ unmap(layer->layer_surface);
+ }
+ wl_list_remove(&layer->link);
+ wl_list_remove(&layer->destroy.link);
+ wl_list_remove(&layer->map.link);
+ wl_list_remove(&layer->unmap.link);
+ wl_list_remove(&layer->surface_commit.link);
+ if (layer->layer_surface->output) {
+ wl_list_remove(&layer->output_destroy.link);
+ arrange_layers((struct roots_output *)layer->layer_surface->output->data);
+ }
+ free(layer);
+}
+
+static void handle_map(struct wl_listener *listener, void *data) {
+ struct wlr_layer_surface_v1 *layer_surface = data;
+ struct roots_layer_surface *layer = layer_surface->data;
+ struct wlr_output *wlr_output = layer_surface->output;
+ struct roots_output *output = wlr_output->data;
+ output_damage_whole_local_surface(output, layer_surface->surface,
+ layer->geo.x, layer->geo.y, 0);
+ wlr_surface_send_enter(layer_surface->surface, wlr_output);
+}
+
+static void handle_unmap(struct wl_listener *listener, void *data) {
+ struct roots_layer_surface *layer = wl_container_of(
+ listener, layer, unmap);
+ struct wlr_output *wlr_output = layer->layer_surface->output;
+ struct roots_output *output = wlr_output->data;
+ unmap(layer->layer_surface);
+ input_update_cursor_focus(output->desktop->server->input);
+}
+
+static void popup_handle_map(struct wl_listener *listener, void *data) {
+ struct roots_layer_popup *popup = wl_container_of(listener, popup, map);
+ struct roots_layer_surface *layer = popup->parent;
+ struct wlr_output *wlr_output = layer->layer_surface->output;
+ struct roots_output *output = wlr_output->data;
+ int ox = popup->wlr_popup->geometry.x + layer->geo.x;
+ int oy = popup->wlr_popup->geometry.y + layer->geo.y;
+ output_damage_whole_local_surface(output, popup->wlr_popup->base->surface,
+ ox, oy, 0);
+ input_update_cursor_focus(output->desktop->server->input);
+}
+
+static void popup_handle_unmap(struct wl_listener *listener, void *data) {
+ struct roots_layer_popup *popup = wl_container_of(listener, popup, unmap);
+ struct roots_layer_surface *layer = popup->parent;
+ struct wlr_output *wlr_output = layer->layer_surface->output;
+ struct roots_output *output = wlr_output->data;
+ int ox = popup->wlr_popup->geometry.x + layer->geo.x;
+ int oy = popup->wlr_popup->geometry.y + layer->geo.y;
+ output_damage_whole_local_surface(output, popup->wlr_popup->base->surface,
+ ox, oy, 0);
+}
+
+static void popup_handle_commit(struct wl_listener *listener, void *data) {
+ struct roots_layer_popup *popup = wl_container_of(listener, popup, commit);
+ struct roots_layer_surface *layer = popup->parent;
+ struct wlr_output *wlr_output = layer->layer_surface->output;
+ struct roots_output *output = wlr_output->data;
+ int ox = popup->wlr_popup->geometry.x + layer->geo.x;
+ int oy = popup->wlr_popup->geometry.y + layer->geo.y;
+ output_damage_from_local_surface(output, popup->wlr_popup->base->surface,
+ ox, oy, 0);
+}
+
+static void popup_handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_layer_popup *popup =
+ wl_container_of(listener, popup, destroy);
+
+ wl_list_remove(&popup->map.link);
+ wl_list_remove(&popup->unmap.link);
+ wl_list_remove(&popup->destroy.link);
+ wl_list_remove(&popup->commit.link);
+ free(popup);
+}
+
+static struct roots_layer_popup *popup_create(struct roots_layer_surface *parent,
+ struct wlr_xdg_popup *wlr_popup) {
+ struct roots_layer_popup *popup =
+ calloc(1, sizeof(struct roots_layer_popup));
+ if (popup == NULL) {
+ return NULL;
+ }
+ popup->wlr_popup = wlr_popup;
+ popup->parent = parent;
+ popup->map.notify = popup_handle_map;
+ wl_signal_add(&wlr_popup->base->events.map, &popup->map);
+ popup->unmap.notify = popup_handle_unmap;
+ wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap);
+ popup->destroy.notify = popup_handle_destroy;
+ wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy);
+ popup->commit.notify = popup_handle_commit;
+ wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit);
+ /* TODO: popups can have popups, see xdg_shell::popup_create */
+
+ return popup;
+}
+
+static void handle_new_popup(struct wl_listener *listener, void *data) {
+ struct roots_layer_surface *roots_layer_surface =
+ wl_container_of(listener, roots_layer_surface, new_popup);
+ struct wlr_xdg_popup *wlr_popup = data;
+ popup_create(roots_layer_surface, wlr_popup);
+}
+
+void handle_layer_shell_surface(struct wl_listener *listener, void *data) {
+ struct wlr_layer_surface_v1 *layer_surface = data;
+ struct roots_desktop *desktop =
+ wl_container_of(listener, desktop, layer_shell_surface);
+ wlr_log(WLR_DEBUG, "new layer surface: namespace %s layer %d anchor %d "
+ "size %dx%d margin %d,%d,%d,%d",
+ layer_surface->namespace, layer_surface->layer, layer_surface->layer,
+ layer_surface->client_pending.desired_width,
+ layer_surface->client_pending.desired_height,
+ layer_surface->client_pending.margin.top,
+ layer_surface->client_pending.margin.right,
+ layer_surface->client_pending.margin.bottom,
+ layer_surface->client_pending.margin.left);
+
+ if (!layer_surface->output) {
+ struct roots_input *input = desktop->server->input;
+ struct roots_seat *seat = input_last_active_seat(input);
+ assert(seat); // Technically speaking we should handle this case
+ struct wlr_output *output =
+ wlr_output_layout_output_at(desktop->layout,
+ seat->cursor->cursor->x,
+ seat->cursor->cursor->y);
+ if (!output) {
+ wlr_log(WLR_ERROR, "Couldn't find output at (%.0f,%.0f)",
+ seat->cursor->cursor->x,
+ seat->cursor->cursor->y);
+ output = wlr_output_layout_get_center_output(desktop->layout);
+ }
+ if (output) {
+ layer_surface->output = output;
+ } else {
+ wlr_layer_surface_v1_close(layer_surface);
+ return;
+ }
+ }
+
+ struct roots_layer_surface *roots_surface =
+ calloc(1, sizeof(struct roots_layer_surface));
+ if (!roots_surface) {
+ return;
+ }
+
+ roots_surface->surface_commit.notify = handle_surface_commit;
+ wl_signal_add(&layer_surface->surface->events.commit,
+ &roots_surface->surface_commit);
+
+ roots_surface->output_destroy.notify = handle_output_destroy;
+ wl_signal_add(&layer_surface->output->events.destroy,
+ &roots_surface->output_destroy);
+
+ roots_surface->destroy.notify = handle_destroy;
+ wl_signal_add(&layer_surface->events.destroy, &roots_surface->destroy);
+ roots_surface->map.notify = handle_map;
+ wl_signal_add(&layer_surface->events.map, &roots_surface->map);
+ roots_surface->unmap.notify = handle_unmap;
+ wl_signal_add(&layer_surface->events.unmap, &roots_surface->unmap);
+ roots_surface->new_popup.notify = handle_new_popup;
+ wl_signal_add(&layer_surface->events.new_popup, &roots_surface->new_popup);
+ // TODO: Listen for subsurfaces
+
+ roots_surface->layer_surface = layer_surface;
+ layer_surface->data = roots_surface;
+
+ struct roots_output *output = layer_surface->output->data;
+ wl_list_insert(&output->layers[layer_surface->layer], &roots_surface->link);
+
+ // Temporarily set the layer's current state to client_pending
+ // So that we can easily arrange it
+ struct wlr_layer_surface_v1_state old_state = layer_surface->current;
+ layer_surface->current = layer_surface->client_pending;
+
+ arrange_layers(output);
+
+ layer_surface->current = old_state;
+}
diff --git a/rootston/main.c b/rootston/main.c
new file mode 100644
index 00000000..7e25dab1
--- /dev/null
+++ b/rootston/main.c
@@ -0,0 +1,81 @@
+#define _POSIX_C_SOURCE 200112L
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/backend.h>
+#include <wlr/backend/headless.h>
+#include <wlr/backend/multi.h>
+#include <wlr/config.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/util/log.h>
+#include "rootston/config.h"
+#include "rootston/server.h"
+
+struct roots_server server = { 0 };
+
+int main(int argc, char **argv) {
+ wlr_log_init(WLR_DEBUG, NULL);
+ server.config = roots_config_create_from_args(argc, argv);
+ server.wl_display = wl_display_create();
+ server.wl_event_loop = wl_display_get_event_loop(server.wl_display);
+ assert(server.config && server.wl_display && server.wl_event_loop);
+
+ server.backend = wlr_backend_autocreate(server.wl_display, NULL);
+ if (server.backend == NULL) {
+ wlr_log(WLR_ERROR, "could not start backend");
+ return 1;
+ }
+
+ server.renderer = wlr_backend_get_renderer(server.backend);
+ assert(server.renderer);
+ server.data_device_manager =
+ wlr_data_device_manager_create(server.wl_display);
+ wlr_renderer_init_wl_display(server.renderer, server.wl_display);
+ server.desktop = desktop_create(&server, server.config);
+ server.input = input_create(&server, server.config);
+
+ const char *socket = wl_display_add_socket_auto(server.wl_display);
+ if (!socket) {
+ wlr_log_errno(WLR_ERROR, "Unable to open wayland socket");
+ wlr_backend_destroy(server.backend);
+ return 1;
+ }
+
+ wlr_log(WLR_INFO, "Running compositor on wayland display '%s'", socket);
+ setenv("_WAYLAND_DISPLAY", socket, true);
+
+ if (!wlr_backend_start(server.backend)) {
+ wlr_log(WLR_ERROR, "Failed to start backend");
+ wlr_backend_destroy(server.backend);
+ wl_display_destroy(server.wl_display);
+ return 1;
+ }
+
+ setenv("WAYLAND_DISPLAY", socket, true);
+#if WLR_HAS_XWAYLAND
+ if (server.desktop->xwayland != NULL) {
+ struct roots_seat *xwayland_seat =
+ input_get_seat(server.input, ROOTS_CONFIG_DEFAULT_SEAT_NAME);
+ wlr_xwayland_set_seat(server.desktop->xwayland, xwayland_seat->seat);
+ }
+#endif
+
+ if (server.config->startup_cmd != NULL) {
+ const char *cmd = server.config->startup_cmd;
+ pid_t pid = fork();
+ if (pid < 0) {
+ wlr_log(WLR_ERROR, "cannot execute binding command: fork() failed");
+ } else if (pid == 0) {
+ execl("/bin/sh", "/bin/sh", "-c", cmd, (void *)NULL);
+ }
+ }
+
+ wl_display_run(server.wl_display);
+#if WLR_HAS_XWAYLAND
+ wlr_xwayland_destroy(server.desktop->xwayland);
+#endif
+ wl_display_destroy_clients(server.wl_display);
+ wl_display_destroy(server.wl_display);
+ return 0;
+}
diff --git a/rootston/meson.build b/rootston/meson.build
new file mode 100644
index 00000000..db90a508
--- /dev/null
+++ b/rootston/meson.build
@@ -0,0 +1,30 @@
+sources = [
+ 'bindings.c',
+ 'config.c',
+ 'cursor.c',
+ 'desktop.c',
+ 'ini.c',
+ 'input.c',
+ 'keyboard.c',
+ 'layer_shell.c',
+ 'main.c',
+ 'output.c',
+ 'seat.c',
+ 'switch.c',
+ 'text_input.c',
+ 'virtual_keyboard.c',
+ 'wl_shell.c',
+ 'xdg_shell.c',
+ 'xdg_shell_v6.c',
+]
+
+if conf_data.get('WLR_HAS_XWAYLAND', 0) == 1
+ sources += 'xwayland.c'
+endif
+
+executable(
+ 'rootston',
+ sources,
+ dependencies: [wlroots, wlr_protos, pixman],
+ build_by_default: get_option('rootston'),
+)
diff --git a/rootston/output.c b/rootston/output.c
new file mode 100644
index 00000000..f950d4dc
--- /dev/null
+++ b/rootston/output.c
@@ -0,0 +1,918 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <time.h>
+#include <wlr/backend/drm.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_presentation_time.h>
+#include <wlr/types/wlr_wl_shell.h>
+#include <wlr/types/wlr_xdg_shell_v6.h>
+#include <wlr/types/wlr_xdg_shell.h>
+#include <wlr/util/log.h>
+#include <wlr/util/region.h>
+#include "rootston/config.h"
+#include "rootston/layers.h"
+#include "rootston/output.h"
+#include "rootston/server.h"
+
+/**
+ * Rotate a child's position relative to a parent. The parent size is (pw, ph),
+ * the child position is (*sx, *sy) and its size is (sw, sh).
+ */
+void rotate_child_position(double *sx, double *sy, double sw, double sh,
+ double pw, double ph, float rotation) {
+ if (rotation != 0.0) {
+ // Coordinates relative to the center of the subsurface
+ double ox = *sx - pw/2 + sw/2,
+ oy = *sy - ph/2 + sh/2;
+ // Rotated coordinates
+ double rx = cos(rotation)*ox - sin(rotation)*oy,
+ ry = cos(rotation)*oy + sin(rotation)*ox;
+ *sx = rx + pw/2 - sw/2;
+ *sy = ry + ph/2 - sh/2;
+ }
+}
+
+struct layout_data {
+ double x, y;
+ int width, height;
+ float rotation;
+};
+
+static void get_layout_position(struct layout_data *data, double *lx, double *ly,
+ const struct wlr_surface *surface, int sx, int sy) {
+ double _sx = sx, _sy = sy;
+ rotate_child_position(&_sx, &_sy, surface->current.width,
+ surface->current.height, data->width, data->height, data->rotation);
+ *lx = data->x + _sx;
+ *ly = data->y + _sy;
+}
+
+static void surface_for_each_surface(struct wlr_surface *surface,
+ double lx, double ly, float rotation, struct layout_data *layout_data,
+ wlr_surface_iterator_func_t iterator, void *user_data) {
+ layout_data->x = lx;
+ layout_data->y = ly;
+ layout_data->width = surface->current.width;
+ layout_data->height = surface->current.height;
+ layout_data->rotation = rotation;
+
+ wlr_surface_for_each_surface(surface, iterator, user_data);
+}
+
+static void view_for_each_surface(struct roots_view *view,
+ struct layout_data *layout_data, wlr_surface_iterator_func_t iterator,
+ void *user_data) {
+ layout_data->x = view->box.x;
+ layout_data->y = view->box.y;
+ layout_data->width = view->wlr_surface->current.width;
+ layout_data->height = view->wlr_surface->current.height;
+ layout_data->rotation = view->rotation;
+
+ switch (view->type) {
+ case ROOTS_XDG_SHELL_V6_VIEW:
+ wlr_xdg_surface_v6_for_each_surface(view->xdg_surface_v6, iterator,
+ user_data);
+ break;
+ case ROOTS_XDG_SHELL_VIEW:
+ wlr_xdg_surface_for_each_surface(view->xdg_surface, iterator,
+ user_data);
+ break;
+ case ROOTS_WL_SHELL_VIEW:
+ wlr_wl_shell_surface_for_each_surface(view->wl_shell_surface, iterator,
+ user_data);
+ break;
+#if WLR_HAS_XWAYLAND
+ case ROOTS_XWAYLAND_VIEW:
+ wlr_surface_for_each_surface(view->wlr_surface, iterator, user_data);
+ break;
+#endif
+ }
+}
+
+#if WLR_HAS_XWAYLAND
+static void xwayland_children_for_each_surface(
+ struct wlr_xwayland_surface *surface,
+ wlr_surface_iterator_func_t iterator, struct layout_data *layout_data,
+ void *user_data) {
+ struct wlr_xwayland_surface *child;
+ wl_list_for_each(child, &surface->children, parent_link) {
+ if (child->mapped) {
+ surface_for_each_surface(child->surface, child->x, child->y, 0,
+ layout_data, iterator, user_data);
+ }
+ xwayland_children_for_each_surface(child, iterator, layout_data,
+ user_data);
+ }
+}
+#endif
+
+static void drag_icons_for_each_surface(struct roots_input *input,
+ wlr_surface_iterator_func_t iterator, struct layout_data *layout_data,
+ void *user_data) {
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &input->seats, link) {
+ struct roots_drag_icon *drag_icon;
+ wl_list_for_each(drag_icon, &seat->drag_icons, link) {
+ if (!drag_icon->wlr_drag_icon->mapped) {
+ continue;
+ }
+ surface_for_each_surface(drag_icon->wlr_drag_icon->surface,
+ drag_icon->x, drag_icon->y, 0, layout_data,
+ iterator, user_data);
+ }
+ }
+}
+
+static void layer_for_each_surface(struct wl_list *layer,
+ const struct wlr_box *output_layout_box,
+ wlr_surface_iterator_func_t iterator, struct layout_data *layout_data,
+ void *user_data) {
+ struct roots_layer_surface *roots_surface;
+ wl_list_for_each(roots_surface, layer, link) {
+ struct wlr_layer_surface_v1 *layer = roots_surface->layer_surface;
+
+ layout_data->x = roots_surface->geo.x + output_layout_box->x;
+ layout_data->y = roots_surface->geo.y + output_layout_box->y;
+ layout_data->width = roots_surface->geo.width;
+ layout_data->height = roots_surface->geo.height;
+ layout_data->rotation = 0;
+ wlr_layer_surface_v1_for_each_surface(layer, iterator, user_data);
+ }
+}
+
+static void output_for_each_surface(struct roots_output *output,
+ wlr_surface_iterator_func_t iterator, struct layout_data *layout_data,
+ void *user_data) {
+ struct wlr_output *wlr_output = output->wlr_output;
+ struct roots_desktop *desktop = output->desktop;
+ struct roots_server *server = desktop->server;
+
+ const struct wlr_box *output_box =
+ wlr_output_layout_get_box(desktop->layout, wlr_output);
+
+ if (output->fullscreen_view != NULL) {
+ struct roots_view *view = output->fullscreen_view;
+
+ view_for_each_surface(view, layout_data, iterator, user_data);
+
+#if WLR_HAS_XWAYLAND
+ if (view->type == ROOTS_XWAYLAND_VIEW) {
+ xwayland_children_for_each_surface(view->xwayland_surface,
+ iterator, layout_data, user_data);
+ }
+#endif
+ } else {
+ struct roots_view *view;
+ wl_list_for_each_reverse(view, &desktop->views, link) {
+ view_for_each_surface(view, layout_data, iterator, user_data);
+ }
+
+ drag_icons_for_each_surface(server->input, iterator,
+ layout_data, user_data);
+ }
+
+ size_t len = sizeof(output->layers) / sizeof(output->layers[0]);
+ for (size_t i = 0; i < len; ++i) {
+ layer_for_each_surface(&output->layers[i], output_box,
+ iterator, layout_data, user_data);
+ }
+}
+
+
+struct render_data {
+ struct layout_data layout;
+ struct roots_output *output;
+ struct timespec *when;
+ pixman_region32_t *damage;
+ float alpha;
+};
+
+/**
+ * Checks whether a surface at (lx, ly) intersects an output. If `box` is not
+ * NULL, it populates it with the surface box in the output, in output-local
+ * coordinates.
+ */
+static bool surface_intersect_output(struct wlr_surface *surface,
+ struct wlr_output_layout *output_layout, struct wlr_output *wlr_output,
+ double lx, double ly, float rotation, struct wlr_box *box) {
+ double ox = lx, oy = ly;
+ wlr_output_layout_output_coords(output_layout, wlr_output, &ox, &oy);
+
+ ox += surface->sx;
+ oy += surface->sy;
+
+ if (box != NULL) {
+ box->x = ox * wlr_output->scale;
+ box->y = oy * wlr_output->scale;
+ box->width = surface->current.width * wlr_output->scale;
+ box->height = surface->current.height * wlr_output->scale;
+ }
+
+ struct wlr_box layout_box = {
+ .x = lx, .y = ly,
+ .width = surface->current.width, .height = surface->current.height,
+ };
+ wlr_box_rotated_bounds(&layout_box, &layout_box, rotation);
+ return wlr_output_layout_intersects(output_layout, wlr_output, &layout_box);
+}
+
+static void scissor_output(struct roots_output *output, pixman_box32_t *rect) {
+ struct wlr_output *wlr_output = output->wlr_output;
+ struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend);
+ assert(renderer);
+
+ struct wlr_box box = {
+ .x = rect->x1,
+ .y = rect->y1,
+ .width = rect->x2 - rect->x1,
+ .height = rect->y2 - rect->y1,
+ };
+
+ int ow, oh;
+ wlr_output_transformed_resolution(wlr_output, &ow, &oh);
+
+ enum wl_output_transform transform =
+ wlr_output_transform_invert(wlr_output->transform);
+ wlr_box_transform(&box, &box, transform, ow, oh);
+
+ wlr_renderer_scissor(renderer, &box);
+}
+
+static void render_surface(struct wlr_surface *surface, int sx, int sy,
+ void *_data) {
+ struct render_data *data = _data;
+ struct roots_output *output = data->output;
+ float rotation = data->layout.rotation;
+
+ struct wlr_texture *texture = wlr_surface_get_texture(surface);
+ if (texture == NULL) {
+ return;
+ }
+
+ struct wlr_renderer *renderer =
+ wlr_backend_get_renderer(output->wlr_output->backend);
+ assert(renderer);
+
+ double lx, ly;
+ get_layout_position(&data->layout, &lx, &ly, surface, sx, sy);
+
+ struct wlr_box box;
+ bool intersects = surface_intersect_output(surface, output->desktop->layout,
+ output->wlr_output, lx, ly, rotation, &box);
+ if (!intersects) {
+ return;
+ }
+
+ struct wlr_box rotated;
+ wlr_box_rotated_bounds(&rotated, &box, rotation);
+
+ pixman_region32_t damage;
+ pixman_region32_init(&damage);
+ pixman_region32_union_rect(&damage, &damage, rotated.x, rotated.y,
+ rotated.width, rotated.height);
+ pixman_region32_intersect(&damage, &damage, data->damage);
+ bool damaged = pixman_region32_not_empty(&damage);
+ if (!damaged) {
+ goto damage_finish;
+ }
+
+ float matrix[9];
+ enum wl_output_transform transform =
+ wlr_output_transform_invert(surface->current.transform);
+ wlr_matrix_project_box(matrix, &box, transform, rotation,
+ output->wlr_output->transform_matrix);
+
+ int nrects;
+ pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects);
+ for (int i = 0; i < nrects; ++i) {
+ scissor_output(output, &rects[i]);
+ wlr_render_texture_with_matrix(renderer, texture, matrix, data->alpha);
+ }
+
+damage_finish:
+ pixman_region32_fini(&damage);
+}
+
+static void get_decoration_box(struct roots_view *view,
+ struct roots_output *output, struct wlr_box *box) {
+ struct wlr_output *wlr_output = output->wlr_output;
+
+ struct wlr_box deco_box;
+ view_get_deco_box(view, &deco_box);
+ double sx = deco_box.x - view->box.x;
+ double sy = deco_box.y - view->box.y;
+ rotate_child_position(&sx, &sy, deco_box.width, deco_box.height,
+ view->wlr_surface->current.width,
+ view->wlr_surface->current.height, view->rotation);
+ double x = sx + view->box.x;
+ double y = sy + view->box.y;
+
+ wlr_output_layout_output_coords(output->desktop->layout, wlr_output, &x, &y);
+
+ box->x = x * wlr_output->scale;
+ box->y = y * wlr_output->scale;
+ box->width = deco_box.width * wlr_output->scale;
+ box->height = deco_box.height * wlr_output->scale;
+}
+
+static void render_decorations(struct roots_view *view,
+ struct render_data *data) {
+ if (!view->decorated || view->wlr_surface == NULL) {
+ return;
+ }
+
+ struct roots_output *output = data->output;
+ struct wlr_renderer *renderer =
+ wlr_backend_get_renderer(output->wlr_output->backend);
+ assert(renderer);
+
+ struct wlr_box box;
+ get_decoration_box(view, output, &box);
+
+ struct wlr_box rotated;
+ wlr_box_rotated_bounds(&rotated, &box, view->rotation);
+
+ pixman_region32_t damage;
+ pixman_region32_init(&damage);
+ pixman_region32_union_rect(&damage, &damage, rotated.x, rotated.y,
+ rotated.width, rotated.height);
+ pixman_region32_intersect(&damage, &damage, data->damage);
+ bool damaged = pixman_region32_not_empty(&damage);
+ if (!damaged) {
+ goto damage_finish;
+ }
+
+ float matrix[9];
+ wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL,
+ view->rotation, output->wlr_output->transform_matrix);
+ float color[] = { 0.2, 0.2, 0.2, view->alpha };
+
+ int nrects;
+ pixman_box32_t *rects =
+ pixman_region32_rectangles(&damage, &nrects);
+ for (int i = 0; i < nrects; ++i) {
+ scissor_output(output, &rects[i]);
+ wlr_render_quad_with_matrix(renderer, color, matrix);
+ }
+
+damage_finish:
+ pixman_region32_fini(&damage);
+}
+
+static void render_view(struct roots_view *view, struct render_data *data) {
+ // Do not render views fullscreened on other outputs
+ if (view->fullscreen_output != NULL &&
+ view->fullscreen_output != data->output) {
+ return;
+ }
+
+ data->alpha = view->alpha;
+ render_decorations(view, data);
+ view_for_each_surface(view, &data->layout, render_surface, data);
+}
+
+static void render_layer(struct roots_output *output,
+ const struct wlr_box *output_layout_box, struct render_data *data,
+ struct wl_list *layer) {
+ data->alpha = 1;
+ layer_for_each_surface(layer, output_layout_box, render_surface,
+ &data->layout, data);
+}
+
+static void surface_send_frame_done(struct wlr_surface *surface, int sx, int sy,
+ void *_data) {
+ struct render_data *data = _data;
+ struct roots_output *output = data->output;
+ struct timespec *when = data->when;
+ float rotation = data->layout.rotation;
+
+ double lx, ly;
+ get_layout_position(&data->layout, &lx, &ly, surface, sx, sy);
+
+ if (!surface_intersect_output(surface, output->desktop->layout,
+ output->wlr_output, lx, ly, rotation, NULL)) {
+ return;
+ }
+
+ wlr_surface_send_frame_done(surface, when);
+}
+
+static void render_output(struct roots_output *output) {
+ struct wlr_output *wlr_output = output->wlr_output;
+ struct roots_desktop *desktop = output->desktop;
+ struct roots_server *server = desktop->server;
+ struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend);
+ assert(renderer);
+
+ if (!wlr_output->enabled) {
+ return;
+ }
+
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ float clear_color[] = {0.25f, 0.25f, 0.25f, 1.0f};
+
+ const struct wlr_box *output_box =
+ wlr_output_layout_get_box(desktop->layout, wlr_output);
+
+ // Check if we can delegate the fullscreen surface to the output
+ if (output->fullscreen_view != NULL &&
+ output->fullscreen_view->wlr_surface != NULL) {
+ struct roots_view *view = output->fullscreen_view;
+
+ // Make sure the view is centered on screen
+ struct wlr_box view_box;
+ view_get_box(view, &view_box);
+ double view_x = (double)(output_box->width - view_box.width) / 2 +
+ output_box->x;
+ double view_y = (double)(output_box->height - view_box.height) / 2 +
+ output_box->y;
+ view_move(view, view_x, view_y);
+
+ // Fullscreen views are rendered on a black background
+ clear_color[0] = clear_color[1] = clear_color[2] = 0;
+ }
+
+ bool needs_swap;
+ pixman_region32_t damage;
+ pixman_region32_init(&damage);
+ if (!wlr_output_damage_make_current(output->damage, &needs_swap, &damage)) {
+ return;
+ }
+
+ struct render_data data = {
+ .output = output,
+ .when = &now,
+ .damage = &damage,
+ .alpha = 1.0,
+ };
+
+ if (!needs_swap) {
+ // Output doesn't need swap and isn't damaged, skip rendering completely
+ goto damage_finish;
+ }
+
+ wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height);
+
+ if (!pixman_region32_not_empty(&damage)) {
+ // Output isn't damaged but needs buffer swap
+ goto renderer_end;
+ }
+
+ if (server->config->debug_damage_tracking) {
+ wlr_renderer_clear(renderer, (float[]){1, 1, 0, 1});
+ }
+
+ int nrects;
+ pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects);
+ for (int i = 0; i < nrects; ++i) {
+ scissor_output(output, &rects[i]);
+ wlr_renderer_clear(renderer, clear_color);
+ }
+
+ render_layer(output, output_box, &data,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]);
+ render_layer(output, output_box, &data,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]);
+
+ // If a view is fullscreen on this output, render it
+ if (output->fullscreen_view != NULL) {
+ struct roots_view *view = output->fullscreen_view;
+
+ if (view->wlr_surface != NULL) {
+ view_for_each_surface(view, &data.layout, render_surface, &data);
+ }
+
+ // During normal rendering the xwayland window tree isn't traversed
+ // because all windows are rendered. Here we only want to render
+ // the fullscreen window's children so we have to traverse the tree.
+#if WLR_HAS_XWAYLAND
+ if (view->type == ROOTS_XWAYLAND_VIEW) {
+ xwayland_children_for_each_surface(view->xwayland_surface,
+ render_surface, &data.layout, &data);
+ }
+#endif
+ } else {
+ // Render all views
+ struct roots_view *view;
+ wl_list_for_each_reverse(view, &desktop->views, link) {
+ render_view(view, &data);
+ }
+ // Render top layer above shell views
+ render_layer(output, output_box, &data,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]);
+ }
+
+ // Render drag icons
+ data.alpha = 1.0;
+ drag_icons_for_each_surface(server->input, render_surface, &data.layout,
+ &data);
+
+ render_layer(output, output_box, &data,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]);
+
+renderer_end:
+ wlr_output_render_software_cursors(wlr_output, &damage);
+ wlr_renderer_scissor(renderer, NULL);
+ wlr_renderer_end(renderer);
+
+ int width, height;
+ wlr_output_transformed_resolution(wlr_output, &width, &height);
+
+ if (server->config->debug_damage_tracking) {
+ pixman_region32_union_rect(&damage, &damage, 0, 0, width, height);
+ }
+
+ enum wl_output_transform transform =
+ wlr_output_transform_invert(wlr_output->transform);
+ wlr_region_transform(&damage, &damage, transform, width, height);
+
+ if (!wlr_output_damage_swap_buffers(output->damage, &now, &damage)) {
+ goto damage_finish;
+ }
+ output->last_frame = desktop->last_frame = now;
+
+damage_finish:
+ pixman_region32_fini(&damage);
+
+ // Send frame done events to all surfaces
+ output_for_each_surface(output, surface_send_frame_done,
+ &data.layout, &data);
+}
+
+void output_damage_whole(struct roots_output *output) {
+ wlr_output_damage_add_whole(output->damage);
+}
+
+static bool view_accept_damage(struct roots_output *output,
+ struct roots_view *view) {
+ if (view->wlr_surface == NULL) {
+ return false;
+ }
+ if (output->fullscreen_view == NULL) {
+ return true;
+ }
+ if (output->fullscreen_view == view) {
+ return true;
+ }
+#if WLR_HAS_XWAYLAND
+ if (output->fullscreen_view->type == ROOTS_XWAYLAND_VIEW &&
+ view->type == ROOTS_XWAYLAND_VIEW) {
+ // Special case: accept damage from children
+ struct wlr_xwayland_surface *xsurface = view->xwayland_surface;
+ while (xsurface != NULL) {
+ if (output->fullscreen_view->xwayland_surface == xsurface) {
+ return true;
+ }
+ xsurface = xsurface->parent;
+ }
+ }
+#endif
+ return false;
+}
+
+struct damage_data {
+ struct layout_data layout;
+ struct roots_output *output;
+};
+
+static void damage_whole_surface(struct wlr_surface *surface, int sx, int sy,
+ void *_data) {
+ struct damage_data *data = _data;
+ struct roots_output *output = data->output;
+ float rotation = data->layout.rotation;
+
+ double lx, ly;
+ get_layout_position(&data->layout, &lx, &ly, surface, sx, sy);
+
+ if (!wlr_surface_has_buffer(surface)) {
+ return;
+ }
+
+ int ow, oh;
+ wlr_output_transformed_resolution(output->wlr_output, &ow, &oh);
+
+ struct wlr_box box;
+ bool intersects = surface_intersect_output(surface, output->desktop->layout,
+ output->wlr_output, lx, ly, rotation, &box);
+ if (!intersects) {
+ return;
+ }
+
+ wlr_box_rotated_bounds(&box, &box, rotation);
+
+ wlr_output_damage_add_box(output->damage, &box);
+}
+
+void output_damage_whole_local_surface(struct roots_output *output,
+ struct wlr_surface *surface, double ox, double oy, float rotation) {
+ struct wlr_output_layout_output *layout = wlr_output_layout_get(
+ output->desktop->layout, output->wlr_output);
+ struct damage_data data = { .output = output };
+ surface_for_each_surface(surface, ox + layout->x, oy + layout->y, 0,
+ &data.layout, damage_whole_surface, &data);
+}
+
+static void damage_whole_decoration(struct roots_view *view,
+ struct roots_output *output) {
+ if (!view->decorated || view->wlr_surface == NULL) {
+ return;
+ }
+
+ struct wlr_box box;
+ get_decoration_box(view, output, &box);
+
+ wlr_box_rotated_bounds(&box, &box, view->rotation);
+
+ wlr_output_damage_add_box(output->damage, &box);
+}
+
+void output_damage_whole_view(struct roots_output *output,
+ struct roots_view *view) {
+ if (!view_accept_damage(output, view)) {
+ return;
+ }
+
+ damage_whole_decoration(view, output);
+
+ struct damage_data data = { .output = output };
+ view_for_each_surface(view, &data.layout, damage_whole_surface, &data);
+}
+
+void output_damage_whole_drag_icon(struct roots_output *output,
+ struct roots_drag_icon *icon) {
+ struct damage_data data = { .output = output };
+ surface_for_each_surface(icon->wlr_drag_icon->surface, icon->x, icon->y, 0,
+ &data.layout, damage_whole_surface, &data);
+}
+
+static void damage_from_surface(struct wlr_surface *surface, int sx, int sy,
+ void *_data) {
+ struct damage_data *data = _data;
+ struct roots_output *output = data->output;
+ struct wlr_output *wlr_output = output->wlr_output;
+ float rotation = data->layout.rotation;
+
+ double lx, ly;
+ get_layout_position(&data->layout, &lx, &ly, surface, sx, sy);
+
+ if (!wlr_surface_has_buffer(surface)) {
+ return;
+ }
+
+ int ow, oh;
+ wlr_output_transformed_resolution(wlr_output, &ow, &oh);
+
+ struct wlr_box box;
+ surface_intersect_output(surface, output->desktop->layout,
+ wlr_output, lx, ly, rotation, &box);
+
+ int center_x = box.x + box.width/2;
+ int center_y = box.y + box.height/2;
+
+ pixman_region32_t damage;
+ pixman_region32_init(&damage);
+ wlr_surface_get_effective_damage(surface, &damage);
+
+ wlr_region_scale(&damage, &damage, wlr_output->scale);
+ if (ceil(wlr_output->scale) > surface->current.scale) {
+ // When scaling up a surface, it'll become blurry so we need to
+ // expand the damage region
+ wlr_region_expand(&damage, &damage,
+ ceil(wlr_output->scale) - surface->current.scale);
+ }
+ pixman_region32_translate(&damage, box.x, box.y);
+ wlr_region_rotated_bounds(&damage, &damage, rotation, center_x, center_y);
+ wlr_output_damage_add(output->damage, &damage);
+ pixman_region32_fini(&damage);
+}
+
+void output_damage_from_local_surface(struct roots_output *output,
+ struct wlr_surface *surface, double ox, double oy, float rotation) {
+ struct wlr_output_layout_output *layout = wlr_output_layout_get(
+ output->desktop->layout, output->wlr_output);
+ struct damage_data data = { .output = output };
+ surface_for_each_surface(surface, ox + layout->x, oy + layout->y, 0,
+ &data.layout, damage_from_surface, &data);
+}
+
+void output_damage_from_view(struct roots_output *output,
+ struct roots_view *view) {
+ if (!view_accept_damage(output, view)) {
+ return;
+ }
+
+ struct damage_data data = { .output = output };
+ view_for_each_surface(view, &data.layout, damage_from_surface, &data);
+}
+
+static void set_mode(struct wlr_output *output,
+ struct roots_output_config *oc) {
+ int mhz = (int)(oc->mode.refresh_rate * 1000);
+
+ if (wl_list_empty(&output->modes)) {
+ // Output has no mode, try setting a custom one
+ wlr_output_set_custom_mode(output, oc->mode.width, oc->mode.height, mhz);
+ return;
+ }
+
+ struct wlr_output_mode *mode, *best = NULL;
+ wl_list_for_each(mode, &output->modes, link) {
+ if (mode->width == oc->mode.width && mode->height == oc->mode.height) {
+ if (mode->refresh == mhz) {
+ best = mode;
+ break;
+ }
+ best = mode;
+ }
+ }
+ if (!best) {
+ wlr_log(WLR_ERROR, "Configured mode for %s not available", output->name);
+ } else {
+ wlr_log(WLR_DEBUG, "Assigning configured mode to %s", output->name);
+ wlr_output_set_mode(output, best);
+ }
+}
+
+static void output_destroy(struct roots_output *output) {
+ // TODO: cursor
+ //example_config_configure_cursor(sample->config, sample->cursor,
+ // sample->compositor);
+
+ wl_list_remove(&output->link);
+ wl_list_remove(&output->destroy.link);
+ wl_list_remove(&output->mode.link);
+ wl_list_remove(&output->transform.link);
+ wl_list_remove(&output->present.link);
+ wl_list_remove(&output->damage_frame.link);
+ wl_list_remove(&output->damage_destroy.link);
+ free(output);
+}
+
+static void output_handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_output *output = wl_container_of(listener, output, destroy);
+ output_destroy(output);
+}
+
+static void output_damage_handle_frame(struct wl_listener *listener,
+ void *data) {
+ struct roots_output *output =
+ wl_container_of(listener, output, damage_frame);
+ render_output(output);
+}
+
+static void output_damage_handle_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_output *output =
+ wl_container_of(listener, output, damage_destroy);
+ output_destroy(output);
+}
+
+static void output_handle_mode(struct wl_listener *listener, void *data) {
+ struct roots_output *output =
+ wl_container_of(listener, output, mode);
+ arrange_layers(output);
+}
+
+static void output_handle_transform(struct wl_listener *listener, void *data) {
+ struct roots_output *output =
+ wl_container_of(listener, output, transform);
+ arrange_layers(output);
+}
+
+struct presentation_data {
+ struct layout_data layout;
+ struct roots_output *output;
+ struct wlr_presentation_event *event;
+};
+
+static void surface_send_presented(struct wlr_surface *surface, int sx, int sy,
+ void *_data) {
+ struct presentation_data *data = _data;
+ struct roots_output *output = data->output;
+ float rotation = data->layout.rotation;
+
+ double lx, ly;
+ get_layout_position(&data->layout, &lx, &ly, surface, sx, sy);
+
+ if (!surface_intersect_output(surface, output->desktop->layout,
+ output->wlr_output, lx, ly, rotation, NULL)) {
+ return;
+ }
+
+ wlr_presentation_send_surface_presented(output->desktop->presentation,
+ surface, data->event);
+}
+
+static void output_handle_present(struct wl_listener *listener, void *data) {
+ struct roots_output *output =
+ wl_container_of(listener, output, present);
+ struct wlr_output_event_present *output_event = data;
+
+ struct wlr_presentation_event event = {
+ .output = output->wlr_output,
+ .tv_sec = (uint64_t)output_event->when->tv_sec,
+ .tv_nsec = (uint32_t)output_event->when->tv_nsec,
+ .refresh = (uint32_t)output_event->refresh,
+ .seq = (uint64_t)output_event->seq,
+ .flags = output_event->flags,
+ };
+
+ struct presentation_data presentation_data = {
+ .output = output,
+ .event = &event,
+ };
+ output_for_each_surface(output, surface_send_presented,
+ &presentation_data.layout, &presentation_data);
+}
+
+void handle_new_output(struct wl_listener *listener, void *data) {
+ struct roots_desktop *desktop = wl_container_of(listener, desktop,
+ new_output);
+ struct wlr_output *wlr_output = data;
+ struct roots_input *input = desktop->server->input;
+ struct roots_config *config = desktop->config;
+
+ wlr_log(WLR_DEBUG, "Output '%s' added", wlr_output->name);
+ wlr_log(WLR_DEBUG, "'%s %s %s' %"PRId32"mm x %"PRId32"mm", wlr_output->make,
+ wlr_output->model, wlr_output->serial, wlr_output->phys_width,
+ wlr_output->phys_height);
+
+ struct roots_output *output = calloc(1, sizeof(struct roots_output));
+ clock_gettime(CLOCK_MONOTONIC, &output->last_frame);
+ output->desktop = desktop;
+ output->wlr_output = wlr_output;
+ wlr_output->data = output;
+ wl_list_insert(&desktop->outputs, &output->link);
+
+ output->damage = wlr_output_damage_create(wlr_output);
+
+ output->destroy.notify = output_handle_destroy;
+ wl_signal_add(&wlr_output->events.destroy, &output->destroy);
+ output->mode.notify = output_handle_mode;
+ wl_signal_add(&wlr_output->events.mode, &output->mode);
+ output->transform.notify = output_handle_transform;
+ wl_signal_add(&wlr_output->events.transform, &output->transform);
+ output->present.notify = output_handle_present;
+ wl_signal_add(&wlr_output->events.present, &output->present);
+
+ output->damage_frame.notify = output_damage_handle_frame;
+ wl_signal_add(&output->damage->events.frame, &output->damage_frame);
+ output->damage_destroy.notify = output_damage_handle_destroy;
+ wl_signal_add(&output->damage->events.destroy, &output->damage_destroy);
+
+ size_t len = sizeof(output->layers) / sizeof(output->layers[0]);
+ for (size_t i = 0; i < len; ++i) {
+ wl_list_init(&output->layers[i]);
+ }
+
+ struct roots_output_config *output_config =
+ roots_config_get_output(config, wlr_output);
+
+ if ((!output_config || output_config->enable) && !wl_list_empty(&wlr_output->modes)) {
+ struct wlr_output_mode *mode =
+ wl_container_of(wlr_output->modes.prev, mode, link);
+ wlr_output_set_mode(wlr_output, mode);
+ }
+
+ if (output_config) {
+ if (output_config->enable) {
+ if (wlr_output_is_drm(wlr_output)) {
+ struct roots_output_mode_config *mode_config;
+ wl_list_for_each(mode_config, &output_config->modes, link) {
+ wlr_drm_connector_add_mode(wlr_output, &mode_config->info);
+ }
+ } else if (!wl_list_empty(&output_config->modes)) {
+ wlr_log(WLR_ERROR, "Can only add modes for DRM backend");
+ }
+
+ if (output_config->mode.width) {
+ set_mode(wlr_output, output_config);
+ }
+
+ wlr_output_set_scale(wlr_output, output_config->scale);
+ wlr_output_set_transform(wlr_output, output_config->transform);
+ wlr_output_layout_add(desktop->layout, wlr_output, output_config->x,
+ output_config->y);
+ } else {
+ wlr_output_enable(wlr_output, false);
+ }
+ } else {
+ wlr_output_layout_add_auto(desktop->layout, wlr_output);
+ }
+
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &input->seats, link) {
+ roots_seat_configure_cursor(seat);
+ roots_seat_configure_xcursor(seat);
+ }
+
+ arrange_layers(output);
+ output_damage_whole(output);
+}
diff --git a/rootston/rootston.ini.example b/rootston/rootston.ini.example
new file mode 100644
index 00000000..4b75e9c6
--- /dev/null
+++ b/rootston/rootston.ini.example
@@ -0,0 +1,63 @@
+[core]
+# X11 support
+# - true: enables X11, xwayland is started only when an X11 client connects
+# - immediate: enables X11, xwayland is started immediately
+# - false: disables xwayland
+xwayland=false
+
+# Single output configuration. String after colon must match output's name.
+[output:VGA-1]
+# Set logical (layout) coordinates for this screen
+x = 1920
+y = 0
+
+# Screen transformation
+# possible values are:
+# '90', '180' or '270' - rotate output by specified angle clockwise
+# 'flipped' - flip output horizontally
+# 'flipped-90', 'flipped-180', 'flipped-270' - flip output horizontally
+# and rotate by specified angle
+rotate = 90
+
+# Additional video mode to add
+# Format is generated by cvt and is documented in x.org.conf(5)
+modeline = 87.25 720 776 848 976 1440 1443 1453 1493 -hsync +vsync
+modeline = 65.13 768 816 896 1024 1024 1025 1028 1060 -HSync +VSync
+# Select one of the above modes
+mode = 768x1024
+
+[cursor]
+# Restrict cursor movements to single output
+map-to-output = VGA-1
+# Restrict cursor movements to concrete rectangle
+geometry = 2500x800
+# Load a custom XCursor theme
+theme = default
+
+# Single device configuration. String after colon must match device's name.
+[device:PixArt Dell MS116 USB Optical Mouse]
+# Restrict cursor movements for this mouse to single output
+map-to-output = VGA-1
+# Restrict cursor movements for this mouse to concrete rectangle
+geometry = 2500x800
+# tap_enabled=true
+
+[keyboard]
+meta-key = Logo
+
+# Keybindings
+# Maps key combinations with commands to execute
+# Commands include:
+# - "exit" to stop the compositor
+# - "exec" to execute a shell command
+# - "close" to close the current view
+# - "next_window" to cycle through windows
+# - "alpha" to cycle a window's alpha channel
+# - "break_pointer_constraint" to decline and deactivate all pointer constraints
+[bindings]
+Logo+Shift+e = exit
+Logo+q = close
+Logo+m = maximize
+Logo+Escape = break_pointer_constraint
+Alt+Tab = next_window
+Ctrl+Shift+a = alpha
diff --git a/rootston/seat.c b/rootston/seat.c
new file mode 100644
index 00000000..dda2f8df
--- /dev/null
+++ b/rootston/seat.c
@@ -0,0 +1,1473 @@
+#define _POSIX_C_SOURCE 200112L
+#include <assert.h>
+#include <libinput.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wlr/backend/libinput.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_idle.h>
+#include <wlr/types/wlr_layer_shell_v1.h>
+#include "wlr/types/wlr_switch.h"
+#include <wlr/types/wlr_tablet_v2.h>
+#include <wlr/types/wlr_xcursor_manager.h>
+#include <wlr/util/log.h>
+#include "rootston/cursor.h"
+#include "rootston/input.h"
+#include "rootston/keyboard.h"
+#include "rootston/seat.h"
+#include "rootston/text_input.h"
+#include "rootston/xcursor.h"
+
+
+static void handle_keyboard_key(struct wl_listener *listener, void *data) {
+ struct roots_keyboard *keyboard =
+ wl_container_of(listener, keyboard, keyboard_key);
+ struct roots_desktop *desktop = keyboard->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, keyboard->seat->seat);
+ struct wlr_event_keyboard_key *event = data;
+ roots_keyboard_handle_key(keyboard, event);
+}
+
+static void handle_keyboard_modifiers(struct wl_listener *listener,
+ void *data) {
+ struct roots_keyboard *keyboard =
+ wl_container_of(listener, keyboard, keyboard_modifiers);
+ struct roots_desktop *desktop = keyboard->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, keyboard->seat->seat);
+ roots_keyboard_handle_modifiers(keyboard);
+}
+
+static void handle_cursor_motion(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, motion);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_pointer_motion *event = data;
+ roots_cursor_handle_motion(cursor, event);
+}
+
+static void handle_cursor_motion_absolute(struct wl_listener *listener,
+ void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, motion_absolute);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_pointer_motion_absolute *event = data;
+ roots_cursor_handle_motion_absolute(cursor, event);
+}
+
+static void handle_cursor_button(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, button);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_pointer_button *event = data;
+ roots_cursor_handle_button(cursor, event);
+}
+
+static void handle_cursor_axis(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, axis);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_pointer_axis *event = data;
+ roots_cursor_handle_axis(cursor, event);
+}
+
+static void handle_switch_toggle(struct wl_listener *listener, void *data) {
+ struct roots_switch *lid_switch =
+ wl_container_of(listener, lid_switch, toggle);
+ struct roots_desktop *desktop = lid_switch->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, lid_switch->seat->seat);
+ struct wlr_event_switch_toggle *event = data;
+ roots_switch_handle_toggle(lid_switch, event);
+}
+
+static void handle_touch_down(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, touch_down);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_touch_down *event = data;
+ roots_cursor_handle_touch_down(cursor, event);
+}
+
+static void handle_touch_up(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, touch_up);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_touch_up *event = data;
+ roots_cursor_handle_touch_up(cursor, event);
+}
+
+static void handle_touch_motion(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, touch_motion);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_touch_motion *event = data;
+ roots_cursor_handle_touch_motion(cursor, event);
+}
+
+static void handle_tablet_tool_position(struct roots_cursor *cursor,
+ struct roots_tablet *tablet,
+ struct wlr_tablet_tool *tool,
+ bool change_x, bool change_y,
+ double x, double y, double dx, double dy) {
+ if (!change_x && !change_y) {
+ return;
+ }
+
+ switch (tool->type) {
+ case WLR_TABLET_TOOL_TYPE_MOUSE:
+ // They are 0 either way when they weren't modified
+ wlr_cursor_move(cursor->cursor, tablet->device, dx, dy);
+ break;
+ default:
+ wlr_cursor_warp_absolute(cursor->cursor, tablet->device,
+ change_x ? x : NAN, change_y ? y : NAN);
+ }
+
+ double sx, sy;
+ struct roots_view *view = NULL;
+ struct roots_seat *seat = cursor->seat;
+ struct roots_desktop *desktop = seat->input->server->desktop;
+ struct wlr_surface *surface = desktop_surface_at(desktop,
+ cursor->cursor->x, cursor->cursor->y, &sx, &sy, &view);
+ struct roots_tablet_tool *roots_tool = tool->data;
+
+ if (!surface) {
+ wlr_tablet_v2_tablet_tool_notify_proximity_out(roots_tool->tablet_v2_tool);
+ /* XXX: TODO: Fallback pointer semantics */
+ return;
+ }
+
+ if (!wlr_surface_accepts_tablet_v2(tablet->tablet_v2, surface)) {
+ wlr_tablet_v2_tablet_tool_notify_proximity_out(roots_tool->tablet_v2_tool);
+ /* XXX: TODO: Fallback pointer semantics */
+ return;
+ }
+
+ wlr_tablet_v2_tablet_tool_notify_proximity_in(roots_tool->tablet_v2_tool,
+ tablet->tablet_v2, surface);
+
+ wlr_tablet_v2_tablet_tool_notify_motion(roots_tool->tablet_v2_tool, sx, sy);
+}
+
+static void handle_tool_axis(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, tool_axis);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_tablet_tool_axis *event = data;
+ struct roots_tablet_tool *roots_tool = event->tool->data;
+
+ if (!roots_tool) { // Should this be an assert?
+ wlr_log(WLR_DEBUG, "Tool Axis, before proximity");
+ return;
+ }
+
+ /**
+ * We need to handle them ourselves, not pass it into the cursor
+ * without any consideration
+ */
+ handle_tablet_tool_position(cursor, event->device->data, event->tool,
+ event->updated_axes & WLR_TABLET_TOOL_AXIS_X,
+ event->updated_axes & WLR_TABLET_TOOL_AXIS_Y,
+ event->x, event->y, event->dx, event->dy);
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) {
+ wlr_tablet_v2_tablet_tool_notify_pressure(
+ roots_tool->tablet_v2_tool, event->pressure);
+ }
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) {
+ wlr_tablet_v2_tablet_tool_notify_distance(
+ roots_tool->tablet_v2_tool, event->distance);
+ }
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X) {
+ roots_tool->tilt_x = event->tilt_x;
+ }
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y) {
+ roots_tool->tilt_y = event->tilt_y;
+ }
+
+ if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) {
+ wlr_tablet_v2_tablet_tool_notify_tilt(
+ roots_tool->tablet_v2_tool,
+ roots_tool->tilt_x, roots_tool->tilt_y);
+ }
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) {
+ wlr_tablet_v2_tablet_tool_notify_rotation(
+ roots_tool->tablet_v2_tool, event->rotation);
+ }
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) {
+ wlr_tablet_v2_tablet_tool_notify_slider(
+ roots_tool->tablet_v2_tool, event->slider);
+ }
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) {
+ wlr_tablet_v2_tablet_tool_notify_wheel(
+ roots_tool->tablet_v2_tool, event->wheel_delta, 0);
+ }
+}
+
+static void handle_tool_tip(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, tool_tip);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_tablet_tool_tip *event = data;
+ struct roots_tablet_tool *roots_tool = event->tool->data;
+
+ if (event->state == WLR_TABLET_TOOL_TIP_DOWN) {
+ wlr_tablet_v2_tablet_tool_notify_down(roots_tool->tablet_v2_tool);
+ wlr_tablet_tool_v2_start_implicit_grab(roots_tool->tablet_v2_tool);
+ } else {
+ wlr_tablet_v2_tablet_tool_notify_up(roots_tool->tablet_v2_tool);
+ }
+}
+
+static void handle_tablet_tool_destroy(struct wl_listener *listener, void *data) {
+ struct roots_tablet_tool *tool =
+ wl_container_of(listener, tool, tool_destroy);
+
+ wl_list_remove(&tool->link);
+ wl_list_remove(&tool->tool_link);
+
+ wl_list_remove(&tool->tool_destroy.link);
+ wl_list_remove(&tool->set_cursor.link);
+
+ free(tool);
+}
+
+static void handle_tool_button(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, tool_button);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_tablet_tool_button *event = data;
+ struct roots_tablet_tool *roots_tool = event->tool->data;
+
+ wlr_tablet_v2_tablet_tool_notify_button(roots_tool->tablet_v2_tool,
+ (enum zwp_tablet_pad_v2_button_state)event->button,
+ (enum zwp_tablet_pad_v2_button_state)event->state);
+}
+
+static void handle_tablet_tool_set_cursor(struct wl_listener *listener, void *data) {
+ struct roots_tablet_tool *tool =
+ wl_container_of(listener, tool, set_cursor);
+ struct wlr_tablet_v2_event_cursor *evt = data;
+
+
+ struct wlr_seat_pointer_request_set_cursor_event event = {
+ .surface = evt->surface,
+ .hotspot_x = evt->hotspot_x,
+ .hotspot_y = evt->hotspot_y,
+ .serial = evt->serial,
+ .seat_client = evt->seat_client,
+ };
+
+ roots_cursor_handle_request_set_cursor(tool->seat->cursor, &event);
+}
+
+static void handle_tool_proximity(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, tool_proximity);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_tablet_tool_proximity *event = data;
+
+ struct wlr_tablet_tool *tool = event->tool;
+ if (!tool->data) {
+ struct roots_tablet_tool *roots_tool =
+ calloc(1, sizeof(struct roots_tablet_tool));
+ roots_tool->seat = cursor->seat;
+ tool->data = roots_tool;
+ roots_tool->tablet_v2_tool =
+ wlr_tablet_tool_create(desktop->tablet_v2,
+ cursor->seat->seat, tool);
+ roots_tool->tool_destroy.notify = handle_tablet_tool_destroy;
+ wl_signal_add(&tool->events.destroy, &roots_tool->tool_destroy);
+
+ roots_tool->set_cursor.notify = handle_tablet_tool_set_cursor;
+ wl_signal_add(&roots_tool->tablet_v2_tool->events.set_cursor,
+ &roots_tool->set_cursor);
+
+ wl_list_init(&roots_tool->link);
+ wl_list_init(&roots_tool->tool_link);
+ }
+
+ if (event->state == WLR_TABLET_TOOL_PROXIMITY_OUT) {
+ struct roots_tablet_tool *roots_tool = tool->data;
+ wlr_tablet_v2_tablet_tool_notify_proximity_out(roots_tool->tablet_v2_tool);
+ return;
+ }
+
+ handle_tablet_tool_position(cursor, event->device->data, event->tool,
+ true, true, event->x, event->y, 0, 0);
+}
+
+static void handle_request_set_cursor(struct wl_listener *listener,
+ void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, request_set_cursor);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_seat_pointer_request_set_cursor_event *event = data;
+ roots_cursor_handle_request_set_cursor(cursor, event);
+}
+
+static void handle_pointer_focus_change(struct wl_listener *listener,
+ void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, focus_change);
+ struct wlr_seat_pointer_focus_change_event *event = data;
+ roots_cursor_handle_focus_change(cursor, event);
+}
+
+static void seat_reset_device_mappings(struct roots_seat *seat,
+ struct wlr_input_device *device) {
+ struct wlr_cursor *cursor = seat->cursor->cursor;
+ struct roots_config *config = seat->input->config;
+
+ wlr_cursor_map_input_to_output(cursor, device, NULL);
+ struct roots_device_config *dconfig;
+ if ((dconfig = roots_config_get_device(config, device))) {
+ wlr_cursor_map_input_to_region(cursor, device, dconfig->mapped_box);
+ }
+}
+
+static void seat_set_device_output_mappings(struct roots_seat *seat,
+ struct wlr_input_device *device, struct wlr_output *output) {
+ struct wlr_cursor *cursor = seat->cursor->cursor;
+ struct roots_config *config = seat->input->config;
+ struct roots_device_config *dconfig =
+ roots_config_get_device(config, device);
+
+ const char *mapped_output = NULL;
+ if (dconfig != NULL) {
+ mapped_output = dconfig->mapped_output;
+ }
+ if (mapped_output == NULL) {
+ mapped_output = device->output_name;
+ }
+
+ if (mapped_output && strcmp(mapped_output, output->name) == 0) {
+ wlr_cursor_map_input_to_output(cursor, device, output);
+ }
+}
+
+void roots_seat_configure_cursor(struct roots_seat *seat) {
+ struct roots_config *config = seat->input->config;
+ struct roots_desktop *desktop = seat->input->server->desktop;
+ struct wlr_cursor *cursor = seat->cursor->cursor;
+
+ struct roots_pointer *pointer;
+ struct roots_touch *touch;
+ struct roots_tablet *tablet;
+ struct roots_output *output;
+
+ // reset mappings
+ wlr_cursor_map_to_output(cursor, NULL);
+ wl_list_for_each(pointer, &seat->pointers, link) {
+ seat_reset_device_mappings(seat, pointer->device);
+ }
+ wl_list_for_each(touch, &seat->touch, link) {
+ seat_reset_device_mappings(seat, touch->device);
+ }
+ wl_list_for_each(tablet, &seat->tablets, link) {
+ seat_reset_device_mappings(seat, tablet->device);
+ }
+
+ // configure device to output mappings
+ const char *mapped_output = NULL;
+ struct roots_cursor_config *cc =
+ roots_config_get_cursor(config, seat->seat->name);
+ if (cc != NULL) {
+ mapped_output = cc->mapped_output;
+ }
+ wl_list_for_each(output, &desktop->outputs, link) {
+ if (mapped_output &&
+ strcmp(mapped_output, output->wlr_output->name) == 0) {
+ wlr_cursor_map_to_output(cursor, output->wlr_output);
+ }
+
+ wl_list_for_each(pointer, &seat->pointers, link) {
+ seat_set_device_output_mappings(seat, pointer->device,
+ output->wlr_output);
+ }
+ wl_list_for_each(tablet, &seat->tablets, link) {
+ seat_set_device_output_mappings(seat, tablet->device,
+ output->wlr_output);
+ }
+ wl_list_for_each(touch, &seat->touch, link) {
+ seat_set_device_output_mappings(seat, touch->device,
+ output->wlr_output);
+ }
+ }
+}
+
+static void roots_seat_init_cursor(struct roots_seat *seat) {
+ seat->cursor = roots_cursor_create(seat);
+ if (!seat->cursor) {
+ return;
+ }
+ seat->cursor->seat = seat;
+ struct wlr_cursor *wlr_cursor = seat->cursor->cursor;
+ struct roots_desktop *desktop = seat->input->server->desktop;
+ wlr_cursor_attach_output_layout(wlr_cursor, desktop->layout);
+
+ roots_seat_configure_cursor(seat);
+ roots_seat_configure_xcursor(seat);
+
+ // add input signals
+ wl_signal_add(&wlr_cursor->events.motion, &seat->cursor->motion);
+ seat->cursor->motion.notify = handle_cursor_motion;
+
+ wl_signal_add(&wlr_cursor->events.motion_absolute,
+ &seat->cursor->motion_absolute);
+ seat->cursor->motion_absolute.notify = handle_cursor_motion_absolute;
+
+ wl_signal_add(&wlr_cursor->events.button, &seat->cursor->button);
+ seat->cursor->button.notify = handle_cursor_button;
+
+ wl_signal_add(&wlr_cursor->events.axis, &seat->cursor->axis);
+ seat->cursor->axis.notify = handle_cursor_axis;
+
+ wl_signal_add(&wlr_cursor->events.touch_down, &seat->cursor->touch_down);
+ seat->cursor->touch_down.notify = handle_touch_down;
+
+ wl_signal_add(&wlr_cursor->events.touch_up, &seat->cursor->touch_up);
+ seat->cursor->touch_up.notify = handle_touch_up;
+
+ wl_signal_add(&wlr_cursor->events.touch_motion,
+ &seat->cursor->touch_motion);
+ seat->cursor->touch_motion.notify = handle_touch_motion;
+
+ wl_signal_add(&wlr_cursor->events.tablet_tool_axis,
+ &seat->cursor->tool_axis);
+ seat->cursor->tool_axis.notify = handle_tool_axis;
+
+ wl_signal_add(&wlr_cursor->events.tablet_tool_tip, &seat->cursor->tool_tip);
+ seat->cursor->tool_tip.notify = handle_tool_tip;
+
+ wl_signal_add(&wlr_cursor->events.tablet_tool_proximity, &seat->cursor->tool_proximity);
+ seat->cursor->tool_proximity.notify = handle_tool_proximity;
+
+ wl_signal_add(&wlr_cursor->events.tablet_tool_button, &seat->cursor->tool_button);
+ seat->cursor->tool_button.notify = handle_tool_button;
+
+ wl_signal_add(&seat->seat->events.request_set_cursor,
+ &seat->cursor->request_set_cursor);
+ seat->cursor->request_set_cursor.notify = handle_request_set_cursor;
+
+ wl_signal_add(&seat->seat->pointer_state.events.focus_change,
+ &seat->cursor->focus_change);
+ seat->cursor->focus_change.notify = handle_pointer_focus_change;
+
+ wl_list_init(&seat->cursor->constraint_commit.link);
+}
+
+static void roots_drag_icon_handle_surface_commit(struct wl_listener *listener,
+ void *data) {
+ struct roots_drag_icon *icon =
+ wl_container_of(listener, icon, surface_commit);
+ roots_drag_icon_update_position(icon);
+}
+
+static void roots_drag_icon_handle_map(struct wl_listener *listener,
+ void *data) {
+ struct roots_drag_icon *icon =
+ wl_container_of(listener, icon, map);
+ roots_drag_icon_damage_whole(icon);
+}
+
+static void roots_drag_icon_handle_unmap(struct wl_listener *listener,
+ void *data) {
+ struct roots_drag_icon *icon =
+ wl_container_of(listener, icon, unmap);
+ roots_drag_icon_damage_whole(icon);
+}
+
+static void roots_drag_icon_handle_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_drag_icon *icon =
+ wl_container_of(listener, icon, destroy);
+ roots_drag_icon_damage_whole(icon);
+
+ wl_list_remove(&icon->link);
+ wl_list_remove(&icon->surface_commit.link);
+ wl_list_remove(&icon->unmap.link);
+ wl_list_remove(&icon->destroy.link);
+ free(icon);
+}
+
+static void roots_seat_handle_new_drag_icon(struct wl_listener *listener,
+ void *data) {
+ struct roots_seat *seat = wl_container_of(listener, seat, new_drag_icon);
+ struct wlr_drag_icon *wlr_drag_icon = data;
+
+ struct roots_drag_icon *icon = calloc(1, sizeof(struct roots_drag_icon));
+ if (icon == NULL) {
+ return;
+ }
+ icon->seat = seat;
+ icon->wlr_drag_icon = wlr_drag_icon;
+
+ icon->surface_commit.notify = roots_drag_icon_handle_surface_commit;
+ wl_signal_add(&wlr_drag_icon->surface->events.commit, &icon->surface_commit);
+ icon->unmap.notify = roots_drag_icon_handle_unmap;
+ wl_signal_add(&wlr_drag_icon->events.unmap, &icon->unmap);
+ icon->map.notify = roots_drag_icon_handle_map;
+ wl_signal_add(&wlr_drag_icon->events.map, &icon->map);
+ icon->destroy.notify = roots_drag_icon_handle_destroy;
+ wl_signal_add(&wlr_drag_icon->events.destroy, &icon->destroy);
+
+ wl_list_insert(&seat->drag_icons, &icon->link);
+
+ roots_drag_icon_update_position(icon);
+}
+
+void roots_drag_icon_update_position(struct roots_drag_icon *icon) {
+ roots_drag_icon_damage_whole(icon);
+
+ struct wlr_drag_icon *wlr_icon = icon->wlr_drag_icon;
+ struct roots_seat *seat = icon->seat;
+ struct wlr_cursor *cursor = seat->cursor->cursor;
+ if (wlr_icon->is_pointer) {
+ icon->x = cursor->x;
+ icon->y = cursor->y;
+ } else {
+ struct wlr_touch_point *point =
+ wlr_seat_touch_get_point(seat->seat, wlr_icon->touch_id);
+ if (point == NULL) {
+ return;
+ }
+ icon->x = seat->touch_x;
+ icon->y = seat->touch_y;
+ }
+
+ roots_drag_icon_damage_whole(icon);
+}
+
+void roots_drag_icon_damage_whole(struct roots_drag_icon *icon) {
+ struct roots_output *output;
+ wl_list_for_each(output, &icon->seat->input->server->desktop->outputs,
+ link) {
+ output_damage_whole_drag_icon(output, icon);
+ }
+}
+
+static void seat_view_destroy(struct roots_seat_view *seat_view);
+
+static void roots_seat_handle_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_seat *seat = wl_container_of(listener, seat, destroy);
+
+ // TODO: probably more to be freed here
+ wl_list_remove(&seat->destroy.link);
+
+ struct roots_seat_view *view, *nview;
+ wl_list_for_each_safe(view, nview, &seat->views, link) {
+ seat_view_destroy(view);
+ }
+}
+
+void roots_seat_destroy(struct roots_seat *seat) {
+ roots_seat_handle_destroy(&seat->destroy, seat->seat);
+ wlr_seat_destroy(seat->seat);
+}
+
+struct roots_seat *roots_seat_create(struct roots_input *input, char *name) {
+ struct roots_seat *seat = calloc(1, sizeof(struct roots_seat));
+ if (!seat) {
+ return NULL;
+ }
+
+ wl_list_init(&seat->keyboards);
+ wl_list_init(&seat->pointers);
+ wl_list_init(&seat->touch);
+ wl_list_init(&seat->tablets);
+ wl_list_init(&seat->tablet_pads);
+ wl_list_init(&seat->switches);
+ wl_list_init(&seat->views);
+ wl_list_init(&seat->drag_icons);
+
+ seat->input = input;
+
+ seat->seat = wlr_seat_create(input->server->wl_display, name);
+ if (!seat->seat) {
+ free(seat);
+ return NULL;
+ }
+ seat->seat->data = seat;
+
+ roots_seat_init_cursor(seat);
+ if (!seat->cursor) {
+ wlr_seat_destroy(seat->seat);
+ free(seat);
+ return NULL;
+ }
+
+ roots_input_method_relay_init(seat, &seat->im_relay);
+
+ wl_list_insert(&input->seats, &seat->link);
+
+ seat->new_drag_icon.notify = roots_seat_handle_new_drag_icon;
+ wl_signal_add(&seat->seat->events.new_drag_icon, &seat->new_drag_icon);
+ seat->destroy.notify = roots_seat_handle_destroy;
+ wl_signal_add(&seat->seat->events.destroy, &seat->destroy);
+
+ return seat;
+}
+
+static void seat_update_capabilities(struct roots_seat *seat) {
+ uint32_t caps = 0;
+ if (!wl_list_empty(&seat->keyboards)) {
+ caps |= WL_SEAT_CAPABILITY_KEYBOARD;
+ }
+ if (!wl_list_empty(&seat->pointers) || !wl_list_empty(&seat->tablets)) {
+ caps |= WL_SEAT_CAPABILITY_POINTER;
+ }
+ if (!wl_list_empty(&seat->touch)) {
+ caps |= WL_SEAT_CAPABILITY_TOUCH;
+ }
+ wlr_seat_set_capabilities(seat->seat, caps);
+
+ // Hide cursor if seat doesn't have pointer capability
+ if ((caps & WL_SEAT_CAPABILITY_POINTER) == 0) {
+ wlr_cursor_set_image(seat->cursor->cursor, NULL, 0, 0, 0, 0, 0, 0);
+ } else {
+ wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager,
+ seat->cursor->default_xcursor, seat->cursor->cursor);
+ }
+}
+
+static void handle_keyboard_destroy(struct wl_listener *listener, void *data) {
+ struct roots_keyboard *keyboard =
+ wl_container_of(listener, keyboard, device_destroy);
+ struct roots_seat *seat = keyboard->seat;
+ wl_list_remove(&keyboard->device_destroy.link);
+ wl_list_remove(&keyboard->keyboard_key.link);
+ wl_list_remove(&keyboard->keyboard_modifiers.link);
+ roots_keyboard_destroy(keyboard);
+ seat_update_capabilities(seat);
+}
+
+static void seat_add_keyboard(struct roots_seat *seat,
+ struct wlr_input_device *device) {
+ assert(device->type == WLR_INPUT_DEVICE_KEYBOARD);
+ struct roots_keyboard *keyboard =
+ roots_keyboard_create(device, seat->input);
+ if (keyboard == NULL) {
+ wlr_log(WLR_ERROR, "could not allocate keyboard for seat");
+ return;
+ }
+
+ keyboard->seat = seat;
+
+ wl_list_insert(&seat->keyboards, &keyboard->link);
+
+ keyboard->device_destroy.notify = handle_keyboard_destroy;
+ wl_signal_add(&keyboard->device->events.destroy, &keyboard->device_destroy);
+ keyboard->keyboard_key.notify = handle_keyboard_key;
+ wl_signal_add(&keyboard->device->keyboard->events.key,
+ &keyboard->keyboard_key);
+ keyboard->keyboard_modifiers.notify = handle_keyboard_modifiers;
+ wl_signal_add(&keyboard->device->keyboard->events.modifiers,
+ &keyboard->keyboard_modifiers);
+
+ wlr_seat_set_keyboard(seat->seat, device);
+}
+
+static void handle_pointer_destroy(struct wl_listener *listener, void *data) {
+ struct roots_pointer *pointer =
+ wl_container_of(listener, pointer, device_destroy);
+ struct roots_seat *seat = pointer->seat;
+
+ wl_list_remove(&pointer->link);
+ wlr_cursor_detach_input_device(seat->cursor->cursor, pointer->device);
+ wl_list_remove(&pointer->device_destroy.link);
+ free(pointer);
+
+ seat_update_capabilities(seat);
+}
+
+static void seat_add_pointer(struct roots_seat *seat,
+ struct wlr_input_device *device) {
+ struct roots_pointer *pointer = calloc(1, sizeof(struct roots_pointer));
+ if (!pointer) {
+ wlr_log(WLR_ERROR, "could not allocate pointer for seat");
+ return;
+ }
+
+ device->data = pointer;
+ pointer->device = device;
+ pointer->seat = seat;
+ wl_list_insert(&seat->pointers, &pointer->link);
+
+ pointer->device_destroy.notify = handle_pointer_destroy;
+ wl_signal_add(&pointer->device->events.destroy, &pointer->device_destroy);
+
+ wlr_cursor_attach_input_device(seat->cursor->cursor, device);
+ roots_seat_configure_cursor(seat);
+}
+
+static void handle_switch_destroy(struct wl_listener *listener, void *data) {
+ struct roots_switch *lid_switch =
+ wl_container_of(listener, lid_switch, device_destroy);
+ struct roots_seat *seat = lid_switch->seat;
+
+ wl_list_remove(&lid_switch->link);
+ wl_list_remove(&lid_switch->device_destroy.link);
+ free(lid_switch);
+
+ seat_update_capabilities(seat);
+}
+
+static void seat_add_switch(struct roots_seat *seat,
+ struct wlr_input_device *device) {
+ assert(device->type == WLR_INPUT_DEVICE_SWITCH);
+ struct roots_switch *lid_switch = calloc(1, sizeof(struct roots_switch));
+ if (!lid_switch) {
+ wlr_log(WLR_ERROR, "could not allocate switch for seat");
+ return;
+ }
+ device->data = lid_switch;
+ lid_switch->device = device;
+ lid_switch->seat = seat;
+ wl_list_insert(&seat->switches, &lid_switch->link);
+ lid_switch->device_destroy.notify = handle_switch_destroy;
+
+ lid_switch->toggle.notify = handle_switch_toggle;
+ wl_signal_add(&lid_switch->device->lid_switch->events.toggle, &lid_switch->toggle);
+}
+
+static void handle_touch_destroy(struct wl_listener *listener, void *data) {
+ struct roots_pointer *touch =
+ wl_container_of(listener, touch, device_destroy);
+ struct roots_seat *seat = touch->seat;
+
+ wl_list_remove(&touch->link);
+ wlr_cursor_detach_input_device(seat->cursor->cursor, touch->device);
+ wl_list_remove(&touch->device_destroy.link);
+ free(touch);
+
+ seat_update_capabilities(seat);
+}
+
+static void seat_add_touch(struct roots_seat *seat,
+ struct wlr_input_device *device) {
+ struct roots_touch *touch = calloc(1, sizeof(struct roots_touch));
+ if (!touch) {
+ wlr_log(WLR_ERROR, "could not allocate touch for seat");
+ return;
+ }
+
+ device->data = touch;
+ touch->device = device;
+ touch->seat = seat;
+ wl_list_insert(&seat->touch, &touch->link);
+
+ touch->device_destroy.notify = handle_touch_destroy;
+ wl_signal_add(&touch->device->events.destroy, &touch->device_destroy);
+
+ wlr_cursor_attach_input_device(seat->cursor->cursor, device);
+ roots_seat_configure_cursor(seat);
+}
+
+static void handle_tablet_pad_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_tablet_pad *tablet_pad =
+ wl_container_of(listener, tablet_pad, device_destroy);
+ struct roots_seat *seat = tablet_pad->seat;
+
+ wl_list_remove(&tablet_pad->device_destroy.link);
+ wl_list_remove(&tablet_pad->tablet_destroy.link);
+ wl_list_remove(&tablet_pad->attach.link);
+ wl_list_remove(&tablet_pad->link);
+
+ wl_list_remove(&tablet_pad->button.link);
+ wl_list_remove(&tablet_pad->strip.link);
+ wl_list_remove(&tablet_pad->ring.link);
+ free(tablet_pad);
+
+ seat_update_capabilities(seat);
+}
+
+static void handle_pad_tool_destroy(struct wl_listener *listener, void *data) {
+ struct roots_tablet_pad *pad =
+ wl_container_of(listener, pad, tablet_destroy);
+
+ pad->tablet = NULL;
+
+ wl_list_remove(&pad->tablet_destroy.link);
+ wl_list_init(&pad->tablet_destroy.link);
+}
+
+static void attach_tablet_pad(struct roots_tablet_pad *pad,
+ struct roots_tablet *tool) {
+ wlr_log(WLR_DEBUG, "Attaching tablet pad \"%s\" to tablet tool \"%s\"",
+ pad->device->name, tool->device->name);
+
+ pad->tablet = tool;
+
+ wl_list_remove(&pad->tablet_destroy.link);
+ pad->tablet_destroy.notify = handle_pad_tool_destroy;
+ wl_signal_add(&tool->device->events.destroy, &pad->tablet_destroy);
+}
+
+static void handle_tablet_pad_attach(struct wl_listener *listener, void *data) {
+ struct roots_tablet_pad *pad =
+ wl_container_of(listener, pad, attach);
+ struct wlr_tablet_tool *wlr_tool = data;
+ struct roots_tablet *tool = wlr_tool->data;
+
+ attach_tablet_pad(pad, tool);
+}
+
+static void handle_tablet_pad_ring(struct wl_listener *listener, void *data) {
+ struct roots_tablet_pad *pad =
+ wl_container_of(listener, pad, ring);
+ struct wlr_event_tablet_pad_ring *event = data;
+
+ wlr_tablet_v2_tablet_pad_notify_ring(pad->tablet_v2_pad,
+ event->ring, event->position,
+ event->source == WLR_TABLET_PAD_RING_SOURCE_FINGER,
+ event->time_msec);
+}
+
+static void handle_tablet_pad_strip(struct wl_listener *listener, void *data) {
+ struct roots_tablet_pad *pad =
+ wl_container_of(listener, pad, strip);
+ struct wlr_event_tablet_pad_strip *event = data;
+
+ wlr_tablet_v2_tablet_pad_notify_strip(pad->tablet_v2_pad,
+ event->strip, event->position,
+ event->source == WLR_TABLET_PAD_STRIP_SOURCE_FINGER,
+ event->time_msec);
+}
+
+static void handle_tablet_pad_button(struct wl_listener *listener, void *data) {
+ struct roots_tablet_pad *pad =
+ wl_container_of(listener, pad, button);
+ struct wlr_event_tablet_pad_button *event = data;
+
+ wlr_tablet_v2_tablet_pad_notify_mode(pad->tablet_v2_pad,
+ event->group, event->mode, event->time_msec);
+
+ wlr_tablet_v2_tablet_pad_notify_button(pad->tablet_v2_pad,
+ event->button, event->time_msec,
+ (enum zwp_tablet_pad_v2_button_state)event->state);
+}
+
+static void seat_add_tablet_pad(struct roots_seat *seat,
+ struct wlr_input_device *device) {
+ struct roots_tablet_pad *tablet_pad =
+ calloc(1, sizeof(struct roots_tablet_pad));
+ if (!tablet_pad) {
+ wlr_log(WLR_ERROR, "could not allocate tablet_pad for seat");
+ return;
+ }
+
+ device->data = tablet_pad;
+ tablet_pad->device = device;
+ tablet_pad->seat = seat;
+ wl_list_insert(&seat->tablet_pads, &tablet_pad->link);
+
+ tablet_pad->device_destroy.notify = handle_tablet_pad_destroy;
+ wl_signal_add(&tablet_pad->device->events.destroy,
+ &tablet_pad->device_destroy);
+
+ tablet_pad->attach.notify = handle_tablet_pad_attach;
+ wl_signal_add(&tablet_pad->device->tablet_pad->events.attach_tablet,
+ &tablet_pad->attach);
+
+ tablet_pad->button.notify = handle_tablet_pad_button;
+ wl_signal_add(&tablet_pad->device->tablet_pad->events.button, &tablet_pad->button);
+
+ tablet_pad->strip.notify = handle_tablet_pad_strip;
+ wl_signal_add(&tablet_pad->device->tablet_pad->events.strip, &tablet_pad->strip);
+
+ tablet_pad->ring.notify = handle_tablet_pad_ring;
+ wl_signal_add(&tablet_pad->device->tablet_pad->events.ring, &tablet_pad->ring);
+
+ wl_list_init(&tablet_pad->tablet_destroy.link);
+
+ struct roots_desktop *desktop = seat->input->server->desktop;
+ tablet_pad->tablet_v2_pad =
+ wlr_tablet_pad_create(desktop->tablet_v2, seat->seat, device);
+
+ /* Search for a sibling tablet */
+ if (!wlr_input_device_is_libinput(device)) {
+ /* We can only do this on libinput devices */
+ return;
+ }
+
+ struct libinput_device_group *group =
+ libinput_device_get_device_group(wlr_libinput_get_device_handle(device));
+ struct roots_tablet *tool;
+ wl_list_for_each(tool, &seat->tablets, link) {
+ if (!wlr_input_device_is_libinput(tool->device)) {
+ continue;
+ }
+
+ struct libinput_device *li_dev =
+ wlr_libinput_get_device_handle(tool->device);
+ if (libinput_device_get_device_group(li_dev) == group) {
+ attach_tablet_pad(tablet_pad, tool);
+ break;
+ }
+ }
+}
+
+static void handle_tablet_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_tablet *tablet =
+ wl_container_of(listener, tablet, device_destroy);
+ struct roots_seat *seat = tablet->seat;
+
+ wlr_cursor_detach_input_device(seat->cursor->cursor, tablet->device);
+ wl_list_remove(&tablet->device_destroy.link);
+ wl_list_remove(&tablet->link);
+ free(tablet);
+
+ seat_update_capabilities(seat);
+}
+
+static void seat_add_tablet_tool(struct roots_seat *seat,
+ struct wlr_input_device *device) {
+ struct roots_tablet *tablet =
+ calloc(1, sizeof(struct roots_tablet));
+ if (!tablet) {
+ wlr_log(WLR_ERROR, "could not allocate tablet for seat");
+ return;
+ }
+
+ device->data = tablet;
+ tablet->device = device;
+ tablet->seat = seat;
+ wl_list_insert(&seat->tablets, &tablet->link);
+
+ tablet->device_destroy.notify = handle_tablet_destroy;
+ wl_signal_add(&tablet->device->events.destroy,
+ &tablet->device_destroy);
+
+ wlr_cursor_attach_input_device(seat->cursor->cursor, device);
+ roots_seat_configure_cursor(seat);
+
+ struct roots_desktop *desktop = seat->input->server->desktop;
+
+ tablet->tablet_v2 =
+ wlr_tablet_create(desktop->tablet_v2, seat->seat, device);
+
+ struct libinput_device_group *group =
+ libinput_device_get_device_group(wlr_libinput_get_device_handle(device));
+ struct roots_tablet_pad *pad;
+ wl_list_for_each(pad, &seat->tablet_pads, link) {
+ if (!wlr_input_device_is_libinput(pad->device)) {
+ continue;
+ }
+
+ struct libinput_device *li_dev =
+ wlr_libinput_get_device_handle(pad->device);
+ if (libinput_device_get_device_group(li_dev) == group) {
+ attach_tablet_pad(pad, tablet);
+ }
+ }
+}
+
+void roots_seat_add_device(struct roots_seat *seat,
+ struct wlr_input_device *device) {
+ switch (device->type) {
+ case WLR_INPUT_DEVICE_KEYBOARD:
+ seat_add_keyboard(seat, device);
+ break;
+ case WLR_INPUT_DEVICE_POINTER:
+ seat_add_pointer(seat, device);
+ break;
+ case WLR_INPUT_DEVICE_SWITCH:
+ seat_add_switch(seat, device);
+ break;
+ case WLR_INPUT_DEVICE_TOUCH:
+ seat_add_touch(seat, device);
+ break;
+ case WLR_INPUT_DEVICE_TABLET_PAD:
+ seat_add_tablet_pad(seat, device);
+ break;
+ case WLR_INPUT_DEVICE_TABLET_TOOL:
+ seat_add_tablet_tool(seat, device);
+ break;
+ }
+
+ seat_update_capabilities(seat);
+}
+
+void roots_seat_configure_xcursor(struct roots_seat *seat) {
+ const char *cursor_theme = NULL;
+ struct roots_cursor_config *cc =
+ roots_config_get_cursor(seat->input->config, seat->seat->name);
+ if (cc != NULL) {
+ cursor_theme = cc->theme;
+ if (cc->default_image != NULL) {
+ seat->cursor->default_xcursor = cc->default_image;
+ }
+ }
+
+ if (!seat->cursor->xcursor_manager) {
+ seat->cursor->xcursor_manager =
+ wlr_xcursor_manager_create(cursor_theme, ROOTS_XCURSOR_SIZE);
+ if (seat->cursor->xcursor_manager == NULL) {
+ wlr_log(WLR_ERROR, "Cannot create XCursor manager for theme %s",
+ cursor_theme);
+ return;
+ }
+ }
+
+ struct roots_output *output;
+ wl_list_for_each(output, &seat->input->server->desktop->outputs, link) {
+ float scale = output->wlr_output->scale;
+ if (wlr_xcursor_manager_load(seat->cursor->xcursor_manager, scale)) {
+ wlr_log(WLR_ERROR, "Cannot load xcursor theme for output '%s' "
+ "with scale %f", output->wlr_output->name, scale);
+ }
+ }
+
+ wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager,
+ seat->cursor->default_xcursor, seat->cursor->cursor);
+ wlr_cursor_warp(seat->cursor->cursor, NULL, seat->cursor->cursor->x,
+ seat->cursor->cursor->y);
+}
+
+bool roots_seat_has_meta_pressed(struct roots_seat *seat) {
+ struct roots_keyboard *keyboard;
+ wl_list_for_each(keyboard, &seat->keyboards, link) {
+ if (!keyboard->config->meta_key) {
+ continue;
+ }
+
+ uint32_t modifiers =
+ wlr_keyboard_get_modifiers(keyboard->device->keyboard);
+ if ((modifiers ^ keyboard->config->meta_key) == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+struct roots_view *roots_seat_get_focus(struct roots_seat *seat) {
+ if (!seat->has_focus || wl_list_empty(&seat->views)) {
+ return NULL;
+ }
+ struct roots_seat_view *seat_view =
+ wl_container_of(seat->views.next, seat_view, link);
+ return seat_view->view;
+}
+
+static void seat_view_destroy(struct roots_seat_view *seat_view) {
+ struct roots_seat *seat = seat_view->seat;
+
+ if (seat_view->view == roots_seat_get_focus(seat)) {
+ seat->has_focus = false;
+ seat->cursor->mode = ROOTS_CURSOR_PASSTHROUGH;
+ }
+
+ if (seat_view == seat->cursor->pointer_view) {
+ seat->cursor->pointer_view = NULL;
+ }
+
+ wl_list_remove(&seat_view->view_unmap.link);
+ wl_list_remove(&seat_view->view_destroy.link);
+ wl_list_remove(&seat_view->link);
+ free(seat_view);
+
+ // Focus first view
+ if (!wl_list_empty(&seat->views)) {
+ struct roots_seat_view *first_seat_view = wl_container_of(
+ seat->views.next, first_seat_view, link);
+ roots_seat_set_focus(seat, first_seat_view->view);
+ }
+}
+
+static void seat_view_handle_unmap(struct wl_listener *listener, void *data) {
+ struct roots_seat_view *seat_view =
+ wl_container_of(listener, seat_view, view_unmap);
+ seat_view_destroy(seat_view);
+}
+
+static void seat_view_handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_seat_view *seat_view =
+ wl_container_of(listener, seat_view, view_destroy);
+ seat_view_destroy(seat_view);
+}
+
+static struct roots_seat_view *seat_add_view(struct roots_seat *seat,
+ struct roots_view *view) {
+ struct roots_seat_view *seat_view =
+ calloc(1, sizeof(struct roots_seat_view));
+ if (seat_view == NULL) {
+ return NULL;
+ }
+ seat_view->seat = seat;
+ seat_view->view = view;
+
+ wl_list_insert(seat->views.prev, &seat_view->link);
+
+ seat_view->view_unmap.notify = seat_view_handle_unmap;
+ wl_signal_add(&view->events.unmap, &seat_view->view_unmap);
+ seat_view->view_destroy.notify = seat_view_handle_destroy;
+ wl_signal_add(&view->events.destroy, &seat_view->view_destroy);
+
+ return seat_view;
+}
+
+struct roots_seat_view *roots_seat_view_from_view(
+ struct roots_seat *seat, struct roots_view *view) {
+ if (view == NULL) {
+ return NULL;
+ }
+
+ bool found = false;
+ struct roots_seat_view *seat_view = NULL;
+ wl_list_for_each(seat_view, &seat->views, link) {
+ if (seat_view->view == view) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ seat_view = seat_add_view(seat, view);
+ if (seat_view == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed");
+ return NULL;
+ }
+ }
+
+ return seat_view;
+}
+
+bool roots_seat_allow_input(struct roots_seat *seat,
+ struct wl_resource *resource) {
+ return !seat->exclusive_client ||
+ wl_resource_get_client(resource) == seat->exclusive_client;
+}
+
+void roots_seat_set_focus(struct roots_seat *seat, struct roots_view *view) {
+ if (view && !roots_seat_allow_input(seat, view->wlr_surface->resource)) {
+ return;
+ }
+
+ // Make sure the view will be rendered on top of others, even if it's
+ // already focused in this seat
+ if (view != NULL) {
+ wl_list_remove(&view->link);
+ wl_list_insert(&seat->input->server->desktop->views, &view->link);
+ }
+
+ bool unfullscreen = true;
+
+#if WLR_HAS_XWAYLAND
+ if (view && view->type == ROOTS_XWAYLAND_VIEW &&
+ view->xwayland_surface->override_redirect) {
+ unfullscreen = false;
+ }
+#endif
+
+ if (view && unfullscreen) {
+ struct roots_desktop *desktop = view->desktop;
+ struct roots_output *output;
+ struct wlr_box box;
+ view_get_box(view, &box);
+ wl_list_for_each(output, &desktop->outputs, link) {
+ if (output->fullscreen_view &&
+ output->fullscreen_view != view &&
+ wlr_output_layout_intersects(
+ desktop->layout,
+ output->wlr_output, &box)) {
+ view_set_fullscreen(output->fullscreen_view,
+ false, NULL);
+ }
+ }
+ }
+
+ struct roots_view *prev_focus = roots_seat_get_focus(seat);
+ if (view == prev_focus) {
+ return;
+ }
+
+#if WLR_HAS_XWAYLAND
+ if (view && view->type == ROOTS_XWAYLAND_VIEW &&
+ !wlr_xwayland_or_surface_wants_focus(
+ view->xwayland_surface)) {
+ return;
+ }
+#endif
+ struct roots_seat_view *seat_view = NULL;
+ if (view != NULL) {
+ seat_view = roots_seat_view_from_view(seat, view);
+ if (seat_view == NULL) {
+ return;
+ }
+ }
+
+ seat->has_focus = false;
+
+ // Deactivate the old view if it is not focused by some other seat
+ if (prev_focus != NULL && !input_view_has_focus(seat->input, prev_focus)) {
+ view_activate(prev_focus, false);
+ }
+
+ if (view == NULL) {
+ seat->cursor->mode = ROOTS_CURSOR_PASSTHROUGH;
+ wlr_seat_keyboard_clear_focus(seat->seat);
+ roots_input_method_relay_set_focus(&seat->im_relay, NULL);
+ return;
+ }
+
+ wl_list_remove(&seat_view->link);
+ wl_list_insert(&seat->views, &seat_view->link);
+
+ view_damage_whole(view);
+
+ if (seat->focused_layer) {
+ return;
+ }
+
+ view_activate(view, true);
+ seat->has_focus = true;
+
+ // An existing keyboard grab might try to deny setting focus, so cancel it
+ wlr_seat_keyboard_end_grab(seat->seat);
+
+ struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->seat);
+ if (keyboard != NULL) {
+ wlr_seat_keyboard_notify_enter(seat->seat, view->wlr_surface,
+ keyboard->keycodes, keyboard->num_keycodes,
+ &keyboard->modifiers);
+ /* FIXME: Move this to a better place */
+ struct roots_tablet_pad *pad;
+ wl_list_for_each(pad, &seat->tablet_pads, link) {
+ if (pad->tablet) {
+ wlr_tablet_v2_tablet_pad_notify_enter(pad->tablet_v2_pad, pad->tablet->tablet_v2, view->wlr_surface);
+ }
+ }
+ } else {
+ wlr_seat_keyboard_notify_enter(seat->seat, view->wlr_surface,
+ NULL, 0, NULL);
+ }
+
+ if (seat->cursor) {
+ roots_cursor_update_focus(seat->cursor);
+ }
+
+ roots_input_method_relay_set_focus(&seat->im_relay, view->wlr_surface);
+}
+
+/**
+ * Focus semantics of layer surfaces are somewhat detached from the normal focus
+ * flow. For layers above the shell layer, for example, you cannot unfocus them.
+ * You also cannot alt-tab between layer surfaces and shell surfaces.
+ */
+void roots_seat_set_focus_layer(struct roots_seat *seat,
+ struct wlr_layer_surface_v1 *layer) {
+ if (!layer) {
+ seat->focused_layer = NULL;
+ return;
+ }
+ struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->seat);
+ if (!roots_seat_allow_input(seat, layer->resource)) {
+ return;
+ }
+ if (seat->has_focus) {
+ struct roots_view *prev_focus = roots_seat_get_focus(seat);
+ wlr_seat_keyboard_clear_focus(seat->seat);
+ view_activate(prev_focus, false);
+ }
+ seat->has_focus = false;
+ if (layer->layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) {
+ seat->focused_layer = layer;
+ }
+ if (keyboard != NULL) {
+ wlr_seat_keyboard_notify_enter(seat->seat, layer->surface,
+ keyboard->keycodes, keyboard->num_keycodes,
+ &keyboard->modifiers);
+ } else {
+ wlr_seat_keyboard_notify_enter(seat->seat, layer->surface,
+ NULL, 0, NULL);
+ }
+
+
+ if (seat->cursor) {
+ roots_cursor_update_focus(seat->cursor);
+ }
+}
+
+void roots_seat_set_exclusive_client(struct roots_seat *seat,
+ struct wl_client *client) {
+ if (!client) {
+ seat->exclusive_client = client;
+ // Triggers a refocus of the topmost surface layer if necessary
+ // TODO: Make layer surface focus per-output based on cursor position
+ struct roots_output *output;
+ wl_list_for_each(output, &seat->input->server->desktop->outputs, link) {
+ arrange_layers(output);
+ }
+ return;
+ }
+ if (seat->focused_layer) {
+ if (wl_resource_get_client(seat->focused_layer->resource) != client) {
+ roots_seat_set_focus_layer(seat, NULL);
+ }
+ }
+ if (seat->has_focus) {
+ struct roots_view *focus = roots_seat_get_focus(seat);
+ if (wl_resource_get_client(focus->wlr_surface->resource) != client) {
+ roots_seat_set_focus(seat, NULL);
+ }
+ }
+ if (seat->seat->pointer_state.focused_client) {
+ if (seat->seat->pointer_state.focused_client->client != client) {
+ wlr_seat_pointer_clear_focus(seat->seat);
+ }
+ }
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ struct wlr_touch_point *point;
+ wl_list_for_each(point, &seat->seat->touch_state.touch_points, link) {
+ if (point->client->client != client) {
+ wlr_seat_touch_point_clear_focus(seat->seat,
+ now.tv_nsec / 1000, point->touch_id);
+ }
+ }
+ seat->exclusive_client = client;
+}
+
+void roots_seat_cycle_focus(struct roots_seat *seat) {
+ if (wl_list_empty(&seat->views)) {
+ return;
+ }
+
+ struct roots_seat_view *first_seat_view = wl_container_of(
+ seat->views.next, first_seat_view, link);
+ if (!seat->has_focus) {
+ roots_seat_set_focus(seat, first_seat_view->view);
+ return;
+ }
+ if (wl_list_length(&seat->views) < 2) {
+ return;
+ }
+
+ // Focus the next view
+ struct roots_seat_view *next_seat_view = wl_container_of(
+ first_seat_view->link.next, next_seat_view, link);
+ roots_seat_set_focus(seat, next_seat_view->view);
+
+ // Move the first view to the end of the list
+ wl_list_remove(&first_seat_view->link);
+ wl_list_insert(seat->views.prev, &first_seat_view->link);
+}
+
+void roots_seat_begin_move(struct roots_seat *seat, struct roots_view *view) {
+ struct roots_cursor *cursor = seat->cursor;
+ cursor->mode = ROOTS_CURSOR_MOVE;
+ cursor->offs_x = cursor->cursor->x;
+ cursor->offs_y = cursor->cursor->y;
+ if (view->maximized) {
+ cursor->view_x = view->saved.x;
+ cursor->view_y = view->saved.y;
+ } else {
+ cursor->view_x = view->box.x;
+ cursor->view_y = view->box.y;
+ }
+ view_maximize(view, false);
+ wlr_seat_pointer_clear_focus(seat->seat);
+
+ wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager,
+ ROOTS_XCURSOR_MOVE, seat->cursor->cursor);
+}
+
+void roots_seat_begin_resize(struct roots_seat *seat, struct roots_view *view,
+ uint32_t edges) {
+ struct roots_cursor *cursor = seat->cursor;
+ cursor->mode = ROOTS_CURSOR_RESIZE;
+ cursor->offs_x = cursor->cursor->x;
+ cursor->offs_y = cursor->cursor->y;
+ if (view->maximized) {
+ cursor->view_x = view->saved.x;
+ cursor->view_y = view->saved.y;
+ cursor->view_width = view->saved.width;
+ cursor->view_height = view->saved.height;
+ } else {
+ cursor->view_x = view->box.x;
+ cursor->view_y = view->box.y;
+ struct wlr_box box;
+ view_get_box(view, &box);
+ cursor->view_width = box.width;
+ cursor->view_height = box.height;
+ }
+ cursor->resize_edges = edges;
+ view_maximize(view, false);
+ wlr_seat_pointer_clear_focus(seat->seat);
+
+ const char *resize_name = wlr_xcursor_get_resize_name(edges);
+ wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager,
+ resize_name, seat->cursor->cursor);
+}
+
+void roots_seat_begin_rotate(struct roots_seat *seat, struct roots_view *view) {
+ struct roots_cursor *cursor = seat->cursor;
+ cursor->mode = ROOTS_CURSOR_ROTATE;
+ cursor->offs_x = cursor->cursor->x;
+ cursor->offs_y = cursor->cursor->y;
+ cursor->view_rotation = view->rotation;
+ view_maximize(view, false);
+ wlr_seat_pointer_clear_focus(seat->seat);
+
+ wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager,
+ ROOTS_XCURSOR_ROTATE, seat->cursor->cursor);
+}
+
+void roots_seat_end_compositor_grab(struct roots_seat *seat) {
+ struct roots_cursor *cursor = seat->cursor;
+ struct roots_view *view = roots_seat_get_focus(seat);
+
+ if (view == NULL) {
+ return;
+ }
+
+ switch(cursor->mode) {
+ case ROOTS_CURSOR_MOVE:
+ view_move(view, cursor->view_x, cursor->view_y);
+ break;
+ case ROOTS_CURSOR_RESIZE:
+ view_move_resize(view, cursor->view_x, cursor->view_y, cursor->view_width, cursor->view_height);
+ break;
+ case ROOTS_CURSOR_ROTATE:
+ view->rotation = cursor->view_rotation;
+ break;
+ case ROOTS_CURSOR_PASSTHROUGH:
+ break;
+ }
+
+ cursor->mode = ROOTS_CURSOR_PASSTHROUGH;
+}
+
+struct roots_seat *input_last_active_seat(struct roots_input *input) {
+ struct roots_seat *seat = NULL, *_seat;
+ wl_list_for_each(_seat, &input->seats, link) {
+ if (!seat || (seat->seat->last_event.tv_sec > _seat->seat->last_event.tv_sec &&
+ seat->seat->last_event.tv_nsec > _seat->seat->last_event.tv_nsec)) {
+ seat = _seat;
+ }
+ }
+ return seat;
+}
diff --git a/rootston/switch.c b/rootston/switch.c
new file mode 100644
index 00000000..65c5e627
--- /dev/null
+++ b/rootston/switch.c
@@ -0,0 +1,26 @@
+#include <stdlib.h>
+
+#include <wlr/util/log.h>
+
+#include "rootston/bindings.h"
+#include "rootston/config.h"
+#include "rootston/input.h"
+#include "rootston/seat.h"
+#include "rootston/switch.h"
+
+void roots_switch_handle_toggle(struct roots_switch *lid_switch,
+ struct wlr_event_switch_toggle *event) {
+ struct wl_list *bound_switches = &lid_switch->seat->input->server->config->switches;
+ struct roots_switch_config *sc;
+ wl_list_for_each(sc, bound_switches, link) {
+ if ((sc->name != NULL && strcmp(event->device->name, sc->name) != 0) &&
+ (sc->name == NULL && event->switch_type != sc->switch_type)) {
+ continue;
+ }
+ if (sc->switch_state != WLR_SWITCH_STATE_TOGGLE &&
+ event->switch_state != sc->switch_state) {
+ continue;
+ }
+ execute_binding_command(lid_switch->seat, lid_switch->seat->input, sc->command);
+ }
+}
diff --git a/rootston/text_input.c b/rootston/text_input.c
new file mode 100644
index 00000000..70c92761
--- /dev/null
+++ b/rootston/text_input.c
@@ -0,0 +1,310 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <wlr/util/log.h>
+#include "rootston/seat.h"
+#include "rootston/text_input.h"
+
+static struct roots_text_input *relay_get_focusable_text_input(
+ struct roots_input_method_relay *relay) {
+ struct roots_text_input *text_input = NULL;
+ wl_list_for_each(text_input, &relay->text_inputs, link) {
+ if (text_input->pending_focused_surface) {
+ return text_input;
+ }
+ }
+ return NULL;
+}
+
+static struct roots_text_input *relay_get_focused_text_input(
+ struct roots_input_method_relay *relay) {
+ struct roots_text_input *text_input = NULL;
+ wl_list_for_each(text_input, &relay->text_inputs, link) {
+ if (text_input->input->focused_surface) {
+ return text_input;
+ }
+ }
+ return NULL;
+}
+
+static void handle_im_commit(struct wl_listener *listener, void *data) {
+ struct roots_input_method_relay *relay = wl_container_of(listener, relay,
+ input_method_commit);
+
+ struct roots_text_input *text_input = relay_get_focused_text_input(relay);
+ if (!text_input) {
+ return;
+ }
+ struct wlr_input_method_v2 *context = data;
+ assert(context == relay->input_method);
+ if (context->current.preedit.text) {
+ wlr_text_input_v3_send_preedit_string(text_input->input,
+ context->current.preedit.text,
+ context->current.preedit.cursor_begin,
+ context->current.preedit.cursor_end);
+ }
+ if (context->current.commit_text) {
+ wlr_text_input_v3_send_commit_string(text_input->input,
+ context->current.commit_text);
+ }
+ if (context->current.delete.before_length
+ || context->current.delete.after_length) {
+ wlr_text_input_v3_send_delete_surrounding_text(text_input->input,
+ context->current.delete.before_length,
+ context->current.delete.after_length);
+ }
+ wlr_text_input_v3_send_done(text_input->input);
+}
+
+static void text_input_set_pending_focused_surface(
+ struct roots_text_input *text_input, struct wlr_surface *surface) {
+ text_input->pending_focused_surface = surface;
+ wl_signal_add(&surface->events.destroy,
+ &text_input->pending_focused_surface_destroy);
+}
+
+static void text_input_clear_pending_focused_surface(
+ struct roots_text_input *text_input) {
+ wl_list_remove(&text_input->pending_focused_surface_destroy.link);
+ wl_list_init(&text_input->pending_focused_surface_destroy.link);
+ text_input->pending_focused_surface = NULL;
+}
+
+static void handle_im_destroy(struct wl_listener *listener, void *data) {
+ struct roots_input_method_relay *relay = wl_container_of(listener, relay,
+ input_method_destroy);
+ struct wlr_input_method_v2 *context = data;
+ assert(context == relay->input_method);
+ relay->input_method = NULL;
+ struct roots_text_input *text_input = relay_get_focused_text_input(relay);
+ if (text_input) {
+ // keyboard focus is still there, so keep the surface at hand in case
+ // the input method returns
+ text_input_set_pending_focused_surface(text_input,
+ text_input->input->focused_surface);
+ wlr_text_input_v3_send_leave(text_input->input);
+ }
+}
+
+static void relay_send_im_done(struct roots_input_method_relay *relay,
+ struct wlr_text_input_v3 *input) {
+ struct wlr_input_method_v2 *input_method = relay->input_method;
+ if (!input_method) {
+ wlr_log(WLR_INFO, "Sending IM_DONE but im is gone");
+ return;
+ }
+ // TODO: only send each of those if they were modified
+ wlr_input_method_v2_send_surrounding_text(input_method,
+ input->current.surrounding.text, input->current.surrounding.cursor,
+ input->current.surrounding.anchor);
+ wlr_input_method_v2_send_text_change_cause(input_method,
+ input->current.text_change_cause);
+ wlr_input_method_v2_send_content_type(input_method,
+ input->current.content_type.hint, input->current.content_type.purpose);
+ wlr_input_method_v2_send_done(input_method);
+ // TODO: pass intent, display popup size
+}
+
+static struct roots_text_input *text_input_to_roots(
+ struct roots_input_method_relay *relay,
+ struct wlr_text_input_v3 *text_input) {
+ struct roots_text_input *roots_text_input = NULL;
+ wl_list_for_each(roots_text_input, &relay->text_inputs, link) {
+ if (roots_text_input->input == text_input) {
+ return roots_text_input;
+ }
+ }
+ return NULL;
+}
+
+static void handle_text_input_enable(struct wl_listener *listener, void *data) {
+ struct roots_input_method_relay *relay = wl_container_of(listener, relay,
+ text_input_enable);
+ if (relay->input_method == NULL) {
+ wlr_log(WLR_INFO, "Enabling text input when input method is gone");
+ return;
+ }
+ struct roots_text_input *text_input = text_input_to_roots(relay,
+ (struct wlr_text_input_v3*)data);
+ wlr_input_method_v2_send_activate(relay->input_method);
+ relay_send_im_done(relay, text_input->input);
+}
+
+static void handle_text_input_commit(struct wl_listener *listener,
+ void *data) {
+ struct roots_input_method_relay *relay = wl_container_of(listener, relay,
+ text_input_commit);
+ struct roots_text_input *text_input = text_input_to_roots(relay,
+ (struct wlr_text_input_v3*)data);
+ if (!text_input->input->current_enabled) {
+ wlr_log(WLR_INFO, "Inactive text input tried to commit an update");
+ return;
+ }
+ wlr_log(WLR_DEBUG, "Text input committed update");
+ if (relay->input_method == NULL) {
+ wlr_log(WLR_INFO, "Text input committed, but input method is gone");
+ return;
+ }
+ relay_send_im_done(relay, text_input->input);
+}
+
+static void relay_disable_text_input(struct roots_input_method_relay *relay,
+ struct roots_text_input *text_input) {
+ if (relay->input_method == NULL) {
+ wlr_log(WLR_DEBUG, "Disabling text input, but input method is gone");
+ return;
+ }
+ wlr_input_method_v2_send_deactivate(relay->input_method);
+ relay_send_im_done(relay, text_input->input);
+}
+
+static void handle_text_input_disable(struct wl_listener *listener,
+ void *data) {
+ struct roots_input_method_relay *relay = wl_container_of(listener, relay,
+ text_input_disable);
+ struct roots_text_input *text_input = text_input_to_roots(relay,
+ (struct wlr_text_input_v3*)data);
+ relay_disable_text_input(relay, text_input);
+}
+
+static void handle_text_input_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_input_method_relay *relay = wl_container_of(listener, relay,
+ text_input_destroy);
+ struct roots_text_input *text_input = text_input_to_roots(relay,
+ (struct wlr_text_input_v3*)data);
+
+ if (text_input->input->current_enabled) {
+ relay_disable_text_input(relay, text_input);
+ }
+ text_input_clear_pending_focused_surface(text_input);
+ wl_list_remove(&text_input->link);
+ text_input->input = NULL;
+ free(text_input);
+}
+
+static void handle_pending_focused_surface_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_text_input *text_input = wl_container_of(listener, text_input,
+ pending_focused_surface_destroy);
+ struct wlr_surface *surface = data;
+ assert(text_input->pending_focused_surface == surface);
+ text_input->pending_focused_surface = NULL;
+}
+
+struct roots_text_input *roots_text_input_create(
+ struct roots_input_method_relay *relay,
+ struct wlr_text_input_v3 *text_input) {
+ struct roots_text_input *input = calloc(1, sizeof(struct roots_text_input));
+ if (!input) {
+ return NULL;
+ }
+ input->input = text_input;
+ input->relay = relay;
+
+ wl_signal_add(&text_input->events.enable, &relay->text_input_enable);
+ relay->text_input_enable.notify = handle_text_input_enable;
+
+ wl_signal_add(&text_input->events.commit, &relay->text_input_commit);
+ relay->text_input_commit.notify = handle_text_input_commit;
+
+ wl_signal_add(&text_input->events.disable, &relay->text_input_disable);
+ relay->text_input_disable.notify = handle_text_input_disable;
+
+ wl_signal_add(&text_input->events.destroy, &relay->text_input_destroy);
+ relay->text_input_destroy.notify = handle_text_input_destroy;
+
+ input->pending_focused_surface_destroy.notify =
+ handle_pending_focused_surface_destroy;
+ wl_list_init(&input->pending_focused_surface_destroy.link);
+ return input;
+}
+
+static void relay_handle_text_input(struct wl_listener *listener,
+ void *data) {
+ struct roots_input_method_relay *relay = wl_container_of(listener, relay,
+ text_input_new);
+ struct wlr_text_input_v3 *wlr_text_input = data;
+ if (relay->seat->seat != wlr_text_input->seat) {
+ return;
+ }
+
+ struct roots_text_input *text_input = roots_text_input_create(relay,
+ wlr_text_input);
+ if (!text_input) {
+ return;
+ }
+ wl_list_insert(&relay->text_inputs, &text_input->link);
+}
+
+static void relay_handle_input_method(struct wl_listener *listener,
+ void *data) {
+ struct roots_input_method_relay *relay = wl_container_of(listener, relay,
+ input_method_new);
+ struct wlr_input_method_v2 *input_method = data;
+ if (relay->seat->seat != input_method->seat) {
+ return;
+ }
+
+ if (relay->input_method != NULL) {
+ wlr_log(WLR_INFO, "Attempted to connect second input method to a seat");
+ wlr_input_method_v2_send_unavailable(input_method);
+ return;
+ }
+
+ relay->input_method = input_method;
+ wl_signal_add(&relay->input_method->events.commit,
+ &relay->input_method_commit);
+ relay->input_method_commit.notify = handle_im_commit;
+ wl_signal_add(&relay->input_method->events.destroy,
+ &relay->input_method_destroy);
+ relay->input_method_destroy.notify = handle_im_destroy;
+
+ struct roots_text_input *text_input = relay_get_focusable_text_input(relay);
+ if (text_input) {
+ wlr_text_input_v3_send_enter(text_input->input,
+ text_input->pending_focused_surface);
+ text_input_clear_pending_focused_surface(text_input);
+ }
+}
+
+void roots_input_method_relay_init(struct roots_seat *seat,
+ struct roots_input_method_relay *relay) {
+ relay->seat = seat;
+ wl_list_init(&relay->text_inputs);
+
+ relay->text_input_new.notify = relay_handle_text_input;
+ wl_signal_add(&seat->input->server->desktop->text_input->events.text_input,
+ &relay->text_input_new);
+
+ relay->input_method_new.notify = relay_handle_input_method;
+ wl_signal_add(
+ &seat->input->server->desktop->input_method->events.input_method,
+ &relay->input_method_new);
+}
+
+void roots_input_method_relay_set_focus(struct roots_input_method_relay *relay,
+ struct wlr_surface *surface) {
+ struct roots_text_input *text_input;
+ wl_list_for_each(text_input, &relay->text_inputs, link) {
+ if (text_input->pending_focused_surface) {
+ assert(text_input->input->focused_surface == NULL);
+ if (surface != text_input->pending_focused_surface) {
+ text_input_clear_pending_focused_surface(text_input);
+ }
+ } else if (text_input->input->focused_surface) {
+ assert(text_input->pending_focused_surface == NULL);
+ if (surface != text_input->input->focused_surface) {
+ relay_disable_text_input(relay, text_input);
+ wlr_text_input_v3_send_leave(text_input->input);
+ }
+ } else if (surface
+ && wl_resource_get_client(text_input->input->resource)
+ == wl_resource_get_client(surface->resource)) {
+ if (relay->input_method) {
+ wlr_text_input_v3_send_enter(text_input->input, surface);
+ } else {
+ text_input_set_pending_focused_surface(text_input, surface);
+ }
+ }
+ }
+}
diff --git a/rootston/virtual_keyboard.c b/rootston/virtual_keyboard.c
new file mode 100644
index 00000000..e862caf4
--- /dev/null
+++ b/rootston/virtual_keyboard.c
@@ -0,0 +1,21 @@
+#define _POSIX_C_SOURCE 199309L
+
+#include <wlr/util/log.h>
+#include <wlr/types/wlr_virtual_keyboard_v1.h>
+#include "rootston/virtual_keyboard.h"
+#include "rootston/seat.h"
+
+void handle_virtual_keyboard(struct wl_listener *listener, void *data) {
+ struct roots_desktop *desktop =
+ wl_container_of(listener, desktop, virtual_keyboard_new);
+ struct wlr_virtual_keyboard_v1 *keyboard = data;
+
+ struct roots_seat *seat = input_seat_from_wlr_seat(desktop->server->input,
+ keyboard->seat);
+ if (!seat) {
+ wlr_log(WLR_ERROR, "could not find roots seat");
+ return;
+ }
+
+ roots_seat_add_device(seat, &keyboard->input_device);
+}
diff --git a/rootston/wl_shell.c b/rootston/wl_shell.c
new file mode 100644
index 00000000..2bf4f4c2
--- /dev/null
+++ b/rootston/wl_shell.c
@@ -0,0 +1,295 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/types/wlr_wl_shell.h>
+#include <wlr/util/log.h>
+#include "rootston/desktop.h"
+#include "rootston/input.h"
+#include "rootston/server.h"
+
+static void popup_destroy(struct roots_view_child *child) {
+ assert(child->destroy == popup_destroy);
+ struct roots_wl_shell_popup *popup = (struct roots_wl_shell_popup *)child;
+ if (popup == NULL) {
+ return;
+ }
+ wl_list_remove(&popup->destroy.link);
+ wl_list_remove(&popup->set_state.link);
+ wl_list_remove(&popup->new_popup.link);
+ view_child_finish(&popup->view_child);
+ free(popup);
+}
+
+static void popup_handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_popup *popup =
+ wl_container_of(listener, popup, destroy);
+ popup_destroy((struct roots_view_child *)popup);
+}
+
+static void popup_handle_set_state(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_popup *popup =
+ wl_container_of(listener, popup, set_state);
+ popup_destroy((struct roots_view_child *)popup);
+}
+
+static struct roots_wl_shell_popup *popup_create(struct roots_view *view,
+ struct wlr_wl_shell_surface *wlr_wl_shell_surface);
+
+static void popup_handle_new_popup(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_popup *popup =
+ wl_container_of(listener, popup, new_popup);
+ struct wlr_wl_shell_surface *wlr_wl_shell_surface = data;
+ popup_create(popup->view_child.view, wlr_wl_shell_surface);
+}
+
+static struct roots_wl_shell_popup *popup_create(struct roots_view *view,
+ struct wlr_wl_shell_surface *wlr_wl_shell_surface) {
+ struct roots_wl_shell_popup *popup =
+ calloc(1, sizeof(struct roots_wl_shell_popup));
+ if (popup == NULL) {
+ return NULL;
+ }
+ popup->wlr_wl_shell_surface = wlr_wl_shell_surface;
+ popup->view_child.destroy = popup_destroy;
+ view_child_init(&popup->view_child, view, wlr_wl_shell_surface->surface);
+ popup->destroy.notify = popup_handle_destroy;
+ wl_signal_add(&wlr_wl_shell_surface->events.destroy, &popup->destroy);
+ popup->set_state.notify = popup_handle_set_state;
+ wl_signal_add(&wlr_wl_shell_surface->events.set_state, &popup->set_state);
+ popup->new_popup.notify = popup_handle_new_popup;
+ wl_signal_add(&wlr_wl_shell_surface->events.new_popup, &popup->new_popup);
+ return popup;
+}
+
+
+static void resize(struct roots_view *view, uint32_t width, uint32_t height) {
+ assert(view->type == ROOTS_WL_SHELL_VIEW);
+ struct wlr_wl_shell_surface *surf = view->wl_shell_surface;
+ wlr_wl_shell_surface_configure(surf, WL_SHELL_SURFACE_RESIZE_NONE, width,
+ height);
+}
+
+static void close(struct roots_view *view) {
+ assert(view->type == ROOTS_WL_SHELL_VIEW);
+ struct wlr_wl_shell_surface *surf = view->wl_shell_surface;
+ wl_client_destroy(surf->client);
+}
+
+static void destroy(struct roots_view *view) {
+ assert(view->type == ROOTS_WL_SHELL_VIEW);
+ struct roots_wl_shell_surface *roots_surface = view->roots_wl_shell_surface;
+ wl_list_remove(&roots_surface->destroy.link);
+ wl_list_remove(&roots_surface->request_move.link);
+ wl_list_remove(&roots_surface->request_resize.link);
+ wl_list_remove(&roots_surface->request_maximize.link);
+ wl_list_remove(&roots_surface->request_fullscreen.link);
+ wl_list_remove(&roots_surface->set_state.link);
+ wl_list_remove(&roots_surface->set_title.link);
+ wl_list_remove(&roots_surface->set_class.link);
+ wl_list_remove(&roots_surface->surface_commit.link);
+ free(roots_surface);
+}
+
+static void handle_request_move(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_move);
+ struct roots_view *view = roots_surface->view;
+ struct roots_input *input = view->desktop->server->input;
+ struct wlr_wl_shell_surface_move_event *e = data;
+ struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat);
+ if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ return;
+ }
+ roots_seat_begin_move(seat, view);
+}
+
+static void handle_request_resize(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_resize);
+ struct roots_view *view = roots_surface->view;
+ struct roots_input *input = view->desktop->server->input;
+ struct wlr_wl_shell_surface_resize_event *e = data;
+ struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat);
+ if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ return;
+ }
+ roots_seat_begin_resize(seat, view, e->edges);
+}
+
+static void handle_request_maximize(struct wl_listener *listener,
+ void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_maximize);
+ struct roots_view *view = roots_surface->view;
+ //struct wlr_wl_shell_surface_maximize_event *e = data;
+ view_maximize(view, true);
+}
+
+static void handle_request_fullscreen(struct wl_listener *listener,
+ void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_fullscreen);
+ struct roots_view *view = roots_surface->view;
+ struct wlr_wl_shell_surface_set_fullscreen_event *e = data;
+ view_set_fullscreen(view, true, e->output);
+}
+
+static void handle_set_state(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, set_state);
+ struct roots_view *view = roots_surface->view;
+ struct wlr_wl_shell_surface *surface = view->wl_shell_surface;
+ if (view->maximized &&
+ surface->state != WLR_WL_SHELL_SURFACE_STATE_MAXIMIZED) {
+ view_maximize(view, false);
+ }
+ if (view->fullscreen_output != NULL &&
+ surface->state != WLR_WL_SHELL_SURFACE_STATE_FULLSCREEN) {
+ view_set_fullscreen(view, false, NULL);
+ }
+}
+
+static void handle_set_title(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, set_state);
+ view_set_title(roots_surface->view,
+ roots_surface->view->wl_shell_surface->title);
+}
+
+static void handle_set_class(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, set_state);
+ view_set_app_id(roots_surface->view,
+ roots_surface->view->wl_shell_surface->class);
+}
+
+static void handle_surface_commit(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, surface_commit);
+ struct roots_view *view = roots_surface->view;
+ struct wlr_surface *wlr_surface = view->wlr_surface;
+
+ view_apply_damage(view);
+
+ int width = wlr_surface->current.width;
+ int height = wlr_surface->current.height;
+ view_update_size(view, width, height);
+
+ double x = view->box.x;
+ double y = view->box.y;
+ if (view->pending_move_resize.update_x) {
+ x = view->pending_move_resize.x + view->pending_move_resize.width -
+ width;
+ view->pending_move_resize.update_x = false;
+ }
+ if (view->pending_move_resize.update_y) {
+ y = view->pending_move_resize.y + view->pending_move_resize.height -
+ height;
+ view->pending_move_resize.update_y = false;
+ }
+ view_update_position(view, x, y);
+}
+
+static void handle_new_popup(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, new_popup);
+ struct wlr_wl_shell_surface *wlr_wl_shell_surface = data;
+ popup_create(roots_surface->view, wlr_wl_shell_surface);
+}
+
+static void handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, destroy);
+ view_destroy(roots_surface->view);
+}
+
+void handle_wl_shell_surface(struct wl_listener *listener, void *data) {
+ struct roots_desktop *desktop =
+ wl_container_of(listener, desktop, wl_shell_surface);
+ struct wlr_wl_shell_surface *surface = data;
+
+ if (surface->state == WLR_WL_SHELL_SURFACE_STATE_POPUP) {
+ wlr_log(WLR_DEBUG, "new wl shell popup");
+ return;
+ }
+
+ wlr_log(WLR_DEBUG, "new wl shell surface: title=%s, class=%s",
+ surface->title, surface->class);
+ wlr_wl_shell_surface_ping(surface);
+
+ struct roots_wl_shell_surface *roots_surface =
+ calloc(1, sizeof(struct roots_wl_shell_surface));
+ if (!roots_surface) {
+ return;
+ }
+ roots_surface->destroy.notify = handle_destroy;
+ wl_signal_add(&surface->events.destroy, &roots_surface->destroy);
+ roots_surface->new_popup.notify = handle_new_popup;
+ wl_signal_add(&surface->events.new_popup, &roots_surface->new_popup);
+ roots_surface->request_move.notify = handle_request_move;
+ wl_signal_add(&surface->events.request_move, &roots_surface->request_move);
+ roots_surface->request_resize.notify = handle_request_resize;
+ wl_signal_add(&surface->events.request_resize,
+ &roots_surface->request_resize);
+ roots_surface->request_maximize.notify = handle_request_maximize;
+ wl_signal_add(&surface->events.request_maximize,
+ &roots_surface->request_maximize);
+ roots_surface->request_fullscreen.notify =
+ handle_request_fullscreen;
+ wl_signal_add(&surface->events.request_fullscreen,
+ &roots_surface->request_fullscreen);
+
+ roots_surface->set_state.notify = handle_set_state;
+ wl_signal_add(&surface->events.set_state, &roots_surface->set_state);
+ roots_surface->set_title.notify = handle_set_title;
+ wl_signal_add(&surface->events.set_title, &roots_surface->set_title);
+ roots_surface->set_class.notify = handle_set_class;
+ wl_signal_add(&surface->events.set_class, &roots_surface->set_class);
+ roots_surface->surface_commit.notify = handle_surface_commit;
+ wl_signal_add(&surface->surface->events.commit, &roots_surface->surface_commit);
+
+ struct roots_view *view = view_create(desktop);
+ if (!view) {
+ free(roots_surface);
+ return;
+ }
+ view->type = ROOTS_WL_SHELL_VIEW;
+ view->box.width = surface->surface->current.width;
+ view->box.height = surface->surface->current.height;
+
+ view->wl_shell_surface = surface;
+ view->roots_wl_shell_surface = roots_surface;
+ view->resize = resize;
+ view->close = close;
+ view->destroy = destroy;
+ roots_surface->view = view;
+
+ view_map(view, surface->surface);
+ view_setup(view);
+
+ wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle,
+ view->wl_shell_surface->title ?: "none");
+ wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle,
+ view->wl_shell_surface->class ?: "none");
+
+ if (surface->state == WLR_WL_SHELL_SURFACE_STATE_TRANSIENT) {
+ // We need to map it relative to the parent
+ bool found = false;
+ struct roots_view *parent;
+ wl_list_for_each(parent, &desktop->views, link) {
+ if (parent->type == ROOTS_WL_SHELL_VIEW &&
+ parent->wl_shell_surface == surface->parent) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ view_move(view,
+ parent->box.x + surface->transient_state->x,
+ parent->box.y + surface->transient_state->y);
+ }
+ }
+}
diff --git a/rootston/xdg_shell.c b/rootston/xdg_shell.c
new file mode 100644
index 00000000..da8909ba
--- /dev/null
+++ b/rootston/xdg_shell.c
@@ -0,0 +1,565 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/types/wlr_xdg_shell.h>
+#include <wlr/util/log.h>
+#include "rootston/cursor.h"
+#include "rootston/desktop.h"
+#include "rootston/input.h"
+#include "rootston/server.h"
+
+static void popup_destroy(struct roots_view_child *child) {
+ assert(child->destroy == popup_destroy);
+ struct roots_xdg_popup *popup = (struct roots_xdg_popup *)child;
+ if (popup == NULL) {
+ return;
+ }
+ wl_list_remove(&popup->destroy.link);
+ wl_list_remove(&popup->new_popup.link);
+ wl_list_remove(&popup->map.link);
+ wl_list_remove(&popup->unmap.link);
+ view_child_finish(&popup->view_child);
+ free(popup);
+}
+
+static void popup_handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_xdg_popup *popup =
+ wl_container_of(listener, popup, destroy);
+ popup_destroy((struct roots_view_child *)popup);
+}
+
+static void popup_handle_map(struct wl_listener *listener, void *data) {
+ struct roots_xdg_popup *popup = wl_container_of(listener, popup, map);
+ view_damage_whole(popup->view_child.view);
+ input_update_cursor_focus(popup->view_child.view->desktop->server->input);
+}
+
+static void popup_handle_unmap(struct wl_listener *listener, void *data) {
+ struct roots_xdg_popup *popup = wl_container_of(listener, popup, unmap);
+ view_damage_whole(popup->view_child.view);
+}
+
+static struct roots_xdg_popup *popup_create(struct roots_view *view,
+ struct wlr_xdg_popup *wlr_popup);
+
+static void popup_handle_new_popup(struct wl_listener *listener, void *data) {
+ struct roots_xdg_popup *popup =
+ wl_container_of(listener, popup, new_popup);
+ struct wlr_xdg_popup *wlr_popup = data;
+ popup_create(popup->view_child.view, wlr_popup);
+}
+
+static void popup_unconstrain(struct roots_xdg_popup *popup) {
+ // get the output of the popup's positioner anchor point and convert it to
+ // the toplevel parent's coordinate system and then pass it to
+ // wlr_xdg_popup_v6_unconstrain_from_box
+
+ // TODO: unconstrain popups for rotated windows
+ if (popup->view_child.view->rotation != 0.0) {
+ return;
+ }
+
+ struct roots_view *view = popup->view_child.view;
+ struct wlr_output_layout *layout = view->desktop->layout;
+ struct wlr_xdg_popup *wlr_popup = popup->wlr_popup;
+
+ int anchor_lx, anchor_ly;
+ wlr_xdg_popup_get_anchor_point(wlr_popup, &anchor_lx, &anchor_ly);
+
+ int popup_lx, popup_ly;
+ wlr_xdg_popup_get_toplevel_coords(wlr_popup, wlr_popup->geometry.x,
+ wlr_popup->geometry.y, &popup_lx, &popup_ly);
+ popup_lx += view->box.x;
+ popup_ly += view->box.y;
+
+ anchor_lx += popup_lx;
+ anchor_ly += popup_ly;
+
+ double dest_x = 0, dest_y = 0;
+ wlr_output_layout_closest_point(layout, NULL, anchor_lx, anchor_ly,
+ &dest_x, &dest_y);
+
+ struct wlr_output *output =
+ wlr_output_layout_output_at(layout, dest_x, dest_y);
+
+ if (output == NULL) {
+ return;
+ }
+
+ int width = 0, height = 0;
+ wlr_output_effective_resolution(output, &width, &height);
+
+ // the output box expressed in the coordinate system of the toplevel parent
+ // of the popup
+ struct wlr_box output_toplevel_sx_box = {
+ .x = output->lx - view->box.x,
+ .y = output->ly - view->box.y,
+ .width = width,
+ .height = height
+ };
+
+ wlr_xdg_popup_unconstrain_from_box(
+ popup->wlr_popup, &output_toplevel_sx_box);
+}
+
+static struct roots_xdg_popup *popup_create(struct roots_view *view,
+ struct wlr_xdg_popup *wlr_popup) {
+ struct roots_xdg_popup *popup =
+ calloc(1, sizeof(struct roots_xdg_popup));
+ if (popup == NULL) {
+ return NULL;
+ }
+ popup->wlr_popup = wlr_popup;
+ popup->view_child.destroy = popup_destroy;
+ view_child_init(&popup->view_child, view, wlr_popup->base->surface);
+ popup->destroy.notify = popup_handle_destroy;
+ wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy);
+ popup->map.notify = popup_handle_map;
+ wl_signal_add(&wlr_popup->base->events.map, &popup->map);
+ popup->unmap.notify = popup_handle_unmap;
+ wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap);
+ popup->new_popup.notify = popup_handle_new_popup;
+ wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup);
+
+ popup_unconstrain(popup);
+
+ return popup;
+}
+
+
+static void get_size(const struct roots_view *view, struct wlr_box *box) {
+ assert(view->type == ROOTS_XDG_SHELL_VIEW);
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+
+ struct wlr_box geo_box;
+ wlr_xdg_surface_get_geometry(surface, &geo_box);
+ box->width = geo_box.width;
+ box->height = geo_box.height;
+}
+
+static void activate(struct roots_view *view, bool active) {
+ assert(view->type == ROOTS_XDG_SHELL_VIEW);
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+ if (surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
+ wlr_xdg_toplevel_set_activated(surface, active);
+ }
+}
+
+static void apply_size_constraints(struct wlr_xdg_surface *surface,
+ uint32_t width, uint32_t height, uint32_t *dest_width,
+ uint32_t *dest_height) {
+ *dest_width = width;
+ *dest_height = height;
+
+ struct wlr_xdg_toplevel_state *state = &surface->toplevel->current;
+ if (width < state->min_width) {
+ *dest_width = state->min_width;
+ } else if (state->max_width > 0 &&
+ width > state->max_width) {
+ *dest_width = state->max_width;
+ }
+ if (height < state->min_height) {
+ *dest_height = state->min_height;
+ } else if (state->max_height > 0 &&
+ height > state->max_height) {
+ *dest_height = state->max_height;
+ }
+}
+
+static void resize(struct roots_view *view, uint32_t width, uint32_t height) {
+ assert(view->type == ROOTS_XDG_SHELL_VIEW);
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+ if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ uint32_t constrained_width, constrained_height;
+ apply_size_constraints(surface, width, height, &constrained_width,
+ &constrained_height);
+
+ wlr_xdg_toplevel_set_size(surface, constrained_width,
+ constrained_height);
+}
+
+static void move_resize(struct roots_view *view, double x, double y,
+ uint32_t width, uint32_t height) {
+ assert(view->type == ROOTS_XDG_SHELL_VIEW);
+ struct roots_xdg_surface *roots_surface = view->roots_xdg_surface;
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+ if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ bool update_x = x != view->box.x;
+ bool update_y = y != view->box.y;
+
+ uint32_t constrained_width, constrained_height;
+ apply_size_constraints(surface, width, height, &constrained_width,
+ &constrained_height);
+
+ if (update_x) {
+ x = x + width - constrained_width;
+ }
+ if (update_y) {
+ y = y + height - constrained_height;
+ }
+
+ view->pending_move_resize.update_x = update_x;
+ view->pending_move_resize.update_y = update_y;
+ view->pending_move_resize.x = x;
+ view->pending_move_resize.y = y;
+ view->pending_move_resize.width = constrained_width;
+ view->pending_move_resize.height = constrained_height;
+
+ uint32_t serial = wlr_xdg_toplevel_set_size(surface, constrained_width,
+ constrained_height);
+ if (serial > 0) {
+ roots_surface->pending_move_resize_configure_serial = serial;
+ } else if (roots_surface->pending_move_resize_configure_serial == 0) {
+ view_update_position(view, x, y);
+ }
+}
+
+static void maximize(struct roots_view *view, bool maximized) {
+ assert(view->type == ROOTS_XDG_SHELL_VIEW);
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+ if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ wlr_xdg_toplevel_set_maximized(surface, maximized);
+}
+
+static void set_fullscreen(struct roots_view *view, bool fullscreen) {
+ assert(view->type == ROOTS_XDG_SHELL_VIEW);
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+ if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ wlr_xdg_toplevel_set_fullscreen(surface, fullscreen);
+}
+
+static void close(struct roots_view *view) {
+ assert(view->type == ROOTS_XDG_SHELL_VIEW);
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+ struct wlr_xdg_popup *popup = NULL;
+ wl_list_for_each(popup, &surface->popups, link) {
+ wlr_xdg_surface_send_close(popup->base);
+ }
+ wlr_xdg_surface_send_close(surface);
+}
+
+static void destroy(struct roots_view *view) {
+ assert(view->type == ROOTS_XDG_SHELL_VIEW);
+ struct roots_xdg_surface *roots_xdg_surface = view->roots_xdg_surface;
+ wl_list_remove(&roots_xdg_surface->surface_commit.link);
+ wl_list_remove(&roots_xdg_surface->destroy.link);
+ wl_list_remove(&roots_xdg_surface->new_popup.link);
+ wl_list_remove(&roots_xdg_surface->map.link);
+ wl_list_remove(&roots_xdg_surface->unmap.link);
+ wl_list_remove(&roots_xdg_surface->request_move.link);
+ wl_list_remove(&roots_xdg_surface->request_resize.link);
+ wl_list_remove(&roots_xdg_surface->request_maximize.link);
+ wl_list_remove(&roots_xdg_surface->request_fullscreen.link);
+ wl_list_remove(&roots_xdg_surface->set_title.link);
+ wl_list_remove(&roots_xdg_surface->set_app_id.link);
+ roots_xdg_surface->view->xdg_surface->data = NULL;
+ free(roots_xdg_surface);
+}
+
+static void handle_request_move(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, request_move);
+ struct roots_view *view = roots_xdg_surface->view;
+ struct roots_input *input = view->desktop->server->input;
+ struct wlr_xdg_toplevel_move_event *e = data;
+ struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat);
+ // TODO verify event serial
+ if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ return;
+ }
+ roots_seat_begin_move(seat, view);
+}
+
+static void handle_request_resize(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, request_resize);
+ struct roots_view *view = roots_xdg_surface->view;
+ struct roots_input *input = view->desktop->server->input;
+ struct wlr_xdg_toplevel_resize_event *e = data;
+ // TODO verify event serial
+ struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat);
+ assert(seat);
+ if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ return;
+ }
+ roots_seat_begin_resize(seat, view, e->edges);
+}
+
+static void handle_request_maximize(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, request_maximize);
+ struct roots_view *view = roots_xdg_surface->view;
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+
+ if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ view_maximize(view, surface->toplevel->client_pending.maximized);
+}
+
+static void handle_request_fullscreen(struct wl_listener *listener,
+ void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, request_fullscreen);
+ struct roots_view *view = roots_xdg_surface->view;
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+ struct wlr_xdg_toplevel_set_fullscreen_event *e = data;
+
+ if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ view_set_fullscreen(view, e->fullscreen, e->output);
+}
+
+static void handle_set_title(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, set_title);
+
+ view_set_title(roots_xdg_surface->view,
+ roots_xdg_surface->view->xdg_surface->toplevel->title);
+}
+
+static void handle_set_app_id(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, set_app_id);
+
+ view_set_app_id(roots_xdg_surface->view,
+ roots_xdg_surface->view->xdg_surface->toplevel->app_id);
+}
+
+static void handle_surface_commit(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_surface =
+ wl_container_of(listener, roots_surface, surface_commit);
+ struct roots_view *view = roots_surface->view;
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+
+ if (!surface->mapped) {
+ return;
+ }
+
+ view_apply_damage(view);
+
+ struct wlr_box size;
+ get_size(view, &size);
+ view_update_size(view, size.width, size.height);
+
+ uint32_t pending_serial =
+ roots_surface->pending_move_resize_configure_serial;
+ if (pending_serial > 0 && pending_serial >= surface->configure_serial) {
+ double x = view->box.x;
+ double y = view->box.y;
+ if (view->pending_move_resize.update_x) {
+ x = view->pending_move_resize.x + view->pending_move_resize.width -
+ size.width;
+ }
+ if (view->pending_move_resize.update_y) {
+ y = view->pending_move_resize.y + view->pending_move_resize.height -
+ size.height;
+ }
+ view_update_position(view, x, y);
+
+ if (pending_serial == surface->configure_serial) {
+ roots_surface->pending_move_resize_configure_serial = 0;
+ }
+ }
+}
+
+static void handle_new_popup(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, new_popup);
+ struct wlr_xdg_popup *wlr_popup = data;
+ popup_create(roots_xdg_surface->view, wlr_popup);
+}
+
+static void handle_map(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, map);
+ struct roots_view *view = roots_xdg_surface->view;
+
+ struct wlr_box box;
+ get_size(view, &box);
+ view->box.width = box.width;
+ view->box.height = box.height;
+
+ view_map(view, view->xdg_surface->surface);
+ view_setup(view);
+
+ wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle,
+ view->xdg_surface->toplevel->title ?: "none");
+ wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle,
+ view->xdg_surface->toplevel->app_id ?: "none");
+}
+
+static void handle_unmap(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, unmap);
+ view_unmap(roots_xdg_surface->view);
+}
+
+static void handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, destroy);
+ view_destroy(roots_xdg_surface->view);
+}
+
+void handle_xdg_shell_surface(struct wl_listener *listener, void *data) {
+ struct wlr_xdg_surface *surface = data;
+ assert(surface->role != WLR_XDG_SURFACE_ROLE_NONE);
+
+ if (surface->role == WLR_XDG_SURFACE_ROLE_POPUP) {
+ wlr_log(WLR_DEBUG, "new xdg popup");
+ return;
+ }
+
+ struct roots_desktop *desktop =
+ wl_container_of(listener, desktop, xdg_shell_surface);
+
+ wlr_log(WLR_DEBUG, "new xdg toplevel: title=%s, app_id=%s",
+ surface->toplevel->title, surface->toplevel->app_id);
+ wlr_xdg_surface_ping(surface);
+
+ struct roots_xdg_surface *roots_surface =
+ calloc(1, sizeof(struct roots_xdg_surface));
+ if (!roots_surface) {
+ return;
+ }
+ roots_surface->surface_commit.notify = handle_surface_commit;
+ wl_signal_add(&surface->surface->events.commit,
+ &roots_surface->surface_commit);
+ roots_surface->destroy.notify = handle_destroy;
+ wl_signal_add(&surface->events.destroy, &roots_surface->destroy);
+ roots_surface->map.notify = handle_map;
+ wl_signal_add(&surface->events.map, &roots_surface->map);
+ roots_surface->unmap.notify = handle_unmap;
+ wl_signal_add(&surface->events.unmap, &roots_surface->unmap);
+ roots_surface->request_move.notify = handle_request_move;
+ wl_signal_add(&surface->toplevel->events.request_move,
+ &roots_surface->request_move);
+ roots_surface->request_resize.notify = handle_request_resize;
+ wl_signal_add(&surface->toplevel->events.request_resize,
+ &roots_surface->request_resize);
+ roots_surface->request_maximize.notify = handle_request_maximize;
+ wl_signal_add(&surface->toplevel->events.request_maximize,
+ &roots_surface->request_maximize);
+ roots_surface->request_fullscreen.notify = handle_request_fullscreen;
+ wl_signal_add(&surface->toplevel->events.request_fullscreen,
+ &roots_surface->request_fullscreen);
+ roots_surface->set_title.notify = handle_set_title;
+ wl_signal_add(&surface->toplevel->events.set_title, &roots_surface->set_title);
+ roots_surface->set_app_id.notify = handle_set_app_id;
+ wl_signal_add(&surface->toplevel->events.set_app_id,
+ &roots_surface->set_app_id);
+ roots_surface->new_popup.notify = handle_new_popup;
+ wl_signal_add(&surface->events.new_popup, &roots_surface->new_popup);
+ surface->data = roots_surface;
+
+ struct roots_view *view = view_create(desktop);
+ if (!view) {
+ free(roots_surface);
+ return;
+ }
+ view->type = ROOTS_XDG_SHELL_VIEW;
+
+ view->xdg_surface = surface;
+ view->roots_xdg_surface = roots_surface;
+ view->activate = activate;
+ view->resize = resize;
+ view->move_resize = move_resize;
+ view->maximize = maximize;
+ view->set_fullscreen = set_fullscreen;
+ view->close = close;
+ view->destroy = destroy;
+ roots_surface->view = view;
+
+ if (surface->toplevel->client_pending.maximized) {
+ view_maximize(view, true);
+ }
+ if (surface->toplevel->client_pending.fullscreen) {
+ view_set_fullscreen(view, true, NULL);
+ }
+}
+
+
+
+static void decoration_handle_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_xdg_toplevel_decoration *decoration =
+ wl_container_of(listener, decoration, destroy);
+
+ decoration->surface->xdg_toplevel_decoration = NULL;
+ view_update_decorated(decoration->surface->view, false);
+ wl_list_remove(&decoration->destroy.link);
+ wl_list_remove(&decoration->request_mode.link);
+ wl_list_remove(&decoration->surface_commit.link);
+ free(decoration);
+}
+
+static void decoration_handle_request_mode(struct wl_listener *listener,
+ void *data) {
+ struct roots_xdg_toplevel_decoration *decoration =
+ wl_container_of(listener, decoration, request_mode);
+
+ enum wlr_xdg_toplevel_decoration_v1_mode mode =
+ decoration->wlr_decoration->client_pending_mode;
+ if (mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE) {
+ mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
+ }
+ wlr_xdg_toplevel_decoration_v1_set_mode(decoration->wlr_decoration, mode);
+}
+
+static void decoration_handle_surface_commit(struct wl_listener *listener,
+ void *data) {
+ struct roots_xdg_toplevel_decoration *decoration =
+ wl_container_of(listener, decoration, surface_commit);
+
+ bool decorated = decoration->wlr_decoration->current_mode ==
+ WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
+ view_update_decorated(decoration->surface->view, decorated);
+}
+
+void handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data) {
+ struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration = data;
+
+ wlr_log(WLR_DEBUG, "new xdg toplevel decoration");
+
+ struct roots_xdg_surface *xdg_surface = wlr_decoration->surface->data;
+ assert(xdg_surface != NULL);
+ struct wlr_xdg_surface *wlr_xdg_surface = xdg_surface->view->xdg_surface;
+
+ struct roots_xdg_toplevel_decoration *decoration =
+ calloc(1, sizeof(struct roots_xdg_toplevel_decoration));
+ if (decoration == NULL) {
+ return;
+ }
+ decoration->wlr_decoration = wlr_decoration;
+ decoration->surface = xdg_surface;
+ xdg_surface->xdg_toplevel_decoration = decoration;
+
+ decoration->destroy.notify = decoration_handle_destroy;
+ wl_signal_add(&wlr_decoration->events.destroy, &decoration->destroy);
+ decoration->request_mode.notify = decoration_handle_request_mode;
+ wl_signal_add(&wlr_decoration->events.request_mode,
+ &decoration->request_mode);
+ decoration->surface_commit.notify = decoration_handle_surface_commit;
+ wl_signal_add(&wlr_xdg_surface->surface->events.commit,
+ &decoration->surface_commit);
+
+ decoration_handle_request_mode(&decoration->request_mode, wlr_decoration);
+}
diff --git a/rootston/xdg_shell_v6.c b/rootston/xdg_shell_v6.c
new file mode 100644
index 00000000..8d989aef
--- /dev/null
+++ b/rootston/xdg_shell_v6.c
@@ -0,0 +1,495 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/types/wlr_xdg_shell_v6.h>
+#include <wlr/util/log.h>
+#include "rootston/desktop.h"
+#include "rootston/input.h"
+#include "rootston/server.h"
+
+static void popup_destroy(struct roots_view_child *child) {
+ assert(child->destroy == popup_destroy);
+ struct roots_xdg_popup_v6 *popup = (struct roots_xdg_popup_v6 *)child;
+ if (popup == NULL) {
+ return;
+ }
+ wl_list_remove(&popup->destroy.link);
+ wl_list_remove(&popup->new_popup.link);
+ wl_list_remove(&popup->map.link);
+ wl_list_remove(&popup->unmap.link);
+ view_child_finish(&popup->view_child);
+ free(popup);
+}
+
+static void popup_handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_xdg_popup_v6 *popup =
+ wl_container_of(listener, popup, destroy);
+ popup_destroy((struct roots_view_child *)popup);
+}
+
+static void popup_handle_map(struct wl_listener *listener, void *data) {
+ struct roots_xdg_popup_v6 *popup =
+ wl_container_of(listener, popup, map);
+ view_damage_whole(popup->view_child.view);
+ input_update_cursor_focus(popup->view_child.view->desktop->server->input);
+}
+
+static void popup_handle_unmap(struct wl_listener *listener, void *data) {
+ struct roots_xdg_popup_v6 *popup =
+ wl_container_of(listener, popup, unmap);
+ view_damage_whole(popup->view_child.view);
+}
+
+static struct roots_xdg_popup_v6 *popup_create(struct roots_view *view,
+ struct wlr_xdg_popup_v6 *wlr_popup);
+
+static void popup_handle_new_popup(struct wl_listener *listener, void *data) {
+ struct roots_xdg_popup_v6 *popup =
+ wl_container_of(listener, popup, new_popup);
+ struct wlr_xdg_popup_v6 *wlr_popup = data;
+ popup_create(popup->view_child.view, wlr_popup);
+}
+
+static void popup_unconstrain(struct roots_xdg_popup_v6 *popup) {
+ // get the output of the popup's positioner anchor point and convert it to
+ // the toplevel parent's coordinate system and then pass it to
+ // wlr_xdg_popup_v6_unconstrain_from_box
+
+ // TODO: unconstrain popups for rotated windows
+ if (popup->view_child.view->rotation != 0.0) {
+ return;
+ }
+
+ struct roots_view *view = popup->view_child.view;
+ struct wlr_output_layout *layout = view->desktop->layout;
+ struct wlr_xdg_popup_v6 *wlr_popup = popup->wlr_popup;
+
+ int anchor_lx, anchor_ly;
+ wlr_xdg_popup_v6_get_anchor_point(wlr_popup, &anchor_lx, &anchor_ly);
+
+ int popup_lx, popup_ly;
+ wlr_xdg_popup_v6_get_toplevel_coords(wlr_popup, wlr_popup->geometry.x,
+ wlr_popup->geometry.y, &popup_lx, &popup_ly);
+ popup_lx += view->box.x;
+ popup_ly += view->box.y;
+
+ anchor_lx += popup_lx;
+ anchor_ly += popup_ly;
+
+ double dest_x = 0, dest_y = 0;
+ wlr_output_layout_closest_point(layout, NULL, anchor_lx, anchor_ly,
+ &dest_x, &dest_y);
+
+ struct wlr_output *output =
+ wlr_output_layout_output_at(layout, dest_x, dest_y);
+
+ if (output == NULL) {
+ return;
+ }
+
+ int width = 0, height = 0;
+ wlr_output_effective_resolution(output, &width, &height);
+
+ // the output box expressed in the coordinate system of the toplevel parent
+ // of the popup
+ struct wlr_box output_toplevel_sx_box = {
+ .x = output->lx - view->box.x,
+ .y = output->ly - view->box.y,
+ .width = width,
+ .height = height
+ };
+
+ wlr_xdg_popup_v6_unconstrain_from_box(popup->wlr_popup, &output_toplevel_sx_box);
+}
+
+static struct roots_xdg_popup_v6 *popup_create(struct roots_view *view,
+ struct wlr_xdg_popup_v6 *wlr_popup) {
+ struct roots_xdg_popup_v6 *popup =
+ calloc(1, sizeof(struct roots_xdg_popup_v6));
+ if (popup == NULL) {
+ return NULL;
+ }
+ popup->wlr_popup = wlr_popup;
+ popup->view_child.destroy = popup_destroy;
+ view_child_init(&popup->view_child, view, wlr_popup->base->surface);
+ popup->destroy.notify = popup_handle_destroy;
+ wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy);
+ popup->map.notify = popup_handle_map;
+ wl_signal_add(&wlr_popup->base->events.map, &popup->map);
+ popup->unmap.notify = popup_handle_unmap;
+ wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap);
+ popup->new_popup.notify = popup_handle_new_popup;
+ wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup);
+
+ popup_unconstrain(popup);
+
+ return popup;
+}
+
+
+static void get_size(const struct roots_view *view, struct wlr_box *box) {
+ assert(view->type == ROOTS_XDG_SHELL_V6_VIEW);
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+
+ struct wlr_box geo_box;
+ wlr_xdg_surface_v6_get_geometry(surface, &geo_box);
+ box->width = geo_box.width;
+ box->height = geo_box.height;
+}
+
+static void activate(struct roots_view *view, bool active) {
+ assert(view->type == ROOTS_XDG_SHELL_V6_VIEW);
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+ if (surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) {
+ wlr_xdg_toplevel_v6_set_activated(surface, active);
+ }
+}
+
+static void apply_size_constraints(struct wlr_xdg_surface_v6 *surface,
+ uint32_t width, uint32_t height, uint32_t *dest_width,
+ uint32_t *dest_height) {
+ *dest_width = width;
+ *dest_height = height;
+
+ struct wlr_xdg_toplevel_v6_state *state = &surface->toplevel->current;
+ if (width < state->min_width) {
+ *dest_width = state->min_width;
+ } else if (state->max_width > 0 &&
+ width > state->max_width) {
+ *dest_width = state->max_width;
+ }
+ if (height < state->min_height) {
+ *dest_height = state->min_height;
+ } else if (state->max_height > 0 &&
+ height > state->max_height) {
+ *dest_height = state->max_height;
+ }
+}
+
+static void resize(struct roots_view *view, uint32_t width, uint32_t height) {
+ assert(view->type == ROOTS_XDG_SHELL_V6_VIEW);
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+ if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ uint32_t constrained_width, constrained_height;
+ apply_size_constraints(surface, width, height, &constrained_width,
+ &constrained_height);
+
+ wlr_xdg_toplevel_v6_set_size(surface, constrained_width,
+ constrained_height);
+}
+
+static void move_resize(struct roots_view *view, double x, double y,
+ uint32_t width, uint32_t height) {
+ assert(view->type == ROOTS_XDG_SHELL_V6_VIEW);
+ struct roots_xdg_surface_v6 *roots_surface = view->roots_xdg_surface_v6;
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+ if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ bool update_x = x != view->box.x;
+ bool update_y = y != view->box.y;
+
+ uint32_t constrained_width, constrained_height;
+ apply_size_constraints(surface, width, height, &constrained_width,
+ &constrained_height);
+
+ if (update_x) {
+ x = x + width - constrained_width;
+ }
+ if (update_y) {
+ y = y + height - constrained_height;
+ }
+
+ view->pending_move_resize.update_x = update_x;
+ view->pending_move_resize.update_y = update_y;
+ view->pending_move_resize.x = x;
+ view->pending_move_resize.y = y;
+ view->pending_move_resize.width = constrained_width;
+ view->pending_move_resize.height = constrained_height;
+
+ uint32_t serial = wlr_xdg_toplevel_v6_set_size(surface, constrained_width,
+ constrained_height);
+ if (serial > 0) {
+ roots_surface->pending_move_resize_configure_serial = serial;
+ } else if (roots_surface->pending_move_resize_configure_serial == 0) {
+ view_update_position(view, x, y);
+ }
+}
+
+static void maximize(struct roots_view *view, bool maximized) {
+ assert(view->type == ROOTS_XDG_SHELL_V6_VIEW);
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+ if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ wlr_xdg_toplevel_v6_set_maximized(surface, maximized);
+}
+
+static void set_fullscreen(struct roots_view *view, bool fullscreen) {
+ assert(view->type == ROOTS_XDG_SHELL_V6_VIEW);
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+ if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ wlr_xdg_toplevel_v6_set_fullscreen(surface, fullscreen);
+}
+
+static void close(struct roots_view *view) {
+ assert(view->type == ROOTS_XDG_SHELL_V6_VIEW);
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+ struct wlr_xdg_popup_v6 *popup = NULL;
+ wl_list_for_each(popup, &surface->popups, link) {
+ wlr_xdg_surface_v6_send_close(popup->base);
+ }
+ wlr_xdg_surface_v6_send_close(surface);
+}
+
+static void destroy(struct roots_view *view) {
+ assert(view->type == ROOTS_XDG_SHELL_V6_VIEW);
+ struct roots_xdg_surface_v6 *roots_xdg_surface = view->roots_xdg_surface_v6;
+ wl_list_remove(&roots_xdg_surface->surface_commit.link);
+ wl_list_remove(&roots_xdg_surface->destroy.link);
+ wl_list_remove(&roots_xdg_surface->new_popup.link);
+ wl_list_remove(&roots_xdg_surface->map.link);
+ wl_list_remove(&roots_xdg_surface->unmap.link);
+ wl_list_remove(&roots_xdg_surface->request_move.link);
+ wl_list_remove(&roots_xdg_surface->request_resize.link);
+ wl_list_remove(&roots_xdg_surface->request_maximize.link);
+ wl_list_remove(&roots_xdg_surface->request_fullscreen.link);
+ wl_list_remove(&roots_xdg_surface->set_title.link);
+ wl_list_remove(&roots_xdg_surface->set_app_id.link);
+ free(roots_xdg_surface);
+}
+
+static void handle_request_move(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, request_move);
+ struct roots_view *view = roots_xdg_surface->view;
+ struct roots_input *input = view->desktop->server->input;
+ struct wlr_xdg_toplevel_v6_move_event *e = data;
+ struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat);
+ // TODO verify event serial
+ if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ return;
+ }
+ roots_seat_begin_move(seat, view);
+}
+
+static void handle_request_resize(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, request_resize);
+ struct roots_view *view = roots_xdg_surface->view;
+ struct roots_input *input = view->desktop->server->input;
+ struct wlr_xdg_toplevel_v6_resize_event *e = data;
+ // TODO verify event serial
+ struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat);
+ assert(seat);
+ if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ return;
+ }
+ roots_seat_begin_resize(seat, view, e->edges);
+}
+
+static void handle_request_maximize(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, request_maximize);
+ struct roots_view *view = roots_xdg_surface->view;
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+
+ if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ view_maximize(view, surface->toplevel->client_pending.maximized);
+}
+
+static void handle_request_fullscreen(struct wl_listener *listener,
+ void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, request_fullscreen);
+ struct roots_view *view = roots_xdg_surface->view;
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+ struct wlr_xdg_toplevel_v6_set_fullscreen_event *e = data;
+
+ if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ view_set_fullscreen(view, e->fullscreen, e->output);
+}
+
+static void handle_set_title(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, set_title);
+
+ view_set_title(roots_xdg_surface->view,
+ roots_xdg_surface->view->xdg_surface_v6->toplevel->title);
+}
+
+static void handle_set_app_id(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, set_app_id);
+
+ view_set_app_id(roots_xdg_surface->view,
+ roots_xdg_surface->view->xdg_surface_v6->toplevel->app_id);
+}
+
+static void handle_surface_commit(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_surface =
+ wl_container_of(listener, roots_surface, surface_commit);
+ struct roots_view *view = roots_surface->view;
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+
+ if (!surface->mapped) {
+ return;
+ }
+
+ view_apply_damage(view);
+
+ struct wlr_box size;
+ get_size(view, &size);
+ view_update_size(view, size.width, size.height);
+
+ uint32_t pending_serial =
+ roots_surface->pending_move_resize_configure_serial;
+ if (pending_serial > 0 && pending_serial >= surface->configure_serial) {
+ double x = view->box.x;
+ double y = view->box.y;
+ if (view->pending_move_resize.update_x) {
+ x = view->pending_move_resize.x + view->pending_move_resize.width -
+ size.width;
+ }
+ if (view->pending_move_resize.update_y) {
+ y = view->pending_move_resize.y + view->pending_move_resize.height -
+ size.height;
+ }
+ view_update_position(view, x, y);
+
+ if (pending_serial == surface->configure_serial) {
+ roots_surface->pending_move_resize_configure_serial = 0;
+ }
+ }
+}
+
+static void handle_new_popup(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, new_popup);
+ struct wlr_xdg_popup_v6 *wlr_popup = data;
+ popup_create(roots_xdg_surface->view, wlr_popup);
+}
+
+static void handle_map(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, map);
+ struct roots_view *view = roots_xdg_surface->view;
+
+ struct wlr_box box;
+ get_size(view, &box);
+ view->box.width = box.width;
+ view->box.height = box.height;
+
+ view_map(view, view->xdg_surface_v6->surface);
+ view_setup(view);
+
+ wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle,
+ view->xdg_surface_v6->toplevel->title ?: "none");
+ wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle,
+ view->xdg_surface_v6->toplevel->app_id ?: "none");
+}
+
+static void handle_unmap(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, unmap);
+ view_unmap(roots_xdg_surface->view);
+}
+
+static void handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, destroy);
+ view_destroy(roots_xdg_surface->view);
+}
+
+void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data) {
+ struct wlr_xdg_surface_v6 *surface = data;
+ assert(surface->role != WLR_XDG_SURFACE_V6_ROLE_NONE);
+
+ if (surface->role == WLR_XDG_SURFACE_V6_ROLE_POPUP) {
+ wlr_log(WLR_DEBUG, "new xdg popup");
+ return;
+ }
+
+ struct roots_desktop *desktop =
+ wl_container_of(listener, desktop, xdg_shell_v6_surface);
+
+ wlr_log(WLR_DEBUG, "new xdg toplevel: title=%s, app_id=%s",
+ surface->toplevel->title, surface->toplevel->app_id);
+ wlr_xdg_surface_v6_ping(surface);
+
+ struct roots_xdg_surface_v6 *roots_surface =
+ calloc(1, sizeof(struct roots_xdg_surface_v6));
+ if (!roots_surface) {
+ return;
+ }
+ roots_surface->surface_commit.notify = handle_surface_commit;
+ wl_signal_add(&surface->surface->events.commit,
+ &roots_surface->surface_commit);
+ roots_surface->destroy.notify = handle_destroy;
+ wl_signal_add(&surface->events.destroy, &roots_surface->destroy);
+ roots_surface->map.notify = handle_map;
+ wl_signal_add(&surface->events.map, &roots_surface->map);
+ roots_surface->unmap.notify = handle_unmap;
+ wl_signal_add(&surface->events.unmap, &roots_surface->unmap);
+ roots_surface->request_move.notify = handle_request_move;
+ wl_signal_add(&surface->toplevel->events.request_move,
+ &roots_surface->request_move);
+ roots_surface->request_resize.notify = handle_request_resize;
+ wl_signal_add(&surface->toplevel->events.request_resize,
+ &roots_surface->request_resize);
+ roots_surface->request_maximize.notify = handle_request_maximize;
+ wl_signal_add(&surface->toplevel->events.request_maximize,
+ &roots_surface->request_maximize);
+ roots_surface->request_fullscreen.notify = handle_request_fullscreen;
+ wl_signal_add(&surface->toplevel->events.request_fullscreen,
+ &roots_surface->request_fullscreen);
+ roots_surface->set_title.notify = handle_set_title;
+ wl_signal_add(&surface->toplevel->events.set_title, &roots_surface->set_title);
+ roots_surface->set_app_id.notify = handle_set_app_id;
+ wl_signal_add(&surface->toplevel->events.set_app_id,
+ &roots_surface->set_app_id);
+ roots_surface->new_popup.notify = handle_new_popup;
+ wl_signal_add(&surface->events.new_popup, &roots_surface->new_popup);
+
+ struct roots_view *view = view_create(desktop);
+ if (!view) {
+ free(roots_surface);
+ return;
+ }
+ view->type = ROOTS_XDG_SHELL_V6_VIEW;
+
+ view->xdg_surface_v6 = surface;
+ view->roots_xdg_surface_v6 = roots_surface;
+ view->activate = activate;
+ view->resize = resize;
+ view->move_resize = move_resize;
+ view->maximize = maximize;
+ view->set_fullscreen = set_fullscreen;
+ view->close = close;
+ view->destroy = destroy;
+ roots_surface->view = view;
+
+ if (surface->toplevel->client_pending.maximized) {
+ view_maximize(view, true);
+ }
+ if (surface->toplevel->client_pending.fullscreen) {
+ view_set_fullscreen(view, true, NULL);
+ }
+}
diff --git a/rootston/xwayland.c b/rootston/xwayland.c
new file mode 100644
index 00000000..f3f962e8
--- /dev/null
+++ b/rootston/xwayland.c
@@ -0,0 +1,351 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/util/log.h>
+#include <wlr/xwayland.h>
+#include "rootston/server.h"
+#include "rootston/server.h"
+
+static void activate(struct roots_view *view, bool active) {
+ assert(view->type == ROOTS_XWAYLAND_VIEW);
+ wlr_xwayland_surface_activate(view->xwayland_surface, active);
+}
+
+static void move(struct roots_view *view, double x, double y) {
+ assert(view->type == ROOTS_XWAYLAND_VIEW);
+ struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface;
+ view_update_position(view, x, y);
+ wlr_xwayland_surface_configure(xwayland_surface, x, y,
+ xwayland_surface->width, xwayland_surface->height);
+}
+
+static void apply_size_constraints(
+ struct wlr_xwayland_surface *xwayland_surface, uint32_t width,
+ uint32_t height, uint32_t *dest_width, uint32_t *dest_height) {
+ *dest_width = width;
+ *dest_height = height;
+
+ struct wlr_xwayland_surface_size_hints *size_hints =
+ xwayland_surface->size_hints;
+ if (size_hints != NULL) {
+ if (width < (uint32_t)size_hints->min_width) {
+ *dest_width = size_hints->min_width;
+ } else if (size_hints->max_width > 0 &&
+ width > (uint32_t)size_hints->max_width) {
+ *dest_width = size_hints->max_width;
+ }
+ if (height < (uint32_t)size_hints->min_height) {
+ *dest_height = size_hints->min_height;
+ } else if (size_hints->max_height > 0 &&
+ height > (uint32_t)size_hints->max_height) {
+ *dest_height = size_hints->max_height;
+ }
+ }
+}
+
+static void resize(struct roots_view *view, uint32_t width, uint32_t height) {
+ assert(view->type == ROOTS_XWAYLAND_VIEW);
+ struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface;
+
+ uint32_t constrained_width, constrained_height;
+ apply_size_constraints(xwayland_surface, width, height, &constrained_width,
+ &constrained_height);
+
+ wlr_xwayland_surface_configure(xwayland_surface, xwayland_surface->x,
+ xwayland_surface->y, constrained_width, constrained_height);
+}
+
+static void move_resize(struct roots_view *view, double x, double y,
+ uint32_t width, uint32_t height) {
+ assert(view->type == ROOTS_XWAYLAND_VIEW);
+ struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface;
+
+ bool update_x = x != view->box.x;
+ bool update_y = y != view->box.y;
+
+ uint32_t constrained_width, constrained_height;
+ apply_size_constraints(xwayland_surface, width, height, &constrained_width,
+ &constrained_height);
+
+ if (update_x) {
+ x = x + width - constrained_width;
+ }
+ if (update_y) {
+ y = y + height - constrained_height;
+ }
+
+ view->pending_move_resize.update_x = update_x;
+ view->pending_move_resize.update_y = update_y;
+ view->pending_move_resize.x = x;
+ view->pending_move_resize.y = y;
+ view->pending_move_resize.width = constrained_width;
+ view->pending_move_resize.height = constrained_height;
+
+ wlr_xwayland_surface_configure(xwayland_surface, x, y, constrained_width,
+ constrained_height);
+}
+
+static void close(struct roots_view *view) {
+ assert(view->type == ROOTS_XWAYLAND_VIEW);
+ wlr_xwayland_surface_close(view->xwayland_surface);
+}
+
+static void maximize(struct roots_view *view, bool maximized) {
+ assert(view->type == ROOTS_XWAYLAND_VIEW);
+
+ wlr_xwayland_surface_set_maximized(view->xwayland_surface, maximized);
+}
+
+static void set_fullscreen(struct roots_view *view, bool fullscreen) {
+ assert(view->type == ROOTS_XWAYLAND_VIEW);
+
+ wlr_xwayland_surface_set_fullscreen(view->xwayland_surface, fullscreen);
+}
+
+static void destroy(struct roots_view *view) {
+ assert(view->type == ROOTS_XWAYLAND_VIEW);
+ struct roots_xwayland_surface *roots_surface = view->roots_xwayland_surface;
+ wl_list_remove(&roots_surface->destroy.link);
+ wl_list_remove(&roots_surface->request_configure.link);
+ wl_list_remove(&roots_surface->request_move.link);
+ wl_list_remove(&roots_surface->request_resize.link);
+ wl_list_remove(&roots_surface->request_maximize.link);
+ wl_list_remove(&roots_surface->set_title.link);
+ wl_list_remove(&roots_surface->set_class.link);
+ wl_list_remove(&roots_surface->map.link);
+ wl_list_remove(&roots_surface->unmap.link);
+ free(roots_surface);
+}
+
+static void handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, destroy);
+ view_destroy(roots_surface->view);
+}
+
+static void handle_request_configure(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_configure);
+ struct wlr_xwayland_surface *xwayland_surface =
+ roots_surface->view->xwayland_surface;
+ struct wlr_xwayland_surface_configure_event *event = data;
+
+ view_update_position(roots_surface->view, event->x, event->y);
+
+ wlr_xwayland_surface_configure(xwayland_surface, event->x, event->y,
+ event->width, event->height);
+}
+
+static struct roots_seat *guess_seat_for_view(struct roots_view *view) {
+ // the best we can do is to pick the first seat that has the surface focused
+ // for the pointer
+ struct roots_input *input = view->desktop->server->input;
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &input->seats, link) {
+ if (seat->seat->pointer_state.focused_surface == view->wlr_surface) {
+ return seat;
+ }
+ }
+ return NULL;
+}
+
+static void handle_request_move(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_move);
+ struct roots_view *view = roots_surface->view;
+ struct roots_seat *seat = guess_seat_for_view(view);
+
+ if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ return;
+ }
+
+ roots_seat_begin_move(seat, view);
+}
+
+static void handle_request_resize(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_resize);
+ struct roots_view *view = roots_surface->view;
+ struct roots_seat *seat = guess_seat_for_view(view);
+ struct wlr_xwayland_resize_event *e = data;
+
+ if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ return;
+ }
+ roots_seat_begin_resize(seat, view, e->edges);
+}
+
+static void handle_request_maximize(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_maximize);
+ struct roots_view *view = roots_surface->view;
+ struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface;
+
+ bool maximized = xwayland_surface->maximized_vert &&
+ xwayland_surface->maximized_horz;
+ view_maximize(view, maximized);
+}
+
+static void handle_request_fullscreen(struct wl_listener *listener,
+ void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_fullscreen);
+ struct roots_view *view = roots_surface->view;
+ struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface;
+
+ view_set_fullscreen(view, xwayland_surface->fullscreen, NULL);
+}
+
+static void handle_set_title(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, set_title);
+
+ view_set_title(roots_surface->view,
+ roots_surface->view->xwayland_surface->title);
+}
+
+static void handle_set_class(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, set_class);
+
+ view_set_app_id(roots_surface->view,
+ roots_surface->view->xwayland_surface->class);
+}
+
+static void handle_surface_commit(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, surface_commit);
+ struct roots_view *view = roots_surface->view;
+ struct wlr_surface *wlr_surface = view->wlr_surface;
+
+ view_apply_damage(view);
+
+ int width = wlr_surface->current.width;
+ int height = wlr_surface->current.height;
+ view_update_size(view, width, height);
+
+ double x = view->box.x;
+ double y = view->box.y;
+ if (view->pending_move_resize.update_x) {
+ x = view->pending_move_resize.x + view->pending_move_resize.width -
+ width;
+ view->pending_move_resize.update_x = false;
+ }
+ if (view->pending_move_resize.update_y) {
+ y = view->pending_move_resize.y + view->pending_move_resize.height -
+ height;
+ view->pending_move_resize.update_y = false;
+ }
+ view_update_position(view, x, y);
+}
+
+static void handle_map(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, map);
+ struct wlr_xwayland_surface *surface = data;
+ struct roots_view *view = roots_surface->view;
+
+ view->box.x = surface->x;
+ view->box.y = surface->y;
+ view->box.width = surface->surface->current.width;
+ view->box.height = surface->surface->current.height;
+
+ roots_surface->surface_commit.notify = handle_surface_commit;
+ wl_signal_add(&surface->surface->events.commit,
+ &roots_surface->surface_commit);
+
+ view_map(view, surface->surface);
+
+ if (!surface->override_redirect) {
+ if (surface->decorations == WLR_XWAYLAND_SURFACE_DECORATIONS_ALL) {
+ view->decorated = true;
+ view->border_width = 4;
+ view->titlebar_height = 12;
+ }
+
+ view_setup(view);
+
+ wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle,
+ view->xwayland_surface->title ?: "none");
+ wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle,
+ view->xwayland_surface->class ?: "none");
+ } else {
+ view_initial_focus(view);
+ }
+}
+
+static void handle_unmap(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, unmap);
+ struct roots_view *view = roots_surface->view;
+
+ wl_list_remove(&roots_surface->surface_commit.link);
+ view_unmap(view);
+}
+
+void handle_xwayland_surface(struct wl_listener *listener, void *data) {
+ struct roots_desktop *desktop =
+ wl_container_of(listener, desktop, xwayland_surface);
+
+ struct wlr_xwayland_surface *surface = data;
+ wlr_log(WLR_DEBUG, "new xwayland surface: title=%s, class=%s, instance=%s",
+ surface->title, surface->class, surface->instance);
+ wlr_xwayland_surface_ping(surface);
+
+ struct roots_xwayland_surface *roots_surface =
+ calloc(1, sizeof(struct roots_xwayland_surface));
+ if (roots_surface == NULL) {
+ return;
+ }
+
+ roots_surface->destroy.notify = handle_destroy;
+ wl_signal_add(&surface->events.destroy, &roots_surface->destroy);
+ roots_surface->request_configure.notify = handle_request_configure;
+ wl_signal_add(&surface->events.request_configure,
+ &roots_surface->request_configure);
+ roots_surface->map.notify = handle_map;
+ wl_signal_add(&surface->events.map, &roots_surface->map);
+ roots_surface->unmap.notify = handle_unmap;
+ wl_signal_add(&surface->events.unmap, &roots_surface->unmap);
+ roots_surface->request_move.notify = handle_request_move;
+ wl_signal_add(&surface->events.request_move, &roots_surface->request_move);
+ roots_surface->request_resize.notify = handle_request_resize;
+ wl_signal_add(&surface->events.request_resize,
+ &roots_surface->request_resize);
+ roots_surface->request_maximize.notify = handle_request_maximize;
+ wl_signal_add(&surface->events.request_maximize,
+ &roots_surface->request_maximize);
+ roots_surface->request_fullscreen.notify = handle_request_fullscreen;
+ wl_signal_add(&surface->events.request_fullscreen,
+ &roots_surface->request_fullscreen);
+ roots_surface->set_title.notify = handle_set_title;
+ wl_signal_add(&surface->events.set_title, &roots_surface->set_title);
+ roots_surface->set_class.notify = handle_set_class;
+ wl_signal_add(&surface->events.set_class,
+ &roots_surface->set_class);
+
+ struct roots_view *view = view_create(desktop);
+ if (view == NULL) {
+ free(roots_surface);
+ return;
+ }
+ view->type = ROOTS_XWAYLAND_VIEW;
+ view->box.x = surface->x;
+ view->box.y = surface->y;
+
+ view->xwayland_surface = surface;
+ view->roots_xwayland_surface = roots_surface;
+ view->activate = activate;
+ view->resize = resize;
+ view->move = move;
+ view->move_resize = move_resize;
+ view->maximize = maximize;
+ view->set_fullscreen = set_fullscreen;
+ view->close = close;
+ view->destroy = destroy;
+ roots_surface->view = view;
+}