Skip to content

Commit b91982c

Browse files
committed
Implemented SystemTrayManager
1 parent dd62ca4 commit b91982c

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
using System.Runtime.InteropServices;
5+
using Windows.Win32;
6+
using Windows.Win32.Foundation;
7+
using Windows.Win32.UI.Shell;
8+
using Windows.Win32.UI.WindowsAndMessaging;
9+
10+
namespace Files.App.Storage
11+
{
12+
/// <summary>
13+
/// Exposes a manager to create or delete an system tray icon you provide.
14+
/// </summary>
15+
public unsafe partial class SystemTrayManager : IDisposable
16+
{
17+
string _szWndClassName = null!;
18+
string _szToolTip = null!;
19+
HICON _hIcon = default;
20+
private Guid _id;
21+
private uint _dwCallbackMsgId;
22+
Action<uint> _callback = null!;
23+
24+
private HWND _hWnd = default;
25+
private WNDPROC? _wndProc;
26+
private bool _isInitialized;
27+
private uint _dwTaskbarRestartMsgId;
28+
private bool _isShown;
29+
30+
public void Initialize(string szWndClassName, string szToolTip, HICON hIcon, Guid id, uint dwCallbackMsgId, Action<uint> callback)
31+
{
32+
_szWndClassName = szWndClassName;
33+
_szToolTip = szToolTip;
34+
_hIcon = hIcon;
35+
_id = id;
36+
_dwCallbackMsgId = dwCallbackMsgId;
37+
_callback = callback;
38+
39+
_isInitialized = true;
40+
}
41+
42+
public bool CreateIcon()
43+
{
44+
// Not expected usage
45+
if (!_isInitialized)
46+
throw new InvalidOperationException($"{nameof(SystemTrayManager)} is not initialized. Call {nameof(Initialize)}() before using this method.");
47+
48+
if (_hWnd.IsNull)
49+
_hWnd = CreateIconWindow(_szWndClassName);
50+
51+
NOTIFYICONDATAW data = default;
52+
data.cbSize = (uint)sizeof(NOTIFYICONDATAW);
53+
data.hWnd = _hWnd;
54+
data.uCallbackMessage = _dwCallbackMsgId;
55+
data.guidItem = _id;
56+
data.hIcon = _hIcon;
57+
data.uFlags = NOTIFY_ICON_DATA_FLAGS.NIF_MESSAGE | NOTIFY_ICON_DATA_FLAGS.NIF_ICON | NOTIFY_ICON_DATA_FLAGS.NIF_TIP | NOTIFY_ICON_DATA_FLAGS.NIF_GUID | NOTIFY_ICON_DATA_FLAGS.NIF_SHOWTIP;
58+
data.szTip = _szToolTip;
59+
data.Anonymous.uVersion = 4u;
60+
61+
if (_isShown)
62+
{
63+
return PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_DELETE, &data);
64+
}
65+
else
66+
{
67+
bool fRes = PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_DELETE, &data);
68+
if (!fRes) return false;
69+
70+
fRes = PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_ADD, &data);
71+
if (!fRes) return false;
72+
73+
fRes = PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_SETVERSION, &data);
74+
if (!fRes) return false;
75+
76+
_isShown = true;
77+
78+
return true;
79+
}
80+
}
81+
82+
public bool DeleteIcon()
83+
{
84+
if (_isShown)
85+
{
86+
NOTIFYICONDATAW data = default;
87+
data.cbSize = (uint)sizeof(NOTIFYICONDATAW);
88+
data.hWnd = _hWnd;
89+
data.guidItem = _id;
90+
data.uFlags = NOTIFY_ICON_DATA_FLAGS.NIF_GUID;
91+
data.Anonymous.uVersion = 4u;
92+
93+
return PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_DELETE, &data);
94+
}
95+
96+
return true;
97+
}
98+
99+
private HWND CreateIconWindow(string szWndClassName)
100+
{
101+
fixed (char* pszWndClassName = szWndClassName)
102+
{
103+
_wndProc ??= new(WndProc);
104+
105+
WNDCLASSEXW wndClass = default;
106+
107+
wndClass.cbSize = (uint)sizeof(WNDCLASSEXW);
108+
wndClass.style = WNDCLASS_STYLES.CS_DBLCLKS;
109+
wndClass.hInstance = PInvoke.GetModuleHandle(default(PCWSTR));
110+
wndClass.lpszClassName = pszWndClassName;
111+
wndClass.lpfnWndProc = (delegate* unmanaged[Stdcall]<HWND, uint, WPARAM, LPARAM, LRESULT>)
112+
Marshal.GetFunctionPointerForDelegate(_wndProc);
113+
114+
PInvoke.RegisterClassEx(&wndClass);
115+
116+
_dwTaskbarRestartMsgId = PInvoke.RegisterWindowMessage("TaskbarCreated");
117+
118+
return PInvoke.CreateWindowEx(
119+
WINDOW_EX_STYLE.WS_EX_LEFT, pszWndClassName, default,
120+
WINDOW_STYLE.WS_OVERLAPPED, 0, 0, 1, 1, HWND.Null, HMENU.Null, HINSTANCE.Null, null);
121+
}
122+
}
123+
124+
private LRESULT WndProc(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam)
125+
{
126+
if (uMsg == _dwCallbackMsgId)
127+
{
128+
_callback((uint)(lParam.Value & 0xFFFF));
129+
130+
return default;
131+
}
132+
else if (uMsg is PInvoke.WM_DESTROY)
133+
{
134+
DeleteIcon();
135+
136+
return default;
137+
}
138+
else if (uMsg == _dwTaskbarRestartMsgId && _isInitialized)
139+
{
140+
DeleteIcon();
141+
CreateIcon();
142+
}
143+
144+
return PInvoke.DefWindowProc(hWnd, uMsg, wParam, lParam);
145+
}
146+
147+
public void Dispose()
148+
{
149+
if (!_hWnd.IsNull)
150+
PInvoke.DestroyWindow(_hWnd);
151+
152+
if (!_hIcon.IsNull)
153+
PInvoke.DestroyIcon(_hIcon);
154+
155+
_wndProc = null;
156+
}
157+
}
158+
}

0 commit comments

Comments
 (0)