Jump to content

_RefreshTrayIcons() - _RefreshNotificationAreaIcons() UDF - Tray icons refresh redux


rover
 Share

Recommended Posts

Remove orphaned Notification Area toolbar buttons (icons)

left behind after an application crash or process close

Edit: Dec 5 2k11

Added _RefreshNotificationAreaIcons() UDF for Win7/2008R2 - x86/x64

Updated for 64 bit OS support.

I suppose this issue will finally be put to rest in Win 8...

Orphaned icons still hang around in the User Promoted Notification Area and Overflow Notification Area in Win 7,

but the system removes orphaned icons from the System Promoted Notification Area after 30 seconds.

Now, why didn't they do that for the other two areas?

The only use I have for this is replacing the _RefreshSystemTray() code in the SciTE AutoIt3Wrapper script.

/Edit

-------------------------------

Win 2k/XP/2003 Version - x86 32 bit only

tested on Win2000, WinXP and Vista

Synopsis: Each taskbar Notification Area toolbar button/icon has an application

window handle associated with it that receives the notification messages.

If the window handle owner process no longer exists the orphaned button can be deleted.

The window handle and application identifier of the icon must match to allow deletion with Shell_NotifyIcon API.

(application identifier value is set by developer, so value is sometimes only 0 or 1 (AutoIt EXEs return 1))

[Disclaimer]

_RefreshTrayIcons() accesses the explorer memspace so it will inherently have a risk of an access violation explorer crash

under unforeseen error conditions or third party shell modifications.

this function uses the same memory reading code used in the UDF controls functions.

even though there is error handling and limited write permission to explorer memspace, personal use only is recommended.

use Valik's _RefreshSystemTray() mouse over method in an enterprise environment as it is fail-safe (no explorer memory access)

Don't call this function from a service:

MicroSoft's Raymond Chen at 'The Old New Thing' blog, states Shell_NotifyIcon API should never be called from a service.

Edit: Looks like the don't run in a service warning is about creating a tray icon for a service

which apparently is a security risk - the system not allowing explorer to send messages to a higher security context service process (notification messages).

The UDF sends messages to and reads from explorer memory

but does not receive messages so I don't think that warning is applicable.

This method of clearing icons is a hack and not recommended for use in production code as mentioned by

posters to Raymond Chen's Blog, but certainly suitable for private use.

[/Disclaimer]

Thanks to:

Tuape's SysTray UDF for ideas

wraithdu's NOTIFYICONDATA code

Get Shell_NotifyIcon to show tray balloon tip

NOTIFYICONDATA Structure

http://msdn.microsoft.com/en-us/library/bb773352.aspx

Valik's mouse over method

_RefreshSystemTray(), Remove dead icons from Notification area

and MSDN and other sources

Tested on: Win 2000, Win XP and Vista

Untested: Win 2003, Win 2008 and Win 7

(haven't looked into changes in tray for above OS, maybe MS finally checks if buttons are abandoned by closed processes?...)

Untested on 64 bit OS - not currently supported

Examples:

_RefreshNotificationAreaIcons() - Vista/2008/Win7/2008R2 - x86/x64

included in UDF

_RefreshSystemTray() - Win2k/XP/2003 - x86 only

;#NoTrayIcon
Opt('MustDeclareVars', 1)
#include <File.au3>
#include <Date.au3>

;Usage
;_RefreshTrayIcons()
;Demo
;//create 10 orphaned tray area buttons from closed processes
_CleanTrayTest()
;Two examples
;//clear icon if monitored process closed or crashed
;//could be used with dual scripts that restart a program if process is closed (if tray icon used)
;_ProcessMonitor("processname.exe")
;//tray icon monitor with error logging
;monitors tray for orphaned icons, reports to console and log file (for debug testing)
;_TrayMonitor(1000)

;//create 10 orphaned tray area buttons from closed processes
Func _CleanTrayTest()
    Local $iButtonCnt, $iErr, $iExt, $iTimer
    If MsgBox(1 + 262144, "Notification Area icon refresh demo", _
            "About to create and process close 10 temporary GUI's" & _
            @CRLF & "and orphan 10 icons in tray" & _
            @CRLF & "OK to proceed or Cancel") = 2 Then Exit
    _RunTestGUI(10)
    $iTimer = TimerInit()
    $iButtonCnt = _RefreshTrayIcons()
    $iErr = @error
    $iExt = @extended
    ConsoleWrite("- Runtime  " & Round(TimerDiff($iTimer)) & " Milliseconds" & @CRLF)
    ConsoleWrite("+ Deleted  " & $iButtonCnt & " orphaned tray notification area icon(s)" & _
            @CRLF & "! Error:   " & $iErr & " : Extended: " & $iExt & @CRLF & @CRLF)
    Exit
EndFunc   ;==>_CleanTrayTest
;//tray monitor with error logging
Func _TrayMonitor($iChkRate = 5000)
    Local $iButtonCnt, $iErr, $iExt, $iErrBuffer, $iExtBuffer
    While 1
        Sleep($iChkRate)
        $iButtonCnt = _RefreshTrayIcons()
        $iErr = @error
        $iExt = @extended
        Select
            Case $iButtonCnt = 0 And $iErr = 0 And $iExt = 0
                ContinueLoop
            Case $iButtonCnt < 1 And $iErr = 183 And $iExt = 183 ;instance already running
                $iErrBuffer = $iErr
                $iExtBuffer = $iExt
                _FileWriteLog(@ScriptDir & "\RefreshTrayIcons.log", "Deleted  " & _
                        $iButtonCnt & " orphaned tray notification area icon(s)" & _
                        @CRLF & "! Error:   " & $iErrBuffer & " : Extended: " & $iExtBuffer, -1)
                ConsoleWrite("+ " & _Now() & @CRLF & "+ Deleted  " & $iButtonCnt & _
                        " orphaned tray notification area icon(s)" & _
                        @CRLF & "! Error:   " & $iErrBuffer & " : Extended: " & $iExtBuffer & @CRLF & @CRLF)
            Case $iButtonCnt < 1 And $iErrBuffer = $iErr And $iExtBuffer = $iExt
                ContinueLoop
            Case Else
                $iErrBuffer = $iErr
                $iExtBuffer = $iExt
                _FileWriteLog(@ScriptDir & "\RefreshTrayIcons.log", "Deleted  " & $iButtonCnt & _
                        " orphaned tray notification area icon(s)" & _
                        @CRLF & "! Error:   " & $iErrBuffer & " : Extended: " & $iExtBuffer, -1)
                ConsoleWrite("+ " & _Now() & @CRLF & "+ Deleted  " & $iButtonCnt & _
                        " orphaned tray notification area icon(s)" & _
                        @CRLF & "! Error:   " & $iErrBuffer & " : Extended: " & $iExtBuffer & @CRLF & @CRLF)
                ;ExitLoop
        EndSelect
    WEnd
    Exit
EndFunc   ;==>_TrayMonitor
;//monitor a process
Func _ProcessMonitor($sProcessname)
    While ProcessExists($sProcessname)
        Sleep(2000)
    WEnd
    Local $iButtonCnt = _RefreshTrayIcons()
    Local $iErr = @error, $iExt = @extended
    ConsoleWrite("+ Deleted  " & $iButtonCnt & _
            " orphaned tray notification area icon(s) for process: " & _
            $sProcessname & @CRLF & "! Error:   " & $iErr & _
            " : Extended: " & $iExt & @CRLF & @CRLF)
    Exit
EndFunc   ;==>_ProcessMonitor

Func _RunTestGUI($iGui = 10)
    Local $aPID[$iGui], $line, $sTemp = EnvGet("TEMP"), $iCnt
    Local $file = FileOpen($sTemp & "\RunTestGUI.au3", 2)
    If $file = -1 Then Return
    $line = 'GuiCreate("Test GUI", 400, 100, -1, -1, -1, 8)' & @CR & 'GuiSetState()' & _
            @CR & 'TraySetIcon(@AutoItExe,-3)' & @CR & 'While 1' & @CR & _
            'If GuiGetMsg() = -3 Then Exit' & @CR & 'WEnd'
    FileWrite($file, $line)
    FileClose($file)
  
    ConsoleWrite(@CRLF & "> Creating test GUI processes" & @CRLF)
    For $i = 0 To $iGui - 1
        $aPID[$i] = Run(@AutoItExe & " /AutoIt3ExecuteScript " & $sTemp & "\RunTestGUI.au3")
        WinWait("Test GUI", "", 2)
        If ProcessExists($aPID[$i]) Then $iCnt += 1
    Next
  
    ConsoleWrite("- Closing  " & $iCnt & " test GUI processes" & @CRLF)
    Sleep(2000)
    WinSetOnTop("Test GUI", "", 1)
    WinSetTitle("Test GUI", "", "ORPHANING TRAY ICONS")
    Sleep(2000)
    $iCnt = 0
  
    For $i = 0 To $iGui - 1
        ProcessClose($aPID[$i])
        ProcessWaitClose($aPID[$i], 2)
        If Not ProcessExists($aPID[$i]) Then $iCnt += 1
    Next
    ConsoleWrite("! Closed   " & $iCnt & " processes - tray icons orphaned" & @CRLF)
  
    For $i = 0 To 10
        Sleep(20)
        FileDelete($sTemp & "\RunTestGUI.au3")
        If Not FileExists($sTemp & "\RunTestGUI.au3") Then ExitLoop
    Next
    Sleep(3000)
EndFunc   ;==>_RunTestGUI

UDF:

_RefreshNotificationAreaIcons() UDF - Vista/2008/Win7/2008R2 - x86/x64

64 bit OS support. Tested on Win 7-Win 2008R2

_RefreshNotificationAreaIcons.au3

_RefreshTrayIcons() UDF - Win2k/XP/2003 - x86 only

_RefreshTrayIcons.au3

Examples

_RefreshTrayIcons_examples.au3

Edited by rover

I see fascists...

Link to comment
Share on other sites

Really thorough UDF, nice work!

There's another SysTray UDF here - http://www.autoitscript.com/forum/index.php?showtopic=13704

that uses the same method to get the owner's window handle. However it uses the TB_DELETEBUTTON message along with the icon index to remove icons. I'm not sure it will work for your purpose, but might be easier to implement than the Shell_NotifyIcon API.

Link to comment
Share on other sites

Really thorough UDF, nice work!

There's another SysTray UDF here - http://www.autoitscript.com/forum/index.php?showtopic=13704

that uses the same method to get the owner's window handle. However it uses the TB_DELETEBUTTON message along with the icon index to remove icons. I'm not sure it will work for your purpose, but might be easier to implement than the Shell_NotifyIcon API.

Hi wraithdu, thanks

I have a link up to Tuape's UDF, I see you've done some updating to it.

I was using the TB_DELETEBUTTON method in the early version but switched to Shell_NotifyIcon

SendMessage TB_DELETEBUTTON is approx. 3x faster than Shell_NotifyIcon

** but requires a WM_SIZE message with SIZE_RESTORED to be sent to Shell_TrayWnd to resize toolbar after

icons are deleted, whereas Shell_NotifyIcon handles that internally. (Win 2000, WinXP and Vista)

(**this is most noticeable if many orphaned icons are removed from the toolbar)

I don't know if there are any drawbacks to deleting a tray toolbar button with TB_DELETEBUTTON

instead of the recommended API.

I prefer the Shell_NotifyIcon NIM_DELETE method as it won't delete a button unless a

matching owner handle and identifier is supplied, which along with a process and IsHWnd check

practically eliminates the possibility of an accidental deletion of a valid tray icon.

The Shell_TrayWnd window in WinXP and Vista (other OS untested but probably work) responds to WM_SIZE, but Win 2000 does not.

so the early version of this UDF using TB_DELETEBUTTON added a hidden icon to the tray to trigger a

re-size as a workaround for Win 2000. (tried many other methods to trigger a re-size without success)

TraySetIcon("blank") ;set blank icon to trigger toolbar re-size on exit

Opt("TrayIconHide", 1) ;required to hide blank icon so toolbar does not visibly re-size on exit

I'm revising the run in service comment. It looks like it's only applicable to adding a tray icon for a service

which apparently is a security risk - the system not allowing explorer to send messages to higher security

context service process (returned notification messages).

The UDF sends messages to and reads from explorer memory but does not receive messages.

cheers

I see fascists...

Link to comment
Share on other sites

  • 10 months later...

Does anyone know how to get this to work with Windows 7? :(

 "I believe that when we leave a place, part of it goes with us and part of us remains... Go anywhere, when it is quiet, and just listen.. After a while, you will hear the echoes of all our conversations, every thought and word we've exchanged.... Long after we are gone our voices will linger in these walls for as long as this place remains."

Link to comment
Share on other sites

  • 1 year later...

Hi, I tried to run this on Windows 7 SP1 (64bits) and the 10 orphaned icons were still in the tray after the script ended. Is there something I'm missing?

Welcome to the forums Wh1sp3r

The example script was not updated for the x64 version, it only works with the x86 version.

Just run _RefreshNotificationAreaIcons.au3 after running the example script.

I see fascists...

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...