; WinSpy - Window Information Tool

#SingleInstance Off
#NoEnv
#MaxMem 640
#NoTrayIcon
SetWorkingDir %A_ScriptDir%
DetectHiddenWindows On
CoordMode Mouse, Screen
SetControlDelay -1
SetWinDelay -1
SetBatchLines -1
ListLines Off
#KeyHistory 0

Global AppName := "WinSpy"
, Version := "1.0.3"
, IniFile := AppName . ".ini"
, ResDir := A_ScriptDir . "\Resources"

, hFindTool
, Bitmap1 := ResDir . "\FindTool1.bmp"
, Bitmap2 := ResDir . "\FindTool2.bmp"
, hCrossHair := DllCall("LoadImage", "Int", 0
    , "Str", ResDir . "\CrossHair.cur"
    , "Int", 2 ; IMAGE_CURSOR
    , "Int", 32, "Int", 32
    , "UInt", 0x10, "Ptr") ; LR_LOADFROMFILE
, hOldCursor
, Dragging := False

, g_Borders := []
, g_hWnd
, hSpyWnd
, hTab
, hStylesTab
, hWindowsTab
, g_Style
, g_ExStyle
, g_ExtraStyle
, g_WinMsgs := ""
, hCbxMsg
, Cursors := {}
, oStyles := {}
, Workaround := True
, FindDlgExist := False
, MenuViewerExist := False
, hTreeWnd := 0
, TreeIcons := ResDir . "\TreeIcons.icl"
, ImageList
, g_TreeShowAll := False
, g_Minimized
, g_MouseCoordMode := "Screen"

, g_DetectHidden
, g_Minimize
, g_AlwaysOnTop

IniRead g_DetectHidden, %IniFile%, Settings, DetectHidden, 0
IniRead g_Minimize, %IniFile%, Settings, CompactMode, 0
IniRead g_AlwaysOnTop, %IniFile%, Settings, AlwaysOnTop, 0

IniRead g_ShowBorder, %IniFile%, Screenshot, ShowBorder, 1
IniRead g_BorderColor, %IniFile%, Screenshot, BorderColor, 0xFF0000
g_BorderColorTemp := g_BorderColor
IniRead g_BorderWidth, %IniFile%, Screenshot, BorderWidth, 4

Menu Tray, Icon, %ResDir%\WinSpy.ico

; Main window
Gui Spy: New, LabelSpy hWndhSpyWnd
SetWindowIcon(hSpyWnd, ResDir . "\WinSpy.ico", 1)

Gui Font, s9, Segoe UI

Gui Add, Picture, hWndhFindTool gFindToolHandler x10 y12 w31 h28, %Bitmap1%
Gui Add, Text, x48 y10 w198, Drag the Finder Tool over a window`nto select it, then release the mouse
Gui Add, CheckBox, gSetDHW x247 y8 Checked%g_DetectHidden%, &Detect Hidden Windows
Gui Add, CheckBox, vg_Minimize gSetMinimize x247 y28 Checked%g_Minimize%, Compact &Mode

Gui Add, Tab3, hWndhTab vTab gTabHandler x10 y50 w382 h373 AltSubmit -Wrap, General|Styles|Details|Messages|Extra|Windows|Process
; General
Gui Tab, 1
    Gui Add, Text, x28 y88 w62 h23 +0x200, Handle:
    Gui Add, Edit, vEdtHandle gSetHandle x109 y90 w180 h21
    Gui Add, Button, hWndhBtnCommands gShowCommandsMenu x297 y88 w83 h23, Commands
    Gui Add, Text, vTxtText x28 y119 w62 h21 +0x200, Text:
    Gui Add, Edit, vEdtText x109 y120 w180 h21
    Gui Add, Button, vBtnSetText gSetText x297 y118 w83 h23, Set Text

    Gui Add, Text, x28 y146 w62 h21 +0x200, Class:
    Gui Add, Edit, vEdtClass x109 y150 w270 h21 -E0x200 ReadOnly

    Gui Add, Text, x28 y176 w62 h21 +0x200, ClassNN:
    Gui Add, Edit, vEdtClassNN x109 y180 w270 h21 -E0x200 ReadOnly
    Gui Add, Text, x23 y209 w357 0x10

    Gui Add, Text, x28 y220 w62 h21 +0x200, Style:
    Gui Add, Edit, vEdtStyle x109 y224 w270 h21 -E0x200 ReadOnly
    Gui Add, Text, x28 y250 w62 h21 +0x200, Extended:
    Gui Add, Edit, vEdtExStyle x109 y254 w270 h21 -E0x200 ReadOnly
    Gui Add, Text, x23 y283 w357 0x10

    Gui Add, Text, x28 y294 w62 h21 +0x200, Position:
    Gui Add, Edit, vEdtPosition x109 y298 w180 h21 -E0x200 ReadOnly
    Gui Add, Button, gShowXYWHDlg x297 y294 w83 h23, Change...
    Gui Add, Text, x28 y324 w62 h21 +0x200, Size:
    Gui Add, Edit, vEdtSize x109 y328 w180 h21 -E0x200 ReadOnly
    Gui Add, Text, x23 y357 w357 0x10

    Gui Add, Text, x28 y368 w80 h21 +0x200, Cursor:
    Gui Add, Edit, vEdtCursor x109 y372 w180 h21 -E0x200 ReadOnly
    Gui Add, DropDownList, vMouseCoordMode gSetMouseCoordMode x297 y368 w83, Client|Window|Screen||

; Styles
Gui Tab, 2
    Gui Add, Custom, ClassSysTabControl32 hWndhStylesTab gStylesTabHandler x21 y85 w360 h230
    Tab_AddItem(hStylesTab, "Styles")
    Tab_AddItem(hStylesTab, "Extended Styles")

    ; ListBox style +0x108: no integral height and simplified multiple selection
    Gui Add, ListBox
        , hWndhLbxStyles vLbxStyles gLbxStylesHandler x28 y116 w344 h190 +0x108 -E0x200 T133
    Gui Add, ListBox
        , hWndhLbxExStyles vLbxExStyles gLbxStylesHandler x28 y116 w344 h190 +0x108 -E0x200 T133 Hidden
    Gui Add, ListBox
        , hWndhLbxExtraStyles vLbxExtraStyles gLbxStylesHandler x28 y116 w344 h190 +0x108 -E0x200 T133 Hidden

    Gui Add, GroupBox, vGrpDesc x21 y319 w268 h91, Description
    Gui Add, Text, vTxtDesc gShowDescription x32 y338 w245 h64 +0x80, Left/Right-click an item to see its description.

    Gui Add, Edit, vEdtStyleSum x297 y326 w83 h23, 0x00000000
    Gui Add, Edit, vEdtExStyleSum x297 y326 w83 h23 Hidden, 0x00000000
    Gui Add, Edit, vEdtExtraStyleSum x297 y326 w83 h23 Hidden, 0x00000000

    Gui Add, Button, gApplyStyle x297 y355 w83 h24, Apply
    Gui Add, Button, gResetStyle x297 y386 w83 h24, Reset

; Details
Gui Tab, 3
    Gui Add, ListView, hWndhClassInfo x21 y84 w360 h200 +LV0x14000, Property|Value
    LV_ModifyCol(1, 138)
    LV_ModifyCol(2, 201)

    Gui Add, Text, x21 y288 w360 h20, Window Properties:
    Gui Add, ListView, hWndhPropInfo x21 y306 w360 h104 +LV0x14000, Property|Data
    LV_ModifyCol(1, 238)
    LV_ModifyCol(2, 100)

; Messages
Gui Tab, 4
    Gui Add, Text, x30 y94 w63 h23 +0x200, Message:
    Gui Add, ComboBox, hWndhCbxMsg vCbxMessages x105 y94 w182
    SendMessage 0x1701, 20, 0,, ahk_id %hCbxMsg% ; CB_SETMINVISIBLE
    Gui Add, Link, gGoogleSearch x299 y97 w78 h23, <a>Google Search</a>
    Gui Add, Text, x30 y127 w63 h23 +0x200, wParam:
    Gui Add, Edit, vwParam x105 y127 w182 h23
    Gui Add, DropDownList, vwParamType x297 y126 w78, Number||String
    Gui Add, Text, x30 y161 w63 h23 +0x200, lParam:
    Gui Add, Edit, vlParam x105 y161 w182 h23
    Gui Add, DropDownList, vlParamType x297 y160 w78, Number||String
    Gui Add, Button, gSendMsg x97 y206 w99 h24, SendMessage
    Gui Add, Button, gPostMsg x203 y206 w99 h24, PostMessage
    Gui Add, GroupBox, x84 y242 w232 h51 Center, Result
    Gui Add, Edit, vResult x103 y264 w195 h21 Center -E0x200 ReadOnly
    Gui Add, Picture, x58 y390 w16 h16 +Icon2, user32.dll
    Gui Add, Text, x80 y390 w290 h23, Some messages may crash the target application.

; Extra
Gui Tab, 5
    Gui Add, ListView, hWndhExtraInfo x21 y84 w360 h261 +LV0x14000, Property|Value

    Gui Add, Button, hWndhBtnMenu gShowMenuViewer x21 y355 w83 h23, Menu...
    Gui Add, Button, gShowScrollBarInfo x21 y387 w83 h23, Scroll Bars...

; Windows
Gui Tab, 6
    Gui Add, Custom, ClassSysTabControl32 hWndhWindowsTab gWindowsTabHandler x21 y85 w360 h255
    Tab_AddItem(hWindowsTab, "Child Windows")
    Tab_AddItem(hWindowsTab, "Sibling Windows")

    Gui Add, ListView, hWndhChildList gChildListHandler x28 y116 w344 h214 -E0x200 +LV0x14000
    , Handle|Class Name|Window Text
    LV_ModifyCol(1, 76)
    LV_ModifyCol(2, 113)
    LV_ModifyCol(3, 136)
    Gui Add, ListView, hWndhSiblingList gSiblingListHandler x28 y116 w344 h214 -E0x200 +LV0x14000 Hidden
    , Handle|Class Name|Window Text
    LV_ModifyCol(1, 76)
    LV_ModifyCol(2, 113)
    LV_ModifyCol(3, 136)

    Gui Add, Text, x33 y352 w48 h23 +0x200, Parent:
    Gui Add, Link, vParentLink gLinkToHandle x87 y357 w300 h20, <a>0x00000000</a>
    Gui Add, Text, x33 y380 w48 h23 +0x200, Owner:
    Gui Add, Link, vOwnerLink gLinkToHandle x87 y385 w300 h20, <a>0x00000000</a>

; Process
Gui Tab, 7
    Gui Add, Picture, vProgIcon x21 y82 w32 h32 Icon3, shell32.dll
    Gui Add, Text, vProgName x60 y82 w180 h20, N/A
    Gui Add, Text, vProgVer x60 y96 w180 h20 +0x200
    Gui Add, ListView, hWndhProcInfo x21 y120 w360 h257 +LV0x14000, Property|Value
    LV_ModifyCol(1, 100)
    LV_ModifyCol(2, 256)
    Gui Add, Button, gEndProcess x21 y386 w83 h23, End Process
    Gui Add, Button, gOpenFolder x110 y386 w83 h23, Open Folder

Gui Tab
Gui Add, Button, hWndhBtnOpts gShowSettingsDlg x10 y432 w24 h24
Gui Add, Button, gShowFindDlg x39 y432 w84 h24, &Find...
Gui Add, Button, gShowTree x128 y432 w84 h24, &Tree...
Gui Add, Button, gCopyToClipboard x217 y432 w84 h24, &Copy
Gui Add, Button, gScreenshot x306 y432 w84 h24, &Screenshot

SetButtonIcon(hBtnOpts, ResDir . "\Settings.ico")

; Show main window
IniRead px, %IniFile%, Settings, x, Center
IniRead py, %IniFile%, Settings, y, Center
Gui Show, x%px% y%py% w400 h465 Hide, %AppName% ; Show main window
If (g_Minimize) {
    WinMove ahk_id %hSpyWnd%,,,,, 78
    g_Minimized := True
}
Gui Show

SetExplorerTheme(hClassInfo)
SetExplorerTheme(hPropInfo)
SetExplorerTheme(hExtraInfo)
SetExplorerTheme(hChildList)
SetExplorerTheme(hSiblingList)
SetExplorerTheme(hProcInfo)

; Commands menu
Menu CommandsMenu, Add, Visible, MenuHandler
Menu CommandsMenu, Add, Enabled, MenuHandler
Menu CommandsMenu, Add, Always on Top, MenuHandler
Menu CommandsMenu, Add
Menu CommandsMenu, Add, Redraw Window, MenuHandler
Menu CommandsMenu, Add
Menu CommandsMenu, Add, Close Window, MenuHandler
Global hCommandsMenu := MenuGetHandle("CommandsMenu")

RegRead Sep, HKEY_CURRENT_USER\Control Panel\International, sThousand
If (Sep == "") {
    Sep := "."
}

OnMessage(0x100, "OnWM_KEYDOWN")
OnMessage(0x112, "OnWM_SYSCOMMAND")
OnMessage(0x200, "OnWM_MOUSEMOVE")
OnMessage(0x202, "OnWM_LBUTTONUP")
OnMessage(0x204, "OnWM_RBUTTONDOWN")

LoadCursors()

hSysMenu := DllCall("GetSystemMenu", "Ptr", hSpyWnd, "Int", False, "Ptr")
DllCall("InsertMenu", "Ptr", hSysMenu, "UInt", 5, "UInt", 0x400, "UPtr", 0xC0DE, "Str", "About...")
DllCall("InsertMenu", "Ptr", hSysMenu, "UInt", 5, "UInt", 0xC00, "UPtr", 0, "Str", "") ; Separator

Return ; End of the auto-execute section

SpyEscape:
SpyClose:
    p := GetWindowPlacement(hSpyWnd)

    If (!FileExist(IniFile)) {
        FileAppend % "[Settings]`n`n[Screenshot]", %IniFile%, UTF-16
    }

    IniWrite % p.x, %IniFile%, Settings, x
    IniWrite % p.y, %IniFile%, Settings, y

    IniWrite %g_DetectHidden%, %IniFile%, Settings, DetectHidden
    IniWrite %g_Minimize%, %IniFile%, Settings, CompactMode
    IniWrite %g_AlwaysOnTop%, %IniFile%, Settings, AlwaysOnTop

    IniWrite %g_ShowBorder%, %IniFile%, Screenshot, ShowBorder
    IniWrite %g_BorderColor%, %IniFile%, Screenshot, BorderColor
    IniWrite %g_BorderWidth%, %IniFile%, Screenshot, BorderWidth

    ExitApp

IsChild(hWnd) {
    WinGet Style, Style, ahk_id %hWnd%
    Return Style & 0x40000000 ; WS_CHILD
}

ShowWindowInfo(ClassNN := "") {
    GuiControl -g, EdtHandle

    If (IsChild(g_hWnd)) {
        LoadControlInfo(ClassNN)
    } Else {
        LoadWindowInfo()
    }

    GuiControl +gSetHandle, EdtHandle

    GoSub LoadStyles
    GoSub LoadClassInfo
    GoSub LoadProperties
    GoSub LoadExtraInfo
    GoSub LoadWindowsTab

    GuiControlGet Tab,, Tab, %hTab%
    TabHandler(Tab)

    GoSub UpdateTitleBar
}

LoadWindowInfo() {
    Gui Spy: Default

    ; Handle
    GuiControl,, EdtHandle, % Format("0x{:X}", g_hWnd)

    ; Title
    WinGetTitle Title, ahk_id %g_hWnd%
    GuiControl,, EdtText, %Title%
    GuiControl,, TxtText, Title:
    GuiControl,, BtnSetText, Set Title

    ; Class
    WinGetClass Class, ahk_id %g_hWnd%
    GuiControl,, EdtClass, %Class%

    GuiControl,, EdtClassNN, N/A

    ; Style
    WinGet g_Style, Style, ahk_id %g_hWnd%
    If ((g_Style & 0x00FF0000) == 0xCF0000) {
        StyleInfo := " (overlapped window)"
    } Else If (g_Style & 0x80880000) {
        StyleInfo := " (popup window)"
    } Else {
        StyleInfo := ""
    }
    GuiControl,, EdtStyle, % Format("0x{:08X}", g_Style) . StyleInfo

    ; Extended style
    WinGet g_ExStyle, ExStyle, ahk_id %g_hWnd%
    GuiControl,, EdtExStyle, % Format("0x{:08X}", g_ExStyle)

    ; Position/size
    SetFormat Integer, D
    WinGetPos X, Y, W, H, ahk_id %g_hWnd%
    wi := GetWindowInfo(g_hWnd)
    GuiControl,, EdtPosition, % X . ", " . Y . " (" . wi.ClientX . ", " . wi.ClientY . ")"
    GuiControl,, EdtSize, % W . " x " . H . " (" . wi.ClientW . " x " . wi.ClientH . ")"
}

LoadControlInfo(ClassNN) {
    If (ClassNN == "") {
        ClassNN := GetClassNNEx(g_hWnd)
    }

    Gui Spy: Default
    SetFormat Integer, Hex

    ; Handle
    GuiControl,, EdtHandle, % Format("0x{:X}", g_hWnd)

    ; Class
    WinGetClass, Class, ahk_id %g_hWnd%
    GuiControl,, EdtClass, %Class%

    ; Control text
    ControlGetText Text,, ahk_id %g_hWnd%
    GuiControl,, EdtText, %Text%
    GuiControl,, TxtText, Text:
    GuiControl,, BtnSetText, Set Text

    ; ClassNN
    GuiControl,, EdtClassNN, %ClassNN%

    ; Style
    ControlGet g_Style, Style,,, ahk_id %g_hWnd%
    GuiControl,, EdtStyle, %g_Style%

    ; Extended style
    ControlGet g_ExStyle, ExStyle,,, ahk_id %g_hWnd%
    GuiControl,, EdtExStyle, %g_ExStyle%

    ; Position/Size
    SetFormat Integer, D
    GetWindowPos(g_hWnd, X, Y, W, H)
    wi := GetWindowInfo(g_hWnd)
    Pos := X . ", " . Y ; Relative to parent

    hParent := GetParent(g_hWnd)
    hAncestor := GetAncestor(g_hWnd)
    If (hParent != hAncestor) {
        VarSetCapacity(RECT, 16, 0)
        DllCall("GetWindowRect", "Ptr", g_hWnd, "Ptr", &RECT)
        DllCall("MapWindowPoints", "Ptr", 0, "Ptr", GetAncestor(g_hWnd), "Ptr", &RECT, "UInt", 1)
        AX := NumGet(RECT, 0, "Int")
        AY := NumGet(RECT, 4, "Int")
        Pos .= " (" . AX ", " . AY . ")" ; Relative to ancestor
    }

    GuiControl,, EdtPosition, %Pos%

    If (W != wi.ClientW || H != wi.ClientH) {
        GuiControl,, EdtSize, % W . " x " . H . " (" . wi.ClientW . " x " . wi.ClientH . ")"
    } Else {
        GuiControl,, EdtSize, %W% x %H%
    }

    g_ExtraStyle := GetExtraStyle(g_hWnd)
}

GetStatusBarText(hWnd) {
    SB_Text := ""
    hParentWnd := GetParent(hWnd)

    SendMessage 0x406, 0, 0,, ahk_id %hWnd% ; SB_GETPARTS
    Count := ErrorLevel
    If (Count != "FAIL") {
        Loop %Count% {
            StatusBarGetText PartText, %A_Index%, ahk_id %hParentWnd%
            SB_Text .= PartText . "|"
        }
    }

    Return SubStr(SB_Text, 1, -1)
}

TabHandler:
    Gui Spy: Submit, NoHide
    TabHandler(Tab)
Return

TabHandler(Tab) {
    If (Tab == 7) { ; Process
        GoSub LoadProcessProperties
    } Else If (Tab == 4) { ; Messages
        GoSub LoadMessages
        WinSet Redraw,, ahk_id %hCbxMsg%
    }
}

; Flag: GA_PARENT = 1, GA_ROOT = 2, GA_ROOTOWNER  = 3
GetAncestor(hWnd, Flag := 2) {
    Return DllCall("GetAncestor", "Ptr", hWnd, "UInt", Flag, "Ptr")
}

GetClassNNEx(hWnd) {
    hAncestor := GetAncestor(hWnd)
    If (!hAncestor) {
        Return
    }

    WinGetClass BaseClass, ahk_id %hWnd%
    NN := 0

    WinGet ControlList, ControlListHwnd, % "ahk_id " . hAncestor
    Loop Parse, ControlList, `n
    {
        WinGetClass Class, ahk_id %A_LoopField%
        If (Class == BaseClass) {
            NN++
            If (A_LoopField == hWnd) {
                Return Class . NN
            }
        }
    }
}

SetHandle:
    Gui Spy: Submit, NoHide

    If (!Dragging && WinExist("ahk_id " . EdtHandle)) {
        g_hWnd := EdtHandle
        ShowWindowInfo()
    }
Return

MenuHandler:
    If (A_ThisMenuItem == "Visible") {
        ShowWindow(g_hWnd, !IsWindowVisible(g_hWnd))
    } Else If (A_ThisMenuItem == "Enabled") {
        DllCall("EnableWindow", "Ptr", g_hWnd, "UInt", !IsWindowEnabled(g_hWnd))
    } Else If (A_ThisMenuItem == "Always on Top") {
        WinSet AlwaysOnTop, Toggle, ahk_id %g_hWnd%
    } Else If (A_ThisMenuItem == "Close Window") {
        WinClose ahk_id %g_hWnd%
    } Else If (A_ThisMenuItem == "Redraw Window") {
        WinSet Redraw,, ahk_id %g_hWnd%
    }
Return

UpdateCommandsMenu() {
    Visible := IsWindowVisible(g_hWnd)
    Enabled := IsWindowEnabled(g_hWnd)
    WinGet ExStyle, ExStyle, ahk_id %g_hWnd%

    Menu CommandsMenu, % (Visible) ? "Check" : "Uncheck", Visible
    Menu CommandsMenu, % (Enabled) ? "Check" : "Uncheck", Enabled

    Menu CommandsMenu, % (ExStyle & 0x8) ? "Check" : "Uncheck", Always on Top ; WS_EX_TOPMOST
    Menu CommandsMenu, % (IsChild(g_hWnd)) ? "Disable" : "Enable", Always on Top
}

IsWindowEnabled(hWnd) {
    Return DllCall("IsWindowEnabled", "Ptr", hWnd)
}

ShowCommandsMenu:
    UpdateCommandsMenu()

    Flags:= 0x8 ; TPM_TOPALIGN | TPM_RIGHTALIGN
    WingetPos wx, wy, ww, wh, ahk_id %hSpyWnd%
    ControlGetPos cx, cy, cw, ch,, ahk_id %hBtnCommands%
    x := wx + cx + cw
    y := wy + cy + ch
    DllCall("TrackPopupMenu", "Ptr", hCommandsMenu, "UInt", 0x8, "Int", x, "Int", y, "Int", 0, "Ptr", hSpyWnd, "Ptr", 0)
Return

SetText:
    Gui Spy: Submit, NoHide
    If (IsChild(g_hWnd)) {
        ControlSetText,, %EdtText%, ahk_id %g_hWnd%
    } Else {
        WinSetTitle ahk_id %g_hWnd%,, %EdtText%
    }
Return

ShowBorder(hWnd, Duration := 500, Color := "0x3FBBE3", r := 3) {
    Local x, y, w, h, Index

    WinGetPos x, y, w, h, ahk_id %hWnd%
    If (!w) {
        Return
    }

    g_Borders := []
    Loop 4 {
        Index := A_Index + 90
        Gui %Index%: +hWndhBorder -Caption +ToolWindow +AlwaysOnTop
        Gui %Index%: Color, %Color%
        g_Borders.Push(hBorder)
    }

    Gui 91: Show, % "NA x" (x - r) " y" (y - r) " w" (w + r + r) " h" r ; Top
    Gui 92: Show, % "NA x" (x - r) " y" (y + h) " w" (w + r + r) " h" r ; Bottom
    Gui 93: Show, % "NA x" (x - r) " y" y " w" r " h" h ; Left
    Gui 94: Show, % "NA x" (x + w) " y" y " w" r " h" h ; Right

    If (Duration != -1) {
        Sleep %Duration%
        Loop 4 {
            Index := A_Index + 90
            Gui %Index%: Destroy
        }
    }
}

GetClassLong(hWnd, Param) {
    Static GetClassLong := A_PtrSize == 8 ? "GetClassLongPtr" : "GetClassLong"
    Return DllCall(GetClassLong, "Ptr", hWnd, "Int", Param)
}

GetWindowLong(hWnd, Param) {
    ;GetWindowLong := A_PtrSize == 8 ? "GetWindowLongPtr" : "GetWindowLong"
    Return DllCall("GetWindowLong", "Ptr", hWnd, "Int", Param)
}

; Details
LoadClassInfo:
    Gui ListView, %hClassInfo%
    LV_Delete()

    SetFormat Integer, H
    WinGetClass ClassName, ahk_id %g_hWnd%
    ClassStyle := GetClassLong(g_hWnd, -26)

    LV_Add("", "Class name", ClassName)
    LV_Add("", "Control ID", GetWindowLong(g_hWnd, -12))
    LV_Add("", "Font", GetFont())
    LV_Add("", "Window procedure", GetClassLong(g_hWnd, -24))
    LV_Add("", "Instance handle", GetClassLong(g_hWnd, -16))
    LV_Add("", "Class style", ClassStyle . GetClassStyles(ClassStyle))
    LV_Add("", "Icon handle", GetClassLong(g_hWnd, -14))
    LV_Add("", "Small icon handle", GetClassLong(g_hWnd, -34))
    LV_Add("", "Cursor handle", GetCursor(GetClassLong(g_hWnd, -12)))
    LV_Add("", "Background Brush", GetSysColorName(GetClassLong(g_hWnd, -10) - 1))
    LV_Add("", "Menu name", GetClassLong(g_hWnd, -8))
    LV_Add("", "Window extra bytes", GetClassLong(g_hWnd, -18))
    LV_Add("", "Class extra bytes", GetClassLong(g_hWnd, -20))
    LV_Add("", "Class atom", GetClassLong(g_hWnd, -32))
    LV_Add("", "User data", GetWindowLong(g_hWnd, -21))
    SetFormat Integer, D
    LV_Add("", "Unicode", DllCall("IsWindowUnicode", "Ptr", g_hWnd) ? "Yes" : "No")
    LV_Add("", "Tab order index", GetTabOrderIndex(g_hWnd))
    LV_Add("", "Help context ID", DllCall("GetWindowContextHelpId", "Ptr", g_hWnd))
    LV_Add("", "Touch-capable", DllCall("IsTouchWindow", "Ptr", g_hWnd, "Ptr", 0))
Return

GetFont() {
    FontName := FontSize := FontStyle := ""

    Wingetclass Class, ahk_id %g_hWnd%
    If (Class == "Scintilla") {
        FontName := Scintilla_GetFont(g_hWnd)
        FontSize := SendMsg(2485, 32) ; SCI_STYLEGETSIZE, STYLE_DEFAULT
    } Else {
        Control_GetFont(g_hWnd, FontName, FontSize, FontStyle)
        If (FontName == "" || FontSize > 1000) {
            Return "System default"
        }
    }

    FontInfo := FontName . ", " . Format("{:d}", FontSize)
    If (FontStyle != "") {
        FontInfo .= ", " . FontStyle
    }

    Return FontInfo
}

; www.autohotkey.com/forum/viewtopic.php?p=465438#465438
Control_GetFont(hWnd, ByRef Name, ByRef Size, ByRef Style, IsGDIFontSize := 0) {
    SendMessage 0x31, 0, 0, , ahk_id %hWnd% ; WM_GETFONT
    If (ErrorLevel == "FAIL") {
        Return
    }

    hFont := Errorlevel
    VarSetCapacity(LOGFONT, LOGFONTSize := 60 * (A_IsUnicode ? 2 : 1 ))
    DllCall("GetObject", "Ptr", hFont, "Int", LOGFONTSize, "Ptr", &LOGFONT)

    Name := DllCall("MulDiv", "Int", &LOGFONT + 28, "Int", 1, "Int", 1, "Str")

    Style := Trim((Weight := NumGet(LOGFONT, 16, "Int")) == 700 ? "Bold" : (Weight == 400) ? "" : " w" . Weight
    . (NumGet(LOGFONT, 20, "UChar") ? " Italic" : "")
    . (NumGet(LOGFONT, 21, "UChar") ? " Underline" : "")
    . (NumGet(LOGFONT, 22, "UChar") ? " Strikeout" : ""))

    Size := IsGDIFontSize ? -NumGet(LOGFONT, 0, "Int") : Round((-NumGet(LOGFONT, 0, "Int") * 72) / A_ScreenDPI)
}

Scintilla_GetFont(hWnd) {
    WinGet PID, PID, ahk_id %hWnd%
    If !(hProc := DllCall("OpenProcess", "UInt", 0x438, "Int", False, "UInt", PID, "Ptr")) {
        Return
    }

    ; LF_FACESIZE := 32
    Address := DllCall("VirtualAllocEx", "Ptr", hProc, "Ptr", 0, "UPtr", 32, "UInt", 0x1000, "UInt", 4, "Ptr")

    SendMessage 2486, 32, Address,, ahk_id %hWnd% ; SCI_STYLEGETFONT, STYLE_DEFAULT
    If (ErrorLevel != "FAIL") {
        VarSetCapacity(FontName, 32, 0)
        DllCall("ReadProcessMemory", "Ptr", hProc, "Ptr", Address, "Ptr", &FontName, "UPtr", 32, "Ptr", 0)
        FontName := StrGet(&FontName, "UTF-8")
    }

    DllCall("VirtualFreeEx", "Ptr", hProc, "Ptr", Address, "UPtr", 0, "UInt", 0x8000) ; MEM_RELEASE
    DllCall("CloseHandle", "Ptr", hProc)

    Return FontName
}

Scintilla_GetLexerLanguage(hWnd) {
    WinGet PID, PID, ahk_id %hWnd%
    If !(hProc := DllCall("OpenProcess", "UInt", 0x438, "Int", False, "UInt", PID, "Ptr")) {
        Return
    }

    Sendmessage 4012, 0, 0,, ahk_id %hWnd% ; SCI_GETLEXERLANGUAGE
    BufferSize := ErrorLevel
    Address := DllCall("VirtualAllocEx", "Ptr", hProc, "Ptr", 0, "UPtr", BufferSize, "UInt", 0x1000, "UInt", 4, "Ptr")

    Sendmessage 4012, 0, Address,, ahk_id %hWnd% ; SCI_GETLEXERLANGUAGE
    If (ErrorLevel != "FAIL") {
        VarSetCapacity(LexerName, BufferSize, 0)
        DllCall("ReadProcessMemory", "Ptr", hProc, "Ptr", Address, "Ptr", &LexerName, "UPtr", 32, "Ptr", 0)
        LexerName := StrGet(&LexerName, "UTF-8")
    }

    DllCall("VirtualFreeEx", "Ptr", hProc, "Ptr", Address, "UPtr", 0, "UInt", 0x8000) ; MEM_RELEASE
    DllCall("CloseHandle", "Ptr", hProc)

    Return LexerName
}

GetClassStyles(Style) {
    Static CS := {0x1: "CS_VREDRAW"
    , 0x2: "CS_HREDRAW"
    , 0x8: "CS_DBLCLKS"
    , 0x20: "CS_OWNDC"
    , 0x40: "CS_CLASSDC"
    , 0x80: "CS_PARENTDC"
    , 0x200: "CS_NOCLOSE"
    , 0x800: "CS_SAVEBITS"
    , 0x1000: "CS_BYTEALIGNCLIENT"
    , 0x2000: "CS_BYTEALIGNWINDOW"
    , 0x4000: "CS_GLOBALCLASS"
    , 0x10000: "CS_IME"
    , 0x20000: "CS_DROPSHADOW"}

    Styles := " ("
    For k, v in CS {
        If (Style & k) {
            Styles .= v ", "
        }
    }

    Return RTrim(Styles, ", ") . ")"
}

LoadCursors() {
    Static Constants := {"IDC_ARROW": 32512
        , "IDC_IBEAM": 32513
        , "IDC_WAIT": 32514
        , "IDC_CROSS": 32515
        , "IDC_UPARROW": 32516
        , "IDC_SIZENWSE": 32642
        , "IDC_SIZENESW": 32643
        , "IDC_SIZEWE": 32644
        , "IDC_SIZENS": 32645
        , "IDC_SIZEALL": 32646
        , "IDC_NO": 32648
        , "IDC_HAND": 32649
        , "IDC_APPSTARTING": 32650
        , "IDC_HELP": 32651}

    For Key, Value in Constants {
        hCursor := DllCall("LoadCursor", "Ptr", 0, "UInt", Value, "Ptr")
        Cursors[hCursor] := Key
    }
}

GetCursor(CursorHandle) {
    Cursor := Cursors[CursorHandle]
    Return (Cursor != "") ? Cursor : CursorHandle
}

GetTabOrderIndex(hWnd) {
    hParent := GetAncestor(hWnd)

    WinGet ControlList, ControlListHwnd, ahk_id %hParent%
    Index := 1
    Loop Parse, ControlList, `n
    {
        If (!IsWindowVisible(A_LoopField)) {
            Continue
        }

        WinGet Style, Style, ahk_id %A_LoopField%
        If !(Style & 0x10000) { ; WS_TABSTOP
            Continue
        }

        If (A_LoopField == hWnd) {
            Return Index
        }

        Index++
    }

    Return 0
}

Tab_AddItem(hTab, Text) {
    VarSetCapacity(TCITEM, 16 + A_PtrSize * 3, 0)
    NumPut(0x1, TCITEM, 0, "UInt") ; TCIF_TEXT
    NumPut(&Text, TCITEM, 8 + A_PtrSize, "Ptr")
    SendMessage 0x1304, 0, 0,, ahk_id %hTab% ; TCM_GETITEMCOUNT
    SendMessage 0x133E, %ErrorLevel%, &TCITEM, , ahk_id %hTab% ; TCM_INSERTITEMW
}

StylesTabHandler:
    If (A_GuiEvent == "N") {
        Code := NumGet(A_EventInfo + 0, A_PtrSize * 2, "Int")
        If (Code == -551) { ; TCN_SELCHANGE
            SendMessage 0x130B, 0, 0,, ahk_id %hStylesTab% ; TCM_GETCURSEL
            nTab := Errorlevel + 1
            If (nTab == 1) {
                GuiControl Hide, ListBox3
                GuiControl Hide, ListBox2
                GuiControl Show, ListBox1

                GuiControl Hide, EdtExtraStyleSum
                GuiControl Hide, EdtExStyleSum
                GuiControl Show, EdtStyleSum
            } Else If (nTab == 2) {
                GuiControl Hide, ListBox1
                GuiControl Hide, ListBox3
                GuiControl Show, ListBox2

                GuiControl Hide, EdtExtraStyleSum
                GuiControl Hide, EdtStyleSum
                GuiControl Show, EdtExStyleSum
            } Else If (nTab == 3) {
                GuiControl Hide, ListBox1
                GuiControl Hide, ListBox2
                GuiControl Show, ListBox3

                GuiControl Hide, EdtStyleSum
                GuiControl Hide, EdtExStyleSum
                GuiControl Show, EdtExtraStyleSum
            }
        }
    }
Return

; Styles
LoadStyles:
    Gui Spy: Default

    GuiControl,, EdtStyleSum, %g_Style%
    GuiControl,, EdtExStyleSum, %g_ExStyle%
    GuiControl,, EdtExtraStyleSum, %g_ExtraStyle%

    WinGetClass Class, ahk_id %g_hWnd%
    If (Class == "") {
        Return
    }

    ; Load control styles
    LoadStyles(Class, "ListBox1")

    If (Class == "ToolbarWindow32" || Class == "ReBarWindow32") {
        LoadStyles("CommonControls", "ListBox1", True)
    }

    ; Load window styles
    LoadStyles("Window", "ListBox1", True)
    LoadStyles("WindowEx", "ListBox2")

    ; Delete the third tab
    SendMessage 0x1308, 2, 0,, ahk_id %hStylesTab% ; TCM_DELETEITEM
    If (ErrorLevel == True) {
        GuiControl Hide, ListBox3
        GuiControl Show, ListBox1
        SendMessage 0x1330, 0, 0,, ahk_id %hStylesTab% ; TCM_SETCURFOCUS
        Sleep 0
        SendMessage 0x130C, 0, 0,, ahk_id %hStylesTab% ; TCM_SETCURSEL
    }

    If (Class == "ComboBox" && g_Style & 0x10) { ; CBS_OWNERDRAWFIXED
        Class := "ComboBoxEx"
    }

    ; Add third tab
    If (Class == "SysListView32"
    ||  Class == "SysTreeView32"
    ||  Class == "SysTabControl32"
    ||  Class == "ToolbarWindow32"
    ||  Class == "ComboBoxEx") {
        Tab_AddItem(hStylesTab, RegExReplace(Class, "Sys|32|Control|Window") . " Extended Styles")
        LoadStyles(Class . "Ex", "ListBox3")
    }

    WStyle := g_Style
    Type := 0
    If (Class == "Button") {
        Type := WStyle & 0xF ; BS_TYPEMASK
        WStyle &= ~Type
    } Else If (Class == "SysListView32") {
        Type := WStyle & 0x3 ; LVS_TYPEMASK
        WStyle &= ~Type
    } Else If (Class == "Static") {
        Type := WStyle & 0x1F ; SS_TYPEMASK
        WStyle &= ~Type
    }

    ControlGet Items, List,,, ahk_id %hLbxStyles%
    Loop Parse, Items, `n
    {
        LStyle := StrSplit(A_LoopField, "`t")[2]
        If (WStyle & LStyle || Type == LStyle) {
            WStyle &= ~LStyle
            GuiControl Choose, %hLbxStyles%, %A_Index%
        }
    }

    If (WStyle) {
        Leftover := Format("0x{:08X}", WStyle)
        GuiControl,, %hLbxStyles%, % Leftover . "`t" . Leftover . "||"
    }

    SendMessage 0x115, 6, 0,, ahk_id %hLbxStyles% ; WM_VSCROLL, scroll to top
    WinSet Redraw,, ahk_id %hLbxStyles%

    ; Extended styles
    WExStyle := g_ExStyle
    ControlGet Items, List,,, ahk_id %hLbxExStyles%
    Loop Parse, Items, `n
    {
        LExStyle := StrSplit(A_LoopField, "`t")[2]
        If (WExStyle & LExStyle || LExStyle == 0) {
            WExStyle &= ~LExStyle
            GuiControl Choose, %hLbxExStyles%, %A_Index%
        }
    }

    If (WExStyle) {
        Leftover := Format("0x{:08X}", WExStyle)
        GuiControl,, %hLbxExStyles%, % Leftover . "`t" . Leftover . "||"
    }

    SendMessage 0x115, 6, 0,, ahk_id %hLbxExStyles% ; WM_VSCROLL, scroll to top
    WinSet Redraw,, ahk_id %hLbxExStyles%

    ; Extra control styles (LV, TV, Toolbar, Tab)
    ExtraStyle := g_ExtraStyle
    ControlGet Items, List,,, ahk_id %hLbxExtraStyles%
    Loop Parse, Items, `n
    {
        LExtraStyle := StrSplit(A_LoopField, "`t")[2]
        If (ExtraStyle & LExtraStyle || LExtraStyle == 0) {
            ExtraStyle &= ~LExtraStyle
            GuiControl Choose, %hLbxExtraStyles%, %A_Index%
        }
    }
Return

LoadStyles(IniSection, ListBox, Append := False) {
    Static IniFile := A_ScriptDir . "\Constants\Styles.ini"
    IniRead Section, %IniFile%, %IniSection%

    Child := (IniSection == "Window" && IsChild(g_hWnd)) ? True : False

    Values := ""
    Loop Parse, Section, `n
    {
        Fields := StrSplit(A_LoopField, "|")
        Const := Fields[1]

        If (Child && (Const == "WS_MAXIMIZEBOX" || Const == "WS_MINIMIZEBOX" || Const == "WS_OVERLAPPED")) {
            Continue
        }

        If (!Child && (Const == "WS_TABSTOP" || Const == "WS_GROUP")) {
            Continue
        }

        Values .= Const . "`t" . Fields[2] . "|"
        oStyles[Const] := {"Value": Fields[2], "Desc": Fields[3]}
    }

    Gui Spy: Default
    GuiControl,, %ListBox%, % (Append) ? Values : "|" . Values
}

LbxStylesHandler:
    Gui Spy: Default
    GuiControlGet hLbx, hWnd, %A_GuiControl%
    GuiControl -AltSubmit, %hLbx%

    GuiControlGet Items,, %hLbx%

    Sum := 0
    Loop Parse, Items, |
    {
        StringSplit Field, A_LoopField, `t
        Sum += Field2
    }

    GuiControl,, % StrReplace(A_GuiControl, "Lbx", "Edt") . "um", % Format("0x{:08X}", Sum)

    ; Style description
    GuiControl +AltSubmit, %hLbx%
    SendMessage 0x188, 0, 0,, ahk_id %hLbx% ; LB_GETCURSEL
    If (ErrorLevel != "FAIL") {
        ; Credits to just_me
        Index := ErrorLevel
        SendMessage 0x18A, %Index%, 0,, ahk_id %hLbx% ; LB_GETTEXTLEN
        Len := ErrorLevel
        VarSetCapacity(LB_Text, Len << !!A_IsUnicode, 0)
        SendMessage 0x189, %Index%, % &LB_Text,, ahk_id %hLbx% ; LB_GETTEXT
        Const := StrSplit(StrGet(&LB_Text, Len), "`t")[1]
        Desc := StrReplace(oStyles[Const].Desc, "\n", "`n")
        GuiControl,, GrpDesc, %Const%
        GuiControl,, TxtDesc, %Desc%
    }
Return

ApplyStyle:
    SendMessage 0x130B, 0, 0,, ahk_id %hStylesTab% ; TCM_GETCURSEL
    nTab := ErrorLevel + 1

    If (nTab == 1) {
        GuiControlGet Style,, EdtStyleSum
        WinSet Style, %Style%, ahk_id %g_hWnd%
    } Else If (nTab == 2) {
        GuiControlGet ExStyle,, EdtExStyleSum
        WinSet ExStyle, %ExStyle%, ahk_id %g_hWnd%
    } Else If (nTab == 3) {
        GuiControlGet ExtraStyle,, EdtExtraStyleSum
        WinGetClass Class, ahk_id %g_hWnd%

        If (Class == "SysListView32") {
            SendMessage 0x1036, 0, %ExtraStyle%,, ahk_id %g_hWnd% ; LVM_SETEXTENDEDLISTVIEWSTYLE
        } Else If (Class == "SysTreeView32") {
            SendMessage 0x112C, 0, %ExtraStyle%,, ahk_id %g_hWnd% ; TVM_SETEXTENDEDSTYLE
        } Else If (Class == "SysTabControl32") {
            SendMessage 0x1334, 0, %ExtraStyle%,, ahk_id %g_hWnd% ; TCM_SETEXTENDEDSTYLE
        } Else If (Class == "ToolbarWindow32") {
            SendMessage 0x454, 0, %ExtraStyle%,, ahk_id %g_hWnd% ; TB_SETEXTENDEDSTYLE
        } Else If (Class == "ComboBox" && (g_Style & 0x10)) {
            SendMessage 0x40E, 0, %ExtraStyle%,, ahk_id %g_hWnd% ; CBEM_SETEXTENDEDSTYLE
        }
    }

    ; 0x17: SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE
    DllCall("SetWindowPos", "Ptr", g_hWnd, "UInt", 0, "Int", 0, "Int", 0, "Int", 0, "Int", 0, "UInt", 0x17)
    WinSet Redraw,, ahk_id %g_hWnd%
Return

ResetStyle:
    nTab := DllCall("SendMessage", "Ptr", hStylesTab, "UInt", 0x130B, "UInt", 0, "UInt", 0) + 1
    If (nTab == 1) {
        GuiControl, Spy:, EdtStyleSum, %g_Style%
    } Else If (nTab == 2) {
        GuiControl, Spy:, EdtExStyleSum, %g_ExStyle%
    } Else If (nTab == 3) {
        GuiControl, Spy:, EdtExtraStyleSum, %g_ExtraStyle%
    }

    GoSub LoadStyles
Return

GetExtraStyle(hWnd) {
    WinGetClass Class, ahk_id %hWnd%

    If (Class == "SysListView32") {
        Message := 0x1037 ; LVM_GETEXTENDEDLISTVIEWSTYLE
    } Else If (Class == "SysTreeView32") {
        Message := 0x112D ; TVM_GETEXTENDEDSTYLE
    } Else If (Class == "SysTabControl32") {
        Message := 0x1335 ; TCM_GETEXTENDEDSTYLE
    } Else If (Class == "ToolbarWindow32") {
        Message := 0x455 ; TB_GETEXTENDEDSTYLE
    } Else If (Class == "ComboBox" && g_Style & 0x10) {
        Message := 0x409 ; CBEM_GETEXTENDEDSTYLE
    }

    SendMessage %Message%, 0, 0,, ahk_id %hWnd%
    Return Format("0x{:08X}", ErrorLevel)
}

ShowDescription:
    GuiControlGet Const,, GrpDesc
    GuiControlGet Desc,, TxtDesc

    Gui Desc: New, LabelDesc -SysMenu OwnerSpy
    Gui Color, White
    Gui Margin, 10, 0
    Gui Add, CheckBox, x0 y0 w0 h0

    Gui Add, Picture, x12 y12 w32 h32 Icon5, user32.dll
    Gui Font, s12 c0x003399, Segoe UI
    Gui Add, Text, x58 y15 w473 h23 +0x200, %Const%
    Gui Font

    Gui Font, s10, Segoe UI
    Gui Add, Edit, vEdtDesc x55 y55 w444 Multi -VScroll -E0x200, %Desc%
    Gui Font

    GuicontrolGet Pos, Pos, EdtDesc
    py := PosY + PosH + 20
    Gui Add, Text, hWndhFooter x-1 y%py% w533 h48 -Background

    Gui Font, s9, Segoe UI
    Gui Add, Button, gDescClose x432 yp+12 w88 h25 Default, &Close

    Gui Show, w531, Style Description
Return

DescEscape:
DescClose:
    Gui Desc: Destroy
Return

; Messages
LoadMessages:
    WinGetClass Class, ahk_id %g_hWnd%

    Constants := GetMessages(Class)

    Gui Spy: Default
    GuiControlGet CurrentItem,, CbxMessages, Text
    GuiControl,, CbxMessages, |%Constants%
    GuiControl Text, CbxMessages, %CurrentItem%

    ; Common Control Messages
    If (Class == "ToolbarWindow32" || Class == "ReBarWindow32") {
        GuiControl,, CbxMessages, % GetMessages("CommonControls")
    }

    If (g_WinMsgs == "") {
        g_WinMsgs := GetMessages("Window")
    }

    GuiControl,, CbxMessages, %g_WinMsgs%
Return

GetMessages(Class) {
    Static IniFile := A_ScriptDir . "\Constants\Messages.ini"

    If (Class == "") {
        Return
    }

    IniRead Section, %IniFile%, %Class%

    Constants := ""
    Loop Parse, Section, `n
    {
        Constants .= StrSplit(A_LoopField, "=")[1] . "|"
    }

    Sort Constants, D|
    Return Constants
}

SendMsg:
PostMsg:
    Gui Spy: Submit, NoHide

    Function := (A_ThisLabel == "SendMsg") ? "SendMessage" : "PostMessage"

    If CbxMessages is Not Number
    {
        If (SubStr(CbxMessages, 1, 3) == "WM_") {
            ClassName := "Window"
        } Else {
            ClassName := GetClassName(g_hWnd)
        }

        IniRead Message, %A_ScriptDir%\Constants\Messages.ini, %ClassName%, %CbxMessages%
        If (Message == "ERROR") {
            Gui Spy: +OwnDialogs
            MsgBox 0x10, %AppName%, %CbxMessages%: invalid message.
            Return
        }
    } Else {
        Message := CbxMessages
    }

    DataTypes := {"Number": "UPtr", "String": "WStr"}
    wType := DataTypes[wParamType]
    lType := DataTypes[lParamType]

    Result := DllCall(Function, "Ptr", g_hWnd, "UInt", Message, wType, wParam, lType, lParam)
    GuiControl,, Result, %Result%
Return

GoogleSearch:
    GuiControlGet Message,, CbxMessages

    If (Message == "") {
        Return
    }

    If (SubStr(Message, 1, 3) == "SCI") {
        URL := "http://www.scintilla.org/ScintillaDoc.html#"
    } Else {
        URL := "https://www.google.com/#q="
    }

    Try {
        Run %URL%%Message%
    }
Return

CopyToClipboard:
    Gui Spy: Default

    CRLF := "`r`n"
    Output := ""

    If (Tab == 1) { ; General
        Gui Spy: Submit, NoHide
        Output .= "[General]" . CRLF
        Output .= "Handle:`t" . EdtHandle . CRLF
        Output .= "Text:`t" . EdtText . CRLF
        Output .= "Class:`t" . EdtClass . CRLF
        Output .= "ClassNN:`t" . EdtClassNN . CRLF
        Output .= "Style:`t" . EdtStyle . CRLF
        Output .= "Extended:`t" . EdtExStyle . CRLF
        Output .= "Position:`t" . EdtPosition . CRLF
        Output .= "Size:`t" . EdtSize . CRLF
        Output .= "Cursor:`t" . EdtCursor . CRLF
    }
    Else If (Tab == 2) { ; Styles
        If (g_Style) {
            GuiControlGet Styles,, %hLbxStyles%
            Output .= "[Styles]" . CRLF . StrReplace(Styles, "|", CRLF) . CRLF . CRLF
        }

        If (g_ExStyle) {
            GuiControlGet ExStyles,, %hLbxExStyles%
            Output .= "[ExStyles]" . CRLF . StrReplace(ExStyles, "|", CRLF) . CRLF . CRLF
        }

        If (g_ExtraStyle) {
            GuiControlGet ExtraStyles,, %hLbxExtraStyles%
            Output .= "[ExtraStyles]" . CRLF . StrReplace(ExtraStyles, "|", CRLF)
        }
    }
    Else If (Tab == 3) { ; Details
        ControlGet, ClassInfo, List,,, ahk_id %hClassInfo%
        ControlGet, PropInfo, List,,, ahk_id %hPropInfo%
        Output .= "[Details]" . CRLF . ClassInfo . CRLF . CRLF . "[Properties]" . CRLF . PropInfo
    }
    Else If (Tab == 5) { ; Extra
        ControlGet, ExtraInfo, List,,, ahk_id %hExtraInfo%
        Output .= "[Extra]" . CRLF . ExtraInfo
    }
    Else If (Tab == 6) { ; Windows
        ControlGet, Child, List,,, ahk_id %hChildList%
        If (Child != "") {
            Output .= "[Child]" . CRLF . Child . CRLF . CRLF
        }

        ControlGet, Sibling, List,,, ahk_id %hSiblingList%
        If (Sibling != "") {
            Output .= "[Sibling]" . CRLF . Sibling . CRLF . CRLF
        }

        GuiControlGet ParentLink,, ParentLink
        Output .= "Parent:`t" . RegExReplace(ParentLink, "\<\/?a\>") . CRLF
        GuiControlGet OwnerLink,, OwnerLink
        Output .= "Owner:`t" . RegExReplace(OwnerLink, "\<\/?a\>")
    }
    Else If (Tab == 7) { ; Process
        ControlGet, ProcInfo, List,,, ahk_id %hProcInfo%
        Output .= "[Process]" . CRLF . ProcInfo
    }

    Clipboard := RTrim(Output, CRLF)
Return

ControlFromPoint(mx, my, hWnd) {
    hParent := GetParent(hWnd)
    If (hParent == 0) {
        hParent := hWnd
    }

    SmallerArea := 999999999
    hChildWnd := 0

    WinGet List, ControlListHwnd, ahk_id %hParent% ; EnumChildWindows
    Loop Parse, List, `n
    {
        VarSetCapacity(RECT, 16, 0)
        DllCall("GetWindowRect", "Ptr", A_LoopField, "Ptr", &RECT)
        Left := NumGet(RECT, 0, "Int")
        Top := NumGet(RECT, 4, "Int")
        Right := NumGet(RECT, 8, "Int")
        Bottom := NumGet(RECT, 12, "Int")

        If ((mx >= Left) && (mx <= Right) && (my >= Top) && (my <= Bottom)) {
            Area := (Right - Left) * (Bottom - Top)
            If (Area < SmallerArea) {
                SmallerArea := Area
                hChildWnd := A_LoopField
            }
        }
    }

    Return (hChildWnd == 0) ? hWnd : hChildWnd
}

; Details tab
LoadProperties:
    Gui Spy: ListView, %hPropInfo%
    LV_Delete()

    Callback := RegisterCallback("PropEnumProcEx", "F")
    DllCall("EnumPropsEx", "Ptr", g_hWnd, "Ptr", Callback, "UInt", lParam := 0)
Return

PropEnumProcEx(hWnd, lpszString, hData, dwData) {
    Global hPropInfo

    Property := StrGet(lpszString, "UTF-16")
    If (Property == "") {
        Property := lpszString . " (Atom)"
    }

    Gui Spy: ListView, %hPropInfo%
    LV_Add("", Property, Format("0x{:08X}", hData))

    Return True
}

LoadExtraInfo:
    Gui Spy: ListView, %hExtraInfo%

    LV_Delete()
    While (LV_GetText(foo, 0, 1)) {
        LV_DeleteCol(1)
    }

    WinGetClass Class, ahk_id %g_hWnd%

    If (Class == "Edit" || InStr(Class, "RICHEDIT")) {
        LV_InsertCol(1, "169", "Property")
        LV_InsertCol(2, "169", "Value")

        ControlGetText Text,, ahk_id %g_hWnd%
        Length := StrLen(Text) . " characters"
        ControlGet Lines, LineCount,,, ahk_id %g_hWnd%
        ControlGet CurLine, CurrentLine,,, ahk_id %g_hWnd%
        ControlGet CurCol, CurrentCol,,, ahk_id %g_hWnd%

        LV_Add("", "Length", Length)
        LV_Add("", "Current line", CurLine)
        LV_Add("", "Current column", CurCol)
        LV_Add("", "Line count", Lines)
        LV_Add("", "Text Limit", SendMsg(0xD5) . " bytes") ; EM_GETLIMITTEXT
        LV_Add("", "Modified", {0: "False", 1: "True"}[SendMsg(0xB8)]) ; EM_GETMODIFY
    }
    Else If (InStr(Class, "Scintilla")) {

        LV_InsertCol(1, "169", "Property")
        LV_InsertCol(2, "169", "Value")

        LexerName := Scintilla_GetLexerLanguage(g_hWnd)
        CodePage := SendMsg(2137) ; SCI_GETCODEPAGE
        Pos := SendMsg(2008)
        Line := SendMsg(2166, Pos)
        Char := SendMsg(2007, Pos)
        Size := FormatBytes(SendMsg(2006), Sep, "B", 0) . " bytes"
        SelSize := FormatBytes(SendMsg(2161, 0, 0) - 1, Sep, "B", 0) . " bytes"

        LV_Add("", "Lexer", SendMsg(4002) . (LexerName != "" ? " (" . LexerName . ")" : "")) ; SCI_GETLEXER
        LV_Add("", "Current position", Pos + 1) ; SCI_GETCURRENTPOS
        LV_Add("", "Char at position", Char . " (""" . Chr(Char) . """)") ; SCI_GETCHARAT
        LV_Add("", "Style at position", SendMsg(2010, Pos)) ; SCI_GETSTYLEAT
        LV_Add("", "Current line", Line + 1) ; SCI_LINEFROMPOSITION
        LV_Add("", "Position from line", SendMsg(2167, Line) + 1) ; SCI_POSITIONFROMLINE
        LV_Add("", "Line end position", SendMsg(2136, Line) + 1) ; SCI_GETLINEENDPOSITION
        LV_Add("", "Line length", SendMsg(2350, Line)) ; SCI_LINELENGTH
        LV_Add("", "Current column", SendMsg(2129, Pos) + 1) ; SCI_GETCOLUMN
        LV_Add("", "Line count", SendMsg(2154)) ; SCI_GETLINECOUNT
        LV_Add("", "Document size", Size) ; SCI_GETLENGTH
        LV_Add("", "File encoding", (CodePage == 65001 ? "UTF-8 (65001)" : CodePage)) ; SCI_GETCODEPAGE
        LV_Add("", "Modified", SendMsg(2159) ? "True" : "False") ; SCI_GETMODIFY
        LV_Add("", "Read only", SendMsg(2140) ? "True" : "False") ; SCI_GETREADONLY
        LV_Add("", "Wrap mode", SendMsg(2269)) ; SCI_GETWRAPMODE
        LV_Add("", "Tab width", SendMsg(2121)) ; SCI_GETTABWIDTH
        LV_Add("", "Indent with spaces", !SendMsg(2125) ? "True" : "False") ; SCI_GETUSETABS
        LV_Add("", "Show indentation guides", SendMsg(2133) ? "True" : "False") ; SCI_GETINDENTATIONGUIDES
        LV_Add("", "EOL mode", {0: "CRLF", 1: "CR", 2: "LF"}[SendMsg(2030)]) ; SCI_GETEOLMODE
        LV_Add("", "Paste convert EOL", SendMsg(2468) ? "True" : "False") ; SCI_GETPASTECONVERTENDINGS
        LV_Add("", "Overtype mode", SendMsg(2187) ? "1 (overtype)" : "0 (insert)") ; SCI_GETOVERTYPE
        LV_Add("", "Anchor position", SendMsg(2009) + 1) ; SCI_GETANCHOR
        LV_Add("", "Selection start", SendMsg(2143) + 1) ; SCI_GETSELECTIONSTART
        LV_Add("", "Selection end", SendMsg(2145) + 1) ; SCI_GETSELECTIONEND
        LV_Add("", "Selected text length", SelSize) ; SCI_GETSELTEXT
        LV_Add("", "Selection mode", SendMsg(2423)) ; SCI_GETSELECTIONMODE
        LV_Add("", "Selection is rectangular", SendMsg(2372) ? "True" : "False") ; SCI_SELECTIONISRECTANGLE
        LV_Add("", "Virtual space options", SendMsg(2597)) ; SCI_GETVIRTUALSPACEOPTIONS
        LV_Add("", "Rectangular selection modifier", SendMsg(2599)) ; SCI_GETRECTANGULARSELECTIONMODIFIER
        ; SCI_GETMOUSESELECTIONRECTANGULARSWITCH
        LV_Add("", "Mouse rectangular selection", SendMsg(2669) ? "True" : "False")
        LV_Add("", "Selection start line position", SendMsg(2424, Line) + 1) ; SCI_GETLINESELSTARTPOSITION
        LV_Add("", "Selection end line position", SendMsg(2425, Line) + 1) ; SCI_GETLINESELENDPOSITION
        LV_Add("", "Multiple selection", SendMsg(2564) ? "True" : "False") ; SCI_GETMULTIPLESELECTION
        ; SCI_GETADDITIONALSELECTIONTYPING
        LV_Add("", "Additional selection typing", SendMsg(2566) ? "True" : "False")
        LV_Add("", "Multipaste", SendMsg(2615)) ; SCI_GETMULTIPASTE
        LV_Add("", "Line height", SendMsg(2279, Line)) ; SCI_TEXTHEIGHT
        LV_Add("", "Baseline extra ascent", SendMsg(2526)) ; SCI_GETEXTRAASCENT
        LV_Add("", "Baseline extra descent", SendMsg(2528)) ; SCI_GETEXTRADESCENT
        LV_Add("", "Lines on screen", SendMsg(2370)) ; SCI_LINESONSCREEN
        LV_Add("", "First visible line", SendMsg(2152) + 1) ; SCI_GETFIRSTVISIBLELINE
        LV_Add("", "Current line wrap count", SendMsg(2235, Line)) ; SCI_WRAPCOUNT
        LV_Add("", "Mouse hover time", SendMsg(2265)) ; SCI_GETMOUSEDWELLTIME
        LV_Add("", "Word start position", SendMsg(2266, Pos, 1) + 1) ; SCI_WORDSTARTPOSITION
        LV_Add("", "Word end position", SendMsg(2267, Pos, 1) + 1) ; SCI_WORDENDPOSITION
        LV_Add("", "Autocomplete ignore case", SendMsg(2116) ? "True" : "False") ; SCI_AUTOCGETIGNORECASE
        LV_Add("", "Autocomplete list presorted", SendMsg(2661)) ; SCI_AUTOCGETORDER
        LV_Add("", "Autocomplete list max rows", SendMsg(2211)) ; SCI_AUTOCGETMAXHEIGHT
        LV_Add("", "Position before", SendMsg(2417, Pos) + 1) ; SCI_POSITIONBEFORE
        LV_Add("", "Position after", SendMsg(2418, Pos) + 1) ; SCI_POSITIONAFTER
        LV_Add("", "Current indicator", SendMsg(2501)) ; SCI_GETINDICATORCURRENT
        LV_Add("", "Target start", SendMsg(2191) + 1) ; SCI_GETTARGETSTART
        LV_Add("", "Target end", SendMsg(2193) + 1) ; SCI_GETTARGETEND
        LV_Add("", "Search flags", SendMsg(2199)) ; SCI_GETSEARCHFLAGS
        LV_Add("", "Error status", SendMsg(2383)) ; SCI_GETSTATUS
        LV_Add("", "Font quality", SendMsg(2612)) ; SCI_GETFONTQUALITY
        LV_Add("", "Technology (drawing API)", SendMsg(2631)) ; SCI_GETTECHNOLOGY
        LV_Add("", "Buffered drawing", SendMsg(2034) ? "True" : "False") ; SCI_GETBUFFEREDDRAW
        LV_Add("", "Zoom factor", SendMsg(2374)) ; SCI_GETZOOM
        LV_Add("", "Edge mode", SendMsg(2362)) ; SCI_GETEDGEMODE 
        LV_Add("", "Edge column", SendMsg(2360) + 1) ; SCI_GETEDGECOLUMN
        LV_Add("", "Scroll width", SendMsg(2275)) ; SCI_GETSCROLLWIDTH
        LV_Add("", "Scroll width tracking", SendMsg(2517) ? "True" : "False") ; SCI_GETSCROLLWIDTHTRACKING
        LV_Add("", "End at last line", SendMsg(2278) ? "True" : "False") ; SCI_GETENDATLASTLINE
        LV_Add("", "View white space", SendMsg(2020)) ; SCI_GETVIEWWS
        LV_Add("", "White space size", SendMsg(2087)) ; SCI_GETWHITESPACESIZE
        LV_Add("", "View EOL characters", SendMsg(2355) ? "True" : "False") ; SCI_GETVIEWEOL
        LV_Add("", "Caret width", SendMsg(2189)) ; SCI_GETCARETWIDTH
        LV_Add("", "Caret blinking rate", SendMsg(2075) . " ms") ; SCI_GETCARETPERIOD
        LV_Add("", "Markers in current line", SendMsg(2046, Line)) ; SCI_MARKERGET
        LV_Add("", "Automatic fold", SendMsg(2664)) ; SCI_GETAUTOMATICFOLD
        LV_Add("", "All lines visible", SendMsg(2236) ? "True" : "False") ; SCI_GETALLLINESVISIBLE
        ;LV_Add("", "", SendMsg()) ;

        Loop 5 { ; The maximum number of margins
            i := A_Index - 1
            LV_Add("", "Margin " . A_Index . " - type, width, mask"
            , SendMsg(2241, i) . ", " . SendMsg(2243, i) . ", " . SendMsg(2245, i))
            ; SCI_GETMARGINTYPEN, SCI_GETMARGINWIDTHN, SCI_GETMARGINMASKN
        }

    }
    Else If (Class == "ToolbarWindow32") {

        LV_InsertCol(1, "41", "Index")
        LV_InsertCol(2, "85", "Command ID")
        LV_InsertCol(3, "212", "Button Text")

        Items := GetToolbarItems(g_hWnd)
        For Each, Item in Items {
            LV_Add("", A_Index, Item.ID, Item.String)
        }
    }
    Else If (Class == "SysHeader32") {

        LV_InsertCol(1, "42", "Index")
        LV_InsertCol(2, "48", "Width")
        LV_InsertCol(3, "248", "Text")

        Items := GetHeaderInfo(g_hWnd)
        For Each, Item in Items {
            LV_Add("", A_Index, Item.Width, Item.Text)
        }
    }
    Else If (Class == "msctls_progress32") {

        LV_InsertCol(1, "169", "Property")
        LV_InsertCol(2, "169", "Value")

        LV_Add("", "Range", SendMsg(0x407, 1) . " - " . SendMsg(0x407, 0)) ; PBM_GETRANGE
        LV_Add("", "Position", SendMsg(0x408)) ; PBM_GETPOS
        LV_Add("", "Step increment", SendMsg(0x40D)) ; PBM_GETSTEP
        LV_Add("", "State", {1: "Normal", 2: "Error", 3: "Paused"}[SendMsg(0x411)]) ; PBM_GETSTATE
    }
    Else If (Class ~= "ListBox" || Class ~= "ComboBox") {

        LV_InsertCol(1, "38", "Line")
        LV_InsertCol(2, "300", "Text")

        ControlGet ItemList, List,,, ahk_id %g_hWnd%
        Loop Parse, ItemList, `n
        {
            LV_Add("", A_Index, A_LoopField)
        }
    }
    Else If (Class == "msctls_statusbar32") {

        LV_InsertCol(1, "38", "Part")
        LV_InsertCol(2, "300", "Text")

        SB_Text := GetStatusBarText(g_hWnd)
        Loop Parse, SB_Text, |
        {
            LV_Add("", A_Index, A_LoopField)
        }
    }
    Else If (Class == "SysTabControl32") {

        LV_InsertCol(1, "42", "Index")
        LV_InsertCol(2, "296", "Text")

        Tabs := ControlGetTabs(g_hWnd)
        Loop % Tabs.Length() {
            LV_Add("", A_Index, Tabs[A_Index])
        }
    }
    Else If (Class == "SysListView32") {

        SendMessage 0x101F, 0, 0,, ahk_id %g_hWnd% ; LVM_GETHEADER
        hHeader := ErrorLevel
        SendMessage 0x1200, 0, 0,, ahk_id %hHeader% ; HDM_GETITEMCOUNT
        Columns := ErrorLevel + 1

        Loop %Columns% {
            ColTitle := A_Index == 1 ? "Index" : "Column " . A_Index - 1
            LV_InsertCol(A_Index, "", ColTitle)
        }

        ControlGet ItemList, List,,, ahk_id %g_hWnd%
        Loop Parse, ItemList, `n
        {
            Items := StrSplit(A_LoopField, A_Tab)
            LV_Add("", A_Index, Items*)
        }

        Loop %Columns% {
            LV_ModifyCol(A_Index, "AutoHdr")
        }
    }

    ; GetMenu return value: If the window is a child window, the return value is undefined.
    If (!IsChild(g_hWnd) && hMenu := GetMenu(g_hWnd)) {
        GuiControl Enable, %hBtnMenu%
    } Else {
        GuiControl Disable, %hBtnMenu%
    }
Return

SendMsg(Message, wParam := 0, lParam := 0) {
    SendMessage %Message%, %wParam%, %lParam%,, ahk_id %g_hWnd%
    Return ErrorLevel
}

Screenshot:
    If (!WinExist("ahk_id" . g_hWnd)) {
        Gui Spy: +OwnDialogs
        MsgBox 0x40010, Error, Window no longer exists.
        Return
    }

    If (IsChild(g_hWnd)) {
        WinActivate % "ahk_id" . GetAncestor(g_hWnd)
        Sleep 100
        If (g_ShowBorder) {
            ShowBorder(g_hWnd, -1, g_BorderColor, g_BorderWidth)
            Sleep 100
            Send !{PrintScreen}
            Sleep 200
        } Else {
            CaptureWindow(hSpyWnd, g_hWnd)
        }
    } Else {
        WinActivate ahk_id %g_hWnd%
        Sleep 100
        Send !{PrintScreen}
    }

    Loop 4 {
        Index := A_Index + 90
        Gui %Index%: Destroy
    }

    WinActivate ahk_id %hSpyWnd%
    Gui Spy: +OwnDialogs
    MsgBox 0x40040, %AppName%, Content copied to the clipboard.
Return

CaptureWindow(hwndOwner, hwnd) {
    VarSetCapacity(RECT, 16, 0)
    DllCall("GetWindowRect", "Ptr", hwnd, "Ptr", &RECT)
    width  := NumGet(RECT, 8, "Int")  - NumGet(RECT, 0, "Int")
    height := NumGet(RECT, 12, "Int") - NumGet(RECT, 4, "Int")

    hdc    := DllCall("GetDC", "Ptr", 0, "Ptr")
    hdcMem := DllCall("CreateCompatibleDC", "Ptr", hdc, "UPtr")
    hBmp   := DllCall("CreateCompatibleBitmap", "Ptr", hdc, "Int", width, "Int", height, "UPtr")
    hdcOld := DllCall("SelectObject", "Ptr", hdcMem, "Ptr", hBmp)

    DllCall("BitBlt", "Ptr", hdcMem
        , "Int", 0, "Int", 0, "Int", width, "Int", height
        , "Ptr", hdc, "Int", Numget(RECT, 0, "Int"), "Int", Numget(RECT, 4, "Int")
        , "UInt", 0x00CC0020) ; SRCCOPY

    DllCall("SelectObject", "Ptr", hdcMem, "Ptr", hdcOld)

    DllCall("OpenClipboard", "Ptr", hwndOwner) ; Clipboard owner
    DllCall("EmptyClipboard")
    DllCall("SetClipboardData", "UInt", 0x2, "Ptr", hBmp) ; CF_BITMAP
    DllCall("CloseClipboard")

    DllCall("ReleaseDC", "Ptr", 0, "Ptr", hdc)

    Return True
}

GetToolbarItems(hToolbar) {
    WinGet PID, PID, ahk_id %hToolbar%

    If !(hProc := DllCall("OpenProcess", "UInt", 0x438, "Int", False, "UInt", PID, "Ptr")) {
        Return
    }

    If (A_Is64bitOS) {
        Try DllCall("IsWow64Process", "Ptr", hProc, "Int*", Is32bit := true)
    } Else {
        Is32bit := True
    }

    RPtrSize := Is32bit ? 4 : 8
    TBBUTTON_SIZE := 8 + (RPtrSize * 3)

    SendMessage 0x418, 0, 0,, ahk_id %hToolbar% ; TB_BUTTONCOUNT
    ButtonCount := ErrorLevel

    IDs := [] ; Command IDs
    Loop %ButtonCount% {
        Address := DllCall("VirtualAllocEx", "Ptr", hProc, "Ptr", 0, "UPtr", TBBUTTON_SIZE, "UInt", 0x1000, "UInt", 4, "Ptr")

        SendMessage 0x417, % A_Index - 1, Address,, ahk_id %hToolbar% ; TB_GETBUTTON
        If (ErrorLevel == 1) {
            VarSetCapacity(TBBUTTON, TBBUTTON_SIZE, 0)
            DllCall("ReadProcessMemory", "Ptr", hProc, "Ptr", Address, "Ptr", &TBBUTTON, "UPtr", TBBUTTON_SIZE, "Ptr", 0)
            IDs.Push(NumGet(&TBBUTTON, 4, "Int"))
        }

        DllCall("VirtualFreeEx", "Ptr", hProc, "Ptr", Address, "UPtr", 0, "UInt", 0x8000) ; MEM_RELEASE
    }

    ToolbarItems := []
    Loop % IDs.Length() {
        ButtonID := IDs[A_Index]
        ;SendMessage 0x44B, %ButtonID% , 0,, ahk_id %hToolbar% ; TB_GETBUTTONTEXTW
        ;BufferSize := ErrorLevel * 2
        BufferSize := 128

        Address := DllCall("VirtualAllocEx", "Ptr", hProc, "Ptr", 0, "UPtr", BufferSize, "UInt", 0x1000, "UInt", 4, "Ptr")

        SendMessage 0x44B, %ButtonID%, Address,, ahk_id %hToolbar% ; TB_GETBUTTONTEXTW

        VarSetCapacity(Buffer, BufferSize, 0)
        DllCall("ReadProcessMemory", "Ptr", hProc, "Ptr", Address, "Ptr", &Buffer, "UPtr", BufferSize, "Ptr", 0)

        ToolbarItems.Push({"ID": IDs[A_Index], "String": Buffer})

        DllCall("VirtualFreeEx", "Ptr", hProc, "Ptr", Address, "UPtr", 0, "UInt", 0x8000) ; MEM_RELEASE
    }

    DllCall("CloseHandle", "Ptr", hProc)

    Return ToolbarItems
}

LoadWindowsTab:
    GoSub LoadChildList
    GoSub LoadSiblingList

    hParent := GetParent(g_hWnd)
    ParentClass := (hParent) ? " (" GetClassName(hParent) . ")" : ""
    ParentLink := "<a>" . Format("0x{:08X}", hParent) . "</a>" . ParentClass
    GuiControl, Spy:, ParentLink, %ParentLink%

    hOwner := GetOwner(g_hWnd)
    OwnerClass := (hOwner) ? " (" GetClassName(hOwner) . ")" : ""
    OwnerLink := "<a>" . Format("0x{:08X}", hOwner) . "</a>" . OwnerClass
    GuiControl, Spy:, OwnerLink, %OwnerLink%
Return

LinkToHandle:
    GuiControlGet LinkText, Spy:, %A_GuiControl%
    If (RegExMatch(LinkText, "(0x\w+)", Match)) {
        ShowWindowInfoIfExist(Match)
    }
Return

WindowsTabHandler:
    If (A_GuiEvent == "N") {
        Code := NumGet(A_EventInfo + 0, A_PtrSize * 2, "Int")
        If (Code == -551) { ; TCN_SELCHANGE
            SendMessage 0x130B, 0, 0,, ahk_id %hWindowsTab% ; TCM_GETCURSEL
            nTab := Errorlevel + 1

            If (nTab == 1) {
                GuiControl Hide, %hSiblingList%
                GuiControl Show, %hChildList%
            } Else {
                GuiControl Hide, %hChildList%
                GuiControl Show, %hSiblingList%
            }
        }
    }
Return

LoadChildList:
    Gui Spy: ListView, %hChildList%
    LV_Delete()

    WinGet ChildList, ControlListHwnd, ahk_id %g_hWnd%
    Loop Parse, ChildList, `n
    {
        If (GetParent(A_LoopField) != g_hWnd) {
            Continue
        }

        WinGetClass Class, ahk_id %A_LoopField%
        ControlGetText Text,, ahk_id %A_LoopField%
        LV_Add("", Format("0x{:08X}", A_LoopField), Class, Text)
    }
Return

LoadSiblingList:
    Gui Spy: ListView, %hSiblingList%
    LV_Delete()

    hParent := GetParent(g_hWnd)

    If (IsChild(g_hWnd)) {
        WinGet SiblingList, ControlListHwnd, ahk_id %hParent%
        Loop Parse, SiblingList, `n
        {
            If (A_LoopField == g_hWnd) {
                Continue
            }

            If (GetParent(A_LoopField) != hParent) {
                Continue
            }

            WinGetClass Class, ahk_id %A_LoopField%
            ControlGetText Text,, ahk_id %A_LoopField%
            LV_Add("", Format("0x{:08X}", A_LoopField), Class, Text)
        }

    } Else {
        WinGet WinList, List, % (hParent == 0) ? "" : "ahk_id " . hParent
        Loop %WinList% {
            hWnd := WinList%A_Index%

            If (hWnd == g_hWnd) {
                Continue
            }

            WinGetClass Class, ahk_id %hWnd%
            WinGetTitle Text, ahk_id %hWnd%
            LV_Add("", Format("0x{:08X}", hWnd), Class, Text)
        }
    }
Return

ChildListHandler:
SiblingListHandler:
    Gui Spy: ListView, % (A_ThisLabel == "ChildListHandler") ? hChildList : hSiblingList
    If (A_GuiEvent != "ColClick") {
        LV_GetText(hWnd, LV_GetNext())
        ShowWindowInfoIfExist(hWnd)
    }
Return

OnWM_KEYDOWN(wParam, lParam, msg, hWnd) {
    Global

    If (wParam == 112) { ; F1
        GoSub ShowHelp

    } Else If (wParam == 113) { ; F2
        If (hParent := GetParent(g_hWnd)) {
            g_hWnd := hParent
            ShowWindowInfo()
        }

    } Else If (wParam == 114) { ; F3
        GoSub ShowFindDlg

    } Else If (wParam == 115) { ; F4
        GoSub ShowTree

    } Else If (wParam == 116) { ; F5
        If (WinActive("ahk_id" . hTreeWnd)) {
            Return
        }

        Gui Spy: Submit, NoHide
        ShowWindowInfoIfExist(EdtHandle)

    } Else If (wParam == 117) { ; F6
        GoSub FlashWindow

    } Else If (wParam == 118) { ; F7
        GoSub ShowXYWHDlg

    } Else If (wParam == 119) { ; F8
        GoSub CopyToClipboard

    } Else If (wParam == 120) { ; F9
        GoSub Screenshot
    }
}

UpdateTitleBar:
    WinGetClass Class, ahk_id %g_hWnd%
    hWnd := Format("0x{:X}", g_hWnd)
    WinSetTitle ahk_id %hSpyWnd%,, %AppName% [%hWnd%`, %Class%]
Return

; https://autohotkey.com/board/topic/70727-ahk-l-controlgettabs/
ControlGetTabs(hTab) {
    Static MAX_TEXT_LENGTH := 260
         , MAX_TEXT_SIZE := MAX_TEXT_LENGTH * (A_IsUnicode ? 2 : 1)

    WinGet PID, PID, ahk_id %hTab%

    ; Open the process for read/write and query info.
    ; PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION
    If !(hProc := DllCall("OpenProcess", "UInt", 0x438, "Int", False, "UInt", PID, "Ptr")) {
        Return
    }

    ; Should we use the 32-bit struct or the 64-bit struct?
    If (A_Is64bitOS) {
        Try DllCall("IsWow64Process", "Ptr", hProc, "Int*", Is32bit := true)
    } Else {
        Is32bit := True
    }

    RPtrSize := Is32bit ? 4 : 8
    TCITEM_SIZE := 16 + RPtrSize * 3

    ; Allocate a buffer in the (presumably) remote process.
    remote_item := DllCall("VirtualAllocEx", "Ptr", hProc, "Ptr", 0
                         , "uPtr", TCITEM_SIZE + MAX_TEXT_SIZE
                         , "UInt", 0x1000, "UInt", 4, "Ptr") ; MEM_COMMIT, PAGE_READWRITE
    remote_text := remote_item + TCITEM_SIZE

    ; Prepare the TCITEM structure locally.
    VarSetCapacity(TCITEM, TCITEM_SIZE, 0)
    NumPut(1, TCITEM, 0, "UInt") ; mask (TCIF_TEXT)
    NumPut(remote_text, TCITEM, 8 + RPtrSize) ; pszText
    NumPut(MAX_TEXT_LENGTH, TCITEM, 8 + RPtrSize * 2, "Int") ; cchTextMax

    ; Write the local structure into the remote buffer.
    DllCall("WriteProcessMemory", "Ptr", hProc, "Ptr", remote_item, "Ptr", &TCITEM, "UPtr", TCITEM_SIZE, "Ptr", 0)

    Tabs := []
    VarSetCapacity(TabText, MAX_TEXT_SIZE)

    SendMessage 0x1304, 0, 0,, ahk_id %hTab% ; TCM_GETITEMCOUNT
    Loop % (ErrorLevel != "FAIL") ? ErrorLevel : 0 {
        ; Retrieve the item text.
        SendMessage, % (A_IsUnicode) ? 0x133C : 0x1305, A_Index - 1, remote_item,, ahk_id %hTab% ; TCM_GETITEM
        If (ErrorLevel == 1) { ; Success
            DllCall("ReadProcessMemory", "Ptr", hProc, "Ptr", remote_text, "Ptr", &TabText, "UPtr", MAX_TEXT_SIZE, "Ptr", 0)
        } Else {
            TabText := ""
        }

        Tabs[A_Index] := TabText
    }

    ; Release the remote memory and handle.
    DllCall("VirtualFreeEx", "Ptr", hProc, "Ptr", remote_item, "UPtr", 0, "UInt", 0x8000) ; MEM_RELEASE
    DllCall("CloseHandle", "Ptr", hProc)

    Return Tabs
}

SetDHW:
    g_DetectHidden := !g_DetectHidden
Return

SetMinimize:
    g_Minimize := !g_Minimize
Return

SetExplorerTheme(hWnd) {
    DllCall("UxTheme.dll\SetWindowTheme", "Ptr", hWnd, "WStr", "Explorer", "Ptr", 0)
}

SetButtonIcon(hButton, File, Index := 1) {
    himl := DllCall("ImageList_Create", "Int", 16, "Int", 16, "UInt", 0x20, "Int", 1, "Int", 1, "Ptr") ; ILC_COLOR32
    IL_Add(himl, File, Index)
    VarSetCapacity(BUTTON_IMAGELIST, 20 + A_PtrSize, 0)
    NumPut(himl, BUTTON_IMAGELIST, 0, "Ptr")
    NumPut(4, BUTTON_IMAGELIST, 16 + A_PtrSize, "UInt") ; Alignment (BUTTON_IMAGELIST_ALIGN_CENTER)
    SendMessage 0x1602, 0, &BUTTON_IMAGELIST,, ahk_id %hButton% ; BCM_SETIMAGELIST
    Return ErrorLevel
}

ShowXYWHDlg:
    Gui XYWH: New, LabelXYWH
    Gui Font, s9, Segoe UI
    Gui Color, White

    Gui Add, GroupBox, x10 y6 w145 h105, Relative to:
    Gui Add, Radio, vClientCoords gSetXYWH x25 y27 w120 h23 +Checked, Client area
    Gui Add, Radio, vWindowCoords gSetXYWH x25 y51 w120 h23, Window border
    Gui Add, Radio, vScreenCoords gSetXYWH x25 y75 w120 h23, Screen coords

    Gui Add, GroupBox, x166 y5 w253 h105
    Gui Add, Text, x182 y31 w26 h23 +0x200, X:
    Gui Add, Edit, vEdtX x209 y31 w70 h21
    Gui Add, UpDown, gMoveWindow Range-64000-64000 +0x80
    Gui Add, Text, x303 y31 w26 h23 +0x200, Y:
    Gui Add, Edit, vEdtY x330 y31 w70 h21
    Gui Add, UpDown, gMoveWindow Range-64000-64000 +0x80
    Gui Add, Text, x182 y69 w26 h23 +0x200, W:
    Gui Add, Edit, vEdtW x209 y69 w70 h21
    Gui Add, UpDown, gMoveWindow Range-64000-64000 +0x80
    Gui Add, Text, x303 y69 w26 h23 +0x200, H:
    Gui Add, Edit, vEdtH x330 y69 w70 h21
    Gui Add, UpDown, gMoveWindow Range-64000-64000 +0x80

    Gui Add, Text, x-1 y121 w460 h49 +0x200 -Background +Border
    Gui Add, Button, gResetXYWH x9 y133 w88 h25, &Reset
    Gui Add, Button, gMoveWindow x235 y133 w88 h25 +Default, &Apply
    Gui Add, Button, gXYWHClose x331 y133 w88 h25, &Close

    Gui Show, w429 h170, Position and Size

    g_NewXYWH := True
    GoSub SetXYWH

    If (IsChild(g_hWnd)) {
        GuiControl Enable, ClientCoords
        GuiControl Enable, WindowCoords
        GuiControl Enable, ScreenCoords
    } Else {
        GuiControl Disable, ClientCoords
        GuiControl Disable, WindowCoords
        GuiControl Disable, ScreenCoords
        GuiControl,, ScreenCoords, 1
    }
Return

XYWHEscape:
XYWHClose:
    Gui XYWH: Destroy
Return

SetXYWH:
    Gui XYWH: Submit, NoHide

    If (IsChild(g_hWnd)) {
        If (ClientCoords) {
            GetWindowPos(g_hWnd, X, Y, W, H)
        } Else If (WindowCoords) {
            ControlGetPos X, Y, W, H,, ahk_id %g_hWnd%
        } Else If (ScreenCoords) {
            WinGetPos X, Y, W, H, ahk_id %g_hWnd%
        }
    } Else { ; Top-level window
        WinGetPos X, Y, W, H, ahk_id %g_hWnd%
    }

    GuiControl, XYWH:, EdtX, %X%
    GuiControl, XYWH:, EdtY, %Y%
    GuiControl, XYWH:, EdtW, %W%
    GuiControl, XYWH:, EdtH, %H%

    If (g_NewXYWH) {
        g_BackupXYWH := [X, Y, W, H]
        g_NewXYWH := False
    }
Return

MoveWindow:
    Gui XYWH: Submit, NoHide

    If (IsChild(g_hWnd)) {

        If (ClientCoords) {
            SetWindowPos(g_hWnd, EdtX, EdtY, EdtW, EdtH, 0, 0x14) ; SWP_NOACTIVATE | SWP_NOZORDER
        } Else If (WindowCoords) {
            ControlMove,, %EdtX%, %EdtY%, %EdtW%, %EdtH%, ahk_id %g_hWnd%
        } Else If (ScreenCoords) {
            VarSetCapacity(POINT, 8, 0)
            NumPut(EdtX, POINT, 0)
            NumPut(EdtY, POINT, 4)
            DllCall("ScreenToClient", "Ptr", GetParent(g_hWnd), "Ptr", &POINT) ; PARENT
            X := NumGet(POINT, 0)
            Y := NumGet(POINT, 4)
            SetWindowPos(g_hWnd, X, Y, EdtW, EdtH, 0, 0x14) ; SWP_NOACTIVATE | SWP_NOZORDER
        }
    } Else {
        WinMove ahk_id %g_hWnd%,, %EdtX%, %EdtY%, %EdtW%, %EdtH%
    }

    WinSet Redraw,, ahk_id %g_hWnd%
Return

ResetXYWH:
    Gui XYWH: Submit, NoHide

    If (IsChild(g_hWnd)) {
        GuiControl, XYWH:, ClientCoords, 1
    }

    GuiControl,, EdtX, % g_BackupXYWH[1]
    GuiControl,, EdtY, % g_BackupXYWH[2]
    GuiControl,, EdtW, % g_BackupXYWH[3]
    GuiControl,, EdtH, % g_BackupXYWH[4]
    GoSub MoveWindow
Return

OnWM_RBUTTONDOWN(wParam, lParam, msg, hWnd) {
    Global
    If (hWnd == hLbxStyles || hWnd == hLbxExStyles || hWnd == hLbxExtraStyles) {
        SendMessage 0x1A9, 0, lParam,, ahk_id %hWnd% ; LB_ITEMFROMPOINT
        Index := ErrorLevel
        SendMessage 0x18A, %Index%, 0,, ahk_id %hWnd% ; LB_GETTEXTLEN
        Len := ErrorLevel
        VarSetCapacity(LB_Text, Len << !!A_IsUnicode, 0)
        SendMessage 0x189, %Index%, % &LB_Text,, ahk_id %hWnd% ; LB_GETTEXT
        Const := StrSplit(StrGet(&LB_Text, Len), "`t")[1]
        Desc := StrReplace(oStyles[Const].Desc, "\n", "`n")
        GuiControl,, GrpDesc, %Const%
        GuiControl,, TxtDesc, %Desc%
    }
}

ShowWindowInfoIfExist(hWnd) {
    If (IsWindow(hWnd)) {
        g_hWnd := hWnd
        ShowWindowInfo()
    } Else {
        Gui Spy: +OwnDialogs
        MsgBox 0x40010, %AppName%, Invalid window handle.
    }
}

; Returns an object containing the text and width of each item of a remote SysHeader32 control
GetHeaderInfo(hHeader) {
    Static MAX_TEXT_LENGTH := 260
         , MAX_TEXT_SIZE := MAX_TEXT_LENGTH * (A_IsUnicode ? 2 : 1)

    WinGet PID, PID, ahk_id %hHeader%

    ; Open the process for read/write and query info.
    ; PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION
    If !(hProc := DllCall("OpenProcess", "UInt", 0x438, "Int", False, "UInt", PID, "Ptr")) {
        Return
    }

    ; Should we use the 32-bit struct or the 64-bit struct?
    If (A_Is64bitOS) {
        Try DllCall("IsWow64Process", "Ptr", hProc, "Int*", Is32bit := True)
    } Else {
        Is32bit := True
    }

    RPtrSize := Is32bit ? 4 : 8
    cbHDITEM := (4 * 6) + (RPtrSize * 6)

    ; Allocate a buffer in the remote process.
    remote_item := DllCall("VirtualAllocEx", "Ptr", hProc, "Ptr", 0
                         , "UPtr", cbHDITEM + MAX_TEXT_SIZE
                         , "UInt", 0x1000, "UInt", 4, "Ptr") ; MEM_COMMIT, PAGE_READWRITE
    remote_text := remote_item + cbHDITEM

    ; Prepare the HDITEM structure locally.
    VarSetCapacity(HDITEM, cbHDITEM, 0)
    NumPut(0x3, HDITEM, 0, "UInt") ; mask (HDI_WIDTH | HDI_TEXT)
    NumPut(remote_text, HDITEM, 8, "Ptr") ; pszText
    NumPut(MAX_TEXT_LENGTH, HDITEM, 8 + RPtrSize * 2, "Int") ; cchTextMax

    ; Write the local structure into the remote buffer.
    DllCall("WriteProcessMemory", "Ptr", hProc, "Ptr", remote_item, "Ptr", &HDITEM, "UPtr", cbHDITEM, "Ptr", 0)

    HDInfo := {}
    VarSetCapacity(HDText, MAX_TEXT_SIZE)

    SendMessage 0x1200, 0, 0,, ahk_id %hHeader% ; HDM_GETITEMCOUNT
    Loop % (ErrorLevel != "FAIL") ? ErrorLevel : 0 {
        ; Retrieve the item text.
        SendMessage, % (A_IsUnicode) ? 0x120B : 0x1203, A_Index - 1, remote_item,, ahk_id %hHeader% ; HDM_GETITEMW
        If (ErrorLevel == 1) { ; Success
            DllCall("ReadProcessMemory", "Ptr", hProc, "Ptr", remote_item, "Ptr", &HDITEM, "UPtr", cbHDITEM, "Ptr", 0)
            DllCall("ReadProcessMemory", "Ptr", hProc, "Ptr", remote_text, "Ptr", &HDText, "UPtr", MAX_TEXT_SIZE, "Ptr", 0)
        } Else {
            HDText := ""
        }

        HDInfo.Push({"Width": NumGet(HDITEM, 4, "UInt"), "Text": HDText})
    }

    ; Release the remote memory and handle.
    DllCall("VirtualFreeEx", "Ptr", hProc, "Ptr", remote_item, "UPtr", 0, "UInt", 0x8000) ; MEM_RELEASE
    DllCall("CloseHandle", "Ptr", hProc)

    Return HDInfo
}

SetMouseCoordMode:
    GuiControlGet g_MouseCoordMode, Spy:, MouseCoordMode
Return

GetClientCoords(hWnd, ByRef x, ByRef y) {
    VarSetCapacity(POINT, 8, 0)
    NumPut(x, POINT, 0, "Int")
    NumPut(y, POINT, 4, "Int")
    hParent := GetParent(hWnd)
    DllCall("ScreenToClient", "Ptr", (hParent == 0 ? hWnd : hParent), "Ptr", &POINT)
    x := NumGet(POINT, 0, "Int")
    y := NumGet(POINT, 4, "Int")
}

GetWindowCoords(hWnd, ByRef x, ByRef y) {
    hParent := GetParent(hWnd)
    WinGetPos px, py,,, % "ahk_id" . (hParent == 0 ? hWnd : hParent)
    x := x - px
    y := y - py
}

; fnbar: 0 = horizontal, 1 = vertical, 2 = hWnd is a scroll bar
GetScrollInfo(hWnd, fnBar := 1) {
    Local o := {}
    NumPut(VarSetCapacity(SCROLLINFO, 28, 0), SCROLLINFO, 0, "UInt")
    NumPut(0x1F, SCROLLINFO, 4, "UInt") ; fMask: SIF_ALL
    DllCall("GetScrollInfo", "Ptr", hWnd, "Int", fnBar, "Ptr", &SCROLLINFO)
    o.Min  := NumGet(SCROLLINFO, 8, "Int")
    o.Max  := NumGet(SCROLLINFO, 12, "Int")
    o.Page := NumGet(SCROLLINFO, 16, "UInt")
    o.Pos  := NumGet(SCROLLINFO, 20, "Int")
    Return o
}

ShowScrollBarInfo:
    V := H := "No"
    WinGet Style, Style, ahk_id %g_hWnd%
    WinGetClass Class, ahk_id %g_hWnd%

    If (Class == "ScrollBar") {
        If (Style & 1) { ; SBS_VERT
            V := "Yes"
            VSB := GetScrollInfo(g_hWnd, 2)
        } Else {
            H := "Yes"
            HSB := GetScrollInfo(g_hWnd, 2)
        }
    } Else {
        If (Style & 0x200000) { ; WS_VSCROLL
            V := "Yes"
        }
        If (Style & 0x100000) { ; WS_HSCROLL
            H := "Yes"
        }

        HSB := GetScrollInfo(g_hWnd, 0)
        VSB := GetScrollInfo(g_hWnd, 1)
    }

    HPercent := (HSB.Pos) ? " (" . Round(HSB.Pos / (HSB.Max - HSB.Min) * 100) . "%)" : ""
    VPercent := (VSB.Pos) ? " (" . Round(VSB.Pos / (VSB.Max - VSB.Min) * 100) . "%)" : ""

    Gui ScrollInfo: New, LabelScrollInfo hWndhScrollInfo -MinimizeBox OwnerSpy
    SetWindowIcon(hScrollInfo, ResDir . "\TreeIcons.icl", 31)
    Gui Font, s9, Segoe UI
    Gui Color, White
    Gui Add, CheckBox, w0 y0

    Gui Add, GroupBox, x15 y13 w148 h152, Horizontal Scrollbar
    Gui Add, Text, x24 y32 w60 h23 +0x200, Visible:
    Gui Add, Edit, x86 y36 w60 h21 -E0x200, %H%
    Gui Add, Text, x24 y57 w60 h23 +0x200, Minimum:
    Gui Add, Edit, x86 y61 w60 h21 -E0x200, % HSB.Min
    Gui Add, Text, x24 y82 w60 h23 +0x200, Maximum:
    Gui Add, Edit, x86 y86 w60 h21 -E0x200, % HSB.Max
    Gui Add, Text, x24 y107 w60 h23 +0x200, Position:
    Gui Add, Edit, x86 y111 w70 h21 -E0x200, % HSB.Pos . HPercent
    Gui Add, Text, x24 y132 w60 h23 +0x200, Page size:
    Gui Add, Edit, x86 y136 w60 h21 -E0x200, % HSB.Page

    Gui Add, GroupBox, x178 y13 w148 h152, Vertical Scrollbar
    Gui Add, Text, x188 y32 w56 h23 +0x200, Visible:
    Gui Add, Edit, x250 y36 w60 h21 -E0x200, %V%
    Gui Add, Text, x188 y57 w56 h23 +0x200, Minimum:
    Gui Add, Edit, x250 y61 w60 h21 -E0x200, % VSB.Min
    Gui Add, Text, x188 y82 w56 h23 +0x200, Maximum:
    Gui Add, Edit, x250 y86 w60 h21 -E0x200, % VSB.Max
    Gui Add, Text, x188 y107 w56 h23 +0x200, Position:
    Gui Add, Edit, x250 y111 w70 h21 -E0x200, % VSB.Pos . VPercent
    Gui Add, Text, x188 y132 w56 h23 +0x200, Page size:
    Gui Add, Edit, x250 y136 w60 h21 -E0x200, % VSB.Page

    Gui Add, Text, x-1 y180 w343 h50 -Background +Border
    Gui Add, Button, gScrollInfoClose x247 y193 w84 h24 +Default, &OK

    WinGetPos, X, Y,,, ahk_id %hSpyWnd%
    x += 30
    y += 109
    Gui Show, x%x% y%y% w341 h229, Scrollbars
Return

ScrollInfoEscape:
ScrollInfoClose:
    Gui ScrollInfo: Destroy
Return

GetSysColorName(Value) {
    Static SysColors := {0: "COLOR_SCROLLBAR"
    , 1: "COLOR_BACKGROUND"
    , 2: "COLOR_ACTIVECAPTION"
    , 3: "COLOR_INACTIVECAPTION"
    , 4: "COLOR_MENU"
    , 5: "COLOR_WINDOW"
    , 6: "COLOR_WINDOWFRAME"
    , 7: "COLOR_MENUTEXT"
    , 8: "COLOR_WINDOWTEXT"
    , 9: "COLOR_CAPTIONTEXT"
    , 10: "COLOR_ACTIVEBORDER"
    , 11: "COLOR_INACTIVEBORDER"
    , 12: "COLOR_APPWORKSPACE"
    , 13: "COLOR_HIGHLIGHT"
    , 14: "COLOR_HIGHLIGHTTEXT"
    , 15: "COLOR_BTNFACE"
    , 16: "COLOR_BTNSHADOW"
    , 17: "COLOR_GRAYTEXT"
    , 18: "COLOR_BTNTEXT"
    , 19: "COLOR_INACTIVECAPTIONTEXT"
    , 20: "COLOR_BTNHIGHLIGHT"
    , 21: "COLOR_3DDKSHADOW"
    , 22: "COLOR_3DLIGHT"
    , 23: "COLOR_INFOTEXT"
    , 24: "COLOR_INFOBK"
    , 26: "COLOR_HOTLIGHT"
    , 27: "COLOR_GRADIENTACTIVECAPTION"
    , 28: "COLOR_GRADIENTINACTIVECAPTION"
    , 29: "COLOR_MENUHILIGHT"
    , 30: "COLOR_MENUBAR"}

    Color := SysColors[Value]
    Return (Color == "") ? Value : Color
}

GetWindowPos(hWnd, ByRef X, ByRef Y, ByRef W, ByRef H) {
    VarSetCapacity(RECT, 16, 0)
    DllCall("GetWindowRect", "Ptr", hWnd, "Ptr", &RECT)
    DllCall("MapWindowPoints", "Ptr", 0, "Ptr", GetParent(hWnd), "Ptr", &RECT, "UInt", 2)
    X := NumGet(RECT, 0, "Int")
    Y := NumGet(RECT, 4, "Int")
    w := NumGet(RECT, 8, "Int") - X
    H := NumGet(RECT, 12, "Int") - Y
}

FindToolHandler:
    If (g_Minimize) {
        WinMove ahk_id %hSpyWnd%,,,,, 78
        g_Minimized := True
    }

    Dragging := True

    GuiControl,, %hFindTool%, %Bitmap2%

    DllCall("SetCapture", "Ptr", hSpyWnd)
    hOldCursor := DllCall("SetCursor", "Ptr", hCrossHair, "Ptr")
Return

OnWM_MOUSEMOVE(wParam, lParam, msg, hWnd) {
    Static hOldWnd := 0

    If (Dragging) {
        MouseGetPos x, y, hWin, hCtl, 2

        g_hWnd := (hCtl == "") ? hWin : hCtl

        If (g_MouseCoordMode != "Screen") {
            SendMessage 0x84, 0, % y << 16 | x,, ahk_id %hWnd% ; WM_NCHITTEST
            HitTest := ErrorLevel

            If (HitTest == 1 || hCtl != "") { ; 1 = HTCLIENT
                If (g_MouseCoordMode == "Client") {
                    GetClientCoords(g_hWnd, x, y)
                } Else If (g_MouseCoordMode == "Window") {
                    GetWindowCoords(g_hWnd, x, y)
                }
            }
        }

        GuiControl, Spy:, EdtCursor, %x%, %y%

        If (g_DetectHidden) {
            g_hWnd := ControlFromPoint(x, y, g_hWnd)
        }

        If (g_hWnd != hOldWnd && !IsBorder(g_hWnd)) {
            ShowBorder(g_hWnd, -1)
            If (IsChild(g_hWnd)) {
                MouseGetPos,,,, ClassNN
                LoadControlInfo(ClassNN)
            } Else {
                LoadWindowInfo()
            }
            GoSub UpdateTitleBar
        }

        hOldWnd := g_hWnd
    }
}

OnWM_LBUTTONUP(wParam, lParam, msg, hWnd) {
    If (Dragging) {
        Dragging := False

        DllCall("ReleaseCapture")
        DllCall("SetCursor", "Ptr", hOldCursor)
        GuiControl,, %hFindTool%, %Bitmap1%

        Loop 4 {
            Index := A_Index + 90
            Gui %Index%: Destroy
        }

        MouseGetPos,,,, ClassNN
        ShowWindowInfo()

        If (g_Minimized) {
            If (Workaround) {
                SendMessage 0x130C, 1, 0,, ahk_id %hTab% ; TCM_SETCURSEL
                SendMessage 0x1330, 0, 0,, ahk_id %hTab% ; TCM_SETCURFOCUS
                Sleep 0
                SendMessage 0x130C, 0, 0,, ahk_id %hTab% ; TCM_SETCURSEL
                Workaround := False
            }

            WinMove ahk_id %hSpyWnd%,,,,, 493
        }
    }
}

SetWindowIcon(hWnd, Filename, Index := 1) {
    Local hSmIcon := LoadPicture(Filename, "w16 Icon" . Index, ErrorLevel)
    SendMessage 0x80, 0, hSmIcon,, ahk_id %hWnd% ; WM_SETICON, ICON_SMALL
    Return ErrorLevel
}

GetWindowPlacement(hWnd) {
    VarSetCapacity(WINDOWPLACEMENT, 44, 0)
    NumPut(44, WINDOWPLACEMENT)
    DllCall("GetWindowPlacement", "Ptr", hWnd, "Ptr", &WINDOWPLACEMENT)
    Result := {}
    Result.x := NumGet(WINDOWPLACEMENT, 7 * 4, "UInt")
    Result.y := NumGet(WINDOWPLACEMENT, 8 * 4, "UInt")
    Result.w := NumGet(WINDOWPLACEMENT, 9 * 4, "UInt") - Result.x
    Result.h := NumGet(WINDOWPLACEMENT, 10 * 4, "UInt") - Result.y
    Result.showCmd := NumGet(WINDOWPLACEMENT, 8, "UInt")
    ; 1 = normal, 2 = minimized, 3 = maximized
    Return Result
}

GetWindowInfo(hWnd) {
    NumPut(VarSetCapacity(WINDOWINFO, 60, 0), WINDOWINFO)
    DllCall("GetWindowInfo", "Ptr", hWnd, "Ptr", &WINDOWINFO)
    wi := Object()
    wi.WindowX := NumGet(WINDOWINFO, 4, "Int")
    wi.WindowY := NumGet(WINDOWINFO, 8, "Int")
    wi.WindowW := NumGet(WINDOWINFO, 12, "Int") - wi.WindowX
    wi.WindowH := NumGet(WINDOWINFO, 16, "Int") - wi.WindowY
    wi.ClientX := NumGet(WINDOWINFO, 20, "Int")
    wi.ClientY := NumGet(WINDOWINFO, 24, "Int")
    wi.ClientW := NumGet(WINDOWINFO, 28, "Int") - wi.ClientX
    wi.ClientH := NumGet(WINDOWINFO, 32, "Int") - wi.ClientY
    wi.Style   := NumGet(WINDOWINFO, 36, "UInt")
    wi.ExStyle := NumGet(WINDOWINFO, 40, "UInt")
    wi.Active  := NumGet(WINDOWINFO, 44, "UInt")
    wi.BorderW := NumGet(WINDOWINFO, 48, "UInt")
    wi.BorderH := NumGet(WINDOWINFO, 52, "UInt")
    wi.Atom    := NumGet(WINDOWINFO, 56, "UShort")
    wi.Version := NumGet(WINDOWINFO, 58, "UShort")
    Return wi
}

GetParent(hWnd) {
    Return DllCall("GetParent", "Ptr", hWnd, "Ptr")
}

GetOwner(hWnd) {
    Return DllCall("GetWindow", "Ptr", hWnd, "UInt", 4, "Ptr") ; GW_OWNER
}

ShowWindow(hWnd, nCmdShow := 1) {
    DllCall("ShowWindow", "Ptr", hWnd, "Int", nCmdShow)
}

IsWindow(hWnd) {
    Return DllCall("IsWindow", "Ptr", hWnd)
}

IsWindowVisible(hWnd) {
    Return DllCall("IsWindowVisible", "Ptr", hWnd)
}

GetMenu(hWnd) {
    Return DllCall("GetMenu", "Ptr", hWnd, "Ptr")
}

GetSubMenu(hMenu, nPos) {
    Return DllCall("GetSubMenu", "Ptr", hMenu, "Int", nPos, "Ptr")
}

GetMenuItemCount(hMenu) {
    Return DllCall("GetMenuItemCount", "Ptr", hMenu)
}

GetMenuItemID(hMenu, nPos) {
    Return DllCall("GetMenuItemID", "Ptr", hMenu, "Int", nPos)
}

GetMenuString(hMenu, uIDItem) {
    ; uIDItem: the zero-based relative position of the menu item
    Local lpString, MenuItemID
    VarSetCapacity(lpString, 4096)
    If !(DllCall("GetMenuString", "Ptr", hMenu, "UInt", uIDItem, "Str", lpString, "Int", 4096, "UInt", 0x400)) {
        MenuItemID := GetMenuItemID(hMenu, uIDItem)
        If (MenuItemID > -1) {
            Return "SEPARATOR"
        } Else {
            Return (GetSubMenu(hMenu, uIDItem)) ? "SUBMENU" : "ERROR"
        }
    }
    Return lpString
}

GetClassName(hWnd) {
    WinGetClass Class, ahk_id %hWnd%
    Return Class
}

GetFileIcon(File, SmallIcon := 1) {
    VarSetCapacity(SHFILEINFO, cbFileInfo := A_PtrSize + 688)
    If (DllCall("Shell32.dll\SHGetFileInfoW"
        , "WStr", File
        , "UInt", 0
        , "Ptr" , &SHFILEINFO
        , "UInt", cbFileInfo
        , "UInt", 0x100 | SmallIcon)) { ; SHGFI_ICON
        Return NumGet(SHFILEINFO, 0, "Ptr")
    }
}

SetWindowPos(hWnd, x, y, w, h, hWndInsertAfter := 0, uFlags := 0x40) { ; SWP_SHOWWINDOW
    Return DllCall("SetWindowPos", "Ptr", hWnd, "Ptr", hWndInsertAfter, "Int", x, "Int", y, "Int", w, "Int", h, "UInt", uFlags)
}

ShowSettingsDlg:
    Gui Settings: New, LabelSettings hWndhSettingsDlg -MinimizeBox OwnerSpy
    SetWindowIcon(hSettingsDlg, ResDir . "\Settings.ico")
    Gui Font, s9, Segoe UI
    Gui Color, White

    Gui Add, GroupBox, x8 y7 w319 h56, %AppName%
    Gui Add, CheckBox, vg_AlwaysOnTop x20 y27 w291 h23 Checked%g_AlwaysOnTop%, Show the window always on top

    Gui Add, GroupBox, x8 y69 w319 h152, Screenshot
    Gui Add, Radio, % "x20 y88 w290 h23 " . (!g_ShowBorder ? "Checked" : "")
    , Capture the contents of the control only
    Gui Add, Radio, vg_ShowBorder x20 y118 w290 h23 Checked%g_ShowBorder%
    , Display a border around the control
    Gui Add, Text, x36 y150 w78 h23 +0x200, Border color:
    Gui Add, Progress, vBorderColorPreview x119 y151 w23 h23 +0x800000 c%g_BorderColor%, 100
    Gui Add, Button, gChooseBorderColor x150 y150 w80 h24, Choose
    Gui Add, Text, x36 y183 w79 h23 +0x200, Border width:
    Gui Add, Edit, vg_BorderWidth x119 y184 w42 h21
    Gui Add, UpDown, x159 y184 w18 h21, %g_BorderWidth%

    Gui Add, Text, x-1 y231 w338 h48 -Background +Border
    Gui Add, Button, gApplySettings x152 y243 w84 h24 +Default, &OK
    Gui Add, Button, gSettingsClose x243 y243 w84 h24, &Cancel
    Gui Show, w335 h278, Settings
Return

SettingsEscape:
SettingsClose:
    Gui Settings: Destroy
Return

ApplySettings:
    Gui Settings: Submit
    WinSet AlwaysOnTop, % g_AlwaysOnTop ? "On" : "Off", ahk_id %hSpyWnd%
    g_ShowBorder := (g_ShowBorder == 2) ? 1 : 0
    g_BorderColor := g_BorderColorTemp
Return

ChooseBorderColor:
    g_BorderColorTemp := g_BorderColor
    If (ChooseColor(g_BorderColorTemp, hSettingsDlg)) {
        GuiControl, Settings: +c%g_BorderColorTemp%, BorderColorPreview
    }
Return

ChooseColor(ByRef Color, hOwner := 0) {
    rgbResult := ((Color & 0xFF) << 16) + (Color & 0xFF00) + ((Color >> 16) & 0xFF)

    VarSetCapacity(CUSTOM, 64, 0)
    NumPut(VarSetCapacity(CHOOSECOLOR, A_PtrSize * 9, 0), CHOOSECOLOR, 0)
    NumPut(hOwner, CHOOSECOLOR, A_PtrSize)
    NumPut(rgbResult, CHOOSECOLOR, A_PtrSize * 3)
    NumPut(&CUSTOM, CHOOSECOLOR, A_PtrSize * 4) ; COLORREF *lpCustColors
    NumPut(0x103, CHOOSECOLOR, A_PtrSize * 5) ; Flags: CC_ANYCOLOR | CC_RGBINIT | CC_FULLOPEN

    RetVal := DllCall("comdlg32\ChooseColorA", "Str", CHOOSECOLOR)
    If (ErrorLevel != 0 || RetVal == 0) {
        Return False
    }

    rgbResult := NumGet(CHOOSECOLOR, A_PtrSize * 3)
    Color := (rgbResult & 0xFF00) + ((rgbResult & 0xFF0000) >> 16) + ((rgbResult & 0xFF) << 16)
    Color := Format("0x{:06X}", Color)
    Return True
}

OnWM_SYSCOMMAND(wParam, lParam, msg, hWnd) {
    If (wParam == 0xC0DE) {
        Gui Spy: +OwnDialogs
        MsgBox 0x40040, About, %AppName% %Version%`nWindow information tool`n`nCredits:`n - J Brown (WinSpy++ developer)`n - Lexicos (AutoHotkey developer)
    }
}

ShowFindDlg:
    If (FindDlgExist) {
        Gui Find: Show
    } Else {
        Gui Find: New, LabelFind hWndhFindDlg
        Gui Font, s9, Segoe UI
        Gui Color, White

        Gui Add, Text, x15 y16 w81 h23 +0x200, Text or Title:
        Gui Add, Edit, hWndhEdtFindByText vEdtFindByText gFindWindow x144 y17 w286 h21
        Gui Add, CheckBox, vChkFindRegEx x441 y16 w120 h23, Regular Expression
        Gui Add, Text, x15 y54 w79 h23 +0x200, Class Name:
        Gui Add, ComboBox, vCbxFindByClass gFindWindow x144 y54 w286
        Gui Add, Text, x15 y93 w110 h23 +0x200, Process ID or Name:
        Gui Add, ComboBox, vCbxFindByProcess gFindWindow x144 y93 w286

        Gui Add, ListView, hWndhFindList gFindListHandler x10 y130 w554 h185 +LV0x14000
        , hWnd|Class|Text|Process
        LV_ModifyCol(1, 0)
        LV_ModifyCol(2, 133)
        LV_ModifyCol(3, 285)
        LV_ModifyCol(4, 112)

        Gui Add, Text, x-1 y329 w576 h49 +Border -Background
        Gui Add, Button, gFindOK x381 y342 w88 h25 Default, &OK
        Gui Add, Button, gFindClose x477 y342 w88 h25, &Cancel

        Gui Show, w574 h377, Find Window
        SetExplorerTheme(hFindList)

        FindDlgExist := True
    }

    ; Unique class names
    Global Classes := []
    WinGet WinList, List
    Loop %WinList% {
        hThisWnd := WinList%A_Index%
        WinGetClass WClass, ahk_id %hThisWnd%
        AddUniqueClass(WClass)

        WinGet ControlList, ControlListHwnd, ahk_id %hThisWnd%
        Loop Parse, ControlList, `n
        {
            WinGetClass CClass, ahk_id %A_LoopField%
            AddUniqueClass(CClass)
        }
    }

    ClassList := ""
    Loop % Classes.Length()  {
        ClassList .= Classes[A_Index] . "|"
    }

    GuiControl,, CbxFindByClass, %ClassList%

    ; Unique process names
    Processes := []
    For Process in ComObjGet("winmgmts:").ExecQuery("Select * from Win32_Process") {
        If (Process.ProcessID < 10) {
            Continue
        }

        Unique := True
        Loop % Processes.Length() {
            If (Process.Name == Processes[A_Index]) {
                Unique := False
                Break
            }
        }

        If (Unique) {
            Processes.Push(Process.Name)
        }
    }

    ProcList := ""
    MaxItems := Processes.Length()
    Loop %MaxItems%  {
        ProcList .= Processes[MaxItems - A_Index + 1] . "|"
    }

    GuiControl,, CbxFindByProcess, %ProcList%
Return

AddUniqueClass(ClassName) {
    Local Unique := True
    Loop % Classes.Length() {
        If (ClassName == Classes[A_Index]) {
            Unique := False
            Break
        }
    }

    If (Unique) {
        Classes.Push(ClassName)
    }
}

FindEscape:
FindClose:
    Gui Find: Hide
Return

FindWindow:
    Gui Find: Submit, NoHide

    Gui ListView, %hFindList%
    GuiControl -Redraw, %hFindList%
    LV_Delete()

    WinGet WinList, List
    Loop %WinList% {
        hThisWnd := WinList%A_Index%
        If (hThisWnd == hFindDlg) {
            Continue
        }

        WinGetClass WClass, ahk_id %hThisWnd%
        WinGetTitle WTitle, ahk_id %hThisWnd%
        WinGet WProcess, ProcessName, ahk_id %hThisWnd%
        WinGet WProcPID, PID, ahk_id %hThisWnd%

        If (MatchCriteria(WTitle, WClass, IsNumber(CbxFindByProcess) ? WProcPID : WProcess)) {
            LV_Add("", hThisWnd, WClass, WTitle, WProcess)
        }

        WinGet ControlList, ControlListHwnd, ahk_id %hThisWnd%
        Loop Parse, ControlList, `n
        {
            ControlGetText CText,, ahk_id %A_LoopField%
            WinGetClass CClass, ahk_id %A_LoopField%
            WinGet CProcess, ProcessName, ahk_id %A_LoopField%
            WinGet CProcPID, PID, ahk_id %A_LoopField%

            If (MatchCriteria(CText, CClass, IsNumber(CbxFindByProcess) ? CProcPID : CProcess)) {
                LV_Add("", A_LoopField, CClass, CText, CProcess)
            }
        }
    }

    GuiControl +Redraw, %hFindList%
Return

MatchCriteria(Text, Class, Process) {
    Global

    If (EdtFindByText != "") {
        If (ChkFindRegEx) {
            If (RegExMatch(Text, EdtFindByText) < 1) {
                Return False
            }
        } Else {
            If (!InStr(Text, EdtFindByText)) {
                Return False
            }
        }
    }

    If (CbxFindByClass != "" && !InStr(Class, CbxFindByClass)) {
        Return False
    }

    If (CbxFindByProcess != "") {
        Return IsNumber(Process) ? CbxFindByProcess == Process : InStr(Process, CbxFindByProcess)
    }

    Return True
}

FindOK:
    Gui ListView, %hFindList%
    LV_GetText(hWnd, LV_GetNext())
    GuiControl, Spy:, EdtHandle, %hWnd%
    WinActivate ahk_id %hSpyWnd%
    Gui Find: Hide
Return

FindListHandler:
    If (A_GuiEvent == "DoubleClick") {
        GoSub FindOK
    }
Return

CreateImageList() {
    ImageList := IL_Create(32)
    IL_Add(ImageList, TreeIcons, 1)  ; Generic window icon
    IL_Add(ImageList, TreeIcons, 2)  ; Desktop (#32769)
    IL_Add(ImageList, TreeIcons, 3)  ; Dialog (#32770)
    IL_Add(ImageList, TreeIcons, 4)  ; Button
    IL_Add(ImageList, TreeIcons, 5)  ; CheckBox
    IL_Add(ImageList, TreeIcons, 6)  ; ComboBox
    IL_Add(ImageList, TreeIcons, 7)  ; DateTime
    IL_Add(ImageList, TreeIcons, 8)  ; Edit
    IL_Add(ImageList, TreeIcons, 9)  ; GroupBox
    IL_Add(ImageList, TreeIcons, 10) ; Hotkey
    IL_Add(ImageList, TreeIcons, 11) ; Icon
    IL_Add(ImageList, TreeIcons, 12) ; Link
    IL_Add(ImageList, TreeIcons, 13) ; ListBox
    IL_Add(ImageList, TreeIcons, 14) ; ListView
    IL_Add(ImageList, TreeIcons, 15) ; MonthCal
    IL_Add(ImageList, TreeIcons, 16) ; Picture
    IL_Add(ImageList, TreeIcons, 17) ; Progress
    IL_Add(ImageList, TreeIcons, 18) ; Radio
    IL_Add(ImageList, TreeIcons, 19) ; RichEdit
    IL_Add(ImageList, TreeIcons, 20) ; Separator
    IL_Add(ImageList, TreeIcons, 21) ; Slider
    IL_Add(ImageList, TreeIcons, 22) ; Status bar
    IL_Add(ImageList, TreeIcons, 23) ; Tab
    IL_Add(ImageList, TreeIcons, 24) ; Text
    IL_Add(ImageList, TreeIcons, 25) ; Toolbar
    IL_Add(ImageList, TreeIcons, 26) ; Tooltips
    IL_Add(ImageList, TreeIcons, 27) ; TreeView
    IL_Add(ImageList, TreeIcons, 28) ; UpDown
    IL_Add(ImageList, TreeIcons, 29) ; IE
    IL_Add(ImageList, TreeIcons, 30) ; Scintilla
    IL_Add(ImageList, TreeIcons, 31) ; ScrollBar
    IL_Add(ImageList, TreeIcons, 32) ; SysHeader
    Return ImageList
}

ShowTree:
    If (WinExist("ahk_id" . hTreeWnd)) {
        Gui Tree: Show
        SetWindowIcon(hTreeWnd, "shell32.dll", 42)
    } Else {
        Gui Tree: New, LabelTree hWndhTreeWnd +Resize
        SetWindowIcon(hTreeWnd, "shell32.dll", 42)

        Menu TreeMenu, Add, &Reload`tF5, LoadTree
        Menu TreeMenu, Add
        Menu TreeMenu, Add, E&xit`tEsc, TreeClose
        Menu MenuBar, Add, &Tree, :TreeMenu
        Menu ViewMenu, Add, Show &Hidden Windows, ToggleHiddenWindows
        Menu ViewMenu, Add
        Menu ViewMenu, Add, &Flash Window`tF6, FlashWindow
        Menu ViewMenu, Add
        Menu ViewMenu, Add, E&xpand All Nodes, ExpandAll
        Menu ViewMenu, Add, &Collapse All Nodes, CollapseAll
        Menu MenuBar, Add, &View, :ViewMenu
        Gui Tree: Menu, MenuBar

        Gui Font, s9, Segoe UI
        Gui Add, TreeView, hWndhTree gTreeHandler x0 y0 w681 h445 -Lines +0x9000

        Gui Show, w681 h445, %AppName% - Tree

        TV_SetImageList(CreateImageList())
        SetExplorerTheme(hTree)
    }

    GoSub LoadTree
Return

TreeEscape:
TreeClose:
    Gui Tree: Hide
Return

TreeSize:
    If (A_EventInfo == 1) { ; Minimized
        Return
    }

    AutoXYWH("wh", hTree)
Return

LoadTree:
    Global TreeIDs := {}

    Gui Tree: Default
    TV_Delete()

    RootID := TV_Add("Desktop", 0, "Icon2")
    TreeIDs[RootID] := DllCall("GetDesktopWindow", "Ptr")

    WinGet WinList, List
    Loop %WinList% {
        hWnd := WinList%A_Index%

        WinGetClass Class, ahk_id %hWnd%
        WinGetTitle Title, ahk_id %hWnd%
        If (Title != "") {
            Title := " - " . Title
        }

        Invisible := !IsWindowVisible(hWnd)

        If (!g_TreeShowAll && Invisible) {
            Continue
        }

        If (Invisible) {
            Title .= " (hidden)"
        }

        Icon := GetWindowIcon(hWnd, Class, True)

        ID := TV_Add(Class . Title, RootID, "Icon" . Icon)
        TreeIDs[ID] := hWnd
        Tree(hWnd, ID)
    }

    TV_Modify(RootID, "+Expand")

    For Key, Value in TreeIDs {
        If (g_hWnd == Value) {
            TV_Modify(Key, "Select")
        }
    }
Return

Tree(hParentWnd, ParentID) {
    WinGet WinList, ControlListHwnd, ahk_id %hParentWnd%
    Loop Parse, WinList, `n
    {
        If (GetParent(A_LoopField) != hParentWnd) {
            Continue
        }

        WinGetClass Class, ahk_id %A_LoopField%
        If (IsChild(A_LoopField)) {
            ControlGetText Text,, ahk_id %A_LoopField%
        } Else {
            WinGetTitle Text,, ahk_id %A_LoopField%
        }

        If (Text != "") {
            Text := " - " . Text
        }

        Invisible := !IsWindowVisible(A_LoopField)

        If (!g_TreeShowAll && Invisible) {
            Continue
        }

        If (Invisible) {
            Text .= " (hidden)"
        }

        Icon := GetWindowIcon(A_LoopField, Class)

        ID := TV_Add(Class . Text, ParentID, "Icon" . Icon)
        TreeIDs[ID] := A_LoopField
        Tree(A_LoopField, ID)
    }
}

TreeHandler:
    If (A_GuiEvent == "DoubleClick") {
        g_hWnd := TreeIDs[A_EventInfo]
        ShowWindowInfo()
    }
Return

GetWindowIcon(hWnd, Class, TopLevel := False) {
    Static Classes := {0:0
    , "#32770": 3
    , "Button": 4
    , "CheckBox": 5
    , "ComboBox": 6
    , "SysDateTimePick32": 7
    , "Edit": 8
    , "GroupBox": 9
    , "msctls_hotkey32": 10
    , "Icon": 11
    , "SysLink": 12
    , "ListBox": 13
    , "SysListView32": 14
    , "SysMonthCal32": 15
    , "Picture": 16
    , "msctls_progress32": 17
    , "Radio": 18
    , "RebarWindow32": 25
    , "RichEdit": 19
    , "Separator": 20
    , "msctls_trackbar32": 21
    , "msctls_statusbar32": 22
    , "SysTabControl32": 23
    , "Static": 24
    , "ToolbarWindow32": 25
    , "tooltips_class32": 26
    , "SysTreeView32": 27
    , "msctls_updown32": 28
    , "Internet Explorer_Server": 29
    , "Scintilla": 30
    , "ScrollBar": 31
    , "SysHeader32": 32}

    If (Class == "Button") {
        WinGet Style, Style, ahk_id %hWnd%
        Type := Style & 0xF
        If (Type == 7) {
            Class := "GroupBox"
        } Else If (Type ~= "2|3|5|6") {
            Class := "CheckBox"
        } Else If (Type ~= "4|9") {
            Class := "Radio"
        } Else {
            Class := "Button"
        }
    } Else If (Class == "Static") {
        WinGet Style, Style, ahk_id %hWnd%
        Type := Style & 0x1F ; SS_TYPEMASK
        If (Type == 3) {
            Class := "Icon"
        } Else If (Type == 14) {
            Class := "Picture"
        } Else If (Type == 0x10) {
            Class := "Separator"
        } Else {
            Class := "Static"
        }
    } Else If (InStr(Class, "RICHED", True) == 1) {
        Class := "RichEdit" ; RICHEDIT50W
    }

    Icon := Classes[Class]
    If (Icon != "") {
        Return Icon
    }

    SendMessage 0x7F, 2, 0,, ahk_id %hWnd% ; WM_GETICON, ICON_SMALL2
    hIcon := ErrorLevel

    If (hIcon == 0 && TopLevel) {
        WinGet ProcessPath, ProcessPath, ahk_id %hWnd%
        hIcon := GetFileIcon(ProcessPath)
    }

    IconIndex := (hIcon) ? IL_Add(ImageList, "HICON: " . hIcon) : 1
    Return IconIndex
}

ToggleHiddenWindows:
    g_TreeShowAll := !g_TreeShowAll
    GoSub LoadTree
    Menu ViewMenu, ToggleCheck, Show &Hidden Windows
Return

CollapseAll:
ExpandAll:
    Expand := (A_ThisLabel == "ExpandAll") ? "+Expand" : "-Expand"

    ItemID := 0
    Loop {
        ItemID := TV_GetNext(ItemID, "Full")
        If (!ItemID) {
            Break
        }

        TV_Modify(ItemID, Expand)
    }
Return

FlashWindow:
    If (A_Gui == "Tree") {
        hWnd := TreeIDs[TV_GetSelection()]
    } Else {
        hWnd := g_hWnd
    }

    ShowBorder(hWnd, 200, 0xFF0000)
    Sleep 200
    ShowBorder(hWnd, 200, 0xFF0000)
    Sleep 200
    ShowBorder(hWnd, 200, 0xFF0000)
Return

; Based on a script written by Lexicos
ShowMenuViewer:
    If (MenuViewerExist) {
        Gui MenuViewer: Show
    } Else {
        Gui MenuViewer: New, +LabelMenuViewer +hWndhMenuViewer +Resize
        Gui Font, s9, Segoe UI
        Gui Color, 0xF1F5FB

        Gui Add, ListView, hWndhMenuList vLVMenu x0 y0 w600 h400 +LV0x14000, Menu Item String|Keyboard|Menu ID
        LV_ModifyCol(1, 410)
        LV_ModifyCol(2, 103)
        LV_ModifyCol(3, "65 Integer")
        SetExplorerTheme(hMenuList)

        Gui Add, Edit, hWndhEdtMenuSearch vEdtMenuSearch gSearchMenu x8 y408 w200 h23 +0x2000000 ; WS_CLIPCHILDREN
        DllCall("SendMessage", "Ptr", hEdtMenuSearch, "UInt", 0x1501, "Ptr", 1, "WStr", "Search")

        Gui Add, Picture, hWndhSearchIcon x178 y1 w16 h16, %ResDir%\Search.ico
        DllCall("SetParent", "Ptr", hSearchIcon, "Ptr", hEdtMenuSearch)
        WinSet Style, -0x40000000, ahk_id %hSearchIcon% ; -WS_CHILD
        ControlFocus,, ahk_id %hEdtMenuSearch%

        NoAmpersands := True
        Gui Add, CheckBox, vNoAmpersands gShowMenuItems x220 y408 w167 h23 Checked%NoAmpersands%
        , Remove Ampersands (&&)
        Gui Add, Button, vBtnMenuCopy gCopyMenuList x466 y407 w125 h25, Copy to Clipboard
        Gui Show, w600 h440, Menu Viewer
        MenuViewerExist := True
    }

    GoSub ShowMenuItems
Return

ShowMenuItems:
    Gui MenuViewer: Default
    Gui Submit, NoHide

    LV_Delete()
    hMenu := GetMenu(g_hWnd)
    If (hMenu) {
        MenuItems := []
        GetMenuItems(hMenu, "", "")
    }
Return

GetMenuItems(hMenu, Prefix, ByRef Commands) {
    Global
    ItemCount := GetMenuItemCount(hMenu)

    Loop %ItemCount% {
        ItemString := GetMenuString(hMenu, A_Index - 1)
        ItemID := GetMenuItemID(hMenu, A_Index - 1)

        RegExMatch(ItemString, "\t(.+)", Keyboard)
        ItemString := RegExReplace(ItemString, "\t.*")
        If (ItemString == "SEPARATOR") {
            ItemString := "----------------------------"
        }

        MenuItems.Push([ItemString, Keyboard1, ItemID])

        If (NoAmpersands) {
            ItemString := StrReplace(ItemString, "&")
        }

        LV_Add("", Prefix . ItemString, Keyboard1, ItemID)

        If (ItemID == -1) { ; Submenu
            hSubMenu := GetSubMenu(hMenu, A_Index - 1)
            If (hSubMenu) {
                Prefix .= "        "
                GetMenuItems(hSubMenu, Prefix, Commands)
                Prefix := StrReplace(Prefix, "        ", "",, 1)
                Continue
            }
        }
    }
}

SearchMenu:
    Gui MenuViewer: Submit, NoHide

    Gui ListView, %hMenuList%
    LV_Delete()

    Loop % MenuItems.Length() {
        MenuItem := MenuItems[A_Index][1]
        Keyboard := MenuItems[A_Index][2]
        MenuID   := MenuItems[A_Index][3]

        If (NoAmpersands) {
            MenuItem := StrReplace(MenuItem, "&")
        }

        If (InStr(MenuItem, EdtMenuSearch)
        ||  InStr(Keyboard, EdtMenuSearch)
        ||  InStr(MenuID, EdtMenuSearch)) {
            LV_Add("", MenuItem, Keyboard, MenuID)
        }
    }
Return

CopyMenuList:
    Gui MenuViewer: Default
    ControlGet MenuList, List,,, ahk_id %hMenuList%
    Clipboard := StrReplace(MenuList, "        ", "`t")
Return

MenuViewerEscape:
MenuViewerClose:
    Gui MenuViewer: Hide
Return

MenuViewerSize:
    If (A_EventInfo == 1) { ; Minimized
        Return
    }

    AutoXYWH("wh", hMenuList)
    AutoXYWH("y", hEdtMenuSearch)
    AutoXYWH("y", hSearchIcon)
    AutoXYWH("y", "NoAmpersands")
    AutoXYWH("xy", "BtnMenuCopy")
Return

MenuViewerContextMenu:
    Row := LV_GetNext()
    LV_GetText(MenuString, Row, 1)
    LV_GetText(MenuID, Row, 3)

    If (A_GuiControl == "LVMenu" && MenuID > 0 && !InStr(MenuString, "-----")) {
        Menu MenuMenu, Add, Invoke Menu Command, InvokeMenuCommand
        Menu MenuMenu, Show
    }
Return

InvokeMenuCommand:
    PostMessage 0x111, %MenuID%,,, ahk_id %g_hWnd%
Return

LoadProcessProperties:
    Gui Spy: Submit, NoHide

    WinGet PID, PID, ahk_id %g_hWnd%

    StrQuery := "SELECT * FROM Win32_Process WHERE ProcessId=" . PID
    Enum := ComObjGet("winmgmts:").ExecQuery(StrQuery)._NewEnum
    If (Enum[Process]) {
        ExePath := Process.ExecutablePath

        hIcon := GetFileIcon(ExePath, 0)
        If (!hIcon || ExePath == "") {
            hIcon := DllCall("LoadIcon", "Ptr", 0, "UInt", 32512, "Ptr") ; IDI_APPLICATION
        }

        GuiControl,, ProgIcon, % "HICON:" . hIcon
        GuiControl,, ProgName, % Process.Name
        FileGetVersion ProgVer, %ExePath%
        GuiControl,, ProgVer, %ProgVer%

        Gui ListView, %hProcInfo%
        LV_Delete()
        LV_Add("", "Path", ExePath)
        LV_Add("", "Command line", Process.CommandLine)
        LV_Add("", "Process ID", Process.ProcessId)
        LV_Add("", "Thread ID", DllCall("GetWindowThreadProcessId", "Ptr", g_hWnd, "Ptr", 0))
        CreationDate := Process.CreationDate
        SubStr(CreationDate, 1, InStr(CreationDate, ".") - 1)
        FormatTime CreationDate, %CreationDate% D1 T0 ; Short date and time with seconds
        LV_Add("", "Started", CreationDate)
        LV_Add("", "Working Size", FormatBytes(Process.WorkingSetSize, Sep))
        LV_Add("", "Virtual Size", FormatBytes(Process.VirtualSize, Sep))
        LV_Add("", "Image Type", GetImageType(PID))
    }
Return

GetImageType(PID) {
    ; PROCESS_QUERY_INFORMATION
    hProc := DllCall("OpenProcess", "UInt", 0x400, "Int", False, "UInt", PID, "Ptr")
    If (!hProc) {
        Return "N/A"
    }

    If (A_Is64bitOS) {
        ; Determines whether the specified process is running under WOW64.
        Try DllCall("IsWow64Process", "Ptr", hProc, "Int*", Is32Bit := True)
    } Else {
        Is32Bit := True
    }

    DllCall("CloseHandle", "Ptr", hProc)

    Return (Is32Bit) ? "32-bit" : "64-bit"
}

EndProcess:
    GuiControlGet Filename,, ProgName
    If (Filename == "N/A") {
        Return
    }

    Gui Spy: +OwnDialogs
    MsgBox 0x40031, %AppName%, Are you sure you want to exit %Filename%?
    IfMsgBox OK, {
        WinGet PID, PID, ahk_id %g_hWnd%
        Process Close, %PID%
        If (ErrorLevel == 0) {
            Gui Spy: +OwnDialogs
            MsgBox 0x40010, Error, The process named %Filename% with PID %PID% could not be ended.
        }
    }
Return

OpenFolder:
    Gui ListView, %hProcInfo%
    LV_GetText(ExePath, 1, 2)
    If (ExePath != "") {
        Run *open explorer.exe /select`,"%ExePath%"
    }
Return

FormatBytes(Value, sThousand := ".", Unit := -1, ShowUnit := True) {
    If ((Unit == -1 && Value > 999) || Unit == "K") {
        Value /= 1024
        Unit := ShowUnit ? " K" : ""
    } Else {
        Unit := ShowUnit ? " B" : ""
    }

    a := ""
    Loop % StrLen(Value) {
        a .= SubStr(Value, 1 - A_Index, 1)
        If (Mod(A_Index, 3) == 0) {
            a .= sThousand
        }
    }

    a := RTrim(a, sThousand)

    b := ""
    Loop % StrLen(a) {
        b .= SubStr(a, 1 - A_Index, 1)
    }

    Return b . Unit
}

; http://ahkscript.org/boards/viewtopic.php?t=1079
AutoXYWH(DimSize, cList*) {
    Static cInfo := {}

    If (DimSize = "reset") {
        Return cInfo := {}
    }

    For i, ctrl in cList {
        ctrlID := A_Gui ":" ctrl
        If (cInfo[ctrlID].x = "") {
            GuiControlGet i, %A_Gui%: Pos, %ctrl%
            MMD := InStr(DimSize, "*") ? "MoveDraw" : "Move"
            fx := fy := fw := fh := 0
            For i, dim in (a := StrSplit(RegExReplace(DimSize, "i)[^xywh]"))) {
                If (!RegExMatch(DimSize, "i)" . dim . "\s*\K[\d.-]+", f%dim%)) {
                    f%dim% := 1
                }
            }
            cInfo[ctrlID] := {x: ix, fx: fx, y: iy, fy: fy, w: iw, fw: fw, h: ih, fh: fh, gw: A_GuiWidth, gh: A_GuiHeight, a: a, m: MMD}
        } Else If (cInfo[ctrlID].a.1) {
            dgx := dgw := A_GuiWidth - cInfo[ctrlID].gw, dgy := dgh := A_GuiHeight - cInfo[ctrlID].gh
            Options := ""
            For i, dim in cInfo[ctrlID]["a"] {
                Options .= dim . (dg%dim% * cInfo[ctrlID]["f" . dim] + cInfo[ctrlID][dim]) . A_Space
            }
            GuiControl, % A_Gui ":" cInfo[ctrlID].m, % ctrl, % Options
        }
    }
}

IsBorder(hWnd) {
    Loop % g_Borders.Length() {
        If (g_Borders[A_Index] == hWnd) {
            Return True
        }
    }
    Return False
}

IsNumber(n) {
    If n Is Number
        Return True
    Return False
}

ShowHelp:
Gui Spy: +OwnDialogs
MsgBox 0x40, %AppName% Keyboard Shortcuts, 
(
F2:  Go to the parent window
F3:  Show the Find dialog
F4:  Show the hierarchical window tree
F5:  Reload window information
F6:  Highlight window location
F7:  Position and Size dialog
F8:  Copy information to the clipboard
F9:  Copy screenshot to the clipboard
)
Return