Window Spy v2; can ControlGetPos be affected by coordmode?

Discuss the future of the AutoHotkey language
User avatar
Maestr0
Posts: 81
Joined: 05 Dec 2013, 17:43

Window Spy v2; can ControlGetPos be affected by coordmode?

22 May 2019, 03:16

I've updated windowspy.ahk to v2 code (as of 2.0-a100-52515e2)

I managed to tackle most issues, as far as I'm aware, there is only one left:
v2's ControlGetPos uses as default the coordinates relative to the upper-left corner of the target window's client area, whereas v1's default is relative to the target window's upper-left corner.

Feedback welcomed.

Code: Select all

;
; Window Spy v2
;

	#NoTrayIcon
	#SingleInstance ignore
	SetWorkingDir A_ScriptDir
	CoordMode("Pixel", "Screen")
	CoordMode("Mouse", "Window") ; in v2 by default all built-in functions except those documented otherwise (e.g. WinMove and InputBox) use coordinates that are relative to the active window's client area, whereas in v1 the default is to use coordinates that are relative to the active window.

	txtNotFrozen := "(Hold Ctrl or Shift to suspend updates)"
	txtFrozen := "(Updates suspended)"
	txtMouseCtrl := "Control Under Mouse Position"
	txtFocusCtrl := "Focused Control"

	Gui := GuiCreate("AlwaysOnTop Resize MinSize", "Window Spy v2")
	hGui := Gui.Hwnd
	Gui.Add("Text",,"Window Title, Class and Process:")
	gui_Ctrl_FollowMouse := Gui.Add("Checkbox", "yp xp+200 w120 Right vgui_Ctrl_FollowMouse", "Follow Mouse")
	gui_Ctrl_Title := Gui.Add("Edit", "xm w320 r4 ReadOnly -Wrap vgui_Ctrl_Title")
	Gui.Add("Text",,"Mouse Position:")
	gui_Ctrl_MousePos := Gui.Add("Edit", "w320 r4 ReadOnly vgui_Ctrl_MousePos")
	gui_Ctrl_CtrlLabel := Gui.Add("Text", "w320 vgui_Ctrl_CtrlLabel", txtFocusCtrl ":")
	gui_Ctrl_Ctrl := Gui.Add("Edit", "w320 r4 ReadOnly vgui_Ctrl_Ctrl")
	Gui.Add("Text",, "Active Window Position:")
	gui_Ctrl_Pos := Gui.Add("Edit", "w320 r2 ReadOnly vgui_Ctrl_Pos")
	Gui.Add("Text",, "Status Bar Text:")
	gui_Ctrl_SBText := Gui.Add("Edit", "w320 r2 ReadOnly vgui_Ctrl_SBText")
	gui_Ctrl_IsSlow := Gui.Add("Checkbox", "vgui_Ctrl_IsSlow", "Slow TitleMatchMode")
	Gui.Add("Text",, "Visible Text:")
	gui_Ctrl_VisText := Gui.Add("Edit", "w320 r2 ReadOnly vgui_Ctrl_VisText")
	Gui.Add("Text",, "All Text:")
	gui_Ctrl_AllText := Gui.Add("Edit", "w320 r2 ReadOnly vgui_Ctrl_AllText")
	gui_Ctrl_Freeze := Gui.Add("Text", "w320 r1 vgui_Ctrl_Freeze",txtNotFrozen)
	Gui.OnEvent("Size", "Gui_Size")
	Gui.OnEvent("Close", "ExitApp")
	Gui.Show("NA")
	GetClientSize(hGui, temp)
	horzMargin := temp*96//A_ScreenDPI - 320
	SetTimer("Update", 250)
return

Gui_Size(GuiObj, MinMax, Width, Height) {
	global horzMargin
	if !horzMargin
		return
	SetTimer("Update", A_EventInfo=1 ? "Off" : "On") ; Suspend on minimize
	ctrlW := Width - horzMargin
	list := "gui_Ctrl_Title,gui_Ctrl_MousePos,gui_Ctrl_Ctrl,gui_Ctrl_Pos,gui_Ctrl_SBText,gui_Ctrl_VisText,gui_Ctrl_AllText,gui_Ctrl_Freeze"
	Loop Parse list, ","
		GuiObj.Control[A_LoopField].Move("w" ctrlW)
}

Update() {
	global
	Ctrl_FollowMouse := Gui.Control["gui_Ctrl_FollowMouse"].Value ; gui_Ctrl_FollowMouse.Value
	CoordMode("Mouse", "Screen")

	MouseGetPos(msX, msY, msWin, msCtrl)
	actWin := WinExist("A")
	if (Ctrl_FollowMouse)
	{
		curWin := msWin
		curCtrl := msCtrl
		WinExist("ahk_id " curWin)
	}
	else
	{
		curWin := actWin
		curCtrl := ControlGetClassNN(ControlGetFocus()) ; in v2 ControlGetFocus returns the HWND as opposed to v1, which returns the identifier of the control, which consists of its classname followed by its sequence number within its parent window, e.g. Button12
	}
	t1 := WinGetTitle()
	t2 := WinGetClass()
	if (curWin = hGui || t2 = "MultitaskingViewFrame") ; Our Gui || Alt-tab
	{
		UpdateText("gui_Ctrl_Freeze", txtFrozen)
		return
	}
	UpdateText("gui_Ctrl_Freeze", txtNotFrozen)
	t3 := WinGetProcessName()
	t4 := WinGetPID()
	UpdateText("gui_Ctrl_Title", 
		t1 "`n"
		. "ahk_class " t2 "`n"
		. "ahk_exe " t3 "`n"
		. "ahk_pid " t4)
	CoordMode("Mouse", "Window")
	MouseGetPos(mrX, mrY)
	CoordMode("Mouse", "Client")
	MouseGetPos(mcX, mcY)
	mClr := PixelGetColor(msX,msY)
	mClr := SubStr(mClr, 3)
	UpdateText("gui_Ctrl_MousePos", 
		"Screen:" A_Tab msX 	", " A_Tab msY A_Tab " (less often used)`n" 
		. "Window:" A_Tab mrX 	", " A_Tab mrY A_Tab " (default)`n"
		. "Client:" A_Tab mcX 	", " A_Tab mcY A_Tab " (recommended)`n"
		. "Color:" A_Tab mClr A_Tab A_Tab " (Red=" SubStr(mClr, 1, 2) " Green=" SubStr(mClr, 3, 2) " Blue=" SubStr(mClr, 5) ")")
	UpdateText("gui_Ctrl_CtrlLabel", (Ctrl_FollowMouse ? txtMouseCtrl : txtFocusCtrl) ":")
	if (curCtrl)
	{
		ctrlTxt := ControlGetText(curCtrl)
		cText := "ClassNN:" A_Tab curCtrl "`nText:" A_Tab textMangle(ctrlTxt)
		; v2's ControlGetPos uses as default the coordinates relative to the upper-left corner of the target window's client area, whereas v1's default is relative to the target window's upper-left corner 
		ControlGetPos(cX, cY, cW, cH, curCtrl)
		cText .= "`n" A_Tab "x: " cX A_Tab "y: " cY A_Tab "w: " cW A_Tab "h: " cH
		WinToClient(curWin, cX, cY)
		curCtrlHwnd := ControlGetHwnd(curCtrl)
		GetClientSize(curCtrlHwnd, cW, cH)
		cText .= "`nClient:" A_Tab "x: " cX A_Tab "y: " cY A_Tab "w: " cW A_Tab "h: " cH
	}
	else
		cText := ""
	UpdateText("gui_Ctrl_Ctrl", cText)
	WinGetPos(wX, wY, wW, wH)
	GetClientSize(curWin, wcW, wcH)
	UpdateText("gui_Ctrl_Pos", 
		A_Tab "x: " wX A_Tab "y: " wY A_Tab "w: " wW A_Tab "h: " wH "`n"
		. "Client:" A_Tab "x: 0" A_Tab "y: 0" A_Tab "w: " wcW A_Tab "h: " wcH)
	sbTxt := ""
	Loop
	{
		ovi := StatusBarGetText(A_Index)
		if !ovi
			break
		sbTxt .= "(" A_Index "):`t" textMangle(ovi) "`n"
	}
	sbTxt := SubStr(sbTxt,1,StrLen(sbTxt)-1)
	UpdateText("gui_Ctrl_SBText", sbTxt)
	bSlow := Gui.Control["gui_Ctrl_IsSlow"].Value ; gui_Ctrl_IsSlow.Value
	if (bSlow)
	{
		DetectHiddenText("Off")
		ovVisText := WinGetText()
		DetectHiddenText("On")
		ovAllText := WinGetText()
	}
	else
	{
		ovVisText := WinGetTextFast(false)
		ovAllText := WinGetTextFast(true)
	}
	UpdateText("gui_Ctrl_VisText", ovVisText)
	UpdateText("gui_Ctrl_AllText", ovAllText)
}

WinGetTextFast(detect_hidden)
{
	; WinGetText ALWAYS uses the "fast" mode - TitleMatchMode only affects
	; WinText/ExcludeText parameters.  In Slow mode, GetWindowText() is used
	; to retrieve the text of each control.
	controls := WinGetControlsHwnd()
	static WINDOW_TEXT_SIZE := 32767 ; Defined in AutoHotkey source.
	VarSetCapacity(buf, WINDOW_TEXT_SIZE * (A_IsUnicode ? 2 : 1))
	text := ""
	try	; is there a better way to prevent the "No object to invoke" error?
	{
		Loop Controls.Count() ; v2's "WinGetControlsHwnd" returns an array, whereas v1's "WinGet, ControlListHwnd" would return a `n delimited string
		{
			if !detect_hidden && !DllCall("IsWindowVisible", "ptr", Controls[A_Index])
				continue
			if !DllCall("GetWindowText", "ptr", Controls[A_Index], "str", buf, "int", WINDOW_TEXT_SIZE)
				continue
			text .= buf "`r`n"
		}
	}
	return text
}

UpdateText(ControlID, NewText)
{
	; 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).
	static OldText := {}
	global Gui
	if (OldText[ControlID] != NewText)
	{
		Gui.Control[ControlID].Value := NewText
		OldText[ControlID] := NewText
	}
}

GetClientSize(hWnd, ByRef w := "", ByRef h := "")
{
	VarSetCapacity(rect, 16)
	DllCall("GetClientRect", "ptr", hWnd, "ptr", &rect)
	w := NumGet(rect, 8, "int")
	h := NumGet(rect, 12, "int")
}

WinToClient(hWnd, ByRef x, ByRef y)
{
    WinGetPos(wX, wY,,, "ahk_id " hWnd)
    x += wX
	y += wY
    VarSetCapacity(pt, 8), NumPut(y, NumPut(x, pt, "int"), "int")
    if !DllCall("ScreenToClient", "ptr", hWnd, "ptr", &pt)
        return false
    x := NumGet(pt, 0, "int"), y := NumGet(pt, 4, "int")
    return true
}

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

~*Ctrl::
~*Shift::
	SetTimer("Update", "Off")
	UpdateText("gui_Ctrl_Freeze", txtFrozen)
return

~*Ctrl up::
~*Shift up::
	SetTimer("Update", "On")
return
Attachments
winspy2.jpg
winspy2.jpg (84.02 KiB) Viewed 367 times
Last edited by Maestr0 on 22 May 2019, 03:48, edited 2 times in total.
User avatar
Maestr0
Posts: 81
Joined: 05 Dec 2013, 17:43

Re: Window Spy v2; can ControlGetPos be affected by coordmode?

22 May 2019, 03:23

Oops, just noticed I didn't do the resizing part. I'll do that now and update the top post. (update: done)
User avatar
Maestr0
Posts: 81
Joined: 05 Dec 2013, 17:43

Re: Window Spy v2; can ControlGetPos be affected by coordmode?

22 May 2019, 03:47

I just realized that people would want to copy the Window Title, Class and Process information, so I'll remove the tabs and colons from that edit box.
User avatar
Maestr0
Posts: 81
Joined: 05 Dec 2013, 17:43

Re: Window Spy v2; can ControlGetPos be affected by coordmode?

22 May 2019, 04:14

by clicking on the Window Title text element or the edit box, the script below (not the one in the top post) will automatically add to clipboard the correct text for scripts

update: now with tooltip so the user knows they can left-click

Code: Select all

;
; Window Spy v2
;

	#NoTrayIcon
	#SingleInstance ignore
	SetWorkingDir A_ScriptDir
	CoordMode("Pixel", "Screen")
	CoordMode("Mouse", "Window") ; in v2 by default all built-in functions except those documented otherwise (e.g. WinMove and InputBox) use coordinates that are relative to the active window's client area, whereas in v1 the default is to use coordinates that are relative to the active window.

	txtNotFrozen := "(Hold Ctrl or Shift to suspend updates)"
	txtFrozen := "(Updates suspended)"
	txtMouseCtrl := "Control Under Mouse Position"
	txtFocusCtrl := "Focused Control"

	Gui := GuiCreate("AlwaysOnTop Resize MinSize", "Window Spy v2")
	hGui := Gui.Hwnd
	Gui.Add("Text",,"Window Title, Class and Process:").OnEvent("Click",() => copy("gui_Ctrl_Title"))
	gui_Ctrl_FollowMouse := Gui.Add("Checkbox", "yp xp+200 w120 Right vgui_Ctrl_FollowMouse", "Follow Mouse")
	gui_Ctrl_Title := Gui.Add("Edit", "xm w320 r4 ReadOnly -Wrap vgui_Ctrl_Title")
	gui_Ctrl_Title.OnEvent("Focus",() => copy("gui_Ctrl_Title"))
	Gui.Add("Text",,"Mouse Position:")
	gui_Ctrl_MousePos := Gui.Add("Edit", "w320 r4 ReadOnly vgui_Ctrl_MousePos")
	gui_Ctrl_CtrlLabel := Gui.Add("Text", "w320 vgui_Ctrl_CtrlLabel", txtFocusCtrl ":")
	gui_Ctrl_Ctrl := Gui.Add("Edit", "w320 r4 ReadOnly vgui_Ctrl_Ctrl")
	Gui.Add("Text",, "Active Window Position:")
	gui_Ctrl_Pos := Gui.Add("Edit", "w320 r2 ReadOnly vgui_Ctrl_Pos")
	Gui.Add("Text",, "Status Bar Text:")
	gui_Ctrl_SBText := Gui.Add("Edit", "w320 r2 ReadOnly vgui_Ctrl_SBText")
	gui_Ctrl_IsSlow := Gui.Add("Checkbox", "vgui_Ctrl_IsSlow", "Slow TitleMatchMode")
	Gui.Add("Text",, "Visible Text:")
	gui_Ctrl_VisText := Gui.Add("Edit", "w320 r2 ReadOnly vgui_Ctrl_VisText")
	Gui.Add("Text",, "All Text:")
	gui_Ctrl_AllText := Gui.Add("Edit", "w320 r2 ReadOnly vgui_Ctrl_AllText")
	gui_Ctrl_Freeze := Gui.Add("Text", "w320 r1 vgui_Ctrl_Freeze",txtNotFrozen)
	Gui.OnEvent("Size", "Gui_Size")
	Gui.OnEvent("Close", "ExitApp")
	Gui.Show("NA")
	GetClientSize(hGui, temp)
	horzMargin := temp*96//A_ScreenDPI - 320
	SetTimer("Update", 250)
	OnMessage(0x200, "Help")

return

Help(wParam, lParam, Msg) {
	MouseGetPos(msX, msY, msWin, msCtrl)
	if ( msCtrl = "Edit1" ) or ( msCtrl = "Static1" )
		ToolTip("Left-click to copy Window Title, Class and Process information to clipboard.")
	else
		Tooltip()
}

copy(GUIctrl) {
	global
	if GUIctrl = "gui_Ctrl_Title"
		clipboard := text_title_class_process
	else
		clipboard := Gui.Control[GUIctrl].Value
	ToolTip("Window Title, Class and Process information copied to clipboard.")
	SetTimer () => ToolTip(), -3000
}

Gui_Size(GuiObj, MinMax, Width, Height) {
	global horzMargin
	if !horzMargin
		return
	SetTimer("Update", A_EventInfo=1 ? "Off" : "On") ; Suspend on minimize
	ctrlW := Width - horzMargin
	list := "gui_Ctrl_Title,gui_Ctrl_MousePos,gui_Ctrl_Ctrl,gui_Ctrl_Pos,gui_Ctrl_SBText,gui_Ctrl_VisText,gui_Ctrl_AllText,gui_Ctrl_Freeze"
	Loop Parse list, ","
		GuiObj.Control[A_LoopField].Move("w" ctrlW)
}

Update() {
	global
	Ctrl_FollowMouse := Gui.Control["gui_Ctrl_FollowMouse"].Value ; gui_Ctrl_FollowMouse.Value
	CoordMode("Mouse", "Screen")

	MouseGetPos(msX, msY, msWin, msCtrl)
	actWin := WinExist("A")
	if (Ctrl_FollowMouse)
	{
		curWin := msWin
		curCtrl := msCtrl
		WinExist("ahk_id " curWin)
	}
	else
	{
		curWin := actWin
		curCtrl := ControlGetClassNN(ControlGetFocus()) ; in v2 ControlGetFocus returns the HWND as opposed to v1, which returns the identifier of the control, which consists of its classname followed by its sequence number within its parent window, e.g. Button12
	}
	t1 := WinGetTitle()
	t2 := WinGetClass()
	if (curWin = hGui || t2 = "MultitaskingViewFrame") ; Our Gui || Alt-tab
	{
		UpdateText("gui_Ctrl_Freeze", txtFrozen)
		return
	}
	UpdateText("gui_Ctrl_Freeze", txtNotFrozen)
	t3 := WinGetProcessName()
	t4 := WinGetPID()
	text_title_class_process := t1 "`nahk_class " t2 "`nahk_exe " t3 "`nahk_pid " t4
	UpdateText("gui_Ctrl_Title", "wintitle: " A_Tab A_Tab t1 "`n"
		. "ahk_class: " A_Tab t2 "`n"
		. "ahk_exe: " A_Tab A_Tab t3 "`n"
		. "ahk_pid: " A_Tab A_Tab t4)
	CoordMode("Mouse", "Window")
	MouseGetPos(mrX, mrY)
	CoordMode("Mouse", "Client")
	MouseGetPos(mcX, mcY)
	mClr := PixelGetColor(msX,msY)
	mClr := SubStr(mClr, 3)
	UpdateText("gui_Ctrl_MousePos", 
		"Screen:" A_Tab msX 	", " A_Tab msY A_Tab " (less often used)`n" 
		. "Window:" A_Tab mrX 	", " A_Tab mrY A_Tab " (default)`n"
		. "Client:" A_Tab mcX 	", " A_Tab mcY A_Tab " (recommended)`n"
		. "Color:" A_Tab mClr A_Tab A_Tab " (Red=" SubStr(mClr, 1, 2) " Green=" SubStr(mClr, 3, 2) " Blue=" SubStr(mClr, 5) ")")
	UpdateText("gui_Ctrl_CtrlLabel", (Ctrl_FollowMouse ? txtMouseCtrl : txtFocusCtrl) ":")
	if (curCtrl)
	{
		ctrlTxt := ControlGetText(curCtrl)
		cText := "ClassNN:" A_Tab curCtrl "`nText:" A_Tab textMangle(ctrlTxt)
		; v2's ControlGetPos uses as default the coordinates relative to the upper-left corner of the target window's client area, whereas v1's default is relative to the target window's upper-left corner 
		ControlGetPos(cX, cY, cW, cH, curCtrl)
		cText .= "`n" A_Tab "x: " cX A_Tab "y: " cY A_Tab "w: " cW A_Tab "h: " cH
		WinToClient(curWin, cX, cY)
		curCtrlHwnd := ControlGetHwnd(curCtrl)
		GetClientSize(curCtrlHwnd, cW, cH)
		cText .= "`nClient:" A_Tab "x: " cX A_Tab "y: " cY A_Tab "w: " cW A_Tab "h: " cH
	}
	else
		cText := ""
	UpdateText("gui_Ctrl_Ctrl", cText)
	WinGetPos(wX, wY, wW, wH)
	GetClientSize(curWin, wcW, wcH)
	UpdateText("gui_Ctrl_Pos", 
		A_Tab "x: " wX A_Tab "y: " wY A_Tab "w: " wW A_Tab "h: " wH "`n"
		. "Client:" A_Tab "x: 0" A_Tab "y: 0" A_Tab "w: " wcW A_Tab "h: " wcH)
	sbTxt := ""
	Loop
	{
		ovi := StatusBarGetText(A_Index)
		if !ovi
			break
		sbTxt .= "(" A_Index "):`t" textMangle(ovi) "`n"
	}
	sbTxt := SubStr(sbTxt,1,StrLen(sbTxt)-1)
	UpdateText("gui_Ctrl_SBText", sbTxt)
	bSlow := Gui.Control["gui_Ctrl_IsSlow"].Value ; gui_Ctrl_IsSlow.Value
	if (bSlow)
	{
		DetectHiddenText("Off")
		ovVisText := WinGetText()
		DetectHiddenText("On")
		ovAllText := WinGetText()
	}
	else
	{
		ovVisText := WinGetTextFast(false)
		ovAllText := WinGetTextFast(true)
	}
	UpdateText("gui_Ctrl_VisText", ovVisText)
	UpdateText("gui_Ctrl_AllText", ovAllText)
}

WinGetTextFast(detect_hidden)
{
	; WinGetText ALWAYS uses the "fast" mode - TitleMatchMode only affects
	; WinText/ExcludeText parameters.  In Slow mode, GetWindowText() is used
	; to retrieve the text of each control.
	controls := WinGetControlsHwnd()
	static WINDOW_TEXT_SIZE := 32767 ; Defined in AutoHotkey source.
	VarSetCapacity(buf, WINDOW_TEXT_SIZE * (A_IsUnicode ? 2 : 1))
	text := ""
	try	; is there a better way to prevent the "No object to invoke" error?
	{
		Loop Controls.Count() ; v2's "WinGetControlsHwnd" returns an array, whereas v1's "WinGet, ControlListHwnd" would return a `n delimited string
		{
			if !detect_hidden && !DllCall("IsWindowVisible", "ptr", Controls[A_Index])
				continue
			if !DllCall("GetWindowText", "ptr", Controls[A_Index], "str", buf, "int", WINDOW_TEXT_SIZE)
				continue
			text .= buf "`r`n"
		}
	}
	return text
}

UpdateText(ControlID, NewText)
{
	; 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).
	static OldText := {}
	global Gui
	if (OldText[ControlID] != NewText)
	{
		Gui.Control[ControlID].Value := NewText
		OldText[ControlID] := NewText
	}
}

GetClientSize(hWnd, ByRef w := "", ByRef h := "")
{
	VarSetCapacity(rect, 16)
	DllCall("GetClientRect", "ptr", hWnd, "ptr", &rect)
	w := NumGet(rect, 8, "int")
	h := NumGet(rect, 12, "int")
}

WinToClient(hWnd, ByRef x, ByRef y)
{
    WinGetPos(wX, wY,,, "ahk_id " hWnd)
    x += wX
	y += wY
    VarSetCapacity(pt, 8), NumPut(y, NumPut(x, pt, "int"), "int")
    if !DllCall("ScreenToClient", "ptr", hWnd, "ptr", &pt)
        return false
    x := NumGet(pt, 0, "int"), y := NumGet(pt, 4, "int")
    return true
}

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

~*Ctrl::
~*Shift::
	SetTimer("Update", "Off")
	UpdateText("gui_Ctrl_Freeze", txtFrozen)
return

~*Ctrl up::
~*Shift up::
	SetTimer("Update", "On")
return

Return to “AutoHotkey v2 Development”

Who is online

Users browsing this forum: No registered users and 10 guests