Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 30 additions & 10 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,33 @@ assignees: ""

## What happened

<!-- What did you do, what did you expect, and what happened instead? -->
<!-- What did you do, what did you expect, and what happened instead? Say which mouse it
happened on if you use more than one. -->

## Environment

- Upright version (Settings → About, or the DMG name):
- How you installed it (built from source, DMG, Xcode ⌘R):
- macOS version (Apple menu → About This Mac):
- Mac (Apple silicon or Intel):
- Mouse connection (USB cable, Unifying receiver, Bluetooth):
- Mouse firmware (Settings → Device):
- Logi Options+, Logitech Options, Solaar or LogiOps installed or running:

## Your mice

<!-- Upright works with several Logitech mice at once, so say what is connected. Only the
MX Vertical has been tested on hardware; if your model is something else, that matters. -->

- Mouse model(s), as shown in Settings → Mice:
- Number of mice connected at the same time:
- How each one connects, and the receiver layout. For example: "MX Vertical on a Unifying
receiver (slot 1), MX Master 3S on a Bolt receiver, Lift over Bluetooth". Say how many
receivers you have, which mice share one, and whether a keyboard is paired to the same
receiver:
- Is the same mouse reachable in two ways at once (for example a cable and its receiver, or
Bluetooth and a receiver)? yes / no
- Mouse firmware (Settings → Mice → the mouse):
- The mouse the problem is about shows an "Untested model" badge: yes / no

## Permissions

- Accessibility shown as allowed in Settings → Permissions: yes / no
Expand All @@ -32,19 +47,24 @@ assignees: ""
2.
3.

## `make probe` output
## `make probe ARGS=--all` output

<!-- Run `make probe` (or `swift run --package-path UprightKit upright-probe`) with Upright
quit, then paste the output. Add `ARGS=--divert` if the problem is with button events.
If you can't run it, say why. -->
<!-- Quit Upright, then run `make probe ARGS=--all` (or
`swift run --package-path UprightKit upright-probe --all`) with all the affected mice
connected and awake, and paste the whole output. It lists every Logitech HID++ interface,
including ones Upright ignores, and every line starts with its mouse, for example
[MX Master 3S · Bolt slot 2]. Add `--divert` instead if the problem is about button events.
The output contains your mice's unit IDs and serial numbers; replace them with XXXX if you
prefer. If you can't run it, say why. -->

```
```

## Log from the Device pane
## Device report and log

<!-- Settings → Device → Activity. Turn on "Raw HID++ reports" if the problem is about what the
mouse sends, reproduce it, click Copy, and paste here. -->
<!-- Settings → Mice → select the mouse → "Copy Device Report" (leaves the unit ID and serial
number out). Then, under Activity, turn on "Raw HID++ reports" if the problem is about what
the mouse sends, reproduce it, click Copy, and paste both here. -->

```
```
Expand Down
18 changes: 13 additions & 5 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ labels: enhancement
assignees: ""
---

<!-- If you want Upright to support a mouse it doesn't know yet, use the "Request support for a
mouse" template instead. -->

## What do you want to do

<!-- Describe the task, not the solution: "I want to ..." -->
Expand All @@ -16,19 +19,24 @@ assignees: ""

## Idea

<!-- How could Upright do it? Where in the app would it live? -->
<!-- How could Upright do it? Where in the app would it live? Should it apply to every mouse,
one model, or one mouse? -->

## Hardware and environment

- Mouse model and connection (USB cable, Unifying receiver, Bluetooth):
- Mouse model(s) it is about:
- Number of mice you use at the same time:
- Connections and receiver layout (for example "MX Master 3S on a Bolt receiver, MX Vertical
over Bluetooth"):
- macOS version:
- If it needs a mouse feature: the output of `make probe` (the feature table shows what the
mouse supports):
- If it needs a mouse feature: the output of `make probe ARGS=--all` (the feature table
shows what the mouse supports; replace unit IDs and serial numbers with XXXX if you prefer):

```
```

## Scope check

- [ ] This is about the Logitech MX Vertical (other mice are out of scope)
- [ ] It is about Logitech mice that speak HID++ 2.0 and have programmable buttons (see
`docs/DEVICES.md`)
- [ ] It doesn't need a kernel extension, a driver or Logitech software
76 changes: 76 additions & 0 deletions .github/ISSUE_TEMPLATE/new_mouse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
name: Request support for a mouse
about: Your Logitech mouse isn't listed, shows up as unsupported, or you tried an untested model
title: "New mouse: "
labels: new mouse
assignees: ""
---

<!-- Upright decides what to serve by what a mouse answers: HID++ 2.0 with programmable buttons
(REPROG_CONTROLS_V4). Many models that aren't listed may already work, and only the MX Vertical
has been tested on hardware, so a report from a different mouse is valuable even if everything
works. See docs/DEVICES.md for the list and what each status means. -->

## The mouse

- Name and model number (on the box or the underside):
- Is it listed in `docs/DEVICES.md`? yes (status: ...) / no

## What Upright shows

<!-- Open Settings → Mice with the mouse connected and awake. Tick what you see. -->

- [ ] It is listed as a mouse, with its buttons in Settings → Buttons
- [ ] It is listed as a mouse with an "Untested model" badge
- [ ] It is under "Other Logitech devices", with this reason:
- [ ] It doesn't appear anywhere

## Connection and receivers

- Number of mice connected at the same time (of any model):
- How this mouse connects: USB cable / Bluetooth / Unifying receiver / Bolt receiver / Nano receiver
- Receiver layout: which receiver it is in, the slot number if you know it, the other mice and
keyboards sharing that receiver, and how many receivers there are. For example: "Bolt
receiver with this mouse and an MX Keys; a Unifying receiver with an MX Vertical":
- Is it also paired by Bluetooth or cable at the same time? yes / no
- macOS version:
- Mac (Apple silicon or Intel):
- Upright version:

## `make probe ARGS=--all` output

<!-- Required. Quit Upright, connect the mouse the way you use it (wake it by moving it), and run
`make probe ARGS=--all` (or `swift run --package-path UprightKit upright-probe --all`). Paste the
whole output; it lists every Logitech HID++ interface, what Upright does with each, the
mouse's identity, feature table and button table. If you can connect the mouse in more than one
way (receiver and Bluetooth), run it once for each and paste both, because the identity block
can show whether the unit ID is the same on both. Leave the mouse's unit ID and serial number
as they are or replace them with XXXX, but say whether the unit ID matched between runs. -->

```
```

## Device report

<!-- Optional. Settings → Mice → select the mouse → "Copy Device Report". -->

```
```

## Does it work?

<!-- Only if Upright serves the mouse. Set a mapping in Settings → Buttons, for example Forward →
Mission Control, and tick what holds. Try each connection you have. -->

- [ ] A remapped button does what I set
- [ ] The mapping stops when I turn off "Remap this mouse" or quit Upright
- [ ] The mouse reconnects after sleep or after turning it off and on
- [ ] Pointer speed, battery and Easy-Switch show up (if the mouse has them)
- [ ] Nothing changed for my other mice
- Anything that is wrong or strange:

## Helping out

- [ ] I can test builds or pull requests on this mouse
- [ ] I'd like to add the model myself (see "Adding a model from an `upright-probe --all` dump"
in CONTRIBUTING.md)
20 changes: 15 additions & 5 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,27 @@

- [ ] `make test` passes
- [ ] `make app` builds (needed if you touched `Upright/` or the Xcode project)
- [ ] New Swift files are added to `Upright.xcodeproj/project.pbxproj`
- [ ] Tried it with a real MX Vertical. Connection: USB cable / Unifying / Bluetooth
- [ ] New Swift files are added to `Upright.xcodeproj/project.pbxproj` exactly once (the check
in CONTRIBUTING.md prints nothing)
- [ ] `make snapshots` renders the changed screens (if you touched a view)
- [ ] Tried it on real hardware. Mouse model(s) and connection (USB cable / Unifying / Bolt / Nano
/ Bluetooth), and how many mice at once:
- [ ] Simulator only (no hardware). Which profiles or scenarios:
- macOS version:

<!-- For HID++ changes, paste relevant `make probe` output or Activity log lines. -->
<!-- For HID++ changes, paste relevant `make probe ARGS=--all` output or Activity log lines.
For a new model, the `--all` dump the entry is based on. -->

## Checklist

- [ ] Code outside `UprightKit/Sources/UprightKit/Platform/` uses Foundation only (it runs on Linux in CI)
- [ ] Tests cover the change, using the simulated mouse rather than real hardware
- [ ] New HID++ messages are documented in `docs/HIDPP.md`
- [ ] Tests cover the change, using the simulator (`SimulatedReceiver` / `SimulatedDirect` with
`DeviceProfiles`) rather than real hardware
- [ ] Every test method in a `@MainActor` test class is `async` (Linux CI fails otherwise)
- [ ] New HID++ messages are documented in `docs/HIDPP.md`, with whether they were tested on hardware
- [ ] Anything about a model other than the MX Vertical is marked as untested on hardware
(`.publishedDump`, `docs/DEVICES.md`) unless it was
- [ ] A catalog change keeps existing model keys, and `docs/DEVICES.md` matches `DeviceCatalog`
- [ ] User-facing behavior changes are reflected in `README.md` / `INSTALL.md`
- [ ] Added a line under **Unreleased** in `CHANGELOG.md`
- [ ] No saved-data format was broken (mappings and settings still load; old data is migrated)
86 changes: 84 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,50 @@ All notable changes to Upright are listed here. The format follows

## [Unreleased]

## [1.1.0] - 2026-10-14

### Added

- **Multi-mouse support.** Upright now works with Logitech HID++ 2.0 mice other than the MX
Vertical, and with several mice at once, over any mix of USB cable, Bluetooth and Unifying,
Bolt or Nano receivers, including several mice on one receiver. A mouse is served when it
answers HID++ 2.0 as a pointer and offers programmable buttons (`REPROG_CONTROLS_V4`), not
because of its name or product ID. Keyboards and other non-mice are never opened.
**Only the MX Vertical is verified on hardware.** Every other model, Bolt and Nano receivers,
and two real mice at once are verified in the simulator only.
- **Device catalog** with 23 models (MX Vertical, the MX Master, MX Anywhere, MX Ergo, Lift,
M720, M585/M590, Signature M650 L and M550, ERGO M575, Marathon M705, Pebble M350, M510 (v2)
and M185/M235/M310 families), built from published Solaar and libratbag data and marked
**published data, untested on hardware**. See [docs/DEVICES.md](docs/DEVICES.md), which is
derived from `DeviceCatalog.swift`.
- **Receivers:** Bolt (`C548`, Bolt pairing layout) and Nano receivers join Unifying. Lightspeed
receivers (G and PRO mice, which have no programmable-button feature) and the 27 MHz receiver
are never opened.
- **Mice pane** (replaces Device): connected mice, mice seen before ("Not Connected") and a
collapsed "Other Logitech devices" list with the reason a device isn't configured. Per mouse: a
nickname, **Remap this mouse**, its pointer speed, Easy-Switch, battery, device info and
**Copy Device Report** for issues.
- **Per-model mappings.** A **Configure** picker in Settings → Buttons chooses the model being
edited, including models that aren't connected. Each model has its own buttons and per-app
profiles; two mice of one model share them. "Copy setup from…" and "Use this setup for all my
mice" are available.
- **Menu-bar panel with several mice:** one row per mouse with its connection and battery,
"Reconnect All Mice", and button, pointer-speed and Easy-Switch sections for the mouse last
used. The battery percentage next to the icon shows the lowest battery, and low-battery
notifications name the mouse.
- **A mouse seen over two links** (for example receiver and cable) is recognized by its unit ID
and used once, the other link kept on standby.
- **Demo Mode:** `Upright --demo` runs the real interface against simulated mice (an MX
Vertical, an MX Master 3S and a Lift) with its own preferences, no event taps and no
permission prompts. `Upright --demo --export-snapshots <dir>` (and `make snapshots`) renders
the interface offscreen into light and dark PNGs plus a `manifest.json`; the screenshots in
`media/snapshots/` and the README come from it.
- **`upright-probe`:** `--all` also reads interfaces Upright doesn't use (read-only), output lines
carry a per-mouse label such as `[MX Master 3S · Bolt slot 2]`, and `--divert` can be limited to
mice whose label contains some text.
- The mouse simulator (`SimulatedReceiver`, `SimulatedDirect`, `DeviceProfiles`) now lives in
UprightKit, so Demo Mode and the tests share it.

- **Per-app profiles.** A global mapping table plus per-app overrides, chosen by the frontmost
app's bundle ID. A button without an override uses the global action; an override can also
give a button back to the mouse in that app.
Expand All @@ -23,8 +65,8 @@ All notable changes to Upright are listed here. The format follows
- **Battery:** approximate levels for mice that report only a coarse level, low-battery
notifications at 15% and 5%, and an optional percentage next to the menu-bar icon.
- **Scrolling pane:** reverse mouse-wheel direction independently of the trackpad (wheel
events only), a speed multiplier, an "only while the MX Vertical is connected" option and
experimental smooth scrolling. All off by default.
events only), a speed multiplier, an "only while a Logitech mouse is connected" option and
experimental smooth scrolling. All off by default. They apply to every mouse wheel.
- **Export and import** of mappings and profiles as JSON, with a schema version.
- **First-run onboarding** for new installs.
- **Reset Permission** button (Settings → Permissions and onboarding) that clears a stale
Expand All @@ -37,6 +79,20 @@ All notable changes to Upright are listed here. The format follows

### Changed

- **Mappings are saved per model** in a new format (schema v3), and a mouse model without a
saved setup keeps every button native. See "Migration and downgrade notes" below.
- Upright is described as remapping Logitech mice over HID++, built and tested on the MX
Vertical. Documentation, the About pane and the permission texts no longer say it only
supports the MX Vertical.
- **Settings → Device is now Settings → Mice.**
- **Scrolling:** the option is now "Only while a Logitech mouse is connected" (any supported
mouse, not just the MX Vertical). The saved setting keeps its name, so nothing is reset. Scroll
settings remain global to every mouse wheel, because macOS doesn't say which mouse a scroll
came from.
- Bluetooth matching now includes the mouse interface of any Logitech mouse, which is where
Bluetooth mice carry HID++. Interfaces Upright can't use are never opened.
- Pointer-speed controls and actions are shown only for mice that offer `ADJUSTABLE_DPI`
(`0x2201`); Upright uses no other speed feature.
- **Fresh installs divert no buttons.** Back, Forward and the Top button keep their native
behavior. Users upgrading from 1.0 keep their previous mappings (including the old defaults
if they never changed them).
Expand All @@ -59,6 +115,32 @@ All notable changes to Upright are listed here. The format follows
- Documentation no longer says the app uses system materials, and now states that the MX
Vertical pairs over Unifying or Bluetooth only (not Bolt).

### Migration and downgrade notes

- **From 1.x, or from a build that saved the single-setup format (schema v2).** On first launch the
saved setup moves into the **MX Vertical's** setup in the new schema v3 library
(`mappingProfiles.v3`), because only an MX Vertical could have produced it. Other models start
with every button native. Upgrading without saved data keeps the old defaults on the MX
Vertical, and a fresh install remaps nothing.
- **Nothing is deleted.** The v2 data (`mappingProfiles.v2`) is copied once, unchanged, to
`mappingProfiles.v2.preV3Backup`, and the v2 key itself is never written or removed again.
The 1.x key is not touched either. Data that can't be read is backed up under
`mappingProfiles.corruptBackup.*` instead of being reset.
- **Pointer speed.** The single saved pointer speed is copied once to the MX Vertical's
per-mouse preferences, and the old key is left in place.
- **Going back to an older build.** An older build reads the frozen v2 data, which is your MX
Vertical setup **as it was when you upgraded**. It does not see anything you changed in the new
version, and nothing you change in the older build reaches the new one: once a v3 library
exists, it always wins. Setups for other models are only in the v3 library and are ignored by
older builds.
- **Export and import.** Exported files now hold every model's setup. They also carry the MX
Vertical's setup as top-level `global` and `apps` so an older build can still import them.
Importing a file from an older build asks which model it should go to, shows what it will
replace, and backs up your current setup first so the import can be undone. Importing a v3 file
replaces only the models the file contains.
- **Per-mouse data** (nickname, remap switch, pointer speed, last seen) stays on this Mac and is
never exported.

## [1.0.0]

Initial release.
Expand Down
Loading
Loading