aboutsummaryrefslogtreecommitdiff
path: root/sway/input
diff options
context:
space:
mode:
Diffstat (limited to 'sway/input')
-rw-r--r--sway/input/cursor.c384
-rw-r--r--sway/input/input-manager.c445
-rw-r--r--sway/input/keyboard.c504
-rw-r--r--sway/input/seat.c675
4 files changed, 2008 insertions, 0 deletions
diff --git a/sway/input/cursor.c b/sway/input/cursor.c
new file mode 100644
index 00000000..15a61cbf
--- /dev/null
+++ b/sway/input/cursor.c
@@ -0,0 +1,384 @@
+#define _XOPEN_SOURCE 700
+#ifdef __linux__
+#include <linux/input-event-codes.h>
+#elif __FreeBSD__
+#include <dev/evdev/input-event-codes.h>
+#endif
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/types/wlr_xcursor_manager.h>
+#include "list.h"
+#include "log.h"
+#include "sway/input/cursor.h"
+#include "sway/layers.h"
+#include "sway/output.h"
+#include "sway/tree/view.h"
+#include "wlr-layer-shell-unstable-v1-protocol.h"
+
+static struct wlr_surface *layer_surface_at(struct sway_output *output,
+ struct wl_list *layer, double ox, double oy, double *sx, double *sy) {
+ struct sway_layer_surface *sway_layer;
+ wl_list_for_each_reverse(sway_layer, layer, link) {
+ struct wlr_surface *wlr_surface =
+ sway_layer->layer_surface->surface;
+ double _sx = ox - sway_layer->geo.x;
+ double _sy = oy - sway_layer->geo.y;
+ // TODO: Test popups/subsurfaces
+ if (wlr_surface_point_accepts_input(wlr_surface, _sx, _sy)) {
+ *sx = _sx;
+ *sy = _sy;
+ return wlr_surface;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * Returns the container at the cursor's position. If there is a surface at that
+ * location, it is stored in **surface (it may not be a view).
+ */
+static struct sway_container *container_at_cursor(struct sway_cursor *cursor,
+ struct wlr_surface **surface, double *sx, double *sy) {
+ // check for unmanaged views first
+ struct wl_list *unmanaged = &root_container.sway_root->xwayland_unmanaged;
+ struct sway_xwayland_unmanaged *unmanaged_surface;
+ wl_list_for_each_reverse(unmanaged_surface, unmanaged, link) {
+ struct wlr_xwayland_surface *xsurface =
+ unmanaged_surface->wlr_xwayland_surface;
+
+ double _sx = cursor->cursor->x - unmanaged_surface->lx;
+ double _sy = cursor->cursor->y - unmanaged_surface->ly;
+ if (wlr_surface_point_accepts_input(xsurface->surface, _sx, _sy)) {
+ *surface = xsurface->surface;
+ *sx = _sx;
+ *sy = _sy;
+ return NULL;
+ }
+ }
+
+ // find the output the cursor is on
+ struct wlr_output_layout *output_layout =
+ root_container.sway_root->output_layout;
+ struct wlr_output *wlr_output =
+ wlr_output_layout_output_at(output_layout,
+ cursor->cursor->x, cursor->cursor->y);
+ if (wlr_output == NULL) {
+ return NULL;
+ }
+ struct sway_output *output = wlr_output->data;
+ double ox = cursor->cursor->x, oy = cursor->cursor->y;
+ wlr_output_layout_output_coords(output_layout, wlr_output, &ox, &oy);
+
+ // find the focused workspace on the output for this seat
+ struct sway_container *ws =
+ seat_get_focus_inactive(cursor->seat, output->swayc);
+ if (ws && ws->type != C_WORKSPACE) {
+ ws = container_parent(ws, C_WORKSPACE);
+ }
+ if (!ws) {
+ return output->swayc;
+ }
+
+ if ((*surface = layer_surface_at(output,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],
+ ox, oy, sx, sy))) {
+ return ws;
+ }
+ if ((*surface = layer_surface_at(output,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
+ ox, oy, sx, sy))) {
+ return ws;
+ }
+
+ struct sway_container *c;
+ if ((c = container_at(ws, cursor->cursor->x, cursor->cursor->y,
+ surface, sx, sy))) {
+ return c;
+ }
+
+ if ((*surface = layer_surface_at(output,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
+ ox, oy, sx, sy))) {
+ return ws;
+ }
+ if ((*surface = layer_surface_at(output,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
+ ox, oy, sx, sy))) {
+ return ws;
+ }
+
+ c = seat_get_focus_inactive(cursor->seat, output->swayc);
+ if (c) {
+ return c;
+ }
+ if (!c && output->swayc->children->length) {
+ c = output->swayc->children->items[0];
+ return c;
+ }
+
+ return output->swayc;
+}
+
+void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time) {
+ struct wlr_seat *seat = cursor->seat->wlr_seat;
+ struct wlr_surface *surface = NULL;
+ double sx, sy;
+ struct sway_container *c = container_at_cursor(cursor, &surface, &sx, &sy);
+ if (c && config->focus_follows_mouse) {
+ seat_set_focus_warp(cursor->seat, c, false);
+ }
+
+ // reset cursor if switching between clients
+ struct wl_client *client = NULL;
+ if (surface != NULL) {
+ client = wl_resource_get_client(surface->resource);
+ }
+ if (client != cursor->image_client) {
+ wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager,
+ "left_ptr", cursor->cursor);
+ cursor->image_client = client;
+ }
+
+ // send pointer enter/leave
+ if (surface != NULL) {
+ if (seat_is_input_allowed(cursor->seat, surface)) {
+ wlr_seat_pointer_notify_enter(seat, surface, sx, sy);
+ wlr_seat_pointer_notify_motion(seat, time, sx, sy);
+ }
+ } else {
+ wlr_seat_pointer_clear_focus(seat);
+ }
+}
+
+static void handle_cursor_motion(struct wl_listener *listener, void *data) {
+ struct sway_cursor *cursor = wl_container_of(listener, cursor, motion);
+ struct wlr_event_pointer_motion *event = data;
+ wlr_cursor_move(cursor->cursor, event->device,
+ event->delta_x, event->delta_y);
+ cursor_send_pointer_motion(cursor, event->time_msec);
+}
+
+static void handle_cursor_motion_absolute(
+ struct wl_listener *listener, void *data) {
+ struct sway_cursor *cursor =
+ wl_container_of(listener, cursor, motion_absolute);
+ struct wlr_event_pointer_motion_absolute *event = data;
+ wlr_cursor_warp_absolute(cursor->cursor, event->device, event->x, event->y);
+ cursor_send_pointer_motion(cursor, event->time_msec);
+}
+
+void dispatch_cursor_button(struct sway_cursor *cursor,
+ uint32_t time_msec, uint32_t button, enum wlr_button_state state) {
+ struct wlr_surface *surface = NULL;
+ double sx, sy;
+ struct sway_container *cont =
+ container_at_cursor(cursor, &surface, &sx, &sy);
+ if (surface && wlr_surface_is_layer_surface(surface)) {
+ struct wlr_layer_surface *layer =
+ wlr_layer_surface_from_wlr_surface(surface);
+ if (layer->current.keyboard_interactive) {
+ seat_set_focus_layer(cursor->seat, layer);
+ return;
+ }
+ }
+ // Avoid moving keyboard focus from a surface that accepts it to one
+ // that does not unless the change would move us to a new workspace.
+ //
+ // This prevents, for example, losing focus when clicking on swaybar.
+ if (surface && cont && cont->type != C_VIEW) {
+ struct sway_container *new_ws = cont;
+ if (new_ws && new_ws->type != C_WORKSPACE) {
+ new_ws = container_parent(new_ws, C_WORKSPACE);
+ }
+ struct sway_container *old_ws = seat_get_focus(cursor->seat);
+ if (old_ws && old_ws->type != C_WORKSPACE) {
+ old_ws = container_parent(old_ws, C_WORKSPACE);
+ }
+ if (new_ws != old_ws) {
+ seat_set_focus(cursor->seat, cont);
+ }
+ } else {
+ seat_set_focus(cursor->seat, cont);
+ }
+
+ wlr_seat_pointer_notify_button(cursor->seat->wlr_seat,
+ time_msec, button, state);
+}
+
+static void handle_cursor_button(struct wl_listener *listener, void *data) {
+ struct sway_cursor *cursor = wl_container_of(listener, cursor, button);
+ struct wlr_event_pointer_button *event = data;
+ dispatch_cursor_button(cursor,
+ event->time_msec, event->button, event->state);
+}
+
+static void handle_cursor_axis(struct wl_listener *listener, void *data) {
+ struct sway_cursor *cursor = wl_container_of(listener, cursor, axis);
+ struct wlr_event_pointer_axis *event = data;
+ wlr_seat_pointer_notify_axis(cursor->seat->wlr_seat, event->time_msec,
+ event->orientation, event->delta);
+}
+
+static void handle_touch_down(struct wl_listener *listener, void *data) {
+ struct sway_cursor *cursor = wl_container_of(listener, cursor, touch_down);
+ struct wlr_event_touch_down *event = data;
+ wlr_log(L_DEBUG, "TODO: handle touch down event: %p", event);
+}
+
+static void handle_touch_up(struct wl_listener *listener, void *data) {
+ struct sway_cursor *cursor = wl_container_of(listener, cursor, touch_up);
+ struct wlr_event_touch_up *event = data;
+ wlr_log(L_DEBUG, "TODO: handle touch up event: %p", event);
+}
+
+static void handle_touch_motion(struct wl_listener *listener, void *data) {
+ struct sway_cursor *cursor =
+ wl_container_of(listener, cursor, touch_motion);
+ struct wlr_event_touch_motion *event = data;
+ wlr_log(L_DEBUG, "TODO: handle touch motion event: %p", event);
+}
+
+static void handle_tool_axis(struct wl_listener *listener, void *data) {
+ struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_axis);
+ struct wlr_event_tablet_tool_axis *event = data;
+
+ if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X) &&
+ (event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) {
+ wlr_cursor_warp_absolute(cursor->cursor, event->device,
+ event->x, event->y);
+ cursor_send_pointer_motion(cursor, event->time_msec);
+ } else if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X)) {
+ wlr_cursor_warp_absolute(cursor->cursor, event->device, event->x, -1);
+ cursor_send_pointer_motion(cursor, event->time_msec);
+ } else if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) {
+ wlr_cursor_warp_absolute(cursor->cursor, event->device, -1, event->y);
+ cursor_send_pointer_motion(cursor, event->time_msec);
+ }
+}
+
+static void handle_tool_tip(struct wl_listener *listener, void *data) {
+ struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_tip);
+ struct wlr_event_tablet_tool_tip *event = data;
+ dispatch_cursor_button(cursor, event->time_msec,
+ BTN_LEFT, event->state == WLR_TABLET_TOOL_TIP_DOWN ?
+ WLR_BUTTON_PRESSED : WLR_BUTTON_RELEASED);
+}
+
+static void handle_tool_button(struct wl_listener *listener, void *data) {
+ struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_button);
+ struct wlr_event_tablet_tool_button *event = data;
+ // TODO: the user may want to configure which tool buttons are mapped to
+ // which simulated pointer buttons
+ switch (event->state) {
+ case WLR_BUTTON_PRESSED:
+ if (cursor->tool_buttons == 0) {
+ dispatch_cursor_button(cursor,
+ event->time_msec, BTN_RIGHT, event->state);
+ }
+ cursor->tool_buttons++;
+ break;
+ case WLR_BUTTON_RELEASED:
+ if (cursor->tool_buttons == 1) {
+ dispatch_cursor_button(cursor,
+ event->time_msec, BTN_RIGHT, event->state);
+ }
+ cursor->tool_buttons--;
+ break;
+ }
+}
+
+static void handle_request_set_cursor(struct wl_listener *listener,
+ void *data) {
+ struct sway_cursor *cursor =
+ wl_container_of(listener, cursor, request_set_cursor);
+ struct wlr_seat_pointer_request_set_cursor_event *event = data;
+
+ struct wl_client *focused_client = NULL;
+ struct wlr_surface *focused_surface =
+ cursor->seat->wlr_seat->pointer_state.focused_surface;
+ if (focused_surface != NULL) {
+ focused_client = wl_resource_get_client(focused_surface->resource);
+ }
+
+ // TODO: check cursor mode
+ if (focused_client == NULL ||
+ event->seat_client->client != focused_client) {
+ wlr_log(L_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->image_client = focused_client;
+}
+
+void sway_cursor_destroy(struct sway_cursor *cursor) {
+ if (!cursor) {
+ return;
+ }
+
+ wlr_xcursor_manager_destroy(cursor->xcursor_manager);
+ wlr_cursor_destroy(cursor->cursor);
+ free(cursor);
+}
+
+struct sway_cursor *sway_cursor_create(struct sway_seat *seat) {
+ struct sway_cursor *cursor = calloc(1, sizeof(struct sway_cursor));
+ if (!sway_assert(cursor, "could not allocate sway cursor")) {
+ return NULL;
+ }
+
+ struct wlr_cursor *wlr_cursor = wlr_cursor_create();
+ if (!sway_assert(wlr_cursor, "could not allocate wlr cursor")) {
+ free(cursor);
+ return NULL;
+ }
+
+ cursor->seat = seat;
+ wlr_cursor_attach_output_layout(wlr_cursor,
+ root_container.sway_root->output_layout);
+
+ // input events
+ wl_signal_add(&wlr_cursor->events.motion, &cursor->motion);
+ cursor->motion.notify = handle_cursor_motion;
+
+ wl_signal_add(&wlr_cursor->events.motion_absolute,
+ &cursor->motion_absolute);
+ cursor->motion_absolute.notify = handle_cursor_motion_absolute;
+
+ wl_signal_add(&wlr_cursor->events.button, &cursor->button);
+ cursor->button.notify = handle_cursor_button;
+
+ wl_signal_add(&wlr_cursor->events.axis, &cursor->axis);
+ cursor->axis.notify = handle_cursor_axis;
+
+ wl_signal_add(&wlr_cursor->events.touch_down, &cursor->touch_down);
+ cursor->touch_down.notify = handle_touch_down;
+
+ wl_signal_add(&wlr_cursor->events.touch_up, &cursor->touch_up);
+ cursor->touch_up.notify = handle_touch_up;
+
+ wl_signal_add(&wlr_cursor->events.touch_motion,
+ &cursor->touch_motion);
+ cursor->touch_motion.notify = handle_touch_motion;
+
+ // TODO: tablet protocol support
+ // Note: We should emulate pointer events for clients that don't support the
+ // tablet protocol when the time comes
+ wl_signal_add(&wlr_cursor->events.tablet_tool_axis,
+ &cursor->tool_axis);
+ cursor->tool_axis.notify = handle_tool_axis;
+
+ wl_signal_add(&wlr_cursor->events.tablet_tool_tip, &cursor->tool_tip);
+ cursor->tool_tip.notify = handle_tool_tip;
+
+ wl_signal_add(&wlr_cursor->events.tablet_tool_button, &cursor->tool_button);
+ cursor->tool_button.notify = handle_tool_button;
+
+ wl_signal_add(&seat->wlr_seat->events.request_set_cursor,
+ &cursor->request_set_cursor);
+ cursor->request_set_cursor.notify = handle_request_set_cursor;
+
+ cursor->cursor = wlr_cursor;
+
+ return cursor;
+}
diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c
new file mode 100644
index 00000000..ae55d2a1
--- /dev/null
+++ b/sway/input/input-manager.c
@@ -0,0 +1,445 @@
+#define _XOPEN_SOURCE 700
+#include <ctype.h>
+#include <float.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <libinput.h>
+#include <math.h>
+#include <wlr/backend/libinput.h>
+#include <wlr/types/wlr_input_inhibitor.h>
+#include "sway/config.h"
+#include "sway/input/input-manager.h"
+#include "sway/input/seat.h"
+#include "sway/server.h"
+#include "stringop.h"
+#include "list.h"
+#include "log.h"
+
+static const char *default_seat = "seat0";
+
+// TODO make me not global
+struct sway_input_manager *input_manager;
+
+struct input_config *current_input_config = NULL;
+struct seat_config *current_seat_config = NULL;
+
+struct sway_seat *input_manager_current_seat(struct sway_input_manager *input) {
+ struct sway_seat *seat = config->handler_context.seat;
+ if (!seat) {
+ seat = input_manager_get_default_seat(input_manager);
+ }
+ return seat;
+}
+
+struct sway_seat *input_manager_get_seat(
+ struct sway_input_manager *input, const char *seat_name) {
+ struct sway_seat *seat = NULL;
+ wl_list_for_each(seat, &input->seats, link) {
+ if (strcmp(seat->wlr_seat->name, seat_name) == 0) {
+ return seat;
+ }
+ }
+
+ return seat_create(input, seat_name);
+}
+
+static char *get_device_identifier(struct wlr_input_device *device) {
+ int vendor = device->vendor;
+ int product = device->product;
+ char *name = strdup(device->name);
+ name = strip_whitespace(name);
+
+ char *p = name;
+ for (; *p; ++p) {
+ if (*p == ' ') {
+ *p = '_';
+ }
+ }
+
+ const char *fmt = "%d:%d:%s";
+ int len = snprintf(NULL, 0, fmt, vendor, product, name) + 1;
+ char *identifier = malloc(len);
+ if (!identifier) {
+ wlr_log(L_ERROR, "Unable to allocate unique input device name");
+ return NULL;
+ }
+
+ snprintf(identifier, len, fmt, vendor, product, name);
+ free(name);
+ return identifier;
+}
+
+static struct sway_input_device *input_sway_device_from_wlr(
+ struct sway_input_manager *input, struct wlr_input_device *device) {
+ struct sway_input_device *input_device = NULL;
+ wl_list_for_each(input_device, &input->devices, link) {
+ if (input_device->wlr_device == device) {
+ return input_device;
+ }
+ }
+ return NULL;
+}
+
+static bool input_has_seat_configuration(struct sway_input_manager *input) {
+ struct sway_seat *seat = NULL;
+ wl_list_for_each(seat, &input->seats, link) {
+ struct seat_config *seat_config = seat_get_config(seat);
+ if (seat_config) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void input_manager_libinput_config_pointer(
+ struct sway_input_device *input_device) {
+ struct wlr_input_device *wlr_device = input_device->wlr_device;
+ struct input_config *ic = input_device_get_config(input_device);
+ struct libinput_device *libinput_device;
+
+ if (!ic || !wlr_input_device_is_libinput(wlr_device)) {
+ return;
+ }
+
+ libinput_device = wlr_libinput_get_device_handle(wlr_device);
+ wlr_log(L_DEBUG, "input_manager_libinput_config_pointer(%s)",
+ ic->identifier);
+
+ if (ic->accel_profile != INT_MIN) {
+ wlr_log(L_DEBUG, "libinput_config_pointer(%s) accel_set_profile(%d)",
+ ic->identifier, ic->accel_profile);
+ libinput_device_config_accel_set_profile(libinput_device,
+ ic->accel_profile);
+ }
+ if (ic->click_method != INT_MIN) {
+ wlr_log(L_DEBUG, "libinput_config_pointer(%s) click_set_method(%d)",
+ ic->identifier, ic->click_method);
+ libinput_device_config_click_set_method(libinput_device,
+ ic->click_method);
+ }
+ if (ic->drag_lock != INT_MIN) {
+ wlr_log(L_DEBUG,
+ "libinput_config_pointer(%s) tap_set_drag_lock_enabled(%d)",
+ ic->identifier, ic->click_method);
+ libinput_device_config_tap_set_drag_lock_enabled(libinput_device,
+ ic->drag_lock);
+ }
+ if (ic->dwt != INT_MIN) {
+ wlr_log(L_DEBUG, "libinput_config_pointer(%s) dwt_set_enabled(%d)",
+ ic->identifier, ic->dwt);
+ libinput_device_config_dwt_set_enabled(libinput_device, ic->dwt);
+ }
+ if (ic->left_handed != INT_MIN) {
+ wlr_log(L_DEBUG,
+ "libinput_config_pointer(%s) left_handed_set_enabled(%d)",
+ ic->identifier, ic->left_handed);
+ libinput_device_config_left_handed_set(libinput_device,
+ ic->left_handed);
+ }
+ if (ic->middle_emulation != INT_MIN) {
+ wlr_log(L_DEBUG,
+ "libinput_config_pointer(%s) middle_emulation_set_enabled(%d)",
+ ic->identifier, ic->middle_emulation);
+ libinput_device_config_middle_emulation_set_enabled(libinput_device,
+ ic->middle_emulation);
+ }
+ if (ic->natural_scroll != INT_MIN) {
+ wlr_log(L_DEBUG,
+ "libinput_config_pointer(%s) natural_scroll_set_enabled(%d)",
+ ic->identifier, ic->natural_scroll);
+ libinput_device_config_scroll_set_natural_scroll_enabled(
+ libinput_device, ic->natural_scroll);
+ }
+ if (ic->pointer_accel != FLT_MIN) {
+ wlr_log(L_DEBUG, "libinput_config_pointer(%s) accel_set_speed(%f)",
+ ic->identifier, ic->pointer_accel);
+ libinput_device_config_accel_set_speed(libinput_device,
+ ic->pointer_accel);
+ }
+ if (ic->scroll_method != INT_MIN) {
+ wlr_log(L_DEBUG, "libinput_config_pointer(%s) scroll_set_method(%d)",
+ ic->identifier, ic->scroll_method);
+ libinput_device_config_scroll_set_method(libinput_device,
+ ic->scroll_method);
+ }
+ if (ic->send_events != INT_MIN) {
+ wlr_log(L_DEBUG, "libinput_config_pointer(%s) send_events_set_mode(%d)",
+ ic->identifier, ic->send_events);
+ libinput_device_config_send_events_set_mode(libinput_device,
+ ic->send_events);
+ }
+ if (ic->tap != INT_MIN) {
+ wlr_log(L_DEBUG, "libinput_config_pointer(%s) tap_set_enabled(%d)",
+ ic->identifier, ic->tap);
+ libinput_device_config_tap_set_enabled(libinput_device, ic->tap);
+ }
+}
+
+static void handle_device_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_input_device *device = data;
+
+ struct sway_input_device *input_device =
+ input_sway_device_from_wlr(input_manager, device);
+
+ if (!sway_assert(input_device, "could not find sway device")) {
+ return;
+ }
+
+ wlr_log(L_DEBUG, "removing device: '%s'",
+ input_device->identifier);
+
+ struct sway_seat *seat = NULL;
+ wl_list_for_each(seat, &input_manager->seats, link) {
+ seat_remove_device(seat, input_device);
+ }
+
+ wl_list_remove(&input_device->link);
+ wl_list_remove(&input_device->device_destroy.link);
+ free(input_device->identifier);
+ free(input_device);
+}
+
+static void handle_new_input(struct wl_listener *listener, void *data) {
+ struct sway_input_manager *input =
+ wl_container_of(listener, input, new_input);
+ struct wlr_input_device *device = data;
+
+ struct sway_input_device *input_device =
+ calloc(1, sizeof(struct sway_input_device));
+ if (!sway_assert(input_device, "could not allocate input device")) {
+ return;
+ }
+
+ input_device->wlr_device = device;
+ input_device->identifier = get_device_identifier(device);
+ wl_list_insert(&input->devices, &input_device->link);
+
+ wlr_log(L_DEBUG, "adding device: '%s'",
+ input_device->identifier);
+
+ if (input_device->wlr_device->type == WLR_INPUT_DEVICE_POINTER) {
+ input_manager_libinput_config_pointer(input_device);
+ }
+
+ struct sway_seat *seat = NULL;
+ if (!input_has_seat_configuration(input)) {
+ wlr_log(L_DEBUG, "no seat configuration, using default seat");
+ seat = input_manager_get_seat(input, default_seat);
+ seat_add_device(seat, input_device);
+ return;
+ }
+
+ bool added = false;
+ wl_list_for_each(seat, &input->seats, link) {
+ struct seat_config *seat_config = seat_get_config(seat);
+ bool has_attachment = seat_config &&
+ (seat_config_get_attachment(seat_config, input_device->identifier) ||
+ seat_config_get_attachment(seat_config, "*"));
+
+ if (has_attachment) {
+ seat_add_device(seat, input_device);
+ added = true;
+ }
+ }
+
+ if (!added) {
+ wl_list_for_each(seat, &input->seats, link) {
+ struct seat_config *seat_config = seat_get_config(seat);
+ if (seat_config && seat_config->fallback == 1) {
+ seat_add_device(seat, input_device);
+ added = true;
+ }
+ }
+ }
+
+ if (!added) {
+ wlr_log(L_DEBUG,
+ "device '%s' is not configured on any seats",
+ input_device->identifier);
+ }
+
+ wl_signal_add(&device->events.destroy, &input_device->device_destroy);
+ input_device->device_destroy.notify = handle_device_destroy;
+}
+
+static void handle_inhibit_activate(struct wl_listener *listener, void *data) {
+ struct sway_input_manager *input_manager = wl_container_of(
+ listener, input_manager, inhibit_activate);
+ struct sway_seat *seat;
+ wl_list_for_each(seat, &input_manager->seats, link) {
+ seat_set_exclusive_client(seat, input_manager->inhibit->active_client);
+ }
+}
+
+static void handle_inhibit_deactivate(struct wl_listener *listener, void *data) {
+ struct sway_input_manager *input_manager = wl_container_of(
+ listener, input_manager, inhibit_deactivate);
+ struct sway_seat *seat;
+ wl_list_for_each(seat, &input_manager->seats, link) {
+ seat_set_exclusive_client(seat, NULL);
+ struct sway_container *previous = seat_get_focus(seat);
+ if (previous) {
+ wlr_log(L_DEBUG, "Returning focus to %p %s '%s'", previous,
+ container_type_to_str(previous->type), previous->name);
+ // Hack to get seat to re-focus the return value of get_focus
+ seat_set_focus(seat, previous->parent);
+ seat_set_focus(seat, previous);
+ }
+ }
+}
+
+struct sway_input_manager *input_manager_create(
+ struct sway_server *server) {
+ struct sway_input_manager *input =
+ calloc(1, sizeof(struct sway_input_manager));
+ if (!input) {
+ return NULL;
+ }
+ input->server = server;
+
+ wl_list_init(&input->devices);
+ wl_list_init(&input->seats);
+
+ // create the default seat
+ input_manager_get_seat(input, default_seat);
+
+ input->new_input.notify = handle_new_input;
+ wl_signal_add(&server->backend->events.new_input, &input->new_input);
+
+ input->inhibit = wlr_input_inhibit_manager_create(server->wl_display);
+ input->inhibit_activate.notify = handle_inhibit_activate;
+ wl_signal_add(&input->inhibit->events.activate,
+ &input->inhibit_activate);
+ input->inhibit_deactivate.notify = handle_inhibit_deactivate;
+ wl_signal_add(&input->inhibit->events.deactivate,
+ &input->inhibit_deactivate);
+
+ return input;
+}
+
+bool input_manager_has_focus(struct sway_input_manager *input,
+ struct sway_container *container) {
+ struct sway_seat *seat = NULL;
+ wl_list_for_each(seat, &input->seats, link) {
+ if (seat_get_focus(seat) == container) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void input_manager_set_focus(struct sway_input_manager *input,
+ struct sway_container *container) {
+ struct sway_seat *seat;
+ wl_list_for_each(seat, &input->seats, link) {
+ seat_set_focus(seat, container);
+ }
+}
+
+void input_manager_apply_input_config(struct sway_input_manager *input,
+ struct input_config *input_config) {
+ struct sway_input_device *input_device = NULL;
+ wl_list_for_each(input_device, &input->devices, link) {
+ if (strcmp(input_device->identifier, input_config->identifier) == 0) {
+ if (input_device->wlr_device->type == WLR_INPUT_DEVICE_POINTER) {
+ input_manager_libinput_config_pointer(input_device);
+ }
+
+ struct sway_seat *seat = NULL;
+ wl_list_for_each(seat, &input->seats, link) {
+ seat_configure_device(seat, input_device);
+ }
+ }
+ }
+}
+
+void input_manager_apply_seat_config(struct sway_input_manager *input,
+ struct seat_config *seat_config) {
+ wlr_log(L_DEBUG, "applying new seat config for seat %s",
+ seat_config->name);
+ struct sway_seat *seat = input_manager_get_seat(input, seat_config->name);
+ if (!seat) {
+ return;
+ }
+
+ seat_apply_config(seat, seat_config);
+
+ // for every device, try to add it to a seat and if no seat has it
+ // attached, add it to the fallback seats.
+ struct sway_input_device *input_device = NULL;
+ wl_list_for_each(input_device, &input->devices, link) {
+ list_t *seat_list = create_list();
+ struct sway_seat *seat = NULL;
+ wl_list_for_each(seat, &input->seats, link) {
+ struct seat_config *seat_config = seat_get_config(seat);
+ if (!seat_config) {
+ continue;
+ }
+ if (seat_config_get_attachment(seat_config, "*") ||
+ seat_config_get_attachment(seat_config,
+ input_device->identifier)) {
+ list_add(seat_list, seat);
+ }
+ }
+
+ if (seat_list->length) {
+ wl_list_for_each(seat, &input->seats, link) {
+ bool attached = false;
+ for (int i = 0; i < seat_list->length; ++i) {
+ if (seat == seat_list->items[i]) {
+ attached = true;
+ break;
+ }
+ }
+ if (attached) {
+ seat_add_device(seat, input_device);
+ } else {
+ seat_remove_device(seat, input_device);
+ }
+ }
+ } else {
+ wl_list_for_each(seat, &input->seats, link) {
+ struct seat_config *seat_config = seat_get_config(seat);
+ if (seat_config && seat_config->fallback == 1) {
+ seat_add_device(seat, input_device);
+ } else {
+ seat_remove_device(seat, input_device);
+ }
+ }
+ }
+ list_free(seat_list);
+ }
+}
+
+void input_manager_configure_xcursor(struct sway_input_manager *input) {
+ struct sway_seat *seat = NULL;
+ wl_list_for_each(seat, &input->seats, link) {
+ seat_configure_xcursor(seat);
+ }
+}
+
+struct sway_seat *input_manager_get_default_seat(
+ struct sway_input_manager *input) {
+ struct sway_seat *seat = NULL;
+ wl_list_for_each(seat, &input->seats, link) {
+ if (strcmp(seat->wlr_seat->name, "seat0") == 0) {
+ return seat;
+ }
+ }
+ return seat;
+}
+
+struct input_config *input_device_get_config(struct sway_input_device *device) {
+ struct input_config *input_config = NULL;
+ for (int i = 0; i < config->input_configs->length; ++i) {
+ input_config = config->input_configs->items[i];
+ if (strcmp(input_config->identifier, device->identifier) == 0) {
+ return input_config;
+ }
+ }
+
+ return NULL;
+}
diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c
new file mode 100644
index 00000000..dbb0c359
--- /dev/null
+++ b/sway/input/keyboard.c
@@ -0,0 +1,504 @@
+#include <assert.h>
+#include <wlr/backend/multi.h>
+#include <wlr/backend/session.h>
+#include "sway/input/seat.h"
+#include "sway/input/keyboard.h"
+#include "sway/input/input-manager.h"
+#include "sway/commands.h"
+#include "log.h"
+
+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 size_t pressed_keysyms_length(xkb_keysym_t *pressed_keysyms) {
+ size_t n = 0;
+ for (size_t i = 0; i < SWAY_KEYBOARD_PRESSED_KEYSYMS_CAP; ++i) {
+ if (pressed_keysyms[i] != XKB_KEY_NoSymbol) {
+ ++n;
+ }
+ }
+ return n;
+}
+
+static ssize_t pressed_keysyms_index(xkb_keysym_t *pressed_keysyms,
+ xkb_keysym_t keysym) {
+ for (size_t i = 0; i < SWAY_KEYBOARD_PRESSED_KEYSYMS_CAP; ++i) {
+ if (pressed_keysyms[i] == keysym) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+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 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 bool binding_matches_key_state(struct sway_binding *binding,
+ enum wlr_key_state key_state) {
+ if (key_state == WLR_KEY_PRESSED && !binding->release) {
+ return true;
+ }
+ if (key_state == WLR_KEY_RELEASED && binding->release) {
+ return true;
+ }
+
+ return false;
+}
+
+static void keyboard_execute_command(struct sway_keyboard *keyboard,
+ struct sway_binding *binding) {
+ wlr_log(L_DEBUG, "running command for binding: %s",
+ binding->command);
+ config_clear_handler_context(config);
+ config->handler_context.seat = keyboard->seat_device->sway_seat;
+ struct cmd_results *results = execute_command(binding->command, NULL);
+ if (results->status != CMD_SUCCESS) {
+ wlr_log(L_DEBUG, "could not run command for binding: %s (%s)",
+ binding->command, results->error);
+ }
+ free_cmd_results(results);
+}
+
+/**
+ * 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 sway_keyboard *keyboard,
+ xkb_keysym_t *pressed_keysyms, uint32_t modifiers, size_t keysyms_len) {
+ for (size_t i = 0; i < keysyms_len; ++i) {
+ xkb_keysym_t keysym = pressed_keysyms[i];
+ if (keysym >= XKB_KEY_XF86Switch_VT_1 &&
+ keysym <= XKB_KEY_XF86Switch_VT_12) {
+ if (wlr_backend_is_multi(server.backend)) {
+ struct wlr_session *session =
+ wlr_multi_get_session(server.backend);
+ if (session) {
+ unsigned vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1;
+ wlr_session_change_vt(session, vt);
+ }
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Execute keyboard bindings bound with `bindysm` for the given keyboard state.
+ *
+ * Returns true if the keysym was handled by a binding and false if the event
+ * should be propagated to clients.
+ */
+static bool keyboard_execute_bindsym(struct sway_keyboard *keyboard,
+ xkb_keysym_t *pressed_keysyms, uint32_t modifiers,
+ enum wlr_key_state key_state) {
+ // configured bindings
+ int n = pressed_keysyms_length(pressed_keysyms);
+ list_t *keysym_bindings = config->current_mode->keysym_bindings;
+ for (int i = 0; i < keysym_bindings->length; ++i) {
+ struct sway_binding *binding = keysym_bindings->items[i];
+ if (!binding_matches_key_state(binding, key_state) ||
+ modifiers ^ binding->modifiers ||
+ n != binding->keys->length) {
+ continue;
+ }
+
+ bool match = true;
+ for (int j = 0; j < binding->keys->length; ++j) {
+ match =
+ pressed_keysyms_index(pressed_keysyms,
+ *(int*)binding->keys->items[j]) >= 0;
+
+ if (!match) {
+ break;
+ }
+ }
+
+ if (match) {
+ keyboard_execute_command(keyboard, binding);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool binding_matches_keycodes(struct wlr_keyboard *keyboard,
+ struct sway_binding *binding, struct wlr_event_keyboard_key *event) {
+ assert(binding->bindcode);
+
+ uint32_t keycode = event->keycode + 8;
+
+ if (!binding_matches_key_state(binding, event->state)) {
+ return false;
+ }
+
+ uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard);
+ if (modifiers ^ binding->modifiers) {
+ return false;
+ }
+
+ // on release, the released key must be in the binding
+ if (event->state == WLR_KEY_RELEASED) {
+ bool found = false;
+ for (int i = 0; i < binding->keys->length; ++i) {
+ uint32_t binding_keycode = *(uint32_t*)binding->keys->items[i] + 8;
+ if (binding_keycode == keycode) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+
+ // every keycode in the binding must be present in the pressed keys on the
+ // keyboard
+ for (int i = 0; i < binding->keys->length; ++i) {
+ uint32_t binding_keycode = *(uint32_t*)binding->keys->items[i] + 8;
+ if (event->state == WLR_KEY_RELEASED && keycode == binding_keycode) {
+ continue;
+ }
+
+ bool found = false;
+ for (size_t j = 0; j < keyboard->num_keycodes; ++j) {
+ xkb_keycode_t keycode = keyboard->keycodes[j] + 8;
+ if (keycode == binding_keycode) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ return false;
+ }
+ }
+
+ // every keycode pressed on the keyboard must be present within the binding
+ // keys (unless it is a modifier)
+ for (size_t i = 0; i < keyboard->num_keycodes; ++i) {
+ xkb_keycode_t keycode = keyboard->keycodes[i] + 8;
+ bool found = false;
+ for (int j = 0; j < binding->keys->length; ++j) {
+ uint32_t binding_keycode = *(uint32_t*)binding->keys->items[j] + 8;
+ if (binding_keycode == keycode) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ if (!binding->modifiers) {
+ return false;
+ }
+
+ // check if it is a modifier, which we know matched from the check
+ // above
+ const xkb_keysym_t *keysyms;
+ int num_keysyms =
+ xkb_state_key_get_syms(keyboard->xkb_state,
+ keycode, &keysyms);
+ if (num_keysyms != 1 || !keysym_is_modifier(keysyms[0])) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Execute keyboard bindings bound with `bindcode` for the given keyboard state.
+ *
+ * Returns true if the keysym was handled by a binding and false if the event
+ * should be propagated to clients.
+ */
+static bool keyboard_execute_bindcode(struct sway_keyboard *keyboard,
+ struct wlr_event_keyboard_key *event) {
+ struct wlr_keyboard *wlr_keyboard =
+ keyboard->seat_device->input_device->wlr_device->keyboard;
+ list_t *keycode_bindings = config->current_mode->keycode_bindings;
+ for (int i = 0; i < keycode_bindings->length; ++i) {
+ struct sway_binding *binding = keycode_bindings->items[i];
+ if (binding_matches_keycodes(wlr_keyboard, binding, event)) {
+ keyboard_execute_command(keyboard, binding);
+ 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 sway_keyboard *keyboard,
+ xkb_keycode_t keycode, const xkb_keysym_t **keysyms,
+ uint32_t *modifiers) {
+ struct wlr_input_device *device =
+ keyboard->seat_device->input_device->wlr_device;
+ *modifiers = wlr_keyboard_get_modifiers(device->keyboard);
+ xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods2(
+ device->keyboard->xkb_state, keycode, XKB_CONSUMED_MODE_XKB);
+ *modifiers = *modifiers & ~consumed;
+
+ return xkb_state_key_get_syms(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 sway_keyboard *keyboard,
+ xkb_keycode_t keycode, const xkb_keysym_t **keysyms,
+ uint32_t *modifiers) {
+ struct wlr_input_device *device =
+ keyboard->seat_device->input_device->wlr_device;
+ *modifiers = wlr_keyboard_get_modifiers(device->keyboard);
+
+ xkb_layout_index_t layout_index = xkb_state_key_get_layout(
+ device->keyboard->xkb_state, keycode);
+ return xkb_keymap_key_get_syms_by_level(device->keyboard->keymap,
+ keycode, layout_index, 0, keysyms);
+}
+
+static void handle_keyboard_key(struct wl_listener *listener, void *data) {
+ struct sway_keyboard *keyboard =
+ wl_container_of(listener, keyboard, keyboard_key);
+ struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat;
+ struct wlr_input_device *wlr_device =
+ keyboard->seat_device->input_device->wlr_device;
+ struct wlr_event_keyboard_key *event = data;
+
+ xkb_keycode_t keycode = event->keycode + 8;
+ bool handled = false;
+
+ // handle keycodes
+ handled = keyboard_execute_bindcode(keyboard, event);
+
+ // handle translated keysyms
+ if (!handled && event->state == WLR_KEY_RELEASED) {
+ handled = keyboard_execute_bindsym(keyboard,
+ keyboard->pressed_keysyms_translated,
+ keyboard->modifiers_translated,
+ event->state);
+ }
+ const xkb_keysym_t *translated_keysyms;
+ size_t translated_keysyms_len =
+ keyboard_keysyms_translated(keyboard, keycode, &translated_keysyms,
+ &keyboard->modifiers_translated);
+ pressed_keysyms_update(keyboard->pressed_keysyms_translated,
+ translated_keysyms, translated_keysyms_len, event->state);
+ if (!handled && event->state == WLR_KEY_PRESSED) {
+ handled = keyboard_execute_bindsym(keyboard,
+ keyboard->pressed_keysyms_translated,
+ keyboard->modifiers_translated,
+ event->state);
+ }
+
+ // Handle raw keysyms
+ if (!handled && event->state == WLR_KEY_RELEASED) {
+ handled = keyboard_execute_bindsym(keyboard,
+ keyboard->pressed_keysyms_raw, keyboard->modifiers_raw,
+ event->state);
+ }
+ const xkb_keysym_t *raw_keysyms;
+ size_t raw_keysyms_len =
+ keyboard_keysyms_raw(keyboard, keycode, &raw_keysyms, &keyboard->modifiers_raw);
+ pressed_keysyms_update(keyboard->pressed_keysyms_raw, raw_keysyms,
+ raw_keysyms_len, event->state);
+ if (!handled && event->state == WLR_KEY_PRESSED) {
+ handled = keyboard_execute_bindsym(keyboard,
+ keyboard->pressed_keysyms_raw, keyboard->modifiers_raw,
+ event->state);
+ }
+
+ // Compositor bindings
+ if (!handled && event->state == WLR_KEY_PRESSED) {
+ handled =
+ keyboard_execute_compositor_binding(keyboard,
+ keyboard->pressed_keysyms_translated,
+ keyboard->modifiers_translated,
+ translated_keysyms_len);
+ }
+ if (!handled && event->state == WLR_KEY_PRESSED) {
+ handled =
+ keyboard_execute_compositor_binding(keyboard,
+ keyboard->pressed_keysyms_raw, keyboard->modifiers_raw,
+ raw_keysyms_len);
+ }
+
+ if (!handled || event->state == WLR_KEY_RELEASED) {
+ wlr_seat_set_keyboard(wlr_seat, wlr_device);
+ wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec,
+ event->keycode, event->state);
+ }
+}
+
+static void handle_keyboard_modifiers(struct wl_listener *listener,
+ void *data) {
+ struct sway_keyboard *keyboard =
+ wl_container_of(listener, keyboard, keyboard_modifiers);
+ struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat;
+ struct wlr_input_device *wlr_device =
+ keyboard->seat_device->input_device->wlr_device;
+ wlr_seat_set_keyboard(wlr_seat, wlr_device);
+ wlr_seat_keyboard_notify_modifiers(wlr_seat, &wlr_device->keyboard->modifiers);
+}
+
+struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat,
+ struct sway_seat_device *device) {
+ struct sway_keyboard *keyboard =
+ calloc(1, sizeof(struct sway_keyboard));
+ if (!sway_assert(keyboard, "could not allocate sway keyboard")) {
+ return NULL;
+ }
+
+ keyboard->seat_device = device;
+ device->keyboard = keyboard;
+
+ wl_list_init(&keyboard->keyboard_key.link);
+ wl_list_init(&keyboard->keyboard_modifiers.link);
+
+ return keyboard;
+}
+
+void sway_keyboard_configure(struct sway_keyboard *keyboard) {
+ struct xkb_rule_names rules;
+ memset(&rules, 0, sizeof(rules));
+ struct input_config *input_config =
+ input_device_get_config(keyboard->seat_device->input_device);
+ struct wlr_input_device *wlr_device =
+ keyboard->seat_device->input_device->wlr_device;
+
+ if (input_config && input_config->xkb_layout) {
+ rules.layout = input_config->xkb_layout;
+ } else {
+ rules.layout = getenv("XKB_DEFAULT_LAYOUT");
+ }
+ if (input_config && input_config->xkb_model) {
+ rules.model = input_config->xkb_model;
+ } else {
+ rules.model = getenv("XKB_DEFAULT_MODEL");
+ }
+
+ if (input_config && input_config->xkb_options) {
+ rules.options = input_config->xkb_options;
+ } else {
+ rules.options = getenv("XKB_DEFAULT_OPTIONS");
+ }
+
+ if (input_config && input_config->xkb_rules) {
+ rules.rules = input_config->xkb_rules;
+ } else {
+ rules.rules = getenv("XKB_DEFAULT_RULES");
+ }
+
+ if (input_config && input_config->xkb_variant) {
+ rules.variant = input_config->xkb_variant;
+ } else {
+ rules.variant = getenv("XKB_DEFAULT_VARIANT");
+ }
+
+ struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ if (!sway_assert(context, "cannot create XKB context")) {
+ return;
+ }
+
+ struct xkb_keymap *keymap =
+ xkb_keymap_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS);
+
+ if (!keymap) {
+ wlr_log(L_DEBUG, "cannot configure keyboard: keymap does not exist");
+ xkb_context_unref(context);
+ return;
+ }
+
+ xkb_keymap_unref(keyboard->keymap);
+ keyboard->keymap = keymap;
+ wlr_keyboard_set_keymap(wlr_device->keyboard, keyboard->keymap);
+
+ wlr_keyboard_set_repeat_info(wlr_device->keyboard, 25, 600);
+ xkb_context_unref(context);
+ struct wlr_seat *seat = keyboard->seat_device->sway_seat->wlr_seat;
+ wlr_seat_set_keyboard(seat, wlr_device);
+
+ wl_list_remove(&keyboard->keyboard_key.link);
+ wl_signal_add(&wlr_device->keyboard->events.key, &keyboard->keyboard_key);
+ keyboard->keyboard_key.notify = handle_keyboard_key;
+
+ wl_list_remove(&keyboard->keyboard_modifiers.link);
+ wl_signal_add( &wlr_device->keyboard->events.modifiers,
+ &keyboard->keyboard_modifiers);
+ keyboard->keyboard_modifiers.notify = handle_keyboard_modifiers;
+}
+
+void sway_keyboard_destroy(struct sway_keyboard *keyboard) {
+ if (!keyboard) {
+ return;
+ }
+ wl_list_remove(&keyboard->keyboard_key.link);
+ wl_list_remove(&keyboard->keyboard_modifiers.link);
+ free(keyboard);
+}
diff --git a/sway/input/seat.c b/sway/input/seat.c
new file mode 100644
index 00000000..467e5087
--- /dev/null
+++ b/sway/input/seat.c
@@ -0,0 +1,675 @@
+#define _XOPEN_SOURCE 700
+#define _POSIX_C_SOURCE 199309L
+#include <assert.h>
+#include <strings.h>
+#include <time.h>
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_xcursor_manager.h>
+#include "sway/debug.h"
+#include "sway/tree/container.h"
+#include "sway/tree/workspace.h"
+#include "sway/input/seat.h"
+#include "sway/input/cursor.h"
+#include "sway/input/input-manager.h"
+#include "sway/input/keyboard.h"
+#include "sway/ipc-server.h"
+#include "sway/layers.h"
+#include "sway/output.h"
+#include "sway/tree/container.h"
+#include "sway/tree/view.h"
+#include "log.h"
+
+static void seat_device_destroy(struct sway_seat_device *seat_device) {
+ if (!seat_device) {
+ return;
+ }
+
+ sway_keyboard_destroy(seat_device->keyboard);
+ wlr_cursor_detach_input_device(seat_device->sway_seat->cursor->cursor,
+ seat_device->input_device->wlr_device);
+ wl_list_remove(&seat_device->link);
+ free(seat_device);
+}
+
+void seat_destroy(struct sway_seat *seat) {
+ struct sway_seat_device *seat_device, *next;
+ wl_list_for_each_safe(seat_device, next, &seat->devices, link) {
+ seat_device_destroy(seat_device);
+ }
+ sway_cursor_destroy(seat->cursor);
+ wl_list_remove(&seat->link);
+ wlr_seat_destroy(seat->wlr_seat);
+}
+
+static struct sway_seat_container *seat_container_from_container(
+ struct sway_seat *seat, struct sway_container *con);
+
+static void seat_container_destroy(struct sway_seat_container *seat_con) {
+ struct sway_container *con = seat_con->container;
+ struct sway_container *child = NULL;
+
+ if (con->children != NULL) {
+ for (int i = 0; i < con->children->length; ++i) {
+ child = con->children->items[i];
+ struct sway_seat_container *seat_child =
+ seat_container_from_container(seat_con->seat, child);
+ seat_container_destroy(seat_child);
+ }
+ }
+
+ wl_list_remove(&seat_con->destroy.link);
+ wl_list_remove(&seat_con->link);
+ free(seat_con);
+}
+
+static void seat_send_focus(struct sway_seat *seat,
+ struct sway_container *con) {
+ if (con->type != C_VIEW) {
+ return;
+ }
+ struct sway_view *view = con->sway_view;
+ if (view->type == SWAY_VIEW_XWAYLAND) {
+ struct wlr_xwayland *xwayland =
+ seat->input->server->xwayland;
+ wlr_xwayland_set_seat(xwayland, seat->wlr_seat);
+ }
+ view_set_activated(view, true);
+ struct wlr_keyboard *keyboard =
+ wlr_seat_get_keyboard(seat->wlr_seat);
+ if (keyboard) {
+ wlr_seat_keyboard_notify_enter(seat->wlr_seat,
+ view->surface, keyboard->keycodes,
+ keyboard->num_keycodes, &keyboard->modifiers);
+ } else {
+ wlr_seat_keyboard_notify_enter(
+ seat->wlr_seat, view->surface, NULL, 0, NULL);
+ }
+}
+
+static struct sway_container *seat_get_focus_by_type(struct sway_seat *seat,
+ struct sway_container *container, enum sway_container_type type) {
+ if (container->type == C_VIEW || container->children->length == 0) {
+ return container;
+ }
+
+ struct sway_seat_container *current = NULL;
+ wl_list_for_each(current, &seat->focus_stack, link) {
+ if (current->container->type != type && type != C_TYPES) {
+ continue;
+ }
+
+ if (container_has_child(container, current->container)) {
+ return current->container;
+ }
+ }
+
+ return NULL;
+}
+
+void seat_focus_inactive_children_for_each(struct sway_seat *seat,
+ struct sway_container *container,
+ void (*f)(struct sway_container *container, void *data), void *data) {
+ struct sway_seat_container *current = NULL;
+ wl_list_for_each(current, &seat->focus_stack, link) {
+ if (current->container->parent == NULL) {
+ continue;
+ }
+ if (current->container->parent == container) {
+ f(current->container, data);
+ }
+ }
+}
+
+struct sway_container *seat_get_focus_inactive_view(struct sway_seat *seat,
+ struct sway_container *container) {
+ return seat_get_focus_by_type(seat, container, C_VIEW);
+}
+
+static void handle_seat_container_destroy(struct wl_listener *listener,
+ void *data) {
+ struct sway_seat_container *seat_con =
+ wl_container_of(listener, seat_con, destroy);
+ struct sway_seat *seat = seat_con->seat;
+ struct sway_container *con = seat_con->container;
+ struct sway_container *parent = con->parent;
+ struct sway_container *focus = seat_get_focus(seat);
+
+ bool set_focus =
+ focus != NULL &&
+ (focus == con || container_has_child(con, focus)) &&
+ con->type != C_WORKSPACE;
+
+ seat_container_destroy(seat_con);
+
+ if (set_focus) {
+ struct sway_container *next_focus = NULL;
+ while (next_focus == NULL) {
+ next_focus = seat_get_focus_by_type(seat, parent, C_VIEW);
+
+ if (next_focus == NULL && parent->type == C_WORKSPACE) {
+ next_focus = parent;
+ break;
+ }
+
+ parent = parent->parent;
+ }
+
+ // the structure change might have caused it to move up to the top of
+ // the focus stack without sending focus notifications to the view
+ if (seat_get_focus(seat) == next_focus) {
+ seat_send_focus(seat, next_focus);
+ } else {
+ seat_set_focus(seat, next_focus);
+ }
+ }
+}
+
+static struct sway_seat_container *seat_container_from_container(
+ struct sway_seat *seat, struct sway_container *con) {
+ if (con->type == C_ROOT || con->type == C_OUTPUT) {
+ // these don't get seat containers ever
+ return NULL;
+ }
+
+ struct sway_seat_container *seat_con = NULL;
+ wl_list_for_each(seat_con, &seat->focus_stack, link) {
+ if (seat_con->container == con) {
+ return seat_con;
+ }
+ }
+
+ seat_con = calloc(1, sizeof(struct sway_seat_container));
+ if (seat_con == NULL) {
+ wlr_log(L_ERROR, "could not allocate seat container");
+ return NULL;
+ }
+
+ seat_con->container = con;
+ seat_con->seat = seat;
+ wl_list_insert(seat->focus_stack.prev, &seat_con->link);
+ wl_signal_add(&con->events.destroy, &seat_con->destroy);
+ seat_con->destroy.notify = handle_seat_container_destroy;
+
+ return seat_con;
+}
+
+static void handle_new_container(struct wl_listener *listener, void *data) {
+ struct sway_seat *seat = wl_container_of(listener, seat, new_container);
+ struct sway_container *con = data;
+ seat_container_from_container(seat, con);
+}
+
+static void collect_focus_iter(struct sway_container *con, void *data) {
+ struct sway_seat *seat = data;
+ if (con->type > C_WORKSPACE) {
+ return;
+ }
+ struct sway_seat_container *seat_con =
+ seat_container_from_container(seat, con);
+ if (!seat_con) {
+ return;
+ }
+ wl_list_remove(&seat_con->link);
+ wl_list_insert(&seat->focus_stack, &seat_con->link);
+}
+
+struct sway_seat *seat_create(struct sway_input_manager *input,
+ const char *seat_name) {
+ struct sway_seat *seat = calloc(1, sizeof(struct sway_seat));
+ if (!seat) {
+ return NULL;
+ }
+
+ seat->wlr_seat = wlr_seat_create(input->server->wl_display, seat_name);
+ if (!sway_assert(seat->wlr_seat, "could not allocate seat")) {
+ free(seat);
+ return NULL;
+ }
+
+ seat->cursor = sway_cursor_create(seat);
+ if (!seat->cursor) {
+ wlr_seat_destroy(seat->wlr_seat);
+ free(seat);
+ return NULL;
+ }
+
+ // init the focus stack
+ wl_list_init(&seat->focus_stack);
+
+ container_for_each_descendant_dfs(&root_container,
+ collect_focus_iter, seat);
+
+ wl_signal_add(&root_container.sway_root->events.new_container,
+ &seat->new_container);
+ seat->new_container.notify = handle_new_container;
+
+ seat->input = input;
+ wl_list_init(&seat->devices);
+
+ wlr_seat_set_capabilities(seat->wlr_seat,
+ WL_SEAT_CAPABILITY_KEYBOARD |
+ WL_SEAT_CAPABILITY_POINTER |
+ WL_SEAT_CAPABILITY_TOUCH);
+
+ seat_configure_xcursor(seat);
+
+ wl_list_insert(&input->seats, &seat->link);
+
+ return seat;
+}
+
+static void seat_apply_input_config(struct sway_seat *seat,
+ struct sway_seat_device *sway_device) {
+ struct input_config *ic = input_device_get_config(
+ sway_device->input_device);
+ if (!ic) {
+ return;
+ }
+ wlr_log(L_DEBUG, "Applying input config to %s",
+ sway_device->input_device->identifier);
+ if (ic->mapped_output) {
+ struct sway_container *output = NULL;
+ for (int i = 0; i < root_container.children->length; ++i) {
+ struct sway_container *_output = root_container.children->items[i];
+ if (strcasecmp(_output->name, ic->mapped_output) == 0) {
+ output = _output;
+ break;
+ }
+ }
+ if (output) {
+ wlr_cursor_map_input_to_output(seat->cursor->cursor,
+ sway_device->input_device->wlr_device,
+ output->sway_output->wlr_output);
+ wlr_log(L_DEBUG, "Mapped to output %s", output->name);
+ }
+ }
+}
+
+static void seat_configure_pointer(struct sway_seat *seat,
+ struct sway_seat_device *sway_device) {
+ wlr_cursor_attach_input_device(seat->cursor->cursor,
+ sway_device->input_device->wlr_device);
+ seat_apply_input_config(seat, sway_device);
+}
+
+static void seat_configure_keyboard(struct sway_seat *seat,
+ struct sway_seat_device *seat_device) {
+ if (!seat_device->keyboard) {
+ sway_keyboard_create(seat, seat_device);
+ }
+ struct wlr_keyboard *wlr_keyboard =
+ seat_device->input_device->wlr_device->keyboard;
+ sway_keyboard_configure(seat_device->keyboard);
+ wlr_seat_set_keyboard(seat->wlr_seat,
+ seat_device->input_device->wlr_device);
+ struct sway_container *focus = seat_get_focus(seat);
+ if (focus && focus->type == C_VIEW) {
+ // force notify reenter to pick up the new configuration
+ wlr_seat_keyboard_clear_focus(seat->wlr_seat);
+ wlr_seat_keyboard_notify_enter(seat->wlr_seat,
+ focus->sway_view->surface, wlr_keyboard->keycodes,
+ wlr_keyboard->num_keycodes, &wlr_keyboard->modifiers);
+ }
+}
+
+static void seat_configure_tablet_tool(struct sway_seat *seat,
+ struct sway_seat_device *sway_device) {
+ wlr_cursor_attach_input_device(seat->cursor->cursor,
+ sway_device->input_device->wlr_device);
+ seat_apply_input_config(seat, sway_device);
+}
+
+static struct sway_seat_device *seat_get_device(struct sway_seat *seat,
+ struct sway_input_device *input_device) {
+ struct sway_seat_device *seat_device = NULL;
+ wl_list_for_each(seat_device, &seat->devices, link) {
+ if (seat_device->input_device == input_device) {
+ return seat_device;
+ }
+ }
+
+ return NULL;
+}
+
+void seat_configure_device(struct sway_seat *seat,
+ struct sway_input_device *input_device) {
+ struct sway_seat_device *seat_device =
+ seat_get_device(seat, input_device);
+ if (!seat_device) {
+ return;
+ }
+
+ switch (input_device->wlr_device->type) {
+ case WLR_INPUT_DEVICE_POINTER:
+ seat_configure_pointer(seat, seat_device);
+ break;
+ case WLR_INPUT_DEVICE_KEYBOARD:
+ seat_configure_keyboard(seat, seat_device);
+ break;
+ case WLR_INPUT_DEVICE_TABLET_TOOL:
+ seat_configure_tablet_tool(seat, seat_device);
+ break;
+ case WLR_INPUT_DEVICE_TABLET_PAD:
+ case WLR_INPUT_DEVICE_TOUCH:
+ wlr_log(L_DEBUG, "TODO: configure other devices");
+ break;
+ }
+}
+
+void seat_add_device(struct sway_seat *seat,
+ struct sway_input_device *input_device) {
+ if (seat_get_device(seat, input_device)) {
+ seat_configure_device(seat, input_device);
+ return;
+ }
+
+ struct sway_seat_device *seat_device =
+ calloc(1, sizeof(struct sway_seat_device));
+ if (!seat_device) {
+ wlr_log(L_DEBUG, "could not allocate seat device");
+ return;
+ }
+
+ wlr_log(L_DEBUG, "adding device %s to seat %s",
+ input_device->identifier, seat->wlr_seat->name);
+
+ seat_device->sway_seat = seat;
+ seat_device->input_device = input_device;
+ wl_list_insert(&seat->devices, &seat_device->link);
+
+ seat_configure_device(seat, input_device);
+}
+
+void seat_remove_device(struct sway_seat *seat,
+ struct sway_input_device *input_device) {
+ struct sway_seat_device *seat_device =
+ seat_get_device(seat, input_device);
+
+ if (!seat_device) {
+ return;
+ }
+
+ wlr_log(L_DEBUG, "removing device %s from seat %s",
+ input_device->identifier, seat->wlr_seat->name);
+
+ seat_device_destroy(seat_device);
+}
+
+void seat_configure_xcursor(struct sway_seat *seat) {
+ // TODO configure theme and size
+ const char *cursor_theme = NULL;
+
+ if (!seat->cursor->xcursor_manager) {
+ seat->cursor->xcursor_manager =
+ wlr_xcursor_manager_create(cursor_theme, 24);
+ if (sway_assert(seat->cursor->xcursor_manager,
+ "Cannot create XCursor manager for theme %s",
+ cursor_theme)) {
+ return;
+ }
+ }
+
+ for (int i = 0; i < root_container.children->length; ++i) {
+ struct sway_container *output_container =
+ root_container.children->items[i];
+ struct wlr_output *output =
+ output_container->sway_output->wlr_output;
+ bool result =
+ wlr_xcursor_manager_load(seat->cursor->xcursor_manager,
+ output->scale);
+
+ sway_assert(!result,
+ "Cannot load xcursor theme for output '%s' with scale %f",
+ // TODO: Fractional scaling
+ output->name, (double)output->scale);
+ }
+
+ wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager,
+ "left_ptr", seat->cursor->cursor);
+ wlr_cursor_warp(seat->cursor->cursor, NULL, seat->cursor->cursor->x,
+ seat->cursor->cursor->y);
+}
+
+bool seat_is_input_allowed(struct sway_seat *seat,
+ struct wlr_surface *surface) {
+ struct wl_client *client = wl_resource_get_client(surface->resource);
+ return !seat->exclusive_client || seat->exclusive_client == client;
+}
+
+void seat_set_focus_warp(struct sway_seat *seat,
+ struct sway_container *container, bool warp) {
+ if (seat->focused_layer) {
+ return;
+ }
+
+ struct sway_container *last_focus = seat_get_focus(seat);
+ if (container && last_focus == container) {
+ return;
+ }
+
+ if (container) {
+ struct sway_seat_container *seat_con =
+ seat_container_from_container(seat, container);
+ if (seat_con == NULL) {
+ return;
+ }
+
+ // put all the anscestors of this container on top of the focus stack
+ struct sway_seat_container *parent =
+ seat_container_from_container(seat,
+ seat_con->container->parent);
+ while (parent) {
+ wl_list_remove(&parent->link);
+ wl_list_insert(&seat->focus_stack, &parent->link);
+
+ parent =
+ seat_container_from_container(seat,
+ parent->container->parent);
+ }
+
+ wl_list_remove(&seat_con->link);
+ wl_list_insert(&seat->focus_stack, &seat_con->link);
+
+ if (container->type == C_VIEW && !seat_is_input_allowed(
+ seat, container->sway_view->surface)) {
+ wlr_log(L_DEBUG, "Refusing to set focus, input is inhibited");
+ return;
+ }
+
+ if (container->type == C_VIEW) {
+ seat_send_focus(seat, container);
+ }
+ }
+
+ if (last_focus) {
+ struct sway_container *last_ws = last_focus;
+ if (last_ws && last_ws->type != C_WORKSPACE) {
+ last_ws = container_parent(last_ws, C_WORKSPACE);
+ }
+ if (last_ws) {
+ ipc_event_workspace(last_ws, container, "focus");
+ if (!workspace_is_visible(last_ws)
+ && last_ws->children->length == 0) {
+ container_destroy(last_ws);
+ }
+ }
+
+ if (config->mouse_warping && warp) {
+ struct sway_container *last_output = last_focus;
+ if (last_output && last_output->type != C_OUTPUT) {
+ last_output = container_parent(last_output, C_OUTPUT);
+ }
+ struct sway_container *new_output = container;
+ if (new_output && new_output->type != C_OUTPUT) {
+ new_output = container_parent(new_output, C_OUTPUT);
+ }
+ if (new_output && last_output && new_output != last_output) {
+ double x = new_output->x + container->x +
+ container->width / 2.0;
+ double y = new_output->y + container->y +
+ container->height / 2.0;
+ struct wlr_output *wlr_output =
+ new_output->sway_output->wlr_output;
+ if (!wlr_output_layout_contains_point(
+ root_container.sway_root->output_layout,
+ wlr_output, seat->cursor->cursor->x,
+ seat->cursor->cursor->y)) {
+ wlr_cursor_warp(seat->cursor->cursor, NULL, x, y);
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ cursor_send_pointer_motion(seat->cursor, now.tv_nsec / 1000);
+ }
+ }
+ }
+ }
+
+ if (last_focus && last_focus->type == C_VIEW &&
+ !input_manager_has_focus(seat->input, last_focus)) {
+ struct sway_view *view = last_focus->sway_view;
+ view_set_activated(view, false);
+ }
+
+ seat->has_focus = (container != NULL);
+
+ update_debug_tree();
+}
+
+void seat_set_focus(struct sway_seat *seat,
+ struct sway_container *container) {
+ seat_set_focus_warp(seat, container, true);
+}
+
+void seat_set_focus_surface(struct sway_seat *seat,
+ struct wlr_surface *surface) {
+ if (seat->focused_layer != NULL) {
+ return;
+ }
+ if (seat->has_focus) {
+ struct sway_container *focus = seat_get_focus(seat);
+ if (focus->type == C_VIEW) {
+ wlr_seat_keyboard_clear_focus(seat->wlr_seat);
+ view_set_activated(focus->sway_view, false);
+ }
+ seat->has_focus = false;
+ }
+ struct wlr_keyboard *keyboard =
+ wlr_seat_get_keyboard(seat->wlr_seat);
+ if (keyboard) {
+ wlr_seat_keyboard_notify_enter(seat->wlr_seat, surface,
+ keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers);
+ } else {
+ wlr_seat_keyboard_notify_enter(seat->wlr_seat, surface, NULL, 0, NULL);
+ }
+}
+
+void seat_set_focus_layer(struct sway_seat *seat,
+ struct wlr_layer_surface *layer) {
+ if (!layer && seat->focused_layer) {
+ seat->focused_layer = NULL;
+ struct sway_container *previous = seat_get_focus(seat);
+ if (previous) {
+ wlr_log(L_DEBUG, "Returning focus to %p %s '%s'", previous,
+ container_type_to_str(previous->type), previous->name);
+ // Hack to get seat to re-focus the return value of get_focus
+ seat_set_focus(seat, previous->parent);
+ seat_set_focus(seat, previous);
+ }
+ return;
+ } else if (!layer || seat->focused_layer == layer) {
+ return;
+ }
+ seat_set_focus_surface(seat, layer->surface);
+ if (layer->layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) {
+ seat->focused_layer = layer;
+ }
+}
+
+void seat_set_exclusive_client(struct sway_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
+ for (int i = 0; i < root_container.children->length; ++i) {
+ struct sway_container *output = root_container.children->items[i];
+ if (!sway_assert(output->type == C_OUTPUT,
+ "root container has non-output child")) {
+ continue;
+ }
+ arrange_layers(output->sway_output);
+ }
+ return;
+ }
+ if (seat->focused_layer) {
+ if (wl_resource_get_client(seat->focused_layer->resource) != client) {
+ seat_set_focus_layer(seat, NULL);
+ }
+ }
+ if (seat->has_focus) {
+ struct sway_container *focus = seat_get_focus(seat);
+ if (focus->type == C_VIEW && wl_resource_get_client(
+ focus->sway_view->surface->resource) != client) {
+ seat_set_focus(seat, NULL);
+ }
+ }
+ if (seat->wlr_seat->pointer_state.focused_client) {
+ if (seat->wlr_seat->pointer_state.focused_client->client != client) {
+ wlr_seat_pointer_clear_focus(seat->wlr_seat);
+ }
+ }
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ struct wlr_touch_point *point;
+ wl_list_for_each(point, &seat->wlr_seat->touch_state.touch_points, link) {
+ if (point->client->client != client) {
+ wlr_seat_touch_point_clear_focus(seat->wlr_seat,
+ now.tv_nsec / 1000, point->touch_id);
+ }
+ }
+ seat->exclusive_client = client;
+}
+
+struct sway_container *seat_get_focus_inactive(struct sway_seat *seat,
+ struct sway_container *container) {
+ return seat_get_focus_by_type(seat, container, C_TYPES);
+}
+
+struct sway_container *sway_seat_get_focus(struct sway_seat *seat) {
+ if (!seat->has_focus) {
+ return NULL;
+ }
+ return seat_get_focus_inactive(seat, &root_container);
+}
+
+struct sway_container *seat_get_focus(struct sway_seat *seat) {
+ if (!seat->has_focus) {
+ return NULL;
+ }
+ return seat_get_focus_inactive(seat, &root_container);
+}
+
+void seat_apply_config(struct sway_seat *seat,
+ struct seat_config *seat_config) {
+ struct sway_seat_device *seat_device = NULL;
+
+ if (!seat_config) {
+ return;
+ }
+
+ wl_list_for_each(seat_device, &seat->devices, link) {
+ seat_configure_device(seat, seat_device->input_device);
+ }
+}
+
+struct seat_config *seat_get_config(struct sway_seat *seat) {
+ struct seat_config *seat_config = NULL;
+ for (int i = 0; i < config->seat_configs->length; ++i ) {
+ seat_config = config->seat_configs->items[i];
+ if (strcmp(seat->wlr_seat->name, seat_config->name) == 0) {
+ return seat_config;
+ }
+ }
+
+ return NULL;
+}