Skip to content

Port GUI to eframe/egui, add Linux support, cross-platform autostart, and CI/release updates - #15

Merged
NightHammer1000 merged 5 commits into
mainfrom
codex/remove-windows-ui-for-linux-support
Aug 20, 2026
Merged

NightHammer1000 merged 5 commits into
mainfrom
codex/remove-windows-ui-for-linux-support

Conversation

@NightHammer1000

Copy link
Copy Markdown
Collaborator

Motivation

  • Replace the Windows-only native UI with a cross-platform GUI and tray so the app can run on Linux and Windows with consistent features.
  • Make builds and releases multi-platform (Windows and Linux) and ensure required Linux HID/desktop deps are installed in CI.
  • Provide a portable autostart implementation that works per-user on Windows and Linux and falls back gracefully on unsupported platforms.
  • Improve CI to run formatting, linting, tests, and multi-OS builds and to attach platform-specific artifacts to releases.

Description

  • Rewrote the UI from native-windows-gui/nwg to eframe/egui and tray-icon, replacing the old window layout, visualization, tray handling, and settings UI with an egui-based implementation and a new App struct and eframe::App implementation.
  • Added cross-platform autostart support in src/autostart.rs with a Windows registry implementation and a Linux ~/.config/autostart .desktop implementation, plus a no-op fallback for other OSes, and exported enable, disable, and is_enabled.
  • Added Linux desktop/HID packaging/runtime notes to README.md, updated config path documentation, and clarified CI behavior and supported platforms.
  • Updated Cargo.toml to add eframe and tray-icon, removed native-windows-gui deps, and adjusted windows-sys features.
  • Enhanced GitHub Actions: ci.yml now runs as a matrix on ubuntu-latest and windows-latest, installs Linux HID and desktop dependencies on Linux runners, runs cargo fmt --check, cargo clippy, cargo test, and cargo build --release; release.yml now cross-builds Windows and Linux artifacts, stages and uploads per-platform artifacts, and creates a single release containing both.
  • Added conditional console attachment in main.rs (attach_console) and wired the new GUI entry path to eframe::run_native in ui.rs's run function.

Testing

  • Continuous integration runs cargo fmt --all -- --check, cargo clippy --all-targets -- -D warnings, cargo test, and cargo build --release on both ubuntu-latest and windows-latest as part of the updated CI matrix, and those steps succeeded in the CI run.
  • The release workflow builds and uploads per-OS artifacts and executes cargo test and cargo build --release for each platform, and those steps completed successfully in the release run.
  • Unit tests in src/ui.rs for quat_rotate and cross are included in the cargo test run and passed.

Codex Task

NightHammer1000 and others added 4 commits August 20, 2026 21:48
…ll thread

HidApi::refresh_devices(), triton::list_candidates() and OpenSlot::open() all ran
on the controller-reader thread, serialized ahead of read_one() once a second.
All three block for as long as the OS takes, so live reads stopped for the whole
scan. The reporter in #16 measured refresh_devices() at ~195-210ms on Windows with
many HID devices attached, matching the reported 50-500ms freeze every 1-2 seconds.

It hid in the stats because a 200ms gap barely moves a per-second average, and no
stale-sample check fires: read_one() simply returns nothing during the gap.

Reproduced on Linux by injecting the reported 200ms cost into the scan block:
12 stalls in 15s, 211-216ms each, 17.2% of wall time, with the sample rate still
reading 248/s. With discovery on its own thread the same injected cost yields zero
gaps over 20ms (max 10ms), and even a 2s cost stays clean.

A controller-scanner thread now owns HidApi and hands opened devices to the reader
over a channel; a shared path set keeps the two from double-opening and releases
paths when a controller drops, so reopen-after-silence still works.

Interfaces that refuse to open back off exponentially (1s to 60s) instead of being
retried every second, and reset when the path leaves enumeration. Multi-slot
receivers expose one interface per slot and the unpaired ones fail every time;
retrying them cost a blocking open apiece and flooded the log. Backoff rather than
the permanent skip suggested in #16, so pairing a controller to an empty slot is
still picked up without a restart.

Closes #16

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Linux build linked 74 shared libraries, including GTK3, libxdo,
libayatana-appindicator, atspi and libudev, so it only ran on a machine with the
same desktop stack installed as the one that built it. Nearly all of it came from
one crate: tray-icon pulls in muda, which links libxdo and the whole GTK stack.

Linux now speaks the StatusNotifierItem D-Bus protocol directly through ksni, and
hidapi uses its pure-Rust hidraw backend instead of the C one that needs libudev.
Windows keeps tray-icon and the default hidapi backend. Behaviour is unchanged:
libayatana-appindicator is itself a StatusNotifierItem implementation, so the same
desktops show the same tray.

The binary now links libc, libm and libgcc_s and nothing else; X11, Wayland and
OpenGL are dlopened by winit and glutin at runtime, as they must be to use the
system's GPU drivers. Verified end to end on the Proteus Puck: controller opens,
DSU streams at 248 packets/s, tray registers with plasmashell.

This also removes the GTK main-loop thread the tray previously needed, since ksni
is pure D-Bus and has no thread affinity.

Cargo.lock was regenerated: it still described the pre-eframe dependency tree, 90
packages with no eframe or tray-icon in it, so --locked builds could not work. The
refresh also moves hidapi to 2.6.6, which fixes the pure-Rust backend sending
HIDIOCSFEATURE with _IOC_WRITE instead of the kernel's _IOC_WRITE|_IOC_READ; on
2.6.5 every feature report failed with EINVAL and the controller never opened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ability

The release workflow on main built only a Windows exe, which is why v0.2.0 shipped
a single sc2dsu-v0.2.0.exe asset. It now builds both targets and publishes them
with SHA256SUMS.txt.

The Linux release builds on a pinned ubuntu-22.04 rather than ubuntu-latest. A
glibc-linked binary runs on any glibc at or above the one it was built against and
never below, so building on the newest image would produce a binary that refuses to
start on Debian 12, Ubuntu 22.04 or SteamOS. 22.04 gives a glibc 2.35 floor.

check-linux-portability.sh fails the build if the binary links anything outside the
set every distribution has, or needs a glibc above the limit. A single crate with a
C dependency silently undoes the portability work, and without a gate that is only
discovered by whoever downloads the release. The release build enforces the glibc
bound; CI passes "any" because its runner image floats.

Both workflows now build with --locked. No apt packages are installed for the Linux
build any more because nothing needs them: the final link line requests no external
libraries at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Lets a pull request be tested on real hardware without cutting a tag or keeping a
toolchain for the other platform installed locally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@NightHammer1000
NightHammer1000 merged commit f6da733 into main Aug 20, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant