Skip to content

Commit 89e2b1b

Browse files
committed
Refactor tray icon event handler setup and cleanup
Introduces StartEventListening and StopEventListening methods to manage platform-specific event handler setup and cleanup for tray icons on macOS and Windows. Event handlers and monitoring are now initialized only when listeners are present, improving resource management and preventing unnecessary callbacks after destruction. Property and method names are updated for clarity and consistency.
1 parent b8173ae commit 89e2b1b

File tree

3 files changed

+151
-45
lines changed

3 files changed

+151
-45
lines changed

src/platform/macos/tray_icon_macos.mm

Lines changed: 79 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
static const void* kTrayIconIdKey = &kTrayIconIdKey;
2222

2323
@interface NSStatusBarButtonTarget : NSObject
24-
@property(nonatomic, copy) TrayIconClickedBlock leftClickedBlock;
25-
@property(nonatomic, copy) TrayIconRightClickedBlock rightClickedBlock;
26-
@property(nonatomic, copy) TrayIconDoubleClickedBlock doubleClickedBlock;
24+
@property(nonatomic, copy) TrayIconClickedBlock left_clicked_callback;
25+
@property(nonatomic, copy) TrayIconRightClickedBlock right_clicked_callback;
26+
@property(nonatomic, copy) TrayIconDoubleClickedBlock double_clicked_callback;
2727
- (void)handleStatusItemEvent:(id)sender;
2828
@end
2929

@@ -37,7 +37,8 @@ - (void)handleStatusItemEvent:(id)sender;
3737
Impl(NSStatusItem* status_item)
3838
: ns_status_item_(status_item),
3939
ns_status_bar_button_target_(nil),
40-
menu_closed_listener_id_(0) {
40+
menu_closed_listener_id_(0),
41+
click_handler_setup_(false) {
4142
if (status_item) {
4243
// Check if ID already exists in the associated object
4344
NSNumber* allocated_id = objc_getAssociatedObject(status_item, kTrayIconIdKey);
@@ -50,16 +51,6 @@ - (void)handleStatusItemEvent:(id)sender;
5051
objc_setAssociatedObject(status_item, kTrayIconIdKey, [NSNumber numberWithLong:id_],
5152
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
5253
}
53-
54-
// Create and set up button target
55-
ns_status_bar_button_target_ = [[NSStatusBarButtonTarget alloc] init];
56-
57-
// Set up event handlers
58-
[status_item.button setTarget:ns_status_bar_button_target_];
59-
[status_item.button setAction:@selector(handleStatusItemEvent:)];
60-
61-
// Enable right-click handling
62-
[status_item.button sendActionOn:NSEventMaskLeftMouseUp | NSEventMaskRightMouseUp];
6354
}
6455
}
6556

@@ -70,21 +61,13 @@ - (void)handleStatusItemEvent:(id)sender;
7061
menu_closed_listener_id_ = 0;
7162
}
7263

73-
// Clean up blocks first
74-
if (ns_status_bar_button_target_) {
75-
ns_status_bar_button_target_.leftClickedBlock = nil;
76-
ns_status_bar_button_target_.rightClickedBlock = nil;
77-
ns_status_bar_button_target_.doubleClickedBlock = nil;
78-
ns_status_bar_button_target_ = nil;
64+
// Clean up event handlers if they were set up
65+
if (click_handler_setup_) {
66+
CleanupEventHandlers();
7967
}
8068

8169
// Then clean up the status item
8270
if (ns_status_item_) {
83-
// Remove target and action to prevent callbacks after destruction
84-
if (ns_status_item_.button) {
85-
[ns_status_item_.button setTarget:nil];
86-
[ns_status_item_.button setAction:nil];
87-
}
8871
// Clear menu reference
8972
ns_status_item_.menu = nil;
9073

@@ -102,12 +85,57 @@ - (void)handleStatusItemEvent:(id)sender;
10285
}
10386
}
10487

88+
void SetupEventHandlers() {
89+
if (click_handler_setup_) {
90+
return; // Already set up
91+
}
92+
93+
if (!ns_status_item_ || !ns_status_item_.button) {
94+
return;
95+
}
96+
97+
// Create and set up button target
98+
ns_status_bar_button_target_ = [[NSStatusBarButtonTarget alloc] init];
99+
100+
// Set up event handlers
101+
[ns_status_item_.button setTarget:ns_status_bar_button_target_];
102+
[ns_status_item_.button setAction:@selector(handleStatusItemEvent:)];
103+
104+
// Enable right-click handling
105+
[ns_status_item_.button sendActionOn:NSEventMaskLeftMouseUp | NSEventMaskRightMouseUp];
106+
107+
click_handler_setup_ = true;
108+
}
109+
110+
void CleanupEventHandlers() {
111+
if (!click_handler_setup_) {
112+
return; // Not set up
113+
}
114+
115+
// Clean up blocks first
116+
if (ns_status_bar_button_target_) {
117+
ns_status_bar_button_target_.left_clicked_callback = nil;
118+
ns_status_bar_button_target_.right_clicked_callback = nil;
119+
ns_status_bar_button_target_.double_clicked_callback = nil;
120+
ns_status_bar_button_target_ = nil;
121+
}
122+
123+
// Remove target and action to prevent callbacks after destruction
124+
if (ns_status_item_ && ns_status_item_.button) {
125+
[ns_status_item_.button setTarget:nil];
126+
[ns_status_item_.button setAction:nil];
127+
}
128+
129+
click_handler_setup_ = false;
130+
}
131+
105132
NSStatusItem* ns_status_item_;
106133
NSStatusBarButtonTarget* ns_status_bar_button_target_;
107134

108135
TrayIconId id_;
109136
std::shared_ptr<Menu> context_menu_;
110137
size_t menu_closed_listener_id_;
138+
bool click_handler_setup_;
111139
};
112140

113141
TrayIcon::TrayIcon() : TrayIcon(nullptr) {}
@@ -126,23 +154,38 @@ - (void)handleStatusItemEvent:(id)sender;
126154
// Initialize the Impl with the status item
127155
pimpl_ = std::make_unique<Impl>(status_item);
128156

129-
// Set up click handlers
157+
// Event handlers will be set up automatically when first listener is added
158+
// via StartEventListening() override
159+
}
160+
161+
TrayIcon::~TrayIcon() = default;
162+
163+
void TrayIcon::StartEventListening() {
164+
// Called automatically when first listener is added
165+
// Set up platform event monitoring
166+
pimpl_->SetupEventHandlers();
167+
168+
// Set up click handler blocks
130169
if (pimpl_->ns_status_bar_button_target_) {
131-
pimpl_->ns_status_bar_button_target_.leftClickedBlock = ^{
170+
pimpl_->ns_status_bar_button_target_.left_clicked_callback = ^{
132171
Emit<TrayIconClickedEvent>(pimpl_->id_);
133172
};
134173

135-
pimpl_->ns_status_bar_button_target_.rightClickedBlock = ^{
174+
pimpl_->ns_status_bar_button_target_.right_clicked_callback = ^{
136175
Emit<TrayIconRightClickedEvent>(pimpl_->id_);
137176
};
138177

139-
pimpl_->ns_status_bar_button_target_.doubleClickedBlock = ^{
178+
pimpl_->ns_status_bar_button_target_.double_clicked_callback = ^{
140179
Emit<TrayIconDoubleClickedEvent>(pimpl_->id_);
141180
};
142181
}
143182
}
144183

145-
TrayIcon::~TrayIcon() = default;
184+
void TrayIcon::StopEventListening() {
185+
// Called automatically when last listener is removed
186+
// Clean up platform event monitoring
187+
pimpl_->CleanupEventHandlers();
188+
}
146189

147190
TrayIconId TrayIcon::GetId() {
148191
return pimpl_->id_;
@@ -350,18 +393,18 @@ - (void)handleStatusItemEvent:(id)sender {
350393
(event.type == NSEventTypeLeftMouseUp &&
351394
(event.modifierFlags & NSEventModifierFlagControl))) {
352395
// Right click or Ctrl+Left click
353-
if (_rightClickedBlock) {
354-
_rightClickedBlock();
396+
if (_right_clicked_callback) {
397+
_right_clicked_callback();
355398
}
356399
} else if (event.type == NSEventTypeLeftMouseUp) {
357400
// Check for double click
358401
if (event.clickCount == 2) {
359-
if (_doubleClickedBlock) {
360-
_doubleClickedBlock();
402+
if (_double_clicked_callback) {
403+
_double_clicked_callback();
361404
}
362405
} else {
363-
if (_leftClickedBlock) {
364-
_leftClickedBlock();
406+
if (_left_clicked_callback) {
407+
_left_clicked_callback();
365408
}
366409
}
367410
}

src/platform/windows/tray_icon_windows.cpp

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class TrayIcon::Impl {
3333
using RightClickedCallback = std::function<void(TrayIconId)>;
3434
using DoubleClickedCallback = std::function<void(TrayIconId)>;
3535

36-
Impl() : hwnd_(nullptr), icon_handle_(nullptr) {
36+
Impl() : hwnd_(nullptr), icon_handle_(nullptr), window_proc_handle_id_(-1), event_monitoring_setup_(false) {
3737
tray_icon_id_ = IdAllocator::Allocate<TrayIcon>();
3838
}
3939

@@ -43,9 +43,11 @@ class TrayIcon::Impl {
4343
DoubleClickedCallback double_clicked_callback)
4444
: hwnd_(hwnd),
4545
icon_handle_(nullptr),
46+
window_proc_handle_id_(-1),
4647
clicked_callback_(std::move(clicked_callback)),
4748
right_clicked_callback_(std::move(right_clicked_callback)),
48-
double_clicked_callback_(std::move(double_clicked_callback)) {
49+
double_clicked_callback_(std::move(double_clicked_callback)),
50+
event_monitoring_setup_(false) {
4951
tray_icon_id_ = IdAllocator::Allocate<TrayIcon>();
5052
// Initialize NOTIFYICONDATA structure
5153
ZeroMemory(&nid_, sizeof(NOTIFYICONDATAW));
@@ -55,16 +57,14 @@ class TrayIcon::Impl {
5557
nid_.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
5658
nid_.uCallbackMessage = WM_USER + 1; // Custom message for tray icon events
5759

58-
window_proc_handle_id_ = WindowMessageDispatcher::GetInstance().RegisterHandler(
59-
hwnd, [this](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
60-
return HandleWindowProc(hwnd, message, wparam, lparam);
61-
});
60+
// Event monitoring will be set up when first listener is added
61+
// via StartEventListening() override
6262
}
6363

6464
~Impl() {
65-
// Unregister window procedure handler
66-
if (window_proc_handle_id_ != -1) {
67-
WindowMessageDispatcher::GetInstance().UnregisterHandler(window_proc_handle_id_);
65+
// Clean up event monitoring if it was set up
66+
if (event_monitoring_setup_) {
67+
CleanupEventMonitoring();
6868
}
6969

7070
if (hwnd_) {
@@ -105,12 +105,45 @@ class TrayIcon::Impl {
105105
return std::nullopt; // Let default window procedure handle it
106106
}
107107

108+
void SetupEventMonitoring() {
109+
if (event_monitoring_setup_) {
110+
return; // Already set up
111+
}
112+
113+
if (!hwnd_) {
114+
return;
115+
}
116+
117+
// Register window procedure handler
118+
window_proc_handle_id_ = WindowMessageDispatcher::GetInstance().RegisterHandler(
119+
hwnd_, [this](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
120+
return HandleWindowProc(hwnd, message, wparam, lparam);
121+
});
122+
123+
event_monitoring_setup_ = true;
124+
}
125+
126+
void CleanupEventMonitoring() {
127+
if (!event_monitoring_setup_) {
128+
return; // Not set up
129+
}
130+
131+
// Unregister window procedure handler
132+
if (window_proc_handle_id_ != -1) {
133+
WindowMessageDispatcher::GetInstance().UnregisterHandler(window_proc_handle_id_);
134+
window_proc_handle_id_ = -1;
135+
}
136+
137+
event_monitoring_setup_ = false;
138+
}
139+
108140
int window_proc_handle_id_;
109141
HWND hwnd_;
110142
NOTIFYICONDATAW nid_;
111143
std::shared_ptr<Menu> context_menu_;
112144
HICON icon_handle_;
113145
TrayIconId tray_icon_id_;
146+
bool event_monitoring_setup_;
114147

115148
// Callback functions for event emission
116149
ClickedCallback clicked_callback_;
@@ -158,6 +191,18 @@ TrayIcon::TrayIcon(void* native_tray_icon) {
158191

159192
TrayIcon::~TrayIcon() {}
160193

194+
void TrayIcon::StartEventListening() {
195+
// Called automatically when first listener is added
196+
// Set up platform event monitoring
197+
pimpl_->SetupEventMonitoring();
198+
}
199+
200+
void TrayIcon::StopEventListening() {
201+
// Called automatically when last listener is removed
202+
// Clean up platform event monitoring
203+
pimpl_->CleanupEventMonitoring();
204+
}
205+
161206
TrayIconId TrayIcon::GetId() {
162207
return pimpl_->tray_icon_id_;
163208
}

src/tray_icon.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,24 @@ class TrayIcon : public EventEmitter<TrayIconEvent>, public NativeObjectProvider
333333
bool CloseContextMenu();
334334

335335
protected:
336+
/**
337+
* @brief Called when the first listener is added.
338+
*
339+
* Subclasses can override this to start platform-specific event monitoring.
340+
* This is called automatically by the EventEmitter when transitioning from
341+
* 0 to 1+ listeners.
342+
*/
343+
void StartEventListening() override;
344+
345+
/**
346+
* @brief Called when the last listener is removed.
347+
*
348+
* Subclasses can override this to stop platform-specific event monitoring.
349+
* This is called automatically by the EventEmitter when transitioning from
350+
* 1+ to 0 listeners.
351+
*/
352+
void StopEventListening() override;
353+
336354
/**
337355
* @brief Internal method to get the platform-specific native tray icon object.
338356
*

0 commit comments

Comments
 (0)