Skip to content

Commit b4b520a

Browse files
committed
Add configurable context menu trigger for tray icons
Introduces ContextMenuTrigger enum and related API to control how tray icon context menus are shown (None, Clicked, RightClicked, DoubleClicked). Implements trigger logic for Windows and macOS, stores trigger setting for Linux, and exposes C/C++ API for setting and querying the trigger mode. Updates example applications to demonstrate usage and display current trigger mode.
1 parent e7fb964 commit b4b520a

File tree

8 files changed

+296
-16
lines changed

8 files changed

+296
-16
lines changed

examples/tray_icon_c_example/main.c

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,27 @@ int main() {
172172
// Set the context menu
173173
native_tray_icon_set_context_menu(tray_icon, menu);
174174

175+
// Set context menu trigger to automatically show menu on right click
176+
native_tray_icon_set_context_menu_trigger(tray_icon, NATIVE_CONTEXT_MENU_TRIGGER_RIGHT_CLICKED);
177+
178+
// Get and display the current trigger mode
179+
native_context_menu_trigger_t current_trigger = native_tray_icon_get_context_menu_trigger(tray_icon);
180+
printf("Context menu trigger mode: ");
181+
switch (current_trigger) {
182+
case NATIVE_CONTEXT_MENU_TRIGGER_NONE:
183+
printf("None (manual control)\n");
184+
break;
185+
case NATIVE_CONTEXT_MENU_TRIGGER_CLICKED:
186+
printf("Left Click\n");
187+
break;
188+
case NATIVE_CONTEXT_MENU_TRIGGER_RIGHT_CLICKED:
189+
printf("Right Click\n");
190+
break;
191+
case NATIVE_CONTEXT_MENU_TRIGGER_DOUBLE_CLICKED:
192+
printf("Double Click\n");
193+
break;
194+
}
195+
175196
// Set up tray icon event listeners using new API
176197
native_tray_icon_add_listener(tray_icon, NATIVE_TRAY_ICON_EVENT_CLICKED, on_tray_clicked, NULL);
177198
native_tray_icon_add_listener(tray_icon, NATIVE_TRAY_ICON_EVENT_RIGHT_CLICKED,
@@ -200,10 +221,12 @@ int main() {
200221

201222
printf("\n=== Tray icon and menu are now active ===\n");
202223
printf("- Click the tray icon to see click message\n");
203-
printf("- Right click the tray icon to open context menu\n");
224+
printf("- Right click the tray icon to auto-open context menu\n");
204225
printf("- Double click the tray icon to see double click message\n");
205226
printf("- Use menu items to interact with the application\n");
206227
printf("- Click 'Exit' to quit\n");
228+
printf("\nNote: Context menu automatically shows on right-click\n");
229+
printf(" because we set NATIVE_CONTEXT_MENU_TRIGGER_RIGHT_CLICKED.\n");
207230
printf("\nRunning... (Press Ctrl+C to force quit)\n");
208231

209232
int exit_code = native_run_example_app();

examples/tray_icon_example/main.cpp

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,12 @@ int main() {
4848
std::cout << "Tray icon ID: " << event.GetTrayIconId() << std::endl;
4949
});
5050

51-
trayIcon->AddListener<TrayIconRightClickedEvent>(
52-
[trayIcon](const TrayIconRightClickedEvent& event) {
53-
std::cout << "*** TRAY ICON RIGHT CLICKED! ***" << std::endl;
54-
std::cout << "This is the right click handler working!" << std::endl;
55-
std::cout << "Tray icon ID: " << event.GetTrayIconId() << std::endl;
56-
trayIcon->OpenContextMenu();
57-
});
51+
trayIcon->AddListener<TrayIconRightClickedEvent>([](const TrayIconRightClickedEvent& event) {
52+
std::cout << "*** TRAY ICON RIGHT CLICKED! ***" << std::endl;
53+
std::cout << "This is the right click handler working!" << std::endl;
54+
std::cout << "Tray icon ID: " << event.GetTrayIconId() << std::endl;
55+
// Note: Context menu will be auto-triggered by SetContextMenuTrigger below
56+
});
5857

5958
trayIcon->AddListener<TrayIconDoubleClickedEvent>([](const TrayIconDoubleClickedEvent& event) {
6059
std::cout << "*** TRAY ICON DOUBLE CLICKED! ***" << std::endl;
@@ -105,6 +104,29 @@ int main() {
105104
// Set the context menu to the tray icon
106105
trayIcon->SetContextMenu(context_menu);
107106

107+
// Set context menu trigger to automatically show menu on right click
108+
// This is the common behavior on Windows and most desktop environments
109+
trayIcon->SetContextMenuTrigger(ContextMenuTrigger::RightClicked);
110+
111+
// Get and display the current trigger mode
112+
ContextMenuTrigger currentTrigger = trayIcon->GetContextMenuTrigger();
113+
std::cout << "Context menu trigger mode: ";
114+
switch (currentTrigger) {
115+
case ContextMenuTrigger::None:
116+
std::cout << "None (manual control)";
117+
break;
118+
case ContextMenuTrigger::Clicked:
119+
std::cout << "Left Click";
120+
break;
121+
case ContextMenuTrigger::RightClicked:
122+
std::cout << "Right Click";
123+
break;
124+
case ContextMenuTrigger::DoubleClicked:
125+
std::cout << "Double Click";
126+
break;
127+
}
128+
std::cout << std::endl;
129+
108130
// Show the tray icon
109131
if (trayIcon->SetVisible(true)) {
110132
std::cout << "Tray icon is now visible!" << std::endl;
@@ -121,10 +143,15 @@ int main() {
121143
std::cout << "========================================" << std::endl;
122144
std::cout << "Tray icon example is now running!" << std::endl;
123145
std::cout << "Try clicking on the tray icon:" << std::endl;
124-
std::cout << "- Left click: Single click" << std::endl;
125-
std::cout << "- Right click: Opens context menu" << std::endl;
126-
std::cout << "- Double click: Quick double click" << std::endl;
146+
std::cout << "- Left click: Single click event" << std::endl;
147+
std::cout << "- Right click: Auto-opens context menu (via SetContextMenuTrigger)" << std::endl;
148+
std::cout << "- Double click: Quick double click event" << std::endl;
127149
std::cout << "- Context menu: Right-click to see options including Exit" << std::endl;
150+
std::cout << std::endl;
151+
std::cout << "Note: The context menu is automatically shown on right-click" << std::endl;
152+
std::cout << " because we set ContextMenuTrigger::RightClicked." << std::endl;
153+
std::cout << " You can also use Clicked, DoubleClicked, or None for manual control." << std::endl;
154+
std::cout << std::endl;
128155
std::cout << "Use the Exit menu item to quit the application." << std::endl;
129156
std::cout << "========================================" << std::endl;
130157

src/capi/tray_icon_c.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,67 @@ native_menu_t native_tray_icon_get_context_menu(native_tray_icon_t tray_icon) {
230230
}
231231
}
232232

233+
void native_tray_icon_set_context_menu_trigger(native_tray_icon_t tray_icon,
234+
native_context_menu_trigger_t trigger) {
235+
if (!tray_icon)
236+
return;
237+
238+
try {
239+
auto tray_icon_ptr = static_cast<TrayIcon*>(tray_icon);
240+
241+
// Convert C enum to C++ enum
242+
ContextMenuTrigger cpp_trigger;
243+
switch (trigger) {
244+
case NATIVE_CONTEXT_MENU_TRIGGER_NONE:
245+
cpp_trigger = ContextMenuTrigger::None;
246+
break;
247+
case NATIVE_CONTEXT_MENU_TRIGGER_CLICKED:
248+
cpp_trigger = ContextMenuTrigger::Clicked;
249+
break;
250+
case NATIVE_CONTEXT_MENU_TRIGGER_RIGHT_CLICKED:
251+
cpp_trigger = ContextMenuTrigger::RightClicked;
252+
break;
253+
case NATIVE_CONTEXT_MENU_TRIGGER_DOUBLE_CLICKED:
254+
cpp_trigger = ContextMenuTrigger::DoubleClicked;
255+
break;
256+
default:
257+
cpp_trigger = ContextMenuTrigger::None;
258+
break;
259+
}
260+
261+
tray_icon_ptr->SetContextMenuTrigger(cpp_trigger);
262+
} catch (...) {
263+
// Ignore exceptions
264+
}
265+
}
266+
267+
native_context_menu_trigger_t native_tray_icon_get_context_menu_trigger(
268+
native_tray_icon_t tray_icon) {
269+
if (!tray_icon)
270+
return NATIVE_CONTEXT_MENU_TRIGGER_NONE;
271+
272+
try {
273+
auto tray_icon_ptr = static_cast<TrayIcon*>(tray_icon);
274+
ContextMenuTrigger cpp_trigger = tray_icon_ptr->GetContextMenuTrigger();
275+
276+
// Convert C++ enum to C enum
277+
switch (cpp_trigger) {
278+
case ContextMenuTrigger::None:
279+
return NATIVE_CONTEXT_MENU_TRIGGER_NONE;
280+
case ContextMenuTrigger::Clicked:
281+
return NATIVE_CONTEXT_MENU_TRIGGER_CLICKED;
282+
case ContextMenuTrigger::RightClicked:
283+
return NATIVE_CONTEXT_MENU_TRIGGER_RIGHT_CLICKED;
284+
case ContextMenuTrigger::DoubleClicked:
285+
return NATIVE_CONTEXT_MENU_TRIGGER_DOUBLE_CLICKED;
286+
default:
287+
return NATIVE_CONTEXT_MENU_TRIGGER_NONE;
288+
}
289+
} catch (...) {
290+
return NATIVE_CONTEXT_MENU_TRIGGER_NONE;
291+
}
292+
}
293+
233294
bool native_tray_icon_get_bounds(native_tray_icon_t tray_icon, native_rectangle_t* bounds) {
234295
if (!tray_icon || !bounds)
235296
return false;

src/capi/tray_icon_c.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ typedef enum {
5858
NATIVE_TRAY_ICON_EVENT_DOUBLE_CLICKED = 2
5959
} native_tray_icon_event_type_t;
6060

61+
/**
62+
* Context menu trigger modes
63+
* Defines how the context menu is triggered for a tray icon
64+
*/
65+
typedef enum {
66+
NATIVE_CONTEXT_MENU_TRIGGER_NONE = 0, // Manual control only
67+
NATIVE_CONTEXT_MENU_TRIGGER_CLICKED = 1, // Left click triggers menu
68+
NATIVE_CONTEXT_MENU_TRIGGER_RIGHT_CLICKED = 2, // Right click triggers menu
69+
NATIVE_CONTEXT_MENU_TRIGGER_DOUBLE_CLICKED = 3 // Double click triggers menu
70+
} native_context_menu_trigger_t;
71+
6172
/**
6273
* Event callback function type
6374
*/
@@ -164,6 +175,24 @@ void native_tray_icon_set_context_menu(native_tray_icon_t tray_icon, native_menu
164175
FFI_PLUGIN_EXPORT
165176
native_menu_t native_tray_icon_get_context_menu(native_tray_icon_t tray_icon);
166177

178+
/**
179+
* Set the context menu trigger behavior
180+
* @param tray_icon The tray icon
181+
* @param trigger The desired trigger behavior
182+
*/
183+
FFI_PLUGIN_EXPORT
184+
void native_tray_icon_set_context_menu_trigger(native_tray_icon_t tray_icon,
185+
native_context_menu_trigger_t trigger);
186+
187+
/**
188+
* Get the current context menu trigger behavior
189+
* @param tray_icon The tray icon
190+
* @return The current trigger behavior
191+
*/
192+
FFI_PLUGIN_EXPORT
193+
native_context_menu_trigger_t native_tray_icon_get_context_menu_trigger(
194+
native_tray_icon_t tray_icon);
195+
167196
/**
168197
* Get the screen bounds of the tray icon
169198
* @param tray_icon The tray icon

src/platform/linux/tray_icon_linux.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ class TrayIcon::Impl {
3333
title_(std::nullopt),
3434
tooltip_(std::nullopt),
3535
context_menu_(nullptr),
36-
visible_(false) {
36+
visible_(false),
37+
context_menu_trigger_(ContextMenuTrigger::None) {
3738
id_ = IdAllocator::Allocate<TrayIcon>();
3839
}
3940

@@ -59,6 +60,7 @@ class TrayIcon::Impl {
5960
bool visible_;
6061
TrayIconId id_;
6162
std::vector<guint> pending_cleanup_sources_; // Track GLib timeout source IDs
63+
ContextMenuTrigger context_menu_trigger_;
6264
};
6365

6466
TrayIcon::TrayIcon() : pimpl_(std::make_unique<Impl>(nullptr)) {
@@ -276,6 +278,16 @@ bool TrayIcon::CloseContextMenu() {
276278
return true;
277279
}
278280

281+
void TrayIcon::SetContextMenuTrigger(ContextMenuTrigger trigger) {
282+
pimpl_->context_menu_trigger_ = trigger;
283+
// Note: On Linux with AppIndicator, the menu is automatically shown on right-click
284+
// This setting is stored for compatibility but has limited effect on AppIndicator behavior
285+
}
286+
287+
ContextMenuTrigger TrayIcon::GetContextMenuTrigger() {
288+
return pimpl_->context_menu_trigger_;
289+
}
290+
279291
void* TrayIcon::GetNativeObjectInternal() const {
280292
return static_cast<void*>(pimpl_->app_indicator_);
281293
}

src/platform/macos/tray_icon_macos.mm

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ - (void)handleStatusItemEvent:(id)sender;
3939
: ns_status_item_(status_item),
4040
ns_status_bar_button_target_(nil),
4141
menu_closed_listener_id_(0),
42-
click_handler_setup_(false) {
42+
click_handler_setup_(false),
43+
context_menu_trigger_(ContextMenuTrigger::None) {
4344
if (status_item) {
4445
// Check if ID already exists in the associated object
4546
NSNumber* allocated_id = objc_getAssociatedObject(status_item, kTrayIconIdKey);
@@ -137,6 +138,7 @@ void CleanupEventHandlers() {
137138
std::shared_ptr<Menu> context_menu_;
138139
size_t menu_closed_listener_id_;
139140
bool click_handler_setup_;
141+
ContextMenuTrigger context_menu_trigger_;
140142
};
141143

142144
TrayIcon::TrayIcon() : TrayIcon(nullptr) {}
@@ -170,14 +172,26 @@ void CleanupEventHandlers() {
170172
if (pimpl_->ns_status_bar_button_target_) {
171173
pimpl_->ns_status_bar_button_target_.left_clicked_callback_ = ^{
172174
Emit<TrayIconClickedEvent>(pimpl_->id_);
175+
// Auto-trigger context menu if configured
176+
if (pimpl_->context_menu_trigger_ == ContextMenuTrigger::Clicked) {
177+
OpenContextMenu();
178+
}
173179
};
174180

175181
pimpl_->ns_status_bar_button_target_.right_clicked_callback_ = ^{
176182
Emit<TrayIconRightClickedEvent>(pimpl_->id_);
183+
// Auto-trigger context menu if configured
184+
if (pimpl_->context_menu_trigger_ == ContextMenuTrigger::RightClicked) {
185+
OpenContextMenu();
186+
}
177187
};
178188

179189
pimpl_->ns_status_bar_button_target_.double_clicked_callback_ = ^{
180190
Emit<TrayIconDoubleClickedEvent>(pimpl_->id_);
191+
// Auto-trigger context menu if configured
192+
if (pimpl_->context_menu_trigger_ == ContextMenuTrigger::DoubleClicked) {
193+
OpenContextMenu();
194+
}
181195
};
182196
}
183197
}
@@ -366,6 +380,14 @@ void CleanupEventHandlers() {
366380
return pimpl_->context_menu_->Close();
367381
}
368382

383+
void TrayIcon::SetContextMenuTrigger(ContextMenuTrigger trigger) {
384+
pimpl_->context_menu_trigger_ = trigger;
385+
}
386+
387+
ContextMenuTrigger TrayIcon::GetContextMenuTrigger() {
388+
return pimpl_->context_menu_trigger_;
389+
}
390+
369391
void* TrayIcon::GetNativeObjectInternal() const {
370392
return (__bridge void*)pimpl_->ns_status_item_;
371393
}

src/platform/windows/tray_icon_windows.cpp

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ class TrayIcon::Impl {
3838
: hwnd_(nullptr),
3939
icon_handle_(nullptr),
4040
window_proc_handle_id_(-1),
41-
event_monitoring_setup_(false) {
41+
event_monitoring_setup_(false),
42+
context_menu_trigger_(ContextMenuTrigger::None) {
4243
tray_icon_id_ = IdAllocator::Allocate<TrayIcon>();
4344
}
4445

@@ -52,7 +53,8 @@ class TrayIcon::Impl {
5253
clicked_callback_(std::move(clicked_callback)),
5354
right_clicked_callback_(std::move(right_clicked_callback)),
5455
double_clicked_callback_(std::move(double_clicked_callback)),
55-
event_monitoring_setup_(false) {
56+
event_monitoring_setup_(false),
57+
context_menu_trigger_(ContextMenuTrigger::None) {
5658
tray_icon_id_ = IdAllocator::Allocate<TrayIcon>();
5759
// Initialize NOTIFYICONDATA structure
5860
ZeroMemory(&nid_, sizeof(NOTIFYICONDATAW));
@@ -149,6 +151,7 @@ class TrayIcon::Impl {
149151
HICON icon_handle_;
150152
TrayIconId tray_icon_id_;
151153
bool event_monitoring_setup_;
154+
ContextMenuTrigger context_menu_trigger_;
152155

153156
// Callback functions for event emission
154157
ClickedCallback clicked_callback_;
@@ -175,14 +178,28 @@ TrayIcon::TrayIcon(void* native_tray_icon) {
175178
// The tray_icon_id will be allocated inside Impl constructor
176179
if (hwnd) {
177180
// Create callback functions that emit events
178-
auto clicked_callback = [this](TrayIconId id) { this->Emit<TrayIconClickedEvent>(id); };
181+
auto clicked_callback = [this](TrayIconId id) {
182+
this->Emit<TrayIconClickedEvent>(id);
183+
// Auto-trigger context menu if configured
184+
if (pimpl_ && pimpl_->context_menu_trigger_ == ContextMenuTrigger::Clicked) {
185+
this->OpenContextMenu();
186+
}
187+
};
179188

180189
auto right_clicked_callback = [this](TrayIconId id) {
181190
this->Emit<TrayIconRightClickedEvent>(id);
191+
// Auto-trigger context menu if configured
192+
if (pimpl_ && pimpl_->context_menu_trigger_ == ContextMenuTrigger::RightClicked) {
193+
this->OpenContextMenu();
194+
}
182195
};
183196

184197
auto double_clicked_callback = [this](TrayIconId id) {
185198
this->Emit<TrayIconDoubleClickedEvent>(id);
199+
// Auto-trigger context menu if configured
200+
if (pimpl_ && pimpl_->context_menu_trigger_ == ContextMenuTrigger::DoubleClicked) {
201+
this->OpenContextMenu();
202+
}
186203
};
187204

188205
pimpl_ =
@@ -372,6 +389,14 @@ bool TrayIcon::CloseContextMenu() {
372389
return pimpl_->context_menu_->Close();
373390
}
374391

392+
void TrayIcon::SetContextMenuTrigger(ContextMenuTrigger trigger) {
393+
pimpl_->context_menu_trigger_ = trigger;
394+
}
395+
396+
ContextMenuTrigger TrayIcon::GetContextMenuTrigger() {
397+
return pimpl_->context_menu_trigger_;
398+
}
399+
375400
void* TrayIcon::GetNativeObjectInternal() const {
376401
return reinterpret_cast<void*>(static_cast<uintptr_t>(pimpl_->tray_icon_id_));
377402
}

0 commit comments

Comments
 (0)