Page 1 of 1

WinAPI: Shell_NotifyIcon_() experimental testcode

Posted: Sat Nov 11, 2023 5:28 pm
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() 

Re: WinAPI: Shell_NotifyIcon_() experimental testcode

Posted: Mon Nov 13, 2023 12:01 pm
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

Re: WinAPI: Shell_NotifyIcon_() experimental testcode

Posted: Mon Nov 20, 2023 11:41 am
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.