Skip to content

feat(#266): move the window onto the 3D panel on Linux - #352

Merged
dfattal merged 1 commit into
DisplayXR:mainfrom
byungjul:feat/linux-move-to-display
Oct 6, 2026
Merged

dfattal merged 1 commit into
DisplayXR:mainfrom
byungjul:feat/linux-move-to-display

Conversation

@byungjul

@byungjul byungjul commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Part of #266 (open on the 3D panel), Linux side.

Problem

On Linux a DisplayXR app opens on the OS primary display. When the DS1 isn't the primary, the app shows up on the laptop screen in 2D, with eye tracking paused, until the user drags it onto the panel.

The runtime already knows where the panel is. On Linux it reports the panel rect through XR_DXR_display_info v18, in X root coordinates:

[DisplayXR-PROV] Display desktop rect: (3840,0 3840x2160) confirmed=1 primary=0 dev='HDMI-1' [v18]

But dxr_prov_move_window_to_display returned 0 off Windows, so DisplayXRTargetDisplay / DisplayXRProvider.MoveWindowToDisplay did nothing on Linux.

Fix

  • The provider now keeps the panel extent (v18 desktopRect.extent) as well as its origin. A v16 runtime reports no extent; there the panel's native pixel size is used.
  • New Linux arm of dxr_prov_move_window_to_display, with the same gating as Windows: it needs an origin and skips an unconfirmed fallback rect (confirmed=0). It calls the new displayxr_linux_move_app_window_to_rect, which:
    • centres Unity's window on the panel with _NET_MOVERESIZE_WINDOW (client origin, StaticGravity), clamped so a window larger than the panel keeps its top-left on it;
    • leaves the window alone if its centre is already on the panel, as Windows does.
  • It runs on Unity's main thread, so it uses the main-thread X connection under its mutex, never s_dpy (the THREADS rule from feat(#332): Linux click-through and right-drag move for the transparent overlay #346). In transparent mode the overlay follows Unity's window, as it does for the right-drag.
  • Docs: DisplayXRTargetDisplay and MoveWindowToDisplay now say Windows and Linux.

Platforms: Linux behaviour change, and only for apps that use DisplayXRTargetDisplay / MoveWindowToDisplay (opt-in). Windows and macOS are unchanged; the provider only stores one more field.

Testing

Ubuntu 26.04 (GNOME, XWayland), laptop eDP as the primary display, DS1 on HDMI-1 to its right at 200%. App: lenovo-avatar (transparent) with a small bootstrap that adds DisplayXRTargetDisplay at startup. Runtime 2.21.11, plugin = v2.21.0 + this change.

  • Two launches. In both the app opened on the laptop screen and moved to the centre of the DS1 once the session was up:

    [DisplayXR-LNX] move_to_display: (1886,232) 840x1448 -> (5340,356) on the panel (3840,0 3840x2160)
    [DisplayXR] TargetDisplay: window is on the DisplayXR panel.
    

    xwininfo confirms that Unity's window and the overlay both end up at (5340,356).

  • On the DS1, after the move: 3D on, tracking on, weave correct (checked by eye).

  • Clicks after the move are correct (checked by hand on the DS1): the HUD slider follows the cursor, a left-drag on the avatar rotates it, and a click beside the avatar reaches the window behind. LeiaUnitySimpleSDK moves with Screen.MoveMainWindowTo because an X11 move behind Unity's back offset Input.mousePosition there. The DisplayXR pointer path reads the Input System and is not affected.

lenovo-avatar doesn't use DisplayXRTargetDisplay yet. Adding it is a separate app PR, since it also changes Windows (the window moves when the panel isn't the primary).

🤖 Generated with Claude Code

The runtime already reports the panel's desktop rect on Linux (XR_DXR_display_info
v18: X root coordinates, e.g. HDMI-1 (3840,0 3840x2160) confirmed=1), but
dxr_prov_move_window_to_display returned 0 off Windows, so DisplayXRTargetDisplay /
DisplayXRProvider.MoveWindowToDisplay did nothing and the app stayed on the
primary display (and in 2D, with tracking paused) until dragged across.

- The provider keeps the panel extent as well as its origin (v18).
- Linux arm of dxr_prov_move_window_to_display: same gating as Windows (needs an
  origin; skips an unconfirmed fallback rect), then
  displayxr_linux_move_app_window_to_rect centres Unity's window on the panel
  with _NET_MOVERESIZE_WINDOW, or leaves it if its centre is already there.
  Main-thread X connection under its mutex, never s_dpy. The transparent
  overlay follows Unity's window as usual.
- Docs: TargetDisplay / MoveWindowToDisplay now say Windows and Linux.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@byungjul
byungjul requested a review from dfattal as a code owner October 6, 2026 04:46

@dfattal dfattal left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. The Linux arm mirrors the Windows gating (no origin or unconfirmed panel → no move; already on the panel → no move). It runs on the main-thread X connection under s_hit_mutex, never s_dpy, per the #346 THREADS rule, and the panel rect is in X root coordinates, the same space as Unity's window, so there's no DPI conversion. The v16 fallback to native pixel size is sensible. Thanks for verifying the input path after the move. Merging.

@dfattal
dfattal merged commit 2ef8de7 into DisplayXR:main Oct 6, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants