mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
GFX control, no-tokio, no-async, dbus client refactor
- Working gfx modes <iGPU only, dGPU only, or hybrid> - Add signal for gfx vendor change and make CLI wait for signal - Add polling for led brightness to save to config - Move daemon to zbus crate - dbus client refactor - Further dbus methods and updates - Add basic notification user daemon and systemd service
This commit is contained in:
@@ -10,6 +10,7 @@ pub static CONFIG_PATH: &str = "/etc/asusd/asusd.conf";
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
pub gfx_managed: bool,
|
||||
pub active_profile: String,
|
||||
pub toggle_profiles: Vec<String>,
|
||||
// TODO: remove power_profile
|
||||
@@ -50,34 +51,38 @@ impl Config {
|
||||
|
||||
fn create_default(file: &mut File, supported_led_modes: &[u8]) -> Self {
|
||||
// create a default config here
|
||||
let mut c = Config::default();
|
||||
c.bat_charge_limit = 100;
|
||||
c.kbd_backlight_mode = 0;
|
||||
c.kbd_led_brightness = 1;
|
||||
let mut config = Config::default();
|
||||
config.gfx_managed = true;
|
||||
|
||||
config.bat_charge_limit = 100;
|
||||
config.kbd_backlight_mode = 0;
|
||||
config.kbd_led_brightness = 1;
|
||||
|
||||
for n in supported_led_modes {
|
||||
c.kbd_backlight_modes.push(AuraModes::from(*n))
|
||||
config.kbd_backlight_modes.push(AuraModes::from(*n))
|
||||
}
|
||||
|
||||
let profile = Profile::default();
|
||||
c.power_profiles.insert("normal".into(), profile);
|
||||
config.power_profiles.insert("normal".into(), profile);
|
||||
|
||||
let mut profile = Profile::default();
|
||||
profile.fan_preset = 1;
|
||||
c.power_profiles.insert("boost".into(), profile);
|
||||
config.power_profiles.insert("boost".into(), profile);
|
||||
|
||||
let mut profile = Profile::default();
|
||||
profile.fan_preset = 2;
|
||||
c.power_profiles.insert("silent".into(), profile);
|
||||
config.power_profiles.insert("silent".into(), profile);
|
||||
|
||||
c.toggle_profiles.push("normal".into());
|
||||
c.toggle_profiles.push("boost".into());
|
||||
c.toggle_profiles.push("silent".into());
|
||||
c.active_profile = "normal".into();
|
||||
config.toggle_profiles.push("normal".into());
|
||||
config.toggle_profiles.push("boost".into());
|
||||
config.toggle_profiles.push("silent".into());
|
||||
config.active_profile = "normal".into();
|
||||
|
||||
// Should be okay to unwrap this as is since it is a Default
|
||||
let json = serde_json::to_string_pretty(&c).unwrap();
|
||||
let json = serde_json::to_string_pretty(&config).unwrap();
|
||||
file.write_all(json.as_bytes())
|
||||
.unwrap_or_else(|_| panic!("Could not write {}", CONFIG_PATH));
|
||||
c
|
||||
config
|
||||
}
|
||||
|
||||
pub fn read(&mut self) {
|
||||
@@ -97,6 +102,17 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_new() -> Result<Config, Box<dyn std::error::Error>> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&CONFIG_PATH)
|
||||
.unwrap_or_else(|err| panic!("Error reading {}: {}", CONFIG_PATH, err));
|
||||
let mut buf = String::new();
|
||||
file.read_to_string(&mut buf)?;
|
||||
let x: Config = serde_json::from_str(&buf)?;
|
||||
Ok(x)
|
||||
}
|
||||
|
||||
pub fn write(&self) {
|
||||
let mut file = File::create(CONFIG_PATH).expect("Couldn't overwrite config");
|
||||
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
|
||||
|
||||
@@ -9,16 +9,13 @@ const INIT: u8 = 0xc2;
|
||||
const APPLY: u8 = 0xc3;
|
||||
const SET: u8 = 0xc4;
|
||||
|
||||
use crate::config::Config;
|
||||
use asus_nb::error::AuraError;
|
||||
use log::{error, info, warn};
|
||||
use rusb::{Device, DeviceHandle};
|
||||
use std::convert::TryInto;
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::task::JoinHandle;
|
||||
use zbus::dbus_interface;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
@@ -34,32 +31,24 @@ pub struct CtrlAnimeDisplay {
|
||||
initialised: bool,
|
||||
}
|
||||
|
||||
use ::dbus::{nonblock::SyncConnection, tree::Signal};
|
||||
use async_trait::async_trait;
|
||||
//AnimatrixWrite
|
||||
pub trait Dbus {
|
||||
fn set_anime(&mut self, input: Vec<Vec<u8>>);
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::Controller for CtrlAnimeDisplay {
|
||||
type A = Vec<Vec<u8>>;
|
||||
|
||||
/// Spawns two tasks which continuously check for changes
|
||||
fn spawn_task_loop(
|
||||
mut self,
|
||||
_: Arc<Mutex<Config>>,
|
||||
mut recv: Receiver<Self::A>,
|
||||
_: Option<Arc<SyncConnection>>,
|
||||
_: Option<Arc<Signal<()>>>,
|
||||
) -> 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));
|
||||
}
|
||||
})]
|
||||
impl crate::ZbusAdd for CtrlAnimeDisplay {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(&"/org/asuslinux/Anime".try_into().unwrap(), self)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
async fn reload_from_config(&mut self, _: &mut Config) -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl Dbus for CtrlAnimeDisplay {
|
||||
fn set_anime(&mut self, input: Vec<Vec<u8>>) {
|
||||
self.do_command(AnimatrixCommand::WriteImage(input))
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,15 +89,15 @@ impl CtrlAnimeDisplay {
|
||||
Err(rusb::Error::NoDevice)
|
||||
}
|
||||
|
||||
pub async fn do_command(&mut self, command: AnimatrixCommand) -> Result<(), AuraError> {
|
||||
pub fn do_command(&mut self, command: AnimatrixCommand) -> Result<(), AuraError> {
|
||||
if !self.initialised {
|
||||
self.do_initialization().await?
|
||||
self.do_initialization()?
|
||||
}
|
||||
|
||||
match command {
|
||||
AnimatrixCommand::WriteImage(effect) => self.write_image(effect).await?,
|
||||
AnimatrixCommand::Set => self.do_set().await?,
|
||||
AnimatrixCommand::Apply => self.do_apply().await?,
|
||||
AnimatrixCommand::WriteImage(effect) => self.write_image(effect)?,
|
||||
AnimatrixCommand::Set => self.do_set()?,
|
||||
AnimatrixCommand::Apply => self.do_apply()?,
|
||||
//AnimatrixCommand::ReloadLast => self.reload_last_builtin(&config).await?,
|
||||
}
|
||||
Ok(())
|
||||
@@ -116,7 +105,7 @@ impl CtrlAnimeDisplay {
|
||||
|
||||
/// Should only be used if the bytes you are writing are verified correct
|
||||
#[inline]
|
||||
async fn write_bytes(&self, message: &[u8]) -> Result<(), AuraError> {
|
||||
fn write_bytes(&self, message: &[u8]) -> Result<(), AuraError> {
|
||||
match self.handle.write_control(
|
||||
0x21, // request_type
|
||||
0x09, // request
|
||||
@@ -150,22 +139,22 @@ impl CtrlAnimeDisplay {
|
||||
///
|
||||
/// Where led brightness is 0..255, low to high
|
||||
#[inline]
|
||||
async fn write_image(&mut self, image: Vec<Vec<u8>>) -> Result<(), AuraError> {
|
||||
fn write_image(&mut self, image: Vec<Vec<u8>>) -> Result<(), AuraError> {
|
||||
for row in image.iter() {
|
||||
self.write_bytes(row).await?;
|
||||
self.write_bytes(row)?;
|
||||
}
|
||||
self.do_flush().await?;
|
||||
self.do_flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn do_initialization(&mut self) -> Result<(), AuraError> {
|
||||
fn do_initialization(&mut self) -> Result<(), AuraError> {
|
||||
let mut init = [0; PACKET_SIZE];
|
||||
init[0] = DEV_PAGE; // This is the USB page we're using throughout
|
||||
for (idx, byte) in INIT_STR.as_bytes().iter().enumerate() {
|
||||
init[idx + 1] = *byte
|
||||
}
|
||||
self.write_bytes(&init).await?;
|
||||
self.write_bytes(&init)?;
|
||||
|
||||
// clear the init array and write other init message
|
||||
for ch in init.iter_mut() {
|
||||
@@ -174,43 +163,43 @@ impl CtrlAnimeDisplay {
|
||||
init[0] = DEV_PAGE; // write it to be sure?
|
||||
init[1] = INIT;
|
||||
|
||||
self.write_bytes(&init).await?;
|
||||
self.write_bytes(&init)?;
|
||||
self.initialised = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn do_flush(&mut self) -> Result<(), AuraError> {
|
||||
fn do_flush(&mut self) -> Result<(), AuraError> {
|
||||
let mut flush = [0; PACKET_SIZE];
|
||||
flush[0] = DEV_PAGE;
|
||||
flush[1] = WRITE;
|
||||
flush[2] = 0x03;
|
||||
|
||||
self.write_bytes(&flush).await?;
|
||||
self.write_bytes(&flush)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn do_set(&mut self) -> Result<(), AuraError> {
|
||||
fn do_set(&mut self) -> Result<(), AuraError> {
|
||||
let mut flush = [0; PACKET_SIZE];
|
||||
flush[0] = DEV_PAGE;
|
||||
flush[1] = SET;
|
||||
flush[2] = 0x01;
|
||||
flush[3] = 0x80;
|
||||
|
||||
self.write_bytes(&flush).await?;
|
||||
self.write_bytes(&flush)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn do_apply(&mut self) -> Result<(), AuraError> {
|
||||
fn do_apply(&mut self) -> Result<(), AuraError> {
|
||||
let mut flush = [0; PACKET_SIZE];
|
||||
flush[0] = DEV_PAGE;
|
||||
flush[1] = APPLY;
|
||||
flush[2] = 0x01;
|
||||
flush[3] = 0x80;
|
||||
|
||||
self.write_bytes(&flush).await?;
|
||||
self.write_bytes(&flush)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,74 +1,84 @@
|
||||
use crate::config::Config;
|
||||
use log::{error, info, warn};
|
||||
use std::error::Error;
|
||||
use crate::{config::Config, error::RogError};
|
||||
//use crate::dbus::DbusEvents;
|
||||
use log::{info, warn};
|
||||
use std::convert::TryInto;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::task::JoinHandle;
|
||||
use std::sync::Mutex;
|
||||
use zbus::dbus_interface;
|
||||
|
||||
static BAT_CHARGE_PATH: &str = "/sys/class/power_supply/BAT0/charge_control_end_threshold";
|
||||
|
||||
pub struct CtrlCharge {
|
||||
path: &'static str,
|
||||
config: Arc<Mutex<Config>>,
|
||||
}
|
||||
|
||||
use ::dbus::{nonblock::SyncConnection, tree::Signal};
|
||||
use async_trait::async_trait;
|
||||
trait Dbus {
|
||||
fn set_limit(&mut self, charge: u8);
|
||||
fn limit(&self) -> i8;
|
||||
fn notify_charge(&self, limit: u8) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::Controller for CtrlCharge {
|
||||
type A = u8;
|
||||
|
||||
/// Spawns two tasks which continuously check for changes
|
||||
fn spawn_task_loop(
|
||||
self,
|
||||
config: Arc<Mutex<Config>>,
|
||||
mut recv: Receiver<Self::A>,
|
||||
_: Option<Arc<SyncConnection>>,
|
||||
_: Option<Arc<Signal<()>>>,
|
||||
) -> 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!("charge_limit: {}", err));
|
||||
}
|
||||
})]
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl Dbus for CtrlCharge {
|
||||
fn set_limit(&mut self, limit: u8) {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
self.set(limit, &mut config).unwrap();
|
||||
self.notify_charge(limit).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
async fn reload_from_config(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {
|
||||
config.read();
|
||||
info!("Reloaded battery charge limit");
|
||||
self.set_charge_limit(config.bat_charge_limit, config)
|
||||
fn limit(&self) -> i8 {
|
||||
if let Ok(config) = self.config.try_lock() {
|
||||
return config.bat_charge_limit as i8;
|
||||
}
|
||||
-1
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_charge(&self, limit: u8) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
impl crate::ZbusAdd for CtrlCharge {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(&"/org/asuslinux/Charge".try_into().unwrap(), self)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlCharge {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
config.read();
|
||||
info!("Reloaded battery charge limit");
|
||||
self.set(config.bat_charge_limit, &mut config)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlCharge {
|
||||
pub fn new() -> Result<Self, Box<dyn Error>> {
|
||||
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
|
||||
let path = CtrlCharge::get_battery_path()?;
|
||||
info!("Device has battery charge threshold control");
|
||||
Ok(CtrlCharge { path })
|
||||
Ok(CtrlCharge { path, config })
|
||||
}
|
||||
|
||||
fn get_battery_path() -> Result<&'static str, std::io::Error> {
|
||||
fn get_battery_path() -> Result<&'static str, RogError> {
|
||||
if Path::new(BAT_CHARGE_PATH).exists() {
|
||||
Ok(BAT_CHARGE_PATH)
|
||||
} else {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"Charge control not available",
|
||||
Err(RogError::MissingFunction(
|
||||
"Charge control not available".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn set_charge_limit(
|
||||
&self,
|
||||
limit: u8,
|
||||
config: &mut Config,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
pub(super) fn set(&self, limit: u8, config: &mut Config) -> Result<(), RogError> {
|
||||
if limit < 20 || limit > 100 {
|
||||
warn!(
|
||||
"Unable to set battery charge limit, must be between 20-100: requested {}",
|
||||
@@ -79,12 +89,9 @@ impl CtrlCharge {
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(self.path)
|
||||
.map_err(|err| {
|
||||
warn!("Failed to open battery charge limit path: {}", err);
|
||||
err
|
||||
})?;
|
||||
.map_err(|err| RogError::Path(self.path.into(), err))?;
|
||||
file.write_all(limit.to_string().as_bytes())
|
||||
.unwrap_or_else(|err| error!("Could not write to {}, {}", BAT_CHARGE_PATH, err));
|
||||
.map_err(|err| RogError::Write(self.path.into(), err))?;
|
||||
info!("Battery charge limit: {}", limit);
|
||||
|
||||
config.read();
|
||||
|
||||
@@ -1,149 +1,200 @@
|
||||
use crate::config::Config;
|
||||
use crate::config::Profile;
|
||||
use crate::config::{Config, Profile};
|
||||
use asus_nb::profile::ProfileEvent;
|
||||
use log::{error, info, warn};
|
||||
use std::error::Error;
|
||||
use log::{info, warn};
|
||||
use std::convert::TryInto;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::task::JoinHandle;
|
||||
use std::sync::Mutex;
|
||||
use zbus::dbus_interface;
|
||||
|
||||
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";
|
||||
static AMD_BOOST_PATH: &str = "/sys/devices/system/cpu/cpufreq/boost";
|
||||
|
||||
pub struct CtrlFanAndCPU {
|
||||
path: &'static str,
|
||||
pub path: &'static str,
|
||||
config: Arc<Mutex<Config>>,
|
||||
}
|
||||
|
||||
use ::dbus::{nonblock::SyncConnection, tree::Signal};
|
||||
use async_trait::async_trait;
|
||||
pub struct DbusFanAndCpu {
|
||||
inner: Arc<Mutex<CtrlFanAndCPU>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::Controller for CtrlFanAndCPU {
|
||||
type A = ProfileEvent;
|
||||
impl DbusFanAndCpu {
|
||||
pub fn new(inner: Arc<Mutex<CtrlFanAndCPU>>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns two tasks which continuously check for changes
|
||||
fn spawn_task_loop(
|
||||
self,
|
||||
config: Arc<Mutex<Config>>,
|
||||
mut recv: Receiver<Self::A>,
|
||||
_: Option<Arc<SyncConnection>>,
|
||||
_: Option<Arc<Signal<()>>>,
|
||||
) -> Vec<JoinHandle<()>> {
|
||||
let gate1 = Arc::new(Mutex::new(self));
|
||||
let gate2 = gate1.clone();
|
||||
let config1 = config.clone();
|
||||
// spawn an endless loop
|
||||
vec![
|
||||
tokio::spawn(async move {
|
||||
while let Some(event) = recv.recv().await {
|
||||
let mut config = config1.lock().await;
|
||||
let mut lock = gate1.lock().await;
|
||||
|
||||
config.read();
|
||||
lock.handle_profile_event(&event, &mut config)
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl DbusFanAndCpu {
|
||||
fn set_profile(&self, profile: String) {
|
||||
if let Ok(event) = serde_json::from_str(&profile) {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
|
||||
cfg.read();
|
||||
ctrl.handle_profile_event(&event, &mut cfg)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
self.notify_profile(&cfg.active_profile)
|
||||
.unwrap_or_else(|_| ());
|
||||
}
|
||||
}),
|
||||
// need to watch file path
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::time::delay_for(std::time::Duration::from_millis(100)).await;
|
||||
let mut lock = gate2.lock().await;
|
||||
let mut config = config.lock().await;
|
||||
lock.fan_mode_check_change(&mut config)
|
||||
.unwrap_or_else(|err| warn!("fan_ctrl: {}", err));
|
||||
}
|
||||
}),
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn reload_from_config(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {
|
||||
let mut file = OpenOptions::new().write(true).open(self.path)?;
|
||||
file.write_all(format!("{}\n", config.power_profile).as_bytes())
|
||||
.unwrap_or_else(|err| error!("Could not write to {}, {}", self.path, err));
|
||||
let profile = config.active_profile.clone();
|
||||
self.set_profile(&profile, config)?;
|
||||
info!(
|
||||
"Reloaded fan mode: {:?}",
|
||||
FanLevel::from(config.power_profile)
|
||||
);
|
||||
fn profile(&mut self) -> String {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.try_lock() {
|
||||
cfg.read();
|
||||
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
|
||||
if let Ok(json) = serde_json::to_string(profile) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"Failed".to_string()
|
||||
}
|
||||
|
||||
fn profiles(&mut self) -> String {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.try_lock() {
|
||||
cfg.read();
|
||||
if let Ok(json) = serde_json::to_string(&cfg.power_profiles) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
}
|
||||
"Failed".to_string()
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_profile(&self, profile: &str) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
impl crate::ZbusAdd for DbusFanAndCpu {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(&"/org/asuslinux/Profile".try_into().unwrap(), self)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlFanAndCPU {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Ok(mut config) = self.config.clone().try_lock() {
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(self.path)
|
||||
.map_err(|err| RogError::Path(self.path.into(), err))?;
|
||||
file.write_all(format!("{}\n", config.power_profile).as_bytes())
|
||||
.map_err(|err| RogError::Write(self.path.into(), err))?;
|
||||
let profile = config.active_profile.clone();
|
||||
self.set(&profile, &mut config)?;
|
||||
info!(
|
||||
"Reloaded fan mode: {:?}",
|
||||
FanLevel::from(config.power_profile)
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::CtrlTask for CtrlFanAndCPU {
|
||||
fn do_task(&mut self) -> Result<(), RogError> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(self.path)
|
||||
.map_err(|err| RogError::Path(self.path.into(), err))?;
|
||||
let mut buf = [0u8; 1];
|
||||
file.read_exact(&mut buf)
|
||||
.map_err(|err| RogError::Read(self.path.into(), err))?;
|
||||
if let Some(num) = char::from(buf[0]).to_digit(10) {
|
||||
if let Ok(mut config) = self.config.clone().try_lock() {
|
||||
if config.power_profile != num as u8 {
|
||||
config.read();
|
||||
|
||||
let mut i = config
|
||||
.toggle_profiles
|
||||
.iter()
|
||||
.position(|x| x == &config.active_profile)
|
||||
.map(|i| i + 1)
|
||||
.unwrap_or(0);
|
||||
if i >= config.toggle_profiles.len() {
|
||||
i = 0;
|
||||
}
|
||||
|
||||
let new_profile = config
|
||||
.toggle_profiles
|
||||
.get(i)
|
||||
.unwrap_or(&config.active_profile)
|
||||
.clone();
|
||||
|
||||
self.set(&new_profile, &mut config)?;
|
||||
|
||||
info!("Profile was changed: {}", &new_profile);
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(RogError::DoTask("Fan-level could not be parsed".into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlFanAndCPU {
|
||||
pub fn new() -> Result<Self, Box<dyn Error>> {
|
||||
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
|
||||
let path = CtrlFanAndCPU::get_fan_path()?;
|
||||
info!("Device has thermal throttle control");
|
||||
Ok(CtrlFanAndCPU { path })
|
||||
Ok(CtrlFanAndCPU { path, config })
|
||||
}
|
||||
|
||||
fn get_fan_path() -> Result<&'static str, std::io::Error> {
|
||||
fn get_fan_path() -> Result<&'static str, RogError> {
|
||||
if Path::new(FAN_TYPE_1_PATH).exists() {
|
||||
Ok(FAN_TYPE_1_PATH)
|
||||
} else if Path::new(FAN_TYPE_2_PATH).exists() {
|
||||
Ok(FAN_TYPE_2_PATH)
|
||||
} else {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"Fan mode not available, you may require a v5.8 series kernel or newer",
|
||||
Err(RogError::MissingFunction(
|
||||
"Fan mode not available, you may require a v5.8 series kernel or newer".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn fan_mode_check_change(
|
||||
&mut self,
|
||||
config: &mut Config,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut file = OpenOptions::new().read(true).open(self.path)?;
|
||||
let mut buf = [0u8; 1];
|
||||
file.read_exact(&mut buf)?;
|
||||
if let Some(num) = char::from(buf[0]).to_digit(10) {
|
||||
if config.power_profile != num as u8 {
|
||||
config.read();
|
||||
pub(super) fn do_update(&mut self, config: &mut Config) -> Result<(), RogError> {
|
||||
config.read();
|
||||
|
||||
let mut i = config
|
||||
.toggle_profiles
|
||||
.iter()
|
||||
.position(|x| x == &config.active_profile)
|
||||
.map(|i| i + 1)
|
||||
.unwrap_or(0);
|
||||
if i >= config.toggle_profiles.len() {
|
||||
i = 0;
|
||||
}
|
||||
|
||||
let new_profile = config
|
||||
.toggle_profiles
|
||||
.get(i)
|
||||
.unwrap_or(&config.active_profile)
|
||||
.clone();
|
||||
|
||||
self.set_profile(&new_profile, config)?;
|
||||
|
||||
info!("Profile was changed: {}", &new_profile);
|
||||
}
|
||||
return Ok(());
|
||||
let mut i = config
|
||||
.toggle_profiles
|
||||
.iter()
|
||||
.position(|x| x == &config.active_profile)
|
||||
.map(|i| i + 1)
|
||||
.unwrap_or(0);
|
||||
if i >= config.toggle_profiles.len() {
|
||||
i = 0;
|
||||
}
|
||||
let err = std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"Fan-level could not be parsed",
|
||||
);
|
||||
Err(Box::new(err))
|
||||
|
||||
let new_profile = config
|
||||
.toggle_profiles
|
||||
.get(i)
|
||||
.unwrap_or(&config.active_profile)
|
||||
.clone();
|
||||
|
||||
self.set(&new_profile, config)?;
|
||||
|
||||
info!("Profile was changed: {}", &new_profile);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn set_fan_mode(
|
||||
&mut self,
|
||||
preset: u8,
|
||||
config: &mut Config,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
pub(super) fn set_fan_mode(&mut self, preset: u8, config: &mut Config) -> Result<(), RogError> {
|
||||
let mode = config.active_profile.clone();
|
||||
let mut fan_ctrl = OpenOptions::new().write(true).open(self.path)?;
|
||||
let mut fan_ctrl = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(self.path)
|
||||
.map_err(|err| RogError::Path(self.path.into(), err))?;
|
||||
config.read();
|
||||
let mut mode_config = config
|
||||
.power_profiles
|
||||
@@ -154,7 +205,7 @@ impl CtrlFanAndCPU {
|
||||
config.write();
|
||||
fan_ctrl
|
||||
.write_all(format!("{}\n", preset).as_bytes())
|
||||
.unwrap_or_else(|err| error!("Could not write to {}, {}", self.path, err));
|
||||
.map_err(|err| RogError::Write(self.path.into(), err))?;
|
||||
info!("Fan mode set to: {:?}", FanLevel::from(preset));
|
||||
self.set_pstate_for_fan_mode(&mode, config)?;
|
||||
self.set_fan_curve_for_fan_mode(&mode, config)?;
|
||||
@@ -165,8 +216,9 @@ impl CtrlFanAndCPU {
|
||||
&mut self,
|
||||
event: &ProfileEvent,
|
||||
config: &mut Config,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
) -> Result<(), RogError> {
|
||||
match event {
|
||||
ProfileEvent::Toggle => self.do_update(config)?,
|
||||
ProfileEvent::ChangeMode(mode) => {
|
||||
self.set_fan_mode(*mode, config)?;
|
||||
}
|
||||
@@ -204,21 +256,24 @@ impl CtrlFanAndCPU {
|
||||
profile.fan_curve = Some(curve.clone());
|
||||
}
|
||||
|
||||
self.set_profile(&profile_key, config)?;
|
||||
self.set(&profile_key, config)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_profile(&mut self, profile: &str, config: &mut Config) -> Result<(), Box<dyn Error>> {
|
||||
fn set(&mut self, profile: &str, config: &mut Config) -> Result<(), RogError> {
|
||||
let mode_config = config
|
||||
.power_profiles
|
||||
.get(profile)
|
||||
.ok_or_else(|| RogError::MissingProfile(profile.into()))?;
|
||||
let mut fan_ctrl = OpenOptions::new().write(true).open(self.path)?;
|
||||
let mut fan_ctrl = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(self.path)
|
||||
.map_err(|err| RogError::Path(self.path.into(), err))?;
|
||||
fan_ctrl
|
||||
.write_all(format!("{}\n", mode_config.fan_preset).as_bytes())
|
||||
.unwrap_or_else(|err| error!("Could not write to {}, {}", self.path, err));
|
||||
.map_err(|err| RogError::Write(self.path.into(), err))?;
|
||||
config.power_profile = mode_config.fan_preset;
|
||||
|
||||
self.set_pstate_for_fan_mode(profile, config)?;
|
||||
@@ -230,12 +285,7 @@ impl CtrlFanAndCPU {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_pstate_for_fan_mode(
|
||||
&self,
|
||||
// mode: FanLevel,
|
||||
mode: &str,
|
||||
config: &mut Config,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
fn set_pstate_for_fan_mode(&self, mode: &str, config: &mut Config) -> Result<(), RogError> {
|
||||
info!("Setting pstate");
|
||||
let mode_config = config
|
||||
.power_profiles
|
||||
@@ -257,25 +307,17 @@ impl CtrlFanAndCPU {
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(AMD_BOOST_PATH)
|
||||
.map_err(|err| {
|
||||
warn!("Failed to open AMD boost: {}", err);
|
||||
err
|
||||
})?;
|
||||
.map_err(|err| RogError::Path(self.path.into(), err))?;
|
||||
|
||||
let boost = if mode_config.turbo { "1" } else { "0" }; // opposite of Intel
|
||||
file.write_all(boost.as_bytes())
|
||||
.unwrap_or_else(|err| error!("Could not write to {}, {}", AMD_BOOST_PATH, err));
|
||||
.map_err(|err| RogError::Write(AMD_BOOST_PATH.into(), err))?;
|
||||
info!("AMD CPU Turbo: {}", boost);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_fan_curve_for_fan_mode(
|
||||
&self,
|
||||
// mode: FanLevel,
|
||||
mode: &str,
|
||||
config: &Config,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
fn set_fan_curve_for_fan_mode(&self, mode: &str, config: &Config) -> Result<(), RogError> {
|
||||
let mode_config = &config
|
||||
.power_profiles
|
||||
.get(mode)
|
||||
|
||||
@@ -3,135 +3,180 @@ static LED_APPLY: [u8; 17] = [0x5d, 0xb4, 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];
|
||||
|
||||
use crate::{config::Config, error::RogError, laptops::HELP_ADDRESS};
|
||||
use asus_nb::{
|
||||
aura_brightness_bytes, aura_modes::AuraModes, fancy::KeyColourArray, DBUS_IFACE, DBUS_PATH,
|
||||
LED_MSG_LEN,
|
||||
};
|
||||
use dbus::{channel::Sender, nonblock::SyncConnection, tree::Signal};
|
||||
use asus_nb::{aura_brightness_bytes, aura_modes::AuraModes, fancy::KeyColourArray, LED_MSG_LEN};
|
||||
use log::{info, warn};
|
||||
use std::error::Error;
|
||||
use std::convert::TryInto;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::task::JoinHandle;
|
||||
use std::sync::Mutex;
|
||||
use zbus::dbus_interface;
|
||||
|
||||
pub struct CtrlKbdBacklight {
|
||||
led_node: Option<String>,
|
||||
#[allow(dead_code)]
|
||||
kbd_node: Option<String>,
|
||||
bright_node: String,
|
||||
pub bright_node: String,
|
||||
supported_modes: Vec<u8>,
|
||||
flip_effect_write: bool,
|
||||
config: Arc<Mutex<Config>>,
|
||||
}
|
||||
|
||||
use async_trait::async_trait;
|
||||
pub struct DbusKbdBacklight {
|
||||
inner: Arc<Mutex<CtrlKbdBacklight>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::Controller for CtrlKbdBacklight {
|
||||
type A = AuraModes;
|
||||
impl DbusKbdBacklight {
|
||||
pub fn new(inner: Arc<Mutex<CtrlKbdBacklight>>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns two tasks which continuously check for changes
|
||||
fn spawn_task_loop(
|
||||
self,
|
||||
config: Arc<Mutex<Config>>,
|
||||
mut recv: Receiver<Self::A>,
|
||||
connection: Option<Arc<SyncConnection>>,
|
||||
signal: Option<Arc<Signal<()>>>,
|
||||
) -> Vec<JoinHandle<()>> {
|
||||
let gate1 = Arc::new(Mutex::new(self));
|
||||
let gate2 = gate1.clone();
|
||||
trait Dbus {
|
||||
fn set_led(&mut self, data: String);
|
||||
fn ledmode(&self) -> String;
|
||||
fn notify_led(&self, data: &str) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
let config1 = config.clone();
|
||||
vec![
|
||||
tokio::spawn(async move {
|
||||
while let Some(command) = recv.recv().await {
|
||||
let mut config = config1.lock().await;
|
||||
let mut lock = gate1.lock().await;
|
||||
match &command {
|
||||
impl crate::ZbusAdd for DbusKbdBacklight {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(&"/org/asuslinux/Led".try_into().unwrap(), self)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl DbusKbdBacklight {
|
||||
fn set_led_mode(&mut self, data: String) {
|
||||
if let Ok(data) = serde_json::from_str(&data) {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
|
||||
match &data {
|
||||
AuraModes::PerKey(_) => {
|
||||
lock.do_command(command, &mut config)
|
||||
.await
|
||||
ctrl.do_command(data, &mut cfg)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
_ => {
|
||||
let json = serde_json::to_string(&command).unwrap();
|
||||
lock.do_command(command, &mut config)
|
||||
.await
|
||||
let json = serde_json::to_string(&data).unwrap();
|
||||
ctrl.do_command(data, &mut cfg)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
connection
|
||||
.as_ref()
|
||||
.expect("LED Controller must have DBUS connection")
|
||||
.send(
|
||||
signal
|
||||
.as_ref()
|
||||
.expect("LED Controller must have DBUS signal")
|
||||
.msg(&DBUS_PATH.into(), &DBUS_IFACE.into())
|
||||
.append1(json),
|
||||
)
|
||||
.unwrap_or_else(|_| 0);
|
||||
self.notify_led(&json).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::time::delay_for(std::time::Duration::from_millis(100)).await;
|
||||
let mut lock = gate2.lock().await;
|
||||
let mut config = config.lock().await;
|
||||
lock.let_bright_check_change(&mut config)
|
||||
.unwrap_or_else(|err| warn!("led_ctrl: {}", err));
|
||||
}
|
||||
}),
|
||||
]
|
||||
}
|
||||
} else {
|
||||
warn!("SetKeyBacklight could not deserialise");
|
||||
}
|
||||
}
|
||||
|
||||
async fn reload_from_config(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {
|
||||
// set current mode (if any)
|
||||
if self.supported_modes.len() > 1 {
|
||||
if self.supported_modes.contains(&config.kbd_backlight_mode) {
|
||||
let mode = config
|
||||
.get_led_mode_data(config.kbd_backlight_mode)
|
||||
.ok_or(RogError::NotSupported)?
|
||||
.to_owned();
|
||||
self.write_mode(&mode).await?;
|
||||
info!("Reloaded last used mode");
|
||||
} else {
|
||||
warn!(
|
||||
"An unsupported mode was set: {}, reset to first mode available",
|
||||
<&str>::from(&<AuraModes>::from(config.kbd_backlight_mode))
|
||||
);
|
||||
for (idx, mode) in config.kbd_backlight_modes.iter_mut().enumerate() {
|
||||
if !self.supported_modes.contains(&mode.into()) {
|
||||
config.kbd_backlight_modes.remove(idx);
|
||||
config.write();
|
||||
break;
|
||||
fn led_mode(&self) -> String {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(cfg) = ctrl.config.clone().try_lock() {
|
||||
if let Some(mode) = cfg.get_led_mode_data(cfg.kbd_backlight_mode) {
|
||||
if let Ok(json) = serde_json::to_string(&mode) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
config.kbd_backlight_mode = self.supported_modes[0];
|
||||
// TODO: do a recursive call with a boxed dyn future later
|
||||
let mode = config
|
||||
.get_led_mode_data(config.kbd_backlight_mode)
|
||||
.ok_or(RogError::NotSupported)?
|
||||
.to_owned();
|
||||
self.write_mode(&mode).await?;
|
||||
info!("Reloaded last used mode");
|
||||
}
|
||||
}
|
||||
warn!("SetKeyBacklight could not deserialise");
|
||||
"SetKeyBacklight could not deserialise".to_string()
|
||||
}
|
||||
|
||||
// Reload brightness
|
||||
let bright = config.kbd_led_brightness;
|
||||
let bytes = aura_brightness_bytes(bright);
|
||||
self.write_bytes(&bytes).await?;
|
||||
info!("Reloaded last used brightness");
|
||||
fn led_modes(&self) -> String {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(cfg) = ctrl.config.clone().try_lock() {
|
||||
if let Ok(json) = serde_json::to_string(&cfg.kbd_backlight_modes) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
}
|
||||
warn!("SetKeyBacklight could not deserialise");
|
||||
"SetKeyBacklight could not deserialise".to_string()
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_led(&self, data: &str) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlKbdBacklight {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
// set current mode (if any)
|
||||
if let Ok(mut config) = self.config.clone().try_lock() {
|
||||
if self.supported_modes.len() > 1 {
|
||||
if self.supported_modes.contains(&config.kbd_backlight_mode) {
|
||||
let mode = config
|
||||
.get_led_mode_data(config.kbd_backlight_mode)
|
||||
.ok_or(RogError::NotSupported)?
|
||||
.to_owned();
|
||||
self.write_mode(&mode)?;
|
||||
info!("Reloaded last used mode");
|
||||
} else {
|
||||
warn!(
|
||||
"An unsupported mode was set: {}, reset to first mode available",
|
||||
<&str>::from(&<AuraModes>::from(config.kbd_backlight_mode))
|
||||
);
|
||||
for (idx, mode) in config.kbd_backlight_modes.iter_mut().enumerate() {
|
||||
if !self.supported_modes.contains(&mode.into()) {
|
||||
config.kbd_backlight_modes.remove(idx);
|
||||
config.write();
|
||||
break;
|
||||
}
|
||||
}
|
||||
config.kbd_backlight_mode = self.supported_modes[0];
|
||||
// TODO: do a recursive call with a boxed dyn future later
|
||||
let mode = config
|
||||
.get_led_mode_data(config.kbd_backlight_mode)
|
||||
.ok_or(RogError::NotSupported)?
|
||||
.to_owned();
|
||||
self.write_mode(&mode)?;
|
||||
info!("Reloaded last used mode");
|
||||
}
|
||||
}
|
||||
|
||||
// Reload brightness
|
||||
let bright = config.kbd_led_brightness;
|
||||
let bytes = aura_brightness_bytes(bright);
|
||||
self.write_bytes(&bytes)?;
|
||||
info!("Reloaded last used brightness");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::CtrlTask for CtrlKbdBacklight {
|
||||
fn do_task(&mut self) -> Result<(), RogError> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&self.bright_node)
|
||||
.map_err(|err| RogError::Path((&self.bright_node).into(), err))?;
|
||||
let mut buf = [0u8; 1];
|
||||
file.read_exact(&mut buf)
|
||||
.map_err(|err| RogError::Read("buffer".into(), err))?;
|
||||
if let Some(num) = char::from(buf[0]).to_digit(10) {
|
||||
if let Ok(mut config) = self.config.clone().try_lock() {
|
||||
if config.kbd_led_brightness != num as u8 {
|
||||
config.read();
|
||||
config.kbd_led_brightness = num as u8;
|
||||
config.write();
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
Err(RogError::ParseLED)
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlKbdBacklight {
|
||||
#[inline]
|
||||
pub fn new(id_product: &str, condev_iface: Option<&String>, supported_modes: Vec<u8>) -> Self {
|
||||
pub fn new(
|
||||
id_product: &str,
|
||||
condev_iface: Option<&String>,
|
||||
supported_modes: Vec<u8>,
|
||||
config: Arc<Mutex<Config>>,
|
||||
) -> Self {
|
||||
// TODO: return error if *all* nodes are None
|
||||
CtrlKbdBacklight {
|
||||
led_node: Self::get_node_failover(id_product, None, Self::scan_led_node).ok(),
|
||||
@@ -140,14 +185,15 @@ impl CtrlKbdBacklight {
|
||||
bright_node: "/sys/class/leds/asus::kbd_backlight/brightness".to_string(),
|
||||
supported_modes,
|
||||
flip_effect_write: false,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_node_failover(
|
||||
id_product: &str,
|
||||
iface: Option<&String>,
|
||||
fun: fn(&str, Option<&String>) -> Result<String, std::io::Error>,
|
||||
) -> Result<String, std::io::Error> {
|
||||
fun: fn(&str, Option<&String>) -> Result<String, RogError>,
|
||||
) -> Result<String, RogError> {
|
||||
for n in 0..=2 {
|
||||
// 0,1,2 inclusive
|
||||
match fun(id_product, iface) {
|
||||
@@ -157,32 +203,34 @@ impl CtrlKbdBacklight {
|
||||
warn!("Looking for node: {}", e.to_string());
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
} else {
|
||||
return Err(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Shouldn't be possible to reach this...
|
||||
let err = std::io::Error::new(std::io::ErrorKind::NotFound, "node not found");
|
||||
Err(err)
|
||||
Err(RogError::NotFound(format!("{}, {:?}", id_product, iface)))
|
||||
}
|
||||
|
||||
fn scan_led_node(id_product: &str, _: Option<&String>) -> Result<String, std::io::Error> {
|
||||
fn scan_led_node(id_product: &str, _: Option<&String>) -> Result<String, RogError> {
|
||||
let mut enumerator = udev::Enumerator::new().map_err(|err| {
|
||||
warn!("{}", err);
|
||||
err
|
||||
RogError::Udev("enumerator failed".into(), err)
|
||||
})?;
|
||||
enumerator.match_subsystem("hidraw").map_err(|err| {
|
||||
warn!("{}", err);
|
||||
err
|
||||
RogError::Udev("match_subsystem failed".into(), err)
|
||||
})?;
|
||||
|
||||
for device in enumerator.scan_devices()? {
|
||||
for device in enumerator.scan_devices().map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("scan_devices failed".into(), err)
|
||||
})? {
|
||||
if let Some(parent) = device
|
||||
.parent_with_subsystem_devtype("usb", "usb_device")
|
||||
.map_err(|err| {
|
||||
warn!("{}", err);
|
||||
err
|
||||
RogError::Udev("parent_with_subsystem_devtype failed".into(), err)
|
||||
})?
|
||||
{
|
||||
if parent.attribute_value("idProduct").unwrap() == id_product {
|
||||
@@ -194,30 +242,34 @@ impl CtrlKbdBacklight {
|
||||
}
|
||||
}
|
||||
}
|
||||
let err = std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"ASUS LED device node not found",
|
||||
);
|
||||
warn!("Did not find a hidraw node for LED control, your device may be unsupported or require a kernel patch, see: {}", HELP_ADDRESS);
|
||||
Err(err)
|
||||
Err(RogError::MissingFunction(
|
||||
"ASUS LED device node not found".into(),
|
||||
))
|
||||
}
|
||||
|
||||
fn scan_kbd_node(id_product: &str, iface: Option<&String>) -> Result<String, std::io::Error> {
|
||||
let mut enumerator = udev::Enumerator::new()?;
|
||||
fn scan_kbd_node(id_product: &str, iface: Option<&String>) -> Result<String, RogError> {
|
||||
let mut enumerator = udev::Enumerator::new().map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("enumerator failed".into(), err)
|
||||
})?;
|
||||
enumerator.match_subsystem("input").map_err(|err| {
|
||||
warn!("{}", err);
|
||||
err
|
||||
RogError::Udev("match_subsystem failed".into(), err)
|
||||
})?;
|
||||
enumerator
|
||||
.match_property("ID_MODEL_ID", id_product)
|
||||
.map_err(|err| {
|
||||
warn!("{}", err);
|
||||
err
|
||||
RogError::Udev("match_property failed".into(), err)
|
||||
})?;
|
||||
|
||||
for device in enumerator.scan_devices().map_err(|err| {
|
||||
warn!("{}", err);
|
||||
err
|
||||
}).map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("scan_devices failed".into(), err)
|
||||
})? {
|
||||
if let Some(dev_node) = device.devnode() {
|
||||
if let Some(inum) = device.property_value("ID_USB_INTERFACE_NUM") {
|
||||
@@ -230,63 +282,39 @@ impl CtrlKbdBacklight {
|
||||
}
|
||||
}
|
||||
}
|
||||
let err = std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"ASUS keyboard 'Consumer Device' node not found",
|
||||
);
|
||||
|
||||
warn!("Did not find keyboard consumer device node, if expected functions are missing please file an issue at {}", HELP_ADDRESS);
|
||||
Err(err)
|
||||
Err(RogError::MissingFunction(
|
||||
"ASUS keyboard 'Consumer Device' node not found".into(),
|
||||
))
|
||||
}
|
||||
|
||||
fn let_bright_check_change(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {
|
||||
let mut file = OpenOptions::new().read(true).open(&self.bright_node)?;
|
||||
let mut buf = [0u8; 1];
|
||||
file.read_exact(&mut buf)?;
|
||||
if let Some(num) = char::from(buf[0]).to_digit(10) {
|
||||
if config.kbd_led_brightness != num as u8 {
|
||||
config.read();
|
||||
config.kbd_led_brightness = num as u8;
|
||||
config.write();
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
let err = std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"LED brightness could not be parsed",
|
||||
);
|
||||
Err(Box::new(err))
|
||||
}
|
||||
|
||||
pub async fn do_command(
|
||||
&mut self,
|
||||
mode: AuraModes,
|
||||
config: &mut Config,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
self.set_and_save(mode, config).await
|
||||
pub fn do_command(&mut self, mode: AuraModes, config: &mut Config) -> Result<(), RogError> {
|
||||
self.set_and_save(mode, config)
|
||||
}
|
||||
|
||||
/// Should only be used if the bytes you are writing are verified correct
|
||||
#[inline]
|
||||
async fn write_bytes(&self, message: &[u8]) -> Result<(), Box<dyn Error>> {
|
||||
fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> {
|
||||
if let Some(led_node) = &self.led_node {
|
||||
if let Ok(mut file) = OpenOptions::new().write(true).open(led_node) {
|
||||
file.write_all(message).unwrap();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(Box::new(RogError::NotSupported))
|
||||
Err(RogError::NotSupported)
|
||||
}
|
||||
|
||||
/// Write an effect block
|
||||
#[inline]
|
||||
async fn write_effect(&mut self, effect: &[Vec<u8>]) -> Result<(), Box<dyn Error>> {
|
||||
fn write_effect(&mut self, effect: &[Vec<u8>]) -> Result<(), RogError> {
|
||||
if self.flip_effect_write {
|
||||
for row in effect.iter().rev() {
|
||||
self.write_bytes(row).await?;
|
||||
self.write_bytes(row)?;
|
||||
}
|
||||
} else {
|
||||
for row in effect.iter() {
|
||||
self.write_bytes(row).await?;
|
||||
self.write_bytes(row)?;
|
||||
}
|
||||
}
|
||||
self.flip_effect_write = !self.flip_effect_write;
|
||||
@@ -297,15 +325,11 @@ impl CtrlKbdBacklight {
|
||||
///
|
||||
/// This needs to be universal so that settings applied by dbus stick
|
||||
#[inline]
|
||||
async fn set_and_save(
|
||||
&mut self,
|
||||
mode: AuraModes,
|
||||
config: &mut Config,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
fn set_and_save(&mut self, mode: AuraModes, config: &mut Config) -> Result<(), RogError> {
|
||||
match mode {
|
||||
AuraModes::LedBrightness(n) => {
|
||||
let bytes: [u8; LED_MSG_LEN] = (&mode).into();
|
||||
self.write_bytes(&bytes).await?;
|
||||
self.write_bytes(&bytes)?;
|
||||
config.read();
|
||||
config.kbd_led_brightness = n;
|
||||
config.write();
|
||||
@@ -314,15 +338,15 @@ impl CtrlKbdBacklight {
|
||||
AuraModes::PerKey(v) => {
|
||||
if v.is_empty() || v[0].is_empty() {
|
||||
let bytes = KeyColourArray::get_init_msg();
|
||||
self.write_bytes(&bytes).await?;
|
||||
self.write_bytes(&bytes)?;
|
||||
} else {
|
||||
self.write_effect(&v).await?;
|
||||
self.write_effect(&v)?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
config.read();
|
||||
let mode_num: u8 = u8::from(&mode);
|
||||
self.write_mode(&mode).await?;
|
||||
self.write_mode(&mode)?;
|
||||
config.kbd_backlight_mode = mode_num;
|
||||
config.set_mode_data(mode);
|
||||
config.write();
|
||||
@@ -332,14 +356,14 @@ impl CtrlKbdBacklight {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn write_mode(&mut self, mode: &AuraModes) -> Result<(), Box<dyn Error>> {
|
||||
fn write_mode(&mut self, mode: &AuraModes) -> Result<(), RogError> {
|
||||
match mode {
|
||||
AuraModes::PerKey(v) => {
|
||||
if v.is_empty() || v[0].is_empty() {
|
||||
let bytes = KeyColourArray::get_init_msg();
|
||||
self.write_bytes(&bytes).await?;
|
||||
self.write_bytes(&bytes)?;
|
||||
} else {
|
||||
self.write_effect(v).await?;
|
||||
self.write_effect(v)?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -349,20 +373,20 @@ impl CtrlKbdBacklight {
|
||||
if self.supported_modes.contains(&mode_num) {
|
||||
let bytes: [[u8; LED_MSG_LEN]; 4] = mode.into();
|
||||
for array in bytes.iter() {
|
||||
self.write_bytes(array).await?;
|
||||
self.write_bytes(array)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if self.supported_modes.contains(&mode_num) {
|
||||
let bytes: [u8; LED_MSG_LEN] = mode.into();
|
||||
self.write_bytes(&bytes).await?;
|
||||
self.write_bytes(&bytes)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.write_bytes(&LED_SET).await?;
|
||||
self.write_bytes(&LED_SET)?;
|
||||
// Changes won't persist unless apply is set
|
||||
self.write_bytes(&LED_APPLY).await?;
|
||||
self.write_bytes(&LED_APPLY)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
use daemon::{
|
||||
config::Config, ctrl_anime::CtrlAnimeDisplay, ctrl_charge::CtrlCharge,
|
||||
ctrl_fan_cpu::CtrlFanAndCPU, ctrl_leds::CtrlKbdBacklight, dbus::dbus_create_tree,
|
||||
laptops::match_laptop,
|
||||
};
|
||||
use ctrl_gfx::ctrl_gfx::CtrlGraphics;
|
||||
use daemon::config::Config;
|
||||
use daemon::ctrl_anime::CtrlAnimeDisplay;
|
||||
use daemon::ctrl_charge::CtrlCharge;
|
||||
use daemon::ctrl_fan_cpu::{CtrlFanAndCPU, DbusFanAndCpu};
|
||||
use daemon::ctrl_leds::{CtrlKbdBacklight, DbusKbdBacklight};
|
||||
use daemon::laptops::match_laptop;
|
||||
|
||||
use dbus::{
|
||||
channel::Sender,
|
||||
nonblock::{Process, SyncConnection},
|
||||
tree::Signal,
|
||||
};
|
||||
use dbus_tokio::connection;
|
||||
|
||||
use asus_nb::{DBUS_IFACE, DBUS_NAME, DBUS_PATH};
|
||||
use daemon::Controller;
|
||||
use asus_nb::DBUS_NAME;
|
||||
use daemon::{CtrlTask, Reloadable, ZbusAdd};
|
||||
use log::LevelFilter;
|
||||
use log::{error, info, warn};
|
||||
use std::error::Error;
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use zbus::fdo;
|
||||
use zbus::Connection;
|
||||
|
||||
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut logger = env_logger::Builder::new();
|
||||
logger
|
||||
.target(env_logger::Target::Stdout)
|
||||
@@ -30,7 +27,7 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.init();
|
||||
|
||||
info!("Version: {}", daemon::VERSION);
|
||||
start_daemon().await?;
|
||||
start_daemon()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -43,209 +40,100 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// 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>> {
|
||||
fn start_daemon() -> Result<(), Box<dyn Error>> {
|
||||
let laptop = match_laptop();
|
||||
let mut config = if let Some(laptop) = laptop.as_ref() {
|
||||
let config = if let Some(laptop) = laptop.as_ref() {
|
||||
Config::default().load(laptop.supported_modes())
|
||||
} else {
|
||||
Config::default().load(&[])
|
||||
};
|
||||
|
||||
let mut led_control = if let Some(laptop) = laptop {
|
||||
Some(CtrlKbdBacklight::new(
|
||||
let connection = Connection::new_system()?;
|
||||
fdo::DBusProxy::new(&connection)?
|
||||
.request_name(DBUS_NAME, fdo::RequestNameFlags::ReplaceExisting.into())?;
|
||||
let mut object_server = zbus::ObjectServer::new(&connection);
|
||||
|
||||
let config = Arc::new(Mutex::new(config));
|
||||
|
||||
match CtrlCharge::new(config.clone()) {
|
||||
Ok(mut ctrl) => {
|
||||
// Do a reload of any settings
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| warn!("Battery charge limit: {}", err));
|
||||
// Then register to dbus server
|
||||
ctrl.add_to_server(&mut object_server);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("charge_control: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
match CtrlAnimeDisplay::new() {
|
||||
Ok(ctrl) => {
|
||||
ctrl.add_to_server(&mut object_server);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("AniMe control: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
match CtrlGraphics::new() {
|
||||
Ok(mut ctrl) => {
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| warn!("Gfx controller: {}", err));
|
||||
ctrl.add_to_server(&mut object_server);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Gfx control: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Collect tasks for task thread
|
||||
let mut tasks: Vec<Arc<Mutex<dyn CtrlTask + Send>>> = Vec::new();
|
||||
|
||||
match CtrlFanAndCPU::new(config.clone()) {
|
||||
Ok(mut ctrl) => {
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| warn!("Profile control: {}", err));
|
||||
let tmp = Arc::new(Mutex::new(ctrl));
|
||||
DbusFanAndCpu::new(tmp.clone()).add_to_server(&mut object_server);
|
||||
tasks.push(tmp);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Profile control: {}", err);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(laptop) = laptop {
|
||||
let ctrl = CtrlKbdBacklight::new(
|
||||
laptop.usb_product(),
|
||||
laptop.condev_iface(),
|
||||
laptop.supported_modes().to_owned(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut charge_control = CtrlCharge::new().map_or_else(
|
||||
|err| {
|
||||
error!("{}", err);
|
||||
None
|
||||
},
|
||||
Some,
|
||||
);
|
||||
|
||||
let mut fan_control = CtrlFanAndCPU::new().map_or_else(
|
||||
|err| {
|
||||
error!("{}", err);
|
||||
None
|
||||
},
|
||||
Some,
|
||||
);
|
||||
|
||||
// Reload settings
|
||||
if let Some(ctrl) = fan_control.as_mut() {
|
||||
ctrl.reload_from_config(&mut config)
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("Fan mode: {}", err));
|
||||
config,
|
||||
);
|
||||
let tmp = Arc::new(Mutex::new(ctrl));
|
||||
DbusKbdBacklight::new(tmp.clone()).add_to_server(&mut object_server);
|
||||
tasks.push(tmp);
|
||||
}
|
||||
|
||||
if let Some(ctrl) = charge_control.as_mut() {
|
||||
ctrl.reload_from_config(&mut config)
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("Battery charge limit: {}", err));
|
||||
}
|
||||
// TODO: implement messaging between threads to check fails
|
||||
// These tasks generally read a sys path or file to check for a
|
||||
// change
|
||||
let _handle = std::thread::Builder::new()
|
||||
.name("asusd watch".to_string())
|
||||
.spawn(move || loop {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
if let Some(ctrl) = led_control.as_mut() {
|
||||
ctrl.reload_from_config(&mut config)
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("Reload settings: {}", err));
|
||||
}
|
||||
|
||||
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_NAME, false, true, true)
|
||||
.await?;
|
||||
|
||||
let config = Arc::new(Mutex::new(config));
|
||||
let (
|
||||
tree,
|
||||
aura_command_recv,
|
||||
animatrix_recv,
|
||||
_fan_mode_recv,
|
||||
charge_limit_recv,
|
||||
profile_recv,
|
||||
led_changed_signal,
|
||||
fanmode_signal,
|
||||
charge_limit_signal,
|
||||
) = dbus_create_tree(config.clone());
|
||||
|
||||
// We add the tree to the connection so that incoming method calls will be handled.
|
||||
tree.start_receive_send(&*connection);
|
||||
|
||||
// Send boot signals
|
||||
send_boot_signals(
|
||||
connection.clone(),
|
||||
config.clone(),
|
||||
fanmode_signal.clone(),
|
||||
charge_limit_signal.clone(),
|
||||
led_changed_signal.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// For helping with processing signals
|
||||
start_signal_task(
|
||||
connection.clone(),
|
||||
config.clone(),
|
||||
fanmode_signal,
|
||||
charge_limit_signal,
|
||||
);
|
||||
|
||||
// Begin all tasks
|
||||
let mut handles = Vec::new();
|
||||
if let Ok(ctrl) = CtrlAnimeDisplay::new() {
|
||||
handles.append(&mut ctrl.spawn_task_loop(config.clone(), animatrix_recv, None, None));
|
||||
}
|
||||
|
||||
if let Some(ctrl) = fan_control.take() {
|
||||
handles.append(&mut ctrl.spawn_task_loop(config.clone(), profile_recv, None, None));
|
||||
}
|
||||
|
||||
if let Some(ctrl) = charge_control.take() {
|
||||
handles.append(&mut ctrl.spawn_task_loop(config.clone(), charge_limit_recv, None, None));
|
||||
}
|
||||
|
||||
if let Some(ctrl) = led_control.take() {
|
||||
handles.append(&mut ctrl.spawn_task_loop(
|
||||
config.clone(),
|
||||
aura_command_recv,
|
||||
Some(connection.clone()),
|
||||
Some(led_changed_signal),
|
||||
));
|
||||
}
|
||||
|
||||
connection.process_all();
|
||||
for handle in handles {
|
||||
handle.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: Move these in to the controllers tasks
|
||||
fn start_signal_task(
|
||||
connection: Arc<SyncConnection>,
|
||||
config: Arc<Mutex<Config>>,
|
||||
fanmode_signal: Arc<Signal<()>>,
|
||||
charge_limit_signal: Arc<Signal<()>>,
|
||||
) {
|
||||
tokio::spawn(async move {
|
||||
// Some small things we need to track, without passing all sorts of stuff around
|
||||
let mut last_fan_mode = config.lock().await.power_profile;
|
||||
let mut last_charge_limit = config.lock().await.bat_charge_limit;
|
||||
loop {
|
||||
// Use tokio sleep to not hold up other threads
|
||||
tokio::time::delay_for(std::time::Duration::from_millis(500)).await;
|
||||
|
||||
let config = config.lock().await;
|
||||
if config.power_profile != last_fan_mode {
|
||||
last_fan_mode = config.power_profile;
|
||||
connection
|
||||
.send(
|
||||
fanmode_signal
|
||||
.msg(&DBUS_PATH.into(), &DBUS_IFACE.into())
|
||||
.append1(last_fan_mode),
|
||||
)
|
||||
.unwrap_or_else(|_| 0);
|
||||
for ctrl in tasks.iter() {
|
||||
if let Ok(mut lock) = ctrl.try_lock() {
|
||||
lock.do_task().unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if config.bat_charge_limit != last_charge_limit {
|
||||
last_charge_limit = config.bat_charge_limit;
|
||||
connection
|
||||
.send(
|
||||
charge_limit_signal
|
||||
.msg(&DBUS_PATH.into(), &DBUS_IFACE.into())
|
||||
.append1(last_charge_limit),
|
||||
)
|
||||
.unwrap_or_else(|_| 0);
|
||||
}
|
||||
loop {
|
||||
if let Err(err) = object_server.try_handle_next() {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn send_boot_signals(
|
||||
connection: Arc<SyncConnection>,
|
||||
config: Arc<Mutex<Config>>,
|
||||
fanmode_signal: Arc<Signal<()>>,
|
||||
charge_limit_signal: Arc<Signal<()>>,
|
||||
led_changed_signal: Arc<Signal<()>>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let config = config.lock().await;
|
||||
|
||||
if let Some(data) = config.get_led_mode_data(config.kbd_backlight_mode) {
|
||||
connection
|
||||
.send(
|
||||
led_changed_signal
|
||||
.msg(&DBUS_PATH.into(), &DBUS_IFACE.into())
|
||||
.append1(serde_json::to_string(data)?),
|
||||
)
|
||||
.unwrap_or_else(|_| 0);
|
||||
}
|
||||
|
||||
connection
|
||||
.send(
|
||||
fanmode_signal
|
||||
.msg(&DBUS_PATH.into(), &DBUS_IFACE.into())
|
||||
.append1(config.power_profile),
|
||||
)
|
||||
.unwrap_or_else(|_| 0);
|
||||
|
||||
connection
|
||||
.send(
|
||||
charge_limit_signal
|
||||
.msg(&DBUS_PATH.into(), &DBUS_IFACE.into())
|
||||
.append1(config.bat_charge_limit),
|
||||
)
|
||||
.unwrap_or_else(|_| 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,259 +0,0 @@
|
||||
use crate::config::Config;
|
||||
use asus_nb::profile::ProfileEvent;
|
||||
use asus_nb::{aura_modes::AuraModes, DBUS_IFACE, DBUS_PATH};
|
||||
use dbus::tree::{Factory, MTSync, Method, MethodErr, Signal, Tree};
|
||||
use log::warn;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{
|
||||
mpsc::{channel, Receiver, Sender},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
fn set_keyboard_backlight(sender: Mutex<Sender<AuraModes>>) -> Method<MTSync, ()> {
|
||||
let factory = Factory::new_sync::<()>();
|
||||
factory
|
||||
// method for ledmessage
|
||||
.method("SetKeyBacklight", (), {
|
||||
move |m| {
|
||||
let json: &str = m.msg.read1()?;
|
||||
if let Ok(mut lock) = sender.try_lock() {
|
||||
if let Ok(data) = serde_json::from_str(json) {
|
||||
lock.try_send(data).unwrap_or_else(|err| {
|
||||
warn!("SetKeyBacklight over mpsc failed: {}", err)
|
||||
});
|
||||
} else {
|
||||
warn!("SetKeyBacklight could not deserialise");
|
||||
}
|
||||
Ok(vec![])
|
||||
} else {
|
||||
Err(MethodErr::failed("Could not lock daemon for access"))
|
||||
}
|
||||
}
|
||||
})
|
||||
.inarg::<&str, _>("json")
|
||||
.annotate("org.freedesktop.DBus.Method.NoReply", "true")
|
||||
}
|
||||
|
||||
fn get_keyboard_backlight(config: Arc<Mutex<Config>>) -> Method<MTSync, ()> {
|
||||
let factory = Factory::new_sync::<()>();
|
||||
factory
|
||||
.method("GetKeyBacklight", (), {
|
||||
move |m| {
|
||||
if let Ok(lock) = config.try_lock() {
|
||||
for mode in &lock.kbd_backlight_modes {
|
||||
if lock.kbd_backlight_mode == <u8>::from(mode) {
|
||||
let mode = serde_json::to_string(&mode).unwrap();
|
||||
let mret = m.msg.method_return().append1(mode);
|
||||
return Ok(vec![mret]);
|
||||
}
|
||||
}
|
||||
Err(MethodErr::failed(
|
||||
"Keyboard LED mode set to an invalid mode",
|
||||
))
|
||||
} else {
|
||||
Err(MethodErr::failed("Could not lock config for access"))
|
||||
}
|
||||
}
|
||||
})
|
||||
.outarg::<&str, _>("json")
|
||||
}
|
||||
|
||||
fn get_keyboard_backlight_modes(config: Arc<Mutex<Config>>) -> Method<MTSync, ()> {
|
||||
let factory = Factory::new_sync::<()>();
|
||||
factory
|
||||
.method("GetKeyBacklightModes", (), {
|
||||
move |m| {
|
||||
if let Ok(lock) = config.try_lock() {
|
||||
let mode = serde_json::to_string(&lock.kbd_backlight_modes).unwrap();
|
||||
let mret = m.msg.method_return().append1(mode);
|
||||
Ok(vec![mret])
|
||||
} else {
|
||||
Err(MethodErr::failed("Could not lock config for access"))
|
||||
}
|
||||
}
|
||||
})
|
||||
.outarg::<&str, _>("json")
|
||||
}
|
||||
|
||||
fn set_animatrix(
|
||||
sender: Mutex<Sender<Vec<Vec<u8>>>>, // need mutex only to get interior mutability in MTSync
|
||||
) -> Method<MTSync, ()> {
|
||||
let factory = Factory::new_sync::<()>();
|
||||
factory
|
||||
// method for ledmessage
|
||||
.method("AnimatrixWrite", (), {
|
||||
move |m| {
|
||||
let mut iter = m.msg.iter_init();
|
||||
let byte_array: Vec<Vec<u8>> = vec![iter.read()?, iter.read()?];
|
||||
if let Ok(mut lock) = sender.try_lock() {
|
||||
// Ignore errors if the channel is already full
|
||||
lock.try_send(byte_array).unwrap_or_else(|_err| {});
|
||||
Ok(vec![])
|
||||
} else {
|
||||
Err(MethodErr::failed("Could not lock daemon for access"))
|
||||
}
|
||||
}
|
||||
})
|
||||
.inarg::<Vec<u8>, _>("bytearray1")
|
||||
.inarg::<Vec<u8>, _>("bytearray2")
|
||||
.annotate("org.freedesktop.DBus.Method.NoReply", "true")
|
||||
}
|
||||
|
||||
fn set_fan_mode(sender: Mutex<Sender<u8>>) -> Method<MTSync, ()> {
|
||||
let factory = Factory::new_sync::<()>();
|
||||
factory
|
||||
// method for ledmessage
|
||||
.method("SetFanMode", (), {
|
||||
move |m| {
|
||||
if let Ok(mut lock) = sender.try_lock() {
|
||||
let mut iter = m.msg.iter_init();
|
||||
let byte: u8 = iter.read()?;
|
||||
lock.try_send(byte).unwrap_or_else(|_err| {});
|
||||
Ok(vec![])
|
||||
} else {
|
||||
Err(MethodErr::failed("Could not lock daemon for access"))
|
||||
}
|
||||
}
|
||||
})
|
||||
.inarg::<u8, _>("mode")
|
||||
.annotate("org.freedesktop.DBus.Method.NoReply", "true")
|
||||
}
|
||||
|
||||
fn get_fan_mode(config: Arc<Mutex<Config>>) -> Method<MTSync, ()> {
|
||||
let factory = Factory::new_sync::<()>();
|
||||
factory
|
||||
.method("GetFanMode", (), {
|
||||
move |m| {
|
||||
if let Ok(lock) = config.try_lock() {
|
||||
let mret = m.msg.method_return().append1(lock.power_profile);
|
||||
Ok(vec![mret])
|
||||
} else {
|
||||
Err(MethodErr::failed("Could not lock config for access"))
|
||||
}
|
||||
}
|
||||
})
|
||||
.outarg::<u8, _>("mode")
|
||||
}
|
||||
|
||||
fn get_charge_limit(config: Arc<Mutex<Config>>) -> Method<MTSync, ()> {
|
||||
let factory = Factory::new_sync::<()>();
|
||||
factory
|
||||
.method("GetChargeLimit", (), {
|
||||
move |m| {
|
||||
if let Ok(lock) = config.try_lock() {
|
||||
let mret = m.msg.method_return().append1(lock.bat_charge_limit);
|
||||
Ok(vec![mret])
|
||||
} else {
|
||||
Err(MethodErr::failed("Could not lock config for access"))
|
||||
}
|
||||
}
|
||||
})
|
||||
.outarg::<u8, _>("limit")
|
||||
}
|
||||
|
||||
fn set_charge_limit(sender: Mutex<Sender<u8>>) -> Method<MTSync, ()> {
|
||||
let factory = Factory::new_sync::<()>();
|
||||
factory
|
||||
// method for ledmessage
|
||||
.method("SetChargeLimit", (), {
|
||||
move |m| {
|
||||
if let Ok(mut lock) = sender.try_lock() {
|
||||
let mut iter = m.msg.iter_init();
|
||||
let byte: u8 = iter.read()?;
|
||||
lock.try_send(byte).unwrap_or_else(|_err| {});
|
||||
Ok(vec![])
|
||||
} else {
|
||||
Err(MethodErr::failed("Could not lock daemon for access"))
|
||||
}
|
||||
}
|
||||
})
|
||||
.inarg::<u8, _>("limit")
|
||||
.annotate("org.freedesktop.DBus.Method.NoReply", "true")
|
||||
}
|
||||
|
||||
fn set_profile(sender: Sender<ProfileEvent>) -> Method<MTSync, ()> {
|
||||
let factory = Factory::new_sync::<()>();
|
||||
factory
|
||||
// method for profile
|
||||
.method("ProfileCommand", (), {
|
||||
move |m| {
|
||||
let mut iter = m.msg.iter_init();
|
||||
let byte: String = iter.read()?;
|
||||
if let Ok(byte) = serde_json::from_str(&byte) {
|
||||
sender.clone().try_send(byte).unwrap_or_else(|_err| {});
|
||||
}
|
||||
|
||||
Ok(vec![])
|
||||
}
|
||||
})
|
||||
.inarg::<String, _>("limit")
|
||||
.annotate("org.freedesktop.DBus.Method.NoReply", "true")
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn dbus_create_tree(
|
||||
config: Arc<Mutex<Config>>,
|
||||
) -> (
|
||||
Tree<MTSync, ()>,
|
||||
Receiver<AuraModes>,
|
||||
Receiver<Vec<Vec<u8>>>,
|
||||
Receiver<u8>,
|
||||
Receiver<u8>,
|
||||
Receiver<ProfileEvent>,
|
||||
Arc<Signal<()>>,
|
||||
Arc<Signal<()>>,
|
||||
Arc<Signal<()>>,
|
||||
) {
|
||||
let (aura_command_send, aura_command_recv) = channel::<AuraModes>(1);
|
||||
let (animatrix_send, animatrix_recv) = channel::<Vec<Vec<u8>>>(1);
|
||||
let (fan_mode_send, fan_mode_recv) = channel::<u8>(1);
|
||||
let (profile_send, profile_recv) = channel::<ProfileEvent>(1);
|
||||
let (charge_send, charge_recv) = channel::<u8>(1);
|
||||
|
||||
let factory = Factory::new_sync::<()>();
|
||||
|
||||
let key_backlight_changed = Arc::new(
|
||||
factory
|
||||
.signal("KeyBacklightChanged", ())
|
||||
.sarg::<&str, _>("json"),
|
||||
);
|
||||
let chrg_limit_changed = Arc::new(
|
||||
factory
|
||||
.signal("ChargeLimitChanged", ())
|
||||
.sarg::<u8, _>("limit"),
|
||||
);
|
||||
let fanmode_changed = Arc::new(factory.signal("FanModeChanged", ()).sarg::<u8, _>("mode"));
|
||||
|
||||
let tree = factory
|
||||
.tree(())
|
||||
.add(
|
||||
factory.object_path(DBUS_PATH, ()).introspectable().add(
|
||||
factory
|
||||
.interface(DBUS_IFACE, ())
|
||||
.add_m(set_keyboard_backlight(Mutex::new(aura_command_send)))
|
||||
.add_m(set_animatrix(Mutex::new(animatrix_send)))
|
||||
.add_m(set_fan_mode(Mutex::new(fan_mode_send)))
|
||||
.add_m(set_profile(profile_send))
|
||||
.add_m(set_charge_limit(Mutex::new(charge_send)))
|
||||
.add_m(get_fan_mode(config.clone()))
|
||||
.add_m(get_charge_limit(config.clone()))
|
||||
.add_m(get_keyboard_backlight(config.clone()))
|
||||
.add_m(get_keyboard_backlight_modes(config))
|
||||
.add_s(key_backlight_changed.clone())
|
||||
.add_s(fanmode_changed.clone())
|
||||
.add_s(chrg_limit_changed.clone()),
|
||||
),
|
||||
)
|
||||
.add(factory.object_path("/", ()).introspectable());
|
||||
(
|
||||
tree,
|
||||
aura_command_recv,
|
||||
animatrix_recv,
|
||||
fan_mode_recv,
|
||||
charge_recv,
|
||||
profile_recv,
|
||||
key_backlight_changed,
|
||||
fanmode_changed,
|
||||
chrg_limit_changed,
|
||||
)
|
||||
}
|
||||
@@ -1,21 +1,58 @@
|
||||
use std::fmt;
|
||||
use std::convert::From;
|
||||
use intel_pstate::PStateError;
|
||||
use rog_fan_curve::CurveError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RogError {
|
||||
ParseFanLevel,
|
||||
ParseVendor,
|
||||
ParseLED,
|
||||
MissingProfile(String),
|
||||
Udev(String, std::io::Error),
|
||||
Path(String, std::io::Error),
|
||||
Read(String, std::io::Error),
|
||||
Write(String, std::io::Error),
|
||||
NotSupported,
|
||||
NotFound(String),
|
||||
IntelPstate(PStateError),
|
||||
FanCurve(CurveError),
|
||||
DoTask(String),
|
||||
MissingFunction(String),
|
||||
}
|
||||
|
||||
impl std::error::Error for RogError {}
|
||||
|
||||
impl fmt::Display for RogError {
|
||||
// This trait requires `fmt` with this exact signature.
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
RogError::ParseFanLevel => write!(f, "Parse error"),
|
||||
RogError::ParseFanLevel => write!(f, "Parse profile error"),
|
||||
RogError::ParseVendor => write!(f, "Parse gfx vendor error"),
|
||||
RogError::ParseLED => write!(f, "Parse LED error"),
|
||||
RogError::MissingProfile(profile) => write!(f, "Profile does not exist {}", profile),
|
||||
RogError::Udev(deets, error) => write!(f, "udev {}: {}", deets, error),
|
||||
RogError::Path(path, error) => write!(f, "Path {}: {}", path, error),
|
||||
RogError::Read(path, error) => write!(f, "Read {}: {}", path, error),
|
||||
RogError::Write(path, error) => write!(f, "Write {}: {}", path, error),
|
||||
RogError::NotSupported => write!(f, "Not supported"),
|
||||
RogError::NotFound(deets) => write!(f, "Not found: {}", deets),
|
||||
RogError::IntelPstate(err) => write!(f, "Intel pstate error: {}", err),
|
||||
RogError::FanCurve(err) => write!(f, "Custom fan-curve error: {}", err),
|
||||
RogError::DoTask(deets) => write!(f, "Task error: {}", deets),
|
||||
RogError::MissingFunction(deets) => write!(f, "Missing functionality: {}", deets),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RogError {}
|
||||
|
||||
impl From<PStateError> for RogError {
|
||||
fn from(err: PStateError) -> Self {
|
||||
RogError::IntelPstate(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CurveError> for RogError {
|
||||
fn from(err: CurveError) -> Self {
|
||||
RogError::FanCurve(err)
|
||||
}
|
||||
}
|
||||
@@ -10,36 +10,31 @@ pub mod ctrl_fan_cpu;
|
||||
///
|
||||
pub mod ctrl_leds;
|
||||
///
|
||||
pub mod dbus;
|
||||
/// Laptop matching to determine capabilities
|
||||
pub mod laptops;
|
||||
|
||||
mod error;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use config::Config;
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc::Receiver, Mutex};
|
||||
use tokio::task::JoinHandle;
|
||||
use crate::error::RogError;
|
||||
use zbus::ObjectServer;
|
||||
|
||||
pub static VERSION: &str = "1.1.2";
|
||||
pub static VERSION: &str = "2.0.0";
|
||||
|
||||
use ::dbus::{nonblock::SyncConnection, tree::Signal};
|
||||
pub trait Reloadable {
|
||||
fn reload(&mut self) -> Result<(), RogError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Controller {
|
||||
pub trait ZbusAdd {
|
||||
fn add_to_server(self, server: &mut ObjectServer);
|
||||
}
|
||||
|
||||
pub trait CtrlTask {
|
||||
fn do_task(&mut self) -> Result<(), RogError>;
|
||||
}
|
||||
|
||||
pub trait CtrlTaskComplex {
|
||||
type A;
|
||||
|
||||
async fn reload_from_config(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>>;
|
||||
|
||||
/// Spawn an infinitely running task (usually) which checks a Receiver for input,
|
||||
/// and may send a signal over dbus
|
||||
fn spawn_task_loop(
|
||||
self,
|
||||
config: Arc<Mutex<Config>>,
|
||||
recv: Receiver<Self::A>,
|
||||
connection: Option<Arc<SyncConnection>>,
|
||||
signal: Option<Arc<Signal<()>>>,
|
||||
) -> Vec<JoinHandle<()>>;
|
||||
fn do_task(&mut self, config: &mut Config, event: Self::A);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,13 @@ use asus_nb::{
|
||||
core_dbus::AuraDbusClient,
|
||||
profile::{ProfileCommand, ProfileEvent},
|
||||
};
|
||||
use ctrl_gfx::vendors::GfxVendors;
|
||||
use daemon::ctrl_fan_cpu::FanLevel;
|
||||
use gumdrop::Options;
|
||||
use log::LevelFilter;
|
||||
use std::io::Write;
|
||||
use yansi_term::Colour::Green;
|
||||
use yansi_term::Colour::Red;
|
||||
|
||||
#[derive(Options)]
|
||||
struct CLIStart {
|
||||
@@ -20,6 +23,8 @@ struct CLIStart {
|
||||
pwr_profile: Option<FanLevel>,
|
||||
#[options(meta = "CHRG", help = "<20-100>")]
|
||||
chg_limit: Option<u8>,
|
||||
#[options(help = "Set graphics mode: <nvidia, hybrid, compute, integrated>")]
|
||||
graphics: Option<GfxVendors>,
|
||||
#[options(command)]
|
||||
command: Option<Command>,
|
||||
}
|
||||
@@ -40,8 +45,7 @@ struct LedModeCommand {
|
||||
command: Option<SetAuraBuiltin>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut logger = env_logger::Builder::new();
|
||||
logger
|
||||
.target(env_logger::Target::Stdout)
|
||||
@@ -78,5 +82,52 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(chg_limit) = parsed.chg_limit {
|
||||
writer.write_charge_limit(chg_limit)?;
|
||||
}
|
||||
if let Some(gfx) = parsed.graphics {
|
||||
println!("Updating settings, please wait...");
|
||||
println!("If this takes longer than 30s, ctrl+c then check journalctl");
|
||||
|
||||
writer.write_gfx_mode(gfx)?;
|
||||
let res = writer.wait_gfx_changed()?;
|
||||
match res.as_str() {
|
||||
"reboot" => println!(
|
||||
"{}\n{}",
|
||||
Green.paint("\nGraphics vendor mode changed successfully\n"),
|
||||
Red.paint("\nPlease reboot to complete switch to iGPU\n")
|
||||
),
|
||||
"restartx" => {
|
||||
println!(
|
||||
"{}",
|
||||
Green.paint("\nGraphics vendor mode changed successfully\n")
|
||||
);
|
||||
restart_x()?;
|
||||
std::process::exit(1)
|
||||
}
|
||||
_ => std::process::exit(-1),
|
||||
}
|
||||
std::process::exit(-1)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn restart_x() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Restart X server? y/n");
|
||||
|
||||
let mut buf = String::new();
|
||||
std::io::stdin().read_line(&mut buf).expect("Input failed");
|
||||
let input = buf.chars().next().unwrap() as char;
|
||||
|
||||
if input == 'Y' || input == 'y' {
|
||||
println!("Restarting X server");
|
||||
let status = std::process::Command::new("systemctl")
|
||||
.arg("restart")
|
||||
.arg("display-manager.service")
|
||||
.status()?;
|
||||
|
||||
if !status.success() {
|
||||
println!("systemctl: display-manager returned with {}", status);
|
||||
}
|
||||
} else {
|
||||
println!("{}", Red.paint("Cancelled. Please restart X when ready"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user