I hesitated to create my own topic but it's probably better to publish here, next to Axolotl's code for Windows 11.
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.
- 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
If it works for you too, it might be a good idea to assemble the 2 versions in one
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