openlogi_core/binding/button.rs
1//! Rebindable mouse/keyboard button identifiers.
2
3use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7/// One of the user-rebindable hotspots on a Logi mouse. The order matches the
8/// physical layout from front to side; [`ButtonId::ALL`] is consumed by the
9/// default-binding generator and the popover trigger list.
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
11pub enum ButtonId {
12 /// The primary button. Rebindable in the config schema, but the OS hook
13 /// never suppresses it — see [`ButtonId::is_os_hook_button`].
14 LeftClick,
15 /// The secondary button. Like [`ButtonId::LeftClick`], it always passes
16 /// through the OS hook.
17 RightClick,
18 /// The wheel click — one of the three buttons the OS hook remaps.
19 MiddleClick,
20 /// The thumb-side "back" button (mouse button 4), remapped by the OS hook.
21 Back,
22 /// The thumb-side "forward" button (mouse button 5), remapped by the OS hook.
23 Forward,
24 /// The "ModeShift" button under the wheel — typically used for SmartShift /
25 /// DPI cycle. Named `DpiToggle` for historical reasons.
26 DpiToggle,
27 /// The horizontal thumb wheel's click. Kept in [`ButtonId::ALL`] so its
28 /// default still seeds and dispatches when the wheel is diverted, even
29 /// though the mouse model surfaces one paired rotation control instead of
30 /// the click (see `mouse_model::geometry`).
31 Thumbwheel,
32 /// Rotating the thumb wheel "up" (positive rotation). Bound, by default, to
33 /// continuous horizontal scroll; see the agent-core `watchers`-side dispatch.
34 ThumbwheelScrollUp,
35 /// Rotating the thumb wheel "down" (negative rotation).
36 ThumbwheelScrollDown,
37 /// The HID++ gesture button on MX-line devices. The press itself
38 /// fires the bound action; swipe directions are P1.5 territory.
39 GestureButton,
40 /// Keyboard F-row "Search" control (`0x1b04` CID `0x00d4`,
41 /// `MultiPlatform_Search`) — F4 on the Signature series.
42 KeySearch,
43 /// Keyboard "Dictation" control (CID `0x0103`) — F5 on the Signature series.
44 KeyDictation,
45 /// Keyboard "Emoji" control (CID `0x0108`) — F6 on the Signature series.
46 KeyEmoji,
47 /// Keyboard "Screen Capture" control (CID `0x010a`) — F7 on the Signature
48 /// series.
49 KeyScreenCapture,
50 /// Keyboard "Mute Microphone" control (CID `0x011c`) — F8 on the Signature
51 /// series.
52 KeyMicMute,
53 /// Keyboard "Play/Pause" control (CID `0x00e5`) — F9 on the Signature series.
54 KeyPlayPause,
55 /// Keyboard "Mute" control (CID `0x00e7`) — F10 on the Signature series.
56 KeyMute,
57 /// Keyboard "Volume Down" control (CID `0x00e8`) — F11 on the Signature
58 /// series.
59 KeyVolumeDown,
60 /// Keyboard "Volume Up" control (CID `0x00e9`) — F12 on the Signature
61 /// series.
62 KeyVolumeUp,
63 /// The MX Master 4 Haptic Sense Panel — the touch-sensitive thumb rest
64 /// (Logi metadata slot `ASSIGNMENT_NAME_SHOW_RADIAL_MENU`, HID++ CID
65 /// `0x01a0`). A separate physical control from [`ButtonId::GestureButton`];
66 /// captured over HID++ like it, and eligible as the gesture owner.
67 HapticPanel,
68 /// Tilting the main wheel left — `0x1b04` CID `0x005b` ("Left Scroll"),
69 /// Logi metadata slot `SLOT_NAME_LEFT_SCROLL_BUTTON`. A distinct control
70 /// from the thumb wheel: it is a plain divertable button, not a rotation,
71 /// and it lives on the main wheel of mice like the MX Anywhere 2S.
72 WheelTiltLeft,
73 /// Tilting the main wheel right — `0x1b04` CID `0x005d` ("Right Scroll"),
74 /// Logi metadata slot `SLOT_NAME_RIGHT_SCROLL_BUTTON`. Counterpart to
75 /// [`ButtonId::WheelTiltLeft`].
76 ///
77 /// Declared last: the TOML config and any serialized form encode the
78 /// variant identifier / index, so new buttons are append-only.
79 WheelTiltRight,
80}
81
82impl ButtonId {
83 /// Every rebindable button in declaration (physical front-to-side) order —
84 /// the iteration source for default-binding seeding and the popover
85 /// trigger list.
86 pub const ALL: [ButtonId; 13] = [
87 ButtonId::LeftClick,
88 ButtonId::RightClick,
89 ButtonId::MiddleClick,
90 ButtonId::WheelTiltLeft,
91 ButtonId::WheelTiltRight,
92 ButtonId::Back,
93 ButtonId::Forward,
94 ButtonId::DpiToggle,
95 ButtonId::Thumbwheel,
96 ButtonId::ThumbwheelScrollUp,
97 ButtonId::ThumbwheelScrollDown,
98 ButtonId::GestureButton,
99 ButtonId::HapticPanel,
100 ];
101
102 /// The divertable keyboard F-row controls, in F-row order. Kept out of
103 /// [`ButtonId::ALL`]: that array seeds mouse defaults and the mouse
104 /// popover trigger list, while keyboard keys stay native unless the user
105 /// binds them (an unbound key is never diverted).
106 pub const KEYBOARD_KEYS: [ButtonId; 9] = [
107 ButtonId::KeySearch,
108 ButtonId::KeyDictation,
109 ButtonId::KeyEmoji,
110 ButtonId::KeyScreenCapture,
111 ButtonId::KeyMicMute,
112 ButtonId::KeyPlayPause,
113 ButtonId::KeyMute,
114 ButtonId::KeyVolumeDown,
115 ButtonId::KeyVolumeUp,
116 ];
117
118 /// Whether this button is one the OS hook (macOS `CGEventTap` / Linux evdev)
119 /// remaps: Middle, Back, or Forward. The primary L/R clicks always pass
120 /// through (suppressing them would brick the mouse), and the DPI / thumb /
121 /// dedicated gesture controls aren't visible to the OS hook at all (they're
122 /// captured over HID++). These are exactly the buttons that can become an
123 /// OS-hook gesture button, so the hook's remap gate and the gesture-owner
124 /// projection share this one definition.
125 #[must_use]
126 pub fn is_os_hook_button(self) -> bool {
127 matches!(
128 self,
129 ButtonId::MiddleClick | ButtonId::Back | ButtonId::Forward
130 )
131 }
132
133 /// Whether this button is a HID++ gesture source — a control that is
134 /// captured over HID++ raw-XY diversion (never the OS hook) and can
135 /// therefore own the gesture role with swipe directions: the dedicated
136 /// gesture button, or the MX Master 4 haptic panel. The capture layer maps
137 /// each to its control ID.
138 #[must_use]
139 pub fn is_hidpp_gesture_source(self) -> bool {
140 matches!(self, ButtonId::GestureButton | ButtonId::HapticPanel)
141 }
142
143 /// Human-readable label for popovers and tooltips.
144 #[must_use]
145 pub fn label(self) -> &'static str {
146 match self {
147 ButtonId::LeftClick => "Left Click",
148 ButtonId::RightClick => "Right Click",
149 ButtonId::MiddleClick => "Middle Click",
150 ButtonId::WheelTiltLeft => "Tilt Left",
151 ButtonId::WheelTiltRight => "Tilt Right",
152 ButtonId::Back => "Back",
153 ButtonId::Forward => "Forward",
154 ButtonId::DpiToggle => "DPI Toggle",
155 ButtonId::Thumbwheel => "Thumb Wheel",
156 ButtonId::ThumbwheelScrollUp => "Thumb Wheel Up",
157 ButtonId::ThumbwheelScrollDown => "Thumb Wheel Down",
158 ButtonId::GestureButton => "Gesture Button",
159 ButtonId::KeySearch => "Search Key",
160 ButtonId::KeyDictation => "Dictation Key",
161 ButtonId::KeyEmoji => "Emoji Key",
162 ButtonId::KeyScreenCapture => "Screen Capture Key",
163 ButtonId::KeyMicMute => "Mic Mute Key",
164 ButtonId::KeyPlayPause => "Play/Pause Key",
165 ButtonId::KeyMute => "Mute Key",
166 ButtonId::KeyVolumeDown => "Volume Down Key",
167 ButtonId::KeyVolumeUp => "Volume Up Key",
168 ButtonId::HapticPanel => "Haptic Panel",
169 }
170 }
171}
172
173impl fmt::Display for ButtonId {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 f.write_str(self.label())
176 }
177}