From 08a1ed9aa6e9c0fa61cfba2511fed9b4251bde9b Mon Sep 17 00:00:00 2001 From: "Frederik P." Date: Sat, 7 Sep 2024 15:00:33 +0200 Subject: [PATCH] implemented changing display refresh rate --- Cargo.toml | 1 + TODO.md | 2 - shared/src/logging.rs | 8 +-- src/config/mod.rs | 8 ++- src/config/state_config.rs | 13 ++-- src/display/mod.rs | 113 +++++++++++++++++++++++++++++++ src/display/refresh_rate_mode.rs | 8 +++ src/main.rs | 6 +- src/services/power_service.rs | 7 +- 9 files changed, 146 insertions(+), 20 deletions(-) create mode 100644 src/display/mod.rs create mode 100644 src/display/refresh_rate_mode.rs diff --git a/Cargo.toml b/Cargo.toml index aae38b1..075921b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ windows = { workspace = true, features = [ "Win32_System_Services", "Win32_System_Threading", "Win32_UI_WindowsAndMessaging", + "Win32_Graphics_Gdi", ] } serde = { workspace = true } serde_json = "^1.0" diff --git a/TODO.md b/TODO.md index 4b84a79..4640904 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1 @@ # TODO - -- Implement "profiles" with different options. Such as; power mode, refresh rate and possibly others. Point is that you can have lots more change when the laptop switches from AC to battery than just the power mode. diff --git a/shared/src/logging.rs b/shared/src/logging.rs index 067dd04..1db5832 100644 --- a/shared/src/logging.rs +++ b/shared/src/logging.rs @@ -21,10 +21,10 @@ pub struct Logger { } impl Logger { - pub const fn new(source_name: &'static str, process_name: &'static str) -> Self { + pub const fn new(source_name: &'static str, group_name: &'static str) -> Self { Self { source_name, - process_name, + process_name: group_name, log_path: OnceCell::new(), } } @@ -39,9 +39,7 @@ impl Logger { } #[cfg(not(debug_assertions))] - pub fn debug(&self, input: A) { - drop(input) - } + pub fn debug(&self, _input: A) {} pub fn log(&self, input: A, level: LogLevel) { let log_path = self.log_path.get_or_init(|| { diff --git a/src/config/mod.rs b/src/config/mod.rs index c94a1f2..3ada751 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -5,7 +5,7 @@ use autopower_shared::logging::Logger; pub use config_error::ConfigError; use state_config::StateConfig; -use crate::power_scheme::PowerScheme; +use crate::{display::RefreshRateMode, power_scheme::PowerScheme}; use serde::{Deserialize, Serialize}; use std::{ fs::File, @@ -25,11 +25,13 @@ impl Default for PowerConfig { Self { wired_config: StateConfig { power_scheme: PowerScheme::HighPerformance, - screen_refresh_rate: "max".to_owned(), + change_refresh_rate: true, + screen_refresh_rate: RefreshRateMode::Max, }, battery_config: StateConfig { power_scheme: PowerScheme::Balanced, - screen_refresh_rate: "60".to_owned(), + change_refresh_rate: true, + screen_refresh_rate: RefreshRateMode::Value(60), }, } } diff --git a/src/config/state_config.rs b/src/config/state_config.rs index 3024c5a..f3833ea 100644 --- a/src/config/state_config.rs +++ b/src/config/state_config.rs @@ -1,10 +1,11 @@ -use crate::power_scheme::PowerScheme; +use crate::{display::RefreshRateMode, power_scheme::PowerScheme}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] pub struct StateConfig { pub(super) power_scheme: PowerScheme, - pub(super) screen_refresh_rate: String, // Use string instead of number so we can specify things like "max" or "min" + pub(super) change_refresh_rate: bool, + pub(super) screen_refresh_rate: RefreshRateMode, } impl StateConfig { @@ -12,7 +13,11 @@ impl StateConfig { self.power_scheme } - pub fn get_refresh_rate(&self) -> &str { - &self.screen_refresh_rate + pub fn should_change_refresh_rate(&self) -> bool { + self.change_refresh_rate + } + + pub fn get_refresh_rate(&self) -> RefreshRateMode { + self.screen_refresh_rate } } diff --git a/src/display/mod.rs b/src/display/mod.rs new file mode 100644 index 0000000..70c1f4b --- /dev/null +++ b/src/display/mod.rs @@ -0,0 +1,113 @@ +mod refresh_rate_mode; + +pub use refresh_rate_mode::RefreshRateMode; + +use crate::Result; +use autopower_shared::logging::Logger; +use windows::Win32::Graphics::Gdi::{ + ChangeDisplaySettingsW, EnumDisplaySettingsW, CDS_TYPE, DEVMODEW, DISP_CHANGE_BADDUALVIEW, + DISP_CHANGE_BADFLAGS, DISP_CHANGE_BADMODE, DISP_CHANGE_BADPARAM, DISP_CHANGE_FAILED, + DISP_CHANGE_NOTUPDATED, DISP_CHANGE_RESTART, DISP_CHANGE_SUCCESSFUL, ENUM_CURRENT_SETTINGS, + ENUM_DISPLAY_SETTINGS_MODE, +}; + +const LOGGER: Logger = Logger::new("display", "autopower"); + +fn get_current_display_mode() -> Result { + LOGGER.debug("Getting current display mode..."); + let mut devmode = DEVMODEW::default(); + devmode.dmSize = size_of::() as u16; + unsafe { + EnumDisplaySettingsW(None, ENUM_CURRENT_SETTINGS, &mut devmode).ok()?; + } + Ok(devmode) +} + +fn get_display_modes_with_current_res_color() -> Result<(Vec, DEVMODEW)> { + LOGGER.debug("Getting all display modes with current resolution and color..."); + let current_mode = get_current_display_mode()?; + let mut devmode = DEVMODEW::default(); + devmode.dmSize = size_of::() as u16; + let mut buf = vec![]; + for i in 0.. { + unsafe { + if !EnumDisplaySettingsW(None, ENUM_DISPLAY_SETTINGS_MODE(i), &mut devmode).as_bool() { + break; + } + } + if devmode.dmBitsPerPel != current_mode.dmBitsPerPel + || devmode.dmPelsHeight != current_mode.dmPelsHeight + || devmode.dmPelsWidth != current_mode.dmPelsWidth + { + continue; + } + buf.push(devmode); + } + LOGGER.debug("Getting all display modes with current resolution and color..."); + Ok((buf, current_mode)) +} + +fn get_closest_match_display_mode(mode: RefreshRateMode) -> Result { + LOGGER.debug(format!( + "Getting closest match display mode with specified refresh rate: {:?}...", + mode + )); + let (refresh_rate_modes, current_mode) = get_display_modes_with_current_res_color()?; + match mode { + RefreshRateMode::Max => { + let mut max = current_mode; + for elem in &refresh_rate_modes { + let elem_refresh = elem.dmDisplayFrequency; + if elem_refresh > max.dmDisplayFrequency { + max = *elem; + } + } + Ok(max) + } + RefreshRateMode::Value(val) => { + let mut closest_match = current_mode; + let mut closest_match_dist = 1000; + for elem in &refresh_rate_modes { + let elem_refresh = elem.dmDisplayFrequency; + let dist = val.abs_diff(elem_refresh); + if dist < closest_match_dist { + closest_match = *elem; + closest_match_dist = dist; + } + } + Ok(closest_match) + } + RefreshRateMode::Min => { + let mut min = current_mode; + for elem in &refresh_rate_modes { + let elem_refresh = elem.dmDisplayFrequency; + if elem_refresh < min.dmDisplayFrequency { + min = *elem; + } + } + Ok(min) + } + } +} + +pub fn set_display_refresh_rate(mode: RefreshRateMode) -> Result<()> { + let new_mode = get_closest_match_display_mode(mode)?; + unsafe { + let flags = ChangeDisplaySettingsW(Some(&new_mode), CDS_TYPE(0)); + if flags != DISP_CHANGE_SUCCESSFUL { + let msg = match flags { + DISP_CHANGE_BADDUALVIEW => "Could not change display settings! (BADDUALVIEW)", + DISP_CHANGE_BADFLAGS => "Could not change display settings! (BADFLAGS)", + DISP_CHANGE_BADMODE => "Could not change display settings! (BADMODE)", + DISP_CHANGE_BADPARAM => "Could not change display settings! (BADPARAM)", + DISP_CHANGE_FAILED => "Could not change display settings! (FAILED)", + DISP_CHANGE_NOTUPDATED => "Could not change display settings! (NOTUPDATED)", + DISP_CHANGE_RESTART => "Could not change display settings! (RESTART)", + _ => "Could not change display settings! (unknown code)", + }; + LOGGER.error(msg); + return Err(msg.into()); + } + } + Ok(()) +} diff --git a/src/display/refresh_rate_mode.rs b/src/display/refresh_rate_mode.rs new file mode 100644 index 0000000..91c811f --- /dev/null +++ b/src/display/refresh_rate_mode.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +pub enum RefreshRateMode { + Max, + Value(u32), + Min, +} diff --git a/src/main.rs b/src/main.rs index 249eab9..65aac6a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use autopower_shared::logging::Logger; mod config; +mod display; mod handler_data; mod notification_provider; mod power_scheme; @@ -21,8 +22,5 @@ fn main() -> Result<()> { return Ok(()); } - if let Err(e) = services::start::() { - LOGGER.error(format!("Fatal error!\n {}", e)) - } - Ok(()) + services::start::() } diff --git a/src/services/power_service.rs b/src/services/power_service.rs index e79bd27..13f6282 100644 --- a/src/services/power_service.rs +++ b/src/services/power_service.rs @@ -1,7 +1,7 @@ use super::WindowsService; use crate::{ - config::PowerConfig, handler_data::HandlerData, notification_provider::NotificationProvider, - power_scheme::set_power_scheme, + config::PowerConfig, display::set_display_refresh_rate, handler_data::HandlerData, + notification_provider::NotificationProvider, power_scheme::set_power_scheme, }; use autopower_shared::{logging::Logger, winstr::to_win32_wstr}; use std::{ffi::c_void, mem::ManuallyDrop}; @@ -82,6 +82,9 @@ impl PowerService { wired_config.get_power_scheme(), self.notification_provider.as_mut().unwrap(), )?; + if wired_config.should_change_refresh_rate() { + set_display_refresh_rate(wired_config.get_refresh_rate())?; + } Ok(()) }