mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
Big refactor out of Aura LED data structs
This commit is contained in:
1016
rog-core/Cargo.lock
generated
Normal file
1016
rog-core/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
rog-core/Cargo.toml
Normal file
45
rog-core/Cargo.toml
Normal file
@@ -0,0 +1,45 @@
|
||||
[package]
|
||||
name = "rog-daemon"
|
||||
version = "0.8.0"
|
||||
license = "MPL-2.0"
|
||||
readme = "README.md"
|
||||
authors = ["Luke <luke@ljones.dev>"]
|
||||
repository = "https://github.com/flukejones/rog-core"
|
||||
homepage = "https://github.com/flukejones/rog-core"
|
||||
description = "A daemon app for ASUS GX502 and similar laptops to control missing features"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "daemon"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "rog-core"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
rog-aura = { path = "../aura" }
|
||||
rusb = "^0.5.5"
|
||||
|
||||
# cli and logging
|
||||
gumdrop = "^0.8.0"
|
||||
log = "^0.4.8"
|
||||
env_logger = "^0.7.1"
|
||||
|
||||
# async
|
||||
dbus = { version = "^0.8.2", features = ["futures"] }
|
||||
dbus-tokio = "^0.5.1"
|
||||
tokio = { version = "0.2.4", features = ["rt-threaded", "macros", "sync"] }
|
||||
|
||||
# serialisation
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
toml = "0.5"
|
||||
|
||||
# Device control
|
||||
# sysfs-class = "^0.1.2" # used for backlight control mostly
|
||||
# cpu power management
|
||||
intel-pstate = "^0.2.1"
|
||||
# virtualisation of HID, mainly for outputting consumer key codes
|
||||
uhid-virt = "^0.0.4"
|
||||
#keycode = "0.3"
|
||||
57
rog-core/src/config.rs
Normal file
57
rog-core/src/config.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use rog_aura::BuiltInModeBytes;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
pub static CONFIG_PATH: &str = "/etc/rogcore.conf";
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
pub fan_mode: u8,
|
||||
pub brightness: u8,
|
||||
pub current_mode: [u8; 4],
|
||||
pub builtin_modes: BuiltInModeBytes,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn read(mut self) -> Self {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&CONFIG_PATH)
|
||||
.expect("config file error");
|
||||
let mut buf = String::new();
|
||||
if let Ok(l) = file.read_to_string(&mut buf) {
|
||||
if l == 0 {
|
||||
// create a default config here
|
||||
let mut c = Config::default();
|
||||
c.current_mode[0] = 0x5d;
|
||||
c.current_mode[1] = 0xb3;
|
||||
let toml = toml::to_string(&c).unwrap();
|
||||
file.write_all(toml.as_bytes())
|
||||
.expect("Writing default config failed");
|
||||
self = c;
|
||||
} else {
|
||||
self = toml::from_str(&buf).unwrap();
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn write(&self) {
|
||||
let mut file = File::create(CONFIG_PATH).expect("Couldn't overwrite config");
|
||||
let toml = toml::to_string(self).expect("Parse config to JSON failed");
|
||||
file.write_all(toml.as_bytes())
|
||||
.expect("Saving config failed");
|
||||
}
|
||||
|
||||
pub fn set_field_from(&mut self, bytes: &[u8]) {
|
||||
if bytes[0] == 0x5a && bytes[1] == 0xba {
|
||||
self.brightness = bytes[4];
|
||||
} else if bytes[0] == 0x5d && bytes[1] == 0xb3 {
|
||||
self.current_mode.copy_from_slice(&bytes[0..4]);
|
||||
self.builtin_modes.set_field_from(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
552
rog-core/src/core.rs
Normal file
552
rog-core/src/core.rs
Normal file
@@ -0,0 +1,552 @@
|
||||
// Return show-stopping errors, otherwise map error to a log level
|
||||
|
||||
use crate::{config::Config, virt_device::VirtKeys};
|
||||
use gumdrop::Options;
|
||||
use log::{debug, error, info, warn};
|
||||
use rog_aura::{aura_brightness_bytes, error::AuraError, BuiltInModeByte};
|
||||
use rusb::DeviceHandle;
|
||||
use std::error::Error;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Read, Write};
|
||||
use std::marker::{PhantomData, PhantomPinned};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::ptr::NonNull;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
static LED_INIT1: [u8; 2] = [0x5d, 0xb9];
|
||||
static LED_INIT2: &str = "]ASUS Tech.Inc."; // ] == 0x5d
|
||||
static LED_INIT3: [u8; 6] = [0x5d, 0x05, 0x20, 0x31, 0, 0x08];
|
||||
static LED_INIT4: &str = "^ASUS Tech.Inc."; // ^ == 0x5e
|
||||
static LED_INIT5: [u8; 6] = [0x5e, 0x05, 0x20, 0x31, 0, 0x08];
|
||||
|
||||
// Only these two packets must be 17 bytes
|
||||
static LED_APPLY: [u8; 17] = [0x5d, 0xb4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
static LED_SET: [u8; 17] = [0x5d, 0xb5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
static FAN_TYPE_1_PATH: &str = "/sys/devices/platform/asus-nb-wmi/throttle_thermal_policy";
|
||||
static FAN_TYPE_2_PATH: &str = "/sys/devices/platform/asus-nb-wmi/fan_boost_mode";
|
||||
|
||||
/// ROG device controller
|
||||
///
|
||||
/// For the GX502GW the LED setup sequence looks like:
|
||||
///
|
||||
/// -` LED_INIT1`
|
||||
/// - `LED_INIT3`
|
||||
/// - `LED_INIT4`
|
||||
/// - `LED_INIT2`
|
||||
/// - `LED_INIT4`
|
||||
pub(crate) struct RogCore {
|
||||
handle: DeviceHandle<rusb::GlobalContext>,
|
||||
virt_keys: VirtKeys,
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
|
||||
impl RogCore {
|
||||
pub(crate) fn new(
|
||||
vendor: u16,
|
||||
product: u16,
|
||||
led_endpoint: u8,
|
||||
) -> Result<RogCore, Box<dyn Error>> {
|
||||
let mut dev_handle = RogCore::get_device(vendor, product)?;
|
||||
dev_handle.set_active_configuration(0).unwrap_or(());
|
||||
|
||||
let dev_config = dev_handle.device().config_descriptor(0).unwrap();
|
||||
// Interface with outputs
|
||||
let mut interface = 0;
|
||||
for iface in dev_config.interfaces() {
|
||||
for desc in iface.descriptors() {
|
||||
for endpoint in desc.endpoint_descriptors() {
|
||||
if endpoint.address() == led_endpoint {
|
||||
info!("INTERVAL: {:?}", endpoint.interval());
|
||||
info!("MAX_PKT_SIZE: {:?}", endpoint.max_packet_size());
|
||||
info!("SYNC: {:?}", endpoint.sync_type());
|
||||
info!("TRANSFER_TYPE: {:?}", endpoint.transfer_type());
|
||||
info!("ENDPOINT: {:X?}", endpoint.address());
|
||||
interface = desc.interface_number();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dev_handle.set_auto_detach_kernel_driver(true).unwrap();
|
||||
dev_handle.claim_interface(interface)?;
|
||||
|
||||
Ok(RogCore {
|
||||
handle: dev_handle,
|
||||
virt_keys: VirtKeys::new(),
|
||||
_pin: PhantomPinned,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn reload(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {
|
||||
// let mode_curr = self.config.current_mode[3];
|
||||
// let mode = self
|
||||
// .config
|
||||
// .builtin_modes
|
||||
// .get_field_from(BuiltInModeByte::from(mode_curr).into())
|
||||
// .unwrap()
|
||||
// .to_owned();
|
||||
// self.aura_write_messages(&[&mode])?;
|
||||
|
||||
let path = if Path::new(FAN_TYPE_1_PATH).exists() {
|
||||
FAN_TYPE_1_PATH
|
||||
} else if Path::new(FAN_TYPE_2_PATH).exists() {
|
||||
FAN_TYPE_2_PATH
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let mut file = OpenOptions::new().write(true).open(path)?;
|
||||
file.write_all(format!("{:?}\n", config.fan_mode).as_bytes())?;
|
||||
self.set_pstate_for_fan_mode(FanLevel::from(config.fan_mode))?;
|
||||
info!("Reloaded last saved settings");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn virt_keys(&mut self) -> &mut VirtKeys {
|
||||
&mut self.virt_keys
|
||||
}
|
||||
|
||||
fn get_device(
|
||||
vendor: u16,
|
||||
product: u16,
|
||||
) -> Result<DeviceHandle<rusb::GlobalContext>, rusb::Error> {
|
||||
for device in rusb::devices().unwrap().iter() {
|
||||
let device_desc = device.device_descriptor().unwrap();
|
||||
if device_desc.vendor_id() == vendor && device_desc.product_id() == product {
|
||||
return device.open();
|
||||
}
|
||||
}
|
||||
Err(rusb::Error::NoDevice)
|
||||
}
|
||||
|
||||
pub(crate) fn fan_mode_step(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {
|
||||
let path = if Path::new(FAN_TYPE_1_PATH).exists() {
|
||||
FAN_TYPE_1_PATH
|
||||
} else if Path::new(FAN_TYPE_2_PATH).exists() {
|
||||
FAN_TYPE_2_PATH
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let mut fan_ctrl = OpenOptions::new().read(true).write(true).open(path)?;
|
||||
|
||||
let mut buf = String::new();
|
||||
if let Ok(_) = fan_ctrl.read_to_string(&mut buf) {
|
||||
let mut n = u8::from_str_radix(&buf.trim_end(), 10)?;
|
||||
info!("Current fan mode: {:#?}", FanLevel::from(n));
|
||||
// wrap around the step number
|
||||
if n < 2 {
|
||||
n += 1;
|
||||
} else {
|
||||
n = 0;
|
||||
}
|
||||
info!("Fan mode stepped to: {:#?}", FanLevel::from(n));
|
||||
fan_ctrl.write_all(format!("{:?}\n", n).as_bytes())?;
|
||||
self.set_pstate_for_fan_mode(FanLevel::from(n))?;
|
||||
config.fan_mode = n;
|
||||
config.write();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_pstate_for_fan_mode(&self, mode: FanLevel) -> Result<(), Box<dyn Error>> {
|
||||
// Set CPU pstate
|
||||
if let Ok(pstate) = intel_pstate::PState::new() {
|
||||
match mode {
|
||||
FanLevel::Normal => {
|
||||
pstate.set_min_perf_pct(0)?;
|
||||
pstate.set_max_perf_pct(100)?;
|
||||
pstate.set_no_turbo(false)?;
|
||||
info!("CPU pstate: normal");
|
||||
}
|
||||
FanLevel::Boost => {
|
||||
pstate.set_min_perf_pct(50)?;
|
||||
pstate.set_max_perf_pct(100)?;
|
||||
pstate.set_no_turbo(false)?;
|
||||
info!("CPU pstate: boost");
|
||||
}
|
||||
FanLevel::Silent => {
|
||||
pstate.set_min_perf_pct(0)?;
|
||||
pstate.set_max_perf_pct(70)?;
|
||||
pstate.set_no_turbo(true)?;
|
||||
info!("CPU pstate: silent, no-turbo");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A direct call to systemd to suspend the PC.
|
||||
///
|
||||
/// This avoids desktop environments being required to handle it
|
||||
/// (which means it works while in a TTY also)
|
||||
pub(crate) fn suspend_with_systemd(&self) {
|
||||
std::process::Command::new("systemctl")
|
||||
.arg("suspend")
|
||||
.spawn()
|
||||
.map_or_else(|err| warn!("Failed to suspend: {}", err), |_| {});
|
||||
}
|
||||
|
||||
/// A direct call to rfkill to suspend wireless devices.
|
||||
///
|
||||
/// This avoids desktop environments being required to handle it (which
|
||||
/// means it works while in a TTY also)
|
||||
pub(crate) fn toggle_airplane_mode(&self) {
|
||||
match Command::new("rfkill").arg("list").output() {
|
||||
Ok(output) => {
|
||||
if output.status.success() {
|
||||
if let Ok(out) = String::from_utf8(output.stdout) {
|
||||
if out.contains(": yes") {
|
||||
Command::new("rfkill")
|
||||
.arg("unblock")
|
||||
.arg("all")
|
||||
.spawn()
|
||||
.map_or_else(
|
||||
|err| warn!("Could not unblock rf devices: {}", err),
|
||||
|_| {},
|
||||
);
|
||||
} else {
|
||||
Command::new("rfkill")
|
||||
.arg("block")
|
||||
.arg("all")
|
||||
.spawn()
|
||||
.map_or_else(
|
||||
|err| warn!("Could not block rf devices: {}", err),
|
||||
|_| {},
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("Could not list rf devices");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Could not list rf devices: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_raw_device_handle(&mut self) -> NonNull<DeviceHandle<rusb::GlobalContext>> {
|
||||
// Breaking every damn lifetime guarantee rust gives us
|
||||
unsafe {
|
||||
NonNull::new_unchecked(&mut self.handle as *mut DeviceHandle<rusb::GlobalContext>)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Lifetime is tied to `DeviceHandle` from `RogCore`
|
||||
pub(crate) struct KeyboardReader<'d, C: 'd>
|
||||
where
|
||||
C: rusb::UsbContext,
|
||||
{
|
||||
handle: NonNull<DeviceHandle<C>>,
|
||||
endpoint: u8,
|
||||
filter: Vec<u8>,
|
||||
_phantom: PhantomData<&'d DeviceHandle<C>>,
|
||||
}
|
||||
|
||||
/// UNSAFE
|
||||
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<'d, C> KeyboardReader<'d, C>
|
||||
where
|
||||
C: rusb::UsbContext,
|
||||
{
|
||||
pub fn new(device_handle: NonNull<DeviceHandle<C>>, key_endpoint: u8, filter: Vec<u8>) -> 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<DeviceHandle<C>>,
|
||||
led_endpoint: u8,
|
||||
initialised: bool,
|
||||
_phantom: PhantomData<&'d DeviceHandle<C>>,
|
||||
}
|
||||
|
||||
/// 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<DeviceHandle<C>>, led_endpoint: u8) -> Self {
|
||||
LedWriter {
|
||||
handle: device_handle,
|
||||
led_endpoint,
|
||||
initialised: false,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
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 => {}
|
||||
_ => error!("Failed to read keyboard interrupt: {:?}", err),
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn aura_write_messages(&mut self, messages: &[&[u8]]) -> Result<(), AuraError> {
|
||||
if !self.initialised {
|
||||
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).await?;
|
||||
self.aura_write(&LED_SET).await?;
|
||||
}
|
||||
// Changes won't persist unless apply is set
|
||||
self.aura_write(&LED_APPLY).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write an effect block
|
||||
///
|
||||
/// `aura_effect_init` must be called any effect routine, and called only once.
|
||||
pub async fn async_write_effect(
|
||||
&self,
|
||||
endpoint: u8,
|
||||
effect: Vec<Vec<u8>>,
|
||||
) -> Result<(), AuraError> {
|
||||
for row in effect.iter() {
|
||||
match unsafe { self.handle.as_ref() }.write_interrupt(
|
||||
endpoint,
|
||||
row,
|
||||
Duration::from_millis(1),
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(err) => match err {
|
||||
rusb::Error::Timeout => {}
|
||||
_ => error!("Failed to write LED interrupt: {:?}", err),
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn aura_set_and_save(
|
||||
&mut self,
|
||||
supported_modes: &[BuiltInModeByte],
|
||||
bytes: &[u8],
|
||||
config: &mut Config,
|
||||
) -> Result<(), AuraError> {
|
||||
let mode = BuiltInModeByte::from(bytes[3]);
|
||||
if bytes[1] == 0xbc {
|
||||
self.aura_write(bytes).await?;
|
||||
return Ok(());
|
||||
} else if supported_modes.contains(&mode) || bytes[1] == 0xba {
|
||||
let messages = [bytes];
|
||||
self.aura_write_messages(&messages).await?;
|
||||
config.set_field_from(bytes);
|
||||
config.write();
|
||||
return Ok(());
|
||||
}
|
||||
warn!("{:?} not supported", mode);
|
||||
Err(AuraError::NotSupported)
|
||||
}
|
||||
|
||||
pub(crate) async fn aura_bright_inc(
|
||||
&mut self,
|
||||
supported_modes: &[BuiltInModeByte],
|
||||
max_bright: u8,
|
||||
config: &mut Config,
|
||||
) -> Result<(), AuraError> {
|
||||
let mut bright = config.brightness;
|
||||
if bright < max_bright {
|
||||
bright += 1;
|
||||
config.brightness = bright;
|
||||
let bytes = aura_brightness_bytes(bright);
|
||||
self.aura_set_and_save(supported_modes, &bytes, config)
|
||||
.await?;
|
||||
info!("Increased LED brightness to {:#?}", bright);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn aura_bright_dec(
|
||||
&mut self,
|
||||
supported_modes: &[BuiltInModeByte],
|
||||
min_bright: u8,
|
||||
config: &mut Config,
|
||||
) -> Result<(), AuraError> {
|
||||
let mut bright = config.brightness;
|
||||
if bright > min_bright {
|
||||
bright -= 1;
|
||||
config.brightness = bright;
|
||||
let bytes = aura_brightness_bytes(bright);
|
||||
self.aura_set_and_save(supported_modes, &bytes, config)
|
||||
.await?;
|
||||
info!("Decreased LED brightness to {:#?}", bright);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Select next Aura effect
|
||||
///
|
||||
/// If the current effect is the last one then the effect selected wraps around to the first.
|
||||
pub(crate) async fn aura_mode_next(
|
||||
&mut self,
|
||||
supported_modes: &[BuiltInModeByte],
|
||||
config: &mut Config,
|
||||
) -> Result<(), AuraError> {
|
||||
// TODO: different path for multi-zone (byte 2 controlled, non-zero)
|
||||
let mode_curr = 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 = config
|
||||
.builtin_modes
|
||||
.get_field_from(supported_modes[idx_next].into())
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
self.aura_set_and_save(supported_modes, &mode_next, config)
|
||||
.await?;
|
||||
info!("Switched LED mode to {:#?}", supported_modes[idx_next]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Select previous Aura effect
|
||||
///
|
||||
/// If the current effect is the first one then the effect selected wraps around to the last.
|
||||
pub(crate) async fn aura_mode_prev(
|
||||
&mut self,
|
||||
supported_modes: &[BuiltInModeByte],
|
||||
config: &mut Config,
|
||||
) -> Result<(), AuraError> {
|
||||
// TODO: different path for multi-zone (byte 2 controlled, non-zero)
|
||||
let mode_curr = 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 = config
|
||||
.builtin_modes
|
||||
.get_field_from(supported_modes[idx_next].into())
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
self.aura_set_and_save(supported_modes, &mode_next, config)
|
||||
.await?;
|
||||
info!("Switched LED mode to {:#?}", supported_modes[idx_next]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
pub struct LedBrightness {
|
||||
level: u8,
|
||||
}
|
||||
impl LedBrightness {
|
||||
pub fn level(&self) -> u8 {
|
||||
self.level
|
||||
}
|
||||
}
|
||||
impl FromStr for LedBrightness {
|
||||
type Err = AuraError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.to_lowercase();
|
||||
match s.as_str() {
|
||||
"off" => Ok(LedBrightness { level: 0x00 }),
|
||||
"low" => Ok(LedBrightness { level: 0x01 }),
|
||||
"med" => Ok(LedBrightness { level: 0x02 }),
|
||||
"high" => Ok(LedBrightness { level: 0x03 }),
|
||||
_ => {
|
||||
println!("Missing required argument, must be one of:\noff,low,med,high\n");
|
||||
Err(AuraError::ParseBrightness)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum FanLevel {
|
||||
Normal,
|
||||
Boost,
|
||||
Silent,
|
||||
}
|
||||
|
||||
impl From<u8> for FanLevel {
|
||||
fn from(n: u8) -> Self {
|
||||
match n {
|
||||
0 => FanLevel::Normal,
|
||||
1 => FanLevel::Boost,
|
||||
2 => FanLevel::Silent,
|
||||
_ => FanLevel::Normal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FanLevel> for u8 {
|
||||
fn from(n: FanLevel) -> Self {
|
||||
match n {
|
||||
FanLevel::Normal => 0,
|
||||
FanLevel::Boost => 1,
|
||||
FanLevel::Silent => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
227
rog-core/src/daemon.rs
Normal file
227
rog-core/src/daemon.rs
Normal file
@@ -0,0 +1,227 @@
|
||||
use crate::{config::Config, core::*, laptops::match_laptop};
|
||||
use dbus::{
|
||||
nonblock::Process,
|
||||
tree::{Factory, MTSync, Method, MethodErr, Tree},
|
||||
};
|
||||
use dbus_tokio::connection;
|
||||
use log::{error, info, warn};
|
||||
use rog_aura::{DBUS_IFACE, DBUS_PATH};
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
type LedMsgType = Arc<Mutex<Option<Vec<u8>>>>;
|
||||
type EffectType = Arc<Mutex<Option<Vec<Vec<u8>>>>>;
|
||||
|
||||
// Timing is such that:
|
||||
// - interrupt write is minimum 1ms (sometimes lower)
|
||||
// - read interrupt must timeout, minimum of 1ms
|
||||
// - for a single usb packet, 2ms total.
|
||||
// - to maintain constant times of 1ms, per-key colours should use
|
||||
// the effect endpoint so that the complete colour block is written
|
||||
// as fast as 1ms per row of the matrix inside it. (10ms total time)
|
||||
//
|
||||
// DBUS processing takes 6ms if not tokiod
|
||||
pub async fn start_daemon() -> Result<(), Box<dyn Error>> {
|
||||
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
|
||||
},
|
||||
);
|
||||
// Reload settings
|
||||
rogcore.reload(&mut config).await?;
|
||||
|
||||
// Set up the mutexes
|
||||
let led_writer = Arc::new(Mutex::new(LedWriter::new(
|
||||
rogcore.get_raw_device_handle(),
|
||||
laptop.led_endpoint(),
|
||||
)));
|
||||
let config = Arc::new(Mutex::new(config));
|
||||
let (resource, connection) = connection::new_system_sync()?;
|
||||
tokio::spawn(async {
|
||||
let err = resource.await;
|
||||
panic!("Lost connection to D-Bus: {}", err);
|
||||
});
|
||||
|
||||
connection
|
||||
.request_name(DBUS_IFACE, false, true, true)
|
||||
.await?;
|
||||
|
||||
let (tree, input, effect) = dbus_create_tree();
|
||||
// We add the tree to the connection so that incoming method calls will be handled.
|
||||
tree.start_receive_send(&*connection);
|
||||
|
||||
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_reader = KeyboardReader::new(
|
||||
rogcore.get_raw_device_handle(),
|
||||
laptop.key_endpoint(),
|
||||
laptop.key_filter().to_owned(),
|
||||
);
|
||||
|
||||
let led_writer1 = led_writer.clone();
|
||||
let config1 = config.clone();
|
||||
// start the keyboard reader and laptop-action loop
|
||||
let key_read_handle = tokio::spawn(async move {
|
||||
loop {
|
||||
let data = keyboard_reader.poll_keyboard().await;
|
||||
if let Some(bytes) = data {
|
||||
laptop
|
||||
.run(&mut rogcore, &led_writer1, &config1, bytes)
|
||||
.await
|
||||
.map_err(|err| warn!("{:?}", err))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// start the LED writer loop
|
||||
let led_write_handle = tokio::spawn(async move {
|
||||
let mut time_mark = Instant::now();
|
||||
loop {
|
||||
connection.process_all();
|
||||
|
||||
let led_writer = led_writer.clone();
|
||||
if let Ok(mut lock) = input.try_lock() {
|
||||
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)
|
||||
.await
|
||||
.map_err(|err| warn!("{:?}", err))
|
||||
.unwrap();
|
||||
time_mark = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
// Write a colour block
|
||||
let led_writer = led_writer.clone();
|
||||
if let Ok(mut lock) = effect.try_lock() {
|
||||
// Spawn a writer
|
||||
if let Some(stuff) = lock.take() {
|
||||
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(300));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
led_write_handle.await?;
|
||||
key_read_handle.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dbus_create_ledmsg_method(msg: LedMsgType) -> Method<MTSync, ()> {
|
||||
let factory = Factory::new_sync::<()>();
|
||||
factory
|
||||
// method for ledmessage
|
||||
.method("ledmessage", (), {
|
||||
move |m| {
|
||||
let bytes: Vec<u8> = m.msg.read1()?;
|
||||
if let Ok(mut lock) = msg.try_lock() {
|
||||
*lock = Some(bytes.to_vec());
|
||||
let mret = m
|
||||
.msg
|
||||
.method_return()
|
||||
.append1(&format!("Wrote {:x?}", bytes));
|
||||
Ok(vec![mret])
|
||||
} else {
|
||||
Err(MethodErr::failed("Could not lock daemon for access"))
|
||||
}
|
||||
}
|
||||
})
|
||||
.outarg::<&str, _>("reply")
|
||||
.inarg::<Vec<u8>, _>("bytearray")
|
||||
}
|
||||
|
||||
fn dbus_create_ledeffect_method(effect: EffectType) -> Method<MTSync, ()> {
|
||||
let factory = Factory::new_sync::<()>();
|
||||
factory
|
||||
// method for ledmessage
|
||||
.method("ledeffect", (), {
|
||||
move |m| {
|
||||
if let Ok(mut lock) = effect.try_lock() {
|
||||
let mut iter = m.msg.iter_init();
|
||||
let byte_array: Vec<Vec<u8>> = vec![
|
||||
iter.read()?,
|
||||
iter.read()?,
|
||||
iter.read()?,
|
||||
iter.read()?,
|
||||
iter.read()?,
|
||||
iter.read()?,
|
||||
iter.read()?,
|
||||
iter.read()?,
|
||||
iter.read()?,
|
||||
iter.read()?,
|
||||
];
|
||||
|
||||
*lock = Some(byte_array);
|
||||
let mret = m.msg.method_return().append1(&format!("Got effect part"));
|
||||
Ok(vec![mret])
|
||||
} else {
|
||||
Err(MethodErr::failed("Could not lock daemon for access"))
|
||||
}
|
||||
}
|
||||
})
|
||||
.outarg::<&str, _>("reply")
|
||||
.inarg::<Vec<u8>, _>("bytearray")
|
||||
.inarg::<Vec<u8>, _>("bytearray")
|
||||
.inarg::<Vec<u8>, _>("bytearray")
|
||||
.inarg::<Vec<u8>, _>("bytearray")
|
||||
.inarg::<Vec<u8>, _>("bytearray")
|
||||
.inarg::<Vec<u8>, _>("bytearray")
|
||||
.inarg::<Vec<u8>, _>("bytearray")
|
||||
.inarg::<Vec<u8>, _>("bytearray")
|
||||
.inarg::<Vec<u8>, _>("bytearray")
|
||||
.inarg::<Vec<u8>, _>("bytearray")
|
||||
}
|
||||
|
||||
fn dbus_create_tree() -> (Tree<MTSync, ()>, LedMsgType, EffectType) {
|
||||
let input: LedMsgType = Arc::new(Mutex::new(None));
|
||||
let effect: EffectType = Arc::new(Mutex::new(None));
|
||||
|
||||
let factory = Factory::new_sync::<()>();
|
||||
let tree = factory.tree(()).add(
|
||||
factory.object_path(DBUS_PATH, ()).add(
|
||||
factory
|
||||
.interface(DBUS_IFACE, ())
|
||||
.add_m(dbus_create_ledmsg_method(input.clone()))
|
||||
.add_m(dbus_create_ledeffect_method(effect.clone())),
|
||||
),
|
||||
);
|
||||
(tree, input, effect)
|
||||
}
|
||||
341
rog-core/src/laptops/mod.rs
Normal file
341
rog-core/src/laptops/mod.rs
Normal file
@@ -0,0 +1,341 @@
|
||||
use crate::config::Config;
|
||||
use crate::core::{LedWriter, RogCore};
|
||||
use rog_aura::{error::AuraError, BuiltInModeByte};
|
||||
//use keycode::{KeyMap, KeyMappingId, KeyState, KeyboardState};
|
||||
use crate::virt_device::ConsumerKeys;
|
||||
use log::{info, warn};
|
||||
|
||||
pub(crate) fn match_laptop() -> LaptopBase {
|
||||
for device in rusb::devices().unwrap().iter() {
|
||||
let device_desc = device.device_descriptor().unwrap();
|
||||
if device_desc.vendor_id() == 0x0b05 {
|
||||
match device_desc.product_id() {
|
||||
0x1869 | 0x1866 => {
|
||||
info!("Found GX502 or similar");
|
||||
return LaptopBase {
|
||||
usb_vendor: 0x0B05,
|
||||
usb_product: 0x1866,
|
||||
report_filter_bytes: vec![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: vec![
|
||||
BuiltInModeByte::Single,
|
||||
BuiltInModeByte::Breathing,
|
||||
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(),
|
||||
};
|
||||
}
|
||||
0x1854 => {
|
||||
info!("Found GL753 or similar");
|
||||
return LaptopBase {
|
||||
usb_vendor: 0x0B05,
|
||||
usb_product: 0x1854,
|
||||
report_filter_bytes: vec![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: vec![
|
||||
BuiltInModeByte::Single,
|
||||
BuiltInModeByte::Breathing,
|
||||
BuiltInModeByte::Cycle,
|
||||
],
|
||||
// backlight: Backlight::new("intel_backlight").unwrap(),
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
panic!("could not match laptop");
|
||||
}
|
||||
|
||||
pub(super) struct LaptopBase {
|
||||
usb_vendor: u16,
|
||||
usb_product: u16,
|
||||
report_filter_bytes: Vec<u8>,
|
||||
min_led_bright: u8,
|
||||
max_led_bright: u8,
|
||||
led_endpoint: u8,
|
||||
key_endpoint: u8,
|
||||
supported_modes: Vec<BuiltInModeByte>,
|
||||
//backlight: Backlight,
|
||||
}
|
||||
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
impl LaptopBase {
|
||||
/// Pass in LedWriter as Mutex so it is only locked when required
|
||||
pub(super) async fn run<'a, C>(
|
||||
&self,
|
||||
rogcore: &mut RogCore,
|
||||
led_writer: &Mutex<LedWriter<'a, C>>,
|
||||
config: &Mutex<Config>,
|
||||
key_buf: [u8; 32],
|
||||
) -> Result<(), AuraError>
|
||||
where
|
||||
C: rusb::UsbContext,
|
||||
{
|
||||
match self.usb_product {
|
||||
0x1869 | 0x1866 => {
|
||||
self.gx502_runner(rogcore, led_writer, config, key_buf)
|
||||
.await
|
||||
}
|
||||
0x1854 => {
|
||||
self.gl753_runner(rogcore, led_writer, config, key_buf)
|
||||
.await
|
||||
}
|
||||
_ => panic!("No runner available for this device"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn led_endpoint(&self) -> u8 {
|
||||
self.led_endpoint
|
||||
}
|
||||
pub(super) fn key_endpoint(&self) -> u8 {
|
||||
self.key_endpoint
|
||||
}
|
||||
pub(super) fn key_filter(&self) -> &[u8] {
|
||||
&self.report_filter_bytes
|
||||
}
|
||||
pub(super) fn usb_vendor(&self) -> u16 {
|
||||
self.usb_vendor
|
||||
}
|
||||
pub(super) fn usb_product(&self) -> u16 {
|
||||
self.usb_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<'a, C>(
|
||||
&self,
|
||||
rogcore: &mut RogCore,
|
||||
led_writer: &Mutex<LedWriter<'a, C>>,
|
||||
config: &Mutex<Config>,
|
||||
key_buf: [u8; 32],
|
||||
) -> 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();
|
||||
match GX502Keys::from(key_buf[1]) {
|
||||
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)
|
||||
.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)
|
||||
.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)
|
||||
.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)
|
||||
.await?;
|
||||
}
|
||||
GX502Keys::ScreenBrightUp => {
|
||||
rogcore.virt_keys().press(ConsumerKeys::BacklightInc.into())
|
||||
} //self.backlight.step_up(),
|
||||
GX502Keys::ScreenBrightDown => {
|
||||
rogcore.virt_keys().press(ConsumerKeys::BacklightDec.into())
|
||||
} //self.backlight.step_down(),
|
||||
GX502Keys::Sleep => rogcore.suspend_with_systemd(),
|
||||
GX502Keys::AirplaneMode => rogcore.toggle_airplane_mode(),
|
||||
GX502Keys::MicToggle => {}
|
||||
GX502Keys::Fan => {
|
||||
let mut config = config.lock().await;
|
||||
rogcore.fan_mode_step(&mut config).unwrap_or_else(|err| {
|
||||
warn!("Couldn't toggle fan mode: {:?}", err);
|
||||
});
|
||||
}
|
||||
GX502Keys::ScreenToggle => {
|
||||
rogcore.virt_keys().press(ConsumerKeys::BacklightTog.into());
|
||||
}
|
||||
GX502Keys::TouchPadToggle => {
|
||||
let mut key = [0u8; 32];
|
||||
key[0] = 0x01;
|
||||
key[3] = 0x070;
|
||||
rogcore.virt_keys().press(key);
|
||||
}
|
||||
GX502Keys::Rog => {
|
||||
//rogcore.aura_effect_init()?;
|
||||
//rogcore.aura_write_effect(&self.per_key_led)?;
|
||||
let mut key = [0u8; 32];
|
||||
key[0] = 0x01;
|
||||
key[3] = 0x68; // XF86Tools? F13
|
||||
rogcore.virt_keys().press(key);
|
||||
}
|
||||
GX502Keys::None => {
|
||||
if key_buf[0] != 0x5A {
|
||||
info!("Unmapped key, attempt passthrough: {:X?}", &key_buf[1]);
|
||||
rogcore.virt_keys().press(key_buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// GL753VE == 0x1854, 4 zone keyboard
|
||||
async fn gl753_runner<'a, C>(
|
||||
&self,
|
||||
rogcore: &mut RogCore,
|
||||
led_writer: &Mutex<LedWriter<'a, C>>,
|
||||
config: &Mutex<Config>,
|
||||
key_buf: [u8; 32],
|
||||
) -> 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();
|
||||
match GL753Keys::from(key_buf[1]) {
|
||||
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)
|
||||
.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)
|
||||
.await?;
|
||||
}
|
||||
GL753Keys::ScreenBrightUp => {
|
||||
rogcore.virt_keys().press(ConsumerKeys::BacklightInc.into())
|
||||
}
|
||||
GL753Keys::ScreenBrightDown => {
|
||||
rogcore.virt_keys().press(ConsumerKeys::BacklightDec.into())
|
||||
}
|
||||
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, attempt passthrough: {:X?}", &key_buf[1]);
|
||||
rogcore.virt_keys().press(key_buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum GX502Keys {
|
||||
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<u8> for GX502Keys {
|
||||
fn from(byte: u8) -> Self {
|
||||
match byte {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum GL753Keys {
|
||||
Rog = 0x38,
|
||||
ScreenToggle = 0x35,
|
||||
ScreenBrightDown = 0x10,
|
||||
ScreenBrightUp = 0x20,
|
||||
TouchPadToggle = 0x6b,
|
||||
Sleep = 0x6C,
|
||||
AirplaneMode = 0x88,
|
||||
LedBrightUp = 0xC4,
|
||||
LedBrightDown = 0xC5,
|
||||
None,
|
||||
}
|
||||
|
||||
impl From<u8> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
7
rog-core/src/lib.rs
Normal file
7
rog-core/src/lib.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
mod config;
|
||||
/// The core module which allows writing to LEDs or polling the
|
||||
/// laptop keyboard attached devices
|
||||
pub mod core;
|
||||
pub mod daemon;
|
||||
pub mod laptops;
|
||||
mod virt_device;
|
||||
74
rog-core/src/main.rs
Normal file
74
rog-core/src/main.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use daemon::{core::LedBrightness, daemon::start_daemon};
|
||||
use env_logger::{Builder, Target};
|
||||
use gumdrop::Options;
|
||||
use log::LevelFilter;
|
||||
use rog_aura::{cli_options::SetAuraBuiltin, AuraDbusWriter, LED_MSG_LEN};
|
||||
|
||||
static VERSION: &'static str = "0.8.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 = "<off, low, med, high>")]
|
||||
bright: Option<LedBrightness>,
|
||||
#[options(command)]
|
||||
command: Option<Command>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
enum Command {
|
||||
#[options(help = "Set the keyboard lighting from built-in modes")]
|
||||
LedMode(LedModeCommand),
|
||||
}
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
struct LedModeCommand {
|
||||
#[options(help = "print help message")]
|
||||
help: bool,
|
||||
#[options(command, required)]
|
||||
command: Option<SetAuraBuiltin>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut builder = Builder::new();
|
||||
builder.target(Target::Stdout);
|
||||
builder.format_timestamp(None);
|
||||
builder.filter(None, LevelFilter::Info).init();
|
||||
|
||||
let parsed = CLIStart::parse_args_default_or_exit();
|
||||
if parsed.daemon {
|
||||
start_daemon().await?;
|
||||
}
|
||||
if parsed.version {
|
||||
println!("Version: {}", VERSION);
|
||||
}
|
||||
|
||||
let writer = AuraDbusWriter::new()?;
|
||||
|
||||
if let Some(Command::LedMode(mode)) = parsed.command {
|
||||
if let Some(command) = mode.command {
|
||||
// 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() {
|
||||
writer.write_bytes(arr)?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
writer.write_builtin_mode(&command)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(brightness) = parsed.bright {
|
||||
writer.write_brightness(brightness.level())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
166
rog-core/src/virt_device.rs
Normal file
166
rog-core/src/virt_device.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
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<std::fs::File>,
|
||||
}
|
||||
|
||||
impl VirtKeys {
|
||||
pub(crate) fn new() -> Self {
|
||||
VirtKeys {
|
||||
device: UHIDDevice::create(CreateParams {
|
||||
name: String::from("Virtual ROG buttons"),
|
||||
phys: String::from(""),
|
||||
uniq: String::from(""),
|
||||
bus: Bus::USB,
|
||||
vendor: 0x0b05,
|
||||
product: 0x1866,
|
||||
version: 0,
|
||||
country: 0,
|
||||
// This is a device which emits the usage code as a whole, rather than as bits
|
||||
rd_data: [
|
||||
// Consumer Device TLC
|
||||
0x05, 0x0C, // Usage Page (Consumer)
|
||||
0x09, 0x01, // Usage (Consumer Control)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x85, 0x02, // Report ID (2)
|
||||
0x19, 0x00, // Usage Minimum (Unassigned)
|
||||
0x2A, 0x3C, 0x02, // Usage Maximum (AC Format)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0x3C, 0x02, // Logical Maximum (572)
|
||||
0x75, 0x10, // Report Size (16)
|
||||
0x95, 0x02, // Report Count (2)
|
||||
0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State)
|
||||
0xC0, //
|
||||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||
0x09, 0x06, // Usage (Keyboard)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x85, 0x01, // Report ID (1)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x95, 0x08, // Report Count (8)
|
||||
0x05, 0x07, // Usage Page (Kbrd/Keypad)
|
||||
0x19, 0xE0, // Usage Minimum (0xE0)
|
||||
0x29, 0xE7, // Usage Maximum (0xE7)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State)
|
||||
0x95, 0x05, // Report Count (5)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x05, 0x08, // Usage Page (LEDs)
|
||||
0x19, 0x01, // Usage Minimum (Num Lock)
|
||||
0x29, 0x05, // Usage Maximum (Kana)
|
||||
0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x75, 0x03, // Report Size (3)
|
||||
0x91, 0x03, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State)
|
||||
0x95, 0x1E, // Report Count (30)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
0x05, 0x07, // Usage Page (Kbrd/Keypad)
|
||||
0x19, 0x00, // Usage Minimum (0x00)
|
||||
0x2A, 0xFF, 0x00, // Usage Maximum (0xFF)
|
||||
0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State)
|
||||
0xC0, // End Collection
|
||||
]
|
||||
.to_vec(),
|
||||
})
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A single on/off key press
|
||||
pub(crate) fn press(&mut self, input: [u8; 32]) {
|
||||
self.device.write(&input).unwrap();
|
||||
let mut reset = [0u8; 32];
|
||||
reset[0] = input[0];
|
||||
self.device.write(&reset).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum ConsumerKeys {
|
||||
Power = 0x30,
|
||||
Sleep = 0x32,
|
||||
Menu = 0x0040,
|
||||
|
||||
MediaRecord = 0xB2,
|
||||
MediaFastFwd = 0xB3,
|
||||
MediaRewind = 0xB4,
|
||||
MediaNext = 0xB5,
|
||||
MediaPrev = 0xB6,
|
||||
MediaStop = 0xB7,
|
||||
MediaPlayPause = 0xCD,
|
||||
MediaPause = 0xB0,
|
||||
|
||||
MediaVolMute = 0xE2,
|
||||
MediaVolUp = 0xE9,
|
||||
MediaVolDown = 0xEA,
|
||||
|
||||
BacklightInc = 0x006F,
|
||||
BacklightDec = 0x0070,
|
||||
|
||||
BacklightTog = 0x072, // USAGE (Backlight toggle? display toggle?)
|
||||
BacklightMin = 0x73,
|
||||
BacklightMax = 0x74,
|
||||
|
||||
ControlConfig = 0x183,
|
||||
|
||||
LaunchWordEditor = 0x184,
|
||||
LaunchTextEditor = 0x185,
|
||||
LaunchSpreadSheet = 0x186,
|
||||
LaunchGraphicsEditor = 0x187,
|
||||
LaunchPresentationApp = 0x188,
|
||||
LaunchDatabaseEditor = 0x189,
|
||||
LaunchEmailApp = 0x18A,
|
||||
LaunchNewsReader = 0x18B,
|
||||
LaunchCalendarApp = 0x018e,
|
||||
LaunchTaskManagementApp = 0x18F,
|
||||
LaunchWebBrowser = 0x196,
|
||||
ControlPanel = 0x19F,
|
||||
|
||||
VideoOutStep = 0x82,
|
||||
|
||||
Documents = 0x1A7,
|
||||
FileBrowser = 0x1B4,
|
||||
ImageBrowser = 0x1B6,
|
||||
AudioBrowser = 0x1B7,
|
||||
MovieBrowser = 0x1B8,
|
||||
}
|
||||
|
||||
impl From<ConsumerKeys> for [u8; 32] {
|
||||
fn from(key: ConsumerKeys) -> Self {
|
||||
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;
|
||||
bytes
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user