Phase 1 (MVP) of #25, built to the plan in #25 (comment) with the decisions made there: everyone in the call, speakers lit; shown while Lotus isn't focused; off by default (beta); voice only.
Web side: LotusGuild/cinny feat/voice-overlay (setting, useTauriOverlay). Either can merge first. Until both are in, nothing calls the command and nothing shows.
What it does
While you're in a call with the setting on and Lotus isn't the focused window, a small window sits in a corner of the monitor your foreground app is on. It shows:
a header with your own Muted / Deafened / PTT state;
everyone in the call (you first), with a green ring on whoever is speaking and a "Muted" pill on muted people;
"+N more" past 10 people.
How (src-tauri/src/native/overlay.rs)
Separate window, no injection:
transparent, no decorations, always on top, no taskbar button;
focusable(false) plus WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW, shown with SW_SHOWNOACTIVATE and SetWindowPos(…, SWP_NOACTIVATE);
set_ignore_cursor_events(true), so clicks pass through.
It must never take focus from a game.
Page: three static files (src-tauri/overlay/) served by the binary over the lotusoverlay custom protocol.
CSP default-src 'none'; script-src 'self'; style-src 'self', so no network.
No IPC. No capability names the overlay window, and the new command also rejects any caller that isn't main.
Rust pushes state with eval of a serde JSON literal. The page builds the DOM with textContent only, so display names can't inject markup.
set_overlay_state (async, because creating a window from a sync command can deadlock on Windows):
sanitises its input: at most 50 people, names cut to 40 characters, a known corner;
stores the snapshot and shows or hides the window;
tracks the main window's Focused events.
Placement: the work area of the foreground window's monitor (GetForegroundWindow → monitor_from_point), DPI-scaled, 16 px margin, chosen corner. It's recomputed on each update.
Elsewhere: Windows only; the command is a no-op on other platforms.
Wiring: the command is declared in build.rs and granted in capabilities/migrated.json, like every other command.
Tests
cargo test --lib: 37/37, 9 of them new:
show/hide rule;
corner parsing;
input sanitising;
window size;
placement in each corner and on tiny or offset monitors;
script escaping (a name containing ");alert(1);//</script> and U+2028 round-trips as data);
the protocol serving only the page files;
the page using no innerHTML and no external URLs.
cargo check and cargo clippy for x86_64-pc-windows-gnu are clean, so the Windows-only code type-checks. The MSVC build hasn't run yet; I'm starting the Windows smoke workflow on this branch.
Not included: the regenerated src-tauri/gen/schemas/*. The build rewrites them, and they only add the new allow-set-overlay-state permission.
Needs a real Windows check before it leaves beta
Settings → Calls → In-game overlay on, join a call, alt-tab into a borderless game: the overlay appears in the chosen corner, the game keeps focus, and clicks pass through.
The speaking ring follows whoever talks, and mute/deafen/PTT show in the header.
Focusing Lotus hides it, and hanging up hides it.
A transparent background (no white or black box) on your setup.
An exclusive-fullscreen game: expected not to show it (documented in the setting's help text).
Phase 1 (MVP) of #25, built to the plan in https://code.lotusguild.org/LotusGuild/cinny-desktop/issues/25#issuecomment-6206 with the decisions made there: **everyone in the call, speakers lit; shown while Lotus isn't focused; off by default (beta); voice only.**
Web side: LotusGuild/cinny `feat/voice-overlay` (setting, `useTauriOverlay`). Either can merge first. Until both are in, nothing calls the command and nothing shows.
## What it does
While you're in a call with the setting on and Lotus isn't the focused window, a small window sits in a corner of the monitor your foreground app is on. It shows:
- a header with your own **Muted / Deafened / PTT** state;
- **everyone in the call** (you first), with a green ring on whoever is speaking and a "Muted" pill on muted people;
- "+N more" past 10 people.
## How (`src-tauri/src/native/overlay.rs`)
- **Separate window, no injection:**
- transparent, no decorations, always on top, no taskbar button;
- `focusable(false)` plus `WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW`, shown with `SW_SHOWNOACTIVATE` and `SetWindowPos(…, SWP_NOACTIVATE)`;
- `set_ignore_cursor_events(true)`, so clicks pass through.
It must never take focus from a game.
- **Page:** three static files (`src-tauri/overlay/`) served by the binary over the `lotusoverlay` custom protocol.
- CSP `default-src 'none'; script-src 'self'; style-src 'self'`, so no network.
- **No IPC.** No capability names the `overlay` window, and the new command also rejects any caller that isn't `main`.
- Rust pushes state with `eval` of a serde JSON literal. The page builds the DOM with `textContent` only, so display names can't inject markup.
- **`set_overlay_state`** (async, because creating a window from a sync command can deadlock on Windows):
- sanitises its input: at most 50 people, names cut to 40 characters, a known corner;
- stores the snapshot and shows or hides the window;
- tracks the main window's `Focused` events.
- **Placement:** the work area of the foreground window's monitor (`GetForegroundWindow` → `monitor_from_point`), DPI-scaled, 16 px margin, chosen corner. It's recomputed on each update.
- **Elsewhere:** Windows only; the command is a no-op on other platforms.
- **Wiring:** the command is declared in `build.rs` and granted in `capabilities/migrated.json`, like every other command.
## Tests
- `cargo test --lib`: **37/37**, 9 of them new:
- show/hide rule;
- corner parsing;
- input sanitising;
- window size;
- placement in each corner and on tiny or offset monitors;
- script escaping (a name containing `");alert(1);//</script>` and U+2028 round-trips as data);
- the protocol serving only the page files;
- the page using no `innerHTML` and no external URLs.
- `cargo check` and `cargo clippy` for **`x86_64-pc-windows-gnu`** are clean, so the Windows-only code type-checks. The MSVC build hasn't run yet; I'm starting the Windows smoke workflow on this branch.
- Not included: the regenerated `src-tauri/gen/schemas/*`. The build rewrites them, and they only add the new `allow-set-overlay-state` permission.
## Needs a real Windows check before it leaves beta
1. Settings → Calls → In-game overlay on, join a call, alt-tab into a **borderless** game: the overlay appears in the chosen corner, the game **keeps focus**, and clicks pass through.
2. The speaking ring follows whoever talks, and mute/deafen/PTT show in the header.
3. Focusing Lotus hides it, and hanging up hides it.
4. A transparent background (no white or black box) on your setup.
5. An exclusive-fullscreen game: expected **not** to show it (documented in the setting's help text).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
While you're in a call and Lotus isn't the focused window, a small
always-on-top, click-through window lists everyone in the call, lights
up whoever is speaking, and shows your own mute / deafen / PTT state.
- A separate window, nothing injected into the game: no anti-cheat
surface. Never takes focus (focusable(false), WS_EX_NOACTIVATE,
SW_SHOWNOACTIVATE) and ignores the mouse.
- Its page is three static files served by the binary over the
`lotusoverlay` custom protocol, with a strict CSP (no network) and no
IPC (no capability names the window). Rust pushes state in as a JSON
literal; the page renders with textContent only.
- New `set_overlay_state` command (main window only) takes the call
snapshot from cinny's `useTauriOverlay`. Sanitised: bounded list and
names, known corner. Placed in the chosen corner of the foreground
window's monitor, DPI-scaled.
- Windows only; a no-op elsewhere (Linux has no in-app calls yet, #21).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
jared
merged commit 3f1af92520 into main2026-11-09 21:31:59 -05:00
jared
deleted branch feat/voice-overlay2026-11-09 21:32:29 -05:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Phase 1 (MVP) of #25, built to the plan in #25 (comment) with the decisions made there: everyone in the call, speakers lit; shown while Lotus isn't focused; off by default (beta); voice only.
Web side: LotusGuild/cinny
feat/voice-overlay(setting,useTauriOverlay). Either can merge first. Until both are in, nothing calls the command and nothing shows.What it does
While you're in a call with the setting on and Lotus isn't the focused window, a small window sits in a corner of the monitor your foreground app is on. It shows:
How (
src-tauri/src/native/overlay.rs)Separate window, no injection:
focusable(false)plusWS_EX_NOACTIVATE | WS_EX_TOOLWINDOW, shown withSW_SHOWNOACTIVATEandSetWindowPos(…, SWP_NOACTIVATE);set_ignore_cursor_events(true), so clicks pass through.It must never take focus from a game.
Page: three static files (
src-tauri/overlay/) served by the binary over thelotusoverlaycustom protocol.default-src 'none'; script-src 'self'; style-src 'self', so no network.overlaywindow, and the new command also rejects any caller that isn'tmain.evalof a serde JSON literal. The page builds the DOM withtextContentonly, so display names can't inject markup.set_overlay_state(async, because creating a window from a sync command can deadlock on Windows):Focusedevents.Placement: the work area of the foreground window's monitor (
GetForegroundWindow→monitor_from_point), DPI-scaled, 16 px margin, chosen corner. It's recomputed on each update.Elsewhere: Windows only; the command is a no-op on other platforms.
Wiring: the command is declared in
build.rsand granted incapabilities/migrated.json, like every other command.Tests
cargo test --lib: 37/37, 9 of them new:");alert(1);//</script>and U+2028 round-trips as data);innerHTMLand no external URLs.cargo checkandcargo clippyforx86_64-pc-windows-gnuare clean, so the Windows-only code type-checks. The MSVC build hasn't run yet; I'm starting the Windows smoke workflow on this branch.src-tauri/gen/schemas/*. The build rewrites them, and they only add the newallow-set-overlay-statepermission.Needs a real Windows check before it leaves beta
🤖 Generated with Claude Code
https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA