Make Application Icon visible with Systray (Windows only)

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

Make Application Icon visible with Systray (Windows only)

Post by Axolotl »

If you use the AddSystrayIcon() in your application your Icon is hidden in the systray menu window on default.
There is a (smart) way of making the icon visible with the help of the registry.

See this small example base on the Help AddSysTrayIcon() example.

Code: Select all

; 
; Visibility of the Application Icon if we use the Systray 
; Version: 0.1 
; 
; Works on Windows 11 
; 

; ---------------------------------------------------------------------------------------------------------------------

Procedure SetNotifyIconIsPromoted(TopKey, sKeyName.s, State, ComputerName.s = "") ; State == 0 or 1 
  Protected hKey, lhRemoteRegistry, r1, ret_value 
  Protected lpData.s{256}, lpcbData, lValue 
 
  sKeyName = Trim(sKeyName) 

  If ComputerName = ""
    r1 = RegOpenKeyEx_(TopKey, sKeyName, 0, #KEY_ALL_ACCESS, @hKey)
  Else
    r1 = RegConnectRegistry_(ComputerName, TopKey, @lhRemoteRegistry)
    If r1 = #ERROR_SUCCESS
      r1 = RegOpenKeyEx_(lhRemoteRegistry, sKeyName, 0, #KEY_ALL_ACCESS, @hKey)
    EndIf 
  EndIf
 
  If r1 = #ERROR_SUCCESS
    If State = 0 Or State = 1   ; check the parameter 
      r1 = RegSetValueEx_(hKey, "IsPromoted", 0, #REG_DWORD, @State, 4) 
    EndIf 

    If r1 = #ERROR_SUCCESS
      ret_value = #True
    EndIf
  EndIf
 
  RegCloseKey_(hKey) 

  If lhRemoteRegistry 
    RegCloseKey_(lhRemoteRegistry)
  EndIf
 
  ProcedureReturn ret_value 
EndProcedure

; ---------------------------------------------------------------------------------------------------------------------

Procedure GetNotifyIconIsPromoted(TopKey, sKeyName.s, ComputerName.s = "") 
  Protected ret_value, hKey, lhRemoteRegistry
  Protected lpData.s{256} 
  Protected lpcbData, lType, lpDataDWORD, r1 
 
  sKeyName = Trim(sKeyName, "\")  ; 

  If ComputerName = ""
    r1 = RegOpenKeyEx_(TopKey, sKeyName, 0, #KEY_ALL_ACCESS, @hKey)
  Else
    r1 = RegConnectRegistry_(ComputerName, TopKey, @lhRemoteRegistry)
    If r1 = #ERROR_SUCCESS 
      r1 = RegOpenKeyEx_(lhRemoteRegistry, sKeyName, 0, #KEY_ALL_ACCESS, @hKey) 
    EndIf
  EndIf
 
  If r1 = #ERROR_SUCCESS  
    lpcbData = 4 ; 
    r1 = RegQueryValueEx_(hKey, "IsPromoted", 0, @lType, @lpDataDWORD, @lpcbData)
    If r1 = #ERROR_SUCCESS And lType = #REG_DWORD 
      ret_value = lpDataDWORD 
      Debug " IsPromoted == " + Str(lpDataDWORD) 
    EndIf 

    RegCloseKey_(hKey) 
  EndIf 
 
  If lhRemoteRegistry 
    RegCloseKey_(lhRemoteRegistry)
  EndIf
 
  ProcedureReturn ret_value 
EndProcedure 

; ---------------------------------------------------------------------------------------------------------------------

Procedure.s GetKeyNotifyIcon(TopKey, sKeyName.s, sProgramFileName.s, ComputerName.s = "") 
  Protected hKey, lhRemoteRegistry, r1, index, lpcbData, lType, lpftLastWriteTime.FILETIME 
  Protected lpData.s{256}, subkey.s, listsubkey.s 
  Protected NewList listsubkeys.s() 
 
  sKeyName = Trim(sKeyName, "\")  ; 

  If ComputerName = ""
    r1 = RegOpenKeyEx_(TopKey, sKeyName, 0, #KEY_ALL_ACCESS, @hKey)
  Else
    r1 = RegConnectRegistry_(ComputerName, TopKey, @lhRemoteRegistry)
    If r1 = #ERROR_SUCCESS 
      r1 = RegOpenKeyEx_(lhRemoteRegistry, sKeyName, 0, #KEY_ALL_ACCESS, @hKey) 
    EndIf
  EndIf

  If r1 = #ERROR_SUCCESS  ; with an open key 
    For index = 0 To 1000   ; <---------------------- should be enough ??? 
      lpcbData = 255 
      r1 = RegEnumKeyEx_(hKey, index, @lpData, @lpcbData, 0, 0, 0, @lpftLastWriteTime)
      If r1 = #ERROR_SUCCESS And lpcbData 
        AddElement(listsubkeys()) 
        listsubkeys() = Left(lpData, lpcbData)  ; <--- 
      Else    
        Break ; 
      EndIf 
    Next index  

    RegCloseKey_(hKey)  ; <------- close key 

    ForEach listsubkeys() 
      subkey = sKeyName + "\" + listsubkeys() 
      If lhRemoteRegistry
        r1 = RegOpenKeyEx_(lhRemoteRegistry, subkey, 0, #KEY_ALL_ACCESS, @hKey) 
      Else 
        r1 = RegOpenKeyEx_(TopKey, subkey, 0, #KEY_ALL_ACCESS, @hKey)
      EndIf 

      If r1 = #ERROR_SUCCESS 
        lpcbData = 255 
        r1 = RegQueryValueEx_(hKey, "ExecutablePath", 0, @lType, @lpData, @lpcbData)
        If r1 = #ERROR_SUCCESS And lType = #REG_SZ 
          If Left(lpData, lpcbData - 1) = sProgramFileName 
            listsubkey = subkey  ; <--- keep and return it !!! 
            Debug " => found the key " 
            Break ; get out of here 
          EndIf 
        EndIf 
        RegCloseKey_(hKey)  ; <----- close key 
      EndIf 
    Next listsubkeys() 

    ClearList(listsubkeys())  
  EndIf
 
  If lhRemoteRegistry 
    RegCloseKey_(lhRemoteRegistry)
  EndIf
 
  ProcedureReturn listsubkey  
EndProcedure


CompilerIf #PB_Compiler_IsMainFile 

  Define state, NotifyIconKey.s 
  Define ProgFileName.s = ProgramFilename() 

  If OpenWindow(0, 0, 0, 300, 100, "", #PB_Window_Invisible)
    AddSysTrayIcon(0, WindowID(0), LoadImage(0, #PB_Compiler_Home + "Examples\Sources\Data\CdPlayer.ico"))

    NotifyIconKey = GetKeyNotifyIcon(#HKEY_CURRENT_USER, "Control Panel\NotifyIconSettings", ProgFileName) 
    Debug " => " + NotifyIconKey 

    ; Create a pop-up menu and a Systray icon (CD symbol) with this menu associated:
    If CreatePopupImageMenu(0)
      If NotifyIconKey
        MenuItem(1, "Toggle Icon Visibility") 
        MenuBar() 
      EndIf 
      MenuItem(0, "Exit")
    EndIf

    Repeat
      Select WaitWindowEvent()
        Case #PB_Event_SysTray
          Select EventType()
            Case #PB_EventType_RightClick, #PB_EventType_LeftClick
              DisplayPopupMenu(0, WindowID(0)) ; Show pop-up menu after a mouse-click on the Systray icon
          EndSelect

        Case #PB_Event_Menu
          Select EventMenu()
            Case 1 ; Toggle Icon Visibility 
              If NotifyIconKey 
                state = 1 - GetNotifyIconIsPromoted(#HKEY_CURRENT_USER, NotifyIconKey) ; toggle 
                SetMenuItemState(0, 1, state) 
                SetNotifyIconIsPromoted(#HKEY_CURRENT_USER, NotifyIconKey, state) 
              EndIf 

            Case 0 ; Exit 
              RemoveSysTrayIcon(0)
              FreeMenu(0)
              CloseWindow(0)
              End
          EndSelect
      EndSelect
    ForEver
  EndIf
CompilerEndIf 
Mostly running PureBasic <latest stable version and current alpha/beta> (x64) on Windows 11 Home
Little John
Addict
Addict
Posts: 4518
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: Make Application Icon visible with Systray (Windows only)

Post by Little John »

The code works fine here (Windows 11).
Many thanks again :!:
BarryG
Addict
Addict
Posts: 3205
Joined: Thu Apr 18, 2019 8:17 am

Re: Make Application Icon visible with Systray (Windows only)

Post by BarryG »

Wow, this totally contradicts what Raymond Chen said is possible. Maybe Microsoft has relaxed the rules?
Axolotl
Enthusiast
Enthusiast
Posts: 424
Joined: Wed Dec 31, 2008 3:36 pm

Re: Make Application Icon visible with Systray (Windows only)

Post by Axolotl »

Yes, as far as I understood, this behavior was introduced in the 22H2 release of win11.
Mostly running PureBasic <latest stable version and current alpha/beta> (x64) on Windows 11 Home
BarryG
Addict
Addict
Posts: 3205
Joined: Thu Apr 18, 2019 8:17 am

Re: Make Application Icon visible with Systray (Windows only)

Post by BarryG »

I still use Win 10, though.
Axolotl
Enthusiast
Enthusiast
Posts: 424
Joined: Wed Dec 31, 2008 3:36 pm

Re: Make Application Icon visible with Systray (Windows only)

Post by Axolotl »

So, that means it does not work on Win10, right?
You cannot move the icon from the flyover window to the systray and back to the UP-ARROW by mouse?
And you do not have the registry key "HKCU\Control Panel\NotifyIconSettings"?
Mostly running PureBasic <latest stable version and current alpha/beta> (x64) on Windows 11 Home
BarryG
Addict
Addict
Posts: 3205
Joined: Thu Apr 18, 2019 8:17 am

Re: Make Application Icon visible with Systray (Windows only)

Post by BarryG »

The icon DID show in the System Tray on Win 10 at first, even though I had it set for NO icons to appear. But subsequent runs of the code did NOT show it anymore. No, I don't have a Registry entry for "HKCU\Control Panel\NotifyIconSettings".

Your code does NOT create the "Toggle" menu item for me, so I can't select it from the System Tray icon:

Code: Select all

MenuItem(1, "Toggle Icon Visibility")
I only get an icon with "Exit" shown when I expand the tray when clicking the Up Arrow switch.

Image
Axolotl
Enthusiast
Enthusiast
Posts: 424
Joined: Wed Dec 31, 2008 3:36 pm

Re: Make Application Icon visible with Systray (Windows only)

Post by Axolotl »

Yepp. That's a sign.
Without an existing registry key the code doesn't add the menu item. Because without the registry key you cannot use the feature.
I guess that makes sense, right?
Thanks for testing and confirming.
Mostly running PureBasic <latest stable version and current alpha/beta> (x64) on Windows 11 Home
BarryG
Addict
Addict
Posts: 3205
Joined: Thu Apr 18, 2019 8:17 am

Re: Make Application Icon visible with Systray (Windows only)

Post by BarryG »

Yep, I just took an actual look at your code (instead of just running it, lol) and I see what you mean. All good.
Axolotl
Enthusiast
Enthusiast
Posts: 424
Joined: Wed Dec 31, 2008 3:36 pm

Re: Make Application Icon visible with Systray (Windows only)

Post by Axolotl »

Update on the Registry stuff...

Note: I have only read it, not tried it.
Programs that use the SysTray are saved by windows in the so-called 'NotifyIcon Cache'. They remain even if the program no longer exists on the computer. This can also be very inconvenient with the temporary executables when developing.
The following entries are responsible for this caching:

Code: Select all

; Keys: 
;   HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify
;   HKEY_CLASSES_ROOT\                 Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify
; Entries: 
;     IconStreams
;     PastIconsStream (if still available) 
By deleting these two (four) entries you get rid of all the old stuff.
These keys are created again after a restart.
Mostly running PureBasic <latest stable version and current alpha/beta> (x64) on Windows 11 Home
User avatar
ChrisR
Addict
Addict
Posts: 1098
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

Re: Make Application Icon visible with Systray (Windows only)

Post by ChrisR »

Here is an interesting article with a Powershell script about TrayNotify, IconStreams on Windows 7, 10 too, I guess.

And at the end, it is specified that: the key is loaded into memory by Explorer and then written back to the registry on shutdown.
So to be done by a program, you'd have to kill explorer, run the program and then restart explorer. Microsoft really doesn't want us to be able to do it.

My Rot13 Procedure if needed

Code: Select all

Procedure.s Rot13(Text.s)
  *Text.Character = @Text
  While *Text\c <> 0
    If (*Text\c >= 'A' And *Text\c <= 'M')
      *Text\c + 13
    ElseIf (*Text\c >= 'N' And *Text\c <= 'Z')
      *Text\c - 13
    ElseIf *Text\c >= 'a' And *Text\c <= 'm'
      *Text\c + 13
    ElseIf *Text\c >= 'n' And *Text\c <= 'z'
      *Text\c - 13
    EndIf
    *Text + SizeOf(Character)
  Wend
  ProcedureReturn Text
EndProcedure
Little John
Addict
Addict
Posts: 4518
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: Make Application Icon visible with Systray (Windows only)

Post by Little John »

ChrisR, thank you for the information!
User avatar
ChrisR
Addict
Addict
Posts: 1098
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

Re: Make Application Icon visible with Systray (Windows only)

Post by ChrisR »

I hesitated to create my own topic but it's probably better to publish here, next to Axolotl's code for Windows 11.

It's the same thing, Make Application Icon Visible in the Notification Area
But here for Windows 7 up to Windows 10, or maybe up to Windows 11 22H2.

It requires administrator rights to write the notification Icon visibility setting in TrayNotify, IconStreams registry value.
And it requires an explorer restart, done with Thunder93's Restart Manager for:
- Shutdown Explorer and thus unload "IconStreams" memory in the TrayNotify, IconStreams registry
- Set the notification Icon visibility setting in IconStreams registry value (for the application passed in parameter)
- Restart Explorer which will reload the IconStreams registry value in memory and reload the notification area icons

Seems to work here but thanks for your feedback
If it works for you too, it might be a good idea to assemble the 2 versions in one :wink:

Code: Select all

;- Top
;  -------------------------------------------------------------------------------------------------------------------------------------------------
;          Title: Make Application Icon visible with Systray for Windows 7, 10
;    Description: If you use the AddSystrayIcon() in your application your Icon is hidden in the systray menu window on default.
;                 There is a way of making the icon visible With the help of the registry.
;                 - Here For Windows 7 And Windows 10
;                 - For Windows 11 (22H2 and up), see Axolotl Tricks'n' Tips: 
;                     Make Application Icon visible With Systray (Windows 11): https://www.purebasic.fr/english/viewtopic.php?t=82862
;    Source Name: VisibleTrayIcon.pb
;         Author: ChrisR
;  Creation Date: 2023-12-06
;        Version: 1.0
;         Credit:  Micah Rowland: Windows 7 Notification Area Automation: https://tmintner.wordpress.com/2011/07/08/windows-7-notification-area-automation-falling-back-down-the-binary-registry-rabbit-hole/
;                  Thunder93: Restart Manager: https://www.purebasic.fr/english/viewtopic.php?p=487291&sid=409eaafd74ee645308ad6165bca18329#p487291
;     PB-Version: 6.0 or other
;             OS: Windows Only
;          Forum: https://www.purebasic.fr/english/viewtopic.php?t=82862
;  -------------------------------------------------------------------------------------------------------------------------------------------------
; USAGE: (See examples)
;   VisibleIconTray(ProgFileName) - Full path to the program file name
;     | Return:
;     |   1                = Icon Visible in notification area
;     |   0                = Icon in the "Hidden" part of the notification area, the owerflow area 
;     |   #PB_Default (-1) = Switching from visible to hidden state was unsuccessful. Registry not updated! Program not found in IconStreams registry value!  
;  -------------------------------------------------------------------------------------------------------------------------------------------------

EnableExplicit

#ShowRestartExplorerMessage = #False   ; #True | #False

#RmRebootReasonNone = 0
#RmForceShutdown = 1

#RM_SESSION_KEY_LEN = SizeOf(GUID)
#CCH_RM_SESSION_KEY = #RM_SESSION_KEY_LEN * 2
#CCH_RM_MAX_APP_NAME = 255
#CCH_RM_MAX_SVC_NAME = 63

Structure RM_UNIQUE_PROCESS
  dwProcessId.l
  ProcessStartTime.FILETIME
EndStructure

Structure RM_PROCESS_INFO
  Process.RM_UNIQUE_PROCESS
  strAppName.w[#CCH_RM_MAX_APP_NAME+1]
  strServiceShortName.w[#CCH_RM_MAX_SVC_NAME+1]
  ApplicationType.l   ;RM_APP_TYPE
  AppStatus.l
  TSSessionId.l
  bRestartable.l
EndStructure

Prototype.l EnumProcesses(*pProcessIds, cb.l, *pBytesReturned)
Prototype.l GetProcessImageFileName(hProcess, lpImageFileName, nSize.l)

Prototype.l RmEndSession(dwSessionHandle.l)
Prototype.l RmGetList(dwSessionHandle.l, *pnProcInfoNeeded, *pnProcInfo, rgAffectedApps, lpdwRebootReasons.l)
Prototype.l RmRegisterResources(dwSessionHandle.l, nFiles.l, rgsFilenames.l, nApplications.l, rgApplications, nServices.l, *rgsServiceNames)
Prototype.l RmRestart(dwSessionHandle.l, dwRestartFlags.l, fnStatus.i)
Prototype.l RmShutdown(dwSessionHandle.l, lActionFlags.l, fnStatus.i)
Prototype.l RmStartSession(pSessionHandle, dwSessionFlags.l, strSessionKey)

Procedure.s ExpandString(String.s)
  Protected Expand.s{261}
  ExpandEnvironmentStrings_(String, @Expand, 255)
  ProcedureReturn Expand
EndProcedure

Procedure.s HexRot13(Text.s)
  Protected Result.s, *Text.Character = @Text
  ; Convert to Rot13 and then to hex with register-like formatting
  While *Text\c <> 0
    If (*Text\c >= 'A' And *Text\c <= 'M')
      *Text\c + 13
    ElseIf (*Text\c >= 'N' And *Text\c <= 'Z')
      *Text\c - 13
    ElseIf *Text\c >= 'a' And *Text\c <= 'm'
      *Text\c + 13
    ElseIf *Text\c >= 'n' And *Text\c <= 'z'
      *Text\c - 13
    EndIf
    Result + RSet(Hex(*Text\c), 2, "0") + "00"
    *Text + SizeOf(Character)
  Wend
  ProcedureReturn Result
EndProcedure

Procedure RegVisibleApp(Path.s)
  ; Set notification Icon visibility setting in TrayNotify, IconStreams resistry value for the application in parameter
  ; #HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify", "IconStreams"
  Protected hKey, lpType = #REG_BINARY, *lpData, lpcbData.i, create, String.s, Visible, RetVal = #PB_Default, PosPath, Hex.s, i
  
  If RegOpenKeyEx_(#HKEY_CURRENT_USER, "Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify", 0, #KEY_READ | #KEY_WRITE, @hKey) = #ERROR_SUCCESS
    If RegQueryValueEx_(hKey, "IconStreams", 0, @lpType, 0, @lpcbData) = #ERROR_SUCCESS
      If lpcbData = 0 : ProcedureReturn RetVal : EndIf
      *lpData = AllocateMemory(lpcbData)
      RegQueryValueEx_(hKey, "IconStreams", 0, @lpType, *lpData, @lpcbData)
      For i = 0 To lpcbData - 1
        String + RSet(Hex(PeekB(*lpData + i) & $FF), 2, "0")
      Next

      Path = HexRot13(Path)
      PosPath = FindString(String, Path)
      If PosPath
        ; Optional Save IconStreams Value to IconStreamA
        ; RegSetValueEx_(hKey, "IconStreamA", 0, #REG_BINARY, *lpData, lpcbData) = #ERROR_SUCCESS
        
        ;Debug "Visible 0(No)/2(Yes) = " + Mid(String, PosPath+1057, 1) + " : " + Mid(String, PosPath-1, 1059)  ;(528+1)*2+1)
        If Mid(String, PosPath+1057, 1) = "0"
          Debug "Make Application Icon Visible in Systray Notification Area"
          ReplaceString(String, "0", "2", #PB_String_InPlace, PosPath+1057, 1)
          Visible = #True
        Else
          Debug "Put Application Icon in the Owerflow Area"
          ReplaceString(String, "2", "0", #PB_String_InPlace, PosPath+1057, 1)
          Visible = #False
        EndIf
        ;Debug "Visible 0(No)/2(Yes) = " + Mid(String, PosPath+1057, 1) + " : " + Mid(String, PosPath-1, 1059)  ;(528+1)*2+1)
        
        For i = 0 To lpcbData - 1
          Hex = "$" + Mid(String, (i * 2) + 1, 2)
          PokeB(*lpData + i, Val(Hex))
        Next
        If RegSetValueEx_(hKey, "IconStreams", 0, #REG_BINARY, *lpData, lpcbData)  = #ERROR_SUCCESS
          RetVal = Visible
        EndIf
        
      EndIf
      ;ShowMemoryViewer(@String + (PosPath-1)*2, (528+1)*4-1)
      FreeMemory(*lpData)
    EndIf  
    RegCloseKey_(hKey)
  EndIf
  
  ProcedureReturn RetVal
EndProcedure

Procedure ProcHandle2(ProcName$, Array Process.RM_UNIQUE_PROCESS(1))
  #NbProcessesMax = 1024
  Global Dim ProcessesArray.l(#NbProcessesMax)  
  
  Protected.l bytesReturned, cProcesses, ImageName.s
  Protected.FILETIME ftCreate, ftExit, ftKernel, ftUser
  Protected hProcess, i
  
  Protected Lib_psapi = OpenLibrary(#PB_Any, "psapi.dll")
  If Lib_psapi
    Protected EnumProcesses.EnumProcesses = GetFunction(Lib_psapi, "EnumProcesses")
    Protected GetProcessImageFileName.GetProcessImageFileName = GetFunction(Lib_psapi, "GetProcessImageFileNameW")
    
    EnumProcesses(@ProcessesArray(), #NbProcessesMax, @bytesReturned)  
    ; Calculate how many process identifiers were returned.  
    cProcesses = bytesReturned / SizeOf(LONG)  
    
    For i = 0 To cProcesses - 1    
      hProcess = OpenProcess_(#PROCESS_QUERY_INFORMATION|#PROCESS_VM_READ, #False, ProcessesArray(i))
      If hProcess
        ImageName = Space(4096)
        If GetProcessImageFileName(hProcess, @ImageName, 4096) > 0        
          ImageName = LCase(GetFilePart(ImageName))
          If ProcName$ = ImageName
            If GetProcessTimes_(hProcess, @ftCreate, @ftExit, @ftKernel, @ftUser)
              If Process(0)\dwProcessId = 0
                Process(0)\ProcessStartTime = ftCreate
                Process(0)\dwProcessId = ProcessesArray(i)
              ElseIf CompareFileTime_(Process(0)\ProcessStartTime, @ftCreate) = -1
                Process(0)\ProcessStartTime = ftCreate
                Process(0)\dwProcessId = ProcessesArray(i)
              EndIf
            EndIf
          EndIf        
        EndIf      
        CloseHandle_(hProcess)
      EndIf    
    Next
    
    CloseLibrary(Lib_psapi)
  EndIf
  
  ProcedureReturn Process(0)\dwProcessId  
EndProcedure

Procedure ExplorerRestart(Path.s)
  Protected.l PID, RmSession = -1, dwError, rebootReason, nProcInfoNeeded, nProcInfo = 10, RetVal = #PB_Default
  Dim rgApplications.RM_UNIQUE_PROCESS(0)
  Dim RmSessionKey.w(#CCH_RM_SESSION_KEY+1)
  Dim rgpi.RM_PROCESS_INFO(10)
  
  PID = ProcHandle2("explorer.exe", rgApplications())  
  If PID = 0 : Debug "ProcHandle2 Failed" : ProcedureReturn RetVal : EndIf
  
  Protected Lib_RstrtMgr = OpenLibrary(#PB_Any, "RstrtMgr.dll")
  If Lib_RstrtMgr
    Protected RmEndSession.RmEndSession = GetFunction(Lib_RstrtMgr, "RmEndSession")
    Protected RmGetList.RmGetList = GetFunction(Lib_RstrtMgr, "RmGetList")
    Protected RmRegisterResources.RmRegisterResources = GetFunction(Lib_RstrtMgr, "RmRegisterResources")
    Protected RmRestart.RmRestart = GetFunction(Lib_RstrtMgr, "RmRestart")
    Protected RmShutdown.RmShutdown = GetFunction(Lib_RstrtMgr, "RmShutdown")
    Protected RmStartSession.RmStartSession = GetFunction(Lib_RstrtMgr, "RmStartSession")
    
    If RmStartSession(@RmSession, 0, @RmSessionKey()) = #ERROR_SUCCESS
      dwError = RmRegisterResources(RmSession, 0, #Null, 1, rgApplications(), 0, #Null)    
      
      dwError = RmGetList(RmSession, @nProcInfoNeeded, @nProcInfo, rgpi(), @rebootReason)
      
      If rebootReason = #RmRebootReasonNone
        CompilerIf #ShowRestartExplorerMessage
          Protected RestartWindow, RestartTxt, RestartFont = LoadFont(#PB_Any, "", 11, #PB_Font_Bold)
          RestartWindow = OpenWindow(#PB_Any, 0, 0, 920, 30, "", #PB_Window_BorderLess | #PB_Window_ScreenCentered)
          RestartTxt = TextGadget(#PB_Any, 0, 0, 920, 30, "Windows Explorer is restarted to apply changes and reload the notification area icons. Be patient. ", #PB_Text_Center | #SS_CENTERIMAGE)
          SetGadgetColor(RestartTxt, #PB_Gadget_FrontColor, #Blue) : SetGadgetFont(RestartTxt, FontID(RestartFont)) 
          Delay(1000)
        CompilerEndIf
        
        ; Shutdown and Restart explorer
        RmShutdown(RmSession, #RmForceShutdown, 0)
        
        CompilerIf #ShowRestartExplorerMessage : SetGadgetText(RestartTxt, GetGadgetText(RestartTxt) + "....") : CompilerEndIf
        
        ; Set notification Icon visibility setting in TrayNotify, IconStreams resistry value for the application in parameter
        RetVal = RegVisibleApp(Path)
        
        If RmRestart(RmSession, 0, #Null) = dwError   
          Debug "RmRestart: Re-launched Explorer Successfully"
        Else
          Debug "RmRestart Error: " + dwError
          RetVal = #PB_Default
        EndIf
        
        CompilerIf #ShowRestartExplorerMessage : CloseWindow(RestartWindow) : CompilerEndIf
      EndIf
      
      RmEndSession(RmSession)
      RmSession = -1
      RmSessionKey(0) = 0    
    EndIf
    
    CloseLibrary(Lib_RstrtMgr)
  EndIf
  
  ProcedureReturn RetVal
EndProcedure

Procedure VisibleIconTray(Path.s)
  Protected OS.OSVERSIONINFOEX, hKey, RetVal = #PB_Default
  
  OS\dwOSVersionInfoSize = SizeOf(OSVERSIONINFOEX)
  GetVersionEx_(OS)
  ; No Windows 11 to test in which version the notification zone was changed. With the new "IsPromoted" registry key in Windows 11 22H2 (build 22621)
  ; For windows 7 and up (Build >= 7600), it's probably best to test whether "IsPromoted" regsitry key is present.
  If OS\dwBuildNumber < 	7600 ; Or OS\dwBuildNumber >= 22621
    MessageRequester("Visible Tray Icon Warning!", "This program to make the Icon visible in the notification area Works only with Windows 7 and up to Windows 11 22H2", #PB_MessageRequester_Ok | #PB_MessageRequester_Warning)
    ProcedureReturn RetVal  
  EndIf
  If RegOpenKeyEx_(#HKEY_CURRENT_USER, "Control Panel\NotifyIconSettings", 0, #KEY_READ, @hKey) = #ERROR_SUCCESS
    MessageRequester("Visible Tray Icon Warning!", "This program to make the Icon visible in the notification area Works only with Windows 7 and up to Windows 11 22H2", #PB_MessageRequester_Ok | #PB_MessageRequester_Warning)
    RegCloseKey_(hKey)
    ProcedureReturn RetVal
  EndIf
  If FileSize(Path) > 0
    RetVal = ExplorerRestart(ExpandString(Path))
  EndIf
  
  ProcedureReturn RetVal
EndProcedure


CompilerIf #PB_Compiler_IsMainFile 
     
  ;- Example:
  ; Define ProgFileName.s = "D:\xxxxx\xxxxx.exe"
  ; VisibleIconTray(ProgFileName)
  
  ; Example2: Compiled as an external program and call it with the program path as parameter. AddSysTrayIcon must have been done previously 
  ; If CountProgramParameters() = 1
  ;   Define ProgFileName.s = ProgramParameter(0)
  ;   If FileSize(ProgFileName) > 0
  ;     End VisibleIconTray(ProgFileName)
  ;   EndIf
  ; EndIf
  ; End #PB_Default
  
  Global TBCreated_Message = RegisterWindowMessage_("TaskbarCreated")
  
  Procedure AddSysTray()
    Protected SavState
    ; Exemple if needed, load Icon directly from the executable resource
    ;Protected hinstance, iconhWnd
    ;hinstance = GetClassLong_(WindowID(0), #GCL_HMODULE)
    ;If hinstance
    ;  iconhWnd = LoadIcon_(hinstance, 1)
    ;  If iconhWnd
    ;    AddSysTrayIcon(0, WindowID(0), iconhWnd)
    ;  EndIf
    ;EndIf
    
    ; Create or Recreate SysTray Icon (CD symbol) and associated popup menu. Save previous State to be restored after restarting explorer 
    If IsMenu(0) : SavState = GetMenuItemState(0, 1) : EndIf
    
    AddSysTrayIcon(0, WindowID(0), LoadImage(0, #PB_Compiler_Home + "Examples\Sources\Data\CdPlayer.ico"))
    SysTrayIconToolTip(0, "Toggle Icon Visibility")
    
    If CreatePopupImageMenu(0)
      MenuItem(1, "Toggle Icon Visibility") 
      MenuBar() 
      MenuItem(0, "Exit")
    EndIf
    SetMenuItemState(0, 1, SavState)
  EndProcedure
  
  Procedure WinCallback(hWnd, Msg, wParam, lParam)
    If Msg = TBCreated_Message
      ; Recreate Systray Icon and Popup menu on "TaskbarCreated" Message to display it again after restarting explorer
      AddSysTray()
    EndIf
    ProcedureReturn #PB_ProcessPureBasicEvents
  EndProcedure
  
  Define State, ProgFileName.s = ProgramFilename() 
  
  If OpenWindow(0, -10, -10, 1, 1, "", #PB_Window_Invisible)
    SetWindowCallback(@WinCallback())
    AddSysTray()
    
    Repeat
      Select WaitWindowEvent()
        Case #PB_Event_SysTray
          Select EventType()
            Case #PB_EventType_RightClick, #PB_EventType_LeftClick
              DisplayPopupMenu(0, WindowID(0)) ; Show pop-up menu after a mouse-click on the Systray icon
          EndSelect
          
        Case #PB_Event_Menu
          Select EventMenu()
            Case 1   ; Toggle Icon Visibility
              State = VisibleIconTray(ProgFileName)
              If State <> #PB_Default
                SetMenuItemState(0, 1, State)
              EndIf
              
            Case 0   ; Exit 
              RemoveSysTrayIcon(0)
              FreeMenu(0)
              CloseWindow(0)
              End
          EndSelect
      EndSelect
    ForEver
  EndIf
  
CompilerEndIf
 
; IDE Options = PureBasic 6.03 LTS (Windows - x64)
; EnableAdmin
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Re: Make Application Icon visible with Systray (Windows only)

Post by Oso »

Interestingly, with my freshly-installed Windows 11 Pro 22H2, I cannot make a Systray icon appear at all, even with Axolotl's code. I guess this might be because Windows has not yet been activated and therefore personalisation is not possible, but even so, it seems a bit odd.

Can anyone kindly confirm if that's the reason? Thanks.
Axolotl
Enthusiast
Enthusiast
Posts: 424
Joined: Wed Dec 31, 2008 3:36 pm

Re: Make Application Icon visible with Systray (Windows only)

Post by Axolotl »

When I run e.g. the help examaple code for AddSysTrayIcon the system creates a new key under HKCU\Control Panel\NotifyIconSettings\
(Unfortunately) with ExecutablePath = "....\AppData\Local\Temp\PureBasic_Compilation0.exe"
The Icon is in the fly over window. If you drag&drop it to the systray part (see mouse changed to a pin) the value IsPromoted = 1 is created.
Drag&drop the icon on the up-arrow (which means moving the icon to the fly over window) the IsPromoted changed to 0.

That's how it behaves here on my system (but it is old and upgraded from Win10)....
Mostly running PureBasic <latest stable version and current alpha/beta> (x64) on Windows 11 Home
Post Reply