Add native Linux support (CLI + GTK app) - #22
Open
melcodesdev wants to merge 19 commits into
Open
Conversation
A command-line bridge and a GTK/libadwaita desktop app for Linux, added alongside the Windows and macOS builds without changing them. - Platform layer in src/linux/ (mirrors src/macos/): a hidraw backend, a HID report-descriptor parser standing in for Windows HidP_* / macOS IOKit element parsing, and the logger/string shims. Reuses the shared core and the POSIX UDP transport (src/macos/output_udp_posix.cpp). - CMake if(LINUX) target plus a dependency-free Makefile; a Linux CI workflow. - Final Euler output correction (outputSource/outputSign in FilterConfig, applied in orientation.cpp) driven live by the GUI's Calibrate wizard; identity by default so Windows/macOS behaviour is unchanged. - udev rule for rootless hidraw access, a desktop-aware global recenter shortcut, and per-game OpenTrack setup for Steam/Proton. - Docs: docs/LINUX.md (setup and troubleshooting), docs/ARCHITECTURE.md (how the pipeline works), PORTING.md, and Linux sections in the README. Validated on a WH-1000XM5, including Assetto Corsa Competizione through Proton. Builds clean and the Linux test suite passes.
- make install (DESTDIR/PREFIX-aware) does a self-contained system install: the CLI, GUI, scripts, udev rule, desktop entry, icon, and bin launchers. - GUI resolves scripts/extras from an install prefix as well as a repo checkout. - Makefile appends -std=c++20/-Iinclude/-pthread with override, so a packager's exported CXXFLAGS (makepkg, dpkg-buildflags) no longer drops them. - packaging/aur: a PKGBUILD (+ .install, .SRCINFO) building the Linux target. makedepends git/gcc/make; depends gtk4/libadwaita/python-gobject.
- packaging/appimage/build-appimage.sh assembles an AppImage from the system install via linuxdeploy + the GTK plugin, bundling Python and gi. Marked as a starting point (GTK4/PyGObject AppImages need iteration); a README documents the approach and the reliable make install fallback. - docs/LINUX.md gains an Installing section (AUR, system install, AppImage, from source).
Sony headsets advertise the Bluetooth HID service that carries the head tracker, but BlueZ often connects only audio, so no /dev/hidraw node appears and the app looks stuck on 'not detected / grant permission'. scripts/connect-headset-hid.sh asks BlueZ to connect the HID profile (ConnectProfile 0x1124) on a connected Sony device. The GUI calls it automatically on a failed scan (rate-limited) and offers it as a Reconnect headset banner button and a Reconnect head tracker menu item.
The Sony trackers report only orientation (no gyro/accel), so the headset's mounting tilt cannot be leveled against gravity. It shows up as looking up/down bleeding into roll (a pure head pitch splits across pitch and roll in a fixed ratio). Emitting the recentered orientation as a world-frame relative rotation (now * center^-1) instead of body-frame (center^-1 * now) cancels the mounting tilt: a pure pitch stays pure. Verified in simulation (20 deg pitch -> pitch 20, roll 0, vs roll -1.6/yaw -10 before). Opt-in via FilterConfig.levelOutput (bridge --level, control-socket LEVEL, and a Settings toggle), off by default so existing calibrations and the Windows/macOS builds are unchanged. Recenter level-and-forward after enabling.
Fold in the fixes found while building the release AppImage: fetch appimagetool and run tools extract-and-run (no FUSE), tolerate a missing gtk-4.0 modules dir, bundle the full Python stdlib plus gi/cairo (not the host site-packages), write the AppRun outside the AppDir, bundle the Adwaita icon theme, and clear the plugin-forced GTK_THEME so libadwaita styles natively. Ignore build artifacts.
packaging/deb-rpm/build-packages.sh turns 'make install' into native .deb and .rpm packages via fpm (correct per-distro deps, udev reload hook). README covers the downloadable-files path and the hosted-repo options (Fedora COPR, openSUSE OBS, Launchpad PPA) for real apt/dnf-by-name installs.
packaging/rpm/sony-head-tracker.spec builds the CLI + GUI from make/make install for Fedora COPR (open signup) and openSUSE OBS, so users get a real dnf repo: 'dnf copr enable ...' then 'dnf install sony-head-tracker'. Clarify in the guide that repo-add is one-time and official-archive inclusion is a separate process.
debian/ (control, rules, changelog, source/format, postinst) builds the CLI + GTK app via debhelper for Debian/Ubuntu. README covers openSUSE OBS (apt repo, multi-distro), a Launchpad PPA, and a plain local .deb. Needs g++ 13+, so target Debian 13 / Ubuntu 24.04 and newer.
This fork is the Linux port, so the README now opens with it: a Linux platform badge, a note on what this repository is, and a full "Quick start (Linux)" walkthrough (install options and runtime packages, the desktop app steps, the CLI, udev device access, OpenTrack/Proton, and the global recenter shortcut) ahead of everything else. Windows and macOS quick starts move under "Other platforms", the Build section leads with Linux, and the shared sections (compatibility, probe, orientation, protocol notes) now mention Linux first rather than reading as Windows-only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018FbWNUH7k88GfN7JDwcmbn
…h-first-dmzh9n docs: lead the README with the Linux walkthrough
Reported by nar3n12 (issue NicholasSlattery#9): the Grant device access button did nothing on a Debian-based distro, and they fell back to running the app with sudo. The old code discarded pkexec's stdout, stderr and exit code and collapsed every failure into one dead-end sentence, so neither the user nor we could tell what went wrong. - Send the rule to root through a quoted heredoc instead of interpolating a path into a root shell. That also fixes the AppImage case (root cannot read the squashfuse mount the rule lives on) and paths containing a quote. - Chain the udevadm steps with || markers, so a reload failure no longer reports that the rule was not installed when it was. - Guard for a missing pkexec, add a timeout, a re-entrancy guard, and a hint when the prompt may be behind the window. - Classify the failure (no pkexec, no polkit agent, not authorized, cancelled, setuid, timeout) and always offer a Show me how dialog with a copy-and-paste sudo command that embeds the rule, so it works in every install layout. - If the rule installs but access still does not appear, diagnose it: rule does not match this headset (offers to add a matching rule), not in the input group, session not active on the seat, udev did not reload, or reconnect needed. - Keep the failure banner on screen instead of letting the idle rescan erase it.
From a 12-family distro audit (Debian/Arch/RPM/SUSE/musl/non-systemd/NixOS/ immutable/SteamOS/legacy/arch-axes/desktops). Highest reach per effort first: - GUI: gate on libadwaita >= 1.4 with an actionable message. 'Adw 1' only pins the API series, so Ubuntu 22.04 (1.1), Debian 12 and derivatives (1.2) and Leap 15.5 imported fine then died with a bare AttributeError mid-UI. - Makefile: CXX defaults to c++ (clang++ on Chimera, which ships no GCC); probe for -latomic (32-bit ARM has no 8-byte atomic); PYTHON is overridable (NixOS, Guix, Leap's python311); install no longer uses GNU-only 'install -D', which BSD-derived install (Chimera) rejects. - aarch64: nothing in the code was x86 specific, only packaging metadata said so. PKGBUILD arch adds aarch64, debian/control becomes Architecture: any, and the deb/rpm builder derives the arch from the host. - debian/control: 'g++ (>= 13)' was inert because Debian's g++ carries epoch 4, so g++ 12 satisfied it. Now '(>= 4:13) | g++-13 | g++-14'. libadwaita and gir1.2-adw-1 gain the >= 1.4 floor the GUI actually needs. - AppImage: honour sys.platlibdir, so an image built on Fedora, RHEL or SUSE (lib64) is no longer dead on arrival.
The desktop entry is Terminal=false, so raising SystemExit for the libadwaita version gate wrote to stderr that nobody sees: launching from the application menu on an older distribution would have failed completely silently. Report startup blockers through a small GTK window, falling back to zenity, kdialog or notify-send, and keep printing to stderr for terminal users. Found by adversarially verifying the previous commit's gate.
…case Adversarial review of the gate found the remedy it suggested was circular: it told users to use the AppImage, but an AppImage cannot bundle glibc, and every release shipping libadwaita < 1.4 also ships an older glibc than these binaries need (verified: our binary requires GLIBC_2.38; Ubuntu 22.04 has 2.35, Debian 12 has 2.36, Leap 15.5 has 2.31). It now names releases that actually work and points at the command line bridge, which genuinely does not need libadwaita. Also: - Handle a missing GTK or libadwaita typelib (packaged separately on Debian and Ubuntu as gir1.2-gtk-4.0 and gir1.2-adw-1). That raised at gi.require_version before the gate could run, so it was another invisible failure. It now reports through zenity, kdialog or notify-send, using no GTK call. - Apply the libadwaita >= 1.4 floor consistently: it was only in debian/control, so the rpm spec, the AUR PKGBUILD and the fpm builder still installed onto systems where the app then refused to start. - Report the micro version too, and drop a stale AppImage status note.
Steam's July 2026 Linux breakdown puts SteamOS Holo at 22.24 percent, the single largest platform this app's users run, ahead of CachyOS 14.31, Arch 8.29, Mint 22.3 8.12 and Bazzite 7.73. Our binary would not load there. The reason is narrow. SteamOS 3.8 ships gcc 15.1.1 and glibc 2.41, so glibc is not the problem, but its libstdc++ caps at GLIBCXX_3.4.34 and exactly three of our undefined symbols exceed that, all of them std::format internals. Linking libstdc++ and libgcc statically removes the ceiling entirely (verified: no GLIBCXX references remain, GLIBC stays at 2.38 which SteamOS exceeds). - make portable (or PORTABLE=1) links the C++ runtime statically. 284K becomes 2.5M, which is the right trade for a redistributable binary. - The default build is unchanged and still links dynamically, which is what distribution packages require. - The AppImage now builds the portable CLI, so it runs on the Deck. This also settles a question the other way: rewriting the std::format call sites (167 occurrences across 17 files, not the 21 first estimated) would be a large change to code Windows and macOS also compile, and it would buy distributions that barely appear in this population. Ubuntu 22.04 and Mint 21 are not in the Steam top 16 at all, and the Mint that is (22.3) ships libadwaita 1.5.
The previous version reordered the README to lead with Linux, which rewrote 120 lines of the upstream front page. Restore it exactly and add Linux the same way macOS was added: a third subsection under Quick start and under Build, in the order the platforms arrived. Net effect on the upstream README is now +59 -0. The Linux-first arrangement stays on the fork, where it belongs.
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.
Native Linux support (CLI + GTK app)
Adds Linux alongside Windows and macOS. Platform code lives in
src/linux/,mirroring
src/macos/, and reuses the shared tracking core and the POSIX UDPtransport. No Windows or macOS source file is deleted, and neither build changes
behaviour.
Exactly what this touches outside
src/linux/, so you can judge it quickly:include/sony_head_tracker/types.hppFilterConfigfieldsoutputSource{0,1,2},outputSign{1,1,1},levelOutput{false}are identity, so the filter behaves exactly as before unless a user opts in.src/orientation.cppCMakeLists.txtif(LINUX)block only, mirroring the existingif(APPLE)one.README.md### Linuxsubsection under Quick start and under Build, third, in the order the platforms arrived, exactly mirroring how macOS was added. Nothing existing is reordered, reworded or removed.docs/PROTOCOL.md,.gitignore,CHANGELOG.mdI verified PR #19's added maths tests (pitch/roll coverage) still pass against
these changes: 11/11.
Opening this against
linux-supportas you asked in #9. Thanks for bringing thebranch up to date with main, that made it a clean fast-forward.
Apologies for the long gap between saying this was ready and actually opening it.
I was away on holiday. In the meantime two people in #9 tried the fork and
reported back, which is reflected in the testing notes below.
What it adds
probe/dump/bridge): a hidraw backend and a HID reportdescriptor parser (the Linux stand-in for
HidP_*/ IOKit element parsing),emitting the same OpenTrack UDP and JSON wire formats as the other platforms.
recenter, a desktop-aware global recenter shortcut, per-game OpenTrack setup for
Steam and Proton, and one-click rootless device access via a udev rule.
no
/dev/hidraw*node appears and the tracker looks absent. The app asks BlueZto attach the HID profile and rescans.
into roll. Off by default, so nothing changes unless it is enabled.
if(LINUX)block mirroring the macOS one, plus adependency-free Makefile with a DESTDIR/PREFIX aware
install, and Linux CI.packaging/): AUR PKGBUILD (makepkg verified), an RPM spec (liveon Fedora COPR), Debian packaging, and an AppImage builder. Happy to drop these
if you would rather own distribution yourself.
docs/LINUX.md,docs/ARCHITECTURE.md,PORTING.md, and Linuxsections in the README.
Portability work
Audited against the distributions Linux gamers actually run (Steam, July 2026):
SteamOS 22.2%, CachyOS 14.3%, Arch 8.3%, Mint 8.1%, Bazzite 7.7%.
make portablelinks libstdc++ statically. SteamOS 3.8 caps atGLIBCXX_3.4.34and exactly three
std::formatsymbols exceed it, so without this the binarywill not load on a Steam Deck. The default build is unchanged and still links
dynamically, which is what distribution packages want.
it in a window, because the desktop entry is
Terminal=falseand stderr is neverseen by someone launching from a menu.
Tested
Validated on a WH-1000XM5, including Assetto Corsa Competizione through Proton.
Two other people in #9 have confirmed the port on their own hardware and
distributions (XM-6 earbuds on a Debian base, and FlightGear). The Linux test
suite passes and the shared core is untouched.
Happy to split this up, drop the packaging, or rework anything that does not fit
how you want the project to go.