Link to home
Start Free TrialLog in
Avatar of Ark
ArkFlag for Russian Federation

asked on

Who lives in the system tray?

Hello, experts.
I'm trying to make app which enum all running applications with icon(s) in system tray. What I need is hWnd and uId of these applications. Knowing this, I can easyly Modify/Remove icons from system tray.
PS. I tried to get this info using tooltips. At this moment I have a code which determine tooltips quantity at TrayNotifyWnd. But I can not get TooltipInfo structure from Shell. May be there is another way to do this?
I'll wait for a few days for different solution and then show existing code.


Avatar of Richie_Simonetti
Richie_Simonetti
Flag of Argentina image

ping...
Avatar of Paul__420
Paul__420

might wanna check this out:
http://www.allapi.net/vbasic/tray.zip.php
First I want to make sure you know that for Windows 2000 (version 5.0 of the shell) some of the tray icon behavior has changed.  Ive pasted the following from MSDN in case you dont have it.  See the remarks section.

Shell_NotifyIcon
Sends a message to the taskbar's status area.
BOOL Shell_NotifyIcon(
    DWORD dwMessage,
    PNOTIFYICONDATA pnid
);
Parameters
dwMessage
[in] Variable of type DWORD that specifies the action to be taken. It can have one of the following values:
NIM_ADD
Adds an icon to the status area. The hWnd and uID members of the NOTIFYICONDATA structure pointed to by pnid will be used to identify the icon in future calls to Shell_NotifyIcon.
NIM_DELETE
Deletes an icon from the status area. Use the hWnd and uID members of the NOTIFYICONDATA structure pointed to by pnid to identify the icon to be deleted.
NIM_MODIFY
Modifies an icon in the status area. Use the hWnd and uID members of the NOTIFYICONDATA structure pointed to by pnid to identify the icon to be modified.
NIM_SETFOCUS
Version 5.0. Returns focus to the taskbar notification area. Taskbar icons should use this message when they have completed their user interface operation. For example, if the taskbar icon displays a context menu, but the user presses ESCAPE to cancel it, use NIM_SETFOCUS to return focus to the taskbar notification area.
NIM_SETVERSION
Version 5.0. Instructs the taskbar to behave according to the version number specified in the uVersion member of the structure pointed to by pnid. This message allows you to specify whether you want the version 5.0 behavior found on Microsoft. Windows. 2000 systems, or that found with earlier shell versions. The default value for uVersion is zero, indicating that the original Windows 95 notify icon behavior should be used. For details, see the Remarks section.
pnid
[in] Address of a NOTIFYICONDATA structure. The content of the structure depends on the value of dwMessage.
Return Values
Returns TRUE if successful, or FALSE otherwise. If dwMessage is set to NIM_SETVERSION, the function returns TRUE if the version was successfully changed, or FALSE if the requested version is not supported.
Remarks
The taskbar notification area is sometimes erroneously called the "tray."
Version 5.0 of the shell, found on Windows 2000, handles Shell_NotifyIcon mouse and keyboard events differently than earlier shell versions, found on Windows NT. 4.0, Windows 95, and Windows 98. The differences are:
7      If a user selects a notify icon's context menu with the keyboard, the version 5.0 shell sends the associated application a WM_CONTEXTMENU message. Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages.
7      If a user selects a notify icon with the keyboard and activates it with the space bar or ENTER key, the version 5.0 shell sends the associated application an NIN_KEYSELECT notification. Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages.
7      If a user selects a notify icon with the mouse and activates it with the ENTER key, the version 5.0 shell sends the associated application an NIN_SELECT notification. Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages.
7      If a user passes the mouse pointer over an icon with which a balloon tooltip is associated, the version 5.0 shell sends the following messages:
7      NIN_BALLOONSHOW - Sent when the balloon is shown (balloons are queued).
7      NIN_BALLOONHIDE - Sent when the balloon disappearswhen the icon is deleted, for example. This message is not sent if the balloon is dismissed because of a timeout or mouse click by the user.
7      NIN_BALLOONTIMEOUT - Sent when the balloon is dismissed because of a timeout.
7      NIN_BALLOONUSERCLICK - Sent when the balloon is dismissed because the user clicked the mouse.
You can select which way the shell should behave by calling Shell_NotifyIcon with dwMessage set to NIM_SETVERSION. Set the uVersion member of the NOTIFYICONDATA structure to indicate whether you want version 5.0 or pre-version 5.0 behavior.
Note The messages discussed above are not conventional Windows messages. They are sent as the lParam value of the application-defined message that is specified when the icon is added with NIM_ADD.
Requirements
  Version 4.00 and later of Shell32.dll
  Windows NT/2000: Requires Windows NT 4.0 or later.
  Windows 95/98: Requires Windows 95 or later.
  Header: Declared in shellapi.h.
  Import Library: shell32.lib.


The notifyicondata structure has also changed.

typedef struct _NOTIFYICONDATA {
    DWORD cbSize;
    HWND hWnd;
    UINT uID;
    UINT uFlags;
    UINT uCallbackMessage;
    HICON hIcon;
    TCHAR szTip[64];
    DWORD dwState; //Version 5.0
    DWORD dwStateMask; //Version 5.0
    TCHAR szInfo[256]; //Version 5.0
    union {
        UINT  uTimeout; //Version 5.0
        UINT  uVersion; //Version 5.0
    } DUMMYUNIONNAME;
    TCHAR szInfoTitle[64]; //Version 5.0
    DWORD dwInfoFlags; //Version 5.0
} NOTIFYICONDATA, *PNOTIFYICONDATA;


After researching this for a couple hours, I have not been able to pull off what you want.  I was able to hack some handles to applications running with a task bar icon and tried to change their icons remove them, but nothing seemed to work.  The running applications continue to maintain pointers to anything in the tray.  I would think that if anything removed the tray icon, the application might have problems if it references it.  To me it would be dangerous to force the removal of the icon.

To find an applications handle, I used the findwindow call.  I assume you know how to do this.  What I think is that the uID is private to the application for the reasons I mention above.  If you find a way to do this, I would certainly like to know.

Here is the code Ive been using.


Private Sub Form_Load()

End Sub
'Declare a user-defined variable to pass to the Shell_NotifyIcon
      'function.
      Private Type NOTIFYICONDATA
         cbSize As Long
         hWnd As Long
         uId As Long
         uFlags As Long
         uCallBackMessage As Long
         hIcon As Long
         szTip As String * 64
         dwState As Long
         dwStateMask As Long
         szInfo As String * 256
         uTimeout As Long
         uVersion As Long
         szInfoTitle As String * 64
         dwInfoFlags As Long
      End Type

'typedef struct _NOTIFYICONDATA {
'    DWORD cbSize;
'    HWND hWnd;
'    UINT uID;
'    UINT uFlags;
'    UINT uCallbackMessage;
'    HICON hIcon;
'    TCHAR szTip[64];
'    DWORD dwState; //Version 5.0
'    DWORD dwStateMask; //Version 5.0
'    TCHAR szInfo[256]; //Version 5.0
'    union {
'        UINT  uTimeout; //Version 5.0
'        UINT  uVersion; //Version 5.0
'    } DUMMYUNIONNAME;
'    TCHAR szInfoTitle[64]; //Version 5.0
'    DWORD dwInfoFlags; //Version 5.0
'} NOTIFYICONDATA, *PNOTIFYICONDATA;



      'Declare the constants for the API function. These constants can be
      'found in the header file Shellapi.h.

      'The following constants are the messages sent to the
      'Shell_NotifyIcon function to add, modify, or delete an icon from the
      'taskbar status area.
      Private Const NIM_ADD = &H0
      Private Const NIM_MODIFY = &H1
      Private Const NIM_DELETE = &H2

      'The following constant is the message sent when a mouse event occurs
      'within the rectangular boundaries of the icon in the taskbar status
      'area.
      Private Const WM_MOUSEMOVE = &H200

      'The following constants are the flags that indicate the valid
      'members of the NOTIFYICONDATA data type.
      Private Const NIF_MESSAGE = &H1
      Private Const NIF_ICON = &H2
      Private Const NIF_TIP = &H4

      'The following constants are used to determine the mouse input on the
      'the icon in the taskbar status area.

      'Left-click constants.
      Private Const WM_LBUTTONDBLCLK = &H203   'Double-click
      Private Const WM_LBUTTONDOWN = &H201     'Button down
      Private Const WM_LBUTTONUP = &H202       'Button up

      'Right-click constants.
      Private Const WM_RBUTTONDBLCLK = &H206   'Double-click
      Private Const WM_RBUTTONDOWN = &H204     'Button down
      Private Const WM_RBUTTONUP = &H205       'Button up

      'Declare the API function call.
      Private Declare Function Shell_NotifyIcon Lib "shell32" _
         Alias "Shell_NotifyIconA" _
         (ByVal dwMessage As Long, pnid As NOTIFYICONDATA) As Boolean

      'Dimension a variable as the user-defined data type.
      Dim nid As NOTIFYICONDATA
     
     
      Private Declare Function FindWindow Lib "user32" Alias _
    "FindWindowA" (ByVal lpClassName As Any, _
                   ByVal lpWindowName As Any) As Long




      Private Sub Command1_Click()
         
         
         'Click this button to add an icon to the taskbar status area



         'Set the individual values of the NOTIFYICONDATA data type.
         nid.cbSize = Len(nid)
         nid.hWnd = Form1.hWnd
         nid.uId = vbNull
         nid.uFlags = NIF_ICON Or NIF_TIP Or NIF_MESSAGE
         nid.uCallBackMessage = WM_MOUSEMOVE
         nid.hIcon = Form1.Icon
         nid.szTip = "Taskbar Status Area Sample Program" & vbNullChar

         'Call the Shell_NotifyIcon function to add the icon to the taskbar
         'status area.
         Shell_NotifyIcon NIM_ADD, nid
      End Sub

      Private Sub Command2_Click()
         'Click this button to delete the added icon from the taskbar
         'status area by calling the Shell_NotifyIcon function.
         Shell_NotifyIcon NIM_DELETE, nid
      End Sub

      Private Sub Form_Load()
         'Set the captions of the command button when the form loads.
         Command1.Caption = "Add an Icon"
         Command2.Caption = "Delete Icon"
      End Sub

      Private Sub Form_Terminate()
         'Delete the added icon from the taskbar status area when the
         'program ends.
         Shell_NotifyIcon NIM_DELETE, nid
      End Sub

      Private Sub Form_MouseMove _
         (Button As Integer, _
          Shift As Integer, _
          x As Single, _
          Y As Single)
          'Event occurs when the mouse pointer is within the rectangular
          'boundaries of the icon in the taskbar status area.
          Dim msg As Long
          Dim sFilter As String
          msg = x / Screen.TwipsPerPixelX
          Select Case msg
             Case WM_LBUTTONDOWN
             Case WM_LBUTTONUP
             Case WM_LBUTTONDBLCLK
             CommonDialog1.DialogTitle = "Select an Icon"
             sFilter = "Icon Files (*.ico)|*.ico"
             sFilter = sFilter & "|All Files (*.*)|*.*"
             CommonDialog1.Filter = sFilter
             CommonDialog1.ShowOpen
             If CommonDialog1.FileName <> "" Then
                Form1.Icon = LoadPicture(CommonDialog1.FileName)
                nid.hIcon = Form1.Icon
                Shell_NotifyIcon NIM_MODIFY, nid
             End If
             Case WM_RBUTTONDOWN
                Dim ToolTipString As String
                ToolTipString = InputBox("Enter the new ToolTip:", _
                                  "Change ToolTip")
                If ToolTipString <> "" Then
                   nid.szTip = ToolTipString & vbNullChar
                   Shell_NotifyIcon NIM_MODIFY, nid
                End If
             Case WM_RBUTTONUP
             Case WM_RBUTTONDBLCLK
          End Select
      End Sub
 

Avatar of Ark

ASKER

Hello
First of all, sorry for my English, seems it's a reason for misunderstanding. I DON'T NEED CODE TO PUT APP INTO SYSTEM TRAY. I want to know which app already did this.
OK, here is my code:
'===bas module code===
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Function EnumWindows& Lib "user32" (ByVal lpEnumFunc As Long, ByVal lParam As Long)
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long, lpdwProcessId As Long) As Long
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long

Dim tx As TextBox
Dim NWThreadID As Long, NWPid As Long

Private Function GetTrayNotifyWnd() As Long
  GetTrayNotifyWnd = FindWindowEx(FindWindow("Shell_TrayWnd", vbNullString), 0, "TrayNotifyWnd", vbNullString)
End Function

Private Function GetWndClass(hWnd As Long) As String
  Dim k As Long, sName As String
  sName = Space$(128)
  k = GetClassName(hWnd, sName, 128)
  If k > 0 Then sName = Left$(sName, k) Else sName = "No class"
  GetWndClass = sName
End Function

Function EnumWinProc(ByVal hWnd As Long, ByVal lParam As Long) As Long
  Dim pid As Long, tid As Long
  Dim nCount As Long
  tid = GetWindowThreadProcessId(hWnd, pid)
  If pid = NWPid And GetWndClass(hWnd) = "tooltips_class32" And (lStyle And 2) <> 2 Then
     nCount = SendMessage(hWnd, TTM_GETTOOLCOUNT, 0&, ByVal 0&)' Works fine! Return correct number of icons
     ti.hWnd = hWnd
     ti.uId = 1
     ti.cbSize = Len(ti)
     ti.lpszText = String$(80, 0)
' GPF Explorer and clear system tray :(
'     x = SendMessage(hWnd, TTM_ENUMTOOLS, 1, ByVal memPtr)
     tx = tx & "Icons count = " & nCount
'     tx = tx & vbCrLf & GetStrFromBufferA(ti.lpszText)
     EnumWinProc = 0
     Exit Function
  End If
  EnumWinProc = 1
End Function

Public Sub GetTrayList(txt As TextBox)
  Set tx = txt
  tx = "Windows:"
  NWThreadID = GetWindowThreadProcessId(GetTrayNotifyWnd, NWPid)
  EnumWindows AddressOf EnumWinProc, 0
  Set tx = Nothing
End Sub

'===Form code===
Private Sub Command1_Click()
  GetTrayList Text1
End Sub

Cheers
I've got URL's I think you might find interesting

www.mvps.org
www.mvps.org/vbnet
www.codehound.com

hi Ark,
i think that maybe the index specified for ENUMTOOLS constant is not necessarily 1 and this can cause GPF.  from http://www.microsoft.com/msj/defaultframe.asp?page=/msj/1298/controlspy3/controlspy3.htm&nav=/msj/1298/newnav.htm

"Despite its name, TTM_ENUMTOOLS is used to retrieve information on a single tool based on index. Tool index values are set internally by the control and are not unique. Indexes change as tools are inserted and removed. To get information about the tool in which a tip is being displayed, use TTM_GETCURRENTTOOL."

by this description of tools it seems that we cannot always gaurantee we can be well aware of available indexes?
Avatar of Ark

ASKER

Hello, AzraSound
I also tried TTM_GETTOOLINFO with same result.
Seems problem is in different process memory allocation. Same prb (GPF in explorer) occure when trying to receive RECT structures from desktop icons. I thing I need a way to inject my process into Shell process memory.
Recently I found SHLoadInProc API, but still donno how to work with it.

Cheers
i think that your hypothesis about different process memory is most likely the correct one.

>>Recently I found SHLoadInProc API, but still donno how to work with it.

from what i read, it seems you will create an ActiveX dll that performs your code above to grab info from tools in systray, simply pass SHLoadInProc the clsid of your component...i think using UUID structure.
Avatar of Ark

ASKER

Yes, but I have first to compile dll to get UUID and only then test it. Is there any way to use SHLoadInProc with current process? I also can use ReadProcessMemory (and I'll try it) but AFAIK it works only under w9x.

Cheers
>>Is there any way to use SHLoadInProc
with current process?

since it requires a guid as a parameter i dont see how we can make use of that function without first generating a component.


>>I also can use ReadProcessMemory (and I'll try it) but AFAIK it works only under
w9x.

according to MS, this function is valid for all wins:
http://msdn.microsoft.com/library/psdk/winbase/debug_2mpl.htm

however, b/c of tighter security features in NT, that may be why it is more difficult to ensure that it functions correctly

btw, ever think of trying to implement all of those debugging APIs w/ VB?  recently came across a product, www.mutek.com, that reminded me of those functions
You can use fgets (in stdio.h) to read a line :
char *fgets(char *s, int n, FILE *stream)

fgets reads characters from stream into the string s. It stops when it reads either n - 1 characters or a newline character, whichever comes first.
Sorry, my above comment has nothig to do with this question - it's a mistake in window switching, ignore it.
My friend (and I'm sure, yours) Edanmo, has a link on his web site http://www.domaindlx.com/e_morcillo/scripts/cod/default.asp?page=grl#systray (look for "A form in the system tray") that might be of interest to you, Ark.  It't not a straight-out answer to your question, but theres some code to get the hWnd of the system tray, which might lead you to a solution.
Avatar of Ark

ASKER

Hi
AzraSound, there is a VBDebugger at morcillo's site. Donno if you speak about this functionality.
Anthony, thanks for link. I already surf the WEB for all about system tray. See my code above:
Private Function GetTrayNotifyWnd() As Long
 GetTrayNotifyWnd = FindWindowEx(FindWindow("Shell_TrayWnd", vbNullString), 0, "TrayNotifyWnd", vbNullString)
End Function

Now I know all process of systray notification. I can replace SysTray with my own systray which belong to my process and all app will place their icons into my tray.
Other app co-operate with systray trough WM_COPYDATA message. SysTray process should store these data (I thing this is array of pointers) somewhere in local memory. When I use GETTOOLINFO, shell try to fill TOOLTIPINFO structure which belong to MY process and get GPF. But same thing occure when I allocate global memory block for this structure (?). (BTW, my code above have a typo in SendMessage(...TTM_ENUMTOOL) - last parameter should be ti ByRef - memPtr is Global memory block address).
No I look on another way - how to find this array of pointers where Tray store info about NOTIFYICON data.

Cheers
>>AzraSound, there is a VBDebugger at morcillo's site

yes i am familiar with it but i find its not very stable, in fact, i could not get it to work on my machine (Win2k Pro).  it also does not make use of any of the debugging APIs that i was talking about...those included in imghlp.dll (and now the newer dbghelp.dll included with Win2k - this dll is redistributable so i can send it to you if interested)


>>I can replace SysTray with my own systray which belong
to my process and all app will place their icons into my tray

and what route did you end up taking to get there?
Avatar of Ark

ASKER

No problem to create new window with "Shell_TrayWnd" class and then send registered message "TaskbarCreated" to all windows (HWND_BROADCAST). All good applications should update their icon in tray. Since I own this window I can subclass it and wait for WM_COPYDATA and store NOTIFAYICON data in array/collection. But most applications don't monitor TaskbarCreated message (even MS sample doesn't!), so they wont restore icons and I can get only new created icons. Of course, I can run my app at startap, but I want a way to manage existing system tray.

Cheers
what does lStyle refer to in your above code?
Avatar of Ark

ASKER

Oops, sorry, forgot:
 lStyle = GetWindowLong(hWnd, GWL_STYLE)

all tooltips in taskbar, except of tray icons tooltips, have TTS_NOPREFIX (&H2) stile included.

Cheers
i am trying to implement your code using component and SHLoadInProc.  i was simply testing SHLoadInProc with a dummy component to ensure it was working and it appears to work fine, but it seems explorer does not properly release the component after calling it.  all the documentation i have read indicates it calls the component and then releases it immediately, but i can not recompile the component at this time as i receive "permission denied" errors.  this tells me explorer must still have access to the object...any thoughts/ideas?
in my class for the component i simply wrote to a text file in the initialize and terminate events to show me that it was indeed called and destroyed in the call to SHLoadInProc.  the text file indicates that the class was instantiated and destroyed, but as i stated, Windows is still holding on to it somehow...
Avatar of Ark

ASKER

Hello, AzraSound
Recently downloaded a must have book:
"Programming Applications for Microsoft Windows" by Jeffrey Richter. If you need it - here is a link
http://retro3.newmail.ru/richter.rar1
it's in chm format(11M in archive). Rename .rar1 to .rar
Start Setup\Ebook\setup.exe

There are description how to inject your process into another (Chapter 22).

Cheers
is the .chm file on your server?  i havent been able to get to it all day...
Avatar of Ark

ASKER

Hello
Go here:
http://ganz.newmail.ru/Dokz.html
First link is to Richter's book. This file is in rar format. After downloading rename it to *.rar, unrar it and start setup.
There are other interesting books at this site. Their description are in Russian. You can download them, but note - No.2(Programming Windows by Charles Petzold, 5th Edition) and No.7(Visual C++ 6.0) are in Russian. Other are in English.

Cheers
PS. Donno, may be this site allow downloading only from Russian servers? If you'll still have a problem with downloading, let me know, I'll download what you need and place at my server or post you via e-mail.

Cheers
thanks Ark,
the download does start now, but stops transmitting after only a few kbytes.  i am not sure what the reason may be, but it appears i can reach the site...i will continue to try tonight and tomorrow.
I don't know much about it
check this code,

Private Sub Form_Load()
    Me.AutoRedraw = True
    GetTrayNotifyWnd = FindWindow("Shell_TrayWnd", vbNullString) ', 0, "TrayNotifyWnd", vbNullString)
    EnumChildWindows GetTrayNotifyWnd, AddressOf EnumChildProc, ByVal 0&
End Sub

'in a module
Option Explicit
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Declare Function EnumChildWindows Lib "user32" (ByVal hWndParent As Long, ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Public GetTrayNotifyWnd  As Long
Public Function EnumChildProc(ByVal hwnd As Long, ByVal lParam As Long) As Long
    Form1.Print hwnd
    'continue enumeration
    EnumChildProc = 1
End Function
hi Ark, the same is for me. i can't download the files from that server.
Any help really aprecciated.
Avatar of Ark

ASKER

Hi, Richie
Unfortunatelly, same for me, seems this file have been removed. Give me your e-mail, I'll send it to you (11M total)

Cheers
Hi Ark & AzraSound,
>There are description how to inject your process into another
It is possible, that make our process as a child process or thread of another process.
plz, tell me possible r not.I will ask a valuable question

ricardo.simonetti@fmc-ag.com
(Thanks, Ark)
pong...
Avatar of Ark

ASKER

So far so long...
What I have now:
Though I still can not get the list of programs in tray, I can safelly get infor from another processese. Here it is:

'============mSharedMemory.BAS module=============
Option Explicit
'Some API (SendMessage for example) use pointers to structures to be filled
'with some data. If you're sending such message to window belong to your
'process - no problem. But if you try to send this message to different
'process - GPF can occure, because structure address belong to calling process
'memory space and target process can not achive this address. Here is
'work around.
'For Win95/98/ME we can use File Mapping, because OS place mapped files
'into shareable memory space. But we can't use this trick for NT - NT map
'files into calling process memory area. In this case, we can use
'VirtualAllocEx function to reserve memory inside target process.

'=========Checking OS staff=============
Private Type OSVERSIONINFO
    dwOSVersionInfoSize As Long
    dwMajorVersion As Long
    dwMinorVersion As Long
    dwBuildNumber As Long
    dwPlatformId As Long
    szCSDVersion As String * 128
End Type
Private Declare Function GetVersionEx Lib "kernel32" Alias "GetVersionExA" (LpVersionInformation As OSVERSIONINFO) As Long

'========= Win95/98/ME Shared memory staff===============
Private Declare Function CreateFileMapping Lib "kernel32" Alias "CreateFileMappingA" (ByVal hFile As Long, ByVal lpFileMappigAttributes As Long, ByVal flProtect As Long, ByVal dwMaximumSizeHigh As Long, ByVal dwMaximumSizeLow As Long, ByVal lpName As String) As Long
Private Declare Function MapViewOfFile Lib "kernel32" (ByVal hFileMappingObject As Long, ByVal dwDesiredAccess As Long, ByVal dwFileOffsetHigh As Long, ByVal dwFileOffsetLow As Long, ByVal dwNumberOfBytesToMap As Long) As Long
Private Declare Function UnmapViewOfFile Lib "kernel32" (lpBaseAddress As Any) As Long
Const STANDARD_RIGHTS_REQUIRED = &HF0000
Const SECTION_QUERY = &H1
Const SECTION_MAP_WRITE = &H2
Const SECTION_MAP_READ = &H4
Const SECTION_MAP_EXECUTE = &H8
Const SECTION_EXTEND_SIZE = &H10
Const SECTION_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED Or SECTION_QUERY Or SECTION_MAP_WRITE Or SECTION_MAP_READ Or SECTION_MAP_EXECUTE Or SECTION_EXTEND_SIZE
Const FILE_MAP_ALL_ACCESS = SECTION_ALL_ACCESS

'============NT Shared memory staff======================
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Const PROCESS_VM_OPERATION = &H8
Const PROCESS_VM_READ = &H10
Const PROCESS_VM_WRITE = &H20
Const PROCESS_ALL_ACCESS = 0
Private Declare Function VirtualAllocEx Lib "kernel32" (ByVal hProcess As Long, ByVal lpAddress As Long, ByVal dwSize As Long, ByVal flAllocationType As Long, ByVal flProtect As Long) As Long
Private Declare Function VirtualFreeEx Lib "kernel32" (ByVal hProcess As Long, lpAddress As Any, ByVal dwSize As Long, ByVal dwFreeType As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long

'==========Memory access constants===========
Private Const PAGE_NOACCESS = &H1&
Private Const PAGE_READONLY = &H2&
Private Const PAGE_READWRITE = &H4&
Private Const PAGE_WRITECOPY = &H8&
Private Const PAGE_EXECUTE = &H10&
Private Const PAGE_EXECUTE_READ = &H20&
Private Const PAGE_EXECUTE_READWRITE = &H40&
Private Const PAGE_EXECUTE_WRITECOPY = &H80&
Private Const PAGE_GUARD = &H100&
Private Const PAGE_NOCACHE = &H200&

Public Function GetMemShared95(ByVal memSize As Long, hFile As Long) As Long
    hFile = CreateFileMapping(&HFFFFFFFF, 0, PAGE_READWRITE, 0, memSize, vbNullString)
    GetMemShared95 = MapViewOfFile(hFile, FILE_MAP_ALL_ACCESS, 0, 0, 0)
End Function

Public Sub FreeMemShared95(ByVal hFile As Long, ByVal lpMem As Long)
    UnmapViewOfFile lpMem
    CloseHandle hFile
End Sub

Public Function GetMemSharedNT(ByVal pid As Long, ByVal memSize As Long, hProcess As Long) As Long
    hProcess = OpenProcess(PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE, False, pid)
    GetMemSharedNT = VirtualAllocEx(ByVal hProcess, ByVal 0&, ByVal memSize, MEM_RESERVE Or MEM_COMMIT, PAGE_READWRITE)
End Function

Public Sub FreeMemSharedNT(ByVal hProcess As Long, ByVal MemAddress As Long, ByVal memSize As Long)
   Call VirtualFreeEx(hProcess, ByVal MemAddress, memSize, MEM_RELEASE)
   CloseHandle hProcess
End Sub

Public Function IsWindowsNT() As Boolean
   Dim verinfo As OSVERSIONINFO
   verinfo.dwOSVersionInfoSize = Len(verinfo)
   If (GetVersionEx(verinfo)) = 0 Then Exit Function
   If verinfo.dwPlatformId = 2 Then IsWindowsNT = True
End Function
'============END OF mSharedMemory.BAS MODULE===========
'============BAS MODULE FOR GETTING TOOLTIPINFO========
Option Explicit

'=========ToolTips and TrayFindow=========
Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Type TOOLINFO
    cbSize      As Long
    uFlags      As Long
    hWnd        As Long
    uId         As Long
    cRect       As RECT
    hInst       As Long
    lpszText    As Long 'LPCSTR
    lParam      As Long
End Type

Private Const WM_USER = &H400
Private Const TTM_GETTOOLCOUNT = (WM_USER + 13)
Private Const TTM_ENUMTOOLSA = (WM_USER + 14)
Private Const TTM_ENUMTOOLSW = (WM_USER + 58)

Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Function EnumWindows& Lib "user32" (ByVal lpEnumFunc As Long, ByVal lParam As Long)
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long, lpdwProcessId As Long) As Long
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare Function GetWindowLong& Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long)
Const GWL_STYLE = (-16)

Private Declare Function GetCurrentProcessId Lib "kernel32" () As Long


Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Private Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpDest As Any, lpSource As Any, ByVal cBytes As Long)

Dim tx As TextBox
Dim NWThreadID As Long, NWPid As Long

Public Sub GetTrayList(txt As TextBox)
   Set tx = txt
   tx = "Windows:"
   NWThreadID = GetWindowThreadProcessId(GetTrayNotifyWnd, NWPid)
   EnumWindows AddressOf EnumWinProc, 0
   Set tx = Nothing
End Sub

Private Function GetTrayNotifyWnd() As Long
   GetTrayNotifyWnd = FindWindowEx(FindWindow("Shell_TrayWnd", vbNullString), 0, "TrayNotifyWnd", vbNullString)
End Function

Function EnumWinProc(ByVal hWnd As Long, ByVal lParam As Long) As Long
   Dim pid As Long, tid As Long, lStyle As Long
   Dim hProcess As Long, lpSysShared As Long, dwSize As Long
   Dim nCount As Long, lWritten As Long, hFileMapping As Long
   Dim ti As TOOLINFO
   tid = GetWindowThreadProcessId(hWnd, pid)
   lStyle = GetWindowLong(hWnd, GWL_STYLE)
   If pid = NWPid And GetWndClass(hWnd) = "tooltips_class32" And (lStyle And 2) <> 2 Then
      nCount = SendMessage(hWnd, TTM_GETTOOLCOUNT, 0&, ByVal 0&)
      tx = tx & "Icons count = " & nCount
      ti.cbSize = Len(ti)
      dwSize = ti.cbSize
      If IsWindowsNT Then
         lpSysShared = GetMemSharedNT(pid, dwSize, hProcess)
         WriteProcessMemory hProcess, ByVal lpSysShared, ti, dwSize, lWritten
         For i = 0 To nCount - 1
             Call SendMessage(hWnd, TTM_ENUMTOOLSW, i, ByVal lpSysShared)
             ReadProcessMemory hProcess, ByVal lpSysShared, ti, dwSize, lWritten
         Next i
         FreeMemSharedNT hProcess, lpSysShared, dwSize
      Else
         lpSysShared = GetMemShared95(dwSize, hFileMapping)
         CopyMemory ByVal lpSysShared, ti, dwSize
         For i = 0 To nCount - 1
             Call SendMessage(hWnd, TTM_ENUMTOOLSA, i, ByVal lpSysShared)
             CopyMemory ti, ByVal lpSysShared, dwSize
             Debug.Print ti.hWnd, ti.lpszText, ti.hInst, ti.uId, ti.lParam
         Next i
         FreeMemShared95 hFileMapping, lpSysShared
      End If
      EnumWinProc = 0
      Exit Function
   End If
   EnumWinProc = 1
End Function

Private Function GetWndClass(hWnd As Long) As String
   Dim k As Long, sName As String
   sName = Space$(128)
   k = GetClassName(hWnd, sName, 128)
   If k > 0 Then sName = Left$(sName, k) Else sName = "No class"
   GetWndClass = sName
End Function
'===============END OF BAS MODULE=======================

I've got correct TOOLIFO structure with TrayNotify hWnd as .hWnd, some .hInst and .uId (donno what are they), but lpszText member is -1 (LPSTR_CALLBACK). Damn! Now I have to dig deeper looking for this callback function which return text!
'=======================================================
Sorry to all for long silence. To apologize, here is BONUS:

'Dancing desktop sample
'There is now problem to change desktop icons position - desktop is simple ListView control.
'But to restore it, you have to save previous position. Here is a problem -
'you need to pass structure to Shell. So we have to use above mSharedMemory.bas module.
'Add this module to your project
'Add second bas module:
'=====Desktop.Bas module code=======
Option Explicit

Public Enum SHUFFLE_TYPE
    RANDOM
    SINE
    CIRCLES
'More depend on your fantasy and geomethry knowledge :)
End Enum
'=========Desktop SysListView staff=============
Type POINTAPI
     x As Long
     y As Long
End Type

Private Declare Function FindWindow& Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String)
Private Declare Function FindWindowEx& Lib "user32" Alias "FindWindowExA" (ByVal hWndParent As Long, ByVal hWndChildAfter As Long, ByVal lpClassName As String, ByVal lpWindowName As String)
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long, lpdwProcessId As Long) As Long
Private Declare Function GetWindowLong& Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long)
Const GWL_STYLE = (-16)
Private Const LVS_AUTOARRANGE = &H100

Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Private Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpDest As Any, lpSource As Any, ByVal cBytes As Long)
Private Declare Function GetParent Lib "user32" (ByVal hWnd As Long) As Long

Private Declare Function SendMessage& Lib "user32" Alias "SendMessageA" (ByVal hWnd&, ByVal wMsg&, ByVal wParam&, lParam As Any)
Private Const LVM_FIRST = &H1000
Private Const LVM_GETTITEMCOUNT& = (LVM_FIRST + 4)
Private Const LVM_SETITEMPOSITION& = (LVM_FIRST + 15)
Private Const LVM_GETITEMPOSITION& = (LVM_FIRST + 16)
Private Const WM_COMMAND = &H111
Private Const IDM_TOGGLEAUTOARRANGE = &H7041

'=============================================================
Dim ptOriginal() As POINTAPI
Dim ptCurrent() As POINTAPI
Dim xScreen As Long, yScreen As Long
Dim bAutoArrange As Boolean

Public Sub ShuffleDesktopIcons(ByVal ShuffleType As SHUFFLE_TYPE)
   Dim h As Long, nCount As Long, i As Long
   Dim FactorX As Single, FactorY As Single, Radius As Single
   Dim x As Long, y As Long, cx As Long, cy As Long
   FactorX = Rnd
   FactorY = Rnd
   Radius = xScreen * Rnd / 2
   h = GetSysLVHwnd
   nCount = SendMessage(h, LVM_GETTITEMCOUNT, 0, 0&)
   For i = 0 To nCount - 1
       Select Case ShuffleType
              Case RANDOM
                   x = Int(Rnd * xScreen)
                   y = Int(Rnd * yScreen)
              Case SINE
                   x = FactorX * xScreen * i \ nCount
                   y = FactorY * yScreen * (1 - Sin(i * 6.28 / nCount)) \ 2
              Case CIRCLES
                   x = xScreen / 2 - Radius * Cos(i * 6.28 / nCount)
                   y = yScreen / 2 - Radius * Sin(i * 6.28 / nCount)
       End Select
       Call SendMessage(h, LVM_SETITEMPOSITION, i, ByVal CLng(x + y * &H10000))
   Next
End Sub

Public Sub RestoreDesktopIcons()
   Dim h As Long, nCount As Long, i As Long
   h = GetSysLVHwnd
   nCount = SendMessage(h, LVM_GETTITEMCOUNT, 0, 0&)
   For i = 0 To nCount - 1
       Call SendMessage(h, LVM_SETITEMPOSITION, i, ByVal CLng(ptOriginal(i).x + ptOriginal(i).y * &H10000))
   Next
   If bAutoArrange Then
      Call SendMessage(GetParent(h), WM_COMMAND, IDM_TOGGLEAUTOARRANGE, ByVal 0&)
   End If
End Sub

Public Function StoreDeskTopInfo() As Boolean
   Dim pid As Long, tid As Long, lStyle As Long
   Dim hProcess As Long, lpSysShared As Long, dwSize As Long
   Dim nCount As Long, lWritten As Long, hFileMapping As Long
   Dim h As Long, i As Long
   h = GetSysLVHwnd
   If h = 0 Then Exit Function
   If (GetWindowLong(h, GWL_STYLE) And LVS_AUTOARRANGE) = LVS_AUTOARRANGE Then
      bAutoArrange = True
      Call SendMessage(GetParent(h), WM_COMMAND, IDM_TOGGLEAUTOARRANGE, ByVal 0&)
   End If
   tid = GetWindowThreadProcessId(h, pid)
   nCount = SendMessage(h, LVM_GETTITEMCOUNT, 0, 0&)
   If nCount = 0 Then Exit Function
   xScreen = Screen.Width \ Screen.TwipsPerPixelX
   yScreen = Screen.Height \ Screen.TwipsPerPixelY
   ReDim ptOriginal(nCount - 1)
   ReDim ptCurrent(nCount - 1)
   dwSize = Len(ptOriginal(0))
   If IsWindowsNT Then
      lpSysShared = GetMemSharedNT(pid, dwSize, hProcess)
      WriteProcessMemory hProcess, ByVal lpSysShared, ptOriginal(0), dwSize, lWritten
      For i = 0 To nCount - 1
          SendMessage h, LVM_GETITEMPOSITION, i, ByVal lpSysShared
          ReadProcessMemory hProcess, ByVal lpSysShared, ptOriginal(i), dwSize, lWritten
      Next i
      FreeMemSharedNT hProcess, lpSysShared, dwSize
   Else
      lpSysShared = GetMemShared95(dwSize, hFileMapping)
      CopyMemory ByVal lpSysShared, ptOriginal(0), dwSize
      For i = 0 To nCount - 1
          SendMessage h, LVM_GETITEMPOSITION, i, ByVal lpSysShared
          CopyMemory ptOriginal(i), ByVal lpSysShared, dwSize
          ptCurrent(i).x = xScreen / 2
          ptCurrent(i).y = yScreen / 2
      Next i
      FreeMemShared95 hFileMapping, lpSysShared
   End If
   StoreDeskTopInfo = True
End Function

Private Function GetSysLVHwnd() As Long
   Dim h As Long
   h = FindWindow("Progman", vbNullString)
   h = FindWindowEx(h, 0, "SHELLDLL_defVIEW", vbNullString)
   GetSysLVHwnd = FindWindowEx(h, 0, "SysListView32", vbNullString)
End Function

'==========Form code========
'add 2 command button, label, combobox(dropdown list) and timer to form (with default names).

Dim bRunning As Boolean

Private Sub Command1_Click()
   If Not bRunning Then
      If StoreDeskTopInfo Then Timer1.Enabled = True
      Caption = "See your desktop dancing!"
      bRunning = True
      Command1.Enabled = False
      Command2.Enabled = True
   End If
End Sub

Private Sub Command2_Click()
   If bRunning Then
      Timer1.Enabled = False
      RestoreDesktopIcons
      bRunning = False
      Caption = "Desktop shuffle sample"
      Command1.Enabled = True
      Command2.Enabled = False
   End If
End Sub

Private Sub Form_Load()
   Timer1.Interval = 200
   Timer1.Enabled = False
   Caption = "Desktop shuffle demo"
   Label1 = "Desktop shuffling type"
   With Combo1
        .AddItem "RANDOM"
        .AddItem "SINE"
        .AddItem "CIRCLES"
        .ListIndex = 0
   End With
   Command1.Caption = "&Start"
   Command2.Caption = "&Stop"
   Command1.Enabled = True
   Command2.Enabled = False
End Sub

Private Sub Form_Unload(Cancel As Integer)
   If bRunning Then RestoreDesktopIcons
End Sub

Private Sub Timer1_Timer()
    ShuffleDesktopIcons Combo1.ListIndex
End Sub
'============End form code================

Tested on w98 - works OK. Not tested on NT, but should work too.

I'm going to close this question.

Cheers
Avatar of Ark

ASKER

Got It!!!!!!!!!!!!!!!

Add mSharedMemory.bas
Add second module:
'==========bas module code==========
Option Explicit

Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Type TOOLINFO
    cbSize      As Long
    uFlags      As Long
    hWnd        As Long
    uId         As Long
    cRect       As RECT
    hInst       As Long
    lpszText    As Long 'LPCSTR
    lParam      As Long
End Type

Private Const WM_USER = &H400
Private Const TTM_GETTOOLCOUNT = (WM_USER + 13)
Private Const TTM_ENUMTOOLSA = (WM_USER + 14)
Private Const TTM_ENUMTOOLSW = (WM_USER + 58)
Private Const TTM_GETTEXT = (WM_USER + 11)

Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Function EnumWindows& Lib "user32" (ByVal lpEnumFunc As Long, ByVal lParam As Long)
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long, lpdwProcessId As Long) As Long
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare Function GetWindowLong& Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long)
Const GWL_STYLE = (-16)

Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Private Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpDest As Any, lpSource As Any, ByVal cBytes As Long)
Private Declare Function CopyStringA Lib "kernel32" Alias "lstrcpyA" (ByVal NewString As String, ByVal OldString As Long) As Long
Private Declare Function lstrlenW Lib "kernel32" (ByVal lpString As Long) As Long
Private Declare Function lstrlenA Lib "kernel32" (ByVal lpString As Long) As Long

Dim sTips() As String
Dim NWThreadID As Long, NWPid As Long

Public Function GetTrayList() As Variant
   NWThreadID = GetWindowThreadProcessId(GetTrayNotifyWnd, NWPid)
   EnumWindows AddressOf EnumWinProc, 0
   GetTrayList = sTips
End Function

Private Function GetTrayNotifyWnd() As Long
   GetTrayNotifyWnd = FindWindowEx(FindWindow("Shell_TrayWnd", vbNullString), 0, "TrayNotifyWnd", vbNullString)
End Function

Function EnumWinProc(ByVal hWnd As Long, ByVal lParam As Long) As Long
   Dim pid As Long, tid As Long, lStyle As Long
   Dim hProcess As Long, nCount As Long, lWritten As Long, i As Long
   Dim lpSysShared As Long, hFileMapping As Long, dwSize As Long
   Dim lpSysShared2 As Long, hFileMapping2 As Long
   Dim ti As TOOLINFO
   Dim sTipText As String * 80
   tid = GetWindowThreadProcessId(hWnd, pid)
   lStyle = GetWindowLong(hWnd, GWL_STYLE)
   If pid = NWPid And GetWndClass(hWnd) = "tooltips_class32" And (lStyle And 2) <> 2 Then
      nCount = SendMessage(hWnd, TTM_GETTOOLCOUNT, 0&, ByVal 0&)
      ReDim sTips(nCount - 1)
      ti.cbSize = Len(ti)
      dwSize = ti.cbSize
      If IsWindowsNT Then
         lpSysShared = GetMemSharedNT(pid, dwSize, hProcess)
         lpSysShared2 = GetMemSharedNT(pid, LenB(sTipText), hProcess)
         WriteProcessMemory hProcess, ByVal lpSysShared, ti, dwSize, lWritten
         WriteProcessMemory hProcess, ByVal lpSysShared2, sTipText, LenB(sTipText), lWritten
         For i = 0 To nCount - 1
             Call SendMessage(hWnd, TTM_ENUMTOOLSW, i, ByVal lpSysShared)
             ReadProcessMemory hProcess, ByVal lpSysShared, ti, dwSize, lWritten
             ti.lpszText = lpSysShared2
             WriteProcessMemory hProcess, ByVal lpSysShared, ti, dwSize, lWritten
             Call SendMessage(hWnd, TTM_GETTEXT, 0&, ByVal lpSysShared)
             ReadProcessMemory hProcess, ByVal lpSysShared, ti, dwSize, lWritten
             sTips(i) = StrFromPtrW(ti.lpszText)
         Next i
         FreeMemSharedNT hProcess, lpSysShared, dwSize
         FreeMemSharedNT hProcess, lpSysShared2, LenB(sTipText)
      Else
         lpSysShared = GetMemShared95(dwSize, hFileMapping)
         lpSysShared2 = GetMemShared95(LenB(sTipText), hFileMapping2)
         CopyMemory ByVal lpSysShared, ti, dwSize
         CopyMemory ByVal lpSysShared2, sTipText, LenB(sTipText)
         For i = 0 To nCount - 1
             Call SendMessage(hWnd, TTM_ENUMTOOLSA, i, ByVal lpSysShared)
             CopyMemory ti, ByVal lpSysShared, dwSize
             ti.lpszText = lpSysShared2
             CopyMemory ByVal lpSysShared, ti, dwSize
             Call SendMessage(hWnd, TTM_GETTEXT, 0&, ByVal lpSysShared)
             CopyMemory ti, ByVal lpSysShared, dwSize
             sTips(i) = StrFromPtrA(ti.lpszText)
         Next i
         FreeMemShared95 hFileMapping, lpSysShared
         FreeMemShared95 hFileMapping2, lpSysShared2
      End If
      EnumWinProc = 0
      Exit Function
   End If
   EnumWinProc = 1
End Function

Private Function GetWndClass(hWnd As Long) As String
   Dim k As Long, sName As String
   sName = Space$(128)
   k = GetClassName(hWnd, sName, 128)
   If k > 0 Then sName = Left$(sName, k) Else sName = "No class"
   GetWndClass = sName
End Function

Private Function StrFromPtrA(ByVal lpszA As Long, Optional nSize As Long = 0) As String
   Dim s As String, bTrim As Boolean
   If nSize = 0 Then
      nSize = lstrlenA(lpszA)
      bTrim = True
   End If
   s = String(nSize, Chr$(0))
   CopyStringA s, ByVal lpszA
   If bTrim Then s = TrimNULL(s)
   StrFromPtrA = s
End Function

Private Function StrFromPtrW(ByVal lpszW As Long, Optional nSize As Long = 0) As String
   Dim s As String, bTrim As Boolean
   If nSize = 0 Then
      nSize = lstrlenW(lpszW)
      bTrim = True
   End If
   s = String(nSize, Chr$(0))
   CopyMemory ByVal StrPtr(s), ByVal lpszW, nSize
   If bTrim Then s = TrimNULL(s)
   StrFromPtrW = s
End Function

Private Function TrimNULL(ByVal str As String) As String
    If InStr(str, Chr$(0)) > 0& Then
        TrimNULL = Left$(str, InStr(str, Chr$(0)) - 1&)
    Else
        TrimNULL = str
    End If
End Function

'========Form code=======
'add label,ListBox and command button on form
Private Sub Command1_Click()
    Dim sTrayList As Variant
    sTrayList = GetTrayList
    Label1 = "Icons in tray: " & UBound(sTrayList) + 1
    List1.Clear
    For i = 0 To UBound(sTrayList)
        List1.AddItem i + 1 & ". " & sTrayList(i)
    Next i
End Sub

Private Sub Form_Load()
   Caption = "Who lives in the system tray?"
   Command1.Caption = "&Get List"
   Label1 = "Icons in tray: "
End Sub

'Not sure for NT (not tested). If tips text stored in ANSI, we should change StrFromPtrW to StrFromPtrA.

Tada!!

PS Can anybody test code on NT please?

Cheers
Need a couple constants:

MEM_RESERVE
MEM_COMMIT
ASKER CERTIFIED SOLUTION
Avatar of AzraSound
AzraSound
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Ark

ASKER

Hello Tony
I'm very glad you're fine. Hope all your family and friends are fine too.
God bless America.

Regards
Thanks Ark...luckily, I live in the southern U.S. and was not harmed, physically, by the attacks.  The friends of ours in N.Y. have contacted us and are ok.  At this time, we are simply taking it day by day and trying to recover physically, emotionally, and psychologically.  I thank you deeply for your kind words.
Is there anyway to enumerate a list with the name of the executable from which the icon in the systray comes from? Such as

1. Outlook.exe
2. Norton.exe

etc.
Avatar of Ark

ASKER

Working on this but still without success.
Hi Experts

I too have been looking into doing this, and unfortunately am very new to the API and its workings, I asked on:

https://www.experts-exchange.com/questions/21145831/Where-do-the-systray-items-come-from.html

Basically I just need to figure out how to make it work on W2k/WXP

I also need to be able to get the icon info, tooltip, context menu, parent application to be able to activate or close the running app

250 points up for grabs, if you wanna answer on the new question (Q_21145831)

Thanks