Skip to content

feat: viture-glasses/external monitor#536

Open
maxjivi05 wants to merge 30 commits into
WinNative-Emu:mainfrom
maxjivi05:external-monitor
Open

feat: viture-glasses/external monitor#536
maxjivi05 wants to merge 30 commits into
WinNative-Emu:mainfrom
maxjivi05:external-monitor

Conversation

@maxjivi05

Copy link
Copy Markdown
Contributor

Initial testing for Viture Glasses support and external monitor and tv as well as Casting to different displays.

maxjivi05 added 22 commits June 1, 2026 21:09
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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread app/src/main/runtime/display/ExternalDisplayController.java Outdated
Comment thread app/src/main/runtime/display/VitureGlasses.kt
maxjivi05 added 7 commits June 9, 2026 09:43
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
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.

1 participant