Skip to content
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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

Carterpersall
Copy link

@Carterpersall Carterpersall commented Mar 20, 2023

Hello, I'm a contributor to https://github.com/lptstr/winfetch (you've talked to the maintainer in #112), and would like to start applying some of the experience I gained there to this project.

  • Uses a combination of my implementation in Fix Resolution Bug lptstr/winfetch#156 and this one
    • SetProcessDpiAwareness tells the OS to give the program the "real" resolution of each monitor instead of the scaled resolution
    • Uses EnumDisplayMonitors to get the information of each monitor
      • Uses a callback function to get the information on each monitor using GetMonitorInfoW and store the result in a vector

TODO:

  • [ ] Implement my method in Fix Resolution Bug lptstr/winfetch#156
    • Only works in admin instances of PowerShell, but I need to test if it's the same for Rust
      • It is the same in Rust, and it seems that the crate WinReg does not anticipate a registry key requiring special permissions as it is not working properly
    • Probably more effort than it's worth

@Carterpersall Carterpersall marked this pull request as draft March 20, 2023 18:37
@CarterLi
Copy link

Why EnumDisplayMonitors instead of EnumDisplayDevicesW + EnumDisplaySettingsW (ENUM_CURRENT_SETTINGS)?

  1. EnumDisplayDevicesW is not affected by DpiAwareness, so no SetProcessDpiAwareness needed.
  2. EnumDisplayDevicesW doesn't use callback, so it is easier to use
  3. EnumDisplayDevicesW provides refresh rate

While EnumDisplayMonitors is still useful to fetch scaled resolutions. with SetProcessDpiAwareness(PROCESS_DPI_UNAWARE), if needed

@Carterpersall
Copy link
Author

Why EnumDisplayMonitors instead of EnumDisplayDevicesW + EnumDisplaySettingsW (ENUM_CURRENT_SETTINGS)?

Because I didn't know it existed. The Win32 API is such a chaotic mess of useful functions that something always seems to slip by.
I'll see if I can throw together another implementation using those functions.

@CarterLi
Copy link

CarterLi commented Mar 21, 2023

This is an example that fetchs both pixel resolutions, scaled resolutions and refresh rates.

https://github.com/LinusDierheimer/fastfetch/blob/b3da6b0e89c0decb9ea648e1d98a75fa6ac40225/src/detection/displayserver/displayserver_windows.c#L48

@CarterLi
Copy link

There are other interesting things. For example: fetching OS caption without querying WMI: https://github.com/LinusDierheimer/fastfetch/blob/b3da6b0e89c0decb9ea648e1d98a75fa6ac40225/src/detection/os/os_windows.cpp#L34

@Carterpersall
Copy link
Author

This is an example that fetchs both pixel resolutions, scaled resolutions and refresh rates.

LinusDierheimer/fastfetch@b3da6b0/src/detection/displayserver/displayserver_windows.c#L48

I got an implementation working, thanks for the tip though

There are other interesting things. For example: fetching OS caption without querying WMI: LinusDierheimer/fastfetch@b3da6b0/src/detection/os/os_windows.cpp#L34

Yeah, there are a lot of things that most programs default to calling WMI for that can be found by just using the registry. That's one thing I intend on working on

While working on the new implementation, I discovered that EnumDisplayDevicesW also returns the system's display adapters, giving yet another way to retrieve the system's GPUs.

@Carterpersall
Copy link
Author

Implementation Speeds:

  • Command used: hyperfine --warmup 20 --prepare "cmd" --min-runs 500 ".\macchina.exe --show resolution"
    • Compiled in release mode

Plugged in:

  • EnumDisplayDevicesW
    • 14.5 ms
  • EnumDisplayMonitors
    • 14.1 ms
  • WMI
    • 203 ms

On Battery life:

  • EnumDisplayDevicesW
    • 15.2 ms
  • EnumDisplayMonitors
    • 13.5 ms
  • WMI
    • 227.2 ms

When heavily throttled (used power plans to downclock CPU to 1.06 GHz to simulate a slow computer)

  • EnumDisplayDevicesW
    • 49.2 ms
  • EnumDisplayMonitors
    • 47.3 ms
  • WMI
    • 697.3 ms

@Carterpersall Carterpersall marked this pull request as ready for review April 13, 2023 21:13
@CarterLi
Copy link

CarterLi commented Apr 14, 2023

Besides being slow, WMI Win32_VideoController reports one resolution per GPU adapter only. I see no benefits use WMI here.

Only gets one monitor per adapter
@Carterpersall
Copy link
Author

Besides being slow, WMI Win32_VideoController reports one resolution per GPU adapter only. I see no benefits use WMI here.

Yeah, I probably should've checked for that before adding it. I've removed the WMI implementation in the most recent commit

@grtcdr grtcdr requested a review from uttarayan21 April 21, 2023 13:46
@uttarayan21
Copy link
Member

@grtcdr I don't think I'm qualified to review this. I don't have enough experience with windows to comment on this.
However I can help with the testing of this.

@grtcdr
Copy link
Member

grtcdr commented Apr 24, 2023

Neither am I, that's okay. I'd appreciate it if someone else (@CarterLi, perhaps) gave a final review before merging.

@CarterLi
Copy link

Sorry, but I'm not familiar with rust.

This is what I got on my MBP

image

  1. macchina should use the same format on different platforms, including on Windows. That is: pixelHxpixelV@refreshRatefps (as scaledHxscaledV). However the code seems to use another format
  2. I still see no benefits adding EnumDisplayMonitors as a backup, as EnumDisplaySettingsW usually won't fail (it's supported in Windows 2000+), but adding backups generally complicates the code.
  3. The code seems verbose. I don't think it's necessary to push everything into a vector

@Carterpersall
Copy link
Author

1. macchina should use the same format on different platforms, including on Windows. That is: `pixelH`x`pixelV`@`refreshRate`fps (as `scaledH`x`scaledV`). However the code [seems to use another format](https://github.com/Macchina-CLI/libmacchina/pull/147/files#diff-3b7ba22f3645054b5b1c07651b4c378e01c0f8eda295e24e4bedf4e6e3a79382R232)

Yeah, I probably should've checked the current formatting when implementing it. I've changed the formatting of the output to match the rest of the program.

2. I still see no benefits adding `EnumDisplayMonitors` as a backup, as `EnumDisplaySettingsW` usually won't fail (it's supported in Windows 2000+), but adding backups generally complicates the code.

I can remove the second implementation if you wish, but I don't think it complicates the code much due to the clear separator between the implementations.

3. The code seems verbose. [I don't think it's necessary to push everything into a vector](https://github.com/Macchina-CLI/libmacchina/pull/147/files#diff-3b7ba22f3645054b5b1c07651b4c378e01c0f8eda295e24e4bedf4e6e3a79382R181)

A vector has to be used in the selected location due to the need for a while statement instead of an iterator. I was able to use an iterator instead of a vector for the resolution variable, but I found that the implementation caused a significant decrease in readability with no tangible speed improvement.

@CarterLi
Copy link

I can remove the second implementation if you wish, but I don't think it complicates the code much due to the clear separator between the implementations.

It's useless. That's all.

A vector has to be used in the selected location due to the need for a while statement instead of an iterator. I was able to use an iterator instead of a vector for the resolution variable, but I found that the implementation caused a significant decrease in readability with no tangible speed improvement.

It has nothing to do with vectors or iterators. You call EnumDisplayDevicesW. You get a DISPLAY_DEVICEW instance. You pass DISPLAY_DEVICEW::DeviceName into EnumDisplaySettingsW. You are done.

https://github.com/CarterLi/fastfetch/blob/e5f851dcbb94de35c34fb8c5e3dd8300bb56a1cc/src/detection/displayserver/displayserver_windows.c#L49

return Ok(resolutions
.iter()
.map(
|resolution| match (resolution.dmDisplayFrequency, resolution.dmLogPixels) {
Copy link

@CarterLi CarterLi Apr 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try updating the scale factor of your monitors? For me, dmLogPixels won't be updated before I restarting my laptap.

In addition, according to MSDN, EnumDisplaySettings sets only these 5 DEMMODE members.

image

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you test multiple monitors?

Copy link
Author

@Carterpersall Carterpersall Apr 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try updating the scale factor of your monitors? For me, dmLogPixels won't be updated before I restarting my laptap.

In addition, according to MSDN, EnumDisplaySettings sets only these 5 DEMMODE members.

image

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:

  1. Only show the scaled resolution of the primary monitor and have the bug where it doesn't update as a known issue
  2. Don't show a scaled resolution
  3. Do what Fastfetch does and merge the two implementations into a single one that uses EnumDisplaySettingsW to get the resolution and refresh rate and uses EnumDisplayMonitors without SetProcessDpiAwareness to get the scaled resolution. This would likely be about twice as slow, but would add a maximum of a few milliseconds.

I'm going to try implementing option 3 and see how much slower it is.

Did you test multiple monitors?

When a monitor is added or removed, the output correctly updates

Choose a reason for hiding this comment

The 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.

|resolution| match (resolution.dmDisplayFrequency, resolution.dmLogPixels) {
(0, 0) => format!("{}x{}", resolution.dmPelsWidth, resolution.dmPelsHeight),
(0, _) => format!(
"{}x{} (as {}x{})",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the resolution is not scaled. Don't print (as {}x{})

PCWSTR::null(),
index as u32,
&mut devices[index],
EDD_GET_DEVICE_INTERFACE_NAME,

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 set EDD_GET_DEVICE_INTERFACE_NAME

@Carterpersall
Copy link
Author

It has nothing to do with vectors or iterators. You call EnumDisplayDevicesW. You get a DISPLAY_DEVICEW instance. You pass DISPLAY_DEVICEW::DeviceName into EnumDisplaySettingsW. You are done.

CarterLi/fastfetch@e5f851d/src/detection/displayserver/displayserver_windows.c#L49

I see what you mean now, I have no idea why I didn't do it like that in the first place.

@Carterpersall
Copy link
Author

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.

I created an implementation of this and the EnumDisplayDevicesW + EnumDisplaySettingsW combo and it seems that the combo is consistently faster by about 0.5ms

If the resolution is not scaled. Don't print (as {}x{})

That also should be fixed now

@CarterLi
Copy link

CarterLi commented Apr 27, 2023

I created an implementation of this

No. Although fastfetch has switched to QueryDisplayConfig, you don't have to. Just keep using EnumDisplayDevicesW and GetDeviceCaps.

DISPLAY_DEVICEW displayDevice;

//...

HDC hdc = CreateICW(displayDevice.DeviceName, NULL, NULL, NULL);
int width = GetDeviceCaps(hdc, HORZRES);
int height = GetDeviceCaps(hdc, VERTRES);
DeleteDC(hdc);

fastfetch has switched to QueryDisplayConfig because QueryDisplayConfig provides decimal refresh rates which is used by some monitors. In addition, QueryDisplayConfig provides monitor name, which can be used to match the information of fastfetch's brightness module (like the backlight readout in macchina).

image

If you don't need these features, you don't need QueryDisplayConfig

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants