Repository navigation
feat(#332): Linux click-through and right-drag move for the transparent overlay - #346
Conversation
dfattal
left a comment
There was a problem hiding this comment.
Thanks. This is a solid port: the region math matches displayxr_win32.c, the platform guards are clean, and the undecorate lifetime keeps the #334 invariant (session-fail goes through LifecycleStop). One blocking issue, the rest are follow-ups.
Blocking
B1. The per-frame region build reaches s_dpy from Unity's main thread. That's the hazard #335 was built to avoid, reached indirectly here.
lin_rects_add_wsui(displayxr_linux.c:~935) runs on everyhit_mask/hit_rectcall, so every frame, as does the single-zone fallback inhit_mask(:~1020). Both calldxr_prov_get_zone_rect_px(0, …).- With no 3D zone active, that goes
ps_window_size→displayxr_linux_window_size→XGetGeometry(s_dpy, …), a synchronous round trip on the connection the graphics thread uses every frame (displayxr_linux_track_windowfromGfxPopulateNextFrameDesc, and the render-rectps_window_size) and the runtime's Vulkan surface borrows. Nothing callsXInitThreads. - lenovo-avatar has a zone active, so
ps_zone_active()short-circuits and never touches X. That's why your testing didn't hit it. Any transparent Linux app without a zone would. - Fix: on Linux, don't call
dxr_prov_get_zone_rect_px(0)unless a zone is active, and use(0, 0, dst_w, dst_h)from C# for the HUD frame (it's the same window). Or read a provider-cached size. The fix for #291 adds exactly that,dxr_prov_get_composited_size(an atomic filled whereverps_window_sizeruns, so never a live X query from the caller); it's coming in a PR shortly. Please also add a one-line comment that nothing on the main thread may reachs_dpy.
Follow-ups (non-blocking)
- N1. Stuck drag. The drag only ends when the managed
IsRightPressedreads false. If a WM grab (Super/overview, a keybinding) eats the release, the window follows the cursor with no button held.lin_root_pointeralready doesXQueryPointer: end the drag when!(mask & Button3Mask). Also clears_drag_activeindisplayxr_linux_destroy_weave_window. - N2.
hit_mask(NULL)differs from Windows. Windows doesSetWindowRgn(NULL), so the whole window becomes clickable. Linux keeps the last silhouette as the input region. Reset the input shape there, or document the difference. - N3. Teardown reset goes out on a different X connection.
lin_reset_input_regionusess_dpy, while every region set useds_hit_dpy. Order is only guaranteed within one connection. Reset ons_hit_dpy+XSync. Also makelin_hit_readyrequire theXShapeCombineMasksymbol, so the reset can't silently no-op. - N4. No X error handler. An async
BadWindowexits the process. Exposure is limited (only our windows and Unity's), but the new per-frames_hit_dpyrequests ons_winadd some. Consider a scoped trap around them. - N5. Geometry restore depends on the WM. It depends on the WM honouring
_NET_MOVERESIZE_WINDOWsize under StaticGravity, andmeasuredis read once at LifecycleStart. OneXGetGeometrycheck on the nexttrack_windowtick, logging a mismatch, would catch a regression of the +74 px growth. - N6. HUD union uses the full layer rect (widened by disparity), regardless of alpha. That's fine at today's sizes; please document it.
- N7. Linux maps a single-zone sub-rect mask into zone 0's rect, while Windows maps it to the full client. Windows probably has the misalignment you describe, so I'll file it on our side.
- N8. Nits:
DXR_WSUI_MAX_SLOTSduplicates the header (include it or_Static_assert). The hit and focus X connections could be shared, since both are main-thread only. The C# Linux path callsTryGetStereoMatricestwice per frame, and it still renders and reads back the silhouette when the session fell back to the opaque child.
Note: Screen.* is the right space on Linux for this PR's region, since it lives on Unity's own window. That's unlike Windows, where Screen.* describes the cloaked window (#291).
🤖 Generated with Claude Code
…transparent overlay Clicks outside the avatar now reach the desktop on Linux, and a right-button drag on the avatar moves the window, as on Windows. - Click-through: the Windows click-through exports (hit_mask, hit_rect, hit_active, surround_rect/_mask) now exist on Linux with the same semantics. They build an XShape INPUT region on Unity's cloaked window (the overlay above it takes no input). Live window-space UI layers are unioned in, so a HUD outside the avatar still takes its clicks. A single zone with a sub-rect maps the mask into that zone's rect. - Unity's window is undecorated while cloaked: mutter takes input over a decorated window's whole frame and ignored the input shape. Its client origin and size are kept, and decorations come back with the cloak. - Right-drag move: displayxr_linux_drag_window follows the root pointer and moves Unity's window with _NET_MOVERESIZE_WINDOW; the overlay follows it. - C#: DisplayXRTransparentOverlay runs the hit-test, pointer events and the silhouette mask on Linux too. The click-through bindings in DisplayXRNative move to a Windows + Linux block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- No main-thread path reaches s_dpy any more (review B1): the region code reads the new cached dxr_prov_get_active_zone_rect_px instead of dxr_prov_get_zone_rect_px(0), which without a zone fell back to a live XGetGeometry on the connection the graphics thread and runtime share. - HUD rects are placed in window space, as the layer and the apps' routers read them (they landed ~200 px low in zone space; HUD clicks fell through). - s_hit_dpy is guarded by a mutex; the teardown reset goes out on it with an XSync, and a flag stops a late push from re-shaping after teardown. - hit_mask(NULL) resets the input shape (the whole window catches), as Windows does. - The drag also ends when XQueryPointer says Button3 is up (a WM grab can eat the release), and at teardown. - One check that the WM honoured the restored size, logged on mismatch. - C#: skip all of it when the session fell back to the opaque child (new displayxr_linux_click_through_active), and read the stereo views once per frame. - Include displayxr_window_space_ui.h instead of duplicating its constant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ubble clicks land What the user sees is the override-redirect overlay, always on top; what takes the clicks is Unity's window beneath it. After the user focused another window, the WM raised it over Unity's, and a click on a HUD or the bubble went to that window. _NET_WM_STATE_ABOVE keeps Unity's window in the overlay's stacking place, as the Windows overlay HWND is WS_EX_TOPMOST. Same lifetime as the cloak. Docs: the Linux section of click-through-mask.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0e32b7f to
16b8c61
Compare
|
Thanks for the review. Pushed (rebased on main, which now has #345): B1 (main thread reaching Follow-ups:
Two more fixes from DS1 testing:
The PR description is updated. |
dfattal
left a comment
There was a problem hiding this comment.
B1 verified: dxr_prov_get_active_zone_rect_px reads only cached session state and never queries the window, so the main thread no longer reaches s_dpy. N1–N3, N5, N6 and N8 look good, and the reasoning on N4 holds (XSetErrorHandler is process-global and libX11 is shared with Unity). The zone→window-space HUD fix and _NET_WM_STATE_ABOVE are good catches. Once #349 lands, switching the HUD frame to dxr_prov_get_composited_size makes sense as a follow-up. Merging.
…from the merged source Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…350) (#351) SetWindowRgn is a visual clip, not only a hit mask, so a window-space UI HUD off the avatar silhouette was cut away. Every region the transparent overlay builds (hit mask + surround union, and the AABB hit-rect path before the first mask) now unions the client-pixel rect of each live wsui slot, natively and with no app call, mirroring the Linux input region (#346). The fractions are of the whole window client, not zone 0. The runtime draws the layer into each per-view tile, and the tile spans the full window in both plain-projection and zones frames. The rect is widened by |disparity|/2 per side and scaled by the same client size the silhouette uses. A slot counts only while a texture is registered, so a disabled HUD drops out. HUD rects feed the region hash (and bypass the hit-rect hysteresis), so toggling re-applies the region and a static HUD costs no SetWindowRgn. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Part of #332 (Linux transparent overlay: click-through and window handling).
What changes
On Linux, in a transparent session:
WS_EX_TOPMOST.How
Click-through: an XShape input region on Unity's window. The weave overlay is an ARGB override-redirect top-level that takes no input; Unity's cloaked window beneath it gets the clicks. The Windows click-through exports now exist on Linux with the same arguments and semantics:
displayxr_set_overlay_hit_mask,_hit_rect,_hit_active,_surround_rectand_surround_mask. They build the same region (silhouette mask, the AABB until the first mask, the surround rect/mask). Instead ofSetWindowRgn, the region becomes the input shape of Unity's window.hit_mask(NULL)resets it (the whole window catches), as on Windows.Linux-specific differences:
displayxr_win32.cmaps that case to the full client (David is filing that on the Windows side)._NET_WM_STATE_ABOVE). The visible overlay is always on top, but once another window was focused, the WM raised it over Unity's window. A click on a HUD or the bubble the user could see then went to that window instead.All three window changes have the cloak's lifetime: they are set at overlay creation and restored in
displayxr_linux_destroy_weave_window, so the #334 invariant holds.Right-drag move.
displayxr_linux_drag_window(right_pressed)is called every frame. It follows the root pointer and moves Unity's window with_NET_MOVERESIZE_WINDOW(StaticGravity); the overlay follows it indisplayxr_linux_track_window. The drag ends when Unity reports the button up, or whenXQueryPointersays Button3 is up (a WM grab can eat the release), or at teardown.Threads. The exports run on Unity's main thread and use their own connection,
s_hit_dpy, guarded by a mutex that the teardown reset also takes. Nothing reachable from them touchess_dpy(the graphics thread's and the runtime's connection). That includesdxr_prov_get_zone_rect_px(0), which without a zone falls back to a liveXGetGeometry(s_dpy). The region code reads the new cacheddxr_prov_get_active_zone_rect_pxinstead.C#.
DisplayXRTransparentOverlayruns the hit-test, pointer events (onPointerEnter/Down/Up/Click) and silhouette mask on Linux, as on Windows/macOS. It skips all of it when the session fell back to the opaque child (displayxr_linux_click_through_active). The five click-through bindings inDisplayXRNativemove from the Windows-only block to a Windows + Linux block, with unchanged declarations.Platforms
displayxr_linux.c, a Linux-only block indisplayxr_exports.h, and the new cached getter indisplayxr_provider_session.cpp(unused on Windows/macOS) change. The C# changes are a binding move and new#elif UNITY_STANDALONE_LINUXbranches.Testing
Ubuntu 26.04 (GNOME, XWayland), RTX 4090 Laptop, Leia DS1. App: the lenovo-avatar Linux player. Runtime: v2.21.11 with #1783/#1784 applied, so HUDs show outside the avatar.
Checked by hand:
From the log,
xwininfo/xpropandXShapeGetRectangles:_NET_WM_STATE_ABOVE.hit_mask: … (silhouette N, surround 1, wsui 2)).Not covered: a transparent app without a 3D zone (lenovo always has one; that path now reads only cached state), and other WMs (KDE, X11 sessions without XWayland).
lenovo-avatar follow-up: it pushes its bubble surround rect only under
UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN. My test build widened that guard locally. The app change has to wait for a plugin release with these Linux bindings.🤖 Generated with Claude Code