#NoEnv
SetBatchLines, -1
SetIcons()
MyPID := DllCall("GetCurrentProcessId")
Return
^+z:: ; Ctrl + Shift + Z
; Select an area in the source window with the mouse to display in the resizable GUI.
; Use the mouse wheel or '-' and '=' keys to scale the image in the GUI when it is active.
area := GetArea()
hWndSource := WindowFromArea(area)
ThumbGUIs.CreateThumbnail(hWndSource, area)
Return
#If WinActive("ahk_class AutoHotkeyGUI ahk_pid " . MyPID)
*-:: ZoomImage("decrease")
*=:: ZoomImage("increase")
#If
SetIcons() {
static WM_SETICON := 0x80, ICON_BIG := 1
hIcon16 := HIconFromBase64("16", 16)
hIcon32 := HIconFromBase64("32", 32)
UngruppingTrayIcon.Add(hIcon16, "Thumbnails.ahk")
DllCall("SendMessage", "Ptr", A_ScriptHwnd, "UInt", WM_SETICON, "Int", ICON_BIG, "Ptr", hIcon32)
}
GetArea() {
area := {}
StartSelection(area)
while !area.w
Sleep, 100
Return area
}
WindowFromArea(area) {
hWnd := DllCall("WindowFromPoint", "UInt64", area.x + area.w//2 | (area.y + area.h//2) << 32, "Ptr")
hWnd := DllCall("GetAncestor", "Ptr", hWnd, "UInt", GA_ROOT := 2, "Ptr")
WinGetPos, x, y,,, ahk_id %hWnd%
area.x -= x, area.y -= y
Return hWnd
}
ZoomImage(action) {
static WM_MOUSEWHEEL := 0x20A
WHEEL_DELTA := action = "decrease" ? -120 : 120
SendMessage, WM_MOUSEWHEEL, WHEEL_DELTA << 16,,, A
}
StartSelection(area) {
handler := Func("Select").Bind(area)
Hotkey, LButton, % handler, On
ReplaceSystemCursors("IDC_CROSS")
}
Select(area) {
hGui := CreateSelectionGui()
Hook := new WindowsHook(WH_MOUSE_LL := 14, "LowLevelMouseProc", hGui)
Loop {
KeyWait, LButton
WinGetPos, X, Y, W, H, ahk_id %hGui%
} until w > 0
ReplaceSystemCursors("")
Hotkey, LButton, Off
Hook := ""
Gui, %hGui%:Destroy
for k, v in ["x", "y", "w", "h"]
area[v] := %v%
}
ReplaceSystemCursors(IDC = "")
{
static IMAGE_CURSOR := 2, SPI_SETCURSORS := 0x57
, exitFunc := Func("ReplaceSystemCursors").Bind("")
, SysCursors := { IDC_APPSTARTING: 32650
, IDC_ARROW : 32512
, IDC_CROSS : 32515
, IDC_HAND : 32649
, IDC_HELP : 32651
, IDC_IBEAM : 32513
, IDC_NO : 32648
, IDC_SIZEALL : 32646
, IDC_SIZENESW : 32643
, IDC_SIZENWSE : 32642
, IDC_SIZEWE : 32644
, IDC_SIZENS : 32645
, IDC_UPARROW : 32516
, IDC_WAIT : 32514 }
if !IDC {
DllCall("SystemParametersInfo", "UInt", SPI_SETCURSORS, "UInt", 0, "UInt", 0, "UInt", 0)
OnExit(exitFunc, 0)
}
else {
hCursor := DllCall("LoadCursor", "Ptr", 0, "UInt", SysCursors[IDC], "Ptr")
for k, v in SysCursors {
hCopy := DllCall("CopyImage", "Ptr", hCursor, "UInt", IMAGE_CURSOR, "Int", 0, "Int", 0, "UInt", 0, "Ptr")
DllCall("SetSystemCursor", "Ptr", hCopy, "UInt", v)
}
OnExit(exitFunc)
}
}
CreateSelectionGui() {
Gui, New, +hwndhGui +Alwaysontop -Caption +LastFound +ToolWindow +E0x20 -DPIScale
WinSet, Transparent, 130
Gui, Color, FFC800
Return hGui
}
LowLevelMouseProc(nCode, wParam, lParam) {
static WM_MOUSEMOVE := 0x200, WM_LBUTTONUP := 0x202
, coords := [], startMouseX, startMouseY, hGui
, timer := Func("LowLevelMouseProc").Bind("timer", "", "")
if (nCode = "timer") {
while coords[1] {
point := coords.RemoveAt(1)
mouseX := point[1], mouseY := point[2]
x := startMouseX < mouseX ? startMouseX : mouseX
y := startMouseY < mouseY ? startMouseY : mouseY
w := Abs(mouseX - startMouseX)
h := Abs(mouseY - startMouseY)
try Gui, %hGUi%: Show, x%x% y%y% w%w% h%h% NA
}
}
else {
hGui := A_EventInfo
if (wParam = WM_LBUTTONUP)
startMouseX := startMouseY := ""
if (wParam = WM_MOUSEMOVE) {
mouseX := NumGet(lParam + 0, "Int")
mouseY := NumGet(lParam + 4, "Int")
if (startMouseX = "") {
startMouseX := mouseX
startMouseY := mouseY
}
coords.Push([mouseX, mouseY])
SetTimer, % timer, -10
}
Return DllCall("CallNextHookEx", "Ptr", 0, "Int", nCode, "UInt", wParam, "Ptr", lParam)
}
}
class WindowsHook {
__New(type, callback, eventInfo := "", isGlobal := true) {
this.callbackPtr := RegisterCallback(callback, "Fast", 3, eventInfo)
this.hHook := DllCall("SetWindowsHookEx", "Int", type, "Ptr", this.callbackPtr
, "Ptr", !isGlobal ? 0 : DllCall("GetModuleHandle", "UInt", 0, "Ptr")
, "UInt", isGlobal ? 0 : DllCall("GetCurrentThreadId"), "Ptr")
}
__Delete() {
DllCall("UnhookWindowsHookEx", "Ptr", this.hHook)
DllCall("GlobalFree", "Ptr", this.callBackPtr, "Ptr")
}
}
class ThumbGUIs
{
CreateThumbnail(hWndSource, area) {
static firstCall := true
if firstCall {
this.Thumbnails := []
DllCall("RegisterShellHookWindow", "Ptr", A_ScriptHwnd)
this.shellHookMsg := DllCall("RegisterWindowMessage", "Str", "SHELLHOOK")
this.onShellHook := ObjBindMethod(this, "ShellProc")
this.onSysCommand := ObjBindMethod(this, "WM_SYSCOMMAND")
this.onMouseWheel := ObjBindMethod(this, "WM_MOUSEWHEEL")
this.onGuiSize := ObjBindMethod(this, "WM_SIZE")
OnExit( ObjBindMethod(this, "Clear") )
firstCall := false
}
this.Thumbnails.Push( new this.Thumb(hWndSource, area) )
if this.Thumbnails.Count() = 1 {
OnMessage(this.shellHookMsg, this.onShellHook)
OnMessage(0x112, this.onSysCommand)
OnMessage(0x20A, this.onMouseWheel)
OnMessage(0x005, this.onGuiSize)
}
}
Clear() {
this.Thumbnails := ""
DllCall("DeregisterShellHookWindow", "Ptr", A_ScriptHwnd)
}
ShellProc(nCode, hWnd) {
static HSHELL_WINDOWDESTROYED := 2, HSHELL_REDRAW := 6
if !(nCode = HSHELL_WINDOWDESTROYED || nCode = HSHELL_REDRAW)
Return
for k, Wnd in this.Thumbnails {
if !(hWnd = Wnd.hGui || hWnd = Wnd.hSource || !WinExist("ahk_id" . Wnd.hSource))
continue
if (nCode = HSHELL_WINDOWDESTROYED) {
(!ToRemove && ToRemove := [])
ToRemove.InsertAt(1, k)
}
else if (nCode = HSHELL_REDRAW && hWnd = Wnd.hSource) {
WinGetTitle, title, % "ahk_id" . Wnd.hSource
try Gui, % Wnd.hGui . ": Show", NA, % Wnd.title[1] := title
}
}
for k, v in ToRemove
this.Thumbnails.RemoveAt(v)
timer := ObjBindMethod(this, "RemoveThumbWithNoSource")
SetTimer, % timer, -100
}
RemoveThumbWithNoSource() {
if !this.Thumbnails.Count()
Return
for k, Wnd in this.Thumbnails {
if !WinExist("ahk_id" . Wnd.hSource) {
(!ToRemove && ToRemove := [])
ToRemove.InsertAt(1, k)
}
}
for k, v in ToRemove
this.Thumbnails.RemoveAt(v)
if !this.Thumbnails.Count() {
OnMessage(0x112, this.onSysCommand, 0)
OnMessage(0x20E, this.onMouseWheel, 0)
OnMessage(0x005, this.onGuiSize, 0)
OnMessage(this.shellHookMsg, this.onShellHook, 0)
}
}
WM_SYSCOMMAND(wp, lp) {
if (wp != 0xF060) ; SC_CLOSE
Return
for k, Wnd in this.Thumbnails {
if (A_Gui = Wnd.hGui) {
Gui, %A_Gui%: Destroy
break
}
}
}
WM_MOUSEWHEEL(wp, lp) {
for k, Wnd in this.Thumbnails {
if (A_Gui != Wnd.hGui)
continue
guiWidth := Wnd.guiWidth
guiHeight := Wnd.guiHeight
image_w := Wnd.area.w
image_h := Wnd.area.h
real_image_w := Wnd.area.real_w
real_image_h := Wnd.area.real_h
WHEEL_DELTA := wp >> 16
ratio := WHEEL_DELTA > 0 ? 1.08 : 0.97
new_image_w := image_w * ratio
new_image_h := image_h * ratio
; this approach won't let to skip 100 %
if (new_image_w >= real_image_w && image_w < real_image_w)
|| (new_image_w <= real_image_w && image_w > real_image_w)
{
image_w := Wnd.area.w := real_image_w ; 100 %
image_h := Wnd.area.h := real_image_h ; 100 %
}
else {
image_w := Wnd.area.w := new_image_w
image_h := Wnd.area.h := new_image_h
}
image_x := image_w < guiWidth ? (guiWidth - image_w)//2 : 0
image_y := image_h < guiHeight ? (guiHeight - image_h)//2 : 0
Wnd.ThumbInst.SetThumbRegion(image_x, image_y, image_w, image_h)
Gui, % Wnd.hGui . ": Show", NA, % "Thumbnail size: " . Round(image_w / real_image_w * 100) . " %"
timer := Wnd.RestoreTitleTimer
SetTimer, % timer, -1000
break
}
}
WM_SIZE(wp, lp) {
if (wp = 1) ; SIZE_MINIMIZED
Return
for k, Wnd in this.Thumbnails {
if (A_Gui = Wnd.hGui && found := true)
break
}
if !found
Return
Wnd.guiWidth := guiWidth := lp & 0xFFFF
Wnd.guiHeight := guiHeight := lp >> 16
image_w := Wnd.area.w
image_h := Wnd.area.h
a := image_w < guiWidth
b := image_h < guiHeight
if (a || b) {
Wnd.ThumbInst.SetThumbRegion( a ? (guiWidth - image_w)//2 : 0
, b ? (guiHeight - image_h)//2 : 0
, image_w, image_h )
}
}
class Thumb {
__New(hSource, area) {
this.area := area
area.real_w := area.w
area.real_h := area.h
this.hSource := hSource
this.title := []
this.hGui := this.CreateGui(300, 200)
this.onTimer := new this.Timer(this.hGui, this.title)
this.RestoreTitleTimer := ObjBindMethod(this.onTimer, "RestoreTitle")
}
__Delete() {
this.ThumbInst := ""
try Gui, % this.hGui . ": Destroy"
}
CreateGui(guiWidth, guiHeight) {
static WM_SETICON := 0x80, ICON_SMALL := 0
this.guiWidth := guiWidth
this.guiHeight := guiHeight
hSource := this.hSource, image := this.area
WinGetTitle, title, ahk_id %hSource%
this.title[1] := title
Gui, New, +hwndhGui +Resize +AlwaysOnTop +Owner -DpiScale -MaximizeBox -MinimizeBox, % title
a := image.w < guiWidth
b := image.h < guiHeight
try this.ThumbInst := new ThumbnailGui( hGui , a ? (guiWidth - image.w)//2 : 0
, b ? (guiHeight - image.h)//2 : 0
, image.w, image.h
, hSource, image.x, image.y, image.w, image.h )
catch e {
MsgBox, 16, Error, % e.Message
Gui, Destroy
Return
}
Gui, Show, w%guiWidth% h%guiHeight%
SendMessage, WM_SETICON, ICON_SMALL, this.GetWindowIcon(hSource),, ahk_id %hGui%
Return hGui
}
GetWindowIcon(hWnd) {
static WM_GETICON := 0x007F, ICON_SMALL := 0, GCLP_HICONSM := -34
, GetClassLong := "GetClassLong" . (A_PtrSize = 4 ? "" : "Ptr")
SendMessage, WM_GETICON, ICON_SMALL, A_ScreenDPI,, ahk_id %hWnd%
if !smallIcon := ErrorLevel
smallIcon := DllCall(GetClassLong, "Ptr", hWnd, "Int", GCLP_HICONSM, "Ptr")
Return smallIcon
}
class Timer {
__New(hGui, ObjTitle) {
this.hGui := hGui
this.title := ObjTitle
}
RestoreTitle() {
Gui, % this.hGui . ": Show", NA, % this.title[1]
}
}
}
}
class ThumbnailGui
{
__New(hWndThumb, thumbX, thumbY, thumbW, thumbH, hWndSource, sourceX, sourceY, sourceW, sourceH, clientOnly := false) {
this.hLib := DllCall("LoadLibrary", "Str", "dwmapi.dll")
DllCall("dwmapi\DwmIsCompositionEnabled", "UIntP", enabled)
if !enabled {
DllCall("FreeLibrary", "Ptr", this.hLib)
MsgBox, 64, DWM, DWM is disabled, enable it for the script to work!
ExitApp
}
if DllCall("dwmapi\DwmRegisterThumbnail", "Ptr", hWndThumb, "Ptr", hWndSource, "PtrP", hThumbnailId) != 0
throw "DwmRegisterThumbnail failed"
this.ThumbnailId := hThumbnailId
this.SetSourceRegion(sourceX, sourceY, sourceW, sourceH, clientOnly)
this.SetThumbRegion(thumbX, thumbY, thumbW, thumbH)
}
__Delete() {
DllCall("dwmapi\DwmUnregisterThumbnail", "Ptr", this.ThumbnailId)
DllCall("FreeLibrary", "Ptr", this.hLib)
}
SetSourceRegion(X, Y, W, H, clientOnly) {
this.SetRegion(DWM_TNP_RECTSOURCE := 0x2, X, Y, W, H, clientOnly)
}
SetThumbRegion(X, Y, W, H) {
this.SetRegion(DWM_TNP_RECTDESTINATION := 0x1, X, Y, W, H)
}
SetRegion(flag, X, Y, W, H, clientOnly := false) {
static DWM_TNP_SOURCECLIENTAREAONLY := 0x10
, DWM_TNP_RECTDESTINATION := 0x1
, DWM_TNP_RECTSOURCE := 0x2
VarSetCapacity(DWM_THUMBNAIL_PROPERTIES, 48, 0)
flags := flag | DWM_TNP_SOURCECLIENTAREAONLY*(flag = DWM_TNP_RECTSOURCE)
NumPut( flags , DWM_THUMBNAIL_PROPERTIES)
NumPut( X , DWM_THUMBNAIL_PROPERTIES, 4 + 16*(flag = DWM_TNP_RECTSOURCE) )
NumPut( Y , DWM_THUMBNAIL_PROPERTIES, 8 + 16*(flag = DWM_TNP_RECTSOURCE) )
NumPut( X + W , DWM_THUMBNAIL_PROPERTIES, 12 + 16*(flag = DWM_TNP_RECTSOURCE) )
NumPut( Y + H , DWM_THUMBNAIL_PROPERTIES, 16 + 16*(flag = DWM_TNP_RECTSOURCE) )
NumPut( clientOnly, DWM_THUMBNAIL_PROPERTIES, 44, "UInt")
DllCall("dwmapi\DwmUpdateThumbnailProperties", "Ptr", this.ThumbnailId, "Ptr", &DWM_THUMBNAIL_PROPERTIES)
}
}
class UngruppingTrayIcon
{
#NoTrayIcon
Add(hIcon, trayTip := "") {
static NIM_ADD := 0x00000000
, flags := ( NIF_MESSAGE := 0x00000001 )
| ( NIF_ICON := 0x00000002 )
| ( NIF_TIP := 0x00000004 )
| ( NIF_GUID := 0x00000020 )
(trayTip = "" && trayTip := A_ScriptName)
VarSetCapacity(NOTIFYICONDATA, size := 64 + StrPut(trayTip, "CP0"), 0)
NumPut(size , &NOTIFYICONDATA)
NumPut(A_ScriptHwnd , &NOTIFYICONDATA + A_PtrSize)
NumPut(flags , &NOTIFYICONDATA + A_PtrSize*2 + 4)
NumPut(nMsg := 0x404, &NOTIFYICONDATA + A_PtrSize*2 + 8)
NumPut(hIcon , &NOTIFYICONDATA + A_PtrSize*3 + 8)
StrPut(trayTip , &NOTIFYICONDATA + A_PtrSize*4 + 8, "CP0")
DllCall("RtlMoveMemory", "Ptr", &NOTIFYICONDATA + size - A_PtrSize - 16, "Ptr", this.CreateGuid(_), "Ptr", 16)
if !DllCall("Shell32\Shell_NotifyIcon", "UInt", NIM_ADD, "Ptr", &NOTIFYICONDATA)
throw "Shell_NotifyIcon failed, error " . A_LastError
OnExit( ObjBindMethod(this, "Remove") )
}
Remove() {
static NIM_DELETE := 0x00000002
VarSetCapacity(NOTIFYICONDATA, size := 16, 0)
NumPut(size , NOTIFYICONDATA)
NumPut(A_ScriptHwnd, NOTIFYICONDATA, A_PtrSize)
DllCall("Shell32\Shell_NotifyIcon", "UInt", NIM_DELETE, "Ptr", &NOTIFYICONDATA)
}
CreateGuid(ByRef GUID) {
VarSetCapacity(GUID, 16, 0)
DllCall("Ole32\CoCreateGuid", "Ptr", &GUID)
Return &GUID
}
}
HIconFromBase64(name, height := 16) {
Icon16 =
(LTrim Join
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xh
BQAAAAFzUkdCAK7OHOkAAABaUExURUdwTHGvSG6uRnGvSICAgHGvSXGvSHGvSFWq
VXKuR3GvSICfQG+wSnCvSHGvSHCuSHKtSXGqVXGvSHGvSHGyR3GvSHGuSHOwSXCu
S3OsRni0S4C/QHOtSnGvSAWWj+4AAAAddFJOUwDYLK0C6MvpAy/yCDfc0LE4CfPm
K+vkKikoEQQf7zoKCwAAAFVJREFUGNOdjkcOgCAABFFBwN77/P+bxgjBxJPubTbZ
IsRftU2RP9B2KE0We67p5XAYTXJzCXJECAPRxRVMFrZ91ZAGAxZnuAjKR1zpHErf
s+9j33QCgyYGnB7HPS8AAAAASUVORK5CYII=
)
Icon32 =
(LTrim Join
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xh
BQAAAAFzUkdCAK7OHOkAAACrUExURUdwTHGvSHCuSHGvSHGvSHGvSICfQHGvSICA
gFWqVXS5RnKvSHGvSXGvSXGvSHGvSGqqQHGvR3GvSHGvSHGvSHGwSHOsRoC/QHGv
SHGvSHKuR3KvR3KxSHKwSXGwSHGuSHOtSnGvSAD/AHaxTnCvSHWqSnCuS3KuSHeq
RHGvR3KuSHKvSHGvSHKwSXGvSXCvR3KuRnGuR3GuSXSyRnCwR3CvSHGvSHGuSXGv
SJfuEOkAAAA4dFJOUwD9Uvr4+wj8AgMLYKzo3skM7+r+66coBPCwL51OXueOH/YB
DVkYKW4PyHJcqVfhXUxofiGEf+CFAhESuQAAAL5JREFUOMvlUscWgjAQXJoUQey9
Ym/Ylf//MjMESxAfnHUOye7MJlsSov/DYuU6znI+/iJP1CDCSE6Q2wpT9M16Z+vM
UOtxvYWTW27nYdcEudEENyXj5hcsoiG8qvHSixIYmWZstZ53aLmHblXgK2QiOadQ
RyBFEWboBR0aYAOzJzukNJ6lxHvrUh+ba9KhQD3OlRMCgFiAmAI4iimEIkN4YpFi
mwyneJtvgzr714v3Oaj0UWd4rPTnzvBhfhl3ug0y2WJ4yTUAAAAASUVORK5CYII=
)
VarSetCapacity(data, size := StrLen( RTrim(Icon%name%, "=") )*3//4)
DllCall("Crypt32\CryptStringToBinary", "Ptr", &Icon%name%, "UInt", StrLen(Icon%name%)
, "UInt", CRYPT_STRING_BASE64 := 1, "Ptr", &data
, "UIntP", size, "Ptr", 0, "Ptr", 0)
hIcon := DllCall("CreateIconFromResourceEx", "Ptr", &data, "UInt", size, "UInt", true
, "UInt", 0x30000, "Int", height, "Int", height, "UInt", 0)
Return hIcon
}