Skip to content

Commit 7457f4b

Browse files
committed
Refactor Windows tray icon and menu handling
Improves tray icon management by introducing callback-based event handling, using tray icon IDs directly, and managing window ownership for cleanup. Refactors window acquisition logic in menu and tray icon code, and updates context menu alignment. These changes enhance reliability and maintainability of tray icon and menu operations on Windows.
1 parent 6b69022 commit 7457f4b

File tree

2 files changed

+135
-71
lines changed

2 files changed

+135
-71
lines changed

src/platform/windows/menu_windows.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -485,13 +485,21 @@ bool Menu::Open(double x, double y) {
485485
pimpl_->visible_ = true;
486486

487487
POINT pt = {static_cast<int>(x), static_cast<int>(y)};
488-
HWND hwnd = GetActiveWindow();
488+
// Try to get the foreground window first
489+
HWND hwnd = GetForegroundWindow();
489490
if (!hwnd) {
490-
hwnd = GetDesktopWindow();
491+
hwnd = GetActiveWindow();
492+
return hwnd;
493+
}
494+
495+
// If still no window, try to find any top-level window
496+
if (!hwnd) {
497+
hwnd = FindWindow(nullptr, nullptr);
491498
}
492499

493500
// Show the context menu
494-
TrackPopupMenu(pimpl_->hmenu_, TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, nullptr);
501+
TrackPopupMenu(pimpl_->hmenu_, TPM_BOTTOMALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0,
502+
hwnd, nullptr);
495503

496504
pimpl_->visible_ = false;
497505
return true;

src/platform/windows/tray_icon_windows.cpp

Lines changed: 124 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
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

149205
TrayIcon::~TrayIcon() {}
150206

151207
TrayIconId TrayIcon::GetId() {
152-
return pimpl_->id_;
208+
return pimpl_->tray_icon_id_;
153209
}
154210

155211
void 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

333389
void* 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

Comments
 (0)