diff --git a/src/gpu_profile.rs b/src/gpu_profile.rs new file mode 100644 index 0000000..622d33a --- /dev/null +++ b/src/gpu_profile.rs @@ -0,0 +1,89 @@ +use crate::file_utils::get_reader; +use std::{error::Error, fmt::Display, io::Read, str::FromStr}; + +const POW_PROF_PATH: &str = "/sys/class/drm/card0/device/pp_power_profile_mode"; + +pub fn get_set_vr_pow_prof_cmd() -> String { + format!("sudo sh -c \"echo '4' > {}\"", POW_PROF_PATH) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GpuPowerProfile { + // 3D Full Screen, inverted cause can't start with number + Fullscreen3D, + PowerSaving, + Video, + VR, + Compute, + Custom, +} + +#[derive(Debug)] +pub struct GpuPowerProfileParseErr; + +impl GpuPowerProfileParseErr { + pub fn new() -> Self { + Self {} + } +} + +impl Display for GpuPowerProfileParseErr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("GpuPowerProfileParseErr") + } +} + +impl Error for GpuPowerProfileParseErr { + fn source(&self) -> Option<&(dyn Error + 'static)> { + None + } + + fn cause(&self) -> Option<&dyn Error> { + self.source() + } +} + +impl FromStr for GpuPowerProfile { + type Err = GpuPowerProfileParseErr; + + fn from_str(s: &str) -> Result { + let s = s.to_lowercase(); + let s = s.trim(); + if !s.contains('*') { + return Err(Self::Err::new()); + } + if s.contains("3d_full_screen") { + return Ok(Self::Fullscreen3D); + } + if s.contains("power_saving") { + return Ok(Self::PowerSaving); + } + if s.contains("video") { + return Ok(Self::Video); + } + if s.contains("vr") { + return Ok(Self::VR); + } + if s.contains("compute") { + return Ok(Self::Compute); + } + if s.contains("custom") { + return Ok(Self::Custom); + } + + Err(Self::Err::new()) + } +} + +pub fn get_gpu_power_profile() -> Option { + if let Some(mut reader) = get_reader(&POW_PROF_PATH.into()) { + let mut txt = String::new(); + reader.read_to_string(&mut txt).ok()?; + for line in txt.split('\n') { + if let Ok(pp) = GpuPowerProfile::from_str(line) { + return Some(pp); + } + } + } + None +} diff --git a/src/main.rs b/src/main.rs index 6603097..133fd36 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,6 +37,7 @@ pub mod runner_pipeline; pub mod steamvr_utils; pub mod ui; pub mod xr_devices; +pub mod gpu_profile; fn restore_steam_xr_files() { let active_runtime = get_current_active_runtime(); diff --git a/src/ui/main_view.rs b/src/ui/main_view.rs index e234003..7d99f9c 100644 --- a/src/ui/main_view.rs +++ b/src/ui/main_view.rs @@ -6,6 +6,7 @@ use super::steam_launch_options_box::{SteamLaunchOptionsBox, SteamLaunchOptionsB use crate::config::Config; use crate::constants::APP_NAME; use crate::file_utils::mount_has_nosuid; +use crate::gpu_profile::{get_gpu_power_profile, GpuPowerProfile, get_set_vr_pow_prof_cmd}; use crate::profile::{LighthouseDriver, Profile}; use crate::steamvr_utils::chaperone_info_exists; use crate::ui::app::{ @@ -278,6 +279,89 @@ impl SimpleComponent for MainView { set_wrap_mode: gtk::pango::WrapMode::Word, } }, + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_hexpand: true, + set_vexpand: false, + set_spacing: 12, + set_margin_top: 12, + set_margin_bottom: 12, + // TODO: maybe add manual refresh btn? + #[track = "model.changed(Self::selected_profile())"] + set_visible: get_gpu_power_profile() != Some(GpuPowerProfile::VR), + gtk::Separator { + set_orientation: gtk::Orientation::Horizontal, + set_hexpand: true, + }, + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_margin_start: 12, + set_margin_end: 12, + set_spacing: 12, + set_hexpand: true, + gtk::Image { + set_icon_name: Some("dialog-warning-symbolic"), + add_css_class: "warning", + }, + gtk::Label { + add_css_class: "warning", + add_css_class: "heading", + set_label: "Warning" + } + }, + gtk::Label { + set_label: concat!( + "Your GPU Power Profile is not set to VR. ", + "This will cause noticeable stutter when running XR ", + "applications. Activate the VR profile witrh the ", + "following command:", + ), + set_margin_start: 12, + set_margin_end: 12, + add_css_class: "warning", + set_wrap: true, + set_wrap_mode: gtk::pango::WrapMode::Word, + }, + gtk::Box { + set_margin_start: 12, + set_margin_end: 12, + set_orientation: gtk::Orientation::Horizontal, + set_spacing: 6, + gtk::ScrolledWindow { + add_css_class: "card", + set_vscrollbar_policy: gtk::PolicyType::Never, + set_overflow: gtk::Overflow::Hidden, + gtk::TextView { + set_hexpand: true, + set_vexpand: false, + set_monospace: true, + set_editable: false, + set_left_margin: 6, + set_right_margin: 6, + set_top_margin: 6, + set_bottom_margin: 18, + #[wrap(Some)] + set_buffer: cmdbuf = >k::TextBuffer { + set_text: get_set_vr_pow_prof_cmd().as_str(), + } + } + }, + gtk::Button { + add_css_class: "flat", + add_css_class: "circular", + set_tooltip_text: Some("Copy"), + set_icon_name: "edit-copy-symbolic", + set_vexpand: false, + set_valign: gtk::Align::Center, + connect_clicked => move |_| { + gtk::gdk::Display::default() + .expect("Could not find default display") + .clipboard() + .set_text(get_set_vr_pow_prof_cmd().as_str()); + } + }, + }, + }, model.steam_launch_options_box.widget(), model.install_wivrn_box.widget(), gtk::Box {