use crate::api_commands::{
ViaChannelId, ViaCommandId, ViaLightingValue, ViaQmkAudioValue, ViaQmkBacklightValue,
ViaQmkLedMatrixValue, ViaQmkRgbMatrixValue, ViaQmkRgblightValue,
};
use crate::scan::KeyboardDeviceInfo;
use crate::{utils, Error, Result};
use hidapi::HidApi;
use std::str::FromStr;
use std::vec;
#[cfg(feature = "python")]
use pyo3::prelude::*;
#[cfg(feature = "python")]
use pyo3::types::PyType;
const COMMAND_START: u8 = 0x00;
pub const RAW_EPSIZE: usize = 32;
pub const DATA_BUFFER_SIZE: usize = 28;
pub const PROTOCOL_ALPHA: u16 = 7;
pub const PROTOCOL_BETA: u16 = 8;
pub const PROTOCOL_GAMMA: u16 = 9;
pub const PROTOCOL_V3: u16 = 11;
pub type Layer = u8;
pub type Row = u8;
pub type Column = u8;
#[cfg_attr(feature = "python", pyclass(get_all, set_all, from_py_object))]
#[derive(Clone, Copy, Debug)]
pub struct MatrixInfo {
pub rows: u8,
pub cols: u8,
}
#[cfg_attr(feature = "python", pyclass(from_py_object))]
#[derive(Clone, Copy, Debug)]
pub enum KeyboardValue {
Uptime = 0x01,
LayoutOptions = 0x02,
SwitchMatrixState = 0x03,
FirmwareVersion = 0x04,
DeviceIndication = 0x05,
}
impl FromStr for KeyboardValue {
type Err = &'static str;
fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
match s {
"Uptime" => Ok(KeyboardValue::Uptime),
"LayoutOptions" => Ok(KeyboardValue::LayoutOptions),
"SwitchMatrixState" => Ok(KeyboardValue::SwitchMatrixState),
"FirmwareVersion" => Ok(KeyboardValue::FirmwareVersion),
"DeviceIndication" => Ok(KeyboardValue::DeviceIndication),
_ => Err("Invalid KeyboardValue"),
}
}
}
fn hid_command_on_device(
device: &hidapi::HidDevice,
command: ViaCommandId,
bytes: Vec<u8>,
) -> Result<Vec<u8>> {
let mut command_bytes: Vec<u8> = vec![command as u8];
command_bytes.extend(bytes);
hid_send_on_device(device, command_bytes.clone())
.map_err(|send_err| Error::SendCommand(command, send_err.to_string()))?;
let buffer = hid_read_on_device(device)?;
if buffer.starts_with(&command_bytes) {
Ok(buffer)
} else {
Err(Error::BadCommandResponse(command))
}
}
fn hid_read_on_device(device: &hidapi::HidDevice) -> Result<Vec<u8>> {
let mut buffer = vec![0; RAW_EPSIZE];
device.read(&mut buffer)?;
Ok(buffer)
}
fn hid_send_on_device(device: &hidapi::HidDevice, bytes: Vec<u8>) -> Result<()> {
if bytes.len() > RAW_EPSIZE {
return Err(Error::size_mismatch(
"send buffer overflow",
RAW_EPSIZE,
bytes.len(),
));
}
let mut command_bytes: Vec<u8> = vec![COMMAND_START];
command_bytes.extend(bytes);
let mut padded_array = vec![0; RAW_EPSIZE + 1];
for (idx, &val) in command_bytes.iter().enumerate() {
padded_array[idx] = val;
}
let bytes_written = device.write(&padded_array)?;
if bytes_written == RAW_EPSIZE + 1 {
return Ok(());
}
Err(Error::size_mismatch(
"unexpected number of bytes written",
bytes_written,
RAW_EPSIZE + 1,
))
}
#[cfg_attr(feature = "python", pyclass(unsendable))]
pub struct KeyboardApi {
device: hidapi::HidDevice,
protocol_version: u16,
}
#[cfg(feature = "python")]
#[pymethods]
impl KeyboardApi {
#[new]
pub fn py_new(vid: u16, pid: u16, usage_page: u16) -> Result<Self> {
KeyboardApi::new(vid, pid, usage_page)
}
#[classmethod]
#[pyo3(name = "from_device")]
pub fn py_from_device(_cls: &Bound<'_, PyType>, device: &KeyboardDeviceInfo) -> Result<Self> {
KeyboardApi::from_device(device)
}
}
impl KeyboardApi {
pub fn new(vid: u16, pid: u16, usage_page: u16) -> Result<KeyboardApi> {
let api = HidApi::new()?;
let device = api
.device_list()
.find(|device| {
device.vendor_id() == vid
&& device.product_id() == pid
&& device.usage_page() == usage_page
})
.ok_or(Error::NoSuchKeyboard {
vid,
pid,
usage_page,
})?
.open_device(&api)?;
let protocol_version = Self::read_protocol_version(&device)?;
Ok(KeyboardApi {
device,
protocol_version,
})
}
pub fn from_device(device: &KeyboardDeviceInfo) -> Result<KeyboardApi> {
Self::new(device.vendor_id, device.product_id, device.usage_page)
}
fn read_protocol_version(device: &hidapi::HidDevice) -> Result<u16> {
let buffer = hid_command_on_device(device, ViaCommandId::GetProtocolVersion, vec![])?;
Ok(utils::shift_to_16_bit(buffer[1], buffer[2]))
}
}
#[cfg_attr(feature = "python", pymethods)]
impl KeyboardApi {
pub fn hid_command(&self, command: ViaCommandId, bytes: Vec<u8>) -> Result<Vec<u8>> {
hid_command_on_device(&self.device, command, bytes)
}
pub fn hid_read(&self) -> Result<Vec<u8>> {
hid_read_on_device(&self.device)
}
pub fn hid_send(&self, bytes: Vec<u8>) -> Result<()> {
hid_send_on_device(&self.device, bytes)
}
pub fn get_protocol_version(&self) -> Result<u16> {
Ok(self.protocol_version)
}
pub fn get_layer_count(&self) -> Result<u8> {
match self.protocol_version {
version if version >= PROTOCOL_BETA => self
.hid_command(ViaCommandId::DynamicKeymapGetLayerCount, vec![])
.map(|val| val[1]),
_ => Ok(4),
}
}
pub fn get_key(&self, layer: Layer, row: Row, col: Column) -> Result<u16> {
self.hid_command(ViaCommandId::DynamicKeymapGetKeycode, vec![layer, row, col])
.map(|val| utils::shift_to_16_bit(val[4], val[5]))
}
pub fn set_key(&self, layer: Layer, row: Row, column: Column, val: u16) -> Result<u16> {
let val_bytes = utils::shift_from_16_bit(val);
let bytes = vec![layer, row, column, val_bytes.0, val_bytes.1];
self.hid_command(ViaCommandId::DynamicKeymapSetKeycode, bytes)
.map(|val| utils::shift_to_16_bit(val[4], val[5]))
}
pub fn read_raw_matrix(&self, matrix_info: MatrixInfo, layer: Layer) -> Result<Vec<u16>> {
match self.protocol_version {
version if version >= PROTOCOL_BETA => self.fast_read_raw_matrix(matrix_info, layer),
version if version == PROTOCOL_ALPHA => self.slow_read_raw_matrix(matrix_info, layer),
version => Err(Error::UnsupportedProtocol(version)),
}
}
fn get_keymap_buffer(&self, offset: u16, size: u8) -> Result<Vec<u8>> {
if size > DATA_BUFFER_SIZE as u8 {
return Err(Error::size_mismatch(
"read data size too large",
DATA_BUFFER_SIZE,
size as usize,
));
}
let offset_bytes = utils::shift_from_16_bit(offset);
self.hid_command(
ViaCommandId::DynamicKeymapGetBuffer,
vec![offset_bytes.0, offset_bytes.1, size],
)
.map(|val| val[4..(size as usize + 4)].to_vec())
}
fn fast_read_raw_matrix(&self, matrix_info: MatrixInfo, layer: Layer) -> Result<Vec<u16>> {
const MAX_KEYCODES_PARTIAL: usize = 14;
let length = matrix_info.rows as usize * matrix_info.cols as usize;
let buffer_len = length.div_ceil(MAX_KEYCODES_PARTIAL);
let mut remaining = length;
let mut result = Vec::new();
for _ in 0..buffer_len {
if remaining < MAX_KEYCODES_PARTIAL {
let val = self.get_keymap_buffer(
layer as u16 * length as u16 * 2 + 2 * (length - remaining) as u16,
(remaining * 2) as u8,
)?;
result.extend(val);
remaining = 0;
} else {
let val = self.get_keymap_buffer(
layer as u16 * length as u16 * 2 + 2 * (length - remaining) as u16,
(MAX_KEYCODES_PARTIAL * 2) as u8,
)?;
result.extend(val);
remaining -= MAX_KEYCODES_PARTIAL;
}
}
Ok(utils::shift_buffer_to_16_bit(&result))
}
fn slow_read_raw_matrix(&self, matrix_info: MatrixInfo, layer: Layer) -> Result<Vec<u16>> {
let length = matrix_info.rows as usize * matrix_info.cols as usize;
let mut res = Vec::new();
for i in 0..length {
let row = (i as u16 / matrix_info.cols as u16) as u8;
let col = (i as u16 % matrix_info.cols as u16) as u8;
res.push(self.get_key(layer, row, col)?);
}
Ok(res)
}
pub fn write_raw_matrix(&self, matrix_info: MatrixInfo, keymap: Vec<Vec<u16>>) -> Result<()> {
match self.protocol_version {
version if version >= PROTOCOL_BETA => self.fast_write_raw_matrix(keymap)?,
version if version == PROTOCOL_ALPHA => {
self.slow_write_raw_matrix(matrix_info, keymap)?
}
version => return Err(Error::UnsupportedProtocol(version)),
}
Ok(())
}
fn slow_write_raw_matrix(&self, matrix_info: MatrixInfo, keymap: Vec<Vec<u16>>) -> Result<()> {
for (layer_idx, layer) in keymap.iter().enumerate() {
for (key_idx, keycode) in layer.iter().enumerate() {
let row = (key_idx as u16 / matrix_info.cols as u16) as u8;
let col = (key_idx as u16 % matrix_info.cols as u16) as u8;
self.set_key(layer_idx as u8, row, col, *keycode)?;
}
}
Ok(())
}
fn fast_write_raw_matrix(&self, keymap: Vec<Vec<u16>>) -> Result<()> {
let data: Vec<u16> = keymap
.iter()
.flat_map(|layer| layer.iter().cloned())
.collect();
let shifted_data = utils::shift_buffer_from_16_bit(&data);
for offset in (0..shifted_data.len()).step_by(DATA_BUFFER_SIZE) {
let offset_bytes = utils::shift_from_16_bit(offset as u16);
let end = std::cmp::min(offset + DATA_BUFFER_SIZE, shifted_data.len());
let buffer = shifted_data[offset..end].to_vec();
let mut bytes = vec![offset_bytes.0, offset_bytes.1, buffer.len() as u8];
bytes.extend(buffer);
self.hid_command(ViaCommandId::DynamicKeymapSetBuffer, bytes)?;
}
Ok(())
}
pub fn get_keyboard_value(
&self,
command: KeyboardValue,
parameters: Vec<u8>,
result_length: usize,
) -> Result<Vec<u8>> {
match command {
KeyboardValue::FirmwareVersion | KeyboardValue::DeviceIndication
if self.protocol_version < PROTOCOL_V3 =>
{
return Err(Error::UnsupportedProtocol(self.protocol_version));
}
_ => {}
}
let parameters_length = parameters.len();
let mut bytes = vec![command as u8];
bytes.extend(parameters);
self.hid_command(ViaCommandId::GetKeyboardValue, bytes)
.map(|val| val[2 + parameters_length..2 + parameters_length + result_length].to_vec())
}
pub fn set_keyboard_value(&self, command: KeyboardValue, parameters: Vec<u8>) -> Result<()> {
match command {
KeyboardValue::FirmwareVersion | KeyboardValue::DeviceIndication
if self.protocol_version < PROTOCOL_V3 =>
{
return Err(Error::UnsupportedProtocol(self.protocol_version));
}
_ => {}
}
let mut bytes = vec![command as u8];
bytes.extend(parameters);
self.hid_command(ViaCommandId::SetKeyboardValue, bytes)
.map(|_| ())
}
pub fn get_encoder_value(&self, layer: Layer, id: u8, is_clockwise: bool) -> Result<u16> {
self.hid_command(
ViaCommandId::DynamicKeymapGetEncoder,
vec![layer, id, is_clockwise as u8],
)
.map(|val| utils::shift_to_16_bit(val[4], val[5]))
}
pub fn set_encoder_value(
&self,
layer: Layer,
id: u8,
is_clockwise: bool,
keycode: u16,
) -> Result<()> {
let keycode_bytes = utils::shift_from_16_bit(keycode);
let bytes = vec![
layer,
id,
is_clockwise as u8,
keycode_bytes.0,
keycode_bytes.1,
];
self.hid_command(ViaCommandId::DynamicKeymapSetEncoder, bytes)
.map(|_| ())
}
pub fn get_custom_menu_value(&self, command_bytes: Vec<u8>) -> Result<Vec<u8>> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedProtocol(self.protocol_version));
}
let command_length = command_bytes.len();
self.hid_command(ViaCommandId::CustomMenuGetValue, command_bytes)
.map(|val| val[0..command_length].to_vec())
}
pub fn set_custom_menu_value(&self, args: Vec<u8>) -> Result<()> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedProtocol(self.protocol_version));
}
self.hid_command(ViaCommandId::CustomMenuSetValue, args)
.map(|_| ())
}
pub fn save_custom_menu(&self, channel: u8) -> Result<()> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedProtocol(self.protocol_version));
}
let bytes = vec![channel];
self.hid_command(ViaCommandId::CustomMenuSave, bytes)
.map(|_| ())
}
pub fn get_backlight_brightness(&self) -> Result<u8> {
if self.protocol_version >= PROTOCOL_V3 {
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![
ViaChannelId::IdQmkBacklightChannel as u8,
ViaQmkBacklightValue::IdQmkBacklightBrightness as u8,
],
)
.map(|val| val[3])
} else {
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![ViaLightingValue::IdBacklightBrightness as u8],
)
.map(|val| val[2])
}
}
pub fn set_backlight_brightness(&self, brightness: u8) -> Result<()> {
if self.protocol_version >= PROTOCOL_V3 {
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![
ViaChannelId::IdQmkBacklightChannel as u8,
ViaQmkBacklightValue::IdQmkBacklightBrightness as u8,
brightness,
],
)
.map(|_| ())
} else {
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![ViaLightingValue::IdBacklightBrightness as u8, brightness],
)
.map(|_| ())
}
}
pub fn get_backlight_effect(&self) -> Result<u8> {
if self.protocol_version >= PROTOCOL_V3 {
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![
ViaChannelId::IdQmkBacklightChannel as u8,
ViaQmkBacklightValue::IdQmkBacklightEffect as u8,
],
)
.map(|val| val[3])
} else {
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![ViaLightingValue::IdBacklightEffect as u8],
)
.map(|val| val[2])
}
}
pub fn set_backlight_effect(&self, effect: u8) -> Result<()> {
if self.protocol_version >= PROTOCOL_V3 {
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![
ViaChannelId::IdQmkBacklightChannel as u8,
ViaQmkBacklightValue::IdQmkBacklightEffect as u8,
effect,
],
)
.map(|_| ())
} else {
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![ViaLightingValue::IdBacklightEffect as u8, effect],
)
.map(|_| ())
}
}
pub fn get_rgblight_brightness(&self) -> Result<u8> {
if self.protocol_version >= PROTOCOL_V3 {
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![
ViaChannelId::IdQmkRgblightChannel as u8,
ViaQmkRgblightValue::IdQmkRgblightBrightness as u8,
],
)
.map(|val| val[3])
} else {
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![ViaLightingValue::IdQmkRgblightBrightness as u8],
)
.map(|val| val[2])
}
}
pub fn set_rgblight_brightness(&self, brightness: u8) -> Result<()> {
if self.protocol_version >= PROTOCOL_V3 {
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![
ViaChannelId::IdQmkRgblightChannel as u8,
ViaQmkRgblightValue::IdQmkRgblightBrightness as u8,
brightness,
],
)
.map(|_| ())
} else {
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![ViaLightingValue::IdQmkRgblightBrightness as u8, brightness],
)
.map(|_| ())
}
}
pub fn get_rgblight_effect(&self) -> Result<u8> {
if self.protocol_version >= PROTOCOL_V3 {
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![
ViaChannelId::IdQmkRgblightChannel as u8,
ViaQmkRgblightValue::IdQmkRgblightEffect as u8,
],
)
.map(|val| val[3])
} else {
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![ViaLightingValue::IdQmkRgblightEffect as u8],
)
.map(|val| val[2])
}
}
pub fn set_rgblight_effect(&self, effect: u8) -> Result<()> {
if self.protocol_version >= PROTOCOL_V3 {
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![
ViaChannelId::IdQmkRgblightChannel as u8,
ViaQmkRgblightValue::IdQmkRgblightEffect as u8,
effect,
],
)
.map(|_| ())
} else {
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![ViaLightingValue::IdQmkRgblightEffect as u8, effect],
)
.map(|_| ())
}
}
pub fn get_rgblight_effect_speed(&self) -> Result<u8> {
if self.protocol_version >= PROTOCOL_V3 {
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![
ViaChannelId::IdQmkRgblightChannel as u8,
ViaQmkRgblightValue::IdQmkRgblightEffectSpeed as u8,
],
)
.map(|val| val[3])
} else {
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![ViaLightingValue::IdQmkRgblightEffectSpeed as u8],
)
.map(|val| val[2])
}
}
pub fn set_rgblight_effect_speed(&self, speed: u8) -> Result<()> {
if self.protocol_version >= PROTOCOL_V3 {
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![
ViaChannelId::IdQmkRgblightChannel as u8,
ViaQmkRgblightValue::IdQmkRgblightEffectSpeed as u8,
speed,
],
)
.map(|_| ())
} else {
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![ViaLightingValue::IdQmkRgblightEffectSpeed as u8, speed],
)
.map(|_| ())
}
}
pub fn get_rgblight_color(&self) -> Result<(u8, u8)> {
if self.protocol_version >= PROTOCOL_V3 {
return self
.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![
ViaChannelId::IdQmkRgblightChannel as u8,
ViaQmkRgblightValue::IdQmkRgblightColor as u8,
],
)
.map(|val| (val[3], val[4]));
}
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![ViaLightingValue::IdQmkRgblightColor as u8],
)
.map(|val| (val[2], val[3]))
}
pub fn set_rgblight_color(&self, hue: u8, sat: u8) -> Result<()> {
if self.protocol_version >= PROTOCOL_V3 {
return self
.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![
ViaChannelId::IdQmkRgblightChannel as u8,
ViaQmkRgblightValue::IdQmkRgblightColor as u8,
hue,
sat,
],
)
.map(|_| ());
}
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![ViaLightingValue::IdQmkRgblightColor as u8, hue, sat],
)
.map(|_| ())
}
pub fn get_rgb_matrix_brightness(&self) -> Result<u8> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("RGB matrix"));
}
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![
ViaChannelId::IdQmkRgbMatrixChannel as u8,
ViaQmkRgbMatrixValue::IdQmkRgbMatrixBrightness as u8,
],
)
.map(|val| val[3])
}
pub fn set_rgb_matrix_brightness(&self, brightness: u8) -> Result<()> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("RGB matrix"));
}
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![
ViaChannelId::IdQmkRgbMatrixChannel as u8,
ViaQmkRgbMatrixValue::IdQmkRgbMatrixBrightness as u8,
brightness,
],
)
.map(|_| ())
}
pub fn get_rgb_matrix_effect(&self) -> Result<u8> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("RGB matrix"));
}
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![
ViaChannelId::IdQmkRgbMatrixChannel as u8,
ViaQmkRgbMatrixValue::IdQmkRgbMatrixEffect as u8,
],
)
.map(|val| val[3])
}
pub fn set_rgb_matrix_effect(&self, effect: u8) -> Result<()> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("RGB matrix"));
}
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![
ViaChannelId::IdQmkRgbMatrixChannel as u8,
ViaQmkRgbMatrixValue::IdQmkRgbMatrixEffect as u8,
effect,
],
)
.map(|_| ())
}
pub fn get_rgb_matrix_effect_speed(&self) -> Result<u8> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("RGB matrix"));
}
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![
ViaChannelId::IdQmkRgbMatrixChannel as u8,
ViaQmkRgbMatrixValue::IdQmkRgbMatrixEffectSpeed as u8,
],
)
.map(|val| val[3])
}
pub fn set_rgb_matrix_effect_speed(&self, speed: u8) -> Result<()> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("RGB matrix"));
}
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![
ViaChannelId::IdQmkRgbMatrixChannel as u8,
ViaQmkRgbMatrixValue::IdQmkRgbMatrixEffectSpeed as u8,
speed,
],
)
.map(|_| ())
}
pub fn get_rgb_matrix_color(&self) -> Result<(u8, u8)> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("RGB matrix"));
}
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![
ViaChannelId::IdQmkRgbMatrixChannel as u8,
ViaQmkRgbMatrixValue::IdQmkRgbMatrixColor as u8,
],
)
.map(|val| (val[3], val[4]))
}
pub fn set_rgb_matrix_color(&self, hue: u8, sat: u8) -> Result<()> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("RGB matrix"));
}
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![
ViaChannelId::IdQmkRgbMatrixChannel as u8,
ViaQmkRgbMatrixValue::IdQmkRgbMatrixColor as u8,
hue,
sat,
],
)
.map(|_| ())
}
pub fn get_led_matrix_brightness(&self) -> Result<u8> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("LED matrix"));
}
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![
ViaChannelId::IdQmkLedMatrixChannel as u8,
ViaQmkLedMatrixValue::IdQmkLedMatrixBrightness as u8,
],
)
.map(|val| val[3])
}
pub fn set_led_matrix_brightness(&self, brightness: u8) -> Result<()> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("LED matrix"));
}
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![
ViaChannelId::IdQmkLedMatrixChannel as u8,
ViaQmkLedMatrixValue::IdQmkLedMatrixBrightness as u8,
brightness,
],
)
.map(|_| ())
}
pub fn get_led_matrix_effect(&self) -> Result<u8> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("LED matrix"));
}
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![
ViaChannelId::IdQmkLedMatrixChannel as u8,
ViaQmkLedMatrixValue::IdQmkLedMatrixEffect as u8,
],
)
.map(|val| val[3])
}
pub fn set_led_matrix_effect(&self, effect: u8) -> Result<()> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("LED matrix"));
}
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![
ViaChannelId::IdQmkLedMatrixChannel as u8,
ViaQmkLedMatrixValue::IdQmkLedMatrixEffect as u8,
effect,
],
)
.map(|_| ())
}
pub fn get_led_matrix_effect_speed(&self) -> Result<u8> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("LED matrix"));
}
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![
ViaChannelId::IdQmkLedMatrixChannel as u8,
ViaQmkLedMatrixValue::IdQmkLedMatrixEffectSpeed as u8,
],
)
.map(|val| val[3])
}
pub fn set_led_matrix_effect_speed(&self, speed: u8) -> Result<()> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("LED matrix"));
}
self.hid_command(
ViaCommandId::CustomMenuSetValue,
vec![
ViaChannelId::IdQmkLedMatrixChannel as u8,
ViaQmkLedMatrixValue::IdQmkLedMatrixEffectSpeed as u8,
speed,
],
)
.map(|_| ())
}
pub fn save_lighting(&self) -> Result<()> {
self.hid_command(ViaCommandId::CustomMenuSave, vec![])
.map(|_| ())
}
pub fn get_audio_enabled(&self) -> Result<bool> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("audio"));
}
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![
ViaChannelId::IdQmkAudioChannel as u8,
ViaQmkAudioValue::IdQmkAudioEnable as u8,
],
)
.map(|val| val[3] == 1)
}
pub fn set_audio_enabled(&self, enabled: bool) -> Result<()> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("audio"));
}
let bytes = vec![
ViaChannelId::IdQmkAudioChannel as u8,
ViaQmkAudioValue::IdQmkAudioEnable as u8,
enabled as u8,
];
self.hid_command(ViaCommandId::CustomMenuSetValue, bytes)
.map(|_| ())
}
pub fn get_audio_clicky_enabled(&self) -> Result<bool> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("audio"));
}
self.hid_command(
ViaCommandId::CustomMenuGetValue,
vec![
ViaChannelId::IdQmkAudioChannel as u8,
ViaQmkAudioValue::IdQmkAudioClickyEnable as u8,
],
)
.map(|val| val[3] == 1)
}
pub fn set_audio_clicky_enabled(&self, enabled: bool) -> Result<()> {
if self.protocol_version < PROTOCOL_V3 {
return Err(Error::UnsupportedFeature("audio"));
}
let bytes = vec![
ViaChannelId::IdQmkAudioChannel as u8,
ViaQmkAudioValue::IdQmkAudioClickyEnable as u8,
enabled as u8,
];
self.hid_command(ViaCommandId::CustomMenuSetValue, bytes)
.map(|_| ())
}
pub fn get_macro_count(&self) -> Result<u8> {
let bytes = vec![];
self.hid_command(ViaCommandId::DynamicKeymapMacroGetCount, bytes)
.map(|val| val[1])
}
fn get_macro_buffer_size(&self) -> Result<u16> {
let bytes = vec![];
self.hid_command(ViaCommandId::DynamicKeymapMacroGetBufferSize, bytes)
.map(|val| utils::shift_to_16_bit(val[1], val[2]))
}
pub fn get_macro_bytes(&self) -> Result<Vec<u8>> {
let macro_buffer_size = self.get_macro_buffer_size()? as usize;
let mut all_bytes = Vec::new();
for offset in (0..macro_buffer_size).step_by(DATA_BUFFER_SIZE) {
let offset_bytes = utils::shift_from_16_bit(offset as u16);
let remaining_bytes = macro_buffer_size - offset;
let bytes = vec![offset_bytes.0, offset_bytes.1, DATA_BUFFER_SIZE as u8];
let val = self.hid_command(ViaCommandId::DynamicKeymapMacroGetBuffer, bytes)?;
if remaining_bytes < DATA_BUFFER_SIZE {
all_bytes.extend(val[4..(4 + remaining_bytes)].to_vec())
} else {
all_bytes.extend(val[4..].to_vec())
}
}
Ok(all_bytes)
}
pub fn set_macro_bytes(&self, data: Vec<u8>) -> Result<()> {
let macro_buffer_size = self.get_macro_buffer_size()?;
let size = data.len();
if size > macro_buffer_size as usize {
return Err(Error::size_mismatch(
"macro data buffer overflow",
macro_buffer_size as usize,
size,
));
}
self.reset_macros()?;
let last_offset = macro_buffer_size - 1;
let last_offset_bytes = utils::shift_from_16_bit(last_offset);
self.hid_command(
ViaCommandId::DynamicKeymapMacroSetBuffer,
vec![last_offset_bytes.0, last_offset_bytes.1, 1, 0xff],
)?;
for offset in (0..data.len()).step_by(DATA_BUFFER_SIZE) {
let offset_bytes = utils::shift_from_16_bit(offset as u16);
let end = std::cmp::min(offset + DATA_BUFFER_SIZE, data.len());
let buffer = data[offset..end].to_vec();
let mut bytes = vec![offset_bytes.0, offset_bytes.1, buffer.len() as u8];
bytes.extend(buffer);
self.hid_command(ViaCommandId::DynamicKeymapMacroSetBuffer, bytes)?;
}
self.hid_command(
ViaCommandId::DynamicKeymapMacroSetBuffer,
vec![last_offset_bytes.0, last_offset_bytes.1, 1, 0x00],
)?;
Ok(())
}
pub fn reset_macros(&self) -> Result<()> {
let bytes = vec![];
self.hid_command(ViaCommandId::DynamicKeymapMacroReset, bytes)
.map(|_| ())
}
pub fn reset_eeprom(&self) -> Result<()> {
let bytes = vec![];
self.hid_command(ViaCommandId::EepromReset, bytes)
.map(|_| ())
}
pub fn jump_to_bootloader(&self) -> Result<()> {
let bytes = vec![];
self.hid_command(ViaCommandId::BootloaderJump, bytes)
.map(|_| ())
}
}