WinAPI: Shell_NotifyIcon_() experimental testcode

Share your advanced PureBasic knowledge/code with the community.
Axolotl
Addict
Addict
Posts: 837
Joined: Wed Dec 31, 2008 3:36 pm

WinAPI: Shell_NotifyIcon_() experimental testcode

Post by Axolotl »

I got curious about this topic after reading a forum post.
Before I delete it, I'll make a quick backup here. :mrgreen:

Code: Select all

; --- update V0.1 : changed the NOTIFYICONDATA structure to receive more events 
;
EnableExplicit 

; Define an extended version of NOTIFYICONDATA 
; 
Structure NOTIFYICONDATA_2 Align #PB_Structure_AlignC   ; typedef struct _NOTIFYICONDATAW {
  cbSize.l                                              ;   DWORD cbSize;
  hWnd.i                                                ;   HWND  hWnd;
  uID.l                                                 ;   UINT  uID;
  uFlags.l                                              ;   UINT  uFlags;
  uCallbackMessage.l                                    ;   UINT  uCallbackMessage;
  hIcon.i                                               ;   HICON hIcon;
; If OSVersion() <= #PB_OS_Windows_2000                       ; #if ...
;   szTip.s{64}                                         ;   WCHAR szTip[64];
; Else                                                  ; #else
    szTip.s{128}                                        ;   WCHAR szTip[128];
; EndIf                                                 ; #endif
  dwState.l                                             ;   DWORD dwState;
  dwStateMask.l                                         ;   DWORD dwStateMask;
  szInfo.s{256}                                         ;   WCHAR szInfo[256];
  StructureUnion                                        ;   union {
    uTimeout.l                                          ;     UINT uTimeout;
    uVersion.l                                          ;     UINT uVersion;
  EndStructureUnion                                     ;   } DUMMYUNIONNAME;
  szInfoTitle.s{64}                                     ;   WCHAR szInfoTitle[64];
  dwInfoFlags.l                                         ;   DWORD dwInfoFlags;

  ; different behavior 
  guidItem.GUID                                         ;   GUID  guidItem;
  hBalloonIcon.l                                        ;   HICON hBalloonIcon;
EndStructure                                            ; } NOTIFYICONDATAW, *PNOTIFYICONDATAW;

; ; 
; ; --- update V0.1 
; ; with these members we lost some messages (#NIN_POPUPOPEN and #NIN_POPUPCLOSE) 
; ; 
; Structure NOTIFYICONDATA_3 Extends NOTIFYICONDATA_2 Align #PB_Structure_AlignC 
;  ;                                                     ; typedef struct _NOTIFYICONDATAW { 
;  ;
;  guidItem.GUID                                         ;   GUID  guidItem;
;  hBalloonIcon.l                                        ;   HICON hBalloonIcon;
; EndStructure                                           ; } NOTIFYICONDATAW, *PNOTIFYICONDATAW;

; Define UI constants 
;
Enumeration EWindow 
  #WINDOW 
EndEnumeration

Enumeration EGadget 
  #GADGET_btnAdd 
  #GADGET_btnDelete 

  #GADGET_btnShowInfo 
  #GADGET_btnShowError  
  #GADGET_btnShowWarn 

  #GADGET_btnHide

  #GADGET_lstTrace 
EndEnumeration

Enumeration EEvent #PB_Event_FirstCustomValue  
  #EVENT_Begin 
  #EVENT_Finished 
EndEnumeration

Enumeration EShow 
  #ShowInfo 
  #ShowError  
  #ShowWarn 
EndEnumeration

; Define event constants and others 
; 
#WM_APP_ICONNOTIFY = #WM_APP + 1  
; 
#APP_NOTIFYICON_ID  = 1 


; Define uFlags .. missing definitions 
; 
#NIF_MESSAGE  = $00000001   ; (0x00000001)  0x00000001. The uCallbackMessage member is valid.
#NIF_ICON     = $00000002   ; (0x00000002)  0x00000002. The hIcon member is valid.
#NIF_TIP      = $00000004   ; (0x00000004)  0x00000004. The szTip member is valid.
#NIF_STATE    = $00000008   ; (0x00000008)  0x00000008. The dwState And dwStateMask members are valid.
#NIF_INFO     = $00000010   ; (0x00000010)  0x00000010. Display a balloon notification. The szInfo, szInfoTitle, dwInfoFlags, And uTimeout members are valid. Note that uTimeout is valid only in Windows 2000 And Windows XP.
;     To display the balloon notification, specify NIF_INFO And provide text in szInfo.
;     To remove a balloon notification, specify NIF_INFO And provide an empty string through szInfo.
;     To add a notification area icon without displaying a notification, do Not set the NIF_INFO flag.
#NIF_GUID     = $00000020   ; (0x00000020)  0x00000020.     Windows 7 And later: The guidItem is valid.    Windows Vista And earlier: Reserved.
#NIF_REALTIME = $00000040   ; (0x00000040)  0x00000040. Windows Vista And later. If the balloon notification cannot be displayed immediately, discard it. Use this flag For notifications that represent real-time information which would be meaningless Or misleading If displayed at a later time. For example, a message that states "Your telephone is ringing." NIF_REALTIME is meaningful only when combined With the NIF_INFO flag.
#NIF_SHOWTIP  = $00000080   ; (0x00000080)  0x00000080. Windows Vista And later. Use the standard tooltip. Normally, when uVersion is set To NOTIFYICON_VERSION_4, the standard tooltip is suppressed And can be replaced by the application-drawn, pop-up UI. If the application wants To show the standard tooltip With NOTIFYICON_VERSION_4, it can specify NIF_SHOWTIP To indicate the standard tooltip should still be shown. 

; Maybe we have an Application Icon 
;
Global g_hAppIcon = 0 

Declare Trace(Message.s) 


Procedure ShowBalloon(Title.s, Info.s, Show=#ShowInfo, NoSound=#False) 
  Protected nid.NOTIFYICONDATA_2 

  nid\cbSize = SizeOf(NOTIFYICONDATA_2)  
  nid\hWnd   = WindowID(#WINDOW) 
  nid\uID    = #APP_NOTIFYICON_ID 
  nid\uFlags = #NIF_INFO

  Select Show 
    Case #ShowInfo  : nid\dwInfoFlags = #NIIF_INFO 
    Case #ShowError : nid\dwInfoFlags = #NIIF_ERROR 
    Case #ShowWarn  : nid\dwInfoFlags = #NIIF_WARNING 
  EndSelect 

  If NoSound 
    nid\dwInfoFlags | #NIIF_NOSOUND 
  EndIf 

  nid\szInfo      = Info 
  nid\szInfoTitle = Title 
  nid\uTimeout = 2 ; s ? this does not work, because system setting is used always! 

  ProcedureReturn Shell_NotifyIcon_(#NIM_MODIFY, nid) 
EndProcedure 

Procedure ChangeTooltip(TipText.s="")   ; Empty TipText hides the tooltip 
  Protected nid.NOTIFYICONDATA_2 

  nid\cbSize      = SizeOf(NOTIFYICONDATA_2) 
  nid\hWnd        = WindowID(#WINDOW) 
  nid\uID         = #APP_NOTIFYICON_ID 
  nid\uFlags      = #NIF_TIP | #NIF_SHOWTIP | #NIF_INFO 
  nid\dwInfoFlags = #NIIF_INFO 
  nid\szTip       = TipText 
  
  ProcedureReturn Shell_NotifyIcon_(#NIM_MODIFY, nid) 
EndProcedure 

Procedure AddNotificationIcon()
  Protected nid.NOTIFYICONDATA_2 

  nid\cbSize      = SizeOf(NOTIFYICONDATA_2) 
  nid\hWnd        = WindowID(#WINDOW) 
  nid\uID         = #APP_NOTIFYICON_ID 
  nid\uFlags      = #NIF_TIP | #NIF_SHOWTIP | #NIF_MESSAGE | #NIF_ICON 
  nid\dwInfoFlags = #NIIF_INFO 

  nid\uCallbackMessage = #WM_APP_ICONNOTIFY   ; <-- self defined event for the callback procedure 

  If g_hAppIcon 
    nid\hIcon = g_hAppIcon 
  Else 
    nid\hIcon = LoadIcon_(0, #IDI_INFORMATION) ; <- plan-b :) 
  EndIf 

  nid\szTip = "Some Tooltip for this beautiful piece of code." 
  Shell_NotifyIcon_(#NIM_ADD, nid) 

  nid\uVersion = 4 ; #NOTIFYICON_VERSION_4  ; == 4 .. Use the current behavior. 
  Shell_NotifyIcon_(#NIM_SETVERSION, nid) 

  ProcedureReturn 1   ; 
EndProcedure 

Procedure DeleteNotificationIcon() 
  Protected nid.NOTIFYICONDATA_2 

  nid\cbSize = SizeOf(NOTIFYICONDATA_2) 
  nid\hWnd   = WindowID(#WINDOW) 
  nid\uID    = #APP_NOTIFYICON_ID 

  ProcedureReturn Shell_NotifyIcon_(#NIM_DELETE, nid) 
EndProcedure 

; 
; 
Procedure WindowCallback(hWnd, uMsg, wParam, lParam)
  Select uMsg 
    Case #WM_APP_ICONNOTIFY   
      Select (lParam & $FFFF) 
        Case #NIN_BALLOONTIMEOUT
          Trace("-> Balloon timed out or was closed by the user") 
          PostEvent(#EVENT_Finished) 
        
        Case #NIN_BALLOONUSERCLICK
          Trace("-> Balloon got clicked by the user") 
        
        Case #NIN_BALLOONHIDE
          Trace("-> Balloon got hidden")
        
        Case #NIN_SELECT 
          Trace("-> User has clicked the Icon")
        
        Case 1026 ; ???? 
          Trace("-> An event for what ...")

        Case 1030 ; #NIN_POPUPOPEN ; 
          Trace("-> Mouse enters the Icon Area ")

        Case 1031 ; #NIN_POPUPCLOSE ; 
          Trace("-> Mouse leaves the Icon Area ")

        Case #WM_MOUSEMOVE 
          Trace("-> mouse move on Icon ")

        Case #WM_LBUTTONDOWN, #WM_LBUTTONDBLCLK, #WM_RBUTTONDOWN, #WM_RBUTTONUP     
          Trace("-> Mouse Button Events (we do not need)")
          ; eat this message (because of default shows undetected messages ...)  
        
        Case #WM_CONTEXTMENU  ; <-- on Right Mouse Click 
          Trace("-> Context Menu ... ")

        Case #WM_LBUTTONUP 
          Trace("-> Systrayicon got clicked.") 
      
        Default ; <-- which messages are received by this 
          Trace("-> Default with LOWORD(lParam) ==  " + Str(lParam & $FFFF))

      EndSelect ; (lParam & $FFFF) 
  EndSelect ; uMsg 

  ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure

; --- 

Procedure Trace(Message.s) 
  Static s_lastMessage.s, s_count = 0, s_index = 0 

  If Message = s_lastMessage 
    s_count + 1 
    SetGadgetItemText(#GADGET_lstTrace, s_index, s_lastMessage + "(" + s_count + ")") 
  Else ; If Message <> s_lastMessage 
    s_count = 0   
    s_index + 1 
    AddGadgetItem(#GADGET_lstTrace, s_index, Message) 
    SetGadgetState(#GADGET_lstTrace, s_index) 
    s_lastMessage = Message 
  EndIf 
  ;Debug "TRACE[" + RSet(Str(s_index), 4) + "]: " + Message 
EndProcedure 

Procedure main() 
  Protected title.s, info.s 

  If OpenWindow(#WINDOW, 0, 0, 308+176, 232, "Icon Test", #PB_Window_SystemMenu|#PB_Window_ScreenCentered) 
    StickyWindow(#WINDOW, 1)  ; <--- I want this window on top (for test reasons) .. should not hide behinde the IDE 

    ButtonGadget(#GADGET_btnAdd,    8,   8, 160, 24, "Add NotifiyIcon")
    ButtonGadget(#GADGET_btnDelete, 8,  40, 160, 24, "Delete NotifiyIcon")

    ButtonGadget(#GADGET_btnShowInfo,  8,  80, 160, 24, "Show Info Message")
    ButtonGadget(#GADGET_btnShowError, 8, 112, 160, 24, "Show Error Message")
    ButtonGadget(#GADGET_btnShowWarn,  8, 144, 160, 24, "Show Warn Message")
    ButtonGadget(#GADGET_btnHide,      8, 176, 160, 24, "Hide Balloon Message")

    ListViewGadget(#GADGET_lstTrace, 176, 8, 300, 216, $4000) ; #LBS_NOSEL  
    
    ; You might wanna specify a path which corresponds to any valid .ico file on your system.
    g_hAppIcon = LoadImage(0, #PB_Compiler_Home + "\Examples\Sources - Advanced\Waponez II\Waponez.ico") 

    SetWindowCallback(@WindowCallback(), #WINDOW) 

    AddNotificationIcon() 
    DisableGadget(#GADGET_btnAdd, 1) 
    AddGadgetItem(#GADGET_lstTrace, -1, "Show Notification Events:") 

    Repeat
      Select WaitWindowEvent()
        Case #PB_Event_CloseWindow  ; <---------------------------------------- Event 
          Break  ; say good bye 

        Case #EVENT_Begin  ; <------------------------------------------------- Event
          DisableGadget(#GADGET_btnShowInfo, 1) 
          DisableGadget(#GADGET_btnShowError, 1) 
          DisableGadget(#GADGET_btnShowWarn, 1) 

        Case #EVENT_Finished  ; <---------------------------------------------- Event
          DisableGadget(#GADGET_btnShowInfo, 0) 
          DisableGadget(#GADGET_btnShowError, 0) 
          DisableGadget(#GADGET_btnShowWarn, 0) 

        Case #PB_Event_Gadget  ; <--------------------------------------------- Event
          Select EventGadget()
            Case #GADGET_btnAdd   
              DisableGadget(#GADGET_btnAdd, 1) 
              DisableGadget(#GADGET_btnDelete, 0) 
              AddNotificationIcon() 

            Case #GADGET_btnDelete 
              DisableGadget(#GADGET_btnAdd, 0) 
              DisableGadget(#GADGET_btnDelete, 1) 
              DeleteNotificationIcon() 

            Case #GADGET_btnShowInfo  
              PostEvent(#EVENT_Begin) 
              title = "This is a balloon information." 
              info  = "This is some example text being displayed in a balloon." + #LF$ + #LF$ + "(c) 2023 nice stuff."   
              ShowBalloon(title, info, #ShowInfo, #True) 

            Case #GADGET_btnShowError 
              PostEvent(#EVENT_Begin) 
              title = "This is the balloon error." 
              info  = "This is some example text being displayed in a balloon."  + #LF$ + #LF$ + "Ohh, this is not good."  
              ShowBalloon(title, info, #ShowError) ; <-- with ballon with sound !!

            Case #GADGET_btnShowWarn  
              PostEvent(#EVENT_Begin) 
              title = "This is the balloon warning." 
              info  = "This is some example text being displayed in a balloon." + #LF$ + #LF$ + "Relax -- Nothing is under control."  
              ShowBalloon(title, info, #ShowWarn, #True) 

            Case #GADGET_btnHide 
              PostEvent(#EVENT_Finished) 
              Debug "MAINLOOP:  Hide Tooltip " 
              ChangeTooltip("")   ; <-- hide the ballon 

          EndSelect ; EventGadget() 

        Case #PB_Event_SysTray  ; <-------------------------------------------- Event
          Debug "MAINLOOP: SysTray Event " 

      EndSelect ; WaitWindowEvent() 
    ForEver ; repeat until #PB_Event_CloseWindow 
  EndIf ; OpenWindow() 
  ProcedureReturn 0 
EndProcedure 

End main() 
Last edited by Axolotl on Mon Nov 20, 2023 11:42 am, edited 2 times in total.
Just because it worked doesn't mean it works.
PureBasic 6.04 (x86) and <latest stable version and current alpha/beta> (x64) on Windows 11 Home. Now started with Linux (VM: Ubuntu 22.04).
Axolotl
Addict
Addict
Posts: 837
Joined: Wed Dec 31, 2008 3:36 pm

Re: WinAPI: Shell_NotifyIcon_() experimental testcode

Post by Axolotl »

I made a small update on the above code....
Now there are to more events received by the callback (#NIN_POPUPOPEN and #NIN_POPUPCLOSE)
It doesn't matter whether the icon is in the flywindow for hidden icons or in the systray.

HINT:
if you want change the visibility of the icon, you have to use the registry directly:

Code: Select all

HKEY_CURRENT_USER\Control Panel\NotifyIconSettings\<[b]UniqueAppID[/b]>\IsPromoted [REG_DWWORD 0 or 1] 
Where IsPromoted = 1 means the Icon of the app is visible in the systray directly and not in the fly window (menu).
Example Code
Just because it worked doesn't mean it works.
PureBasic 6.04 (x86) and <latest stable version and current alpha/beta> (x64) on Windows 11 Home. Now started with Linux (VM: Ubuntu 22.04).
Axolotl
Addict
Addict
Posts: 837
Joined: Wed Dec 31, 2008 3:36 pm

Re: WinAPI: Shell_NotifyIcon_() experimental testcode

Post by Axolotl »

I checked my code above with the latest PB V6.10 (Fred provided this single exe in some thread).
I have built the structure exactly as msdn specifies and it worked perfectly, including the updated registry entry on Win11 22H2 et seq.
Just because it worked doesn't mean it works.
PureBasic 6.04 (x86) and <latest stable version and current alpha/beta> (x64) on Windows 11 Home. Now started with Linux (VM: Ubuntu 22.04).
Post Reply