[Bug]: Actions Ring — `post_key` events are never resolved by the receiving app (beep); `post_unicode` from the same slot works
- Dominant language
- Rust
- Stars
- 21k
- Forks
- 675
- Avg merge
- 2d 5h
- Merged PRs (30d)
- 172
Description
### Pre-flight checklist
- [x] I searched [existing issues](https://github.com/AprilNEA/OpenLogi/issues?q=is%3Aissue) and this is not a duplicate.
- [x] I am on the [latest release](https://github.com/AprilNEA/OpenLogi/releases/latest) or a recent `master` build.
- [x] I quit **Logi Options+** before running OpenLogi (the two apps fight over HID++ access and only one can own a receiver at a time).
### Which part of OpenLogi?
Both
### OpenLogi version
0.7.3 also reproduced on 0.7.2
### Operating system
macOS
### OS version & architecture
macOS 26.x, Apple silicon
### Device model
MX Master 4 + Logi Bolt receiver
### How is the device connected?
Logi Bolt receiver
### Affected area(s)
- [ ] Device discovery / detection
- [x] Button remapping
- [ ] DPI control
- [ ] SmartShift
- [ ] Per-application profiles
- [ ] Battery status
- [ ] Settings / configuration (TOML)
- [ ] Auto-update
- [ ] Menu bar / tray
- [x] Other
### What happened?
Actions Ring slots whose action is synthesised by **`post_key`** never take
effect: the frontmost app emits the macOS unhandled-key-equivalent beep and
nothing happens. Every other injection mechanism works from the very same ring
slot, and **`post_key` itself works when the same action is bound to a mouse
button**.
| From the ring | Mechanism in `inject/macos.rs` | Result |
| --- | --- | --- |
| `Cut` / `Copy` / `Paste` | `post_key` — keycode + `CGEventFlags` | ❌ beep, no effect |
| `CustomShortcut = "A"` | `post_key` — keycode, **no modifiers** | ❌ beep, nothing typed |
| `TypeText = "hello"` | `post_unicode` — keycode 0 + `CGEventKeyboardSetUnicodeString` | ✅ types |
| `PlayPause` | `post_media_key` — NX system-defined event | ✅ (launches Music) |
| `RunShellCommand` | process spawn, no event | ✅ file created |
| `MissionControl` | `CoreDockSendNotification` | ✅ |
All six work when bound to a mouse button.
The decisive pair is rows 2 and 3. `kVK_ANSI_A` **is keycode 0**, and
`post_unicode` also posts keycode 0. Two events, same process, same dispatch
path, same keycode, no modifiers on either — one types, one beeps. The only
difference is that `post_unicode` attaches a unicode payload and `post_key`
relies on the system resolving keycode → character.
So this is not about modifier flags (a bare key fails identically), not about
CGEvent posting being unavailable on that thread (two other CGEvent varieties
succeed from it), and not about the action failing to dispatch (see the log).
## Where I think it comes from
`Agent::action_ring_activate` (`crates/openlogi-agent/src/server.rs`) hands the
action to the same `ActionDispatcher` the button paths use, and `dispatch_action`
(`crates/openlogi-agent-core/src/hook_runtime.rs`) forwards it to
`openlogi_inject::execute`. The code is identical for both sources.
What differs is the calling context: a button press executes on the
**CGEventTap callback thread**, a ring activation inside the **tarpc RPC
handler** on a tokio worker. Everything that survives that move carries its own
payload or bypasses the keyboard entirely — `post_unicode` (unicode string),
`post_media_key` (NX system-defined), `CoreDockSendNotification`, a process
spawn. The one mechanism that depends on the system translating a virtual
keycode is the one that fails.
If that is the cause, attaching a unicode string to `post_key`'s events, or
posting from a context with the right text-input state, would likely fix it.
## Second issue
`ERROR gpui::window: RefCell already borrowed` is logged by the overlay on
essentially **every** ring open — every open in the runs above, and 11 of 15 in
an earlier run. Reproducible on every launch; may deserve its own issue.
Happy to run further tests, capture full logs, or try a patched build.
### Steps to reproduce
## Steps to reproduce
1. MX Master 4 on a Bolt receiver, macOS. `HapticPanel` in gesture mode with `Click = "ShowActionsRing"` (default).
2. Config the ring with two slots:
```toml
[devices."".action_ring.default.slots]
Top = { action = "Cut" }
Right = { action = { CustomShortcut = "A" } }
```
3. TextEdit: type `test`, select it.
4. Press the Haptic Sense Panel, **left-click** the `Cut` slot → beep, nothing cut.
5. Click in the document, open the ring, left-click the `CustomShortcut` slot → beep, no `a`.
6. Bind either action to a mouse button and repeat → both work every time.
## Ruled out during investigation
- **Accessibility**: granted; `accessibility trust changed granted=true` and `OS input hook installed` in every failing run.
- **Focus**: the target app keeps key focus — `TypeText` from the same ring slot lands in the same document, and the selection survives the failed `Cut`.
- **Clipboard state**: `Paste` fails with the clipboard populated by hand.
- **Dispatch**: `Agent.action_ring_activate` is sent for every attempt (log below), and non-event actions from the ring succeed.
- **Third-party input software**: reproduced with Parallels Desktop, BetterDisplay and Hammerspoon all fully quit, and again with them running.
- **Stale processes**: exactly one `openlogi-agent` and one `openlogi-overlay`.
- **Selection method**: slots activated by left-click (pressing the trigger again dismisses).
### Diagnostics report
```markdown
### OpenLogi Diagnostics
**App**
- OpenLogi (GUI): v0.7.4 (release)
- Agent: v0.7.4 (connected)
- IPC protocol: GUI 23 / agent 23
- Inventory: ready
- OS: macOS 26.6.2 (arm64)
- Locale: en-CA (UI: en)
- Accessibility: granted · Input hook: installed
- Launch at login: yes · Menu bar: yes · Update check: on
- Running from: source build (dev)
- Config: schema 4 · 3 configured device(s) · thumbwheel 14
**Assets**
- Source: user cache · Index: loaded (210 models) · User cache: present
- Cache path: ~/.local/share/openlogi/assets · Bundle assets: absent
**Devices (3)**
- Wave Keys — keyboard (codename: Wave Keys 670)
- Connection: Logi Bolt receiver · Online: yes · Battery: 100% (discharging, full)
- Capabilities: buttons=yes, pointer=no, lighting=no
- Model: 3b383 (model-ids: b383/0000/0000, ext-model: 03)
- Transports: BTLE
- Render: wave_keys · Slot 1
- Logi M550 L — mouse
- Connection: Logi Bolt receiver · Online: no · Battery: n/a
- Capabilities: not probed
- Model: slot2
- Render: ⚠️ none (silhouette) · Slot 2
- MX Master 4 — mouse (codename: MX Master 4)
- Connection: Logi Bolt receiver · Online: yes · Battery: 100% (discharging, full)
- Capabilities: buttons=yes, pointer=yes, lighting=no
- Model: 2b042 (model-ids: b042/0000/0000, ext-model: 02)
- Transports: BTLE
- Render: mx_master_4 · Slot 3
**Receivers (1)**
- Logi Bolt Receiver (VID 046d / PID c548)
```
### `openlogi list` output
```text
Logi Bolt Receiver (3....2, vid=046d pid=c548)
├─ slot 1 ● Wave Keys 670 (keyboard, wpid=?, battery=100% full (discharging))
│ model_ids=[b383,0000,0000] ext=03 serial=— unit_id=d....9 transports=btle
├─ slot 2 ○ Logi M550 L (mouse, wpid=?, battery=—)
└─ slot 3 ● MX Master 4 (mouse, wpid=?, battery=100% full (discharging))
model_ids=[b042,0000,0000] ext=02 serial=2....8 unit_id=2....1 transports=btle
```
### Logs
```shell
## Log (0.7.3, `OPENLOGI_LOG=debug`, filtered)
Representative attempts; the activation RPC is sent every time and nothing
happens. No `warn!` from the injector on any attempt.
07:17:46.519 DEBUG watchers::accessibility: accessibility trust changed granted=true
07:17:46.519 INFO openlogi_agent: accessibility granted — installing OS mouse hook
07:17:46.528 INFO hook_runtime: OS input hook installed
07:18:12.974 DEBUG session::gesture: gesture click button=Haptic Panel
07:18:12.974 DEBUG watchers::gesture: gesture → action ... button=Haptic Panel direction=Click action=Actions Ring
07:18:13.025 ERROR gpui::window: RefCell already borrowed
07:18:13.505 DEBUG RPC{otel.name=Agent.action_ring_hover}: tarpc::client: SendRequest
07:18:14.685 DEBUG RPC{otel.name=Agent.action_ring_activate}: tarpc::client: SendRequest <- Cut, nothing cut
07:18:18.460 DEBUG session::gesture: gesture click button=Haptic Panel
07:18:20.741 DEBUG RPC{otel.name=Agent.action_ring_activate}: tarpc::client: SendRequest <- Cut, nothing cut
07:18:24.775 DEBUG session::gesture: gesture click button=Haptic Panel
07:18:25.963 DEBUG RPC{otel.name=Agent.action_ring_activate}: tarpc::client: SendRequest <- Cut, nothing cut
```
### macOS permissions (if applicable)
- [x] OpenLogi has **Accessibility** permission (needed to remap buttons via the event tap).
- [ ] OpenLogi has **Input Monitoring** permission (needed for Bluetooth-direct devices and capture).
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with inject/macos.rs, then trace Agent::action_ring_activate in crates/openlogi-agent/src/server.rs through dispatch_action in crates/openlogi-agent-core/src/hook_runtime.rs. Reproduce the ring configuration on macOS and compare post_key with post_unicode across the ring and mouse-button paths. Done means Cut and CustomShortcut actions from the ring take effect without breaking the other injection mechanisms.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- macos, rust
- Domain
- desktop, operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100