feat: viture-glasses/external monitor#536
Open
maxjivi05 wants to merge 30 commits into
Open
Conversation
Swap the game onto a connected external display via a Presentation while the touch controls and slide-in menu stay on the phone. Adds an Output tab with a Send-to-display / Return-to-phone toggle, resolution and refresh controls, aspect ratio, and a Game Mode (HDMI ALLM) toggle. - Hybrid resolution/refresh: real Display.Modes switch the panel via preferredDisplayModeId; standard tiers (4K/2K/1080/720/480, 60-165Hz) render via SurfaceHolder.setFixedSize and are hardware-scaled, so the options work even on single-mode sinks. Re-scans on EDID change. - Exact swap-back: capture the phone surface size and re-measure/layout the reparented SurfaceView so it returns to full size. - Fix native use-after-free on repeated swaps by joining the retiring render thread and serializing attach/detach/destroy. - Show the EDID product name (DeviceProductInfo) for the connected device. - Translate all new strings into 14 locales.
…asses controls Reverse-engineered the Viture SpaceWalker control protocol (libglasses-internal.so) and added a pure-Kotlin VitureGlasses controller that talks to the glasses' MCU over USB: force display mode / refresh (1080p@120), 3D/SBS, brightness, and the electrochromic shade. Wired into ExternalDisplayController and the Output tab, gated so it only appears when Viture glasses are detected; TVs/monitors keep the standard Android display path. - VitureGlasses.kt: USB detect (vendor 0x35CA) + permission + device-attach receiver, V1 packet framing (CRC-16/XMODEM) verified byte-for-byte against the decompiled builder, per-model command IDs, MCU HID interface selection. - ExternalDisplayController: force the Viture refresh over USB when connected; expose brightness/shade/3D; re-apply mode when the glasses finish opening. - Output tab: a capability-gated "Glasses" card (brightness/shade/3D). - Aggressive Surface.setFrameRate (FIXED_SOURCE + CHANGE_FRAME_RATE_ALWAYS) for refresh. - New strings translated into all 14 locales.
…r-scaling fallback Verify after preferredDisplayModeId whether the panel actually moved; when the sink ignores the switch and hardware-scales (e.g. OnePlus external output, proven unswitchable even via the system setUserPreferredDisplayMode), latch into render- buffer scaling and relabel the Output tab with the panel's true native mode so the UI never implies a switch that did not happen.
# Conflicts: # app/src/main/runtime/display/XServerDisplayActivity.java
Reverse-engineered and on-device-verified the Viture USB control protocol (SpaceWalker libglasses-internal.so). Commands go to HID interface 1: V1 packets out INT endpoint 0x02, state reports back on INT 0x82 — not the IMU (0x01) or CDC (0x03) endpoints. Verified on a Viture Pro XR: display-mode forces the panel 60->120Hz, and brightness/volume/film(electrochromic)/3D all confirmed via the MCU's echo reports. - VitureGlasses.kt: add setVolume, capture the MCU IN endpoint, add readState(). - ExternalDisplayController.java: volume getters/setter. - Output-tab glasses card: Volume slider (+ string in all 14 locales).
# Conflicts: # app/src/main/runtime/display/XServerDrawerMenu.kt
…0330) The Pro XR (and all non-Beast models) drive the sunblock film with a binary on/off command msgId 0x000E, not the stepped 0x0330 — verified from the decompiled .rodata PID tables (0x101d is in the binary group) and from SpaceWalker's own setFilmModeFloat path. The stepped command was echoed but ignored by the firmware, which is why the film never darkened. filmIsStepped() now returns isBeast(), so the Output card shows an on/off toggle for the Pro XR.
…ay swap Adds a single app-wide GlassesManager that owns the one Viture USB controller and the persisted glasses settings, shared by the library and the in-game swap so they never double-claim the interface. A glasses icon (shown only when connected) sits left of the library Filter button and opens a Material 3 sheet to set Refresh (60/90/120), Brightness, Volume, Sunblock and 3D; values persist and re-apply on every connect. Launching a game now swaps automatically to the external display (the old Mirror/Swap prompt is removed), and glasses come up at the persisted rate (120 by default) from the start. The in-game Output controls now delegate to GlassesManager so both surfaces share one source of truth.
Redesign the library glasses sheet into a clean two-column layout that fits the landscape viewport so the Sunblock and 3D toggles are always visible. Brightness and Volume now default to 100% and are applied to the glasses on every connect (previously the unset default did nothing until dragged); values are shown as a percentage and persist, re-applying on reconnect like refresh rate.
The XServer-menu glasses controls routed every slider tick through GlassesManager -> listener -> renderDrawerMenu + applyOutputMode, recomposing the whole drawer and re-sending the display-mode command on each drag pixel, making the sliders janky/unresponsive. The listener now distinguishes a connection change from a settings change and only re-applies the mode / re-renders on (re)connect; settings persist + apply via the shared controller as before.
…nal display are active When a physical controller is connected AND the game is swapped to an external display (glasses/monitor/TV), the touch controls are hidden and a Material 3 gauge HUD is shown at the top of the phone (the touchpad remains as the trackpad). When either is absent, the normal touch controls + on-screen overlay return. The gauge HUD is a single source of truth with the XServer HUD toggles: FrameRating pushes live values each tick and the menu pushes which elements are enabled, so the same metrics appear in both the in-game overlay and the phone gauges.
… external display The gauge HUD now takes half the screen (left half in landscape, top half in portrait) with a centered grid of larger gauges, and shows whenever a physical controller + external-display swap are active regardless of the FPS-monitor toggle. FrameRating is always created so values feed the gauges, and FPS frames are recorded in HUD mode too. Re-laid out on orientation change.
…rash + blank HUD) A ComposeView nested inside the game-frame AndroidView crashed on detach (AndroidComposeViewAccessibilityDelegateCompat NPE in onViewDetachedFromWindow) and never rendered. Render the half-screen gauge HUD directly in the XServer Compose host instead, toggled by PerformanceHudState.visible, consuming touches in its half so the rest stays a trackpad. Also keep the touch controls hidden in HUD mode even when showInputControls/startTouchscreenTimeout fire during setup.
When the controller HUD mirrors the metrics to the Compose overlay, the on-screen FrameRating view is hidden, so recordGameFrame bailed before computing FPS and the window/renderer was never detected. Detect the game window in HUD mode (it is display-independent, so FPS/frametime now track the window shown on the glasses) and let recordGameFrame run while the overlay is hidden via a hudMirrorActive flag. Gauges stay on screen whenever their element is enabled (N/A for a momentarily missing value) instead of disappearing on a single bad read, temperature folds into the battery gauge, and the renderer pill is pinned just above the bottom so it is never clipped.
Resolve two conflicts, keeping both sides' behavior: - startTouchscreenTimeout: combine the HUD-mode guard (!controllerHudMode) with main's inputControlsRevealAllowed gate so touch controls stay hidden in the performance-HUD path and no longer flash on boot. - XServerDrawerState: keep main's cursorSpeed field alongside the external-display Output pane and Viture glasses fields. Verified: standardDebug builds clean and the glasses/output + performance-HUD wiring (controllerHudMode, hudMirrorActive, syncFrameRatingWithExistingWindows, the Output pane and Viture fields) is preserved.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bdc9362ccc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Adds a Record button to the X server drawer that opens a settings popup (frame rate, resolution, quality presets, and a Record UI toggle) and captures gameplay to WinNative/Recordings as H.264/AAC MP4. Capture taps the renderer's composited output via a Vulkan mirror swapchain on a MediaCodec input surface, so it records the game on any display (phone, TV, monitor, glasses), sampled to the chosen fps and scaled to the chosen resolution. Game audio is teed from the ALSA bridge. With Record UI on, the HUD and on-screen controls are alpha-composited over the game in the mirror.
verifyPhysicalSwitch now requires both resolution and refresh to match the requested mode before treating it as a physical switch; matching only one (e.g. an ignored resolution change at the same Hz) skipped the render-scaling fallback and left the Output UI claiming a switch. Disabling 3D on Viture glasses now restores the saved refresh instead of forcing 1080p60, so a non-3D (re)connect no longer drops 90/120 Hz to 60.
# Conflicts: # app/src/main/runtime/display/XServerDrawerMenu.kt
# Conflicts: # app/src/main/runtime/display/renderer/VulkanRenderer.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Initial testing for Viture Glasses support and external monitor and tv as well as Casting to different displays.