/* * Cage: A Wayland kiosk. * * Copyright (C) 2018-2020 Jente Hidskes * * See the LICENSE file accompanying this file. */ #define _POSIX_C_SOURCE 200809L #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if CAGE_HAS_XWAYLAND #include #endif #include "output.h" #include "seat.h" #include "server.h" #include "view.h" #if CAGE_HAS_XWAYLAND #include "xwayland.h" #endif static void drag_icon_update_position(struct cg_drag_icon *drag_icon); /* XDG toplevels may have nested surfaces, such as popup windows for context * menus or tooltips. This function tests if any of those are underneath the * coordinates lx and ly (in output Layout Coordinates). If so, it sets the * surface pointer to that wlr_surface and the sx and sy coordinates to the * coordinates relative to that surface's top-left corner. * * This function iterates over all of our surfaces and attempts to find one * under the cursor. If desktop_view_at returns a view, there is also a * surface. There cannot be a surface without a view, either. It's both or * nothing. */ static struct cg_view * desktop_view_at(struct cg_server *server, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) { struct wlr_scene_node *node = wlr_scene_node_at(&server->scene->tree.node, lx, ly, sx, sy); if (node == NULL || node->type != WLR_SCENE_NODE_BUFFER) { return NULL; } struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(scene_buffer); if (!scene_surface) { return NULL; } *surface = scene_surface->surface; /* Walk up the tree until we find a node with a data pointer. When done, * we've found the node representing the view. */ while (!node->data) { if (!node->parent) { node = NULL; break; } node = &node->parent->node; } assert(node != NULL); return node->data; } static void press_cursor_button(struct cg_seat *seat, struct wlr_input_device *device, uint32_t time, uint32_t button, uint32_t state, double lx, double ly) { struct cg_server *server = seat->server; if (state == WLR_BUTTON_PRESSED) { double sx, sy; struct wlr_surface *surface; struct cg_view *view = desktop_view_at(server, lx, ly, &surface, &sx, &sy); struct cg_view *current = seat_get_focus(seat); if (view == current) { return; } /* Focus that client if the button was pressed and it has no open dialogs. */ if (view && !view_is_transient_for(current, view)) { seat_set_focus(seat, view); } } } static void update_capabilities(struct cg_seat *seat) { uint32_t caps = 0; if (!wl_list_empty(&seat->keyboard_groups)) { caps |= WL_SEAT_CAPABILITY_KEYBOARD; } if (!wl_list_empty(&seat->pointers)) { caps |= WL_SEAT_CAPABILITY_POINTER; } if (!wl_list_empty(&seat->touch)) { caps |= WL_SEAT_CAPABILITY_TOUCH; } wlr_seat_set_capabilities(seat->seat, caps); /* Hide cursor if the seat doesn't have pointer capability. */ if ((caps & WL_SEAT_CAPABILITY_POINTER) == 0) { wlr_cursor_unset_image(seat->cursor); } else { wlr_cursor_set_xcursor(seat->cursor, seat->xcursor_manager, DEFAULT_XCURSOR); } } static void map_input_device_to_output(struct cg_seat *seat, struct wlr_input_device *device, const char *output_name) { if (!output_name) { wlr_log(WLR_INFO, "Input device %s cannot be mapped to an output device\n", device->name); return; } struct cg_output *output; wl_list_for_each (output, &seat->server->outputs, link) { if (strcmp(output_name, output->wlr_output->name) == 0) { wlr_log(WLR_INFO, "Mapping input device %s to output device %s\n", device->name, output->wlr_output->name); wlr_cursor_map_input_to_output(seat->cursor, device, output->wlr_output); return; } } wlr_log(WLR_INFO, "Couldn't map input device %s to an output\n", device->name); } static void handle_touch_destroy(struct wl_listener *listener, void *data) { struct cg_touch *touch = wl_container_of(listener, touch, destroy); struct cg_seat *seat = touch->seat; wl_list_remove(&touch->link); wlr_cursor_detach_input_device(seat->cursor, &touch->touch->base); wl_list_remove(&touch->destroy.link); free(touch); update_capabilities(seat); } static void handle_new_touch(struct cg_seat *seat, struct wlr_touch *wlr_touch) { struct cg_touch *touch = calloc(1, sizeof(struct cg_touch)); if (!touch) { wlr_log(WLR_ERROR, "Cannot allocate touch"); return; } touch->seat = seat; touch->touch = wlr_touch; wlr_cursor_attach_input_device(seat->cursor, &wlr_touch->base); wl_list_insert(&seat->touch, &touch->link); touch->destroy.notify = handle_touch_destroy; wl_signal_add(&wlr_touch->base.events.destroy, &touch->destroy); map_input_device_to_output(seat, &wlr_touch->base, wlr_touch->output_name); } static void handle_pointer_destroy(struct wl_listener *listener, void *data) { struct cg_pointer *pointer = wl_container_of(listener, pointer, destroy); struct cg_seat *seat = pointer->seat; wl_list_remove(&pointer->link); wlr_cursor_detach_input_device(seat->cursor, &pointer->pointer->base); wl_list_remove(&pointer->destroy.link); free(pointer); update_capabilities(seat); } static void handle_new_pointer(struct cg_seat *seat, struct wlr_pointer *wlr_pointer) { struct cg_pointer *pointer = calloc(1, sizeof(struct cg_pointer)); if (!pointer) { wlr_log(WLR_ERROR, "Cannot allocate pointer"); return; } pointer->seat = seat; pointer->pointer = wlr_pointer; wlr_cursor_attach_input_device(seat->cursor, &wlr_pointer->base); wl_list_insert(&seat->pointers, &pointer->link); pointer->destroy.notify = handle_pointer_destroy; wl_signal_add(&wlr_pointer->base.events.destroy, &pointer->destroy); map_input_device_to_output(seat, &wlr_pointer->base, wlr_pointer->output_name); } static void handle_virtual_pointer(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, new_virtual_pointer); struct cg_seat *seat = server->seat; struct wlr_virtual_pointer_v1_new_pointer_event *event = data; struct wlr_virtual_pointer_v1 *pointer = event->new_pointer; struct wlr_pointer *wlr_pointer = &pointer->pointer; /* We'll want to map the device back to an output later, this is a bit * sub-optimal (we could just keep the suggested_output), but just copy * its name so we do like other devices */ if (event->suggested_output != NULL) { wlr_pointer->output_name = strdup(event->suggested_output->name); } /* TODO: event->suggested_seat should be checked if we handle multiple seats */ handle_new_pointer(seat, wlr_pointer); update_capabilities(seat); } static void handle_modifier_event(struct wlr_keyboard *keyboard, struct cg_seat *seat) { wlr_seat_set_keyboard(seat->seat, keyboard); wlr_seat_keyboard_notify_modifiers(seat->seat, &keyboard->modifiers); wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static bool handle_keybinding(struct cg_server *server, xkb_keysym_t sym) { #ifdef DEBUG if (sym == XKB_KEY_Escape) { server_terminate(server); return true; } #endif if (server->allow_vt_switch && sym >= XKB_KEY_XF86Switch_VT_1 && sym <= XKB_KEY_XF86Switch_VT_12) { if (wlr_backend_is_multi(server->backend)) { if (server->session) { unsigned vt = sym - XKB_KEY_XF86Switch_VT_1 + 1; wlr_session_change_vt(server->session, vt); } } } else { return false; } wlr_idle_notifier_v1_notify_activity(server->idle, server->seat->seat); return true; } static void handle_key_event(struct wlr_keyboard *keyboard, struct cg_seat *seat, void *data) { struct wlr_keyboard_key_event *event = data; /* Translate from libinput keycode to an xkbcommon keycode. */ xkb_keycode_t keycode = event->keycode + 8; const xkb_keysym_t *syms; int nsyms = xkb_state_key_get_syms(keyboard->xkb_state, keycode, &syms); bool handled = false; uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard); if ((modifiers & WLR_MODIFIER_ALT) && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { /* If Alt is held down and this button was pressed, we * attempt to process it as a compositor * keybinding. */ for (int i = 0; i < nsyms; i++) { handled = handle_keybinding(seat->server, syms[i]); } } if (!handled) { /* Otherwise, we pass it along to the client. */ wlr_seat_set_keyboard(seat->seat, keyboard); wlr_seat_keyboard_notify_key(seat->seat, event->time_msec, event->keycode, event->state); } wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void handle_keyboard_group_key(struct wl_listener *listener, void *data) { struct cg_keyboard_group *cg_group = wl_container_of(listener, cg_group, key); handle_key_event(&cg_group->wlr_group->keyboard, cg_group->seat, data); } static void handle_keyboard_group_modifiers(struct wl_listener *listener, void *data) { struct cg_keyboard_group *group = wl_container_of(listener, group, modifiers); handle_modifier_event(&group->wlr_group->keyboard, group->seat); } static void cg_keyboard_group_add(struct wlr_keyboard *keyboard, struct cg_seat *seat, bool virtual) { /* We apparently should not group virtual keyboards, * so create a new group with it */ if (!virtual) { struct cg_keyboard_group *group; wl_list_for_each (group, &seat->keyboard_groups, link) { if (group->is_virtual) continue; struct wlr_keyboard_group *wlr_group = group->wlr_group; if (wlr_keyboard_group_add_keyboard(wlr_group, keyboard)) { wlr_log(WLR_DEBUG, "Added new keyboard to existing group"); return; } } } /* This is reached if and only if the keyboard could not be inserted into * any group */ struct cg_keyboard_group *cg_group = calloc(1, sizeof(struct cg_keyboard_group)); if (cg_group == NULL) { wlr_log(WLR_ERROR, "Failed to allocate keyboard group."); return; } cg_group->seat = seat; cg_group->is_virtual = virtual; cg_group->wlr_group = wlr_keyboard_group_create(); if (cg_group->wlr_group == NULL) { wlr_log(WLR_ERROR, "Failed to create wlr keyboard group."); goto cleanup; } cg_group->wlr_group->data = cg_group; wlr_keyboard_set_keymap(&cg_group->wlr_group->keyboard, keyboard->keymap); wlr_keyboard_set_repeat_info(&cg_group->wlr_group->keyboard, keyboard->repeat_info.rate, keyboard->repeat_info.delay); wlr_log(WLR_DEBUG, "Created keyboard group"); wlr_keyboard_group_add_keyboard(cg_group->wlr_group, keyboard); wl_list_insert(&seat->keyboard_groups, &cg_group->link); wl_signal_add(&cg_group->wlr_group->keyboard.events.key, &cg_group->key); cg_group->key.notify = handle_keyboard_group_key; wl_signal_add(&cg_group->wlr_group->keyboard.events.modifiers, &cg_group->modifiers); cg_group->modifiers.notify = handle_keyboard_group_modifiers; return; cleanup: if (cg_group && cg_group->wlr_group) { wlr_keyboard_group_destroy(cg_group->wlr_group); } free(cg_group); } static void keyboard_group_destroy(struct cg_keyboard_group *keyboard_group) { wl_list_remove(&keyboard_group->key.link); wl_list_remove(&keyboard_group->modifiers.link); wlr_keyboard_group_destroy(keyboard_group->wlr_group); wl_list_remove(&keyboard_group->link); free(keyboard_group); } static void handle_new_keyboard(struct cg_seat *seat, struct wlr_keyboard *keyboard, bool virtual) { struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!context) { wlr_log(WLR_ERROR, "Unable to create XKB context"); return; } struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS); if (!keymap) { wlr_log(WLR_ERROR, "Unable to configure keyboard: keymap does not exist"); xkb_context_unref(context); return; } wlr_keyboard_set_keymap(keyboard, keymap); xkb_keymap_unref(keymap); xkb_context_unref(context); wlr_keyboard_set_repeat_info(keyboard, 25, 600); cg_keyboard_group_add(keyboard, seat, virtual); wlr_seat_set_keyboard(seat->seat, keyboard); } static void handle_virtual_keyboard(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, new_virtual_keyboard); struct cg_seat *seat = server->seat; struct wlr_virtual_keyboard_v1 *keyboard = data; struct wlr_keyboard *wlr_keyboard = &keyboard->keyboard; /* TODO: If multiple seats are supported, check keyboard->seat * to select the appropriate one */ handle_new_keyboard(seat, wlr_keyboard, true); update_capabilities(seat); } static void handle_new_input(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, new_input); struct wlr_input_device *device = data; switch (device->type) { case WLR_INPUT_DEVICE_KEYBOARD: handle_new_keyboard(seat, wlr_keyboard_from_input_device(device), false); break; case WLR_INPUT_DEVICE_POINTER: handle_new_pointer(seat, wlr_pointer_from_input_device(device)); break; case WLR_INPUT_DEVICE_TOUCH: handle_new_touch(seat, wlr_touch_from_input_device(device)); break; case WLR_INPUT_DEVICE_SWITCH: wlr_log(WLR_DEBUG, "Switch input is not implemented"); return; case WLR_INPUT_DEVICE_TABLET: case WLR_INPUT_DEVICE_TABLET_PAD: wlr_log(WLR_DEBUG, "Tablet input is not implemented"); return; } update_capabilities(seat); } static void handle_request_set_primary_selection(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, request_set_primary_selection); struct wlr_seat_request_set_primary_selection_event *event = data; wlr_seat_set_primary_selection(seat->seat, event->source, event->serial); } static void handle_request_set_selection(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, request_set_selection); struct wlr_seat_request_set_selection_event *event = data; wlr_seat_set_selection(seat->seat, event->source, event->serial); } static void handle_request_set_cursor(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, request_set_cursor); struct wlr_seat_pointer_request_set_cursor_event *event = data; struct wlr_surface *focused_surface = event->seat_client->seat->pointer_state.focused_surface; bool has_focused = focused_surface != NULL && focused_surface->resource != NULL; struct wl_client *focused_client = NULL; if (has_focused) { focused_client = wl_resource_get_client(focused_surface->resource); } /* This can be sent by any client, so we check to make sure * this one actually has pointer focus first. */ if (focused_client == event->seat_client->client) { wlr_cursor_set_surface(seat->cursor, event->surface, event->hotspot_x, event->hotspot_y); } } static void handle_touch_down(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, touch_down); struct wlr_touch_down_event *event = data; double lx, ly; wlr_cursor_absolute_to_layout_coords(seat->cursor, &event->touch->base, event->x, event->y, &lx, &ly); double sx, sy; struct wlr_surface *surface; struct cg_view *view = desktop_view_at(seat->server, lx, ly, &surface, &sx, &sy); uint32_t serial = 0; if (view) { serial = wlr_seat_touch_notify_down(seat->seat, surface, event->time_msec, event->touch_id, sx, sy); } if (serial && wlr_seat_touch_num_points(seat->seat) == 1) { seat->touch_id = event->touch_id; seat->touch_lx = lx; seat->touch_ly = ly; press_cursor_button(seat, &event->touch->base, event->time_msec, BTN_LEFT, WLR_BUTTON_PRESSED, lx, ly); } wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void handle_touch_up(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, touch_up); struct wlr_touch_up_event *event = data; if (!wlr_seat_touch_get_point(seat->seat, event->touch_id)) { return; } if (wlr_seat_touch_num_points(seat->seat) == 1) { press_cursor_button(seat, &event->touch->base, event->time_msec, BTN_LEFT, WLR_BUTTON_RELEASED, seat->touch_lx, seat->touch_ly); } wlr_seat_touch_notify_up(seat->seat, event->time_msec, event->touch_id); wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void handle_touch_motion(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, touch_motion); struct wlr_touch_motion_event *event = data; if (!wlr_seat_touch_get_point(seat->seat, event->touch_id)) { return; } double lx, ly; wlr_cursor_absolute_to_layout_coords(seat->cursor, &event->touch->base, event->x, event->y, &lx, &ly); double sx, sy; struct wlr_surface *surface; struct cg_view *view = desktop_view_at(seat->server, lx, ly, &surface, &sx, &sy); if (view) { wlr_seat_touch_point_focus(seat->seat, surface, event->time_msec, event->touch_id, sx, sy); wlr_seat_touch_notify_motion(seat->seat, event->time_msec, event->touch_id, sx, sy); } else { wlr_seat_touch_point_clear_focus(seat->seat, event->time_msec, event->touch_id); } if (event->touch_id == seat->touch_id) { seat->touch_lx = lx; seat->touch_ly = ly; } wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void handle_touch_frame(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, touch_frame); wlr_seat_touch_notify_frame(seat->seat); wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void handle_cursor_frame(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, cursor_frame); wlr_seat_pointer_notify_frame(seat->seat); wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void handle_cursor_axis(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, cursor_axis); struct wlr_pointer_axis_event *event = data; wlr_seat_pointer_notify_axis(seat->seat, event->time_msec, event->orientation, event->delta, event->delta_discrete, event->source, event->relative_direction); wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void handle_cursor_button(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, cursor_button); struct wlr_pointer_button_event *event = data; wlr_seat_pointer_notify_button(seat->seat, event->time_msec, event->button, event->state); press_cursor_button(seat, &event->pointer->base, event->time_msec, event->button, event->state, seat->cursor->x, seat->cursor->y); wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void process_cursor_motion(struct cg_seat *seat, uint32_t time_msec, double dx, double dy, double dx_unaccel, double dy_unaccel) { double sx, sy; struct wlr_seat *wlr_seat = seat->seat; struct wlr_surface *surface = NULL; struct cg_view *view = desktop_view_at(seat->server, seat->cursor->x, seat->cursor->y, &surface, &sx, &sy); if (!view) { wlr_seat_pointer_clear_focus(wlr_seat); } else { wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy); wlr_seat_pointer_notify_motion(wlr_seat, time_msec, sx, sy); } if (dx != 0 || dy != 0) { wlr_relative_pointer_manager_v1_send_relative_motion(seat->server->relative_pointer_manager, wlr_seat, (uint64_t) time_msec * 1000, dx, dy, dx_unaccel, dy_unaccel); } struct cg_drag_icon *drag_icon; wl_list_for_each (drag_icon, &seat->drag_icons, link) { drag_icon_update_position(drag_icon); } wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void handle_cursor_motion_absolute(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, cursor_motion_absolute); struct wlr_pointer_motion_absolute_event *event = data; double lx, ly; wlr_cursor_absolute_to_layout_coords(seat->cursor, &event->pointer->base, event->x, event->y, &lx, &ly); double dx = lx - seat->cursor->x; double dy = ly - seat->cursor->y; wlr_cursor_warp_absolute(seat->cursor, &event->pointer->base, event->x, event->y); process_cursor_motion(seat, event->time_msec, dx, dy, dx, dy); wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void handle_cursor_motion_relative(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, cursor_motion_relative); struct wlr_pointer_motion_event *event = data; wlr_cursor_move(seat->cursor, &event->pointer->base, event->delta_x, event->delta_y); process_cursor_motion(seat, event->time_msec, event->delta_x, event->delta_y, event->unaccel_dx, event->unaccel_dy); wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void drag_icon_update_position(struct cg_drag_icon *drag_icon) { struct wlr_drag_icon *wlr_icon = drag_icon->wlr_drag_icon; struct cg_seat *seat = drag_icon->seat; struct wlr_touch_point *point; switch (wlr_icon->drag->grab_type) { case WLR_DRAG_GRAB_KEYBOARD: return; case WLR_DRAG_GRAB_KEYBOARD_POINTER: drag_icon->lx = seat->cursor->x; drag_icon->ly = seat->cursor->y; break; case WLR_DRAG_GRAB_KEYBOARD_TOUCH: point = wlr_seat_touch_get_point(seat->seat, wlr_icon->drag->touch_id); if (!point) { return; } drag_icon->lx = seat->touch_lx; drag_icon->ly = seat->touch_ly; break; } wlr_scene_node_set_position(&drag_icon->scene_tree->node, drag_icon->lx, drag_icon->ly); } static void handle_drag_icon_destroy(struct wl_listener *listener, void *data) { struct cg_drag_icon *drag_icon = wl_container_of(listener, drag_icon, destroy); wl_list_remove(&drag_icon->link); wl_list_remove(&drag_icon->destroy.link); wlr_scene_node_destroy(&drag_icon->scene_tree->node); free(drag_icon); } static void handle_request_start_drag(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, request_start_drag); struct wlr_seat_request_start_drag_event *event = data; if (wlr_seat_validate_pointer_grab_serial(seat->seat, event->origin, event->serial)) { wlr_seat_start_pointer_drag(seat->seat, event->drag, event->serial); return; } struct wlr_touch_point *point; if (wlr_seat_validate_touch_grab_serial(seat->seat, event->origin, event->serial, &point)) { wlr_seat_start_touch_drag(seat->seat, event->drag, event->serial, point); return; } // TODO: tablet grabs wlr_log(WLR_DEBUG, "Ignoring start_drag request: could not validate pointer/touch serial %" PRIu32, event->serial); wlr_data_source_destroy(event->drag->source); } static void handle_start_drag(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, start_drag); struct wlr_drag *wlr_drag = data; struct wlr_drag_icon *wlr_drag_icon = wlr_drag->icon; if (wlr_drag_icon == NULL) { return; } struct cg_drag_icon *drag_icon = calloc(1, sizeof(struct cg_drag_icon)); if (!drag_icon) { return; } drag_icon->seat = seat; drag_icon->wlr_drag_icon = wlr_drag_icon; drag_icon->scene_tree = wlr_scene_subsurface_tree_create(&seat->server->scene->tree, wlr_drag_icon->surface); if (!drag_icon->scene_tree) { free(drag_icon); return; } drag_icon->destroy.notify = handle_drag_icon_destroy; wl_signal_add(&wlr_drag_icon->events.destroy, &drag_icon->destroy); wl_list_insert(&seat->drag_icons, &drag_icon->link); drag_icon_update_position(drag_icon); } static void handle_destroy(struct wl_listener *listener, void *data) { struct cg_seat *seat = wl_container_of(listener, seat, destroy); wl_list_remove(&seat->destroy.link); wl_list_remove(&seat->cursor_motion_relative.link); wl_list_remove(&seat->cursor_motion_absolute.link); wl_list_remove(&seat->cursor_button.link); wl_list_remove(&seat->cursor_axis.link); wl_list_remove(&seat->cursor_frame.link); wl_list_remove(&seat->touch_down.link); wl_list_remove(&seat->touch_up.link); wl_list_remove(&seat->touch_motion.link); wl_list_remove(&seat->touch_frame.link); wl_list_remove(&seat->request_set_cursor.link); wl_list_remove(&seat->request_set_selection.link); wl_list_remove(&seat->request_set_primary_selection.link); struct cg_keyboard_group *group, *group_tmp; wl_list_for_each_safe (group, group_tmp, &seat->keyboard_groups, link) { wlr_keyboard_group_destroy(group->wlr_group); free(group); } struct cg_pointer *pointer, *pointer_tmp; wl_list_for_each_safe (pointer, pointer_tmp, &seat->pointers, link) { handle_pointer_destroy(&pointer->destroy, NULL); } struct cg_touch *touch, *touch_tmp; wl_list_for_each_safe (touch, touch_tmp, &seat->touch, link) { handle_touch_destroy(&touch->destroy, NULL); } wl_list_remove(&seat->new_input.link); wlr_xcursor_manager_destroy(seat->xcursor_manager); if (seat->cursor) { wlr_cursor_destroy(seat->cursor); } free(seat); } struct cg_seat * seat_create(struct cg_server *server, struct wlr_backend *backend) { struct cg_seat *seat = calloc(1, sizeof(struct cg_seat)); if (!seat) { wlr_log(WLR_ERROR, "Cannot allocate seat"); return NULL; } seat->seat = wlr_seat_create(server->wl_display, "seat0"); if (!seat->seat) { wlr_log(WLR_ERROR, "Cannot allocate seat0"); free(seat); return NULL; } seat->server = server; seat->destroy.notify = handle_destroy; wl_signal_add(&seat->seat->events.destroy, &seat->destroy); seat->cursor = wlr_cursor_create(); if (!seat->cursor) { wlr_log(WLR_ERROR, "Unable to create cursor"); wl_list_remove(&seat->destroy.link); free(seat); return NULL; } wlr_cursor_attach_output_layout(seat->cursor, server->output_layout); if (!seat->xcursor_manager) { seat->xcursor_manager = wlr_xcursor_manager_create(NULL, XCURSOR_SIZE); if (!seat->xcursor_manager) { wlr_log(WLR_ERROR, "Cannot create XCursor manager"); wlr_cursor_destroy(seat->cursor); wl_list_remove(&seat->destroy.link); free(seat); return NULL; } } seat->cursor_motion_relative.notify = handle_cursor_motion_relative; wl_signal_add(&seat->cursor->events.motion, &seat->cursor_motion_relative); seat->cursor_motion_absolute.notify = handle_cursor_motion_absolute; wl_signal_add(&seat->cursor->events.motion_absolute, &seat->cursor_motion_absolute); seat->cursor_button.notify = handle_cursor_button; wl_signal_add(&seat->cursor->events.button, &seat->cursor_button); seat->cursor_axis.notify = handle_cursor_axis; wl_signal_add(&seat->cursor->events.axis, &seat->cursor_axis); seat->cursor_frame.notify = handle_cursor_frame; wl_signal_add(&seat->cursor->events.frame, &seat->cursor_frame); seat->touch_down.notify = handle_touch_down; wl_signal_add(&seat->cursor->events.touch_down, &seat->touch_down); seat->touch_up.notify = handle_touch_up; wl_signal_add(&seat->cursor->events.touch_up, &seat->touch_up); seat->touch_motion.notify = handle_touch_motion; wl_signal_add(&seat->cursor->events.touch_motion, &seat->touch_motion); seat->touch_frame.notify = handle_touch_frame; wl_signal_add(&seat->cursor->events.touch_frame, &seat->touch_frame); seat->request_set_cursor.notify = handle_request_set_cursor; wl_signal_add(&seat->seat->events.request_set_cursor, &seat->request_set_cursor); seat->request_set_selection.notify = handle_request_set_selection; wl_signal_add(&seat->seat->events.request_set_selection, &seat->request_set_selection); seat->request_set_primary_selection.notify = handle_request_set_primary_selection; wl_signal_add(&seat->seat->events.request_set_primary_selection, &seat->request_set_primary_selection); wl_list_init(&seat->keyboards); wl_list_init(&seat->keyboard_groups); wl_list_init(&seat->pointers); wl_list_init(&seat->touch); seat->new_input.notify = handle_new_input; wl_signal_add(&backend->events.new_input, &seat->new_input); server->new_virtual_keyboard.notify = handle_virtual_keyboard; server->new_virtual_pointer.notify = handle_virtual_pointer; wl_list_init(&seat->drag_icons); seat->request_start_drag.notify = handle_request_start_drag; wl_signal_add(&seat->seat->events.request_start_drag, &seat->request_start_drag); seat->start_drag.notify = handle_start_drag; wl_signal_add(&seat->seat->events.start_drag, &seat->start_drag); return seat; } void seat_destroy(struct cg_seat *seat) { if (!seat) { return; } wl_list_remove(&seat->request_start_drag.link); wl_list_remove(&seat->start_drag.link); struct cg_keyboard_group *keyboard_group, *keyboard_group_tmp; wl_list_for_each_safe (keyboard_group, keyboard_group_tmp, &seat->keyboard_groups, link) { keyboard_group_destroy(keyboard_group); } // Destroying the wlr seat will trigger the destroy handler on our seat, // which will in turn free it. wlr_seat_destroy(seat->seat); } struct cg_view * seat_get_focus(struct cg_seat *seat) { struct wlr_surface *prev_surface = seat->seat->keyboard_state.focused_surface; if (!prev_surface) { return NULL; } return view_from_wlr_surface(prev_surface); } void seat_set_focus(struct cg_seat *seat, struct cg_view *view) { struct cg_server *server = seat->server; struct wlr_seat *wlr_seat = seat->seat; struct cg_view *prev_view = seat_get_focus(seat); if (!view || prev_view == view) { return; } #if CAGE_HAS_XWAYLAND if (view->type == CAGE_XWAYLAND_VIEW) { struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view); if (!wlr_xwayland_surface_override_redirect_wants_focus(xwayland_view->xwayland_surface)) { return; } } #endif if (prev_view) { view_activate(prev_view, false); } /* Move the view to the front, but only if it isn't a fullscreen view. */ if (!view_is_primary(view)) { wl_list_remove(&view->link); wl_list_insert(&server->views, &view->link); } view_activate(view, true); char *title = view_get_title(view); struct cg_output *output; wl_list_for_each (output, &server->outputs, link) { output_set_window_title(output, title); } free(title); struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(wlr_seat); if (keyboard) { wlr_seat_keyboard_notify_enter(wlr_seat, view->wlr_surface, keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); } else { wlr_seat_keyboard_notify_enter(wlr_seat, view->wlr_surface, NULL, 0, NULL); } process_cursor_motion(seat, -1, 0, 0, 0, 0); } void seat_center_cursor(struct cg_seat *seat) { /* Place the cursor in the center of the output layout. */ struct wlr_box layout_box; wlr_output_layout_get_box(seat->server->output_layout, NULL, &layout_box); wlr_cursor_warp(seat->cursor, NULL, layout_box.width / 2, layout_box.height / 2); }