/** * Looking Glass * Copyright © 2017-2026 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "wayland.h" #include #include #include #include #include #include #include #include "app.h" #include "common/debug.h" #define MTRACE(fmt, ...) \ do \ { \ const uint64_t seq = app_mouseSeq(); \ if (seq) \ app_mouseTrace(__FILE__, __LINE__, __FUNCTION__, seq, \ "wl." fmt, ##__VA_ARGS__); \ } \ while (0) #define MLOG(seq, fmt, ...) \ do \ { \ if (seq) \ app_mouseTrace(__FILE__, __LINE__, __FUNCTION__, seq, \ "wl." fmt, ##__VA_ARGS__); \ } \ while (0) static uint32_t proxyId(void * proxy) { return proxy ? wl_proxy_get_id(proxy) : 0; } static void inputPosition(void * opaque, double x, double y) { (void)opaque; app_updateCursorPos(x, y); } static void inputRelative(void * opaque, double x, double y, double rawX, double rawY) { (void)opaque; app_handleMouseRelative(x, y, rawX, rawY); } static void inputButton(void * opaque, unsigned int button, bool pressed) { (void)opaque; if (pressed) app_handleButtonPress(button); else app_handleButtonRelease(button); } static void inputWheel(void * opaque, double motion) { (void)opaque; app_handleWheelMotion(motion); } static void inputKey(void * opaque, int scancode, bool pressed) { (void)opaque; if (pressed) app_handleKeyPress(scancode); else app_handleKeyRelease(scancode); } static void inputText(void * opaque, const char * text) { (void)opaque; /* Test overlay state after the key callback, matching the native event * handler's ordering. */ if (app_isOverlayMode()) app_handleKeyboardTyped(text); } static void inputModifiers(void * opaque, bool ctrl, bool shift, bool alt, bool super) { (void)opaque; app_handleKeyboardModifiers(ctrl, shift, alt, super); } static void inputLEDs(void * opaque, bool numLock, bool capsLock, bool scrollLock) { (void)opaque; app_handleKeyboardLEDs(numLock, capsLock, scrollLock); } static void inputEnter(void * opaque, bool entered) { (void)opaque; app_handleEnterEvent(entered); } static void inputFocus(void * opaque, bool focused) { (void)opaque; if (!focused) waylandCBInvalidate(); app_handleFocusEvent(focused); } static const LG_DSInputSink inputSink = { .position = inputPosition, .relative = inputRelative, .button = inputButton, .wheel = inputWheel, .key = inputKey, .text = inputText, .modifiers = inputModifiers, .leds = inputLEDs, .enter = inputEnter, .focus = inputFocus, }; // Mouse-handling listeners. static void pointerMotionHandler(void * data, struct wl_pointer * pointer, uint32_t time, wl_fixed_t sxW, wl_fixed_t syW) { wlMotionAbs(&wlWm.motion, wl_fixed_to_double(sxW), wl_fixed_to_double(syW)); MTRACE("abs time=%u pos=%.3f,%.3f", time, wlWm.motion.x, wlWm.motion.y); wlInputPointerMotion(&wlWm.input, wlWm.motion.x, wlWm.motion.y); } static void pointerEnterHandler(void * data, struct wl_pointer * pointer, uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW, wl_fixed_t syW) { MTRACE("enter main=%d surface=%p serial=%u pos=%.3f,%.3f", surface == wlWm.surface, (void *)surface, serial, wl_fixed_to_double(sxW), wl_fixed_to_double(syW)); if (!wlInputPointerEnter(&wlWm.input, surface == wlWm.surface)) return; wl_pointer_set_cursor(pointer, serial, wlWm.cursor, wlWm.cursorHotX, wlWm.cursorHotY); wlWm.pointerEnterSerial = serial; wlMotionAbs(&wlWm.motion, wl_fixed_to_double(sxW), wl_fixed_to_double(syW)); wlInputPointerMotion(&wlWm.input, wlWm.motion.x, wlWm.motion.y); } static void pointerLeaveHandler(void * data, struct wl_pointer * pointer, uint32_t serial, struct wl_surface * surface) { MTRACE("leave main=%d surface=%p serial=%u", surface == wlWm.surface, (void *)surface, serial); wlInputPointerLeave(&wlWm.input, surface == wlWm.surface); } static void pointerAxisHandler(void * data, struct wl_pointer * pointer, uint32_t serial, uint32_t axis, wl_fixed_t value) { wlInputPointerAxis(&wlWm.input, axis == WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_to_double(value)); } static void pointerButtonHandler(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t stateW) { wlInputPointerButton(&wlWm.input, button, stateW == WL_POINTER_BUTTON_STATE_PRESSED); } static const struct wl_pointer_listener pointerListener = { .enter = pointerEnterHandler, .leave = pointerLeaveHandler, .motion = pointerMotionHandler, .button = pointerButtonHandler, .axis = pointerAxisHandler, }; static void lockedHandler(void * data, struct zwp_locked_pointer_v1 * pointer) { bool valid; LG_LOCK(wlWm.surfaceLock); valid = pointer == wlWm.lockedPointer; if (valid) atomic_store_explicit(&wlWm.lockActive, true, memory_order_release); LG_UNLOCK(wlWm.surfaceLock); MTRACE("lock active=1 id=%u valid=%d", proxyId(pointer), valid); } static void unlockedHandler(void * data, struct zwp_locked_pointer_v1 * pointer) { bool valid; LG_LOCK(wlWm.surfaceLock); valid = pointer == wlWm.lockedPointer; if (valid) atomic_store_explicit(&wlWm.lockActive, false, memory_order_release); LG_UNLOCK(wlWm.surfaceLock); MTRACE("lock active=0 id=%u valid=%d", proxyId(pointer), valid); } static const struct zwp_locked_pointer_v1_listener lockedListener = { .locked = lockedHandler, .unlocked = unlockedHandler, }; static struct zwp_locked_pointer_v1 * createLock(void) { struct zwp_locked_pointer_v1 * pointer = zwp_pointer_constraints_v1_lock_pointer( wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); zwp_locked_pointer_v1_add_listener(pointer, &lockedListener, NULL); return pointer; } static void relativePointerMotionHandler(void * data, struct zwp_relative_pointer_v1 *pointer, uint32_t timeHi, uint32_t timeLo, wl_fixed_t dxW, wl_fixed_t dyW, wl_fixed_t dxUnaccelW, wl_fixed_t dyUnaccelW) { const bool active = atomic_load_explicit(&wlWm.lockActive, memory_order_acquire); const double dx = wl_fixed_to_double(dxW); const double dy = wl_fixed_to_double(dyW); const double rawX = wl_fixed_to_double(dxUnaccelW); const double rawY = wl_fixed_to_double(dyUnaccelW); if (active) MTRACE("rel time=%u:%u delta=%.3f,%.3f raw=%.3f,%.3f " "pos=%.3f,%.3f", timeHi, timeLo, dx, dy, rawX, rawY, wlWm.motion.x, wlWm.motion.y); wlInputRelativeMotion(&wlWm.input, active, dx, dy, rawX, rawY); } static const struct zwp_relative_pointer_v1_listener relativePointerListener = { .relative_motion = relativePointerMotionHandler, }; // Keyboard-handling listeners. static void keyboardKeymapHandler(void * data, struct wl_keyboard * keyboard, uint32_t format, int fd, uint32_t size) { if (!wlWm.xkb) goto done; if (wlWm.keymap) { xkb_keymap_unref(wlWm.keymap); wlWm.keymap = NULL; } if (wlWm.xkbState) { xkb_state_unref(wlWm.xkbState); wlWm.xkbState = NULL; } if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { DEBUG_WARN("Unsupported keymap format, keyboard input will not work: %d", format); goto done; } char * map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (map == MAP_FAILED) { DEBUG_ERROR("Failed to mmap keymap: %s", strerror(errno)); goto done; } wlWm.keymap = xkb_keymap_new_from_string(wlWm.xkb, map, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); if (!wlWm.keymap) DEBUG_WARN("Failed to load keymap, keyboard input will not work"); munmap(map, size); if (wlWm.keymap) { wlWm.xkbState = xkb_state_new(wlWm.keymap); if (!wlWm.xkbState) DEBUG_WARN("Failed to create xkb_state"); } done: close(fd); } bool waylandGetKeyLabel(int key, char * label, size_t size) { if (!wlWm.xkbState) return false; key += 8; // xkb scancode is evdev scancode + 8 xkb_keysym_t sym = xkb_state_key_get_one_sym(wlWm.xkbState, key); sym = xkb_keysym_to_upper(sym); const uint32_t codepoint = xkb_keysym_to_utf32(sym); return codepoint > 0x20 && codepoint != 0x7F && xkb_keysym_to_utf8(sym, label, size) > 0; } static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard, uint32_t serial, struct wl_surface * surface, struct wl_array * keys) { if (surface != wlWm.surface) { wlInputKeyboardEnter(&wlWm.input, false, NULL, 0); return; } wlWm.keyboardEnterSerial = serial; wlInputKeyboardEnter(&wlWm.input, true, keys->data, keys->size / sizeof(uint32_t)); } static void keyboardLeaveHandler(void * data, struct wl_keyboard * keyboard, uint32_t serial, struct wl_surface * surface) { wlInputKeyboardLeave(&wlWm.input, surface == wlWm.surface); } static void keyboardKeyHandler(void * data, struct wl_keyboard * keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { if (!wlWm.input.focused) return; const bool pressed = state == WL_KEYBOARD_KEY_STATE_PRESSED; if (!wlWm.xkbState || !pressed) { wlInputKeyboardKey(&wlWm.input, key, pressed, NULL); return; } const xkb_keycode_t xkbKey = key + 8; const int size = xkb_state_key_get_utf8(wlWm.xkbState, xkbKey, NULL, 0); if (size <= 0) { wlInputKeyboardKey(&wlWm.input, key, true, NULL); return; } char buffer[size + 1]; xkb_state_key_get_utf8(wlWm.xkbState, xkbKey, buffer, size + 1); wlInputKeyboardKey(&wlWm.input, key, true, buffer); } static void keyboardModifiersHandler(void * data, struct wl_keyboard * keyboard, uint32_t serial, uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { if (!wlWm.xkbState) return; xkb_state_update_mask(wlWm.xkbState, modsDepressed, modsLatched, modsLocked, 0, 0, group); const bool ctrl = xkb_state_mod_name_is_active(wlWm.xkbState, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE) > 0; const bool shift = xkb_state_mod_name_is_active(wlWm.xkbState, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE) > 0; const bool alt = xkb_state_mod_name_is_active(wlWm.xkbState, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE) > 0; const bool super = xkb_state_mod_name_is_active(wlWm.xkbState, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE) > 0; const bool numLock = xkb_state_led_name_is_active(wlWm.xkbState, XKB_LED_NAME_NUM) > 0; const bool capsLock = xkb_state_led_name_is_active(wlWm.xkbState, XKB_LED_NAME_CAPS) > 0; const bool scrollLock = xkb_state_led_name_is_active(wlWm.xkbState, XKB_LED_NAME_SCROLL) > 0; wlInputKeyboardState(&wlWm.input, ctrl, shift, alt, super, numLock, capsLock, scrollLock); } static const struct wl_keyboard_listener keyboardListener = { .keymap = keyboardKeymapHandler, .enter = keyboardEnterHandler, .leave = keyboardLeaveHandler, .key = keyboardKeyHandler, .modifiers = keyboardModifiersHandler, }; static void ensureCapturePointer(bool requestCapture) { uint32_t relativeId = 0; uint32_t lockId = 0; uint64_t lockSeq = 0; bool captureRequested = false; bool havePointer = false; bool haveRelativeManager = false; bool haveConstraints = false; INTERLOCKED_SECTION(wlWm.surfaceLock, { if (requestCapture) wlWm.captureRequested = true; captureRequested = wlWm.captureRequested; havePointer = !!wlWm.pointer; haveRelativeManager = !!wlWm.relativePointerManager; haveConstraints = !!wlWm.pointerConstraints; if (captureRequested && havePointer && haveRelativeManager && haveConstraints) { if (!wlWm.relativePointer) { wlWm.relativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer( wlWm.relativePointerManager, wlWm.pointer); zwp_relative_pointer_v1_add_listener(wlWm.relativePointer, &relativePointerListener, NULL); relativeId = proxyId(wlWm.relativePointer); } if (!wlWm.lockedPointer) { atomic_store_explicit(&wlWm.lockActive, false, memory_order_release); wlWm.lockedPointer = createLock(); lockId = proxyId(wlWm.lockedPointer); lockSeq = app_mouseSeq(); } } }); if (relativeId) MTRACE("rel req id=%u why=capture", relativeId); MLOG(lockSeq, "lock req id=%u why=capture", lockId); if (captureRequested && (!havePointer || !haveRelativeManager || !haveConstraints)) MTRACE("capture skip pointer=%d relMgr=%d constraints=%d", havePointer, haveRelativeManager, haveConstraints); } static void waylandCleanUpPointer(bool notify) { uint32_t lockId = 0; uint64_t lockSeq = 0; INTERLOCKED_SECTION(wlWm.surfaceLock, { atomic_store_explicit(&wlWm.lockActive, false, memory_order_release); if (wlWm.lockedPointer) { lockId = proxyId(wlWm.lockedPointer); zwp_locked_pointer_v1_destroy(wlWm.lockedPointer); wlWm.lockedPointer = NULL; lockSeq = app_mouseSeq(); } if (wlWm.relativePointer) { zwp_relative_pointer_v1_destroy(wlWm.relativePointer); wlWm.relativePointer = NULL; } wl_pointer_destroy(wlWm.pointer); wlWm.pointer = NULL; }); MLOG(lockSeq, "lock destroy id=%u why=pointer", lockId); wlInputPointerDisconnect(&wlWm.input, notify); } // Seat-handling listeners. static void handlePointerCapability(uint32_t capabilities) { bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER; if (!hasPointer && wlWm.pointer) waylandCleanUpPointer(true); else if (hasPointer && !wlWm.pointer) { LG_LOCK(wlWm.surfaceLock); wlWm.pointer = wl_seat_get_pointer(wlWm.seat); wl_pointer_add_listener(wlWm.pointer, &pointerListener, NULL); LG_UNLOCK(wlWm.surfaceLock); waylandSetPointer(wlWm.cursorId); ensureCapturePointer(false); } } static void handleKeyboardCapability(uint32_t capabilities) { bool hasKeyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD; if (!hasKeyboard && wlWm.keyboard) { wl_keyboard_destroy(wlWm.keyboard); wlWm.keyboard = NULL; } else if (hasKeyboard && !wlWm.keyboard) { wlWm.keyboard = wl_seat_get_keyboard(wlWm.seat); wl_keyboard_add_listener(wlWm.keyboard, &keyboardListener, NULL); } } static void seatCapabilitiesHandler(void * data, struct wl_seat * seat, uint32_t capabilities) { wlWm.capabilities = capabilities; handlePointerCapability(capabilities); handleKeyboardCapability(capabilities); } static void seatNameHandler(void * data, struct wl_seat * seat, const char * name) { // Do nothing. } static const struct wl_seat_listener seatListener = { .capabilities = seatCapabilitiesHandler, .name = seatNameHandler, }; bool waylandInputInit(bool allowNoInput) { wlInputInit(&wlWm.input, &inputSink, NULL); if (!wlWm.seat) { MTRACE("input seat=0 allowNone=%d", allowNoInput); if (allowNoInput) { DEBUG_WARN("Compositor missing wl_seat, input will be disabled"); return true; } DEBUG_ERROR("Compositor missing wl_seat, will not proceed"); return false; } if (!wlWm.relativePointerManager) DEBUG_WARN("zwp_relative_pointer_manager_v1 not exported by compositor, " "mouse will not be captured"); if (!wlWm.pointerConstraints) DEBUG_WARN("zwp_pointer_constraints_v1 not exported by compositor, mouse " "will not be captured"); if (!wlWm.keyboardInhibitManager) DEBUG_WARN("zwp_keyboard_shortcuts_inhibit_manager_v1 not exported by " "compositor, keyboard will not be grabbed"); MTRACE("input seat=1 relMgr=%d constraints=%d", !!wlWm.relativePointerManager, !!wlWm.pointerConstraints); wlWm.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!wlWm.xkb) DEBUG_WARN("Failed to initialize xkb, keyboard input will not work"); wl_seat_add_listener(wlWm.seat, &seatListener, NULL); wl_display_roundtrip(wlWm.display); return true; } void waylandInputFree(void) { if (!wlWm.seat) return; if (wlWm.pointer) waylandCleanUpPointer(false); // The only legal way the keyboard can be null is if it never existed. // When unplugged, the compositor must have an inert object. if (wlWm.keyboard) wl_keyboard_destroy(wlWm.keyboard); wl_seat_destroy(wlWm.seat); if (wlWm.xkbState) xkb_state_unref(wlWm.xkbState); if (wlWm.keymap) xkb_keymap_unref(wlWm.keymap); if (wlWm.xkb) xkb_context_unref(wlWm.xkb); } void waylandGrabPointer(void) { } void waylandUngrabPointer(void) { } void waylandCapturePointer(void) { ensureCapturePointer(true); } void waylandUncapturePointer(void) { uint32_t lockId = 0; uint32_t relativeId = 0; uint64_t lockSeq = 0; INTERLOCKED_SECTION(wlWm.surfaceLock, { wlWm.captureRequested = false; atomic_store_explicit(&wlWm.lockActive, false, memory_order_release); if (wlWm.lockedPointer) { lockId = proxyId(wlWm.lockedPointer); zwp_locked_pointer_v1_destroy(wlWm.lockedPointer); wlWm.lockedPointer = NULL; lockSeq = app_mouseSeq(); } if (wlWm.relativePointer) { relativeId = proxyId(wlWm.relativePointer); zwp_relative_pointer_v1_destroy(wlWm.relativePointer); wlWm.relativePointer = NULL; } }); MLOG(lockSeq, "lock destroy id=%u why=uncapture", lockId); if (relativeId) MTRACE("rel destroy id=%u why=uncapture", relativeId); } bool waylandIsPointerGrabbed(void) { return false; } bool waylandIsPointerCaptured(void) { return atomic_load_explicit(&wlWm.lockActive, memory_order_acquire); } void waylandGrabKeyboard(void) { if (wlWm.seat && wlWm.keyboardInhibitManager && !wlWm.keyboardInhibitor) { wlWm.keyboardInhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts( wlWm.keyboardInhibitManager, wlWm.surface, wlWm.seat); } } void waylandUngrabKeyboard(void) { if (wlWm.keyboardInhibitor) { zwp_keyboard_shortcuts_inhibitor_v1_destroy(wlWm.keyboardInhibitor); wlWm.keyboardInhibitor = NULL; } } void waylandWarpPointer(int x, int y, bool exiting) { const int reqX = x; const int reqY = y; if (!wlWm.input.pointerInSurface) { MTRACE("warp drop=surface target=%d,%d exit=%d", x, y, exiting); return; } if (wlWm.lockedPointer) { MTRACE("warp drop=lock target=%d,%d exit=%d", x, y, exiting); return; } if (!wlWm.pointerWarpper) { LG_LOCK(wlWm.surfaceLock); if (wlWm.lockedPointer) { LG_UNLOCK(wlWm.surfaceLock); MTRACE("warp drop=lock-race target=%d,%d exit=%d", x, y, exiting); return; } } int width, height; wlWm.desktop->getSize(&width, &height); if (x < 0) x = 0; else if (x >= width) x = width - 1; if (y < 0) y = 0; else if (y >= height) y = height - 1; uint32_t tempId = 0; uint64_t warpSeq; if (wlWm.pointerWarpper) { wp_pointer_warp_v1_warp_pointer(wlWm.pointerWarpper, wlWm.surface, wlWm.pointer, wl_fixed_from_int(x), wl_fixed_from_int(y), wlWm.pointerEnterSerial); warpSeq = app_mouseSeq(); } else { struct wl_region * region = wl_compositor_create_region(wlWm.compositor); wl_region_add(region, x, y, 1, 1); struct zwp_confined_pointer_v1 * confine = zwp_pointer_constraints_v1_confine_pointer( wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, region, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); tempId = proxyId(confine); wl_surface_commit(wlWm.surface); zwp_confined_pointer_v1_destroy(confine); wl_surface_commit(wlWm.surface); wl_region_destroy(region); warpSeq = app_mouseSeq(); LG_UNLOCK(wlWm.surfaceLock); } MLOG(warpSeq, "warp req=%d,%d target=%d,%d exit=%d temp=%u", reqX, reqY, x, y, exiting, tempId); } void waylandRealignPointer(void) { wlInputRealignPointer(&wlWm.input, wlWm.motion.x, wlWm.motion.y); } void waylandGuestPointerUpdated(double x, double y, double localX, double localY) { (void)x; (void)y; (void)localX; (void)localY; }