Skip to content

Commit

Permalink
implemented changing display refresh rate
Browse files Browse the repository at this point in the history
  • Loading branch information
F0903 committed Sep 7, 2024
1 parent 783d219 commit 08a1ed9
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 20 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 0 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 3 additions & 5 deletions shared/src/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
Expand All @@ -39,9 +39,7 @@ impl Logger {
}

#[cfg(not(debug_assertions))]
pub fn debug<A: Display>(&self, input: A) {
drop(input)
}
pub fn debug<A: Display>(&self, _input: A) {}

pub fn log<A: Display>(&self, input: A, level: LogLevel) {
let log_path = self.log_path.get_or_init(|| {
Expand Down
8 changes: 5 additions & 3 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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),
},
}
}
Expand Down
13 changes: 9 additions & 4 deletions src/config/state_config.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
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 {
pub fn get_power_scheme(&self) -> PowerScheme {
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
}
}
113 changes: 113 additions & 0 deletions src/display/mod.rs
Original file line number Diff line number Diff line change
@@ -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<DEVMODEW> {
LOGGER.debug("Getting current display mode...");
let mut devmode = DEVMODEW::default();
devmode.dmSize = size_of::<DEVMODEW>() as u16;
unsafe {
EnumDisplaySettingsW(None, ENUM_CURRENT_SETTINGS, &mut devmode).ok()?;
}
Ok(devmode)
}

fn get_display_modes_with_current_res_color() -> Result<(Vec<DEVMODEW>, 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::<DEVMODEW>() 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<DEVMODEW> {
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(())
}
8 changes: 8 additions & 0 deletions src/display/refresh_rate_mode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
pub enum RefreshRateMode {
Max,
Value(u32),
Min,
}
6 changes: 2 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use autopower_shared::logging::Logger;

mod config;
mod display;
mod handler_data;
mod notification_provider;
mod power_scheme;
Expand All @@ -21,8 +22,5 @@ fn main() -> Result<()> {
return Ok(());
}

if let Err(e) = services::start::<services::PowerService>() {
LOGGER.error(format!("Fatal error!\n {}", e))
}
Ok(())
services::start::<services::PowerService>()
}
7 changes: 5 additions & 2 deletions src/services/power_service.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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(())
}

Expand Down

0 comments on commit 08a1ed9

Please sign in to comment.