feat(wayland): touch, with the first finger standing in for the pointer

The seat's wl_touch was never bound, so a touchscreen could not drive a
window at all. Every finger is now reported through onTouchDown, onTouchMove,
onTouchUp and onTouchCancel (TouchPoint: the compositor's id and the
position in physical pixels). The first finger down also enters the window —
Device::focusedWindow and the pointer serial the clipboard needs — presses
the left button, moves it and releases it when it lifts, so an application
written for a pointer works on a phone as it is.
This commit is contained in:
Jorijn van der Graaf 2026-10-18 01:48:30 +02:00
commit aed6142cce
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
5 changed files with 104 additions and 0 deletions

View file

@ -546,12 +546,82 @@ void Device::TickKeyRepeats() {
} }
} }
// The first finger stands in for the pointer: it enters the window (so the
// clipboard has a serial and Device::focusedWindow a window), presses the
// left button, moves it and releases it. Every finger is also reported as
// such, in physical pixels.
void Device::touch_down(void*, wl_touch*, std::uint32_t serial, std::uint32_t, wl_surface* surface, std::int32_t id, wl_fixed_t x, wl_fixed_t y) {
Window* window = nullptr;
for (Window* candidate : windows) {
if (candidate->surface == surface) window = candidate;
}
if (window == nullptr) return;
touchWindow = window;
window->lastPointerSerial_ = serial;
const Vector<float, 2> pos = Vector<float, 2>(wl_fixed_to_double(x), wl_fixed_to_double(y)) * window->scale;
touchPositions[id] = { pos.x, pos.y };
window->onTouchDown.Invoke({ id, pos });
if (window->primaryTouchId < 0) {
window->primaryTouchId = id;
focusedWindow = window;
window->currentMousePos = pos;
window->mouseLeftHeld = true;
window->onMouseLeftClick.Invoke();
}
}
void Device::touch_up(void*, wl_touch*, std::uint32_t, std::uint32_t, std::int32_t id) {
if (touchWindow == nullptr) return;
Window* window = touchWindow;
const auto at = touchPositions.find(id);
const Vector<float, 2> pos = at == touchPositions.end() ? window->currentMousePos : Vector<float, 2>(at->second.first, at->second.second);
touchPositions.erase(id);
window->onTouchUp.Invoke({ id, pos });
if (id == window->primaryTouchId) {
window->primaryTouchId = -1;
window->mouseLeftHeld = false;
window->onMouseLeftRelease.Invoke();
}
if (touchPositions.empty()) touchWindow = nullptr;
}
void Device::touch_motion(void*, wl_touch*, std::uint32_t, std::int32_t id, wl_fixed_t x, wl_fixed_t y) {
if (touchWindow == nullptr) return;
Window* window = touchWindow;
const Vector<float, 2> pos = Vector<float, 2>(wl_fixed_to_double(x), wl_fixed_to_double(y)) * window->scale;
touchPositions[id] = { pos.x, pos.y };
window->onTouchMove.Invoke({ id, pos });
if (id == window->primaryTouchId) {
window->currentMousePos = pos;
window->onMouseMove.Invoke();
}
}
void Device::touch_frame(void*, wl_touch*) {}
void Device::touch_cancel(void*, wl_touch*) {
if (touchWindow == nullptr) return;
Window* window = touchWindow;
touchPositions.clear();
touchWindow = nullptr;
if (window->primaryTouchId >= 0) {
window->primaryTouchId = -1;
window->mouseLeftHeld = false;
window->onMouseLeftRelease.Invoke();
}
window->onTouchCancel.Invoke();
}
void Device::seat_handle_capabilities(void* data, wl_seat* seat, uint32_t capabilities) { void Device::seat_handle_capabilities(void* data, wl_seat* seat, uint32_t capabilities) {
seat = seat; seat = seat;
if (capabilities & WL_SEAT_CAPABILITY_POINTER) { if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
wl_pointer* pointer = wl_seat_get_pointer(seat); wl_pointer* pointer = wl_seat_get_pointer(seat);
wl_pointer_add_listener(pointer, &pointer_listener, nullptr); wl_pointer_add_listener(pointer, &pointer_listener, nullptr);
} }
if ((capabilities & WL_SEAT_CAPABILITY_TOUCH) && wlTouch == nullptr) {
wlTouch = wl_seat_get_touch(seat);
wl_touch_add_listener(wlTouch, &touch_listener, nullptr);
}
if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
wl_keyboard* keyboard = wl_seat_get_keyboard(seat); wl_keyboard* keyboard = wl_seat_get_keyboard(seat);
wl_keyboard_add_listener(keyboard, &keyboard_listener, nullptr); wl_keyboard_add_listener(keyboard, &keyboard_listener, nullptr);

View file

@ -557,6 +557,10 @@ Window::~Window() {
std::erase(Device::windows, this); std::erase(Device::windows, this);
if (Device::focusedWindow == this) Device::focusedWindow = nullptr; if (Device::focusedWindow == this) Device::focusedWindow = nullptr;
if (Device::keyboardWindow == this) Device::keyboardWindow = nullptr; if (Device::keyboardWindow == this) Device::keyboardWindow = nullptr;
if (Device::touchWindow == this) {
Device::touchWindow = nullptr;
Device::touchPositions.clear();
}
#endif #endif
} }
#endif #endif

View file

@ -60,6 +60,9 @@ export namespace Crafter {
inline static xkb_state* xkb_state; inline static xkb_state* xkb_state;
inline static std::vector<Window*> windows; inline static std::vector<Window*> windows;
inline static wl_pointer* wlPointer; inline static wl_pointer* wlPointer;
inline static wl_touch* wlTouch = nullptr;
inline static Window* touchWindow = nullptr; // the window under the fingers
inline static std::unordered_map<std::int32_t, std::pair<float, float>> touchPositions; // per finger, physical px
// wl_data_device_manager + wl_data_device drive copy/paste. Bound // wl_data_device_manager + wl_data_device drive copy/paste. Bound
// lazily in handle_global; the data device is created once both // lazily in handle_global; the data device is created once both
// the manager and the seat are present (registry binding order // the manager and the seat are present (registry binding order
@ -90,6 +93,11 @@ export namespace Crafter {
static void PointerListenerHandleAxis(void*, wl_pointer*, std::uint32_t time, std::uint32_t axis, wl_fixed_t value); static void PointerListenerHandleAxis(void*, wl_pointer*, std::uint32_t time, std::uint32_t axis, wl_fixed_t value);
static void PointerListenerHandleEnter(void* data, wl_pointer* wl_pointer, std::uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y); static void PointerListenerHandleEnter(void* data, wl_pointer* wl_pointer, std::uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y);
static void PointerListenerHandleLeave(void*, wl_pointer*, std::uint32_t, wl_surface*); static void PointerListenerHandleLeave(void*, wl_pointer*, std::uint32_t, wl_surface*);
static void touch_down(void* data, wl_touch* touch, std::uint32_t serial, std::uint32_t time, wl_surface* surface, std::int32_t id, wl_fixed_t x, wl_fixed_t y);
static void touch_up(void* data, wl_touch* touch, std::uint32_t serial, std::uint32_t time, std::int32_t id);
static void touch_motion(void* data, wl_touch* touch, std::uint32_t time, std::int32_t id, wl_fixed_t x, wl_fixed_t y);
static void touch_frame(void* data, wl_touch* touch);
static void touch_cancel(void* data, wl_touch* touch);
constexpr static wl_pointer_listener pointer_listener = { constexpr static wl_pointer_listener pointer_listener = {
.enter = PointerListenerHandleEnter, .enter = PointerListenerHandleEnter,
@ -98,6 +106,13 @@ export namespace Crafter {
.button = pointer_handle_button, .button = pointer_handle_button,
.axis = PointerListenerHandleAxis, .axis = PointerListenerHandleAxis,
}; };
constexpr static wl_touch_listener touch_listener = {
.down = touch_down,
.up = touch_up,
.motion = touch_motion,
.frame = touch_frame,
.cancel = touch_cancel,
};
constexpr static wl_keyboard_listener keyboard_listener = { constexpr static wl_keyboard_listener keyboard_listener = {
.keymap = keyboard_keymap, .keymap = keyboard_keymap,
.enter = keyboard_enter, .enter = keyboard_enter,

View file

@ -18,6 +18,13 @@ export namespace Crafter {
Vector<float, 2> mouseDelta; Vector<float, 2> mouseDelta;
}; };
// One finger on a touchscreen, in the window's physical pixels; the id is
// the compositor's and stays with the finger until it lifts.
struct TouchPoint {
std::int32_t id;
Vector<float, 2> position;
};
struct ScaleData2D { struct ScaleData2D {
Vector<std::int16_t, 2, 2> position; Vector<std::int16_t, 2, 2> position;
Vector<std::int16_t, 2, 2> size; Vector<std::int16_t, 2, 2> size;

View file

@ -98,6 +98,14 @@ export namespace Crafter {
// touchpads, free-spinning wheels) is accumulated backend-side // touchpads, free-spinning wheels) is accumulated backend-side
// until a whole detent is reached. // until a whole detent is reached.
Event<std::uint32_t> onMouseScroll; Event<std::uint32_t> onMouseScroll;
// Touch, from the seat's wl_touch. The first finger down also drives
// currentMousePos and the left-button events until it lifts, so a
// pointer-only application works on a touchscreen as it is.
Event<TouchPoint> onTouchDown;
Event<TouchPoint> onTouchMove;
Event<TouchPoint> onTouchUp;
Event<void> onTouchCancel;
std::int32_t primaryTouchId = -1;
Vector<float, 2> currentMousePos; Vector<float, 2> currentMousePos;
Vector<float, 2> lastMousePos; Vector<float, 2> lastMousePos;
Vector<float, 2> mouseDelta; Vector<float, 2> mouseDelta;