1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
//! One device's stored settings, a field at a time: the getters and setters
//! that address a [`DeviceConfig`](super::DeviceConfig) entry by its config key.
//!
//! A getter answers for a device with no entry the way a fresh entry would, and
//! a setter creates the entry it needs. Settings with rules of their own live
//! beside this file: gesture mode in [`gestures`](super::gestures),
//! per-application profiles in [`per_app`](super::per_app).
use std::collections::BTreeMap;
use super::{
CameraControls, Config, DeviceIdentity, LightSettings, Lighting, ScrollResolution, SmartShift,
ThumbwheelSensitivity,
};
use crate::binding::{
ActionRingConfig, ActionRingIcon, ActionRingSlot, Binding, ButtonId, RingAction,
};
use crate::hid::Dpi;
impl Config {
/// The bindings stored for `device_key` as they were committed, or an
/// empty map when the device has none yet. The effective per-button map,
/// with defaults and the per-app overlay applied, is
/// [`crate::bindings::bindings_for`].
#[must_use]
pub fn stored_bindings(&self, device_key: &str) -> BTreeMap<ButtonId, Binding> {
self.devices
.get(device_key)
.map(|d| d.bindings.clone())
.unwrap_or_default()
}
/// Records `binding` for `button` on `device_key`, creating the device
/// entry if needed. Replaces the whole binding (use
/// [`Self::set_gesture_direction`] to edit one direction of a gesture
/// binding in place).
pub fn set_binding(&mut self, device_key: &str, button: ButtonId, binding: Binding) {
self.devices
.entry(device_key.to_string())
.or_default()
.bindings
.insert(button, binding);
}
/// Actions Ring settings for `device_key`, falling back to defaults when
/// the device has no saved ring configuration.
#[must_use]
pub fn action_ring(&self, device_key: &str) -> ActionRingConfig {
self.devices
.get(device_key)
.map(|device| device.action_ring.clone())
.unwrap_or_default()
}
/// Enable or disable `device_key`'s Actions Ring.
pub fn set_action_ring_enabled(&mut self, device_key: &str, enabled: bool) {
self.devices
.entry(device_key.to_string())
.or_default()
.action_ring
.enabled = enabled;
}
/// Enable or disable ring hover and activation haptics.
pub fn set_action_ring_haptics(&mut self, device_key: &str, enabled: bool) {
self.devices
.entry(device_key.to_string())
.or_default()
.action_ring
.haptics = enabled;
}
/// Replace or clear one slot in the default Actions Ring layout.
pub fn set_action_ring_slot(
&mut self,
device_key: &str,
slot: ActionRingSlot,
action: Option<RingAction>,
) {
self.devices
.entry(device_key.to_string())
.or_default()
.action_ring
.default
.set_action(slot, action);
}
/// Set or restore the action-derived icon for one default ring slot.
pub fn set_action_ring_icon(
&mut self,
device_key: &str,
slot: ActionRingSlot,
icon: Option<ActionRingIcon>,
) {
self.devices
.entry(device_key.to_string())
.or_default()
.action_ring
.default
.set_icon(slot, icon);
}
/// The ordered DPI preset list for `device_key`, or an empty `Vec` if the
/// device has none configured yet.
#[must_use]
pub fn dpi_presets(&self, device_key: &str) -> Vec<Dpi> {
self.devices
.get(device_key)
.map(|d| d.dpi_presets.clone())
.unwrap_or_default()
}
/// Replace the DPI preset list for `device_key`. Pass an empty `Vec` to
/// clear (the device block is kept; the field is just omitted on save
/// thanks to `skip_serializing_if`).
pub fn set_dpi_presets(&mut self, device_key: &str, presets: Vec<Dpi>) {
self.devices
.entry(device_key.to_string())
.or_default()
.dpi_presets = presets;
}
/// The last-known [`DeviceIdentity`] for `device_key`, or `None` if the
/// device has never been seen online (or was configured before identities
/// were recorded).
#[must_use]
pub fn device_identity(&self, device_key: &str) -> Option<&DeviceIdentity> {
self.devices
.get(device_key)
.and_then(|d| d.identity.as_ref())
}
/// Record (or refresh) the identity captured for `device_key` while it was
/// online, creating the device entry if needed.
pub fn set_device_identity(&mut self, device_key: &str, identity: DeviceIdentity) {
self.devices
.entry(device_key.to_string())
.or_default()
.identity = Some(identity.without_unit_identifiers());
}
/// Drop everything recorded for `device_key` — identity, custom name, and
/// per-device settings. Returns whether an entry existed.
pub fn remove_device(&mut self, device_key: &str) -> bool {
self.devices.remove(device_key).is_some()
}
/// The user-assigned name for `device_key`, if one is configured.
#[must_use]
pub fn device_custom_name(&self, device_key: &str) -> Option<&str> {
self.devices
.get(device_key)
.and_then(|device| device.custom_name.as_deref())
}
/// Set the user-assigned name for `device_key`, or clear it to use the
/// hardware model name again.
pub fn set_device_custom_name(&mut self, device_key: &str, custom_name: Option<String>) {
self.devices
.entry(device_key.to_string())
.or_default()
.custom_name = custom_name;
}
/// Iterate every device we've recorded an identity for, as
/// `(config_key, identity)`. Used to seed offline placeholder cards so a
/// known device stays visible (with its panels) before any live probe.
pub fn known_identities(&self) -> impl Iterator<Item = (&str, &DeviceIdentity)> {
self.devices
.iter()
.filter_map(|(k, d)| d.identity.as_ref().map(|i| (k.as_str(), i)))
}
/// The lighting config for `device_key`, or `None` if unset.
#[must_use]
pub fn lighting(&self, device_key: &str) -> Option<Lighting> {
self.devices
.get(device_key)
.and_then(|d| d.lighting.clone())
}
/// Replace the lighting config for `device_key`.
pub fn set_lighting(&mut self, device_key: &str, lighting: Lighting) {
self.devices
.entry(device_key.to_string())
.or_default()
.lighting = Some(lighting);
}
/// The saved UVC image controls for `device_key`, or `None` if never set.
#[must_use]
pub fn camera_controls(&self, device_key: &str) -> Option<CameraControls> {
self.devices
.get(device_key)
.and_then(|d| d.camera_controls.clone())
}
/// Replace the saved UVC image controls for `device_key`.
pub fn set_camera_controls(&mut self, device_key: &str, controls: CameraControls) {
self.devices
.entry(device_key.to_string())
.or_default()
.camera_controls = Some(controls);
}
/// The saved custom camera profiles for `device_key` (name → snapshot).
#[must_use]
pub fn camera_profiles(&self, device_key: &str) -> BTreeMap<String, CameraControls> {
self.devices
.get(device_key)
.map(|d| d.camera_profiles.clone())
.unwrap_or_default()
}
/// Save (or overwrite) a custom camera profile for `device_key`.
pub fn save_camera_profile(&mut self, device_key: &str, name: &str, snap: CameraControls) {
self.devices
.entry(device_key.to_string())
.or_default()
.camera_profiles
.insert(name.to_string(), snap);
}
/// Delete a custom camera profile, clearing the active selection if it
/// named it. Unknown names are a no-op.
pub fn delete_camera_profile(&mut self, device_key: &str, name: &str) {
if let Some(device) = self.devices.get_mut(device_key) {
device.camera_profiles.remove(name);
if device.camera_profile.as_deref() == Some(name) {
device.camera_profile = None;
}
}
}
/// The last-applied camera profile name for `device_key`, if any.
#[must_use]
pub fn camera_active_profile(&self, device_key: &str) -> Option<String> {
self.devices
.get(device_key)
.and_then(|d| d.camera_profile.clone())
}
/// Record which camera profile `device_key` last applied.
pub fn set_camera_active_profile(&mut self, device_key: &str, name: Option<String>) {
self.devices
.entry(device_key.to_string())
.or_default()
.camera_profile = name;
}
/// The standalone-light config for `device_key`, or `None` if unset.
#[must_use]
pub fn light(&self, device_key: &str) -> Option<LightSettings> {
self.devices.get(device_key).and_then(|d| d.light)
}
/// Replace the standalone-light config for `device_key`.
pub fn set_light(&mut self, device_key: &str, light: LightSettings) {
self.devices
.entry(device_key.to_string())
.or_default()
.light = Some(light);
}
/// The committed sensor DPI for `device_key`, or `None` if never set.
#[must_use]
pub fn dpi(&self, device_key: &str) -> Option<Dpi> {
self.devices.get(device_key).and_then(|d| d.dpi)
}
/// Record the committed sensor DPI for `device_key`, so the agent can
/// re-apply it when the device reconnects (#189).
pub fn set_dpi(&mut self, device_key: &str, dpi: Dpi) {
self.devices.entry(device_key.to_string()).or_default().dpi = Some(dpi);
}
/// The SmartShift wheel config for `device_key`, or `None` if never set.
#[must_use]
pub fn smartshift(&self, device_key: &str) -> Option<SmartShift> {
self.devices.get(device_key).and_then(|d| d.smartshift)
}
/// The persisted keyboard Fn-lock state for `device_key`, or `None` when
/// the user never set one (the keyboard keeps its own state).
#[must_use]
pub fn fn_lock(&self, device_key: &str) -> Option<bool> {
self.devices.get(device_key).and_then(|d| d.fn_lock)
}
/// Record the SmartShift wheel config for `device_key`, so the agent can
/// re-apply it when the device reconnects (#189).
pub fn set_smartshift(&mut self, device_key: &str, smartshift: SmartShift) {
self.devices
.entry(device_key.to_string())
.or_default()
.smartshift = Some(smartshift);
}
/// Whether `device_key`'s scroll wheel is inverted (issue #126). `false`
/// (the native direction) for an unconfigured or absent device.
#[must_use]
pub fn invert_scroll(&self, device_key: &str) -> bool {
self.devices
.get(device_key)
.is_some_and(|d| d.invert_scroll)
}
/// Set whether `device_key`'s scroll wheel is inverted. The agent reads this
/// on the next `ReloadConfig` and applies it in the OS hook.
pub fn set_invert_scroll(&mut self, device_key: &str, invert: bool) {
self.devices
.entry(device_key.to_string())
.or_default()
.invert_scroll = invert;
}
/// The configured wheel resolution for `device_key`, or `None` when
/// OpenLogi should leave the device's current resolution unchanged.
#[must_use]
pub fn scroll_resolution(&self, device_key: &str) -> Option<ScrollResolution> {
self.devices
.get(device_key)
.and_then(|device| device.scroll_resolution)
}
/// Set the wheel resolution OpenLogi should restore for `device_key`.
/// Passing `None` returns the device to its unmanaged default state.
pub fn set_scroll_resolution(
&mut self,
device_key: &str,
resolution: Option<ScrollResolution>,
) {
self.devices
.entry(device_key.to_string())
.or_default()
.scroll_resolution = resolution;
}
/// Whether OpenLogi manages `device_key` at all (capture + volatile
/// re-apply). Unconfigured devices are managed.
#[must_use]
pub fn device_enabled(&self, device_key: &str) -> bool {
self.devices.get(device_key).is_none_or(|d| d.enabled)
}
/// Enable or disable OpenLogi's management of `device_key`.
pub fn set_device_enabled(&mut self, device_key: &str, enabled: bool) {
self.devices
.entry(device_key.to_string())
.or_default()
.enabled = enabled;
}
/// The effective thumb-wheel sensitivity for `device_key`: the device's
/// override when set, else the app-wide default.
#[must_use]
pub fn thumbwheel_sensitivity(&self, device_key: &str) -> ThumbwheelSensitivity {
self.devices
.get(device_key)
.and_then(|d| d.thumbwheel_sensitivity)
.unwrap_or(self.app_settings.thumbwheel_sensitivity)
}
/// Set (or clear, with `None`) `device_key`'s thumb-wheel sensitivity
/// override.
pub fn set_device_thumbwheel_sensitivity(
&mut self,
device_key: &str,
sensitivity: Option<ThumbwheelSensitivity>,
) {
self.devices
.entry(device_key.to_string())
.or_default()
.thumbwheel_sensitivity = sensitivity;
}
}