use log::{error, info, warn}; use std::error::Error; use std::io::Write; use std::iter::FromIterator; use std::path::Path; use std::process::Command; use std::str::FromStr; use sysfs_class::{PciDevice, SysClass}; use zbus::dbus_interface; use crate::vendors::*; use crate::*; use crate::{error::GfxError, system::*}; pub struct CtrlGraphics { bus: PciBus, amd: Vec, intel: Vec, nvidia: Vec, #[allow(dead_code)] other: Vec, initfs_cmd: Option, } trait Dbus { fn set_vendor(&mut self, vendor: String); fn notify_gfx(&self, vendor: &str) -> zbus::Result<()>; fn notify_action(&self, action: &str) -> zbus::Result<()>; } #[cfg(feature = "use-zbus")] use std::convert::TryInto; #[cfg(feature = "use-zbus")] #[dbus_interface(name = "org.asuslinux.Daemon")] impl Dbus for CtrlGraphics { fn set_vendor(&mut self, vendor: String) { if let Ok(tmp) = GfxVendors::from_str(&vendor) { let action = self.set(tmp).unwrap_or_else(|err| { warn!("{}", err); format!("Failed: {}", err.to_string()) }); self.notify_gfx(&vendor) .unwrap_or_else(|err| warn!("{}", err)); self.notify_action(&action) .unwrap_or_else(|err| warn!("{}", err)); } } #[dbus_interface(signal)] fn notify_gfx(&self, vendor: &str) -> zbus::Result<()>; #[dbus_interface(signal)] fn notify_action(&self, action: &str) -> zbus::Result<()>; } impl CtrlGraphics { pub fn new() -> std::io::Result { let bus = PciBus::new()?; info!("Rescanning PCI bus"); bus.rescan()?; let devs = PciDevice::all()?; let functions = |parent: &PciDevice| -> Vec { 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!("{}: 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()?; if 0x03 == (c >> 16) & 0xFF { match dev.vendor()? { 0x1002 => { info!("{}: AMD graphics", dev.id()); amd.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev))); } 0x10DE => { info!("{}: NVIDIA graphics", dev.id()); nvidia.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev))); } 0x8086 => { info!("{}: Intel graphics", dev.id()); intel.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev))); } vendor => { info!("{}: Other({:X}) graphics", dev.id(), vendor); other.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev))); } } } } let mut initfs_cmd = None; if Path::new(INITRAMFS_PATH).exists() { let mut cmd = Command::new("update-initramfs"); cmd.arg("-u"); initfs_cmd = Some(cmd); info!("Using initramfs update command 'update-initramfs'"); } else if Path::new(DRACUT_PATH).exists() { let mut cmd = Command::new("dracut"); cmd.arg("-f"); initfs_cmd = Some(cmd); info!("Using initramfs update command 'dracut'"); } Ok(CtrlGraphics { bus, amd, intel, nvidia, other, initfs_cmd, }) } #[cfg(feature = "use-zbus")] pub fn add_to_server(self, server: &mut zbus::ObjectServer) { server .at(&"/org/asuslinux/Gfx".try_into().unwrap(), self) .unwrap(); } pub fn reload(&mut self) -> Result<(), Box> { self.auto_power()?; info!("Reloaded gfx mode: {:?}", CtrlGraphics::get_vendor()?); Ok(()) } fn can_switch(&self) -> bool { !self.nvidia.is_empty() && (!self.intel.is_empty() || !self.amd.is_empty()) } fn get_prime_discrete() -> Result { let s = std::fs::read_to_string(PRIME_DISCRETE_PATH) .map_err(|err| GfxError::Read(PRIME_DISCRETE_PATH.into(), err))? .trim() .to_owned(); Ok(s) } fn set_prime_discrete(mode: &str) -> Result<(), GfxError> { std::fs::write(PRIME_DISCRETE_PATH, mode) .map_err(|err| GfxError::Read(PRIME_DISCRETE_PATH.into(), err))?; Ok(()) } /// Associated method to get which vendor mode is set pub fn get_vendor() -> Result { let modules = Module::all().map_err(|err| GfxError::Read("get_vendor".into(), err))?; let vendor = if modules .iter() .any(|module| module.name == "nouveau" || module.name == "nvidia") { let mode = match Self::get_prime_discrete() { Ok(m) => m, Err(_) => "nvidia".to_string(), }; if mode == "on-demand" { "hybrid".to_string() } else if mode == "off" { "compute".to_string() } else { "nvidia".to_string() } } else { "integrated".to_string() }; Ok(vendor) } pub fn is_switching_prime_modes(vendor: &GfxVendors) -> Result { let prev_mode = GfxVendors::from_str(&Self::get_vendor()?)?; let x = (prev_mode == GfxVendors::Hybrid || prev_mode == GfxVendors::Nvidia) && (*vendor == GfxVendors::Hybrid || *vendor == GfxVendors::Nvidia); Ok(x) } /// Write out config files if required, enable/disable relevant services, and update the ramdisk pub fn set(&mut self, vendor: GfxVendors) -> Result { //self.switchable_or_fail()?; let mode = if vendor == GfxVendors::Hybrid { "on-demand\n" } else if vendor == GfxVendors::Nvidia { "on\n" } else { // Integrated or Compute "off\n" }; info!("Setting {} to {}", PRIME_DISCRETE_PATH, mode); Self::set_prime_discrete(mode)?; // Switching from hybrid to/from nvidia shouldn't require a ramdisk update // or a reboot. let switching_prime_modes = Self::is_switching_prime_modes(&vendor)?; { info!("Creating {}", MODPROBE_PATH); let mut file = std::fs::OpenOptions::new() .create(true) .truncate(true) .write(true) .open(MODPROBE_PATH) .map_err(|err| GfxError::Path(MODPROBE_PATH.into(), err))?; let text = if vendor == GfxVendors::Hybrid { MODPROBE_HYBRID } else if vendor == GfxVendors::Compute { MODPROBE_COMPUTE } else if vendor == GfxVendors::Nvidia { MODPROBE_NVIDIA } else { MODPROBE_INTEGRATED }; file.write_all(text) .and_then(|_| file.sync_all()) .map_err(|err| GfxError::Write(MODPROBE_PATH.into(), err))?; } info!("Creating {}", PRIMARY_GPU_XORG_PATH); // begin section for non-separated Nvidia xorg modules // eg, not put in their own directory let mut file = std::fs::OpenOptions::new() .create(true) .truncate(true) .write(true) .open(PRIMARY_GPU_XORG_PATH) .map_err(|err| GfxError::Write(PRIMARY_GPU_XORG_PATH.into(), err))?; let text = if vendor == GfxVendors::Nvidia { [PRIMARY_GPU_BEGIN, PRIMARY_GPU_NVIDIA, PRIMARY_GPU_END].concat() } else { [PRIMARY_GPU_BEGIN, PRIMARY_GPU_END].concat() }; file.write_all(&text) .and_then(|_| file.sync_all()) .map_err(|err| GfxError::Write(MODPROBE_PATH.into(), err))?; let action = if vendor == GfxVendors::Nvidia { info!("Enabling nvidia-fallback.service"); "enable" } else { info!("Disabling nvidia-fallback.service"); "disable" }; let status = Command::new("systemctl") .arg(action) .arg("nvidia-fallback.service") .status() .map_err(|err| GfxError::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 ); } let mut required_action = GfxCtrlAction::None; if !switching_prime_modes { info!("Updating initramfs"); if let Some(cmd) = self.initfs_cmd.as_mut() { let status = cmd .status() .map_err(|err| GfxError::Write(format!("{:?}", cmd), err))?; if !status.success() { error!("Ram disk update failed"); } else { info!("Successfully updated iniramfs"); } } required_action = GfxCtrlAction::Reboot; } else if switching_prime_modes { required_action = GfxCtrlAction::RestartX; } Ok(required_action.into()) } pub fn get_power(&self) -> Option { if self.can_switch() { return Some(self.nvidia.iter().any(GraphicsDevice::exists)); } None } fn set_power(&self, power: bool) -> Result<(), GfxError> { // self.switchable_or_fail()?; if power { info!("Enabling graphics power"); self.bus .rescan() .map_err(|err| GfxError::Bus("bus rescan error".into(), err))?; } else { info!("Disabling graphics power"); // Unbind NVIDIA graphics devices and their functions let unbinds = self.nvidia.iter().map(|dev| dev.unbind()); // Remove NVIDIA graphics devices and their functions let removes = self.nvidia.iter().map(|dev| dev.remove()); Result::from_iter(unbinds.chain(removes)) .map_err(|err| GfxError::Command("device unbind error".into(), err))?; } Ok(()) } fn auto_power(&self) -> Result<(), GfxError> { let vendor = CtrlGraphics::get_vendor()?; self.set_power(vendor != "integrated") } }