feat(M4): Windows WinForms client, TOFU identity pinning, VAD threshold + always-on mode
Core ABI extensions (voicecat.h):
- vc_list_channels / vc_list_users / vc_list_user_streams — pull-based snapshot getters
for the channel-tree and user-list UI; session_model_mu_ guards cross-thread reads
- VC_EVENT_JOIN_RESULT / vc_join_channel — channel join with optional password
- VC_EVENT_SERVER_IDENTITY + vc_confirm_server_identity — TOFU gate that blocks io_thread_
until the UI approves or rejects; pins TLS leaf-cert SHA-256 (not declared Ed25519)
- vc_get_server_identity_display — Ed25519 fingerprint for human-readable display only
- VC_INPUT_ALWAYS_ON = 2 in vc_input_mode — transmit unconditionally, no VAD gate
- vc_set_vad_threshold — live RMS threshold update (0.0–1.0); EnergyVadProcessor stores
it atomically so the audio RT path reads without a lock
C++ implementation:
- SessionModel::apply_snapshot / apply_channel_event fixed to populate parent_id,
password_protected, and max_users (were permanently zeroed)
- TlsContext::peer_cert_fingerprint — SHA-256 of peer leaf cert DER via mbedTLS
- TofuStore split into peek (read-only) + pin (write) so first-connect only persists
after user approval; tofu_store_path in vc_config for per-user pin file location
- TcpAcceptor uses dual-stack IPv6+IPv4 fallback (fixes localhost → ::1 on Windows)
- windows-client CMake preset: Release shared DLL, static MinGW runtime, no tools/tests
- New C++ tests: test_channel_user_list_abi, test_tofu_flow (14/14 green)
Windows client (clients/windows/ — .NET 10 WinForms):
- VoiceCat.Interop: LibraryImport P/Invoke surface, UnmanagedCallersOnly callbacks,
Channel<VoiceCatEvent> event delivery drained by 30ms WinForms Timer
- VoiceCat.App: ConnectDialog (saved servers, DPAPI password storage), ServerIdentity-
Dialog (TOFU first-connect / mismatch warning), MainForm (channel TreeView, user
ListBox, RichTextBox chat, voice controls, device pickers, VAD/PTT/always-on mode,
per-user gain/mute/NR tuning, VAD sensitivity TrackBar, level meter ProgressBar)
- PttKeyCaptureDialog — focus-scoped PTT key capture (documented limitation)
- PerUserTuningDialog — real-time gain/mute/NR applied to all of a user's streams
- Accessibility: explicit AccessibleName/Description on every control, & mnemonics,
Activity log ListBox as durable screen-reader record, AutomationNotification for
curated live announcements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 00:35:16 +02:00
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
|
|
|
|
// Raw P/Invoke surface over core/include/voicecat.h, via LibraryImport (source-generated —
|
|
|
|
|
// no runtime reflection marshaling stub; see docs/tech-stack.md §2). One entry per voicecat.h
|
|
|
|
|
// function. `vc_client*` is represented as a raw `nint` here — VoiceCatClientHandle (a
|
|
|
|
|
// SafeHandle) owns the create/destroy lifetime one level up; these declarations never see a
|
|
|
|
|
// SafeHandle directly, per .NET's own SafeHandle convention.
|
|
|
|
|
//
|
|
|
|
|
// "voicecat" resolves to voicecat.dll via the OS's standard DLL search order (same directory
|
|
|
|
|
// as the .exe first) — see clients/windows/README.md for how it gets there at build time.
|
|
|
|
|
namespace VoiceCat.Interop;
|
|
|
|
|
|
|
|
|
|
internal static partial class NativeMethods
|
|
|
|
|
{
|
|
|
|
|
private const string LibName = "voicecat";
|
|
|
|
|
|
|
|
|
|
// ── Lifecycle ────────────────────────────────────────────────────────────────────────
|
|
|
|
|
// NOTE: these two return `const char*` pointing at STATIC string literals the core never
|
|
|
|
|
// expects the caller to free. Declaring them as `string` with StringMarshalling.Utf8
|
|
|
|
|
// would be wrong: the built-in Utf8StringMarshaller's return-value convention assumes the
|
|
|
|
|
// native callee allocated the string FOR this call and that the marshaller should free it
|
|
|
|
|
// afterward — calling that on a static literal corrupts the heap (confirmed: it crashes
|
|
|
|
|
// with STATUS_HEAP_CORRUPTION / 0xC0000374). Return the raw pointer instead and convert
|
|
|
|
|
// with Marshal.PtrToStringUTF8 ourselves, without ever freeing it — see VoiceCatClient.cs.
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial nint vc_version_string();
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial nint vc_result_string(VcResult code);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial nint vc_client_create(in VcConfigNative cfg, VcCallbacksNative cb);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial void vc_client_destroy(nint c);
|
|
|
|
|
|
|
|
|
|
// ── Connection & auth (async; results via on_event) ────────────────────────────────────
|
|
|
|
|
[LibraryImport(LibName, StringMarshalling = StringMarshalling.Utf8)]
|
|
|
|
|
internal static partial VcResult vc_connect(nint c, string host, ushort port);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_disconnect(nint c);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName, StringMarshalling = StringMarshalling.Utf8)]
|
|
|
|
|
internal static partial VcResult vc_authenticate_guest(nint c, string nickname);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName, StringMarshalling = StringMarshalling.Utf8)]
|
|
|
|
|
internal static partial VcResult vc_authenticate_user(nint c, string username, string password);
|
|
|
|
|
|
|
|
|
|
// ── Channels ─────────────────────────────────────────────────────────────────────────
|
|
|
|
|
[LibraryImport(LibName, StringMarshalling = StringMarshalling.Utf8)]
|
|
|
|
|
internal static partial VcResult vc_join_channel(nint c, uint channelId, string? password);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_leave_channel(nint c);
|
|
|
|
|
|
feat: fix voice join/leave, channel edit defaults, channel-update stream restart
Three bugs fixed across the full stack (proto/server/core/ABI/Win/macOS/iOS):
1. Join/Leave Voice now truly subscribes/unsubscribes from the voice plane.
Previously the button only toggled the local mic — receiving was always on
(gated by channel membership alone). Added a protocol-level voice subscription
concept: new SubscribeVoiceRequest/UnsubscribeVoiceRequest/VoiceSubscriptionResult
proto messages, User.voice_subscribed field, vc_join_voice/vc_leave_voice C ABI
functions, VC_EVENT_VOICE_STATE event, server-side voice_subscribed flag checked
by the SFU relay recipient filter, and core-client gating of remote-stream
decoder setup. All three clients rewired to subscribe+mic on Join / unsubscribe
on Leave. Text chat works regardless of voice subscription.
2. Channel edit dialog now shows the channel's actual current settings. The read
struct vc_channel was missing sort_order and audio fields — only the write
struct vc_channel_info had them. Extended vc_channel with both (additive, no
ABI break), updated the session model and list_channels marshaling to populate
them, and updated all three clients' edit callers to use actual channel info
instead of hardcoded defaults.
3. Channel parameter updates now automatically restart everyone's streams.
Previously editing a channel's audio config persisted and broadcast a
ChannelEvent::UPDATED, but no layer restarted streams — encoders/decoders are
frozen at announce time. handle_channel_event now detects audio-config changes
on the user's current channel and stop->starts each active local stream. The
server reads the updated config on re-announce; peers wire up fresh decoders
at the new ssrc.
All 29 CTest tests pass; Windows DLL + C# client build clean. Apple clients not
yet compile-verified (Windows environment).
2026-06-24 14:29:39 +02:00
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_join_voice(nint c);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_leave_voice(nint c);
|
|
|
|
|
|
feat(M4): Windows WinForms client, TOFU identity pinning, VAD threshold + always-on mode
Core ABI extensions (voicecat.h):
- vc_list_channels / vc_list_users / vc_list_user_streams — pull-based snapshot getters
for the channel-tree and user-list UI; session_model_mu_ guards cross-thread reads
- VC_EVENT_JOIN_RESULT / vc_join_channel — channel join with optional password
- VC_EVENT_SERVER_IDENTITY + vc_confirm_server_identity — TOFU gate that blocks io_thread_
until the UI approves or rejects; pins TLS leaf-cert SHA-256 (not declared Ed25519)
- vc_get_server_identity_display — Ed25519 fingerprint for human-readable display only
- VC_INPUT_ALWAYS_ON = 2 in vc_input_mode — transmit unconditionally, no VAD gate
- vc_set_vad_threshold — live RMS threshold update (0.0–1.0); EnergyVadProcessor stores
it atomically so the audio RT path reads without a lock
C++ implementation:
- SessionModel::apply_snapshot / apply_channel_event fixed to populate parent_id,
password_protected, and max_users (were permanently zeroed)
- TlsContext::peer_cert_fingerprint — SHA-256 of peer leaf cert DER via mbedTLS
- TofuStore split into peek (read-only) + pin (write) so first-connect only persists
after user approval; tofu_store_path in vc_config for per-user pin file location
- TcpAcceptor uses dual-stack IPv6+IPv4 fallback (fixes localhost → ::1 on Windows)
- windows-client CMake preset: Release shared DLL, static MinGW runtime, no tools/tests
- New C++ tests: test_channel_user_list_abi, test_tofu_flow (14/14 green)
Windows client (clients/windows/ — .NET 10 WinForms):
- VoiceCat.Interop: LibraryImport P/Invoke surface, UnmanagedCallersOnly callbacks,
Channel<VoiceCatEvent> event delivery drained by 30ms WinForms Timer
- VoiceCat.App: ConnectDialog (saved servers, DPAPI password storage), ServerIdentity-
Dialog (TOFU first-connect / mismatch warning), MainForm (channel TreeView, user
ListBox, RichTextBox chat, voice controls, device pickers, VAD/PTT/always-on mode,
per-user gain/mute/NR tuning, VAD sensitivity TrackBar, level meter ProgressBar)
- PttKeyCaptureDialog — focus-scoped PTT key capture (documented limitation)
- PerUserTuningDialog — real-time gain/mute/NR applied to all of a user's streams
- Accessibility: explicit AccessibleName/Description on every control, & mnemonics,
Activity log ListBox as durable screen-reader record, AutomationNotification for
curated live announcements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 00:35:16 +02:00
|
|
|
// ── Local media streams ─────────────────────────────────────────────────────────────────
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_stream_start(nint c, in VcStreamDescNative desc,
|
|
|
|
|
out uint outStreamId);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_stream_stop(nint c, uint streamId);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName, StringMarshalling = StringMarshalling.Utf8)]
|
|
|
|
|
internal static partial VcResult vc_set_input_device(nint c, uint streamId, string? deviceId);
|
|
|
|
|
|
2026-06-23 20:48:26 +02:00
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_set_capture_channels(nint c, uint streamId, uint channels);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_audio_restart(nint c);
|
|
|
|
|
|
feat(M4): Windows WinForms client, TOFU identity pinning, VAD threshold + always-on mode
Core ABI extensions (voicecat.h):
- vc_list_channels / vc_list_users / vc_list_user_streams — pull-based snapshot getters
for the channel-tree and user-list UI; session_model_mu_ guards cross-thread reads
- VC_EVENT_JOIN_RESULT / vc_join_channel — channel join with optional password
- VC_EVENT_SERVER_IDENTITY + vc_confirm_server_identity — TOFU gate that blocks io_thread_
until the UI approves or rejects; pins TLS leaf-cert SHA-256 (not declared Ed25519)
- vc_get_server_identity_display — Ed25519 fingerprint for human-readable display only
- VC_INPUT_ALWAYS_ON = 2 in vc_input_mode — transmit unconditionally, no VAD gate
- vc_set_vad_threshold — live RMS threshold update (0.0–1.0); EnergyVadProcessor stores
it atomically so the audio RT path reads without a lock
C++ implementation:
- SessionModel::apply_snapshot / apply_channel_event fixed to populate parent_id,
password_protected, and max_users (were permanently zeroed)
- TlsContext::peer_cert_fingerprint — SHA-256 of peer leaf cert DER via mbedTLS
- TofuStore split into peek (read-only) + pin (write) so first-connect only persists
after user approval; tofu_store_path in vc_config for per-user pin file location
- TcpAcceptor uses dual-stack IPv6+IPv4 fallback (fixes localhost → ::1 on Windows)
- windows-client CMake preset: Release shared DLL, static MinGW runtime, no tools/tests
- New C++ tests: test_channel_user_list_abi, test_tofu_flow (14/14 green)
Windows client (clients/windows/ — .NET 10 WinForms):
- VoiceCat.Interop: LibraryImport P/Invoke surface, UnmanagedCallersOnly callbacks,
Channel<VoiceCatEvent> event delivery drained by 30ms WinForms Timer
- VoiceCat.App: ConnectDialog (saved servers, DPAPI password storage), ServerIdentity-
Dialog (TOFU first-connect / mismatch warning), MainForm (channel TreeView, user
ListBox, RichTextBox chat, voice controls, device pickers, VAD/PTT/always-on mode,
per-user gain/mute/NR tuning, VAD sensitivity TrackBar, level meter ProgressBar)
- PttKeyCaptureDialog — focus-scoped PTT key capture (documented limitation)
- PerUserTuningDialog — real-time gain/mute/NR applied to all of a user's streams
- Accessibility: explicit AccessibleName/Description on every control, & mnemonics,
Activity log ListBox as durable screen-reader record, AutomationNotification for
curated live announcements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 00:35:16 +02:00
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_set_input_mode(nint c, VcInputMode mode);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_set_vad_threshold(nint c, float threshold);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_set_push_to_talk(nint c, int active);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_set_self_mute(nint c, int micMuted, int deafened);
|
|
|
|
|
|
feat(windows): UI overhaul -- toolbar, unified log, PM windows, channel counts, output volume
- Voice actions (Join Voice, Share Screen Audio) moved to a ToolStrip toolbar and
a new Voice menu in the menu bar; removed from the bottom voice panel
- Activity log and chat log collapsed into a single RichTextBox (rtbLog); activity
events appear in gray, chat messages in default color
- Private messaging reworked: each conversation opens in its own modeless
PrivateMessageForm instead of sharing the main chat log via a scope dropdown;
cboScope removed; main compose bar always sends to the current channel
- New "Messages -> New Private Message..." menu item (Ctrl+P) opens a UserPickerDialog
listing all connected server users (not just the current channel) so you can PM
anyone on the server
- Channel tree now shows live user counts, e.g. "General (3)" -- counts sourced from
the existing _users dictionary which already tracks all server users with channel IDs
- Global output volume slider (TrackBar, 0-100, default 80) added to the right panel;
wired to new vc_set_output_volume C ABI function that applies a master gain multiplier
in the audio engine playback callback after mixing all streams
- vc_set_output_volume added end-to-end: voicecat.h, audio_engine.h/.cpp,
client.h/.cpp, voicecat.cpp, NativeMethods.cs, VoiceCatClient.cs
- Documented Windows PowerShell ctest requirement in AGENTS.md and CLAUDE.md:
MinGW binaries exit 0xc0000139 in Git Bash; always run ctest/.exe via PowerShell
22/22 ctest green (PowerShell); dotnet build 0 warnings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 14:24:54 +02:00
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_set_output_volume(nint c, float gain);
|
|
|
|
|
|
feat(clients): persist input settings, add mic input gain, fix iOS chat + VoiceOver
Input mode (VAD/PTT/Always-On), VAD threshold, and the new mic gain were
applied to the core + UI but never saved, so every relaunch reset to VAD
defaults. Each client now persists them and re-applies on connect:
- iOS: UserDefaults (SessionState.loadAndApplyVoiceSettings + setter writes)
- macOS: UserDefaults via MainWindowController didSet + loadPersistedAudioSettings
(settings window also restores the VAD slider from the stored threshold)
- Windows: new Models/VoiceSettings.cs (JSON at %AppData%\VoiceCat\voice.json,
mirrors FeedbackSettings) loaded/applied in MainForm
Add global send-side mic gain API vc_set_input_gain (applied to MIC PCM in
on_capture_frame before the VAD gate, clamped to int16) + Swift/C# bindings,
and a 0-300% (default 100%) mic-volume slider on all three clients.
Fix iOS chat: ChatView called sendText(scope:.channel) with no targetId (0),
so channel messages went nowhere; now passes session.currentChannelId.
Fix iOS per-user tuning for VoiceOver: the tuning sheet was long-press
.contextMenu only (invisible to VoiceOver); UserRow now also exposes the same
buttons via .accessibilityActions (no visual change).
Verified: core builds clean; ctest 24/27 (3 pre-existing teardown crashes,
reproduced with changes stashed); VoiceCatMac + VoiceCatiOS (arm64 sim) build
SUCCEEDED; VoiceCat.Interop dotnet build succeeded. Windows App not built
(WinForms can't build on macOS) — follows existing patterns.
2026-06-23 03:35:26 +02:00
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_set_input_gain(nint c, float gain);
|
|
|
|
|
|
feat(clients): wire RNNoise mic noise reduction into Windows, macOS, and iOS
Expose the existing send-side vc_set_input_noise_reduction C ABI (MIC-only,
mono, LOCAL — denoises captured mic PCM before input gain and VAD/PTT gate)
as a persisted global toggle in each client's audio settings, applied live
and re-applied on Join Voice. Mirrors the existing mic-gain wiring pattern.
- Shared Swift (VoiceCatCore): add setInputNoiseReduction(_:) wrapper
- Windows: P/Invoke + SetInputNoiseReduction wrapper, MicNoiseReduction in
VoiceSettings, new checkbox in AudioSettingsForm (layout shifted +28px),
apply on Join Voice; also fix stale 'planned - currently passthrough'
label on the receive-side per-user NR checkbox (RNNoise now backs it)
- macOS: inputNoiseReduction state + UserDefaults in MainWindowController,
NR checkbox + nrChanged action in SettingsWindowController
- iOS: inputNoiseReduction in VoiceState + setter + restore in SessionState,
NR Toggle in SettingsView Voice section
Aux/screen are out of scope by design (core's NR guards kind == MIC). Apple
builds require a rebuilt VoiceCatCore.xcframework with VOICECAT_HAS_NS.
2026-06-23 14:11:18 +02:00
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_set_input_noise_reduction(nint c, int enable);
|
|
|
|
|
|
feat(M4): Windows WinForms client, TOFU identity pinning, VAD threshold + always-on mode
Core ABI extensions (voicecat.h):
- vc_list_channels / vc_list_users / vc_list_user_streams — pull-based snapshot getters
for the channel-tree and user-list UI; session_model_mu_ guards cross-thread reads
- VC_EVENT_JOIN_RESULT / vc_join_channel — channel join with optional password
- VC_EVENT_SERVER_IDENTITY + vc_confirm_server_identity — TOFU gate that blocks io_thread_
until the UI approves or rejects; pins TLS leaf-cert SHA-256 (not declared Ed25519)
- vc_get_server_identity_display — Ed25519 fingerprint for human-readable display only
- VC_INPUT_ALWAYS_ON = 2 in vc_input_mode — transmit unconditionally, no VAD gate
- vc_set_vad_threshold — live RMS threshold update (0.0–1.0); EnergyVadProcessor stores
it atomically so the audio RT path reads without a lock
C++ implementation:
- SessionModel::apply_snapshot / apply_channel_event fixed to populate parent_id,
password_protected, and max_users (were permanently zeroed)
- TlsContext::peer_cert_fingerprint — SHA-256 of peer leaf cert DER via mbedTLS
- TofuStore split into peek (read-only) + pin (write) so first-connect only persists
after user approval; tofu_store_path in vc_config for per-user pin file location
- TcpAcceptor uses dual-stack IPv6+IPv4 fallback (fixes localhost → ::1 on Windows)
- windows-client CMake preset: Release shared DLL, static MinGW runtime, no tools/tests
- New C++ tests: test_channel_user_list_abi, test_tofu_flow (14/14 green)
Windows client (clients/windows/ — .NET 10 WinForms):
- VoiceCat.Interop: LibraryImport P/Invoke surface, UnmanagedCallersOnly callbacks,
Channel<VoiceCatEvent> event delivery drained by 30ms WinForms Timer
- VoiceCat.App: ConnectDialog (saved servers, DPAPI password storage), ServerIdentity-
Dialog (TOFU first-connect / mismatch warning), MainForm (channel TreeView, user
ListBox, RichTextBox chat, voice controls, device pickers, VAD/PTT/always-on mode,
per-user gain/mute/NR tuning, VAD sensitivity TrackBar, level meter ProgressBar)
- PttKeyCaptureDialog — focus-scoped PTT key capture (documented limitation)
- PerUserTuningDialog — real-time gain/mute/NR applied to all of a user's streams
- Accessibility: explicit AccessibleName/Description on every control, & mnemonics,
Activity log ListBox as durable screen-reader record, AutomationNotification for
curated live announcements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 00:35:16 +02:00
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_set_remote_stream(nint c, uint userId, uint streamId,
|
|
|
|
|
float gain, int muted, int noiseReduction);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
2026-06-18 02:06:44 +02:00
|
|
|
internal static partial VcResult vc_get_remote_stream(nint c, uint userId, uint streamId,
|
|
|
|
|
out VcRemoteStreamStateNative outState);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
feat(M4): Windows WinForms client, TOFU identity pinning, VAD threshold + always-on mode
Core ABI extensions (voicecat.h):
- vc_list_channels / vc_list_users / vc_list_user_streams — pull-based snapshot getters
for the channel-tree and user-list UI; session_model_mu_ guards cross-thread reads
- VC_EVENT_JOIN_RESULT / vc_join_channel — channel join with optional password
- VC_EVENT_SERVER_IDENTITY + vc_confirm_server_identity — TOFU gate that blocks io_thread_
until the UI approves or rejects; pins TLS leaf-cert SHA-256 (not declared Ed25519)
- vc_get_server_identity_display — Ed25519 fingerprint for human-readable display only
- VC_INPUT_ALWAYS_ON = 2 in vc_input_mode — transmit unconditionally, no VAD gate
- vc_set_vad_threshold — live RMS threshold update (0.0–1.0); EnergyVadProcessor stores
it atomically so the audio RT path reads without a lock
C++ implementation:
- SessionModel::apply_snapshot / apply_channel_event fixed to populate parent_id,
password_protected, and max_users (were permanently zeroed)
- TlsContext::peer_cert_fingerprint — SHA-256 of peer leaf cert DER via mbedTLS
- TofuStore split into peek (read-only) + pin (write) so first-connect only persists
after user approval; tofu_store_path in vc_config for per-user pin file location
- TcpAcceptor uses dual-stack IPv6+IPv4 fallback (fixes localhost → ::1 on Windows)
- windows-client CMake preset: Release shared DLL, static MinGW runtime, no tools/tests
- New C++ tests: test_channel_user_list_abi, test_tofu_flow (14/14 green)
Windows client (clients/windows/ — .NET 10 WinForms):
- VoiceCat.Interop: LibraryImport P/Invoke surface, UnmanagedCallersOnly callbacks,
Channel<VoiceCatEvent> event delivery drained by 30ms WinForms Timer
- VoiceCat.App: ConnectDialog (saved servers, DPAPI password storage), ServerIdentity-
Dialog (TOFU first-connect / mismatch warning), MainForm (channel TreeView, user
ListBox, RichTextBox chat, voice controls, device pickers, VAD/PTT/always-on mode,
per-user gain/mute/NR tuning, VAD sensitivity TrackBar, level meter ProgressBar)
- PttKeyCaptureDialog — focus-scoped PTT key capture (documented limitation)
- PerUserTuningDialog — real-time gain/mute/NR applied to all of a user's streams
- Accessibility: explicit AccessibleName/Description on every control, & mnemonics,
Activity log ListBox as durable screen-reader record, AutomationNotification for
curated live announcements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 00:35:16 +02:00
|
|
|
internal static partial VcResult vc_get_stream_audio_config(nint c, uint userId,
|
|
|
|
|
uint streamId, out VcAudioConfigNative outCfg);
|
|
|
|
|
|
|
|
|
|
// TEST-ONLY in the core (see voicecat.h) — declared for ABI parity; the real app never
|
|
|
|
|
// calls this (no microphone-bypass path in production UI).
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static unsafe partial VcResult vc_test_inject_capture(nint c, uint streamId,
|
|
|
|
|
short* pcm, nuint samples);
|
|
|
|
|
|
feat: external PCM feed/tap API (vc_stream_feed_pcm + vc_set_pcm_sink)
Promotes vc_test_inject_capture (mono-only, TEST-ONLY) to a public,
stereo-capable production API and adds a symmetric PCM tap on the
receive side. Enables ReplayKit (iOS), ScreenCaptureKit (macOS), bots,
soundboards, and custom clients — all without a hardware audio device.
Core C++:
- voicecat.h: new vc_stream_feed_pcm, vc_pcm_sink_cb typedef,
vc_set_pcm_sink; vc_test_inject_capture kept as deprecated alias
- audio_engine: stereo-aware inject_capture (channels param + ring
reset on channel-count change); atomic pcm_sink_ fired per decoded
frame in on_playback; RemoteStream carries user_id/stream_id for
RT-safe sink metadata; init_recv_stream takes user_id+stream_id
- client.cpp: stream_feed_pcm / set_pcm_sink implementations;
sync_remote_streams passes user_id/stream_id to init_recv_stream
- voicecat.cpp: trampolines + channels=1/2 validation
Tests: test_external_pcm (headless, 3 sub-tests: mono round-trip,
stereo feed L≠R, sink metadata+disable). ctest 23/23.
Swift: feedPcm / setPcmSink in VoiceCatClient.swift + 4 XCTest
smoke tests (ExternalPcmTests.swift).
C#: StreamFeedPcm / SetPcmSink in VoiceCatClient.cs + NativeMethods.cs
(vc_stream_feed_pcm unsafe P/Invoke, VcPcmSinkCallback delegate,
vc_set_pcm_sink via nint) + 4 xUnit smoke tests (ExternalPcmTests.cs).
Docs: architecture.md §4 new subsection, voice.md §9 updated
(macOS/iOS now reference vc_stream_feed_pcm), protocol.md §8 explicit
no-protocol-change note, roadmap.md M5 entry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 17:52:09 +02:00
|
|
|
// ── External PCM feed / tap ──────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static unsafe partial VcResult vc_stream_feed_pcm(nint c, uint streamId,
|
|
|
|
|
short* pcm, nuint samplesPerChannel, uint channels);
|
|
|
|
|
|
|
|
|
|
// Delegate type for the PCM sink callback — callers convert to a native function
|
|
|
|
|
// pointer via Marshal.GetFunctionPointerForDelegate (for instance members) or by casting
|
|
|
|
|
// a static lambda to delegate* unmanaged<> (for [UnmanagedCallersOnly] statics).
|
|
|
|
|
// Keep the delegate alive for the lifetime of the sink registration.
|
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
|
|
|
public delegate void VcPcmSinkCallback(IntPtr user, uint userId, uint streamId,
|
|
|
|
|
IntPtr pcm, nuint samplesPerChannel, uint channels, uint sampleRate);
|
|
|
|
|
|
|
|
|
|
// cb is a raw function pointer (IntPtr.Zero = disable). Use
|
|
|
|
|
// Marshal.GetFunctionPointerForDelegate(sinkDelegate) to convert from VcPcmSinkCallback.
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_set_pcm_sink(nint c, nint cb, IntPtr user);
|
|
|
|
|
|
feat(M4): Windows WinForms client, TOFU identity pinning, VAD threshold + always-on mode
Core ABI extensions (voicecat.h):
- vc_list_channels / vc_list_users / vc_list_user_streams — pull-based snapshot getters
for the channel-tree and user-list UI; session_model_mu_ guards cross-thread reads
- VC_EVENT_JOIN_RESULT / vc_join_channel — channel join with optional password
- VC_EVENT_SERVER_IDENTITY + vc_confirm_server_identity — TOFU gate that blocks io_thread_
until the UI approves or rejects; pins TLS leaf-cert SHA-256 (not declared Ed25519)
- vc_get_server_identity_display — Ed25519 fingerprint for human-readable display only
- VC_INPUT_ALWAYS_ON = 2 in vc_input_mode — transmit unconditionally, no VAD gate
- vc_set_vad_threshold — live RMS threshold update (0.0–1.0); EnergyVadProcessor stores
it atomically so the audio RT path reads without a lock
C++ implementation:
- SessionModel::apply_snapshot / apply_channel_event fixed to populate parent_id,
password_protected, and max_users (were permanently zeroed)
- TlsContext::peer_cert_fingerprint — SHA-256 of peer leaf cert DER via mbedTLS
- TofuStore split into peek (read-only) + pin (write) so first-connect only persists
after user approval; tofu_store_path in vc_config for per-user pin file location
- TcpAcceptor uses dual-stack IPv6+IPv4 fallback (fixes localhost → ::1 on Windows)
- windows-client CMake preset: Release shared DLL, static MinGW runtime, no tools/tests
- New C++ tests: test_channel_user_list_abi, test_tofu_flow (14/14 green)
Windows client (clients/windows/ — .NET 10 WinForms):
- VoiceCat.Interop: LibraryImport P/Invoke surface, UnmanagedCallersOnly callbacks,
Channel<VoiceCatEvent> event delivery drained by 30ms WinForms Timer
- VoiceCat.App: ConnectDialog (saved servers, DPAPI password storage), ServerIdentity-
Dialog (TOFU first-connect / mismatch warning), MainForm (channel TreeView, user
ListBox, RichTextBox chat, voice controls, device pickers, VAD/PTT/always-on mode,
per-user gain/mute/NR tuning, VAD sensitivity TrackBar, level meter ProgressBar)
- PttKeyCaptureDialog — focus-scoped PTT key capture (documented limitation)
- PerUserTuningDialog — real-time gain/mute/NR applied to all of a user's streams
- Accessibility: explicit AccessibleName/Description on every control, & mnemonics,
Activity log ListBox as durable screen-reader record, AutomationNotification for
curated live announcements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 00:35:16 +02:00
|
|
|
// ── Text ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
[LibraryImport(LibName, StringMarshalling = StringMarshalling.Utf8)]
|
|
|
|
|
internal static partial VcResult vc_send_text(nint c, VcTextScope scope, uint targetId,
|
|
|
|
|
string utf8);
|
|
|
|
|
|
|
|
|
|
// ── Device enumeration ───────────────────────────────────────────────────────────────
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_list_devices(nint c, VcDeviceKind kind,
|
|
|
|
|
out VcDeviceListNative outList);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial void vc_free_device_list(ref VcDeviceListNative list);
|
|
|
|
|
|
|
|
|
|
// ── M4: channel / user / stream snapshot getters ────────────────────────────────────────
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_list_channels(nint c, out VcChannelListNative outList);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial void vc_free_channel_list(ref VcChannelListNative list);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_list_users(nint c, out VcUserListNative outList);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial void vc_free_user_list(ref VcUserListNative list);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_list_user_streams(nint c, uint userId,
|
|
|
|
|
out VcStreamSummaryListNative outList);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial void vc_free_stream_summary_list(ref VcStreamSummaryListNative list);
|
|
|
|
|
|
|
|
|
|
// ── M4: TOFU server-identity gate ───────────────────────────────────────────────────────
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_confirm_server_identity(nint c, int accept);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_get_server_identity_display(nint c, nint outBuf,
|
|
|
|
|
nuint bufCap, out nuint outLen);
|
2026-06-17 16:31:29 +02:00
|
|
|
|
|
|
|
|
// ── M5: Moderation & admin ─────────────────────────────────────────────────────────────
|
|
|
|
|
[LibraryImport(LibName, StringMarshalling = StringMarshalling.Utf8)]
|
|
|
|
|
internal static partial VcResult vc_kick_user(nint c, uint userId, string? reason);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName, StringMarshalling = StringMarshalling.Utf8)]
|
|
|
|
|
internal static partial VcResult vc_ban_user(nint c, uint userId, string? reason,
|
|
|
|
|
ulong expiresUnixMs);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_set_permission(nint c, uint userId,
|
|
|
|
|
in VcPermissionsNative perms);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_set_server_mute(nint c, uint userId, int muted,
|
|
|
|
|
int deafened);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_move_user(nint c, uint userId, uint channelId);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_create_channel(nint c, in VcChannelInfoNative info);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_edit_channel(nint c, in VcChannelInfoNative info);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_delete_channel(nint c, uint channelId);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName, StringMarshalling = StringMarshalling.Utf8)]
|
|
|
|
|
internal static partial VcResult vc_create_account(nint c, string username, string password);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName, StringMarshalling = StringMarshalling.Utf8)]
|
|
|
|
|
internal static partial VcResult vc_reset_password(nint c, string username,
|
|
|
|
|
string newPassword);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName, StringMarshalling = StringMarshalling.Utf8)]
|
|
|
|
|
internal static partial VcResult vc_delete_account(nint c, string username);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_list_accounts(nint c);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_get_account_list(nint c, out VcAccountListNative outList);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial void vc_free_account_list(ref VcAccountListNative list);
|
|
|
|
|
|
|
|
|
|
[LibraryImport(LibName)]
|
|
|
|
|
internal static partial VcResult vc_get_permissions(nint c, out VcPermissionsNative outPerms);
|
feat(M4): Windows WinForms client, TOFU identity pinning, VAD threshold + always-on mode
Core ABI extensions (voicecat.h):
- vc_list_channels / vc_list_users / vc_list_user_streams — pull-based snapshot getters
for the channel-tree and user-list UI; session_model_mu_ guards cross-thread reads
- VC_EVENT_JOIN_RESULT / vc_join_channel — channel join with optional password
- VC_EVENT_SERVER_IDENTITY + vc_confirm_server_identity — TOFU gate that blocks io_thread_
until the UI approves or rejects; pins TLS leaf-cert SHA-256 (not declared Ed25519)
- vc_get_server_identity_display — Ed25519 fingerprint for human-readable display only
- VC_INPUT_ALWAYS_ON = 2 in vc_input_mode — transmit unconditionally, no VAD gate
- vc_set_vad_threshold — live RMS threshold update (0.0–1.0); EnergyVadProcessor stores
it atomically so the audio RT path reads without a lock
C++ implementation:
- SessionModel::apply_snapshot / apply_channel_event fixed to populate parent_id,
password_protected, and max_users (were permanently zeroed)
- TlsContext::peer_cert_fingerprint — SHA-256 of peer leaf cert DER via mbedTLS
- TofuStore split into peek (read-only) + pin (write) so first-connect only persists
after user approval; tofu_store_path in vc_config for per-user pin file location
- TcpAcceptor uses dual-stack IPv6+IPv4 fallback (fixes localhost → ::1 on Windows)
- windows-client CMake preset: Release shared DLL, static MinGW runtime, no tools/tests
- New C++ tests: test_channel_user_list_abi, test_tofu_flow (14/14 green)
Windows client (clients/windows/ — .NET 10 WinForms):
- VoiceCat.Interop: LibraryImport P/Invoke surface, UnmanagedCallersOnly callbacks,
Channel<VoiceCatEvent> event delivery drained by 30ms WinForms Timer
- VoiceCat.App: ConnectDialog (saved servers, DPAPI password storage), ServerIdentity-
Dialog (TOFU first-connect / mismatch warning), MainForm (channel TreeView, user
ListBox, RichTextBox chat, voice controls, device pickers, VAD/PTT/always-on mode,
per-user gain/mute/NR tuning, VAD sensitivity TrackBar, level meter ProgressBar)
- PttKeyCaptureDialog — focus-scoped PTT key capture (documented limitation)
- PerUserTuningDialog — real-time gain/mute/NR applied to all of a user's streams
- Accessibility: explicit AccessibleName/Description on every control, & mnemonics,
Activity log ListBox as durable screen-reader record, AutomationNotification for
curated live announcements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 00:35:16 +02:00
|
|
|
}
|