Code for using Windows AutoHotkey to immediately move mouse pointer to particular monitor
...several users who have 8 or more monitors connected to their Windows 10 computers
; Joe Winograd 21-Aug-2021
Version:="6"
#Warn,UseUnsetLocal ; warning on uninitialized variables
#NoEnv ; avoid checking empty variables to see if they are environment variables
#SingleInstance Force ; replace old instance immediately
SetBatchLines,-1 ; run at maximum speed
Gosub,InitializeVars ; initialize all variables
Gosub,ConfigureInitialTray ; configure initial system tray (notification area)
Return
InitializeVars:
SplitPath,A_ScriptName,,,,ProgramName ; ProgramName==>script name without path or extension
FileGetTime,ScriptDateTime,%A_ScriptName%,M ; modified time of script
FormatTime,ScriptDateTime,%ScriptDateTime%,yyyyMMdd-HHmmss
; icon file name is now hard-coded so that script can have any name without having to change the icon file name (had been based on ProgramName in previous versions)
IconFile:=A_ScriptDir . "\MoveMouseToMonitor.ico" ; credit to the Oxygen team at https://iconarchive.com/show/oxygen-icons-by-oxygen-icons.org.html for the monitor and mouse icons
; *** begin variables to change ***
IdentifySeconds:=3 ; length of time in seconds for monitor identifier (its number or letter) to stay on screen
OffsetX:=-1 ; move mouse to this number of pixels from left edge of monitor (-1 means center)
OffsetY:=-1 ; move mouse to this number of pixels from top edge of monitor (-1 means center)
;OffsetX:=0 ; example - left edge
;OffsetY:=0 ; example - top edge
;OffsetX:=400 ; example - 400 pixels from left edge
;OffsetY:=300 ; example - 300 pixels from top edge
; modifier keys: ! is Alt ^ is Ctrl + is Shift # is Win (the Windows logo key)
HotkeyModifiers:="!^" ; modifier keys for identifier keys (0-9 and a-z): ! is Alt, ^ is Ctrl, + is Shift, # is Win ((Windows logo key) - modifiers may be in any order
; *** end variables to change ***
SplashOffset:=128 ; size of Identify Monitor PNGs divided by 2 so that this splash offset centers the images on the screen (the PNGs are 256x256)
; need to be careful in code when to use the Windows monitor number, which is always a number, and the script monitor identifier, which is a number or letter
; these two arrays make it easy to switch between the Windows monitor number and the script monitor identifier
; this is a simple array - the index is the monitor number, the value is the monitor identifier
MonitorIDs:=["1","2","3","4","5","6","7","8","9","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
; this is an associative array - the key:value pair is monitor identifier:monitor number
MonitorNums:={1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",a:"10",b:"11",c:"12",d:13,e:"14",f:"15",g:"16",h:"17",i:"18",j:"19",k:"20",l:"21",m:"22",n:"23",o:"24",p:"25",q:"26",r:"27",s:"28",t:"29",u:"30",v:"31",w:"32",x:"33",y:"34",z:"35"} ; associative array with key:value pairs for getting (Windows) monitor number from (script) monitor identifier
IdentifyMilliseconds:=IdentifySeconds*1000 ; Sleep time is in milliseconds
NumModifiers:=StrLen(HotkeyModifiers) ; get number of modifier keys
Hotkey,%HotkeyModifiers%0,MoveMousePrimary,On ; define Modifiers+0 hotkey to move mouse to Primary monitor regardless of its monitor identifier
SysGet,OrigNumMons,MonitorCount ; original number of monitors when script was run
Loop,%OrigNumMons% ; process all monitors
{
MonitorHotkey:=HotkeyModifiers . MonitorIDs[A_Index]
Hotkey,%MonitorHotkey%,MoveMouseMon,On ; define Modifiers+ID hotkey
}
Return
ConfigureInitialTray:
Menu,Tray,NoStandard ; do not use standard AutoHotkey context menu
Menu,Tray,Add,Show &Monitor and Virtual Screen Information,ContextMenu
Menu,Tray,Add,&Identify Monitors,ContextMenu
Menu,Tray,Add,Start with &Windows (On/Off toggle),ContextMenu
StartupLink:=A_Startup . "\" . ProgramName . ".lnk"
If (FileExist(StartupLink))
Menu,Tray,Check,Start with &Windows (On/Off toggle)
Else
Menu,Tray,Uncheck,Start with &Windows (On/Off toggle)
Menu,Tray,Add,&Reload Script,ContextMenu
Menu,Tray,Add,&About,ContextMenu
Menu,Tray,Add,E&xit,ContextMenu
Menu,Tray,Default,Show &Monitor and Virtual Screen Information
HotkeyModifiersTip:=StrReplace(HotkeyModifiers,"+","Shift+") ; do this first so we don't catch the other plus signs
HotkeyModifiersTip:=StrReplace(HotkeyModifiersTip,"!","Alt+")
HotkeyModifiersTip:=StrReplace(HotkeyModifiersTip,"^","Ctrl+")
HotkeyModifiersTip:=StrReplace(HotkeyModifiersTip,"#","Win+")
TrayTip:=ProgramName . "`n" . HotkeyModifiersTip . "Number 0-9 or Letter a-z (zero always primary)`nRight-click for context menu"
Menu,Tray,Tip,%TrayTip%
Menu,Tray,Icon,%IconFile%
Return
MoveMouseMon:
MonID:=SubStr(A_ThisHotkey,NumModifiers+1,1) ; monitor identifier is after modifiers
MonNum:=MonitorNums[MonID] ; get monitor number from monitor identifier
PerformMove(MonNum,OffsetX,OffsetY) ; move it
Return
MoveMousePrimary:
SysGet,PrimaryMonNum,MonitorPrimary ; get number of Primary monitor (the Windows number, not the script identifier)
PerformMove(PrimaryMonNum,OffsetX,OffsetY) ; move it
Return
PerformMove(MoveMonNum,OffX,OffY)
{
global MoveX,MoveY
Gosub,CheckNumMonsChanged ; before performing move, check if the number of monitors has changed
RestoreDPI:=DllCall("SetThreadDpiAwarenessContext","ptr",-3,"ptr") ; enable per-monitor DPI awareness and save current value to restore it when done - thanks to lexikos for this
SysGet,Coordinates%MoveMonNum%,Monitor,%MoveMonNum% ; get coordinates for this monitor
Left:=Coordinates%MoveMonNum%Left
Right:=Coordinates%MoveMonNum%Right
Top:=Coordinates%MoveMonNum%Top
Bottom:=Coordinates%MoveMonNum%Bottom
If (OffX=-1)
MoveX:=Left+(Floor(0.5*(Right-Left))) ; center
Else
MoveX:=Left+OffX
If (OffY=-1)
MoveY:=Top+(Floor(0.5*(Bottom-Top))) ; center
Else
MoveY:=Top+OffY
DllCall("SetCursorPos","int",MoveX,"int",MoveY) ; first call to move it - usually works but not always
Sleep,10 ; wait a few milliseconds for first call to settle
DllCall("SetCursorPos","int",MoveX,"int",MoveY) ; second call sometimes needed
DllCall("SetThreadDpiAwarenessContext","ptr",RestoreDPI,"ptr") ; restore previous DPI awareness - thanks to lexikos for this
Return
}
CheckNumMonsChanged:
SysGet,CurrNumMons,MonitorCount ; current number of monitors
If (OrigNumMons!=CurrNumMons)
{
MsgBox,4144,Warning,Number of monitors changed since script was run`nOriginal=%OrigNumMons%`nCurrent=%CurrNumMons%`n`nWill reload script when you click OK button
; since the number of monitors has changed, disable all hotkeys - the reload will enable the new/correct ones
Loop,%OrigNumMons% ; process all current monitors
{
MonitorHotkey:=HotkeyModifiers . MonitorIDs[A_Index]
Hotkey,%MonitorHotkey%,,Off ; disable hotkey
}
Reload
Sleep,2000 ; give Reload two seconds to work during this Sleep - if Reload successful, will never get to code below
MsgBox,4112,Error,Unable to reload script`nWill exit when you click OK button`nYou will have to re-run the script manually
ExitApp
}
Return ; number of monitors has not changed
ContextMenu:
If (A_ThisMenuItem="Show &Monitor and Virtual Screen Information")
{
Gosub,CheckNumMonsChanged ; before showing info, check if number of monitors has changed
RestoreDPI:=DllCall("SetThreadDpiAwarenessContext","ptr",-3,"ptr") ; enable per-monitor DPI awareness and save current value to restore it when done - thanks to lexikos for this
AllMons:=""
Loop,%OrigNumMons% ; process all monitors
{
MonNum:=A_Index
SysGet,MonName,MonitorName,%MonNum% ; get name of this monitor
SysGet,Coordinates%MonNum%,Monitor,%MonNum% ; get coordinates for this monitor
Left:=Coordinates%MonNum%Left
Right:=Coordinates%MonNum%Right
Top:=Coordinates%MonNum%Top
Bottom:=Coordinates%MonNum%Bottom
AllMons:=AllMons . MonitorIDs[A_Index] . ": " . MonName . " L=" . Left . " R=" . Right . " T=" . Top . " B=" . Bottom . "`n"
}
SysGet,VirtualX,76 ; coordinate for left side of virtual screen
SysGet,VirtualY,77 ; coordinate for top of virtual screen
SysGet,VirtualW,78 ; width of virtual screen
SysGet,VirtualH,79 ; height of virtual screen
SysGet,PrimaryMonNum,MonitorPrimary ; get number of Primary monitor (the Windows number, not the script identifier)
MsgBox,4160,%ProgramName%,Monitor numbers/letters`, names`, coordinates:`n%AllMons%Primary monitor number/letter: %PrimaryMonNum%`n`nVirtual screen information:`nVirtualX=%VirtualX%`nVirtualY=%VirtualY%`nVirtualW=%VirtualW%`nVirtualH=%VirtualH%
DllCall("SetThreadDpiAwarenessContext","ptr",RestoreDPI,"ptr") ; restore previous DPI awareness - thanks to lexikos for this
Return
}
If (A_ThisMenuItem="&Identify Monitors")
{
Gosub,CheckNumMonsChanged ; before identifying, check if the number of monitors has changed
RestoreDPI:=DllCall("SetThreadDpiAwarenessContext","ptr",-3,"ptr") ; enable per-monitor DPI awareness and save current value to restore it when done - thanks to lexikos for this
VarSetCapacity(PosPtr,4,0) ; get current position so it can be restored after identify
DllCall("GetCursorPos","ptr",&PosPtr)
SaveX:=NumGet(PosPtr,0,"int")
SaveY:=NumGet(PosPtr,4,"int")
SysGet,NumMons,MonitorCount
Loop,%NumMons% ; process all monitors
{
MonID:=MonitorIDs[A_Index] ; get identifier
PerformMove(MonID,-1,-1) ; put identifier in center of screen
IdentifyImage:=A_ScriptDir . "\Monitor" . MonID . ".png" ; credit to https://iconarchive.com/show/red-orb-alphabet-icons-by-iconarchive.html for the images
SplashX:=MoveX-SplashOffset ; center splash image horizontally
SplashY:=MoveY-SplashOffset ; center splash image vertically
SplashImage,%MonID%:%IdentifyImage%,B X%SplashX% Y%SplashY%
}
Sleep,%IdentifyMilliseconds%
Loop,%NumMons% ; process all monitors
{
MonID:=MonitorIDs[A_Index]
SplashImage,%MonID%:Off
}
DllCall("SetCursorPos","int",SaveX,"int",SaveY)
DllCall("SetThreadDpiAwarenessContext","ptr",RestoreDPI,"ptr") ; restore previous DPI awareness - thanks to lexikos for this
Return
}
If (A_ThisMenuItem="Start with &Windows (On/Off toggle)")
{
If (FileExist(StartupLink))
{
; it's on, so this click turns it off
Menu,Tray,Uncheck,Start with &Windows (On/Off toggle)
FileDelete,%StartupLink%
Return
}
Else
{
; it's off, so this click turns it on
Menu,Tray,Check,Start with &Windows (On/Off toggle)
FileCreateShortcut,%A_ScriptFullPath%,%StartupLink%,%A_ScriptDir%,,%ProgramName%,%IconFile%
{
If (ErrorLevel!=0)
{
MsgBox,4112,Fatal Error,Error Level=%ErrorLevel% trying to create Startup shortcut:`n%StartupLink%
ExitApp
}
}
Return
}
}
If (A_ThisMenuItem="&Reload Script")
{
Reload
Sleep,2000 ; give Reload two seconds to work during this Sleep - if Reload successful, will never get to code below
MsgBox,4112,Error,Unable to reload script`nWill exit when you click OK button`nYou will have to reload the script manually
ExitApp
}
If (A_ThisMenuItem="&About")
{
MsgBox,4160,About %ProgramName%,%A_ScriptFullPath%`n`nVersion %Version%`n`nModified: %ScriptDateTime%
Return
}
If (A_ThisMenuItem="E&xit")
{
MsgBox,4388,%ProgramName% - Terminate?,Are you sure you want to quit and deactivate all hotkeys?
IfMsgBox,No
Return
ExitApp
}
Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.
Comments (38)
Author
Commented:Author
Commented:A quick note to let you know that I haven't found a work-around with AutoHotkey. If I happen to come across one, I'll let you know, of course, but at this point I'm not going to be actively researching it. Regards, Joe
Commented:
Much appreciated for it! I have also been looking for it, but seems most forum said its not possible at the moment. I have also added this feature request to the PowerToys, hopefully someday they can implement it.
https://github.com/microsoft/PowerToys/preleases/
Thank you once again Joe! Much appreciated!
Cheers
Richard
Author
Commented:Great idea to make it a feature request for PowerToys! Please let me know if you get any feedback on it. Thanks, Joe
Commented:
View More