|
| 1 | +//go:build linux |
| 2 | + |
| 3 | +package linux |
| 4 | + |
| 5 | +import ( |
| 6 | + "context" |
| 7 | + "image" |
| 8 | + "os" |
| 9 | +) |
| 10 | + |
| 11 | +// Wayland compositor-IPC cursor position. A Wayland client cannot query the |
| 12 | +// global pointer position, so the wlroots client keeps a cache that a plain |
| 13 | +// user-driven mouse move invalidates (wlroots_client.c). The layer-shell |
| 14 | +// discovery refresh corrects it, but depends on the compositor delivering a |
| 15 | +// wl_pointer.enter to a freshly mapped surface within its budget — which |
| 16 | +// Hyprland does not reliably do (#1279). Hyprland exposes the authoritative |
| 17 | +// position over its CLI instead, so the sync path asks it first and keeps |
| 18 | +// discovery as the fallback for the compositors that expose no such query |
| 19 | +// (Sway and niri today). |
| 20 | + |
| 21 | +// waylandCompositorCursorPosition returns the physical cursor position from |
| 22 | +// the running compositor's IPC, when it exposes one. ok=false means "no such |
| 23 | +// query here" — the caller falls back to layer-shell discovery, so a stale |
| 24 | +// HYPRLAND_INSTANCE_SIGNATURE in a non-Hyprland session degrades to the |
| 25 | +// pre-IPC behavior (hyprctl fails to connect and reports nothing). |
| 26 | +// |
| 27 | +// Reached only behind waylandUsesWlrClientStack, which tests the backend the |
| 28 | +// adapter was built with; the socket variable picks the CLI, never the backend |
| 29 | +// (internal/adapter/platform/AGENTS.md). |
| 30 | +func waylandCompositorCursorPosition(ctx context.Context) (image.Point, bool) { |
| 31 | + if os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") == "" { |
| 32 | + return image.Point{}, false |
| 33 | + } |
| 34 | + |
| 35 | + return hyprlandCursorPosition(ctx) |
| 36 | +} |
| 37 | + |
| 38 | +// hyprlandCursorPosition reads `hyprctl -j cursorpos`, whose x/y are global |
| 39 | +// layout (logical) pixels. That is the space the screen list already lives in: |
| 40 | +// the wlroots client fills each screen's origin and size from |
| 41 | +// zxdg_output_v1.logical_position/logical_size (neru_xdg_output_listener, |
| 42 | +// wlroots_client.c), so on a scaled monitor both sides shrink together and |
| 43 | +// containment checks against screen bounds stay consistent — the same |
| 44 | +// agreement hyprlandFocusedWindowBounds already relies on for `activewindow`. |
| 45 | +func hyprlandCursorPosition(ctx context.Context) (image.Point, bool) { |
| 46 | + var pos struct { |
| 47 | + X int `json:"x"` |
| 48 | + Y int `json:"y"` |
| 49 | + } |
| 50 | + if !compositorJSONContext(ctx, &pos, "hyprctl", "-j", "cursorpos") { |
| 51 | + return image.Point{}, false |
| 52 | + } |
| 53 | + |
| 54 | + return image.Point{X: pos.X, Y: pos.Y}, true |
| 55 | +} |
0 commit comments