From 8d7749516d70b8a27df4441c2b3e717de1a7a724 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Fri, 12 Jun 2026 19:24:22 +0900 Subject: Slice 2: tinywl port — kernel compositor runs nested, manages toplevels, touch added MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Server contract (pimpl, create/run/dispatch/terminate) over a faithful tinywl 0.20.1 port: outputs via wlr_scene, xdg-shell toplevels+popups, focus, interactive move/resize, keyboard/pointer through wlr_cursor — plus touch (down/up/motion/cancel/frame via seat notifies with per-point origin tracking), which tinywl lacks. RAII Listener replaces manual wl_list_remove bookkeeping; shutdown ordering documented in kernel.md. xkbcommon added as a system dep. Verified: nested under labwc (output WL-1, foot mapped and focused on GLES2) and a headless+pixman boot test in the kernel suite. --- packages/kernel/src/input.cpp | 316 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 316 insertions(+) create mode 100644 packages/kernel/src/input.cpp (limited to 'packages/kernel/src/input.cpp') diff --git a/packages/kernel/src/input.cpp b/packages/kernel/src/input.cpp new file mode 100644 index 0000000..93bee74 --- /dev/null +++ b/packages/kernel/src/input.cpp @@ -0,0 +1,316 @@ +#include "server_impl.hpp" + +#include + +namespace unbox::kernel { + +// ---- 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 (layout "us" etc.); unbox.toml takes over later. + 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*) { + // The seat exposes one logical keyboard; swap the active device in. + 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); + + bool handled = false; + const std::uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard->keyboard); + if ((modifiers & WLR_MODIFIER_ALT) != 0 && + event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for (int i = 0; i < nsyms; i++) { + handled = handle_keybinding(syms[i]); + } + } + if (!handled) { + 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 (acceleration, tap…) 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 aggregates touch devices too and emits layout-mapped + // touch_* events (handled below). + wlr_cursor_attach_input_device(cursor, device); +} + +// ---- Compositor keybindings ------------------------------------------------------ + +auto Server::Impl::handle_keybinding(std::uint32_t keysym) -> bool { + // Slice-2 placeholder bindings (Alt held), replaced by the keybinding + // filter chain in slice 5. + switch (keysym) { + case XKB_KEY_Escape: + wl_display_terminate(display); + return true; + case XKB_KEY_F1: + if (mapped_toplevels.size() >= 2) { + focus_toplevel(mapped_toplevels.back()); + } + return true; + default: + return false; + } +} + +// ---- Pointer (via wlr_cursor) ------------------------------------------------------ + +void Server::Impl::process_cursor_move() { + wlr_scene_node_set_position(&grabbed_toplevel->scene_tree->node, + static_cast(cursor->x - grab_x), + static_cast(cursor->y - grab_y)); +} + +void Server::Impl::process_cursor_resize() { + // Resizing moves the node when dragging top/left edges; the client is + // asked for the new size (it commits a matching buffer later). + Toplevel* toplevel = grabbed_toplevel; + const double border_x = cursor->x - grab_x; + const double border_y = cursor->y - grab_y; + int new_left = grab_geobox.x; + int new_right = grab_geobox.x + grab_geobox.width; + int new_top = grab_geobox.y; + int new_bottom = grab_geobox.y + grab_geobox.height; + + if ((resize_edges & WLR_EDGE_TOP) != 0) { + new_top = static_cast(border_y); + if (new_top >= new_bottom) { + new_top = new_bottom - 1; + } + } else if ((resize_edges & WLR_EDGE_BOTTOM) != 0) { + new_bottom = static_cast(border_y); + if (new_bottom <= new_top) { + new_bottom = new_top + 1; + } + } + if ((resize_edges & WLR_EDGE_LEFT) != 0) { + new_left = static_cast(border_x); + if (new_left >= new_right) { + new_left = new_right - 1; + } + } else if ((resize_edges & WLR_EDGE_RIGHT) != 0) { + new_right = static_cast(border_x); + if (new_right <= new_left) { + new_right = new_left + 1; + } + } + + wlr_box* geo_box = &toplevel->xdg_toplevel->base->geometry; + wlr_scene_node_set_position(&toplevel->scene_tree->node, new_left - geo_box->x, + new_top - geo_box->y); + wlr_xdg_toplevel_set_size(toplevel->xdg_toplevel, new_right - new_left, + new_bottom - new_top); +} + +void Server::Impl::process_cursor_motion(std::uint32_t time_msec) { + if (cursor_mode == CursorMode::Move) { + process_cursor_move(); + return; + } + if (cursor_mode == CursorMode::Resize) { + process_cursor_resize(); + return; + } + + double sx = 0; + double sy = 0; + wlr_surface* surface = nullptr; + Toplevel* toplevel = toplevel_at(cursor->x, cursor->y, &surface, &sx, &sy); + if (toplevel == nullptr) { + // Over no toplevel: the compositor draws its own default cursor. + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + } + if (surface != nullptr) { + // Enter gives the surface pointer focus; wlroots dedupes repeats. + wlr_seat_pointer_notify_enter(seat, surface, sx, sy); + wlr_seat_pointer_notify_motion(seat, time_msec, sx, sy); + } else { + wlr_seat_pointer_clear_focus(seat); + } +} + +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); + process_cursor_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); + process_cursor_motion(event->time_msec); + }); + cursor_button.connect(cursor->events.button, [this](void* data) { + const auto* event = static_cast(data); + wlr_seat_pointer_notify_button(seat, event->time_msec, event->button, event->state); + if (event->state == WL_POINTER_BUTTON_STATE_RELEASED) { + reset_cursor_mode(); + } else { + // Click-to-focus. + double sx = 0; + double sy = 0; + wlr_surface* surface = nullptr; + focus_toplevel(toplevel_at(cursor->x, cursor->y, &surface, &sx, &sy)); + } + }); + cursor_axis.connect(cursor->events.axis, [this](void* data) { + const auto* event = static_cast(data); + wlr_seat_pointer_notify_axis(seat, event->time_msec, event->orientation, event->delta, + event->delta_discrete, event->source, + event->relative_direction); + }); + cursor_frame.connect(cursor->events.frame, [this](void*) { + wlr_seat_pointer_notify_frame(seat); + }); + + // ---- Touch (tinywl doesn't have this; the CF-AX3 does) ---- + 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); + double sx = 0; + double sy = 0; + wlr_surface* surface = nullptr; + Toplevel* toplevel = toplevel_at(lx, ly, &surface, &sx, &sy); + if (toplevel != nullptr) { + focus_toplevel(toplevel); // tap raises + focuses + } + if (surface != nullptr) { + touch_points.insert_or_assign(event->touch_id, + TouchPoint{surface, lx - sx, ly - sy}); + wlr_seat_touch_notify_down(seat, surface, event->time_msec, event->touch_id, sx, sy); + } + }); + cursor_touch_motion.connect(cursor->events.touch_motion, [this](void* data) { + const auto* event = static_cast(data); + auto it = touch_points.find(event->touch_id); + if (it == touch_points.end()) { + return; // down landed on no surface; nothing is grabbed + } + double lx = 0; + double ly = 0; + wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, + &lx, &ly); + wlr_seat_touch_notify_motion(seat, event->time_msec, event->touch_id, + lx - it->second.origin_x, ly - it->second.origin_y); + }); + cursor_touch_up.connect(cursor->events.touch_up, [this](void* data) { + const auto* event = static_cast(data); + touch_points.erase(event->touch_id); + wlr_seat_touch_notify_up(seat, event->time_msec, event->touch_id); + }); + cursor_touch_cancel.connect(cursor->events.touch_cancel, [this](void* data) { + const auto* event = static_cast(data); + if (wlr_touch_point* point = wlr_seat_touch_get_point(seat, event->touch_id)) { + wlr_seat_touch_notify_cancel(seat, point->client); + } + touch_points.erase(event->touch_id); + }); + cursor_touch_frame.connect(cursor->events.touch_frame, [this](void*) { + wlr_seat_touch_notify_frame(seat); + }); +} + +// ---- Seat requests ------------------------------------------------------------- + +void Server::Impl::attach_seat_handlers() { + seat_request_cursor.connect(seat->events.request_set_cursor, [this](void* data) { + const auto* event = static_cast(data); + // Any client may send this; honor only the pointer-focused one. + 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 -- cgit v1.2.3