openlogi_core/binding/defaults.rs
1//! Default bindings for a fresh device / gesture map.
2
3use super::action::Action;
4use super::button::ButtonId;
5use super::gesture::GestureDirection;
6use super::value::Binding;
7
8/// Sensible defaults for a fresh device so the panel isn't empty on first run.
9///
10/// Thumbwheel / GestureButton defaults match what Logi Options+ ships for
11/// MX-line devices: thumb wheel click → App Exposé, gesture button →
12/// Mission Control. The thumb wheel isn't captured yet; the dedicated gesture button is
13/// (per-direction, see [`default_gesture_binding`]). The bindings persist
14/// regardless so the user only configures once.
15///
16/// `GestureButton`'s entry here is vestigial: in the merged [`Binding`] model
17/// the gesture button defaults to [`Binding::Gesture`] (see
18/// [`default_binding_for`]), so this single-action value is never the source of
19/// truth for it. It is retained only so the per-button-`Action` callers (the
20/// hook map, scroll defaults, labels) stay total.
21#[must_use]
22pub fn default_binding(button: ButtonId) -> Action {
23 match button {
24 ButtonId::LeftClick => Action::LeftClick,
25 ButtonId::RightClick => Action::RightClick,
26 ButtonId::MiddleClick => Action::MiddleClick,
27 ButtonId::Back => Action::BrowserBack,
28 ButtonId::Forward => Action::BrowserForward,
29 ButtonId::DpiToggle => Action::CycleDpiPresets,
30 ButtonId::Thumbwheel => Action::AppExpose,
31 // The thumb wheel scrolls horizontally by default: rotating it produces
32 // continuous horizontal scroll, with "up" → right and "down" → left.
33 // The wheel watcher renders these two actions as smooth, sensitivity-
34 // scaled scrolling rather than the discrete per-press burst a button
35 // would get (see `watchers::gesture`).
36 ButtonId::ThumbwheelScrollUp => Action::HorizontalScrollRight,
37 ButtonId::ThumbwheelScrollDown => Action::HorizontalScrollLeft,
38 ButtonId::GestureButton => Action::MissionControl,
39 ButtonId::HapticPanel => Action::ShowActionsRing,
40 // Keyboard keys stay on their native firmware function until the user
41 // explicitly binds them; an unbound key is never diverted, so a
42 // `None` default keeps the projection total without capturing anything.
43 ButtonId::KeySearch
44 | ButtonId::KeyDictation
45 | ButtonId::KeyEmoji
46 | ButtonId::KeyScreenCapture
47 | ButtonId::KeyMicMute
48 | ButtonId::KeyPlayPause
49 | ButtonId::KeyMute
50 | ButtonId::KeyVolumeDown
51 | ButtonId::KeyVolumeUp => Action::None,
52 }
53}
54
55/// Per-direction defaults for the gesture button. These are captured live over
56/// HID++ `0x1b04` (raw-XY diversion) and dispatched like any other binding; the
57/// defaults give the picker something sensible to show on first run.
58#[must_use]
59pub fn default_gesture_binding(direction: GestureDirection) -> Action {
60 match direction {
61 GestureDirection::Up => Action::MissionControl,
62 GestureDirection::Down => Action::ShowDesktop,
63 GestureDirection::Left => Action::PrevTab,
64 GestureDirection::Right => Action::NextTab,
65 GestureDirection::Click => Action::AppExpose,
66 }
67}
68
69/// The canonical default [`Binding`] for a fresh button in the merged model.
70///
71/// [`ButtonId::GestureButton`] defaults to [`Binding::Gesture`] populated from
72/// [`default_gesture_binding`] — preserving the existing per-direction swipe
73/// behavior — so the GUI mode toggle and the runtime agree it starts in gesture
74/// mode. Every other button defaults to [`Binding::Single`] of its
75/// [`default_binding`].
76///
77/// This is the seed when a button is first promoted to a gesture binding (see
78/// [`Config::set_gesture_direction`](crate::config::Config::set_gesture_direction)),
79/// so a freshly-customized gesture button always carries a full default
80/// direction map — including a [`GestureDirection::Click`] — rather than a sparse
81/// map whose click would project to a no-op [`Action::None`].
82#[must_use]
83pub fn default_binding_for(button: ButtonId) -> Binding {
84 match button {
85 ButtonId::GestureButton => Binding::Gesture(
86 GestureDirection::ALL
87 .into_iter()
88 .map(|d| (d, default_gesture_binding(d)))
89 .collect(),
90 ),
91 other => Binding::Single(default_binding(other)),
92 }
93}