mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-01-22 09:23:19 +01:00
578 lines
17 KiB
Rust
578 lines
17 KiB
Rust
use log::warn;
|
|
use serde::{Deserialize, Serialize};
|
|
#[cfg(feature = "dbus")]
|
|
use zbus::zvariant::Type;
|
|
|
|
/// The `LedCode` used in setting up keyboard layouts is important because it
|
|
/// determines the idexing for an RGB value in the final USB packets (for
|
|
/// per-key addressable keyboards).
|
|
#[derive(Debug, Default, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
|
|
pub enum LedCode {
|
|
VolUp,
|
|
VolDown,
|
|
MicMute,
|
|
#[default]
|
|
RogApp,
|
|
RogFan,
|
|
Esc,
|
|
F1,
|
|
F2,
|
|
F3,
|
|
F4,
|
|
F5,
|
|
F6,
|
|
F7,
|
|
F8,
|
|
F9,
|
|
F10,
|
|
F11,
|
|
F12,
|
|
Del,
|
|
Tilde,
|
|
N1,
|
|
N2,
|
|
N3,
|
|
N4,
|
|
N5,
|
|
N6,
|
|
N7,
|
|
N8,
|
|
N9,
|
|
N0,
|
|
Hyphen,
|
|
Equals,
|
|
Backspace,
|
|
/// For keyboards where the backspace button has 3 LED
|
|
Backspace3_1,
|
|
Backspace3_2,
|
|
Backspace3_3,
|
|
Home,
|
|
Tab,
|
|
Q,
|
|
W,
|
|
E,
|
|
R,
|
|
T,
|
|
Y,
|
|
U,
|
|
I,
|
|
O,
|
|
P,
|
|
LBracket,
|
|
RBracket,
|
|
BackSlash,
|
|
PgUp,
|
|
Caps,
|
|
A,
|
|
S,
|
|
D,
|
|
F,
|
|
G,
|
|
H,
|
|
J,
|
|
K,
|
|
L,
|
|
SemiColon,
|
|
Quote,
|
|
Return,
|
|
/// For keyboards where the return button has 3 LED
|
|
Return3_1,
|
|
Return3_2,
|
|
Return3_3,
|
|
PgDn,
|
|
LShift,
|
|
/// For keyboards where the left shift button has 3 LED
|
|
LShift3_1,
|
|
LShift3_2,
|
|
LShift3_3,
|
|
Z,
|
|
X,
|
|
C,
|
|
V,
|
|
B,
|
|
N,
|
|
M,
|
|
Comma,
|
|
Period,
|
|
FwdSlash,
|
|
Star,
|
|
NumPadDel,
|
|
NumPadPlus,
|
|
NumPadEnter,
|
|
NumPadPause,
|
|
NumPadPrtSc,
|
|
NumPadHome,
|
|
NumLock,
|
|
Rshift,
|
|
Rshift3_1,
|
|
Rshift3_2,
|
|
Rshift3_3,
|
|
End,
|
|
LCtrl,
|
|
LFn,
|
|
Meta,
|
|
LAlt,
|
|
Spacebar,
|
|
/// For keyboards where the spacebar button has 5 LED
|
|
Spacebar5_1,
|
|
Spacebar5_2,
|
|
Spacebar5_3,
|
|
Spacebar5_4,
|
|
Spacebar5_5,
|
|
Pause,
|
|
RAlt,
|
|
PrtSc,
|
|
RCtrl,
|
|
Up,
|
|
Down,
|
|
Left,
|
|
Right,
|
|
RFn,
|
|
MediaPlay,
|
|
MediaStop,
|
|
MediaNext,
|
|
MediaPrev,
|
|
LidLogo,
|
|
LidLeft,
|
|
LidRight,
|
|
/// Used by per-key and multizoned
|
|
LightbarRight,
|
|
/// Used by per-key and multizoned
|
|
LightbarRightCorner,
|
|
/// Used by per-key and multizoned
|
|
LightbarRightBottom,
|
|
/// Used by per-key and multizoned
|
|
LightbarLeftBottom,
|
|
/// Used by per-key and multizoned
|
|
LightbarLeftCorner,
|
|
/// Used by per-key and multizoned
|
|
LightbarLeft,
|
|
/// Use if the keyboard supports only a single zone. This zone uses the same
|
|
/// packet data as the `Zoned*` below
|
|
SingleZone,
|
|
/// Use if the keyboard supports 4 zones, this is the left zone
|
|
ZonedKbLeft,
|
|
/// Use if the keyboard supports 4 zones, this is the left-center zone
|
|
ZonedKbLeftMid,
|
|
/// Use if the keyboard supports 4 zones, this is the right-center zone
|
|
ZonedKbRightMid,
|
|
/// Use if the keyboard supports 4 zones, this is the right zone
|
|
ZonedKbRight,
|
|
/// To be ignored by effects
|
|
Spacing,
|
|
/// To be ignored by effects
|
|
Blocking,
|
|
}
|
|
|
|
impl LedCode {
|
|
pub fn is_placeholder(&self) -> bool {
|
|
matches!(self, Self::Spacing | Self::Blocking)
|
|
}
|
|
|
|
pub fn is_keyboard_zone(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
Self::ZonedKbLeft | Self::ZonedKbLeftMid | Self::ZonedKbRightMid | Self::ZonedKbRight
|
|
)
|
|
}
|
|
|
|
pub fn is_lightbar_zone(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
Self::LightbarLeft
|
|
| Self::LightbarLeftCorner
|
|
| Self::LightbarLeftBottom
|
|
| Self::LightbarRightBottom
|
|
| Self::LightbarRightCorner
|
|
| Self::LightbarRight
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Represents the per-key raw USB packets
|
|
pub type UsbPackets = Vec<Vec<u8>>;
|
|
|
|
/// A `UsbPackets` contains all data to change the full set of keyboard
|
|
/// key colours individually.
|
|
///
|
|
/// Each row of the internal array is a full HID packet that can be sent
|
|
/// to the keyboard EC. One row controls one group of keys, these keys are not
|
|
/// necessarily all on the same row of the keyboard, with some splitting between
|
|
/// two rows.
|
|
#[cfg_attr(feature = "dbus", derive(Type))]
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
pub struct LedUsbPackets {
|
|
/// The packet data used to send data to the USB keyboard
|
|
usb_packets: UsbPackets,
|
|
/// Wether or not this packet collection is zoned. The determines which
|
|
/// starting bytes are used and what the indexing is for lightbar RGB
|
|
/// colours
|
|
zoned: bool,
|
|
}
|
|
|
|
impl Default for LedUsbPackets {
|
|
fn default() -> Self {
|
|
Self::new_per_key()
|
|
}
|
|
}
|
|
|
|
impl LedUsbPackets {
|
|
/// Set up a series of per-key packets. This includes setting all the
|
|
/// required starting bytes per packet, but does not set any colours.
|
|
///
|
|
/// These packets will not work with per-zone keyboards
|
|
pub fn new_per_key() -> Self {
|
|
let mut set = vec![vec![0u8; 64]; 11];
|
|
// set[0].copy_from_slice(&KeyColourArray::get_init_msg());
|
|
for (count, row) in set.iter_mut().enumerate() {
|
|
row[0] = 0x5d; // Report ID
|
|
row[1] = 0xbc; // Mode = custom??, 0xb3 is builtin
|
|
row[2] = 0x00;
|
|
row[3] = 0x01; // ??
|
|
row[4] = 0x01; // ??, 4,5,6 are normally RGB for builtin mode colours
|
|
row[5] = 0x01; // ??
|
|
row[6] = (count as u8) << 4; // Key group
|
|
if count == 10 {
|
|
row[7] = 0x08; // 0b00001000
|
|
} else {
|
|
row[7] = 0x10; // 0b00010000 addressing? flips for group a0
|
|
}
|
|
row[8] = 0x00;
|
|
}
|
|
Self {
|
|
usb_packets: set,
|
|
zoned: false,
|
|
}
|
|
}
|
|
|
|
/// Create new zoned packets. Although the result is a nested `Vec` only the
|
|
/// first vector is available. The final packet is slightly different
|
|
/// for single-zoned compared to multizoned.
|
|
///
|
|
/// This packet will not work with per-key keyboards
|
|
///
|
|
/// Wireshark captures show:
|
|
/// ```ignore
|
|
/// 5d,bc,01,01,00,00,00,00,00,ff,00,00, RED, single zone
|
|
/// 5d,bc,01,01,04,00,00,00,00,ff,00,00, RED, multizone
|
|
/// ```
|
|
pub fn new_zoned(multizoned: bool) -> Self {
|
|
let mut pkt = vec![0u8; 64];
|
|
pkt[0] = 0x5d; // Report ID
|
|
pkt[1] = 0xbc; // Mode = custom??, 0xb3 is builtin
|
|
pkt[2] = 0x01;
|
|
pkt[3] = 0x01; // ??
|
|
if !multizoned {
|
|
pkt[4] = 0x00; // This doesn't actually seem to matter on this
|
|
// keyboard?
|
|
} else {
|
|
pkt[4] = 0x04; // ??, 4,5,6 are normally RGB for builtin mode
|
|
// colours
|
|
}
|
|
Self {
|
|
usb_packets: vec![pkt],
|
|
zoned: true,
|
|
}
|
|
}
|
|
|
|
/// Initialise and clear the keyboard for custom effects, this must be done
|
|
/// for every time mode switches from builtin to custom
|
|
#[inline]
|
|
pub const fn get_init_msg() -> [u8; 64] {
|
|
let mut init = [0u8; 64];
|
|
init[0] = 0x5d; // Report ID
|
|
init[1] = 0xbc; // Mode = custom??, 0xb3 is builtin
|
|
init
|
|
}
|
|
|
|
/// Set the RGB colour of an `LedCode`
|
|
#[inline]
|
|
pub fn set(&mut self, key: LedCode, r: u8, g: u8, b: u8) {
|
|
if let Some(c) = self.rgb_for_led_code(key) {
|
|
c[0] = r;
|
|
c[1] = g;
|
|
c[2] = b;
|
|
}
|
|
}
|
|
|
|
/// Indexes in to `UsbPackets` at the correct row and column
|
|
/// to set a series of three bytes to the chosen R,G,B values
|
|
///
|
|
/// Indexing is different for `zoned` and assumes that only one packet is
|
|
/// generated for all the zones
|
|
fn rgb_for_led_code(&mut self, led_code: LedCode) -> Option<&mut [u8]> {
|
|
let zoned = self.zoned;
|
|
// Tuples are indexes in to array
|
|
#[allow(clippy::match_same_arms)]
|
|
let (row, col) = match led_code {
|
|
LedCode::VolDown => (0, 15),
|
|
LedCode::VolUp => (0, 18),
|
|
LedCode::MicMute => (0, 21),
|
|
LedCode::RogApp => (0, 24),
|
|
//
|
|
LedCode::Esc => (1, 24),
|
|
LedCode::F1 => (1, 30),
|
|
LedCode::F2 => (1, 33),
|
|
LedCode::F3 => (1, 36),
|
|
LedCode::F4 => (1, 39),
|
|
LedCode::F5 => (1, 45),
|
|
LedCode::F6 => (1, 48),
|
|
LedCode::F7 => (1, 51),
|
|
LedCode::F8 => (1, 54),
|
|
//
|
|
LedCode::F9 => (2, 12),
|
|
LedCode::F10 => (2, 15),
|
|
LedCode::F11 => (2, 18),
|
|
LedCode::F12 => (2, 21),
|
|
LedCode::Del => (2, 24),
|
|
LedCode::Tilde => (2, 39),
|
|
LedCode::N1 => (2, 42),
|
|
LedCode::N2 => (2, 45),
|
|
LedCode::N3 => (2, 48),
|
|
LedCode::N4 => (2, 51),
|
|
LedCode::N5 => (2, 54),
|
|
//
|
|
LedCode::N6 => (3, 9),
|
|
LedCode::N7 => (3, 12),
|
|
LedCode::N8 => (3, 15),
|
|
LedCode::N9 => (3, 18),
|
|
LedCode::N0 => (3, 21),
|
|
LedCode::Hyphen => (3, 24),
|
|
LedCode::Equals => (3, 27),
|
|
LedCode::Backspace3_1 => (3, 30),
|
|
LedCode::Backspace3_2 => (3, 33),
|
|
LedCode::Backspace3_3 => (3, 36),
|
|
LedCode::Home => (3, 39),
|
|
LedCode::Tab => (3, 54),
|
|
//
|
|
LedCode::Q => (4, 9),
|
|
LedCode::W => (4, 12),
|
|
LedCode::E => (4, 15),
|
|
LedCode::R => (4, 18),
|
|
LedCode::T => (4, 21),
|
|
LedCode::Y => (4, 24),
|
|
LedCode::U => (4, 27),
|
|
LedCode::I => (4, 30),
|
|
LedCode::O => (4, 33),
|
|
LedCode::P => (4, 36),
|
|
LedCode::LBracket => (4, 39),
|
|
LedCode::RBracket => (4, 42),
|
|
LedCode::BackSlash => (4, 45),
|
|
LedCode::PgUp => (4, 54),
|
|
//
|
|
LedCode::Caps => (5, 21),
|
|
LedCode::A => (5, 24),
|
|
LedCode::S => (5, 27),
|
|
LedCode::D => (5, 30),
|
|
LedCode::F => (5, 33),
|
|
LedCode::G => (5, 36),
|
|
LedCode::H => (5, 39),
|
|
LedCode::J => (5, 42),
|
|
LedCode::K => (5, 45),
|
|
LedCode::L => (5, 48),
|
|
LedCode::SemiColon => (5, 51),
|
|
LedCode::Quote => (5, 54),
|
|
//
|
|
LedCode::Return => (6, 9),
|
|
LedCode::Return3_1 => (6, 12),
|
|
LedCode::Return3_2 => (6, 15),
|
|
LedCode::Return3_3 => (6, 18),
|
|
LedCode::PgDn => (6, 21),
|
|
LedCode::LShift => (6, 36),
|
|
// TODO: Find correct locations
|
|
LedCode::LShift3_1 => (6, 36),
|
|
LedCode::LShift3_2 => (6, 36),
|
|
LedCode::LShift3_3 => (6, 36),
|
|
LedCode::Z => (6, 42),
|
|
LedCode::X => (6, 45),
|
|
LedCode::C => (6, 48),
|
|
LedCode::V => (6, 51),
|
|
LedCode::B => (6, 54),
|
|
//
|
|
LedCode::N => (7, 9),
|
|
LedCode::M => (7, 12),
|
|
LedCode::Comma => (7, 15),
|
|
LedCode::Period => (7, 18),
|
|
LedCode::FwdSlash => (7, 21),
|
|
LedCode::Rshift => (7, 24),
|
|
LedCode::Rshift3_1 => (7, 27),
|
|
LedCode::Rshift3_2 => (7, 30),
|
|
LedCode::Rshift3_3 => (7, 33),
|
|
LedCode::End => (7, 36),
|
|
LedCode::LCtrl => (7, 51),
|
|
LedCode::LFn => (7, 54),
|
|
//
|
|
LedCode::Meta => (8, 9),
|
|
LedCode::LAlt => (8, 12),
|
|
LedCode::Spacebar5_1 => (8, 15),
|
|
LedCode::Spacebar5_2 => (8, 18),
|
|
LedCode::Spacebar5_3 => (8, 21),
|
|
LedCode::Spacebar5_4 => (8, 24),
|
|
LedCode::Spacebar5_5 => (8, 27),
|
|
LedCode::RAlt => (8, 30),
|
|
LedCode::PrtSc => (8, 33),
|
|
LedCode::RCtrl => (8, 36),
|
|
LedCode::Up => (8, 42),
|
|
LedCode::RFn => (8, 51),
|
|
//
|
|
LedCode::Left => (9, 54),
|
|
//
|
|
LedCode::Down => (10, 9),
|
|
LedCode::Right => (10, 12),
|
|
LedCode::LidLogo => (11, 9),
|
|
LedCode::LidLeft => (11, 36),
|
|
LedCode::LidRight => (11, 39),
|
|
//
|
|
LedCode::SingleZone | LedCode::ZonedKbLeft => (0, 9),
|
|
LedCode::ZonedKbLeftMid => (0, 12),
|
|
LedCode::ZonedKbRightMid => (0, 15),
|
|
LedCode::ZonedKbRight => (0, 18),
|
|
LedCode::LightbarRight => if zoned {(0, 27)} else { (11, 15)},
|
|
LedCode::LightbarRightCorner => if zoned {(0, 30)} else {(11, 18)},
|
|
LedCode::LightbarRightBottom => if zoned {(0, 33)} else{(11, 21)},
|
|
LedCode::LightbarLeftBottom => if zoned {(0, 36)} else{(11, 24)},
|
|
LedCode::LightbarLeftCorner => if zoned {(0, 39)} else{(11, 27)},
|
|
LedCode::LightbarLeft => if zoned {(0, 42)} else{(11, 30)},
|
|
//
|
|
LedCode::Spacing
|
|
| LedCode::Blocking
|
|
// TODO: the addressing of the following
|
|
| LedCode::MediaPlay
|
|
| LedCode::MediaStop
|
|
| LedCode::MediaPrev
|
|
| LedCode::MediaNext
|
|
| LedCode::Pause
|
|
| LedCode::NumLock
|
|
| LedCode::Star
|
|
| LedCode::NumPadDel
|
|
| LedCode::NumPadPlus
|
|
| LedCode::NumPadEnter
|
|
| LedCode::NumPadPause
|
|
| LedCode::NumPadPrtSc
|
|
| LedCode::NumPadHome
|
|
| LedCode::RogFan
|
|
| LedCode::Spacebar
|
|
| LedCode::Backspace => return None,
|
|
};
|
|
|
|
if self.zoned && row > 0 {
|
|
warn!(
|
|
"LedCode {led_code:?} for zoned is not correct or out of Zone range. Setting to 0",
|
|
);
|
|
return None;
|
|
}
|
|
|
|
Some(&mut self.usb_packets[row][col..=col + 2])
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get(&self) -> UsbPackets {
|
|
self.usb_packets.clone()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_ref(&self) -> &UsbPackets {
|
|
&self.usb_packets
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_mut(&mut self) -> &mut UsbPackets {
|
|
&mut self.usb_packets
|
|
}
|
|
}
|
|
|
|
impl From<LedUsbPackets> for UsbPackets {
|
|
fn from(k: LedUsbPackets) -> Self {
|
|
k.usb_packets
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::advanced::{LedCode, LedUsbPackets, UsbPackets};
|
|
|
|
macro_rules! colour_check_zoned {
|
|
($zone:expr, $pkt_idx_start:expr) => {
|
|
let mut zone = LedUsbPackets::new_zoned(true);
|
|
let c = zone.rgb_for_led_code($zone).unwrap();
|
|
c[0] = 255;
|
|
c[1] = 255;
|
|
c[2] = 255;
|
|
|
|
let pkt: UsbPackets = zone.into();
|
|
assert_eq!(pkt[0][$pkt_idx_start], 0xff);
|
|
assert_eq!(pkt[0][$pkt_idx_start + 1], 0xff);
|
|
assert_eq!(pkt[0][$pkt_idx_start + 2], 0xff);
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn zone_to_packet_check() {
|
|
let zone = LedUsbPackets::new_zoned(true);
|
|
let pkt: UsbPackets = zone.into();
|
|
assert_eq!(pkt[0][0], 0x5d);
|
|
assert_eq!(pkt[0][1], 0xbc);
|
|
assert_eq!(pkt[0][2], 0x01);
|
|
assert_eq!(pkt[0][3], 0x01);
|
|
assert_eq!(pkt[0][4], 0x04);
|
|
|
|
colour_check_zoned!(LedCode::ZonedKbLeft, 9);
|
|
colour_check_zoned!(LedCode::ZonedKbLeftMid, 12);
|
|
colour_check_zoned!(LedCode::ZonedKbRightMid, 15);
|
|
colour_check_zoned!(LedCode::ZonedKbRight, 18);
|
|
|
|
colour_check_zoned!(LedCode::LightbarRight, 27);
|
|
colour_check_zoned!(LedCode::LightbarRightCorner, 30);
|
|
colour_check_zoned!(LedCode::LightbarRightBottom, 33);
|
|
colour_check_zoned!(LedCode::LightbarLeftBottom, 36);
|
|
colour_check_zoned!(LedCode::LightbarLeftCorner, 39);
|
|
colour_check_zoned!(LedCode::LightbarLeft, 42);
|
|
}
|
|
|
|
#[test]
|
|
fn perkey_to_packet_check() {
|
|
let per_key = LedUsbPackets::new_per_key();
|
|
let pkt: UsbPackets = per_key.into();
|
|
assert_eq!(pkt[0][0], 0x5d);
|
|
assert_eq!(pkt[0][1], 0xbc);
|
|
assert_eq!(pkt[0][2], 0x00);
|
|
assert_eq!(pkt[0][3], 0x01);
|
|
assert_eq!(pkt[0][4], 0x01);
|
|
assert_eq!(pkt[0][5], 0x01);
|
|
|
|
let mut per_key = LedUsbPackets::new_per_key();
|
|
let c = per_key.rgb_for_led_code(LedCode::D).unwrap();
|
|
c[0] = 255;
|
|
c[1] = 255;
|
|
c[2] = 255;
|
|
let c = per_key.rgb_for_led_code(LedCode::O).unwrap();
|
|
c[0] = 255;
|
|
c[1] = 255;
|
|
c[2] = 255;
|
|
let c = per_key.rgb_for_led_code(LedCode::N0).unwrap();
|
|
c[0] = 255;
|
|
c[1] = 255;
|
|
c[2] = 255;
|
|
let c = per_key.rgb_for_led_code(LedCode::M).unwrap();
|
|
c[0] = 255;
|
|
c[1] = 255;
|
|
c[2] = 255;
|
|
|
|
let pkt: UsbPackets = per_key.into();
|
|
assert_eq!(pkt[5][30], 0xff); // D, red
|
|
assert_eq!(pkt[5][31], 0xff); // D
|
|
assert_eq!(pkt[5][32], 0xff); // D
|
|
assert_eq!(pkt[5][33], 0x00); // D
|
|
|
|
assert_eq!(pkt[4][33], 0xff); // O, red
|
|
assert_eq!(pkt[4][34], 0xff); // O
|
|
assert_eq!(pkt[4][35], 0xff); // O
|
|
assert_eq!(pkt[4][36], 0x00); // O
|
|
|
|
assert_eq!(pkt[7][12], 0xff); // M, red
|
|
assert_eq!(pkt[7][13], 0xff); // M
|
|
assert_eq!(pkt[7][14], 0xff); // M
|
|
assert_eq!(pkt[7][15], 0x00); // M
|
|
}
|
|
}
|