Metadata-Version: 2.4
Name: mx4-haptic
Version: 0.1.2
Summary: Python mouse events and HID++ haptics for the Logitech MX Master 4 on macOS
License-Expression: MIT
Project-URL: Homepage, https://github.com/paveleu/mx4-haptic
Project-URL: Repository, https://github.com/paveleu/mx4-haptic
Project-URL: Issues, https://github.com/paveleu/mx4-haptic/issues
Keywords: logitech,mx-master-4,mouse,haptics,hidpp,macos
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: MacOS X
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: System :: Hardware :: Hardware Drivers
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pynput<2,>=1.7.6
Requires-Dist: hidapi<1,>=0.15; sys_platform == "darwin"
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: ruff>=0.9; extra == "dev"
Requires-Dist: twine>=6; extra == "dev"
Dynamic: license-file

# mx4-haptic

Python mouse events and direct HID++ haptic control for the Logitech MX Master 4 on macOS. It does not use Logi Options+.

## Install

```bash
python -m pip install mx4-haptic
```

Python 3.10 or newer is required. The package installs `pynput` for mouse events and `hidapi` on macOS for haptics.

## Mouse buttons and scroll

`MouseEvents` reports the mouse events macOS makes available to the process. Events are global, and the library does not suppress them by default.

```python
from mx4_haptic import MXMaster4


def on_button(event):
    print(event.phase, event.button, event.x, event.y)


def on_scroll(event):
    print(event.axis, event.dx, event.dy)


with MXMaster4(on_button=on_button, on_scroll=on_scroll) as mx4:
    mx4.join()  # Run until the listener is stopped or the process exits.
```

`event.phase` is `"down"` or `"up"`. `event.button` uses `left`, `right`, and `middle` for standard buttons. macOS button numbers 3 and 4 map to `x1` and `x2`; higher numbers use `button_<number>`. Scroll events carry raw `dx` and `dy` deltas; `event.axis` is `horizontal`, `vertical`, `both`, or `none`. macOS does not identify which physical wheel produced a delta, and Natural Scrolling can affect its sign.

The listener needs macOS Input Monitoring or Accessibility permission. These are OS-level events: a hardware control is available only if macOS exposes it as a mouse event. Raw HID++ button diversion is not included yet.

To listen without writing code:

```bash
mx4-haptic listen
```

## Haptics

Haptic requests go directly to the mouse's HID++ feature `0x19B0`. The current implementation supports a directly connected MX Master 4 over Bluetooth. It discovers waveform support and refuses to play an ID the device does not advertise.

```python
from mx4_haptic import HapticController, Waveform


with HapticController() as haptics:
    print(haptics.get_state())
    print(haptics.get_supported_waveforms())
    haptics.play(Waveform.KNOCK)
    haptics.set_level(45)  # Writes the intensity to the mouse.
```

Playback does not change the mouse configuration. `set_level()` is an explicit, persistent device setting change and preserves the other configuration flags.

```bash
mx4-haptic haptics status
mx4-haptic haptics play KNOCK
mx4-haptic haptics level 45
```

## Development

```bash
python -m venv .venv
source .venv/bin/activate
python -m pip install -e '.[dev]'
ruff check src tests
pytest
python -m build
python -m twine check dist/*
```

The tests do not require a mouse. Physical event delivery and haptic feedback require an MX Master 4 and macOS permissions.

## Release and PyPI publishing

GitHub Actions runs lint, hardware-free tests, and package build checks. Publishing runs only when a GitHub Release is published. It uses PyPI Trusted Publishing (OIDC); no long-lived PyPI API token is stored in GitHub.

Before the first release, add a pending Trusted Publisher in your PyPI account. PyPI can create the project automatically on the first successful publish; a pending publisher does not reserve the name until then. Follow the [PyPI setup guide](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/) and enter:

- Project name: `mx4-haptic`
- Owner: `paveleu`
- Repository: `mx4-haptic`
- Workflow: `publish.yml`
- Environment: `pypi`

Then update the version in `pyproject.toml` and `src/mx4_haptic/__init__.py`, push the commit, and publish a GitHub Release for the matching tag, for example `v0.1.2`. The release workflow builds and checks the package, then publishes the built files to PyPI.

## License

MIT. See [LICENSE](LICENSE).
