diff options
Diffstat (limited to 'sway/input')
-rw-r--r-- | sway/input/cursor.c | 384 | ||||
-rw-r--r-- | sway/input/input-manager.c | 445 | ||||
-rw-r--r-- | sway/input/keyboard.c | 504 | ||||
-rw-r--r-- | sway/input/seat.c | 675 |
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; +} |