7

I'm trying to read the ToolTip text from the system tray for an application that is not my own. Basically just what I figure will be the easiest way to pull some status information.

What would be the easiest way to pull the ToolTip text using C#?

Adam Haile
  • 30,705
  • 58
  • 191
  • 286
  • I'd be interested in finding out if someone has a solution to this. I encountered it a year back but couldn't figure it. I tried methods like getting the main HWND of the app and retrieving the title but it didn't work for the app I was trying to access. Maybe it might be different for others. – Craig White Jun 16 '11 at 02:44
  • I think the problem your going to have with this, is that there are literally a TON of ways to show a tooltip, from custom windows controls, to standard windows messages, to even paint driven UI's, and what not. Do you have some more detail? Like what program it is, etc? –  Jun 17 '11 at 14:15
  • It's the Pandora One desktop Application. Unfortunately, it's an Adobe AIR app, however it does create a standard looking system tray icon which displays the currently playing track. Even better would actually be to get that Icon's context menu text, since it also has Playing/Paused status in that... which would be greatly helpful. – Adam Haile Jun 17 '11 at 15:42

2 Answers2

7

Let's start with finding systray window handle:

    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpClassName, string lpWindowName);

    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    static IntPtr GetSystemTrayHandle()
    {
        IntPtr hWndTray = FindWindow("Shell_TrayWnd", null);
        if (hWndTray != IntPtr.Zero)
        {
            hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "TrayNotifyWnd", null);
            if (hWndTray != IntPtr.Zero)
            {
                hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "SysPager", null);
                if (hWndTray != IntPtr.Zero)
                {
                    hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "ToolbarWindow32", null);
                    return hWndTray;
                }
            }
        }

        return IntPtr.Zero;
    }

Systray window is a toolbar class, you need to get information for a single icon:

    private static unsafe bool GetTBButton(IntPtr hToolbar, int i, ref TBBUTTON tbButton, ref string text, ref IntPtr ipWindowHandle)
    {
        // One page
        const int BUFFER_SIZE = 0x1000;

        byte[] localBuffer = new byte[BUFFER_SIZE];

        UInt32 processId = 0;
        UInt32 threadId = User32.GetWindowThreadProcessId(hToolbar, out processId);

        IntPtr hProcess = Kernel32.OpenProcess(ProcessRights.ALL_ACCESS, false, processId);
        if (hProcess == IntPtr.Zero) { Debug.Assert(false); return false; }

        IntPtr ipRemoteBuffer = Kernel32.VirtualAllocEx(
            hProcess,
            IntPtr.Zero,
            new UIntPtr(BUFFER_SIZE),
            MemAllocationType.COMMIT,
            MemoryProtection.PAGE_READWRITE);

        if (ipRemoteBuffer == IntPtr.Zero) { Debug.Assert(false); return false; }

        // TBButton
        fixed (TBBUTTON* pTBButton = &tbButton)
        {
            IntPtr ipTBButton = new IntPtr(pTBButton);

            int b = (int)User32.SendMessage(hToolbar, TB.GETBUTTON, (IntPtr)i, ipRemoteBuffer);
            if (b == 0) { Debug.Assert(false); return false; }

            // this is fixed
            Int32 dwBytesRead = 0;
            IntPtr ipBytesRead = new IntPtr(&dwBytesRead);

            bool b2 = Kernel32.ReadProcessMemory(
                hProcess,
                ipRemoteBuffer,
                ipTBButton,
                new UIntPtr((uint)sizeof(TBBUTTON)),
                ipBytesRead);

            if (!b2) { Debug.Assert(false); return false; }
        }

        // button text
        fixed (byte* pLocalBuffer = localBuffer)
        {
            IntPtr ipLocalBuffer = new IntPtr(pLocalBuffer);

            int chars = (int)User32.SendMessage(hToolbar, TB.GETBUTTONTEXTW, (IntPtr)tbButton.idCommand, ipRemoteBuffer);
            if (chars == -1) { Debug.Assert(false); return false; }

            // this is fixed
            Int32 dwBytesRead = 0;
            IntPtr ipBytesRead = new IntPtr(&dwBytesRead);

            bool b4 = Kernel32.ReadProcessMemory(
                hProcess,
                ipRemoteBuffer,
                ipLocalBuffer,
                new UIntPtr(BUFFER_SIZE),
                ipBytesRead);

            if (!b4) { Debug.Assert(false); return false; }

            text = Marshal.PtrToStringUni(ipLocalBuffer, chars);

            if (text == " ") text = String.Empty;
        }

        Kernel32.VirtualFreeEx(
            hProcess,
            ipRemoteBuffer,
            UIntPtr.Zero,
            MemAllocationType.RELEASE);

        Kernel32.CloseHandle(hProcess);

        return true;
    }

Now, all you have to do is iterate through buttons and get data:

        IntPtr _ToolbarWindowHandle = GetSystemTrayHandle();
        UInt32 count = User32.SendMessage(_ToolbarWindowHandle, TB.BUTTONCOUNT, 0, 0);

        for (int i = 0; i < count; i++)
        {
            TBBUTTON tbButton = new TBBUTTON();
            string text = String.Empty;
            IntPtr ipWindowHandle = IntPtr.Zero;

            bool b = GetTBButton(_ToolbarWindowHandle, i, ref tbButton, ref text, ref ipWindowHandle);

        }
NetMage
  • 26,163
  • 3
  • 34
  • 55
michalczerwinski
  • 1,069
  • 9
  • 6
  • For the sake of simplicity I omitted few PInvoke declarations, you should have no problems finding them on your own – michalczerwinski Jun 17 '11 at 21:30
  • Still trying to get this working... what is TB.GETBUTTON from? I cannot find the needed structure anywhere. – Adam Haile Jun 20 '11 at 19:08
  • Out of curiosity, any chance you know how to grab the text from the context menu of a specific SysTray icon? Also, is there any easy way with the above to get the tray icon handle for a specific application? – Adam Haile Jun 20 '11 at 19:39
  • I believe this can only work in Win7 if you select "Always show all icons and notifications on the taskbar" because otherwise the buttons are unavailable without the user opening the hidden icons. – Ben Bryant Jun 11 '15 at 14:02
  • 1
    Your GetTBButton takes a ref paramer ipWindowHandle but never seems to actually set or use it? – NetMage Mar 05 '16 at 00:14
3

In case anyone comes across this thread and has the same need, I posted a thread asking how to properly implement the code example and received a lot of help, and a working solution here:

Trouble implementing code example using PInvoke Declarations

Jason O
  • 753
  • 9
  • 28