From 11548217b76985e013d9e37ab4122cb3ec1cdb01 Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 23 Apr 2020 14:51:20 +1200 Subject: [PATCH] Attempt to support GA502DU, GL753VE. Bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 13 +++ src/aura.rs | 39 +++++++ src/cli_options.rs | 16 +++ src/core.rs | 2 + src/laptops/gl753.rs | 146 +++++++++++++++++++++++++++ src/laptops/{gx502gw.rs => gx502.rs} | 72 ++++++------- src/laptops/mod.rs | 25 +++-- src/main.rs | 22 +++- 10 files changed, 292 insertions(+), 47 deletions(-) create mode 100644 src/laptops/gl753.rs rename src/laptops/{gx502gw.rs => gx502.rs} (76%) diff --git a/Cargo.lock b/Cargo.lock index 7baf885e..a9765ee3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -397,7 +397,7 @@ checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" [[package]] name = "rog-daemon" -version = "0.4.1" +version = "0.5.0" dependencies = [ "aho-corasick", "dbus", diff --git a/Cargo.toml b/Cargo.toml index e37ab71a..4c9abf39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rog-daemon" -version = "0.4.1" +version = "0.5.0" authors = ["Luke "] edition = "2018" diff --git a/README.md b/README.md index 03ada60f..5c885e3e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ rog-core is a utility for Linux to control many aspects (eventually) of the ASUS ROG laptops like the Zephyrus GX502GW. +One of the benefits of this app (for me at least) is that you *don't* require a kernel with correct support for the laptop, or custom patched modules. The app reads and writes direct to the device interrupts, and can be customised (in source) quite extensively to do what you want such as directly controlling your laptop backlight rather than emitting a key-press for the DE to handle. There is also the possibility of rebinding fn keys to be macros which emit a series of keyboard presses. + 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. @@ -73,6 +75,17 @@ Currently if no options are supplied for the CLI mode selection then a default i As the daemon currently stands it should be enough for a functional system. +## Other Laptops + +**Supported:** + +- GX502 (Tested on GX502GW) + +**Please help test or provide info for:** + +- GA502 (attempts to use same profile as GX502GW) +- GL753 (attempted support from researching 2nd-hand info, multizone may work) + ## Wireshark captures TODO: see `./wireshark_data/` for some captures. diff --git a/src/aura.rs b/src/aura.rs index 8015c4e1..5201360e 100644 --- a/src/aura.rs +++ b/src/aura.rs @@ -46,6 +46,9 @@ pub fn aura_brightness_bytes(brightness: u8) -> [u8; 17] { /// /// Bytes 0 and 1 should always be 5d, b3 /// +/// On multizone laptops byte 2 is the zone number, RGB in usual +/// place, byte 3 set to zero +/// /// Byte 3 sets the mode type: /// - 00 = static /// - 01 = breathe (can set two colours) @@ -126,6 +129,7 @@ impl From for [u8; LED_MSG_LEN] { SetAuraBuiltin::Pulse(_) => msg[3] = 0x0a, SetAuraBuiltin::ThinZoomy(_) => msg[3] = 0x0b, SetAuraBuiltin::WideZoomy(_) => msg[3] = 0x0c, + _ => panic!("Mode not convertable to array"), } match mode { @@ -163,6 +167,37 @@ impl From for [u8; LED_MSG_LEN] { msg[5] = settings.colour.1; msg[6] = settings.colour.2; } + _ => panic!("Mode not convertable to array"), + } + msg + } +} + +impl From for [[u8; LED_MSG_LEN]; 4] { + fn from(mode: SetAuraBuiltin) -> Self { + let mut msg = [[0u8; LED_MSG_LEN]; 4]; + for i in 0..4 { + msg[i][0] = 0x5d; + msg[i][1] = 0xb3; + msg[i][2] = i as u8 + 1; + } + + match mode { + SetAuraBuiltin::MultiStatic(settings) => { + msg[0][4] = settings.colour1.0; + msg[0][5] = settings.colour1.1; + msg[0][6] = settings.colour1.2; + msg[1][4] = settings.colour2.0; + msg[1][5] = settings.colour2.1; + msg[1][6] = settings.colour2.2; + msg[2][4] = settings.colour3.0; + msg[2][5] = settings.colour3.1; + msg[2][6] = settings.colour3.2; + msg[3][4] = settings.colour4.0; + msg[3][5] = settings.colour4.1; + msg[3][6] = settings.colour4.2; + } + _ => panic!("Mode not convertable to array"), } msg } @@ -184,6 +219,7 @@ pub struct BuiltInModeBytes { pub pulse: [u8; LED_MSG_LEN], pub thinzoomy: [u8; LED_MSG_LEN], pub widezoomy: [u8; LED_MSG_LEN], + pub multi_static: [[u8; LED_MSG_LEN]; 4], } impl BuiltInModeBytes { pub fn set_field_from(&mut self, bytes: &[u8]) { @@ -249,6 +285,9 @@ impl Default for BuiltInModeBytes { widezoomy: <[u8; LED_MSG_LEN]>::from( SetAuraBuiltin::WideZoomy(SingleColour::default()), ), + multi_static: <[[u8; LED_MSG_LEN]; 4]>::from(SetAuraBuiltin::MultiStatic( + MultiColour::default(), + )), } } } diff --git a/src/cli_options.rs b/src/cli_options.rs index ab938cd6..aa8852e8 100644 --- a/src/cli_options.rs +++ b/src/cli_options.rs @@ -107,6 +107,20 @@ pub struct SingleColour { pub colour: Colour, } +#[derive(Debug, Default, Options)] +pub struct MultiColour { + #[options(help = "print help message")] + help: bool, + #[options(meta = "HEX", help = "set the RGB value e.g, ff00ff")] + pub colour1: Colour, + #[options(meta = "HEX", help = "set the RGB value e.g, ff00ff")] + pub colour2: Colour, + #[options(meta = "HEX", help = "set the RGB value e.g, ff00ff")] + pub colour3: Colour, + #[options(meta = "HEX", help = "set the RGB value e.g, ff00ff")] + pub colour4: Colour, +} + #[derive(Debug, Default, Options)] pub struct SingleSpeedDirection { #[options(help = "print help message")] @@ -160,6 +174,8 @@ pub enum SetAuraBuiltin { ThinZoomy(SingleColour), #[options(help = "set a wide vertical line zooming from left")] WideZoomy(SingleColour), + #[options(help = "4-zone multi-colour")] + MultiStatic(MultiColour), } impl Default for SetAuraBuiltin { diff --git a/src/core.rs b/src/core.rs index f0108014..438aa554 100644 --- a/src/core.rs +++ b/src/core.rs @@ -272,6 +272,7 @@ impl RogCore { &mut self, supported_modes: &[BuiltInModeByte], ) -> Result<(), AuraError> { + // TODO: different path for multi-zone (byte 2 controlled, non-zero) 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 { @@ -295,6 +296,7 @@ impl RogCore { &mut self, supported_modes: &[BuiltInModeByte], ) -> Result<(), AuraError> { + // TODO: different path for multi-zone (byte 2 controlled, non-zero) let mode_curr = self.config.current_mode[3]; let idx = supported_modes.binary_search(&mode_curr.into()).unwrap(); let idx_next = if idx > 0 { diff --git a/src/laptops/gl753.rs b/src/laptops/gl753.rs new file mode 100644 index 00000000..aa92c34b --- /dev/null +++ b/src/laptops/gl753.rs @@ -0,0 +1,146 @@ +use crate::aura::BuiltInModeByte; +use crate::core::{Backlight, RogCore}; +use crate::error::AuraError; +use crate::virt_device::ConsumerKeys; +//use keycode::{KeyMap, KeyMappingId, KeyState, KeyboardState}; +use super::Laptop; +use log::info; + +pub(super) struct LaptopGL753 { + 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; 3], + backlight: Backlight, +} + +impl LaptopGL753 { + pub(super) fn new() -> Self { + LaptopGL753 { + usb_vendor: 0x0B05, + usb_product: 0x1854, + // from `cat /sys/class/dmi/id/board_name` + board_name: "LaptopGL753VE", + // from `cat /sys/class/dmi/id/product_family` + prod_family: "ROG Strix", + report_filter_bytes: [0x5a, 0x02], + min_led_bright: 0x00, + max_led_bright: 0x03, + //from `lsusb -vd 0b05:1866` + led_endpoint: 0x04, + //from `lsusb -vd 0b05:1866` + key_endpoint: 0x83, + supported_modes: [ + BuiltInModeByte::Stable, + BuiltInModeByte::Breathe, + BuiltInModeByte::Cycle, + ], + backlight: Backlight::new("intel_backlight").unwrap(), + } + } +} + +impl LaptopGL753 { + fn do_keypress_actions(&self, rogcore: &mut RogCore) -> Result<(), AuraError> { + if let Some(key_buf) = rogcore.poll_keyboard(&self.report_filter_bytes) { + match GL753Keys::from(key_buf[1]) { + GL753Keys::LedBrightUp => { + rogcore.aura_bright_inc(&self.supported_modes, self.max_led_bright)?; + } + GL753Keys::LedBrightDown => { + rogcore.aura_bright_dec(&self.supported_modes, self.min_led_bright)?; + } + GL753Keys::ScreenBrightUp => self.backlight.step_up(), + GL753Keys::ScreenBrightDown => self.backlight.step_down(), + GL753Keys::Sleep => rogcore.suspend_with_systemd(), + GL753Keys::AirplaneMode => rogcore.toggle_airplane_mode(), + GL753Keys::ScreenToggle => { + rogcore.virt_keys().press(ConsumerKeys::BacklightTog.into()); + } + GL753Keys::TouchPadToggle => { + let mut key = [0u8; 32]; + key[0] = 0x01; + key[3] = 0x070; + rogcore.virt_keys().press(key); + } + GL753Keys::Rog => { + let mut key = [0u8; 32]; + key[0] = 0x01; + key[3] = 0x68; // XF86Tools? F13 + rogcore.virt_keys().press(key); + } + GL753Keys::None => { + if key_buf[0] != 0x5A { + info!("Unmapped key array: {:X?}", &key_buf); + info!("Attempting passthrough: {:X?}", &key_buf[1]); + rogcore.virt_keys().press(key_buf); + } + } + } + } + Ok(()) + } +} + +impl Laptop for LaptopGL753 { + fn run(&self, rogcore: &mut RogCore) -> Result<(), AuraError> { + self.do_keypress_actions(rogcore) + } + 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 + } +} + +enum GL753Keys { + Rog = 0x38, + ScreenToggle = 0x35, + ScreenBrightDown = 0x10, + ScreenBrightUp = 0x20, + TouchPadToggle = 0x6b, + Sleep = 0x6C, + AirplaneMode = 0x88, + LedBrightUp = 0xC4, + LedBrightDown = 0xC5, + None, +} + +impl From for GL753Keys { + fn from(byte: u8) -> Self { + match byte { + 0x38 => GL753Keys::Rog, + 0x35 => GL753Keys::ScreenToggle, + 0x10 => GL753Keys::ScreenBrightDown, + 0x20 => GL753Keys::ScreenBrightUp, + 0x6b => GL753Keys::TouchPadToggle, + 0x6C => GL753Keys::Sleep, + 0x88 => GL753Keys::AirplaneMode, + 0xC4 => GL753Keys::LedBrightUp, + 0xC5 => GL753Keys::LedBrightDown, + _ => GL753Keys::None, + } + } +} diff --git a/src/laptops/gx502gw.rs b/src/laptops/gx502.rs similarity index 76% rename from src/laptops/gx502gw.rs rename to src/laptops/gx502.rs index 3d393aac..2ea736fa 100644 --- a/src/laptops/gx502gw.rs +++ b/src/laptops/gx502.rs @@ -6,7 +6,7 @@ use crate::virt_device::ConsumerKeys; use super::Laptop; use log::info; -pub(super) struct LaptopGX502GW { +pub(super) struct LaptopGX502 { usb_vendor: u16, usb_product: u16, board_name: &'static str, @@ -21,7 +21,7 @@ pub(super) struct LaptopGX502GW { per_key_led: Vec, } -impl LaptopGX502GW { +impl LaptopGX502 { pub(super) fn new() -> Self { // Setting up a sample effect to run when ROG pressed let mut per_key_led = Vec::new(); @@ -56,7 +56,7 @@ impl LaptopGX502GW { } // Find backlight - LaptopGX502GW { + LaptopGX502 { usb_vendor: 0x0B05, usb_product: 0x1866, // from `cat /sys/class/dmi/id/board_name` @@ -90,34 +90,34 @@ impl LaptopGX502GW { } } -impl LaptopGX502GW { +impl LaptopGX502 { fn do_keypress_actions(&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 => { + match GX502Keys::from(key_buf[1]) { + GX502Keys::LedBrightUp => { rogcore.aura_bright_inc(&self.supported_modes, self.max_led_bright)?; } - GX502GWKeys::LedBrightDown => { + GX502Keys::LedBrightDown => { rogcore.aura_bright_dec(&self.supported_modes, self.min_led_bright)?; } - GX502GWKeys::AuraNext => rogcore.aura_mode_next(&self.supported_modes)?, - GX502GWKeys::AuraPrevious => rogcore.aura_mode_prev(&self.supported_modes)?, - GX502GWKeys::ScreenBrightUp => self.backlight.step_up(), - GX502GWKeys::ScreenBrightDown => self.backlight.step_down(), - GX502GWKeys::Sleep => rogcore.suspend_with_systemd(), - GX502GWKeys::AirplaneMode => rogcore.toggle_airplane_mode(), - GX502GWKeys::MicToggle => {} - GX502GWKeys::Fan => {} - GX502GWKeys::ScreenToggle => { + GX502Keys::AuraNext => rogcore.aura_mode_next(&self.supported_modes)?, + GX502Keys::AuraPrevious => rogcore.aura_mode_prev(&self.supported_modes)?, + GX502Keys::ScreenBrightUp => self.backlight.step_up(), + GX502Keys::ScreenBrightDown => self.backlight.step_down(), + GX502Keys::Sleep => rogcore.suspend_with_systemd(), + GX502Keys::AirplaneMode => rogcore.toggle_airplane_mode(), + GX502Keys::MicToggle => {} + GX502Keys::Fan => {} + GX502Keys::ScreenToggle => { rogcore.virt_keys().press(ConsumerKeys::BacklightTog.into()); } - GX502GWKeys::TouchPadToggle => { + GX502Keys::TouchPadToggle => { let mut key = [0u8; 32]; key[0] = 0x01; key[3] = 0x070; rogcore.virt_keys().press(key); } - GX502GWKeys::Rog => { + GX502Keys::Rog => { rogcore.aura_effect_init()?; rogcore.aura_write_effect(&self.per_key_led)?; // let mut key = [0u8; 32]; @@ -125,7 +125,7 @@ impl LaptopGX502GW { // key[3] = 0x68; // XF86Tools? F13 // rogcore.virt_keys().press(key); } - GX502GWKeys::None => { + GX502Keys::None => { if key_buf[0] != 0x5A { info!("Unmapped key, attempt passthrough: {:X?}", &key_buf[1]); rogcore.virt_keys().press(key_buf); @@ -137,7 +137,7 @@ impl LaptopGX502GW { } } -impl Laptop for LaptopGX502GW { +impl Laptop for LaptopGX502 { fn run(&self, rogcore: &mut RogCore) -> Result<(), AuraError> { self.do_keypress_actions(rogcore) } @@ -164,7 +164,7 @@ impl Laptop for LaptopGX502GW { } } -enum GX502GWKeys { +enum GX502Keys { Rog = 0x38, MicToggle = 0x7C, Fan = 0xAE, @@ -181,23 +181,23 @@ enum GX502GWKeys { None, } -impl From for GX502GWKeys { +impl From for GX502Keys { 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, + 0x38 => GX502Keys::Rog, + 0x7C => GX502Keys::MicToggle, + 0xAE => GX502Keys::Fan, + 0x35 => GX502Keys::ScreenToggle, + 0x10 => GX502Keys::ScreenBrightDown, + 0x20 => GX502Keys::ScreenBrightUp, + 0x6b => GX502Keys::TouchPadToggle, + 0x6C => GX502Keys::Sleep, + 0x88 => GX502Keys::AirplaneMode, + 0xC4 => GX502Keys::LedBrightUp, + 0xC5 => GX502Keys::LedBrightDown, + 0xB2 => GX502Keys::AuraPrevious, + 0xB3 => GX502Keys::AuraNext, + _ => GX502Keys::None, } } } diff --git a/src/laptops/mod.rs b/src/laptops/mod.rs index 18e111b1..98e07d86 100644 --- a/src/laptops/mod.rs +++ b/src/laptops/mod.rs @@ -4,17 +4,28 @@ use crate::error::AuraError; //use keycode::{KeyMap, KeyMappingId, KeyState, KeyboardState}; use log::info; -mod gx502gw; -use gx502gw::LaptopGX502GW; +// GL753VE == 0x1854, 4 zone keyboard +mod gl753; +use gl753::LaptopGL753; + +// 0x1866, per-key LEDs, media-keys split from vendor specific +mod gx502; +use gx502::LaptopGX502; 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" => { - info!("Found GX502GW"); - Box::new(LaptopGX502GW::new()) + match &board_name.as_str()[..5] { + // TODO: FX503VD - 0x1869, + // These two models seem to have the same characteristics + "GX502" | "GA502" => { + info!("Found GX502 or GA502 series"); + Box::new(LaptopGX502::new()) + } + // LED should work for this, but unsure of keys + "GL753" => { + info!("Found GL753 series"); + Box::new(LaptopGL753::new()) } _ => { panic!("could not match laptop"); diff --git a/src/main.rs b/src/main.rs index 167332af..903dd5d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,10 +10,14 @@ use env_logger::{Builder, Target}; use gumdrop::Options; use log::LevelFilter; +static VERSION: &'static str = "0.5.0"; + #[derive(Debug, Options)] struct CLIStart { #[options(help = "print help message")] help: bool, + #[options(help = "show program version number")] + version: bool, #[options(help = "start daemon")] daemon: bool, #[options(meta = "VAL", help = "")] @@ -46,12 +50,26 @@ fn main() -> Result<(), Box> { if parsed.daemon { start_daemon()?; } + if parsed.version { + println!("Version: {}", VERSION); + } match parsed.command { Some(Command::LedMode(mode)) => { if let Some(command) = mode.command { - let mode = <[u8; LED_MSG_LEN]>::from(command); - dbus_led_builtin_write(&mode)?; + // Check for special modes here, eg, per-key or multi-zone + match command { + SetAuraBuiltin::MultiStatic(_) => { + let byte_arr = <[[u8; LED_MSG_LEN]; 4]>::from(command); + for arr in byte_arr.iter() { + dbus_led_builtin_write(arr)?; + } + } + _ => { + let mode = <[u8; LED_MSG_LEN]>::from(command); + dbus_led_builtin_write(&mode)?; + } + } } } None => {}