determine if scaling is not 100% for monitor of a window, change PerMonitorSettings in registry, reflect change, verify

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
somethingfly
Posts: 38
Joined: 23 Mar 2015, 17:23

determine if scaling is not 100% for monitor of a window, change PerMonitorSettings in registry, reflect change, verify

Post by somethingfly » 19 Jul 2017, 06:31

So, recently I realized my code didn't work on my laptop screen whenever there was a pixelsearch or imagesearch, though these worked fine when I was using external monitors. Turns out my laptop display defaults to 150%, further it is pretty common with modern laptops. Manually changing the scaling to 100% works, but I want to be able to do this automatically for other people who might be using a laptop.

To make it more annoying, when a monitor makes 150% its default, it's really hard to determine when a display is doing this. The A_ScreenDPI will still say it is 96. Currently, my only way of telling it's not really 96 is to do an imagesearch for the application icon in the window bar, and seeing if it fails. I'm hoping there's a better way.

I do know of a way to change it. In HKCU\Control Panel\Desktop\PerMonitorSettings\ I have three keys for three monitors (not sure if they get created unless you try to change the scaling at least once) based on I believe DeviceID. For each, the DpiValue is 0. But for my external monitors a DpiValue of 0 means 100%, and for my laptop screen 0 means 150%. It turns out I need to change the value for the key for my laptop screen to either FFFFFFFE or FFFFFFFF to change the scaling to 100%, and for me it's FFFFFFFE (it's really stupid, look at flashman's response here: https://community.spiceworks.com/topic/ ... ay-scaling ). (Another guy says you need to change also in HKLM\System\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors but I haven't noticed that, https://www.reddit.com/r/Batch/comments ... aling_via/ .)

I'd really like to know /which/ DeviceID to use, but I can't seem to figure out what DeviceIDs (per this permonitorsettings) are even connected to my system, let alone the one that's currently doing a non-100% scaling with the window I need. The only scripts I've managed to get to work are "just me"'s from https://autohotkey.com/board/topic/9453 ... h-unicode/ and "learning one"'s from https://autohotkey.com/board/topic/7903 ... on-needed/ but neither are exactly what I need.

Then, to get it to actually change the display scaling without having to log out and log back in, I can think of nothing but the relatively non-elegant method of a sequence of windows-key-P, up arrow, enter, esc, window-key-P, down arrow, enter, esc.

So far, my best option seems to do an imagesearch for the icon in the window bar, if fails, loop through the registry for a PerMonitorSettings\%DeviceID% (again, hoping the key exists already, I don't know if it only got created when I manually changed it for my laptop screen the first time), if it has a 0 for DpiValue, try FFFFFFFE, then windows-key-p sequence, then imagesearch again, if it fails, try FFFFFFFF, then windows-key-p, then imagesearch, if it fails, go back 0, then go to the next %DeviceID%, etc.

There's got to be a better way to: A) determine if the scaling is not 100% on any specific monitor that has a specific window (e.g. notepad), B) determine which key has the value I need to change in HKCU\Control Panel\Desktop\PerMonitorSettings\ (creating it if needed), C) change the value to FFFFFFFE or FFFFFFFF and have the OS reflect the change (without logging out/in), and D) verify that the necessary monitor is indeed at 100%. Alternatively, you can show me how to get imagesearch and pixelsearch to work when scaling is changed to, say, 150% (though sifting through ahk forums suggests, at least for imagesearch, that it's a lost cause).


somethingfly
Posts: 38
Joined: 23 Mar 2015, 17:23

Re: determine if scaling is not 100% for monitor of a window, change PerMonitorSettings in registry, reflect change, ver

Post by somethingfly » 19 Jul 2017, 15:32

Thanks Bobo. So, I had come across those, but I never tried them because so many people complained they didn't work on Windows10. That's my fault for not at least trying, because it does somewhat work. I actually worked off the "psuedocode" at the end of https://technet.microsoft.com/en-us/lib ... spx#system .

BUT, I have to log out and log back in after I do the ^1 hotkey to see the change (hence that part about "Require logoff/logon" in that link). (All the extra stuff with the ini file in my code is because I need to revert it much later, per the ^2.) I need someway to force the registry change to come in effect that does not involve actually logging off. Perhaps something akin to the dll call to ChangeDisplaySettingsA that "garry" references here https://autohotkey.com/boards/viewtopic.php?t=10626 (I think I read somewhere that changing resolution can sometimes take the place of a full log off and log on). Can someone help with something to end of the ^1 that doesn't require logging off and log but will make the OS display the new dpi? If that doesn't work, then I'm back to having to change the permonitorsettings, because that at least can be put into effect using windows-key-p sequence.

Thanks again.

Code: Select all

^1::
ChangeReg("Win8DpiScaling",1)
ChangeReg("LogPixels",96)
return

^2::
RevertReg("Win8DpiScaling")
RevertReg("LogPixels")
return

ChangeReg(ValueName,CorrectValue)
{
    RegRead, Value, HKEY_CURRENT_USER\Control Panel\Desktop, %ValueName%
    If (Value != CorrectValue)
    {
        IniWrite, %ValueName%, Reg.ini, DPI, %ValueName%Name
        IniWrite, %Value%, Reg.ini, DPI, %ValueName%
        RegWrite, REG_DWORD, HKEY_CURRENT_USER\Control Panel\Desktop, %ValueName%, %CorrectValue%
    }
    Return
}

RevertReg(ValueName)
{
    IniRead, ValueName0, Reg.ini, DPI, %ValueName%Name, %A_Space%
    IniRead, Value, Reg.ini, DPI, %ValueName%, %A_Space%.
    if (ValueName0) and (Value = "")
        RegDelete, HKEY_CURRENT_USER\Control Panel\Desktop, %ValueName0%
    else if (ValueName0)
        RegWrite, REG_DWORD, HKEY_CURRENT_USER\Control Panel\Desktop, %ValueName0%, %Value%
    FormatTime, TimeString, , yyyyMMdd'_'HHmmss
    newfile := "Reg" TimeString ".ini"
    Filemove, Reg.ini, %newfile%
    Return
}

BoBo
Posts: 6564
Joined: 13 May 2014, 17:15

Re: determine if scaling is not 100% for monitor of a window, change PerMonitorSettings in registry, reflect change, ver

Post by BoBo » 19 Jul 2017, 16:03

To kill explorer.exe and restart its process is AFAIK the equivalent of a restart => Process

somethingfly
Posts: 38
Joined: 23 Mar 2015, 17:23

Re: determine if scaling is not 100% for monitor of a window, change PerMonitorSettings in registry, reflect change, ver

Post by somethingfly » 19 Jul 2017, 17:13

Nope, tried this script, as well as RestartExplorer functions from "mikeoerti", "ZoltanZan", and "SKAN" in https://autohotkey.com/board/topic/9390 ... icial-way/ and https://autohotkey.com/boards/viewtopic.php?t=905, as well as opening task manager at killing explorer.exe manually. I don't know if they don't work because of Windows 10 or because of the nature of the registry change.

To reproduce in a Windows 10 environment (multiple monitors may or may not be necessary), start with my original values in the registry at HKEY_CURRENT_USER\Control Panel\Desktop of Win8DpiScaling at 0 and no entry at all for LogPixels, and at least one monitor at a 150%. Then change Win8DpiScaling to 1 and create a dword for LogPixels at 96. You'll notice the monitor at 150% will stay that way until you actually ctrl-alt-del sign out and sign in. At which point that monitor will go to 100%.

Code: Select all

^1::
ChangeReg("Win8DpiScaling",1)
ChangeReg("LogPixels",96)
RestartExplorer()
return

^2::
RevertReg("Win8DpiScaling")
RevertReg("LogPixels")
RestartExplorer()
return

ChangeReg(ValueName,CorrectValue)
{
    RegRead, Value, HKEY_CURRENT_USER\Control Panel\Desktop, %ValueName%
    If (Value != CorrectValue)
    {
        IniWrite, %ValueName%, Reg.ini, DPI, %ValueName%Name
        IniWrite, %Value%, Reg.ini, DPI, %ValueName%
        RegWrite, REG_DWORD, HKEY_CURRENT_USER\Control Panel\Desktop, %ValueName%, %CorrectValue%
    }
    Return
}

RevertReg(ValueName)
{
    IniRead, ValueName0, Reg.ini, DPI, %ValueName%Name, %A_Space%
    IniRead, Value, Reg.ini, DPI, %ValueName%, %A_Space%.
    if (ValueName0) and (Value = "")
        RegDelete, HKEY_CURRENT_USER\Control Panel\Desktop, %ValueName0%
    else if (ValueName0)
        RegWrite, REG_DWORD, HKEY_CURRENT_USER\Control Panel\Desktop, %ValueName0%, %Value%
    FormatTime, TimeString, , yyyyMMdd'_'HHmmss
    newfile := "Reg" TimeString ".ini"
    Filemove, Reg.ini, %newfile%
    Return
}

RestartExplorer()
{
    Process, Close, explorer.exe
    Loop
    {
        Process, Exist, explorer.exe
        if errorLevel
            Sleep 500
        else
            break
    }
    Run, explorer.exe
    Loop
    {
        Process, Exist, explorer.exe
        if !errorLevel
            Sleep 500
        else
            break
    }
}

somethingfly
Posts: 38
Joined: 23 Mar 2015, 17:23

Re: determine if scaling is not 100% for monitor of a window, change PerMonitorSettings in registry, reflect change, ver

Post by somethingfly » 19 Jul 2017, 18:18

Again, I think my best bet would be to find out the DeviceID (as they appear in HKLM\System\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors) of the monitor that has a particular window. If I have that I can create the key with the one of two options needed in HKCU\Control Panel\Desktop\PerMonitorSettings. Because that setting, at least I know I can change by just using a Win-P sequence (unfortunately restarting explorer doesn't work for that either).

somethingfly
Posts: 38
Joined: 23 Mar 2015, 17:23

Re: determine if scaling is not 100% for monitor of a window, change PerMonitorSettings in registry, reflect change, ver

Post by somethingfly » 20 Jul 2017, 13:15

Anyone know how to run something like the powershell "Get-CimInstance -Namespace root\wmi -ClassName WmiMonitorBasicDisplayParams" from autohotkey? Per https://blogs.technet.microsoft.com/hey ... formation/ I want to run that command and then copy all the strings that start with "Device\" and end in "\" and save as variables? (FYI, I meant by actually running something like Get-CimInstance and/or Get-WmiObject within ahk, but since most computers nowadays have powershell, I find this also works, so it's no big deal:

Code: Select all

Clipboard =
Run PowerShell.exe -Command &{((Get-CimInstance -Namespace root\wmi -ClassName WmiMonitorBasicDisplayParams) | Out-String).Trim() | Set-Clipboard},, hide
Clipwait
msgbox, %clipboard%
)

somethingfly
Posts: 38
Joined: 23 Mar 2015, 17:23

Re: determine if scaling is not 100% for monitor of a window, change PerMonitorSettings in registry, reflect change, ver

Post by somethingfly » 22 Jul 2017, 07:31

Alright, here's my code. It works, it takes about 20 seconds on three monitors, much faster on one, handles scenarios where the default is 150% or default is 100% and any permutation of 150% and 100% scaling for monitors (as far as I can tell). But I'm sure someone can improve upon it greatly. Obviously, if you need the RegValOrig.ini to revert (as I will) then that's a simple enough task, and I'll code for that later for myself, but wasn't part of my question. Frankly, I don't need this code so much that I should have spent all this time on it, but I hate to start a thread and not provide at least some closure for anyone who might attempt this later, and I'm sure there are some who can use either this code or a cleaned-up version. Again, if people can improve upon this code, please do so and post it here. Note, I used some of "shinywong"'s from https://autohotkey.com/board/topic/6946 ... h-monitor/ as well as "shadowphoenix"'s from https://autohotkey.com/boards/viewtopic.php?t=12684 , who himself/herself says adopted it from someone else (I think it was "Lexikos" did it first in ansi and then someone made the change to unicode, whatever, I'll leave it to the ahknthopologists to dig through it)...

Code: Select all

^3::
SysGet, MonitorCount, MonitorCount
Loop, %MonitorCount%
{
    IfWinExist, test%a_index%.txt - Notepad
        WinClose
}
Loop, %MonitorCount%
{
    MonNum := a_index
    FileAppend, , test%MonNum%.txt
    Run, Notepad.exe test%MonNum%.txt
    WinWait, test%MonNum%.txt - Notepad
    WinActivate
    WinWaitActive
    WinGet, windowHandle, ID, test%MonNum%.txt - Notepad
    Loop
    {
        GetMonitorIndexFromWindow(windowHandle, MonitorIndex)
        if (MonitorIndex = MonNum)
            break
        Send, #+{right}
    }
    WinMaximize
}
TestMonitors(MonitorCount,notfoundtotal1)
if !notfoundtotal1
    Goto, CleanUp
currentwidth := a_screenwidth
currentheight := a_screenheight
tempwidth := 1.5*currentwidth
tempheight := 1.5*currentheight
ChangeResolution(tempwidth, tempheight)
if (currentwidth = a_screenwidth)
    doagainlater := "y"
else
    ChangeResolution(currentwidth, currentheight)
ifnotexist, RegValOrig.ini
    GetRegValOrig()
Clipboard =
Run PowerShell.exe -Command &{((Get-CimInstance -Namespace root\wmi -ClassName WmiMonitorBasicDisplayParams) | Out-String).Trim() | Set-Clipboard},, hide
Clipwait
Loop, %MonitorCount%
{
   RegExMatch(clipboard,"DISPLAY\\(.*?)\\", m)
   MonitorPossibleName%a_index% := m1
   StringReplace, clipboard, clipboard, DISPLAY\
}
ResetDisplays(currentwidth,currentheight)
Loop, %MonitorCount%
{
   MonPosName = % MonitorPossibleName%a_index%
   Loop
   {
       IniRead, DeviceID, RegValOrig.ini, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors, Key%a_index%, %A_Space%
       if !DeviceID
           break
       IniRead, YesOrNo, RegValOrig.ini, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors, Key%a_index%Used, %A_Space%
       StringGetPos, Pos, DeviceID, %MonPosName%
       If !YesOrNo and (Pos = 0)
           IniWrite, Yes, RegValOrig.ini, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors, Key%a_index%Used
       Else
           Continue
       Loop
       {
           IniRead, NameNum, RegValOrig.ini, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, Name%a_index%, %A_Space%
           if !NameNum
               break
           if NameNum = DpiValue
               IniRead, ValueNum, RegValOrig.ini, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, Value%a_index%
       }
       if (ValueNum) and (ValueNum != 0)
       {
           RegWrite, REG_DWORD, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, DpiValue, 0
           ResetDisplays(currentwidth,currentheight)
           TestMonitors(MonitorCount,notfoundtotal2) 0
           if (notfoundtotal2 < notfoundtotal1)
               break
           else
               RegWrite, REG_DWORD, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, DpiValue, %ValueNum%
       }
       if NameNum != 4294967294
       {
           RegWrite, REG_DWORD, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, DpiValue, 4294967294
           RegRead, CurrentValue, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, DpiValue
           ResetDisplays(currentwidth,currentheight)
           TestMonitors(MonitorCount,notfoundtotal2)
           if (notfoundtotal2 < notfoundtotal1)
               break
           else if (!ValueNum)
               RegDelete, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%
           else
               RegWrite, REG_DWORD, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, DpiValue, %ValueNum%
       }
       if NameNum != 4294967295
       {
           RegWrite, REG_DWORD, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, DpiValue, 4294967295
           ResetDisplays(currentwidth,currentheight)
           TestMonitors(MonitorCount,notfoundtotal2)
           if (notfoundtotal2 < notfoundtotal1)
               break
           else if (!ValueNum)
               RegDelete, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%
           else
               RegWrite, REG_DWORD, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, DpiValue, %ValueNum%
       }
   }
   if !notfoundtotal2
      break
   if notfoundtotal2 < notfoundtotal1
      notfoundtotal1 := notfoundtotal2
}
FormatTime, TimeString, , yyyyMMdd'_'HHmmss
newfile := "RegValOrig" TimeString ".ini"
Filemove, RegValOrig.ini, %newfile%
if doagainlater
    ChangeResolution(tempwidth, tempheight)
CleanUp:
Loop, %MonitorCount%
{
    IfWinExist, test%a_index%.txt - Notepad
        WinClose
    FileDelete, test%a_index%.txt
}
return

GetMonitorIndexFromWindow(windowHandle, ByRef monitorIndex)
{
    ; Starts with 1.
    monitorIndex := 1
    VarSetCapacity(monitorInfo, 40)
    NumPut(40, monitorInfo)
    if (monitorHandle := DllCall("MonitorFromWindow", "uint", windowHandle, "uint", 0x2)) && DllCall("GetMonitorInfo", "uint", monitorHandle, "uint", &monitorInfo) 
    {
        monitorLeft   := NumGet(monitorInfo,  4, "Int")
        monitorTop    := NumGet(monitorInfo,  8, "Int")
        monitorRight  := NumGet(monitorInfo, 12, "Int")
        monitorBottom := NumGet(monitorInfo, 16, "Int")
        workLeft      := NumGet(monitorInfo, 20, "Int")
        workTop       := NumGet(monitorInfo, 24, "Int")
        workRight     := NumGet(monitorInfo, 28, "Int")
        workBottom    := NumGet(monitorInfo, 32, "Int")
        isPrimary     := NumGet(monitorInfo, 36, "Int") & 1
        SysGet, monitorCount, MonitorCount
        Loop, %monitorCount%
        {
            SysGet, tempMon, Monitor, %A_Index%
            ; Compare location to determine the monitor index.
            if ((monitorLeft = tempMonLeft) and (monitorTop = tempMonTop) and (monitorRight = tempMonRight) and (monitorBottom = tempMonBottom))
            {
                monitorIndex := A_Index
                break
            }
        }
    }
    return
}

GetRegValOrig()
{
    ValueNum := 1
    LastSubKey =
    Loop, Reg, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings, VR
    {
        if A_LoopRegSubKey = LastSubKey
            ValueNum++
        RegRead, Value
        IniWrite, %A_LoopRegName%, RegValOrig.ini, %A_LoopRegKey%\%A_LoopRegSubKey%, Name%ValueNum%
        IniWrite, %A_LoopRegType%, RegValOrig.ini, %A_LoopRegKey%\%A_LoopRegSubKey%, Type%ValueNum%
        IniWrite, %Value%, RegValOrig.ini, %A_LoopRegKey%\%A_LoopRegSubKey%, Value%ValueNum%
        LastSubKey := A_LoopRegSubKey
    }
    Loop, Reg, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors, K
       IniWrite, %A_LoopRegName%, RegValOrig.ini, %A_LoopRegKey%\%A_LoopRegSubKey%, Key%A_index%
}

TestMonitors(MonitorCount,ByRef notfoundtotal)
{
    notfoundtotal := 0
    Loop, %MonitorCount%
    {
        WinActivate, test%a_index%.txt - Notepad
        PixelSearch, , , 0, 0, 60, 60, 0xB6A76A, 0, Fast
        if errorlevel
            notfoundtotal++
    }
}


ResetDisplays(currentwidth,currentheight)
{
    ; if dpi for primary is already 150%, changing res to what it thinks it is, may shrink res by 125%
    ; e.g. if width is 1920, sys may think it is 1280, changing to 1280 may make sys think it is 1024
    ; 1280/1024 = 1.25, thus need 1.5 x 1280 = 1920
    ChangeResolution(currentwidth, currentheight)
    if ((currentwidth/a_screenwidth) = 1.25)
    {
        newwidth := 1.5*currentwidth
        newheight := 1.5*currentheight
        ChangeResolution(newwidth, newheight)
    }
    else
    {
        if (currentwidth != 1280) and (currentheight != 720)
        {
            altwidth := 1280
            altheight := 720
        }
        else
        {
            altwidth := 800
            altheight := 600
        }
        ChangeResolution(altwidth, altheight)
        ChangeResolution(currentwidth, currentheight)
    }
    return
}

ChangeResolution(Screen_Width := 1920, Screen_Height := 1080, Color_Depth := 32)
{
	VarSetCapacity(Device_Mode,156,0)
	NumPut(156,Device_Mode,36) 
	DllCall( "EnumDisplaySettingsA", UInt,0, UInt,-1, UInt,&Device_Mode )
	NumPut(0x5c0000,Device_Mode,40) 
	NumPut(Color_Depth,Device_Mode,104)
	NumPut(Screen_Width,Device_Mode,108)
	NumPut(Screen_Height,Device_Mode,112)
	Return DllCall( "ChangeDisplaySettingsA", UInt,&Device_Mode, UInt,0 )
}
Return

KeypressGuy
Posts: 16
Joined: 17 Jul 2017, 16:55

Re: determine if scaling is not 100% for monitor of a window, change PerMonitorSettings in registry, reflect change, ver

Post by KeypressGuy » 24 Jul 2017, 12:43

I applaud your gumption! and will be giving your code a test-drive.

I have had the same problems with portability and have often wished for a solution, but never did much about it.

For me, it's not just scaling. Brightness and color settings differing from machine to machine cause my ImageSearches not to match. Rather than allowing wide color-variation on ImageSearch, I would prefer to preset/reset the brightness/resolution/ClearType for the duration of the script, then set them back it to where I had found them. I reckon this could get complicated since video card makers often provide their own control panel offering custom settings for fine-tuning colors, brightness, antialiasing, etc.

Until now I have kept my display set to my Windows' defaults and require computers I send scripts to to match... often with disappointing results.

somethingfly
Posts: 38
Joined: 23 Mar 2015, 17:23

Re: determine if scaling is not 100% for monitor of a window, change PerMonitorSettings in registry, reflect change, ver

Post by somethingfly » 02 Aug 2017, 18:24

I did a little cleaning up, as well as create a hotkey (^4) to revert back to whatever the original settings were. Also, put in commenting. Hope this helps some people.

Code: Select all

^3::
; get total number of monitors
SysGet, MonitorCount, MonitorCount
; check if test notepad windows are open from a previous test, close if so
Loop, %MonitorCount%
{
    IfWinExist, test%a_index%.txt - Notepad
        WinClose
}
; for each monitor open a test notepad window, which are blank and just for trying to find a pixel in the notepad icon
Loop, %MonitorCount%
{
    MonNum := a_index
    FileAppend, , test%MonNum%.txt
    Run, Notepad.exe test%MonNum%.txt, max
    WinWait, test%MonNum%.txt - Notepad
    WinActivate
    WinWaitActive
    WinGet, windowHandle, ID, test%MonNum%.txt - Notepad
; makes sure test1.txt is on monitor 1, and test2 is on monitor 2, etc., windows-key-right moves to another monitor
    Loop
    {
        GetMonitorIndexFromWindow(windowHandle, MonitorIndex)
        if (MonitorIndex = MonNum)
            break
        Send, #+{right}
    }
    WinMaximize
}
; see how many currently can't find the pixel in the notepad icon
TestMonitors(MonitorCount,notfoundtotal1)
; if pixel can be found in each, then presumably dpi scale is 100 for each, go to the end of the script
if !notfoundtotal1
    Goto, CleanUp
; get what system thinsk is width/height of primary monitor.  If primary monitor is at 150% dpi, then this will be by 1.5x smaller than it really is
currentwidth := a_screenwidth
currentheight := a_screenheight
; create a temporary width/height that would be the true resolution (if dpi scale is 150%)
tempwidth := 1.5*currentwidth
tempheight := 1.5*currentheight
; change it to this temporary resolution
ChangeResolution(tempwidth, tempheight)
; if the system still reads width as 1.5x less than it was just set it to be, either that resolution can't be done or dpi scale is 150%
; in the latter case, when primary monitor later gets fixed later to 100%, later increasing resolution 1.5x should set it to current, true resolution.
if (currentwidth = a_screenwidth)
    doagainlater := "y"
; but if the system does read a resolution change, then there's a good chance primary monitor is at 100% and meant to be at this lower resolution
else
    ChangeResolution(currentwidth, currentheight)
; checks to see if RegValOrig.ini already exists, if so, renames the file in case needed later
ifexist, RegValOrig.ini
{
   FormatTime, TimeString, , yyyyMMdd'_'HHmmss
   newfile := "RegValOrig" TimeString ".ini"
   Filemove, RegValOrig.ini, %newfile%
}
;Create a an ini of the current registry values for ScaleFactors and PerMonitorSettings
GetRegVal()
; rename the ini to "orig" as another one will be created later if reverting with other hotkey.
Filemove, RegVal.ini, RegValOrig.ini
; save clipboard and run powershell for monitor info piped into the clipboard
; powershell from here: https://blogs.technet.microsoft.com/heyscriptingguy/2013/10/03/use-powershell-to-discover-multi-monitor-information/
ClipOrig := ClipboardAll
Clipboard =
Run PowerShell.exe -Command &{((Get-CimInstance -Namespace root\wmi -ClassName WmiMonitorBasicDisplayParams) | Out-String).Trim() | Set-Clipboard},, hide
Clipwait
; regex's to get IDs of the monitors (as seen in Device Manager / Details / Hardware Ids, e.g. LEN40A9, which defaults to 150% scaling) currently connected
; the order doesn't seem to correspond directly to primary, secondary, etc. order from sysget, hence MonitorPossibleName variable
Loop, %MonitorCount%
{
   RegExMatch(clipboard,"DISPLAY\\(.*?)\\", m)
   MonitorPossibleName%a_index% := m1
   StringReplace, clipboard, clipboard, DISPLAY\
}
; restore clipboard, as all we want is monitorpossiblename1, monitorpossiblename2, etc.
Clipboard := ClipOrig
; do a reset of displays, which changes the primary display to a value, and then returns it to current, may not be necessary at this point in script
ResetDisplays(currentwidth,currentheight)
; do registry changes on each monitor, i.e. loop per the monitor count, or until pixel search works on each test notepad window, whichever comes first
Loop, %MonitorCount%
{
; start with first monitor from the powershell
   MonPosName = % MonitorPossibleName%a_index%
   Loop
   {
; read from ini which read from the registry the deviceIDs from ScaleFactors
; ScaleFactors IDs are the same as what can be used in PerMonitorSettings, and include DevicesIDs of any monitor that has ever been connected (I think)
; these IDs start with the string from the powershell (e.g. LEN40A90) but then have a much longer string that follows, determined by some method I don't know
       IniRead, DeviceID, RegValOrig.ini, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors, Key%a_index%, %A_Space%
       if !DeviceID
           break
; Once a DeviceID has been used it'll be marked in the ini, so it can be skipped next time
       IniRead, YesOrNo, RegValOrig.ini, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors, Key%a_index%Used, %A_Space%
; if the powershell is indeed it at the beginning of the scalefactor id, the position will be 0.
       StringGetPos, Pos, DeviceID, %MonPosName%
; not used before, and has the powershell id at the beginning, then mark as used and use it
       If !YesOrNo and (Pos = 0)
           IniWrite, Yes, RegValOrig.ini, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors, Key%a_index%Used
       Else
           Continue
; empty the variables that will be used in the subsequent internal loop, in case this is a later external loop
       HasNonDpiValueName =
       ValueNum =
; Go through and see if PerMonitorSettings have already been set for this deviceID--until you set them once using "Display settings" they do not exist
       Loop
       {
           IniRead, NameNum, RegValOrig.ini, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, Name%a_index%, %A_Space%
           if !NameNum
               break
; I'm not sure if there are other possible values in the key than DpiValue, could never find that out, so first need to verify the name is DpiValue 
           if NameNum = DpiValue
               IniRead, ValueNum, RegValOrig.ini, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, Value%a_index%
; if there are other values than DpiValue, marking it, so that the entire key doesn't get deleted if we need to delete DpiValue
           else
               HasNonDpiValueName := "y"
       }
; Since I'm only testing three possible values 0, -2 (4294967294), and -1 (4294967295), only doing this three times.
; 0 would be the default for that monitor (could be 100% or 150%, there may be others, but 150% is afaik only for high-res modern laptop screens)
; -2 would be two options above default (e.g. my lenovo defaults at 150%, but offers 125% above, -1, 100% above that, -2 -- also 175%, 1, and 200%, 2)
; -1 would be one option above default (other monitors don't have 5 options like my lenovo, and might be 100%, 150% default, 200% -- hence -1, 0, 1)
       Loop, 3
       {
           if a_index = 1
               TestValue := 0
           if a_index = 2
               TestValue := 4294967294
           if a_index = 3
               TestValue := 4294967295
; first determine that the testvalue to switch to isn't currently being used in permonitorsettings
           if (ValueNum != TestValue)
           {
; no setting at all in permonitorsettings, or a setting of 0, is the same, and results the same with !ValueNum, so if !ValueNum: no point in testing 0
               if (TestValue = 0) and (!ValueNum)
                   continue
; write the test value to the registry
               RegWrite, REG_DWORD, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, DpiValue, %TestValue%
; reset the displays (i.e. change to a different resolution, and then back, on primary) to pickup the registry change
               ResetDisplays(currentwidth,currentheight)
; test the monitors to get a total number of not-found pixel searches, if the registry change was helpful, it should switch reduce by one.
               TestMonitors(MonitorCount,notfoundtotal2)
; if it does, then no need to check the rest of the test values
               if (notfoundtotal2 < notfoundtotal1)
                   break
; if it doesn't work, and 0/null for original PerMonitorSettings, and no other values in the key for PerMonitorSettings, then delete the whole key 
               else if (!ValueNum) and (!HasNonDpiValueName)
                   RegDelete, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%
; if it doesn't work, and 0/null for original PerMonitorSettings (and presumably other values in the key) then delete just DpiValue 
               else if (!ValueNum)
                   RegDelete, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, DpiValue
; if it doesn't work, and there is a value for original PerMonitorSettings, then use that value again
               else
                   RegWrite, REG_DWORD, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, DpiValue, %ValueNum%
           }
       }
   }
; if finally pixels are found through each monitor, no need to change/test any more
   if !notfoundtotal2
      break
; but if the not-found-number has reduced, but not yet completely gone, then make the current number the number to check against in the next loop
   if notfoundtotal2 < notfoundtotal1
      notfoundtotal1 := notfoundtotal2
}
; again, if the primary monitor was one of the monitors at 150%, this should change it to the proper full resolution.
if doagainlater
    ChangeResolution(tempwidth, tempheight)
; deleting the test notepads.
CleanUp:
Loop, %MonitorCount%
{
    IfWinExist, test%a_index%.txt - Notepad
        WinClose
    FileDelete, test%a_index%.txt
}
return



^4::
; checks to see if RegValOrig.ini exists
ifnotexist, RegValOrig.ini
{
    msgbox, needs RegValOrig.ini in script directory, restore/rename file as needed and try again
    return
}
; checks to see if RegVal.ini already exists, if so, renames the file so it doesn't get overwritten (in case user needs it later)
ifexist, RegVal.ini
{
   FormatTime, TimeString, , yyyyMMdd'_'HHmmss
   newfile := "RegVal" TimeString ".ini"
   Filemove, RegVal.ini, %newfile%
}
; gets the current registry values
GetRegVal()
; section names in the ini correspond to keys in PerMonitorSettings, e.g. the devices with set dpi scales, and "ScaleFactors," which we don't need 
IniRead, SectionNamesOrig, RegValOrig.ini
IniRead, SectionNames, RegVal.ini
; first goes through the list of recently created keys
Loop, parse, SectionNames, `n
{
; skipping the ScaleFactors parent key
    if (A_LoopField = "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors")
        Continue
; selecting a key in the current registry (from the list, from the ini)
    SelKey := A_LoopField 
; clearing the variable for selected key in the old registry
    SelKeyOrig =
; going through old registry keys and seeing if any match the shelected key from the current registry
    Loop, parse, SectionNamesOrig, `n
    {
        if (A_LoopField = SelKey)
        {
; if so, set as selected key in the old registry
            SelKeyOrig := A_LoopField      
            Break
        }
    }
; if there is no matching key in the old registry, then it must be a recently created key, and thus the entire current key can be safely deleted.
    if !SelKeyOrig
        RegDelete, %SelKey%
}
; go through the list of old registry keys
Loop, parse, SectionNamesOrig, `n
{
; again, skip ScaleFactors parent key
    if (A_LoopField = "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors")
        Continue
; selecting key in the old registry
    SelKeyOrig := A_LoopField 
; clearing selected key in the current registry
    SelKey =
; going through current registry keys and seeing if any match selected key in old registry
    Loop, parse, SectionNames, `n
    {
        if (A_LoopField = SelKeyOrig)
        {
; if so, set selected key from current registry 
            SelKey := A_LoopField
; clear the ValueNum, which will correspond to a value for DpiValue if it exists in current registry
            ValueNum =
; current registry key section might have a value for DpiValue or none or (possibly, never seen) more than just DpiValue
            Loop
            {
                IniRead, NameNum, RegVal.ini, %SelKey%, Name%a_index%, %A_Space%
; loop breaks when no more names for values found
                if !NameNum
                    break
; if the name is "DpiValue" then we want the corresponding value, ValueNum
                if (NameNum = "DpiValue")
                    IniRead, ValueNum, RegVal.ini, %SelKey%, Value%a_index%
            }
            Break
        }
    }
; clear the ValueNumOrig, which will correspond to a value for DpiValue if it exists in old registry
    ValueNumOrig =
; go through old registry key section for a DpiValue, and if found set as ValueNumOrig
    Loop
    {
        IniRead, NameNumOrig, RegValOrig.ini, %SelKeyOrig%, Name%a_index%, %A_Space%
        if !NameNumOrig
            break
        if (NameNumOrig = "DpiValue")
            IniRead, ValueNumOrig, RegValOrig.ini, %SelKeyOrig%, Value%a_index%
    }
; if a value is found for DpiValue in old registry, then we add/change it in the new (creates key if needed)
    if ValueNumOrig
        RegWrite, REG_DWORD, %SelKeyOrig%, DpiValue, %ValueNumOrig%
; but if no old value is found, though there is a value in the new registry, then we delete just that value
    else if ValueNum
        RegDelete, %SelKey%, DpiValue
}
; reset displays to make the effects permanent
ResetDisplays(a_screenwidth,a_screenheight)
return


GetMonitorIndexFromWindow(windowHandle, ByRef monitorIndex)
{
    ; Starts with 1.
    monitorIndex := 1
    VarSetCapacity(monitorInfo, 40)
    NumPut(40, monitorInfo)
    if (monitorHandle := DllCall("MonitorFromWindow", "uint", windowHandle, "uint", 0x2)) && DllCall("GetMonitorInfo", "uint", monitorHandle, "uint", &monitorInfo) 
    {
        monitorLeft   := NumGet(monitorInfo,  4, "Int")
        monitorTop    := NumGet(monitorInfo,  8, "Int")
        monitorRight  := NumGet(monitorInfo, 12, "Int")
        monitorBottom := NumGet(monitorInfo, 16, "Int")
        workLeft      := NumGet(monitorInfo, 20, "Int")
        workTop       := NumGet(monitorInfo, 24, "Int")
        workRight     := NumGet(monitorInfo, 28, "Int")
        workBottom    := NumGet(monitorInfo, 32, "Int")
        isPrimary     := NumGet(monitorInfo, 36, "Int") & 1
        SysGet, monitorCount, MonitorCount
        Loop, %monitorCount%
        {
            SysGet, tempMon, Monitor, %A_Index%
            ; Compare location to determine the monitor index.
            if ((monitorLeft = tempMonLeft) and (monitorTop = tempMonTop) and (monitorRight = tempMonRight) and (monitorBottom = tempMonBottom))
            {
                monitorIndex := A_Index
                break
            }
        }
    }
    return
}

GetRegVal()
{
    ValueNum := 1
    LastSubKey =
    Loop, Reg, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings, VR
    {
        if A_LoopRegSubKey = LastSubKey
            ValueNum++
        RegRead, Value
        IniWrite, %A_LoopRegName%, RegVal.ini, %A_LoopRegKey%\%A_LoopRegSubKey%, Name%ValueNum%
        IniWrite, %A_LoopRegType%, RegVal.ini, %A_LoopRegKey%\%A_LoopRegSubKey%, Type%ValueNum%
        IniWrite, %Value%, RegVal.ini, %A_LoopRegKey%\%A_LoopRegSubKey%, Value%ValueNum%
        LastSubKey := A_LoopRegSubKey
    }
    Loop, Reg, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors, K
       IniWrite, %A_LoopRegName%, RegVal.ini, %A_LoopRegKey%\%A_LoopRegSubKey%, Key%A_index%
}

TestMonitors(MonitorCount,ByRef notfoundtotal)
{
    notfoundtotal := 0
    Loop, %MonitorCount%
    {
        WinActivate, test%a_index%.txt - Notepad
        PixelSearch, , , 0, 0, 60, 60, 0xB6A76A, 0, Fast
        if errorlevel
            notfoundtotal++
    }
}


ResetDisplays(currentwidth,currentheight)
{
    ; if dpi for primary is already 150%, changing res to what it thinks it is, will actually register as a shrink in res by 125%
    ; e.g. if width is 1920, sys may think it is 1280, and if so, changing to 1280, will make sys think it is 1024
    ; thus even though really it has reduced by 1.5 (1920/1280 = 1.5) it will register as 1.25 (1280/1024 = 1.25) and will need 1.5 x 1280 = 1920
    ChangeResolution(currentwidth, currentheight)
    if ((currentwidth/a_screenwidth) = 1.25)
    {
        newwidth := 1.5*currentwidth
        newheight := 1.5*currentheight
        ChangeResolution(newwidth, newheight)
    }
    else
    {
        if (currentwidth != 1280) and (currentheight != 720)
        {
            altwidth := 1280
            altheight := 720
        }
        else
        {
            altwidth := 800
            altheight := 600
        }
        ChangeResolution(altwidth, altheight)
        ChangeResolution(currentwidth, currentheight)
    }
    return
}

ChangeResolution(Screen_Width := 1920, Screen_Height := 1080, Color_Depth := 32)
{
	VarSetCapacity(Device_Mode,156,0)
	NumPut(156,Device_Mode,36) 
	DllCall( "EnumDisplaySettingsA", UInt,0, UInt,-1, UInt,&Device_Mode )
	NumPut(0x5c0000,Device_Mode,40) 
	NumPut(Color_Depth,Device_Mode,104)
	NumPut(Screen_Width,Device_Mode,108)
	NumPut(Screen_Height,Device_Mode,112)
	Return DllCall( "ChangeDisplaySettingsA", UInt,&Device_Mode, UInt,0 )
}
Return

KeypressGuy
Posts: 16
Joined: 17 Jul 2017, 16:55

Re: determine if scaling is not 100% for monitor of a window, change PerMonitorSettings in registry, reflect change, ver

Post by KeypressGuy » 03 Aug 2017, 10:09

This works fine on my ThinkPad with external ViewsSonic monitor. Thank you, and bravo!

What if Microsoft suddenly changes the Notepad icon?
(For the benefit of readers who haven't studied the code, the correct resolution is reached when the correctly scaled Notepad icon is detected in a maximized Notepad window)
Could one use an AHK GUI, maybe even with a custom icon, instead of Notepad in TestMonitors()

somethingfly
Posts: 38
Joined: 23 Mar 2015, 17:23

Re: determine if scaling is not 100% for monitor of a window, change PerMonitorSettings in registry, reflect change, ver

Post by somethingfly » 03 Aug 2017, 20:10

Yeah, I thought about that. And it turns out that it has changed over OS versions, though it /looks/ like it's been the same since Vista according to a google search. I suppose if it changes in the future, and "PixelSearch, , , 0, 0, 60, 60, 0xB6A76A, 0, Fast" doesn't work on the notepad on the new operating system (at 100%), find a new color that would, and make that line conditional on the A_OSVersion.

As for an AHK GUI with a custom icon, if you really want to be able to handle all previous OS's and be near-future-proof, it seems moricons.dll has been around since Windows 3.0 and still around, so I'd start with that.

somethingfly
Posts: 38
Joined: 23 Mar 2015, 17:23

Re: determine if scaling is not 100% for monitor of a window, change PerMonitorSettings in registry, reflect change, ver

Post by somethingfly » 04 Aug 2017, 15:53

Alright, per the quasi-request of using an AHK GUI, which should be slightly more "future proof" (such a fun buzzword), I changed the code to use AHK GUIs instead of notepad. The "GradientColorBand" function (for the ahknthropologists out there) seems to originated from something "SKAN" started (https://autohotkey.com/board/topic/8670 ... genie-100/ -- original code seems lost) that was then modified by "soggos" (how much I do not know, nor if that part was even part of the total code that he/she/it says got modified) on page 3 of same thread, and that I-assume-modified version then seems to get used in a post (https://autohotkey.com/boards/viewtopic.php?t=7312) which then gets modified again by "noname"--and that's the version I used.

Edit, also including maximized background GUIs that would prevent false positives (so if there are 3 monitors, would create 6 GUIs). It seems I couldn't just maximize the GUIs like I did the notepad windows. I'm not entirely sure why one gui maximized per monitor doesn't work (though one notepad maximized per monitor does), but one gui default size per monitor and another gui maximized per monitor behind it seems to work.

Code: Select all

^3::
; get total number of monitors
SysGet, MonitorCount, MonitorCount
; for each monitor open a test gui, which has that single color to find the pixel within, and a maximized background gui to prevent false-positives
MonitorCountx2 := (MonitorCount * 2)
Loop, %MonitorCountx2%
{
    gui, new, ,monitortest%a_index%
    if (a_index <= MonitorCount)
    {
        GradientColorBand("6AA7B6","monitortest.bmp")
        MonNum := a_index
    }
    else
    {
        GradientColorBand("F0F0F0","monitortest.bmp")
        MonNum := a_index - MonitorCount
    }
    gui, add, picture, w10 h10, monitortest.bmp
    gui, show, , monitortest%a_index%
    WinWait, monitortest%a_index%
    WinActivate
    WinWaitActive
    WinGet, windowHandle, ID, monitortest%a_index%
; makes sure monitortest1 is on monitor 1, and monitortest2 is on monitor 2, etc., windows-key-right moves to another monitor
    Loop
    {
        GetMonitorIndexFromWindow(windowHandle, MonitorIndex)
        if (MonitorIndex = MonNum)
            break
        Send, #+{right}
    }
    if (a_index > MonitorCount)
        WinMaximize
}
; see how many currently can't find the pixel in the gui
TestMonitors(MonitorCount,notfoundtotal1)
; if pixel can be found in each, then presumably dpi scale is 100 for each, go to the end of the script
if !notfoundtotal1
    Goto, CleanUp
; get what system thinks is width/height of primary monitor.  If primary monitor is at 150% dpi, then this will be by 1.5x smaller than it really is
currentwidth := a_screenwidth
currentheight := a_screenheight
; create a temporary width/height that would be the true resolution (if dpi scale is 150%)
tempwidth := 1.5*currentwidth
tempheight := 1.5*currentheight
; change it to this temporary resolution
ChangeResolution(tempwidth, tempheight)
; if the system still reads width as 1.5x less than it was just set it to be, either that resolution can't be done or dpi scale is 150%
; in the latter case, when primary monitor later gets fixed later to 100%, later increasing resolution 1.5x should set it to current, true resolution.
if (currentwidth = a_screenwidth)
    doagainlater := "y"
; but if the system does read a resolution change, then there's a good chance primary monitor is at 100% and meant to be at this lower resolution
else
    ChangeResolution(currentwidth, currentheight)
; checks to see if RegValOrig.ini already exists, if so, renames the file in case needed later
ifexist, RegValOrig.ini
{
   FormatTime, TimeString, , yyyyMMdd'_'HHmmss
   newfile := "RegValOrig" TimeString ".ini"
   Filemove, RegValOrig.ini, %newfile%
}
;Create a an ini of the current registry values for ScaleFactors and PerMonitorSettings
GetRegVal()
; rename the ini to "orig" as another one will be created later if reverting with other hotkey.
Filemove, RegVal.ini, RegValOrig.ini
; save clipboard and run powershell for monitor info piped into the clipboard
; powershell from here: https://blogs.technet.microsoft.com/heyscriptingguy/2013/10/03/use-powershell-to-discover-multi-monitor-information/
ClipOrig := ClipboardAll
Clipboard =
Run PowerShell.exe -Command &{((Get-CimInstance -Namespace root\wmi -ClassName WmiMonitorBasicDisplayParams) | Out-String).Trim() | Set-Clipboard},, hide
Clipwait
; regex's to get IDs of the monitors (as seen in Device Manager / Details / Hardware Ids, e.g. LEN40A9, which defaults to 150% scaling) currently connected
; the order doesn't seem to correspond directly to primary, secondary, etc. order from sysget, hence MonitorPossibleName variable
Loop, %MonitorCount%
{
   RegExMatch(clipboard,"DISPLAY\\(.*?)\\", m)
   MonitorPossibleName%a_index% := m1
   StringReplace, clipboard, clipboard, DISPLAY\
}
; restore clipboard, as all we want is monitorpossiblename1, monitorpossiblename2, etc.
Clipboard := ClipOrig
; do a reset of displays, which changes the primary display to a value, and then returns it to current, may not be necessary at this point in script
ResetDisplays(currentwidth,currentheight)
; do registry changes on each monitor, i.e. loop per the monitor count, or until pixel search works on each test gui window, whichever comes first
Loop, %MonitorCount%
{
; start with first monitor from the powershell
   MonPosName = % MonitorPossibleName%a_index%
   Loop
   {
; read from ini which read from the registry the deviceIDs from ScaleFactors
; ScaleFactors IDs are the same as what can be used in PerMonitorSettings, and include DevicesIDs of any monitor that has ever been connected (I think)
; these IDs start with the string from the powershell (e.g. LEN40A90) but then have a much longer string that follows, determined by some method I don't know
       IniRead, DeviceID, RegValOrig.ini, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors, Key%a_index%, %A_Space%
       if !DeviceID
           break
; Once a DeviceID has been used it'll be marked in the ini, so it can be skipped next time
       IniRead, YesOrNo, RegValOrig.ini, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors, Key%a_index%Used, %A_Space%
; if the powershell is indeed it at the beginning of the scalefactor id, the position will be 0.
       StringGetPos, Pos, DeviceID, %MonPosName%
; not used before, and has the powershell id at the beginning, then mark as used and use it
       If !YesOrNo and (Pos = 0)
           IniWrite, Yes, RegValOrig.ini, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors, Key%a_index%Used
       Else
           Continue
; empty the variables that will be used in the subsequent internal loop, in case this is a later external loop
       HasNonDpiValueName =
       ValueNum =
; Go through and see if PerMonitorSettings have already been set for this deviceID--until you set them once using "Display settings" they do not exist
       Loop
       {
           IniRead, NameNum, RegValOrig.ini, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, Name%a_index%, %A_Space%
           if !NameNum
               break
; I'm not sure if there are other possible values in the key than DpiValue, could never find that out, so first need to verify the name is DpiValue 
           if NameNum = DpiValue
               IniRead, ValueNum, RegValOrig.ini, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, Value%a_index%
; if there are other values than DpiValue, marking it, so that the entire key doesn't get deleted if we need to delete DpiValue
           else
               HasNonDpiValueName := "y"
       }
; Since I'm only testing three possible values 0, -2 (4294967294), and -1 (4294967295), only doing this three times.
; 0 would be the default for that monitor (could be 100% or 150%, there may be others, but 150% is afaik only for high-res modern laptop screens)
; -2 would be two options above default (e.g. my lenovo defaults at 150%, but offers 125% above, -1, 100% above that, -2 -- also 175%, 1, and 200%, 2)
; -1 would be one option above default (other monitors don't have 5 options like my lenovo, and might be 100%, 150% default, 200% -- hence -1, 0, 1)
       Loop, 3
       {
           if a_index = 1
               TestValue := 0
           if a_index = 2
               TestValue := 4294967294
           if a_index = 3
               TestValue := 4294967295
; first determine that the testvalue to switch to isn't currently being used in permonitorsettings
           if (ValueNum != TestValue)
           {
; no setting at all in permonitorsettings, or a setting of 0, is the same, and results the same with !ValueNum, so if !ValueNum: no point in testing 0
               if (TestValue = 0) and (!ValueNum)
                   continue
; write the test value to the registry
               RegWrite, REG_DWORD, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, DpiValue, %TestValue%
; reset the displays (i.e. change to a different resolution, and then back, on primary) to pickup the registry change
               ResetDisplays(currentwidth,currentheight)
; test the monitors to get a total number of not-found pixel searches, if the registry change was helpful, it should switch reduce by one.
               TestMonitors(MonitorCount,notfoundtotal2)
; if it does, then no need to check the rest of the test values
               if (notfoundtotal2 < notfoundtotal1)
                   break
; if it doesn't work, and 0/null for original PerMonitorSettings, and no other values in the key for PerMonitorSettings, then delete the whole key 
               else if (!ValueNum) and (!HasNonDpiValueName)
                   RegDelete, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%
; if it doesn't work, and 0/null for original PerMonitorSettings (and presumably other values in the key) then delete just DpiValue 
               else if (!ValueNum)
                   RegDelete, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, DpiValue
; if it doesn't work, and there is a value for original PerMonitorSettings, then use that value again
               else
                   RegWrite, REG_DWORD, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\%DeviceID%, DpiValue, %ValueNum%
           }
       }
   }
; if finally pixels are found through each monitor, no need to change/test any more
   if !notfoundtotal2
      break
; but if the not-found-number has reduced, but not yet completely gone, then make the current number the number to check against in the next loop
   if notfoundtotal2 < notfoundtotal1
      notfoundtotal1 := notfoundtotal2
}
; again, if the primary monitor was one of the monitors at 150%, this should change it to the proper full resolution.
if doagainlater
    ChangeResolution(tempwidth, tempheight)
; closing the test guis (would close anyway on return, but code may be used elsewhere) and deletting the monitortest.bmp
CleanUp:
Loop, %MonitorCountx2%
    WinClose, monitortest%A_Index%
FileDelete, monitortest.bmp
return



^4::
; checks to see if RegValOrig.ini exists
ifnotexist, RegValOrig.ini
{
    msgbox, needs RegValOrig.ini in script directory, restore/rename file as needed and try again
    return
}
; checks to see if RegVal.ini already exists, if so, renames the file so it doesn't get overwritten (in case user needs it later)
ifexist, RegVal.ini
{
   FormatTime, TimeString, , yyyyMMdd'_'HHmmss
   newfile := "RegVal" TimeString ".ini"
   Filemove, RegVal.ini, %newfile%
}
; gets the current registry values
GetRegVal()
; section names in the ini correspond to keys in PerMonitorSettings, e.g. the devices with set dpi scales, and "ScaleFactors," which we don't need 
IniRead, SectionNamesOrig, RegValOrig.ini
IniRead, SectionNames, RegVal.ini
; first goes through the list of recently created keys
Loop, parse, SectionNames, `n
{
; skipping the ScaleFactors parent key
    if (A_LoopField = "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors")
        Continue
; selecting a key in the current registry (from the list, from the ini)
    SelKey := A_LoopField 
; clearing the variable for selected key in the old registry
    SelKeyOrig =
; going through old registry keys and seeing if any match the shelected key from the current registry
    Loop, parse, SectionNamesOrig, `n
    {
        if (A_LoopField = SelKey)
        {
; if so, set as selected key in the old registry
            SelKeyOrig := A_LoopField      
            Break
        }
    }
; if there is no matching key in the old registry, then it must be a recently created key, and thus the entire current key can be safely deleted.
    if !SelKeyOrig
        RegDelete, %SelKey%
}
; go through the list of old registry keys
Loop, parse, SectionNamesOrig, `n
{
; again, skip ScaleFactors parent key
    if (A_LoopField = "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors")
        Continue
; selecting key in the old registry
    SelKeyOrig := A_LoopField 
; clearing selected key in the current registry
    SelKey =
; going through current registry keys and seeing if any match selected key in old registry
    Loop, parse, SectionNames, `n
    {
        if (A_LoopField = SelKeyOrig)
        {
; if so, set selected key from current registry 
            SelKey := A_LoopField
; clear the ValueNum, which will correspond to a value for DpiValue if it exists in current registry
            ValueNum =
; current registry key section might have a value for DpiValue or none or (possibly, never seen) more than just DpiValue
            Loop
            {
                IniRead, NameNum, RegVal.ini, %SelKey%, Name%a_index%, %A_Space%
; loop breaks when no more names for values found
                if !NameNum
                    break
; if the name is "DpiValue" then we want the corresponding value, ValueNum
                if (NameNum = "DpiValue")
                    IniRead, ValueNum, RegVal.ini, %SelKey%, Value%a_index%
            }
            Break
        }
    }
; clear the ValueNumOrig, which will correspond to a value for DpiValue if it exists in old registry
    ValueNumOrig =
; go through old registry key section for a DpiValue, and if found set as ValueNumOrig
    Loop
    {
        IniRead, NameNumOrig, RegValOrig.ini, %SelKeyOrig%, Name%a_index%, %A_Space%
        if !NameNumOrig
            break
        if (NameNumOrig = "DpiValue")
            IniRead, ValueNumOrig, RegValOrig.ini, %SelKeyOrig%, Value%a_index%
    }
; if a value is found for DpiValue in old registry, then we add/change it in the new (creates key if needed)
    if ValueNumOrig
        RegWrite, REG_DWORD, %SelKeyOrig%, DpiValue, %ValueNumOrig%
; but if no old value is found, though there is a value in the new registry, then we delete just that value
    else if ValueNum
        RegDelete, %SelKey%, DpiValue
}
; reset displays to make the effects permanent
ResetDisplays(a_screenwidth,a_screenheight)
return




GradientColorBand(RGB,file)
{ 
    hex:="424D3A000000000000003600000028000000010000000100000001"
    . "0018000000000004000000130B0000130B"
    . "00000000000000000000" substr(RGB,5,2) substr(RGB,3,2) substr(RGB,1,2) "00"
    file := FileOpen(File,"rw" )
    Loop 58
    file.WriteUChar("0x" SubStr(hex,2*a_index-1,2))
    File.Close()
    Return 
}


GetMonitorIndexFromWindow(windowHandle, ByRef monitorIndex)
{
    ; Starts with 1.
    monitorIndex := 1
    VarSetCapacity(monitorInfo, 40)
    NumPut(40, monitorInfo)
    if (monitorHandle := DllCall("MonitorFromWindow", "uint", windowHandle, "uint", 0x2)) && DllCall("GetMonitorInfo", "uint", monitorHandle, "uint", &monitorInfo) 
    {
        monitorLeft   := NumGet(monitorInfo,  4, "Int")
        monitorTop    := NumGet(monitorInfo,  8, "Int")
        monitorRight  := NumGet(monitorInfo, 12, "Int")
        monitorBottom := NumGet(monitorInfo, 16, "Int")
        workLeft      := NumGet(monitorInfo, 20, "Int")
        workTop       := NumGet(monitorInfo, 24, "Int")
        workRight     := NumGet(monitorInfo, 28, "Int")
        workBottom    := NumGet(monitorInfo, 32, "Int")
        isPrimary     := NumGet(monitorInfo, 36, "Int") & 1
        SysGet, monitorCount, MonitorCount
        Loop, %monitorCount%
        {
            SysGet, tempMon, Monitor, %A_Index%
            ; Compare location to determine the monitor index.
            if ((monitorLeft = tempMonLeft) and (monitorTop = tempMonTop) and (monitorRight = tempMonRight) and (monitorBottom = tempMonBottom))
            {
                monitorIndex := A_Index
                break
            }
        }
    }
    return
}

GetRegVal()
{
    ValueNum := 1
    LastSubKey =
    Loop, Reg, HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings, VR
    {
        if A_LoopRegSubKey = LastSubKey
            ValueNum++
        RegRead, Value
        IniWrite, %A_LoopRegName%, RegVal.ini, %A_LoopRegKey%\%A_LoopRegSubKey%, Name%ValueNum%
        IniWrite, %A_LoopRegType%, RegVal.ini, %A_LoopRegKey%\%A_LoopRegSubKey%, Type%ValueNum%
        IniWrite, %Value%, RegVal.ini, %A_LoopRegKey%\%A_LoopRegSubKey%, Value%ValueNum%
        LastSubKey := A_LoopRegSubKey
    }
    Loop, Reg, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors, K
       IniWrite, %A_LoopRegName%, RegVal.ini, %A_LoopRegKey%\%A_LoopRegSubKey%, Key%A_index%
}

TestMonitors(MonitorCount,ByRef notfoundtotal)
{
    notfoundtotal := 0
    Loop, %MonitorCount%
    {
        WinActivate, monitortest%a_index%
        PixelSearch, , , 0, 0, 60, 60, 0xB6A76A, 0, Fast
        if errorlevel
            notfoundtotal++
    }
}


ResetDisplays(currentwidth,currentheight)
{
    ; if dpi for primary is already 150%, changing res to what it thinks it is, will actually register as a shrink in res by 125%
    ; e.g. if width is 1920, sys may think it is 1280, and if so, changing to 1280, will make sys think it is 1024
    ; thus even though really it has reduced by 1.5 (1920/1280 = 1.5) it will register as 1.25 (1280/1024 = 1.25) and will need 1.5 x 1280 = 1920
    ChangeResolution(currentwidth, currentheight)
    if ((currentwidth/a_screenwidth) = 1.25)
    {
        newwidth := 1.5*currentwidth
        newheight := 1.5*currentheight
        ChangeResolution(newwidth, newheight)
    }
    else
    {
        if (currentwidth != 1280) and (currentheight != 720)
        {
            altwidth := 1280
            altheight := 720
        }
        else
        {
            altwidth := 800
            altheight := 600
        }
        ChangeResolution(altwidth, altheight)
        ChangeResolution(currentwidth, currentheight)
    }
    return
}

ChangeResolution(Screen_Width := 1920, Screen_Height := 1080, Color_Depth := 32)
{
	VarSetCapacity(Device_Mode,156,0)
	NumPut(156,Device_Mode,36) 
	DllCall( "EnumDisplaySettingsA", UInt,0, UInt,-1, UInt,&Device_Mode )
	NumPut(0x5c0000,Device_Mode,40) 
	NumPut(Color_Depth,Device_Mode,104)
	NumPut(Screen_Width,Device_Mode,108)
	NumPut(Screen_Height,Device_Mode,112)
	Return DllCall( "ChangeDisplaySettingsA", UInt,&Device_Mode, UInt,0 )
}
Return

User avatar
SteveMylo
Posts: 233
Joined: 22 Jun 2021, 00:50
Location: Australia
Contact:

Re: determine if scaling is not 100% for monitor of a window, change PerMonitorSettings in registry, reflect change, ver

Post by SteveMylo » 30 Nov 2021, 06:20

Just come across this. So where is the ini file it keeps asking for?
Many Thanks

Post Reply

Return to “Ask for Help (v1)”