bug(stage-tamagotchi): mouse tracking does not follow the cursor on native Wayland
Describe the bug
On native Wayland (KDE Plasma), the character does not follow the cursor. "Enable mouse tracking" is on, but the eyes do not move.
Note: I used Claude Code (AI) for the analysis, this report and the fix. I tested the result myself on the system below.
Steps to reproduce
- Start the desktop app in a native Wayland session (
--ozone-platform=wayland, which is the default on KDE Plasma 6). - Turn on mouse tracking in the model settings.
- Move the cursor around the screen, away from the AIRI window.
Expected: The character looks at the cursor, as on X11, Windows and macOS. Actual: The character does not react.
Root cause
This is the same cause as #2521:
screen.getCursorScreenPoint()inapps/stage-tamagotchi/src/main/services/electron/screen.tsdoes not return the global cursor position on Wayland. Chromium only gets pointer events while the cursor is over its own surfaces.BrowserWindow.getBounds()reportsxandyas0, because xdg-shell does not give global window coordinates to clients.
The renderer computes the cursor position relative to the window from these two values (useElectronRelativeMouse). On Wayland the result is wrong. #2522 fixed the Controls Island for this case with DOM hover events, but DOM events cannot help eye tracking, because the cursor is usually outside the window.
A Wayland app cannot get these values by design. The compositor knows them.
Proposed fix (KDE only, tested)
I made a working fix for KDE Plasma:
- A small KWin script (declarative QML,
QtWebSockets) sends the global cursor position and the content geometry of theai-moeru-airiwindows to a loopback WebSocket (ws://127.0.0.1:6181). It sends a message only when a value changes, at most every 16 ms. - A small module in the main process (
kwin-cursor-bridge.ts) connects to it only on native Wayland. It uses the Node built-inWebSocket, so no new dependency is needed.screen.tsandwindow.tsuse these values when the bridge is connected, and use Electron's values when it is not. When the connection closes, the values are cleared, so the app never uses a stale position.
Because the fix corrects the values at the source, other features that depend on the cursor position and window bounds also work correctly on Wayland (click-through hit testing, fade on hover, Controls Island).
Limits:
- It works only on KDE Plasma (KWin). GNOME and wlroots compositors need a different way to get the values.
- The user must install and enable the KWin script. It could ship with the app, or the app could install it on first start.
Questions for maintainers:
- Do you want a PR for this approach? If yes, where should the KWin script live in the repo, and should the app install it automatically?
- Or do you prefer a different approach that works on more compositors?
System Info
System:
OS: Linux 7.2 CachyOS Linux
CPU: (24) x64 AMD Ryzen 9 5900X 12-Core Processor
GPU: NVIDIA GeForce RTX 4090
Desktop: KDE Plasma 6.7.5 (KWin 6.7.5), native Wayland session
Displays: 3 monitors, fractional scaling
AIRI:
Desktop app (stage-tamagotchi), built from main at 2e0e963
Electron: 43.4.1
Binaries:
Node: 24.21.0
pnpm: 11.24.0Validations
- Follow our Code of Conduct
- Read the Contributing Guide.
- Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- Check that this is a concrete bug. For Q&A, please open a GitHub Discussion instead.
Contributions
- I am willing to submit a PR to fix this issue
- I am willing to submit a PR with failing tests (actually just go ahead and do it, thanks!)
Source: moeru-ai/airi