Files
asusctl/rog-aura/src/advanced.rs
2023-01-16 13:23:30 +13:00

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
}
}