From ccefd301020703467dd54c20c5694584b01c414f Mon Sep 17 00:00:00 2001 From: Luke Date: Wed, 22 Apr 2020 14:45:49 +1200 Subject: [PATCH] Further clean up. Prep for trialling per-key led set --- README.md | 9 +- src/aura.rs | 92 ++++++++++++++++- src/config.rs | 13 +-- src/core.rs | 99 ++++++++++++++---- src/laptops.rs | 244 --------------------------------------------- src/lib.rs | 2 +- src/main.rs | 3 +- src/virt_device.rs | 33 +++++- 8 files changed, 212 insertions(+), 283 deletions(-) delete mode 100644 src/laptops.rs diff --git a/README.md b/README.md index 0f19410a..c570434c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ rog-core is a utility for Linux to control many aspects (eventually) of the ASUS ROG laptops like the Zephyrus GX502GW. -The laptop I currently have is the GX502RW and so I'll be using that for the basis of this app. If I get wireshark captures from others with different ROG laptops then I should be able to add something like laptop and feature detection. +The laptop I currently have is the GX502RW and so I'll be using that for the basis of this app. If I get wireshark captures from others with different ROG laptops then I should be able to add them. I'm now looking at the kernel source to see if I can add the inputs correctly so they show up as proper evdev events. @@ -101,9 +101,12 @@ First do `lsusb |grep 0b05` and check the part after `0b05:`, output looks like: Bus 001 Device 005: ID 0b05:1866 ASUSTek Computer, Inc. N-KEY Device ``` -Then do `lsusb -vd 0b05:1866 > ~/laptop_info` and give that to me. +Then do `sudo lsusb -vd 0b05:1866 > ~/laptop_info` and give that to me. -`cat /sys/class/dmi/id/product_name` is the other I'm interested in for the sake of information. +Also required: +- `cat /sys/class/dmi/id/product_name` +- `cat /sys/class/dmi/id/product_family` +- `cat /sys/class/dmi/id/board_name` ## License diff --git a/src/aura.rs b/src/aura.rs index 35e92bbf..fe9981d9 100644 --- a/src/aura.rs +++ b/src/aura.rs @@ -2,6 +2,38 @@ use crate::cli_options::*; use crate::core::LED_MSG_LEN; use serde_derive::{Deserialize, Serialize}; +/// Writes aout the correct byte string for brightness +/// +/// The HID descriptor looks like: +/// +/// ``` +/// 0x06, 0x31, 0xFF, // Usage Page (Vendor Defined 0xFF31) +/// 0x09, 0x76, // Usage (0x76) +/// 0xA1, 0x01, // Collection (Application) +/// 0x85, 0x5A, // Report ID (90) +/// 0x19, 0x00, // Usage Minimum (0x00) +/// 0x2A, 0xFF, 0x00, // Usage Maximum (0xFF) +/// 0x15, 0x00, // Logical Minimum (0) +/// 0x26, 0xFF, 0x00, // Logical Maximum (255) +/// 0x75, 0x08, // Report Size (8) +/// 0x95, 0x05, // Report Count (5) +/// 0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) +/// 0x19, 0x00, // Usage Minimum (0x00) +/// 0x2A, 0xFF, 0x00, // Usage Maximum (0xFF) +/// 0x15, 0x00, // Logical Minimum (0) +/// 0x26, 0xFF, 0x00, // Logical Maximum (255) +/// 0x75, 0x08, // Report Size (8) +/// 0x95, 0x3F, // Report Count (63) +/// 0xB1, 0x00, // Feature (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) +/// 0xC0, // End Collection +/// ``` +pub fn aura_brightness_bytes(brightness: u8) -> [u8; 17] { + // TODO: check brightness range + [ + 0x5A, 0xBA, 0xC5, 0xC4, brightness, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ] +} + /// Parses `SetAuraBuiltin` in to packet data /// /// Byte structure: @@ -44,6 +76,38 @@ use serde_derive::{Deserialize, Serialize}; /// - 0x03 = downwards /// /// Bytes 10, 11, 12 are Red, Green, Blue for second colour if mode supports it +/// +/// The HID descriptor looks like: +/// ``` +/// 0x06, 0x31, 0xFF, // Usage Page (Vendor Defined 0xFF31) +/// 0x09, 0x79, // Usage (0x79) +/// 0xA1, 0x01, // Collection (Application) +/// 0x85, 0x5D, // Report ID (93) +/// 0x19, 0x00, // Usage Minimum (0x00) +/// 0x2A, 0xFF, 0x00, // Usage Maximum (0xFF) +/// 0x15, 0x00, // Logical Minimum (0) +/// 0x26, 0xFF, 0x00, // Logical Maximum (255) +/// 0x75, 0x08, // Report Size (8) +/// 0x95, 0x1F, // Report Count (31) +/// 0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) +/// 0x19, 0x00, // Usage Minimum (0x00) +/// 0x2A, 0xFF, 0x00, // Usage Maximum (0xFF) +/// 0x15, 0x00, // Logical Minimum (0) +/// 0x26, 0xFF, 0x00, // Logical Maximum (255) +/// 0x75, 0x08, // Report Size (8) +/// 0x95, 0x3F, // Report Count (63) +/// 0x91, 0x00, // Output (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) +/// 0x19, 0x00, // Usage Minimum (0x00) +/// 0x2A, 0xFF, 0x00, // Usage Maximum (0xFF) +/// 0x15, 0x00, // Logical Minimum (0) +/// 0x26, 0xFF, 0x00, // Logical Maximum (255) +/// 0x75, 0x08, // Report Size (8) +/// 0x95, 0x3F, // Report Count (63) +/// 0xB1, 0x00, // Feature (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) +/// 0xC0, // End Collection +/// ``` +/// +/// This descriptor is also used for the per-key LED settings impl From for [u8; LED_MSG_LEN] { fn from(mode: SetAuraBuiltin) -> Self { let mut msg = [0u8; LED_MSG_LEN]; @@ -189,7 +253,7 @@ impl Default for BuiltInModeBytes { } } -#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] pub enum BuiltInModeByte { Stable = 0x00, Breathe = 0x01, @@ -229,3 +293,29 @@ impl From for BuiltInModeByte { } } } + +impl From<&u8> for BuiltInModeByte { + fn from(byte: &u8) -> Self { + Self::from(*byte) + } +} + +impl From for u8 { + fn from(byte: BuiltInModeByte) -> Self { + match byte { + BuiltInModeByte::Stable => 0x00, + BuiltInModeByte::Breathe => 0x01, + BuiltInModeByte::Cycle => 0x02, + BuiltInModeByte::Rainbow => 0x03, + BuiltInModeByte::Rain => 0x04, + BuiltInModeByte::Random => 0x05, + BuiltInModeByte::Highlight => 0x06, + BuiltInModeByte::Laser => 0x07, + BuiltInModeByte::Ripple => 0x08, + BuiltInModeByte::Pulse => 0x0a, + BuiltInModeByte::ThinZoomy => 0x0b, + BuiltInModeByte::WideZoomy => 0x0c, + BuiltInModeByte::None => 0xff, + } + } +} diff --git a/src/config.rs b/src/config.rs index db18b94e..0727f286 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,7 +9,7 @@ pub static CONFIG_PATH: &'static str = "/etc/rogcore.conf"; pub struct Config { pub brightness: u8, pub current_mode: [u8; 4], - builtin_modes: BuiltInModeBytes, + pub builtin_modes: BuiltInModeBytes, } impl Config { @@ -53,15 +53,4 @@ impl Config { self.builtin_modes.set_field_from(bytes); } } - - pub fn get_current(&mut self) -> Option> { - let bytes = self.current_mode; - if bytes[0] == 0x5d && bytes[1] == 0xb3 { - return self - .builtin_modes - .get_field_from(bytes[3]) - .map(|b| b.to_vec()); - } - None - } } diff --git a/src/core.rs b/src/core.rs index 5ad2f625..e01ca3ff 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,7 +1,11 @@ // Return show-stopping errors, otherwise map error to a log level use crate::{ - aura::BuiltInModeByte, config::Config, error::AuraError, laptops::*, virt_device::VirtKeys, + aura::{aura_brightness_bytes, BuiltInModeByte}, + config::Config, + error::AuraError, + laptops::*, + virt_device::VirtKeys, }; use aho_corasick::AhoCorasick; use gumdrop::Options; @@ -82,14 +86,6 @@ impl RogCore { &mut self.virt_keys } - pub(crate) fn config(&self) -> &Config { - &self.config - } - - pub(crate) fn config_mut(&mut self) -> &mut Config { - &mut self.config - } - fn get_device( vendor: u16, product: u16, @@ -210,7 +206,7 @@ impl RogCore { } } - pub fn aura_set_and_save( + pub(crate) fn aura_set_and_save( &mut self, supported_modes: &[BuiltInModeByte], bytes: &[u8], @@ -226,6 +222,82 @@ impl RogCore { warn!("{:?} not supported", BuiltInModeByte::from(mode)); Err(AuraError::NotSupported) } + + pub(crate) fn aura_bright_inc( + &mut self, + supported_modes: &[BuiltInModeByte], + max_bright: u8, + ) -> Result<(), AuraError> { + let mut bright = self.config.brightness; + if bright < max_bright { + bright += 1; + self.config.brightness = bright; + } + let bytes = aura_brightness_bytes(bright); + self.aura_set_and_save(supported_modes, &bytes)?; + Ok(()) + } + + pub(crate) fn aura_bright_dec( + &mut self, + supported_modes: &[BuiltInModeByte], + min_bright: u8, + ) -> Result<(), AuraError> { + let mut bright = self.config.brightness; + if bright > min_bright { + bright -= 1; + self.config.brightness = bright; + } + let bytes = aura_brightness_bytes(bright); + self.aura_set_and_save(supported_modes, &bytes)?; + Ok(()) + } + + /// Select next Aura effect + /// + /// If the current effect is the last one then the effect selected wraps around to the first. + pub(crate) fn aura_mode_next( + &mut self, + supported_modes: &[BuiltInModeByte], + ) -> Result<(), AuraError> { + let mode_curr = self.config.current_mode[3]; + let idx = supported_modes.binary_search(&mode_curr.into()).unwrap(); + let idx_next = if idx < supported_modes.len() - 1 { + idx + 1 + } else { + 0 + }; + let mode_next = self + .config + .builtin_modes + .get_field_from(supported_modes[idx_next].into()) + .unwrap() + .to_owned(); + self.aura_set_and_save(supported_modes, &mode_next) + } + + /// Select previous Aura effect + /// + /// If the current effect is the first one then the effect selected wraps around to the last. + pub(crate) fn aura_mode_prev( + &mut self, + supported_modes: &[BuiltInModeByte], + ) -> Result<(), AuraError> { + let mode_curr = self.config.current_mode[3]; + let idx = supported_modes.binary_search(&mode_curr.into()).unwrap(); + let idx_next = if idx > 0 { + idx - 1 + } else { + supported_modes.len() - 1 + }; + let mode_next = self + .config + .builtin_modes + .get_field_from(supported_modes[idx_next].into()) + .unwrap() + .to_owned(); + self.aura_set_and_save(supported_modes, &mode_next) + } } pub(crate) struct Backlight { @@ -308,10 +380,3 @@ impl FromStr for LedBrightness { } } } - -pub fn aura_brightness_bytes(brightness: u8) -> [u8; 17] { - // TODO: check brightness range - [ - 0x5A, 0xBA, 0xC5, 0xC4, brightness, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ] -} diff --git a/src/laptops.rs b/src/laptops.rs deleted file mode 100644 index b7677f3f..00000000 --- a/src/laptops.rs +++ /dev/null @@ -1,244 +0,0 @@ -use crate::aura::BuiltInModeByte; -use crate::core::{aura_brightness_bytes, Backlight, RogCore}; -use crate::error::AuraError; -use crate::virt_device::ConsumerKeys; -//use keycode::{KeyMap, KeyMappingId, KeyState, KeyboardState}; -use log::info; - -pub(crate) fn match_laptop() -> Box { - let dmi = sysfs_class::DmiId::default(); - let board_name = dmi.board_name().unwrap(); - match board_name.as_str() { - // The hell does it have a \n for anyway? - "GX502GW\n" => Box::new(LaptopGX502GW::new()), - _ => { - panic!("could not match laptop"); - } - } -} - -/// All laptop models should implement this trait. The role of a `Laptop` is to -/// "drive" the `RogCore`. -/// -/// `do_hotkey_action` is passed the byte that a hotkey emits, and is expected to -/// perform whichever action matches that. For now the only key bytes passed in are -/// the ones which match `byte[0] == hotkey_group_byte`. On the GX502GW the keyboard -/// has 3 explicit groups: main, vol+media, and the ones that the Linux kernel doesn't -/// map. -pub(crate) trait Laptop { - fn board_name(&self) -> &str; - fn prod_family(&self) -> &str; - fn run(&self, core: &mut RogCore) -> Result<(), AuraError>; - fn led_endpoint(&self) -> u8; - fn key_endpoint(&self) -> u8; - fn usb_vendor(&self) -> u16; - fn usb_product(&self) -> u16; - fn supported_modes(&self) -> &[BuiltInModeByte]; -} - -pub(crate) struct LaptopGX502GW { - usb_vendor: u16, - usb_product: u16, - board_name: &'static str, - prod_family: &'static str, - report_filter_bytes: [u8; 2], - min_led_bright: u8, - max_led_bright: u8, - led_endpoint: u8, - key_endpoint: u8, - supported_modes: [BuiltInModeByte; 12], - backlight: Backlight, -} - -impl LaptopGX502GW { - pub fn new() -> Self { - // Find backlight - LaptopGX502GW { - usb_vendor: 0x0B05, - usb_product: 0x1866, - board_name: "GX502GW", - prod_family: "Zephyrus S", - report_filter_bytes: [0x5a, 0x02], - min_led_bright: 0x00, - max_led_bright: 0x03, - led_endpoint: 0x81, - key_endpoint: 0x83, - supported_modes: [ - BuiltInModeByte::Stable, - BuiltInModeByte::Breathe, - BuiltInModeByte::Cycle, - BuiltInModeByte::Rainbow, - BuiltInModeByte::Rain, - BuiltInModeByte::Random, - BuiltInModeByte::Highlight, - BuiltInModeByte::Laser, - BuiltInModeByte::Ripple, - BuiltInModeByte::Pulse, - BuiltInModeByte::ThinZoomy, - BuiltInModeByte::WideZoomy, - ], - backlight: Backlight::new("intel_backlight").unwrap(), - } - } -} - -impl Laptop for LaptopGX502GW { - // TODO: This really needs to match against u16 in future - fn run(&self, rogcore: &mut RogCore) -> Result<(), AuraError> { - if let Some(key_buf) = rogcore.poll_keyboard(&self.report_filter_bytes) { - match GX502GWKeys::from(key_buf[1]) { - GX502GWKeys::LedBrightUp => { - let mut bright = rogcore.config().brightness; - if bright < self.max_led_bright { - bright += 1; - rogcore.config_mut().brightness = bright; - } - let bytes = aura_brightness_bytes(bright); - rogcore.aura_set_and_save(&self.supported_modes, &bytes)?; - } - GX502GWKeys::LedBrightDown => { - let mut bright = rogcore.config().brightness; - if bright > self.min_led_bright { - bright -= 1; - rogcore.config_mut().brightness = bright; - } - let bytes = aura_brightness_bytes(bright); - rogcore.aura_set_and_save(&self.supported_modes, &bytes)?; - } - GX502GWKeys::AuraNext => { - let mut mode = rogcore.config().current_mode[3] + 1; - if mode > 0x0c { - mode = 0x00 - } else if mode == 0x09 { - mode = 0x0a - } - rogcore.config_mut().current_mode[3] = mode; - if let Some(bytes) = rogcore.config_mut().get_current() { - rogcore.aura_set_and_save(&self.supported_modes, &bytes)?; - } - } - GX502GWKeys::AuraPrevious => { - let mut mode = rogcore.config().current_mode[3]; - if mode == 0x00 { - mode = 0x0c - } else if mode - 1 == 0x09 { - mode = 0x08 - } else { - mode -= 1; - } - rogcore.config_mut().current_mode[3] = mode; - if let Some(bytes) = rogcore.config_mut().get_current() { - rogcore.aura_set_and_save(&self.supported_modes, &bytes)?; - rogcore.config().write(); - } - } - GX502GWKeys::ScreenBrightUp => { - self.backlight.step_up(); - } - GX502GWKeys::ScreenBrightDown => { - self.backlight.step_down(); - } - GX502GWKeys::Sleep => { - // Direct call to systemd - rogcore.suspend_with_systemd(); - //rogcore.virt_keys().press([0x01, 0, 0, 0x82, 0, 0, 0, 0]); - // Power menu - //rogcore.virt_keys().press([0x01, 0, 0, 0x66, 0, 0, 0, 0]); - } - GX502GWKeys::AirplaneMode => { - rogcore.toggle_airplane_mode(); - } - - GX502GWKeys::MicToggle => {} - GX502GWKeys::Fan => {} - GX502GWKeys::ScreenToggle => { - rogcore.virt_keys().press(ConsumerKeys::BacklightTog.into()); - } - GX502GWKeys::TouchPadToggle => { - // F21 key, Touchpad toggle - rogcore.virt_keys().press([0x01, 0, 0, 0x70, 0, 0, 0, 0]); - // rogcore.virt_keys().press([0x01, 0, 0, 0x71, 0, 0, 0, 0]); // Touchpad on F22 - // rogcore.virt_keys().press([0x01, 0, 0, 0x72, 0, 0, 0, 0]); // Touchpad off F23 - } - GX502GWKeys::Rog => { - rogcore.virt_keys().press([0x01, 0, 0, 0x68, 0, 0, 0, 0]); // XF86Tools? F13 - } - - GX502GWKeys::None => { - if key_buf[0] != 0 && key_buf[1] != 0 { - info!( - "Unmapped key, attempt to pass to virtual device: {:X?}", - &key_buf[1] - ); - let mut bytes = [0u8; 8]; - // TODO: code page - bytes[0] = key_buf[0]; - bytes[1] = key_buf[1]; - rogcore.virt_keys().press(bytes); - } - } - } - } - Ok(()) - } - - fn led_endpoint(&self) -> u8 { - self.led_endpoint - } - fn key_endpoint(&self) -> u8 { - self.key_endpoint - } - fn usb_vendor(&self) -> u16 { - self.usb_vendor - } - fn usb_product(&self) -> u16 { - self.usb_product - } - fn supported_modes(&self) -> &[BuiltInModeByte] { - &self.supported_modes - } - fn board_name(&self) -> &str { - &self.board_name - } - fn prod_family(&self) -> &str { - &self.prod_family - } -} - -pub(crate) enum GX502GWKeys { - Rog = 0x38, - MicToggle = 0x7C, - Fan = 0xAE, - ScreenToggle = 0x35, - ScreenBrightDown = 0x10, - ScreenBrightUp = 0x20, - TouchPadToggle = 0x6b, - Sleep = 0x6C, - AirplaneMode = 0x88, - LedBrightUp = 0xC4, - LedBrightDown = 0xC5, - AuraPrevious = 0xB2, - AuraNext = 0xB3, - None, -} - -impl From for GX502GWKeys { - fn from(byte: u8) -> Self { - match byte { - 0x38 => GX502GWKeys::Rog, - 0x7C => GX502GWKeys::MicToggle, - 0xAE => GX502GWKeys::Fan, - 0x35 => GX502GWKeys::ScreenToggle, - 0x10 => GX502GWKeys::ScreenBrightDown, - 0x20 => GX502GWKeys::ScreenBrightUp, - 0x6b => GX502GWKeys::TouchPadToggle, - 0x6C => GX502GWKeys::Sleep, - 0x88 => GX502GWKeys::AirplaneMode, - 0xC4 => GX502GWKeys::LedBrightUp, - 0xC5 => GX502GWKeys::LedBrightDown, - 0xB2 => GX502GWKeys::AuraPrevious, - 0xB3 => GX502GWKeys::AuraNext, - _ => GX502GWKeys::None, - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 5a153e50..2b644c23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -mod aura; +pub mod aura; /// Contains mostly only what is required for parsing CLI options pub mod cli_options; mod config; diff --git a/src/main.rs b/src/main.rs index 42a13cf3..167332af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use daemon::{ + aura::aura_brightness_bytes, cli_options::SetAuraBuiltin, - core::{aura_brightness_bytes, LedBrightness, LED_MSG_LEN}, + core::{LedBrightness, LED_MSG_LEN}, daemon::{start_daemon, DBUS_IFACE, DBUS_NAME, DBUS_PATH}, }; use dbus::Error as DbusError; diff --git a/src/virt_device.rs b/src/virt_device.rs index 1497cada..78d24f3f 100644 --- a/src/virt_device.rs +++ b/src/virt_device.rs @@ -1,5 +1,29 @@ use uhid_virt::{Bus, CreateParams, UHIDDevice}; +/// Create a virtual device to emit key-presses +/// +/// This is required in some instances because either the USB device that +/// an interface for a working set of buttons is also captured, or because +/// there is no equivalent "system" action to take for a key function and +/// a key-press is required to emit a particular key code. +/// +/// The two devices set up mirror that of the GX502GW and can accept the same +/// original byte arrays to emit. +/// - "Consumer Device" generally has all device type controls like media, backlight, power +/// - "Keyboard Device" is a full featured keyboard including special keys like F13-F24 +/// +/// # Some example uses: +/// `rogcore.virt_keys().press([0x01, 0, 0, 0x68, 0, 0, 0, 0]); // F13, Config/Control Panel` +/// +/// `rogcore.virt_keys().press([0x01, 0, 0, 0x70, 0, 0, 0, 0]); // F21, Touchpad toggle, XF86/Gnome` +/// +/// `rogcore.virt_keys().press([0x01, 0, 0, 0x71, 0, 0, 0, 0]); // F22, Touchpad on, XF86/Gnome` +/// +/// `rogcore.virt_keys().press([0x01, 0, 0, 0x72, 0, 0, 0, 0]); // F23, Touchpad off, XF86/Gnome` +/// +/// `rogcore.virt_keys().press([0x01, 0, 0, 0x82, 0, 0, 0, 0]); // Sleep` +/// +/// `rogcore.virt_keys().press([0x01, 0, 0, 0x66, 0, 0, 0, 0]); // Power (menu)` pub(crate) struct VirtKeys { device: UHIDDevice, } @@ -71,9 +95,10 @@ impl VirtKeys { } } - pub(crate) fn press(&mut self, input: [u8; 8]) { + /// A single on/off key press + pub(crate) fn press(&mut self, input: [u8; 32]) { self.device.write(&input).unwrap(); - let mut reset = [0u8; 8]; + let mut reset = [0u8; 32]; reset[0] = input[0]; self.device.write(&reset).unwrap(); } @@ -130,9 +155,9 @@ pub(crate) enum ConsumerKeys { MovieBrowser = 0x1B8, } -impl From for [u8; 8] { +impl From for [u8; 32] { fn from(key: ConsumerKeys) -> Self { - let mut bytes = [0u8; 8]; + let mut bytes = [0u8; 32]; bytes[0] = 0x02; // report ID for consumer bytes[1] = key as u8; bytes[2] = (key as u16 >> 8) as u8;