@@ -9,6 +9,7 @@ import 'package:tray_manager/tray_manager.dart';
99import '../core/plugin_manager.dart' ;
1010import 'package:crossbar_core/crossbar_core.dart' as plugin_model;
1111import 'logger_service.dart' ;
12+ import 'refresh_service.dart' ;
1213import 'scheduler_service.dart' ;
1314import 'settings_service.dart' ;
1415import 'tray/tray_backend.dart' ;
@@ -76,11 +77,13 @@ class TrayService with TrayListener {
7677
7778 // Set up tray based on mode
7879 if (_useSeparateMode) {
79- // SEPARATE MODE: Use only SNI backend, no unified tray_manager
80- LoggerService ().info ('TrayService: Using separate mode (SNI)' );
81- // Icons will be created when plugins output is received
80+ // SEPARATE MODE: Use BOTH tray_manager (crossbar control) + SNI daemons (plugins)
81+ LoggerService ().info ('TrayService: Using separate mode (mixed)' );
82+ // Initialize tray_manager for crossbar control icon (Show/Quit)
83+ await _initUnifiedTray ();
84+ // Plugin icons will be created via daemons when plugins output is received
8285 } else {
83- // UNIFIED MODE: Use tray_manager for single icon
86+ // UNIFIED MODE: Use tray_manager for single icon with all plugins
8487 LoggerService ().info ('TrayService: Using unified mode (tray_manager)' );
8588 await _initUnifiedTray ();
8689 }
@@ -208,39 +211,53 @@ class TrayService with TrayListener {
208211
209212 final menuItems = < MenuItem > [];
210213
211- // Plugin outputs
212- for (final plugin in _pluginManager.plugins.where ((p) => p.enabled)) {
213- final output = _pluginOutputs[plugin.id];
214- if (output != null && output.text != null && output.text! .isNotEmpty) {
215- if (output.menu.isNotEmpty) {
216- final submenuItems = _convertMenuItems (output.menu);
217- menuItems.add (MenuItem .submenu (
218- label: '${output .icon } ${output .text }' ,
219- submenu: Menu (items: submenuItems),
220- ));
221- } else {
222- menuItems.add (MenuItem (
223- label: '${output .icon } ${output .text }' ,
224- disabled: true ,
225- ));
214+ // Plugin outputs - only show in unified mode (in separate mode they have their own icons)
215+ if (! _useSeparateMode) {
216+ for (final plugin in _pluginManager.plugins.where ((p) => p.enabled)) {
217+ final output = _pluginOutputs[plugin.id];
218+ if (output != null && output.text != null && output.text! .isNotEmpty) {
219+ if (output.menu.isNotEmpty) {
220+ final submenuItems = _convertMenuItems (output.menu);
221+ menuItems.add (MenuItem .submenu (
222+ label: '${output .icon } ${output .text }' ,
223+ submenu: Menu (items: submenuItems),
224+ ));
225+ } else {
226+ menuItems.add (MenuItem (
227+ label: '${output .icon } ${output .text }' ,
228+ disabled: true ,
229+ ));
230+ }
226231 }
227232 }
228- }
229233
230- if (menuItems.isNotEmpty) {
231- menuItems.add (MenuItem .separator ());
234+ if (menuItems.isNotEmpty) {
235+ menuItems.add (MenuItem .separator ());
236+ }
232237 }
233238
234239 // Standard menu items
235- menuItems.addAll ([
236- MenuItem (key: 'show' , label: 'Show Crossbar' ),
237- MenuItem (key: 'refresh' , label: 'Refresh All Plugins' ),
238- MenuItem .separator (),
239- MenuItem (key: 'quit' , label: 'Quit' ),
240- ]);
240+ if (_useSeparateMode) {
241+ // In separate mode, only Show and Quit (no Refresh All since plugins have their own)
242+ menuItems.addAll ([
243+ MenuItem (key: 'show' , label: 'Show' ),
244+ MenuItem .separator (),
245+ MenuItem (key: 'quit' , label: 'Quit' ),
246+ ]);
247+ } else {
248+ menuItems.addAll ([
249+ MenuItem (key: 'show' , label: 'Show Crossbar' ),
250+ MenuItem (key: 'refresh' , label: 'Refresh All Plugins' ),
251+ MenuItem .separator (),
252+ MenuItem (key: 'quit' , label: 'Quit' ),
253+ ]);
254+ }
255+
256+ LoggerService ().info ('TrayService: Setting menu with ${menuItems .length } items, separateMode=$_useSeparateMode ' );
241257
242258 try {
243259 await trayManager.setContextMenu (Menu (items: menuItems));
260+ LoggerService ().info ('TrayService: Menu set successfully' );
244261 } catch (e) {
245262 LoggerService ().warning ('Failed to set tray context menu: $e ' );
246263 }
@@ -370,9 +387,9 @@ class TrayService with TrayListener {
370387 // Standard actions
371388 items.addAll ([
372389 TrayMenuItem (label: 'Refresh' , key: 'refresh_$pluginId ' ),
390+ TrayMenuItem (label: 'Disable' , key: 'disable_$pluginId ' ),
373391 const TrayMenuItem .separator (),
374392 const TrayMenuItem (label: 'Show Crossbar' , key: 'show' ),
375- const TrayMenuItem (label: 'Quit' , key: 'quit' ),
376393 ]);
377394
378395 return items;
@@ -487,7 +504,23 @@ class TrayService with TrayListener {
487504
488505 @override
489506 void onTrayMenuItemClick (MenuItem menuItem) {
490- switch (menuItem.key) {
507+ final key = menuItem.key ?? '' ;
508+
509+ // Handle disable_pluginId actions
510+ if (key.startsWith ('disable_' )) {
511+ final pluginId = key.substring (8 );
512+ _disablePlugin (pluginId);
513+ return ;
514+ }
515+
516+ // Handle refresh_pluginId actions
517+ if (key.startsWith ('refresh_' )) {
518+ final pluginId = key.substring (8 );
519+ SchedulerService ().runPluginNow (pluginId);
520+ return ;
521+ }
522+
523+ switch (key) {
491524 case 'show' :
492525 WindowService ().show ();
493526 case 'refresh' :
@@ -497,6 +530,23 @@ class TrayService with TrayListener {
497530 }
498531 }
499532
533+ /// Disables a plugin and removes its tray icon.
534+ Future <void > _disablePlugin (String pluginId) async {
535+ // Disable the plugin via RefreshService (handles outputs and notifications)
536+ await RefreshService ().disablePlugin (pluginId);
537+
538+ // Remove the tray icon
539+ final iconId = _pluginIconIds.remove (pluginId);
540+ if (iconId != null && _backend != null ) {
541+ await _backend! .destroyIcon (iconId);
542+ }
543+
544+ // Clear output
545+ _pluginOutputs.remove (pluginId);
546+
547+ LoggerService ().info ('TrayService: Disabled plugin $pluginId ' );
548+ }
549+
500550 Future <void > dispose () async {
501551 if (! _initialized) return ;
502552
0 commit comments