qmk_via_api/
api.rs

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