-
Notifications
You must be signed in to change notification settings - Fork 37
Contributing
# Fork the repo on GitHub, then: git clone https://github.com/<your-username>/logitune.git cd logitune
Option A: Devcontainer (recommended)
Open the repo in VS Code and accept the "Reopen in Container" prompt. The devcontainer builds the project automatically on creation. See Building — Devcontainer.
Option B: Local
Install dependencies for your distro (see Building — Prerequisites), then:
make build make setup-hooks make test-all
Install the git pre-push hook to catch failures before they reach CI:
make setup-hooks
Since PR #97, cmake -B build auto-activates the tracked hooks/ directory via core.hooksPath — there's no manual step. hooks/pre-push runs five checks before any push:
-
README devices table lint (
scripts/generate-readme-devices.py --check) — regenerates the table on disk and aborts ifdevices/*/descriptor.jsonhas drifted. -
C++ unit + integration tests (
logitune-tests). -
Tray tests (
logitune-tray-tests). -
QML tests (
logitune-qml-tests). -
Python extractor tests (
pytest tests/scripts/test_extractor.py).
If any stage fails the push is blocked; for the README lint the hook leaves the regenerated file in your working tree for you to amend/commit.
# Create a feature branch from master git checkout master git pull git checkout -b feature/my-feature # Make changes, commit, push git add <files> git commit -m "feat: add DPI shift button action" git push -u origin feature/my-feature # Open a PR against master
- Standard: C++20
-
Naming: camelCase for variables and methods, PascalCase for types and classes,
m_prefix for member variables,kprefix for constants - Includes: Group by standard library, Qt, project headers; separated by blank lines
-
Namespaces:
logitunefor production code,logitune::testfor tests,logitune::hidppfor protocol layer -
Qt conventions: Use
QStringLiteral()for string literals,Q_OBJECTmacro for signal/slot classes,connect()with member function pointers (not string-based) - No extra abstractions: Don't add interfaces, factories, or patterns unless they solve a concrete problem. The codebase deliberately avoids unnecessary indirection.
-
Error handling: Use
std::optionalfor fallible operations,qCWarning/qCDebugfor logging, no exceptions in normal flow
- Style: Follow Qt Quick coding conventions
-
Theme: All colors, fonts, and spacing come from the
Themesingleton — never hardcode values - Naming: camelCase for properties and functions, PascalCase for component files
#pragma once // Always use pragma once (not include guards) #include "..." // Project includes first #include <...> // System/Qt includes second namespace logitune { class MyClass : public QObject { Q_OBJECT Q_PROPERTY(int value READ value NOTIFY valueChanged) public: explicit MyClass(QObject *parent = nullptr); int value() const; signals: void valueChanged(); private slots: void onSomethingHappened(); private: int m_value = 0; }; } // namespace logitune
Use Conventional Commits:
<type>: <description>
[optional body]
[optional footer]
| Type | When |
|---|---|
feat |
New feature |
fix |
Bug fix |
refactor |
Code change that neither fixes a bug nor adds a feature |
test |
Adding or updating tests |
docs |
Documentation only |
chore |
Build, CI, tooling changes |
feat: add DPI shift button action
Hold a button to temporarily lower DPI for precision aiming.
Implemented as ButtonAction::DpiShift with configurable target DPI.
feat: native package builds (.deb, .rpm, Arch), devcontainer for GitHub Codespaces
fix: thumb wheel direction — clockwise should zoom in
Read defaultDirection from HID++ GetInfo to normalize
clockwise = positive in software.
refactor: extract TrayManager, fix battery initial value
test: add profile switch behavior tests for display vs hardware profile
For large changes touching multiple subsystems, use a summary line followed by subsection headers in the body:
feat: thumb wheel overhaul — defaultDirection, invert, command processor, reconnect
Thumb wheel:
- Read defaultDirection from HID++ GetInfo to normalize clockwise=positive
- Add thumbWheelInvert as a proper profile field with UI toggle
- Add horizontal scroll injection (REL_HWHEEL) for scroll mode
HID++ command processor:
- New CommandProcessor sends commands sequentially with 10ms pacing
- Eliminates HwError from flooding device during profile switches
Before opening a pull request, verify:
-
make test-allpasses locally (or pre-push hook passed) - New features have tests
- No hardcoded values in QML — use Theme singleton
- No
fprintf/qDebug()— use Qt logging categories (qCDebug(lcXxx),qCInfo(lcXxx),qCWarning(lcXxx)) - New files added to the appropriate
CMakeLists.txt - Commit messages follow conventional commit format
- PR description explains what and why (not how)
The preferred path for new device descriptors is the in-app editor:
- Fork the repo and bootstrap a
descriptor.jsonfor your device (see Adding a Device for the JSON schema). - Run
logitune --edit(pair with--simulate-allif you do not own the hardware) and use the editor to position hotspots, drop in device images, and polish labels. - Save.
git diff devices/<slug>/should show the in-memory changes you made. - Submit a PR with
"status": "beta"unless you have hardware-verified the descriptor.
For the full walkthrough, see Editor Mode and Adding a Device.
| You want to... | Look in... |
|---|---|
| Add a new HID++ feature |
src/core/hidpp/features/ then a capability-table entry in src/core/hidpp/capabilities/
|
| Add a new device |
devices/<slug>/ at the repo root: see Adding a Device
|
| Edit a wiki page |
docs/wiki/*.md in this repo. Wiki is one-way synced from master; edits on the GitHub wiki itself are overwritten on next sync. |
| Add a new desktop environment |
src/core/desktop/ — see Adding a Desktop Environment
|
| Add a new button action type |
src/core/ButtonAction.h and src/app/services/ButtonActionDispatcher.cpp (onDivertedButtonPressed) |
| Add a new QML page |
src/app/qml/pages/ and register in src/app/CMakeLists.txt
|
| Add a new QML component |
src/app/qml/components/ and register in src/app/CMakeLists.txt
|
| Add a new model |
src/app/models/ — create class, register in main.cpp as QML singleton |
| Add a new test |
tests/test_*.cpp and add to tests/CMakeLists.txt
|
| Add a new QML test |
tests/qml/tst_*.qml and add to tests/qml/CMakeLists.txt
|
| Change the protocol layer |
src/core/hidpp/ — Transport, FeatureDispatcher, CommandProcessor |
| Change signal wiring |
src/app/AppRoot.cpp — wireSignals() method |
| Change the UI layout |
src/app/qml/Main.qml (sidebar + page switcher) |
| Debug device communication | Run with --debug, check lcHidpp and lcDevice log categories |
These are intentional choices — please don't "fix" them:
-
No daemon: Logitune runs as a user application, not a system service. Profile switching happens in-process.
-
Direct hidraw: No libhidapi, no libusb. Direct
open()/read()/write()on/dev/hidrawNwithQSocketNotifierfor async I/O. -
CommandProcessor for pacing: All hardware writes go through a 10ms-paced queue. This prevents HwError from command flooding. Do not bypass the queue.
-
Display vs hardware profile: The UI can show a different profile than what's running on hardware. This prevents accidental hardware writes when browsing profiles.
-
softwareId for response matching: HID++ responses use rotating softwareId (1-15) to distinguish from notifications. Without this, async responses get misinterpreted as input events.
-
Friend classes for test access:
AppRootFixtureandtest::AppRootFixtureare friends ofAppRootandDeviceManager. This is intentional — it enables behavioral tests without adding test-only public methods. -
Value members, not heap: AppRoot owns its subsystems as value members (not pointers). DeviceRegistry, DeviceManager, ProfileEngine, models — they are all stack-allocated inside AppRoot. Only desktop integration and input injection use pointer indirection (for DI).
-
KWin script, not polling: On KDE, focus tracking uses a KWin script that calls back via D-Bus, not polling. The poll timer is only a fallback that installs the script on first tick, then stops.
For more details, see Architecture.