From e22d7025b6475cb094fd1368f42a234e7e929e6b Mon Sep 17 00:00:00 2001 From: Pavel Yosifovich Date: Tue, 26 Mar 2024 12:50:07 -0400 Subject: [PATCH] display some properties of automation objects --- WinSpy/AutomationTreeView.cpp | 159 ++++++++++++++++++++++++++++++---- WinSpy/AutomationTreeView.h | 12 ++- WinSpy/WinSpy.rc | 10 +-- WinSpy/pch.h | 1 + 4 files changed, 158 insertions(+), 24 deletions(-) diff --git a/WinSpy/AutomationTreeView.cpp b/WinSpy/AutomationTreeView.cpp index 611d8f5..8ff632c 100644 --- a/WinSpy/AutomationTreeView.cpp +++ b/WinSpy/AutomationTreeView.cpp @@ -13,7 +13,7 @@ LRESULT CAutomationTreeView::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM / LVS_REPORT | LVS_OWNERDATA | LVS_SINGLESEL); m_List.SetExtendedListViewStyle(LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT); - m_List.InsertColumn(0, L"Property", 0, 150); + m_List.InsertColumn(0, L"Property", 0, 180); m_List.InsertColumn(1, L"Value", 0, 350); m_Tree.SetExtendedStyle(TVS_EX_DOUBLEBUFFER, TVS_EX_DOUBLEBUFFER); @@ -67,6 +67,14 @@ LRESULT CAutomationTreeView::OnNodeExpanded(int, LPNMHDR hdr, BOOL&) { return FALSE; } +LRESULT CAutomationTreeView::OnNodeSelected(int, LPNMHDR hdr, BOOL&) { + auto tv = (NMTREEVIEW*)hdr; + auto hItem = tv->itemNew.hItem; + auto data = (IUIAutomationElement*)m_Tree.GetItemData(tv->itemNew.hItem); + UpdateProperties(data); + return 0; +} + LRESULT CAutomationTreeView::OnNodeDeleted(int, LPNMHDR hdr, BOOL&) { auto tv = (NMTREEVIEW*)hdr; auto data = (IUIAutomationElement*)m_Tree.GetItemData(tv->itemOld.hItem); @@ -83,20 +91,20 @@ void CAutomationTreeView::OnActivate(bool active) { } CString CAutomationTreeView::GetColumnText(HWND, int row, int col) const { - return CString(); + auto& p = m_Properties[row]; + switch (col) { + case 0: return p.Name; + case 1: return p.Value.c_str(); + } + return L""; } HTREEITEM CAutomationTreeView::AddElement(IUIAutomationElement* e, HTREEITEM hParent, HTREEITEM hAfter) { CComBSTR name, cls, id; e->get_CurrentName(&name); e->get_CurrentClassName(&cls); - int pid = 0; - e->get_CurrentProcessId(&pid); - e->get_CurrentAutomationId(&id); - UIA_HWND hWnd = nullptr; - e->get_CurrentNativeWindowHandle(&hWnd); CString text; - text.Format(L"%s [%s] (ID: %s, PID: %d, HWND: 0x%p)", name.m_str, cls.m_str, id.m_str, pid, hWnd); + text.Format(L"%s [%s]", name.m_str, cls.m_str); auto hItem = m_Tree.InsertItem(text, hParent, hAfter); e->AddRef(); @@ -112,7 +120,6 @@ void CAutomationTreeView::EnumChildElements(IUIAutomationTreeWalker* pWalker, IU spElem->get_CurrentProcessId(&pid); if (pid != ::GetCurrentProcessId()) { auto node = AddElement(spElem, hParent, hAfter); - //EnumChildElements(pWalker, spElem, node, TVI_LAST, depth + 1); CComPtr spChild; pWalker->GetFirstChildElement(spElem, &spChild); if (spChild) { @@ -131,21 +138,20 @@ void CAutomationTreeView::EnumChildElements(IUIAutomationTreeWalker* pWalker, IU void CAutomationTreeView::InitTree() { CWaitCursor wait; - CComPtr spUI; - spUI.CoCreateInstance(__uuidof(CUIAutomation)); - ATLASSERT(spUI); - if (spUI == nullptr) - return; - + if (m_spUI == nullptr) { + m_spUI.CoCreateInstance(__uuidof(CUIAutomation)); + if (m_spUI == nullptr) + return; + } m_Tree.SetRedraw(FALSE); m_Tree.DeleteAllItems(); CComPtr spWalker; - spUI->get_RawViewWalker(&spWalker); + m_spUI->get_RawViewWalker(&spWalker); ATLASSERT(spWalker); m_spUIWalker = spWalker; CComPtr spRoot; - spUI->GetRootElement(&spRoot); + m_spUI->GetRootElement(&spRoot); auto node = AddElement(spRoot); EnumChildElements(spWalker, spRoot, node); @@ -155,3 +161,122 @@ void CAutomationTreeView::InitTree() { void CAutomationTreeView::UpdateUI() { } + +void CAutomationTreeView::UpdateProperties(IUIAutomationElement* elem) { + m_Properties.clear(); + if (elem) { + static const struct { + PROPERTYID id; + PCWSTR text; + } props[] = { + { UIA_NamePropertyId, L"Name" }, + { UIA_RuntimeIdPropertyId, L"Runtime ID" }, + { UIA_ClassNamePropertyId, L"Class Name" }, + { UIA_ControlTypePropertyId, L"Control Type" }, + { UIA_LocalizedControlTypePropertyId, L"Localized Control Type" }, + { UIA_FullDescriptionPropertyId, L"Full Description" }, + { UIA_AcceleratorKeyPropertyId, L"Accelerator Key" }, + { UIA_AccessKeyPropertyId, L"Access Key" }, + { UIA_AutomationIdPropertyId, L"Automation ID" }, + { UIA_BoundingRectanglePropertyId, L"Bounding Rectangle" }, + { UIA_CenterPointPropertyId, L"Center Point" }, + { UIA_HasKeyboardFocusPropertyId, L"Has Focus" }, + { UIA_HelpTextPropertyId, L"Help Text" }, + { UIA_ItemStatusPropertyId, L"Item Status" }, + { UIA_NativeWindowHandlePropertyId, L"Window Handle" }, + { UIA_ProcessIdPropertyId, L"Process ID" }, + { UIA_SizePropertyId, L"Size" }, + { UIA_OrientationPropertyId, L"Orientation" }, + { UIA_OutlineColorPropertyId, L"Outline Color" }, + { UIA_FillColorPropertyId, L"Fill Color" }, + { UIA_PositionInSetPropertyId, L"Position in Set" }, + { UIA_OutlineThicknessPropertyId, L"Outline Thickness" }, + { UIA_IsEnabledPropertyId, L"Enabled" }, + { UIA_IsOffscreenPropertyId, L"Off Screen" }, + { UIA_IsPasswordPropertyId, L"Password" }, + { UIA_IsEnabledPropertyId, L"Editable" }, + { UIA_ItemTypePropertyId, L"Item Type" }, + { UIA_SizeOfSetPropertyId, L"Size of Set" }, + { UIA_VisualEffectsPropertyId, L"Visual Effects" }, + { UIA_LiveSettingPropertyId, L"Live Setting" }, + { UIA_LevelPropertyId, L"Level" }, + { UIA_IsPeripheralPropertyId, L"Peripheral" }, + { UIA_IsKeyboardFocusablePropertyId, L"Keyboard Focusable" }, + { UIA_IsDialogPropertyId, L"Dialog" }, + { UIA_IsControlElementPropertyId, L"Control Element" }, + { UIA_FrameworkIdPropertyId, L"Framework ID" }, + }; + + for (auto& p : props) { + CComVariant value; + if (S_OK == elem->GetCurrentPropertyValue(p.id, &value)) { + ItemData data; + data.Value = FormatValue(m_spUI, value); + if (data.Value.empty() && S_OK == value.ChangeType(VT_BSTR)) { + data.Value = value.bstrVal; + } + if (!data.Value.empty()) { + data.Name = p.text; + m_Properties.push_back(std::move(data)); + } + } + } + } + + m_List.SetItemCount((int)m_Properties.size()); +} + +std::wstring CAutomationTreeView::FormatValue(IUIAutomation* pUI, VARIANT const& value) { + switch (value.vt) { + case VT_I4: return std::format(L"{} (0x{:X})", value.intVal, value.intVal); + case VT_UI4: return std::format(L"{} (0x{:X})", value.uintVal, value.uintVal); + case VT_BOOL: return value.boolVal ? L"True" : L"False"; + case VT_I4 | VT_ARRAY: + { + int* data, count; + if (S_OK == pUI->IntSafeArrayToNativeArray(value.parray, &data, &count)) { + std::wstring result(L"("); + for (int i = 0; i < count; i++) { + result += std::format(L"{}", data[i]); + if (i < count - 1) + result += L", "; + } + result += L")"; + return result; + } + break; + } + case VT_R8 | VT_ARRAY: + { + RECT* rc; + int count; + if (S_OK == pUI->SafeArrayToRectNativeArray(value.parray, &rc, &count) && count > 0) { + std::wstring result(L"("); + for (int i = 0; i < count; i++) { + result += std::format(L"[{},{}-{},{}]", rc[i].left, rc[i].top, rc[i].right, rc[i].bottom); + if (i < count - 1) + result += L", "; + } + result += L")"; + return result; + } + else { + double* data; + count = value.parray->rgsabound[0].cElements; + if (S_OK == ::SafeArrayAccessData(value.parray, (void**)&data)) { + std::wstring result(L"("); + for (int i = 0; i < count; i++) { + result += std::format(L"{}", data[i]); + if (i < count - 1) + result += L", "; + } + ::SafeArrayUnaccessData(value.parray); + result += L")"; + return result; + } + } + break; + } + } + return L""; +} diff --git a/WinSpy/AutomationTreeView.h b/WinSpy/AutomationTreeView.h index 9134ac7..d4633c4 100644 --- a/WinSpy/AutomationTreeView.h +++ b/WinSpy/AutomationTreeView.h @@ -27,7 +27,7 @@ class CAutomationTreeView : MESSAGE_HANDLER(WM_CREATE, OnCreate) NOTIFY_CODE_HANDLER(TVN_ITEMEXPANDING, OnNodeExpanding) NOTIFY_CODE_HANDLER(TVN_ITEMEXPANDED, OnNodeExpanded) - //NOTIFY_CODE_HANDLER(TVN_SELCHANGED, OnNodeSelected) + NOTIFY_CODE_HANDLER(TVN_SELCHANGED, OnNodeSelected) NOTIFY_CODE_HANDLER(TVN_DELETEITEM, OnNodeDeleted) CHAIN_MSG_MAP(CTreeViewHelper) CHAIN_MSG_MAP(CVirtualListView) @@ -43,17 +43,25 @@ class CAutomationTreeView : void InitTree(); void UpdateUI(); + void UpdateProperties(IUIAutomationElement* elem); + static std::wstring FormatValue(IUIAutomation* pUI, VARIANT const& value); LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); LRESULT OnNodeExpanding(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/); LRESULT OnNodeExpanded(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/); LRESULT OnNodeSelected(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/); LRESULT OnRefresh(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/); - LRESULT OnWindowProperties(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/); LRESULT OnNodeDeleted(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/); + struct ItemData { + PCWSTR Name; + std::wstring Value; + }; + CCustomSplitterWindow m_Splitter; CTreeViewCtrl m_Tree; CListViewCtrl m_List; CComPtr m_spUIWalker; + CComPtr m_spUI; + std::vector m_Properties; }; diff --git a/WinSpy/WinSpy.rc b/WinSpy/WinSpy.rc index 4da503d..88f512b 100644 --- a/WinSpy/WinSpy.rc +++ b/WinSpy/WinSpy.rc @@ -404,8 +404,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,2,5,0 - PRODUCTVERSION 0,2,5,0 + FILEVERSION 0,3,0,0 + PRODUCTVERSION 0,3,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -422,12 +422,12 @@ BEGIN BEGIN VALUE "CompanyName", "Scorpio Software" VALUE "FileDescription", "WinSpy - UI Windows Properties and Messages" - VALUE "FileVersion", "0.2.5.0" + VALUE "FileVersion", "0.3.0.0" VALUE "InternalName", "WinSpy" - VALUE "LegalCopyright", "©2021-2023 Pavel Yosifovich" + VALUE "LegalCopyright", "©2021-2024 Pavel Yosifovich" VALUE "OriginalFilename", "WinSpy.exe" VALUE "ProductName", "WinSpy" - VALUE "ProductVersion", "0.2.5.0" + VALUE "ProductVersion", "0.3.0.0" END END BLOCK "VarFileInfo" diff --git a/WinSpy/pch.h b/WinSpy/pch.h index 49d2383..46fa8e4 100644 --- a/WinSpy/pch.h +++ b/WinSpy/pch.h @@ -32,6 +32,7 @@ extern CAppModule _Module; #include #include #include +#include #if defined _M_IX86 #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")