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:
335
ctrl-gfx/src/ctrl_gfx.rs
Normal file
335
ctrl-gfx/src/ctrl_gfx.rs
Normal file
@@ -0,0 +1,335 @@
|
||||
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<GraphicsDevice>,
|
||||
intel: Vec<GraphicsDevice>,
|
||||
nvidia: Vec<GraphicsDevice>,
|
||||
#[allow(dead_code)]
|
||||
other: Vec<GraphicsDevice>,
|
||||
initfs_cmd: Option<Command>,
|
||||
}
|
||||
|
||||
trait Dbus {
|
||||
fn set_vendor(&mut self, vendor: String);
|
||||
fn notify_gfx(&self, vendor: &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) {
|
||||
self.set(tmp).unwrap_or_else(|err| {
|
||||
warn!("{}", err);
|
||||
format!("Failed: {}", err.to_string())
|
||||
});
|
||||
self.notify_gfx(&vendor)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_gfx(&self, vendor: &str) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
impl CtrlGraphics {
|
||||
pub fn new() -> std::io::Result<CtrlGraphics> {
|
||||
let bus = PciBus::new()?;
|
||||
|
||||
info!("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!("{}: 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<dyn Error>> {
|
||||
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<String, GfxError> {
|
||||
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<String, GfxError> {
|
||||
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<bool, GfxError> {
|
||||
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<String, GfxError> {
|
||||
//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<bool> {
|
||||
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")
|
||||
}
|
||||
}
|
||||
30
ctrl-gfx/src/error.rs
Normal file
30
ctrl-gfx/src/error.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GfxError {
|
||||
ParseVendor,
|
||||
Path(String, std::io::Error),
|
||||
Read(String, std::io::Error),
|
||||
Write(String, std::io::Error),
|
||||
Module(String, std::io::Error),
|
||||
Bus(String, std::io::Error),
|
||||
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::Path(path, error) => write!(f, "Path {}: {}", path, error),
|
||||
GfxError::Read(path, error) => write!(f, "Read {}: {}", path, error),
|
||||
GfxError::Write(path, error) => write!(f, "Write {}: {}", path, error),
|
||||
GfxError::Module(func, error) => write!(f, "Module error: {}: {}", func, error),
|
||||
GfxError::Bus(func, error) => write!(f, "Bus error: {}: {}", func, error),
|
||||
GfxError::Command(func, error) => write!(f, "Command exec error: {}: {}", func, error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for GfxError {}
|
||||
58
ctrl-gfx/src/lib.rs
Normal file
58
ctrl-gfx/src/lib.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
pub mod vendors;
|
||||
|
||||
pub mod error;
|
||||
|
||||
pub mod ctrl_gfx;
|
||||
|
||||
pub mod system;
|
||||
|
||||
const PRIME_DISCRETE_PATH: &str = "/etc/prime-discrete";
|
||||
const MODPROBE_PATH: &str = "/etc/modprobe.d/asusd.conf";
|
||||
const INITRAMFS_PATH: &str = "/usr/sbin/update-initramfs";
|
||||
const DRACUT_PATH: &str = "/usr/bin/dracut";
|
||||
|
||||
static MODPROBE_NVIDIA: &[u8] = MODPROBE_HYBRID;
|
||||
|
||||
static MODPROBE_HYBRID: &[u8] = br#"# Automatically generated by asusd
|
||||
blacklist i2c_nvidia_gpu
|
||||
alias i2c_nvidia_gpu off
|
||||
options nvidia NVreg_DynamicPowerManagement=0x02
|
||||
options nvidia-drm modeset=1
|
||||
"#;
|
||||
|
||||
static MODPROBE_COMPUTE: &[u8] = br#"# Automatically generated by asusd
|
||||
blacklist i2c_nvidia_gpu
|
||||
alias i2c_nvidia_gpu off
|
||||
options nvidia NVreg_DynamicPowerManagement=0x02
|
||||
options nvidia-drm modeset=0
|
||||
"#;
|
||||
|
||||
static MODPROBE_INTEGRATED: &[u8] = br#"# Automatically generated by asusd
|
||||
blacklist i2c_nvidia_gpu
|
||||
blacklist nouveau
|
||||
blacklist nvidia
|
||||
blacklist nvidia-drm
|
||||
blacklist nvidia-modeset
|
||||
alias i2c_nvidia_gpu off
|
||||
alias nouveau off
|
||||
alias nvidia off
|
||||
alias nvidia-drm off
|
||||
alias nvidia-modeset off
|
||||
"#;
|
||||
|
||||
const PRIMARY_GPU_XORG_PATH: &str = "/etc/X11/xorg.conf.d/90-nvidia-primary.conf";
|
||||
|
||||
static PRIMARY_GPU_BEGIN: &[u8] = br#"# Automatically generated by asusd
|
||||
Section "OutputClass"
|
||||
Identifier "nvidia"
|
||||
MatchDriver "nvidia-drm"
|
||||
Driver "nvidia"
|
||||
Option "TripleBuffer" "true"
|
||||
Option "AllowEmptyInitialConfiguration"
|
||||
Option "AllowExternalGpus""#;
|
||||
|
||||
static PRIMARY_GPU_NVIDIA: &[u8] = br#"
|
||||
Option "PrimaryGPU" "true""#;
|
||||
|
||||
static PRIMARY_GPU_END: &[u8] = br#"
|
||||
EndSection"#;
|
||||
127
ctrl-gfx/src/system.rs
Normal file
127
ctrl-gfx/src/system.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rescan(&self) -> io::Result<()> {
|
||||
write(self.path.join("rescan"), "1")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GraphicsDevice {
|
||||
id: String,
|
||||
functions: Vec<PciDevice>,
|
||||
}
|
||||
|
||||
impl GraphicsDevice {
|
||||
pub fn new(id: String, functions: Vec<PciDevice>) -> GraphicsDevice {
|
||||
GraphicsDevice { id, functions }
|
||||
}
|
||||
|
||||
pub fn exists(&self) -> bool {
|
||||
self.functions.iter().any(|func| func.path().exists())
|
||||
}
|
||||
|
||||
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 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!("Remmoved all gfx devices");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
85
ctrl-gfx/src/vendors.rs
Normal file
85
ctrl-gfx/src/vendors.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum GfxVendors {
|
||||
Nvidia,
|
||||
Integrated,
|
||||
Compute,
|
||||
Hybrid,
|
||||
}
|
||||
|
||||
use crate::error::GfxError;
|
||||
use std::str::FromStr;
|
||||
|
||||
impl FromStr for GfxVendors {
|
||||
type Err = GfxError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, GfxError> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"nvidia" => Ok(GfxVendors::Nvidia),
|
||||
"hybrid" => Ok(GfxVendors::Hybrid),
|
||||
"compute" => Ok(GfxVendors::Compute),
|
||||
"integrated" => Ok(GfxVendors::Integrated),
|
||||
"nvidia\n" => Ok(GfxVendors::Nvidia),
|
||||
"hybrid\n" => Ok(GfxVendors::Hybrid),
|
||||
"compute\n" => Ok(GfxVendors::Compute),
|
||||
"integrated\n" => Ok(GfxVendors::Integrated),
|
||||
_ => Err(GfxError::ParseVendor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&GfxVendors> for &str {
|
||||
fn from(mode: &GfxVendors) -> Self {
|
||||
match mode {
|
||||
GfxVendors::Nvidia => "nvidia",
|
||||
GfxVendors::Hybrid => "hybrid",
|
||||
GfxVendors::Compute => "compute",
|
||||
GfxVendors::Integrated => "integrated",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GfxCtrlAction {
|
||||
Reboot,
|
||||
RestartX,
|
||||
None,
|
||||
}
|
||||
|
||||
impl FromStr for GfxCtrlAction {
|
||||
type Err = GfxError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, GfxError> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"reboot" => Ok(GfxCtrlAction::Reboot),
|
||||
"restartx" => Ok(GfxCtrlAction::RestartX),
|
||||
"none" => Ok(GfxCtrlAction::None),
|
||||
_ => Err(GfxError::ParseVendor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&GfxCtrlAction> for &str {
|
||||
fn from(mode: &GfxCtrlAction) -> Self {
|
||||
match mode {
|
||||
GfxCtrlAction::Reboot => "reboot",
|
||||
GfxCtrlAction::RestartX => "restartx",
|
||||
GfxCtrlAction::None => "none",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&GfxCtrlAction> for String {
|
||||
fn from(mode: &GfxCtrlAction) -> Self {
|
||||
match mode {
|
||||
GfxCtrlAction::Reboot => "reboot".into(),
|
||||
GfxCtrlAction::RestartX => "restartx".into(),
|
||||
GfxCtrlAction::None => "none".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GfxCtrlAction> for String {
|
||||
fn from(mode: GfxCtrlAction) -> Self {
|
||||
(&mode).into()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user