Skip to content

Instantly share code, notes, and snippets.

@Konfekt
Last active March 9, 2024 13:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Konfekt/4ff5221604e3fe12f34fffcd1d92dd88 to your computer and use it in GitHub Desktop.
Save Konfekt/4ff5221604e3fe12f34fffcd1d92dd88 to your computer and use it in GitHub Desktop.
; 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