-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Windows: Implement resolution
#147
base: main
Are you sure you want to change the base?
Changes from 8 commits
1e25da9
0cbd78b
a4da83e
852cbed
92923f8
e0c9f2a
fa09675
d1ba5b2
1172e4d
3e85155
39fe676
28fead9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,13 +7,21 @@ use wmi::WMIResult; | |
use wmi::{COMLibrary, Variant, WMIConnection}; | ||
|
||
use windows::{ | ||
core::PSTR, Win32::System::Power::GetSystemPowerStatus, | ||
core::{PCWSTR, PSTR}, | ||
Win32::Foundation::{BOOL, LPARAM, RECT}, | ||
Win32::Graphics::Gdi::{ | ||
EnumDisplayDevicesW, EnumDisplayMonitors, EnumDisplaySettingsW, GetMonitorInfoW, DEVMODEW, | ||
DISPLAY_DEVICEW, ENUM_CURRENT_SETTINGS, HDC, HMONITOR, MONITORINFO, | ||
}, | ||
Win32::System::Power::GetSystemPowerStatus, | ||
Win32::System::Power::SYSTEM_POWER_STATUS, | ||
Win32::System::SystemInformation::GetComputerNameExA, | ||
Win32::System::SystemInformation::GetTickCount64, | ||
Win32::System::SystemInformation::GlobalMemoryStatusEx, | ||
Win32::System::SystemInformation::MEMORYSTATUSEX, | ||
Win32::System::WindowsProgramming::GetUserNameA, | ||
Win32::UI::HiDpi::{SetProcessDpiAwareness, PROCESS_SYSTEM_DPI_AWARE}, | ||
Win32::UI::WindowsAndMessaging::EDD_GET_DEVICE_INTERFACE_NAME, | ||
}; | ||
|
||
impl From<wmi::WMIError> for ReadoutError { | ||
|
@@ -170,7 +178,164 @@ impl GeneralReadout for WindowsGeneralReadout { | |
} | ||
|
||
fn resolution(&self) -> Result<String, ReadoutError> { | ||
Err(ReadoutError::NotImplemented) | ||
let mut devices = Vec::new(); | ||
let mut index = 0; | ||
let mut status = true; | ||
// Iterate over EnumDisplayDevicesW until it returns false | ||
while status { | ||
devices.push(DISPLAY_DEVICEW::default()); | ||
devices[index].cb = std::mem::size_of::<DISPLAY_DEVICEW>() as u32; | ||
status = unsafe { | ||
EnumDisplayDevicesW( | ||
PCWSTR::null(), | ||
index as u32, | ||
&mut devices[index], | ||
EDD_GET_DEVICE_INTERFACE_NAME, | ||
) | ||
.as_bool() | ||
}; | ||
index += 1; | ||
} | ||
// Remove the last element, which will be invalid | ||
devices.pop(); | ||
|
||
// Create a vector to store each monitor's information | ||
let mut resolutions = Vec::new(); | ||
|
||
// Iterate over each device | ||
let mut index = 0; | ||
for device in devices { | ||
resolutions.push(DEVMODEW::default()); | ||
resolutions[index].dmSize = std::mem::size_of::<DEVMODEW>() as u16; | ||
|
||
// Get the current display settings for the device | ||
unsafe { | ||
EnumDisplaySettingsW( | ||
PCWSTR(device.DeviceName.as_ptr()), | ||
ENUM_CURRENT_SETTINGS, | ||
&mut resolutions[index], | ||
); | ||
} | ||
|
||
// Ensure that the resolution is valid | ||
if resolutions[index].dmPelsWidth != 0 && resolutions[index].dmPelsHeight != 0 { | ||
index += 1; | ||
} else { | ||
resolutions.pop(); | ||
} | ||
} | ||
|
||
if !resolutions.is_empty() { | ||
// Format and return the display resolutions and refresh rates | ||
return Ok(resolutions | ||
.iter() | ||
.map( | ||
|resolution| match (resolution.dmDisplayFrequency, resolution.dmLogPixels) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you test multiple monitors? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I didn't before now, and I can confirm that the issue exists for me as well. Along with that, I found that the current implementation doesn't properly get the scale for a second monitor even after a restart. I see a few options moving forward:
I'm going to try implementing option 3 and see how much slower it is.
When a monitor is added or removed, the output correctly updates There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is actually another method by using HDC and GetDeviceCaps. CarterLi/fastfetch@4d7cb25#diff-1f993416a36137ee7af25d94be4928701d89ca08d9ab322fcb3eb0b5ec6d80b6R39 I dont know if it is faster than EnumDisplayMonitors. The code is much simplier at least. |
||
(0, 0) => format!("{}x{}", resolution.dmPelsWidth, resolution.dmPelsHeight), | ||
(0, _) => format!( | ||
"{}x{} (as {}x{})", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the resolution is not scaled. Don't print |
||
resolution.dmPelsWidth, | ||
resolution.dmPelsHeight, | ||
resolution.dmPelsWidth as f32 / (resolution.dmLogPixels as f32 / 96.0), | ||
resolution.dmPelsHeight as f32 / (resolution.dmLogPixels as f32 / 96.0) | ||
), | ||
(_, 0) => format!( | ||
"{}x{}@{}Hz", | ||
resolution.dmPelsWidth, | ||
resolution.dmPelsHeight, | ||
resolution.dmDisplayFrequency | ||
), | ||
(_, _) => format!( | ||
"{}x{}@{}Hz (as {}x{})", | ||
resolution.dmPelsWidth, | ||
resolution.dmPelsHeight, | ||
resolution.dmDisplayFrequency, | ||
resolution.dmPelsWidth as f32 / (resolution.dmLogPixels as f32 / 96.0), | ||
resolution.dmPelsHeight as f32 / (resolution.dmLogPixels as f32 / 96.0) | ||
), | ||
}, | ||
) | ||
.collect::<Vec<String>>() | ||
.join(", ")); | ||
} | ||
|
||
// Backup Implementation 1 | ||
// Sources: | ||
// https://github.com/lptstr/winfetch/pull/156/ | ||
// https://patriksvensson.se/posts/2020/06/enumerating-monitors-in-rust-using-win32-api | ||
|
||
// Create callback function for EnumDisplayMonitors to use | ||
#[allow(non_snake_case, unused_variables)] | ||
extern "system" fn EnumProc( | ||
hMonitor: HMONITOR, | ||
hdcMonitor: HDC, | ||
lprcMonitor: *mut RECT, | ||
dwData: LPARAM, | ||
) -> BOOL { | ||
unsafe { | ||
// Get the userdata where we will store the result | ||
let monitors: &mut Vec<MONITORINFO> = std::mem::transmute(dwData); | ||
|
||
// Initialize the MONITORINFO structure and get a pointer to it | ||
let mut monitor_info: MONITORINFO = std::mem::zeroed(); | ||
monitor_info.cbSize = std::mem::size_of::<MONITORINFO>() as u32; | ||
let monitor_info_ptr = <*mut _>::cast(&mut monitor_info); | ||
|
||
// Call the GetMonitorInfoW Win32 API | ||
let result = GetMonitorInfoW(hMonitor, monitor_info_ptr); | ||
if result.as_bool() { | ||
// Push the information we received to the vector | ||
monitors.push(monitor_info); | ||
} | ||
} | ||
|
||
true.into() | ||
} | ||
|
||
// Set DPI awareness to ensure we get the correct resolution | ||
match unsafe { SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE) } { | ||
Ok(_) => (), | ||
Err(_) => { | ||
return Err(ReadoutError::Other(String::from( | ||
"Failed to set DPI awareness.", | ||
))) | ||
} | ||
} | ||
|
||
// Create vector to store the resulting monitors in | ||
let mut monitors = Vec::<MONITORINFO>::new(); | ||
let userdata = &mut monitors as *mut _; | ||
|
||
// Call the EnumDisplayMonitors win32 API to get each monitor's information | ||
let result = unsafe { | ||
EnumDisplayMonitors( | ||
HDC(0), | ||
std::ptr::null(), | ||
Some(EnumProc), | ||
LPARAM(userdata as isize), | ||
) | ||
}; | ||
|
||
if result.as_bool() { | ||
// Create a vector of strings containing the resolution of each monitor | ||
let monitors_info: Vec<String> = monitors | ||
.iter() | ||
.map(|monitor| { | ||
format!( | ||
"{}x{}", | ||
monitor.rcMonitor.right - monitor.rcMonitor.left, | ||
monitor.rcMonitor.bottom - monitor.rcMonitor.top | ||
) | ||
}) | ||
.collect(); | ||
|
||
return Ok(monitors_info.join(", ")); | ||
} | ||
|
||
// If every implementation failed | ||
Err(ReadoutError::Other( | ||
"Failed to get display information".to_string(), | ||
)) | ||
} | ||
|
||
fn username(&self) -> Result<String, ReadoutError> { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't use
DISPLAY_DEVICE::DeviceID
, don't setEDD_GET_DEVICE_INTERFACE_NAME