Skip to content

Navigation Menu

Sign in
Sign up

Contributing

Mina Maher edited this page Apr 1, 2026 · 6 revisions

🀝 Contributing

πŸš€ Getting Set Up

🍴 Fork and Clone

# Fork the repo on GitHub, then:
git clone https://github.com/<your-username>/logitune.git
cd logitune

πŸ’» Development Environment

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

πŸͺ Pre-Push Hook

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.

🌿 Branch Workflow

# 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

🎨 Code Style

πŸ’» C++

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
⚠️ Error handling Use std::optional for fallible operations, qCWarning/qCDebug for logging, no exceptions in normal flow

🎨 QML

  • Follow Qt Quick coding conventions
  • All colors, fonts, and spacing come from the Theme singleton β€” never hardcode values
  • camelCase for properties and functions, PascalCase for component files

πŸ“„ Header 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

πŸ“ Commit Message Format

Use Conventional Commits:

<type>: <description>
[optional body]
[optional footer]

🏷️ Types

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

πŸ“‹ Examples

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

πŸ“ Multi-topic Commits

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

βœ… PR Checklist

Before opening a pull request, verify:

  • make test-all passes 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.

πŸ—ΊοΈ Where to Find Things

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

πŸ›οΈ Key Design Decisions

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.


Logitune Wiki


🏠 Home

πŸ“š User Guide

πŸ—οΈ Architecture

πŸ”§ Extending

πŸ§ͺ Quality

Clone this wiki locally

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /