Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ecb6e14
docs: phase 4 actions ring design spec
ChristopherLandaverde Apr 26, 2026
a882603
docs: phase 4 actions ring implementation plan
ChristopherLandaverde Apr 26, 2026
090df48
build: add PyQt6 [ring] extra and pytest-qt dev dep for phase 4
ChristopherLandaverde Apr 27, 2026
35ee931
test: add requires_display marker hook for X11-dependent tests
ChristopherLandaverde Apr 27, 2026
877d4e3
feat(config): add Target dataclass and parse_target_string
ChristopherLandaverde Apr 27, 2026
054d686
feat(config): replace Binding.action with Binding.target; shim legacy…
ChristopherLandaverde Apr 27, 2026
699d387
feat(device): emit key-up events with InputEvent.pressed flag
ChristopherLandaverde Apr 27, 2026
ade33e9
feat(overlay): wedge_index for ring hit-test
ChristopherLandaverde Apr 27, 2026
31f19a5
feat(config): add Ring/Segment dataclasses and [rings.X] parser
ChristopherLandaverde Apr 27, 2026
c1b98ba
feat(config): validate ring-target bindings reference an existing ring
ChristopherLandaverde Apr 27, 2026
53ea318
feat(config): validate ring segment count, labels, and action references
ChristopherLandaverde Apr 27, 2026
b5ba3db
feat(cli): check-config errors when ring bindings present but PyQt6 m…
ChristopherLandaverde Apr 27, 2026
d348811
feat(overlay): is_in_dead_zone hit-test
ChristopherLandaverde Apr 27, 2026
c5367f9
feat(overlay): shifted_center_for_screen keeps ring on-screen without…
ChristopherLandaverde Apr 27, 2026
e9521a5
feat(overlay): RingController state machine with re-entrant open + sa…
ChristopherLandaverde Apr 27, 2026
d21d99f
feat(overlay): RingWidget renders transparent always-on-top ring with…
ChristopherLandaverde Apr 27, 2026
b23abb7
refactor(overlay): hoist math import out of paintEvent loop
ChristopherLandaverde Apr 27, 2026
9ca6557
feat(overlay): 75ms fade-in animation on ring open
ChristopherLandaverde Apr 27, 2026
e17ba3b
feat(overlay): CursorPoller — 8ms QTimer over QCursor.pos with no-op …
ChristopherLandaverde Apr 27, 2026
771a53c
feat(overlay): RingController starts/stops CursorPoller around open/c…
ChristopherLandaverde Apr 27, 2026
f49d72b
feat(cli): split listener into command-only and Qt-driven paths; pure…
ChristopherLandaverde Apr 27, 2026
7361fad
refactor(cli): drop unused InputEvent import; simplify result handlin…
ChristopherLandaverde Apr 27, 2026
b1ad6c6
test: end-to-end smoke for Qt listener path with mocked evdev
ChristopherLandaverde Apr 27, 2026
63719a6
docs: example config showcasing a 4-segment ring on BTN_TASK
ChristopherLandaverde Apr 27, 2026
17faa8d
docs: README covers rings, polymorphic targets, and [ring] extra
ChristopherLandaverde Apr 27, 2026
36f27f8
docs: PRD marks radial overlay as shipped
ChristopherLandaverde Apr 27, 2026
37ccb7f
ci: install xvfb + Qt libs and run pytest under xvfb-run for widget t…
ChristopherLandaverde Apr 27, 2026
8bdcb77
fix(cli): force QueuedConnection + main-thread bridge for ring dispatch
ChristopherLandaverde Apr 27, 2026
a2c7ec5
fix(overlay): disable WA_TranslucentBackground for cross-compositor v…
ChristopherLandaverde Apr 27, 2026
051facd
chore: add scripts/dump-keys.py for evdev button mapping; ignore venv/
ChristopherLandaverde Apr 27, 2026
d8e8f23
docs(example): warn about BTN_TASK + gnome-screenshot portability
ChristopherLandaverde Apr 27, 2026
993503a
feat(overlay): LOGITECHMOUSE_THEME env var with dark + brazil palettes
ChristopherLandaverde Apr 27, 2026
ab60e8a
fix(overlay): mask widget to a circle so the rectangle corners stop r…
ChristopherLandaverde Apr 27, 2026
159df70
ci: install libxcb-cursor0 so PyQt6 6.11 can init under xvfb
ChristopherLandaverde Apr 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install
- name: Install xvfb and Qt runtime libs
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
xvfb libgl1 libegl1 libxkbcommon-x11-0 libdbus-1-3 \
libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 \
libxcb-randr0 libxcb-render-util0 libxcb-shape0 \
libxcb-xinerama0 libxcb-xkb1
- name: Install package
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
pip install -e ".[dev,ring]"
- name: Run pytest
run: pytest
run: xvfb-run -a pytest -q
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.venv/
venv/
*.egg-info/
__pycache__/
.pytest_cache/
Expand Down
63 changes: 59 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ Start with the useful behavior first:

## Project status

Phase 2 MVP: the CLI listens on a real Logitech MX device via `evdev` and
fires shell-command actions on configured button presses. No device grabbing,
no overlay, no profiles yet — those are scheduled for later phases.
Phase 4: the radial Actions Ring is implemented. The CLI listens on a real
Logitech MX device via `evdev`, and configured buttons can either fire a
single action on press or open a radial overlay where the released segment
fires the action. X11 only in v1; Wayland support is a separate phase.

## Planned features

Expand All @@ -30,7 +31,7 @@ no overlay, no profiles yet — those are scheduled for later phases.
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pip install -e ".[dev,ring]"
pytest
logitechmouse --help
```
Expand Down Expand Up @@ -65,6 +66,60 @@ See `examples/config.toml` for a working sample. Default examples bind the
gesture button (`BTN_TASK`) because it has no OS-default action — `BTN_SIDE`
and `BTN_EXTRA` will double-fire with browser back/forward in this MVP.

### Rings

A `[rings.NAME]` table defines a radial overlay with 3-12 segments. Each
segment names an existing `[actions.X]` and a label. To open the ring on a
button, set the binding's target to `ring:NAME`:

```toml
[rings.thumb_ring]
segments = [
{ action = "screenshot_area", label = "Area" },
{ action = "screenshot_full", label = "Full" },
{ action = "lock", label = "Lock" },
]

[bindings.gesture_button]
trigger = "BTN_TASK"
target = "ring:thumb_ring"
```

The ring opens on key-down at the cursor position, follows your cursor as you
hold the button, and fires the highlighted segment when you release. Releasing
in the center cancels.

### Targets vs legacy `action = "..."`

Bindings use `target = "kind:name"`:
- `target = "action:screenshot"` - fire `actions.screenshot` on press.
- `target = "ring:thumb_ring"` - open `rings.thumb_ring` on press, fire on release.

The Phase 2 form `action = "screenshot"` is still accepted; the loader maps it
to `target = "action:screenshot"` and logs a one-line migration note.

### Optional install for ring support

The radial ring needs PyQt6. Install with:

```bash
pip install 'logitechmouse[ring]'
```

Without `[ring]` you can still use action-only bindings; configs that define
ring bindings will fail validation with a clear message.

### Themes

The ring ships with a dark default. A second palette is available via env var:

```bash
LOGITECHMOUSE_THEME=brazil logitechmouse listen
```

Themes today: `dark` (default), `brazil` (green/yellow/blue, bandeira do Brasil).
Full theming via config is a polish phase; the env var is the v1 escape hatch.

## Documents

- [Product Requirements](docs/PRD.md)
Expand Down
3 changes: 2 additions & 1 deletion docs/PRD.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ A Linux laptop or desktop user with a Logitech MX mouse who wants one-press prod
- Bind MX mouse buttons to user-defined actions.
- Support command execution and keyboard shortcut emission.
- Make screenshots a first-class built-in workflow.
- Provide a path toward an optional radial overlay.
- Provide an optional radial overlay (Phase 4 — shipped).

## Non-goals

Expand All @@ -36,4 +36,5 @@ The MVP should:
- a user can press one MX button and capture a screenshot
- configuration changes do not require code edits
- the system is modular enough to add overlay support later
- pressing and holding a configured button opens a ring; releasing on a segment fires its action.

Loading
Loading