Enumerate Icons in the notification area
Posted: Mon Oct 01, 2012 12:08 am
I was a little hesitant to post this here since it's something certainly imperfect and unsupported, probably easy to break etc., but maybe can be of some interest, or someone can build upon this so... here it is
EDIT: Replaced GetModuleBaseName() and made some small changes (comments, flags for openprocess, etc.)
Code: Select all
EnableExplicit
; TaskBar Icons enumerator
; By Luis
; PB 4.61 x86/x64
; Based on work from:
;
; Nish Sivakumar
; http://www.codeproject.com/Articles/10807/Shell-Tray-Info-Arrange-your-system-tray-icons
;
; Netmaestro
; http://www.purebasic.fr/english/viewtopic.php?p=302528#p302528
;
; and various people from http://social.msdn.microsoft.com/Forums/en/vbgeneral/thread/70d29077-84e6-43cd-b1b2-d84328276ae6
;
; Tested on Windows XP x86, Windows 7 x86, Windows 7 x64
; It's hackish, unsupported, dirty, etc., expect to see weird things.
; Probably it's more for educational purpose than real usage.
; DeviceNameToDosName() and NormalizeDevicePath() can be useful though.
; Known limitations:
; The bitmap of the icon is not always present, some programs update their icons dinamically in different ways (sendmessage, etc) and sometime the bitmap results blank.
; I'm not 100% sure why.
; The program must be compiled to 32 bit for 32 bit oses and to 64 bit for 64 bit oses.
; The 32 bit version cannot work on a 64 bit os (the 32 bit process cannot read the infos from the 64 bit shell).
; If you find problems and you are able to fix it, thanks.
; If you know a cleaner and less hackish way and want to share it, thanks.
#ICON_POSITION_TASKBAR = 0 ; icons in the notification area of the taskbar
#ICON_POSITION_OVERFLOW = 1 ; icons in the overflow window of Windows 7
; set to 1 to see the kernel-ish device name used in the path
; set to 0 to see the usual C:\ dos name instead (hopefully)
#SHOW_DEVICE_PATH = 0
Structure T_TASKBAR_PROGRAMS
Position.i ; #ICON_POSITION_TASKBAR or #ICON_POSITION_OVERFLOW
IconHandle.i ; the bitmap handle
WinHandle.i ; the handle of icon's window
PID.i ; the pid of the process
Image$ ; the image name of the process
Path$ ; path name of the executable
EndStructure
Structure NOTIFYICONDATA_EX
hWnd.i
uID.l
uFlags.l
uCallbackMessage.l
pad1.b[4]
hIcon.l
EndStructure
Procedure.s DeviceNameToDosName (Device$)
; [DESC]
; Return the DOS name for the specified kernel device mapping.
;
; [INPUT]
; Device$ : The kernel name (like "\Device\HarddiskVolume4", "\Device\Harddisk0\Partition1", etc.)
;
; [RETURN]
; The DOS logical name (A: - Z:)
;
; [NOTES]
; Windows XP
; Ascii/Unicode
; Debug DeviceNameToDosName("\Device\HarddiskVolume1") ; "C:"
Protected Buffer$ = Space(128 + 1)
Protected iNumChars, iCounter, CurrChar.c
Protected Dos$, Dev$
iNumChars = GetLogicalDriveStrings_(128, @Buffer$)
If iNumChars <= 128 And iNumChars > 0
Repeat
CurrChar = PeekC(@Buffer$ + iCounter * SizeOf(Character))
If CurrChar <> 0
Dos$ + Chr(CurrChar)
Else
If Right(Dos$, 1) = "\"
Dos$ = Left(Dos$, Len(Dos$) - 1)
EndIf
Dev$ = Space(#MAX_PATH + 1)
QueryDosDevice_(@Dos$, @Dev$, #MAX_PATH)
If Device$ = Dev$
ProcedureReturn Dos$
EndIf
Dos$ = ""
EndIf
iCounter + 1
Until iCounter >= iNumChars
EndIf
ProcedureReturn ""
EndProcedure
Procedure.s NormalizeDevicePath (DevicePath$)
; [DESC]
; Return the Windows path using drive letters from the specified kernel device path.
;
; [INPUT]
; DevicePath$ : The kernel pathname (like "\Device\HarddiskVolume2\foo\bar\prog.exe")
;
; [RETURN]
; The Windows path using drive letters.
;
; [NOTES]
; Windows XP
; Debug NormalizeDevicePath("\Device\HarddiskVolume2\foo\bar\prog.exe") ; "C:\foo\bar\prog.exe"
Protected iPos
Protected Dev$, Dos$
iPos = FindString(DevicePath$, "\Device\", 1)
If iPos
iPos = FindString (DevicePath$, "\", Len("\Device\") + 1)
If iPos
Dev$ = Left(DevicePath$, iPos - 1)
Dos$ = DeviceNameToDosName(Dev$)
DevicePath$ = ReplaceString(DevicePath$, Dev$, Dos$)
EndIf
EndIf
ProcedureReturn DevicePath$
EndProcedure
Procedure.s GetDeviceFileNameFromPID (iPID)
; [DESC]
; Return the path + filename (in device format) of the executable associated with the process id.
;
; [INPUT]
; The process id of the executable.
;
; [RETURN]
; The path + filename of the process.
;
; [NOTES]
; Returns something like "\Device\HarddiskVolume2\Windows\System32\taskmgr.exe"
Protected hDLL = OpenLibrary(#PB_Any, "psapi.dll")
Protected hProc, *fp
Protected fname$ = Space(#MAX_PATH + 1)
If hDLL
CompilerIf (#PB_Compiler_Unicode = 1)
*fp = GetFunction(hDLL, "GetProcessImageFileNameW")
CompilerElse
*fp = GetFunction(hDLL, "GetProcessImageFileNameA")
CompilerEndIf
If *fp
hProc = OpenProcess_(#PROCESS_QUERY_INFORMATION, #False, iPID)
If hProc
CallFunctionFast(*fp, hProc, @fname$, #MAX_PATH)
CloseHandle_(hProc)
EndIf
EndIf
CloseLibrary(hDLL)
EndIf
ProcedureReturn Trim(fname$)
EndProcedure
Procedure.s GetFileNameFromPID (iPID)
; [DESC]
; Return the path + filename of the executable associated with the process id.
;
; [INPUT]
; The process id of the executable.
;
; [RETURN]
; The path + filename of the process.
;
; [NOTES]
; Returns something like "C:\Windows\System32\taskmgr.exe"
ProcedureReturn NormalizeDevicePath(GetDeviceFileNameFromPID(iPID))
EndProcedure
Procedure.s GetImageNameFromPid (iPID)
; [DESC]
; Return only the filename of the executable associated with the process id.
;
; [INPUT]
; The process id of the executable.
;
; [RETURN]
; The filename of the process.
;
; [NOTES]
; Returns something like "taskmgr.exe"
; Replaced GetModuleBaseName(), see http://msdn.microsoft.com/en-us/library/windows/desktop/ms683196%28v=vs.85%29.aspx
Protected iPos
Protected image$, path$ = GetDeviceFileNameFromPID(iPID)
If path$
path$ = ReverseString(Path$)
iPos = FindString(path$, "\", 1)
If iPos
image$ = ReverseString(Left(path$, iPos - 1))
EndIf
EndIf
ProcedureReturn image$
EndProcedure
Procedure.i FindTaskbarToolbarWindow()
Protected hWnd, hDLL = OpenLibrary(#PB_Any, "psapi.dll")
If hDLL
hWnd = FindWindow_("Shell_TrayWnd", #Null)
If hWnd
hWnd = FindWindowEx_(hWnd, #Null, "TrayNotifyWnd", #Null)
If hWnd
hWnd = FindWindowEx_(hWnd, #Null, "SysPager", #Null)
If hWnd
hWnd = FindWindowEx_(hWnd, #Null, "ToolbarWindow32", #Null)
EndIf
EndIf
EndIf
CloseLibrary(hDLL)
EndIf
ProcedureReturn hWnd
EndProcedure
Procedure.i FindTaskbarToolbarWindowOverflow()
Protected hWnd, hDLL = OpenLibrary(#PB_Any, "psapi.dll")
If hDLL
hWnd = FindWindow_("NotifyIconOverflowWindow", #Null)
If hWnd
hWnd = FindWindowEx_(hWnd, #Null, "ToolbarWindow32", #Null)
EndIf
CloseLibrary(hDLL)
EndIf
ProcedureReturn hWnd
EndProcedure
Procedure.i TaskBarCountIcons (hToolBarWindow)
ProcedureReturn SendMessage_(hToolBarWindow, #TB_BUTTONCOUNT, 0, 0)
EndProcedure
Procedure GetIconInfo (hProc, i, hToolBarWindow, *TBP.T_TASKBAR_PROGRAMS, iPosition)
Protected TBB.TBBUTTON
Protected NID.NOTIFYICONDATA_EX
Protected INFO.ICONINFO
Protected iBytesCount, *pdata
Protected fname$, iPID, image$
*pdata = VirtualAllocEx_(hProc, #Null, SizeOf(TBBUTTON), #MEM_COMMIT, #PAGE_READWRITE)
SendMessage_(hToolBarWindow, #TB_GETBUTTON, i, *pData)
ReadProcessMemory_(hProc, *pData, @TBB, SizeOf(TBBUTTON), @iBytesCount)
ReadProcessMemory_(hProc, TBB\dwData, @NID, SizeOf(NOTIFYICONDATA_EX), @iBytesCount)
GetWindowThreadProcessId_(NID\hWnd, @iPID)
CompilerIf #SHOW_DEVICE_PATH = 1
fname$ = GetDeviceFileNameFromPID(iPID)
CompilerElse
fname$ = GetFileNameFromPID(iPID)
CompilerEndIf
image$ = GetImageNameFromPid(iPID)
If GetIconInfo_(NID\hIcon, @INFO)
*TBP\IconHandle = NID\hIcon
Else
*TBP\IconHandle =0
EndIf
*TBP\WinHandle = NID\hWnd
*TBP\Position = iPosition
*TBP\Image$ = image$
*TBP\Path$ = fname$
*TBP\PID = iPID
VirtualFreeEx_(hProc, *pData, #Null, #MEM_RELEASE)
EndProcedure
Procedure GetIconizedPrograms (hToolBarWindow, iCount, List lstTBP.T_TASKBAR_PROGRAMS(), iPosition)
Protected iPID, hProc, i
Protected TBP.T_TASKBAR_PROGRAMS
GetWindowThreadProcessId_(hToolBarWindow, @iPID)
hProc = OpenProcess_(#PROCESS_VM_OPERATION | #PROCESS_VM_READ, #False, iPID)
For i = 0 To iCount - 1
AddElement(lstTBP())
GetIconInfo(hProc, i, hToolBarWindow, @lstTBP(), iPosition)
Next
CloseHandle_(hProc)
EndProcedure
Procedure.i GetTaskBarPrograms (List lstIcons.T_TASKBAR_PROGRAMS())
Protected hTaskBarToolBarWin = FindTaskbarToolbarWindow()
Protected hTaskBarToolBarOverflowWin = FindTaskbarToolbarWindowOverflow()
ClearList(lstIcons())
If hTaskBarToolBarWin
Protected iIconsCount = TaskBarCountIcons(hTaskBarToolBarWin)
If iIconsCount
GetIconizedPrograms(hTaskBarToolBarWin, iIconsCount, lstIcons(), #ICON_POSITION_TASKBAR)
EndIf
If hTaskBarToolBarOverflowWin
Protected iIconsOverflowCount = TaskBarCountIcons(hTaskBarToolBarOverflowWin)
If iIconsOverflowCount
GetIconizedPrograms(hTaskBarToolBarOverflowWin, iIconsOverflowCount, lstIcons(), #ICON_POSITION_OVERFLOW)
EndIf
EndIf
ProcedureReturn 1
EndIf
ProcedureReturn 0
EndProcedure
; *****************
; * USAGE EXAMPLE *
; *****************
Procedure Main()
Protected iEvent
Protected Row$
Protected NewList lstIcons.T_TASKBAR_PROGRAMS()
GetTaskBarPrograms(lstIcons())
If OpenWindow(0, 10, 10, 800, 600, "Taskbar Notification Area Icons", #PB_Window_SystemMenu)
ListIconGadget(0,0,0,800,600,"Icon", 100, #PB_ListIcon_GridLines)
AddGadgetColumn(0, 1, "Win Handle", 80)
AddGadgetColumn(0, 2, "OVF ?", 45)
AddGadgetColumn(0, 3, "PID", 50)
AddGadgetColumn(0, 4, "Image name", 150)
AddGadgetColumn(0, 5, "Path", 400)
ForEach lstIcons()
Row$ = " (" + Str(lstIcons()\IconHandle) + ")"
Row$ + Chr(10) + Str(lstIcons()\WinHandle)
If lstIcons()\Position = #ICON_POSITION_OVERFLOW
Row$ + Chr(10) + "Y"
Else
Row$ + Chr(10)
EndIf
Row$ + Chr(10) + Str(lstIcons()\PID)
Row$ + Chr(10) + lstIcons()\Image$
Row$ + Chr(10) + lstIcons()\Path$
If lstIcons()\IconHandle
AddGadgetItem(0,-1, Row$, lstIcons()\IconHandle)
Else
AddGadgetItem(0,-1, Row$)
EndIf
Next
Repeat
iEvent = WaitWindowEvent()
Select iEvent
Case #PB_Event_Gadget
Select EventGadget()
EndSelect
EndSelect
Until iEvent = #PB_Event_CloseWindow
EndIf
EndProcedure
Main()
EDIT: Replaced GetModuleBaseName() and made some small changes (comments, flags for openprocess, etc.)