-
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
Tip
This copies scripts/pre-push into .git/hooks/. It runs all three test tiers (C++, tray, QML) before allowing a push.
# 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
| Rule | Convention |
|---|---|
| π Standard | C++20 |
| π·οΈ Variables/methods | camelCase |
| π·οΈ Types/classes | PascalCase |
| π·οΈ Members |
m_ prefix |
| π·οΈ Constants |
k prefix |
| π¦ Includes | Group by standard library, Qt, project headers; separated by blank lines |
| π·οΈ Namespaces |
logitune for production, logitune::test for tests, logitune::hidpp for protocol |
| π Strings | Use QStringLiteral() for string literals |
| π Signals |
connect() with member function pointers (not string-based) |
| π« No over-engineering | Don't add interfaces, factories, or patterns unless they solve a concrete problem |
Use std::optional for fallible operations, qCWarning/qCDebug for logging, no exceptions in normal flow |
- Follow Qt Quick coding conventions
- All colors, fonts, and spacing come from the
Themesingleton β never hardcode values -
camelCasefor properties and functions,PascalCasefor 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: Flatpak packaging, 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 queue, 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 queue:
- New CommandQueue 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)
Important
All PRs must pass the CI pipeline before merging. The pre-push hook helps catch failures early.
| You want to... | Look in... |
|---|---|
| π Add a new HID++ feature |
src/core/hidpp/features/ β see HID++ Protocol
|
| π±οΈ Add a new device |
src/core/devices/ β see Adding a Device
|
| π₯οΈ 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/AppController.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, CommandQueue |
| π Change signal wiring |
src/app/AppController.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 |
Caution
These are intentional choices β please don't "fix" them:
| # | Decision | Rationale |
|---|---|---|
| 1 | No daemon | Logitune runs as a user application, not a system service. Profile switching happens in-process. |
| 2 | Direct hidraw | No libhidapi, no libusb. Direct open()/read()/write() on /dev/hidrawN with QSocketNotifier for async I/O. |
| 3 | CommandQueue for pacing | All hardware writes go through a 10ms-paced queue. This prevents HwError from command flooding. Do not bypass the queue. |
| 4 | 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. |
| 5 | softwareId for response matching | HID++ responses use rotating softwareId (1-15) to distinguish from notifications. Without this, async responses get misinterpreted as input events. |
| 6 | Friend classes for test access |
AppControllerFixture and test::AppControllerFixture are friends of AppController and DeviceManager. This enables behavioral tests without adding test-only public methods. |
| 7 | Value members, not heap | AppController owns its subsystems as value members (not pointers). DeviceRegistry, DeviceManager, ProfileEngine, models β they are all stack-allocated inside AppController. Only desktop integration and input injection use pointer indirection (for DI). |
| 8 | 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. |
Tip
For more details on these decisions, see Architecture.