22#include < windows.h>
33#include < shellapi.h>
44// clang-format on
5+ #include < functional>
56#include < iostream>
67#include < memory>
8+ #include < optional>
79#include < string>
810
911#include " ../../foundation/geometry.h"
@@ -26,18 +28,34 @@ class TrayIcon::Impl {
2628 public:
2729 std::shared_ptr<Image> image_;
2830
29- Impl () : hwnd_(nullptr ), icon_id_(0 ), icon_handle_(nullptr ) {
30- id_ = IdAllocator::Allocate<TrayIcon>();
31+ // Callback function types
32+ using ClickedCallback = std::function<void (TrayIconId, const std::string&)>;
33+ using RightClickedCallback = std::function<void (TrayIconId)>;
34+ using DoubleClickedCallback = std::function<void (TrayIconId)>;
35+
36+ Impl ()
37+ : hwnd_(nullptr ),
38+ icon_handle_ (nullptr ),
39+ owns_window_(false ) {
40+ tray_icon_id_ = IdAllocator::Allocate<TrayIcon>();
3141 }
3242
33- Impl (HWND hwnd, UINT icon_id)
34- : hwnd_(hwnd), icon_id_(icon_id), icon_handle_(nullptr ) {
35- id_ = IdAllocator::Allocate<TrayIcon>();
43+ Impl (HWND hwnd, bool owns_window,
44+ ClickedCallback clicked_callback,
45+ RightClickedCallback right_clicked_callback,
46+ DoubleClickedCallback double_clicked_callback)
47+ : hwnd_(hwnd),
48+ icon_handle_(nullptr ),
49+ owns_window_(owns_window),
50+ clicked_callback_(std::move(clicked_callback)),
51+ right_clicked_callback_(std::move(right_clicked_callback)),
52+ double_clicked_callback_(std::move(double_clicked_callback)) {
53+ tray_icon_id_ = IdAllocator::Allocate<TrayIcon>();
3654 // Initialize NOTIFYICONDATA structure
3755 ZeroMemory (&nid_, sizeof (NOTIFYICONDATAW));
3856 nid_.cbSize = sizeof (NOTIFYICONDATAW);
3957 nid_.hWnd = hwnd_;
40- nid_.uID = icon_id_;
58+ nid_.uID = static_cast <UINT>(tray_icon_id_); // Use tray_icon_id_ directly
4159 nid_.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
4260 nid_.uCallbackMessage = WM_USER + 1 ; // Custom message for tray icon events
4361
@@ -47,25 +65,15 @@ class TrayIcon::Impl {
4765 });
4866 }
4967
50- // Windows-specific method to set internal data
51- void SetWindowsData (HWND hwnd, UINT icon_id) {
52- hwnd_ = hwnd;
53- icon_id_ = icon_id;
54-
55- // Re-initialize NOTIFYICONDATA structure
56- ZeroMemory (&nid_, sizeof (NOTIFYICONDATAW));
57- nid_.cbSize = sizeof (NOTIFYICONDATAW);
58- nid_.hWnd = hwnd_;
59- nid_.uID = icon_id_;
60- nid_.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
61- nid_.uCallbackMessage = WM_USER + 1 ; // Custom message for tray icon events
62- }
63-
6468 ~Impl () {
6569 WindowProcDelegateManager::GetInstance ().UnregisterDelegate (
6670 window_proc_id_);
6771 if (hwnd_) {
6872 Shell_NotifyIconW (NIM_DELETE, &nid_);
73+ // Destroy the window if we own it
74+ if (owns_window_) {
75+ DestroyWindow (hwnd_);
76+ }
6977 }
7078 if (icon_handle_) {
7179 DestroyIcon (icon_handle_);
@@ -77,16 +85,28 @@ class TrayIcon::Impl {
7785 UINT message,
7886 WPARAM wparam,
7987 LPARAM lparam) {
80- if (message == WM_USER + 1 && wparam == icon_id_ ) {
88+ if (message == WM_USER + 1 && wparam == static_cast <WPARAM>(tray_icon_id_) ) {
8189 if (lparam == WM_LBUTTONUP) {
82- std::cout << " TrayIcon: Left button clicked, icon_id = " << icon_id_
90+ std::cout << " TrayIcon: Left button clicked, tray_icon_id = " << tray_icon_id_
8391 << std::endl;
92+ // Call clicked callback
93+ if (clicked_callback_) {
94+ clicked_callback_ (tray_icon_id_, " left" );
95+ }
8496 } else if (lparam == WM_RBUTTONUP) {
85- std::cout << " TrayIcon: Right button clicked, icon_id = " << icon_id_
97+ std::cout << " TrayIcon: Right button clicked, tray_icon_id = " << tray_icon_id_
8698 << std::endl;
99+ // Call right clicked callback
100+ if (right_clicked_callback_) {
101+ right_clicked_callback_ (tray_icon_id_);
102+ }
87103 } else if (lparam == WM_LBUTTONDBLCLK) {
88- std::cout << " TrayIcon: Left button double-clicked, icon_id = "
89- << icon_id_ << std::endl;
104+ std::cout << " TrayIcon: Left button double-clicked, tray_icon_id = "
105+ << tray_icon_id_ << std::endl;
106+ // Call double clicked callback
107+ if (double_clicked_callback_) {
108+ double_clicked_callback_ (tray_icon_id_);
109+ }
90110 }
91111 return 0 ;
92112 }
@@ -95,61 +115,97 @@ class TrayIcon::Impl {
95115
96116 int window_proc_id_;
97117 HWND hwnd_;
98- UINT icon_id_;
99118 NOTIFYICONDATAW nid_;
100119 std::shared_ptr<Menu> context_menu_;
101120 HICON icon_handle_;
102- TrayIconId id_;
121+ TrayIconId tray_icon_id_;
122+ bool owns_window_; // Whether we created the window and should destroy it
123+
124+ // Callback functions for event emission
125+ ClickedCallback clicked_callback_;
126+ RightClickedCallback right_clicked_callback_;
127+ DoubleClickedCallback double_clicked_callback_;
103128};
104129
105- TrayIcon::TrayIcon () : pimpl_(std::make_unique<Impl>()) {
106- // Create a hidden window for this tray icon
107- HINSTANCE hInstance = GetModuleHandle (nullptr );
108-
109- // Register window class for this tray icon
110- static UINT next_class_id = 1 ;
111- std::string class_name =
112- " NativeAPITrayIcon_" + std::to_string (next_class_id++);
113- std::wstring wclass_name = StringToWString (class_name);
114-
115- WNDCLASSW wc = {};
116- wc.lpfnWndProc = DefWindowProc;
117- wc.hInstance = hInstance;
118- wc.lpszClassName = wclass_name.c_str ();
119-
120- if (RegisterClassW (&wc)) {
121- // Don't create a new window for the tray icon.
122- // Try to get an existing window handle from different methods.
123- HWND hwnd = GetForegroundWindow ();
124- if (!hwnd || !IsWindow (hwnd)) {
125- hwnd = GetActiveWindow ();
126- }
127- if (!hwnd || !IsWindow (hwnd)) {
128- hwnd = FindWindow (nullptr , nullptr );
129- }
130+ TrayIcon::TrayIcon () : TrayIcon(nullptr ) {}
131+
132+ TrayIcon::TrayIcon (void * native_tray_icon) {
133+ HWND hwnd = nullptr ;
134+ bool owns_window = false ;
135+
136+ if (native_tray_icon == nullptr ) {
137+ // Create a new tray icon - need a window handle for receiving messages
138+ HINSTANCE hInstance = GetModuleHandle (nullptr );
130139
131- if (hwnd && IsWindow (hwnd)) {
132- // Generate unique icon ID
133- static UINT next_icon_id = 1 ;
134- UINT icon_id = next_icon_id++;
140+ // Register window class for message-only window (once per process)
141+ static bool class_registered = false ;
142+ static std::wstring class_name = L" NativeAPITrayIconWindow" ;
143+
144+ if (!class_registered) {
145+ WNDCLASSW wc = {};
146+ // Use WindowProcDelegateManager to route messages to registered delegates
147+ wc.lpfnWndProc = WindowProcDelegateManager::InternalWindowProc;
148+ wc.hInstance = hInstance;
149+ wc.lpszClassName = class_name.c_str ();
150+
151+ if (RegisterClassW (&wc)) {
152+ class_registered = true ;
153+ }
154+ }
135155
136- // Reinitialize the Impl with the found window and icon ID
137- pimpl_ = std::make_unique<Impl>(hwnd, icon_id);
156+ // Create a message-only window (HWND_MESSAGE as parent)
157+ if (class_registered) {
158+ hwnd = CreateWindowExW (
159+ 0 , // Extended styles
160+ class_name.c_str (), // Window class
161+ L" " , // Window title (empty for message-only window)
162+ 0 , // Window style
163+ 0 , 0 , 0 , 0 , // Position and size (ignored for message-only)
164+ HWND_MESSAGE, // Parent: message-only window
165+ nullptr , // Menu
166+ hInstance, // Instance
167+ nullptr ); // Additional data
168+
169+ if (hwnd) {
170+ owns_window = true ;
171+ }
138172 }
139- // else: Failed to get HWND, keep pimpl_ as default (uninitialized)
173+ } else {
174+ // Wrap existing native tray icon
175+ // In a real implementation, you'd extract HWND from the tray parameter
176+ // For now, this is mainly used by TrayManager for creating uninitialized icons
140177 }
141- }
142178
143- TrayIcon::TrayIcon (void * tray) : pimpl_(std::make_unique<Impl>()) {
144- // In a real implementation, you'd extract HWND and icon ID from the tray
145- // parameter For now, this constructor is mainly used by TrayManager for
146- // creating uninitialized icons
179+ // Initialize the Impl with the window handle
180+ // The tray_icon_id will be allocated inside Impl constructor
181+ if (hwnd) {
182+ // Create callback functions that emit events
183+ auto clicked_callback = [this ](TrayIconId id, const std::string& button) {
184+ this ->Emit <TrayIconClickedEvent>(id, button);
185+ };
186+
187+ auto right_clicked_callback = [this ](TrayIconId id) {
188+ this ->Emit <TrayIconRightClickedEvent>(id);
189+ };
190+
191+ auto double_clicked_callback = [this ](TrayIconId id) {
192+ this ->Emit <TrayIconDoubleClickedEvent>(id);
193+ };
194+
195+ pimpl_ = std::make_unique<Impl>(hwnd, owns_window,
196+ std::move (clicked_callback),
197+ std::move (right_clicked_callback),
198+ std::move (double_clicked_callback));
199+ } else {
200+ // Failed to create window, create uninitialized Impl
201+ pimpl_ = std::make_unique<Impl>();
202+ }
147203}
148204
149205TrayIcon::~TrayIcon () {}
150206
151207TrayIconId TrayIcon::GetId () {
152- return pimpl_->id_ ;
208+ return pimpl_->tray_icon_id_ ;
153209}
154210
155211void TrayIcon::SetIcon (std::shared_ptr<Image> image) {
@@ -248,7 +304,7 @@ Rectangle TrayIcon::GetBounds() {
248304 NOTIFYICONIDENTIFIER niid = {};
249305 niid.cbSize = sizeof (NOTIFYICONIDENTIFIER);
250306 niid.hWnd = pimpl_->hwnd_ ;
251- niid.uID = pimpl_->icon_id_ ;
307+ niid.uID = static_cast <UINT>( pimpl_->tray_icon_id_ ) ;
252308
253309 // Get the rectangle of the notification icon
254310 if (Shell_NotifyIconGetRect (&niid, &rect) == S_OK) {
@@ -290,7 +346,7 @@ bool TrayIcon::IsVisible() {
290346 NOTIFYICONIDENTIFIER niid = {};
291347 niid.cbSize = sizeof (NOTIFYICONIDENTIFIER);
292348 niid.hWnd = pimpl_->hwnd_ ;
293- niid.uID = pimpl_->icon_id_ ;
349+ niid.uID = static_cast <UINT>( pimpl_->tray_icon_id_ ) ;
294350
295351 RECT rect;
296352 return Shell_NotifyIconGetRect (&niid, &rect) == S_OK;
@@ -331,7 +387,7 @@ bool TrayIcon::CloseContextMenu() {
331387}
332388
333389void * TrayIcon::GetNativeObjectInternal () const {
334- return reinterpret_cast <void *>(static_cast <uintptr_t >(pimpl_->icon_id_ ));
390+ return reinterpret_cast <void *>(static_cast <uintptr_t >(pimpl_->tray_icon_id_ ));
335391}
336392
337393} // namespace nativeapi
0 commit comments