How to traverse the system tray and get the icon information in it? Such as the icon, the process that created it...
I found a page that meets my requirements, I rewrote its code in Python using ctypes properly. The Original code is based on Windows 10.
import ctypes
from ctypes import wintypes
# 定义常量
PROCESS_VM_OPERATION = 0x0008
PROCESS_VM_READ = 0x0010
PROCESS_VM_WRITE = 0x0020
MEM_COMMIT = 0x00001000
PAGE_EXECUTE_READWRITE = 0x40
MEM_RELEASE = 0x00008000
TB_BUTTONCOUNT = 0x0400 + 24
TB_GETBUTTON = 0x0417
# 定义结构体
class SYSTEM_INFO(ctypes.Structure):
_fields_ = [
('wProcessorArchitecture', wintypes.WORD),
('wReserved', wintypes.WORD),
('dwPageSize', wintypes.DWORD),
('lpMinimumApplicationAddress', wintypes.LPVOID),
('lpMaximumApplicationAddress', wintypes.LPVOID),
('dwActiveProcessorMask', wintypes.DWORD),
('dwNumberOfProcessors', wintypes.DWORD),
('dwProcessorType', wintypes.DWORD),
('dwAllocationGranularity', wintypes.DWORD),
('wProcessorLevel', wintypes.WORD),
('wProcessorRevision', wintypes.WORD)
]
class TBBUTTON(ctypes.Structure):
_fields_ = [
('iBitmap', ctypes.c_int),
('idCommand', ctypes.c_int),
('fsState', ctypes.c_byte),
('fsStyle', ctypes.c_byte),
('bReserved', ctypes.c_byte * 6),
('dwData', ctypes.c_void_p),
('iString', ctypes.c_void_p)
]
# 定义函数
def Is64bitSystem():
si = SYSTEM_INFO()
ctypes.windll.kernel32.GetNativeSystemInfo(ctypes.byref(si))
return si.wProcessorArchitecture in [ctypes.wintypes.WORD(9), ctypes.wintypes.WORD(6)]
def FindTrayWnd():
hWnd = ctypes.windll.user32.FindWindowW("Shell_TrayWnd", None)
hWnd = ctypes.windll.user32.FindWindowExW(hWnd, None, "TrayNotifyWnd", None)
hWnd = ctypes.windll.user32.FindWindowExW(hWnd, None, "SysPager", None)
hWnd = ctypes.windll.user32.FindWindowExW(hWnd, None, "ToolbarWindow32", None)
return hWnd
def FindNotifyIconOverflowWindow():
hWnd = ctypes.windll.user32.FindWindowW("NotifyIconOverflowWindow", None)
hWnd = ctypes.windll.user32.FindWindowExW(hWnd, None, "ToolbarWindow32", None)
return hWnd
def EnumNotifyWindow(hWnd):
# 获取托盘进程ID
dwProcessId = wintypes.DWORD()
ctypes.windll.user32.GetWindowThreadProcessId(hWnd, ctypes.byref(dwProcessId))
if dwProcessId.value == 0:
print("GetWindowThreadProcessId failed:", ctypes.windll.kernel32.GetLastError())
return False
# 获取托盘进程句柄
hProcess = ctypes.windll.kernel32.OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, False, dwProcessId)
if hProcess == 0:
print("OpenProcess failed:", ctypes.windll.kernel32.GetLastError())
return False
# 在进程虚拟空间中分配内存,用来接收 TBBUTTON 结构体指针
p_tbbutton = ctypes.windll.kernel32.VirtualAllocEx(hProcess, 0, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE)
if p_tbbutton == 0:
print("VirtualAllocEx failed:", ctypes.windll.kernel32.GetLastError())
return False
# 初始化
dw_addr_dwData = 0
buff = ctypes.create_string_buffer(1024)
h_mainWnd = None
i_data_offset = 12
i_str_offset = 18
# 判断 x64
if Is64bitSystem():
i_data_offset += 4
i_str_offset += 6
# 获取托盘图标个数
i_buttons = ctypes.windll.user32.SendMessageW(hWnd, TB_BUTTONCOUNT, 0, 0)
if i_buttons != 0:
print("TB_BUTTONCOUNT message failed:", ctypes.windll.kernel32.GetLastError())
return False
i_buttons = 2
# 遍历托盘
for i in range(i_buttons):
# 获取 TBBUTTON 结构体指针
if not ctypes.windll.user32.SendMessageW(hWnd, TB_GETBUTTON, i, p_tbbutton):
print("TB_GETBUTTON message failed:", ctypes.windll.kernel32.GetLastError())
return False
# 读 TBBUTTON.dwData(附加信息)
ctypes.windll.kernel32.ReadProcessMemory(hProcess, ctypes.c_void_p(p_tbbutton.value + i_data_offset), ctypes.byref(dw_addr_dwData), 4, None)
if dw_addr_dwData:
ctypes.windll.kernel32.ReadProcessMemory(hProcess, ctypes.c_void_p(dw_addr_dwData), buff, 1024, None)
h_mainWnd = ctypes.cast(buff.raw[:4], ctypes.POINTER(wintypes.HWND)).contents
ws_filePath = ctypes.c_wchar_p(buff.raw[i_str_offset:i_str_offset + ctypes.sizeof(wintypes.WCHAR) * ctypes.sizeof(wintypes.MAX_PATH)])
ws_tile = ctypes.c_wchar_p(buff.raw[i_str_offset + ctypes.sizeof(wintypes.WCHAR) * ctypes.sizeof(wintypes.MAX_PATH):])
print("hMainWnd =", hex(h_mainWnd.value))
print("strFilePath =", ws_filePath.value)
print("strTile =", ws_tile.value)
# 清理
dw_addr_dwData = 0
h_mainWnd = None
print()
ctypes.windll.kernel32.VirtualFreeEx(hProcess, p_tbbutton, 0, MEM_RELEASE)
ctypes.windll.kernel32.CloseHandle(hProcess)
return True
def main():
# 解决控制台中文 '?'
import locale
locale.setlocale(locale.LC_ALL, "chs")
# 获取托盘句柄
h_tray = FindTrayWnd()
h_tray_fold = FindNotifyIconOverflowWindow()
# 遍历托盘窗口
if not EnumNotifyWindow(h_tray) or not EnumNotifyWindow(h_tray_fold):
print("EnumNotifyWindow false.")
input()
if __name__ == "__main__":
main()
However, it doesn't work on Windows 11... On the code, line 92, I found that the variable of "i_buttons" are always 0. I don't want to use ANY large library(such as PyQt) to complete my problem, thank you.
You can consider pywinauto it is not a large library like tk: Here is code to recursively traverse
from pywinauto import Desktop
def print_window_tree(window, level=0):
indent = " " * level
print(f"{indent}Level {level}: {window.window_text()}, Handle: {window.handle}, Class Name: {window.class_name()}")
children = window.children()
for child in children:
print_window_tree(child, level + 1)
window = Desktop(backend="uia").window(title="Taskbar")
print_window_tree(window)
here is my output:
Level 0: Taskbar, Handle: 131226, Class Name: Shell_TrayWnd
Level 1: , Handle: 131208, Class Name: TrayNotifyWnd
Level 1: , Handle: 66140, Class Name: Windows.UI.Input.InputSite.WindowClass
Level 2: , Handle: None, Class Name: Taskbar.TaskbarFrameAutomationPeer
Level 3: Start, Handle: None, Class Name: ToggleButton
Level 3: Search, Handle: None, Class Name: ToggleButton
Level 4: Search, Handle: None, Class Name: TextBlock
Level 4: Search - Winter travel planning, Handle: None, Class Name: Button
Level 4: , Handle: None, Class Name: Image
Level 3: Task View, Handle: None, Class Name: ToggleButton
------open applications------
--the below is systray items--
Level 2: Show Hidden Icons, Handle: None, Class Name: SystemTray.NormalButton
Level 2: OneDrive - Personal
Your OneDrive is 92% full, Handle: None, Class Name: SystemTray.NormalButton
Level 3: , Handle: None, Class Name: Image
Level 2: Windows Security - Actions recommended. Windows Security - No actions needed., Handle: None, Class Name: SystemTray.NormalButton
Level 3: , Handle: None, Class Name: Image
Level 2: Tray Input Indicator English (United States)
English (India)
To switch input methods, press Windows key + space., Handle: None, Class Name: SystemTray.NormalButton
Level 2: Network HOME 2.4GHz
Internet access, Handle: None, Class Name: SystemTray.AccentButton
Level 2: Volume Speakers (Realtek(R) Audio): 100%, Handle: None, Class Name: SystemTray.OmniButtonRight
Level 2: Clock 08:37 PM
24.11.24, Handle: None, Class Name: SystemTray.OmniButtonLeft
Level 2: Notifications 5 new notifications, Handle: None, Class Name: SystemTray.AccentButton
Level 2: Show Desktop, Handle: None, Class Name: SystemTray.ShowDesktopButton
(nothing personal)
you can get required info in level 2
The key is to use uia
backend: https://learn.microsoft.com/en-us/windows/win32/winauto/entry-uiauto-win32 instead of win32