Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,7 @@ set(AETHER_SETTINGS_SOURCES
)

set(CORE_SOURCES
src/models/ConnectionWorkspace.cpp
src/core/control/ControlProtocolCodec.cpp # aetherd v1 bounded JSON envelopes
src/core/control/ControlResourceStore.cpp # typed snapshots and revisions
src/core/control/ControlSession.cpp # subscriptions and bounded event queues
Expand Down
5 changes: 5 additions & 0 deletions docs/BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ builds qtkeychain against it, then configure as usual:
```bash
scripts/setup/setup-qt.sh
cmake -B build -G Ninja \
-DREQUIRE_KEYCHAIN=ON \
-DCMAKE_PREFIX_PATH="$(scripts/setup/setup-qt.sh --print-prefix);$(brew --prefix)"
```

Expand All @@ -38,6 +39,10 @@ for fftw, librtlsdr, portaudio and hidapi, but a Homebrew `qt` formula (often
pulled in by something else) lives under the same prefix; naming the pinned Qt
first keeps it the one CMake finds.

`REQUIRE_KEYCHAIN=ON` makes a missing credential store a configuration error.
Keep it enabled when testing saved SmartLink accounts or Icom connections;
without QtKeychain those credentials last only for the current session.

Qt 6.12 needs **Xcode 16** (the macOS 15 SDK): Qt's own CMake stops at
configure with "Qt requires at least version 16 of Xcode" on anything older,
and the script checks before it downloads. Xcode 16 itself needs a macOS 14.5+
Expand Down
6 changes: 6 additions & 0 deletions docs/architecture/aetherd-touchpoint-tags.json
Original file line number Diff line number Diff line change
Expand Up @@ -1732,5 +1732,11 @@
"note": "Explicit per-radio amplifier telemetry association; no radio or tuner commands.",
"split": "",
"confidence": "high"
},
"models/ConnectionWorkspace.h": {
"tag": "universal",
"note": "Versioned local station membership, connection references and operator audio/layout configuration. QtCore only; no vendor wire API or radio operating state.",
"split": "",
"confidence": "high"
}
}
3 changes: 2 additions & 1 deletion docs/architecture/aetherd-touchpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Burndown manifest for the engine/UI decoupling ([RFC](../aetherd-headless-engine-design.md) §2, §10). One row per engine header the UI includes; converting a touchpoint means the UI reaches that surface through the versioned protocol instead of the header.

**Totals:** 274 touchpoint headers (223 core, 51 models) — 274/274 tagged, 0/274 converted.
**Totals:** 275 touchpoint headers (223 core, 52 models) — 275/275 tagged, 0/275 converted.

| Header | Includers | Tag | Status |
|---|---:|---|---|
Expand Down Expand Up @@ -239,6 +239,7 @@ Burndown manifest for the engine/UI decoupling ([RFC](../aetherd-headless-engine
| `models/BandDefs.h` | 5 | universal — Static ARRL band plan table (edges, default freq/mode, GEN/WWV); canonical band-plan data, no vendor ties. | unconverted |
| `models/BandPlanManager.h` | 9 | universal — Band-plan overlay data (segments/spots/license classes, region merge) from JSON; radio-agnostic canon | unconverted |
| `models/BandSettings.h` | 6 | universal — Per-band save/restore of canonical state (freq/mode/filter/AGC/WNB/display range) — band memories, no vendor fields | unconverted |
| `models/ConnectionWorkspace.h` | 1 | universal — Versioned local station membership, connection references and operator audio/layout configuration. QtCore only; no vendor wire API or radio operating state. | unconverted |
| `models/Ctr2ProxyModel.h` | 5 | peripheral(ctr2) — CTR2-Max controller relay: an operator-enabled opaque byte relay between the controller (TCP listener in Wi-Fi mode, USB HID link in USB mode) and a dedicated radio TCP connection. The CTR2 is an independent radio client; nothing passes RadioModel, a backend socket or IRadioBackend. A device that negotiates link extensions (HID Feature report 0x02; never a stock CTR2) also receives AetherSDR's own audio spectrum, from the GUI's Ctr2AudioSpectrumFeeder, beside the relayed stream. A peripheral accessory transport, NOT behind the radio seam. See docs/ctr2-tcp-proxy-design.md and docs/ctr2-usb-relay-design.md (RFC #6091). | unconverted |
| `models/CwDecodeSettings.h` | 9 | ui-support — CW receive backend selection and display preferences in the existing feature-owned settings blob; no radio state. | unconverted |
| `models/CwRxModel.h` | 2 | universal — Receive CW backend catalog and lifecycle over typed PCM; decoder workers remain private and TX is separate. | unconverted |
Expand Down
8 changes: 8 additions & 0 deletions docs/architecture/window-chrome.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,11 @@ built-in demo radio. Native items need real hardware and real pointer input:
macOS traffic-light hover/tiling and pointer hits across the top of the bar
under the unified toolbar; Windows Snap Layouts, Aero Snap and top-edge drift
(#4557); Linux compositor behaviour and fractional-scale drag/resize.

## Connection workspace tabs

`ConnectionPanel::connectionTargets()` supplies saved Location membership, grouped radio paths and discovered candidates. The main window projects these into persistent `RadioTabEntry` values; a hardware serial is separate from the saved tab ID. Discovery updates availability without choosing the operating radio. `RadioSwitcher` owns hidden tabs and `tabOrder`; drag completion saves the full order, including hidden entries. The active session does not affect sorting.

A connection request captures its Location, path and operator before confirmation. Explicit choices supersede startup discovery, scheduled IP probes and credential continuations for that launch. The controller waits for source teardown before dispatching a replacement, and moves the active highlight only on a successful connection. Browsed Location and preferred paths are independent of the last successful connection.

SmartLink replacement calls `RadioModel::prepareWanConnection()` after source teardown and before UDP prebinding and `requestConnect`. This retires a previous simulator or host-DSP backend through the existing production family switch, creates the real SmartLink/VITA transport, and performs no network dial. Connected or in-flight sessions refuse preparation. TLS validation and the existing `connectViaWan` handoff then retain their established ordering.
55 changes: 38 additions & 17 deletions docs/automation-bridge.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ transmit-gated verbs (refused unless `AETHER_AUTOMATION_ALLOW_TX=1` — see
| | [`menu list \| open <name>`](#menu) | Enumerate / pop a menu-bar menu. |
| | [`resize <w> <h> [target]`](#resize) | Resize a window (drives panadapter `x_pixels`). |
| | [`window <state> [target]`](#window) | maximize / restore / minimize / fullscreen. |
| | [`titlebar <action> [id]`](#titlebar) | Drive the unified title bar: selectRadio / showDiscovery / minimize / maximize / close. |
| | [`titlebar <action> [id]`](#titlebar) | Drive the unified title bar: selectRadio / showRadioMenu / showDiscovery / minimize / maximize / close. |
| | [`shortcut <id>`](#shortcut) | Fire a ShortcutManager/MIDI action by id (TX-guarded). |
| | [`midi cc <0-127>`](#midi) | Inject a learned VFO Tune Knob CC event (RX-only). |
| | [`scrollTo <target>`](#scrollto-alias-ensurevisible) | Scroll a widget into its scroll-area viewport. |
Expand Down Expand Up @@ -2785,6 +2785,9 @@ Bare-line forms are also accepted:

```text
connect list
connect profiles
connect profile stations <station-id>
connect profile operators <operator-id>
connect show
connect hide
connect local first
Expand All @@ -2795,6 +2798,17 @@ connect wait 30000
disconnect
```

`connect profiles` returns the versioned connection workspace: saved locations and
their connection paths, operator audio/layout preferences, account labels and
references, and the selected IDs. The API retains the `stations` collection name
for compatibility with saved documents; the UI calls these **Locations**.
It never contains passwords or refresh tokens.
It is available in Observe-only mode. `connect profile stations|operators <id>`
persists a selection for the next connection, including while a radio is live;
it neither connects nor applies audio or radio state. This mutation is refused
in Observe-only mode. Profile creation and editing are exercised through the
normal `connectionProfileEditor` widgets with `invoke`.

`connect show` and `connect hide` idempotently show/raise or hide the
modeless **Connect to Radio** dialog for visual debugging; they do not toggle,
so `connect show` is safe when the dialog is already open. `connect local first`
Expand Down Expand Up @@ -3249,8 +3263,13 @@ Setup instead.
currently clipping configured radios. `radios.tabs[].visibleInTabs` is the
retained tab preference and `visible` is current widget visibility (which can
also change in minimal mode). `radios.tabs[].linkCarrier` identifies
the one tab carrying discovery/heartbeat state. `radios.tabs[].status` is one of `connected` / `available` /
`in use` — a lower-case, untranslated token, and the field to match on.
the one tab carrying discovery/heartbeat state. `radios.tabs[].status` is one of `connected`, `available`,
`in use`, `saved`, `unavailable`, `sign-in required`, `connecting`, `disconnecting`, or `failed` — a lower-case,
untranslated token. `serial` identifies the hardware separately from the stable workspace tab `id`;
`location` names its saved Location. `canConnect`, `connectReason`, and `canCancel` describe connection
availability. `radios.contextMenuVisible` reports an open per-tab menu. `radios.reordering` is true during a tab reorder drag.
The `radios.tabs` array is in visual order; inspect it after `drag radioTab_<id> <dx> 0`, then restart
the same isolated settings profile and compare the order. A drag changes neither `activeId` nor the session.
The text the tab *renders* is sentence case and translated (`Connected`,
`Available`, `In use`, and `Link lost` while the link alarm is up), so
`statusLine` and `accessibleName` deliberately disagree with `status` on
Expand All @@ -3271,16 +3290,17 @@ use QWidget window operations; they do **not** prove a native traffic-light
click, native hover menu, or Windows Snap Layouts. Those require native UI
testing on the target OS.

The radio switcher is also drivable with existing generic bridge verbs:
`invoke radioSwitcherSearch setText <query>`,
`invoke radioSwitcherActions_<radio-id> click`, then invoke the visible menu's
`radioSwitcher_disconnect_<radio-id>`, `radioSwitcher_rename_<radio-id>`,
`radioSwitcher_setup_<radio-id>`, or `radioSwitcher_remove_<radio-id>` action.
Removed tabs offer `radioSwitcher_restore_<radio-id>`. These are widget/action
targets, not new bridge verbs. `radioNicknameEditor` and `saveRadioNickname`
exercise client-owned naming. Radio-owned naming uses Radio Setup instead.
`connectManuallyRow` opens the IP connection page, and `radioSwitcherRescan`
requests discovery without connecting. Inspect enabled states before invoking.
Open a tab's real context menu with `titlebar showRadioMenu <id>`, then use generic `invoke` on
`radioSwitcher_connect_<id>`, `radioSwitcher_disconnect_<id>`, `radioSwitcher_cancel_<id>`,
`radioSwitcher_rename_<id>`, `radioSwitcher_settings_<id>`, or `radioSwitcher_remove_<id>`.
Inspect enabled states and status tips before invoking. Opening a menu never activates its radio.
`radioSwitchConfirm` accepts a replacement; `radioSwitchRetry` and `radioSwitchReconnectPrevious`
exercise failure recovery. Rename uses `connectionProfileName` and the existing profile editor.

`titlebar showDiscovery` retains the searchable saved/discovered picker for inspection and restoration;
its menus use the same actions. `connectManuallyRow` now opens Locations. The title-bar **+** also opens
Locations, preserving the browsed context. In the connection dialog, `connectionRadioRestoreTab`
restores a saved tab and `connectionBackToStation` returns from IP recovery to Locations.

```json
→ {"cmd":"titlebar","action":"selectRadio","target":"1234-5678-9012-3456"}
Expand All @@ -3290,8 +3310,9 @@ requests discovery without connecting. Inspect enabled states before invoking.

| Action | Effect |
|---|---|
| `selectRadio <id>` | Clicks the radio tab whose `id` matches; errors if there is no such tab. |
| `showDiscovery` | Opens the "Discovered radios" popover the `+` button owns. |
| `selectRadio <id>` | Clicks the visible tab matching its workspace ID (or hardware serial). Requests connect/switch; the active highlight follows successful connection. |
| `showRadioMenu <id>` | Opens the visible tab’s context menu without selecting or connecting it. |
| `showDiscovery` | Opens the searchable saved/discovered radio picker. |
| `minimize` / `maximize` / `close` | Activates the matching caption control. |

Every action is validated synchronously — an unknown action or a missing tab
Expand Down Expand Up @@ -4916,7 +4937,7 @@ audio devices require separate hardware validation.
| `invoke` | — | invoke <target> <action> [value…] — drive a control (TX-guarded) |
| `get` | — | get <model> [selector] [property] — live model snapshot; get eqstats [selector] [reset] reports Client EQ paint/cache counters |
| `meterwindow` | — | meterwindow <start [duration_ms]\|status\|stop> — bounded meter ages and unrounded peaks; never keys TX |
| `connect` | — | connect <list\|show\|hide\|local\|ip\|wait> [args] |
| `connect` | — | connect <list\|profiles\|profile stations\|operators <id>\|show\|hide\|local\|ip\|wait> [args] |
| `disconnect` | — | disconnect from the radio |
| `txtest` | — | txtest <twotone\|off> — TX-gated test signal |
| `atu` | — | atu <bypass\|start> — antenna tuner (start is TX-gated) |
Expand Down Expand Up @@ -4958,7 +4979,7 @@ audio devices require separate hardware validation.
| `station` | — | station <name> — set the GUI-client station name |
| `resize` | — | resize <w> <h> [target] — resize a window |
| `window` | — | window <maximize\|restore\|minimize\|fullscreen> [target] |
| `titlebar` | — | titlebar <selectRadio <id>\|showDiscovery\|minimize\|maximize\|close> — drive the unified title bar's own controls |
| `titlebar` | — | titlebar <selectRadio <id>\|showRadioMenu <id>\|showDiscovery\|minimize\|maximize\|close> — drive the unified title bar's own controls |
| `applet` | — | applet <dock <left\|right>\|float <on\|off>\|show\|hide\|state> — drive the applet panel's dock side, floating and visibility |
| `shortcut` | — | shortcut <id> — fire a ShortcutManager/MIDI action (TX-gated) |
| `keyevent` | — | keyevent <press\|release> <action-id\|key-seq> — inject a real key edge through the app event filter (momentary shortcuts only — PTT hold, and the CW keys once bound: their ids ship unbound, so KeyInjectUnbound until the operator binds them in Configure Shortcuts; press is TX-gated; a literal Tab/Backtab moves focus yet reports consumed) |
Expand Down
2 changes: 1 addition & 1 deletion docs/user/docs/anan-g2.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ AetherSDR can receive with an Apache Labs **ANAN-G2** over openHPSDR Protocol 2.

## Setup

The G2 is found automatically on your LAN: open **File → Connect to Radio...** → **On This Network**, select it and click **Connect Selected Radio**.
The G2 is found automatically on your LAN: open **File → Connect to Radio...** → **Nearby radios**, select it and click **Connect Selected Radio**.

Discovery also sends a directed broadcast to each attached IPv4 subnet. This can find a G2 on a second NIC with its own subnet. Two NICs sharing a subnet still use the preferred route; VPNs and routers may block broadcast discovery. If the G2 does not appear, use **Connect by IP** with **Radio type:** set to **ANAN-G2** (see [Manual Connection](./manual-connection.md)).

Expand Down
2 changes: 1 addition & 1 deletion docs/user/docs/demo-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Demo mode runs the full AetherSDR interface against a built-in simulator, so you
## Setup

1. Open the **Connect to Radio** window (**File → Connect to Radio...**).
2. On **On This Network**, the list includes **AetherSDR Demo**, labeled **"Simulator (not on the air)"**.
2. Under **Nearby radios**, the list includes **AetherSDR Demo**, labeled **"Simulator (not on the air)"**.
3. Select it and click **Connect Selected Radio**.

The demo always sorts below real radios and is never chosen over one. There is no menu item or command-line flag; you connect to it like any radio.
Expand Down
Loading
Loading