Better laptop LED mode handling

This commit is contained in:
Luke D Jones
2020-08-11 12:16:23 +12:00
parent 88724a6d05
commit 756c5f674e
16 changed files with 59597 additions and 150 deletions

View File

@@ -3,7 +3,7 @@ use serde_derive::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
pub static CONFIG_PATH: &str = "/etc/asusd.conf";
pub static CONFIG_PATH: &str = "/etc/asusd/asusd.conf";
#[derive(Default, Deserialize, Serialize)]
pub struct Config {

View File

@@ -42,20 +42,20 @@ impl crate::Controller for CtrlAnimeDisplay {
type A = Vec<Vec<u8>>;
/// Spawns two tasks which continuously check for changes
fn spawn_task(
fn spawn_task_loop(
mut self,
_: Arc<Mutex<Config>>,
mut recv: Receiver<Self::A>,
_: Option<Arc<SyncConnection>>,
_: Option<Arc<Signal<()>>>,
) -> JoinHandle<()> {
tokio::spawn(async move {
) -> Vec<JoinHandle<()>> {
vec![tokio::spawn(async move {
while let Some(image) = recv.recv().await {
self.do_command(AnimatrixCommand::WriteImage(image))
.await
.unwrap_or_else(|err| warn!("{}", err));
}
})
})]
}
async fn reload_from_config(&mut self, _: &mut Config) -> Result<(), Box<dyn Error>> {

View File

@@ -23,20 +23,20 @@ impl crate::Controller for CtrlCharge {
type A = u8;
/// Spawns two tasks which continuously check for changes
fn spawn_task(
fn spawn_task_loop(
self,
config: Arc<Mutex<Config>>,
mut recv: Receiver<Self::A>,
_: Option<Arc<SyncConnection>>,
_: Option<Arc<Signal<()>>>,
) -> JoinHandle<()> {
tokio::spawn(async move {
) -> Vec<JoinHandle<()>> {
vec![tokio::spawn(async move {
while let Some(n) = recv.recv().await {
let mut config = config.lock().await;
self.set_charge_limit(n, &mut config)
.unwrap_or_else(|err| warn!("{:?}", err));
}
})
})]
}
async fn reload_from_config(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {

View File

@@ -26,39 +26,40 @@ impl crate::Controller for CtrlFanAndCPU {
type A = u8;
/// Spawns two tasks which continuously check for changes
fn spawn_task(
fn spawn_task_loop(
self,
config: Arc<Mutex<Config>>,
mut recv: Receiver<Self::A>,
_: Option<Arc<SyncConnection>>,
_: Option<Arc<Signal<()>>>,
) -> JoinHandle<()> {
) -> Vec<JoinHandle<()>> {
let gate1 = Arc::new(Mutex::new(self));
let gate2 = gate1.clone();
let config1 = config.clone();
// spawn an endless loop
tokio::spawn(async move {
while let Some(mode) = recv.recv().await {
let mut config = config1.lock().await;
if let Ok(mut lock) = gate1.try_lock() {
lock.set_fan_mode(mode, &mut config)
.unwrap_or_else(|err| warn!("{:?}", err));
}
}
});
// need to watch file path
// TODO: split this out to a struct CtrlFanAndCPUWatcher or similar
tokio::spawn(async move {
loop {
if let Ok(mut lock) = gate2.try_lock() {
let mut config = config.lock().await;
lock.fan_mode_check_change(&mut config)
.unwrap_or_else(|err| warn!("{:?}", err));
vec![
tokio::spawn(async move {
while let Some(mode) = recv.recv().await {
let mut config = config1.lock().await;
if let Ok(mut lock) = gate1.try_lock() {
lock.set_fan_mode(mode, &mut config)
.unwrap_or_else(|err| warn!("{:?}", err));
}
}
}),
// need to watch file path
tokio::spawn(async move {
loop {
if let Ok(mut lock) = gate2.try_lock() {
let mut config = config.lock().await;
lock.fan_mode_check_change(&mut config)
.unwrap_or_else(|err| warn!("{:?}", err));
}
tokio::time::delay_for(std::time::Duration::from_millis(500)).await;
}
})
tokio::time::delay_for(std::time::Duration::from_millis(500)).await;
}
}),
]
}
async fn reload_from_config(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {

View File

@@ -30,14 +30,14 @@ impl crate::Controller for CtrlKbdBacklight {
type A = AuraModes;
/// Spawns two tasks which continuously check for changes
fn spawn_task(
fn spawn_task_loop(
mut self,
config: Arc<Mutex<Config>>,
mut recv: Receiver<Self::A>,
connection: Option<Arc<SyncConnection>>,
signal: Option<Arc<Signal<()>>>,
) -> JoinHandle<()> {
tokio::spawn(async move {
) -> Vec<JoinHandle<()>> {
vec![tokio::spawn(async move {
while let Some(command) = recv.recv().await {
let mut config = config.lock().await;
match &command {
@@ -65,7 +65,7 @@ impl crate::Controller for CtrlKbdBacklight {
}
}
}
})
})]
}
async fn reload_from_config(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {

View File

@@ -41,9 +41,13 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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().load(laptop.supported_modes());
let mut config = if let Some(laptop) = laptop.as_ref() {
Config::default().load(laptop.supported_modes())
} else {
Config::default().load(&[])
};
let mut led_control =
let mut led_control = if let Some(laptop) = laptop {
CtrlKbdBacklight::new(laptop.usb_product(), laptop.supported_modes().to_owned())
.map_or_else(
|err| {
@@ -51,7 +55,10 @@ pub async fn start_daemon() -> Result<(), Box<dyn Error>> {
None
},
Some,
);
)
} else {
None
};
let mut charge_control = CtrlCharge::new().map_or_else(
|err| {
@@ -134,19 +141,19 @@ pub async fn start_daemon() -> Result<(), Box<dyn Error>> {
// Begin all tasks
let mut handles = Vec::new();
if let Ok(ctrl) = CtrlAnimeDisplay::new() {
handles.push(ctrl.spawn_task(config.clone(), animatrix_recv, None, None));
handles.append(&mut ctrl.spawn_task_loop(config.clone(), animatrix_recv, None, None));
}
if let Some(ctrl) = fan_control.take() {
handles.push(ctrl.spawn_task(config.clone(), fan_mode_recv, None, None));
handles.append(&mut ctrl.spawn_task_loop(config.clone(), fan_mode_recv, None, None));
}
if let Some(ctrl) = charge_control.take() {
handles.push(ctrl.spawn_task(config.clone(), charge_limit_recv, None, None));
handles.append(&mut ctrl.spawn_task_loop(config.clone(), charge_limit_recv, None, None));
}
if let Some(ctrl) = led_control.take() {
handles.push(ctrl.spawn_task(
handles.append(&mut ctrl.spawn_task_loop(
config.clone(),
aura_command_recv,
Some(connection.clone()),

View File

@@ -1,112 +1,13 @@
use asus_nb::aura_modes::{
AuraModes, BREATHING, COMET, FLASH, HIGHLIGHT, LASER, MULTISTATIC, PULSE, RAIN, RAINBOW, RGB,
RIPPLE, STAR, STATIC, STROBE,
};
use asus_nb::aura_modes::{AuraModes, BREATHING, STATIC, STROBE};
use log::{info, warn};
use serde_derive::{Deserialize, Serialize};
use std::fs::OpenOptions;
use std::io::Read;
pub static LEDMODE_CONFIG_PATH: &str = "/etc/asusd/asusd-ledmodes.toml";
static HELP_ADDRESS: &str = "https://gitlab.com/asus-linux/asus-nb-ctrl";
pub 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() {
0x1866 => return select_1866_device("1866".to_owned()),
0x1869 => return select_1866_device("1869".to_owned()),
0x1854 => {
info!("Found GL753 or similar");
return LaptopBase {
usb_product: "1854".to_string(),
supported_modes: vec![STATIC, BREATHING, STROBE],
};
}
_ => {}
}
}
}
panic!("could not match laptop");
}
fn select_1866_device(prod: String) -> LaptopBase {
let dmi = sysfs_class::DmiId::default();
let board_name = dmi.board_name().expect("Could not get board_name");
let prod_name = dmi.product_name().expect("Could not get board_name");
info!("Product name: {}", prod_name.trim());
info!("Board name: {}", board_name.trim());
let mut laptop = LaptopBase {
usb_product: prod,
supported_modes: vec![],
};
// AniMe, no RGB
if board_name.starts_with("GA401")
|| board_name.starts_with("GA502")
|| board_name.starts_with("GU502")
{
info!("No RGB control available");
// RGB, per-key settings, no zones
} else if board_name.starts_with("GX502")
|| board_name.starts_with("GX701")
|| board_name.starts_with("G531")
|| board_name.starts_with("GL531")
|| board_name.starts_with("G532")
{
laptop.supported_modes = vec![
STATIC, BREATHING, STROBE, RAINBOW, STAR, RAIN, HIGHLIGHT, LASER, RIPPLE, PULSE, COMET,
FLASH, RGB,
];
} else if board_name.starts_with("G531") || board_name.starts_with("G731") {
laptop.supported_modes = vec![
STATIC,
BREATHING,
STROBE,
RAINBOW,
STAR,
RAIN,
HIGHLIGHT,
LASER,
RIPPLE,
PULSE,
COMET,
FLASH,
MULTISTATIC,
RGB,
];
// RGB, limited effects, no zones
} else if board_name.starts_with("G512LI") || board_name.starts_with("G712LI") {
laptop.supported_modes = vec![STATIC, BREATHING, STROBE, RAINBOW, PULSE];
// RGB, limited effects, 4-zone RGB
} else if board_name.starts_with("GM501")
|| board_name.starts_with("GX531")
|| board_name.starts_with("G512")
|| board_name.starts_with("G712")
{
laptop.supported_modes = vec![STATIC, BREATHING, STROBE, RAINBOW, PULSE, MULTISTATIC];
} else {
warn!(
"Unsupported laptop, please request support at {}",
HELP_ADDRESS
);
warn!("Continuing with minimal support")
}
if !laptop.supported_modes.is_empty() {
info!("Supported Keyboard LED modes are:");
for mode in &laptop.supported_modes {
let mode = <&str>::from(&<AuraModes>::from(*mode));
info!("- {}", mode);
}
info!(
"If these modes are incorrect or missing please request support at {}",
HELP_ADDRESS
);
}
laptop
}
pub struct LaptopBase {
usb_product: String,
supported_modes: Vec<u8>,
@@ -120,3 +21,120 @@ impl LaptopBase {
&self.supported_modes
}
}
pub fn match_laptop() -> Option<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() {
0x1866 => {
let laptop = select_1866_device("1866".to_owned());
print_modes(&laptop.supported_modes);
return Some(laptop);
}
0x1869 => return Some(select_1866_device("1869".to_owned())),
0x1854 => {
info!("Found GL753 or similar");
return Some(LaptopBase {
usb_product: "1854".to_string(),
supported_modes: vec![STATIC, BREATHING, STROBE],
});
}
_ => {}
}
}
}
None
}
fn select_1866_device(prod: String) -> LaptopBase {
let dmi = sysfs_class::DmiId::default();
let board_name = dmi.board_name().expect("Could not get board_name");
let prod_family = dmi.product_family().expect("Could not get product_family");
let prod_name = dmi.product_name().expect("Could not get product_name");
info!("Product name: {}", prod_name.trim());
info!("Board name: {}", board_name.trim());
let mut laptop = LaptopBase {
usb_product: prod,
supported_modes: vec![],
};
if let Some(modes) = LEDModeGroup::load_from_config() {
if let Some(led_modes) = modes.matcher(&prod_family, &board_name) {
laptop.supported_modes = led_modes;
return laptop;
}
}
warn!(
"Unsupported laptop, please request support at {}",
HELP_ADDRESS
);
warn!("Continuing with minimal support");
laptop
}
fn print_modes(supported_modes: &[u8]) {
if !supported_modes.is_empty() {
info!("Supported Keyboard LED modes are:");
for mode in supported_modes {
let mode = <&str>::from(&<AuraModes>::from(*mode));
info!("- {}", mode);
}
info!(
"If these modes are incorrect or missing please request support at {}",
HELP_ADDRESS
);
} else {
info!("No RGB control available");
}
}
#[derive(Debug, Deserialize, Serialize)]
struct LEDModeGroup {
led_modes: Vec<LEDModes>,
}
impl LEDModeGroup {
/// Consumes the LEDModes
fn matcher(self, prod_family: &str, board_name: &str) -> Option<Vec<u8>> {
for led_modes in self.led_modes {
if prod_family.contains(&led_modes.prod_family) {
for board in led_modes.board_names {
if board_name.contains(&board) {
info!("Matched to {} {}", led_modes.prod_family, board);
return Some(led_modes.led_modes);
}
}
}
}
None
}
fn load_from_config() -> Option<Self> {
if let Ok(mut file) = OpenOptions::new().read(true).open(&LEDMODE_CONFIG_PATH) {
let mut buf = String::new();
if let Ok(l) = file.read_to_string(&mut buf) {
if l == 0 {
warn!("{} is empty", LEDMODE_CONFIG_PATH);
} else {
return Some(toml::from_str(&buf).unwrap_or_else(|_| {
panic!("Could not deserialise {}", LEDMODE_CONFIG_PATH)
}));
}
}
}
warn!("Does {} exist?", LEDMODE_CONFIG_PATH);
None
}
}
#[derive(Debug, Deserialize, Serialize)]
struct LEDModes {
prod_family: String,
board_names: Vec<String>,
led_modes: Vec<u8>,
}

View File

@@ -35,11 +35,11 @@ pub trait Controller {
/// Spawn an infinitely running task (usually) which checks a Receiver for input,
/// and may send a signal over dbus
fn spawn_task(
fn spawn_task_loop(
self,
config: Arc<Mutex<Config>>,
recv: Receiver<Self::A>,
connection: Option<Arc<SyncConnection>>,
signal: Option<Arc<Signal<()>>>,
) -> JoinHandle<()>;
) -> Vec<JoinHandle<()>>;
}