Skip to content

feat: Stream UI improvements — mic indicator, mic test meter, and stream control fixes#133

Open
Meganugger wants to merge 7 commits intoOpenCloudGaming:devfrom
OpenCloudFork:feat/stream-ui-improvements
Open

feat: Stream UI improvements — mic indicator, mic test meter, and stream control fixes#133
Meganugger wants to merge 7 commits intoOpenCloudGaming:devfrom
OpenCloudFork:feat/stream-ui-improvements

Conversation

@Meganugger
Copy link

Summary

Improves the streaming UI with a mic status indicator in StreamView, fixes mic indicator overlap/centering with stream controls, and adds a manual start/stop mic test meter in settings. Also includes keyboard layout translation commits that were subsequently reverted (net effect: mic indicator + mic test meter only).

Added

  • Mic active/muted indicator overlay in StreamView
  • Manual start/stop mic test meter in SettingsPage for resource-efficient mic testing
  • Mic test meter styles with level visualization

Updated

  • StreamView.tsx — mic indicator rendering with position and state awareness
  • SettingsPage.tsx — mic test meter with manual start/stop controls
  • styles.css — mic indicator positioning, test meter visualization, overlap fixes

Fixed

  • Mic indicator no longer overlaps with stream control buttons
  • Mic indicator correctly centered in stream view
  • Mic test meter now only runs when manually started (reduces resource usage)

Validation Notes

Recommended smoke checks:

  • Start a stream with mic enabled and verify the mic indicator appears in StreamView
  • Toggle mic during stream and verify indicator updates (active/muted states)
  • Open Settings → Microphone and test the manual start/stop mic meter
  • Verify mic indicator does not overlap with stream control buttons at various window sizes

Meganugger and others added 7 commits February 22, 2026 14:39
…amView

- Add KeyboardLayout type ('auto'|'qwerty'|'azerty'|'qwertz') to Settings
- Detect OS keyboard layout via navigator.keyboard.getLayoutMap() with
  language-based fallback heuristic
- Remap VK codes for non-QWERTY layouts; scancodes stay physical
  (correct for cloud gaming where remote OS interprets scancodes)
- Add Keyboard Layout selector in Settings > Input section
- Show detected/effective layout in stream stats diagnostics panel
- Add MicStatus indicator (Mic/MicOff icon) in StreamView info area
- Subscribe to MicAudioService state changes for live mic status

Co-authored-by: Capy <capy@capy.ai>
- Move sv-mic right offset from 80px to 110px so it clears sv-end
  (64px right + 38px width + 8px gap)
- Match sv-mic height to button height (38px) for visual alignment
- Use inline-flex + justify-content:center + line-height:1 for
  perfect icon+text centering inside the pill
- Works at all window sizes, fullscreen, and DPI scales

Co-authored-by: Capy <capy@capy.ai>
The previous VK-remap approach only handled letter swaps (A/Q, W/Z, Y/Z).
Punctuation and shifted characters (! ? * : ; etc.) were wrong because
they live on different physical keys across layouts.

New approach for non-QWERTY layouts:
- On keydown, if event.key is a single printable character and no
  Ctrl/Meta modifier is active, look up which US-QWERTY physical key +
  Shift produces that character via a complete ASCII lookup table.
- Send the US keystroke (VK + scancode + correct Shift state) as an
  immediate keydown+keyup pair to the remote VM.
- Mark the event.code so the real keyup is skipped (already sent).
- Ctrl/Meta combos (Ctrl+C, Ctrl+V, etc.) bypass translation and use
  the existing scancode path so shortcuts remain correct.
- Non-printable keys (Escape, arrows, F-keys, etc.) are unaffected.

New file: keyboardCharMap.ts — full US-QWERTY printable ASCII table
  (a-z, A-Z, 0-9, all shifted digit-row symbols, all punctuation).

Co-authored-by: Capy <capy@capy.ai>
Copy link

@capy-ai capy-ai bot left a comment

Choose a reason for hiding this comment

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

Added 3 comments

checked={settings.micNoiseSuppression}
onChange={(e) => handleChange("micNoiseSuppression", e.target.checked)}
/>
<span className="settings-toggle-track" />
Copy link

Choose a reason for hiding this comment

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

[🟡 Medium]

The gain slider onChange handler attempts to interact with the running AudioContext but the code is a no-op. It reads micTestContextRef.current.destination into nodes then immediately discards it with void nodes. The actual GainNode created in startMicTest is a local variable and is never stored in a ref, so there is no way to update the gain on the live audio pipeline. Changing the gain slider while the mic test is running has no effect on the meter reading until the user stops and restarts the test.

// opennow-stable/src/renderer/src/components/SettingsPage.tsx
onChange={(e) => {
  const v = parseInt(e.target.value, 10) / 100;
  handleChange("micGain", v);
  if (micTestContextRef.current) {
    const nodes = micTestContextRef.current.destination;
    void nodes; // dead code — gain node is not accessible
  }
}}

Store the GainNode in a ref (e.g. micTestGainRef) inside startMicTest, then update micTestGainRef.current.gain.value in the slider handler.

import { formatShortcutForDisplay, isShortcutMatch, normalizeShortcut } from "./shortcuts";
import { useControllerNavigation } from "./controllerNavigation";

import { MicAudioService } from "./gfn/micAudioService";
Copy link

Choose a reason for hiding this comment

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

[🟠 High]

The App.tsx diff adds imports from ./gfn/micAudioService, ./flight/FlightHidService, and ./gfn/hdrCapability, but none of these modules exist in the repository or in the PR's changed file list. These appear to be from the keyboard layout / HDR commits that the PR description states were reverted. If these imports survive in the final branch state, the build will fail with module-not-found errors. The MicAudioService class is instantiated and used extensively (.configure(), .dispose(), .onStateChange(), .getState()), so this is not just a stale import — it's load-bearing code that depends on a missing module.

// opennow-stable/src/renderer/src/App.tsx
import { MicAudioService } from "./gfn/micAudioService";
import type { MicAudioState } from "./gfn/micAudioService";
import { getFlightHidService } from "./flight/FlightHidService";
import { probeHdrCapability, shouldEnableHdr, buildInitialHdrState } from "./gfn/hdrCapability";

Either add these modules to the PR, or clean up the revert so these imports are removed.

background: var(--card-hover);
border-color: rgba(255,255,255,0.12);
transform: translateY(-1px);

Copy link

Choose a reason for hiding this comment

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

[🟡 Medium]

In the styles.css diff, all new .mic-* CSS rules (.mic-device-select:focus, .mic-refresh-btn, .mic-test-meter-wrap, .mic-level-meter, .mic-level-meter-fill) are inserted between the last property (transform: translateY(-1px);) and the closing } of the .settings-export-logs-btn:hover rule. This places them inside the hover block. In standard CSS, nested selectors are either ignored or handled via the CSS nesting spec (limited browser support in Electron's Chromium). If unintentional, these rules will not apply as top-level selectors and the mic test meter will have no styling. Verify the mic test meter renders correctly, or move these rules outside the hover block.

/* styles.css — new rules inserted before the closing } of :hover */
  transform: translateY(-1px);

.mic-device-select:focus { ... }
.mic-level-meter { ... }
} /* ← this } closes .settings-export-logs-btn:hover */

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