Recent

Author Topic: [SOLVED] How to refresh system tray?  (Read 28934 times)

BigChimp

  • Hero Member
  • *****
  • Posts: 5740
  • Add to the wiki - it's free ;)
    • FPCUp, PaperTiger scanning and other open source projects
[SOLVED] How to refresh system tray?
« on: October 03, 2011, 12:42:02 pm »
Hi all,

(On Windows Vista x64 using FPC/Lazarus x32)
I've developed a small helpdesk solution that wraps VNC and stunnel to get an encrypted remote desktop session to a client/family member.
https://bitbucket.org/reiniero/checkride/overview

Both VNC and stunnel show up in the system tray/taskbar notification area as icons. When closing those programs/services, the icons disappear, but the stunnel icon seems to linger in the system tray until I move my mouse over it. Then it disappears.

I've found this for C#, which apparently uses Win32 API calls to enumerate all icons in the tray, get the file names of the associated processes, and deletes icons without filenames:
http://www.codeproject.com/KB/system/TrayIconBuster.aspx
I suppose I could try and adapt that, unless there is an easier way.

Another approach could be to simulate the mouse moving over the system tray:
http://www.computersecurityarticles.info/security/refreshing-the-taskbar-notification-area/
Code: [Select]
#define FW(x,y) FindWindowEx(x, NULL, y, L”")

void RefreshTaskbarNotificationArea()
{
    HWND hNotificationArea;
    RECT r;

    GetClientRect(
        hNotificationArea = FindWindowEx(
            FW(FW(FW(NULL, L”Shell_TrayWnd”), L”TrayNotifyWnd”), L”SysPager”),
            NULL,
            L”ToolbarWindow32″,
            L”Notification Area”),
        &r);
   
    for (LONG x = 0; x < r.right; x += 5)
        for (LONG y = 0; y < r.bottom; y += 5)
            SendMessage(
                hNotificationArea,
                WM_MOUSEMOVE,
                0,
                (y << 16) + x);
}
This seems to be a bit easier than the first example...

Is there an easier way?  :D

Thanks!
« Last Edit: October 09, 2011, 02:38:22 pm by BigChimp »
Want quicker answers to your questions? Read http://wiki.lazarus.freepascal.org/Lazarus_Faq#What_is_the_correct_way_to_ask_questions_in_the_forum.3F

Open source including papertiger OCR/PDF scanning:
https://bitbucket.org/reiniero

Lazarus trunk+FPC trunk x86, Windows x64 unless otherwise specified

Shebuka

  • Sr. Member
  • ****
  • Posts: 422
Re: How to refresh system tray?
« Reply #1 on: October 03, 2011, 02:25:56 pm »
Try to hide your tray icon before close the application.

BigChimp

  • Hero Member
  • *****
  • Posts: 5740
  • Add to the wiki - it's free ;)
    • FPCUp, PaperTiger scanning and other open source projects
Re: How to refresh system tray?
« Reply #2 on: October 03, 2011, 02:48:18 pm »
How do I do that?

To clarify: I'm calling an external application (stunnel.exe) using TAsyncProcess.... stunnel.exe shows the tray icon, not my own program.
Want quicker answers to your questions? Read http://wiki.lazarus.freepascal.org/Lazarus_Faq#What_is_the_correct_way_to_ask_questions_in_the_forum.3F

Open source including papertiger OCR/PDF scanning:
https://bitbucket.org/reiniero

Lazarus trunk+FPC trunk x86, Windows x64 unless otherwise specified

BigChimp

  • Hero Member
  • *****
  • Posts: 5740
  • Add to the wiki - it's free ;)
    • FPCUp, PaperTiger scanning and other open source projects
Re: How to refresh system tray?
« Reply #3 on: October 09, 2011, 02:38:06 pm »
Solved, thanks to Sven on the FPC mailing list:
Code: [Select]
uses
.... probably not all of these are necessary, but jwawinuser is at least....
  JwaTlHelp32 {for running processes},
  JwaWinType {for processes declarations},
  JwaWinBase {just a guess: for closing process handles},
  JwaWinSvc {for services declarations, always required},
  jwawinuser {for clearing tray icon/notification area},
....
procedure CleanSystemTray;
  {description Clean dead icons from system tray/notification area}
var
  hNotificationArea: HWND;
  r: RECT;
  x: integer;
  y: integer;
begin
  hNotificationArea:=FindWindowEx(
    FindWindowEx(FindWindowEx(FindWindowEx
    (0,0,'Shell_TrayWnd', ''),0,'TrayNotifyWnd', ''),0,'SysPager',''),
    0,
    'ToolbarWindow32',
    'Notification Area');
  GetClientRect(hNotificationArea,r);

  //Now we've got the area, force it to update
  //by sending mouse messages to it.
  x:=0;
  y:=0;
  while x < r.Right do begin
    while y < r.Bottom do begin
      SendMessage(hNotificationArea, WM_MOUSEMOVE, 0, (y shl 16) + x);
      y:=y+5;
    end;
    x:=x+5;
  end;
end; 
Want quicker answers to your questions? Read http://wiki.lazarus.freepascal.org/Lazarus_Faq#What_is_the_correct_way_to_ask_questions_in_the_forum.3F

Open source including papertiger OCR/PDF scanning:
https://bitbucket.org/reiniero

Lazarus trunk+FPC trunk x86, Windows x64 unless otherwise specified

anna

  • Sr. Member
  • ****
  • Posts: 426
Re: How to refresh system tray?
« Reply #4 on: September 02, 2013, 04:58:55 pm »
Solved, thanks to Sven on the FPC mailing list:
Code: [Select]
uses
.... probably not all of these are necessary, but jwawinuser is at least....
  JwaTlHelp32 {for running processes},
  JwaWinType {for processes declarations},
  JwaWinBase {just a guess: for closing process handles},
  JwaWinSvc {for services declarations, always required},
  jwawinuser {for clearing tray icon/notification area},
....
procedure CleanSystemTray;
  {description Clean dead icons from system tray/notification area}
var
  hNotificationArea: HWND;
  r: RECT;
  x: integer;
  y: integer;
begin
  hNotificationArea:=FindWindowEx(
    FindWindowEx(FindWindowEx(FindWindowEx
    (0,0,'Shell_TrayWnd', ''),0,'TrayNotifyWnd', ''),0,'SysPager',''),
    0,
    'ToolbarWindow32',
    'Notification Area');
  GetClientRect(hNotificationArea,r);

  //Now we've got the area, force it to update
  //by sending mouse messages to it.
  x:=0;
  y:=0;
  while x < r.Right do begin
    while y < r.Bottom do begin
      SendMessage(hNotificationArea, WM_MOUSEMOVE, 0, (y shl 16) + x);
      y:=y+5;
    end;
    x:=x+5;
  end;
end; 

FindWindowEx (0,0,string_argument, '')
must be
FindWindowEx (0,0,string_argument, nil)
See behaviour in debugger.
WinXP SP3 Pro Russian 32-bit (5.1.2600)

anna

  • Sr. Member
  • ****
  • Posts: 426
Re: [SOLVED] How to refresh system tray?
« Reply #5 on: September 03, 2013, 04:53:38 am »
When I do terminateprocess() , which have tray icon, and immediately run tray-refreshing by this method , tray not always is refreshed. It seems tray thinks that process is still running (or maybe Windows has not enough time to inform thay, that process already terminated).
Using sleep(1000) helps , but sleep is not good practice.
How to solve this problem whithout sleep?
« Last Edit: September 03, 2013, 04:55:19 am by anna »
WinXP SP3 Pro Russian 32-bit (5.1.2600)

anna

  • Sr. Member
  • ****
  • Posts: 426
Re: [SOLVED] How to refresh system tray?
« Reply #6 on: September 04, 2013, 02:41:16 pm »
When I do terminateprocess() , which have tray icon, and immediately run tray-refreshing by this method , tray not always is refreshed. It seems tray thinks that process is still running (or maybe Windows has not enough time to inform thay, that process already terminated).
Using sleep(1000) helps , but sleep is not good practice.
How to solve this problem whithout sleep?

I solve this problem by enumerating running processes. If process is absent in process list , then tray is successfully cleaned.
WinXP SP3 Pro Russian 32-bit (5.1.2600)

anna

  • Sr. Member
  • ****
  • Posts: 426
Re: [SOLVED] How to refresh system tray?
« Reply #7 on: September 24, 2013, 04:50:03 am »
One more problem comes to light. If some application has system-tray-icon but for some time no clicking it by user, Windows hides this icon. Then when I'm trying to clean system tray, my project cleans only visible area of system tray. So hidden icon sleel present and confuse my project.

It's appears such arrow-button:
http://img443.imageshack.us/img443/2699/9kma.png
So to completely cleaning I must to click it and run cursor over the area. But it is very hard to do it by messages.
 
Can anybody help me? (;_;)

I have tryed to find NotifyIconOhidverflowWindow - window, but in Windows XP there is no such window.


Well, such code as follow works:
Code: [Select]
    function FindTrayToolbar: HWND;
    var arrow_btn: HWND;
    begin
      Result := FindWindow('Shell_TrayWND', nil);
      Result := FindWindowEx(Result, 0, 'TrayNotifyWnd', nil);
      arrow_btn := FindWindowEx(Result, 0, 'Button', nil);
      if arrow_btn <> 0 then begin
        SendMessage(arrow_btn, WM_LBUTTONDOWN, 0, 0);
        SendMessage(arrow_btn, WM_LBUTTONUP, 0, 0);
      end;
      Result := FindWindowEx(Result, 0, 'SysPager', nil);
      Result := FindWindowEx(Result, 0, 'ToolbarWindow32', nil);
    end;   
But I am not sure.
WinXP SP3 Pro Russian 32-bit (5.1.2600)

anna

  • Sr. Member
  • ****
  • Posts: 426
Re: [SOLVED] How to refresh system tray?
« Reply #8 on: September 24, 2013, 04:55:49 am »
Code above does not work, because after clicking arrow-button there is some delay for visual effect of opening area. And my project finishes cleaning before area completely opens. I must add Sleep routine, but sleep(2000) slows down my project , so I must know exactly time needed.

Maybe when area completely opens , there are some messages , which can be hooked ? Help!
« Last Edit: September 24, 2013, 05:15:37 am by anna »
WinXP SP3 Pro Russian 32-bit (5.1.2600)

anna

  • Sr. Member
  • ****
  • Posts: 426
Re: [SOLVED] How to refresh system tray?
« Reply #9 on: September 24, 2013, 11:20:00 am »
I have research a Explorer.exe in OllyDBG for little time. When you hit "Hide Inactive Icons", in TaskBar properties window, Explorer.exe uses RegSetValueExW for change HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\EnableAutoTray and then Explorer.exe uses some very tangled routine for refresh system tray. Routine contain calculating new size and new position of 'TrayNotifyWnd'-class-window and calling  SetWindowPos for it.

When user moves cursor over system tray, Explorer.exe handle messages:
200 (WM_MOUSEMOVE),
201 (WM_LBUTTONDOWN),
202 (WM_LBUTTONUP),
203 (WM_LBUTTONDBLCLK),
204 (WM_RBUTTONDOWN),
205 (WM_RBUTTONUP),
206 (WM_RBUTTONDBLCLK),
207 (WM_MBUTTONDOWN),
208 (WM_MBUTTONUP)
...
20D
In handler there is IsWindow(application mainform handle) call and if it return 0 then some subroutine is executed. Inside subroutine it 's made lot of checks (maybe is process name present in process list etc.) and finally calls DestroyIcon and refresh system tray

« Last Edit: September 24, 2013, 12:44:33 pm by anna »
WinXP SP3 Pro Russian 32-bit (5.1.2600)

anna

  • Sr. Member
  • ****
  • Posts: 426
Re: [SOLVED] How to refresh system tray?
« Reply #10 on: September 24, 2013, 02:27:41 pm »
To find APP MAINFORM HANDLE for IsWindow( APP MAINFORM HANDLE) Explorer.exe calls
/CALL to SendMessageW from explorer.01035DC0
|hWnd = 1F0078
|Message = MSG(445)
|wParam = 0
\lParam = F6FD2C
where F6FD2C - is a pointer to two dword values - current X and Y of cursor in ToolbarWindow32-class-window.
SendMessageW returns some value for  next SendMessageW's wParam.

/CALL to SendMessageW from explorer.01006EEE
|hWnd = 1F0078
|Message = MSG(43F)
|wParam = SendMessageW
 \lParam = F6FCE0

where 1F0078 - ToolbarWindow32-class-window handle
MSG(43F) - suppose undocumented message
$F6FCE0 - pointer to some structure:
$F6FCE0^=$20 00 00 00 - suppose structure size (20 bytes)
$F6FCE4^=$10 00 00 80 - suppose some flag (80000010)
$F6FCE8^=$08 FD F6 00 E3 92 37 7E 38 73 4A 00 00 00 00 00 00 00 00 00 2C FD F6 00 - I don't know what is it, but SendMessage returns answer to $F6FCF4^. This answer is a pointer to some other structure , which contain handle of app mainform ( APP MAINFORM HANDLE), app executable path and some other values.




« Last Edit: September 24, 2013, 03:34:08 pm by anna »
WinXP SP3 Pro Russian 32-bit (5.1.2600)

anna

  • Sr. Member
  • ****
  • Posts: 426
Re: How to refresh system tray?
« Reply #11 on: October 01, 2013, 04:13:34 am »
Solved, thanks to Sven on the FPC mailing list:
Code: [Select]
uses
.... probably not all of these are necessary, but jwawinuser is at least....
  JwaTlHelp32 {for running processes},
  JwaWinType {for processes declarations},
  JwaWinBase {just a guess: for closing process handles},
  JwaWinSvc {for services declarations, always required},
  jwawinuser {for clearing tray icon/notification area},
....
procedure CleanSystemTray;
  {description Clean dead icons from system tray/notification area}
var
  hNotificationArea: HWND;
  r: RECT;
  x: integer;
  y: integer;
begin
  hNotificationArea:=FindWindowEx(
    FindWindowEx(FindWindowEx(FindWindowEx
    (0,0,'Shell_TrayWnd', ''),0,'TrayNotifyWnd', ''),0,'SysPager',''),
    0,
    'ToolbarWindow32',
    'Notification Area');
  GetClientRect(hNotificationArea,r);

  //Now we've got the area, force it to update
  //by sending mouse messages to it.
  x:=0;
  y:=0;
  while x < r.Right do begin
    while y < r.Bottom do begin
      SendMessage(hNotificationArea, WM_MOUSEMOVE, 0, (y shl 16) + x);
      y:=y+5;
    end;
    x:=x+5;
  end;
end; 

  while x < r.Right do begin
    while y < r.Bottom do begin
      SendMessage(hNotificationArea, WM_MOUSEMOVE, 0, (y shl 16) + x);
      y:=y+5;
    end;
    x:=x+5; //This is wrong loop, becouse when comes here it does not send messages
  end;

Must be like that:
      //Now we've got the area, force it to update
      //by sending mouse messages to it.
      x:=0;
      while x < r.Right do begin
        y:=0;
        while y < r.Bottom do begin
          SendMessage(hNotificationArea, WM_MOUSEMOVE, 0, (y shl 16) + x);
          y:=y+1;
        end;
        x:=x+1;
      end; 
« Last Edit: October 01, 2013, 06:00:01 am by anna »
WinXP SP3 Pro Russian 32-bit (5.1.2600)

anna

  • Sr. Member
  • ****
  • Posts: 426
Re: How to refresh system tray?
« Reply #12 on: October 01, 2013, 04:32:16 am »

I have tried to call explorer's proc $01035BB8 (explorer.exe ver. 6.0.2900.5512 CRC32=3a85f4a7) with arguments:
arg1= $01046188
arg2= $00000000
which is called when user unhit "Hide inactive icons" in 'Taskbar and Start Menu Properties'. This routine put value to registry and set new size of Notification area. But even here I must use sleep, becouse stupid Windows does not change size of Notification area imidiatly and take some random time for it. Stupid , ha?

:::::::::::::::::::::::::::::::::::::::::;

As I see it's impossible to clean system tray becouse of some Windows visual effects and some Windows lagging and short freezing which  swallow some messages. You will always need Sleep-procedure of random seconds . So the best way is to kill explorer.exe and run it again.

When I kill Explorer.exe from my project by TerminateProcess(hProcess,0) , something starts Explorer again. Can somebody tell , what other process starts Explorer?
When I kill Explorer.exe from Windows Task Manager, Explorer is not started again. Why?

As I see in 'Event Viewer > Application', Winlogon process starts explorer.exe. And maybe Winlogon can analize, what process have killed explorer.exe and if this is taskmgr.exe, Winlogon do nothing. Am I right?
« Last Edit: October 01, 2013, 05:20:10 am by anna »
WinXP SP3 Pro Russian 32-bit (5.1.2600)