Get Display Scale

Started by MrLeadFoot, March 05, 2025, 10:09:10 PM

Previous topic - Next topic

MrLeadFoot

Is there a way to get the display scale factor such as 100%, 125%, 150%, etc. using WinBatch? I don't need to manipluate it, I just want to be able to know what it is so I can size and positioning windows appropriately for different scale factors.

Thanks in advance.

kdmoyers

This is not really what you asked, but,
I imagine some experimenting with the WinMetrics function might allow you to infer the setting.
Like, under different scales, does the value returned by WinMetrics(-6) change?
The mind is everything; What you think, you become.

spl

Possibly could use WMI....

objWMIService.ExecQuery("SELECT * FROM Win32_DesktopMonitor",,48) get screenheight/screenwidth properties and do the math.

or Win32_VideoController class and VideoModeDescription property.

Stan - formerly stanl [ex-Pundit]

JTaylor

If you can't find a different answer, here is my usual response :-)

http://www.jtdata.com/anonymous/wbOmnibus.zip

Look at snScreenScale() in the Help.  Can't recall why I added it.  If it was just convenient of there was not a good way via WinBatch.

Here is the internal code in case that prompt a direct WinBatch solution.  You can see I made an assumption with the 96 DPI default.  That covers most situations.  High-end monitors might be different.  If you run into that situation let me know.  I probably should tweak this function anyway to allow one to submit the DPI and default to 96.  It is on my list.

LONG CDOM::snScreenScale(HWND hWnd, HINSTANCE hInst, LPLONG lpLong, LPVIPERVAR lpvvArg, LONG nArgCount)
{

    HWND ControlHandle = reinterpret_cast<HWND>(lpvvArg
  • .x);
    //int format = lpvvArg[1].x;
    HDC hdc = GetDC(ControlHandle);
    double lx = GetDeviceCaps(hdc, LOGPIXELSX);
    lx = lx / 96.00;
    return ReturnResult(lx);
}

Jim


td

This topic has been covered before
https://forum.winbatch.com/index.php?msg=16302

A repost of the example in the link:

;; Note: If WinBatch was not manifested to be DPI aware
;; this would not work.  But it is so it does.

hDC = DllCall("User32.dll", long_ptr:"GetDC", lpnull)

LOGPIXELSX = 88
nPixPerInchX = DllCall("Gdi32.dll", long:"GetDeviceCaps", long:hDC, long:LOGPIXELSX)

;; 96 is the default Windows scaling.
nScallingPercent = (nPixPerInchX/96.0) * 100
x =  DllCall("User32.dll", long:"ReleaseDC", lpnull, long:hDC)
Message("Desktop Scalling",  nScallingPercent:"%%")

;; This would require Windows 10 - 1604 or later
;;nPixPerInchX10 = DllCall("User32.dll", long:"GetDpiForWindow", long:DllHwnd(""))

exit

"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

JTaylor

Sounds like it is covered but for what it is worth, I just posted an update which allows you to specify default DPI.

Jim

kdmoyers

handy, thanks Tony!!

interesting behavior of the GetDpiForWindow call:
It seems to not really look at which monitor the specified window is displayed on.  I have two displays, primary one at 100% the other at 125%, and it doesn't matter where the window is located, the answer is always 100%.  So watch out for that if you are running two displays.
The mind is everything; What you think, you become.

td

Quote from: kdmoyers on March 06, 2025, 08:07:29 AMhandy, thanks Tony!!

interesting behavior of the GetDpiForWindow call:
It seems to not really look at which monitor the specified window is displayed on.  I have two displays, primary one at 100% the other at 125%, and it doesn't matter where the window is located, the answer is always 100%.  So watch out for that if you are running two displays.


You see that result because the WinBatch exe is not manifested as being per-monitor DPI aware. If Wiindows is behaving itself, you should always get the primary monitor's DPI.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

td

What happens when you run the following on your two displays out of curiosity?

Major   = WinVersion(1)
BuildNo = WinVersion(2)

if Major >= 10 && BuildNo >= 1607
   PROCESS_DPI_UNAWARE = 0
   PROCESS_SYSTEM_DPI_AWARE = -1
   PROCESS_PER_MONITOR_DPI_AWARE = -2
   Context = DllCall("User32.dll", long:"SetThreadDpiAwarenessContext", long:PROCESS_PER_MONITOR_DPI_AWARE)
   nPixPerInchX10 = DllCall("User32.dll", long:"GetDpiForSystem")   ;, long:DllHwnd("")
   DllCall("User32.dll", long:"SetThreadDpiAwarenessContext", long:Context)
   nScallingPercent = (nPixPerInchX10/96.0)*100
   Message("Desktop Scalling",  nScallingPercent:"%%")
 endif
exit

[edit] It might be better to try it with "GetDpiForWindow" instead of "GetDpiForSystem".
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

kdmoyers

it says "Desktop scalling 100%" from either monitor.

(( to test, I move the Studio window to primary monitor, click run,
then move to Second monitor, click run.))

now trying with getdpi for window...
The mind is everything; What you think, you become.

kdmoyers

using GetDpiForWindow, same tests, same result.
The mind is everything; What you think, you become.

kdmoyers

Here's a thing: what if I compile the program? would that matter?

I put this code at top, to allow setting which monitor it was running on,

boxopen("screen scaling","move me with 5 seconds")
timedelay(5)

but it made no difference.  Still says 100% in all cases.
The mind is everything; What you think, you become.

kdmoyers

(( Clearly windows knows the situation, because as you drag the window from one monitor to the other, as it gets to halfway across, it resizes automatically.  Pretty cool windows feature. ))
The mind is everything; What you think, you become.

spl

Quote from: kdmoyers on March 06, 2025, 10:36:55 AM(( Clearly windows knows the situation, because as you drag the window from one monitor to the other, as it gets to halfway across, it resizes automatically.  Pretty cool windows feature. ))

Just curious; I no longer have multiple monitors, but with more than 1 would running a WMI query against

win32_videocontroller class and parsing caption, CurrentHorizontalResolution, CurrentVerticalResolution

get useful results? Something like
decimals(1)
strComputer = "."
oWMI = GetObject( "winmgmts:\\" : strComputer : "\root\cimv2")
WQL = "SELECT * FROM Win32_videocontroller"  
Monitors= oWMI.ExecQuery(WQL)
ForEach m in Monitors
      caption =  m.caption
      h = m.CurrentHorizontalResolution * 1.0
      v = m.CurrentVerticalResolution * 1.0
      scale = (h/v)*100:"%%"
      Display(5,"Monitor",caption:@LF:h:@LF:v:@LF:scale)
Next   
Monitors=0
oWMI=0
Exit
Stan - formerly stanl [ex-Pundit]

kdmoyers

No dice.  the Geep says "WMI's Win32_VideoController class focuses on the video adapter (e.g., resolution, refresh rate) and does not expose the per-monitor DPI scaling settings. Display scaling (the DPI setting) is managed at the operating system level and isn't available via that WMI class. Instead, you need to use native Windows API functions—such as GetDpiForMonitor from Shcore.dll—to retrieve the effective DPI and calculate the scaling (where 96 DPI equals 100% scaling)."

I tried a powershell script that enumerated the monitors, but it said they were both 100%.

I'm tossing in the towel. Like a lot of these screwy rabbit holes, I don't *really* care, I was just curious.  After all, if we can't take a minute to look under rocks, what are we here for?
The mind is everything; What you think, you become.

JTaylor

So the goal is to get scaling for non-primary monitor?

td

Quote from: kdmoyers on March 06, 2025, 12:58:12 PMNo dice.  the Geep says "WMI's Win32_VideoController class focuses on the video adapter (e.g., resolution, refresh rate) and does not expose the per-monitor DPI scaling settings. Display scaling (the DPI setting) is managed at the operating system level and isn't available via that WMI class. Instead, you need to use native Windows API functions—such as GetDpiForMonitor from Shcore.dll—to retrieve the effective DPI and calculate the scaling (where 96 DPI equals 100% scaling)."

I tried a powershell script that enumerated the monitors, but it said they were both 100%.

I'm tossing in the towel. Like a lot of these screwy rabbit holes, I don't *really* care, I was just curious.  After all, if we can't take a minute to look under rocks, what are we here for?

Thanks for checking it out. The obvious solution would be to set WinBatch manifests multi-monitor DPI aware. The downside is that it took a lot of work to make WinBatch DPI aware and would take a lot more to make all the graphical elements work in that environment. This is particularly true when, as you pointed out, the OS can do it for you in many cases.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

spl

Quote from: kdmoyers on March 06, 2025, 12:58:12 PMI tried a powershell script that enumerated the monitors, but it said they were both 100%.


Yeah. I tried this on win11. Scale is 100% while resolution came out 177.78% [both recommended].
$module = "DisplaySettings"
if (!(Get-Module -ListAvailable -Name $module)) {Install-Module -Name $module}
Import-Module -Name $module
$res = Get-DisplayResolution
$scale = ($res.dmpelswidth/$res.dmPelsHeight).tostring("P")
$scale
Stan - formerly stanl [ex-Pundit]

td

Scaling is not the same as resolution. Scaling is, in effect, changing the number of pixels per inch by changing the size of an inch. That is one reason why the Windows API documentation uses the term "logical" pixels.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

spl

Quote from: td on March 07, 2025, 08:55:20 AMScaling is not the same as resolution.

Of course. But I'm with Kirby that I don't really care or need to know but it is fun digging into the issue. Below PS executes C# code, suspect it normally returns 100 and don't see how it would iterate multiple monitors.
Add-Type @'
  using System;
  using System.Runtime.InteropServices;
  using System.Drawing;

  public class DPI {
    [DllImport("gdi32.dll")]
    static extern int GetDeviceCaps(IntPtr hdc, int nIndex);

    public enum DeviceCap {
      VERTRES = 10,
      DESKTOPVERTRES = 117
    }

    public static float scaling() {
      Graphics g = Graphics.FromHwnd(IntPtr.Zero);
      IntPtr desktop = g.GetHdc();
      int LogicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.VERTRES);
      int PhysicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.DESKTOPVERTRES);

      return (float)PhysicalScreenHeight / (float)LogicalScreenHeight;
    }
  }
'@ -ReferencedAssemblies 'System.Drawing.dll'

[Math]::round([DPI]::scaling(), 2) * 100
Stan - formerly stanl [ex-Pundit]

td

"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

spl

Quote from: td on March 07, 2025, 01:04:39 PMIgnorance is bliss?

Nah.. more like forewarned is forearmed or putting tariffs on Canadian Geese.
Stan - formerly stanl [ex-Pundit]

MrLeadFoot

Quote from: td on March 06, 2025, 07:41:17 AMThis topic has been covered before
https://forum.winbatch.com/index.php?msg=16302

A repost of the example in the link:

;; Note: If WinBatch was not manifested to be DPI aware
;; this would not work.  But it is so it does.

hDC = DllCall("User32.dll", long_ptr:"GetDC", lpnull)

LOGPIXELSX = 88
nPixPerInchX = DllCall("Gdi32.dll", long:"GetDeviceCaps", long:hDC, long:LOGPIXELSX)

;; 96 is the default Windows scaling.
nScallingPercent = (nPixPerInchX/96.0) * 100
x =  DllCall("User32.dll", long:"ReleaseDC", lpnull, long:hDC)
Message("Desktop Scalling",  nScallingPercent:"%%")

;; This would require Windows 10 - 1604 or later
;;nPixPerInchX10 = DllCall("User32.dll", long:"GetDpiForWindow", long:DllHwnd(""))

exit


Thank you. May I ask, what version of WinBatch was this written for?

td

It should work on just about any version released over the last 10 to 15 years. The only thing that is newish is the long_ptr parameter type. If it is an issue, change the type to long.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

MrLeadFoot

Quote from: td on March 11, 2025, 01:22:14 PMIt should work on just about any version released over the last 10 to 15 years. The only thing that is newish is the long_ptr parameter type. If it is an issue, change the type to long.
Thank you, again.

I inadvertently left this line out, and it still works. What does this line do?
x =  DllCall("User32.dll", long:"ReleaseDC", lpnull, long:hDC)

JTaylor

It is releasing the Device Context acquired by this line

   hDC = DllCall("User32.dll", long_ptr:"GetDC", lpnull)

Jim