diff --git a/desktop/linux/run-ft8af.sh b/desktop/linux/run-ft8af.sh new file mode 100755 index 000000000..c983ccd13 --- /dev/null +++ b/desktop/linux/run-ft8af.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Launcher for FT8AF on Linux, working around a real Hamlib version conflict +# (see rig.rs's load_hamlib(): a bare dlopen("libhamlib.so.4"), resolved +# against whatever the dynamic linker finds first). +# +# QMX (and other newer rigs) need a newer Hamlib than the distro package +# usually ships -- confirmed on this machine: the system's +# libhamlib.so.4 is Hamlib 4.5.4 (no QMX support at all), while +# /usr/local/lib/libhamlib.so.4 is a separately-built Hamlib 4.7.1 that does +# have it. Both share the same soname, so whichever the linker resolves +# first wins for any process that doesn't override the search path. +# +# We deliberately do NOT upgrade or replace the system Hamlib package -- +# other software on this machine (CQRLOG) depends on it, and installing a +# newer one over it would break that. Instead this script LD_PRELOADs that +# one library into FT8AF's own process: rig.rs's dlopen("libhamlib.so.4") +# then resolves to the already-loaded object, since the soname matches. +# +# LD_PRELOAD rather than prepending /usr/local/lib to LD_LIBRARY_PATH: the +# latter redirects *every* library FT8AF resolves, so a machine that also has +# a locally built libssl/libcurl/libstdc++ under /usr/local/lib would load +# those instead of the distro copies the binary was linked against -- version +# symbol errors or a crash at startup, from a script whose only job is to +# pick a Hamlib. If no newer build exists, this is a no-op and FT8AF falls +# back to whatever the system provides (same as running the binary directly). +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEV_BIN="$SCRIPT_DIR/../src-tauri/target/release/ft8af" + +# Prefer the dev tree's build when this is run from a checkout, but fall back +# to an installed ft8af on PATH -- the .deb/AppImage CI produces puts it at +# /usr/bin/ft8af, and this script is meant to be copied out of the repo. +if [ -x "$DEV_BIN" ]; then + BIN="$DEV_BIN" +elif BIN="$(command -v ft8af)"; then + : +else + echo "error: no ft8af binary found -- looked at $DEV_BIN and on PATH." >&2 + echo " Build it with 'npm run tauri build' or install the package." >&2 + exit 1 +fi + +HAMLIB=/usr/local/lib/libhamlib.so.4 +if [ -f "$HAMLIB" ]; then + export LD_PRELOAD="$HAMLIB${LD_PRELOAD:+:$LD_PRELOAD}" +fi + +exec "$BIN" "$@" diff --git a/desktop/src/styles.css b/desktop/public/styles.css similarity index 75% rename from desktop/src/styles.css rename to desktop/public/styles.css index 215803e3f..412b33b44 100644 --- a/desktop/src/styles.css +++ b/desktop/public/styles.css @@ -48,6 +48,43 @@ select, input { font-size: 13px; } +/* Scoped test: only the Rig control "Display name" field, targeted by its + placeholder text (no JSX/rebuild needed) -- not touching the global + select/input rule, which broke real keyboard input when modified. */ +input[placeholder="e.g. Flex 6400"] { + color: #fff; +} + +/* Select readability, fixed at the cause rather than per-id. + The original symptom is Linux-only: WebKitGTK draws + // popups render as a separate top-level X window on Linux, so they don't + // show up in a window-scoped screenshot either). + if std::env::args().any(|a| a == "--list-audio") { + let inputs = audio::list_input_devices(); + println!("Input devices ({}):", inputs.len()); + for d in inputs.iter() { + println!( + " {}{} -- {} Hz, {} ch", + if d.is_default { "* " } else { " " }, + d.name, + d.default_sample_rate, + d.channels + ); + } + let outputs = audio::list_output_devices(); + println!("Output devices ({}):", outputs.len()); + for d in outputs.iter() { + println!( + " {}{} -- {} Hz, {} ch", + if d.is_default { "* " } else { " " }, + d.name, + d.default_sample_rate, + d.channels + ); + } + return; + } + + let data_dir = app_data_dir(); let _ = std::fs::create_dir_all(&data_dir); let db = Arc::new( Db::open(data_dir.join("ft8af.sqlite")).expect("failed to open database"), @@ -342,6 +494,7 @@ fn main() { set_band, set_base_freq, set_tx_gain, + set_rx_gain, set_input_device, set_output_device, select_rig, @@ -363,9 +516,99 @@ fn main() { set_config, all_config, set_waterfall_config, + get_custom_css, set_udp_config, get_os_location, ]) .run(tauri::generate_context!()) .expect("error running FT8AF"); } + +#[cfg(test)] +mod tests { + use super::{ + fnv1a64, resolve_styles, split_styles_stamp, stamped_default_css, StylesAction, + DEFAULT_STYLES_CSS, + }; + + const OLD: &str = "body { color: red; }\n"; + const NEW: &str = "body { color: blue; }\n"; + + #[test] + fn stamp_round_trips() { + let seeded = stamped_default_css(OLD); + assert_eq!(split_styles_stamp(&seeded), Some((fnv1a64(OLD), OLD))); + } + + #[test] + fn unstamped_content_has_no_stamp() { + assert_eq!(split_styles_stamp(OLD), None); + // A truncated or malformed marker must not be mistaken for a stamp. + assert_eq!(split_styles_stamp("/* ft8af-seeded: zzzz */\nx"), None); + assert_eq!(split_styles_stamp("/* ft8af-seeded: 00ff"), None); + } + + #[test] + fn missing_or_blank_file_is_seeded() { + // No file at all: first run. + assert_eq!(resolve_styles(None, NEW), StylesAction::Seed); + // A 0-byte file from an interrupted first write reads back Ok("") -- + // serving that would inject an empty