Repository navigation
Architecture
Design principle: dofscroll complements the vendor driver instead of duplicating it, see Buttons.
One shared core and a thin platform layer per OS. The source code is the reference for behavior and the headers are the contracts. Full text: docs/port-architecture.md.
+------------------------------------------------------------+
| UI: dofscroll-ui (Qt6: tray icon and settings window) |
+----------------------+-------------------------------------+
| writes the profile files, asks for reload
+----------------------v-------------------------------------+
| Daemon core (src/daemon_core.*) |
| config, dsp, modes + mode_runner, profile_match, seed |
+----------+-------------------+-----------------+-----------+
| InputSource | Output | ActiveWindow / paths / reload
+----------v------+ +--------v--------+ +----v-----------------+
| Linux: spnav | | X11 XTest | | X11 window class |
| Windows: raw HID| | uinput (Linux) | | XDG paths, SIGHUP |
| macOS: IOHID | | SendInput (Win) | | Win: foreground exe, |
| | | CGEvent (mac) | | named event |
+-----------------+ +-----------------+ | mac: NSWorkspace |
+----------------------+
-
Daemon core (src/daemon_core.*): platform independent. A reader thread stores the newest raw axis values; the main thread runs a fixed tick (
TICK_HZ, default 250 Hz) and turns them into events, so scrolling is even and independent of the driver's event rate. Reloads onSIGHUP(Linux) and when files change; reconnects tospacenavdby itself. -
Platform layer (src/platform/platform.hpp): interfaces
InputSource,ActiveWindowProvider, paths,ReloadListener. Implementations live insrc/platform/linux/,win/andmac/. A scriptedFakeInputSourcemakes the core testable without hardware. -
File layer (
src/platform/fs_posix.cpp,fs_win.cpp,path_rules.hpp): bounded, symlink-aware reads and atomic writes with private permissions, one implementation per OS family. -
Pure logic (unit-tested everywhere):
config,profile_files,profile_migrate,dsp,key_engine,modes,mode_runner,profile_match,axis_convention,mouse_move_math,device_db,hid_descriptor,key_tables,seed.
device -> InputSource -> latest axes (reader thread)
|
tick (250 Hz): per channel mix, dead zone, curve, smoothing,
sensitivity, sign (src/dsp.*)
|
mode + profile decide: wheel / zoom (Ctrl+wheel) / keys / none
|
Output -> X11 / uinput / SendInput / CGEvent
buttons -> BUTTON_<n> action (click, key, nav, mode:...) -> Output
config / modes files change or SIGHUP -> reload -> new settings
active window class -> [app:*] profile -> resolved settings
spacenavd exposes all SpaceMice as one merged stream, so per-device profiles and button counts are impossible through it. With direct HID access each device is its own handle with its own ID and report stream, and the daemon core keeps one lane per device (its own profile, joystick mode, holds and DSP state) over one shared output that counts held keys; one device per model, up to four devices (docs/multi-device.md). The vendor SDK is not shipped, linked or used at build time on any OS (proprietary, revocable license, incompatible with distributing a GPL-3.0-or-later program; a project decision, not legal advice). On Windows and macOS the installed official driver's interface is loaded at run time as a second input path (INPUT); nothing of it is contained in dofscroll.
Port details: port-windows.md, port-macos.md. See also Development and Roadmap.
dofscroll is GPL-3.0-or-later. "3Dconnexion" and "SpaceMouse" are trademarks of 3Dconnexion GmbH or their respective owners; this is an independent project, not endorsed by or affiliated with them. These pages are generated from docs/wiki in the repository: send changes as a pull request.