Page 1 of 1

Mouse Enter/Leave events for systray icons

Posted: Sat Aug 01, 2009 11:48 pm
by netmaestro
I read the coding question here: http://www.purebasic.fr/english/viewtopic.php?t=38276

and figured it wouldn't be too difficult to monitor the mouse in/out of systray icons. Turns out it was a bit more involved than I had thought. Processing the mousemove message sent automatically was simple enough, but how to know when the mouse leaves? Nonetheless I believe I have something usable:

Code: Select all

;======================================================================
; Library:           MonitorSysTrayMouseEvents
; Author:            Lloyd Gallant (netmaestro)
; Date:              August 1, 2009
; Target OS:         Microsoft Windows XP (maybe more)
; Target Compiler:   PureBasic 4.31 and later 
; License:           Unrestricted, no warranty expressed or implied
;======================================================================

#MSG_ST_MOUSEENTER = #WM_APP+1
#MSG_ST_MOUSELEAVE = #WM_APP+2

Structure TRAYICONRECT
  id.l
  loc.RECT
  hot.b
EndStructure

NewList pbTrayButton.TRAYICONRECT()

Procedure Find_PB_TrayIcons()

  Structure TRAYDATA
    hwnd.l                
    uID.l             
    uCallbackMessage.l    
    Reserved.l[2]      
    hIcon.l                
  EndStructure
  
  Shared pbTrayButton()
  
  ; Find the System Tray Icon Toolbar
  Protected hwnd
  hWnd = FindWindow_("Shell_TrayWnd", #Null)
  If hWnd
    hWnd = FindWindowEx_(hWnd, #Null, "TrayNotifyWnd", #Null)
    If hWnd
      hWnd = FindWindowEx_(hWnd,#Null, "SysPager", #Null)
      If hWnd 
        hTray = FindWindowEx_(hWnd, #Null, "ToolbarWindow32", #Null)
      Else
        ProcedureReturn 0
      EndIf
    Else
      ProcedureReturn 0
    EndIf
  Else
    ProcedureReturn 0
  EndIf
  
  ; If we're still here we found the systray toolbar
  count = SendMessage_(hTray, #TB_BUTTONCOUNT, 0, 0)
  
  Dim button.TBBUTTON   (count)
  Dim button_td.TRAYDATA(count)
  
  ; Put it in the can opener 
  dwExplorerThreadId=GetWindowThreadProcessId_(hTray, @dwExplorerProcessId)
  hProc = OpenProcess_(#PROCESS_ALL_ACCESS, #False, dwExplorerProcessId)
  *lpData = VirtualAllocEx_(hProc, #Null, SizeOf(TBBUTTON)*count, #MEM_COMMIT, #PAGE_READWRITE)
  *lpRect = VirtualAllocEx_(hProc, #Null, SizeOf(RECT)*count, #MEM_COMMIT, #PAGE_READWRITE)
  
  ; Enumerate the systray buttons, ignore all but our own
  For i = 0 To count-1
    SendMessage_(hTray, #TB_GETBUTTON, i, *lpData+i*SizeOf(TBBUTTON) )
    ReadProcessMemory_(hProc, *lpData+(i*SizeOf(TBBUTTON)), @button.TBBUTTON(i), SizeOf(TBBUTTON), #Null)
    ReadProcessMemory_(hProc, button(i)\dwData, @button_td.TRAYDATA(i), SizeOf(TRAYDATA), #Null)
    
    ; Is it one of ours?     
    If button_td(i)\uCallbackMessage = 12501 ; PB team-chosen callback messageid, if true it belongs to us
      AddElement(pbTrayButton())
      SendMessage_(hTray, #TB_GETITEMRECT, i, *lpRect+i*SizeOf(RECT))
      ReadProcessMemory_(hProc, *lpRect+(i*SizeOf(RECT)), @pbTrayButton()\loc, SizeOf(RECT), #Null)
      pbTraybutton()\id = button_td(i)\uid
      MapWindowPoints_(hTray, 0, pbTrayButton()\loc,2)
    EndIf
   
  Next
  
  ; Wipe off the can opener and put it away
  VirtualFreeEx_(hProc, *lpData, #Null, #MEM_RELEASE)
  VirtualFreeEx_(hProc, *lpRect, #Null, #MEM_RELEASE)
  CloseHandle_(hProc)
  
  ; Return the number of our Tray icons found and processed
  ProcedureReturn ListSize(pbTrayButton())
  
EndProcedure

Procedure MonitorTray(window)
  Shared pbTrayButton()
  If Find_PB_TrayIcons()
    Repeat
      ForEach pbTrayButton()
        GetCursorPos_(@cp.POINT)
        If PtInRect_(pbTrayButton()\loc, cp\x|(cp\y<<32))
          If Not pbTrayButton()\hot
            pbTrayButton()\hot = 1
            If IsWindow_(window)
              PostMessage_(window, #MSG_ST_MOUSEENTER, pbTrayButton()\id, 0)
            EndIf
          EndIf
        Else
          If pbTrayButton()\hot
            pbTrayButton()\hot = 0
            If IsWindow_(window)
              PostMessage_(window, #MSG_ST_MOUSELEAVE, pbTrayButton()\id, 0)
            EndIf
          EndIf  
        EndIf
        Delay(1)
      Next
    ForEver
  EndIf
EndProcedure
  
Procedure ProcessSystrayMouseMessages(window)
  CreateThread(@MonitorTray(),window)
EndProcedure
A little test prog:

Code: Select all

IncludeFile "MonitorSysTrayMouseEvents.pbi"

OpenWindow(0, 100, 150, 300, 100, "PureBasic - SysTray Example", #PB_Window_SystemMenu)

IconName$ = #PB_Compiler_Home+"examples\sources\data\CdPlayer.ico"
  
AddSysTrayIcon(0, WindowID(0), LoadImage(0, IconName$))
AddSysTrayIcon(1, WindowID(0), LoadImage(0, IconName$))
SysTrayIconToolTip(1, "Icon 1")

ProcessSystrayMouseMessages(WindowID(0))

Repeat
  EventID = WaitWindowEvent()
  
  Select EventID
    Case #MSG_ST_MOUSEENTER
      Debug "Entered Systray Icon " + Str(EventwParam())
    
    Case #MSG_ST_MOUSELEAVE
      Debug "Left Systray Icon " + Str(EventwParam())
  EndSelect
  
Until EventID = #PB_Event_CloseWindow
If you have two systray icons, you may get an enter message before you get the leave message from the other one. But you'll always get it.

Tested on XP SP2 only. If it works on other versions, please let me know.

Posted: Sun Aug 02, 2009 1:57 am
by SFSxOI
Darn it! You beat me to it. :)

Not really...I was working along the same lines for the same thread question as I had an interest in this also and then your code showed up saving what is for me days of work. So I scrapped mine, gonna use yours.

Works here on Vista Ultimate 32 bit just fine. :)

Thank You very much :)

Posted: Sun Aug 02, 2009 2:03 am
by rsts
Help me out here, netmaestro :)

What's supposed to be happening, cause I'm not getting anything :(

Win 7 rtm x64 PB 4.3 32 bit

cheers

Posted: Sun Aug 02, 2009 4:18 am
by RASHAD
Hello big brother
Your old code in the thread refered to by Tomi is working fine
with win 7 ultimate x64

The new one no

I just want to say hello
have a good day my (friend) I hope you accept my friendship

RASHAD

Posted: Sun Aug 02, 2009 6:07 am
by netmaestro
It's supposed to debug a message when you enter the systray icon with the mouse and another one when you leave. I tested it on Win XP but it may not work on win7. I'm not currently testing win7 anymore so I can't really try to find out what's failing there. But it's good news that it's working for SFSxOI on Vista. I still haven't figured out why my QuickSnap program won't work on vista and that's been years!

Wait a second, you guys, it's 64bit Win7 in both failures. I didn't write it targeting x64 at all so I imagine it won't be too difficult to modify. Could someone with x64 take a look at the code and make some adjustments, perhaps get it working for both win32 and x64? It would be much appreciated.

Posted: Sun Aug 02, 2009 2:40 pm
by lexvictory
For x64 all that is needed is the hwnd member of traydata to be changed to a .i
But I went a step further, and managed to get it to work as a 32 bit process (running under wow64) - which I didn't expect to work! (and its not 100% guaranteed to work all the time)
Tested on Windows 7 x64
Not optimised; every call to Is64Bit() calls GetProcAddress_()!

Code: Select all

;======================================================================
; Library:           MonitorSysTrayMouseEvents
; Author:            Lloyd Gallant (netmaestro)
; Date:              August 1, 2009
; Target OS:         Microsoft Windows XP (maybe more)
; Target Compiler:   PureBasic 4.31 and later
; License:           Unrestricted, no warranty expressed or implied
;======================================================================

Procedure Is64Bit()
Protected bIsWow64.l = #False, fnIsWow64Process
 
  fnIsWow64Process = GetProcAddress_(GetModuleHandle_("kernel32"), "IsWow64Process")
 
  If fnIsWow64Process ; Windows XP +
    If Not CallFunctionFast(fnIsWow64Process, GetCurrentProcess_(), @bIsWow64)
      ; handle error
    EndIf
  EndIf
 
  ProcedureReturn bIsWow64 
EndProcedure 

#MSG_ST_MOUSEENTER = #WM_APP+1
#MSG_ST_MOUSELEAVE = #WM_APP+2

Structure TRAYICONRECT
  id.i
  loc.RECT
  hot.b
EndStructure

NewList pbTrayButton.TRAYICONRECT()

Procedure Find_PB_TrayIcons()

  Structure TRAYDATA
    hwnd.i
    uID.l             
    uCallbackMessage.l
    Reserved.l[2]     
    hIcon.i           
  EndStructure
  Structure TRAYDATA_wow64
    hwnd.q
    uID.l             
    uCallbackMessage.l
    Reserved.l[2]     
    hIcon.i           
  EndStructure
  Structure TBBUTTON_wow64
    iBitmap.l
    idCommand.l
    fsState.b
    fsStyle.b
    bReserved.b[6]     ;// padding for alignment
    dwData.q 
    iString.q
  EndStructure

 
  Shared pbTrayButton()
 
  ; Find the System Tray Icon Toolbar
  Protected hwnd
  hWnd = FindWindow_("Shell_TrayWnd", #Null)
  If hWnd
    hWnd = FindWindowEx_(hWnd, #Null, "TrayNotifyWnd", #Null)
    If hWnd
      hWnd = FindWindowEx_(hWnd,#Null, "SysPager", #Null)
      If hWnd
        hTray = FindWindowEx_(hWnd, #Null, "ToolbarWindow32", #Null)
      Else
        ProcedureReturn 0
      EndIf
    Else
      ProcedureReturn 0
    EndIf
  Else
    ProcedureReturn 0
  EndIf
 
  ; If we're still here we found the systray toolbar
  count = SendMessage_(hTray, #TB_BUTTONCOUNT, 0, 0)
 
  
  if Is64Bit()
    Dim button_td2.TRAYDATA_wow64(count)
    Dim button2.TBBUTTON_wow64   (count)
  else 
    Dim button_td.TRAYDATA(count)
    Dim button.TBBUTTON   (count)
  endif 
 
  ; Put it in the can opener
  dwExplorerThreadId=GetWindowThreadProcessId_(hTray, @dwExplorerProcessId)
  hProc = OpenProcess_(#PROCESS_ALL_ACCESS, #False, dwExplorerProcessId)
  if Is64Bit()
    *lpData = VirtualAllocEx_(hProc, #Null, SizeOf(TBBUTTON_wow64)*count, #MEM_COMMIT, #PAGE_READWRITE)
  Else
    *lpData = VirtualAllocEx_(hProc, #Null, SizeOf(TBBUTTON)*count, #MEM_COMMIT, #PAGE_READWRITE)
  Endif
  *lpRect = VirtualAllocEx_(hProc, #Null, SizeOf(RECT)*count, #MEM_COMMIT, #PAGE_READWRITE)
 
  ; Enumerate the systray buttons, ignore all but our own
  For i = 0 To count-1
    if Is64Bit()
      SendMessage_(hTray, #TB_GETBUTTON, i, *lpData+i*SizeOf(TBBUTTON_wow64) )
      ReadProcessMemory_(hProc, *lpData+(i*SizeOf(TBBUTTON_wow64)), @button2.TBBUTTON_wow64(i), SizeOf(TBBUTTON_wow64), #Null)
      ReadProcessMemory_(hProc, button2(i)\dwData, @button_td2.TRAYDATA_wow64(i), SizeOf(TRAYDATA_wow64), #Null)
    else 
      SendMessage_(hTray, #TB_GETBUTTON, i, *lpData+i*SizeOf(TBBUTTON) )
      ReadProcessMemory_(hProc, *lpData+(i*SizeOf(TBBUTTON)), @button.TBBUTTON(i), SizeOf(TBBUTTON), #Null)
      ReadProcessMemory_(hProc, button(i)\dwData, @button_td.TRAYDATA(i), SizeOf(TRAYDATA), #Null)
    endif 
   
    ; Is it one of ours?     
    if Is64Bit()
      compareto = button_td2(i)\uCallbackMessage
    else
      compareto = button_td(i)\uCallbackMessage
    endif 
    If compareto = 12501 ; PB team-chosen callback messageid, if true it belongs to us
      AddElement(pbTrayButton())
      SendMessage_(hTray, #TB_GETITEMRECT, i, *lpRect+i*SizeOf(RECT))
      ReadProcessMemory_(hProc, *lpRect+(i*SizeOf(RECT)), @pbTrayButton()\loc, SizeOf(RECT), #Null)
      if Is64Bit()
        pbTraybutton()\id = button_td2(i)\uid
      Else
        pbTraybutton()\id = button_td(i)\uid
      endif 
      MapWindowPoints_(hTray, 0, pbTrayButton()\loc,2)
    EndIf
   
  Next
 
  ; Wipe off the can opener and put it away
  VirtualFreeEx_(hProc, *lpData, #Null, #MEM_RELEASE)
  VirtualFreeEx_(hProc, *lpRect, #Null, #MEM_RELEASE)
  CloseHandle_(hProc)
 
  ; Return the number of our Tray icons found and processed
  ProcedureReturn ListSize(pbTrayButton())
 
EndProcedure

Procedure MonitorTray(window)
  Shared pbTrayButton()
  If Find_PB_TrayIcons()
    Repeat
      ForEach pbTrayButton()
        GetCursorPos_(@cp.POINT)
        If PtInRect_(pbTrayButton()\loc, cp\x|(cp\y<<32))
          If Not pbTrayButton()\hot
            pbTrayButton()\hot = 1
            If IsWindow_(window)
              PostMessage_(window, #MSG_ST_MOUSEENTER, pbTrayButton()\id, 0)
            EndIf
          EndIf
        Else
          If pbTrayButton()\hot
            pbTrayButton()\hot = 0
            If IsWindow_(window)
              PostMessage_(window, #MSG_ST_MOUSELEAVE, pbTrayButton()\id, 0)
            EndIf
          EndIf 
        EndIf
        Delay(1)
      Next
    ForEver
  EndIf
EndProcedure
 
Procedure ProcessSystrayMouseMessages(window)
  CreateThread(@MonitorTray(),window)
EndProcedure

Posted: Sun Aug 02, 2009 3:05 pm
by rsts
lexvictory wrote:For x64 all that is needed is the hwnd member of traydata to be changed to a .i
Was the first thing I tried.

Still not working here.

Win 7x64 PB 32 bit

cheers

Posted: Sun Aug 02, 2009 9:44 pm
by netmaestro
Thanks lexvictory! It still works fine here on XP.

@rsts, if it's working elsewhere on Win7 x64, there could be something in your setup that's blocking it. Possibly try requesting Administrator mode for Vista, that kind of thing. I don't know for sure, but if another win7 machine will run it, yours could be made to somehow.

Found a problem with it though, if you remove a systray icon and yours gets repositioned, the thread doesn't know where it is. That's no good.

Working on it...

Posted: Mon Aug 03, 2009 3:35 am
by lexvictory
rsts wrote:Win 7x64 PB 32 bits
That's why it didn't work; it might not have been clear, but the changing of the hwnd works only for a 64 bit process.
Did you try my wow64 modifications? (but as stated, may not work due to using a quad as the base reading address for ReadProcessMemory, when on 32bits should be a long)

Posted: Mon Aug 03, 2009 4:46 am
by RASHAD
@rsts
good morning
lexvictory version works fine just do the following
go to
Start ---> properties --> taskbar
Notifaction Area --> Customize
Check always show all icons .......
ok

have a good day my friend

RASHAD