On a 390 px screen the transmit controls were laid out at x=400, off the side of a tray 354 px wide: present, invisible, and reachable only by a horizontal scroll with nothing to say it was there. The page scrolled sideways by a dozen pixels as well. Three causes, each in a different place. A container query at the end of the stylesheet re-imposes `flex-wrap: wrap` on a narrow tray's rows. That is right while a row runs left to right; below the phone breakpoint the row is a column, and wrapping a column starts a *second column* — which is what put the transmit controls beside the tray rather than under it. The rule outranks the phone one, two classes to its one, and sits later in the file, so it now excludes itself below that breakpoint rather than being overridden. The tray is a grid, and a grid column sizes to its content. One row wider than the screen — the mode picker, six buttons across — dragged the whole tray out with it. `minmax(0, 1fr)` lets the column be as narrow as the phone and the rows wrap inside it. The wavelength and signal-strength readouts are given the width of their column on narrow screens, but with padding on a content box that is the column's width plus the padding. Those dozen pixels were the page's sideways scroll. They are border-box now. Also: the rig picker was taking 139 px of a 338 px bar, pushing the rest of the top bar into the overflow menu. It is capped and ellipsised on phones, with the full name still in the menu it opens. The map's filter bar, collapsed, keeps both its anchors and shrinks inside them rather than dropping `left` to be sized by shrink-to-fit, and no longer asks for a compositing layer it does not need. An absolutely positioned, backdrop-filtered, composited box sizing itself from its content is the shape of thing that renders as nothing on an engine other than the one it was written against — which is what Edge does with it. Unverified there: this machine has no Edge to test with. tests/mobile-layout.mjs holds the page to it at 430, 390 and 360 px: no sideways scroll, nothing laid out past the right edge, the transmit controls inside the screen, the rig picker within its cap — and the collapsed filter bar still on screen with a button to bring the filters back. The SSTV panel test stamped its picture with a fixed date, which the panel's own retention window dropped once that date was a day old. It uses a recent stamp now. Signed-off-by: Stan Grams <sjg@haxx.space>
trx-rs splits radio hardware access from user-facing interfaces so you can run
rig control, SDR DSP, decoding, audio streaming, and web access as separate,
composable pieces.
| Backends | Yaesu FT-817, Yaesu FT-450D, SoapySDR |
| Frontends | Web UI, rigctl-compatible TCP, JSON-over-TCP |
| Decoders | AIS, APRS, CW, FT8, RDS, VDES, WSPR |
| Audio | Opus streaming between server, client, and browser |
Quick Start
1. Install dependencies
Debian / Ubuntu
sudo apt install build-essential pkg-config cmake libopus-dev libasound2-dev
# Optional — SDR support
sudo apt install libsoapysdr-dev
Fedora
sudo dnf install gcc pkg-config cmake opus-devel alsa-lib-devel
# Optional — SDR support
sudo dnf install SoapySDR-devel
Arch Linux
sudo pacman -S base-devel pkgconf cmake opus alsa-lib
# Optional — SDR support
sudo pacman -S soapysdr
macOS (Homebrew)
brew install cmake opus
# Optional — SDR support
brew install soapysdr
See Build Requirements in the wiki for details on each library.
Note:
cmakeis required even when a system Opus library is installed. Theaudiopus_syscrate probes for Opus viapkg-config; if it is not found (orpkg-configis unavailable), it falls back to compiling a vendored copy of Opus with CMake. A missingcmaketherefore fails the build withis cmake not installed?rather than a missing-Opus error.
2. Build
cargo build --release
Build without SDR support: cargo build --release --no-default-features
3. Configure
Run the interactive setup wizard to generate config files for your station:
./target/release/trx-configurator
The wizard walks you through rig selection, serial port detection, audio
settings, and frontend options, then writes trx-server.toml and
trx-client.toml.
Alternatively, generate example configs and edit them by hand:
./target/release/trx-server --print-config > trx-server.toml
./target/release/trx-client --print-config > trx-client.toml
4. Run
./target/release/trx-server --config trx-server.toml
./target/release/trx-client --config trx-client.toml
Open the configured HTTP frontend address in a browser (default http://localhost:8080).
How It Works
graph TD
SDR1["SDR #1"] & SDR2["SDR #2"] <-->|USB| S1["trx-server A"]
SDR3["SDR #3"] & FT817["FT-817"] <-->|USB / serial| S2["trx-server B"]
S1 <-->|"JSON-TCP :4530"| C1["trx-client"]
S1 -->|"Opus-TCP per rig"| C1
S2 <-->|"JSON-TCP :4530"| C1
S2 -->|"Opus-TCP per rig"| C1
C1 <-->|internal channels| F1["Web UI :8080"]
C1 <-->|internal channels| F2["rigctl :4532"]
Each trx-server owns one or more rigs and runs DSP, decoding, and audio capture locally.
A trx-client connects to any number of servers over TCP and exposes them through
a unified set of frontends.
Documentation
| Resource | Description |
|---|---|
| User Manual | Configuration, features, and usage |
| Architecture | System design, crate layout, data flow, and internals |
| Optimization Guidelines | Performance guidelines for the real-time DSP pipeline |
| Planned Features | Roadmap and design notes |
| Contributing | Commit conventions, workflow, and code style |
License
GPL-2.0-or-later. See LICENSES for the full license text and
bundled third-party license files. Bundled third-party components retain their
original licenses: Leaflet is BSD-2-Clause, DSEG is OFL-1.1, and opus-decoder
is MIT. The APRS symbol sprites come from
hessu/aprs-symbols; their per-symbol
copyright status is catalogued in
LICENSES/LicenseRef-APRS-Symbols.txt.
