List Taskbar buttons

Share your advanced PureBasic knowledge/code with the community.
Hi-Toro
Enthusiast
Enthusiast
Posts: 265
Joined: Sat Apr 26, 2003 3:23 pm

List Taskbar buttons

Post by Hi-Toro »

Code updated For 5.20+

Hi all,

Other methods of doing this have never quite worked 100% for me, but this seems to do the trick. I'd be interested in hearing if it gets it wrong for anyone (state the program if it does!). You need the debugger turned on, and please note that it won't list the Debug Output window since that's not open when it makes the list!

USAGE: Call GetTBarButtons () and iterate through the TBarButtons () list, which can show the window's title -- "TBarButtons ()\name" -- and window handle -- "TBarButtons ()\window".

Code: Select all

Structure TaskbarButton
    name.s
    window.l
EndStructure

Global NewList TBarButtons.TaskbarButton ()

; The rules for checking whether a button is shown on the Taskbar
; or not are listed at the bottom of this file...

; Note: one difference with Task Manager's output is that this lists
; Task Manager's Taskbar button -- Task Manager skips it!

; GAH! Frigging EditPad and its crazy not-hidden-but-invisible window! (Sorted?)

; Windows 10 fix by BarryG:

Prototype DwmGetWindowAttribute_(hWnd,dwAttribute.l,*pvAttribute,cbAttribute.l)
Procedure IsHwndVisible(hWnd)
  vis=-1 ; Means hWnd not found or couldn't determine visibility status.
  c$=Space(999)
  GetClassName_(hWnd,c$,999)
  If c$<>"ApplicationFrameWindow" And c$<>"Windows.UI.Core.CoreWindow" ; Normal Win32 window.
    vis=IsWindowVisible_(hWnd)
    If vis<>0 : vis=1 : EndIf
  Else ; Windows 10 UWP window, which can return non-zero for IsWindowVisible_() despite not shown!
    Define DwmGetWindowAttribute_.DwmGetWindowAttribute_
    #DWMWA_CLOAKED=14
    DWMAPIDLL=OpenLibrary(#PB_Any,"DWMAPI.DLL")
    If DWMAPIDLL
      DwmGetWindowAttribute_=GetFunction(DWMAPIDLL,"DwmGetWindowAttribute")
      If DwmGetWindowAttribute_ And DwmGetWindowAttribute_(hWnd,#DWMWA_CLOAKED,@Cloaked,SizeOf(Cloaked))=#S_OK
        If Cloaked=0
          vis=1
        Else
          vis=0
        EndIf
      EndIf
      CloseLibrary(DWMAPIDLL)
    EndIf
  EndIf
  ProcedureReturn vis
EndProcedure

Procedure ListTaskbarButtons (window, lparam)

    If IsHwndVisible (window)

        flags = GetWindowLong_ (window, #GWL_EXSTYLE)

        If flags & #WS_EX_APPWINDOW
            visible = #True
        Else
            If flags & #WS_EX_TOOLWINDOW
                visible = #False
            Else
                ; Neither style set...
                If GetParent_ (window) = #Null
                    visible = #True
                EndIf
            EndIf
        EndIf

        ; Not covered in rules, but EditPad 3.5.1 is listed as two buttons when only
        ; one is present, and this flag is in the child window (something to do with
        ; allowing input to reach child windows with this flag)...
       
        If flags & #WS_EX_CONTROLPARENT
            visible = #False
        EndIf
       
        If visible
       
            title$ = Space (1024)
            GetWindowText_ (window, @title$, 1024)

            AddElement (TBarButtons ())
            TBarButtons ()\name = title$
            TBarButtons ()\window = window
           
        EndIf
       
    EndIf

    ProcedureReturn #True

EndProcedure

Procedure GetTBarButtons ()
    ClearList (TBarButtons ())
    EnumWindows_ (@ListTaskBarButtons (), 0)
EndProcedure

; D E M O . . .

; Get list of visible buttons...

GetTBarButtons ()

; Iterate through list...

ResetList (TBarButtons ())
While NextElement (TBarButtons ())
    Debug TBarButtons ()\name + " (handle: " + Str (TBarButtons ()\window) + ")"
Wend

; THE RULES FOR TASKBAR BUTTON VISIBILITY!

; From: http://www.microsoft.com/msj/1197/win321197.aspx

; The rules the taskbar uses to decide whether a button should be shown for a window are really quite simple,
; but are not well documented. When you create a window, the taskbar examines the window's extended style
; to see if either the WS_EX_APPWINDOW (defined as 0x00040000) or WS_EX_TOOLWINDOW (defined as
; 0x00000080) style is turned on. If WS_EX_APPWINDOW is turned on, the taskbar shows a button for the
; window, and if WS_EX_ TOOLWINDOW is turned on, the taskbar does not show a button for the window. You
; should never create a window that has both of these extended styles.

; You can create a window that doesn't have either of these styles. If a window has neither style, the taskbar
; decides to create a button if the window is unowned and does not create a button if the window is owned.

; One final note: before making any of the above tests, the taskbar first checks to see if a window has the
; standard WS_VISIBLE window style turned on. If this style bit is off, the window is hidden; the taskbar
; never shows a button for a hidden window. Only if the WS_VISIBLE style bit is on will the taskbar check
; the WS_EX_APPWINDOW, WS_ EX_TOOLWINDOW, and window ownership information.

Last edited by Hi-Toro on Mon Jun 03, 2019 5:39 pm, edited 1 time in total.
James Boyd
http://www.hi-toro.com/
Death to the Pixies!
Hi-Toro
Enthusiast
Enthusiast
Posts: 265
Joined: Sat Apr 26, 2003 3:23 pm

And...

Post by Hi-Toro »

You can combine it with this to get the processes listed in the Taskbar:

Code: Select all

; -----------------------------------------------------------------------------
; Return a window's process ID from its handle...
; -----------------------------------------------------------------------------

Procedure.l FindWindowProcessID (window)
    GetWindowThreadProcessId_ (window, @pid)
    ProcedureReturn pid
EndProcedure
... and combine it with this to kill the program (only if sending a close message to the window fails!)...

Code: Select all

#PROCESS_TERMINATE = $1
#PROCESS_CREATE_THREAD = $2
#PROCESS_VM_OPERATION = $8
#PROCESS_VM_READ = $10
#PROCESS_VM_WRITE = $20
#PROCESS_DUP_HANDLE = $40
#PROCESS_CREATE_PROCESS = $80
#PROCESS_SET_QUOTA = $100
#PROCESS_SET_INFORMATION = $200
#PROCESS_QUERY_INFORMATION = $400
#PROCESS_ALL_ACCESS = #STANDARD_RIGHTS_REQUIRED | #SYNCHRONIZE | $FFF

; This appears to be pretty much how Windows kills a program if you 'End Process'
; from the Task Manager. Note that this is 'unfriendly'!

Procedure KillProcess (pid)
    phandle = OpenProcess_ (#PROCESS_TERMINATE, #FALSE, pid)
    If phandle <> #NULL
        If TerminateProcess_ (phandle, 1)
            result = #TRUE
        EndIf
        CloseHandle_ (phandle)
    EndIf
    ProcedureReturn result
EndProcedure

; Enter process ID here! I suggest going to Task Manager,
; making sure PIDs are shown (try View menu -> Select columns if
; they are not listed), then run a program and enter its number here...

Debug KillProcess ( x )
Last edited by Hi-Toro on Fri Aug 05, 2005 2:28 am, edited 1 time in total.
James Boyd
http://www.hi-toro.com/
Death to the Pixies!
Hi-Toro
Enthusiast
Enthusiast
Posts: 265
Joined: Sat Apr 26, 2003 3:23 pm

Aaaand this...

Post by Hi-Toro »

You can also get the process name from the window handle using this code. Simple usage: Gosub GetProcessList (see explanation at top of program) and call FindWindowProcessName (window)...

Code: Select all

; -----------------------------------------------------------------------------
; Public domain -- Hi-Toro 2003
; -----------------------------------------------------------------------------
; Return a window's process name from its handle...
; -----------------------------------------------------------------------------

; IMPORTANT! You must paste the following section of code (from here to the
; demo section) at the top of your code, AND paste the part at the bottom
; (the 'GetProcessList' sub-routine) at the bottom of your code. The reason the
; sub-routine is required (rather than a procedure) is that the Win32 function
; 'Process32Next' seems to fail on Windows 9x when called from inside a procedure...

; -----------------------------------------------------------------------------
; Paste at top of your code...
; -----------------------------------------------------------------------------

#TH32CS_SNAPHEAPLIST = $1
#TH32CS_SNAPPROCESS = $2
#TH32CS_SNAPTHREAD = $4
#TH32CS_SNAPMODULE = $8
#TH32CS_SNAPALL = #TH32CS_SNAPHEAPLIST | #TH32CS_SNAPPROCESS | #TH32CS_SNAPTHREAD | #TH32CS_SNAPMODULE
#TH32CS_INHERIT = $80000000
#INVALID_HANDLE_VALUE = -1
#MAX_PATH = 260
#PROCESS32LIB = 9999

Structure PROCESSENTRY32
    dwSize.l
    cntUsage.l
    th32ProcessID.l
    *th32DefaultHeapID.l
    th32ModuleID.l
    cntThreads.l
    th32ParentProcessID.l
    pcPriClassBase.l
    dwFlags.l
    szExeFile.b [#MAX_PATH]
EndStructure

; List used to store processes on 'Gosub GetProcessList'...

NewList Process32.PROCESSENTRY32 ()

; Returns process name from window handle...
; IMPORTANT! You should 'Gosub GetProcessList' before calling this!

Procedure.s FindWindowProcessName (window)
    ResetList (Process32 ())
    While NextElement (Process32 ())
        GetWindowThreadProcessId_ (window, @pid)
        If pid = Process32 ()\th32ProcessID
            exe$ = GetFilePart (PeekS (@Process32 ()\szExeFile))
            LastElement (Process32 ())
        EndIf
    Wend
    ProcedureReturn exe$
EndProcedure

; Returns Process ID from window handle...

Procedure.l FindWindowProcessID (window)
    GetWindowThreadProcessId_ (window, @pid)
    ProcedureReturn pid
EndProcedure

; -----------------------------------------------------------------------------
; D E M O...
; -----------------------------------------------------------------------------

window = OpenWindow (0, 0, 0, 320, 200, #PB_Window_SystemMenu | #PB_Window_ScreenCentered, "Test window")

Gosub GetProcessList

SetTimer_ (WindowID (), 0, 100, 0)

Repeat
    Select WaitWindowEvent ()
        Case #PB_Event_CloseWindow
            End
        Case #WM_TIMER
            Gosub GetProcessList
            GetCursorPos_ (@p.POINT)
            over = WindowFromPoint_ (p\x, p\y)
            SetWindowText_ (window, FindWindowProcessName (over))
    EndSelect
ForEver

; -----------------------------------------------------------------------------
; Paste at bottom of your code...
; -----------------------------------------------------------------------------

End ; Leave this here!

GetProcessList:

    ClearList (Process32 ())

    ; Add processes to Process32 () list...

    If OpenLibrary (#PROCESS32LIB, "kernel32.dll")

        snap = CallFunction (#PROCESS32LIB, "CreateToolhelp32Snapshot", #TH32CS_SNAPPROCESS, 0)

        If snap

            DefType.PROCESSENTRY32 Proc32
            Proc32\dwSize = SizeOf (PROCESSENTRY32)
            
            If CallFunction (#PROCESS32LIB, "Process32First", snap, @Proc32)

                AddElement (Process32 ())
                CopyMemory (@Proc32, @Process32 (), SizeOf (PROCESSENTRY32))
                
                While CallFunction (#PROCESS32LIB, "Process32Next", snap, @Proc32)
                    AddElement (Process32 ())
                    CopyMemory (@Proc32, @Process32 (), SizeOf (PROCESSENTRY32))
                Wend
                
            EndIf    
            CloseHandle_ (snap)
        
        EndIf

        CloseLibrary (#PROCESS32LIB)
        
    EndIf

Return
James Boyd
http://www.hi-toro.com/
Death to the Pixies!
Manuel
User
User
Posts: 14
Joined: Thu Mar 02, 2017 11:15 pm
Location: Barcelona

Re: Aaaand this...

Post by Manuel »

Can this code know which order the buttons on the taskbar are, so I can sort them in an array with index 0 being the first button (leftmost, next to "Start") on the taskbar, and index N being the last button (rightmost, next to the clock) on the taskbar? That would be handy.
Dude
Addict
Addict
Posts: 1907
Joined: Mon Feb 16, 2015 2:49 pm

Re: Aaaand this...

Post by Dude »

+1, in case anyone knows how to sort by actual taskbar order.
BarryG
Addict
Addict
Posts: 3294
Joined: Thu Apr 18, 2019 8:17 am

Re: Aaaand this...

Post by BarryG »

Note: The code in the first post no longer works with Windows 10, and will list taskbar buttons for Windows 10 UWP windows that aren't actually visible (such as Settings, Xbox, and so on). To fix this, use my IsHwndVisible() code from viewtopic.php?f=12&t=72941 and replace the following line in the first post above:

Code: Select all

;If IsWindowVisible_ (window) ; Old.
If IsHwndVisible (window) ; New.
Hi-Toro
Enthusiast
Enthusiast
Posts: 265
Joined: Sat Apr 26, 2003 3:23 pm

Re: List Taskbar buttons

Post by Hi-Toro »

Thanks, BarryG, have updated first post with your code.

(It works on WIndows 7 too!)

Interestingly, while running a quick test, I immediately found a window that doesn't get picked up with either version: the main window of the Reaper DAW (https://reaper.fm/), which appears otherwise standard, appears in the taskbar, but not in the results here.

I won't get to look into it for a while, but will try remember later in the week...
James Boyd
http://www.hi-toro.com/
Death to the Pixies!
BarryG
Addict
Addict
Posts: 3294
Joined: Thu Apr 18, 2019 8:17 am

Re: List Taskbar buttons

Post by BarryG »

Hi-Toro wrote:(It works on WIndows 7 too!)
Yes, my IsHwndVisible() procedure is backwards-compatible. Everyone needs to STOP using IsWindowVisible_() immediately if their app is intended for use with Windows 10 - it returns incorrect results.

As for your Reaper problem, I checked and it's getting marked as "visible = #False" in your EditPad test:

Code: Select all

If flags & #WS_EX_CONTROLPARENT ; For EditPad, but Reaper's window also matches this condition.
  visible = #False
EndIf
I don't have EditPad to test, but I suggest you test the above condition with EditPad's class name (not window title) as well, so it doesn't trigger for other windows.
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4637
Joined: Sun Apr 12, 2009 6:27 am

Re: List Taskbar buttons

Post by RASHAD »

Modified and added BarryG snippet to list the actual and visible Running applications
Tested with Windows 10

Code: Select all

Prototype DwmGetWindowAttribute(hWnd,dwAttribute.l, *pvAttribute,cbAttribute.l)
Global DwmGWA.DwmGetWindowAttribute,hWnd,title${#MAX_PATH}, class${#MAX_PATH}
#DWMWA_CLOAKED = 14

Procedure EnumRunningWindows()
  hWnd = FindWindow_(0, 0)
  Repeat
    hWnd = GetWindow_(hWnd, #GW_HWNDNEXT)
    If hWnd And IsWindowVisible_(hWnd) And GetWindowLongPtr_(hWnd, #GWL_HWNDPARENT) = 0
      GetWindowText_(hWnd, @title$, #MAX_PATH)
      GetClassName_(hWnd,@class$,#MAX_PATH)
      If class$ = "ApplicationFrameWindow" Or class$ = "Windows.UI.Core.CoreWindow"
        dll = OpenLibrary(#PB_Any,"DWMAPI.DLL")
        If dll
          DwmGWA = GetFunction(dll,"DwmGetWindowAttribute")
          If DwmGWA
            DwmGWA(hWnd,#DWMWA_CLOAKED,@Cloaked,SizeOf(Cloaked))
            If Cloaked = 0
              Debug title$ + "   " + Str(hwnd) 
            EndIf
          EndIf
          CloseLibrary(dll)
        EndIf
      ElseIf title$ <> "" And title$ <> "Start" And title$ <> "Program Manager"
        Debug title$ + "   " + Str(hwnd)
      EndIf
    EndIf
  Until hWnd = 0
  ProcedureReturn #False
EndProcedure

EnumRunningWindows()
Egypt my love
BarryG
Addict
Addict
Posts: 3294
Joined: Thu Apr 18, 2019 8:17 am

Re: List Taskbar buttons

Post by BarryG »

Does anyone know how to get the taskbar button order? For example, to work out which window is in taskbar button position 2. Ideally I'd want to get the order of each taskbar button's window in an array, from left to right. I thought at first I could work out the order from the time their window process was launched, but users can drag and drop the button order at any time, so that's not the answer.

I asked ChatGPT for the hell of it, and it suggested this... does it look right? If so, can someone please convert it to PureBasic? Thanks.

Code: Select all

HWND GetTaskbarButtonHWnd(int buttonIndex)
{
    // Find the taskbar's main window
    HWND taskbarHWnd = FindWindowEx(NULL, NULL, L"Shell_TrayWnd", NULL);

    if (taskbarHWnd)
    {
        // Find the taskbar's button container
        HWND buttonContainerHWnd = FindWindowEx(taskbarHWnd, NULL, L"ReBarWindow32", NULL);

        if (buttonContainerHWnd)
        {
            // Find the taskbar button at the specified index
            HWND taskbarButtonHWnd = NULL;
            int currentIndex = 0;

            do
            {
                taskbarButtonHWnd = FindWindowEx(buttonContainerHWnd, taskbarButtonHWnd, L"MSTaskSwWClass", NULL);

                if (taskbarButtonHWnd)
                {
                    // Send TCM_GETITEM message to retrieve the button's index
                    int buttonIndexValue = (int)SendMessage(taskbarButtonHWnd, TCM_GETITEM, 0, (LPARAM)TCIF_STATE);

                    if (buttonIndexValue == buttonIndex)
                        return taskbarButtonHWnd;
                }

                currentIndex++;
            } while (taskbarButtonHWnd && currentIndex < buttonIndex);
        }
    }

    return NULL; // No matching button found
}
Axolotl
Enthusiast
Enthusiast
Posts: 435
Joined: Wed Dec 31, 2008 3:36 pm

Re: List Taskbar buttons

Post by Axolotl »

Hi BarryG,

maybe this is what you want ... and you can go from here......
I only translated the c code to pb.
NOT TESTED.

Code: Select all

Procedure GetTaskbarButtonHWnd(buttonIndex)  
  Protected taskbarHWnd, buttonContainerHWnd, taskbarButtonHWnd 
  Protected currentIndex, buttonIndexValue 
  Protected tci.TC_ITEM  

  tci\mask = #TCIF_STATE  ; TCIF_STATE 

  ;// Find the taskbar's main window 
  taskbarHWnd = FindWindowEx_(#Null, #Null, "Shell_TrayWnd", #Null)  
  If taskbarHWnd 
    ;// Find the taskbar's button container
    buttonContainerHWnd = FindWindowEx_(taskbarHWnd, #Null, "ReBarWindow32", #Null) 
    If buttonContainerHWnd 
      ;// Find the taskbar button at the specified index
      taskbarButtonHWnd = #Null 
      currentIndex = 0 
      Repeat 
        taskbarButtonHWnd = FindWindowEx_(buttonContainerHWnd, taskbarButtonHWnd, "MSTaskSwWClass", #Null) 
        If taskbarButtonHWnd 
          ;// Send TCM_GETITEM message to retrieve the button's index 
          buttonIndexValue = SendMessage_(taskbarButtonHWnd, #TCM_GETITEM, 0, @tci) 
          If buttonIndexValue = buttonIndex 
            ProcedureReturn taskbarButtonHWnd 
          EndIf 
        EndIf 

        currentIndex + 1 
      Until taskbarButtonHWnd = 0 Or currentIndex >= buttonIndex 
;             } while (taskbarButtonHWnd && currentIndex < buttonIndex);
    EndIf 
  EndIf 
  ProcedureReturn 0 ;// No matching button found
EndProcedure  

; Debug " -> " + GetTaskbarButtonHWnd(0) 
Mostly running PureBasic <latest stable version and current alpha/beta> (x64) on Windows 11 Home
Post Reply