Page 1 of 1

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

Posted: 09 Feb 2016, 10:49
by Taurus
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

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

Posted: 09 Feb 2016, 22:32
by lexikos
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?

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

Posted: 09 Feb 2016, 23:28
by guest3456
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

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

Posted: 10 Feb 2016, 02:03
by lexikos
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.

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

Posted: 10 Feb 2016, 03:34
by lexikos
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.

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

Posted: 10 Feb 2016, 10:30
by Taurus
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.

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

Posted: 10 Feb 2016, 18:20
by lexikos
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.

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

Posted: 17 Feb 2016, 23:01
by guest3456
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

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

Posted: 18 Feb 2016, 00:12
by lexikos
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.

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

Posted: 24 Sep 2019, 11:21
by guest3456
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.

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

Posted: 05 Oct 2019, 04:55
by lexikos
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.

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

Posted: 20 Jan 2020, 23:25
by guest3456
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

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

Posted: 07 Feb 2020, 21:59
by lexikos
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.

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

Posted: 14 Sep 2020, 01:50
by Pyonkichi
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.

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

Posted: 14 Sep 2020, 23:33
by lexikos
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?

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

Posted: 15 Sep 2020, 02:44
by Pyonkichi
@lexikos, I mean something like a GuiClose label.

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

Posted: 15 Sep 2020, 23:21
by lexikos
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.

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

Posted: 02 Nov 2020, 18:46
by maraskan_user
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.