Files
templisandClaude Opus 5 e65f9db3ae feat: Linux bulk importer for the Allflame Voyage Solver
Mirrors the solver's Windows-only voyage-import.ahk on KDE Wayland:

- poe-clip-bridge.py: clipboard watcher that keeps only PoE item text,
  serves it on loopback and can trigger a sweep from the browser
- voyage-sweep.py: hovers every cell of the in-game chart grid, Ctrl+C's
  it and hands over the whole batch in one go
- userscript: feeds the batch into the solver page without focusing it

Includes notes on the four Wayland pointer pitfalls this ran into.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 21:39:19 +02:00

108 lines
3.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Notes on driving the pointer under KDE Wayland
Everything here was measured on KDE Plasma 6 / `kwin_wayland`, multi-monitor,
game running as an XWayland client through Proton. If you are automating input
on Wayland, these four are what will bite you.
## 1. `ydotool mousemove --absolute` is useless on multi-monitor
Absolute positioning gets clamped into whatever output the pointer currently
sits on, so the mapping is neither linear nor monotonic:
| requested | landed on |
|---|---|
| `0,0` | `302,1081` |
| `1000,500` | `1855,747` |
| `5000,2500` | `1418,1411` |
| `6000,3000` | `301,1080` |
`xdotool mousemove` (XWarpPointer) does not work either — KWin ignores warp
requests from clients that do not own the pointer focus.
**Use relative motion.**
## 2. Injected relative motion is not applied 1:1
On one of the test monitors every injected delta came out multiplied by exactly
2.00 — not acceleration, a constant factor:
```
sent=-25,-96 moved=-50,-192 ratio=2.00,2.00
sent=+24,+99 moved=+48,+198 ratio=2.00,2.00
```
A naive "move by the remaining error" loop therefore oscillates forever around
the target, roughly ±100 px, and never converges.
**Fix:** learn the factor at runtime instead of hardcoding it. Send
`error / gain`, measure what actually happened, update `gain` with an
exponential moving average. Converges in a handful of iterations regardless of
scale factor or acceleration profile — see `move_to()` in `bin/voyage-sweep.py`.
Large single deltas additionally get distorted by pointer acceleration, so cap
each step (150 px works well).
## 3. `xdotool getmouselocation` freezes outside XWayland surfaces
X only learns the pointer position while the pointer is over an XWayland
surface. Anywhere else it keeps returning a stale value — for hours, with no
error:
```
KWin says: 5102,2532
xdotool says: 4300,2662 # stale, and stays that way
```
That silently breaks any control loop that starts outside the game window.
**Fix:** two-phase. Coarse approach with KWin's own cursor position, which is
always correct, then fine control with `xdotool` once the pointer is inside the
(XWayland) game window, where it is live and fast.
Reading KWin's cursor position without a compositor plugin:
```bash
echo 'print("PROBE " + workspace.cursorPos.x + "," + workspace.cursorPos.y);' > /tmp/probe.js
id=$(qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.loadScript /tmp/probe.js probe)
qdbus6 org.kde.KWin /Scripting/Script$id org.kde.kwin.Script.run
journalctl _COMM=kwin_wayland -n 5 -o cat | grep PROBE
qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.unloadScript probe
```
Roughly 300 ms per reading — fine for a coarse approach, too slow for a loop.
## 4. `wl-copy --clear` is asynchronous
Clearing the clipboard and then polling for "something appeared" is a race: the
poll still reads the *previous* owner's data. In a sweep this looks like every
cell copying the same item — 48 identical charts, all of them the first one.
**Fix:** write a unique sentinel token into the clipboard, then wait for it to be
replaced:
```python
token = f"sweep-{row}-{col}-{time.monotonic_ns()}"
subprocess.run(["wl-copy"], input=token, text=True)
send_ctrl_c()
# ... poll until clipboard != token
```
## Bonus: do not home the pointer into a screen corner
The obvious bootstrap for "get to a known position" — send a huge negative delta
and let it clamp in the corner — triggers KDE's hot corners. Ours opened the
application overview mid-sweep. Approach a known target directly instead.
## Bonus: measure the grid from a screenshot, do not eyeball it
Hand-calibrating cell corners is imprecise. Take a window screenshot, run a
brightness projection per axis to get the cell pitch, then draw the computed
centres back onto the screenshot and look at it:
```bash
spectacle -b -n -a -o window.png
```
That is how the 66.8 × 66.6 px pitch in this repo was derived — and how a wrong
row count (which silently changes the computed pitch) was caught.