dotfiles/win/ahk/WindowManager.ahk

428 lines
11 KiB
AutoHotkey

; WINDOWS MANAGER
; =========================
; KEY BINDING
; =========================
; Send cursor to the center of window while switching
~#1 Up::
~#2 Up::
~#3 Up::
~#4 Up::
~#5 Up::
~#6 Up::
~#7 Up::
~#8 Up::
~#9 Up::
Sleep 100
SetCursorToCenterOfActiveWin()
return
~!Tab Up::
while (GetKeyState("Alt") != 0 or GetKeyState("Tab") != 0) {
Sleep 50
}
Sleep 100
WinGetPos, x, y, w, h, A
SetCursorToCenterOfActiveWin()
return
; =========================
; FUNCTION
; =========================
InitWindowManager() {
LogDebug("InitWindowManager")
; global RATIO := 0.618
global RATIO := 0.382
global ID_SEEN := Object()
global ARRANGEMENT := Object()
global ARRANGEMENT_PATH := A_AppData . "\arrangement.json"
global PADDING := 10
LoadArrangement()
SetTimer, AdjustNewWindow, 1000
}
SetCursorPos(x, y) {
DllCall("SetCursorPos", "int", x, "int", y)
}
FocusWinUnderCursor() {
MouseGetPos, MouseX, MouseY, WinId
WinActivate, ahk_id %WinId%
}
SetCursorToCenterOfActiveWin() {
WinGetPos x, y, w, h, A
SetCursorPos(x + w / 2, y + h / 2)
}
FocusWinByPos(x, y) {
SetCursorPos(x, y)
FocusWinUnderCursor()
SetCursorToCenterOfActiveWin()
}
GetCursorMonGeometry(ByRef x, ByRef y, ByRef w, ByRef h) {
MouseGetPos, MouseX, MouseY
GetMonGeometryByPos(MouseX, MouseY, x, y, w, h)
}
GetActiveWindowMonGeometry(ByRef x, ByRef y, ByRef w, ByRef h) {
WinGetPos, wx, wy, ww, wh, A
GetMonGeometryByPos(wx + ww / 2, wy + wh / 2, x, y, w, h)
}
GetMonGeometryByPos(px, py, ByRef x, ByRef y, ByRef w, ByRef h) {
SysGet, mc, MonitorCount
mi := 1
loop {
if (mi > mc) {
break
}
SysGet, mon, MonitorWorkArea, %mi%
if (monLeft < px and monRight > px and monTop < py and monBottom > py) {
x := monLeft
y := monTop
w := monRight - monLeft
h := monBottom - monTop
return
}
mi := mi + 1
}
LogDebug("unable to find monitor for pos {1}, {2}", px, py)
}
FocusWinByDirection(direction) {
global RATIO
GetCursorMonGeometry(x, y, w, h)
wf := RATIO / 2
hf := 0.5
if (direction = "right")
wf := RATIO + (1 - RATIO) / 2
FocusWinByPos(x + w * wf, y + h * hf)
}
GetActiveWindowMargins(hwnd, monX, monY, monW, monH, ByRef l, ByRef t, ByRef r, ByRef b) {
Static Dummy5693
,RECTPlus
,S_OK:=0x0
,DWMWA_EXTENDED_FRAME_BOUNDS:=9
;-- Workaround for AutoHotkey Basic
PtrType:=(A_PtrSize=8) ? "Ptr":"UInt"
;-- Get the window's dimensions (excluding shadows)
; Note: Only the first 16 bytes of the RECTPlus structure are used by the
; DwmGetWindowAttribute and GetWindowRect functions.
VarSetCapacity(RECTPlus,32,0)
DWMRC:=DllCall("dwmapi\DwmGetWindowAttribute"
,PtrType,hwnd ;-- hwnd
,"UInt",DWMWA_EXTENDED_FRAME_BOUNDS ;-- dwAttribute
,PtrType,&RECTPlus ;-- pvAttribute
,"UInt",16) ;-- cbAttribute
if (DWMRC<>S_OK)
{
if ErrorLevel in -3,-4 ;-- Dll or function not found (older than Vista)
{
;-- Do nothing else (for now)
}
else
{
outputdebug,
(ltrim join`s
Function: %A_ThisFunc% -
Unknown error calling "dwmapi\DwmGetWindowAttribute".
RC=%DWMRC%,
ErrorLevel=%ErrorLevel%,
A_LastError=%A_LastError%.
"GetWindowRect" used instead.
)
}
;-- Collect the position and size from "GetWindowRect"
DllCall("GetWindowRect",PtrType,hWindow,PtrType,&RECTPlus)
}
;-- Populate the output variables
x1 := NumGet(RECTPlus,0,"Int")
y1 := NumGet(RECTPlus,4,"Int")
x2 := NumGet(RECTPlus,8,"Int")
y2 := NumGet(RECTPlus,12,"Int")
WinGetPos, winX, winY, winW, winH, A
;-- Convert to scaled unit
scale := Round((x2 - x1) / winW*10)/10
x1 := (x1 - monX) / scale + monX
y1 := (y1 - monY) / scale + monY
x2 := (x2 - monX) / scale + monX
y2 := (y2 - monY) / scale + monY
w := (x2 - x1)
h := (y2 - y1)
l := x1 - winX
t := y1 - winY
r := winX+winW-x2
b := winY+winH-y2
LogDebug("active window margins: {1} {2} {3} {4}", l, t, r, b)
}
ToggleActiveWinMaximum() {
WinGet, isMax, MinMax, A
if (isMax) {
WinRestore, A
} else {
WinMaximize, A
}
}
ArrangeActiveWindow(method) {
if (method = "monocle") {
WinGet, isMax, MinMax, A
if (not isMax) {
WinMaximize, A
}
} else {
MoveActiveWinByDirection(method)
}
SaveActiveWindowDirection(method)
}
MoveActiveWinByDirection(direction) {
WinGet, isMax, MinMax, A
if (isMax) {
WinRestore, A
}
global RATIO
global PADDING
activeWinId := WinExist("A")
GetActiveWindowMonGeometry(x, y, w, h)
GetActiveWindowMargins(activeWinId, x, y, w, h, l, t, r, b)
LogDebug("monitor geometry x: {1}, y: {2}, w: {3}, h: {4}", x, y, w, h)
; left
wx := x
wy := y
ww := floor(w * RATIO)
wh := h
LogDebug("left geometry: x: {1}, y: {2}, w: {3}, h: {4}", wx, wy, ww, wh)
; right
if (direction = "right") {
wx := wx + ww + floor(PADDING / 2)
ww := w - ww
LogDebug("right geometry: x: {1}, y: {2}, w: {3}, h: {4}", wx, wy, ww, wh)
} else {
wx := wx + PADDING
}
; adjust for aero margins
wx := wx - l
wy := wy - t
ww := ww + l + r
wh := wh + t + b
; padding
ww := ww - floor(PADDING * 1.5)
wy := wy + PADDING
wh := wh - PADDING * 2
WinMove, A,, wx, wy, ww, wh
LogDebug("move win to x: {1}, y: {2}, w: {3}, h: {4}", wx, wy, ww, wh)
}
GetCursorNearestMonitor(direction, ByRef ml, ByRef mt, ByRef mr, ByRef mb) {
MouseGetPos x, y
if (direction = "right") { ; start at right most, and search for nearest monitor
ml := 10000
mt := 10000
mr := 10000
mb := 10000
} else {
ml := -10000
mt := -10000
mr := -10000
mb := -10000
}
SysGet, mc, MonitorCount
mi := 1
loop {
if (mi > mc) {
break
}
SysGet, mon, MonitorWorkArea, %mi%
if (direction = "right") {
if (monLeft > x and monLeft < ml) {
ml := monLeft
mt := monTop
mr := monRight
mb := monBottom
}
} else {
if (monRight < x and monRight > mr) {
ml := monLeft
mt := monTop
mr := monRight
mb := monBottom
}
}
mi := mi + 1
}
return Abs(ml) != 10000
}
MoveCursorToMonitor(direction) {
if (GetCursorNearestMonitor(direction, ml, mt, mr, mb)) {
SetCursorPos((ml+mr)/2, (mt + mb)/2)
FocusWinUnderCursor()
}
}
MoveWindowToMonitor(direction) {
WinRestore, A
if (GetCursorNearestMonitor(direction, ml, mt, mr, mb)) {
mw := mr - ml
mh := mb - mt
LogDebug("move win to mon size: {1}, {2}, {3}, {4}, {5}, {6}", ml, mt, mr, mb, mw, mh)
ww := mw * 0.5
wh := mh * 0.5
wx := ml + ww / 2
wy := mt + wh / 2
LogDebug("move win to mon: {1}, {2}, {3}, {4}", wx, wy, ww, wh)
WinMove, A,, wx, wy, ww, wh
ArrangeActiveWindowFromStorage()
SetCursorToCenterOfActiveWin()
}
}
SaveArrangement() {
global ARRANGEMENT
global ARRANGEMENT_PATH
LogDebug("SaveArrangement to {1} start", ARRANGEMENT_PATH)
file := FileOpen(ARRANGEMENT_PATH, "w")
file.Write(JSON.Dump(ARRANGEMENT,, 2))
file.Close()
LogDebug("SaveArrangement end")
}
LoadArrangement() {
global ARRANGEMENT
global ARRANGEMENT_PATH
LogDebug("LoadArrangement start {1}", ARRANGEMENT_PATH)
try {
FileRead, temp, %ARRANGEMENT_PATH%
ARRANGEMENT := JSON.Load(temp)
} catch {
ARRANGEMENT := Object()
}
if not IsObject(ARRANGEMENT) {
ARRANGEMENT := Object()
}
if not IsObject(ARRANGEMENT["windows"]) {
ARRANGEMENT["windows"] := Object()
}
if not IsObject(ARRANGEMENT["blacklist"]) {
ARRANGEMENT["blacklist"] := Object()
}
if not IsObject(ARRANGEMENT["whitelist"]) {
ARRANGEMENT["whitelist"] := Object()
}
LogDebug("LoadArrangement end")
}
GetActiveWindowPath() {
WinGet processPath, ProcessPath, A
WinGetClass windowClass, A
GetActiveWindowMonGeometry(x, y, w, h)
return Format("{1}_{2}\{3}\{4}", w, h, processPath, windowClass)
}
IsActiveWindowSeen() {
global ID_SEEN
WinGet winId, ID, A
seen := ID_SEEN.HasKey(winId)
ID_SEEN[winId] := true
return seen
}
BlacklistArrangementForActiveWindow() {
global ARRANGEMENT
windowPath := GetActiveWindowPath()
ARRANGEMENT["blacklist"][windowPath] := true
ARRANGEMENT["whitelist"].Delete(windowPath)
SaveArrangement()
}
WhitelistArrangementForActiveWindow() {
global ARRANGEMENT
windowPath := GetActiveWindowPath()
ARRANGEMENT["whitelist"][windowPath] := true
ARRANGEMENT["blacklist"].Delete(windowPath)
SaveArrangement()
}
IgnoreArrangementForActiveWindow() {
global ARRANGEMENT
windowPath := GetActiveWindowPath()
ARRANGEMENT["whitelist"].Delete(windowPath)
ARRANGEMENT["blacklist"].Delete(windowPath)
ARRANGEMENT["windows"].Delete(windowPath)
SaveArrangement()
}
IsActiveWindowBorderless() {
WinGet s, Style, A
if (not s & +0xC00000) {
return true
}
return false
}
IsActiveWindowSizeboxed() {
WinGet, s, Style, A
return s & 0x40000
}
IsActiveWindowArrangable() {
global ARRANGEMENT
if (ARRANGEMENT["blacklist"].HasKey(GetActiveWindowPath())) {
return false
}
if (ARRANGEMENT["whitelist"].HasKey(GetActiveWindowPath())) {
return true
}
if (IsActiveWindowBorderless()) {
return false
}
return true
}
SaveActiveWindowDirection(direction) {
global ARRANGEMENT
key := GetActiveWindowPath()
ARRANGEMENT["windows"][key] := direction
SaveArrangement()
}
ActiveWinInfo() {
WinGetTitle, title, A
WinGetClass, klass, A
WinGet processPath, ProcessPath, A
WinGet id, ID, A
return Format("{1}:{2}[{3}]{4}", processPath, klass, id, title)
}
ArrangeActiveWindowFromStorage() {
global ARRANGEMENT
windowPath := GetActiveWindowPath()
if ARRANGEMENT["windows"].HasKey(windowPath) {
ArrangeActiveWindow(ARRANGEMENT["windows"][windowPath])
}
}
AdjustNewWindow() {
seen := IsActiveWindowSeen()
arrangable := IsActiveWindowArrangable()
wininfo := ActiveWinInfo()
if not seen {
LogDebug("win: {1}, seen: {2}, arrangable: {3}", wininfo, seen, arrangable)
}
if not seen and arrangable {
ArrangeActiveWindowFromStorage()
}
}