// Copyright 2020 - 2025, project-repo and the NEDM contributors // SPDX-License-Identifier: MIT #define _POSIX_C_SOURCE 200812L #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if NEDM_HAS_XWAYLAND #include #endif #include "input_manager.h" #include "keybinding.h" #include "message.h" #include "output.h" #include "seat.h" #include "server.h" #include "view.h" #include "workspace.h" #if NEDM_HAS_XWAYLAND #include "xwayland.h" #endif static void drag_icon_update_position(struct nedm_drag_icon *drag_icon); static void update_capabilities(const struct nedm_seat *seat) { uint32_t caps = 0; if(seat->num_keyboards > 0) { caps |= WL_SEAT_CAPABILITY_KEYBOARD; } if(seat->num_pointers > 0) { caps |= WL_SEAT_CAPABILITY_POINTER; } if(seat->num_touch > 0) { 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) || seat->enable_cursor == false) { wlr_cursor_unset_image(seat->cursor); } else { wlr_cursor_set_xcursor(seat->cursor, seat->xcursor_manager, DEFAULT_XCURSOR); } } static void remove_touch(struct nedm_seat *seat, struct nedm_touch *touch) { if(!touch) { return; } wlr_cursor_detach_input_device(seat->cursor, touch->device->wlr_device); --seat->num_touch; free(touch); } static void new_touch(struct nedm_seat *seat, struct nedm_input_device *input_device) { struct wlr_input_device *device = input_device->wlr_device; struct wlr_touch *wlr_touch = wlr_touch_from_input_device(device); struct nedm_touch *touch = calloc(1, sizeof(struct nedm_touch)); if(!touch) { wlr_log(WLR_ERROR, "Cannot allocate touch"); return; } touch->seat = seat; touch->device = input_device; wlr_cursor_attach_input_device(seat->cursor, device); input_device->touch = touch; ++seat->num_touch; if(wlr_touch->output_name != NULL) { struct nedm_output *output; wl_list_for_each(output, &seat->server->outputs, link) { if(strcmp(wlr_touch->output_name, output->name) == 0) { wlr_cursor_map_input_to_output(seat->cursor, device, output->wlr_output); break; } } } } static void remove_pointer(struct nedm_seat *seat, struct nedm_pointer *pointer) { if(!pointer) { return; } wlr_cursor_detach_input_device(seat->cursor, pointer->device->wlr_device); --seat->num_pointers; free(pointer); } static void new_pointer(struct nedm_seat *seat, struct nedm_input_device *input_device) { struct wlr_input_device *device = input_device->wlr_device; struct wlr_pointer *wlr_pointer = wlr_pointer_from_input_device(device); struct nedm_pointer *pointer = calloc(1, sizeof(struct nedm_pointer)); if(!pointer) { wlr_log(WLR_ERROR, "Cannot allocate pointer"); return; } pointer->seat = seat; pointer->device = input_device; wlr_cursor_attach_input_device(seat->cursor, device); input_device->pointer = pointer; ++seat->num_pointers; if(wlr_pointer->output_name != NULL) { struct nedm_output *output; wl_list_for_each(output, &seat->server->outputs, link) { if(strcmp(wlr_pointer->output_name, output->name) == 0) { wlr_cursor_map_input_to_output(seat->cursor, device, output->wlr_output); break; } } } } static int handle_keyboard_repeat(void *data) { struct nedm_keyboard_group *nedm_group = data; struct wlr_keyboard *wlr_device = &nedm_group->wlr_group->keyboard; if(nedm_group->repeat_keybinding != NULL) { if(wlr_device->repeat_info.rate > 0) { if(wl_event_source_timer_update( nedm_group->key_repeat_timer, 1000 / wlr_device->repeat_info.rate) < 0) { wlr_log(WLR_DEBUG, "failed to update key repeat timer"); } } run_action((*nedm_group->repeat_keybinding)->action, nedm_group->seat->server, (*nedm_group->repeat_keybinding)->data); } return 0; } static void handle_modifier_event(struct wlr_input_device *device, struct nedm_seat *seat) { struct wlr_keyboard *keyboard = wlr_keyboard_from_input_device(device); 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); } void keyboard_disarm_key_repeat(struct nedm_keyboard_group *group) { if(!group) { return; } group->repeat_keybinding = NULL; if(wl_event_source_timer_update(group->key_repeat_timer, 0) < 0) { wlr_log(WLR_DEBUG, "failed to disarm key repeat timer"); } } static bool handle_command_key_bindings(struct nedm_server *server, xkb_keysym_t sym, uint32_t modifiers, uint32_t mode, struct nedm_keyboard_group *group) { if(group->enable_keybindings == false) { return false; } struct keybinding **keybinding = find_keybinding( server->keybindings, &(struct keybinding){.key = sym, .mode = mode, .modifiers = modifiers}); if(server->seat->mode != server->seat->default_mode) { server->seat->mode = server->seat ->default_mode; // Return to mode we are currently in by default double sx, sy; struct wlr_seat *wlr_seat = server->seat->seat; struct wlr_surface *surface = NULL; struct wlr_scene_node *node = wlr_scene_node_at( &server->scene->tree.node, server->seat->cursor->x, server->seat->cursor->y, &sx, &sy); if(server->seat->enable_cursor) { wlr_cursor_set_xcursor(server->seat->cursor, server->seat->xcursor_manager, "left_ptr"); if(node && node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer( wlr_scene_buffer_from_node(node)); if(scene_surface != NULL) { surface = scene_surface->surface; if(surface != NULL) { wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy); } } } } } if(keybinding) { wlr_log( WLR_DEBUG, "Recognized keybinding pressed (key: %d, mode: %d, modifiers: %d)", sym, mode, modifiers); if(group->wlr_group->keyboard.repeat_info.delay > 0) { group->repeat_keybinding = keybinding; if(wl_event_source_timer_update( group->key_repeat_timer, group->wlr_group->keyboard.repeat_info.delay) < 0) { wlr_log(WLR_DEBUG, "failed to set key repeat timer"); } } message_clear(group->seat->server->curr_output); run_action((*keybinding)->action, server, (*keybinding)->data); wlr_idle_notifier_v1_notify_activity(server->idle, server->seat->seat); return true; } else if(mode != 0) { run_action(KEYBINDING_NOOP, server, (union keybinding_params){NULL}); message_printf(server->curr_output, "unbound key pressed"); wlr_log(WLR_DEBUG, "Unknown keybinding pressed (key: %d, mode: %d, modifiers: %d)", sym, mode, modifiers); return true; } else { return false; } } static bool key_is_modifier(const xkb_keysym_t key) { switch(key) { case XKB_KEY_Shift_L: case XKB_KEY_Shift_R: case XKB_KEY_Control_L: case XKB_KEY_Control_R: case XKB_KEY_Alt_L: case XKB_KEY_Alt_R: case XKB_KEY_Super_L: case XKB_KEY_Super_R: case XKB_KEY_Hyper_L: case XKB_KEY_Hyper_R: return true; default: return false; } } static void handle_key_event(struct nedm_keyboard_group *group, struct nedm_seat *seat, void *data) { struct wlr_keyboard_key_event *event = data; struct wlr_keyboard *keyboard = &group->wlr_group->keyboard; /* 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; for(int i = 0; i < nsyms; ++i) { if(event->state == WL_KEYBOARD_KEY_STATE_PRESSED && !key_is_modifier(syms[i])) { uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard); /* Get the consumed_modifiers and remove them from the modifier list */ xkb_mod_mask_t consumed_modifiers = xkb_state_key_get_consumed_mods2(keyboard->xkb_state, keycode, XKB_CONSUMED_MODE_GTK); if(handle_command_key_bindings(seat->server, syms[i], modifiers & ~consumed_modifiers, seat->mode, group)) { handled = true; } } else if(group->repeat_keybinding != NULL && handled == false) { keyboard_disarm_key_repeat(group); } } 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 nedm_keyboard_group *nedm_group = wl_container_of(listener, nedm_group, key); handle_key_event(nedm_group, nedm_group->seat, data); } static void handle_keyboard_group_modifiers(struct wl_listener *listener, __attribute__((unused)) void *_data) { struct nedm_keyboard_group *group = wl_container_of(listener, group, modifiers); handle_modifier_event(&group->wlr_group->keyboard.base, group->seat); } static bool repeat_info_match(struct wlr_keyboard *a, struct wlr_keyboard *b) { return a->repeat_info.rate == b->repeat_info.rate && a->repeat_info.delay == b->repeat_info.delay; } static void nedm_keyboard_group_add(struct nedm_input_device *input_device, struct nedm_seat *seat) { struct wlr_input_device *device = input_device->wlr_device; struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(device); // Virtual devices should not be grouped if(!input_device->is_virtual) { struct nedm_keyboard_group *group; wl_list_for_each(group, &seat->keyboard_groups, link) { struct wlr_keyboard_group *wlr_group = group->wlr_group; if(wlr_keyboard_keymaps_match(wlr_keyboard->keymap, wlr_group->keyboard.keymap) && repeat_info_match(wlr_keyboard, &wlr_group->keyboard) && wlr_keyboard_group_add_keyboard(wlr_group, wlr_keyboard)) { wlr_log(WLR_DEBUG, "Adding keyboard to existing group."); return; } } } /* This is reached if and only if the keyboard could not be inserted into * any group */ struct nedm_keyboard_group *nedm_group = calloc(1, sizeof(struct nedm_keyboard_group)); if(nedm_group == NULL) { wlr_log(WLR_ERROR, "Failed to allocate keyboard group."); return; } nedm_group->seat = seat; nedm_group->wlr_group = wlr_keyboard_group_create(); if(nedm_group->wlr_group == NULL) { wlr_log(WLR_ERROR, "Failed to create wlr keyboard group."); goto cleanup; } nedm_group->wlr_group->data = nedm_group; wlr_keyboard_set_keymap(&nedm_group->wlr_group->keyboard, wlr_keyboard->keymap); wlr_keyboard_set_repeat_info(&nedm_group->wlr_group->keyboard, wlr_keyboard->repeat_info.rate, wlr_keyboard->repeat_info.delay); if(input_device->identifier != NULL) { nedm_group->identifier = strdup(input_device->identifier); } else { nedm_group->identifier = NULL; } wlr_log(WLR_DEBUG, "Created keyboard group."); wlr_keyboard_group_add_keyboard(nedm_group->wlr_group, wlr_keyboard); wl_list_insert(&seat->keyboard_groups, &nedm_group->link); wl_signal_add(&nedm_group->wlr_group->keyboard.events.key, &nedm_group->key); nedm_group->key.notify = handle_keyboard_group_key; wl_signal_add(&nedm_group->wlr_group->keyboard.events.modifiers, &nedm_group->modifiers); nedm_group->key_repeat_timer = wl_event_loop_add_timer( seat->server->event_loop, handle_keyboard_repeat, nedm_group); nedm_group->modifiers.notify = handle_keyboard_group_modifiers; nedm_group->enable_keybindings = true; nedm_input_manager_configure_keyboard_group(nedm_group); return; cleanup: if(nedm_group && nedm_group->wlr_group) { wlr_keyboard_group_destroy(nedm_group->wlr_group); } if(nedm_group && nedm_group->identifier) { free(nedm_group->identifier); } free(nedm_group); } static void new_keyboard(struct nedm_seat *seat, struct nedm_input_device *input_device) { struct wlr_input_device *device = input_device->wlr_device; 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_map_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(wlr_keyboard_from_input_device(device), keymap); xkb_keymap_unref(keymap); xkb_context_unref(context); nedm_keyboard_group_add(input_device, seat); ++seat->num_keyboards; wlr_seat_set_keyboard(seat->seat, wlr_keyboard_from_input_device(device)); } void seat_add_device(struct nedm_seat *seat, struct nedm_input_device *device) { switch(device->wlr_device->type) { case WLR_INPUT_DEVICE_KEYBOARD: new_keyboard(seat, device); break; case WLR_INPUT_DEVICE_POINTER: new_pointer(seat, device); break; case WLR_INPUT_DEVICE_TOUCH: new_touch(seat, 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 destroy_empty_wlr_keyboard_group(void *data) { wlr_keyboard_group_destroy(data); } void remove_keyboard(struct nedm_seat *seat, struct nedm_input_device *keyboard) { if(!keyboard) { return; } struct wlr_seat *wlr_seat = seat->seat; struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(keyboard->wlr_device); struct wlr_keyboard_group *wlr_group = wlr_keyboard->group; if(wlr_group) { wlr_keyboard_group_remove_keyboard(wlr_group, wlr_keyboard); if(wl_list_empty(&wlr_group->devices)) { wlr_log(WLR_DEBUG, "Destroying empty keyboard group %p", (void *)wlr_group); struct nedm_keyboard_group *group = wlr_group->data; if(wlr_seat_get_keyboard(wlr_seat) == &wlr_group->keyboard) { wlr_seat_set_keyboard(wlr_seat, NULL); } wlr_group->data = NULL; wl_list_remove(&group->link); wl_list_remove(&group->key.link); wl_list_remove(&group->modifiers.link); wl_event_source_remove(group->key_repeat_timer); if(group->identifier != NULL) { free(group->identifier); } free(group); // To prevent use-after-free conditions when handling key events, // defer freeing the wlr_keyboard_group until idle wl_event_loop_add_idle(seat->server->event_loop, destroy_empty_wlr_keyboard_group, wlr_group); } } --seat->num_keyboards; } void seat_remove_device(struct nedm_seat *seat, struct nedm_input_device *device) { switch(device->wlr_device->type) { case WLR_INPUT_DEVICE_KEYBOARD: remove_keyboard(seat, device); break; case WLR_INPUT_DEVICE_POINTER: remove_pointer(seat, device->pointer); device->pointer = NULL; break; case WLR_INPUT_DEVICE_TOUCH: remove_touch(seat, device->touch); device->touch = NULL; 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 nedm_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 nedm_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 nedm_seat *seat = wl_container_of(listener, seat, request_set_cursor); if(seat->enable_cursor == false || seat->server->modecursors[seat->mode] != NULL) { return; } 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 nedm_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_scene_node *node = wlr_scene_node_at(&seat->server->scene->tree.node, lx, ly, &sx, &sy); uint32_t serial = 0; if(node && node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(wlr_scene_buffer_from_node(node)); if(scene_surface != NULL) { serial = wlr_seat_touch_notify_down( seat->seat, scene_surface->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; } wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void handle_touch_up(struct wl_listener *listener, void *data) { struct nedm_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; } 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 nedm_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_scene_node *node = wlr_scene_node_at(&seat->server->scene->tree.node, lx, ly, &sx, &sy); if(node && node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(wlr_scene_buffer_from_node(node)); if(scene_surface != NULL) { wlr_seat_touch_point_focus(seat->seat, scene_surface->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_cursor_frame(struct wl_listener *listener, __attribute__((unused)) void *_data) { struct nedm_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 nedm_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 nedm_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); if(event->state == WL_POINTER_BUTTON_STATE_PRESSED) { double sx, sy; struct wlr_scene_node *node = wlr_scene_node_at( &seat->server->scene->tree.node, seat->cursor->x, seat->cursor->y, &sx, &sy); if(node && node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer( wlr_scene_buffer_from_node(node)); if(scene_surface) { struct nedm_view *view = view_from_wlr_surface( seat->server, scene_surface->surface); if(view) { seat_set_focus(seat, view); } } } } wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void process_cursor_motion(struct nedm_seat *seat, uint32_t time) { double sx, sy; struct wlr_seat *wlr_seat = seat->seat; struct wlr_surface *surface = NULL; struct wlr_scene_node *node = wlr_scene_node_at(&seat->server->scene->tree.node, seat->cursor->x, seat->cursor->y, &sx, &sy); if(node && node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(wlr_scene_buffer_from_node(node)); if(scene_surface != NULL) { surface = scene_surface->surface; } } bool skip_update = false; if(seat->active_constraint) { struct wlr_pointer_constraint_v1 *constraint = seat->active_constraint; if(constraint->surface != surface) { wlr_pointer_constraint_v1_send_deactivated(constraint); wl_list_remove(&seat->constraint_destroy.link); seat->active_constraint = NULL; } else if(constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) { // For locked pointer, do not move or warp the cursor, just notify enter/motion if(surface) { wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy); if(time > 0) { wlr_seat_pointer_notify_motion(wlr_seat, time, sx, sy); } } // Do not warp or clamp the cursor for locked pointer skip_update = true; } } if(!skip_update) { if(surface) { bool focus_changed = wlr_seat->pointer_state.focused_surface != surface; if(!focus_changed && time > 0) { wlr_seat_pointer_notify_motion(wlr_seat, time, sx, sy); } wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy); } else { wlr_seat_pointer_clear_focus(wlr_seat); } } if(seat->active_constraint && seat->active_constraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED) { struct wlr_box surface_box; wlr_surface_get_extents(seat->active_constraint->surface, &surface_box); double sx = seat->cursor->x - surface_box.x; double sy = seat->cursor->y - surface_box.y; if(sx < 0) { sx = 0; } if(sy < 0) { sy = 0; } if(sx > surface_box.width) { sx = surface_box.width; } if(sy > surface_box.height) { sy = surface_box.height; } wlr_cursor_warp(seat->cursor, NULL, surface_box.x + sx, surface_box.y + sy); } struct nedm_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); /* Check if cursor switched tile */ struct wlr_output *c_outp = wlr_output_layout_output_at( seat->server->output_layout, seat->cursor->x, seat->cursor->y); struct nedm_output *nedm_outp = NULL; wl_list_for_each(nedm_outp, &seat->server->outputs, link) { if(nedm_outp->wlr_output == c_outp) { break; } } if(c_outp && nedm_outp->workspaces && nedm_outp->curr_workspace < nedm_outp->server->nws && nedm_outp->workspaces[nedm_outp->curr_workspace] && nedm_outp->workspaces[nedm_outp->curr_workspace]->focused_tile) { struct nedm_tile *c_tile; bool first = true; for(c_tile = nedm_outp->workspaces[nedm_outp->curr_workspace]->focused_tile; first || c_tile != nedm_outp->workspaces[nedm_outp->curr_workspace]->focused_tile; c_tile = c_tile->next) { first = false; double ox = seat->cursor->x, oy = seat->cursor->y; wlr_output_layout_output_coords(seat->server->output_layout, c_outp, &ox, &oy); if(c_tile->tile.x <= ox && c_tile->tile.y <= oy && c_tile->tile.x + c_tile->tile.width >= ox && c_tile->tile.y + c_tile->tile.height >= oy) { break; } } if(seat->cursor_tile != NULL && seat->cursor_tile != c_tile && seat->server->running) { ipc_send_event( seat->server, "{'event_name':'cursor_switch_tile','old_output':'%s','old_output_id':%d,'old_tile':%d,'new_output':'%s','new_output_id':%d,'new_tile':%d}", seat->cursor_tile->workspace->output->name, output_get_num(seat->cursor_tile->workspace->output), seat->cursor_tile->id, c_outp->name, output_get_num(nedm_outp), c_tile->id); } seat->cursor_tile = c_tile; } } static void handle_cursor_motion_absolute(struct wl_listener *listener, void *data) { struct nedm_seat *seat = wl_container_of(listener, seat, cursor_motion_absolute); struct wlr_pointer_motion_absolute_event *event = data; wlr_cursor_warp_absolute(seat->cursor, &event->pointer->base, event->x, event->y); process_cursor_motion(seat, event->time_msec); wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); } static void handle_cursor_motion(struct wl_listener *listener, void *data) { struct nedm_seat *seat = wl_container_of(listener, seat, cursor_motion); struct wlr_pointer_motion_event *event = data; // If pointer is locked, do not move the visible cursor, only send relative motion if (seat->active_constraint && seat->active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) { // Only send relative motion to the client if (seat->server->relative_pointer_manager) { wlr_relative_pointer_manager_v1_send_relative_motion( seat->server->relative_pointer_manager, seat->seat, (uint64_t)event->time_msec * 1000, event->delta_x, event->delta_y, event->unaccel_dx, event->unaccel_dy); } // Notify the surface of motion (for button state, etc) process_cursor_motion(seat, event->time_msec); wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat); return; } wlr_cursor_move(seat->cursor, &event->pointer->base, event->delta_x, event->delta_y); process_cursor_motion(seat, event->time_msec); // Send relative motion AFTER cursor position is updated if (seat->server->relative_pointer_manager) { wlr_relative_pointer_manager_v1_send_relative_motion( seat->server->relative_pointer_manager, seat->seat, (uint64_t)event->time_msec * 1000, 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 nedm_drag_icon *drag_icon) { struct wlr_drag_icon *wlr_icon = drag_icon->wlr_drag_icon; struct nedm_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, __attribute__((unused)) void *_data) { struct nedm_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 nedm_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; } 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 nedm_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 nedm_drag_icon *drag_icon = calloc(1, sizeof(struct nedm_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_constraint_destroy(struct wl_listener *listener, __attribute__((unused)) void *data) { struct nedm_seat *seat = wl_container_of(listener, seat, constraint_destroy); seat->active_constraint = NULL; wl_list_remove(&seat->constraint_destroy.link); } static void handle_new_constraint(struct wl_listener *listener, void *data) { struct nedm_seat *seat = wl_container_of(listener, seat, new_constraint); struct wlr_pointer_constraint_v1 *constraint = data; // If there's an existing constraint, destroy it if (seat->active_constraint) { wlr_pointer_constraint_v1_send_deactivated(seat->active_constraint); wl_list_remove(&seat->constraint_destroy.link); seat->active_constraint = NULL; } seat->active_constraint = constraint; wl_signal_add(&constraint->events.destroy, &seat->constraint_destroy); seat->constraint_destroy.notify = handle_constraint_destroy; // Activate the constraint wlr_pointer_constraint_v1_send_activated(constraint); process_cursor_motion(seat, -1); } static void handle_destroy(struct wl_listener *listener, __attribute__((unused)) void *_data) { struct nedm_seat *seat = wl_container_of(listener, seat, destroy); wl_list_remove(&seat->destroy.link); struct nedm_keyboard_group *group, *group_tmp; wl_list_for_each_safe(group, group_tmp, &seat->keyboard_groups, link) { wl_list_remove(&group->link); wl_list_remove(&group->key.link); wl_list_remove(&group->modifiers.link); wl_event_source_remove(group->key_repeat_timer); wlr_keyboard_group_destroy(group->wlr_group); if(group->identifier) { free(group->identifier); } free(group); } struct nedm_input_device *it, *it_tmp; wl_list_for_each_safe(it, it_tmp, &seat->server->input->devices, link) { input_manager_handle_device_destroy(&it->device_destroy, NULL); } wlr_xcursor_manager_destroy(seat->xcursor_manager); wl_list_remove(&seat->cursor_motion.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->request_set_cursor.link); wl_list_remove(&seat->request_set_selection.link); wl_list_remove(&seat->request_set_primary_selection.link); if(seat->cursor) { wlr_cursor_destroy(seat->cursor); } seat->server->seat = NULL; free(seat); } struct nedm_seat * seat_create(struct nedm_server *server) { struct nedm_seat *seat = calloc(1, sizeof(struct nedm_seat)); if(!seat) { wlr_log(WLR_ERROR, "Cannot allocate seat"); return NULL; } seat->enable_cursor = true; 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, server->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.notify = handle_cursor_motion; wl_signal_add(&seat->cursor->events.motion, &seat->cursor_motion); 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->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); seat->new_constraint.notify = handle_new_constraint; wl_signal_add(&server->pointer_constraints->events.new_constraint, &seat->new_constraint); wl_list_init(&seat->keyboard_groups); seat->num_keyboards = 0; seat->num_pointers = 0; seat->num_touch = 0; 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); seat->mode = 0; seat->default_mode = 0; seat->active_constraint = NULL; return seat; } void seat_destroy(struct nedm_seat *seat) { if(!seat) { return; } // Clean up active constraint if (seat->active_constraint) { wlr_pointer_constraint_v1_send_deactivated(seat->active_constraint); wl_list_remove(&seat->constraint_destroy.link); seat->active_constraint = NULL; } wl_list_remove(&seat->new_constraint.link); wl_list_remove(&seat->request_start_drag.link); wl_list_remove(&seat->start_drag.link); // Destroying the wlr seat will trigger the destroy handler on our seat, // which will in turn free it. wlr_seat_destroy(seat->seat); } /* Important: this function returns NULL if we are focused on the background */ struct nedm_view * seat_get_focus(const struct nedm_seat *seat) { return seat->focused_view; } void seat_set_focus(struct nedm_seat *seat, struct nedm_view *view) { struct nedm_server *server = seat->server; struct wlr_seat *wlr_seat = seat->seat; struct nedm_view *prev_view = seat_get_focus(seat); // Clear any active pointer constraint when focus changes if (seat->active_constraint) { wlr_pointer_constraint_v1_send_deactivated(seat->active_constraint); wl_list_remove(&seat->constraint_destroy.link); seat->active_constraint = NULL; } /* Focusing the background */ if(view == NULL) { if(server->curr_output && server->curr_output->workspaces && server->curr_output->curr_workspace < server->nws && server->curr_output->workspaces[server->curr_output->curr_workspace] && server->curr_output->workspaces[server->curr_output->curr_workspace]->focused_tile) { workspace_tile_update_view( server->curr_output->workspaces[server->curr_output->curr_workspace] ->focused_tile, NULL); } seat->focused_view = NULL; if(prev_view != NULL) { view_activate(prev_view, false); } wlr_seat_keyboard_clear_focus(wlr_seat); process_cursor_motion(seat, -1); return; } #if NEDM_HAS_XWAYLAND if(view->type != NEDM_XWAYLAND_VIEW || xwayland_view_should_manage(view)) #endif { struct nedm_workspace *curr_workspace = server->curr_output ->workspaces[server->curr_output->curr_workspace]; workspace_tile_update_view(curr_workspace->focused_tile, view); wl_list_remove(&curr_workspace->views); wl_list_insert(&view->link, &curr_workspace->views); } #if NEDM_HAS_XWAYLAND if(view->type == NEDM_XWAYLAND_VIEW) { wlr_xwayland_set_seat(server->xwayland, seat->seat); } if(view->type == NEDM_XWAYLAND_VIEW && !xwayland_view_should_manage(view)) { const struct nedm_xwayland_view *xwayland_view = xwayland_view_from_view(view); if(!wlr_xwayland_surface_override_redirect_wants_focus( xwayland_view->xwayland_surface)) { return; } } else #endif { if(prev_view != NULL) { view_activate(prev_view, false); } seat->focused_view = view; } view_activate(view, true); struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(wlr_seat); wlr_seat_keyboard_end_grab(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); } wlr_scene_node_raise_to_top(&view->scene_tree->node); process_cursor_motion(seat, -1); wlr_scene_node_set_position( &view->workspace->output->bg->node, output_get_layout_box(view->workspace->output).x, output_get_layout_box(view->workspace->output).y); }