Major restructure to move gfx control out to crate

This commit is contained in:
Luke D. Jones
2021-08-25 11:16:23 +12:00
parent 0ed97db4c1
commit 6ceb5cf939
33 changed files with 2330 additions and 230 deletions

View File

@@ -1,5 +1,4 @@
use log::{error, info, warn};
use rog_types::gfx_vendors::GfxVendors;
use serde_derive::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
@@ -8,33 +7,20 @@ use crate::config_old::*;
use crate::VERSION;
pub static CONFIG_PATH: &str = "/etc/asusd/asusd.conf";
pub static AURA_CONFIG_PATH: &str = "/etc/asusd/asusd.conf";
#[derive(Deserialize, Serialize)]
pub struct Config {
pub gfx_mode: GfxVendors,
/// Only for informational purposes.
#[serde(skip)]
pub gfx_tmp_mode: Option<GfxVendors>,
pub gfx_managed: bool,
pub gfx_vfio_enable: bool,
/// Save charge limit for restoring on boot
pub bat_charge_limit: u8,
}
impl Default for Config {
fn default() -> Self {
impl Config {
fn new() -> Self {
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_tmp_mode: None,
gfx_managed: true,
gfx_vfio_enable: false,
bat_charge_limit: 100,
}
}
}
impl Config {
/// `load` will attempt to read the config, and panic if the dir is missing
pub fn load() -> Self {
let mut file = OpenOptions::new()
@@ -44,47 +30,23 @@ impl Config {
.open(&CONFIG_PATH)
.unwrap_or_else(|_| panic!("The directory /etc/asusd/ is missing")); // okay to cause panic here
let mut buf = String::new();
let config;
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
return Config::create_default(&mut file);
config = Self::new();
} else if let Ok(data) = serde_json::from_str(&buf) {
config = data;
} else if let Ok(data) = serde_json::from_str::<ConfigV352>(&buf) {
config = data.into_current();
info!("Updated config version to: {}", VERSION);
} else {
if let Ok(data) = serde_json::from_str(&buf) {
return data;
} else if let Ok(data) = serde_json::from_str::<ConfigV352>(&buf) {
let config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
return config;
} else if let Ok(data) = serde_json::from_str::<ConfigV341>(&buf) {
let config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
return config;
} else if let Ok(data) = serde_json::from_str::<ConfigV324>(&buf) {
let config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
return config;
} else if let Ok(data) = serde_json::from_str::<ConfigV317>(&buf) {
let config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
return config;
}
warn!("Could not deserialise {}", CONFIG_PATH);
panic!("Please remove {} then restart asusd", CONFIG_PATH);
}
} else {
config = Self::new()
}
Config::create_default(&mut file)
}
fn create_default(file: &mut File) -> Self {
let config = Config::default();
// Should be okay to unwrap this as is since it is a Default
let json = serde_json::to_string_pretty(&config).unwrap();
file.write_all(json.as_bytes())
.unwrap_or_else(|_| panic!("Could not write {}", CONFIG_PATH));
config.write();
config
}
@@ -98,11 +60,8 @@ impl Config {
if l == 0 {
warn!("File is empty {}", CONFIG_PATH);
} else {
let mut x: Config = serde_json::from_str(&buf)
.unwrap_or_else(|_| panic!("Could not deserialise {}", CONFIG_PATH));
// copy over serde skipped values
x.gfx_tmp_mode = self.gfx_tmp_mode;
*self = x;
serde_json::from_str(&buf)
.unwrap_or_else(|_| panic!("Could not deserialise {}", CONFIG_PATH))
}
}
}

View File

@@ -1,88 +1,9 @@
use rog_types::gfx_vendors::GfxVendors;
use serde_derive::{Deserialize, Serialize};
use supergfxctl::gfx_vendors::GfxVendors;
use std::collections::BTreeMap;
use crate::config::Config;
/// for parsing old v3.1.7 config
#[allow(dead_code)]
#[derive(Deserialize)]
pub(crate) struct ConfigV317 {
pub gfx_mode: GfxVendors,
pub gfx_managed: bool,
pub active_profile: String,
pub toggle_profiles: Vec<String>,
#[serde(skip)]
pub curr_fan_mode: u8,
pub bat_charge_limit: u8,
pub kbd_led_brightness: u8,
pub kbd_backlight_mode: u8,
#[serde(skip)]
pub kbd_backlight_modes: Option<bool>,
pub power_profiles: BTreeMap<String, ProfileV317>,
}
impl ConfigV317 {
pub(crate) fn into_current(self) -> Config {
Config {
gfx_mode: self.gfx_mode,
gfx_tmp_mode: None,
gfx_managed: self.gfx_managed,
gfx_vfio_enable: false,
bat_charge_limit: self.bat_charge_limit,
}
}
}
#[derive(Deserialize, Serialize)]
pub struct ConfigV324 {
pub gfx_mode: GfxVendors,
pub gfx_managed: bool,
pub active_profile: String,
pub toggle_profiles: Vec<String>,
#[serde(skip)]
pub curr_fan_mode: u8,
pub bat_charge_limit: u8,
pub power_profiles: BTreeMap<String, ProfileV317>,
}
impl ConfigV324 {
pub(crate) fn into_current(self) -> Config {
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_tmp_mode: None,
gfx_managed: self.gfx_managed,
gfx_vfio_enable: false,
bat_charge_limit: self.bat_charge_limit,
}
}
}
#[derive(Deserialize, Serialize)]
pub struct ConfigV341 {
pub gfx_mode: GfxVendors,
pub gfx_managed: bool,
pub gfx_vfio_enable: bool,
pub active_profile: String,
pub toggle_profiles: Vec<String>,
#[serde(skip)]
pub curr_fan_mode: u8,
pub bat_charge_limit: u8,
pub power_profiles: BTreeMap<String, ProfileV317>,
}
impl ConfigV341 {
pub(crate) fn into_current(self) -> Config {
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_tmp_mode: None,
gfx_managed: self.gfx_managed,
gfx_vfio_enable: false,
bat_charge_limit: self.bat_charge_limit,
}
}
}
#[derive(Deserialize, Serialize)]
pub struct ConfigV352 {
pub gfx_mode: GfxVendors,
@@ -101,10 +22,6 @@ pub struct ConfigV352 {
impl ConfigV352 {
pub(crate) fn into_current(self) -> Config {
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_tmp_mode: None,
gfx_managed: self.gfx_managed,
gfx_vfio_enable: false,
bat_charge_limit: self.bat_charge_limit,
}
}

View File

@@ -0,0 +1,257 @@
use crate::VERSION;
use log::{error, info, warn};
use rog_anime::Fade;
use rog_anime::{error::AnimeError, ActionData, ActionLoader, AnimTime, Vec2};
use serde_derive::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::time::Duration;
pub static ANIME_CONFIG_PATH: &str = "/etc/asusd/anime.conf";
pub static ANIME_CACHE_PATH: &str = "/etc/asusd/anime-cache.conf";
#[derive(Deserialize, Serialize)]
pub struct AnimeConfigV341 {
pub system: Option<ActionLoader>,
pub boot: Option<ActionLoader>,
pub suspend: Option<ActionLoader>,
pub shutdown: Option<ActionLoader>,
}
impl AnimeConfigV341 {
pub(crate) fn into_current(self) -> AnimeConfig {
AnimeConfig {
system: if let Some(ani) = self.system {
vec![ani]
} else {
vec![]
},
boot: if let Some(ani) = self.boot {
vec![ani]
} else {
vec![]
},
wake: if let Some(ani) = self.suspend {
vec![ani]
} else {
vec![]
},
shutdown: if let Some(ani) = self.shutdown {
vec![ani]
} else {
vec![]
},
brightness: 1.0,
awake_enabled: true,
boot_anim_enabled: true,
}
}
}
#[derive(Deserialize, Serialize)]
pub struct AnimeConfigV352 {
pub system: Vec<ActionLoader>,
pub boot: Vec<ActionLoader>,
pub wake: Vec<ActionLoader>,
pub shutdown: Vec<ActionLoader>,
pub brightness: f32,
}
impl AnimeConfigV352 {
pub(crate) fn into_current(self) -> AnimeConfig {
AnimeConfig {
system: self.system,
boot: self.boot,
wake: self.wake,
shutdown: self.shutdown,
brightness: 1.0,
awake_enabled: true,
boot_anim_enabled: true,
}
}
}
#[derive(Deserialize, Serialize, Default)]
pub struct AnimeConfigCached {
pub system: Vec<ActionData>,
pub boot: Vec<ActionData>,
pub wake: Vec<ActionData>,
pub shutdown: Vec<ActionData>,
}
impl AnimeConfigCached {
pub fn init_from_config(&mut self, config: &AnimeConfig) -> Result<(), AnimeError> {
let mut sys = Vec::with_capacity(config.system.len());
for ani in config.system.iter() {
sys.push(ActionData::from_anime_action(ani)?);
}
self.system = sys;
let mut boot = Vec::with_capacity(config.boot.len());
for ani in config.boot.iter() {
boot.push(ActionData::from_anime_action(ani)?);
}
self.boot = boot;
let mut wake = Vec::with_capacity(config.wake.len());
for ani in config.wake.iter() {
wake.push(ActionData::from_anime_action(ani)?);
}
self.wake = wake;
let mut shutdown = Vec::with_capacity(config.shutdown.len());
for ani in config.shutdown.iter() {
shutdown.push(ActionData::from_anime_action(ani)?);
}
self.shutdown = shutdown;
Ok(())
}
}
/// Config for base system actions for the anime display
#[derive(Deserialize, Serialize)]
pub struct AnimeConfig {
pub system: Vec<ActionLoader>,
pub boot: Vec<ActionLoader>,
pub wake: Vec<ActionLoader>,
pub shutdown: Vec<ActionLoader>,
pub brightness: f32,
pub awake_enabled: bool,
pub boot_anim_enabled: bool,
}
impl Default for AnimeConfig {
fn default() -> Self {
AnimeConfig {
system: Vec::new(),
boot: Vec::new(),
wake: Vec::new(),
shutdown: Vec::new(),
brightness: 1.0,
awake_enabled: true,
boot_anim_enabled: true,
}
}
}
impl AnimeConfig {
/// `load` will attempt to read the config, and panic if the dir is missing
pub fn load() -> Self {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&ANIME_CONFIG_PATH)
.unwrap_or_else(|_| {
panic!(
"The file {} or directory /etc/asusd/ is missing",
ANIME_CONFIG_PATH
)
}); // okay to cause panic here
let mut buf = String::new();
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
return AnimeConfig::create_default(&mut file);
} else {
if let Ok(data) = serde_json::from_str(&buf) {
return data;
} else if let Ok(data) = serde_json::from_str::<AnimeConfigV341>(&buf) {
let config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
return config;
} else if let Ok(data) = serde_json::from_str::<AnimeConfigV352>(&buf) {
let config = data.into_current();
config.write();
info!("Updated config version to: {}", VERSION);
return config;
}
AnimeConfig::write_backup(buf);
warn!(
"Could not deserialise {}. Backed up as *-old",
ANIME_CONFIG_PATH
);
}
}
AnimeConfig::create_default(&mut file)
}
fn create_default(file: &mut File) -> Self {
// create a default config here
let config = AnimeConfig {
system: vec![],
boot: vec![ActionLoader::ImageAnimation {
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
scale: 0.9,
angle: 0.65,
translation: Vec2::default(),
brightness: 1.0,
time: AnimTime::Fade(Fade::new(
Duration::from_secs(2),
Some(Duration::from_secs(2)),
Duration::from_secs(2),
)),
}],
wake: vec![ActionLoader::ImageAnimation {
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
scale: 0.9,
angle: 0.65,
translation: Vec2::default(),
brightness: 1.0,
time: AnimTime::Fade(Fade::new(
Duration::from_secs(2),
Some(Duration::from_secs(2)),
Duration::from_secs(2),
)),
}],
shutdown: vec![ActionLoader::ImageAnimation {
file: "/usr/share/asusd/anime/custom/sonic-wait.gif".into(),
scale: 0.9,
angle: 0.0,
translation: Vec2::new(3.0, 2.0),
brightness: 1.0,
time: AnimTime::Infinite,
}],
brightness: 1.0,
awake_enabled: true,
boot_anim_enabled: true,
};
// Should be okay to unwrap this as is since it is a Default
let json = serde_json::to_string_pretty(&config).unwrap();
file.write_all(json.as_bytes())
.unwrap_or_else(|_| panic!("Could not write {}", ANIME_CONFIG_PATH));
config
}
pub fn read(&mut self) {
let mut file = OpenOptions::new()
.read(true)
.open(&ANIME_CONFIG_PATH)
.unwrap_or_else(|err| panic!("Error reading {}: {}", ANIME_CONFIG_PATH, err));
let mut buf = String::new();
if let Ok(l) = file.read_to_string(&mut buf) {
if l == 0 {
warn!("File is empty {}", ANIME_CONFIG_PATH);
} else {
let x: AnimeConfig = serde_json::from_str(&buf)
.unwrap_or_else(|_| panic!("Could not deserialise {}", ANIME_CONFIG_PATH));
*self = x;
}
}
}
pub fn write(&self) {
let mut file = File::create(ANIME_CONFIG_PATH).expect("Couldn't overwrite config");
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
file.write_all(json.as_bytes())
.unwrap_or_else(|err| error!("Could not write config: {}", err));
}
fn write_backup(buf: String) {
let mut path = ANIME_CONFIG_PATH.to_string();
path.push_str("-old");
let mut file = File::create(&path).expect("Couldn't overwrite config");
file.write_all(buf.as_bytes())
.unwrap_or_else(|err| error!("Could not write config: {}", err));
}
}

View File

@@ -0,0 +1,329 @@
pub mod config;
pub mod zbus;
use ::zbus::Connection;
use log::{error, info, warn};
use logind_zbus::ManagerProxy;
use rog_anime::{
usb::{
pkt_for_apply, pkt_for_flush, pkt_for_set_boot, pkt_for_set_on, pkts_for_init, PROD_ID,
VENDOR_ID,
},
ActionData, AnimeDataBuffer, AnimePacketType, ANIME_DATA_LEN,
};
use rog_types::supported::AnimeSupportedFunctions;
use rusb::{Device, DeviceHandle};
use std::{
error::Error,
sync::{Arc, Mutex},
thread::sleep,
};
use std::{
sync::atomic::{AtomicBool, Ordering},
time::Duration,
};
use crate::{error::RogError, GetSupported};
use self::config::{AnimeConfig, AnimeConfigCached};
impl GetSupported for CtrlAnime {
type A = AnimeSupportedFunctions;
fn get_supported() -> Self::A {
AnimeSupportedFunctions(CtrlAnime::get_device(VENDOR_ID, PROD_ID).is_ok())
}
}
pub struct CtrlAnime {
handle: DeviceHandle<rusb::GlobalContext>,
cache: AnimeConfigCached,
config: AnimeConfig,
// set to force thread to exit
thread_exit: Arc<AtomicBool>,
// Set to false when the thread exits
thread_running: Arc<AtomicBool>,
}
impl CtrlAnime {
#[inline]
pub fn new(config: AnimeConfig) -> Result<CtrlAnime, Box<dyn Error>> {
// We don't expect this ID to ever change
let device = CtrlAnime::get_device(0x0b05, 0x193b)?;
let mut device = device.open()?;
device.reset()?;
device.set_auto_detach_kernel_driver(true).map_err(|err| {
error!("Auto-detach kernel driver failed: {}", err);
err
})?;
device.claim_interface(0).map_err(|err| {
error!("Could not claim device interface: {}", err);
err
})?;
info!("Device has an AniMe Matrix display");
let mut cache = AnimeConfigCached::default();
cache.init_from_config(&config)?;
let ctrl = CtrlAnime {
handle: device,
cache,
config,
thread_exit: Arc::new(AtomicBool::new(false)),
thread_running: Arc::new(AtomicBool::new(false)),
};
ctrl.do_initialization();
Ok(ctrl)
}
fn get_device(vendor: u16, product: u16) -> Result<Device<rusb::GlobalContext>, rusb::Error> {
for device in rusb::devices()?.iter() {
let device_desc = device.device_descriptor()?;
if device_desc.vendor_id() == vendor && device_desc.product_id() == product {
return Ok(device);
}
}
Err(rusb::Error::NoDevice)
}
/// Start an action thread. This is classed as a singleton and there should be only
/// one running - so the thread uses atomics to signal run/exit.
///
/// Because this also writes to the usb device, other write tries (display only) *must*
/// get the mutex lock and set the thread_exit atomic.
fn run_thread(inner: Arc<Mutex<CtrlAnime>>, actions: Vec<ActionData>, mut once: bool) {
if actions.is_empty() {
warn!("AniMe system actions was empty");
return;
}
// Loop rules:
// - Lock the mutex **only when required**. That is, the lock must be held for the shortest duration possible.
// - An AtomicBool used for thread exit should be checked in every loop, including nested
// The only reason for this outer thread is to prevent blocking while waiting for the
// next spawned thread to exit
std::thread::Builder::new()
.name("AniMe system thread start".into())
.spawn(move || {
info!("AniMe system thread started");
// Getting copies of these Atomics is done *in* the thread to ensure
// we don't block other threads/main
let thread_exit;
let thread_running;
// First two loops are to ensure we *do* aquire a lock on the mutex
// The reason the loop is required is because the USB writes can block
// for up to 10ms. We can't fail to get the atomics.
loop {
if let Ok(lock) = inner.try_lock() {
thread_exit = lock.thread_exit.clone();
thread_running = lock.thread_running.clone();
// Make any running loop exit first
thread_exit.store(true, Ordering::SeqCst);
break;
}
}
loop {
// wait for other threads to set not running so we know they exited
if !thread_running.load(Ordering::SeqCst) {
thread_exit.store(false, Ordering::SeqCst);
info!("AniMe forced a thread to exit");
break;
}
}
'main: loop {
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
for action in actions.iter() {
match action {
ActionData::Animation(frames) => {
rog_anime::run_animation(frames, thread_exit.clone(), &|frame| {
if let Ok(lock) = inner.try_lock() {
lock.write_data_buffer(frame);
}
})
.unwrap();
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
}
ActionData::Image(image) => {
once = false;
if let Ok(lock) = inner.try_lock() {
lock.write_data_buffer(image.as_ref().clone())
}
}
ActionData::Pause(duration) => sleep(*duration),
ActionData::AudioEq => {}
ActionData::SystemInfo => {}
ActionData::TimeDate => {}
ActionData::Matrix => {}
}
}
if once || actions.is_empty() {
break 'main;
}
}
// Clear the display on exit
if let Ok(lock) = inner.try_lock() {
let data = AnimeDataBuffer::from_vec([0u8; ANIME_DATA_LEN].to_vec());
lock.write_data_buffer(data);
}
// Loop ended, set the atmonics
thread_exit.store(false, Ordering::SeqCst);
thread_running.store(false, Ordering::SeqCst);
info!("AniMe system thread exited");
})
.map(|err| info!("AniMe system thread: {:?}", err))
.ok();
}
fn write_bytes(&self, message: &[u8]) {
match self.handle.write_control(
0x21, // request_type
0x09, // request
0x35e, // value
0x00, // index
message,
Duration::from_millis(200),
) {
Ok(_) => {}
Err(err) => match err {
rusb::Error::Timeout => {}
_ => error!("Failed to write to led interrupt: {}", err),
},
}
}
/// Write only a data packet. This will modify the leds brightness using the
/// global brightness set in config.
fn write_data_buffer(&self, mut buffer: AnimeDataBuffer) {
for led in buffer.get_mut()[7..].iter_mut() {
let mut bright = *led as f32 * self.config.brightness;
if bright > 254.0 {
bright = 254.0;
}
*led = bright as u8;
}
let data = AnimePacketType::from(buffer);
for row in data.iter() {
self.write_bytes(row);
}
self.write_bytes(&pkt_for_flush());
}
fn do_initialization(&self) {
let pkts = pkts_for_init();
self.write_bytes(&pkts[0]);
self.write_bytes(&pkts[1]);
}
}
pub struct CtrlAnimeTask<'a> {
inner: Arc<Mutex<CtrlAnime>>,
_c: Connection,
manager: ManagerProxy<'a>,
}
impl<'a> CtrlAnimeTask<'a> {
pub fn new(inner: Arc<Mutex<CtrlAnime>>) -> Self {
let connection = Connection::new_system().unwrap();
let manager = ManagerProxy::new(&connection).unwrap();
let c1 = inner.clone();
// Run this action when the system starts shutting down
manager
.connect_prepare_for_shutdown(move |shutdown| {
if shutdown {
'outer: loop {
if let Ok(lock) = c1.try_lock() {
lock.thread_exit.store(true, Ordering::SeqCst);
CtrlAnime::run_thread(c1.clone(), lock.cache.shutdown.clone(), false);
break 'outer;
}
}
}
Ok(())
})
.map_err(|err| {
warn!("CtrlAnimeTask: new() {}", err);
err
})
.ok();
let c1 = inner.clone();
// Run this action when the system wakes up from sleep
manager
.connect_prepare_for_sleep(move |sleep| {
if !sleep {
// wait a fraction for things to wake up properly
std::thread::sleep(Duration::from_millis(100));
'outer: loop {
if let Ok(lock) = c1.try_lock() {
lock.thread_exit.store(true, Ordering::SeqCst);
CtrlAnime::run_thread(c1.clone(), lock.cache.wake.clone(), true);
break 'outer;
}
}
}
Ok(())
})
.map_err(|err| {
warn!("CtrlAnimeTask: new() {}", err);
err
})
.ok();
Self {
inner,
_c: connection,
manager,
}
}
}
impl<'a> crate::CtrlTask for CtrlAnimeTask<'a> {
fn do_task(&self) -> Result<(), RogError> {
if let Ok(mut lock) = self.inner.try_lock() {
// Refresh the config and cache incase the user has edited it
let config = AnimeConfig::load();
lock.cache
.init_from_config(&config)
.map_err(|err| {
warn!("CtrlAnimeTask: do_task {}", err);
err
})
.ok();
}
// Check for signals on each task iteration, this will run the callbacks
// if any signal is recieved
self.manager.next_signal()?;
Ok(())
}
}
pub struct CtrlAnimeReloader(pub Arc<Mutex<CtrlAnime>>);
impl crate::Reloadable for CtrlAnimeReloader {
fn reload(&mut self) -> Result<(), RogError> {
if let Ok(lock) = self.0.try_lock() {
lock.write_bytes(&pkt_for_set_on(lock.config.awake_enabled));
lock.write_bytes(&pkt_for_apply());
lock.write_bytes(&pkt_for_set_boot(lock.config.boot_anim_enabled));
lock.write_bytes(&pkt_for_apply());
let action = lock.cache.boot.clone();
CtrlAnime::run_thread(self.0.clone(), action, true);
}
Ok(())
}
}

View File

@@ -0,0 +1,140 @@
use std::sync::{Arc, Mutex};
use log::warn;
use rog_anime::{
usb::{pkt_for_apply, pkt_for_set_boot, pkt_for_set_on},
AnimeDataBuffer, AnimePowerStates,
};
use zbus::dbus_interface;
use zvariant::ObjectPath;
use std::sync::atomic::Ordering;
use super::CtrlAnime;
pub struct CtrlAnimeZbus(pub Arc<Mutex<CtrlAnime>>);
/// The struct with the main dbus methods requires this trait
impl crate::ZbusAdd for CtrlAnimeZbus {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(
&ObjectPath::from_str_unchecked("/org/asuslinux/Anime"),
self,
)
.map_err(|err| {
warn!("CtrlAnimeDisplay: add_to_server {}", err);
err
})
.ok();
}
}
// None of these calls can be guarnateed to succeed unless we loop until okay
// If the try_lock *does* succeed then any other thread trying to lock will not grab it
// until we finish.
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlAnimeZbus {
/// Writes a data stream of length. Will force system thread to exit until it is restarted
fn write(&self, input: AnimeDataBuffer) {
'outer: loop {
if let Ok(lock) = self.0.try_lock() {
lock.thread_exit.store(true, Ordering::SeqCst);
lock.write_data_buffer(input);
break 'outer;
}
}
}
/// Set the global AniMe brightness
fn set_brightness(&self, bright: f32) {
'outer: loop {
if let Ok(mut lock) = self.0.try_lock() {
let mut bright = bright;
if bright < 0.0 {
bright = 0.0
} else if bright > 254.0 {
bright = 254.0;
}
lock.config.brightness = bright;
lock.config.write();
break 'outer;
}
}
}
/// Set whether the AniMe is displaying images/data
fn set_on_off(&self, status: bool) {
'outer: loop {
if let Ok(mut lock) = self.0.try_lock() {
lock.write_bytes(&pkt_for_set_on(status));
lock.config.awake_enabled = status;
lock.config.write();
let states = AnimePowerStates {
enabled: lock.config.awake_enabled,
boot_anim_enabled: lock.config.boot_anim_enabled,
};
self.notify_power_states(&states)
.unwrap_or_else(|err| warn!("{}", err));
break 'outer;
}
}
}
/// Set whether the AniMe will show boot, suspend, or off animations
fn set_boot_on_off(&self, on: bool) {
'outer: loop {
if let Ok(mut lock) = self.0.try_lock() {
lock.write_bytes(&pkt_for_set_boot(on));
lock.write_bytes(&pkt_for_apply());
lock.config.boot_anim_enabled = on;
lock.config.write();
let states = AnimePowerStates {
enabled: lock.config.awake_enabled,
boot_anim_enabled: lock.config.boot_anim_enabled,
};
self.notify_power_states(&states)
.unwrap_or_else(|err| warn!("{}", err));
break 'outer;
}
}
}
/// The main loop is the base system set action if the user isn't running
/// the user daemon
fn run_main_loop(&self, start: bool) {
if start {
'outer: loop {
if let Ok(lock) = self.0.try_lock() {
lock.thread_exit.store(true, Ordering::SeqCst);
CtrlAnime::run_thread(self.0.clone(), lock.cache.system.clone(), false);
break 'outer;
}
}
}
}
/// Get status of if the AniMe LEDs are on
#[dbus_interface(property)]
fn awake_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.awake_enabled;
}
true
}
/// Get the status of if factory system-status animations are enabled
#[dbus_interface(property)]
fn boot_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.boot_anim_enabled;
}
true
}
/// Notify listeners of the status of AniMe LED power and factory system-status animations
#[dbus_interface(signal)]
fn notify_power_states(&self, data: &AnimePowerStates) -> zbus::Result<()>;
}

View File

@@ -0,0 +1,267 @@
use crate::laptops::LaptopLedData;
use log::{error, info, warn};
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, LedBrightness};
use serde_derive::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
pub static AURA_CONFIG_PATH: &str = "/etc/asusd/aura.conf";
#[derive(Deserialize, Serialize)]
pub struct AuraConfigV320 {
pub brightness: u32,
pub current_mode: AuraModeNum,
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
pub multizone: Option<AuraMultiZone>,
}
impl AuraConfigV320 {
pub(crate) fn into_current(self) -> AuraConfig {
AuraConfig {
brightness: <LedBrightness>::from(self.brightness),
current_mode: self.current_mode,
builtins: self.builtins,
multizone: self.multizone,
awake_enabled: true,
sleep_anim_enabled: true,
}
}
}
#[derive(Deserialize, Serialize)]
pub struct AuraConfigV352 {
pub brightness: LedBrightness,
pub current_mode: AuraModeNum,
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
pub multizone: Option<AuraMultiZone>,
}
impl AuraConfigV352 {
pub(crate) fn into_current(self) -> AuraConfig {
AuraConfig {
brightness: self.brightness,
current_mode: self.current_mode,
builtins: self.builtins,
multizone: self.multizone,
awake_enabled: true,
sleep_anim_enabled: true,
}
}
}
#[derive(Deserialize, Serialize)]
pub struct AuraConfig {
pub brightness: LedBrightness,
pub current_mode: AuraModeNum,
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
pub multizone: Option<AuraMultiZone>,
pub awake_enabled: bool,
pub sleep_anim_enabled: bool,
}
impl Default for AuraConfig {
fn default() -> Self {
AuraConfig {
brightness: LedBrightness::Med,
current_mode: AuraModeNum::Static,
builtins: BTreeMap::new(),
multizone: None,
awake_enabled: true,
sleep_anim_enabled: true,
}
}
}
impl AuraConfig {
/// `load` will attempt to read the config, and panic if the dir is missing
pub fn load(supported_led_modes: &LaptopLedData) -> Self {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&AURA_CONFIG_PATH)
.unwrap_or_else(|_| {
panic!(
"The file {} or directory /etc/asusd/ is missing",
AURA_CONFIG_PATH
)
}); // okay to cause panic here
let mut buf = String::new();
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
return AuraConfig::create_default(&mut file, supported_led_modes);
} else {
if let Ok(data) = serde_json::from_str(&buf) {
return data;
} else if let Ok(data) = serde_json::from_str::<AuraConfigV320>(&buf) {
let config = data.into_current();
config.write();
info!("Updated AuraConfig version");
return config;
} else if let Ok(data) = serde_json::from_str::<AuraConfigV352>(&buf) {
let config = data.into_current();
config.write();
info!("Updated AuraConfig version");
return config;
}
warn!("Could not deserialise {}", AURA_CONFIG_PATH);
panic!("Please remove {} then restart asusd", AURA_CONFIG_PATH);
}
}
AuraConfig::create_default(&mut file, supported_led_modes)
}
fn create_default(file: &mut File, support_data: &LaptopLedData) -> Self {
// create a default config here
let mut config = AuraConfig::default();
for n in &support_data.standard {
config
.builtins
.insert(*n, AuraEffect::default_with_mode(*n));
}
// Should be okay to unwrap this as is since it is a Default
let json = serde_json::to_string(&config).unwrap();
file.write_all(json.as_bytes())
.unwrap_or_else(|_| panic!("Could not write {}", AURA_CONFIG_PATH));
config
}
pub fn read(&mut self) {
let mut file = OpenOptions::new()
.read(true)
.open(&AURA_CONFIG_PATH)
.unwrap_or_else(|err| panic!("Error reading {}: {}", AURA_CONFIG_PATH, err));
let mut buf = String::new();
if let Ok(l) = file.read_to_string(&mut buf) {
if l == 0 {
warn!("File is empty {}", AURA_CONFIG_PATH);
} else {
let x: AuraConfig = serde_json::from_str(&buf)
.unwrap_or_else(|_| panic!("Could not deserialise {}", AURA_CONFIG_PATH));
*self = x;
}
}
}
pub fn write(&self) {
let mut file = File::create(AURA_CONFIG_PATH).expect("Couldn't overwrite config");
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
file.write_all(json.as_bytes())
.unwrap_or_else(|err| error!("Could not write config: {}", err));
}
/// Multipurpose, will accept AuraEffect with zones and put in the correct store
pub fn set_builtin(&mut self, effect: AuraEffect) {
match effect.zone() {
AuraZone::None => {
self.builtins.insert(*effect.mode(), effect);
}
_ => {
if let Some(multi) = self.multizone.as_mut() {
multi.set(effect)
}
}
}
}
pub fn get_multizone(&self, aura_type: AuraModeNum) -> Option<&[AuraEffect; 4]> {
if let Some(multi) = &self.multizone {
if aura_type == AuraModeNum::Static {
return Some(multi.static_());
} else if aura_type == AuraModeNum::Breathe {
return Some(multi.breathe());
}
}
None
}
}
#[derive(Deserialize, Serialize)]
pub struct AuraMultiZone {
static_: [AuraEffect; 4],
breathe: [AuraEffect; 4],
}
impl AuraMultiZone {
pub fn set(&mut self, effect: AuraEffect) {
if effect.mode == AuraModeNum::Static {
match effect.zone {
AuraZone::None => {}
AuraZone::One => self.static_[0] = effect,
AuraZone::Two => self.static_[1] = effect,
AuraZone::Three => self.static_[2] = effect,
AuraZone::Four => self.static_[3] = effect,
}
} else if effect.mode == AuraModeNum::Breathe {
match effect.zone {
AuraZone::None => {}
AuraZone::One => self.breathe[0] = effect,
AuraZone::Two => self.breathe[1] = effect,
AuraZone::Three => self.breathe[2] = effect,
AuraZone::Four => self.breathe[3] = effect,
}
}
}
pub fn static_(&self) -> &[AuraEffect; 4] {
&self.static_
}
pub fn breathe(&self) -> &[AuraEffect; 4] {
&self.breathe
}
}
impl Default for AuraMultiZone {
fn default() -> Self {
Self {
static_: [
AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::One,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::Two,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::Three,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Static,
zone: AuraZone::Four,
..Default::default()
},
],
breathe: [
AuraEffect {
mode: AuraModeNum::Breathe,
zone: AuraZone::One,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Breathe,
zone: AuraZone::Two,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Breathe,
zone: AuraZone::Three,
..Default::default()
},
AuraEffect {
mode: AuraModeNum::Breathe,
zone: AuraZone::Four,
..Default::default()
},
],
}
}
}

View File

@@ -0,0 +1,395 @@
// Only these two packets must be 17 bytes
static KBD_BRIGHT_PATH: &str = "/sys/class/leds/asus::kbd_backlight/brightness";
use crate::{
error::RogError,
laptops::{LaptopLedData, ASUS_KEYBOARD_DEVICES},
CtrlTask,
};
use log::{info, warn};
use logind_zbus::ManagerProxy;
use rog_aura::{
usb::{
LED_APPLY, LED_AWAKE_OFF_SLEEP_OFF, LED_AWAKE_OFF_SLEEP_ON, LED_AWAKE_ON_SLEEP_OFF,
LED_AWAKE_ON_SLEEP_ON, LED_SET,
},
AuraEffect, LedBrightness, LED_MSG_LEN,
};
use rog_types::supported::LedSupportedFunctions;
use std::io::{Read, Write};
use std::path::Path;
use std::sync::Arc;
use std::sync::Mutex;
use std::{fs::OpenOptions, thread::spawn};
use zbus::Connection;
use crate::GetSupported;
use super::config::AuraConfig;
impl GetSupported for CtrlKbdLed {
type A = LedSupportedFunctions;
fn get_supported() -> Self::A {
// let mode = <&str>::from(&<AuraModes>::from(*mode));
let multizone_led_mode = false;
let per_key_led_mode = false;
let laptop = LaptopLedData::get_data();
let stock_led_modes = laptop.standard;
LedSupportedFunctions {
brightness_set: CtrlKbdLed::get_kbd_bright_path().is_some(),
stock_led_modes,
multizone_led_mode,
per_key_led_mode,
}
}
}
pub struct CtrlKbdLed {
pub led_node: Option<String>,
pub bright_node: String,
pub supported_modes: LaptopLedData,
pub flip_effect_write: bool,
pub config: AuraConfig,
}
pub struct CtrlKbdLedTask<'a> {
inner: Arc<Mutex<CtrlKbdLed>>,
_c: Connection,
manager: ManagerProxy<'a>,
}
impl<'a> CtrlKbdLedTask<'a> {
pub fn new(inner: Arc<Mutex<CtrlKbdLed>>) -> Self {
let connection = Connection::new_system().unwrap();
let manager = ManagerProxy::new(&connection).unwrap();
let c1 = inner.clone();
// Run this action when the system wakes up from sleep
manager
.connect_prepare_for_sleep(move |sleep| {
if !sleep {
let c1 = c1.clone();
spawn(move || {
// wait a fraction for things to wake up properly
//std::thread::sleep(Duration::from_millis(100));
loop {
if let Ok(ref mut lock) = c1.try_lock() {
lock.set_brightness(lock.config.brightness).ok();
break;
}
}
});
}
Ok(())
})
.map_err(|err| {
warn!("CtrlAnimeTask: new() {}", err);
err
})
.ok();
Self {
inner,
_c: connection,
manager,
}
}
fn update_config(lock: &mut CtrlKbdLed) -> Result<(), RogError> {
let mut file = OpenOptions::new()
.read(true)
.open(&lock.bright_node)
.map_err(|err| match err.kind() {
std::io::ErrorKind::NotFound => {
RogError::MissingLedBrightNode((&lock.bright_node).into(), err)
}
_ => RogError::Path((&lock.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 lock.config.brightness != num.into() {
lock.config.read();
lock.config.brightness = num.into();
lock.config.write();
}
return Ok(());
}
Err(RogError::ParseLed)
}
}
impl<'a> CtrlTask for CtrlKbdLedTask<'a> {
fn do_task(&self) -> Result<(), RogError> {
self.manager.next_signal()?;
if let Ok(ref mut lock) = self.inner.try_lock() {
return Self::update_config(lock);
}
Ok(())
}
}
pub struct CtrlKbdLedReloader(pub Arc<Mutex<CtrlKbdLed>>);
impl crate::Reloadable for CtrlKbdLedReloader {
fn reload(&mut self) -> Result<(), RogError> {
if let Ok(mut ctrl) = self.0.try_lock() {
let current = ctrl.config.current_mode;
if let Some(mode) = ctrl.config.builtins.get(&current).cloned() {
ctrl.do_command(mode).ok();
}
ctrl.set_states_enabled(ctrl.config.awake_enabled, ctrl.config.sleep_anim_enabled)
.map_err(|err| warn!("{}", err))
.ok();
}
Ok(())
}
}
pub struct CtrlKbdLedZbus(pub Arc<Mutex<CtrlKbdLed>>);
impl CtrlKbdLedZbus {
pub fn new(inner: Arc<Mutex<CtrlKbdLed>>) -> Self {
Self(inner)
}
}
impl CtrlKbdLed {
#[inline]
pub fn new(supported_modes: LaptopLedData, config: AuraConfig) -> Result<Self, RogError> {
// TODO: return error if *all* nodes are None
let mut led_node = None;
for prod in ASUS_KEYBOARD_DEVICES.iter() {
match Self::find_led_node(prod) {
Ok(node) => {
led_node = Some(node);
break;
}
Err(err) => warn!("led_node: {}", err),
}
}
let bright_node = Self::get_kbd_bright_path();
if led_node.is_none() && bright_node.is_none() {
return Err(RogError::MissingFunction(
"All keyboard features missing, you may require a v5.11 series kernel or newer"
.into(),
));
}
if bright_node.is_none() {
return Err(RogError::MissingFunction(
"No brightness control, you may require a v5.11 series kernel or newer".into(),
));
}
let ctrl = CtrlKbdLed {
led_node,
bright_node: bright_node.unwrap(), // If was none then we already returned above
supported_modes,
flip_effect_write: false,
config,
};
Ok(ctrl)
}
fn get_kbd_bright_path() -> Option<String> {
if Path::new(KBD_BRIGHT_PATH).exists() {
return Some(KBD_BRIGHT_PATH.to_string());
}
None
}
pub(super) fn get_brightness(&self) -> Result<u8, RogError> {
let mut file = OpenOptions::new()
.read(true)
.open(&self.bright_node)
.map_err(|err| match err.kind() {
std::io::ErrorKind::NotFound => {
RogError::MissingLedBrightNode((&self.bright_node).into(), 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))?;
Ok(buf[0])
}
pub(super) fn set_brightness(&self, brightness: LedBrightness) -> Result<(), RogError> {
let path = Path::new(&self.bright_node);
let mut file =
OpenOptions::new()
.write(true)
.open(&path)
.map_err(|err| match err.kind() {
std::io::ErrorKind::NotFound => {
RogError::MissingLedBrightNode((&self.bright_node).into(), err)
}
_ => RogError::Path((&self.bright_node).into(), err),
})?;
file.write_all(&[brightness.as_char_code()])
.map_err(|err| RogError::Read("buffer".into(), err))?;
Ok(())
}
/// Set if awake/on LED active, and/or sleep animation active
pub(super) fn set_states_enabled(&self, awake: bool, sleep: bool) -> Result<(), RogError> {
let bytes = if awake && sleep {
LED_AWAKE_ON_SLEEP_ON
} else if awake && !sleep {
LED_AWAKE_ON_SLEEP_OFF
} else if !awake && sleep {
LED_AWAKE_OFF_SLEEP_ON
} else if !awake && !sleep {
LED_AWAKE_OFF_SLEEP_OFF
} else {
LED_AWAKE_ON_SLEEP_ON
};
self.write_bytes(&bytes)?;
self.write_bytes(&LED_SET)?;
// Changes won't persist unless apply is set
self.write_bytes(&LED_APPLY)?;
Ok(())
}
fn find_led_node(id_product: &str) -> Result<String, RogError> {
let mut enumerator = udev::Enumerator::new().map_err(|err| {
warn!("{}", err);
RogError::Udev("enumerator failed".into(), err)
})?;
enumerator.match_subsystem("hidraw").map_err(|err| {
warn!("{}", err);
RogError::Udev("match_subsystem failed".into(), err)
})?;
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);
RogError::Udev("parent_with_subsystem_devtype failed".into(), err)
})?
{
if parent
.attribute_value("idProduct")
.ok_or_else(|| RogError::NotFound("LED idProduct".into()))?
== id_product
{
if let Some(dev_node) = device.devnode() {
info!("Using device at: {:?} for LED control", dev_node);
return Ok(dev_node.to_string_lossy().to_string());
}
}
}
}
Err(RogError::MissingFunction(
"ASUS LED device node not found".into(),
))
}
pub(crate) fn do_command(&mut self, mode: AuraEffect) -> Result<(), RogError> {
self.set_and_save(mode)
}
/// Should only be used if the bytes you are writing are verified correct
#[inline]
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) {
// println!("write: {:02x?}", &message);
return file
.write_all(message)
.map_err(|err| RogError::Write("write_bytes".into(), err));
}
}
Err(RogError::NotSupported)
}
/// Write an effect block
#[inline]
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)?;
}
} else {
for row in effect.iter() {
self.write_bytes(row)?;
}
}
self.flip_effect_write = !self.flip_effect_write;
Ok(())
}
/// Used to set a builtin mode and save the settings for it
///
/// This needs to be universal so that settings applied by dbus stick
#[inline]
fn set_and_save(&mut self, mode: AuraEffect) -> Result<(), RogError> {
self.config.read();
self.write_mode(&mode)?;
self.config.current_mode = *mode.mode();
self.config.set_builtin(mode);
self.config.write();
Ok(())
}
#[inline]
pub(super) fn toggle_mode(&mut self, reverse: bool) -> Result<(), RogError> {
let current = self.config.current_mode;
if let Some(idx) = self
.supported_modes
.standard
.iter()
.position(|v| *v == current)
{
let mut idx = idx;
// goes past end of array
if reverse {
if idx == 0 {
idx = self.supported_modes.standard.len() - 1;
} else {
idx -= 1;
}
} else {
idx += 1;
if idx == self.supported_modes.standard.len() {
idx = 0;
}
}
let next = self.supported_modes.standard[idx];
self.config.read();
if let Some(data) = self.config.builtins.get(&next) {
self.write_mode(data)?;
self.config.current_mode = next;
}
self.config.write();
}
Ok(())
}
#[inline]
fn write_mode(&self, mode: &AuraEffect) -> Result<(), RogError> {
if !self.supported_modes.standard.contains(mode.mode()) {
return Err(RogError::NotSupported);
}
let bytes: [u8; LED_MSG_LEN] = mode.into();
self.write_bytes(&bytes)?;
self.write_bytes(&LED_SET)?;
// Changes won't persist unless apply is set
self.write_bytes(&LED_APPLY)?;
Ok(())
}
}

View File

@@ -0,0 +1,3 @@
pub mod config;
pub mod controller;
pub mod zbus;

View File

@@ -0,0 +1,165 @@
use log::{error, warn};
use rog_aura::{AuraEffect, LedBrightness, LedPowerStates};
use zbus::dbus_interface;
use zvariant::ObjectPath;
use super::controller::CtrlKbdLedZbus;
impl crate::ZbusAdd for CtrlKbdLedZbus {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&ObjectPath::from_str_unchecked("/org/asuslinux/Led"), self)
.map_err(|err| {
error!("DbusKbdLed: add_to_server {}", err);
})
.ok();
}
}
/// The main interface for changing, reading, or notfying signals
///
/// LED commands are split between Brightness, Modes, Per-Key
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlKbdLedZbus {
/// Set the keyboard brightness level (0-3)
fn set_brightness(&mut self, brightness: LedBrightness) {
if let Ok(ctrl) = self.0.try_lock() {
ctrl.set_brightness(brightness)
.map_err(|err| warn!("{}", err))
.ok();
}
}
/// Set the keyboard LED to enabled while the device is awake
fn set_awake_enabled(&mut self, enabled: bool) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.set_states_enabled(enabled, ctrl.config.sleep_anim_enabled)
.map_err(|err| warn!("{}", err))
.ok();
ctrl.config.awake_enabled = enabled;
ctrl.config.write();
let states = LedPowerStates {
enabled: ctrl.config.awake_enabled,
sleep_anim_enabled: ctrl.config.sleep_anim_enabled,
};
self.notify_power_states(&states)
.unwrap_or_else(|err| warn!("{}", err));
}
}
/// Set the keyboard LED suspend animation to enabled while the device is suspended
fn set_sleep_enabled(&mut self, enabled: bool) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.set_states_enabled(ctrl.config.awake_enabled, enabled)
.map_err(|err| warn!("{}", err))
.ok();
ctrl.config.sleep_anim_enabled = enabled;
ctrl.config.write();
let states = LedPowerStates {
enabled: ctrl.config.awake_enabled,
sleep_anim_enabled: ctrl.config.sleep_anim_enabled,
};
self.notify_power_states(&states)
.unwrap_or_else(|err| warn!("{}", err));
}
}
fn set_led_mode(&mut self, effect: AuraEffect) {
if let Ok(mut ctrl) = self.0.try_lock() {
match ctrl.do_command(effect) {
Ok(_) => {
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
self.notify_led(mode.clone())
.unwrap_or_else(|err| warn!("{}", err));
}
}
Err(err) => {
warn!("{}", err);
}
}
}
}
fn next_led_mode(&self) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.toggle_mode(false)
.unwrap_or_else(|err| warn!("{}", err));
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
self.notify_led(mode.clone())
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
fn prev_led_mode(&self) {
if let Ok(mut ctrl) = self.0.try_lock() {
ctrl.toggle_mode(true)
.unwrap_or_else(|err| warn!("{}", err));
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
self.notify_led(mode.clone())
.unwrap_or_else(|err| warn!("{}", err));
}
}
}
#[dbus_interface(property)]
fn awake_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.awake_enabled;
}
true
}
#[dbus_interface(property)]
fn sleep_enabled(&self) -> bool {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.config.sleep_anim_enabled;
}
true
}
/// Return the current mode data
#[dbus_interface(property)]
fn led_mode(&self) -> String {
if let Ok(ctrl) = self.0.try_lock() {
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
if let Ok(json) = serde_json::to_string(&mode) {
return json;
}
}
}
warn!("SetKeyBacklight could not deserialise");
"SetKeyBacklight could not deserialise".to_string()
}
/// Return a list of available modes
#[dbus_interface(property)]
fn led_modes(&self) -> String {
if let Ok(ctrl) = self.0.try_lock() {
if let Ok(json) = serde_json::to_string(&ctrl.config.builtins) {
return json;
}
}
warn!("SetKeyBacklight could not deserialise");
"SetKeyBacklight could not serialise".to_string()
}
/// Return the current LED brightness
#[dbus_interface(property)]
fn led_brightness(&self) -> i8 {
if let Ok(ctrl) = self.0.try_lock() {
return ctrl.get_brightness().map(|n| n as i8).unwrap_or(-1);
}
warn!("SetKeyBacklight could not serialise");
-1
}
#[dbus_interface(signal)]
fn notify_led(&self, data: AuraEffect) -> zbus::Result<()>;
#[dbus_interface(signal)]
fn notify_power_states(&self, data: &LedPowerStates) -> zbus::Result<()>;
}

View File

@@ -1,705 +0,0 @@
use ::zbus::Connection;
use ctrl_gfx::error::GfxError;
use ctrl_gfx::*;
use ctrl_rog_bios::CtrlRogBios;
use log::{error, info, warn};
use logind_zbus::{
types::{SessionClass, SessionInfo, SessionState, SessionType},
ManagerProxy, SessionProxy,
};
use rog_types::gfx_vendors::{GfxPower, GfxRequiredUserAction, GfxVendors};
use std::{io::Write, ops::Add, path::Path, time::Instant};
use std::{process::Command, thread::sleep, time::Duration};
use std::{str::FromStr, sync::mpsc};
use std::{sync::Arc, sync::Mutex};
use sysfs_class::RuntimePM;
use sysfs_class::{PciDevice, SysClass};
use system::{GraphicsDevice, PciBus};
use crate::*;
const THREAD_TIMEOUT_MSG: &str = "GFX: thread time exceeded 3 minutes, exiting";
pub struct CtrlGraphics {
bus: PciBus,
_amd: Vec<GraphicsDevice>,
_intel: Vec<GraphicsDevice>,
nvidia: Vec<GraphicsDevice>,
#[allow(dead_code)]
other: Vec<GraphicsDevice>,
config: Arc<Mutex<Config>>,
thread_kill: Arc<Mutex<Option<mpsc::Sender<bool>>>>,
}
impl Reloadable for CtrlGraphics {
fn reload(&mut self) -> Result<(), RogError> {
self.auto_power()?;
info!("GFX: Reloaded gfx mode: {:?}", self.get_gfx_mode()?);
Ok(())
}
}
impl CtrlGraphics {
pub fn new(config: Arc<Mutex<Config>>) -> std::io::Result<CtrlGraphics> {
let bus = PciBus::new()?;
info!("GFX: Rescanning PCI bus");
bus.rescan()?;
let devs = PciDevice::all()?;
let functions = |parent: &PciDevice| -> Vec<PciDevice> {
let mut functions = Vec::new();
if let Some(parent_slot) = parent.id().split('.').next() {
for func in devs.iter() {
if let Some(func_slot) = func.id().split('.').next() {
if func_slot == parent_slot {
info!("GFX: {}: Function for {}", func.id(), parent.id());
functions.push(func.clone());
}
}
}
}
functions
};
let mut amd = Vec::new();
let mut intel = Vec::new();
let mut nvidia = Vec::new();
let mut other = Vec::new();
for dev in devs.iter() {
let c = dev.class().map_err(|err| {
error!(
"GFX: device error: {}, {}",
dev.path().to_string_lossy(),
err
);
err
})?;
if 0x03 == (c >> 16) & 0xFF {
match dev.vendor()? {
0x1002 => {
info!("GFX: {}: AMD graphics", dev.id());
amd.push(GraphicsDevice::new(dev.id().to_owned(), functions(dev)));
}
0x10DE => {
info!("GFX: {}: NVIDIA graphics", dev.id());
dev.set_runtime_pm(sysfs_class::RuntimePowerManagement::On)?;
nvidia.push(GraphicsDevice::new(dev.id().to_owned(), functions(dev)));
}
0x8086 => {
info!("GFX: {}: Intel graphics", dev.id());
intel.push(GraphicsDevice::new(dev.id().to_owned(), functions(dev)));
}
vendor => {
info!("GFX: {}: Other({:X}) graphics", dev.id(), vendor);
other.push(GraphicsDevice::new(dev.id().to_owned(), functions(dev)));
}
}
}
}
Ok(CtrlGraphics {
bus,
_amd: amd,
_intel: intel,
nvidia,
other,
config,
thread_kill: Arc::new(Mutex::new(None)),
})
}
pub fn bus(&self) -> PciBus {
self.bus.clone()
}
pub fn devices(&self) -> Vec<GraphicsDevice> {
self.nvidia.clone()
}
/// Save the selected `Vendor` mode to config
fn save_gfx_mode(vendor: GfxVendors, config: Arc<Mutex<Config>>) {
if let Ok(mut config) = config.lock() {
config.gfx_mode = vendor;
config.write();
}
}
/// Associated method to get which vendor mode is set
pub(super) fn get_gfx_mode(&self) -> Result<GfxVendors, RogError> {
if let Ok(config) = self.config.lock() {
if let Some(mode) = config.gfx_tmp_mode {
return Ok(mode);
}
return Ok(config.gfx_mode);
}
// TODO: Error here
Ok(GfxVendors::Hybrid)
}
pub(super) fn get_runtime_status() -> Result<GfxPower, RogError> {
let path = Path::new("/sys/bus/pci/devices/0000:01:00.0/power/runtime_status");
if path.exists() {
let buf = std::fs::read_to_string(path).map_err(|err| {
RogError::Read(
"/sys/bus/pci/devices/0000:01:00.0/power/runtime_status".to_string(),
err,
)
})?;
Ok(GfxPower::from_str(&buf)?)
} else {
Ok(GfxPower::Off)
}
}
/// Some systems have a fallback service to load nouveau if nvidia fails
fn toggle_fallback_service(vendor: GfxVendors) -> Result<(), RogError> {
let action = if vendor == GfxVendors::Nvidia {
info!("GFX: Enabling nvidia-fallback.service");
"enable"
} else {
info!("GFX: Disabling nvidia-fallback.service");
"disable"
};
let status = Command::new("systemctl")
.arg(action)
.arg("nvidia-fallback.service")
.status()
.map_err(|err| RogError::Command("systemctl".into(), err))?;
if !status.success() {
// Error is ignored in case this service is removed
warn!(
"systemctl: {} (ignore warning if service does not exist!)",
status
);
}
Ok(())
}
/// Write the appropriate xorg config for the chosen mode
fn write_xorg_conf(vendor: GfxVendors) -> Result<(), RogError> {
let text = if vendor == GfxVendors::Nvidia {
[PRIMARY_GPU_BEGIN, PRIMARY_GPU_NVIDIA, PRIMARY_GPU_END].concat()
} else {
[PRIMARY_GPU_BEGIN, PRIMARY_GPU_END].concat()
};
if !Path::new(XORG_PATH).exists() {
std::fs::create_dir(XORG_PATH).map_err(|err| RogError::Write(XORG_PATH.into(), err))?;
}
let file = XORG_PATH.to_string().add(XORG_FILE);
info!("GFX: Writing {}", file);
let mut file = std::fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(&file)
.map_err(|err| RogError::Write(file, err))?;
file.write_all(&text)
.and_then(|_| file.sync_all())
.map_err(|err| RogError::Write(MODPROBE_PATH.into(), err))?;
Ok(())
}
/// Creates the full modprobe.conf required for vfio pass-through
fn get_vfio_conf(devices: &[GraphicsDevice]) -> Vec<u8> {
let mut vifo = MODPROBE_VFIO.to_vec();
for (d_count, dev) in devices.iter().enumerate() {
for (f_count, func) in dev.functions().iter().enumerate() {
let vendor = func.vendor().unwrap();
let device = func.device().unwrap();
unsafe {
vifo.append(format!("{:x}", vendor).as_mut_vec());
}
vifo.append(&mut vec![b':']);
unsafe {
vifo.append(format!("{:x}", device).as_mut_vec());
}
if f_count < dev.functions().len() - 1 {
vifo.append(&mut vec![b',']);
}
}
if d_count < dev.functions().len() - 1 {
vifo.append(&mut vec![b',']);
}
}
let mut conf = MODPROBE_INTEGRATED.to_vec();
conf.append(&mut vifo);
conf
}
fn write_modprobe_conf(vendor: GfxVendors, devices: &[GraphicsDevice]) -> Result<(), RogError> {
info!("GFX: Writing {}", MODPROBE_PATH);
let content = match vendor {
GfxVendors::Nvidia | GfxVendors::Hybrid => {
let mut base = MODPROBE_BASE.to_vec();
base.append(&mut MODPROBE_DRM_MODESET.to_vec());
base
}
GfxVendors::Vfio => Self::get_vfio_conf(devices),
GfxVendors::Integrated => MODPROBE_INTEGRATED.to_vec(),
GfxVendors::Compute => MODPROBE_BASE.to_vec(),
};
let mut file = std::fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(MODPROBE_PATH)
.map_err(|err| RogError::Path(MODPROBE_PATH.into(), err))?;
file.write_all(&content)
.and_then(|_| file.sync_all())
.map_err(|err| RogError::Write(MODPROBE_PATH.into(), err))?;
Ok(())
}
fn unbind_remove_nvidia(devices: &[GraphicsDevice]) -> Result<(), RogError> {
// Unbind NVIDIA graphics devices and their functions
let unbinds = devices.iter().map(|dev| dev.unbind());
// Remove NVIDIA graphics devices and their functions
let removes = devices.iter().map(|dev| dev.remove());
unbinds
.chain(removes)
.collect::<Result<_, _>>()
.map_err(|err| RogError::Command("device unbind error".into(), err))
}
fn unbind_only(devices: &[GraphicsDevice]) -> Result<(), RogError> {
let unbinds = devices.iter().map(|dev| dev.unbind());
unbinds
.collect::<Result<_, _>>()
.map_err(|err| RogError::Command("device unbind error".into(), err))
}
/// Add or remove driver modules
fn do_driver_action(driver: &str, action: &str) -> Result<(), GfxError> {
let mut cmd = Command::new(action);
cmd.arg(driver);
let mut count = 0;
const MAX_TRIES: i32 = 6;
loop {
if count > MAX_TRIES {
let msg = format!("{} {} failed for unknown reason", action, driver);
error!("GFX: {}", msg);
return Ok(()); //Err(RogError::Modprobe(msg));
}
let output = cmd
.output()
.map_err(|err| GfxError::Command(format!("{:?}", cmd), err))?;
if !output.status.success() {
if output
.stderr
.ends_with("is not currently loaded\n".as_bytes())
{
return Ok(());
}
if output.stderr.ends_with("is builtin.\n".as_bytes()) {
return Err(GfxError::VfioBuiltin);
}
if output.stderr.ends_with("Permission denied\n".as_bytes()) {
warn!(
"{} {} failed: {:?}",
action,
driver,
String::from_utf8_lossy(&output.stderr)
);
warn!("GFX: It may be safe to ignore the above error, run `lsmod |grep {}` to confirm modules loaded", driver);
return Ok(());
}
if String::from_utf8_lossy(&output.stderr)
.contains(&format!("Module {} not found", driver))
{
return Err(GfxError::MissingModule(driver.into()));
}
if count >= MAX_TRIES {
let msg = format!(
"{} {} failed: {:?}",
action,
driver,
String::from_utf8_lossy(&output.stderr)
);
return Err(GfxError::Modprobe(msg));
}
} else if output.status.success() {
return Ok(());
}
count += 1;
std::thread::sleep(std::time::Duration::from_millis(50));
}
}
fn do_display_manager_action(action: &str) -> Result<(), RogError> {
let mut cmd = Command::new("systemctl");
cmd.arg(action);
cmd.arg(DISPLAY_MANAGER);
let status = cmd
.status()
.map_err(|err| RogError::Command(format!("{:?}", cmd), err))?;
if !status.success() {
let msg = format!(
"systemctl {} {} failed: {:?}",
action, DISPLAY_MANAGER, status
);
return Err(GfxError::DisplayManagerAction(msg, status).into());
}
Ok(())
}
fn wait_display_manager_state(state: &str) -> Result<(), RogError> {
let mut cmd = Command::new("systemctl");
cmd.arg("is-active");
cmd.arg(DISPLAY_MANAGER);
let mut count = 0;
while count <= (4 * 3) {
// 3 seconds max
let output = cmd
.output()
.map_err(|err| RogError::Command(format!("{:?}", cmd), err))?;
if output.stdout.starts_with(state.as_bytes()) {
return Ok(());
}
std::thread::sleep(std::time::Duration::from_millis(250));
count += 1;
}
Err(GfxError::DisplayManagerTimeout(state.into()).into())
}
/// Determine if we need to logout/thread. Integrated<->Vfio mode does not
/// require logout.
fn is_logout_required(&self, vendor: GfxVendors) -> GfxRequiredUserAction {
if let Ok(config) = self.config.lock() {
let current = config.gfx_mode;
// Modes that can switch without logout
if matches!(
current,
GfxVendors::Integrated | GfxVendors::Vfio | GfxVendors::Compute
) && matches!(
vendor,
GfxVendors::Integrated | GfxVendors::Vfio | GfxVendors::Compute
) {
return GfxRequiredUserAction::None;
}
// Modes that require a switch to integrated first
if matches!(current, GfxVendors::Nvidia | GfxVendors::Hybrid)
&& matches!(vendor, GfxVendors::Compute | GfxVendors::Vfio)
{
return GfxRequiredUserAction::Integrated;
}
}
GfxRequiredUserAction::Logout
}
/// Do a full setup flow for the chosen mode:
///
/// Tasks:
/// - rescan for devices
/// - write xorg config
/// - write modprobe config
/// + add drivers
/// + or remove drivers and devices
///
/// The daemon needs direct access to this function when it detects that the
/// bios has G-Sync switch is enabled
pub fn do_mode_setup_tasks(
vendor: GfxVendors,
vfio_enable: bool,
devices: &[GraphicsDevice],
bus: &PciBus,
) -> Result<(), RogError> {
// Rescan before doing remove or add drivers
bus.rescan()?;
// Make sure the power management is set to auto for nvidia devices
let devs = PciDevice::all()?;
for dev in devs.iter() {
let c = dev.class().map_err(|err| {
error!(
"GFX: device error: {}, {}",
dev.path().to_string_lossy(),
err
);
err
})?;
if 0x03 == (c >> 16) & 0xFF && dev.vendor()? == 0x10DE {
info!("GFX: {}: NVIDIA graphics, setting PM to auto", dev.id());
dev.set_runtime_pm(sysfs_class::RuntimePowerManagement::On)?;
}
}
// Only these modes should have xorg config
if matches!(
vendor,
GfxVendors::Nvidia | GfxVendors::Hybrid | GfxVendors::Integrated
) {
Self::write_xorg_conf(vendor)?;
}
// Write different modprobe to enable boot control to work
Self::write_modprobe_conf(vendor, devices)?;
match vendor {
GfxVendors::Nvidia | GfxVendors::Hybrid | GfxVendors::Compute => {
if vfio_enable {
for driver in VFIO_DRIVERS.iter() {
Self::do_driver_action(driver, "rmmod")?;
}
}
for driver in NVIDIA_DRIVERS.iter() {
Self::do_driver_action(driver, "modprobe")?;
}
}
GfxVendors::Vfio => {
if vfio_enable {
Self::do_driver_action("nouveau", "rmmod")?;
for driver in NVIDIA_DRIVERS.iter() {
Self::do_driver_action(driver, "rmmod")?;
}
Self::unbind_only(devices)?;
Self::do_driver_action("vfio-pci", "modprobe")?;
} else {
return Err(GfxError::VfioDisabled.into());
}
}
GfxVendors::Integrated => {
Self::do_driver_action("nouveau", "rmmod")?;
if vfio_enable {
for driver in VFIO_DRIVERS.iter() {
Self::do_driver_action(driver, "rmmod")?;
}
}
for driver in NVIDIA_DRIVERS.iter() {
Self::do_driver_action(driver, "rmmod")?;
}
Self::unbind_remove_nvidia(devices)?;
}
}
Ok(())
}
/// Check if the user has any graphical uiser sessions that are active or online
fn graphical_user_sessions_exist(
connection: &Connection,
sessions: &[SessionInfo],
) -> Result<bool, RogError> {
for session in sessions {
let session_proxy = SessionProxy::new(connection, session)?;
if session_proxy.get_class()? == SessionClass::User {
match session_proxy.get_type()? {
SessionType::X11 | SessionType::Wayland | SessionType::MIR => {
match session_proxy.get_state()? {
SessionState::Online | SessionState::Active => return Ok(true),
SessionState::Closing | SessionState::Invalid => {}
}
}
_ => {}
}
}
}
Ok(false)
}
/// Spools until all user sessions are ended then switches to requested mode
fn create_mode_change_thread(
vendor: GfxVendors,
devices: Vec<GraphicsDevice>,
bus: PciBus,
thread_stop: mpsc::Receiver<bool>,
config: Arc<Mutex<Config>>,
) -> Result<String, RogError> {
info!("GFX: display-manager thread started");
const SLEEP_PERIOD: Duration = Duration::from_millis(100);
let start_time = Instant::now();
let connection = Connection::new_system()?;
let manager = ManagerProxy::new(&connection)?;
let mut sessions = manager.list_sessions()?;
loop {
let tmp = manager.list_sessions()?;
if !tmp.iter().eq(&sessions) {
info!("GFX thread: Sessions list changed");
sessions = tmp;
}
if !Self::graphical_user_sessions_exist(&connection, &sessions)? {
break;
}
if let Ok(stop) = thread_stop.try_recv() {
if stop {
return Ok("Graphics mode change was cancelled".into());
}
}
// exit if 3 minutes pass
if Instant::now().duration_since(start_time).as_secs() > 180 {
warn!("{}", THREAD_TIMEOUT_MSG);
return Ok(THREAD_TIMEOUT_MSG.into());
}
// Don't spin at max speed
sleep(SLEEP_PERIOD);
}
info!("GFX thread: all graphical user sessions ended, continuing");
Self::do_display_manager_action("stop")?;
Self::wait_display_manager_state("inactive")?;
let mut mode_to_save = vendor;
// Need to change to integrated before we can change to vfio or compute
if let Ok(mut config) = config.try_lock() {
// Since we have a lock, reset tmp to none. This thread should only ever run
// for Integrated, Hybrid, or Nvidia. Tmp is also only for informational
config.gfx_tmp_mode = None;
//
let vfio_enable = config.gfx_vfio_enable;
// Failsafe. In the event this loop is run with a switch from nvidia in use
// to vfio or compute do a forced switch to integrated instead to prevent issues
if matches!(vendor, GfxVendors::Compute | GfxVendors::Vfio)
&& matches!(config.gfx_mode, GfxVendors::Nvidia | GfxVendors::Hybrid)
{
Self::do_mode_setup_tasks(GfxVendors::Integrated, vfio_enable, &devices, &bus)?;
Self::do_display_manager_action("restart")?;
mode_to_save = GfxVendors::Integrated;
} else {
Self::do_mode_setup_tasks(vendor, vfio_enable, &devices, &bus)?;
Self::do_display_manager_action("restart")?;
}
}
// Save selected mode in case of reboot
Self::save_gfx_mode(mode_to_save, config);
info!("GFX thread: display-manager started");
let v: &str = vendor.into();
info!("GFX thread: Graphics mode changed to {} successfully", v);
Ok(format!("Graphics mode changed to {} successfully", v))
}
/// Before starting a new thread the old one *must* be cancelled
fn cancel_mode_change_thread(&self) {
if let Ok(lock) = self.thread_kill.lock() {
if let Some(tx) = lock.as_ref() {
// Cancel the running thread
info!("GFX: Cancelling previous thread");
tx.send(true)
.map_err(|err| {
warn!("GFX thread: {}", err);
})
.ok();
}
}
}
/// The thread is used only in cases where a logout is required
fn setup_mode_change_thread(&mut self, vendor: GfxVendors) {
let config = self.config.clone();
let devices = self.nvidia.clone();
let bus = self.bus.clone();
let (tx, rx) = mpsc::channel();
if let Ok(mut lock) = self.thread_kill.lock() {
*lock = Some(tx);
}
let thread_kill = self.thread_kill.clone();
std::thread::spawn(move || {
Self::create_mode_change_thread(vendor, devices, bus, rx, config)
.map_err(|err| {
error!("GFX: {}", err);
})
.ok();
// clear the tx/rx when done
if let Ok(mut lock) = thread_kill.try_lock() {
*lock = None;
}
});
}
/// Initiates a mode change by starting a thread that will wait until all
/// graphical sessions are exited before performing the tasks required
/// to switch modes.
///
/// For manually calling (not on boot/startup) via dbus
pub fn set_gfx_mode(&mut self, vendor: GfxVendors) -> Result<GfxRequiredUserAction, RogError> {
if let Ok(gsync) = CtrlRogBios::get_gfx_mode() {
if gsync == 1 {
return Err(GfxError::GsyncModeActive.into());
}
}
let vfio_enable = if let Ok(config) = self.config.try_lock() {
config.gfx_vfio_enable
} else {
false
};
if !vfio_enable && matches!(vendor, GfxVendors::Vfio) {
return Err(GfxError::VfioDisabled.into());
}
// Must always cancel any thread running
self.cancel_mode_change_thread();
// determine which method we need here
let action_required = self.is_logout_required(vendor);
match action_required {
GfxRequiredUserAction::Logout => {
info!("GFX: mode change requires a logout to complete");
self.setup_mode_change_thread(vendor);
}
GfxRequiredUserAction::Reboot => {
info!("GFX: mode change requires reboot");
let devices = self.nvidia.clone();
let bus = self.bus.clone();
Self::do_mode_setup_tasks(vendor, vfio_enable, &devices, &bus)?;
info!("GFX: Graphics mode changed to {}", <&str>::from(vendor));
}
GfxRequiredUserAction::Integrated => {
info!("GFX: mode change requires user to be in Integrated mode first");
}
GfxRequiredUserAction::None => {
info!("GFX: mode change does not require logout");
let devices = self.nvidia.clone();
let bus = self.bus.clone();
Self::do_mode_setup_tasks(vendor, vfio_enable, &devices, &bus)?;
info!("GFX: Graphics mode changed to {}", <&str>::from(vendor));
if let Ok(mut config) = self.config.try_lock() {
config.gfx_tmp_mode = None;
if matches!(vendor, GfxVendors::Vfio | GfxVendors::Compute) {
config.gfx_tmp_mode = Some(vendor);
}
}
}
}
Ok(action_required)
}
/// Used only on boot to set correct mode
fn auto_power(&mut self) -> Result<(), RogError> {
let vendor = self.get_gfx_mode()?;
let devices = self.nvidia.clone();
let bus = self.bus.clone();
let vfio_enable = if let Ok(config) = self.config.try_lock() {
config.gfx_vfio_enable
} else {
false
};
Self::do_mode_setup_tasks(vendor, vfio_enable, &devices, &bus)?;
Self::toggle_fallback_service(vendor)?;
Ok(())
}
}

View File

@@ -1,58 +0,0 @@
use std::fmt;
use std::{error, process::ExitStatus};
use crate::error::RogError;
#[derive(Debug)]
pub enum GfxError {
ParseVendor,
ParsePower,
Bus(String, std::io::Error),
DisplayManagerAction(String, ExitStatus),
DisplayManagerTimeout(String),
GsyncModeActive,
VfioBuiltin,
VfioDisabled,
MissingModule(String),
Modprobe(String),
Command(String, std::io::Error),
}
impl fmt::Display for GfxError {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
GfxError::ParseVendor => write!(f, "Could not parse vendor name"),
GfxError::ParsePower => write!(f, "Could not parse dGPU power status"),
GfxError::Bus(func, error) => write!(f, "Bus error: {}: {}", func, error),
GfxError::DisplayManagerAction(action, status) => {
write!(f, "Display-manager action {} failed: {}", action, status)
}
GfxError::DisplayManagerTimeout(state) => {
write!(f, "Timed out waiting for display-manager {} state", state)
}
GfxError::GsyncModeActive => write!(
f,
"Can not switch gfx modes when dedicated/G-Sync mode is active"
),
GfxError::VfioBuiltin => write!(
f,
"Can not switch to vfio mode if the modules are built in to kernel"
),
GfxError::VfioDisabled => {
write!(f, "Can not switch to vfio mode if disabled in config file")
}
GfxError::MissingModule(m) => write!(f, "The module {} is missing", m),
GfxError::Modprobe(detail) => write!(f, "Modprobe error: {}", detail),
GfxError::Command(func, error) => write!(f, "Command exec error: {}: {}", func, error),
}
}
}
impl error::Error for GfxError {}
impl From<GfxError> for RogError {
fn from(err: GfxError) -> Self {
RogError::GfxSwitching(err)
}
}

View File

@@ -1,62 +0,0 @@
pub mod error;
pub mod controller;
pub mod system;
pub mod zbus;
const NVIDIA_DRIVERS: [&str; 4] = ["nvidia_drm", "nvidia_modeset", "nvidia_uvm", "nvidia"];
const VFIO_DRIVERS: [&str; 5] = [
"vfio-pci",
"vfio_iommu_type1",
"vfio_virqfd",
"vfio_mdev",
"vfio",
];
const DISPLAY_MANAGER: &str = "display-manager.service";
const MODPROBE_PATH: &str = "/etc/modprobe.d/asusd.conf";
static MODPROBE_BASE: &[u8] = br#"# Automatically generated by asusd
# If you have issues with i2c_nvidia_gpu, copy the 2 lines below to a
# new blacklist file and uncomment
#blacklist i2c_nvidia_gpu
#alias i2c_nvidia_gpu off
blacklist nouveau
alias nouveau off
options nvidia NVreg_DynamicPowerManagement=0x02
"#;
static MODPROBE_DRM_MODESET: &[u8] = br#"
options nvidia-drm modeset=1
"#;
static MODPROBE_INTEGRATED: &[u8] = br#"# Automatically generated by asusd
blacklist i2c_nvidia_gpu
blacklist nvidia
blacklist nvidia-drm
blacklist nvidia-modeset
blacklist nouveau
alias nouveau off
"#;
static MODPROBE_VFIO: &[u8] = br#"options vfio-pci ids="#;
const XORG_FILE: &str = "90-nvidia-primary.conf";
const XORG_PATH: &str = "/etc/X11/xorg.conf.d/";
static PRIMARY_GPU_BEGIN: &[u8] = br#"# Automatically generated by asusd
Section "OutputClass"
Identifier "nvidia"
MatchDriver "nvidia-drm"
Driver "nvidia"
Option "AllowEmptyInitialConfiguration" "true""#;
static PRIMARY_GPU_NVIDIA: &[u8] = br#"
Option "PrimaryGPU" "true""#;
static PRIMARY_GPU_END: &[u8] = br#"
EndSection"#;

View File

@@ -1,160 +0,0 @@
use log::{error, info, warn};
use std::fs::read_to_string;
use std::{fs::write, io, path::PathBuf};
use sysfs_class::{PciDevice, SysClass};
pub struct Module {
pub name: String,
}
impl Module {
fn parse(line: &str) -> io::Result<Module> {
let mut parts = line.split(' ');
let name = parts
.next()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "module name not found"))?;
Ok(Module {
name: name.to_string(),
})
}
pub fn all() -> io::Result<Vec<Module>> {
let mut modules = Vec::new();
let data = read_to_string("/proc/modules")?;
for line in data.lines() {
let module = Module::parse(line)?;
modules.push(module);
}
Ok(modules)
}
}
#[derive(Clone)]
pub struct PciBus {
path: PathBuf,
}
impl PciBus {
pub fn new() -> io::Result<PciBus> {
let path = PathBuf::from("/sys/bus/pci");
if path.is_dir() {
Ok(PciBus { path })
} else {
Err(io::Error::new(
io::ErrorKind::NotFound,
"pci directory not found",
))
}
}
/// Will rescan the device tree, which adds all removed devices back
pub fn rescan(&self) -> io::Result<()> {
write(self.path.join("rescan"), "1")
}
}
#[derive(Clone)]
pub struct GraphicsDevice {
_id: String,
functions: Vec<PciDevice>,
}
impl GraphicsDevice {
pub fn new(id: String, functions: Vec<PciDevice>) -> GraphicsDevice {
GraphicsDevice { _id: id, functions }
}
pub fn exists(&self) -> bool {
self.functions.iter().any(|func| func.path().exists())
}
pub fn functions(&self) -> &[PciDevice] {
&self.functions
}
pub fn unbind(&self) -> Result<(), std::io::Error> {
for func in self.functions.iter() {
if func.path().exists() {
match func.driver() {
Ok(driver) => {
info!("{}: Unbinding {}", driver.id(), func.id());
unsafe {
driver.unbind(func).map_err(|err| {
error!("gfx unbind: {}", err);
err
})?;
}
}
Err(err) => match err.kind() {
io::ErrorKind::NotFound => (),
_ => {
error!("gfx driver: {:?}, {}", func.path(), err);
return Err(err);
}
},
}
}
}
Ok(())
}
pub fn rebind(&self) -> Result<(), std::io::Error> {
for func in self.functions.iter() {
if func.path().exists() {
match func.driver() {
Ok(driver) => {
info!("{}: Binding {}", driver.id(), func.id());
unsafe {
driver.bind(func).map_err(|err| {
error!("gfx bind: {}", err);
err
})?;
}
}
Err(err) => match err.kind() {
io::ErrorKind::NotFound => (),
_ => {
error!("gfx driver: {:?}, {}", func.path(), err);
return Err(err);
}
},
}
}
}
Ok(())
}
pub fn remove(&self) -> Result<(), std::io::Error> {
for func in self.functions.iter() {
if func.path().exists() {
match func.driver() {
Ok(driver) => {
error!("{}: in use by {}", func.id(), driver.id());
}
Err(why) => match why.kind() {
std::io::ErrorKind::NotFound => {
info!("{}: Removing", func.id());
unsafe {
// ignore errors and carry on
if let Err(err) = func.remove() {
error!("gfx remove: {}", err);
}
}
}
_ => {
error!("Remove device failed");
}
},
}
} else {
warn!("{}: Already removed", func.id());
}
}
info!("Removed all gfx devices");
Ok(())
}
}

View File

@@ -0,0 +1,92 @@
use log::{error, warn};
use rog_profiles::error::ProfileError;
use rog_profiles::{FanCurves, Profile};
use serde_derive::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
#[derive(Deserialize, Serialize, Debug)]
pub struct ProfileConfig {
#[serde(skip)]
config_path: String,
/// For restore on boot
pub active: Profile,
/// States to restore
pub fan_curves: Option<FanCurves>,
}
impl ProfileConfig {
fn new(config_path: String) -> Result<Self, ProfileError> {
let mut platform = ProfileConfig {
config_path,
active: Profile::Balanced,
fan_curves: None,
};
if !Profile::is_platform_profile_supported() {
return Err(ProfileError::NotSupported);
}
if FanCurves::is_fan_curves_supported() {
let mut curves = FanCurves::default();
curves.update_from_platform();
platform.fan_curves = Some(curves);
}
Ok(platform)
}
}
impl ProfileConfig {
pub fn load(config_path: String) -> Self {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&config_path)
.unwrap_or_else(|_| panic!("The directory /etc/asusd/ is missing")); // okay to cause panic here
let mut buf = String::new();
let mut config;
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
config = Self::new(config_path).unwrap();
} else if let Ok(data) = serde_json::from_str(&buf) {
config = data;
config.config_path = config_path;
} else {
warn!("Could not deserialise {}", config_path);
panic!("Please remove {} then restart service", config_path);
}
} else {
config = Self::new(config_path).unwrap()
}
config.write();
config
}
pub fn read(&mut self) {
let mut file = OpenOptions::new()
.read(true)
.open(&self.config_path)
.unwrap_or_else(|err| panic!("Error reading {}: {}", self.config_path, err));
let mut buf = String::new();
if let Ok(l) = file.read_to_string(&mut buf) {
if l == 0 {
warn!("File is empty {}", self.config_path);
} else {
let mut data: ProfileConfig = serde_json::from_str(&buf)
.unwrap_or_else(|_| panic!("Could not deserialise {}", self.config_path));
// copy over serde skipped values
data.config_path = self.config_path.clone();
*self = data;
}
}
}
pub fn write(&self) {
let mut file = File::create(&self.config_path).expect("Couldn't overwrite config");
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
file.write_all(json.as_bytes())
.unwrap_or_else(|err| error!("Could not write config: {}", err));
}
}

View File

@@ -10,18 +10,20 @@ use daemon::ctrl_profiles::controller::CtrlPlatformTask;
use daemon::{
config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported,
};
use daemon::{ctrl_anime::*, ctrl_gfx::controller::CtrlGraphics};
use daemon::{ctrl_anime::*};
use daemon::{
ctrl_profiles::{controller::CtrlPlatformProfile, zbus::ProfileZbus},
laptops::LaptopLedData,
};
use supergfxctl::config::GfxConfig;
use supergfxctl::controller::CtrlGraphics;
use supergfxctl::gfx_vendors::GfxVendors;
use ::zbus::{fdo, Connection, ObjectServer};
use daemon::{CtrlTask, Reloadable, ZbusAdd};
use log::LevelFilter;
use log::{error, info, warn};
use rog_dbus::DBUS_NAME;
use rog_types::gfx_vendors::GfxVendors;
use std::env;
use std::error::Error;
use std::io::Write;
@@ -32,6 +34,7 @@ use daemon::ctrl_rog_bios::CtrlRogBios;
use zvariant::ObjectPath;
static PROFILE_CONFIG_PATH: &str = "/etc/asusd/profile.conf";
static GFX_CONFIG_PATH: &str = "/etc/asusd/supergfx.conf";
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut logger = env_logger::Builder::new();
@@ -81,9 +84,12 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
let mut object_server = ObjectServer::new(&connection);
let config = Config::load();
let enable_gfx_switching = config.gfx_managed;
let config = Arc::new(Mutex::new(config));
let gfx_config = GfxConfig::load(GFX_CONFIG_PATH.into());
let enable_gfx_switching = gfx_config.gfx_managed;
let gfx_config = Arc::new(Mutex::new(gfx_config));
supported.add_to_server(&mut object_server);
match CtrlRogBios::new(config.clone()) {
@@ -169,12 +175,12 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
// Graphics switching requires some checks on boot specifically for g-sync capable laptops
if enable_gfx_switching {
match CtrlGraphics::new(config.clone()) {
match CtrlGraphics::new(gfx_config.clone()) {
Ok(mut ctrl) => {
// Need to check if a laptop has the dedicated gfx switch
if CtrlRogBios::has_dedicated_gfx_toggle() {
if let Ok(ded) = CtrlRogBios::get_gfx_mode() {
if let Ok(config) = config.lock() {
if let Ok(config) = gfx_config.lock() {
if ded == 1 {
warn!("Dedicated GFX toggle is on but driver mode is not nvidia \nSetting to nvidia driver mode");
let devices = ctrl.devices();

View File

@@ -1,10 +1,9 @@
use rog_profiles::error::ProfileError;
use rog_types::error::GraphicsError;
use supergfxctl::error::GfxError;
use std::convert::From;
use std::fmt;
use crate::ctrl_gfx::error::GfxError;
#[derive(Debug)]
pub enum RogError {
ParseVendor,
@@ -24,7 +23,6 @@ pub enum RogError {
Profiles(ProfileError),
Initramfs(String),
Modprobe(String),
Command(String, std::io::Error),
Io(std::io::Error),
Zbus(zbus::Error),
}
@@ -50,7 +48,6 @@ impl fmt::Display for RogError {
RogError::Profiles(deets) => write!(f, "Profile error: {}", deets),
RogError::Initramfs(detail) => write!(f, "Initiramfs error: {}", detail),
RogError::Modprobe(detail) => write!(f, "Modprobe error: {}", detail),
RogError::Command(func, error) => write!(f, "Command exec error: {}: {}", func, error),
RogError::Io(detail) => write!(f, "std::io error: {}", detail),
RogError::Zbus(detail) => write!(f, "Zbus error: {}", detail),
}

View File

@@ -8,8 +8,6 @@ pub mod ctrl_anime;
pub mod ctrl_aura;
/// Control of battery charge level
pub mod ctrl_charge;
/// GPU switching and power
pub mod ctrl_gfx;
/// Control CPU min/max freq and turbo, fan mode, fan curves
///
/// Intel machines can control: