diff --git a/Cargo.toml b/Cargo.toml index a9693dd1..604abf9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,9 +53,8 @@ opt-level = 3 panic = "abort" [profile.dev] -lto = true debug = false -opt-level = 3 +opt-level = 2 #panic = "abort" [profile.bench] diff --git a/examples/per-key-effect-2.rs b/examples/per-key-effect-2.rs index 189e77d6..c73a9d38 100644 --- a/examples/per-key-effect-2.rs +++ b/examples/per-key-effect-2.rs @@ -1,4 +1,4 @@ -use daemon::aura::{BuiltInModeByte, Key, KeyColourArray}; +use daemon::aura::{Key, KeyColourArray}; use daemon::daemon::{DBUS_IFACE, DBUS_NAME, DBUS_PATH}; use dbus::Error as DbusError; use dbus::{ffidisp::Connection, Message}; @@ -6,7 +6,6 @@ use std::{thread, time}; pub fn dbus_led_builtin_write(bytes: &[u8]) -> Result<(), Box> { let bus = Connection::new_system()?; - //let proxy = bus.with_proxy(DBUS_IFACE, "/", Duration::from_millis(5000)); let msg = Message::new_method_call(DBUS_NAME, DBUS_PATH, DBUS_IFACE, "ledmessage")? .append1(bytes.to_vec()); let r = bus.send_with_reply_and_block(msg, 5000)?; @@ -44,8 +43,7 @@ fn main() -> Result<(), Box> { per_key_led.push(key_colours.clone()); } - // It takes each interrupt at least 1ms. 10ms to write complete block. Plus any extra - // penalty time such as read waits + // It takes each interrupt at least 1ms. 10ms to write complete block. let time = time::Duration::from_millis(10); // aim for 100 per second let row = KeyColourArray::get_init_msg(); @@ -58,7 +56,6 @@ fn main() -> Result<(), Box> { thread::sleep(time); for group in &per_key_led { - thread::sleep(time); let group = group.get(); let msg = Message::new_method_call(DBUS_NAME, DBUS_PATH, DBUS_IFACE, "ledeffect")? .append1(&group[0].to_vec()) @@ -72,6 +69,7 @@ fn main() -> Result<(), Box> { .append1(&group[8].to_vec()) .append1(&group[9].to_vec()); bus.send(msg).unwrap(); + thread::sleep(time); } let after = std::time::Instant::now(); let diff = after.duration_since(now); diff --git a/src/core.rs b/src/core.rs index 014c74f1..90825eb2 100644 --- a/src/core.rs +++ b/src/core.rs @@ -12,8 +12,10 @@ use rusb::DeviceHandle; use std::error::Error; use std::fs::OpenOptions; use std::io::{Read, Write}; +use std::marker::PhantomData; use std::path::Path; use std::process::Command; +use std::ptr::NonNull; use std::str::FromStr; use std::time::Duration; @@ -179,36 +181,11 @@ impl RogCore { Ok(()) } - /// Write the bytes read from the device interrupt to the buffer arg, and returns the - /// count of bytes written - /// - /// `report_filter_bytes` is used to filter the data read from the interupt so - /// only the relevant byte array is returned. - pub(crate) async fn poll_keyboard( - handle: &DeviceHandle, - endpoint: u8, - report_filter_bytes: &[u8], - ) -> Option<[u8; 32]> { - let mut buf = [0u8; 32]; - match handle.read_interrupt(endpoint, &mut buf, Duration::from_millis(200)) { - Ok(_) => { - if report_filter_bytes.contains(&buf[0]) - && (buf[1] != 0 || buf[2] != 0 || buf[3] != 0 || buf[4] != 0) - { - return Some(buf); - } - } - Err(err) => match err { - rusb::Error::Timeout => {} - _ => error!("Failed to read keyboard interrupt: {:?}", err), - }, - } - None - } - - pub(crate) fn get_raw_device_handle(&mut self) -> *mut DeviceHandle { + pub(crate) fn get_raw_device_handle(&mut self) -> NonNull> { // Breaking every damn lifetime guarantee rust gives us - &mut self.handle as *mut DeviceHandle + unsafe { + NonNull::new_unchecked(&mut self.handle as *mut DeviceHandle) + } } /// A direct call to systemd to suspend the PC. @@ -262,31 +239,104 @@ impl RogCore { } } -/// UNSAFE: because we're holding a pointer to something that *may* go out of scope while the +/// UNSAFE: Must live as long as RogCore +/// +/// Because we're holding a pointer to something that *may* go out of scope while the /// pointer is held. We're relying on access to struct to be behind a Mutex, and for behaviour /// that may cause invalididated pointer to cause the program to panic rather than continue. -pub(crate) struct LedWriter { - handle: *mut DeviceHandle, - led_endpoint: u8, - initialised: bool, +pub(crate) struct KeyboardReader<'d, C: 'd> +where + C: rusb::UsbContext, +{ + handle: NonNull>, + endpoint: u8, + filter: Vec, + _phantom: PhantomData<&'d DeviceHandle>, } /// UNSAFE -unsafe impl Send for LedWriter {} -unsafe impl Sync for LedWriter {} +unsafe impl<'d, C> Send for KeyboardReader<'d, C> where C: rusb::UsbContext {} +unsafe impl<'d, C> Sync for KeyboardReader<'d, C> where C: rusb::UsbContext {} -impl LedWriter { - pub fn new(device_handle: *mut DeviceHandle, led_endpoint: u8) -> Self { +impl<'d, C> KeyboardReader<'d, C> +where + C: rusb::UsbContext, +{ + pub fn new(device_handle: NonNull>, key_endpoint: u8, filter: Vec) -> Self { + KeyboardReader { + handle: device_handle, + endpoint: key_endpoint, + filter, + _phantom: PhantomData, + } + } + + /// Write the bytes read from the device interrupt to the buffer arg, and returns the + /// count of bytes written + /// + /// `report_filter_bytes` is used to filter the data read from the interupt so + /// only the relevant byte array is returned. + pub(crate) async fn poll_keyboard(&self) -> Option<[u8; 32]> { + let mut buf = [0u8; 32]; + match unsafe { self.handle.as_ref() }.read_interrupt( + self.endpoint, + &mut buf, + Duration::from_millis(200), + ) { + Ok(_) => { + if self.filter.contains(&buf[0]) + && (buf[1] != 0 || buf[2] != 0 || buf[3] != 0 || buf[4] != 0) + { + return Some(buf); + } + } + Err(err) => match err { + rusb::Error::Timeout => {} + _ => error!("Failed to read keyboard interrupt: {:?}", err), + }, + } + None + } +} + +/// UNSAFE: Must live as long as RogCore +/// +/// Because we're holding a pointer to something that *may* go out of scope while the +/// pointer is held. We're relying on access to struct to be behind a Mutex, and for behaviour +/// that may cause invalididated pointer to cause the program to panic rather than continue. +pub(crate) struct LedWriter<'d, C: 'd> +where + C: rusb::UsbContext, +{ + handle: NonNull>, + led_endpoint: u8, + initialised: bool, + _phantom: PhantomData<&'d DeviceHandle>, +} + +/// UNSAFE +unsafe impl<'d, C> Send for LedWriter<'d, C> where C: rusb::UsbContext {} +unsafe impl<'d, C> Sync for LedWriter<'d, C> where C: rusb::UsbContext {} + +impl<'d, C> LedWriter<'d, C> +where + C: rusb::UsbContext, +{ + pub fn new(device_handle: NonNull>, led_endpoint: u8) -> Self { LedWriter { handle: device_handle, led_endpoint, initialised: false, + _phantom: PhantomData, } } - pub fn aura_write(&mut self, message: &[u8]) -> Result<(), AuraError> { - let handle = unsafe { &*self.handle }; - match handle.write_interrupt(self.led_endpoint, message, Duration::from_millis(2)) { + async fn aura_write(&mut self, message: &[u8]) -> Result<(), AuraError> { + match unsafe { self.handle.as_ref() }.write_interrupt( + self.led_endpoint, + message, + Duration::from_millis(1), + ) { Ok(_) => {} Err(err) => match err { rusb::Error::Timeout => {} @@ -296,32 +346,22 @@ impl LedWriter { Ok(()) } - fn aura_write_messages(&mut self, messages: &[&[u8]]) -> Result<(), AuraError> { + async fn aura_write_messages(&mut self, messages: &[&[u8]]) -> Result<(), AuraError> { if !self.initialised { - self.aura_write(&LED_INIT1)?; - self.aura_write(LED_INIT2.as_bytes())?; - self.aura_write(&LED_INIT3)?; - self.aura_write(LED_INIT4.as_bytes())?; - self.aura_write(&LED_INIT5)?; + self.aura_write(&LED_INIT1).await?; + self.aura_write(LED_INIT2.as_bytes()).await?; + self.aura_write(&LED_INIT3).await?; + self.aura_write(LED_INIT4.as_bytes()).await?; + self.aura_write(&LED_INIT5).await?; self.initialised = true; } for message in messages { - self.aura_write(*message)?; - self.aura_write(&LED_SET)?; + self.aura_write(*message).await?; + self.aura_write(&LED_SET).await?; } // Changes won't persist unless apply is set - self.aura_write(&LED_APPLY)?; - Ok(()) - } - - /// Write an effect block - /// - /// `aura_effect_init` must be called any effect routine, and called only once. - pub fn aura_write_effect(&mut self, effect: Vec>) -> Result<(), AuraError> { - for row in effect.iter() { - self.aura_write(row)?; - } + self.aura_write(&LED_APPLY).await?; Ok(()) } @@ -333,9 +373,12 @@ impl LedWriter { endpoint: u8, effect: Vec>, ) -> Result<(), AuraError> { - let handle = unsafe { &*self.handle }; for row in effect.iter() { - match handle.write_interrupt(endpoint, row, Duration::from_millis(2)) { + match unsafe { self.handle.as_ref() }.write_interrupt( + endpoint, + row, + Duration::from_millis(1), + ) { Ok(_) => {} Err(err) => match err { rusb::Error::Timeout => {} @@ -346,7 +389,7 @@ impl LedWriter { Ok(()) } - pub(crate) fn aura_set_and_save( + pub(crate) async fn aura_set_and_save( &mut self, supported_modes: &[BuiltInModeByte], bytes: &[u8], @@ -354,11 +397,11 @@ impl LedWriter { ) -> Result<(), AuraError> { let mode = BuiltInModeByte::from(bytes[3]); if bytes[1] == 0xbc { - self.aura_write(bytes)?; + self.aura_write(bytes).await?; return Ok(()); } else if supported_modes.contains(&mode) || bytes[1] == 0xba { let messages = [bytes]; - self.aura_write_messages(&messages)?; + self.aura_write_messages(&messages).await?; config.set_field_from(bytes); config.write(); return Ok(()); @@ -367,7 +410,7 @@ impl LedWriter { Err(AuraError::NotSupported) } - pub(crate) fn aura_bright_inc( + pub(crate) async fn aura_bright_inc( &mut self, supported_modes: &[BuiltInModeByte], max_bright: u8, @@ -378,13 +421,14 @@ impl LedWriter { bright += 1; config.brightness = bright; let bytes = aura_brightness_bytes(bright); - self.aura_set_and_save(supported_modes, &bytes, config)?; + self.aura_set_and_save(supported_modes, &bytes, config) + .await?; info!("Increased LED brightness to {:#?}", bright); } Ok(()) } - pub(crate) fn aura_bright_dec( + pub(crate) async fn aura_bright_dec( &mut self, supported_modes: &[BuiltInModeByte], min_bright: u8, @@ -395,7 +439,8 @@ impl LedWriter { bright -= 1; config.brightness = bright; let bytes = aura_brightness_bytes(bright); - self.aura_set_and_save(supported_modes, &bytes, config)?; + self.aura_set_and_save(supported_modes, &bytes, config) + .await?; info!("Decreased LED brightness to {:#?}", bright); } Ok(()) @@ -404,7 +449,7 @@ impl LedWriter { /// 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( + pub(crate) async fn aura_mode_next( &mut self, supported_modes: &[BuiltInModeByte], config: &mut Config, @@ -422,7 +467,8 @@ impl LedWriter { .get_field_from(supported_modes[idx_next].into()) .unwrap() .to_owned(); - self.aura_set_and_save(supported_modes, &mode_next, config)?; + self.aura_set_and_save(supported_modes, &mode_next, config) + .await?; info!("Switched LED mode to {:#?}", supported_modes[idx_next]); Ok(()) } @@ -430,7 +476,7 @@ impl LedWriter { /// 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( + pub(crate) async fn aura_mode_prev( &mut self, supported_modes: &[BuiltInModeByte], config: &mut Config, @@ -448,7 +494,8 @@ impl LedWriter { .get_field_from(supported_modes[idx_next].into()) .unwrap() .to_owned(); - self.aura_set_and_save(supported_modes, &mode_next, config)?; + self.aura_set_and_save(supported_modes, &mode_next, config) + .await?; info!("Switched LED mode to {:#?}", supported_modes[idx_next]); Ok(()) } diff --git a/src/daemon.rs b/src/daemon.rs index 15b8a82a..28248a45 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -9,7 +9,7 @@ use dbus::{ }; use dbus_tokio::connection; -use log::{error, info}; +use log::{error, info, warn}; use std::error::Error; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -31,24 +31,25 @@ pub async fn start_daemon() -> Result<(), Box> { let laptop = match_laptop(); let mut config = Config::default().read(); - let mut rogcore = RogCore::new( - laptop.usb_vendor(), - laptop.usb_product(), - laptop.led_endpoint(), - ) - .map_or_else( - |err| { - error!("{}", err); - panic!("{}", err); - }, - |daemon| { - info!("RogCore loaded"); - daemon - }, + let mut rogcore = Box::pin( + RogCore::new( + laptop.usb_vendor(), + laptop.usb_product(), + laptop.led_endpoint(), + ) + .map_or_else( + |err| { + error!("{}", err); + panic!("{}", err); + }, + |daemon| { + info!("RogCore loaded"); + daemon + }, + ), ); // Reload settings rogcore.reload(&mut config).await?; - let usb_dev_handle = unsafe { &*(rogcore.get_raw_device_handle()) }; // Set up the mutexes let led_writer = Arc::new(Mutex::new(LedWriter::new( @@ -56,8 +57,6 @@ pub async fn start_daemon() -> Result<(), Box> { laptop.led_endpoint(), ))); let config = Arc::new(Mutex::new(config)); - let rogcore = Arc::new(Mutex::new(Box::pin(rogcore))); - let (resource, connection) = connection::new_system_sync()?; tokio::spawn(async { let err = resource.await; @@ -65,7 +64,7 @@ pub async fn start_daemon() -> Result<(), Box> { }); connection - .request_name(DBUS_IFACE, false, true, false) + .request_name(DBUS_IFACE, false, true, true) .await?; let (tree, input, effect) = dbus_create_tree(); @@ -75,20 +74,22 @@ pub async fn start_daemon() -> Result<(), Box> { let supported = Vec::from(laptop.supported_modes()); let led_endpoint = laptop.led_endpoint(); + // Keyboard reader goes in separate task because we want a high interrupt timeout + // and don't want that to hold up other tasks, or miss keystrokes { - let keyboard_endpoint = laptop.key_endpoint(); - let report_filter_bytes = laptop.key_filter().to_owned(); + let keyboard_reader = KeyboardReader::new( + rogcore.get_raw_device_handle(), + laptop.key_endpoint(), + laptop.key_filter().to_owned(), + ); // This is *not* safe - let rogcore = rogcore.clone(); let led_writer = led_writer.clone(); let config = config.clone(); + // start the keyboard reader and laptop-action loop tokio::spawn(async move { loop { - let data = - RogCore::poll_keyboard(usb_dev_handle, keyboard_endpoint, &report_filter_bytes) - .await; + let data = unsafe { keyboard_reader.poll_keyboard().await }; if let Some(bytes) = data { - let mut rogcore = rogcore.lock().await; match laptop.run(&mut rogcore, &led_writer, &config, bytes).await { Ok(_) => {} Err(err) => { @@ -101,6 +102,7 @@ pub async fn start_daemon() -> Result<(), Box> { }); } + // start the LED writer loop let mut time_mark = Instant::now(); loop { connection.process_all(); @@ -110,35 +112,36 @@ pub async fn start_daemon() -> Result<(), Box> { if let Some(bytes) = lock.take() { let mut led_writer = led_writer.lock().await; let mut config = config.lock().await; - led_writer.aura_set_and_save(&supported, &bytes, &mut config)?; + led_writer + .aura_set_and_save(&supported, &bytes, &mut config) + .await + .map_err(|err| warn!("{:?}", err)) + .unwrap(); time_mark = Instant::now(); } } // Write a colour block - // Yank data out and drop lock quick (effect write takes 10ms) if let Ok(mut lock) = effect.try_lock() { // Spawn a writer if let Some(stuff) = lock.take() { - tokio::spawn(async move { - let led_writer = led_writer.lock().await; - led_writer - .async_write_effect(led_endpoint, stuff) - .await - .unwrap(); - }); + let led_writer = led_writer.lock().await; + led_writer + .async_write_effect(led_endpoint, stuff) + .await + .map_err(|err| warn!("{:?}", err)) + .unwrap(); time_mark = Instant::now(); } } let now = Instant::now(); // Cool-down steps + // This block is to prevent the loop spooling as fast as possible and saturating the CPU if now.duration_since(time_mark).as_millis() > 500 { std::thread::sleep(Duration::from_millis(200)); } else if now.duration_since(time_mark).as_millis() > 100 { std::thread::sleep(Duration::from_millis(50)); - } else { - std::thread::sleep(Duration::from_micros(5)); } } } diff --git a/src/laptops/mod.rs b/src/laptops/mod.rs index 87442fc3..99652dd9 100644 --- a/src/laptops/mod.rs +++ b/src/laptops/mod.rs @@ -67,8 +67,6 @@ pub(crate) fn match_laptop() -> LaptopBase { panic!("could not match laptop"); } -pub(crate) type LaptopRunner = dyn Fn(&mut RogCore, [u8; 32]) -> Result<(), AuraError>; - pub(super) struct LaptopBase { usb_vendor: u16, usb_product: u16, @@ -85,13 +83,16 @@ use tokio::sync::Mutex; impl LaptopBase { /// Pass in LedWriter as Mutex so it is only locked when required - pub(super) async fn run( + pub(super) async fn run<'a, C>( &self, rogcore: &mut RogCore, - led_writer: &Mutex, + led_writer: &Mutex>, config: &Mutex, key_buf: [u8; 32], - ) -> Result<(), AuraError> { + ) -> Result<(), AuraError> + where + C: rusb::UsbContext, + { match self.usb_product { 0x1869 | 0x1866 => { self.gx502_runner(rogcore, led_writer, config, key_buf) @@ -120,21 +121,21 @@ impl LaptopBase { pub(super) fn usb_product(&self) -> u16 { self.usb_product } - pub(super) fn set_usb_product(&mut self, product: u16) { - self.usb_product = product; - } pub(super) fn supported_modes(&self) -> &[BuiltInModeByte] { &self.supported_modes } // 0x1866, per-key LEDs, media-keys split from vendor specific - async fn gx502_runner( + async fn gx502_runner<'a, C>( &self, rogcore: &mut RogCore, - led_writer: &Mutex, + led_writer: &Mutex>, config: &Mutex, key_buf: [u8; 32], - ) -> Result<(), AuraError> { + ) -> Result<(), AuraError> + where + C: rusb::UsbContext, + { let max_led_bright = self.max_led_bright; let min_led_bright = self.min_led_bright; let supported_modes = self.supported_modes.to_owned(); @@ -142,22 +143,30 @@ impl LaptopBase { GX502Keys::LedBrightUp => { let mut led_writer = led_writer.lock().await; let mut config = config.lock().await; - led_writer.aura_bright_inc(&supported_modes, max_led_bright, &mut config)?; + led_writer + .aura_bright_inc(&supported_modes, max_led_bright, &mut config) + .await?; } GX502Keys::LedBrightDown => { let mut led_writer = led_writer.lock().await; let mut config = config.lock().await; - led_writer.aura_bright_dec(&supported_modes, min_led_bright, &mut config)?; + led_writer + .aura_bright_dec(&supported_modes, min_led_bright, &mut config) + .await?; } GX502Keys::AuraNext => { let mut led_writer = led_writer.lock().await; let mut config = config.lock().await; - led_writer.aura_mode_next(&supported_modes, &mut config)?; + led_writer + .aura_mode_next(&supported_modes, &mut config) + .await?; } GX502Keys::AuraPrevious => { let mut led_writer = led_writer.lock().await; let mut config = config.lock().await; - led_writer.aura_mode_prev(&supported_modes, &mut config)?; + led_writer + .aura_mode_prev(&supported_modes, &mut config) + .await?; } GX502Keys::ScreenBrightUp => { rogcore.virt_keys().press(ConsumerKeys::BacklightInc.into()) @@ -202,13 +211,16 @@ impl LaptopBase { } // GL753VE == 0x1854, 4 zone keyboard - async fn gl753_runner( + async fn gl753_runner<'a, C>( &self, rogcore: &mut RogCore, - led_writer: &Mutex, + led_writer: &Mutex>, config: &Mutex, key_buf: [u8; 32], - ) -> Result<(), AuraError> { + ) -> Result<(), AuraError> + where + C: rusb::UsbContext, + { let max_led_bright = self.max_led_bright; let min_led_bright = self.min_led_bright; let supported_modes = self.supported_modes.to_owned(); @@ -216,12 +228,16 @@ impl LaptopBase { GL753Keys::LedBrightUp => { let mut led_writer = led_writer.lock().await; let mut config = config.lock().await; - led_writer.aura_bright_inc(&supported_modes, max_led_bright, &mut config)?; + led_writer + .aura_bright_inc(&supported_modes, max_led_bright, &mut config) + .await?; } GL753Keys::LedBrightDown => { let mut led_writer = led_writer.lock().await; let mut config = config.lock().await; - led_writer.aura_bright_dec(&supported_modes, min_led_bright, &mut config)?; + led_writer + .aura_bright_dec(&supported_modes, min_led_bright, &mut config) + .await?; } GL753Keys::ScreenBrightUp => { rogcore.virt_keys().press(ConsumerKeys::BacklightInc.into())