Bump Android Gradle Plugin from 8.11.1 to 8.13.1 and Kotlin from
2.2.20 to 2.3.0 to align with newer plugin version requirements.
Kotlin 2.3.0 removed the kotlinOptions DSL free-string assignment.
Migrate to the compilerOptions DSL for JVM target configuration.
Gradle wrapper remains at 8.14 (compatible with AGP 8.13.1).
* feat(macos): add macOS permissions service for Input Monitoring, Local Network, and Notifications
Add MacOSPermissionsService (Dart) + native MethodChannel handler (Swift)
for macOS-specific permissions not covered by permission_handler:
- Input Monitoring (CGPreflightListenEventAccess /
CGRequestListenEventAccess) for global PTT via Event Tap
- Local Network Privacy prompt (NWBrowser for _ts3._tcp, macOS 15+)
- Notifications (UNUserNotificationCenter authorization)
Trace: SRS-198, SRS-297, SRS-300, SysRS-166, SDD-091
Changes:
- Info.plist: add NSBonjourServices array with _ts3._tcp
- macos_permissions_service.dart: Dart service with MethodChannel,
ValueNotifier states, PTT capability derivation (L0Focused /
L1MacOSEventTap), non-macOS short-circuit
- MainFlutterWindow.swift: native handler registered as FlutterPlugin,
Input Monitoring check/request/polling, NWBrowser trigger with
denial detection, UNUserNotificationCenter request
- main.dart: wire service into bootstrap lifecycle, listen for PTT
capability changes from Input Monitoring state
- macos_permissions_service_test.dart: 17 unit tests covering inbound
state changes, outbound calls, lifecycle, error handling, platform
behavior (179/179 full suite pass)
* fix(macos): keep permissions capability state live
* fix(audio): eliminate Android output stutter via Oboe config + lock-free callback
Phase 1 — Oboe configuration:
- Change output stream from Usage::VoiceCommunication to Usage::Game with
ContentType::Sonification to avoid forcing the Legacy (OpenSL ES) data
path on most devices (Oboe issue #2075)
- Switch output format from i16 Mono to f32 Stereo, matching Qint's proven
configuration and eliminating per-callback downmix conversion
- Set buffer size to 2x burst after stream open, reducing default buffer
from 8-20x burst to 2x burst for lower latency
- Remove scratch Mutex<Vec<f32>>; callback writes directly to Oboe buffer
Phase 2 — Lock-free output callback:
- Add audio_event_queue.rs: lock-free SPSC bridge using crossbeam ArrayQueue
with separate packet (lossy) and control (reliable) channels
- OutputCallback now owns AudioHandler directly (no Arc<Mutex<>> on Android)
- Inbound forwarder pushes packets via AudioEventProducer (no mutex)
- set_client_volume pushes control commands via event queue on Android
- iOS/desktop Arc<Mutex<AudioHandler>> path unchanged
* fix(audio): address PR #20 review findings
- Store AudioEventConsumer directly in OutputCallback to eliminate
per-callback Arc clone on the real-time audio thread
- Add SAFETY comment for the unsafe from_raw_parts_mut transmute
- Bound set_client_volume spin-loop to 64 retries with warn log
- Remove redundant crossbeam-utils direct dependency
- Regenerate license inventory for new crossbeam deps (CI fix)
* fix(audio): use ASCII TODO punctuation
* docs(rust): add doc comments to delta enums and fix dead_code warnings
Add missing documentation to ProtocolDelta, CoreDelta, and BridgeDelta
enum variants and their struct fields across the protocol, core, and
bridge crates. Document the ChannelId::ROOT constant and the
take_delta_rx adapter method.
Fix dead_code warnings:
- keyring_disabled: add #[cfg] gate matching its callers
- snapshot_signature: add #[cfg(test)] for future test use
* fix(rust): correct `order` field docs to predecessor channel ID, narrow audio engine cfg gates
- Correct `order` field documentation in ProtocolDelta, SessionEvent,
and BridgeEvent from 'sort order' to 'predecessor channel ID
(TeamSpeak linked-list ordering hint)' per Copilot review feedback.
- Narrow AudioEngine voice_out_tx, voice_activity_selector, and mic_gain
cfg gates from ios+macos+android to android-only, since these fields
are only read from self in android_restart_voice_unit. On iOS/macOS the
values are passed directly to the voice backend at construction time.
* fix(ui): restore speaking status indicators
Speaking state (isSpeaking) is computed from voice activity timestamps
in the protocol layer and cannot be represented as a discrete delta.
The event-driven refactor removed periodic snapshot refreshes, causing
speaking indicators to go stale.
Adds a 750ms periodic snapshot refresh (matching SPEAKING_ACTIVITY_WINDOW)
while the audio stats timer is active (in-channel only). Structural
changes (moves, joins, leaves) are still handled by instant deltas.
* fix(ui): hide server query clients from delta joins
When a ServerQuery client sends a message, a ClientJoined delta fires.
Before PR#15 the periodic snapshot rebuild would include the SQ client
but the snapshot_view filter hid it. With deltas, the client persisted
in the local snapshot. Now ClientJoined deltas skip SQ clients entirely.
* fix(proto): log getconnectioninfo errors instead of silently discarding
Ping and packet loss showing 'Unknown' in the client info sheet is
caused by getconnectioninfo failures being silently swallowed. Now
logs the error with the client_id so the root cause can be diagnosed
(e.g. missing b_client_connectioninfo_view permission on the server).
Also logs clientgetvariables failures.
* fix(proto): refresh non-self client profiles before mapping
* feat(protocol): add ping deviation to client profiles
* chore(ui): regenerate Flutter bridge bindings for ping deviation
* fix(l10n): add ping deviation labels to client info
* feat(ui): show ping deviation in client info sheet
* chore: regenerate Cargo.lock after rebase
* fix(ui): add 1s cool-down to prevent double-tap channel join
voiceJoin returns instantly (fire-and-forget protocol), so the
pending-join guard clears before a second tap lands. The cool-down
prevents the rapid channel oscillation and ClientIsFlooding (524)
that results from double-tapping.
* fix(ui): handle ChannelAlreadyIn as success, ClientIsFlooding with backoff
- ChannelAlreadyIn (0x0302): treat as silent success, update UI state
- ClientIsFlooding (0x020c): show localized snackbar, extend cooldown 5s
- Add l10n strings for flooding error (en + zh)
* fix(proto): use Windows TS3 client version for broadest compatibility
Matches Qint's default (Windows_3_X_X__1). Avoids server-side
behavioral differences with TS5 version strings.
* fix(proto): patch tsproto-types to handle short P-256 coordinates
BigInt::to_bytes_be() strips leading zeros, causing WrongPublicKeyLength
when a server's ephemeral key coordinate starts with 0x00. Patch from
EdisonJwa/tsclientlib fix/p256-short-coordinate-pad branch left-pads
coordinates to the P-256 field size instead of rejecting them.
* refactor(core): stop watchdog from emitting SnapshotChanged
The watchdog now serves only as a liveness probe (miss counting for
reconnection). UI updates are handled entirely by the event-driven
delta path (ProtocolDelta → SessionEvent → BridgeEvent → Flutter).
Removes signature tracking and SnapshotChanged emission from the
supervisor loop. The initial snapshot is still fetched via the
Connected event handler in Flutter.
* refactor(ui): remove channel-join cooldown guard
With event-driven deltas the UI updates instantly on channel moves,
so the 1-second cooldown is no longer needed. Double-taps are handled
by the server (ChannelAlreadyIn → success) and the pending-channel-id
guard prevents overlapping requests.
Also removes the _lastJoinCompletedAt field entirely.
* fix(core): reattach event forwarders after reconnect
The reconnect path swapped in a new ProtocolClient but never took
chat_rx, activity_rx, or delta_rx from it. After the first reconnect,
the event-driven UI pipeline was dead.
Fix by extracting spawn_event_forwarders() helper called on both
initial connect and reconnect. Also replaces lossy try_recv+sleep
polling with proper recv().await for push-based delivery.
* feat(protocol): enrich delta schema with all snapshot-visible fields
ClientJoined now carries input_muted, output_muted, is_server_query,
talk_power, talk_power_granted. ChannelAdded/ChannelUpdated now carry
has_password and needed_talk_power. ClientUpdated also carries
is_server_query, talk_power, talk_power_granted.
This prevents local snapshot drift where fabricated defaults could
hide password requirements, talk-power restrictions, or client type.
* refactor: remove dead SnapshotChanged variant end-to-end
SnapshotChanged is no longer emitted since the watchdog was refactored
to liveness-only. Removes the variant from SessionEvent, BridgeEvent,
and the Flutter switch statement. FRB bindings regenerated.
* fix(ci): regenerate license inventory and fix iOS submodule fetch
- Regenerate docs/security/license-inventory.md to match current lockfile
- Remove submodules: true from checkout (causes hard fail on private submodule)
- Add explicit git submodule update --init --depth=1 with || true fallback
- Check silero-coreml/Package.swift instead of directory existence
* fix(diag): record bridge events in diagnostics page
Connection lost/reconnecting/disconnected and iOS audio interruption
events now appear in the diagnostics dialog alongside existing error
snackbar entries.
* fix(diag): reduce audio callback sample verbosity to debug
The render callback diagnostic sample logged every 100 callbacks
(~2s) at INFO level, flooding the 256-entry release log buffer and
pushing out useful events. Changed to DEBUG so it only appears in
debug builds with the larger 4096-entry buffer.
* fix(diag): add timestamps to Rust diagnostic log entries
* build: add silero-coreml as git submodule
Replaces sibling-directory local package with in-repo submodule.
Updates Xcode relative paths and CI checkout to fetch submodules.
* build: add silero-coreml submodule
Merge reset-style baseline PR after local verification. GitHub Actions did not start because of the account billing/spending-limit blocker documented in the PR body.