[SOLVED] GUI positionning with multiple monitors (hidpi)

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
dev719
Posts: 10
Joined: 09 Nov 2015, 18:49

[SOLVED] GUI positionning with multiple monitors (hidpi)

09 Nov 2015, 19:18

Hi all,
my goal is to show a small notice on the bottom left corner of the current screen on key press.
The problem is that the GUI goes off screen on the second monitor.
My actual config is the following : Windows 10 Pro, 1st monitor integrated 13" 2160x1440 with 150% scaling, 2nd monitor on the right 1920x1080 without scaling

First I create my GUI, then I detect on which monitor the cursor is and finally I show it.

Here's the relevant part of the script that I'm using :

Code: Select all

Gui, OSD: +AlwaysOnTop +LastFound +Owner -Caption
Gui, OSD:Color, 000000
Gui, OSD:Font, s30, Trebuchet MS
Gui, OSD:Add, Text, vOSDText x10 y0 cCCCCCC BackgroundTrans, %text%
tw = 277; text width
MouseGetPos, mx, my
SysGet, monitorsCount, 80
Loop %monitorsCount%{
	SysGet, monitor, Monitor, %A_Index%
	if (monitorLeft <= mx && mx <= monitorRight && monitorTop <= my && my <= monitorBottom){
		SysGet, monitorWorkArea, MonitorWorkArea, %A_Index%
		y := monitorWorkAreaBottom - 73
		
		;debug
		FileAppend,
		(
			Cursor on monitor %A_Index% M X%mx% Y%my% WA L%monitorWorkAreaLeft% R%monitorWorkAreaRight%, T%monitorWorkAreaTop% B%monitorWorkAreaBottom%, %y%
			Show on x%monitorWorkAreaLeft% y%y% h53 w%tw%`n
		), log.txt
		
		Gui, OSD: Show, x%monitorWorkAreaLeft% y%y% h53 w%tw% NA
		Break
	}
}
Some lines of log.txt :

Code: Select all

Cursor on monitor 1 M X2038 Y214 WA L0 R2160, T60 B1440, 1367
Show on x0 y1367 h53 w277
Cursor on monitor 1 M X2038 Y214 WA L0 R2160, T60 B1440, 1367
Show on x0 y1367 h53 w277
Cursor on monitor 2 M X3658 Y1390 WA L3240 R6120, T0 B1560, 1487
Show on x3240 y1487 h53 w277
Cursor on monitor 2 M X3577 Y1336 WA L3240 R6120, T0 B1560, 1487
Show on x3240 y1487 h53 w277
Any help would be appreciated. Thanks.
Last edited by dev719 on 18 Nov 2015, 16:16, edited 3 times in total.
dev719
Posts: 10
Joined: 09 Nov 2015, 18:49

Re: GUI positionning with multiple monitors (hidpi)

11 Nov 2015, 14:27

Tried -DPIScale but couldn't make it work as I wanted. Compatibility mode made the monitor detecting test fail.
Finally solved my problem by moving the gui to the primary screen before moving it to the corresponding screen.
User avatar
trismarck
Posts: 506
Joined: 30 Sep 2013, 01:48
Location: Poland

Re: [SOLVED] GUI positionning with multiple monitors (hidpi)

11 Nov 2015, 20:47

I don't have Windows 10 so can't really test that. Also wonder how -DPIScale works on a system with per-monitor (vs global) DPI setting (which I presume is your setup).
//edit: is it like the re-scaling ratio is taken from the primary monitor or some specific monitor?
dev719 wrote:Tried -DPIScale but couldn't make it work as I wanted. Compatibility mode made the monitor detecting test fail.
What do you mean by the "compatibility mode"?
dev719
Posts: 10
Joined: 09 Nov 2015, 18:49

Re: GUI positionning with multiple monitors (hidpi)

12 Nov 2015, 03:02

I'm speaking about the compatibility mode "Disable scaling for high-resolution displays" (or similar, my Windows is not in English) that you can enable by going on the autohotkey.exe properties on the compatibility tab. In fact it seems it does nothing. Problems are caused by the application opened on the second screen.
dev719
Posts: 10
Joined: 09 Nov 2015, 18:49

Re: GUI positionning with multiple monitors (hidpi)

12 Nov 2015, 17:38

Ok, finally the problem was far more complex than I initially thought.

I ran some tests with differents setups but none did exactly what I expected. Here, I tried to make the gui appear 100px from the top left corner of the screen.
I used the following parameters :
  • DPIScale - DPIScale option enabled or not on the Gui command
  • Compat - High-Resolution screens compatibility mode enable on the Autohotkey executable
  • x0y0 - Trying to fix the coordinates for 2nd and following show on the second screen by sending first the gui first to x0 y0 coordinates.
  • App - Focusing on an app on the second screen (Excel and Firefox both gave me the same results)
For each test, I noted the following things (are given the values that I expected) :
  • x1 - x coordinate (in pixels) on the first screen (150)
  • w1 - width (in pixels) on the first screen (150)
  • x2 - x coordinate (in pixels) on the 2nd screen (2260)
  • w2 - width (in pixels) on the 2nd screen (100)
  • x2 - x coordinate (in pixels) on the 2nd screen after the first show (2260)
  • w2 - width (in pixels) on the 2nd screen after the first show (100)
  • mx tr - mouse x coordinate (given by ahk) on the top right corner of the second screen (3360 = 2160/150% + 1920)
In the two cases with a mx tr value marked >2158, it means that when I went further than the 2/3 of my second screen on the right, nothing happened anymore.
The case the nearest of my expectations is when both DPIScale and Windows app compatibility are enabled but note that it still doesn't work with an app opened on the second screen.
Image
(Is there a better way to post a table ?)

And here's a more complete code :

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
#Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
#SingleInstance force

#HotkeyInterval 2000  ; This is  the default value (milliseconds).
#MaxHotkeysPerInterval 200

Gui, DEBUG: +AlwaysOnTop
Gui, DEBUG:Add, Text, vDEBUGt w300, DEBUG
Gui, DEBUG:Show

OSDInit = 0
OSD(text)
{
	global OSDText, OSDInit
	tw = 0
	
	CustomColor = 000000
	Gui, OSD: +AlwaysOnTop +LastFound +Owner -Caption ;-DPIScale
	Gui, OSD:Color, %CustomColor%
	Gui, OSD:Font, s30, Trebuchet MS
	if (OSDInit = 0){
		OSDInit = 1
		Gui, OSD:Add, Text, vOSDText x10 y0 cCCCCCC BackgroundTrans, %text%
	}
	else{
		w := TextWidth(text, 30, "Trebuchet MS")
		tw := w + 20
		GuiControl, OSD:Move, OSDText, w%w%
		GuiControl, OSD:, OSDText, %text%
	}
	
	MouseGetPos, mx, my
	SysGet, monitorsCount, 80

	Loop %monitorsCount%{
		SysGet, monitor, Monitor, %A_Index%
		if (monitorLeft <= mx && mx <= monitorRight && monitorTop <= my && my <= monitorBottom){
			SysGet, monitorWorkArea, MonitorWorkArea, %A_Index%
			y := monitorWorkAreaBottom - 73
			
			; x0y0 fix
			If(A_Index != 1){
				Gui, OSD: Show, x0 y0 Hide
			}
			
			;Gui, OSD: Show, x%monitorWorkAreaLeft% y%y% h53 w%tw% NA
			;Break
			
			;DEBUG
			ay := monitorWorkAreaTop + 100
			ax := monitorWorkAreaLeft + 100
			GuiControl, DEBUG:, DEBUGt, M X%mx% Y%my% MON %A_Index% WA L%monitorWorkAreaLeft% R%monitorWorkAreaRight%, T%monitorWorkAreaTop% B%monitorWorkAreaBottom%, %ax% %ay%
			Gui, OSD: Show, x%ax% y%ay% h100 w100 NA
			
			Break
		}
	}
	
	SetTimer, RemoveToolTip, 2000
	Return

RemoveToolTip:
	SetTimer, RemoveToolTip, Off
	Gui, OSD: hide
	Return
}

#WheelDown::
	SoundSet -1
	SoundGet, vol
	OSD("Volume " . round(vol) . "%")
	Sleep, 20
Return
#WheelUp::
	SoundSet +1
	SoundGet, vol
	OSD("Volume " . round(vol) . "%")
	Sleep, 20
Return
Note that it may well be something not supported in AutoHotkey. But if you have any idea how to make this right, I'm taking.
Should I try to fill a bug/feature request report somewhere ?
PS : as you may already have seen, english is not my first language. If you have any question about what I wrote, please ask.
User avatar
trismarck
Posts: 506
Joined: 30 Sep 2013, 01:48
Location: Poland

Re: GUI positionning with multiple monitors (hidpi)

13 Nov 2015, 02:16

//edit-2015-11-13: after rethinking, almost everything in the this post is wrong; I'll leave the post for reference.

Here are some thoughts, after reading about DPIScale and Windows DPI settings:
  • Autohotkey determines DPI settings based on the 'screen' (which is really based on the DPI of the primary monitor only I guess)
  • -DPIScale / DPIScale only really affects positions explicitly specified by the user (vs auto-placement of gui controls/windows, which seems to _always_ use the DPI of the primary monitor described above). Positions given part of Gui related commands: Gui, GuiControl, Move and related (vs WinGetPos or ControlGetPos)
  • the size of the text within the control is scaled by Windows itself, so DPIScale doesn't have an effect on the text; only on the 'rectangle' / size of the control in which the text is displayed
  • DPIScale was added mainly for older Autohotkey scripts, created with GUI creator tool (don't remember the exact name of the tool). The GUI creator tool generated a layout of gui controls within the gui window, and that layout had fixed coords, coords that would only 'work' on systems with DPI set to 100%. By adding DPIScale, one could make something like GuiControl, Move, x200 to really move 300px on 150% DPI settings or to make a text control not to be truncated / cut-out at the bottom/right
Here is a note on the compatibility setting. Autohotkey is DPI-aware, so compatibility is disabled by default, one can only enable it. But in this thread's scenario, lets leave enable compatibility disabled.

Some thoughts on how does the above relate to your script:
  • the script uses explicit coords to position Gui window / Gui controls. For explicit coords, DPIScale does take an effect:
    • if DPIScale [for a given window] is enabled, positions of Gui window / controls are rescaled according to the DPI of the primary monitor (150% DPI)
    • if DPIScale [for a given window] is disabled, positions of Gui window / controls are not rescaled (100% DPI)
  • because Autohotkey only really understands one system-level DPI (vs per-monitor DPI), just [statically] using either DPIScale or -DPIScale for particular window won't work - one has to dynamically change DPIScale and redraw controls / the Gui window as the window moves between monitors
The general idea of the script would be something like this:
  • instead of specifying positions of Gui controls through Gui, Add, specify positions later on, with GuiControl, Move. This way, upon doing Gui, -DPIScale or Gui, DPIScale and then moving the controls, Autohotkey will dynamically rescale explicit coords to given DPI settings (either 150% or 100% in the described case):

    Code: Select all

    Gui, -DPIScale
    Gui, Add, Text, vMyText1, abc
    Gui, Add, Text, vMyText2, abc
    Gui, Add, Text, vMyText3, abc
    
    Guicontrol, Move, MyText1, x100 y100
    Guicontrol, Move, MyText2, x100 y120
    Guicontrol, Move, MyText3, x100 y140
    Gui, Show, w500 h500
    sleep 3000
    
    Gui, +DPIScale
    Guicontrol, Move, MyText1, x100 y100
    Guicontrol, Move, MyText2, x100 y120
    Guicontrol, Move, MyText3, x100 y140
    Gui, Show, w500 h500
    	; positions of all controls rescaled correctly
    	; 
    return
    GuiClose:
    GuiEscape:
    Exitapp
    So if Gui is to be displayed on monitor1, enable DPIScale and rescale the Gui window. If Gui is to be displayed on monitor2, disable DPIScale and rescale the Gui window
  • to support moving of the window between monitors, there seems to be a Windows Message for that called WM_DPICHANGED. One could monitor for that message to detect when the window crosses the monitor-with-different-DPI boundary and rescale the Gui window like in the above example when transition takes place
  • In the scenario in this thread, one only deals with 150% DPI or 100% DPI. But if there were more monitors, each having a different DPI setting, one would have to use DPIScale for _each_ particular monitor (which is currently impossible). So perhaps something like DPIScale<DPI> could have been added to Autohotkey so that one could rescale all gui controls according to a custom rescale ratio (according to custom DPI settings) (vs DPIScale just being a bool / ratio being read off of the primary monitor). For now, I guess one could partially workaround that by disabling DPIScale and doing something like: GuiControl, Move, % "x" DPIScale(100) " y" DPIScale(200) (or DPIScale(100, mon1), where DPIScale() would have DPIs of all monitors listed). But would have to rethink the whole approach of whether it would work with auto-sized controls / margins / being compatible with older scripts / other stuff.
What I didn't understand:
  • don't understand how the compatibility mode works, esp. with DPI-aware application
  • don't really understood the DPI virtualization thing
There is also WinGetPosEx(), but I don't think it applies to this particular thread.
dev719 wrote:Finally solved my problem by moving the gui to the primary screen before moving it to the corresponding screen.
By that, do you mean that you've just always moved the window to the primary monitor? From what I've tested, it doesn't matter as on what monitor the Gui window was first shown up - if coords of the Gui window specified explicitly, the size of the window will always be the same (will only depend on DPIScale, but not on which monitor the Gui window was shown).

HTH

//edit:
dev719 wrote:In the two cases with a mx tr value marked >2158, it means that when I went further than the 2/3 of my second screen on the right, nothing happened anymore.
This is because by default, mouse coords are relative to the active window, unless CoordMode is used. The result of MouseGetPos has nothing to do with DPI - it's just pixels on the screen.
User avatar
trismarck
Posts: 506
Joined: 30 Sep 2013, 01:48
Location: Poland

Re: GUI positionning with multiple monitors (hidpi)

13 Nov 2015, 17:49

trismarck wrote:
  • Autohotkey determines DPI settings based on the 'screen' (which is really based on the DPI of the primary monitor only I guess)
Windows 8.1 and up have two separate DPI settings:
  • system DPI setting
  • per-monitor DPI setting.
What Autohotkey reads is the System DPI setting.
Autohotkey is the system DPI–aware application, but not per monitor-DPI aware application.
trismarck wrote:
  • -DPIScale / DPIScale only really affects positions explicitly specified by the user (vs auto-placement of gui controls/windows, which seems to _always_ use the DPI of the primary monitor described above the System DPI settings). Positions given part of Gui related commands: Gui, GuiControl, Move and related (vs WinGetPos or ControlGetPos)
Seems to be correct.
trismarck wrote:
  • the size of the text within the control is scaled by Windows itself, so DPIScale doesn't have an effect on the text; only on the 'rectangle' / size of the control in which the text is displayed
  • DPIScale was added mainly for older Autohotkey scripts, created with GUI creator tool (don't remember the exact name of the tool). The GUI creator tool generated a layout of gui controls within the gui window, and that layout had fixed coords, coords that would only 'work' on systems with DPI set to 100%. By adding DPIScale, one could make something like GuiControl, Move, x200 to really move 300px on 150% DPI settings or to make a text control not to be truncated / cut-out at the bottom/right
Ok.
trismarck wrote:Here is a note on the compatibility setting. Autohotkey is DPI-aware, so compatibility is disabled by default, one can only enable it. But in this thread's scenario, lets leave enable compatibility disabled.
To be precise, Autohotkey is system DPI–aware. And lets refer to the 'compatibility' setting with its full name - "Disable display scaling on high DPI settings", which is disabled by default.
What "Disable display scaling on high DPI settings" does is it enables or disables DPI virtualization.
For Windows 8.1 and up:
  • if the application is not DPI-aware, DPI virtualization makes the application as if the application was per monitor-DPI aware
  • if the application is system DPI–aware, DPI virtualization also makes the application as if the application was per-monitor aware (our case).
    Note that even if the application has some DPI awareness in it, it doesn't mean the DPI virtualization layer won't trigger.
DPI virtualization introduces side-effects.

From this point onward, lets call "Disable display scaling on high DPI settings" "Disable DPI virtualization".

Enabling "Disable display scaling on high DPI settings" seems to mean: disable DPI virtualization (so that everything works as if on Windows XP, which lacks DPI virtualization altogether). But enabling that option also means: do be aware of the system DPI setting (just don't provide any mechanism for fixing DPI related issues). This is why in the table, even if DPI virtualization is disabled, Autohotkey's DPIScale still works (which means that the application is still able to read the system DPI setting and that that setting doesn't have to be 100% at that point):Image
trismarck wrote:
  • the script uses explicit coords to position Gui window / Gui controls. For explicit coords, DPIScale does take an effect:
    • if DPIScale [for a given window] is enabled, positions of Gui window / controls are rescaled according to the DPI of the primary monitor (150% DPI)
    • if DPIScale [for a given window] is disabled, positions of Gui window / controls are not rescaled (100% DPI)
This seems to be correct and is actually the mechanism behind Autohotkey being system DPI–aware.
trismarck wrote:
  • because Autohotkey only really understands one system-level DPI (vs per-monitor DPI), just [statically] using either DPIScale or -DPIScale for particular window won't work - one has to dynamically change DPIScale and redraw controls / the Gui window as the window moves between monitors
Now here is the flaw in my thinking. If DPI virtualization is enabled (which it is, by default), one doesn't have to change DPI settings 'per-monitor' with DPIScale - the DPI virtualization layer will do it for the application. So the only thing the application should do is: enable DPIScale for all windows (which Autohotkey does by default).Image
trismarck wrote:

Code: Select all

Gui, -DPIScale
Gui, Add, Text, vMyText1, abc
Gui, Add, Text, vMyText2, abc
Gui, Add, Text, vMyText3, abc

Guicontrol, Move, MyText1, x100 y100
Guicontrol, Move, MyText2, x100 y120
Guicontrol, Move, MyText3, x100 y140
Gui, Show, w500 h500
sleep 3000

Gui, +DPIScale
Guicontrol, Move, MyText1, x100 y100
Guicontrol, Move, MyText2, x100 y120
Guicontrol, Move, MyText3, x100 y140
Gui, Show, w500 h500
	; positions of all controls rescaled correctly
	; 
return
GuiClose:
GuiEscape:
Exitapp
The problem with this script is that if dimensions of the Gui window are removed (Gui, Show) then the Gui window won't be automatically rescaled. And, this is approach is really the wrong way of support DPI scaled applications in the first place (I presume). This is because, from the point of view of a system DPI–aware application, fonts in all text controls are of same height regardless of DPIScale settings for particular gui windows, so disabling DPIScale for a given window will probably make either the text unnaturally large in relation to the margins or will make the margins too small.
trismarck wrote:
  • In the scenario in this thread, one only deals with 150% DPI or 100% DPI. But if there were more monitors, each having a different DPI setting, one would have to use DPIScale for _each_ particular monitor (which is currently impossible). So perhaps something like DPIScale<DPI> could have been added to Autohotkey so that one could rescale all gui controls according to a custom rescale ratio (according to custom DPI settings) (vs DPIScale just being a bool / ratio being read off of the primary monitor). For now, I guess one could partially workaround that by disabling DPIScale and doing something like: GuiControl, Move, % "x" DPIScale(100) " y" DPIScale(200) (or DPIScale(100, mon1), where DPIScale() would have DPIs of all monitors listed). But would have to rethink the whole approach of whether it would work with auto-sized controls / margins / being compatible with older scripts / other stuff.
I think this falls out too?, bc of the above description.
trismarck wrote://edit:
dev719 wrote:In the two cases with a mx tr value marked >2158, it means that when I went further than the 2/3 of my second screen on the right, nothing happened anymore.
This is because by default, mouse coords are relative to the active window, unless CoordMode is used. The result of MouseGetPos has nothing to do with DPI - it's just pixels on the screen.
This seems correct. Another note about the mouse position is that I guess every even row in the table isn't needed, as the mouse coord was retrieved in relation to the active window, but this has nothing to do with DPIScaling.
trismarck wrote:
dev719 wrote:Finally solved my problem by moving the gui to the primary screen before moving it to the corresponding screen.
By that, do you mean that you've just always moved the window to the primary monitor? From what I've tested, it doesn't matter as on what monitor the Gui window was first shown up - if coords of the Gui window specified explicitly, the size of the window will always be the same (will only depend on DPIScale, but not on which monitor the Gui window was shown).
I suspect that the monitor on which the gui window is shown first doesn't really matter. The case has to do with something else; Gui, OSD: Show, x0 y0 Hide does not use w and h, while Gui, OSD: Show, x%ax% y%ay% h100 w100 NA does use it - if one would specify w and h for both of Gui commands, there would have been no discrepancy:

Code: Select all

- Gui, OSD: Show, x0 y0 Hide ; w100 and h100 missing
- Gui, OSD: Show, x%ax% y%ay% h100 w100 NA
Explanation: for Autohotkey Gui commands, it does matter at what point the WinAPI window is actually created / its coords are calculated. Some Autohotkey commands do create a Gui window, but don't change dimensions of that window. I.e.:

Code: Select all

Gui, Add, Text
	; gui window created
	; gui window has dimensions 0 0 0 0
	
Gui, 2:Show, Hide
	; gui window created
	; gui window has dimensions calculated according to controls inside of the window

Gui, 3:Add, Text
	; gui control created
	; gui control dimensions set
TODO: example for the above, with using DetectHiddenWindows see first example in next post.
TODO: find Ahk documentation about what sentence was about what gui command creates gui window - and create ahk thread about this. Here.
So when one uses Gui, Show, Hide w/o specifying w and h, dimensions of that gui window are calculated anyway, and bc w and h weren't specified, the width and height of that gui window are generated automatically. So in the table, I guess that the 67 number is just coincidently 2/3 of 100, because how what the OSD window contained. But instead of 67, the value could have been different, i.e. 80 or 30.
Or actually, I think the above effect is related to this part of the source code (and to what width the gui control was resized):

Code: Select all

		w := TextWidth(text, 30, "Trebuchet MS")
		tw := w + 20
		GuiControl, OSD:Move, OSDText, w%w%
		GuiControl, OSD:, OSDText, %text%
So 67 is not a coincidence, but the effect described above is likely to still apply.Image


Other miscellaneous stuff:
  • w2 in the legend of the table - add 'b'
  • w2 in the table - add 'a'
[/list]

So the most interesting case is with the 2227 x coord. 2260 vs 2227 may be because: SysGet uses GetSystemMetrics(). GetSystemMetrics() _is_ DPI virtualization aware as described here. So perhaps that DPI virtualization layer interferes with how GetSystemMetrics() reads information about the monitor. Also, because of the values that the author gets, I'd presume there is a taskbar to the left/right of the second monitor? (x'th working area smaller, normally it would have been the y'th coord that changed more.Image
//edit: actually MonitorWorkArea uses SystemParametersInfo(), to which DPI virtualization doesn't apply?

So what one would wonder about that particular case is this: isn't the value returned by SysGet _correct_? (~the value that one might expect if dpi scaling was per-monitor) .

To position the gui window _always_ at 100px from the bottom of the screen (~ignoring DPI rescaling), perhaps moving the window with something like WinMove would work better than Gui, Show? - not sure if it would entirely skip the dpi cause, but at least WinMove works with pixels only, ignoring DPI settings.

I've made a presumption that all pixel related data in the table was gathered with pixel-only aware functions, like WinMove - so that all dimensions are just pixels on the end screen, regardless of any DPI settings and rescaling on top of that. I.e. on the second screen, window was at 2200x500 and was 500px wide and 600px in height.
Update: not sure if there is actually difference between Gui, Show, x y and WinMove in how gui window is positioned.

TODO: perhaps describe the table again, describing each row separately.

Conclusion
  • Use DPIScale, leave DPI virtualization enabled and wonder why x coord isn't like it was supposed to be (is GetSystemMetrics() affected by DPI virtualization?).Image
  • is the system DPI setting 150% DPI or 100% DPI.
Last edited by trismarck on 14 Nov 2015, 23:19, edited 1 time in total.
dev719
Posts: 10
Joined: 09 Nov 2015, 18:49

Re: GUI positionning with multiple monitors (hidpi)

14 Nov 2015, 16:51

I see now that I used MouseGetPos the wrong way. I re-did the tests by setting the coordmode and now obtain consistent values. Now the screen detection works.
Image
That's not relevant here but when I move a window in the gap between the two screens (2161-3239), it appears on the last third of the first screen.
I don't have a taskbar on the left of the second screen. For the purpose of the tests, I moved them on the bottom (again not relevant here but I usually place it on the top on my first screen).
System DPI seems to be 144 (150%). I found this value in the registry (HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics\AppliedDPI). I couldn't find where to retrieve per monitor settings but I'll search more infos.
The pixels values in the table were retrieved manually, using screenshots (not DPI-awares).
In my experience, the gui dimensions didn't change automatically once it has been shown for the first time (i.e. hiding it or moving it without precise dimensions or changing the content didn't resize it). I'll try to find further informations about that. If I move it manually between the screen, it scales as expected.

I don't have time now but I'll post more informations tomorrow. Again, thanks for your help.
User avatar
trismarck
Posts: 506
Joined: 30 Sep 2013, 01:48
Location: Poland

Re: GUI positionning with multiple monitors (hidpi)

14 Nov 2015, 23:15

dev719 wrote:Image
Right, so we can see that if DPI virtualization is enabled, something is wrong with the WorkingArea measurement?
dev719 wrote:That's not relevant here but when I move a window in the gap between the two screens (2161-3239), it appears on the last third of the first screen.
This I sort of don't understand. So are there monitors between monitors that you have listed? Thought that the monitor layout is something like this:Image
dev719 wrote:I don't have a taskbar on the left of the second screen. For the purpose of the tests, I moved them on the bottom (again not relevant here but I usually place it on the top on my first screen).
Ok, I've probably overthought this one. I wrote about the taskbar because I wanted to understand the 2227 measurement (vs the 2260 measurement). First the idea with the taskbar appeared, wrote the response, then read your script and found the following line:

Code: Select all

y := monitorWorkAreaBottom - 73
So the question would be - is there a chance that during testing, you've somehow used y instead of ay to read the measurement? - if one would subtract 73 from some round number, one would get 27 at the end. At least that's the line of my current thinking.
dev719 wrote:System DPI seems to be 144 (150%). I found this value in the registry (HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics\AppliedDPI). I couldn't find where to retrieve per monitor settings but I'll search more infos.
Ok. The per-monitor DPI settings are just set in the Control Panel, so I guess by 'where' you mean the registry.
dev719 wrote:In my experience, the gui dimensions didn't change automatically once it has been shown for the first time (i.e. hiding it or moving it without precise dimensions or changing the content didn't resize it). I'll try to find further informations about that. If I move it manually between the screen, it scales as expected.
What I've meant by this:
trismarck wrote:Some Autohotkey commands do create a Gui window, but don't change dimensions of that window.
was that perhaps what happened was:
  • Gui, Show, Hide was executed, and because w and h weren't specified, dimensions of the gui window were auto-calculated
  • Gui, Show was executed, thus displaying the gui window with auto calculated coords
and contrast that with doing Gui, Show, w100 h100, where coords are explicitly specified so no auto size calculation of the gui window takes place:

Code: Select all

Gui, +hwndhwnd
Gui, Add, Text, , sldkf
Gui, Add, Text, , sldkf

DetectHiddenWindows, On
WinGetPos, , , w, h, ahk_id %hwnd%
msgbox % w " " h	; 69 124

Gui, Show, Hide
WinGetPos, , , w, h, ahk_id %hwnd%
msgbox % w " " h	; 108 165

Gui, Show
WinGetPos, , , w, h, ahk_id %hwnd%
msgbox % w " " h	; 108 165

Gui, Show, w100 h100
WinGetPos, , , w, h, ahk_id %hwnd%
msgbox % w " " h	; 156 195
exitapp
But again, I've overthought it, as I saw that even after using Gui, OSD: Show, x0 y0 Hide, you're always additionally doing Gui, OSD: Show, x%ax% y%ay% h100 w500 NA, so in the end, w and h were specified. Thought that perhaps you're reading dimensions of that not yet shown window, but this seems not to be the case.
In relation to this, my point was that the extra step of 'showing' or 'making ahk calculate and apply coords' of the gui window on the first monitor (x0y0) was irrelevant - from the point of view of the ahk, especially when the DPI virtualization is on, the virtualization layer would make ahk to think that it is run on a system wide 150% DPI, set for all monitors.

Another thing discovered during testing this was: Gui, Show, w100 h100 specifies dimensions for the client area of the window, while WinGetPos, w, h reads width and height of the window itself, including the caption and the border. So to 'fix' this, so that client area width equals width of whole window, one can remove the caption and borders:

Code: Select all

Gui, +hwndhwnd
Gui, -Caption
	; remove caption and borders
	; 
Gui, Add, Text, , sldkfj
Gui, Add, Text, , sldkfj
DetectHiddenWindows, On
WinGetPos, , , w, h, ahk_id %hwnd%
msgbox % w " " h	; 0 0

Gui, Show, Hide
WinGetPos, , , w, h, ahk_id %hwnd%
msgbox % w " " h	; 66 79

Gui, Show
WinGetPos, , , w, h, ahk_id %hwnd%
msgbox % w " " h	; 66 79

Gui, Show, w100 h100
WinGetPos, , , w, h, ahk_id %hwnd%
msgbox % w " " h	; 150 150
exitapp
which you already do. In comparison, Gui, Show, x y uses window position while Gui, Show, w h uses client area width and height.

Another interesting case would be: are functions behind Gui, Show, x y and WinGetPos x y DPI virtualization aware.
dev719
Posts: 10
Joined: 09 Nov 2015, 18:49

Re: GUI positionning with multiple monitors (hidpi)

15 Nov 2015, 18:58

I had to say I was becoming crazy with the issue of the gui being moved on the second call.
I had a look at the ahk source code. A thing that bothered me is that the Gui Show command is implement in a way that if we didn't move the gui, it wouldn't call the MoveWindow command of the Windows API (See here : https://github.com/Lexikos/AutoHotkey_L ... .cpp#L6728). I then recompiled ahk adding debug output (I freezed my PC several times trying to use breakpoints, they don't work well with the mouse/keyboard hook of ahk).

Code: Select all

//...
RECT old_rect;
GetWindowRect(mHwnd, &old_rect);
int old_width = old_rect.right - old_rect.left;
int old_height = old_rect.bottom - old_rect.top;

std::ofstream out;
out.open("log.txt", std::ios::app);
out << "OLD :" << " l" << old_rect.left << " r" << old_rect.right << " t" << old_rect.top << " b" << old_rect.bottom <<" w" << old_width << " h" << old_height << "\n";
out << "NEW :" << " l" << x << " r" << x+width << " t" << y << " b" << y+height << " w" << width << " h" << height << "\n";

// Avoid calling MoveWindow() if nothing changed because it might repaint/redraw even if window size/pos
// didn't change:
if (width != old_width || height != old_height || (x != COORD_UNSPECIFIED && x != old_rect.left)
	|| (y != COORD_UNSPECIFIED && y != old_rect.top)) // v1.0.45: Fixed to be old_rect.top not old_rect.bottom.
{
	out << "It did change\n";
//...
if (mGuiShowHasNeverBeenDone) // This is the first showing of this window.
{
	out << "First show\n";
//...
out << "\n";
out.close();
//...
And it gave me that :

Code: Select all

OLD : l0 r0 t0 b0 w0 h0
NEW : l3340 r3490 t100 b250 w150 h150
It did change
First show

OLD : l3340 r3490 t100 b250 w150 h150
NEW : l3340 r3490 t100 b250 w150 h150

OLD : l3340 r3490 t100 b250 w150 h150
NEW : l3340 r3490 t100 b250 w150 h150

OLD : l3340 r3490 t100 b250 w150 h150
NEW : l3340 r3490 t100 b250 w150 h150
Seeing that, it becomes obvious that Gui Show isn't responsible for this strange behaviour. Note too that during my tests if I launched ahk from my second screen (not after a reload), the problem was inversed (worked fine on the second monitor but not on the first).

After more tests, it's this line who is responsible : (by removing this line, it works as one would expect) It's somehow trigger a rescale/repaint of the window. This will definitely need further investigations.

Code: Select all

GuiControl, OSD:Move, OSDText, w%w%
My code is then fixed when I add the text element with a huge width (doesn't matter in my case anyway) :

Code: Select all

Gui, OSD:Add, Text, vOSDText x10 y0 w999999 cCCCCCC BackgroundTrans, %text%
Let's now talk about this ghost screen (x coordinates between 2161 and 3239) that appears when the compatibility setting is not enabled. Content that I put on these coordinates appears on the top right corner of the main screen and is not scaled (100px = 100px). I did multiple tries to detect his dimensions. It's a 1080(width)x1420(height) pixels screen. I don't have a single idea why it is there. Below a picture where I indicated some relevant coordinates. (Base picture taken from control panel)
Image
I think the 2227 number is in fact 2160 + 100/1.5 = 67. It seems coherent with the 67 width.
EDIT :
trismarck wrote:Right, so we can see that if DPI virtualization is enabled, something is wrong with the WorkingArea measurement?
The correct output I would expect for the working area (dpi aware) would be :
  • Monitor 1 x0-1440 y0-920 because as we have 150% more pixels per dot, we have virtually 150% less pixels
  • Monitor 2 x1440-3360 (1920) y0-1040
Note that both times the taskbar is 40 (virtual) pixels high.
It would help to have a look on how the function works.

Let's now talk about the scaling.
We saw in the log output that the system calls were done with scaled weight and height but xy coordinates were send directly. Scaling appears to be done here : https://github.com/Lexikos/AutoHotkey_L ... .cpp#L6530
It's a simple scaling that is done, independant of the screen.

Code: Select all

case 'X': x = n; break;
case 'Y': y = n; break;
// Allow any width/height to be specified so that the window can be "rolled up" to its title bar:
case 'W': width = Scale(n); break;
case 'H': height = Scale(n); break;
Let's see this Scale function :

Code: Select all

/* script.h:2790 */
// See DPIScale() and DPIUnscale() for more details.
int Scale(int x) { return mUsesDPIScaling ? DPIScale(x) : x; }

/* script.h:310 */
// The following functions are used in GUI DPI scaling, so that
// GUIs designed for a 96 DPI setting (i.e. using absolute coords
// or explicit widths/sizes) can continue to run with mostly no issues.

static inline int DPIScale(int x)
{
	extern int g_ScreenDPI;
	return MulDiv(x, g_ScreenDPI, 96);
}

/* globaldata.cpp:144 */
int g_ScreenDPI = GetScreenDPI();

/* globaldat.cpp:132 */
static int GetScreenDPI()
{
	// The DPI setting can be different for each screen axis, but
	// apparently it is such a rare situation that it is not worth
	// supporting it. So we just retrieve the X axis DPI.

	HDC hdc = GetDC(NULL);
	int dpi = GetDeviceCaps(hdc, LOGPIXELSX);
	ReleaseDC(NULL, hdc);
	return dpi;
}
GetDC, GetDeviceCaps and ReleaseDC belong to the GDI API which is a system library.
The GetDeviceCaps function retrieves device-specific information for the specified device. (https://msdn.microsoft.com/en-us/librar ... 85%29.aspx)
The GetDC function retrieves a handle to a device context (DC) for the client area of a specified window or for the entire screen. (https://msdn.microsoft.com/en-us/librar ... 85%29.aspx)

As it is called without parameter, it retrieves informations for the entire screen.

TL;DR AHK doesn't handle the case where there are multiple monitors with inconsistent dpi settings, letting the OS play with that. It only scales the width and the height.

I then did some experiment with WinGet and it gave me the same xy coordinates that I used but the width and the height were always 150px, even when the pixel size was 100px (2nd screen and "ghost" screen). Shouldn't it be unscaled ?

EDIT (a day later) :
I was able to make my code work as I initially wanted. I decided to disable gui dpiscale and applying a scale manually when relevant.
Even if this works, there are still things I don't understand :
  • Why does GuiControl Move moves and scale the entire window ? <- there's a possible bug somewhere
  • What is the logic behing getting monitors WorkArea ? What is this ghost monitor ?
  • Why does the dpi (A_ScreenDPI) still is 144 when I disable my 1st monitor ? <- maybe it only changes on log off/reboot, I'll have to test that
I will post later a clean version of my definitive code.
dev719
Posts: 10
Joined: 09 Nov 2015, 18:49

Re: GUI positionning with multiple monitors (hidpi)

18 Nov 2015, 16:15

I'll now mark this thread as solved.

Some informations if you have a similar problem :
What the DPIScale parameter does affect :
  • Guis width/height
  • Controls coordinates
  • Controls width/height
All of the above are multiplied by the 1st monitor scale.

First screen dimensions stays the same. Others screen dimensions are multiplied by the primary screen scaling and then divided by their. Others coordinates (mouse, windows positions, ...) follow the same logic.

Font sizse are always scaled.

I recommend disabling the DPIScaling parameter and scaling manually.

Some code snippets:
Get current monitor

Code: Select all

GetMonitor(){
	CoordMode, Mouse, Screen
	MouseGetPos, mx, my
	SysGet, monitorsCount, 80

	Loop %monitorsCount%{
		SysGet, monitor, Monitor, %A_Index%
		if (monitorLeft <= mx && mx <= monitorRight && monitorTop <= my && my <= monitorBottom){
			Return A_Index
		}
	}
	Return 1	
}
Get text field dimensions

Code: Select all

TextSize(text, size, font, ByRef width, ByRef height)
{
	global txt
	
	Critical
	Gui, DummyGUI: destroy
	Gui, DummyGUI: -DPIScale
	Gui, DummyGUI:Font, s%size%, %font%
	Gui, DummyGUI:Add, Text, vTxt, %text%
	GuiControlGet, ov, DummyGUI:Pos, Txt
	Critical Off
	
	width := ovw
	height := ovh
}
Place a 100px square window 100px from the top left corner

Code: Select all

A_Scaling := A_ScreenDPI / 96
SysGet, monitorWorkArea, MonitorWorkArea, 1

Gui, 2: -DPIScale
w := 100 * A_Scaling
h := 100 * A_Scaling
x := monitorWorkAreaLeft + 100 * A_Scaling
y := monitorWorkAreaTop + 100 * A_Scaling
Gui, 2: Show, x%x% y%y% h%h% w%w%
Sticky
Posts: 10
Joined: 12 Apr 2017, 22:08

Re: [SOLVED] GUI positionning with multiple monitors (hidpi)

04 May 2017, 11:18

The above was posted in 2015. It is now 2017.

Q: have there been any updates / improvements wrt multiple monitors with different display scalings?

I am motivated:

* I regularly connect 3 external monitors to my Microsoft Surface Book, nearly always with at least 3 different scalings, but sometimes with 4.

* I am also constantly undocking and docking my SurfBook, and I like to change the display scalings when I do so: I need different scalings when I am using the SurfBook as a laptop than I do when it is docked at my treadmill desk, and when it is doced at a regular desk. In some of these the distance between my eyes and the various displays differs greatly, so I adjust the display scaling to make readable.

* I dislike the term "DPIscale". I prefer the more generic term "Display Scaling".

Sometimes I try to arrange for all of my displays to be have consistent DPIscaling, so that windows stay the same size when moved from one display to another, when measured om the display surface. But that only works well when the displays are arranged in an arc at roughly the same distance from my eyes.

When I cannot arrange such consistent distances, I sometimes prefer angular consistency, so that windows appear the same size from my usual point of view - from where I am sitting, or standing and walking on my treadmill desk. I.e. so that windows subtend the same angles - not DPI "Dots Per Inch", but "Dots per Minute of Arc" or "Dots per Steradian" (solid angle).

And, frankly, more and more I do not bother for such consistency at all. It gives up too much useful detail on the displays that have a large number of useful pixels if adjusted for the lowest pixel count monitors, and a converse problem. Oftentimes my main work is done on a large but relatively low pixel count display (e.g 30" WQXGA 2560x1600), and I use a physically small but high resolution display like the SurfBook's built-in LCD (3000x2000 13.5", 267dpi) as a "holding area" for windows I am not currently using, so I *want* them to be shrunk to near invisibility when I move stuff to them.

More often, I am simply using the different monitors for different purposes - when at my treadmill desk, my SurfBook may be folded in tablet mode right above my keyboard as a touch button pad - rather like the MacBook Touch Bar, but much larger.

* Finally, I occasionally want separate scaling for X and Y. Although Windows only has a single scale factor, assuming square pixels, reality is not always so nice:

In all of these display scaling games, I have often run into the need to change not just the display scaling, but also the "Display Resolution" - e.g. 3000x2000 -> soe standard resolution like 1920x1200 or 1920x1440. Note the different aspect ratios. Some monitors adjust vertical and horizontal scan, leaving blank spaces at top, biottom, or side, while others do built-in scaling and can use all the glass - at the cost of some distortion. Often I am forced to do this because of the limitations of various display adapters (e.g. display may be WQXGA, but only accepts HDMI, and my SurfBook only outputs miniDisplayPort, I don't have an adapter (more likely, am already using the adapter somewhere else), so I use a USB display adapter that can only drive DVI, or ...).

---

I mention the above mainly just to explain why I am quite interested in per-display scaling. Possibly even for separate X and Y.

---

From the above, it looks like the BKM is to disable the old inflexible AHK DPIscaling, create a reference window in pre-scaled coordinates 100x100, and then read back its dimensions in post-scaled coordinates, and then to do all of the scaling oneself. For each display. Whenever the display configuration changes (e.g. when docking, undocking, unplugging monotors, etc.). I'm okay with that, although I would prefer to be able to read the display scaling from the OS for any particular position at any point in time, e.g. at the time I do a MouseGetPos.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: CoffeeChaton, Google [Bot], ntepa, wjt936826577 and 161 guests