qmk_via_api/
api.rs

1use crate::api_commands::{
2    ViaChannelId, ViaCommandId, ViaQmkAudioValue, ViaQmkBacklightValue, ViaQmkLedMatrixValue,
3    ViaQmkRgbMatrixValue, ViaQmkRgblightValue,
4};
5use crate::utils;
6use hidapi::HidApi;
7use pyo3::prelude::*;
8use std::str::FromStr;
9use std::vec;
10
11const COMMAND_START: u8 = 0x00;
12
13pub const RAW_EPSIZE: usize = 32;
14pub const DATA_BUFFER_SIZE: usize = 28;
15
16pub const PROTOCOL_ALPHA: u16 = 7;
17pub const PROTOCOL_BETA: u16 = 8;
18pub const PROTOCOL_GAMMA: u16 = 9;
19
20pub type Layer = u8;
21pub type Row = u8;
22pub type Column = u8;
23
24#[pyclass]
25#[derive(Clone, Copy, Debug)]
26pub struct MatrixInfo {
27    pub rows: u8,
28    pub cols: u8,
29}
30
31#[pyclass]
32#[derive(Clone, Copy, Debug)]
33pub enum KeyboardValue {
34    Uptime = 0x01,
35    LayoutOptions = 0x02,
36    SwitchMatrixState = 0x03,
37    FirmwareVersion = 0x04,
38    DeviceIndication = 0x05,
39}
40
41impl FromStr for KeyboardValue {
42    type Err = &'static str;
43
44    fn from_str(s: &str) -> Result<Self, Self::Err> {
45        match s {
46            "Uptime" => Ok(KeyboardValue::Uptime),
47            "LayoutOptions" => Ok(KeyboardValue::LayoutOptions),
48            "SwitchMatrixState" => Ok(KeyboardValue::SwitchMatrixState),
49            "FirmwareVersion" => Ok(KeyboardValue::FirmwareVersion),
50            "DeviceIndication" => Ok(KeyboardValue::DeviceIndication),
51            _ => Err("Invalid KeyboardValue"),
52        }
53    }
54}
55
56#[pyclass]
57pub struct KeyboardApi {
58    device: hidapi::HidDevice,
59}
60
61#[pymethods]
62impl KeyboardApi {
63    #[new]
64    pub fn new(vid: u16, pid: u16, usage_page: u16) -> PyResult<Self> {
65        let api = HidApi::new().map_err(|e| {
66            PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("Error: {}", e))
67        })?;
68
69        let device = api
70            .device_list()
71            .find(|device| {
72                device.vendor_id() == vid
73                    && device.product_id() == pid
74                    && device.usage_page() == usage_page
75            })
76            .ok_or_else(|| {
77                PyErr::new::<pyo3::exceptions::PyRuntimeError, _>("Could not find keyboard.")
78            })?
79            .open_device(&api)
80            .map_err(|_| {
81                PyErr::new::<pyo3::exceptions::PyRuntimeError, _>("Could not open HID device.")
82            })?;
83
84        Ok(KeyboardApi { device })
85    }
86
87    /// Sends a raw HID command prefixed with the command byte and returns the response if successful.
88    pub fn hid_command(&self, command: ViaCommandId, bytes: Vec<u8>) -> Option<Vec<u8>> {
89        let mut command_bytes: Vec<u8> = vec![command as u8];
90        command_bytes.extend(bytes);
91
92        self.hid_send(command_bytes.clone())?;
93
94        let buffer = self.hid_read()?;
95        if buffer.starts_with(&command_bytes) {
96            Some(buffer)
97        } else {
98            None
99        }
100    }
101
102    /// Reads from the HID device. Returns None if the read fails.
103    pub fn hid_read(&self) -> Option<Vec<u8>> {
104        let mut buffer = vec![0; RAW_EPSIZE];
105        match self.device.read(&mut buffer) {
106            Ok(_) => Some(buffer),
107            Err(_) => None,
108        }
109    }
110
111    /// Sends a raw HID command prefixed with the command byte. Returns None if the send fails.
112    pub fn hid_send(&self, bytes: Vec<u8>) -> Option<()> {
113        if bytes.len() > RAW_EPSIZE {
114            return None;
115        }
116
117        let mut command_bytes: Vec<u8> = vec![COMMAND_START];
118        command_bytes.extend(bytes);
119
120        let mut padded_array = vec![0; RAW_EPSIZE + 1];
121        for (idx, &val) in command_bytes.iter().enumerate() {
122            padded_array[idx] = val;
123        }
124
125        if self.device.write(&padded_array).ok()? == RAW_EPSIZE + 1 {
126            return Some(());
127        }
128
129        None
130    }
131
132    /// Returns the protocol version of the keyboard.
133    pub fn get_protocol_version(&self) -> Option<u16> {
134        self.hid_command(ViaCommandId::GetProtocolVersion, vec![])
135            .map(|val| utils::shift_to_16_bit(val[1], val[2]))
136    }
137
138    /// Returns the number of layers on the keyboard.
139    pub fn get_layer_count(&self) -> Option<u8> {
140        match self.get_protocol_version() {
141            Some(version) if version >= PROTOCOL_BETA => self
142                .hid_command(ViaCommandId::DynamicKeymapGetLayerCount, vec![])
143                .map(|val| val[1]),
144            Some(_) => Some(4),
145            _ => None,
146        }
147    }
148
149    /// Returns the keycode at the given layer, row, and column.
150    pub fn get_key(&self, layer: Layer, row: Row, col: Column) -> Option<u16> {
151        self.hid_command(ViaCommandId::DynamicKeymapGetKeycode, vec![layer, row, col])
152            .map(|val| utils::shift_to_16_bit(val[4], val[5]))
153    }
154
155    /// Sets the keycode at the given layer, row, and column.
156    pub fn set_key(&self, layer: Layer, row: Row, column: Column, val: u16) -> Option<u16> {
157        let val_bytes = utils::shift_from_16_bit(val);
158        let bytes = vec![layer, row, column, val_bytes.0, val_bytes.1];
159        self.hid_command(ViaCommandId::DynamicKeymapSetKeycode, bytes)
160            .map(|val| utils::shift_to_16_bit(val[4], val[5]))
161    }
162
163    /// Returns the keycodes for the given matrix info (number of rows and columns) and layer.
164    pub fn read_raw_matrix(&self, matrix_info: MatrixInfo, layer: Layer) -> Option<Vec<u16>> {
165        match self.get_protocol_version() {
166            Some(version) if version >= PROTOCOL_BETA => {
167                self.fast_read_raw_matrix(matrix_info, layer)
168            }
169            Some(version) if version == PROTOCOL_ALPHA => {
170                self.slow_read_raw_matrix(matrix_info, layer)
171            }
172            _ => None,
173        }
174    }
175
176    fn get_keymap_buffer(&self, offset: u16, size: u8) -> Option<Vec<u8>> {
177        if size > DATA_BUFFER_SIZE as u8 {
178            return None;
179        }
180        let offset_bytes = utils::shift_from_16_bit(offset);
181        self.hid_command(
182            ViaCommandId::DynamicKeymapGetBuffer,
183            vec![offset_bytes.0, offset_bytes.1, size],
184        )
185        .map(|val| val[4..(size as usize + 4)].to_vec())
186    }
187
188    fn fast_read_raw_matrix(&self, matrix_info: MatrixInfo, layer: Layer) -> Option<Vec<u16>> {
189        const MAX_KEYCODES_PARTIAL: usize = 14;
190        let length = matrix_info.rows as usize * matrix_info.cols as usize;
191        let buffer_list = vec![0; length.div_ceil(MAX_KEYCODES_PARTIAL) as usize];
192        let mut remaining = length;
193        let mut result = Vec::new();
194        for _ in 0..buffer_list.len() {
195            if remaining < MAX_KEYCODES_PARTIAL {
196                self.get_keymap_buffer(
197                    layer as u16 * length as u16 * 2 + 2 * (length - remaining) as u16,
198                    (remaining * 2) as u8,
199                )
200                .map(|val| result.extend(val));
201                remaining = 0;
202            } else {
203                self.get_keymap_buffer(
204                    layer as u16 * length as u16 * 2 + 2 * (length - remaining) as u16,
205                    (MAX_KEYCODES_PARTIAL * 2) as u8,
206                )
207                .map(|val| result.extend(val));
208                remaining -= MAX_KEYCODES_PARTIAL;
209            }
210        }
211        Some(utils::shift_buffer_to_16_bit(&result))
212    }
213
214    fn slow_read_raw_matrix(&self, matrix_info: MatrixInfo, layer: Layer) -> Option<Vec<u16>> {
215        let length = matrix_info.rows as usize * matrix_info.cols as usize;
216        let mut res = Vec::new();
217        for i in 0..length {
218            let row = (i as u16 / matrix_info.cols as u16) as u8;
219            let col = (i as u16 % matrix_info.cols as u16) as u8;
220            self.get_key(layer, row, col).map(|val| res.push(val));
221        }
222        Some(res)
223    }
224
225    /// Writes a keymap to the keyboard for the given matrix info (number of rows and columns).
226    pub fn write_raw_matrix(&self, matrix_info: MatrixInfo, keymap: Vec<Vec<u16>>) -> Option<()> {
227        match self.get_protocol_version() {
228            Some(version) if version >= PROTOCOL_BETA => self.fast_write_raw_matrix(keymap),
229            Some(version) if version == PROTOCOL_ALPHA => {
230                self.slow_write_raw_matrix(matrix_info, keymap)
231            }
232            _ => None,
233        }
234    }
235
236    fn slow_write_raw_matrix(&self, matrix_info: MatrixInfo, keymap: Vec<Vec<u16>>) -> Option<()> {
237        for (layer_idx, layer) in keymap.iter().enumerate() {
238            for (key_idx, keycode) in layer.iter().enumerate() {
239                let row = (key_idx as u16 / matrix_info.cols as u16) as u8;
240                let col = (key_idx as u16 % matrix_info.cols as u16) as u8;
241                self.set_key(layer_idx as u8, row, col, *keycode)
242                    .map(|_| ());
243            }
244        }
245        Some(())
246    }
247
248    fn fast_write_raw_matrix(&self, keymap: Vec<Vec<u16>>) -> Option<()> {
249        let data: Vec<u16> = keymap
250            .iter()
251            .flat_map(|layer| layer.iter().cloned())
252            .collect();
253        let shifted_data = utils::shift_buffer_from_16_bit(&data);
254        for offset in (0..shifted_data.len()).step_by(DATA_BUFFER_SIZE) {
255            let offset_bytes = utils::shift_from_16_bit(offset as u16);
256            let end = std::cmp::min(offset + DATA_BUFFER_SIZE, shifted_data.len());
257            let buffer = shifted_data[offset..end].to_vec();
258            let mut bytes = vec![offset_bytes.0, offset_bytes.1, buffer.len() as u8];
259            bytes.extend(buffer);
260            self.hid_command(ViaCommandId::DynamicKeymapSetBuffer, bytes)
261                .map(|_| ());
262        }
263        Some(())
264    }
265
266    /// Returns a keyboard value. This can be used to retrieve keyboard information like uptime, layout options, switch matrix state and firmware version.
267    pub fn get_keyboard_value(
268        &self,
269        command: KeyboardValue,
270        parameters: Vec<u8>,
271        result_length: usize,
272    ) -> Option<Vec<u8>> {
273        let parameters_length = parameters.len();
274        let mut bytes = vec![command as u8];
275        bytes.extend(parameters);
276        self.hid_command(ViaCommandId::GetKeyboardValue, bytes)
277            .map(|val| val[1 + parameters_length..1 + parameters_length + result_length].to_vec())
278    }
279
280    /// Sets a keyboard value. This can be used to set keyboard values like layout options or device indication.
281    pub fn set_keyboard_value(&self, command: KeyboardValue, parameters: Vec<u8>) -> Option<()> {
282        let mut bytes = vec![command as u8];
283        bytes.extend(parameters);
284        self.hid_command(ViaCommandId::SetKeyboardValue, bytes)
285            .map(|_| ())
286    }
287
288    /// Gets the encoder value for the given layer, id, and direction.
289    pub fn get_encoder_value(&self, layer: Layer, id: u8, is_clockwise: bool) -> Option<u16> {
290        self.hid_command(
291            ViaCommandId::DynamicKeymapGetEncoder,
292            vec![layer, id, is_clockwise as u8],
293        )
294        .map(|val| utils::shift_to_16_bit(val[4], val[5]))
295    }
296
297    /// Sets the encoder value for the given layer, id, direction, and keycode.
298    pub fn set_encoder_value(
299        &self,
300        layer: Layer,
301        id: u8,
302        is_clockwise: bool,
303        keycode: u16,
304    ) -> Option<()> {
305        let keycode_bytes = utils::shift_from_16_bit(keycode);
306        let bytes = vec![
307            layer,
308            id,
309            is_clockwise as u8,
310            keycode_bytes.0,
311            keycode_bytes.1,
312        ];
313        self.hid_command(ViaCommandId::DynamicKeymapSetEncoder, bytes)
314            .map(|_| ())
315    }
316
317    /// Get a custom menu value. This is a generic function that can be used to get any value specific to arbitrary keyboard functionalities.
318    pub fn get_custom_menu_value(&self, command_bytes: Vec<u8>) -> Option<Vec<u8>> {
319        let command_length = command_bytes.len();
320        self.hid_command(ViaCommandId::CustomMenuGetValue, command_bytes)
321            .map(|val| val[0..command_length].to_vec())
322    }
323
324    /// Set a custom menu value. This is a generic function that can be used to set any value specific to arbitrary keyboard functionalities.
325    pub fn set_custom_menu_value(&self, args: Vec<u8>) -> Option<()> {
326        self.hid_command(ViaCommandId::CustomMenuSetValue, args)
327            .map(|_| ())
328    }
329
330    /// Saves the custom menu values for the given channel id.
331    pub fn save_custom_menu(&self, channel: u8) -> Option<()> {
332        let bytes = vec![channel];
333        self.hid_command(ViaCommandId::CustomMenuSave, bytes)
334            .map(|_| ())
335    }
336
337    /// Gets the backlight brightness.
338    pub fn get_backlight_brightness(&self) -> Option<u8> {
339        self.hid_command(
340            ViaCommandId::CustomMenuGetValue,
341            vec![
342                ViaChannelId::IdQmkBacklightChannel as u8,
343                ViaQmkBacklightValue::IdQmkBacklightBrightness as u8,
344            ],
345        )
346        .map(|val| val[3])
347    }
348
349    /// Sets the backlight brightness.
350    pub fn set_backlight_brightness(&self, brightness: u8) -> Option<()> {
351        self.hid_command(
352            ViaCommandId::CustomMenuSetValue,
353            vec![
354                ViaChannelId::IdQmkBacklightChannel as u8,
355                ViaQmkBacklightValue::IdQmkBacklightBrightness as u8,
356                brightness,
357            ],
358        )
359        .map(|_| ())
360    }
361
362    /// Gets the backlight effect.
363    pub fn get_backlight_effect(&self) -> Option<u8> {
364        self.hid_command(
365            ViaCommandId::CustomMenuGetValue,
366            vec![
367                ViaChannelId::IdQmkBacklightChannel as u8,
368                ViaQmkBacklightValue::IdQmkBacklightEffect as u8,
369            ],
370        )
371        .map(|val| val[3])
372    }
373
374    /// Sets the backlight effect.
375    pub fn set_backlight_effect(&self, effect: u8) -> Option<()> {
376        self.hid_command(
377            ViaCommandId::CustomMenuSetValue,
378            vec![
379                ViaChannelId::IdQmkBacklightChannel as u8,
380                ViaQmkBacklightValue::IdQmkBacklightEffect as u8,
381                effect,
382            ],
383        )
384        .map(|_| ())
385    }
386
387    /// Gets the RGB light brightness.
388    pub fn get_rgblight_brightness(&self) -> Option<u8> {
389        self.hid_command(
390            ViaCommandId::CustomMenuGetValue,
391            vec![
392                ViaChannelId::IdQmkRgblightChannel as u8,
393                ViaQmkRgblightValue::IdQmkRgblightBrightness as u8,
394            ],
395        )
396        .map(|val| val[3])
397    }
398
399    /// Sets the RGB light brightness.
400    pub fn set_rgblight_brightness(&self, brightness: u8) -> Option<()> {
401        self.hid_command(
402            ViaCommandId::CustomMenuSetValue,
403            vec![
404                ViaChannelId::IdQmkRgblightChannel as u8,
405                ViaQmkRgblightValue::IdQmkRgblightBrightness as u8,
406                brightness,
407            ],
408        )
409        .map(|_| ())
410    }
411
412    /// Gets the RGB light effect.
413    pub fn get_rgblight_effect(&self) -> Option<u8> {
414        self.hid_command(
415            ViaCommandId::CustomMenuGetValue,
416            vec![
417                ViaChannelId::IdQmkRgblightChannel as u8,
418                ViaQmkRgblightValue::IdQmkRgblightEffect as u8,
419            ],
420        )
421        .map(|val| val[3])
422    }
423
424    /// Sets the RGB light effect.
425    pub fn set_rgblight_effect(&self, effect: u8) -> Option<()> {
426        self.hid_command(
427            ViaCommandId::CustomMenuSetValue,
428            vec![
429                ViaChannelId::IdQmkRgblightChannel as u8,
430                ViaQmkRgblightValue::IdQmkRgblightEffect as u8,
431                effect,
432            ],
433        )
434        .map(|_| ())
435    }
436
437    /// Gets the RGB light effect speed.
438    pub fn get_rgblight_effect_speed(&self) -> Option<u8> {
439        self.hid_command(
440            ViaCommandId::CustomMenuGetValue,
441            vec![
442                ViaChannelId::IdQmkRgblightChannel as u8,
443                ViaQmkRgblightValue::IdQmkRgblightEffectSpeed as u8,
444            ],
445        )
446        .map(|val| val[3])
447    }
448
449    /// Sets the RGB light effect speed.
450    pub fn set_rgblight_effect_speed(&self, speed: u8) -> Option<()> {
451        self.hid_command(
452            ViaCommandId::CustomMenuSetValue,
453            vec![
454                ViaChannelId::IdQmkRgblightChannel as u8,
455                ViaQmkRgblightValue::IdQmkRgblightEffectSpeed as u8,
456                speed,
457            ],
458        )
459        .map(|_| ())
460    }
461
462    /// Gets the RGB light color.
463    pub fn get_rgblight_color(&self) -> Option<(u8, u8)> {
464        self.hid_command(
465            ViaCommandId::CustomMenuGetValue,
466            vec![
467                ViaChannelId::IdQmkRgblightChannel as u8,
468                ViaQmkRgblightValue::IdQmkRgblightColor as u8,
469            ],
470        )
471        .map(|val| (val[3], val[4]))
472    }
473
474    /// Sets the RGB light color.
475    pub fn set_rgblight_color(&self, hue: u8, sat: u8) -> Option<()> {
476        self.hid_command(
477            ViaCommandId::CustomMenuSetValue,
478            vec![
479                ViaChannelId::IdQmkRgblightChannel as u8,
480                ViaQmkRgblightValue::IdQmkRgblightColor as u8,
481                hue,
482                sat,
483            ],
484        )
485        .map(|_| ())
486    }
487
488    /// Gets the RGB matrix brightness.
489    pub fn get_rgb_matrix_brightness(&self) -> Option<u8> {
490        self.hid_command(
491            ViaCommandId::CustomMenuGetValue,
492            vec![
493                ViaChannelId::IdQmkRgbMatrixChannel as u8,
494                ViaQmkRgbMatrixValue::IdQmkRgbMatrixBrightness as u8,
495            ],
496        )
497        .map(|val| val[3])
498    }
499
500    /// Sets the RGB matrix brightness.
501    pub fn set_rgb_matrix_brightness(&self, brightness: u8) -> Option<()> {
502        self.hid_command(
503            ViaCommandId::CustomMenuSetValue,
504            vec![
505                ViaChannelId::IdQmkRgbMatrixChannel as u8,
506                ViaQmkRgbMatrixValue::IdQmkRgbMatrixBrightness as u8,
507                brightness,
508            ],
509        )
510        .map(|_| ())
511    }
512
513    /// Gets the RGB matrix effect.
514    pub fn get_rgb_matrix_effect(&self) -> Option<u8> {
515        self.hid_command(
516            ViaCommandId::CustomMenuGetValue,
517            vec![
518                ViaChannelId::IdQmkRgbMatrixChannel as u8,
519                ViaQmkRgbMatrixValue::IdQmkRgbMatrixEffect as u8,
520            ],
521        )
522        .map(|val| val[3])
523    }
524
525    /// Sets the RGB matrix effect.
526    pub fn set_rgb_matrix_effect(&self, effect: u8) -> Option<()> {
527        self.hid_command(
528            ViaCommandId::CustomMenuSetValue,
529            vec![
530                ViaChannelId::IdQmkRgbMatrixChannel as u8,
531                ViaQmkRgbMatrixValue::IdQmkRgbMatrixEffect as u8,
532                effect,
533            ],
534        )
535        .map(|_| ())
536    }
537
538    /// Gets the RGB matrix effect speed.
539    pub fn get_rgb_matrix_effect_speed(&self) -> Option<u8> {
540        self.hid_command(
541            ViaCommandId::CustomMenuGetValue,
542            vec![
543                ViaChannelId::IdQmkRgbMatrixChannel as u8,
544                ViaQmkRgbMatrixValue::IdQmkRgbMatrixEffectSpeed as u8,
545            ],
546        )
547        .map(|val| val[3])
548    }
549
550    /// Sets the RGB matrix effect speed.
551    pub fn set_rgb_matrix_effect_speed(&self, speed: u8) -> Option<()> {
552        self.hid_command(
553            ViaCommandId::CustomMenuSetValue,
554            vec![
555                ViaChannelId::IdQmkRgbMatrixChannel as u8,
556                ViaQmkRgbMatrixValue::IdQmkRgbMatrixEffectSpeed as u8,
557                speed,
558            ],
559        )
560        .map(|_| ())
561    }
562
563    /// Gets the RGB matrix color.
564    pub fn get_rgb_matrix_color(&self) -> Option<(u8, u8)> {
565        self.hid_command(
566            ViaCommandId::CustomMenuGetValue,
567            vec![
568                ViaChannelId::IdQmkRgbMatrixChannel as u8,
569                ViaQmkRgbMatrixValue::IdQmkRgbMatrixColor as u8,
570            ],
571        )
572        .map(|val| (val[3], val[4]))
573    }
574
575    /// Sets the RGB matrix color.
576    pub fn set_rgb_matrix_color(&self, hue: u8, sat: u8) -> Option<()> {
577        self.hid_command(
578            ViaCommandId::CustomMenuSetValue,
579            vec![
580                ViaChannelId::IdQmkRgbMatrixChannel as u8,
581                ViaQmkRgbMatrixValue::IdQmkRgbMatrixColor as u8,
582                hue,
583                sat,
584            ],
585        )
586        .map(|_| ())
587    }
588
589    /// Gets the LED matrix brightness.
590    pub fn get_led_matrix_brightness(&self) -> Option<u8> {
591        self.hid_command(
592            ViaCommandId::CustomMenuGetValue,
593            vec![
594                ViaChannelId::IdQmkLedMatrixChannel as u8,
595                ViaQmkLedMatrixValue::IdQmkLedMatrixBrightness as u8,
596            ],
597        )
598        .map(|val| val[3])
599    }
600
601    /// Sets the LED matrix brightness.
602    pub fn set_led_matrix_brightness(&self, brightness: u8) -> Option<()> {
603        self.hid_command(
604            ViaCommandId::CustomMenuSetValue,
605            vec![
606                ViaChannelId::IdQmkLedMatrixChannel as u8,
607                ViaQmkLedMatrixValue::IdQmkLedMatrixBrightness as u8,
608                brightness,
609            ],
610        )
611        .map(|_| ())
612    }
613
614    /// Gets the LED matrix effect.
615    pub fn get_led_matrix_effect(&self) -> Option<u8> {
616        self.hid_command(
617            ViaCommandId::CustomMenuGetValue,
618            vec![
619                ViaChannelId::IdQmkLedMatrixChannel as u8,
620                ViaQmkLedMatrixValue::IdQmkLedMatrixEffect as u8,
621            ],
622        )
623        .map(|val| val[3])
624    }
625
626    /// Sets the LED matrix effect.
627    pub fn set_led_matrix_effect(&self, effect: u8) -> Option<()> {
628        self.hid_command(
629            ViaCommandId::CustomMenuSetValue,
630            vec![
631                ViaChannelId::IdQmkLedMatrixChannel as u8,
632                ViaQmkLedMatrixValue::IdQmkLedMatrixEffect as u8,
633                effect,
634            ],
635        )
636        .map(|_| ())
637    }
638
639    /// Gets the LED matrix effect speed.
640    pub fn get_led_matrix_effect_speed(&self) -> Option<u8> {
641        self.hid_command(
642            ViaCommandId::CustomMenuGetValue,
643            vec![
644                ViaChannelId::IdQmkLedMatrixChannel as u8,
645                ViaQmkLedMatrixValue::IdQmkLedMatrixEffectSpeed as u8,
646            ],
647        )
648        .map(|val| val[3])
649    }
650
651    /// Sets the LED matrix effect speed.
652    pub fn set_led_matrix_effect_speed(&self, speed: u8) -> Option<()> {
653        self.hid_command(
654            ViaCommandId::CustomMenuSetValue,
655            vec![
656                ViaChannelId::IdQmkLedMatrixChannel as u8,
657                ViaQmkLedMatrixValue::IdQmkLedMatrixEffectSpeed as u8,
658                speed,
659            ],
660        )
661        .map(|_| ())
662    }
663
664    /// Saves the lighting settings.
665    pub fn save_lighting(&self) -> Option<()> {
666        self.hid_command(ViaCommandId::CustomMenuSave, vec![])
667            .map(|_| ())
668    }
669
670    /// Gets the audio enabled state.
671    pub fn get_audio_enabled(&self) -> Option<bool> {
672        self.hid_command(
673            ViaCommandId::CustomMenuGetValue,
674            vec![
675                ViaChannelId::IdQmkAudioChannel as u8,
676                ViaQmkAudioValue::IdQmkAudioEnable as u8,
677            ],
678        )
679        .map(|val| val[3] == 1)
680    }
681
682    /// Sets the audio enabled state.
683    pub fn set_audio_enabled(&self, enabled: bool) -> Option<()> {
684        let bytes = vec![
685            ViaChannelId::IdQmkAudioChannel as u8,
686            ViaQmkAudioValue::IdQmkAudioEnable as u8,
687            enabled as u8,
688        ];
689        self.hid_command(ViaCommandId::CustomMenuSetValue, bytes)
690            .map(|_| ())
691    }
692
693    /// Gets the audio clicky enabled state.
694    pub fn get_audio_clicky_enabled(&self) -> Option<bool> {
695        self.hid_command(
696            ViaCommandId::CustomMenuGetValue,
697            vec![
698                ViaChannelId::IdQmkAudioChannel as u8,
699                ViaQmkAudioValue::IdQmkAudioClickyEnable as u8,
700            ],
701        )
702        .map(|val| val[3] == 1)
703    }
704
705    /// Sets the audio clicky enabled state.
706    pub fn set_audio_clicky_enabled(&self, enabled: bool) -> Option<()> {
707        let bytes = vec![
708            ViaChannelId::IdQmkAudioChannel as u8,
709            ViaQmkAudioValue::IdQmkAudioClickyEnable as u8,
710            enabled as u8,
711        ];
712        self.hid_command(ViaCommandId::CustomMenuSetValue, bytes)
713            .map(|_| ())
714    }
715
716    /// Gets the macro count.
717    pub fn get_macro_count(&self) -> Option<u8> {
718        let bytes = vec![];
719        self.hid_command(ViaCommandId::DynamicKeymapMacroGetCount, bytes)
720            .map(|val| val[1])
721    }
722
723    fn get_macro_buffer_size(&self) -> Option<u16> {
724        let bytes = vec![];
725        self.hid_command(ViaCommandId::DynamicKeymapMacroGetBufferSize, bytes)
726            .map(|val| utils::shift_to_16_bit(val[1], val[2]))
727    }
728
729    /// Gets the macro bytes. All macros are separated by 0x00.
730    pub fn get_macro_bytes(&self) -> Option<Vec<u8>> {
731        let macro_buffer_size = self.get_macro_buffer_size()? as usize;
732        let mut all_bytes = Vec::new();
733        for offset in (0..macro_buffer_size).step_by(DATA_BUFFER_SIZE) {
734            let offset_bytes = utils::shift_from_16_bit(offset as u16);
735            let remaining_bytes = macro_buffer_size - offset;
736            let bytes = vec![offset_bytes.0, offset_bytes.1, DATA_BUFFER_SIZE as u8];
737            match self.hid_command(ViaCommandId::DynamicKeymapMacroGetBuffer, bytes) {
738                Some(val) => {
739                    if remaining_bytes < DATA_BUFFER_SIZE {
740                        all_bytes.extend(val[4..(4 + remaining_bytes)].to_vec())
741                    } else {
742                        all_bytes.extend(val[4..].to_vec())
743                    }
744                }
745                None => return None,
746            }
747        }
748        Some(all_bytes)
749    }
750
751    /// Sets the macro bytes.
752    pub fn set_macro_bytes(&self, data: Vec<u8>) -> Option<()> {
753        let macro_buffer_size = self.get_macro_buffer_size()?;
754        let size = data.len();
755        if size > macro_buffer_size as usize {
756            return None;
757        }
758
759        self.reset_macros()?;
760
761        let last_offset = macro_buffer_size - 1;
762        let last_offset_bytes = utils::shift_from_16_bit(last_offset);
763
764        // Set last byte in buffer to non-zero (0xFF) to indicate write-in-progress
765        self.hid_command(
766            ViaCommandId::DynamicKeymapMacroSetBuffer,
767            vec![last_offset_bytes.0, last_offset_bytes.1, 1, 0xff],
768        )?;
769
770        for offset in (0..data.len()).step_by(DATA_BUFFER_SIZE) {
771            let offset_bytes = utils::shift_from_16_bit(offset as u16);
772            let end = std::cmp::min(offset + DATA_BUFFER_SIZE, data.len());
773            let buffer = data[offset..end].to_vec();
774            let mut bytes = vec![offset_bytes.0, offset_bytes.1, buffer.len() as u8];
775            bytes.extend(buffer);
776            self.hid_command(ViaCommandId::DynamicKeymapMacroSetBuffer, bytes)?;
777        }
778
779        // Set last byte in buffer to zero to indicate write finished
780        self.hid_command(
781            ViaCommandId::DynamicKeymapMacroSetBuffer,
782            vec![last_offset_bytes.0, last_offset_bytes.1, 1, 0x00],
783        )?;
784
785        Some(())
786    }
787
788    /// Resets all saved macros.
789    pub fn reset_macros(&self) -> Option<()> {
790        let bytes = vec![];
791        self.hid_command(ViaCommandId::DynamicKeymapMacroReset, bytes)
792            .map(|_| ())
793    }
794
795    /// Resets the EEPROM, clearing all settings like keymaps and macros.
796    pub fn reset_eeprom(&self) -> Option<()> {
797        let bytes = vec![];
798        self.hid_command(ViaCommandId::EepromReset, bytes)
799            .map(|_| ())
800    }
801
802    /// Jumps to the bootloader.
803    pub fn jump_to_bootloader(&self) -> Option<()> {
804        let bytes = vec![];
805        self.hid_command(ViaCommandId::BootloaderJump, bytes)
806            .map(|_| ())
807    }
808}