CaretGetPos wrong coordinates

Report problems with documented functionality
Descolada
Posts: 1141
Joined: 23 Dec 2021, 02:30

CaretGetPos wrong coordinates

Post by Descolada » 19 Jan 2024, 00:28

CaretGetPos reports incorrect screen coordinates if the screen scaling is not 100% and the caret is in a DPI unaware window (which, albeit rare these days, happened to me). Apparently GetCaretPos doesn't participate in DPI-virtualization and it seems that the returned values are in system-DPI until the control client area the caret is located in, at which point the coordinates are reported in DPI 96. I am not 100% sure of this though - DPI is hard.

Run the following scripts in a computer where the screen scaling is higher than 100% (in my computer, 150%). F1 moves the cursor to incorrect coordinates, F2 to correct coordinates:

TestGui.ahk:

Code: Select all

#Requires AutoHotkey v2

CreateGui(A_Args.Length ? A_Args[1] : -1)

CreateGui(context) {
    static g
    if IsSet(g)
        g.Destroy()
    DllCall("SetThreadDpiAwarenessContext", "ptr", context, "ptr")
    g := Gui()
    g.AddText(, "DPIAwareness test GUI")
    g.OnEvent("Close", (*) => ExitApp())
    g.AddEdit("w300 h200", "`n`n`n`nPress F1 to move mouse to caret")
    g.AddText(,"DPI awareness: ")
    g.AddRadio("YP" (context = -1 ? " Checked" : ""), "Unaware").OnEvent("Click", (*) => (Run('"' A_AhkPath '" "' A_ScriptFullPath '" -1'), ExitApp()))
    g.AddRadio("YP" (context = -2 ? " Checked" : ""), "System").OnEvent("Click", (*) => (Run('"' A_AhkPath '" "' A_ScriptFullPath '" -2'), ExitApp()))
    g.AddRadio("YP" (context = -3 ? " Checked" : ""), "Per-monitor").OnEvent("Click", (*) => (Run('"' A_AhkPath '" "' A_ScriptFullPath '" -3'), ExitApp()))
    g.Show()
}
MoveMouseToCaret.ahk:

Code: Select all

#Requires AutoHotkey v2

CoordMode "Caret", "Screen"
CoordMode "Mouse", "Screen"
F1::CaretGetPos(&caretX, &caretY), MouseMove(caretX, caretY)
F2::{
    CaretGetPos(&caretX, &caretY)
    if (A_ScreenDPI != 96 && !DllCall("GetAwarenessFromDpiAwarenessContext", "ptr", DllCall("GetWindowDpiAwarenessContext", "ptr", hWnd := WinExist("A"), "ptr"), "int")) { ; if DPI-unaware then caret coordinates are system DPI until the the client area of the control in which the caret is located in, at which point the DPI is 96
        WinGetPos(&ctrlX, &ctrlY,,, ControlGetFocus())
        caretX := ctrlX + (caretX - ctrlX) * A_ScreenDPI // 96
        , caretY := ctrlY + (caretY - ctrlY) * A_ScreenDPI // 96
    }
    MouseMove(caretX, caretY)
}

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

Re: CaretGetPos wrong coordinates

Post by lexikos » 22 Mar 2024, 19:54

I did a bunch of testing, but I forget the details as it was several months ago. There are some configurations which aren't covered by your workaround. IIRC, it is when there are two screens with different scaling settings, both greater than 100%. I didn't find a feasible way to detect or calculate the scaling required.

I think I might have chosen not to post at the time because per-monitor DPI awareness had to be enabled with SetThreadDpiAwarenessContext before the CaretGetPos issue was relevant, since one or more of the other functions didn't work in any meaningful way without it.

Descolada
Posts: 1141
Joined: 23 Dec 2021, 02:30

Re: CaretGetPos wrong coordinates

Post by Descolada » 23 Mar 2024, 01:20

@lexikos the F2 hotkey did not account for multiple monitors both greater than 100% scaling. The following should account for that as well:

Code: Select all

F3::{
    CaretGetPos(&caretX, &caretY)
    DpiAwareness := DllCall("GetAwarenessFromDpiAwarenessContext", "ptr", DllCall("GetWindowDpiAwarenessContext", "ptr", hWnd := WinExist("A"), "ptr"), "int")
    WindowDpi := DllCall("GetDpiForWindow", "ptr", hWnd, "uint")
    hMonitor := DllCall("MonitorFromWindow", "ptr", hWnd, "int", 2, "ptr")
    MonitorDpi := (DllCall("Shcore.dll\GetDpiForMonitor", "ptr", hMonitor, "int", 0, "uint*", &dpiX:=0, "uint*", &dpiY:=0), dpiX)
    if (WindowDpi != MonitorDpi) {
        WinGetPos(&ctrlX, &ctrlY,,, ControlGetFocus())
        caretX := ctrlX + (caretX - ctrlX) * MonitorDpi // WindowDpi
        , caretY := ctrlY + (caretY - ctrlY) * MonitorDpi // WindowDpi
    }
    MouseMove(caretX, caretY)
}
but it is not relevant unless thread DPI awareness is per-monitor aware.

The F3 hotkey version caters to two situations (assuming system-aware DPI context, the default):
1) Screen scaling is not 100% and the GUI is DPI unaware, in which case the GUI operates in 100% scaling and CaretGetPos reports the coordinates accordingly
2) The script running F3 hotkey is started in one scaling (eg 150%), then the monitor is switched to another scaling (eg 100%), in which case the script is still operating in 150% scaling, and both CaretGetPos and GetDpiForMonitor will apparently still report 150% scaling


If this isn't worth fixing (and probably isn't, as DPI unaware apps are rare, and so is switching DPI while a script is running), perhaps the documentations for CaretGetPos could be improved (@Ragnar)?

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

Re: CaretGetPos wrong coordinates

Post by lexikos » 23 Mar 2024, 01:50

Am I missing something, or are the GetWindowDpiAwarenessContext and GetAwarenessFromDpiAwarenessContext calls unnecessary?

Can someone confirm that the issue with CaretGetPos exists on Windows >=8.1 <1607? If so, I'm not expecting there to be a fix for those versions. GetDpiForMonitor (like per-monitor awareness in general) is available on Windows 8.1 and later, but GetDpiForWindow requires Windows 10 version 1607 or later.

Descolada
Posts: 1141
Joined: 23 Dec 2021, 02:30

Re: CaretGetPos wrong coordinates

Post by Descolada » 23 Mar 2024, 02:04

Yes, they are unnecessary. I was considering checking for DpiAwareness > 1 before doing any further checks when I was playing around with a per-monitor aware script, but if the script is system-aware then it wouldn't be enough. I accidentally left it in and that line can be removed...

A partial workaround for GetDpiForWindow not being available in Win 8.1 could be using GetProcessDpiAwareness, which is available.

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

Re: CaretGetPos wrong coordinates

Post by lexikos » 23 Mar 2024, 06:13

Last time I read it, the remarks for GetDpiForWindow didn't seem to fully explain how the value is determined. I think I had a cognitive disconnect between "The system DPI" and what it actually means on Windows 10: the baseline DPI for the target process. On Windows 8.1, I think the system DPI is the same for all processes in a session (changes take effect when you log off), so GetProcessDpiAwareness and A_ScreenDPI (or equivalent) would be sufficient.

Post Reply

Return to “Bug Reports”