Skip to content

Instantly share code, notes, and snippets.

@naikrovek
Last active October 12, 2022 08:45
Show Gist options
  • Save naikrovek/b13a77d169de0e192bcf48fec02bc21f to your computer and use it in GitHub Desktop.
Save naikrovek/b13a77d169de0e192bcf48fec02bc21f to your computer and use it in GitHub Desktop.
AutoHotKey Scripts for arranging app windows on a UHD monitor.

start AutoHotKeyu64.exe wm4k.ahd should launch it. both of the files in this gist should be in the same directory.

with NUMLOCK off, pushing number keys will move windows around. 8 will resize a window to fit 1/8th of your screen (1/4 width, 1/2 height.)

Similar operations for 1, 2, 3, 4, and 6. CTRL does things in combination with 3, 4, and 6, so try those.

Numpad * (with numlock off, always) will arrange a window to occupy 1/12th of the screen (1/6th horizontally and 1/2 vertically).

NEW - Numpad+ and Numpad- will adjust a window's position horizontally

Feel free to add any other combinations you want. I don't know AHK well enough to add more.

Written by Roel Hammerschlag and modified by myself.

License: public domain

TODO:

  • support four horizontal placement rows instead of just two, to support stacking windows 3 and 4 high.
  • support nudging up and down.
;------------------------------
;
; Function: WinGetPosEx
;
; Description:
;
; Gets the position, size, and offset of a window. See the *Remarks* section
; for more information.
;
; Parameters:
;
; hWindow - Handle to the window.
;
; X, Y, Width, Height - Output variables. [Optional] If defined, these
; variables contain the coordinates of the window relative to the
; upper-left corner of the screen (X and Y), and the Width and Height of
; the window.
;
; Offset_X, Offset_Y - Output variables. [Optional] Offset, in pixels, of the
; actual position of the window versus the position of the window as
; reported by GetWindowRect. If moving the window to specific
; coordinates, add these offset values to the appropriate coordinate
; (X and/or Y) to reflect the true size of the window.
;
; Returns:
;
; If successful, the address of a RECTPlus structure is returned. The first
; 16 bytes contains a RECT structure that contains the dimensions of the
; bounding rectangle of the specified window. The dimensions are given in
; screen coordinates that are relative to the upper-left corner of the screen.
; The next 8 bytes contain the X and Y offsets (4-byte integer for X and
; 4-byte integer for Y).
;
; Also if successful (and if defined), the output variables (X, Y, Width,
; Height, Offset_X, and Offset_Y) are updated. See the *Parameters* section
; for more more information.
;
; If not successful, FALSE is returned.
;
; Requirement:
;
; Windows 2000+
;
; Remarks, Observations, and Changes:
;
; * Starting with Windows Vista, Microsoft includes the Desktop Window Manager
; (DWM) along with Aero-based themes that use DWM. Aero themes provide new
; features like a translucent glass design with subtle window animations.
; Unfortunately, the DWM doesn't always conform to the OS rules for size and
; positioning of windows. If using an Aero theme, many of the windows are
; actually larger than reported by Windows when using standard commands (Ex:
; WinGetPos, GetWindowRect, etc.) and because of that, are not positioned
; correctly when using standard commands (Ex: gui Show, WinMove, etc.). This
; function was created to 1) identify the true position and size of all
; windows regardless of the window attributes, desktop theme, or version of
; Windows and to 2) identify the appropriate offset that is needed to position
; the window if the window is a different size than reported.
;
; * The true size, position, and offset of a window cannot be determined until
; the window has been rendered. See the example script for an example of how
; to use this function to position a new window.
;
; * 20150906: The "dwmapi\DwmGetWindowAttribute" function can return odd errors
; if DWM is not enabled. One error I've discovered is a return code of
; 0x80070006 with a last error code of 6, i.e. ERROR_INVALID_HANDLE or "The
; handle is invalid." To keep the function operational during this types of
; conditions, the function has been modified to assume that all unexpected
; return codes mean that DWM is not available and continue to process without
; it. When DWM is a possibility (i.e. Vista+), a developer-friendly messsage
; will be dumped to the debugger when these errors occur.
;
; Credit:
;
; Idea and some code from *KaFu* (AutoIt forum)
;
;-------------------------------------------------------------------------------
WinGetPosEx(hWindow,ByRef X="",ByRef Y="",ByRef Width="",ByRef Height="",ByRef Offset_X="",ByRef Offset_Y="")
{
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
; Note: Only the first 16 bytes of the RECTPlus structure are used by the
; DwmGetWindowAttribute and GetWindowRect functions.
VarSetCapacity(RECTPlus,24,0)
DWMRC:=DllCall("dwmapi\DwmGetWindowAttribute"
,PtrType,hWindow ;-- 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
X:=Left :=NumGet(RECTPlus,0,"Int")
Y:=Top :=NumGet(RECTPlus,4,"Int")
Right :=NumGet(RECTPlus,8,"Int")
Bottom :=NumGet(RECTPlus,12,"Int")
Width :=Right-Left
Height :=Bottom-Top
OffSet_X:=0
OffSet_Y:=0
;-- If DWM is not used (older than Vista or DWM not enabled), we're done
if (DWMRC<>S_OK)
Return &RECTPlus
;-- Collect dimensions via GetWindowRect
VarSetCapacity(RECT,16,0)
DllCall("GetWindowRect",PtrType,hWindow,PtrType,&RECT)
GWR_Width :=NumGet(RECT,8,"Int")-NumGet(RECT,0,"Int")
;-- Right minus Left
GWR_Height:=NumGet(RECT,12,"Int")-NumGet(RECT,4,"Int")
;-- Bottom minus Top
;-- Calculate offsets and update output variables
NumPut(Offset_X:=(Width-GWR_Width)//2,RECTPlus,16,"Int")
NumPut(Offset_Y:=(Height-GWR_Height)//2,RECTPlus,20,"Int")
Return &RECTPlus
}
/*
wm4k: butt-simple window management for 4k monitors
(as of v2.2 this works for monitors of any size)
by Roel Hammerschlag
roel@hammerschlag.llc
modifications and adjustments by Jeremiah Johnson
naikrovek {at} gmail.com
http://github.com/naikrovek/
Version 1.0: September 2014
Version 2.0: April 2016
- streamline math & code
- add NumPadMul 1/12 window
- remove NumPad0 "tiny" window
- compensate for transparent window borders in Windows 10
Version 2.1: July 2016
- add NumPadAdd & NumPadSub nudging
Version 2.2: June 2019
- replace hard-coded 4k dimensions with A_ScreenWidth and A_ScreenHeight
- allow #Fnn keys in case the computer doesn't have a numpad
- fix bug where nudge function resized the nudged window
Version 2.3: November 2019
- add landscape 1/3 window
- add 2/3 window
- robust handling of screen edges in nudge function
Version 2.4: November 2019
Version 2.5: June 2020
- ignore +, -, and * on the numpad if numlock is on.
Before using, set values for marginLeft etc. below to reflect
your own Windows Taskbar size & location.
Hotkeys are designed for NumLock OFF.
*/
SetBatchLines, -1
SetTitleMatchMode, 2
SetTitleMatchMode, fast
SetWinDelay, 10
#NoEnv
#SingleInstance force
#WinActivateForce
marginLeft := 0
marginRight := 0
marginTop := 0
marginBottom := 40
xStep := (A_ScreenWidth - marginLeft - marginRight) // 12
yStep := (A_ScreenHeight - marginTop - marginBottom) // 2
yTopRow := marginTop
yBottomRow := marginTop + yStep
topRowTest := marginTop + yStep/2
NumpadEnd:: ; "1" on num pad invokes full-screen window
#F1::
WinMaximize, A
return
NumpadDown:: ; "2" on num pad handles 1/2-screen windows
#F2:: ; (portrait)
width := xStep*6
height := yStep*2
Gosub, spyActiveWindow
x := marginLeft + width * mod(((xInner - marginLeft) // width + 1), 2)
y := yTopRow
WinMove, A, , x - xOffset, y - yOffset, width + wDiff, height + hDiff
return
^NumpadDown:: ; CTRL+"2" on num pad handles 1/2-screen windows
^#F2:: ; (landscape)
width := xStep*12
height := yStep
Gosub, spyActiveWindow
x := 0
Gosub, chooseRow
WinMove, A, , x - xOffset, y - yOffset, width + wDiff, height + hDiff
return
NumpadPgDn:: ; "3" on num pad handles 1/3-screen windows
#F3:: ; (portait format)
width := xStep * 4
height := yStep * 2
Gosub, spyActiveWindow
x := marginLeft + width * mod(((xInner - marginLeft) // width + 1), 3)
y := yTopRow
WinMove, A, , x - xOffset, y - yOffset, width + wDiff, height + hDiff
return
^NumpadPgDn:: ; CTRL+"3" on num pad handles 1/3-screen windows
^#F3:: ; (landscape format)
width := xStep * 8
height := yStep
Gosub, spyActiveWindow
if (x = marginLeft)
x := marginLeft + xStep * 4
else
x := marginLeft
Gosub, chooseRow
WinMove, A, , x - xOffset, y - yOffset, width + wDiff, height + hDiff
return
^+NumpadPgDn:: ; CTRL+SHIFT+"3" on num pad handles 2/3-screen windows
^+#F3:: ; (for laptops/small screens)
width := xStep * 8
height := yStep * 2
Gosub, spyActiveWindow
if (x = marginLeft)
x := marginLeft + xStep * 4
else
x := marginLeft
y := yTopRow
WinMove, A, , x - xOffset, y - yOffset, width + wDiff, height + hDiff
return
^NumpadLeft:: ; CTRL+"4" on num pad handles 1/4-screen windows
^#F4:: ; (portrait format)
width := xStep * 3
height := yStep * 2
Gosub, spyActiveWindow
x := marginLeft + width * mod(((xInner - marginLeft) // width + 1), 4)
y := yTopRow
WinMove, A, , x - xOffset, y - yOffset, width + wDiff, height + hDiff
return
NumpadLeft:: ; "4" on num pad handles 1/4-screen windows
#F4:: ; (landscape format)
width := xStep * 6
height := yStep
Gosub, spyActiveWindow
x := marginLeft + width * mod(((xInner - marginLeft) // width + 1), 2)
Gosub, chooseRow
WinMove, A, , x - xOffset, y - yOffset, width + wDiff, height + hDiff
return
^NumpadRight:: ; CTRL+"6" on num pad handles 1/6-screen windows
^#F6:: ; (portrait format)
width := xStep*2
height := yStep*2
Gosub, spyActiveWindow
x := marginLeft + width * mod(((xInner - marginLeft) // width + 1), 6)
y := yTopRow
WinMove, A, , x - xOffset, y - yOffset, width + wDiff, height + hDiff
return
NumpadRight:: ; "6" on num pad handles 1/6-screen windoows
#F6:: ; (landscape format)
width := xStep * 4
height := yStep
Gosub, spyActiveWindow
x := marginLeft + width * mod(((xInner - marginLeft) // width + 1), 3)
Gosub, chooseRow
WinMove, A, , x - xOffset, y - yOffset, width + wDiff, height + hDiff
return
NumpadUp:: ; "8" on num pad handles 1/8-screen windows
#F8::
width := xStep * 3
height := yStep
Gosub, spyActiveWindow
x := marginLeft + width * mod(((xInner - marginLeft) // width + 1), 4)
Gosub, chooseRow
WinMove, A, , x - xOffset, y - yOffset, width + wDiff, height + hDiff
return
#F12::
width := xStep * 2
height := yStep
Gosub, spyActiveWindow
x := marginLeft + width * mod(((xInner - marginLeft) // width + 1), 6)
Gosub, chooseRow
WinMove, A, , x - xOffset, y - yOffset, width + wDiff, height + hDiff
return
NumpadMult:: ; where the "12" would be, if there was one. :-)
if GetKeyState("NumLock", "T") == 0 {
width := xStep * 2
height := yStep
Gosub, spyActiveWindow
x := marginLeft + width * mod(((xInner - marginLeft) // width + 1), 6)
Gosub, chooseRow
WinMove, A, , x - xOffset, y - yOffset, width + wDiff, height + hDiff
} else {
SendInput {*}
}
return
#F9::
WinSet, AlwaysOnTop, toggle, A
return
NumpadDel:: ; toggle "Always On Top"
if GetKeyState("NumLock", "T") == 0 {
WinSet, AlwaysOnTop, toggle, A
} else {
SendInput {Del}
}
return
#]::
Gosub, spyActiveWindow
x := marginLeft + xStep * mod(min(((xInner - marginLeft) // xStep + 1), 11), 12)
if (yOuter < topRowTest)
y := yTopRow
else
y := yBottomRow
WinMove, A, , x - xOffset, y - yOffset, wOuter, hOuter
return
NumpadAdd:: ; nudge right
if GetKeyState("NumLock", "T") == 0 {
Gosub, spyActiveWindow
x := marginLeft + xStep * mod(min(((xInner - marginLeft) // xStep + 1), 11), 12)
if (yOuter < topRowTest)
y := yTopRow
else
y := yBottomRow
WinMove, A, , x - xOffset, y - yOffset, wOuter, hOuter
} else {
SendInput {+}
}
return
#[::
Gosub, spyActiveWindow
maxLeftSteps := wInner / xStep - 1
x := marginLeft + xStep * mod(max(((xInner - marginLeft) // xStep - 1), - maxLeftSteps), 12)
if (yOuter < topRowTest)
y := yTopRow
else
y := yBottomRow
WinMove, A, , x - xOffset, y - yOffset, wOuter, hOuter
return
NumpadSub:: ; nudge left
if GetKeyState("NumLock", "T") == 0 {
Gosub, spyActiveWindow
maxLeftSteps := wInner / xStep - 1
x := marginLeft + xStep * mod(max(((xInner - marginLeft) // xStep - 1), - maxLeftSteps), 12)
if (yOuter < topRowTest)
y := yTopRow
else
y := yBottomRow
WinMove, A, , x - xOffset, y - yOffset, wOuter, hOuter
} else {
SendInput {-}
}
return
chooseRow:
if (x = marginLeft) {
if (yOuter < topRowTest) ; if the x position just cycled to the left
y := yBottomRow ; edge of the screen then switch rows...
else
y := yTopRow
} else {
if (yOuter < topRowTest) ; ...otherwise, keep it in the same row.
y := yTopRow
else
y := yBottomRow
}
return
spyActiveWindow:
WinRestore, A
WinGet, hA, ID, A
WinGetPos, xOuter, yOuter, wOuter, hOuter, A
WinGetPosEx(hA, xInner, yInner, wInner, hInner)
xOffset := xInner - xOuter
yOffset := yInner - yOuter
wDiff := wOuter - wInner
hDiff := hOuter - hInner
return
#include WinGetPosEx.ahk
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment