Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

Report problems with documented functionality
User avatar
Taurus
Posts: 94
Joined: 20 Jan 2015, 10:31

Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

09 Feb 2016, 10:49

Hi,

I got the following problem:

Code: Select all

SysGet, MonitorWorkArea_, MonitorWorkArea

MsgBox % A_ScreenDPI "-" MonitorWorkArea_Bottom "-" MonitorWorkArea_Right "
Result on Win 10 with 125% text-size (monitor config) is wrong (shows 96 dpi). It includes the dpi-sizing, so you can't use this for positioning own GUIs.

Solution for the ahk-Team:

This has to be done while compiling autohotkey in Visual Studio:

Open source\resources\AutoHotkey.exe.manifest > set dpiaware to "True/PM"

Solution for people who can't wait for an update:

Open your compiled script with resource hacker > Manifest > 1 > set dpiaware to "True/PM"

Final should be: <dpiAware>True/PM</dpiAware>


So have a nice day without high dpi monitor problems anymore! :)

Greetings


Sources:

Figure 2 of http://www.drdobbs.com/windows/coding-f ... 736?pgno=3
https://technet.microsoft.com/en-us/lib ... 28846.aspx

This is interesting to react on dpi-changes while running (but i don't need it, so i didn't used it)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx
:beard: Full Stack Developer > Dev for a better world | PHP for Web | AHK H for Local | with KISS (Keep IT Short and Simple) on Win 10 Pro (Version 2004) x64
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

09 Feb 2016, 22:32

Open source\resources\AutoHotkey.exe.manifest > set dpiaware to "True/PM"
What are you talking about? I'm pretty sure AutoHotkey.exe.manifest has had <dpiAware>true</dpiAware> since A_ScreenDPI was added.

I have just compiled a script and confirmed that it has <dpiAware>true</dpiAware>, although I have not re-tested A_ScreenDPI with 125% DPI yet.

Are you using any tools which modify or replace the manifest resource? Had you manually done so?
guest3456
Posts: 3463
Joined: 09 Oct 2013, 10:31

Re: Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

09 Feb 2016, 23:28

true/pm (per monitor) is different than true (system wide)

https://msdn.microsoft.com/en-us/librar ... s.85).aspx

i think this also affects external win32 api calls as well such as GetPixel

lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

10 Feb 2016, 02:03

Thank you for pointing out what should have been obvious; I was reading it as "true or pm", not literally, and now that you mention it I recall reading about the "true/pm" value somewhere.
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

10 Feb 2016, 03:34

A_ScreenDPI returns 120 on my Windows 10 system, and it is correct.

I also tested with a second screen at 168 (175%), and everything worked as expected. A_ScreenDPI returned the correct DPI of the primary screen; 168 or 120, depending on which screen was set as the primary.

AutoHotkey is not per-monitor DPI aware, so I'm not so sure that changing the value to true/pm is a good idea. Applications which are system DPI aware but not per-monitor DPI aware are automatically scaled when they are on a screen other than the primary one. Without this, AutoHotkey GUIs would scale according to the primary display and stay that size even if they are placed on a secondary display (which could be a vastly different size).

As far as I can tell, true vs true/pm should make no difference whatsoever on a system with only one display.
User avatar
Taurus
Posts: 94
Joined: 20 Jan 2015, 10:31

Re: Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

10 Feb 2016, 10:30

Interessting... it's not working on my system. Only with true/pm. I will test it with true/pm. If it doesn't make any problem, i would prefer this, because it doesn't break Win 8 and older.
:beard: Full Stack Developer > Dev for a better world | PHP for Web | AHK H for Local | with KISS (Keep IT Short and Simple) on Win 10 Pro (Version 2004) x64
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

10 Feb 2016, 18:20

i would prefer this, because it doesn't break Win 8 and older.
Right, but it does break per-monitor scaling on Win 8.1 and newer.
guest3456
Posts: 3463
Joined: 09 Oct 2013, 10:31

Re: Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

17 Feb 2016, 23:01

however, perhaps A_ScreenDPI should be renamed more accurately to A_SystemDPI

i think "Screen DPI" would be retrieved using GetDpiForMonitor()
https://msdn.microsoft.com/en-us/librar ... s.85).aspx

whereas i think GetDeviceCaps() returns the system dpi

lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

18 Feb 2016, 00:12

Maybe, but as I said, I believe it corresponds to the primary screen. That is consistent with A_ScreenWidth/Height.

If it can return something other than the DPI of the primary screen, I would like confirmation.
guest3456
Posts: 3463
Joined: 09 Oct 2013, 10:31

Re: Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

24 Sep 2019, 11:21

revisiting this thread
lexikos wrote:
10 Feb 2016, 03:34
AutoHotkey is not per-monitor DPI aware, so I'm not so sure that changing the value to true/pm is a good idea. Applications which are system DPI aware but not per-monitor DPI aware are automatically scaled when they are on a screen other than the primary one. Without this, AutoHotkey GUIs would scale according to the primary display and stay that size even if they are placed on a secondary display (which could be a vastly different size).
this is true in terms of AHK GUI's themselves. right now, with dpiaware=true, when i drag an AHK gui from my laptop at 125% to my external at 150%, Windows will scale up the AHK gui. if i change the manifest to true/pm, Windows will NOT scale up the gui, since it expects us to check the WM_DPICHANGED msg and rescale and redraw ourselves, like shown in this example:

https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows?redirectedfrom=MSDN#updating-existing-applications

HOWEVER..

there is a another side effect of marking the manifest as dpiaware=true/pm. that is, many win api calls return the coordinate values depending on the dpi awareness of the CALLER. that means, that with AHK being only dpiaware=true, it can sometimes get wrong results. it will get wrong results in this case: when an external monitor uses a different scaling/dpi than the primary monitor. the primary monitor defines the system dpi, which is what A_SCREENDPI reports. but if an external monitor has a different dpi/scaling, and you use AHK to query something like WinGetPos (GetWindowRect) or SysGet MonitorWorkArea, you get wrong scaled results for the size of windows and the work area of the external screen, which will be based on the system dpi, not based on the screen's individual dpi. if you compile and change the manifest to true/pm, then Windows returns correct results to those api calls.

lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

05 Oct 2019, 04:55

That does change things.

The scaling done by some of the APIs seems to depend on which monitor contained the script's last active window.

This appears to work for enabling per-monitor DPI awareness on Windows 10 (probably 1603 and later):

Code: Select all

DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr")
The return value can be passed back to the function to restore the previous context.

This function also allows DPI virtualization to be enabled for specific windows while being disabled for calls to system APIs:
  1. Create the window while DPI awareness is on "system" (-2, already set via the manifest).
  2. Set it to -3 or -4 after creating the window, or prior to calling any system APIs.
This is very unclear in the documentation, if not undocumented, although I suppose the existence of GetWindowDpiAwarenessContext should have been a clue.

On version 1703 and later, -4 (Per Monitor v2) can be used to enable scaling of dialogs, menus, tooltips and some other things. However, it also causes the non-client area (title bar) to scale, which ends up squashing the client area because the script isn't actually per-monitor DPI aware (doesn't respond to WM_DPICHANGED). This can be avoided by setting the context to -3 before creating the GUI, but -4 before creating any tooltips, menus or dialogs.

The DPI awareness temporarily reverts to the process default while you're moving one of the script's windows. Calling SetThreadDpiAwarenessContext immediately before calling WinGetPos, ToolTip, etc. solves the problem. Setting the process default awareness more than once is not possible, and it's already set by the manifest before the script starts.

Win32 dialogs created from a template are automatically scaled with Per Monitor v2. InputBox is scaled in full quality, while MsgBox looks blurry. Weird.


Unfortunately, there's no quick fix for Windows 8.1 users, or old builds of Windows 10. We either have automatically-scaling windows or APIs that tell the truth, not both. Proper per-monitor DPI support would require handling WM_DPICHANGED and repositioning all of the controls. I imagine that rounding could be a problem when moving a GUI back and forward between monitors, unless the position is stored (and updated whenever the control is moved by some other means).

There's a confusing mess of functions because of the different levels of DPI scaling supported by Windows Vista - 8, Windows 8.1 and 10. For Windows 8.1, there is SetProcessDpiAwareness, which cannot be used because the DPI awareness is already set via the manifest. Perhaps we can remove dpiAware from the manifest and use SetProcessDpiAwareness or SetThreadDpiAwarenessContext if available, but since the former only works once, it would probably have to be controlled by a directive if we want to set a default. Also, "Setting the process-default DPI awareness via API call can lead to unexpected application behavior."

Modifying the manifest with a resource editor is relatively trivial. It can also be done programmatically, such as by using the EnableUIAccess_SetManifest() function from the AutoHotkey installer as a basis.
guest3456
Posts: 3463
Joined: 09 Oct 2013, 10:31

Re: Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

20 Jan 2020, 23:25

lexikos wrote:
05 Oct 2019, 04:55
This appears to work for enabling per-monitor DPI awareness on Windows 10 (probably 1603 and later):

Code: Select all

DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr")
...

The DPI awareness temporarily reverts to the process default while you're moving one of the script's windows.


after banging my head against the wall for the last hour, i've discovered something else.

if an AHK messagebox is waiting to be dismissed, the script will not be using the dpi awareness which was set from the api call above, but rather will be using the old -2 dpi awareness context (i'm guessing it falls back to use the default from the manifest file). i guess maybe the msgbox is running in a different windows thread? i thought ahk only used pseudo threads. whatever the case..

run this script, press F7 before dismissing the msgbox, and then press F7 after dismissing it:

Code: Select all

Ptr := A_PtrSize ? "Ptr" : "UInt"
originalContext := DllCall("SetThreadDpiAwarenessContext", ptr, -4, ptr)

msgbox

return


F7::
   threadDPIcontext := DllCall("user32.dll\GetThreadDpiAwarenessContext", "UInt")
   Loop, 5
   {
      this_context := A_Index * -1
      if (DllCall("user32.dll\AreDpiAwarenessContextsEqual", "UInt", threadDPIcontext, "Int", this_context))
      {
         msgbox, % this_context
         break
      }
   }
return

esc::exitapp
so, if you try to use WinGetPos while another msgbox is still pending, you WILL GET WRONG RESULTS. the safest way seems to be to set the value in the manifest

microsoft says you can include both dpiAware and dpiAwareness settings in the manifest to fallback for older versions of windows:
https://docs.microsoft.com/en-us/windows/win32/hidpi/setting-the-default-dpi-awareness-for-a-process

lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

07 Feb 2020, 21:59

If the MsgBox was created by a different Windows thread than the script, it would not affect the script's DPI awareness context. On the contrary, the problem is that there is only one thread, and only one value per thread. When you call MsgBox, some system code creates the dialog, displays it, and runs a message loop until the dialog is closed. At some point during the process, I presume some system code changes the DPI awareness context.

SetThreadDpiAwarenessContext could be called by any other code, as part of a system function, a third party dll used by the script, or another part of the script. The only way to be sure about the current context is to set it yourself immediately before relying on its value.
I wrote:The DPI awareness temporarily reverts to the process default while you're moving one of the script's windows. Calling SetThreadDpiAwarenessContext immediately before calling WinGetPos, ToolTip, etc. solves the problem.
I think that SetThreadDpiAwarenessContext overrides the manifest, so using the manifest to enable DPI awareness might not eliminate the problem, but would perhaps mitigate it since SetThreadDpiAwarenessContext is less likely to be used.
User avatar
Pyonkichi
Posts: 107
Joined: 16 May 2017, 20:40
Contact:

Re: Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

14 Sep 2020, 01:50

I really want an easy way to change the DPI awareness from the scripts. Below are some ideas for future AHK versions.

Adding new commands like SetDpiAwareness, permonitor.
After the command is executed, AHK internally calls SetThreadDpiAwarenessContext API before executing the DPI-affected commands.
Adding the call back function for WM_DPICHANGED instead of using OnMessage will make it easier to implement.
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

14 Sep 2020, 23:33

Pyonkichi wrote:
14 Sep 2020, 01:50
Adding the call back function for WM_DPICHANGED instead of using OnMessage will make it easier to implement.
What? How?
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

15 Sep 2020, 23:21

Calling OnMessage is simple, and would be perhaps the most trivial part of implementing per monitor DPI support. Adding a special label which is called when the WM_DPICHANGED message is received would make negligible difference.
maraskan_user
Posts: 6
Joined: 12 Sep 2015, 06:30

Re: Sizing GUI-Elements: dpi dpiaware has to be set to "True/PM" to work on Win 8.1 and above

02 Nov 2020, 18:46

I've been playing around with GUIs with DPIScale off and handling the WM_DPICHANGED via OnMessage() with a binary with PerMonitorV2 set via manifest. Works all good so far and I'm moving/resizing the controls in my gui as well as the gui itself appropriately with correct results. The issue is with font sizes. I'm not sure how to handle these properly (without restarting the whole process - which defeats the point of handling WM_DPICHANGED)

Thing is, FindOrCreateFont() in script_gui.cpp uses CreateFont() with
hdc = GetDC(HWND_DESKTOP);
int pixels_per_point_y = GetDeviceCaps(hdc, LOGPIXELSY);

...which is an outdated DPI value that's either from the time of logging into windows (When "Fix scaling for apps" is disabled) or from the time the process was launched ("Fix scaling" enabled). There's no way to tell the font related gui commands about our exclusive shiny new DPI value we received from OnMessage.

Edit:
On second thought, maybe adding an option parameter to "Gui,font" to hand over a custom dpi value which, if it exists, it could use in FindOrCreateFont() instead of pixels_per_point_y would allow for scaling fonts based on received WM_DPICHANGED? Only the letters C, S, W and Q are already taken, so maybe it could use D for DPI: Gui,font,s8 d%NewDPI%,Tahoma.

Return to “Bug Reports”

Who is online

Users browsing this forum: No registered users and 41 guests