Skip to content

Instantly share code, notes, and snippets.

@codekoriko
Last active January 7, 2025 01:46
Show Gist options
  • Save codekoriko/f9f2738afd253fe70c7ebc0843df3c91 to your computer and use it in GitHub Desktop.
Save codekoriko/f9f2738afd253fe70c7ebc0843df3c91 to your computer and use it in GitHub Desktop.
for Dual Display: calculate both display extends and Shift cursor from one display to the other on a key press

For Windows Users only

On Dual Display setup, I found it frustrating that the cursor is not bounded to sides of the display anymore. For example reaching the "close window" button needs precise muscle control whereas before you could simply crash again the top & right screen border and smash that window.

So I keep both display separated and created an AutoHotKey script to shift the cursor from my first to my second display on a press of a button.

Setup:

Separate both Display

  • Win+S -> search: "extend" -> "Duplicate or extend a connected Display"
  • Move display 2 far away to the Top/bottom left/right corner.
  • A tiny gap appears between both display rectangle
  • Hit "apply"

Calibration

Upon first launch, the script gonna calibrate the extend of both display

  • Make sure you have at least one window maximized on each Display. (You can move a window to your second display using Win+Shift+Left/Right)
  • Launch the script. It displays a menu of all open window currently in taskbar.
  • First select any maximized window on Display 1
  • then do the same for Display 2

By default the previous mouse button triggers the shift

A file %UserProfile%\display_shifter.ini will be created to store those value.

If you change the Display setup you can redo the calibration Right-clicking on the System Tray Icon and choosing Recalibrate

#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn ; Enable warnings to assist with detecting common errors.
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory.
Menu, Tray, Add, Recalibrate, calibrate
Menu, Tray, Icon, display_shifter.ico
Process, priority,, BelowNormal
EmptyMem()
CoordMode, Mouse, Screen ; mouse coordinates relative to the Screen
; Init global var
EnvGet, vUserProfile, USERPROFILE
init_file_path = %vUserProfile%\display_shifter.ini
global init_file = init_file_path
global disp1_x_min
global disp1_y_min
global disp1_x_max
global disp1_y_max
global disp1_mid_x
global disp1_mid_y
global disp1_last_x
global disp1_last_y
global disp2_x_min
global disp2_y_min
global disp2_x_max
global disp2_y_max
global disp2_mid_x
global disp2_mid_y
global disp2_last_x
global disp2_last_y
; storing dict of {"window title": id}
global window_ids = Object()
; storing dict of {"window title": Icon Handle}
global window_icons = Object()
global window_list_menu
; setup for DllCall to retrieve window icons
global WM_GETICON = 0x007F
global ICON_SMALL = 0
global ICON_BIG = 1
global GCLP_HICON = -14
global GCLP_HICONSM = -34
; if display_shifter.ini file exist we load it, otherwise we initiate calibration
if FileExist(init_file){
IniRead, disp1_x_min, %init_file%, display_1, x_min
IniRead, disp1_y_min, %init_file%, display_1, y_min
IniRead, disp1_x_max, %init_file%, display_1, x_max
IniRead, disp1_y_max, %init_file%, display_1, y_max
IniRead, disp1_mid_x, %init_file%, display_1, x_mid
IniRead, disp1_mid_y, %init_file%, display_1, y_mid
disp1_last_x := disp1_mid_x
disp1_last_y := disp1_mid_y
IniRead, disp2_x_min, %init_file%, display_2, x_min
IniRead, disp2_y_min, %init_file%, display_2, y_min
IniRead, disp2_x_max, %init_file%, display_2, x_max
IniRead, disp2_y_max, %init_file%, display_2, y_max
IniRead, disp2_mid_x, %init_file%, display_2, x_mid
IniRead, disp2_mid_y, %init_file%, display_2, y_mid
disp2_last_x := disp2_mid_x
disp2_last_y := disp2_mid_y
}
else{
calibrate()
}
calibrate(){
global
disp1_x_min = 0
disp1_y_min = 0
disp1_x_max = 0
disp1_y_max = 0
disp1_mid_x = 0
disp1_mid_y = 0
disp1_last_x = 0
disp1_last_y = 0
disp2_x_min = 0
disp2_y_min = 0
disp2_x_max = 0
disp2_y_max = 0
disp2_mid_x = 0
disp2_mid_y = 0
disp2_last_x = 0
disp2_last_y = 0
create_window_menu()
; Msgbox, Select a window Maximized on display 1
Menu, window_list_menu, Show
; Msgbox, display 1: (%disp1_x_min%, %disp1_y_min%) - (%disp1_x_max%, %disp1_y_max%) `n display 2: (%disp2_x_min%, %disp2_y_min%) - (%disp2_x_max%, %disp2_y_max%) `n`n saved position disp_1 (%disp1_last_x%, %disp1_last_y%) `n saved position disp_2 (%disp2_last_x%, %disp2_last_y%)
save_to_init()
}
; retrieve list of opened Window and create menu from it
create_window_menu(){
WinGet, id, List,,,Start|Program Manager|^$
Loop %id%
{
this_id := id%A_Index%
WinGet, minMax, MinMax, ahk_id %id%
WinGetTitle, this_title, ahk_id %this_id%
; get icons
SendMessage, WM_GETICON, ICON_SMALL, 96, , ahk_id %this_id%
if (ErrorLevel != "FAIL")
{
IconHwnd := ErrorLevel
}
if (IconHwnd = 0)
{
IconHwnd := DllCall("GetClassLongPtr", "Ptr", this_id, "Int", GCLP_HICONSM)
}
; skip window not in taskbar
If (minMax != -1) and (this_title != "Program manager") { ; -1 = minimized
WinGetPos,,,,height , ahk_id %this_id%
If (height) {
WinGetTitle, this_title, ahk_id %this_id%
window_ids.Insert(this_title, this_id)
If (IconHwnd) {
window_icons.Insert(this_title, IconHwnd)
}
}
}
}
; delete any previously populated menu items
if (window_list_menu){
Menu, window_list_menu, DeleteAll
}
; create menu listing of all open windows
for title, hid in window_ids {
try {
Menu, window_list_menu, Add, %title% , SetDisplayExtend
If (window_icons.HasKey(title)){
icon_hdl := window_icons[title]
Menu, window_list_menu, Icon, %title%, HICON:*%icon_hdl%
}
} catch e{
continue
}
}
}
offsetter(value, offset) {
if (value < 0)
newval := value - offset
else
newval := value + offset
return newval
}
; set display extend from selected maximized window
SetDisplayExtend() {
if (disp1_mid_x == 0){
win_id := window_ids[A_ThisMenuItem]
WinGetPos, Xw, Yw, Ww, Hw, ahk_id %win_id%
; Xw := offsetter(Xw, 25)
; Yw := offsetter(Hw, 25)
disp1_x_min := Xw
disp1_x_min := disp1_x_min + 9
disp1_y_min := Yw
disp1_y_min := disp1_y_min + 9
disp1_x_max := Xw + Ww
disp1_y_max := Yw + Hw
disp1_y_max := disp1_y_max + 25
disp1_mid_x := Round(disp1_x_min + ((disp1_x_max - disp1_x_min) / 2))
disp1_mid_y := Round(disp1_y_min + ((disp1_y_max - disp1_y_min) / 2))
disp1_last_x := disp1_mid_x
disp1_last_y := disp1_mid_y
; Msgbox, Select a window Maximized on display 2
Menu, window_list_menu, Show
} else {
win_id := window_ids[A_ThisMenuItem]
WinGetPos, Xw, Yw, Ww, Hw, ahk_id %win_id%
; Xw := offsetter(Xw, 20)
; Yw := offsetter(Yw, 20)
disp2_x_min := Xw
disp2_y_min := Yw
disp2_x_max := Xw + Ww
disp2_y_max := Yw + Hw
disp2_mid_x := Round(disp2_x_min + ((disp2_x_max - disp2_x_min) / 2))
disp2_mid_y := Round(disp2_y_min + ((disp2_y_max - disp2_y_min) / 2))
disp2_last_x := disp2_mid_x
disp2_last_y := disp2_mid_y
}
}
; save display extend to file in USERPROFILE
save_to_init(){
Msgbox, display 1 extend: `(%disp1_x_min%`, %disp1_y_min%`) - `(%disp1_x_max%`, %disp1_y_max%`) `ndisplay 2 extend: `(%disp2_x_min%`, %disp2_y_min%`) - `(%disp2_x_max%`, %disp2_y_max%`) `n`nSaved to`:`n`"%init_file%`"
IniWrite, %disp1_x_min%, %init_file%, display_1, x_min
IniWrite, %disp1_y_min%, %init_file%, display_1, y_min
IniWrite, %disp1_x_max%, %init_file%, display_1, x_max
IniWrite, %disp1_y_max%, %init_file%, display_1, y_max
IniWrite, %disp1_mid_x%, %init_file%, display_1, x_mid
IniWrite, %disp1_mid_y%, %init_file%, display_1, y_mid
IniWrite, %disp2_x_min%, %init_file%, display_2, x_min
IniWrite, %disp2_y_min%, %init_file%, display_2, y_min
IniWrite, %disp2_x_max%, %init_file%, display_2, x_max
IniWrite, %disp2_y_max%, %init_file%, display_2, y_max
IniWrite, %disp2_mid_x%, %init_file%, display_2, x_mid
IniWrite, %disp2_mid_y%, %init_file%, display_2, y_mid
}
; triggers the shifting
XButton1::
MouseGetPos, MouseX, MouseY
if ( (MouseX >= disp1_x_min and MouseY >= disp1_y_min) and (MouseX <= disp1_x_max and MouseY <= disp1_y_max)) { ; Mouse is on monitor #1
; MsgBox, Mouse`: %MouseX%x%MouseY% `n Display1 Min %disp1_x_min%x%disp1_y_min% `n Display1 Max %disp1_x_max%x%disp1_y_max% `n Display2 Min %disp2_x_min%x%disp2_y_min% `n Display2 Max %disp2_x_max%x%disp2_y_max% `n therefor belong to screen 1 -> moving to saved position on screen 2 %disp2_last_x%, %disp2_last_y%
disp1_last_y := MouseY
disp1_last_x := MouseX
MouseMove, disp2_last_x, disp2_last_y
MouseMove, disp2_last_x, disp2_last_y
highlight_pos()
} else { ; Mouse is on monitor #1
; MsgBox, Mouse`: %MouseX%x%MouseY% `n Display1 Min %disp1_x_min%x%disp1_y_min% `n Display1 Max %disp1_x_max%x%disp1_y_max% `n Display2 Min %disp2_x_min%x%disp2_y_min% `n Display2 Max %disp2_x_max%x%disp2_y_max% `n therefor belong to screen 2 -> moving to saved position on screen 1 %disp1_last_x%, %disp1_last_y%
disp2_last_y := MouseY
disp2_last_x := MouseX
MouseMove, disp1_last_x, disp1_last_y
MouseMove, disp1_last_x, disp1_last_y
highlight_pos()
}
return
; display a red circle at the cursor's arrival position for easier spotting
highlight_pos()
{
GetKeyState, state, LButton
if (state = "U")
{
Gui, Destroy
Gui, -Caption +ToolWindow +AlwaysOnTop
Gui, Color, Red
Gui, +LastFound
GuiHwnd := WinExist()
DetectHiddenWindows, On
WinSet, Transparent, 100, ahk_id %GuiHwnd%
WinSet, Region, 0-0 W100 H100 E, ahk_id %GuiHwnd% ; An ellipse instead of a rectangle.
MouseGetPos, MouseX, MouseY
posX := MouseX - 50
posY := MouseY - 50
Gui, Show, w500 h500 x%posX% y%posY%
sleep 200
Gui, Destroy
}
}
; Reduce memory footprint from 2MB to 0.3
EmptyMem(PID="AHK Rocks"){
pid:=(pid="AHK Rocks") ? DllCall("GetCurrentProcessId") : pid
h:=DllCall("OpenProcess", "UInt", 0x001F0FFF, "Int", 0, "Int", pid)
DllCall("SetProcessWorkingSetSize", "UInt", h, "Int", -1, "Int", -1)
DllCall("CloseHandle", "Int", h)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment