#include "server_impl.hpp" #include namespace unbox::kernel { // Slice 4: the kernel owns generic input PLUMBING only. It moves the cursor, // tracks seat capabilities, handles the seat's own protocol requests, runs the // key_filter (consume-or-pass), and EMITS typed events. It routes NOTHING to // client surfaces and makes NO focus decision — ext-xdg-shell (and others) do // that from the bus + borrows. The only client forward the kernel still does // is keyboard key passthrough AFTER the filter, because the seat already holds // the focus an extension set via wlr_seat_keyboard_notify_enter. // ---- Device hotplug ----------------------------------------------------------- void Server::Impl::handle_new_input(wlr_input_device* device) { switch (device->type) { case WLR_INPUT_DEVICE_KEYBOARD: new_keyboard(device); break; case WLR_INPUT_DEVICE_POINTER: new_pointer(device); break; case WLR_INPUT_DEVICE_TOUCH: new_touch(device); break; default: break; } update_seat_capabilities(); } void Server::Impl::update_seat_capabilities() { // Always advertise a pointer: we always draw a cursor. std::uint32_t caps = WL_SEAT_CAPABILITY_POINTER; if (!keyboards.empty()) { caps |= WL_SEAT_CAPABILITY_KEYBOARD; } if (!touch_devices.empty()) { caps |= WL_SEAT_CAPABILITY_TOUCH; } wlr_seat_set_capabilities(seat, caps); } void Server::Impl::new_keyboard(wlr_input_device* device) { wlr_keyboard* wlr_kb = wlr_keyboard_from_input_device(device); auto owned = std::make_unique(); Keyboard* keyboard = owned.get(); keyboard->server = this; keyboard->keyboard = wlr_kb; keyboards.push_back(std::move(owned)); // Default XKB keymap; per-config keymap is a later slice. xkb_context* context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); xkb_keymap* keymap = xkb_keymap_new_from_names(context, nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS); wlr_keyboard_set_keymap(wlr_kb, keymap); xkb_keymap_unref(keymap); xkb_context_unref(context); wlr_keyboard_set_repeat_info(wlr_kb, 25, 600); keyboard->modifiers.connect(wlr_kb->events.modifiers, [this, keyboard](void*) { wlr_seat_set_keyboard(seat, keyboard->keyboard); wlr_seat_keyboard_notify_modifiers(seat, &keyboard->keyboard->modifiers); }); keyboard->key.connect(wlr_kb->events.key, [this, keyboard](void* data) { const auto* event = static_cast(data); // libinput keycode -> xkbcommon const std::uint32_t keycode = event->keycode + 8; const xkb_keysym_t* syms = nullptr; const int nsyms = xkb_state_key_get_syms(keyboard->keyboard->xkb_state, keycode, &syms); const std::uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard->keyboard); const bool pressed = event->state == WL_KEYBOARD_KEY_STATE_PRESSED; // Thread each resolved keysym through the key_filter; a filter link may // CONSUME the key (set handled=true) — that is how extensions implement // compositor shortcuts. If any resolution is consumed, suppress the // client forward for this key. bool consumed = false; for (int i = 0; i < nsyms; ++i) { KeyEvent ke{}; ke.keysym = syms[i]; ke.keycode = event->keycode; ke.modifiers = modifiers; ke.pressed = pressed; ke.time_msec = event->time_msec; ke.handled = false; ke = key_filter.apply(ke); consumed = consumed || ke.handled; } // With no resolved syms (e.g. modifier-only) the filter still runs once // so extensions can observe raw modifier keys if they wish. if (nsyms == 0) { KeyEvent ke{}; ke.keysym = XKB_KEY_NoSymbol; ke.keycode = event->keycode; ke.modifiers = modifiers; ke.pressed = pressed; ke.time_msec = event->time_msec; ke = key_filter.apply(ke); consumed = consumed || ke.handled; } if (!consumed) { wlr_seat_set_keyboard(seat, keyboard->keyboard); wlr_seat_keyboard_notify_key(seat, event->time_msec, event->keycode, event->state); } }); keyboard->destroy.connect(device->events.destroy, [this, keyboard](void*) { update_seat_capabilities(); // Last action: destroys `keyboard` (and these listeners with it). keyboards.remove_if([keyboard](const auto& owned) { return owned.get() == keyboard; }); }); wlr_seat_set_keyboard(seat, wlr_kb); } void Server::Impl::new_pointer(wlr_input_device* device) { // All pointer handling is proxied through wlr_cursor; per-device libinput // config is a later slice. wlr_cursor_attach_input_device(cursor, device); } void Server::Impl::new_touch(wlr_input_device* device) { auto owned = std::make_unique(); TouchDevice* touch = owned.get(); touch->server = this; touch->device = device; touch_devices.push_back(std::move(owned)); touch->destroy.connect(device->events.destroy, [this, touch](void*) { update_seat_capabilities(); // Last action: destroys `touch` (and this listener with it). touch_devices.remove_if([touch](const auto& owned) { return owned.get() == touch; }); }); wlr_cursor_attach_input_device(cursor, device); } // ---- Pointer (via wlr_cursor): move cursor + emit, route nothing ------------ void Server::Impl::emit_pointer_motion(std::uint32_t time_msec) { // Slice-3 spike input proof (kernel-internal; NOT a contract): forward // surface-local coords over the spike node so its button hovers. if (ui_spike != nullptr) { if (wlr_scene_node* spike = ui_spike->node()) { int nx = 0; int ny = 0; wlr_scene_node_coords(spike, &nx, &ny); ui_spike->on_pointer_motion(cursor->x - nx, cursor->y - ny); } } const PointerMotionEvent ev{cursor->x, cursor->y, time_msec}; ev_pointer_motion.emit(ev); } void Server::Impl::attach_cursor_handlers() { cursor_motion.connect(cursor->events.motion, [this](void* data) { const auto* event = static_cast(data); wlr_cursor_move(cursor, &event->pointer->base, event->delta_x, event->delta_y); emit_pointer_motion(event->time_msec); }); cursor_motion_absolute.connect(cursor->events.motion_absolute, [this](void* data) { const auto* event = static_cast(data); wlr_cursor_warp_absolute(cursor, &event->pointer->base, event->x, event->y); emit_pointer_motion(event->time_msec); }); cursor_button.connect(cursor->events.button, [this](void* data) { const auto* event = static_cast(data); const bool pressed = event->state == WL_POINTER_BUTTON_STATE_PRESSED; // Slice-3 spike input proof (kernel-internal): forward clicks over the // spike node so its button reacts. if (ui_spike != nullptr) { if (wlr_scene_node* spike = ui_spike->node()) { if (wlr_scene_node_at(spike, cursor->x, cursor->y, nullptr, nullptr) != nullptr) { ui_spike->on_pointer_button(pressed); } } } const PointerButtonEvent ev{event->button, pressed, cursor->x, cursor->y, event->time_msec}; ev_pointer_button.emit(ev); }); cursor_axis.connect(cursor->events.axis, [this](void* data) { const auto* event = static_cast(data); const PointerAxisEvent ev{event->orientation, event->delta, event->delta_discrete, event->source, event->time_msec}; ev_pointer_axis.emit(ev); }); cursor_frame.connect(cursor->events.frame, [this](void*) { ev_pointer_frame.emit(); }); // ---- Touch (tinywl lacks this; the CF-AX3 has it). Convert to layout // coords + emit; extensions route to surfaces. ---- cursor_touch_down.connect(cursor->events.touch_down, [this](void* data) { const auto* event = static_cast(data); double lx = 0; double ly = 0; wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly); const TouchDownEvent ev{event->touch_id, lx, ly, event->time_msec}; ev_touch_down.emit(ev); }); cursor_touch_motion.connect(cursor->events.touch_motion, [this](void* data) { const auto* event = static_cast(data); double lx = 0; double ly = 0; wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly); const TouchMotionEvent ev{event->touch_id, lx, ly, event->time_msec}; ev_touch_motion.emit(ev); }); cursor_touch_up.connect(cursor->events.touch_up, [this](void* data) { const auto* event = static_cast(data); const TouchUpEvent ev{event->touch_id, event->time_msec}; ev_touch_up.emit(ev); }); cursor_touch_cancel.connect(cursor->events.touch_cancel, [this](void* data) { const auto* event = static_cast(data); const TouchCancelEvent ev{event->touch_id}; ev_touch_cancel.emit(ev); }); cursor_touch_frame.connect(cursor->events.touch_frame, [this](void*) { ev_touch_frame.emit(); }); } // ---- Seat requests (generic protocol glue) ---------------------------------- void Server::Impl::attach_seat_handlers() { seat_request_cursor.connect(seat->events.request_set_cursor, [this](void* data) { const auto* event = static_cast(data); if (seat->pointer_state.focused_client == event->seat_client) { wlr_cursor_set_surface(cursor, event->surface, event->hotspot_x, event->hotspot_y); } }); seat_pointer_focus_change.connect(seat->pointer_state.events.focus_change, [this](void* data) { const auto* event = static_cast(data); if (event->new_surface == nullptr) { wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); } }); seat_request_set_selection.connect(seat->events.request_set_selection, [this](void* data) { const auto* event = static_cast(data); wlr_seat_set_selection(seat, event->source, event->serial); }); } } // namespace unbox::kernel