From 2c128127e68955abeeee095514409147107d4317 Mon Sep 17 00:00:00 2001 From: Stanislaw Grams Date: Sat, 7 Feb 2026 09:10:46 +0100 Subject: [PATCH] [refactor](trx-client): remove Qt/QML frontend support 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 Signed-off-by: Stanislaw Grams --- Cargo.lock | 148 +------ Cargo.toml | 1 - OVERVIEW.md | 14 +- README.md | 8 - src/trx-client/Cargo.toml | 5 - src/trx-client/src/config.rs | 17 +- src/trx-client/src/main.rs | 12 +- .../trx-frontend/trx-frontend-qt/Cargo.toml | 24 -- .../trx-frontend-qt/REQUIREMENTS.md | 36 -- .../trx-frontend/trx-frontend-qt/qml/Main.qml | 102 ----- .../trx-frontend/trx-frontend-qt/src/lib.rs | 17 - .../trx-frontend-qt/src/server.rs | 360 ------------------ trx-client.toml.example | 4 - 13 files changed, 19 insertions(+), 729 deletions(-) delete mode 100644 src/trx-client/trx-frontend/trx-frontend-qt/Cargo.toml delete mode 100644 src/trx-client/trx-frontend/trx-frontend-qt/REQUIREMENTS.md delete mode 100644 src/trx-client/trx-frontend/trx-frontend-qt/qml/Main.qml delete mode 100644 src/trx-client/trx-frontend/trx-frontend-qt/src/lib.rs delete mode 100644 src/trx-client/trx-frontend/trx-frontend-qt/src/server.rs diff --git a/Cargo.lock b/Cargo.lock index edf6004..ca2b12b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,7 +65,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -179,7 +179,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -323,12 +323,6 @@ dependencies = [ "alloc-stdlib", ] -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.11.0" @@ -399,7 +393,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -447,56 +441,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "cpp" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bcac3d8234c1fb813358e83d1bb6b0290a3d2b3b5efc6b88bfeaf9d8eec17" -dependencies = [ - "cpp_macros", -] - -[[package]] -name = "cpp_build" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f8638c97fbd79cc6fc80b616e0e74b49bac21014faed590bbc89b7e2676c90" -dependencies = [ - "cc", - "cpp_common", - "lazy_static", - "proc-macro2", - "regex", - "syn 2.0.111", - "unicode-xid", -] - -[[package]] -name = "cpp_common" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25fcfea2ee05889597d35e986c2ad0169694320ae5cc8f6d2640a4bb8a884560" -dependencies = [ - "lazy_static", - "proc-macro2", - "syn 2.0.111", -] - -[[package]] -name = "cpp_macros" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d156158fe86e274820f5a53bc9edb0885a6e7113909497aa8d883b69dd171871" -dependencies = [ - "aho-corasick", - "byteorder", - "cpp_common", - "lazy_static", - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "cpufeatures" version = "0.2.17" @@ -544,7 +488,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.111", + "syn", ] [[package]] @@ -564,7 +508,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", "unicode-xid", ] @@ -607,7 +551,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -718,7 +662,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -1250,43 +1194,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "qmetaobject" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426a57e85d36f055a0c82cb0a8a261d49ba051ab2a2ef5471835f69d477816cd" -dependencies = [ - "cpp", - "cpp_build", - "lazy_static", - "log", - "qmetaobject_impl", - "qttypes", - "semver", -] - -[[package]] -name = "qmetaobject_impl" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc24897c707dcd6963e359e7f2b123857c508f129bed8ac4d3bd575c1a47627" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "qttypes" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7edf5b38c97ad8900ad2a8418ee44b4adceaa866a4a3405e2f1c909871d7ebd" -dependencies = [ - "cpp", - "cpp_build", - "semver", -] - [[package]] name = "quote" version = "1.0.42" @@ -1440,7 +1347,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -1580,17 +1487,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.111" @@ -1610,7 +1506,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -1630,7 +1526,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -1708,7 +1604,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -1811,7 +1707,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -1889,7 +1785,6 @@ dependencies = [ "trx-frontend", "trx-frontend-http", "trx-frontend-http-json", - "trx-frontend-qt", "trx-frontend-rigctl", ] @@ -1938,17 +1833,6 @@ dependencies = [ "trx-frontend", ] -[[package]] -name = "trx-frontend-qt" -version = "0.1.0" -dependencies = [ - "qmetaobject", - "tokio", - "tracing", - "trx-core", - "trx-frontend", -] - [[package]] name = "trx-frontend-rigctl" version = "0.1.0" @@ -2278,7 +2162,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", "synstructure", ] @@ -2299,7 +2183,7 @@ checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -2319,7 +2203,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", "synstructure", ] @@ -2353,7 +2237,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a960352..b7e16b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ members = [ "src/trx-client/trx-frontend", "src/trx-client/trx-frontend/trx-frontend-http", "src/trx-client/trx-frontend/trx-frontend-http-json", - "src/trx-client/trx-frontend/trx-frontend-qt", "src/trx-client/trx-frontend/trx-frontend-rigctl", "src/trx-core", ] diff --git a/OVERVIEW.md b/OVERVIEW.md index 9cf2855..6c4407e 100644 --- a/OVERVIEW.md +++ b/OVERVIEW.md @@ -20,7 +20,6 @@ | TCP CAT transport | Partial (config wiring only) | | JSON TCP control (line-delimited) | Implemented (configurable frontend) | | Plugin registry loading | Implemented (shared libraries) | -| Qt/QML GUI frontend | In progress (Linux only, optional) | | Configuration file (TOML) | Implemented | | Rig state machine | Implemented | | Command handlers | Implemented | @@ -81,10 +80,9 @@ | `trx-frontend` | Frontend trait (`FrontendSpawner`) | | `trx-frontend-http` | Web UI with REST API and SSE | | `trx-frontend-http-json` | JSON-over-TCP control frontend | -| `trx-frontend-qt` | Qt/QML GUI frontend (Linux only, optional) | | `trx-frontend-rigctl` | Hamlib rigctl-compatible TCP interface | | `trx-server` | Server binary — connects to rig backend, exposes JSON TCP control | -| `trx-client` | Client binary — connects to server, runs frontends (HTTP, rigctl, Qt) | +| `trx-client` | Client binary — connects to server, runs frontends (HTTP, rigctl) | --- @@ -103,10 +101,6 @@ Plugin discovery: - Uses shared libraries with a `trx_register` entrypoint. - Searches `./plugins`, `~/.config/trx-rs/plugins`, and any paths in `TRX_PLUGIN_DIRS`. -Qt remote client: -- Uses JSON TCP (`frontends.http_json`) with optional bearer tokens. -- Configure the client with `frontends.qt.remote.enabled/url/auth.token`. - ### Example Configuration ```toml @@ -139,12 +133,6 @@ listen = "127.0.0.1" port = 9000 auth.tokens = ["demo-token"] -[frontends.qt] -enabled = false -remote.enabled = true -remote.url = "127.0.0.1:9000" -remote.auth.token = "demo-token" - [behavior] poll_interval_ms = 500 poll_interval_tx_ms = 100 diff --git a/README.md b/README.md index e6c65d2..d8866cc 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ The rig task is now driven by the controller components (state machine, handlers - HTTP status/control frontend (`trx-frontend-http`) - JSON TCP control frontend (`trx-frontend-http-json`) -- Qt/QML GUI frontend (`trx-frontend-qt`, Linux only, optional via `qt-frontend` feature) - rigctl-compatible TCP frontend (`trx-frontend-rigctl`, listens on 127.0.0.1:4532) ## Plugin discovery @@ -31,13 +30,6 @@ via a `trx_register` entrypoint. Search paths: Example plugin: `examples/trx-plugin-example` -## Qt remote client - -The Qt frontend can run as a remote client over the JSON TCP interface. -Configure the server with `frontends.http_json.auth.tokens` and the client with -`frontends.qt.remote.enabled`, `frontends.qt.remote.url`, and -`frontends.qt.remote.auth.token`. - ## License This project is licensed under the BSD-2-Clause license. See `LICENSES/` for bundled third-party license files. diff --git a/src/trx-client/Cargo.toml b/src/trx-client/Cargo.toml index 258a975..06b5517 100644 --- a/src/trx-client/Cargo.toml +++ b/src/trx-client/Cargo.toml @@ -22,8 +22,3 @@ trx-frontend = { path = "trx-frontend" } trx-frontend-http = { path = "trx-frontend/trx-frontend-http" } trx-frontend-http-json = { path = "trx-frontend/trx-frontend-http-json" } trx-frontend-rigctl = { path = "trx-frontend/trx-frontend-rigctl" } -trx-frontend-qt = { path = "trx-frontend/trx-frontend-qt", optional = true } - -[features] -default = [] -qt-frontend = ["trx-frontend-qt/qt"] diff --git a/src/trx-client/src/config.rs b/src/trx-client/src/config.rs index 1f90d67..90ce386 100644 --- a/src/trx-client/src/config.rs +++ b/src/trx-client/src/config.rs @@ -67,7 +67,7 @@ pub struct RemoteAuthConfig { pub token: Option, } -/// Frontend configurations (client — includes Qt). +/// Frontend configurations. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(default)] pub struct FrontendsConfig { @@ -77,8 +77,6 @@ pub struct FrontendsConfig { pub rigctl: RigctlFrontendConfig, /// JSON TCP frontend settings pub http_json: HttpJsonFrontendConfig, - /// Qt/QML frontend settings - pub qt: QtFrontendConfig, } /// HTTP frontend configuration. @@ -158,14 +156,6 @@ pub struct HttpJsonAuthConfig { pub tokens: Vec, } -/// Qt/QML frontend configuration. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(default)] -pub struct QtFrontendConfig { - /// Whether Qt frontend is enabled - pub enabled: bool, -} - impl ClientConfig { /// Load configuration from a specific file path. pub fn load_from_file(path: &Path) -> Result { @@ -235,7 +225,6 @@ impl ClientConfig { port: 4532, }, http_json: HttpJsonFrontendConfig::default(), - qt: QtFrontendConfig { enabled: false }, }, }; @@ -290,7 +279,6 @@ mod tests { assert_eq!(config.frontends.rigctl.port, 4532); assert!(config.frontends.http_json.enabled); assert_eq!(config.frontends.http_json.port, 0); - assert!(!config.frontends.qt.enabled); assert!(config.remote.url.is_none()); assert_eq!(config.remote.poll_interval_ms, 750); } @@ -311,8 +299,6 @@ enabled = true listen = "127.0.0.1" port = 8080 -[frontends.qt] -enabled = true "#; let config: ClientConfig = toml::from_str(toml_str).unwrap(); @@ -321,7 +307,6 @@ enabled = true assert_eq!(config.remote.auth.token, Some("my-token".to_string())); assert_eq!(config.remote.poll_interval_ms, 500); assert!(config.frontends.http.enabled); - assert!(config.frontends.qt.enabled); } #[test] diff --git a/src/trx-client/src/main.rs b/src/trx-client/src/main.rs index b37835b..3db856b 100644 --- a/src/trx-client/src/main.rs +++ b/src/trx-client/src/main.rs @@ -25,15 +25,11 @@ use trx_frontend_http::register_frontend as register_http_frontend; use trx_frontend_http_json::{register_frontend as register_http_json_frontend, set_auth_tokens}; use trx_frontend_rigctl::register_frontend as register_rigctl_frontend; -#[cfg(feature = "qt-frontend")] -use trx_frontend_qt::register_frontend as register_qt_frontend; - use config::ClientConfig; use remote_client::{parse_remote_url, RemoteClientConfig}; const PKG_DESCRIPTION: &str = concat!(env!("CARGO_PKG_NAME"), " - remote rig client"); const RIG_TASK_CHANNEL_BUFFER: usize = 32; -const QT_FRONTEND_LISTEN_ADDR: ([u8; 4], u16) = ([127, 0, 0, 1], 0); #[derive(Debug, Parser)] #[command( @@ -57,7 +53,7 @@ struct Cli { /// Poll interval in milliseconds #[arg(long = "poll-interval")] poll_interval_ms: Option, - /// Frontend(s) to expose locally (e.g. http,rigctl,qt) + /// Frontend(s) to expose locally (e.g. http,rigctl) #[arg(short = 'f', long = "frontend", value_delimiter = ',', num_args = 1..)] frontends: Option>, /// HTTP frontend listen address @@ -98,8 +94,6 @@ async fn main() -> DynResult<()> { register_http_frontend(); register_http_json_frontend(); register_rigctl_frontend(); - #[cfg(feature = "qt-frontend")] - register_qt_frontend(); let _plugin_libs = plugins::load_plugins(); let cli = Cli::parse(); @@ -155,9 +149,6 @@ async fn main() -> DynResult<()> { if cfg.frontends.http_json.enabled { fes.push("httpjson".to_string()); } - if cfg.frontends.qt.enabled { - fes.push("qt".to_string()); - } if fes.is_empty() { fes.push("http".to_string()); } @@ -239,7 +230,6 @@ async fn main() -> DynResult<()> { "http" => SocketAddr::from((http_listen, http_port)), "rigctl" => SocketAddr::from((rigctl_listen, rigctl_port)), "httpjson" => SocketAddr::from((http_json_listen, http_json_port)), - "qt" => SocketAddr::from(QT_FRONTEND_LISTEN_ADDR), other => { return Err(format!("Frontend missing listen configuration: {}", other).into()); } diff --git a/src/trx-client/trx-frontend/trx-frontend-qt/Cargo.toml b/src/trx-client/trx-frontend/trx-frontend-qt/Cargo.toml deleted file mode 100644 index 7658b5a..0000000 --- a/src/trx-client/trx-frontend/trx-frontend-qt/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-FileCopyrightText: 2025 Stanislaw Grams -# -# SPDX-License-Identifier: BSD-2-Clause - -[package] -name = "trx-frontend-qt" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -default = [] -qt = ["dep:qmetaobject"] - -[dependencies] -trx-core = { path = "../../../trx-core" } -trx-frontend = { path = ".." } -tokio = { workspace = true, features = ["full"] } -tracing = { workspace = true } - -[target.'cfg(target_os = "linux")'.dependencies] -qmetaobject = { version = "0.2", optional = true } diff --git a/src/trx-client/trx-frontend/trx-frontend-qt/REQUIREMENTS.md b/src/trx-client/trx-frontend/trx-frontend-qt/REQUIREMENTS.md deleted file mode 100644 index 272f64d..0000000 --- a/src/trx-client/trx-frontend/trx-frontend-qt/REQUIREMENTS.md +++ /dev/null @@ -1,36 +0,0 @@ -# Qt QML Frontend Requirements - -## Scope -- Provide a Qt Quick (QML) GUI frontend for trx-rs. -- Linux-only support for the initial implementation. -- Use system-wide Qt6 (no vendored Qt). -- Frontend must be optional and feature-gated; default build should not require Qt. - - Feature name in `trx-client`: `qt-frontend`. - -## Functional Requirements -- Show rig status: frequency, mode, PTT state, VFO info, lock state, power state. -- Show basic meters when available: RX signal, TX power/limit/SWR/ALC (as provided by state). -- Allow commands: set frequency, set mode, toggle PTT, power on/off, toggle VFO, lock/unlock, set TX limit (if supported). -- Reflect live updates pushed from the rig task (watch updates). - -## Non-Functional Requirements -- Linux-only for now. -- Build relies on Qt6 libraries/headers installed on the system. -- GUI must be responsive and not block the rig task or frontend thread. -- Minimal but clear UI; no advanced theming or custom widgets required yet. - -## Configuration & Integration -- Expose as a new frontend crate: `trx-frontend-qt`. -- Register via frontend registry under name: `qt`. -- Optional via feature flag (e.g., `qt`) and not part of default workspace features. -- Provide config toggles under `[frontends.qt]` for enable/listen if needed. - - Remote client mode uses JSON TCP with bearer token via `frontends.qt.remote.*`. - -## Packaging/Build -- Document required packages (Qt6 base + QML modules + qmetaobject-rs build prereqs). -- Provide build/run instructions in README/OVERVIEW updates. - -## Out of Scope (for v1) -- Windows/macOS support. -- Offline themes or custom QML assets. -- Advanced settings editor or multi-rig management. diff --git a/src/trx-client/trx-frontend/trx-frontend-qt/qml/Main.qml b/src/trx-client/trx-frontend/trx-frontend-qt/qml/Main.qml deleted file mode 100644 index 6f3848d..0000000 --- a/src/trx-client/trx-frontend/trx-frontend-qt/qml/Main.qml +++ /dev/null @@ -1,102 +0,0 @@ -import QtQuick 2.15 -import QtQuick.Controls 2.15 - -ApplicationWindow { - id: root - visible: true - width: 900 - height: 540 - title: "trx-rs" - - Column { - anchors.centerIn: parent - spacing: 10 - - Label { - text: "trx-rs Qt frontend (stub)" - font.pixelSize: 20 - } - - Label { text: "Frequency: " + rig.freq_text + " (" + rig.freq_hz + " Hz)" } - Label { text: "Mode: " + rig.mode + " Band: " + rig.band } - Label { text: "PTT: " + (rig.tx_enabled ? "TX" : "RX") + " Power: " + (rig.powered ? "On" : "Off") } - Label { text: "Lock: " + (rig.locked ? "Locked" : "Unlocked") } - Label { text: "RX Sig: " + rig.rx_sig + " dB" } - Label { text: "TX Pwr: " + rig.tx_power + " Limit: " + rig.tx_limit + " SWR: " + rig.tx_swr + " ALC: " + rig.tx_alc } - - Row { - spacing: 6 - - TextField { - id: freqInput - width: 140 - placeholderText: "Freq (Hz)" - } - - Button { - text: "Set Freq" - onClicked: rig.set_freq_hz(parseInt(freqInput.text)) - } - - TextField { - id: modeInput - width: 80 - placeholderText: "Mode" - } - - Button { - text: "Set Mode" - onClicked: rig.set_mode(modeInput.text) - } - } - - Row { - spacing: 6 - - Button { - text: rig.tx_enabled ? "PTT Off" : "PTT On" - onClicked: rig.toggle_ptt() - } - Button { - text: rig.powered ? "Power Off" : "Power On" - onClicked: rig.toggle_power() - } - Button { - text: "VFO" - onClicked: rig.toggle_vfo() - } - Button { - text: rig.locked ? "Unlock" : "Lock" - onClicked: rig.locked ? rig.unlock_panel() : rig.lock_panel() - } - } - - Row { - spacing: 6 - TextField { - id: txLimitInput - width: 120 - placeholderText: "TX Limit" - } - Button { - text: "Set Limit" - onClicked: rig.set_tx_limit(parseInt(txLimitInput.text)) - } - } - - Rectangle { - width: 540 - height: 120 - color: "#20252b" - radius: 6 - - Text { - anchors.fill: parent - anchors.margins: 8 - color: "#d0d6de" - text: rig.vfo - font.family: "monospace" - } - } - } -} diff --git a/src/trx-client/trx-frontend/trx-frontend-qt/src/lib.rs b/src/trx-client/trx-frontend/trx-frontend-qt/src/lib.rs deleted file mode 100644 index b2a43ef..0000000 --- a/src/trx-client/trx-frontend/trx-frontend-qt/src/lib.rs +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Stanislaw Grams -// -// SPDX-License-Identifier: BSD-2-Clause - -#[cfg(all(target_os = "linux", feature = "qt"))] -pub mod server; - -#[cfg(all(target_os = "linux", feature = "qt"))] -pub fn register_frontend() { - use trx_frontend::FrontendSpawner; - trx_frontend::register_frontend("qt", server::QtFrontend::spawn_frontend); -} - -#[cfg(not(all(target_os = "linux", feature = "qt")))] -pub fn register_frontend() { - // No-op on non-Linux platforms. -} diff --git a/src/trx-client/trx-frontend/trx-frontend-qt/src/server.rs b/src/trx-client/trx-frontend/trx-frontend-qt/src/server.rs deleted file mode 100644 index d529591..0000000 --- a/src/trx-client/trx-frontend/trx-frontend-qt/src/server.rs +++ /dev/null @@ -1,360 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Stanislaw Grams -// -// SPDX-License-Identifier: BSD-2-Clause - -use std::cell::RefCell; -use std::net::SocketAddr; -use std::path::PathBuf; -use std::thread; - -use qmetaobject::{ - qt_base_class, qt_method, qt_property, qt_signal, queued_callback, QObject, QObjectPinned, - QString, QmlEngine, -}; -use tokio::sync::{mpsc, oneshot, watch}; -use tokio::task::JoinHandle; -use tracing::{info, warn}; - -use trx_core::rig::command::RigCommand; -use trx_core::rig::state::RigMode; -use trx_core::{RigRequest, RigState}; -use trx_frontend::FrontendSpawner; - -/// Qt/QML frontend (Linux-only). -pub struct QtFrontend; - -impl FrontendSpawner for QtFrontend { - fn spawn_frontend( - state_rx: watch::Receiver, - rig_tx: mpsc::Sender, - _callsign: Option, - listen_addr: SocketAddr, - ) -> JoinHandle<()> { - tokio::spawn(async move { - let (update_tx, update_rx) = oneshot::channel::>(); - - spawn_qt_thread(update_tx, listen_addr, rig_tx); - spawn_state_watcher(state_rx, update_rx).await; - }) - } -} - -fn spawn_qt_thread( - update_tx: oneshot::Sender>, - listen_addr: SocketAddr, - rig_tx: mpsc::Sender, -) { - thread::spawn(move || { - let model_cell = Box::leak(Box::new(RefCell::new(RigStateModel::default()))); - let model_ptr = model_cell.as_ptr(); - model_cell.borrow_mut().rig_tx = Some(rig_tx); - - let update = queued_callback(move |state: RigState| unsafe { - // Safe as queued_callback executes on the Qt thread where the model lives. - let model_cell = &mut *model_ptr; - update_model(model_cell, &state); - }); - - if update_tx.send(Box::new(update)).is_err() { - warn!("Qt frontend update channel dropped before init"); - } - - let mut engine = QmlEngine::new(); - engine.set_object_property("rig".into(), unsafe { QObjectPinned::new(model_cell) }); - - let qml_path = qml_main_path(); - info!("Qt frontend loading QML from {}", qml_path.display()); - engine.load_file(QString::from(qml_path.to_string_lossy().to_string())); - info!("Qt frontend running (addr hint: {})", listen_addr); - engine.exec(); - }); -} - -async fn spawn_state_watcher( - mut state_rx: watch::Receiver, - update_rx: oneshot::Receiver>, -) { - let Ok(update) = update_rx.await else { - warn!("Qt frontend update channel closed"); - return; - }; - - update(state_rx.borrow().clone()); - while state_rx.changed().await.is_ok() { - update(state_rx.borrow().clone()); - } -} - -fn qml_main_path() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("qml") - .join("Main.qml") -} - -#[derive(QObject, Default)] -struct RigStateModel { - base: qt_base_class!(trait QObject), - rig_tx: Option>, - freq_hz: qt_property!(u64; NOTIFY freq_hz_changed), - freq_hz_changed: qt_signal!(), - freq_text: qt_property!(QString; NOTIFY freq_text_changed), - freq_text_changed: qt_signal!(), - mode: qt_property!(QString; NOTIFY mode_changed), - mode_changed: qt_signal!(), - band: qt_property!(QString; NOTIFY band_changed), - band_changed: qt_signal!(), - tx_enabled: qt_property!(bool; NOTIFY tx_enabled_changed), - tx_enabled_changed: qt_signal!(), - locked: qt_property!(bool; NOTIFY locked_changed), - locked_changed: qt_signal!(), - powered: qt_property!(bool; NOTIFY powered_changed), - powered_changed: qt_signal!(), - rx_sig: qt_property!(i32; NOTIFY rx_sig_changed), - rx_sig_changed: qt_signal!(), - tx_power: qt_property!(i32; NOTIFY tx_power_changed), - tx_power_changed: qt_signal!(), - tx_limit: qt_property!(i32; NOTIFY tx_limit_changed), - tx_limit_changed: qt_signal!(), - tx_swr: qt_property!(f64; NOTIFY tx_swr_changed), - tx_swr_changed: qt_signal!(), - tx_alc: qt_property!(i32; NOTIFY tx_alc_changed), - tx_alc_changed: qt_signal!(), - vfo: qt_property!(QString; NOTIFY vfo_changed), - vfo_changed: qt_signal!(), - set_freq_hz: qt_method!( - fn set_freq_hz(&self, hz: i64) { - if hz <= 0 { - return; - } - self.send_command(RigCommand::SetFreq(trx_core::radio::freq::Freq { - hz: hz as u64, - })); - } - ), - set_mode: qt_method!( - fn set_mode(&self, mode: QString) { - let mode = parse_mode(&mode.to_string()); - self.send_command(RigCommand::SetMode(mode)); - } - ), - toggle_ptt: qt_method!( - fn toggle_ptt(&self) { - self.send_command(RigCommand::SetPtt(!self.tx_enabled)); - } - ), - toggle_power: qt_method!( - fn toggle_power(&self) { - if self.powered { - self.send_command(RigCommand::PowerOff); - } else { - self.send_command(RigCommand::PowerOn); - } - } - ), - toggle_vfo: qt_method!( - fn toggle_vfo(&self) { - self.send_command(RigCommand::ToggleVfo); - } - ), - lock_panel: qt_method!( - fn lock_panel(&self) { - self.send_command(RigCommand::Lock); - } - ), - unlock_panel: qt_method!( - fn unlock_panel(&self) { - self.send_command(RigCommand::Unlock); - } - ), - set_tx_limit: qt_method!( - fn set_tx_limit(&self, limit: i32) { - if limit < 0 { - return; - } - self.send_command(RigCommand::SetTxLimit(limit as u8)); - } - ), -} - -impl RigStateModel { - fn send_command(&self, cmd: RigCommand) { - let Some(tx) = self.rig_tx.as_ref() else { - warn!("Qt frontend: rig command dropped (channel not set)"); - return; - }; - - let (resp_tx, _resp_rx) = oneshot::channel(); - if tx - .blocking_send(RigRequest { - cmd, - respond_to: resp_tx, - }) - .is_err() - { - warn!("Qt frontend: rig command send failed"); - } - } -} - -fn update_model(model: &mut RigStateModel, state: &RigState) { - let freq_hz = state.status.freq.hz; - if model.freq_hz != freq_hz { - model.freq_hz = freq_hz; - model.freq_hz_changed(); - } - - let freq_text = QString::from(format_freq(freq_hz)); - if model.freq_text != freq_text { - model.freq_text = freq_text; - model.freq_text_changed(); - } - - let mode = QString::from(mode_label(&state.status.mode)); - if model.mode != mode { - model.mode = mode; - model.mode_changed(); - } - - let band = QString::from(state.band_name().unwrap_or_else(|| "--".to_string())); - if model.band != band { - model.band = band; - model.band_changed(); - } - - if model.tx_enabled != state.status.tx_en { - model.tx_enabled = state.status.tx_en; - model.tx_enabled_changed(); - } - - let locked = state.status.lock.unwrap_or(false); - if model.locked != locked { - model.locked = locked; - model.locked_changed(); - } - - let powered = state.control.enabled.unwrap_or(false); - if model.powered != powered { - model.powered = powered; - model.powered_changed(); - } - - let rx_sig = state.status.rx.as_ref().and_then(|rx| rx.sig).unwrap_or(0); - if model.rx_sig != rx_sig { - model.rx_sig = rx_sig; - model.rx_sig_changed(); - } - - let tx_power = state - .status - .tx - .as_ref() - .and_then(|tx| tx.power) - .map(i32::from) - .unwrap_or(0); - if model.tx_power != tx_power { - model.tx_power = tx_power; - model.tx_power_changed(); - } - - let tx_limit = state - .status - .tx - .as_ref() - .and_then(|tx| tx.limit) - .map(i32::from) - .unwrap_or(0); - if model.tx_limit != tx_limit { - model.tx_limit = tx_limit; - model.tx_limit_changed(); - } - - let tx_swr = state - .status - .tx - .as_ref() - .and_then(|tx| tx.swr) - .unwrap_or(0.0) as f64; - if (model.tx_swr - tx_swr).abs() > f64::EPSILON { - model.tx_swr = tx_swr; - model.tx_swr_changed(); - } - - let tx_alc = state - .status - .tx - .as_ref() - .and_then(|tx| tx.alc) - .map(i32::from) - .unwrap_or(0); - if model.tx_alc != tx_alc { - model.tx_alc = tx_alc; - model.tx_alc_changed(); - } - - let vfo = QString::from(vfo_label(state)); - if model.vfo != vfo { - model.vfo = vfo; - model.vfo_changed(); - } -} - -fn format_freq(hz: u64) -> String { - if hz >= 1_000_000_000 { - format!("{:.3} GHz", hz as f64 / 1_000_000_000.0) - } else if hz >= 10_000_000 { - format!("{:.3} MHz", hz as f64 / 1_000_000.0) - } else if hz >= 1_000 { - format!("{:.1} kHz", hz as f64 / 1_000.0) - } else { - format!("{hz} Hz") - } -} - -fn mode_label(mode: &RigMode) -> String { - match mode { - RigMode::LSB => "LSB".to_string(), - RigMode::USB => "USB".to_string(), - RigMode::CW => "CW".to_string(), - RigMode::CWR => "CWR".to_string(), - RigMode::AM => "AM".to_string(), - RigMode::WFM => "WFM".to_string(), - RigMode::FM => "FM".to_string(), - RigMode::DIG => "DIG".to_string(), - RigMode::PKT => "PKT".to_string(), - RigMode::Other(val) => val.clone(), - } -} - -fn parse_mode(value: &str) -> RigMode { - match value.trim().to_uppercase().as_str() { - "LSB" => RigMode::LSB, - "USB" => RigMode::USB, - "CW" => RigMode::CW, - "CWR" => RigMode::CWR, - "AM" => RigMode::AM, - "FM" => RigMode::FM, - "WFM" => RigMode::WFM, - "DIG" | "DIGI" => RigMode::DIG, - "PKT" | "PACKET" => RigMode::PKT, - other => RigMode::Other(other.to_string()), - } -} - -fn vfo_label(state: &RigState) -> String { - let Some(vfo) = state.status.vfo.as_ref() else { - return "--".to_string(); - }; - - let mut lines = Vec::new(); - for (idx, entry) in vfo.entries.iter().enumerate() { - let marker = if vfo.active == Some(idx) { "*" } else { " " }; - let freq = format_freq(entry.freq.hz); - let mode = entry - .mode - .as_ref() - .map(mode_label) - .unwrap_or_else(|| "--".to_string()); - lines.push(format!("{marker} {}: {} {}", entry.name, freq, mode)); - } - lines.join("\\n") -} diff --git a/trx-client.toml.example b/trx-client.toml.example index c0d5eef..330adbd 100644 --- a/trx-client.toml.example +++ b/trx-client.toml.example @@ -47,7 +47,3 @@ listen = "127.0.0.1" port = 0 # List of accepted bearer tokens (empty = no auth) # auth.tokens = ["example-token"] - -[frontends.qt] -# Enable Qt/QML GUI frontend (Linux only, requires system Qt6) -enabled = false