Repository navigation
fix(linux): make the Linux build actually work; add CI - #9
Merged
Merged
Conversation
The Linux port was never verified against a Linux compiler or a Linux desktop, and four things were broken at runtime. * keys: VK codes were handed to enigo's `Key::Other`, which is a Windows virtual-key code only on Windows — on Linux it is an X11 keysym. Letters and digits coincided; everything else did not. F1 typed "p", the arrow keys typed punctuation, and every media and modifier key was a silent no-op. Adds a VK->keysym table in the new `keys_vk` module, with a test asserting every entry of `keys_library::KEYS` maps, so a future key cannot be added without one. The repeat thread also built a fresh `Enigo` every 33ms, which on Linux meant an X11 connection and a keymap upload per tick; it now keeps one. * enigo: switch from the default `xdo` backend to `x11rb`. `xdo` is `#[link(name = "xdo")]`, so libxdo.so.3 was a load-time dependency and the shipped binary would not start without xdotool installed. DT_NEEDED is now libudev, libfontconfig, libgcc_s, libm, libc and the loader — nothing else. * main/ui: `--hidden` is ignored where there is no tray, and the two tray settings are hidden. `src/tray.rs` is Windows-only, so autostart plus "start minimized" left a process with no window, no tray and no close event: `kill` was the only way out. * pulse: force `LC_ALL=C`. Every parser matched English pactl output, so on a translated desktop device and process lists came back empty and `is_muted` always returned false. Mute and volume are now served from the existing list cache instead of spawning a `pactl` per query — the LED engine polls 10x/s per LED, which was up to 60 fork/exec per second at idle. Monitor sources are filtered out of the microphone picker. * serial: `friendly()` only matched Windows error wording, so both of its Linux branches were unreachable. Adds the libc spellings and names the actual fix (the `dialout` group), plus a udev rule in packaging/. * autostart: quote `Exec=` so a path containing spaces works at all, and rewrite a stale entry when the executable has moved. * Drop libpulse-binding and udev: declared, never used, and each one a native build and runtime dependency for nothing. Add .github/workflows/ci.yml. release.yml only fires on tags, so nothing compiled the Linux target between releases — and none of the code above is visible to a Windows developer's compiler. The job builds, tests and prints the binary's DT_NEEDED so a newly linked native library shows up in the log rather than as "it won't start" on a user's machine. It caught a real error in this very changeset on its first run. The pactl parsers and the keysym table live in `audio/pulse_parse` and `keys_vk`, deliberately not cfg-gated, so the logic most likely to regress is unit-testable on any host. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`untruncate_comm` splits /proc/<pid>/cmdline on NUL. The literal was written into the source as an actual 0x00 byte rather than the two-character `\0` escape. Rust accepts both, so it compiled and the tests passed — but a NUL byte makes git and grep classify glue.rs as binary, which turns any merge touching it into a whole-file conflict instead of a three-way merge. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PalmarHealer
added a commit
that referenced
this pull request
Sep 12, 2026
Completes the output-switch volume re-apply on Linux. Until now the trait method fell back to its `None` default there, so the actuator never noticed a default-sink change and the feature was Windows-only. Reads the default sink from the cache `refresh()` already fills from `pactl info`, so the actuator's 400ms poll costs no extra process. Written by the session that did the Linux fixes in #9 and handed over for this branch, since it needs both halves: the trait method from here and the rewritten pulse.rs from there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Linux port had never been compiled by a Linux compiler or run on a Linux desktop. Four things were broken at runtime; this fixes them and adds the CI job that would have caught them.
Blockers
Key simulation was broken for everything except letters and digits. VK codes went straight into enigo's
Key::Other, which is a Windows virtual-key code only on Windows — on Linux it is an X11 keysym. The two numbering schemes coincide for ASCII letters/digits and nowhere else:pReturnis 0xFF0D)%®(XF86AudioLowerVolumeis 0x1008FF11)New
keys_vkmodule with the mapping. A test asserts every entry inkeys_library::KEYShas one, so a key cannot be added later without a keysym.--hiddenstranded the app.src/tray.rsis Windows-only, but the settings offered "Start minimized to tray" everywhere, and autostart wrote--hiddeninto the.desktopfile. On Linux that produced a process with no window, no tray, and no close event —killwas the only exit. The flag is now ignored where there is no tray and the two tray rows are hidden.Serious
All pactl parsing was locale-dependent. Every parser matched English output (
Sink Input #,Name:,yes), which pactl translates. On a German desktop the device and process lists came back empty andis_mutedalways returned false. All invocations now forceLC_ALL=C.Up to 60 fork/exec per second at idle.
is_muted/get_volumespawned apactlper call and the LED engine polls them 10×/s per LED.Volume:andMute:are already in thepactl listoutput that gets refreshed anyway, so reads are served from that cache now. Constant ~8 processes/s regardless of LED count.Smaller
friendly()inserial.rsonly matched Windows error wording, so both of its Linux branches were dead code. Added the libc spellings and the actual fix (dialoutgroup), pluspackaging/99-slidr.rules.Exec=in the.desktopfile was unquoted — a path with a space never launched. Also rewrites a stale entry when the binary has moved.libpulse-bindingandudevwere declared and used nowhere: a native build and runtime dependency each, for nothing.xdotox11rb.xdois#[link(name = "xdo")], making libxdo.so.3 a load-time dependency — the shipped binary refused to start without xdotool. DT_NEEDED is now just libudev, libfontconfig, libgcc_s, libm, libc and the loader./proc/<pid>/commtruncates at 15 chars, so long binaries never matched what PulseAudio reports; recovered fromcmdline.deploy.shpublished the Linux binary mode 0644.CI
release.ymlonly fires on tags, so nothing compiled the Linux target between releases. The newci.ymlbuilds, tests, and prints the binary'sDT_NEEDEDso a newly linked native library is visible in the log instead of surfacing as "it won't start". It caught a real error in this changeset on its first run.The pactl parsers and the keysym table sit in
audio/pulse_parseandkeys_vk, deliberately not cfg-gated, so the logic most likely to regress is unit-testable on any host.Verification
Linux CI green on this content:
cargo check --all-targets,cargo test(35 passed),cargo build --release. Committed from an isolated worktree and verified to build standalone.Not verified: no Linux hardware was available, so nothing here was exercised against a real board or desktop session.
Note for the parallel Windows work: this deliberately leaves out the
default_output_id()trait method and its pulse implementation — those belong with the output-switch feature branch.🤖 Generated with Claude Code
This also unblocks CI for every other branch
Verified independently by the parallel Windows-feature branch (
feature/app-categories-led-sources, run 34690055598): branched off currentmain, its Linux CI fails at the link step withci.ymldeliberately does not installlibxdo-dev, because this PR drops thexdobackend. So the enigoxdo→x11rbchange is not merely a runtime packaging improvement — until it lands, any branch offmaincannot get pastcargo teston Linux. That branch goes green on its own once this merges and it rebases.Known follow-up
default_output_id()is intentionally not implemented for the pulse backend here: the trait method belongs to the output-switch feature on the other branch, and implementing it needs both that trait method and this PR's rewrittenpulse.rscache. Until the two are combined, the output-switch volume re-apply is Windows-only and no-ops on Linux. The pulse side is a five-line addition once both are on the same branch.