A cross-platform desktop application for remote control of Elecraft K4 radios over TCP/IP with real-time audio streaming and spectrum display.
| Platform | Minimum Version | Architecture |
|---|---|---|
| macOS | 15 (Sequoia) — set by LSMinimumSystemVersion in resources/Info.plist.in |
Apple Silicon |
| Windows | 11 | x64 |
| Linux | Raspberry Pi OS / Debian Trixie | ARM64 (Raspberry Pi 4/5) |
| Linux | Any distribution with Flatpak | x86_64 |
- TLS/PSK encrypted connection — TLS 1.2 or later with a pre-shared key on port 9204. PSK needs Qt's OpenSSL TLS backend; the packages ship it. Port 9205 is the unencrypted connection.
- Dual VFO display — frequency, mode, S-meter and tuning rate for VFO A and B
- GPU spectrum — panadapter and waterfall drawn with Qt RHI: Metal on macOS, Direct3D 11 on Windows, OpenGL on Linux
- Mini-pan — compact spectrum view in each VFO area
- Band-plan strip — IARU region 1/2/3 segments across the top of the panadapter
- Dual-channel audio — MAIN and SUB receiver audio (Opus or uncompressed, chosen per radio), with separate volume and balance
- Radio controls — side panels, feature popups, band and mode selection, K4 menu access
- Text decode — windows for the K4's own decoder output (CW, AFSK, FSK, PSK, DATA, SSB), one per receiver
- Macros — CAT command macros on the Fn and PF keys, KPOD buttons, keyboard F1–F12, and a startup macro run on connect
- HaliKey CW keyer — paddles or straight key through a HaliKey (serial V1.4 or MIDI), keyed by QK4's built-in iambic keyer with a local sidetone
- KPOD / KPOD+ — Elecraft KPOD tuning knob and the KPOD+ CW keyer over USB
- KPA1500 — control and status for the Elecraft KPA1500 amplifier
- CAT server — native K4 CAT on TCP port 9299 (default) for WSJT-X, loggers and contest software
- TCI server — TCI 2.0 over WebSocket on port 50001 (default), with RX/TX audio for digital-mode programs
- DX cluster — one or more telnet cluster connections, with spots shown on the panadapter
- Self-contained releases — macOS DMG, Windows ZIP, Raspberry Pi tarball and Linux Flatpak include all dependencies
Pre-built releases are available on the Releases page.
None — the Visual C++ runtime ships inside the package.
One bundle runs on any distribution with Flatpak — no per-distro packages.
# Install Flatpak (if not already installed)
sudo apt install flatpak
# Add the Flathub repository
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
# Install QK4
flatpak install QK4-<version>-linux-x86_64.flatpak
# Install the udev rules — required for KPOD / KPOD+ USB access
sudo curl -o /etc/udev/rules.d/99-kpod.rules https://raw.githubusercontent.com/mikeg-dal/QK4/main/resources/99-kpod.rules
sudo chmod 644 /etc/udev/rules.d/99-kpod.rules
sudo udevadm control --reload-rules
sudo udevadm trigger
# Run QK4
flatpak run io.github.mikeg_dal.QK4After installation you can also launch QK4 from your application menu.
The Flatpak packaging was contributed by @CSVincentS (see #96 and #127), with the original packaging approach and PipeWire tuning from @jmeloranta's Arch AUR build.
- Raspberry Pi 4 or 5 with a desktop session. QK4 runs on the X11 (xcb) platform plugin; under Wayland it runs through XWayland.
- Raspberry Pi OS or Debian based on Trixie, 64-bit. The tarball is built in a Debian Trixie container with the official Qt for ARM64, which it carries along with its own copies of Qt's ICU, OpenSSL and the audio libraries; the rest (X11, Mesa, fontconfig, GLib) comes from the desktop install.
- First run asks for
sudo. The launcher (run.sh) installs a udev rule that gives non-root access to the KPOD and KPOD+ USB devices. Without it, the kernel restricts/dev/hidraw*and the USB device nodes. If you decline, QK4 still starts, but a KPOD or KPOD+ will not work. Once the rule is installed,run.shno longer asks.
Build with the official Qt named in .github/qt-version (currently 6.10.3),
from the Qt Online Installer or
aqtinstall. Every CI runner builds and tests with exactly
that version: macOS, Windows, the Raspberry Pi, the unit and sanitizer tests, and CodeQL. The Flatpak
gets the same minor (6.10) from the KDE runtime, which decides the patch.
Older Qt is not tested. Qt 6.7 is the hard minimum (the panadapter uses QRhiWidget), and a distro
Qt such as Debian Trixie's 6.8 may still build, but CMake warns when the Qt it finds is not the
pinned version. scripts/check-qt-version.sh --local checks the Qt on your machine.
| Dependency | macOS | Windows (vcpkg + Qt Installer) | Linux / Raspberry Pi (apt + official Qt) |
|---|---|---|---|
| C++ compiler | Xcode Command Line Tools | Visual Studio 2019+ Build Tools | g++ |
| CMake | brew install cmake |
Included with VS Build Tools | cmake |
Qt (.github/qt-version) |
Qt Online Installer | Qt Online Installer or aqtinstall | Qt Online Installer or aqtinstall |
| Qt modules | Multimedia, Shader Tools, Serial Port, Svg (select in the installer) | Multimedia, Shader Tools, Serial Port, Svg | Multimedia, Shader Tools, Serial Port, Svg |
| GL / keyboard headers for Qt | — | — | libgl-dev libegl-dev libxkbcommon-dev |
| libopus | brew install opus |
from vcpkg.json |
libopus-dev |
| OpenSSL 3 | brew install openssl@3 |
from vcpkg.json |
libssl-dev |
| HIDAPI | brew install hidapi |
from vcpkg.json |
libhidapi-dev |
| libusb 1.0 | brew install libusb |
from vcpkg.json |
libusb-1.0-0-dev |
| udev, pkg-config | — | — | libudev-dev pkg-config |
| Audio / MIDI | Included with macOS | Included with Windows | libasound2-dev libpulse-dev |
Use Qt from the Qt Online Installer, not Homebrew's qt. A local build against Homebrew Qt cannot
load Qt's OpenSSL TLS backend. It falls back to Secure Transport, which has no PSK cipher suites, so
the encrypted connection on port 9204 fails. CMakeLists.txt looks for opus, hidapi and libusb
under /opt/homebrew/opt/, so the build expects Homebrew on Apple Silicon.
# Install the non-Qt dependencies
brew install cmake opus openssl@3 hidapi libusb
# Check which Qt version the installer put in ~/Qt
ls ~/Qt
# Clone and build (the Qt version CI uses is in .github/qt-version)
git clone https://github.com/mikeg-dal/QK4.git
cd QK4
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$HOME/Qt/6.10.3/macos"
cmake --build build
# Run
./build/QK4.app/Contents/MacOS/QK4
# Create a self-contained app bundle (optional)
cmake --build build --target deployThe vcpkg toolchain file runs vcpkg in manifest mode. It installs opus, hidapi, OpenSSL and
libusb from vcpkg.json during configure, so no separate vcpkg install is needed.
Run these commands from an "x64 Native Tools Command Prompt for VS", so Ninja finds the compiler.
# Install the Qt in .github/qt-version via the Qt Online Installer or aqtinstall
# Required modules: Multimedia, Shader Tools, Serial Port, Svg
# Clone and build (add -DCMAKE_PREFIX_PATH=<Qt>\msvc2022_64 if CMake does not find Qt)
git clone https://github.com/mikeg-dal/QK4.git
cd QK4
cmake -B build -G Ninja -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release
cmake --build build
# Run
.\build\QK4.exe
# Copy the Qt DLLs and plugins next to the executable (optional)
cmake --build build --target deployUse the official Qt, as CI does, not the distribution's. The Qt Online Installer puts it in
~/Qt/<version>/gcc_64 on x86_64 and ~/Qt/<version>/gcc_arm64 on ARM64 (Raspberry Pi 4/5);
aqtinstall does the same without an account.
# Install the non-Qt dependencies (Debian/Ubuntu names)
sudo apt install cmake g++ pkg-config pipx \
libgl-dev libegl-dev libxkbcommon-dev \
libopus-dev libhidapi-dev libusb-1.0-0-dev libssl-dev libudev-dev \
libasound2-dev libpulse-dev
# Install the pinned Qt with aqtinstall (or use the Qt Online Installer)
git clone https://github.com/mikeg-dal/QK4.git
cd QK4
QT_VERSION=$(cat .github/qt-version)
pipx install aqtinstall # then open a new shell, or: export PATH="$HOME/.local/bin:$PATH"
aqt install-qt linux desktop "$QT_VERSION" linux_gcc_64 \
-m qtmultimedia qtshadertools qtserialport -O "$HOME/Qt"
# On a Raspberry Pi: aqt install-qt linux_arm64 desktop "$QT_VERSION" linux_gcc_arm64 ...
# Build (gcc_arm64 instead of gcc_64 on a Raspberry Pi)
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$HOME/Qt/$QT_VERSION/gcc_64"
cmake --build build -j$(nproc)
# Run
./build/QK4A distribution's own Qt 6.7 or later may also work (qt6-base-dev qt6-base-private-dev qt6-multimedia-dev qt6-shadertools-dev qt6-serialport-dev qt6-svg-dev on Debian), but it is not
what CI tests, and CMake will warn about the version.
Builds the same bundle CI produces, without needing the Qt dev packages above — the KDE SDK supplies them.
# Install flatpak-builder and the KDE runtime
sudo apt install flatpak flatpak-builder
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak install flathub org.kde.Platform//6.10 org.kde.Sdk//6.10
# Clone and build
git clone https://github.com/mikeg-dal/QK4.git
cd QK4
flatpak-builder --user --install build-dir flatpak/io.github.mikeg_dal.QK4.json
# Run
flatpak run io.github.mikeg_dal.QK4A local build tracks the main branch and reports its version as main; CI
overrides both to pin the released commit and stamp the real version.
The unit tests use the Qt Test framework. CI runs them on every push and pull request.
# Build every test target, then run them all
cmake --build build --target qk4_tests
ctest --test-dir build --output-on-failure
# Configure the tests alone. From Qt this needs only qtbase; the non-Qt libraries above are still needed
cmake -B build-tests -DQK4_TESTS_ONLY=ON
cmake --build build-tests --target qk4_tests
ctest --test-dir build-tests --output-on-failurectest --test-dir build -N lists the suites. tests/CMakeLists.txt defines them.
- Launch QK4.
- Click the globe icon on the left side panel to open the Server Manager.
- Click Add, then enter a name and your K4's host name or IP address. Scan finds K4s on the local network.
- Encrypted connection: check Use TLS (Encrypted) and enter the radio's PSK as the password. The port changes to 9204.
- Unencrypted connection: leave TLS unchecked. The port is 9205.
- Click Connect. Save keeps the entry in the list for next time.
Application settings are under Tools → Settings… (on macOS, QK4 → Settings…), which opens the Options dialog.
QK4 can write a log file that shows what it was doing when something went wrong. It is off until you turn it on:
- Open Options → Diagnostics and check Write a log file. It applies at once; no restart.
- Reproduce the problem. If a developer asked for more detail, check the subsystem they named (for example CW keying), or set Level to Everything. Bench diagnostics (every CAT command, audio frames) are very verbose and switch themselves off when QK4 restarts.
- Click Open Log Folder and attach
qk4.logto the issue. If QK4 crashed or was restarted since, attachqk4.1.logas well: every launch starts a newqk4.logand moves the previous one toqk4.1.log. Five files of up to 10 MB each are kept.
| Platform | Log folder |
|---|---|
| macOS | ~/Library/Logs/QK4 (also listed in Console.app under Log Reports) |
| Windows | %LOCALAPPDATA%\QK4\Logs (paste it into the Explorer address bar) |
| Linux, Raspberry Pi | ~/.local/state/QK4/logs (or $XDG_STATE_HOME/QK4/logs) |
| Linux Flatpak | ~/.var/app/io.github.mikeg_dal.QK4/.local/state/QK4/logs |
A log contains connection attempts with your radio's address, audio device names and, with bench diagnostics on, the CAT commands sent to the radio. It never contains your radio password or PSK.
From the command line, --log-file writes a log for that launch only, --log-rules "qk4.cw.*.debug=true" adds Qt logging rules, and --log-stderr keeps printing to the terminal while a file is written. QT_LOGGING_RULES works as Qt documents it and overrides the Diagnostics page. Category names are listed in src/logging/README.md.
K4 (TCP 9204 TLS/PSK or 9205) → TcpClient + Protocol (I/O thread) → RadioState → controllers → UI widgets
UI widgets → controllers → ConnectionController → TcpClient → K4
RX audio: Protocol → OpusDecoder (I/O thread) → AudioEngine (audio thread) → speaker
TX audio: microphone → TxAudioPipeline (encode + packetize, AudioTX thread) → TcpClient → K4
Spectrum: Protocol → SpectrumController → PanadapterRhiWidget / MiniPanRhiWidget (GPU)
The thread map and the controller list are in CLAUDE.md and docs/controllers.md.
src/
├── main.cpp # Entry point: settings migration, TLS backend, fonts
├── mainwindow.cpp # Main window and cross-controller wiring
├── controllers/ # One controller per I/O owner or UI slice
├── network/ # K4 link, CAT server, TCI server, DX cluster, KPA1500, discovery
├── audio/ # Audio engine, Opus codecs, resampling, sidetone
├── dsp/ # GPU panadapter, waterfall and mini-pan
├── models/ # RadioState and the K4 menu model
├── settings/ # Persisted settings (RadioSettings)
├── utils/ # Shared helpers (RadioUtils, window fit, macro ids)
├── ui/ # Widgets, popups, overlays, dialogs, Options pages, styling
└── hardware/ # KPOD, KPOD+, HaliKey and the CW keyer
Each directory has a README.md describing its files.
QK4 — Remote control application for Elecraft K4 radios Copyright (C) 2025-2026 Mike Garcia — KF5O
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.