Return 204 for non-WebSocket GET requests to /audio instead of
letting actix-ws reject with 400. This allows the browser's audio
availability probe to work correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
The audio listener was ignoring the CLI --listen override and always
binding to the config default (127.0.0.1), making it unreachable
from remote clients.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Start the output stream paused and only play when TX packets
arrive. Pause again when the packet queue drains to prevent
continuous ALSA buffer underruns (EPIPE errno -32) on Linux.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Use subsecond nanosecond jitter to return a varying signal strength
(2-8) from the dummy backend instead of a static value of 5.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Replace the rolling canvas signal graph with a practical signal
measurement feature. The operator can start/stop measurement to
collect signal samples, then view averaged and peak S-unit results.
Also fix signal display to correctly convert dBm wire format to
S-units using standard S-meter scale (S1=-121dBm, S9=-73dBm,
6dB per S-unit).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
- Make server callsign a clickable link to qrzcq.com
- Center frequency input text and use DSEG14 Classic font
- Include callsign in page title (e.g. N0CALL - trx-frontend-http v0.1.0)
- Block jog wheel when rig lock is active
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Load DSEG14 Classic 14-segment LCD font from CDN and apply it to
the frequency input at 2x size for a realistic radio display look.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
- Add RX/TX volume sliders with GainNode audio chain integration,
scroll wheel support, and percentage display
- Display server callsign and version from SSE event data
- Merge TX Limit hint into its label line
- Add copyright footer with link to haxx.space
- Uniform section spacing via flex gap on #content
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Carry server_callsign and server_version through from received
snapshots into the local RigState. Default client callsign is
N0CALL when not configured.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Set server_callsign and server_version on the rig state so they
are included in snapshots sent to clients. Default callsign is
N0CALL when not configured.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add optional server_callsign and server_version fields to both
RigState and RigSnapshot so that server identity information can
flow through the protocol to clients and frontends.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Reorganize layout: frequency row with jog wheel on top, mode and
transmit/power side by side below. Replace VFO text box with segmented
picker. Add rolling signal history canvas. Track connected SSE clients
and display count in status hint. Unify button heights and add Enter
key support for TX limit input.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
time::interval() fires its first tick immediately. Recreating it on
every loop iteration made the select! always resolve instantly,
turning the main polling loop into a busy-loop (~13% CPU idle).
Replace with a Box::pin(sleep()) that is only reset after it
completes or when the poll duration changes (rx/tx transition).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Instead of running the cpal input stream continuously, start it
paused and only activate when broadcast subscribers are present.
When the last client disconnects, pause the stream and sleep at
100ms intervals polling for new receivers.
This eliminates idle CPU usage from continuous CoreAudio callbacks,
channel allocations, and sample processing when nobody is listening.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Enable audio streaming by default in AudioClientConfig so the
client connects to the server audio port without requiring
explicit configuration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Enable audio streaming by default in AudioConfig.
Fix panic in audio playback thread caused by calling
tokio::runtime::Handle::current() from a plain std::thread.
Use rx.blocking_recv() instead, which is the correct API for
consuming a tokio mpsc receiver from a synchronous context.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Move window._opusDecoder, window._txEncoder, window._nextPlayTime
into closure-scoped variables to avoid polluting the global namespace.
Add showHint() helper to debounce status hint text, preventing
multiple button handlers from fighting over powerHint.textContent.
Throttle audio level indicator updates to max 10/sec instead of
updating on every Opus packet (~50/sec).
Hide audio controls row if the server has no audio configured
(checks /audio endpoint on load).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add a 120-second TX safety timeout that auto-releases PTT if no mic
data flows (browser crash, disconnect). Timer resets on each audio
callback. Shows countdown in status when < 10s remaining.
Add beforeunload handler that releases PTT via navigator.sendBeacon
when the browser tab is closed during TX.
Detect WebCodecs support on page load and show "Audio requires
Chrome/Edge" on non-Chromium browsers instead of silently failing.
Remove dead playBuffer/playNode variables that were declared but
never used. Fix TX AudioData to always use mono (channel 0) with
numberOfChannels: 1 matching the f32-planar format.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Replace all hardcoded colors with CSS custom properties on :root for
easier theming. Add focus-visible outlines for keyboard accessibility.
Add mobile breakpoints for screens <= 480px.
Fix PTT button styling to use theme-aware colors (var(--accent-red))
instead of hardcoded light-theme colors (#ffefef, #f3f3f3).
Remove unused .value, .controls, and .section-title CSS classes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Resize favicon from 1024x1024 to 64x64 and logo from 1024x1024 to
256x256, reducing total image size from ~3 MB to ~70 KB.
Fix SSE heartbeat race condition where the 10s ping interval competed
with the 8s stale threshold, causing spurious reconnects. Now pings
every 5s server-side, with a 15s stale threshold and 5s check interval
client-side.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Split the monolithic status.rs string template into three files under
assets/web/ (index.html, style.css, app.js) loaded via include_str!.
Add /style.css and /app.js endpoints with correct content types.
This makes the frontend editable with proper syntax highlighting and
linting support in editors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add /audio WebSocket endpoint that streams RX Opus frames to the
browser and accepts TX frames back. Browser UI includes RX/TX Audio
toggle buttons with WebCodecs Opus decode/encode and a level indicator.
TX audio automatically engages PTT on start and releases on stop or
WebSocket disconnect.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Connect to the server audio TCP port, relay RX Opus frames into a
broadcast channel and TX frames from an mpsc channel. Pass audio
channels to the HTTP frontend via set_audio_channels. Reconnects
with exponential backoff on disconnect.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add AudioConfig to server configuration with support for RX capture
and TX playback via cpal and Opus encoding. Run a dedicated TCP
listener (default port 4533) that sends StreamInfo on connect, streams
RX Opus frames to clients, and receives TX frames back.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Replace #[tokio::main] with a manual fn main() that builds the tokio
runtime explicitly. All async initialization moves into async_init().
When the appkit frontend is requested, the runtime context is entered
on the main thread and run_appkit_main_thread() is called directly,
giving AppKit thread 0 as required by MainThreadMarker. Ctrl+C is
handled via a spawned task that calls process::exit.
When appkit is not requested, behaviour is unchanged: block on Ctrl+C.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Extract the AppKit event loop from FrontendSpawner::spawn_frontend into
a new public run_appkit_main_thread() function that blocks on the
calling thread. This allows the process main thread (thread 0) to drive
the UI, which is required for MainThreadMarker::new() to succeed.
The FrontendSpawner impl now only spawns the async state watcher task.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Allow connecting with just an IP address (e.g. --url 127.0.0.1)
instead of requiring host:port format.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add a JSON-over-TCP listener so trx-client can connect to trx-server.
Speaks the ClientEnvelope/ClientResponse protocol from trx-core::client.
- New listener.rs module with per-client connection handling
- ListenConfig/AuthConfig in config.rs (default: 127.0.0.1:4532)
- CLI args --listen and --port for override
- Optional token-based authentication
- Updated example config with [listen] section
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Register a dummy rig backend that holds state in memory and responds
to all CAT commands immediately. Useful for development and testing
without hardware.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Add a new trx-frontend-appkit crate using objc2 + AppKit as a
replacement for the removed Qt/QML frontend. The frontend provides
the same feature set: frequency/mode/band display, PTT/power/VFO/lock
controls, signal/TX metering, and frequency/mode/TX-limit input.
Architecture splits platform-agnostic model (model.rs) from AppKit
UI (ui.rs) to facilitate future UIKit porting. State flows from the
async tokio watcher via std::sync::mpsc to the AppKit main thread;
button actions flow back through a channel to stay on the UI thread.
Feature-gated behind `appkit-frontend` cargo feature.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Remove the Linux-only Qt/QML frontend (trx-frontend-qt) crate and all
references to it from the workspace, trx-client binary, configuration,
and documentation. This prepares for replacement with a native macOS
AppKit frontend.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Move the frontend and backend crate trees to live physically under their
respective binary crate directories, grouping related code together
without merging crate boundaries. Also flatten sub-crate nesting by
moving them out of src/ subdirectories into direct children.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Delete trx-bin (all-in-one) and trx-bin-common (shared lib). Each binary
now has its own config, plugins, and helper modules inlined.
- trx-server: backend-only daemon with ServerConfig (general, rig, behavior)
no frontend dependencies
- trx-client: remote client with ClientConfig (general, remote, frontends)
includes all frontend support (http, rigctl, http-json, qt)
- Dedicated config files: trx-server.toml / trx-client.toml
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>