Window Spy for AHKv2 in Dark Mode

Post your working scripts, libraries and tools.
User avatar
NPerovic
Posts: 39
Joined: 31 Dec 2022, 01:25
Contact:

Window Spy for AHKv2 in Dark Mode

02 Oct 2023, 02:31

280425630-3651ab4e-67df-4fff-81c5-68a3e9d5bca4.gif
280425630-3651ab4e-67df-4fff-81c5-68a3e9d5bca4.gif (1.34 MiB) Viewed 428 times
Additional Features
  • Resizable window: Hide what you don’t need and save screen space.
  • Easy copy: Right-click to copy texts. (for window title, classes and process, it will merges multiple lines into one.)
  • Customizable: Change the font, size or wrap long lines to suit your preference.
  • Eye-friendly: Dark mode is easier on the eyes than the default light mode.
How to Use
Save WindowSpy.ahk in C:\Users\<UserName>\Documents\AutoHotkey. Then you can run the Dark Mode WindowSpy from any script’s TrayIcon.

Releases
On GitHub

Code: Select all

/************************************************************************
 * @description Window Spy for AHKv2 in dark mode. [https://github.com/nperovic/Dark_WindowSpy]
 * @file WindowSpy.ahk
 * @author nperovic
 * @date 2023/11/04
 * @version 1.0.1
 ***********************************************************************/

#Requires AutoHotkey v2
#SingleInstance Ignore

try TraySetIcon RegExReplace(A_AhkPath, "iS)^.+?AutoHotkey[^\\]*\\\K.+", "UX\inc\spy.ico")
A_IconHidden := true

SetWorkingDir(A_ScriptDir)
CoordMode("Pixel", "Screen")

WinSpyGui(9, "Segoe UI", false)

~*Shift::
~*Ctrl:: suspend_timer()
~*Ctrl up::
~*Shift up:: SetTimer(Update)

WinSpyGui(fontSize := 11, font := "Segoe UI", Wrap := true)
{
	global oGui
	static WM_RBUTTONDOWN := 0x0204
		 , WM_RBUTTONUP   := 0x0205
		 , SM_CXMENUCHECK := 71
		 , SM_CYMENUCHECK := 72
		 , checkBoxW      := SysGet(SM_CXMENUCHECK)
		 , checkBoxH      := SysGet(SM_CYMENUCHECK)
		 
	DllCall("shell32\SetCurrentProcessExplicitAppUserModelID", "ptr", StrPtr("AutoHotkey.WindowSpy"))

	oGui           := Gui("AlwaysOnTop Resize MinSize MinSizex1 DPIScale", "Window Spy for AHKv2")
	oGui.MarginX   := 10
	oGui.MarginY   := 5
	oGui.BackColor := "1F1F1F"
	oGui.SetFont("cF8F8F8 S" fontSize, font)
	SetDarkMode(oGui)

	oGui.Add("Text", "0x200 h" checkBoxH, "Window Title, Class and Process:").GetPos(,, &tW)
	oGui.AddText("x+m yp Right 0x200 HP W" (320-tW-checkBoxW+oGui.MarginX) , "Follow Mouse")
	oGui.AddCheckBox("x+0 yp Right Checked vCtrl_FollowMouse HP W" checkBoxW).OnEvent("Click", Checkbox_Focus)
	
	oGui.Add("Edit", "xm w320 r5 ReadOnly vCtrl_Title" (Wrap ? "" : " -Wrap"))
	
	oGui.Add("Text", , "Mouse Position")
	oGui.Add("Edit", "w320 r4 ReadOnly vCtrl_MousePos")
	
	oGui.Add("Text", "w320 vCtrl_CtrlLabel", (txtFocusCtrl := "Focused Control") ":")
	oGui.Add("Edit", "w320 r4 ReadOnly vCtrl_Ctrl")
	
	oGui.Add("Text", , "Active Window Postition:")
	oGui.Add("Edit", "w320 r2 ReadOnly vCtrl_Pos")
	
	oGui.Add("Text", , "Status Bar Text:")
	oGui.Add("Edit", "w320 r2 ReadOnly vCtrl_SBText")
	
	oGui.Add("Checkbox", "xm r1 vCtrl_IsSlow W" checkBoxW " H" checkBoxH,).OnEvent("Click", Checkbox_Focus)
	oGui.Add("Text", "x+0 yp HP", "Slow TitleMatchMode")

	oGui.Add("Text", "xm", "Visible Text:")
	oGui.Add("Edit", "w320 r2 ReadOnly vCtrl_VisText")

	oGui.Add("Text", , "All Text:")
	oGui.Add("Edit", "w320 r2 ReadOnly vCtrl_AllText")

	oGui.Add("Text", "w320 r1 vCtrl_Freeze", (txtNotFrozen := "(Hold Ctrl or Shift to suspend updates)"))

	oGui.OnEvent("Close", WinSpyClose)
	oGui.OnEvent("Size", WinSpySize)
	OnMessage(WM_RBUTTONUP, Right_Click_Event)

	for ctrl in ["Follow Mouse", "Slow TitleMatchMode"]
		for event in ["Click", "DoubleClick"]
			oGui[ctrl].OnEvent(event, ToggleCheck)

	for ctrl in oGui
	{
		if ctrl Is Gui.Edit
			ctrl.Opt("cF8F8F8 Background" oGui.BackColor)
		ctrl.SetFont("cF8F8F8")
		SetDarkControl(ctrl)
	}

	oGui.Show("Hide AutoSize")
	oGui.GetClientPos(, , &GuiWidth)
	oGui["Ctrl_FollowMouse"].GetPos(&x_ChBx_FollowMouse)
	oGui["Follow Mouse"].GetPos(&x_Text_FollowMouse)

	oGui.CtrlDistance := Map(
		"Ctrl_FollowMouse", GuiWidth - x_ChBx_FollowMouse,
		"Follow Mouse"    , GuiWidth - x_Text_FollowMouse)

	oGui.txtNotFrozen := txtNotFrozen       ; create properties for futur use
	oGui.txtFrozen    := "(Updates suspended)"
	oGui.txtMouseCtrl := "Control Under Mouse Position"
	oGui.txtFocusCtrl := txtFocusCtrl

	oGui.GetClientPos(, , &Width, &Height)
	WinSpySize(oGui, 0, Width, Height)
	SetTimer(Update, 250)
	oGui.Show("NoActivate AutoSize")

	return oGui
}

ToggleCheck(GuiCtrlObj, p*)
{
	static checkBoxName := Map("Follow Mouse", "Ctrl_FollowMouse", "Slow TitleMatchMode", "Ctrl_IsSlow")

	CheckBoxCtrl := GuiCtrlObj.Gui[checkBoxName[GuiCtrlObj.Value]]
	ControlClick(CheckBoxCtrl.hwnd, GuiCtrlObj.Gui)
}

Checkbox_Focus(GuiCtrlObj, Info) {
	SendMessage(0x8,,, GuiCtrlObj.hwnd, GuiCtrlObj.Gui)
	return 0
}

Right_Click_Event(wParam, lParam, msg, hwnd)
{
	static WM_RBUTTONDOWN := 0x0204
		 , WM_RBUTTONUP   := 0x0205

	GuiCtrl := GuiCtrlFromHwnd(hwnd)

	if (!(GuiCtrl is Gui.Edit) && msg = WM_RBUTTONUP)
		return 0

	A_Clipboard := ""
	
	if (GuiCtrl.Name = "Ctrl_Title")
	   A_Clipboard  := StrReplace(EditGetSelectedText(GuiCtrl, hwnd), "`r`n", "`s")
	else
		ControlSend("^{Ins}", hwnd)

	if ClipWait(1)
	{
		ToolTip("Copied: " A_Clipboard)
		SetTimer(ToolTip, -1000)
	}
	return 0
}

WinSpySize(GuiObj?, MinMax?, Width?, Height?)
{

	Critical("Off")
	Sleep(-1)

	if !GuiObj.HasProp("txtNotFrozen") ; WinSpyGui() not done yet, return until it is
		return

	SetTimer(Update, (MinMax = 0) ? 250 : 0) ; suspend updates on minimize

	ctrlW := Width - (GuiObj.MarginX * 2) ; ctrlW := Width - horzMargin
	list  := "Title,MousePos,Ctrl,Pos,SBText,VisText,AllText,Freeze"

	Loop Parse list, ","
		GuiObj["Ctrl_" A_LoopField].Move(, , ctrlW)

	for CtrlName, Dist in GuiObj.CtrlDistance
	{
		GuiObj[CtrlName].Move(Width - Dist)
		GuiObj[CtrlName].Redraw()
	}
}

WinSpyClose(GuiObj) => ExitApp()

Update(GuiObj?)
{ ; timer, no params
	try TryUpdate(GuiObj?) ; try
}

TryUpdate(GuiObj?)
{
	global oGui
	GuiObj := GuiObj ?? oGui

	if !GuiObj.HasProp("txtNotFrozen") ; WinSpyGui() not done yet, return until it is
		return

	Ctrl_FollowMouse := GuiObj["Ctrl_FollowMouse"].Value
	CoordMode("Mouse", "Screen")
	MouseGetPos(&msX, &msY, &msWin, &msCtrl, 2) ; get ClassNN and hWindow
	actWin := WinExist("A")

	if (Ctrl_FollowMouse)
	{
		curWin := msWin, curCtrl := msCtrl
		WinExist("ahk_id " curWin) ; updating LastWindowFound?
	}
	else
	{
		curWin  := actWin
		curCtrl := ControlGetFocus() ; get focused control hwnd from active win
	}
		curCtrlClassNN := ""
	try curCtrlClassNN := ControlGetClassNN(curCtrl)

	t1 := WinGetTitle(), t2 := WinGetClass()
	if (curWin = GuiObj.hwnd || t2 = "MultitaskingViewFrame")
	{ ; Our Gui || Alt-tab
		UpdateText("Ctrl_Freeze", GuiObj.txtFrozen)
		return
	}

	UpdateText("Ctrl_Freeze", GuiObj.txtNotFrozen)
	t3 := WinGetProcessName(), t4 := WinGetPID()

	WinDataText := t1 "`n" ; ZZZ
		. "ahk_class " t2 "`n"
		. "ahk_exe " t3 "`n"
		. "ahk_pid " t4 "`n"
		. "ahk_id " curWin

	UpdateText("Ctrl_Title", WinDataText)
	CoordMode("Mouse", "Window")
	MouseGetPos(&mrX, &mrY)
	CoordMode("Mouse", "Client")
	MouseGetPos(&mcX, &mcY)
	mClr := PixelGetColor(msX, msY, "RGB")
	mClr := SubStr(mClr, 3)

	mpText := "Screen:`t" msX ", " msY "`n"
		. "Window:`t" mrX ", " mrY "`n"
		. "Client:`t" mcX ", " mcY " (default)`n"
		. "Color :`t" mClr " (Red=" SubStr(mClr, 1, 2) " Green=" SubStr(mClr, 3, 2) " Blue=" SubStr(mClr, 5) ")"

	UpdateText("Ctrl_MousePos", mpText)

	UpdateText("Ctrl_CtrlLabel", (Ctrl_FollowMouse ? GuiObj.txtMouseCtrl : GuiObj.txtFocusCtrl) ":")

	if (curCtrl)
	{
		ctrlTxt := ControlGetText(curCtrl)
		WinGetClientPos(&sX, &sY, &sW, &sH, curCtrl)
		ControlGetPos(&cX, &cY, &cW, &cH, curCtrl)

		cText := "ClassNN:`t" curCtrlClassNN "`n"
			. "Text   :`t" textMangle(ctrlTxt) "`n"
			. "Screen :`tx: " sX "`ty: " sY "`tw: " sW "`th: " sH "`n"
			. "Client :`tx: " cX "`ty: " cY "`tw: " cW "`th: " cH
	}
	else
		cText := ""

	UpdateText("Ctrl_Ctrl", cText)
	wX := "", wY := "", wW := "", wH := ""
	WinGetPos(&wX, &wY, &wW, &wH, "ahk_id " curWin)
	WinGetClientPos(&wcX, &wcY, &wcW, &wcH, "ahk_id " curWin)

	wText := "Screen:`tx: " wX "`ty: " wY "`tw: " wW "`th: " wH "`n"
		. "Client:`tx: " wcX "`ty: " wcY "`tw: " wcW "`th: " wcH

	UpdateText("Ctrl_Pos", wText)
	sbTxt := ""

	Loop
	{
		ovi := ""
		try ovi := StatusBarGetText(A_Index)
		if (ovi = "")
			break
		sbTxt .= "(" A_Index "):`t" textMangle(ovi) "`n"
	}

	sbTxt := SubStr(sbTxt, 1, -1) ; StringTrimRight, sbTxt, sbTxt, 1
	UpdateText("Ctrl_SBText", sbTxt)
	bSlow := GuiObj["Ctrl_IsSlow"].Value ; GuiControlGet, bSlow,, Ctrl_IsSlow

	if (bSlow)
	{
		DetectHiddenText False
		ovVisText := WinGetText() ; WinGetText, ovVisText
		DetectHiddenText True
		ovAllText := WinGetText() ; WinGetText, ovAllText
	}
	else
	{
		ovVisText := WinGetTextFast(false)
		ovAllText := WinGetTextFast(true)
	}

	UpdateText("Ctrl_VisText", ovVisText)
	UpdateText("Ctrl_AllText", ovAllText)
}

; =========================================================================================== 
; WinGetText ALWAYS uses the "slow" mode - TitleMatchMode only affects
; WinText/ExcludeText parameters. In "fast" mode, GetWindowText() is used
; to retrieve the text of each control.
; =========================================================================================== 
WinGetTextFast(detect_hidden)
{
	controls := WinGetControlsHwnd()

	static WINDOW_TEXT_SIZE := 32767 ; Defined in AutoHotkey source.

	buf := Buffer(WINDOW_TEXT_SIZE * 2, 0)

	text := ""

	Loop controls.Length
	{
		hCtl := controls[A_Index]
		if !detect_hidden && !DllCall("IsWindowVisible", "ptr", hCtl)
			continue
		if !DllCall("GetWindowText", "ptr", hCtl, "Ptr", buf.ptr, "int", WINDOW_TEXT_SIZE)
			continue

		text .= StrGet(buf) "`r`n" ; text .= buf "`r`n"
	}
	return text
}

; =========================================================================================== 
; Unlike using a pure GuiControl, this function causes the text of the
; controls to be updated only when the text has changed, preventing periodic
; flickering (especially on older systems).
; =========================================================================================== 
UpdateText(vCtl, NewText)
{
	global oGui
	static OldText := {}
		   ctl     := oGui[vCtl], hCtl := Integer(ctl.hwnd)

	if (!oldText.HasProp(hCtl) Or OldText.%hCtl% != NewText)
	{
		ctl.Value      := NewText
		OldText.%hCtl% := NewText
	}
}

textMangle(x)
{
	elli := false
	if (pos := InStr(x, "`n"))
		x := SubStr(x, 1, pos - 1), elli := true
	else if (StrLen(x) > 40)
		x := SubStr(x, 1, 40), elli := true
	if elli
		x .= " (...)"
	return x
}

suspend_timer()
{
	global oGui
	SetTimer(Update, 0)
	UpdateText("Ctrl_Freeze", oGui.txtFrozen)
}

SetDarkControl(_obj) => DllCall("uxtheme\SetWindowTheme", "ptr", _obj.hwnd, "ptr", StrPtr("DarkMode_Explorer"), "ptr", 0)
	
SetDarkMode(_obj)
{
	For v in [135, 136]
		DllCall(DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "uxtheme", "ptr"), "ptr", v, "ptr"), "int", 2)

	if !(attr := VerCompare(A_OSVersion, "10.0.18985") >= 0 ? 20 : VerCompare(A_OSVersion, "10.0.17763") >= 0 ? 19 : 0)
		return false
	
	DllCall("dwmapi\DwmSetWindowAttribute", "ptr", _obj.hwnd, "int", attr, "int*", true, "int", 4)
}
Last edited by NPerovic on 10 Apr 2024, 15:02, edited 3 times in total.
✨ Dark Theme for Everything
✨ Other Scripts

WKen
Posts: 187
Joined: 21 Feb 2023, 00:01

Re: Window Spy for AHKv2 in Dark Mode

02 Oct 2023, 06:21

Thank you, but I feel that the automatic line wrapping is not friendly and the fonts are a bit big.
User avatar
NPerovic
Posts: 39
Joined: 31 Dec 2022, 01:25
Contact:

Re: Window Spy for AHKv2 in Dark Mode

02 Oct 2023, 07:27

WKen wrote:
02 Oct 2023, 06:21
Thank you, but I feel that the automatic line wrapping is not friendly and the fonts are a bit big.
I've updated the code so you can customise the font size, font and wrap text or not now.

Like this:

Code: Select all

WinSpyGui(9, "Segoe UI", false)
✨ Dark Theme for Everything
✨ Other Scripts

WKen
Posts: 187
Joined: 21 Feb 2023, 00:01

Re: Window Spy for AHKv2 in Dark Mode

02 Oct 2023, 08:09

Thanks!
ozzii
Posts: 482
Joined: 30 Oct 2013, 06:04

Re: Window Spy for AHKv2 in Dark Mode

02 Oct 2023, 08:13

Thank you for my eyes ;)

Return to “Scripts and Functions (v2)”

Who is online

Users browsing this forum: docterry, Google [Bot] and 22 guests