DuckyDeck Studio β Architecture & Developer Reference (v0.1.4)
DuckyDeck Studio (v0.1.4) is a native Linux desktop configurator,
DSVM v2 bytecode compiler, KDE Plasma 6 Wayland Auto-Switcher, and
host automation daemon for the duckyPad Pro (0x0483:0xd11d) and
duckyPad 2020 (0x0483:0xd11c).
v0.1.6 β’
Custom Firmware: v1.1.0 (DDS_FW_1.1.0_b3291568.bin) β’
KDE Plasma 6 Applet: v1.0.2Website & User Guide: https://duckydeckstudio.com β’ Architecture Reference: https://duckydeckstudio.com/architecture.html β’ GitHub Repository: https://github.com/russellmilliner/duckydeck-studio
ποΈ 1. System Overview & Core Design Philosophy
β‘ 1. Zero-Runtime-Wrapper Native Stack
Built directly in Rust (2021 edition) bridged to Qt 6 / KDE Frameworks 6 (Kirigami + KSyntaxHighlighting) via CXX-Qt 0.7, avoiding Electron or Python runtime overhead while delivering native KDE Breeze theming, fractional HiDPI scaling, and first-class Wayland integration.
πΎ 2. Offline-First & Deterministic SD Sync
All profile edits, key scripts, RGB LED overrides, and rule bindings are immediately persisted to a per-device local cache
(~/.local/share/duckydeck-studio/profile_cache/<serial>/) and compared against the hardware microSD state using SHA-256 content fingerprinting before writing and read-back validating .dsb bytecode.
βοΈ 3. Pure-Rust DSVM v2 Compiler
Compiles duckyScript 3 (keyN.txt / keyN-release.txt) directly into duckyPad virtual machine bytecode (.dsb)
with live syntax diagnostics, Shunting-Yard RPN expression evaluation, control flow (IF/ELSE/WHILE), functions, OLED graphics primitives, and PGV (_GV0.._GV31) access.
π 4. Bidirectional Hardware-Host Automation
Combines KWin Wayland window focus events (zbus D-Bus listener) for automatic profile switching (Host β duckyPad)
with USB HID PGV polling (_GV31 encoded via encode_pgv31_trigger) and global F13βF24 shortcut triggers for Web API webhooks and Desktop Application Launching (duckyPad β Host).
πΊοΈ 2. High-Level System Architecture
DuckyDeck Studio is structured into four cleanly separated layers communicating across a thread-safe CXX-Qt bridge:
π 3. Repository & Module Structure
| Path | Responsibility |
|---|---|
Cargo.toml |
Crate manifest (version = "0.1.4", cxx-qt 0.7, hidapi, zbus, tokio, reqwest, keyring, serde, serde_json, sha2). |
build.rs |
CxxQtBuilder configuration registering QML module io.github.duckydeck and compiling src/bridge/device_controller.rs. |
src/main.rs |
Application entrypoint: CLI & D-Bus single-instance IPC client, QApplication initialization, KSyntaxHighlighting registration, QML startup, and automated Native + Flatpak UI test suites. |
src/paths.rs |
XDG directory helpers (XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_CACHE_HOME, XDG_RUNTIME_DIR) and environment sanitization. |
src/api_server.rs |
Embedded HTTP/1.1 REST API Server (/api/v1/*) with constant-time Bearer auth, 8-slot connection cap, 2 MB payload guard, and async job worker. |
src/bridge/device_controller.rs |
Central #[cxx_qt::bridge] DeviceController QObject, profile fallback & Auto-Switcher synchronization invariants, background thread orchestration, and QInvokable wrappers. |
src/bridge/event_log.rs |
Thread-safe ring buffer (SYSTEM_EVENT_LOG), log file appender, and JSON serializer for system diagnostics. |
src/bridge/rest_routes.rs |
REST API route table and request handlers executed via the DeviceController bridge. |
src/bridge/serialize.rs |
JSON serializers for profiles, slots, unsynced diffs, and templates exposed to QML and the REST API. |
src/bridge/showcase.rs |
Built-in profile templates, SVG icon helpers (known_site_svg), and optional --features demo showcase generator. |
src/hardware/storage.rs |
USB HID transport (hidapi), device discovery, RTC sync, PGV burst read/write (0x18/0x19), and submodule re-exports. |
src/hardware/profile.rs |
DuckyProfile and DuckyKeySlot parser/writer (config.txt, profile_info.txt), SHA-256 diffing, and local cache management. |
src/hardware/msc.rs |
USB Mass Storage (0x14) mounting/unmounting via udisksctl (bridged via flatpak-spawn --host inside Flatpak) and microSD capacity inspection. |
src/hardware/firmware.rs |
Embedded DuckyDeck Studio custom firmware (DDS_FW_1.1.0_b3291568.bin), official dekuNukem release catalog downloader, and IEEE CRC32 integrity verification. |
src/hardware/permissions.rs |
Inspects /sys/class/hidraw device nodes for PermissionDenied and invokes pkexec (or flatpak-spawn --host pkexec) to install 70-duckypad.rules. |
src/actions/web_api.rs |
Web API / Webhook & Home Assistant client, OS keyring secret storage, and _GV31 trigger encoding (encode_pgv31_trigger). |
src/actions/app_launch.rs |
Desktop Application Launcher metadata parser/generator (// @DUCKYDECK_APPLAUNCH), host-aware process spawner, and _GV31 / F13βF24 registries. |
src/actions/desktop_apps.rs |
XDG .desktop entry scanner across system, user, and Flatpak directories. |
src/vm/compiler.rs |
Pure-Rust duckyScript 3 β DSVM v2 (.dsb) two-pass bytecode compiler and syntax verifier. |
src/kwin/auto_switcher.rs |
KWin Wayland script injector, zbus D-Bus service (io.github.duckydeck.Studio at /KWinBridge), window picker, profile reorder/deletion rule remapper, and persisted rules (kwin_rules.json). |
src/ai/shortcut_scraper.rs |
Parses ~/.config/kglobalshortcutsrc and converts KDE shortcuts into duckyScript 3 syntax (kde_shortcut_to_duckyscript). |
src/cpp/app_helper.h |
C++ helper bridging QSystemTrayIcon (dynamic profile badge, SD status, Auto-Switcher/Autostart toggles, and "About DuckyDeck Studio" dialog trigger), KColorScheme, KIconLoader, and KSyntaxHighlighting into QML. |
src/qml/*.qml |
14 QML components: Main.qml, HardwareCanvas.qml, OledDesktopWidget.qml, AutoSwitchDrawer.qml, ColorPickerWindow.qml, WebApiBuilder.qml, AppLaunchBuilder.qml, SettingsPage.qml, RestApiSettings.qml, SdSyncDialog.qml, EventLogDialog.qml, PgvInspectorDrawer.qml, AiArchitectModal.qml, and UdevBanner.qml. |
π 4. CXX-Qt Concurrency Model & _GV31 Trigger Encoding
Because Qt GUI objects (QObject) have strict single-thread affinity on the Qt main event loop thread,
src/bridge/device_controller.rs separates state into Qt-thread-owned fields and thread-safe shared registries:
- Qt-Thread Owned State (
DeviceControllerRust): Holdsprofiles: Vec<DuckyProfile>(up to 64 profiles),sd_root_path: Option<PathBuf>, andcopied_slot: Option<DuckyKeySlot>. - Thread-Safe Shared State (
Arc<Mutex<...>>/ Atomics): Holdsswitcher_state,WEB_API_REGISTRY,WEB_API_PGV_REGISTRY,APP_LAUNCH_FKEY_REGISTRY,APP_LAUNCH_PGV_REGISTRY, andSYSTEM_EVENT_LOG. - Cross-Thread UI Updates (
CxxQtThread): Background worker threads never mutateQPropertyvalues directly; they capturelet qt_thread = self.qt_thread();and queue closures onto the Qt event loop viaqt_thread.queue(move |mut qobject| { ... }).
Unified _GV31 Hardware-to-Host Trigger Word
Both Web API (// @DUCKYDECK_WEBAPI) and Desktop Application Launch (// @DUCKYDECK_APPLAUNCH) scripts in pgv trigger mode write a 32-bit magic word into Persistent Global Variable _GV31 (PGV_TRIGGER_INDEX = 31):
// 32-bit _GV31 Trigger Word Layout:
// Bits 16..31 : 0x5741 ("WA" magic header)
// Bit 15 : is_release (0 = On Press, 1 = On Release)
// Bits 8..14 : profile_num (1..64)
// Bits 0..7 : slot_num (1..58)
pub fn encode_pgv31_trigger(profile_num: u8, slot_num: u8, is_release: bool) -> u32 {
let rel_bit = if is_release { 0x8000 } else { 0 };
0x5741_0000 | rel_bit | ((profile_num as u32 & 0x7F) << 8) | (slot_num as u32)
}
When the background USB HID poller reads _GV0.._GV31 via command 0x18 (HID_CMD_DUMP_PGV) and detects a non-zero _GV31 matching decode_pgv31_trigger, it immediately writes _GV31 = 0 via command 0x19 (HID_CMD_WRITE_PGV) to re-arm the trigger and dispatches the matching WEB_API_PGV_REGISTRY or APP_LAUNCH_PGV_REGISTRY action on the Linux host.
π 5. USB HID Protocol, USB Mass Storage & Flatpak Host Bridge
5.1 64-Byte USB HID Report Protocol (src/hardware/storage.rs)
The duckyPad Pro (0x0483:0xd11d) and duckyPad 2020 (0x0483:0xd11c) communicate over a 64-byte vendor-defined USB HID interface (HID_REPORT_ID_OUT = 0x05):
Command Byte (tx[2]) |
Constant Name | Description |
|---|---|---|
0x00 |
HID_CMD_QUERY_INFO |
Returns firmware version, hardware model, serial number, active profile, and 'D','D','S' custom firmware capability signature. |
0x01 |
HID_CMD_GOTO_PROFILE_BY_NUM |
Switches the active hardware profile by number (1..=64). |
0x04 |
HID_CMD_SET_LED_SINGLE |
Sets key RGB LED overrides in real time. |
0x14 |
HID_CMD_SOFTWARE_RESET |
Reboots the duckyPad firmware (tx[3] = 0 normal HID mode, tx[3] = 1 USB Mass Storage mode). |
0x17 |
HID_CMD_GOTO_PROFILE_BY_NAME |
Switches the active hardware profile by folder name. |
0x18 |
HID_CMD_DUMP_PGV |
Reads all 32 32-bit Persistent Global Variables (_GV0.._GV31, 128 bytes) across 3 burst packets (start_idx = 0, 15, 30). |
0x19 |
HID_CMD_WRITE_PGV |
Writes up to 12 (pgv_index | 0x80, i32_le) entries per packet into _GV0.._GV31. |
5.2 USB Mass Storage (MSC) Mounting & Flatpak Host Command Bridging (src/hardware/msc.rs)
When reading or writing profiles or staging firmware on a duckyPad Pro, DuckyDeck Studio sends HID command 0x14 (tx[3] = 1) to enumerate the microSD card as a TinyUSB Mass Storage block device (/dev/disk/by-label/DP24_* or /dev/disk/by-id/*TinyUSB_Flash_Storage*), mounts the FAT32 partition via udisksctl mount --no-user-interaction -b /dev/sdX1, performs SHA-256 read-back verification, flushes buffers via sync, unmounts cleanly via udisksctl unmount, and reboots back into HID mode (0x14, tx[3] = 0).
run_storage_cmd)org.kde.Platform//6.11), host utilities like udisksctl and umount are not installed inside the container's /usr/bin.
src/hardware/msc.rs automatically detects /.flatpak-info and routes udisksctl, umount, sync, and host /proc/mounts queries through
flatpak-spawn --host while accessing the mounted filesystem directly via --filesystem=/run/media:rw and --filesystem=/media:rw.
βοΈ 6. DSVM v2 Bytecode Compiler (src/vm/compiler.rs)
The duckyPad Pro executes compiled DSVM v2 (.dsb) stack-machine bytecode binaries rather than interpreting raw text on-device.
src/vm/compiler.rs implements a complete two-pass compiler in pure Rust:
1οΈβ£ Preprocessor & Directive Preservation
Strips comments while preserving structured visual-builder metadata directives (// @DUCKYDECK_WEBAPI {...} and // @DUCKYDECK_APPLAUNCH {...}), expands DEFINE macros safely (with recursion/expansion caps), and injects $_ALLOW_ABORT / $_DONT_REPEAT header flags.
2οΈβ£ Block & Control-Flow Parser
Resolves nested IF / ELSE_IF / ELSE / END_IF, WHILE / END_WHILE, LOOP0..LOOP9 state machines, and FUNCTION / RETURN call frames into structured intermediate labels.
3οΈβ£ Shunting-Yard RPN Expression Engine
Converts infix arithmetic, bitwise, comparison, and boolean expressions into stack-based RPN instructions (OP_PUSH_CONST, OP_PUSH_VAR, OP_ADD, OP_EQ, OP_BITWISE_AND) and maps user variables alongside hardware globals (_GV0.._GV31).
4οΈβ£ Two-Pass Linker & Bytecode Emitter
Resolves 16-bit jump target offsets and null-terminated UTF-8 string pool addresses across two passes, emitting little-endian .dsb binaries verified for parity against official DSVM v2 semantics.
πͺ 7. Wayland Auto-Switcher, Session D-Bus IPC & Rule Invariants
On KDE Plasma 6 Wayland, DuckyDeck Studio registers the session D-Bus service io.github.duckydeck.Studio at object path
/KWinBridge (interface io.github.duckydeck.KWinBridge) via zbus:
ReportActiveWindow(window_class: &str, window_title: &str): Invoked by the dynamically injected KWin 6 JavaScript hook (duckydeck-kwin6-hook.js) whenever the active Wayland window changes; evaluatesAutoSwitchRuleentries and switches the duckyPad profile over USB HID unless paused while the script editor is open.ReportPickedWindow(window_class: &str, window_title: &str): Invoked when the user clicks a window using the interactive KWin crosshair picker.DispatchIpcCommand(command: &str) -> u32: Handles single-instance activation, system tray actions, and CLI remote control (show,show-about,minimize,toggle,quit,toggle-oled-widget,switch-profile:<n>,trigger-fkey:<F13..F24>).
Profile Fallback & Auto-Switcher Synchronization Invariants
- Default Fallback Profile Guarantee: Whenever a new profile group is created or loaded, if no profiles exist, Profile
#1("Default") is automatically created and marked as thefallback_profile. - Non-Deletable Fallback Profile:
delete_profilerejects deletion of the currently configuredfallback_profileso unmatched windows and reassigned rules always have a valid target. - Automatic Rule Remapping on Reorder & Delete: Reordering profiles (
remap_rules_on_profile_reorder) atomically updatesfallback_profileand every rule's target profile number/name. Deleting a profile (remap_rules_on_profile_delete) shifts higher indices down by 1 and reassigns any rule referencing the deleted profile to thefallback_profile.
First-Run Deck Setup, Legacy Migration & Conflict Resolution
- Conflicting Process Detection: Detects legacy background processes (
duckypad_autoprofile.py,duckypad_autoswitcher,duckypad_configurator) via/procand host process inspection, offering one-click graceful shutdown to prevent USB HID bus contention. - Profile & Rule Discovery: Discovers profiles from the deck microSD, local cache, and legacy dekuNukem backups in
~/.local/share/duckypad_config/profile_backups/(src/hardware/profile.rs). Discovers rules from microSDduckydeck_rules.json, local rules, and legacy auto-switcher files in~/.local/share/duckypad_autoswitcher/(src/kwin/auto_switcher.rs). - Portable SD Rules Bundle:
duckydeck_rules.jsonon the microSD card syncs auto-switcher rules alongside hardware profiles, enabling instant cross-machine rule portability. - Onboarding State Tracking: Persists completion in
first_run_onboarding_state.jsonacross local and host configs.
π¨ 8. Keycap Layout, OLED Desktop Widget & Automated UI Verification
πΉ Option C Header-Footer Split Keycap Layout
Each mechanical keycap cell in src/qml/HardwareCanvas.qml uses a non-overlapping 3-zone vertical structure:
a 15px top header strip (key number + pill-shaped RGB LED bar), a centered 2-line OLED preview area, and a 15px bottom footer strip (APP/API host badges + β»/βΉ execution flags).
π₯οΈ 4-Way Rotated Desktop OLED Widget
src/qml/OledDesktopWidget.qml and the KDE Plasma 6 Applet (plasmoid/io.github.duckydeck.oled) support per-profile orientation (0Β°, 90Β°, 180Β°, 270Β°, or Follow Hardware OLED), applying both 4Γ5 grid coordinate remapping and in-key text rotation.
Automated Scene-Graph UI Test Suite (Native & Flatpak)
src/qml/EventLogDialog.qml and src/main.rs include a comprehensive automated UI verification harness (--ui-test-suite) that walks every window, drawer, and modal across both Dark Mode and Light Mode at 100% and 115% zoom levels, auditing:
- Zero Overflow & Overlap Violations: Audits 160+ keycap instances and all text labels for truncation or sibling bounding-box collisions.
- WCAG Contrast & High-Contrast Controls: Verifies text/background contrast ratios,
HighContrastCheckBoxborder luminance, and 40+HighContrastScrollBarinstances. - 12 Behavioral Invariants: Verifies profile group creation, non-deletable fallback protection, profile reorder/delete rule remapping, Escape-key modal dismissal, zoom persistence, and bundled DuckyDeck Studio custom firmware catalog presence (
duckydeck_studio_firmware_available_in_catalog).
πΎ 9. Persistence, Credentials & Hardware Portability
| Path (Host / XDG) | Flatpak Sandbox Equivalent | Contents |
|---|---|---|
~/.config/duckydeck-studio/kwin_rules.json |
~/.var/app/io.github.duckydeck.Studio/config/duckydeck-studio/kwin_rules.json |
Auto-Switcher rules, fallback profile, UI text scale, tray settings, REST API config, Global Profile Webhook, and Saved Colors palette. |
~/.config/duckydeck-studio/first_run_onboarding_state.json |
~/.var/app/io.github.duckydeck.Studio/config/duckydeck-studio/first_run_onboarding_state.json |
Onboarding setup completion flag and application version metadata. |
<microSD>/duckydeck_rules.json |
<microSD>/duckydeck_rules.json |
Portable KWin Wayland auto-switcher rules bundle (SdPersistedRulesBundle) stored on deck microSD. |
~/.config/duckydeck-studio/oled_widget_geometry.json |
~/.var/app/io.github.duckydeck.Studio/config/duckydeck-studio/oled_widget_geometry.json |
Desktop OLED Widget monitor, position, size, transparency, always-on-top, and frameless settings. |
~/.local/share/duckydeck-studio/profile_cache/<serial>/ |
~/.var/app/io.github.duckydeck.Studio/data/duckydeck-studio/profile_cache/<serial>/ |
Offline mirror of all hardware profiles and .sd_synced_snapshot.json baseline for SHA-256 diffing. |
~/.local/share/duckydeck-studio/firmware/DDS_FW_1.1.0_b3291568.bin |
/app/share/duckydeck-studio/firmware/DDS_FW_1.1.0_b3291568.bin |
Bundled DuckyDeck Studio custom firmware v1.1.0 (also embedded in the binary for automatic extraction). |
π 9.7 Embedded HTTP REST API Server (src/api_server.rs)
DuckyDeck Studio embeds a bounded, stoppable/restartable HTTP/1.1 REST API server (src/api_server.rs and src/bridge/rest_routes.rs):
- Default Security Posture: Disabled by default (
rest_api_enabled = false), bound to loopback127.0.0.1:28491, with constant-time Bearer Token authentication (ddk_<40 hex chars>stored in OS keyring). - Resource Bounds: Capped at
MAX_CONCURRENT_CONNECTIONS = 8(returning503 Service Unavailablewhen full) andMAX_BODY_BYTES = 2 MB(returning413 Payload Too Large). - Async Worker Jobs (
202 Accepted): Long-running hardware operations (/api/v1/sd/sync,/api/v1/sd/mount,/api/v1/sd/unmount,/api/v1/device/reboot) return202 Acceptedwith a job ID pollable viaGET /api/v1/jobs/{id}. - Interactive Documentation: Full OpenAPI 3.0.3 & Swagger 2.0 specs are served at
/api/v1/openapi.yaml,/api/v1/openapi.json, and/api/v1/swagger.json(and documented in REST_API.md).
π¦ 10. Flatpak Sandbox Architecture & Packaging
DuckyDeck Studio is packaged as a self-contained Flatpak bundle (io.github.duckydeck.Studio) built against
org.kde.Platform//6.11 and org.kde.Sdk//6.11 (configured in io.github.duckydeck.Studio.yml and scripts/build-flatpak.sh).
| Sandbox Permission | Purpose |
|---|---|
--device=all, --filesystem=/run/udev:ro |
Direct USB HID access to /dev/hidraw* and block device symlink discovery in /dev/disk/by-label and /dev/disk/by-id. |
--filesystem=/run/media:rw, --filesystem=/media:rw, --filesystem=xdg-run/gvfs:ro |
Read/write access to mounted duckyPad microSD volumes for profile sync and firmware staging. |
--talk-name=org.freedesktop.Flatpak, --system-talk-name=org.freedesktop.UDisks2 |
Allows invoking flatpak-spawn --host udisksctl / umount / pkexec / xdg-open and launching host desktop applications. |
--talk-name=org.kde.KWin, --filesystem=xdg-run/duckydeck-studio:create |
Allows loading the KWin 6 Wayland active-window tracking script and receiving focus callbacks over session D-Bus. |
--talk-name=org.kde.StatusNotifierWatcher, --talk-name=org.freedesktop.secrets |
System tray icon integration and OS keyring credential storage. |
Download, Install & Run the Flatpak
# Download & install the latest Flatpak bundle directly:
curl -LO https://duckydeckstudio.com/releases/duckydeck-studio-v0.1.4-x86_64.flatpak
flatpak install --user -y duckydeck-studio-v0.1.4-x86_64.flatpak
# Launch GUI:
flatpak run io.github.duckydeck.Studio
# Run CLI commands inside Flatpak:
flatpak run io.github.duckydeck.Studio --status
π οΈ 11. Building, Testing & Extending
# 1. Run formatting, clippy (-D warnings), unit tests, and architecture path checks:
bash scripts/check.sh
# 2. Run the Native QML UI Overflow, Contrast & Behavioral Test Suite:
cargo test ui_test_suite::test_full_qml_ui_overflow_and_contrast_suite -- --ignored --nocapture
# 3. Build & install the Flatpak bundle from source:
bash scripts/build-flatpak.sh
flatpak install --user -y --reinstall target/release/duckydeck-studio-v0.1.4-x86_64.flatpak
# 4. Run the Flatpak QML UI Test Suite inside the Flatpak sandbox:
bash scripts/test-flatpak-ui.sh