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