Last active
March 9, 2024 13:55
-
-
Save Konfekt/4ff5221604e3fe12f34fffcd1d92dd88 to your computer and use it in GitHub Desktop.
AHK v2 version of Joe Winograd's splendid Move Mouse to Monitor script from https://www.experts-exchange.com/articles/33932/Keyboard-shortcuts-hotkeys-to-move-mouse-in-multi-monitor-configuration-AutoHotkey-Script.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; Original author: Joe Winograd 21-Aug-2021 | |
; Converted to AHK v2 by Enno 24-Apr-2023 | |
Version:="6" | |
#SingleInstance Force ; replace old instance immediately | |
InitializeVars() ; initialize all variables | |
ConfigureInitialTray() ; configure initial system tray (notification area) | |
Return | |
InitializeVars() | |
{ | |
global | |
SplitPath A_ScriptName, , , , &ProgramName ; ProgramName==>script name without path or extension | |
ScriptDateTime := FileGetTime(A_ScriptName, "M") ; modified time of script | |
ScriptDateTime := FormatTime(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 . "\images\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:=Map("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 | |
OrigNumMons := MonitorGetCount() ; original number of monitors when script was run | |
Loop OrigNumMons ; process all monitors | |
{ | |
MonitorHotkey:=HotkeyModifiers . MonitorIDs[A_Index] | |
; From https://www.autohotkey.com/boards/viewtopic.php?p=364616#p364616 | |
Hotkey(MonitorHotkey, MoveMouseMon, "I1") ; define Modifiers+ID hotkey | |
} | |
Return | |
} | |
ConfigureInitialTray() | |
{ | |
global | |
Tray:= A_TrayMenu | |
Tray.Delete() | |
Tray.Add("Show &Monitor and Virtual Screen Information", ContextMenu) | |
Tray.Add("&Identify Monitors", ContextMenu) | |
Tray.Add("Start with &Windows (On/Off toggle)", ContextMenu) | |
StartupLink := A_Startup . "\" . ProgramName . ".lnk" | |
If (FileExist(StartupLink)) | |
Tray.Check("Start with &Windows (On/Off toggle)") | |
Else | |
Tray.Uncheck("Start with &Windows (On/Off toggle)") | |
Tray.Add("&Reload Script", ContextMenu) | |
Tray.Add("&About", ContextMenu) | |
Tray.Add("E&xit", ContextMenu) | |
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" . "Keyboard combinations for Window management of common applications") | |
; TrayTip(ProgramName . "`n" . HotkeyModifiersTip . "Number 0-9 or Letter a-z (zero always primary)`nRight-click for context menu") | |
IconFile:=A_ScriptDir . "\images\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 | |
TraySetIcon(IconFile) | |
TraySetIcon(IconFile) | |
Return | |
} | |
MoveMouseMon(ThisHotkey) | |
{ | |
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(ThisHotkey) | |
{ | |
PrimaryMonNum := MonitorGetPrimary() ; 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 | |
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 | |
MonitorGet MoveMonNum, &CoordinatesLeft, &CoordinatesTop, &CoordinatesRight, &CoordinatesBottom ; get coordinates for this monitor | |
Left:=CoordinatesLeft | |
Right:=CoordinatesRight | |
Top:=CoordinatesTop | |
Bottom:=CoordinatesBottom | |
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 | |
; activate Monitor | |
Sleep(10) | |
Click() | |
Return | |
} | |
CheckNumMonsChanged() | |
{ | |
CurrNumMons := MonitorGetCount() ; current number of monitors | |
If (OrigNumMons!=CurrNumMons) | |
{ | |
MsgBox("Number of monitors changed since script was run`nOriginal=" OrigNumMons "`nCurrent=" CurrNumMons "`n`nWill reload script when you click OK button", "Warning", 4144) | |
; 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("Unable to reload script`nWill exit when you click OK button`nYou will have to re-run the script manually", "Error", 4112) | |
ExitApp() | |
} | |
Return ; number of monitors has not changed | |
} | |
ContextMenu(A_ThisMenuItem, A_ThisMenuItemPos, MyMenu) | |
{ | |
If (A_ThisMenuItem="Show &Monitor and Virtual Screen Information") | |
{ | |
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 | |
MonName := MonitorGetName(MonNum) ; get name of this monitor | |
MonitorGet(MonNum, &CoordinatesLeft, &CoordinatesTop, &CoordinatesRight, &CoordinatesBottom) ; get coordinates for this monitor | |
Left:=CoordinatesLeft | |
Right:=CoordinatesRight | |
Top:=CoordinatesTop | |
Bottom:=CoordinatesBottom | |
AllMons:=AllMons . MonitorIDs[A_Index] . ": " . MonName . " L=" . Left . " R=" . Right . " T=" . Top . " B=" . Bottom . "`n" | |
} | |
VirtualX := SysGet(76) ; coordinate for left side of virtual screen | |
VirtualY := SysGet(77) ; coordinate for top of virtual screen | |
VirtualW := SysGet(78) ; width of virtual screen | |
VirtualH := SysGet(79) ; height of virtual screen | |
PrimaryMonNum := MonitorGetPrimary() ; get number of Primary monitor (the Windows number, not the script identifier) | |
MsgBox("Monitor numbers/letters, names, coordinates:`n" AllMons "Primary monitor number/letter: " PrimaryMonNum "`n`nVirtual screen information:`nVirtualX=" VirtualX "`nVirtualY=" VirtualY "`nVirtualW=" VirtualW "`nVirtualH=" VirtualH, ProgramName, 4160) | |
DllCall("SetThreadDpiAwarenessContext", "ptr", RestoreDPI, "ptr") ; restore previous DPI awareness - thanks to lexikos for this | |
Return | |
} | |
If (A_ThisMenuItem="&Identify Monitors") | |
{ | |
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 | |
; PosPtr := Buffer(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") | |
; CoordMode "Mouse", "Screen" | |
; MouseGetPos &X, &Y | |
MouseGetScreenPos(&X, &Y) | |
MouseGetScreenPos(&X?, &Y?) { | |
DllCall("GetCursorPos", "uint64*", &v:=0) | |
X := v << 32 >> 32 | |
Y := v >> 32 | |
} | |
SaveX := X | |
SaveY := Y | |
NumMons := MonitorGetCount() | |
SplashImageGuis := Array() | |
Loop NumMons ; process all monitors | |
{ | |
MonID:=MonitorIDs[A_Index] ; get identifier | |
MonNum:=MonitorNums[MonID] ; get monitor number from monitor identifier | |
PerformMove(MonNum,-1,-1) ; put identifier in center of screen | |
SplashX:=MoveX-SplashOffset ; center splash image horizontally | |
SplashY:=MoveY-SplashOffset ; center splash image vertically | |
IdentifyImage:=A_ScriptDir . "\images\Monitor" . MonNum . ".png" ; credit to https://iconarchive.com/show/red-orb-alphabet-icons-by-iconarchive.html for the images | |
SplashImageGuis.Push(Gui("ToolWindow -Sysmenu Disabled")) | |
SplashImageGuis[MonNum].Add("Picture","w200 h-1", IdentifyImage) | |
SplashImageGuis[MonNum].Show("x" . SplashX . " y" . SplashY ) | |
} | |
Sleep(IdentifyMilliseconds) | |
Loop NumMons ; process all monitors | |
{ | |
MonID:=MonitorIDs[A_Index] | |
MonNum:=MonitorNums[MonID] ; get monitor number from monitor identifier | |
SplashImageGuis[MonNum].Destroy() | |
} | |
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 | |
Tray.Uncheck("Start with &Windows (On/Off toggle)") | |
FileDelete(StartupLink) | |
Return | |
} | |
Else | |
{ | |
; it's off, so this click turns it on | |
Tray.Check("Start with &Windows (On/Off toggle)") | |
try FileCreateShortcut A_ScriptFullPath, StartupLink, A_ScriptDir, , ProgramName, IconFile | |
catch as ErrorLevel { | |
MsgBox("Error Level=" ErrorLevel " trying to create Startup shortcut:`n" StartupLink, "Fatal Error", 4112) | |
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("Unable to reload script`nWill exit when you click OK button`nYou will have to reload the script manually", "Error", 4112) | |
ExitApp() | |
} | |
If (A_ThisMenuItem="&About") | |
{ | |
MsgBox(A_ScriptFullPath "`n`nVersion " Version "`n`nModified: " ScriptDateTime, "About " ProgramName, 4160) | |
Return | |
} | |
If (A_ThisMenuItem="E&xit") | |
{ | |
msgResult := MsgBox("Are you sure you want to quit and deactivate all hotkeys?", ProgramName " - Terminate?", 4388) | |
if (msgResult = "No") | |
Return | |
ExitApp() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment