piSync CHANGELOG
================

2026-09-30 - Changed "Keeps a KiwiSDR tuned to piHPSDR" to "Keeps a WebSDR tuned to piHPSDR-Improved" in gui.py

2026-09-29 - Release 0.6 (VERSION 0.6, shown as v0.60)
=======================================================

2026-09-29 - Fix: "filename does not have a .desktop extension" during install
---------------------------------------------------------------------------------
Fixed
  * setup.sh built the launcher in a random temp file (/tmp/tmp.XXXX) and ran desktop-file-validate on
    it, which refuses any file not named *.desktop, so every install printed
    "error: filename does not have a .desktop extension" (followed by "desktop-file-validate reported
    the issues above"). The launcher is now built as piSync.desktop inside a temporary folder, which is
    removed on exit. The installed launcher itself was never affected; only the validation step was.
Verified
  * Full setup.sh run with a scratch HOME: completes, both launchers installed, no leftover temp files.
  * Confirmed fixed by the user on Linux Mint 21.3 and 22.1 and on Kubuntu 22.04 LTS (KDE Plasma).
Not verified
  * desktop-file-validate is not installed on the build machine, so the validator's verdict on the
    launcher's contents was not checked here (the user confirmed the reported error is gone).

2026-09-29 - Dark message boxes ("piSync is installed" and the others)
------------------------------------------------------------------------
Fixed
  * The installer's message boxes ("piSync is installed", errors, and the Uninstall / password
    confirmations) were white with black text on dark-theme systems, because Tk's own tkinter.messagebox
    ignores the app colours on Linux. They are now drawn by a small DarkMessages class (piSync/gui.py)
    in piSync's dark palette, with a coloured i / ! / ? badge, OK (and Cancel) buttons, Enter = first
    button, Escape / closing the window = last. Same calls as before, so no other code changed.
  * The main piSync app window uses the same class for its error messages ("piSync is not set up",
    "Could not start piSync"), so those are dark too.
Verified
  * Real windows on the Raspberry Pi: each dialog opens with the dark colours, and OK, Cancel, Enter and
    Escape return the right result in both the installer and the app. 35 Python tests pass.
  * Reported working by the user on Kubuntu 22.04 LTS (KDE Plasma).
Not verified
  * Linux Mint (not yet reported for this change).

2026-09-29 - Dark theme for the "python3-tk is missing" dialog
---------------------------------------------------------------
Changed
  * When tkinter is missing, piSync-installer can't open its own window and shows the message in the
    desktop's native dialog (zenity or kdialog), which came up white with black text on dark-theme
    systems. On KDE it now prefers kdialog (follows the Plasma theme); otherwise zenity is run with
    GTK_THEME=Adwaita:dark (GTK's built-in dark theme), unless GTK_THEME is already set. The message is
    still also printed to the terminal.
  * Test for the dialog choice, ordering, dark-theme setting, and respecting a user-set GTK_THEME.
Verified
  * Reported working by the user on Linux Mint 21.3 and 22.1, and on Kubuntu 22.04 LTS (KDE Plasma,
    where kdialog is now preferred).

2026-09-29 - Release 0.5 (VERSION 0.5, shown as v0.50)
=======================================================
Includes everything below dated 2026-09-29 back to "WebSDR (PA3FWM) support":
  * WebSDR support alongside KiwiSDR
  * Editable drop-down of previous receiver URLs (and the stale-cache fix for it)
  * piSync-installer, the graphical installer
Tested by the user on Raspberry Pi 500+, Linux Mint 21.3, Linux Mint 22.1 and Kubuntu 22.04 LTS
(KDE Plasma) on Intel/AMD: all working.
Still to be tested by the user: the installer's python3-venv path (pkexec install).
Packaging note: python3-tk is now installed by piHPSDR-Improved's libinstall.sh, so it is always present
for piSync once piHPSDR-Improved is installed. When piSync is installed on its own and python3-tk is
missing, the installer/setup.sh shows the message with the install command, and running that command in
a terminal works (confirmed by the user).

2026-09-29 - piSync-installer (graphical installer)
----------------------------------------------------
Added
  * `piSync-installer` (executable script in the project root, logic in piSync/installer.py): a window
    for people who don't use a terminal. piSync-Icon.png is shown centred at the top (scaled to 128 px),
    with a scrolling output field below it showing setup.sh's output live. Uses the system python3 and
    tkinter only, since the virtual environment doesn't exist until setup has run.
  * Install button (runs `bash setup.sh`, adding --no-desktop-icon if the Desktop-icon tick-box is
    cleared), Uninstall button (asks first; runs `setup.sh --uninstall`), and Open piSync (enabled after
    a successful install). A progress bar animates while working.
  * Close is greyed out while the installer is working and active once it has finished (or before it
    starts). Success, failure and uninstall each show a message and a coloured status line. On failure
    the output stays visible and Install can be pressed again; later steps are not run.
  * If python3-venv is missing (which makes setup.sh stop), the installer explains, asks, then installs
    it with `pkexec apt-get install -y python3-venv` (the desktop's own password dialog). Without apt or
    pkexec it shows the manual command instead.
  * If tkinter itself is missing the script cannot open a window, so it tells the user via zenity/kdialog
    (or the terminal) to install python3-tk.
  * Tests: argv building, package-manager/command choice, step sequencing, failure stopping, a missing
    program, and a real `setup.sh --help` run (tests/test_installer.py).
Verified (Raspberry Pi, Wayland)
  * Real window driven by a script with HOME redirected to a scratch folder (your launchers untouched):
    Install ran setup.sh to completion, buttons disabled during the run and Close/Open enabled after,
    "installed" message shown, launchers created. 34 Python tests pass.
  * Reported working by the user on a Raspberry Pi 500+ (double-clicking piSync-installer and installing).
  * Reported working by the user on Linux Mint 21.3, Linux Mint 22.1 and Kubuntu 22.04 LTS (KDE Plasma)
    on Intel/AMD, including double-clicking the script.
  * python3-tk: piHPSDR-Improved's libinstall.sh now installs it, so it is always available when piSync
    is installed after piHPSDR-Improved. On a stand-alone piSync install without it, the "install
    python3-tk" message is displayed and the terminal command it gives works.
Not verified
  * The pkexec/venv path (python3-venv is already present on the test machines) - ideally test on a
    machine without python3-venv.

2026-09-29 - Fix: URL drop-down shown unstyled (stale browser cache)
---------------------------------------------------------------------
Fixed
  * On Firefox the new URL drop-down appeared as a plain bulleted list beside the box, because the
    browser kept using its old cached style.css. piSync's own page and static files (/_pisync/...) are
    now served with "Cache-Control: no-cache" (they still revalidate and get 304s), so upgrades show
    up straight away. The CSS/JS links also carry ?v=2 to force one refetch now.
  * Proxied receiver pages are not affected by the cache header.
Verified (Raspberry Pi 500+, Firefox)
  * Reported working by the user after the fix: the drop-down now displays as a tidy list, and the
    drop-down feature below works there.
  * Also reported working by the user on Linux Mint 21.3, Linux Mint 22.1 and Kubuntu 22.04 LTS (KDE
    Plasma) on Intel/AMD.

2026-09-29 - Editable drop-down of previous receiver URLs
----------------------------------------------------------
Added
  * The receiver URL box is now an editable drop-down. Type a new URL and press Connect/Enter as
    before, or click the arrow (or press the Down key) to see every receiver used before and click one
    to connect straight away. Up/Down/Enter/Esc work in the list; each entry has an x to remove it.
  * A URL is added (most recent first, max 30) only after the server accepts it, so mistyped URLs that
    fail validation are not remembered. The list is kept in the browser's localStorage, so it is per
    browser and survives restarts; the last-used URL is picked up automatically.
  * Built as a small custom control rather than an HTML datalist, because a datalist only lists
    entries matching the text already in the box, which hides the other URLs once one is filled in.
Verified (Raspberry Pi 500+, Firefox)
  * Reported working by the user, after the cache fix above.
  * Also reported working by the user on Linux Mint 21.3, Linux Mint 22.1 and Kubuntu 22.04 LTS (KDE
    Plasma) on Intel/AMD.

2026-09-29 - WebSDR (PA3FWM) support
-------------------------------------
Added
  * piSync can now follow a classic WebSDR (websdr.org software), e.g. http://hackgreensdr.org:8901/,
    as well as a KiwiSDR. Enter its URL in the same box; the type is detected automatically.
  * static/bridge.js now has two drivers behind the same window.piSync interface (ready/tune/txMute/
    readback/kind). The WebSDR driver calls the page's own setfreqb() (which selects the band), then
    set_mode(), then setfreq() so a CW carrier sits off the dial by the pitch. Doing the mode after the
    band matters: WebSDR swaps USB/LSB when it changes band. Mute uses setmute() and the mute
    checkbox; zoom-to-band uses wfset(4).
  * Kiwi modes are mapped to WebSDR's LSB/USB/CW/AM/FM. Frequencies outside the WebSDR's bands are
    reported and skipped without touching the receiver.
  * The receiver chip now reads "Kiwi: ready" or "WebSDR: ready" according to the detected type.
  * Node tests for the WebSDR driver (detection, readiness, band switch, CW, modes, out-of-range, mute).
Findings
  * WebSDR needed no proxy changes: its WebSockets (/~~stream audio, /~~waterstreamN waterfall) use the
    page's own host, so they go through the existing reverse proxy unchanged.
Verified
  * Against the live Hack Green WebSDR through the proxy: page served with the bridge injected, static
    files, and both WebSocket streams deliver data (same as direct). 28 Python tests + Node tests pass.
  * Reported working by the user on a Raspberry Pi 500+, tuning the live Hack Green WebSDR from
    piHPSDR.
  * Also reported working by the user on Linux Mint 21.3, Linux Mint 22.1 and Kubuntu 22.04 LTS (KDE
    Plasma) on Intel/AMD.
Not verified
  * Other WebSDR versions/skins (this one is server build 20140718).

2026-09-29 - Show the release version in the app window
----------------------------------------------------------
Added
  * The app window now reads the release version from a VERSION file in the project root (e.g.
    "0.3") and shows it at startup as "v0.30" (format "vx.xx"), next to the "piSync" title in the
    header and in the window/taskbar title ("piSync v0.30"). If VERSION is missing or empty, the
    window falls back to plain "piSync" with no error.
  * A version that isn't plain "x" or "x.y" (e.g. a three-part semver) is shown as typed, with a
    leading "v" added if it doesn't already have one, rather than forced into "x.xx".
  * Tests for the formatting (plain numbers, already-prefixed "v"/"V", multi-part versions,
    whitespace, missing/empty file).
Verified (Raspberry Pi, KDE Plasma/Wayland)
  * Real window: title bar reads "piSync v0.30" and the header shows "piSync v0.30" with VERSION
    containing "0.3"; both fall back to plain "piSync" with VERSION removed. 28 Python tests and
    the Node test pass.

2026-09-29 - New app icon
--------------------------
Changed
  * The app window, its taskbar/window icon, and the desktop launcher now use the new
    piSync-Icon.png (a dark piSync logo) instead of pisync-icon-light-512.png. assets/pisync-icon-
    {48,64,128,256,512}.png were regenerated from it; setup.sh and gui.py still reference those
    same asset filenames, so no path changes were needed there beyond the fallback (used only if
    the assets/ folder is missing), which now points at piSync-Icon.png.
  * pisync-icon-light-512.png is no longer used by the app but was left in place, not deleted.

2026-09-28 - "Open piSync page" button
---------------------------------------
Added
  * "Open piSync page" button in the app window (next to Clear). Enabled only while the server is
    running (greyed out when stopped or stopping); opens the piSync web page in the default
    browser and notes it in the output field. If no browser can be started, a message shows the
    address to type in.
  * The address comes from [server] in config.toml (default http://127.0.0.1:8090/_pisync/); a
    server listening on all addresses (0.0.0.0 or ::) is opened via 127.0.0.1, and IPv6 literals are
    bracketed.
  * Test for the address logic (defaults, all-interfaces, custom host and port, IPv6, bad config).
Verified (Raspberry Pi, KDE Plasma/Wayland)
  * In the real window: the button is greyed out before Start, enabled while running, clicking it
    passes http://127.0.0.1:8090/_pisync/ to the system browser hook, it greys out again after
    Stop, and clicking it while stopped does nothing. 26 Python tests pass.
  * Reported working by the user on the Raspberry Pi, including the desktop app and launcher.
Not verified
  * Intel/AMD hardware with KDE Plasma and with Linux Mint Cinnamon (to be tested by the user).

2026-09-28 - Desktop app and launcher
--------------------------------------
Added
  * piSync/gui.py: a small window (tkinter only, no extra packages) for people who don't use a
    terminal. piSync icon at top left; a tick-box "piHPSDR is running and TCP CAT is enabled" that
    must be ticked before Start piSync is enabled; Start/Stop button with a status label
    (Stopped / Running / Exited (code N) in red); a scrolling, read-only output field showing the
    server's output live (follows the output only while scrolled to the bottom, capped at 5000
    lines, errors and warnings coloured) with a Clear button.
  * Dark theme matching the web page, because the supplied icon is light line art on a transparent
    background and would be invisible on a light window.
  * The server runs as `<venv python> -u -m piSync` from the project folder. Stop sends SIGTERM to the
    whole process group (clean shutdown), then SIGKILL after 5 s. Closing the window stops it.
  * If the window process dies for any reason (crash, kill -9, lost display) the kernel terminates
    the server (PR_SET_PDEATHSIG), so an orphan cannot keep holding port 8090.
  * Friendly message when the port is already in use (piSync running elsewhere).
  * A clear pop-up if the virtual environment is missing (tells the user to run ./setup.sh).
  * setup.sh: checks Python >= 3.10, venv and tkinter (prints the exact `sudo apt install ...`
    command, or the dnf/pacman equivalent, if something is missing - it never runs sudo itself),
    creates .venv, installs requirements, and installs piSync.desktop into the application menu and
    the Desktop with absolute paths for wherever the folder is (paths with spaces are handled).
    `--no-desktop-icon` and `--uninstall` are supported; safe to re-run after moving the folder.
  * assets/pisync-icon-{48,64,128,256,512}.png, scaled from pisync-icon-light-512.png (the original
    is unchanged), so users need no image tools.
  * tests/test_gui_process.py: 10 tests for the server-process handling (live output, working
    directory, SIGTERM stop, SIGKILL escalation, grandchildren killed, the parent-death cleanup,
    restart, missing venv, config port).
Fixed (portability for Linux Mint 21 / Ubuntu 22.04 and other Python 3.10 systems)
  * piSync/server.py imported tomllib (Python 3.11+ only); it now falls back to tomli.
  * requirements.txt: aiohttp>=3.11 (proxy.py uses ClientWSTimeout, which needs 3.11; the file
    previously said 3.9) and tomli for Python < 3.11.
Verified (on this Raspberry Pi, KDE Plasma on Wayland, Python 3.11)
  * All 25 Python tests and the Node test pass.
  * The real window: opens with the icon; Start is disabled until ticked; Start streams live
    output; Stop ends the server; starting with port 8090 busy shows the error and returns to
    Stopped; closing the window and kill -9 of the window both leave no server running.
  * ./setup.sh run in a copy whose folder name contains a space, then the launcher started via its
    Exec line; then run for real: launcher installed in the application menu and on the Desktop.
  * The window class Tk registers is "Pisync", which is what StartupWMClass is set to.
Not verified
  * Not run on Linux Mint Cinnamon, on Intel/AMD hardware, or under a real Python 3.10 (only
    3.11 is installed here; the tomli fallback was checked by simulating a missing tomllib).
  * Whether the taskbar groups the window under the launcher icon was not checked visually.

2026-09-28 - Follow the VFO in use (RX1/VFO A and RX2/VFO B)
-------------------------------------------------------------
Added
  * The Kiwi now follows whichever receiver is active in piHPSDR: RX1 = VFO A, RX2 = VFO B.
    Switching receivers retunes the Kiwi; changes to the inactive VFO are ignored.
  * Active receiver is read by polling the read-only `FR;` command (it is not auto-reported).
    In piHPSDR receiver n always uses VFO n, and A/B copy or swap only exchange VFO contents,
    so with one receiver the VFO in use is always A.
  * RX2 frequency from `FB;`; RX2 mode from `ZZME;`, polled only while RX2 is active.
    piHPSDR answers ZZME; with a "ZZMD" label and its raw mode enum (LSB 0, USB 1, DSB 2,
    CWL 3, CWU 4, FMN 5, AM 6, DIGU 7, SPEC 8, DIGL 9, SAM 10, DRM 11), so it is converted to
    the Kenwood digit with the same mapping as piHPSDR's ts2000_mode() (DSB/SPEC/DRM -> LSB).
    This quirk is present in every piHPSDR tree on this machine; a corrected "ZZME" label is
    also accepted.
  * On switching to RX2 the cached VFO B frequency and mode are discarded and re-read, and
    nothing is reported until both arrive, so a stale VFO B mode is never sent to the Kiwi.
  * Page shows "RX1 . VFO A" / "RX2 . VFO B" (highlighted for RX2). The state feed
    (/_pisync/ws) gains "rx" (0/1) and "vfo" ("A"/"B").
  * Tests: parsing of FB/FR/ZZMD, mode enum mapping, following RX1 <-> RX2 with frequency and
    mode changes, ignoring the inactive VFO, and the stale-VFO-B case (checked to fail when the
    invalidation is removed).
Changed
  * CatClient callback is now on_state(freq_hz, mode, tx, rx) and only fires once both
    frequency and mode are known for the selected VFO.
  * Polling is now IF; and FR; every 0.1 s (plus FB; and ZZME; while RX2 is active).
  * Only a fixed set of exact command strings can be sent (SAFE_COMMANDS: AI2;, IF;, FR;, FB;,
    ZZME;), enforced in code and by test. In particular FR<digit>; (switches receiver) is
    never sent.
Verified
  * Reported working by the user on the real system, on RX2 as well as RX1.

2026-09-28 - Mute on TX
-----------------------
Added
  * "Mute on TX" checkbox on the control page. When ticked, the Kiwi audio is muted while
    piHPSDR transmits (PTT, MOX, tune or CW) and restored on return to receive. When
    unticked, audio continues during TX.
  * The setting is remembered in the browser (localStorage); `mute_on_tx` in the [sync]
    section of config.toml sets the default for a fresh browser.
  * Red "TX" badge next to the frequency readout while the radio is transmitting.
  * TX state is read by polling the read-only `IF;` CAT command about every 0.1 s
    (piHPSDR auto-information does not report TX). The IF reply also supplies frequency
    and mode, so it replaces the earlier FA;MD; fallback poll.
  * bridge.js `piSync.txMute(on)`: uses Kiwi's toggle_or_set_mute() and kiwi.muted. It only
    unmutes a Kiwi that piSync itself muted, so a mute set by the user is never overridden,
    and it is idempotent, so a Kiwi reload during TX is muted again.
  * If the CAT link drops during TX, TX is treated as cleared so the Kiwi is not left muted.
  * tests/test_bridge.js: Node test of the bridge logic (mute ownership, tuning, mode and
    range validation) against a stubbed Kiwi.
  * Tests for IF parsing, TX detection by polling, TX cleared on disconnect, and a safety
    test that piSync only ever sends AI2 and read commands.
Safety
  * piSync never sends TX;, RX; or ZZTX<digit>; - on piHPSDR these key the transmitter.
Changed
  * CatClient callback is now on_state(freq_hz, mode, tx); default poll interval 0.1 s.
  * Server state feed (/_pisync/ws) now includes a "tx" field.
  * README documents Mute on TX and the safety rule.
Verified
  * Reported working by the user on the real system.

2026-09-28 - Initial version: piHPSDR -> KiwiSDR sync
-----------------------------------------------------
Added
  * Browser-based app that keeps a KiwiSDR tuned to piHPSDR's frequency, mode and band.
    Modelled on CatSync (Windows), which injects JavaScript into the WebSDR page.
    Radio -> WebSDR only; piSync never changes the radio.
  * Python (aiohttp) server, run with `python -m piSync [--config FILE] [-v]`:
      - CAT client (piSync/cat.py) for piHPSDR's TCP CAT server, default port 19090, with
        automatic reconnect and back-off. Sends AI2; so piHPSDR pushes frequency (FA) and
        mode (MD) changes. Handles partial and multiple ';'-terminated frames.
      - Reverse proxy (piSync/proxy.py) for the chosen Kiwi, serving it same-origin at the
        root path (Kiwi builds absolute /ws/kiwi/<ts>/SND WebSocket URLs). Relays HTTP and
        WebSockets (text and binary), passes Kiwi's always-gzipped JS through unchanged,
        injects bridge.js into the Kiwi HTML, and rewrites redirects.
      - Control page and WebSocket state feed under /_pisync/. A top-level browser visit to
        / is redirected to the control page; the iframe gets the Kiwi.
      - POST /_pisync/api/target sets the Kiwi (scheme://host[:port] only); cross-site
        requests and WebSocket connections are rejected by an Origin check.
  * Control page (static/): Kiwi URL box and Connect, Sync on/off, Resync button, CAT and
    Kiwi status chips, live frequency / mode / band readout, message line. Updates are
    applied at most about 10 times a second. The chosen Kiwi URL is remembered.
  * bridge.js (injected into the Kiwi page) wraps Kiwi's freqmode_set_dsp_kHz() so tuning
    calls Kiwi's own function directly, like CatSync, rather than emulating keys or clicks.
    It validates the mode (Kiwi silently turns unknown modes into "am") and skips
    frequencies outside the Kiwi's coverage, allowing for transverter offsets.
    Optionally re-zooms the waterfall to the band on a band change (zoom_on_band_change).
  * Mode mapping (CAT digit -> Kiwi): 1 LSB->lsb, 2 USB->usb, 3 CW->cw, 4 FM->nbfm,
    5 AM/SAM->am, 6 DIGL->lsb, 7 CW-R->cw, 9 DIGU->usb. Overridable in a [modes] table.
  * Band table (2200 m to 2 m) for the band readout.
  * config.toml (server, cat, kiwi, sync, modes), requirements.txt, README.md,
    piSync.service (optional systemd unit).
  * tests/: unit and integration tests (pytest) and tests/fake_cat.py, a fake piHPSDR CAT
    server (`python -m tests.fake_cat [port]`) for manual runs.
Findings that shaped the design
  * The source of piHPSDR v3.0 on this Pi confirms the default CAT port 19090, that
    AI1 reports frequency only and AI2 also reports mode, and that FA reports the CTUN
    frequency when CTUN is on.
  * KiwiSDR 1.902 client source (g3sdr.com:8073): freqmode_set_dsp_kHz() is the right tuning
    call. ext_tune() was rejected because without a zoom argument it resets the waterfall
    zoom on every call.
Verified
  * Automated tests pass against fakes; the proxy served the real Kiwi page and JS, and a
    live WebSocket handshake through the proxy to g3sdr.com:8073 succeeded.
  * Reported working by the user on the real system.

