diff --git a/example/pubspec.lock b/example/pubspec.lock index 69083bb..98661a7 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1,118 +1,150 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: name: async - url: "https://pub.flutter-io.cn" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.13.0" bitsdojo_window: dependency: "direct main" description: name: bitsdojo_window - url: "https://pub.flutter-io.cn" + sha256: "69afdbea4273d984ef8064be967f8cdc303a79909879867afecbbf56f5ebc35f" + url: "https://pub.dev" source: hosted version: "0.1.2" bitsdojo_window_linux: dependency: transitive description: name: bitsdojo_window_linux - url: "https://pub.flutter-io.cn" + sha256: "9519c0614f98be733e0b1b7cb15b827007886f6fe36a4fb62cf3d35b9dd578ab" + url: "https://pub.dev" source: hosted - version: "0.1.2" + version: "0.1.4" bitsdojo_window_macos: dependency: transitive description: name: bitsdojo_window_macos - url: "https://pub.flutter-io.cn" + sha256: f7c5be82e74568c68c5b8449e2c5d8fd12ec195ecd70745a7b9c0f802bb0268f + url: "https://pub.dev" source: hosted - version: "0.1.2" + version: "0.1.4" bitsdojo_window_platform_interface: dependency: transitive description: name: bitsdojo_window_platform_interface - url: "https://pub.flutter-io.cn" + sha256: "65daa015a0c6dba749bdd35a0f092e7a8ba8b0766aa0480eb3ef808086f6e27c" + url: "https://pub.dev" source: hosted version: "0.1.2" bitsdojo_window_windows: dependency: transitive description: name: bitsdojo_window_windows - url: "https://pub.flutter-io.cn" + sha256: fa982cf61ede53f483e50b257344a1c250af231a3cdc93a7064dd6dc0d720b68 + url: "https://pub.dev" source: hosted - version: "0.1.2" + version: "0.1.6" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.flutter-io.cn" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" characters: dependency: transitive description: name: characters - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.flutter-io.cn" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.4.0" clock: dependency: transitive description: name: clock - url: "https://pub.flutter-io.cn" + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" collection: dependency: transitive description: name: collection - url: "https://pub.flutter-io.cn" + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.19.1" crypto: dependency: transitive description: name: crypto - url: "https://pub.flutter-io.cn" + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.7" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.flutter-io.cn" + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.8" + dart_libayatana_appindicator: + dependency: transitive + description: + name: dart_libayatana_appindicator + sha256: "8eb0e7f96f9693a6156f70732c4ce85350107b8d183b700dad5047ae263ce2ba" + url: "https://pub.dev" + source: hosted + version: "0.1.5" + dbus: + dependency: transitive + description: + name: dbus + sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 + url: "https://pub.dev" + source: hosted + version: "0.7.12" english_words: dependency: "direct main" description: name: english_words - url: "https://pub.flutter-io.cn" + sha256: "6a7ef6473a97bd8571b6b641d006a6e58a7c67e65fb6f3d6d1151cb46b0e983c" + url: "https://pub.dev" source: hosted version: "4.0.0" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.flutter-io.cn" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.3.3" ffi: dependency: transitive description: name: ffi - url: "https://pub.flutter-io.cn" + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "2.2.0" flutter: dependency: "direct main" description: flutter @@ -122,7 +154,8 @@ packages: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.flutter-io.cn" + sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493 + url: "https://pub.dev" source: hosted version: "1.0.4" flutter_test: @@ -130,130 +163,194 @@ packages: description: flutter source: sdk version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" lints: dependency: transitive description: name: lints - url: "https://pub.flutter-io.cn" + sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c + url: "https://pub.dev" source: hosted version: "1.0.1" matcher: dependency: transitive description: name: matcher - url: "https://pub.flutter-io.cn" + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" source: hosted - version: "0.12.11" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.flutter-io.cn" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" source: hosted - version: "0.1.4" + version: "0.11.1" meta: dependency: transitive description: name: meta - url: "https://pub.flutter-io.cn" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.17.0" path: dependency: transitive description: name: path - url: "https://pub.flutter-io.cn" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" source: hosted - version: "1.8.1" + version: "7.0.2" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.flutter-io.cn" + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.8" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - url: "https://pub.flutter-io.cn" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.10.2" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.flutter-io.cn" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.flutter-io.cn" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.flutter-io.cn" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.4.1" system_tray: dependency: "direct main" description: path: ".." relative: true source: path - version: "2.0.1" + version: "2.0.3" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.flutter-io.cn" + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - url: "https://pub.flutter-io.cn" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + url: "https://pub.dev" source: hosted - version: "0.4.9" + version: "0.7.7" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.flutter-io.cn" + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.4.0" uuid: dependency: transitive description: name: uuid - url: "https://pub.flutter-io.cn" + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.flutter-io.cn" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" + source: hosted + version: "15.0.2" win32: dependency: transitive description: name: win32 - url: "https://pub.flutter-io.cn" + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "6.6.1" sdks: - dart: ">=2.17.0 <3.0.0" - flutter: ">=1.22.0" + dart: ">=3.8.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/lib/src/menu.dart b/lib/src/menu.dart index 93ce2bc..7b0a114 100644 --- a/lib/src/menu.dart +++ b/lib/src/menu.dart @@ -1,148 +1,120 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -import 'menu_item.dart'; -import 'utils.dart'; - -const String _kChannelName = "flutter/system_tray/menu_manager"; - -const String _kCreateContextMenu = "CreateContextMenu"; - -const String _kMenuIdKey = 'menu_id'; -const String _kMenuItemIdKey = 'menu_item_id'; -const String _kMenuListKey = 'menu_list'; - -const String _kMenuItemSelectedCallbackMethod = 'MenuItemSelectedCallback'; - -class Menu { - static const MethodChannel _platformChannel = MethodChannel(_kChannelName); - - static final Map _menuMap = {}; - - /// The ID to use the next time a menu needs an ID assigned. - static int _nextMenuId = 1; - - List? _menus; - - int _menuId = 1; - - int _menuItemId = 1; - - bool _updateInProgress = false; - - Menu() { - _platformChannel.setMethodCallHandler(_callbackHandler); - } - - int get menuId => _menuId; - - int get nextMenuItemId { - return _menuItemId++; - } - - Future buildFrom(List menus) async { - _menuId = _nextMenuId++; - _menus = menus; - _menuMap.putIfAbsent(_menuId, () => this); - return await _createContextMenu(_menus!); - } - - T? findItemByName(final String name) { - return _findItemByName(name, _menus!) as T; - } - - MenuItemBase? _findItemByName( - final String name, final List menus) { - MenuItemBase? item; - for (final menuItem in menus) { - if (menuItem is SubMenu) { - item = _findItemByName(name, menuItem.children); - } else if (menuItem.name == name) { - item = menuItem; - } - - if (item != null) { - break; - } - } - return item; - } - - MenuItemBase? _findItemById( - final int? menuItemId, final List? menus) { - MenuItemBase? item; - if (menuItemId != null && menus != null) { - for (final menuItem in menus) { - if (menuItem is SubMenu) { - item = _findItemById(menuItemId, menuItem.children); - } else if (menuItem.menuItemId == menuItemId) { - item = menuItem; - } - - if (item != null) { - break; - } - } - } - return item; - } - - Future _createContextMenu(List menus) async { - bool result = false; - try { - _updateInProgress = true; - - await _channelRepresentationForMenus(menus); - - result = await _platformChannel - .invokeMethod(_kCreateContextMenu, { - _kMenuIdKey: _menuId, - _kMenuListKey: menus.map((e) => e.toJson()).toList(), - }); - _updateInProgress = false; - } on PlatformException catch (e) { - debugPrint('Platform exception create context menu: ${e.message}'); - } - return result; - } - - Future _channelRepresentationForMenus(List menus) async { - _menuItemId = 1; - await _channelRepresentationForMenu(menus); - } - - Future _channelRepresentationForMenu(List menus) async { - for (final menuItem in menus) { - menuItem.channel = _platformChannel; - menuItem.menuId = menuId; - menuItem.menuItemId = nextMenuItemId; - menuItem.imageAbsolutePath = await Utils.getIcon(menuItem.image); - - if (menuItem is SubMenu) { - await _channelRepresentationForMenu(menuItem.children); - } - } - } - - Future _callbackHandler(MethodCall methodCall) async { - if (methodCall.method == _kMenuItemSelectedCallbackMethod) { - if (_updateInProgress) { - debugPrint( - 'Warning: Menu selection callback received during menu update.'); - return; - } - - final int? menuId = methodCall.arguments[_kMenuIdKey]; - final int? menuItemId = methodCall.arguments[_kMenuItemIdKey]; - final MenuItemBase? menuItem = - _findItemById(menuItemId, _menuMap[menuId]?._menus); - - debugPrint('MenuItemBase select menuId:$menuId menuItemId:$menuItemId'); - - final callback = menuItem?.onClicked; - if (callback != null) { - callback(menuItem!); - } - } - } -} +import 'package:flutter/material.dart'; + +import 'menu_item.dart'; +import 'platform/system_tray_platform_interface.dart'; + +class Menu { + static final Map _menuMap = {}; + + /// The ID to use the next time a menu needs an ID assigned. + static int _nextMenuId = 1; + + List? _menus; + + int _menuId = 1; + + int _menuItemId = 1; + + bool _updateInProgress = false; + + Menu() { + SystemTrayPlatform.instance.onMenuItemSelected ??= _callbackHandler; + } + + int get menuId => _menuId; + + int get nextMenuItemId { + return _menuItemId++; + } + + Future buildFrom(List menus) async { + _menuId = _nextMenuId++; + _menus = menus; + _menuMap.putIfAbsent(_menuId, () => this); + + _prepareMenuItems(menus); + + _updateInProgress = true; + bool result = await SystemTrayPlatform.instance.createContextMenu(_menuId, menus); + _updateInProgress = false; + + return result; + } + + void _prepareMenuItems(List menus) { + _menuItemId = 1; + _assignIds(menus); + } + + void _assignIds(List menus) { + for (final menuItem in menus) { + menuItem.menuId = menuId; + menuItem.menuItemId = nextMenuItemId; + + if (menuItem is SubMenu) { + _assignIds(menuItem.children); + } + } + } + + T? findItemByName(final String name) { + return _findItemByName(name, _menus!) as T; + } + + MenuItemBase? _findItemByName( + final String name, final List menus) { + MenuItemBase? item; + for (final menuItem in menus) { + if (menuItem is SubMenu) { + item = _findItemByName(name, menuItem.children); + } else if (menuItem.name == name) { + item = menuItem; + } + + if (item != null) { + break; + } + } + return item; + } + + MenuItemBase? _findItemById( + final int? menuItemId, final List? menus) { + MenuItemBase? item; + if (menuItemId != null && menus != null) { + for (final menuItem in menus) { + if (menuItem is SubMenu) { + item = _findItemById(menuItemId, menuItem.children); + } else if (menuItem.menuItemId == menuItemId) { + item = menuItem; + } + + if (item != null) { + break; + } + } + } + return item; + } + + static void _callbackHandler(int menuId, int menuItemId) { + final Menu? menu = _menuMap[menuId]; + if (menu != null) { + if (menu._updateInProgress) { + debugPrint( + 'Warning: Menu selection callback received during menu update.'); + return; + } + + final MenuItemBase? menuItem = + menu._findItemById(menuItemId, menu._menus); + + debugPrint('MenuItemBase select menuId:$menuId menuItemId:$menuItemId'); + + final callback = menuItem?.onClicked; + if (callback != null) { + callback(menuItem!); + } + } + } +} diff --git a/lib/src/menu_item.dart b/lib/src/menu_item.dart index 5a3a88e..860fc9d 100644 --- a/lib/src/menu_item.dart +++ b/lib/src/menu_item.dart @@ -1,15 +1,5 @@ -import 'package:flutter/services.dart'; -import 'package:system_tray/src/utils.dart'; +import 'package:system_tray/src/platform/system_tray_platform_interface.dart'; -const String _kSetLabel = "SetLabel"; -const String _kSetImage = "SetImage"; -const String _kSetEnable = "SetEnable"; -const String _kSetCheck = "SetCheck"; - -const String _kMenuIdKey = 'menu_id'; -const String _kMenuItemIdKey = 'menu_item_id'; -const String _kIdKey = 'id'; -const String _kTypeKey = 'type'; const String _kMenuTypeLabel = 'label'; const String _kMenuTypeCheckbox = 'checkbox'; const String _kMenuTypeSubMenu = 'submenu'; @@ -19,6 +9,8 @@ const String _kImageKey = 'image'; const String _kSubMenuKey = 'submenu'; const String _kEnabledKey = 'enabled'; const String _kCheckedKey = 'checked'; +const String _kIdKey = 'id'; +const String _kTypeKey = 'type'; /// A callback provided to [MenuItemBase] to handle menu selection. typedef MenuItemSelectedCallback = void Function(MenuItemBase); @@ -40,39 +32,24 @@ abstract class MenuItemBase { } Future setLabel(String label) async { - bool result = await channel?.invokeMethod(_kSetLabel, { - _kMenuIdKey: menuId ?? -1, - _kMenuItemIdKey: menuItemId ?? -1, - _kLabelKey: label, - }); - if (result) { - this.label = label; + if (menuId != null && menuItemId != null) { + await SystemTrayPlatform.instance.setMenuItemLabel(menuId!, menuItemId!, label); } + this.label = label; } Future setImage(String image) async { - String? imageAbsolutePath = await Utils.getIcon(image); - - bool result = await channel?.invokeMethod(_kSetImage, { - _kMenuIdKey: menuId ?? -1, - _kMenuItemIdKey: menuItemId ?? -1, - _kImageKey: imageAbsolutePath, - }); - if (result) { - this.image = image; - this.imageAbsolutePath = imageAbsolutePath; + if (menuId != null && menuItemId != null) { + await SystemTrayPlatform.instance.setMenuItemImage(menuId!, menuItemId!, image); } + this.image = image; } Future setEnable(bool enabled) async { - bool result = await channel?.invokeMethod(_kSetEnable, { - _kMenuIdKey: menuId ?? -1, - _kMenuItemIdKey: menuItemId ?? -1, - _kEnabledKey: enabled, - }); - if (result) { - this.enabled = enabled; + if (menuId != null && menuItemId != null) { + await SystemTrayPlatform.instance.setMenuItemEnable(menuId!, menuItemId!, enabled); } + this.enabled = enabled; } Future setCheck(bool checked) async { @@ -80,17 +57,12 @@ abstract class MenuItemBase { return; } - bool result = await channel?.invokeMethod(_kSetCheck, { - _kMenuIdKey: menuId ?? -1, - _kMenuItemIdKey: menuItemId ?? -1, - _kCheckedKey: checked, - }); - if (result) { - this.checked = checked; + if (menuId != null && menuItemId != null) { + await SystemTrayPlatform.instance.setMenuItemCheck(menuId!, menuItemId!, checked); } + this.checked = checked; } - MethodChannel? channel; int? menuId; int? menuItemId; String? imageAbsolutePath; diff --git a/lib/src/platform/system_tray_linux.dart b/lib/src/platform/system_tray_linux.dart new file mode 100644 index 0000000..2dcb5f2 --- /dev/null +++ b/lib/src/platform/system_tray_linux.dart @@ -0,0 +1,221 @@ +import 'dart:async'; + +import 'package:dart_libayatana_appindicator/dart_libayatana_appindicator.dart'; +import 'package:dbus/dbus.dart'; +import 'package:uuid/uuid.dart'; + +import '../menu_item.dart'; +import '../utils.dart'; +import 'system_tray_platform_interface.dart'; + +/// An implementation of [SystemTrayPlatform] that uses dart_libayatana_appindicator. +class LinuxSystemTray extends SystemTrayPlatform { + AppIndicator? _appIndicator; + final Map> _menus = {}; + final Map> _menuSources = {}; + int? _activeMenuId; + + @override + Future initSystemTray({ + required String iconPath, + String? title, + String? toolTip, + bool isTemplate = false, + }) async { + final String id = const Uuid().v1(); + + String resolvedIcon = await Utils.getIcon(iconPath) ?? iconPath; + + _appIndicator = AppIndicator( + id: id, + iconName: resolvedIcon, + category: AppIndicatorCategory.applicationStatus, + ); + + if (title != null) { + _appIndicator!.title = title; + } + + _appIndicator!.status = AppIndicatorStatus.active; + + _appIndicator!.activateEvents.listen((_) { + onSystemTrayEvent?.call('leftMouseUp'); + }); + + return true; + } + + @override + Future setSystemTrayInfo({ + String? title, + String? iconPath, + String? toolTip, + bool isTemplate = false, + }) async { + if (_appIndicator == null) return false; + + if (iconPath != null) { + String resolvedIcon = await Utils.getIcon(iconPath) ?? iconPath; + _appIndicator!.iconName = resolvedIcon; + } + + if (title != null) { + _appIndicator!.title = title; + } + + return true; + } + + @override + Future getTitle() async { + return _appIndicator?.title ?? ""; + } + + @override + Future destroySystemTray() async { + if (_appIndicator != null) { + _appIndicator!.status = AppIndicatorStatus.passive; + } + _appIndicator = null; + _menus.clear(); + _menuSources.clear(); + } + + @override + Future createContextMenu(int menuId, List menus) async { + _menuSources[menuId] = menus; + await _updateMenu(menuId); + return true; + } + + Future _updateMenu(int menuId) async { + if (_appIndicator == null) return; + + final source = _menuSources[menuId]; + if (source == null) return; + + final items = []; + for (final item in source) { + items.add(await _buildDBusItem(item)); + } + + _menus[menuId] = items; + + if (_activeMenuId == menuId) { + _appIndicator!.setMenu(items); + } + } + + Future _buildDBusItem(MenuItemBase item) async { + final properties = {}; + final children = []; + + properties['enabled'] = DBusBoolean(item.enabled); + properties['label'] = DBusString(item.label); + + if (item.image != null) { + String? iconPath = await Utils.getIcon(item.image); + if (iconPath != null) { + properties['icon-name'] = DBusString(iconPath); + } + } + + if (item is MenuItemCheckbox) { + properties['toggle-type'] = const DBusString('checkmark'); + properties['toggle-state'] = DBusInt32(item.checked ? 1 : 0); + } else if (item is SubMenu) { + properties['children-display'] = const DBusString('submenu'); + for (final child in item.children) { + children.add(await _buildDBusItem(child)); + } + } else if (item is MenuSeparator) { + properties['type'] = const DBusString('separator'); + } + + void Function()? onActivated; + if (item is! MenuSeparator && item is! SubMenu) { + onActivated = () { + if (item.menuId != null && item.menuItemId != null) { + onMenuItemSelected?.call(item.menuId!, item.menuItemId!); + } + }; + } + + int id = item.menuItemId ?? 0; + + return DBusMenuItem( + id: id, + properties: properties, + children: children, + onActivated: onActivated, + ); + } + + @override + Future setContextMenu(int menuId) async { + _activeMenuId = menuId; + if (_appIndicator != null && _menus.containsKey(menuId)) { + _appIndicator!.setMenu(_menus[menuId]!); + } else if (_menuSources.containsKey(menuId)) { + await _updateMenu(menuId); + } + } + + @override + Future popUpContextMenu() async { + // Not supported + } + + MenuItemBase? _findItem(int menuId, int menuItemId) { + final source = _menuSources[menuId]; + if (source == null) return null; + return _findItemInList(source, menuItemId); + } + + MenuItemBase? _findItemInList(List items, int id) { + for (final item in items) { + if (item.menuItemId == id) return item; + if (item is SubMenu) { + final found = _findItemInList(item.children, id); + if (found != null) return found; + } + } + return null; + } + + @override + Future setMenuItemLabel(int menuId, int menuItemId, String label) async { + final item = _findItem(menuId, menuItemId); + if (item != null) { + item.label = label; + await _updateMenu(menuId); + } + } + + @override + Future setMenuItemImage(int menuId, int menuItemId, String image) async { + final item = _findItem(menuId, menuItemId); + if (item != null) { + item.image = image; + await _updateMenu(menuId); + } + } + + @override + Future setMenuItemEnable(int menuId, int menuItemId, bool enabled) async { + final item = _findItem(menuId, menuItemId); + if (item != null) { + item.enabled = enabled; + await _updateMenu(menuId); + } + } + + @override + Future setMenuItemCheck(int menuId, int menuItemId, bool checked) async { + final item = _findItem(menuId, menuItemId); + if (item != null) { + item.checked = checked; + await _updateMenu(menuId); + } + } +} diff --git a/lib/src/platform/system_tray_method_channel.dart b/lib/src/platform/system_tray_method_channel.dart new file mode 100644 index 0000000..736b960 --- /dev/null +++ b/lib/src/platform/system_tray_method_channel.dart @@ -0,0 +1,183 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:uuid/uuid.dart'; + +import '../menu_item.dart'; +import '../utils.dart'; +import 'system_tray_platform_interface.dart'; + +const String _kTrayChannelName = "flutter/system_tray/tray"; +const String _kMenuChannelName = "flutter/system_tray/menu_manager"; + +// Tray methods +const String _kInitSystemTray = "InitSystemTray"; +const String _kSetSystemTrayInfo = "SetSystemTrayInfo"; +const String _kSetContextMenu = "SetContextMenu"; +const String _kPopupContextMenu = "PopupContextMenu"; +const String _kGetTitle = "GetTitle"; +const String _kDestroySystemTray = "DestroySystemTray"; +const String _kSystemTrayEventCallbackMethod = 'SystemTrayEventCallback'; + +// Menu methods +const String _kCreateContextMenu = "CreateContextMenu"; +const String _kMenuItemSelectedCallbackMethod = 'MenuItemSelectedCallback'; +const String _kSetLabel = "SetLabel"; +const String _kSetImage = "SetImage"; +const String _kSetEnable = "SetEnable"; +const String _kSetCheck = "SetCheck"; + +// Keys +const String _kTrayIdKey = "tray_id"; +const String _kTitleKey = "title"; +const String _kIconPathKey = "iconpath"; +const String _kToolTipKey = "tooltip"; +const String _kIsTemplateKey = "is_template"; +const String _kMenuIdKey = 'menu_id'; +const String _kMenuItemIdKey = 'menu_item_id'; +const String _kMenuListKey = 'menu_list'; +const String _kLabelKey = 'label'; +const String _kImageKey = 'image'; +const String _kEnabledKey = 'enabled'; +const String _kCheckedKey = 'checked'; + +/// An implementation of [SystemTrayPlatform] that uses method channels. +class MethodChannelSystemTray extends SystemTrayPlatform { + /// The method channel used to interact with the system tray. + final MethodChannel _trayChannel = const MethodChannel(_kTrayChannelName); + + /// The method channel used to interact with the menu manager. + final MethodChannel _menuChannel = const MethodChannel(_kMenuChannelName); + + MethodChannelSystemTray() { + _trayChannel.setMethodCallHandler(_trayCallbackHandler); + _menuChannel.setMethodCallHandler(_menuCallbackHandler); + } + + Future _trayCallbackHandler(MethodCall methodCall) async { + if (methodCall.method == _kSystemTrayEventCallbackMethod) { + final String eventName = methodCall.arguments; + onSystemTrayEvent?.call(eventName); + } + } + + Future _menuCallbackHandler(MethodCall methodCall) async { + if (methodCall.method == _kMenuItemSelectedCallbackMethod) { + final int menuId = methodCall.arguments[_kMenuIdKey]; + final int menuItemId = methodCall.arguments[_kMenuItemIdKey]; + onMenuItemSelected?.call(menuId, menuItemId); + } + } + + @override + Future initSystemTray({ + required String iconPath, + String? title, + String? toolTip, + bool isTemplate = false, + }) async { + return await _trayChannel.invokeMethod( + _kInitSystemTray, + { + _kTrayIdKey: const Uuid().v1(), + _kTitleKey: title, + _kIconPathKey: await Utils.getIcon(iconPath), + _kToolTipKey: toolTip, + _kIsTemplateKey: isTemplate, + }, + ); + } + + @override + Future setSystemTrayInfo({ + String? title, + String? iconPath, + String? toolTip, + bool isTemplate = false, + }) async { + return await _trayChannel.invokeMethod( + _kSetSystemTrayInfo, + { + _kTitleKey: title, + _kIconPathKey: await Utils.getIcon(iconPath), + _kToolTipKey: toolTip, + _kIsTemplateKey: isTemplate, + }, + ); + } + + @override + Future getTitle() async { + return await _trayChannel.invokeMethod(_kGetTitle); + } + + @override + Future setContextMenu(int menuId) async { + await _trayChannel.invokeMethod(_kSetContextMenu, menuId); + } + + @override + Future popUpContextMenu() async { + await _trayChannel.invokeMethod(_kPopupContextMenu); + } + + @override + Future destroySystemTray() async { + await _trayChannel.invokeMethod(_kDestroySystemTray); + } + + @override + Future createContextMenu(int menuId, List menus) async { + await _resolveMenuImages(menus); + return await _menuChannel.invokeMethod(_kCreateContextMenu, { + _kMenuIdKey: menuId, + _kMenuListKey: menus.map((e) => e.toJson()).toList(), + }); + } + + Future _resolveMenuImages(List menus) async { + for (final menuItem in menus) { + menuItem.imageAbsolutePath = await Utils.getIcon(menuItem.image); + if (menuItem is SubMenu) { + await _resolveMenuImages(menuItem.children); + } + } + } + + @override + Future setMenuItemLabel(int menuId, int menuItemId, String label) async { + await _menuChannel.invokeMethod(_kSetLabel, { + _kMenuIdKey: menuId, + _kMenuItemIdKey: menuItemId, + _kLabelKey: label, + }); + } + + @override + Future setMenuItemImage(int menuId, int menuItemId, String image) async { + String? imageAbsolutePath = await Utils.getIcon(image); + await _menuChannel.invokeMethod(_kSetImage, { + _kMenuIdKey: menuId, + _kMenuItemIdKey: menuItemId, + _kImageKey: imageAbsolutePath, + }); + } + + @override + Future setMenuItemEnable(int menuId, int menuItemId, bool enabled) async { + await _menuChannel.invokeMethod(_kSetEnable, { + _kMenuIdKey: menuId, + _kMenuItemIdKey: menuItemId, + _kEnabledKey: enabled, + }); + } + + @override + Future setMenuItemCheck(int menuId, int menuItemId, bool checked) async { + await _menuChannel.invokeMethod(_kSetCheck, { + _kMenuIdKey: menuId, + _kMenuItemIdKey: menuItemId, + _kCheckedKey: checked, + }); + } +} diff --git a/lib/src/platform/system_tray_platform_interface.dart b/lib/src/platform/system_tray_platform_interface.dart new file mode 100644 index 0000000..9ba15bd --- /dev/null +++ b/lib/src/platform/system_tray_platform_interface.dart @@ -0,0 +1,103 @@ +import 'dart:async'; +import 'dart:io'; + +import '../menu_item.dart'; +import 'system_tray_method_channel.dart'; +import 'system_tray_linux.dart'; + +/// The interface that implementations of system_tray must implement. +abstract class SystemTrayPlatform { + static SystemTrayPlatform? _instance; + + /// The default instance of [SystemTrayPlatform] to use. + /// + /// Defaults to [MethodChannelSystemTray] on non-Linux, and [LinuxSystemTray] on Linux. + static SystemTrayPlatform get instance { + if (_instance == null) { + if (Platform.isLinux) { + _instance = LinuxSystemTray(); + } else { + _instance = MethodChannelSystemTray(); + } + } + return _instance!; + } + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [SystemTrayPlatform] when + /// they register themselves. + static set instance(SystemTrayPlatform instance) { + _instance = instance; + } + + /// Initializes the system tray. + Future initSystemTray({ + required String iconPath, + String? title, + String? toolTip, + bool isTemplate = false, + }) { + throw UnimplementedError('initSystemTray() has not been implemented.'); + } + + /// Sets system tray info. + Future setSystemTrayInfo({ + String? title, + String? iconPath, + String? toolTip, + bool isTemplate = false, + }) { + throw UnimplementedError('setSystemTrayInfo() has not been implemented.'); + } + + /// Returns the title displayed next to the tray icon in the status bar. + Future getTitle() { + throw UnimplementedError('getTitle() has not been implemented.'); + } + + /// Sets the context menu. + Future setContextMenu(int menuId) { + throw UnimplementedError('setContextMenu() has not been implemented.'); + } + + /// Pops up the context menu. + Future popUpContextMenu() { + throw UnimplementedError('popUpContextMenu() has not been implemented.'); + } + + /// Destroys the system tray. + Future destroySystemTray() { + throw UnimplementedError('destroySystemTray() has not been implemented.'); + } + + /// Creates a context menu. + Future createContextMenu(int menuId, List menus) { + throw UnimplementedError('createContextMenu() has not been implemented.'); + } + + /// Sets label for a menu item. + Future setMenuItemLabel(int menuId, int menuItemId, String label) { + throw UnimplementedError('setMenuItemLabel() has not been implemented.'); + } + + /// Sets image for a menu item. + Future setMenuItemImage(int menuId, int menuItemId, String image) { + throw UnimplementedError('setMenuItemImage() has not been implemented.'); + } + + /// Sets enable state for a menu item. + Future setMenuItemEnable(int menuId, int menuItemId, bool enabled) { + throw UnimplementedError('setMenuItemEnable() has not been implemented.'); + } + + /// Sets check state for a menu item. + Future setMenuItemCheck(int menuId, int menuItemId, bool checked) { + throw UnimplementedError('setMenuItemCheck() has not been implemented.'); + } + + /// Callback for system tray events. + void Function(String eventName)? onSystemTrayEvent; + + /// Callback for menu item selection. + void Function(int menuId, int menuItemId)? onMenuItemSelected; +} diff --git a/lib/src/system_tray.dart b/lib/src/system_tray.dart new file mode 100644 index 0000000..ba4ba1e --- /dev/null +++ b/lib/src/system_tray.dart @@ -0,0 +1,97 @@ +import 'dart:async'; + +import 'menu.dart'; +import 'platform/system_tray_platform_interface.dart'; + +/// A callback provided to [SystemTray] to handle system tray click event. +typedef SystemTrayEventCallback = void Function(String eventName); + +/// Representation of system tray +class SystemTray { + SystemTray() { + SystemTrayPlatform.instance.onSystemTrayEvent = _callbackHandler; + } + + /// + SystemTrayEventCallback? _systemTrayEventCallback; + + /// Show a SystemTray icon + Future initSystemTray({ + required String iconPath, + String? title, + String? toolTip, + bool isTemplate = false, + }) async { + return await SystemTrayPlatform.instance.initSystemTray( + iconPath: iconPath, + title: title, + toolTip: toolTip, + isTemplate: isTemplate, + ); + } + + /// Set system info info + Future setSystemTrayInfo({ + String? title, + String? iconPath, + String? toolTip, + bool isTemplate = false, + }) async { + return await SystemTrayPlatform.instance.setSystemTrayInfo( + title: title, + iconPath: iconPath, + toolTip: toolTip, + isTemplate: isTemplate, + ); + } + + /// (Windows\macOS\Linux) Sets the image associated with this tray icon + Future setImage(String image, {bool isTemplate = false}) async { + await setSystemTrayInfo(iconPath: image, isTemplate: isTemplate); + } + + /// (Windows\macOS) Sets the hover text for this tray icon. + Future setToolTip(String toolTip) async { + await setSystemTrayInfo(toolTip: toolTip); + } + + /// (macOS) Sets the title displayed next to the tray icon in the status bar. + Future setTitle(String title) async { + await setSystemTrayInfo(title: title); + } + + /// (macOS) Returns string - the title displayed next to the tray icon in the status bar + Future getTitle() async { + return await SystemTrayPlatform.instance.getTitle(); + } + + /// Sets the native application menu to [menus]. + /// + /// How exactly this is handled is subject to platform interpretation. + /// For instance, special menus that are handled entirely on the native + /// side might be added to the provided menus. + Future setContextMenu(Menu menu) async { + await SystemTrayPlatform.instance.setContextMenu(menu.menuId); + } + + /// Pop up the context menu. + /// + Future popUpContextMenu() async { + await SystemTrayPlatform.instance.popUpContextMenu(); + } + + /// register listener for system tray event. + void registerSystemTrayEventHandler(SystemTrayEventCallback callback) { + _systemTrayEventCallback = callback; + } + + void _callbackHandler(String eventName) { + if (_systemTrayEventCallback != null) { + _systemTrayEventCallback!(eventName); + } + } + + Future destroy() async { + await SystemTrayPlatform.instance.destroySystemTray(); + } +} diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 73124c8..ac5dedd 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -11,29 +11,9 @@ find_package(PkgConfig REQUIRED) add_library(${PLUGIN_NAME} SHARED "system_tray_plugin.cc" "app_window.cc" - "menu_manager.cc" - "menu.cc" - "tray.cc" "errors.cc" ) -pkg_check_modules(APPINDICATOR IMPORTED_TARGET ayatana-appindicator3-0.1) -if(APPINDICATOR_FOUND) - target_compile_definitions(${PLUGIN_NAME} PRIVATE HAVE_AYATANA) -else() - pkg_check_modules(APPINDICATOR IMPORTED_TARGET appindicator3-0.1) -endif() -if(APPINDICATOR_FOUND) - target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::APPINDICATOR) -else() - message( - FATAL_ERROR - "\n" - "The `system_tray` package requires ayatana-appindicator3-0.1 or appindicator3-0.1." - ) -endif() - - apply_standard_settings(${PLUGIN_NAME}) set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) @@ -42,7 +22,6 @@ target_include_directories(${PLUGIN_NAME} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) -target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::APPINDICATOR) # List of absolute paths to libraries that should be bundled with the plugin set(system_tray_bundled_libraries diff --git a/linux/menu.cc b/linux/menu.cc deleted file mode 100644 index 5fd53c4..0000000 --- a/linux/menu.cc +++ /dev/null @@ -1,379 +0,0 @@ -#include "menu.h" - -#include - -#include "errors.h" - -namespace { - -constexpr char kMenuIdKey[] = "menu_id"; -constexpr char kMenuItemIdKey[] = "menu_item_id"; -constexpr char kMenuListKey[] = "menu_list"; -constexpr char kIdKey[] = "id"; -constexpr char kTypeKey[] = "type"; -constexpr char kSeparatorKey[] = "separator"; -constexpr char kSubMenuKey[] = "submenu"; -constexpr char kCheckboxKey[] = "checkbox"; -constexpr char kLabelKey[] = "label"; -constexpr char kImageKey[] = "image"; -constexpr char kEnabledKey[] = "enabled"; -constexpr char kCheckedKey[] = "checked"; - -constexpr char kMenuItemSelectedCallbackMethod[] = "MenuItemSelectedCallback"; - -struct TrayCallbackData { - Menu* menu; - int64_t menu_id; - int64_t menu_item_id; -}; - -} // namespace - -Menu::Menu(FlMethodChannel* channel, int menu_id) noexcept - : channel_(channel), menu_id_(menu_id) {} - -Menu::~Menu() noexcept { - // printf("~Menu this: %p\n", this); -} - -bool Menu::create_context_menu(FlValue* args) { - bool result = false; - - do { - if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { - break; - } - - FlValue* list_value = fl_value_lookup_string(args, kMenuListKey); - if (!list_value || fl_value_get_type(list_value) != FL_VALUE_TYPE_LIST) { - break; - } - - GtkWidget* gtk_menu = value_to_menu(menu_id(), list_value); - if (!gtk_menu) { - break; - } - - gtk_menu_ = GTK_WIDGET(g_object_ref(gtk_menu)); - - result = true; - - } while (false); - - return result; -} - -FlMethodResponse* Menu::set_label(FlValue* args) { - g_autoptr(FlValue) result = fl_value_new_bool(FALSE); - FlMethodResponse* response = nullptr; - - do { - if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { - response = FL_METHOD_RESPONSE(fl_method_error_response_new( - errors::kBadArgumentsError, "", nullptr)); - break; - } - - FlValue* menu_item_id_value = fl_value_lookup_string(args, kMenuItemIdKey); - if (!menu_item_id_value || - fl_value_get_type(menu_item_id_value) != FL_VALUE_TYPE_INT) { - response = FL_METHOD_RESPONSE(fl_method_error_response_new( - errors::kBadArgumentsError, "", nullptr)); - break; - } - - int64_t menu_item_id = fl_value_get_int(menu_item_id_value); - - const gchar* label = nullptr; - FlValue* label_value = fl_value_lookup_string(args, kLabelKey); - if (label_value && fl_value_get_type(label_value) == FL_VALUE_TYPE_STRING) { - label = fl_value_get_string(label_value); - } - - set_label(menu_item_id, label); - - result = fl_value_new_bool(TRUE); - - } while (false); - - if (nullptr == response) { - response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - } - - return response; -} - -FlMethodResponse* Menu::set_image(FlValue* args) { - g_autoptr(FlValue) result = fl_value_new_bool(FALSE); - FlMethodResponse* response = nullptr; - - do { - if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { - response = FL_METHOD_RESPONSE(fl_method_error_response_new( - errors::kBadArgumentsError, "", nullptr)); - break; - } - - FlValue* menu_item_id_value = fl_value_lookup_string(args, kMenuItemIdKey); - if (!menu_item_id_value || - fl_value_get_type(menu_item_id_value) != FL_VALUE_TYPE_INT) { - response = FL_METHOD_RESPONSE(fl_method_error_response_new( - errors::kBadArgumentsError, "", nullptr)); - break; - } - - int64_t menu_item_id = fl_value_get_int(menu_item_id_value); - - const gchar* image = nullptr; - FlValue* image_value = fl_value_lookup_string(args, kImageKey); - if (image_value && fl_value_get_type(image_value) == FL_VALUE_TYPE_STRING) { - image = fl_value_get_string(image_value); - } - - set_image(menu_item_id, image); - - result = fl_value_new_bool(TRUE); - - } while (false); - - if (nullptr == response) { - response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - } - - return response; -} - -FlMethodResponse* Menu::set_enable(FlValue* args) { - g_autoptr(FlValue) result = fl_value_new_bool(FALSE); - FlMethodResponse* response = nullptr; - - do { - if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { - response = FL_METHOD_RESPONSE(fl_method_error_response_new( - errors::kBadArgumentsError, "", nullptr)); - break; - } - - FlValue* menu_item_id_value = fl_value_lookup_string(args, kMenuItemIdKey); - if (!menu_item_id_value || - fl_value_get_type(menu_item_id_value) != FL_VALUE_TYPE_INT) { - response = FL_METHOD_RESPONSE(fl_method_error_response_new( - errors::kBadArgumentsError, "", nullptr)); - break; - } - - int64_t menu_item_id = fl_value_get_int(menu_item_id_value); - - bool enable = true; - FlValue* enable_value = fl_value_lookup_string(args, kEnabledKey); - if (enable_value && - fl_value_get_type(enable_value) == FL_VALUE_TYPE_STRING) { - enable = fl_value_get_bool(enable_value); - } - - set_enable(menu_item_id, enable); - - result = fl_value_new_bool(TRUE); - - } while (false); - - if (nullptr == response) { - response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - } - - return response; -} - -FlMethodResponse* Menu::set_check(FlValue* args) { - g_autoptr(FlValue) result = fl_value_new_bool(FALSE); - FlMethodResponse* response = nullptr; - - do { - if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { - response = FL_METHOD_RESPONSE(fl_method_error_response_new( - errors::kBadArgumentsError, "not map", nullptr)); - break; - } - - FlValue* menu_item_id_value = fl_value_lookup_string(args, kMenuItemIdKey); - if (!menu_item_id_value || - fl_value_get_type(menu_item_id_value) != FL_VALUE_TYPE_INT) { - break; - } - - int64_t menu_item_id = fl_value_get_int(menu_item_id_value); - - bool checked = true; - FlValue* checked_value = fl_value_lookup_string(args, kCheckedKey); - if (checked_value && - fl_value_get_type(checked_value) == FL_VALUE_TYPE_STRING) { - checked = fl_value_get_bool(checked_value); - } - - set_check(menu_item_id, checked); - - result = fl_value_new_bool(TRUE); - - } while (false); - - if (nullptr == response) { - response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - } - - return response; -} - -void Menu::set_label(int64_t menu_item_id, const char* label) {} -void Menu::set_image(int64_t menu_item_id, const char* image) {} -void Menu::set_enable(int64_t menu_item_id, bool enabled) {} -void Menu::set_check(int64_t menu_item_id, bool checked) {} - -int64_t Menu::menu_id() const { - return menu_id_; -} - -GtkWidget* Menu::get_menu() const { - return gtk_menu_; -} - -// static -void Menu::menu_item_callback(GtkMenuItem* item, gpointer user_data) { - TrayCallbackData* callback_data = - reinterpret_cast(user_data); - if (callback_data && callback_data->menu) { - callback_data->menu->handle_menu_item_callback(item, callback_data); - } -} - -void Menu::handle_menu_item_callback(GtkMenuItem* item, gpointer user_data) { - TrayCallbackData* callback_data = - reinterpret_cast(user_data); - - // g_print("handle_menu_item_callback menu_id:%ld, menu_item_id:%ld\n", - // callback_data->menu_id, callback_data->menu_item_id); - - g_autoptr(FlValue) result = fl_value_new_map(); - fl_value_set_string_take(result, kMenuIdKey, - fl_value_new_int(callback_data->menu_id)); - fl_value_set_string_take(result, kMenuItemIdKey, - fl_value_new_int(callback_data->menu_item_id)); - fl_method_channel_invoke_method(channel_, kMenuItemSelectedCallbackMethod, - result, nullptr, nullptr, nullptr); -} - -GtkWidget* Menu::value_to_menu(int64_t menu_id, FlValue* value) { - if (fl_value_get_type(value) != FL_VALUE_TYPE_LIST) { - return nullptr; - } - - GtkWidget* menu = gtk_menu_new(); - - for (size_t i = 0; i < fl_value_get_length(value); ++i) { - GtkWidget* menu_item = - value_to_menu_item(menu_id, fl_value_get_list_value(value, i)); - if (menu_item == nullptr) { - return nullptr; - } - - gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(menu_item)); - } - return GTK_WIDGET(menu); -} - -GtkWidget* Menu::value_to_menu_item(int64_t menu_id, FlValue* value) { - if (fl_value_get_type(value) != FL_VALUE_TYPE_MAP) { - return nullptr; - } - - FlValue* type_value = fl_value_lookup_string(value, kTypeKey); - if (type_value == nullptr || - fl_value_get_type(type_value) != FL_VALUE_TYPE_STRING) { - return nullptr; - } - - GtkWidget* menu_item = nullptr; - - const gchar* type = fl_value_get_string(type_value); - - if (strcmp(type, kSeparatorKey) == 0) { - menu_item = gtk_separator_menu_item_new(); - } else { - const gchar* label = nullptr; - FlValue* label_value = fl_value_lookup_string(value, kLabelKey); - if (label_value != nullptr && - fl_value_get_type(label_value) == FL_VALUE_TYPE_STRING) { - label = fl_value_get_string(label_value); - } - - FlValue* image_value = fl_value_lookup_string(value, kImageKey); - if (image_value != nullptr && - fl_value_get_type(image_value) == FL_VALUE_TYPE_STRING) { - const gchar* image = fl_value_get_string(image_value); - - // g_print("value_to_menu_item type:%s, label:%s, image:%s\n", type, - // label, image); - - menu_item = gtk_menu_item_new(); - - GtkWidget* box_widget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); - GtkWidget* icon_widget = gtk_image_new_from_file(image); - GtkWidget* label_widget = gtk_label_new(label); - - gtk_container_add(GTK_CONTAINER(box_widget), icon_widget); - gtk_container_add(GTK_CONTAINER(box_widget), label_widget); - gtk_container_add(GTK_CONTAINER(menu_item), box_widget); - - gtk_widget_show_all(menu_item); - } else { - g_print("value_to_menu_item type:%s, label:%s\n", type, label); - } - - if (!menu_item) { - if (strcmp(type, kCheckboxKey) == 0) { - menu_item = gtk_check_menu_item_new_with_label(label); - } else { - menu_item = gtk_menu_item_new_with_label(label); - } - } - - FlValue* enabled_value = fl_value_lookup_string(value, kEnabledKey); - if (enabled_value != nullptr && - fl_value_get_type(enabled_value) == FL_VALUE_TYPE_BOOL) { - gtk_widget_set_sensitive(menu_item, - fl_value_get_bool(enabled_value) ? TRUE : FALSE); - } - - if (strcmp(type, kSubMenuKey) == 0) { - GtkWidget* subMenu = - value_to_menu(menu_id, fl_value_lookup_string(value, kSubMenuKey)); - if (subMenu == nullptr) { - return nullptr; - } - gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), subMenu); - } else { - if (strcmp(type, kCheckboxKey) == 0) { - FlValue* checked_value = fl_value_lookup_string(value, kCheckedKey); - if (checked_value && - fl_value_get_type(checked_value) == FL_VALUE_TYPE_BOOL) { - bool checked = fl_value_get_bool(checked_value); - gtk_check_menu_item_set_active( - reinterpret_cast(menu_item), checked); - } - } - - FlValue* id_value = fl_value_lookup_string(value, kIdKey); - if (id_value != nullptr && - fl_value_get_type(id_value) == FL_VALUE_TYPE_INT) { - TrayCallbackData* callback_data = new TrayCallbackData(); - callback_data->menu = this; - callback_data->menu_id = menu_id; - callback_data->menu_item_id = fl_value_get_int(id_value); - - g_signal_connect(G_OBJECT(menu_item), "activate", - G_CALLBACK(Menu::menu_item_callback), callback_data); - } - } - } - - return menu_item; -} \ No newline at end of file diff --git a/linux/menu.h b/linux/menu.h deleted file mode 100644 index 3d12601..0000000 --- a/linux/menu.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef __MENU_H__ -#define __MENU_H__ - -#include -#include -#include - -class Menu { - public: - Menu(FlMethodChannel* channel, int menu_id) noexcept; - ~Menu() noexcept; - - bool create_context_menu(FlValue* args); - FlMethodResponse* set_label(FlValue* args); - FlMethodResponse* set_image(FlValue* args); - FlMethodResponse* set_enable(FlValue* args); - FlMethodResponse* set_check(FlValue* args); - - GtkWidget* get_menu() const; - - protected: - GtkWidget* value_to_menu(int64_t menu_id, FlValue* value); - GtkWidget* value_to_menu_item(int64_t menu_id, FlValue* value); - - static void menu_item_callback(GtkMenuItem* item, gpointer user_data); - void handle_menu_item_callback(GtkMenuItem* item, gpointer user_data); - - int64_t menu_id() const; - - void set_label(int64_t menu_item_id, const char* label); - void set_image(int64_t menu_item_id, const char* image); - void set_enable(int64_t menu_item_id, bool enabled); - void set_check(int64_t menu_item_id, bool checked); - - protected: - FlMethodChannel* channel_ = nullptr; - - int64_t menu_id_ = -1; - - GtkWidget* gtk_menu_ = nullptr; -}; - -#endif // __MENU_H__ \ No newline at end of file diff --git a/linux/menu_manager.cc b/linux/menu_manager.cc deleted file mode 100644 index 58933e2..0000000 --- a/linux/menu_manager.cc +++ /dev/null @@ -1,229 +0,0 @@ -#include "menu_manager.h" - -#include "errors.h" -#include "menu.h" - -constexpr char kCreateContextMenu[] = "CreateContextMenu"; -constexpr char kSetLabel[] = "SetLabel"; -constexpr char kSetImage[] = "SetImage"; -constexpr char kSetEnable[] = "SetEnable"; -constexpr char kSetCheck[] = "SetCheck"; - -namespace { - -constexpr char kMenuIdKey[] = "menu_id"; - -} // namespace - -MenuManager::MenuManager(FlMethodChannel* channel) noexcept - : channel_(channel) {} - -MenuManager::~MenuManager() noexcept { - channel_ = nullptr; -} - -void MenuManager::handle_method_call(FlMethodCall* method_call) { - g_autoptr(FlMethodResponse) response = nullptr; - - const gchar* method = fl_method_call_get_name(method_call); - FlValue* args = fl_method_call_get_args(method_call); - - // g_print("method call %s\n", method); - - if (strcmp(method, kCreateContextMenu) == 0) { - response = create_context_menu(args); - } else if (strcmp(method, kSetLabel) == 0) { - response = set_label(args); - } else if (strcmp(method, kSetImage) == 0) { - response = set_image(args); - } else if (strcmp(method, kSetEnable) == 0) { - response = set_enable(args); - } else if (strcmp(method, kSetCheck) == 0) { - response = set_check(args); - } else { - response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); - } - - g_autoptr(GError) error = nullptr; - if (!fl_method_call_respond(method_call, response, &error)) { - g_warning("Failed to send method call response: %s", error->message); - } -} - -FlMethodResponse* MenuManager::create_context_menu(FlValue* args) { - g_autoptr(FlValue) result = fl_value_new_bool(FALSE); - FlMethodResponse* response = nullptr; - - do { - if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { - response = FL_METHOD_RESPONSE(fl_method_error_response_new( - errors::kBadArgumentsError, "", nullptr)); - break; - } - - FlValue* menu_id_value = fl_value_lookup_string(args, kMenuIdKey); - if (!menu_id_value || - fl_value_get_type(menu_id_value) != FL_VALUE_TYPE_INT) { - response = FL_METHOD_RESPONSE(fl_method_error_response_new( - errors::kBadArgumentsError, "", nullptr)); - break; - } - - int64_t menu_id = fl_value_get_int(menu_id_value); - - std::unique_ptr menu = std::make_unique(channel_, menu_id); - if (!menu) { - response = FL_METHOD_RESPONSE( - fl_method_error_response_new(errors::kOutOfMemoryError, "", nullptr)); - break; - } - - if (!menu->create_context_menu(args)) { - break; - } - - add_menu(menu_id, std::move(menu)); - - result = fl_value_new_bool(TRUE); - - } while (false); - - if (nullptr == response) { - response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - } - - return response; -} - -FlMethodResponse* MenuManager::set_label(FlValue* args) { - g_autoptr(FlValue) result = fl_value_new_bool(FALSE); - FlMethodResponse* response = nullptr; - - do { - std::shared_ptr menu = get_menu(args); - if (!menu) { - response = FL_METHOD_RESPONSE( - fl_method_error_response_new(errors::kNotFoundError, "", nullptr)); - break; - } - - response = menu->set_label(args); - - result = fl_value_new_bool(TRUE); - - } while (false); - - if (nullptr == response) { - response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - } - - return response; -} - -FlMethodResponse* MenuManager::set_image(FlValue* args) { - g_autoptr(FlValue) result = fl_value_new_bool(FALSE); - FlMethodResponse* response = nullptr; - - do { - std::shared_ptr menu = get_menu(args); - if (!menu) { - response = FL_METHOD_RESPONSE( - fl_method_error_response_new(errors::kNotFoundError, "", nullptr)); - break; - } - - response = menu->set_image(args); - - result = fl_value_new_bool(TRUE); - - } while (false); - - if (nullptr == response) { - response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - } - - return response; -} - -FlMethodResponse* MenuManager::set_enable(FlValue* args) { - g_autoptr(FlValue) result = fl_value_new_bool(FALSE); - FlMethodResponse* response = nullptr; - - do { - std::shared_ptr menu = get_menu(args); - if (!menu) { - response = FL_METHOD_RESPONSE( - fl_method_error_response_new(errors::kNotFoundError, "", nullptr)); - break; - } - - response = menu->set_enable(args); - - result = fl_value_new_bool(TRUE); - - } while (false); - - if (nullptr == response) { - response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - } - - return response; -} - -FlMethodResponse* MenuManager::set_check(FlValue* args) { - g_autoptr(FlValue) result = fl_value_new_bool(FALSE); - FlMethodResponse* response = nullptr; - - do { - std::shared_ptr menu = get_menu(args); - if (!menu) { - response = FL_METHOD_RESPONSE( - fl_method_error_response_new(errors::kNotFoundError, "", nullptr)); - break; - } - - response = menu->set_check(args); - - result = fl_value_new_bool(TRUE); - - } while (false); - - if (nullptr == response) { - response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - } - - return response; -} - -bool MenuManager::add_menu(int64_t menu_id, std::unique_ptr menu) { - menus_map_.emplace(menu_id, std::move(menu)); - return true; -} - -std::shared_ptr MenuManager::get_menu(int64_t menu_id) { - auto iter = menus_map_.find(menu_id); - return (iter != menus_map_.end()) ? iter->second : nullptr; -} - -std::shared_ptr MenuManager::get_menu(FlValue* args) { - std::shared_ptr menu; - - do { - if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { - break; - } - - FlValue* menu_id_value = fl_value_lookup_string(args, kMenuIdKey); - if (!menu_id_value || - fl_value_get_type(menu_id_value) != FL_VALUE_TYPE_INT) { - break; - } - - int64_t menu_id = fl_value_get_int(menu_id_value); - - menu = get_menu(menu_id); - - } while (false); - - return menu; -} \ No newline at end of file diff --git a/linux/menu_manager.h b/linux/menu_manager.h deleted file mode 100644 index 578bb5f..0000000 --- a/linux/menu_manager.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef __MENU_MANAGER_H__ -#define __MENU_MANAGER_H__ - -#include -#include -#include -#include - -extern const char kCreateContextMenu[]; -extern const char kSetLabel[]; -extern const char kSetImage[]; -extern const char kSetEnable[]; -extern const char kSetCheck[]; - -class Menu; - -class MenuManager { - public: - MenuManager(FlMethodChannel* channel) noexcept; - ~MenuManager() noexcept; - - void handle_method_call(FlMethodCall* method_call); - - std::shared_ptr get_menu(int64_t menu_id); - - protected: - FlMethodResponse* create_context_menu(FlValue* args); - FlMethodResponse* set_label(FlValue* args); - FlMethodResponse* set_image(FlValue* args); - FlMethodResponse* set_enable(FlValue* args); - FlMethodResponse* set_check(FlValue* args); - - protected: - bool add_menu(int64_t menu_id, std::unique_ptr menu); - std::shared_ptr get_menu(FlValue* args); - - protected: - FlMethodChannel* channel_ = nullptr; - - std::unordered_map> menus_map_; -}; - -#endif // __MENU_MANAGER_H__ \ No newline at end of file diff --git a/linux/system_tray_plugin.cc b/linux/system_tray_plugin.cc index 14b763a..06d356a 100644 --- a/linux/system_tray_plugin.cc +++ b/linux/system_tray_plugin.cc @@ -8,14 +8,10 @@ #include #include "app_window.h" -#include "menu_manager.h" -#include "tray.h" namespace { constexpr char kChannelNameAppWindow[] = "flutter/system_tray/app_window"; -constexpr char kChannelNameMenuManager[] = "flutter/system_tray/menu_manager"; -constexpr char kChannelNameTray[] = "flutter/system_tray/tray"; } // namespace @@ -29,18 +25,12 @@ struct _SystemTrayPlugin { FlPluginRegistrar* registrar; FlMethodChannel* channel_app_window = nullptr; - FlMethodChannel* channel_menu_manager = nullptr; - FlMethodChannel* channel_tray = nullptr; std::unique_ptr app_window; - std::shared_ptr menu_manager; - std::unique_ptr tray; }; G_DEFINE_TYPE(SystemTrayPlugin, system_tray_plugin, g_object_get_type()) -SystemTrayPlugin* g_plugin = nullptr; - // Called when a method call is received from Flutter. static void system_tray_plugin_handle_method_call(SystemTrayPlugin* self, FlMethodCall* method_call) { @@ -48,25 +38,11 @@ static void system_tray_plugin_handle_method_call(SystemTrayPlugin* self, const gchar* method = fl_method_call_get_name(method_call); - g_print("method call %s\n", method); - if (strcmp(method, kInitAppWindow) == 0 || strcmp(method, kShowAppWindow) == 0 || strcmp(method, kHideAppWindow) == 0 || strcmp(method, kCloseAppWindow) == 0) { self->app_window->handle_method_call(method_call); - } else if (strcmp(method, kCreateContextMenu) == 0 || - strcmp(method, kSetLabel) == 0 || strcmp(method, kSetImage) == 0 || - strcmp(method, kSetEnable) == 0 || - strcmp(method, kSetCheck) == 0) { - self->menu_manager->handle_method_call(method_call); - } else if (strcmp(method, kInitSystemTray) == 0 || - strcmp(method, kSetSystemTrayInfo) == 0 || - strcmp(method, kSetContextMenu) == 0 || - strcmp(method, kPopupContextMenu) == 0 || - strcmp(method, kGetTitle) == 0 || - strcmp(method, kDestroySystemTray) == 0) { - self->tray->handle_method_call(method_call); } else { response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); g_autoptr(GError) error = nullptr; @@ -80,10 +56,7 @@ static void system_tray_plugin_dispose(GObject* object) { SystemTrayPlugin* self = SYSTEM_TRAY_PLUGIN(object); g_clear_object(&self->registrar); - g_clear_object(&self->channel_app_window); - g_clear_object(&self->channel_menu_manager); - g_clear_object(&self->channel_tray); G_OBJECT_CLASS(system_tray_plugin_parent_class)->dispose(object); } @@ -92,9 +65,7 @@ static void system_tray_plugin_class_init(SystemTrayPluginClass* klass) { G_OBJECT_CLASS(klass)->dispose = system_tray_plugin_dispose; } -static void system_tray_plugin_init(SystemTrayPlugin* self) { - g_plugin = self; -} +static void system_tray_plugin_init(SystemTrayPlugin* self) {} static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, @@ -104,8 +75,8 @@ static void method_call_cb(FlMethodChannel* channel, } void system_tray_plugin_register_with_registrar(FlPluginRegistrar* registrar) { - SystemTrayPlugin* plugin = - SYSTEM_TRAY_PLUGIN(g_object_new(system_tray_plugin_get_type(), nullptr)); + SystemTrayPlugin* plugin = SYSTEM_TRAY_PLUGIN( + g_object_new(system_tray_plugin_get_type(), nullptr)); plugin->registrar = FL_PLUGIN_REGISTRAR(g_object_ref(registrar)); @@ -115,37 +86,11 @@ void system_tray_plugin_register_with_registrar(FlPluginRegistrar* registrar) { fl_plugin_registrar_get_messenger(registrar), kChannelNameAppWindow, FL_METHOD_CODEC(codec_app_window)); - g_autoptr(FlStandardMethodCodec) codec_menu_manager = - fl_standard_method_codec_new(); - plugin->channel_menu_manager = fl_method_channel_new( - fl_plugin_registrar_get_messenger(registrar), kChannelNameMenuManager, - FL_METHOD_CODEC(codec_menu_manager)); - - g_autoptr(FlStandardMethodCodec) codec_tray = fl_standard_method_codec_new(); - plugin->channel_tray = - fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), - kChannelNameTray, FL_METHOD_CODEC(codec_tray)); - - plugin->app_window = std::make_unique(plugin->registrar, - plugin->channel_app_window); - - plugin->menu_manager = - std::make_shared(plugin->channel_menu_manager); - - plugin->tray = - std::make_unique(plugin->channel_tray, plugin->menu_manager); + plugin->app_window = std::make_unique(registrar, plugin->channel_app_window); fl_method_channel_set_method_call_handler( plugin->channel_app_window, method_call_cb, g_object_ref(plugin), g_object_unref); - fl_method_channel_set_method_call_handler( - plugin->channel_menu_manager, method_call_cb, g_object_ref(plugin), - g_object_unref); - - fl_method_channel_set_method_call_handler( - plugin->channel_tray, method_call_cb, g_object_ref(plugin), - g_object_unref); - g_object_unref(plugin); -} \ No newline at end of file +} diff --git a/linux/tray.cc b/linux/tray.cc deleted file mode 100644 index 8f995a4..0000000 --- a/linux/tray.cc +++ /dev/null @@ -1,399 +0,0 @@ -#ifndef NATIVE_C -#define NATIVE_C - -#include "tray.h" - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "errors.h" -#include "menu.h" -#include "menu_manager.h" - -constexpr char kInitSystemTray[] = "InitSystemTray"; -constexpr char kSetSystemTrayInfo[] = "SetSystemTrayInfo"; -constexpr char kSetContextMenu[] = "SetContextMenu"; -constexpr char kPopupContextMenu[] = "PopupContextMenu"; -constexpr char kGetTitle[] = "GetTitle"; -constexpr char kDestroySystemTray[] = "DestroySystemTray"; - -namespace { - -constexpr char kTrayIdKey[] = "tray_id"; -constexpr char kTitleKey[] = "title"; -constexpr char kIconPathKey[] = "iconpath"; -constexpr char kToolTipKey[] = "tooltip"; - -} // namespace - -Tray::Tray(FlMethodChannel* channel, - std::weak_ptr menu_manager) noexcept - : channel_(channel), menu_manager_(menu_manager) {} - -Tray::~Tray() noexcept { - destroy_indicator(); - - channel_ = nullptr; -} - -bool Tray::init_indicator_api() { - bool ret = false; - - do { - if (indicator_api_inited_) { - ret = true; - break; - } - - void* handle = dlopen("libappindicator3.so.1", RTLD_LAZY); - if (!handle) { - break; - } - - app_indicator_new_ = reinterpret_cast( - dlsym(handle, "app_indicator_new")); - app_indicator_set_status_ = reinterpret_cast( - dlsym(handle, "app_indicator_set_status")); - app_indicator_set_icon_full_ = - reinterpret_cast( - dlsym(handle, "app_indicator_set_icon_full")); - app_indicator_set_attention_icon_full_ = - reinterpret_cast( - dlsym(handle, "app_indicator_set_attention_icon_full")); - app_indicator_set_label_ = reinterpret_cast( - dlsym(handle, "app_indicator_set_label")); - app_indicator_set_title_ = reinterpret_cast( - dlsym(handle, "app_indicator_set_title")); - app_indicator_get_label_ = reinterpret_cast( - dlsym(handle, "app_indicator_get_label")); - app_indicator_set_menu_ = reinterpret_cast( - dlsym(handle, "app_indicator_set_menu")); - - if (!app_indicator_new_ || !app_indicator_set_status_ || - !app_indicator_set_icon_full_ || - !app_indicator_set_attention_icon_full_ || !app_indicator_set_label_ || - !app_indicator_set_label_ || !app_indicator_get_label_ || - !app_indicator_set_menu_) { - break; - } - - indicator_api_inited_ = true; - - ret = true; - } while (false); - - return ret; -} - -bool Tray::create_indicator(const char* tray_id) { - // printf("SystemTray::create_indicator tray_id: %s\n", tray_id); - - bool ret = false; - - do { - if (!tray_id) { - break; - } - - if (!indicator_api_inited_) { - break; - } - - if (!app_indicator_) { - app_indicator_ = app_indicator_new_( - tray_id, "", APP_INDICATOR_CATEGORY_APPLICATION_STATUS); - if (!app_indicator_) { - break; - } - } - - app_indicator_set_status_(app_indicator_, APP_INDICATOR_STATUS_ACTIVE); - ret = true; - } while (false); - - return ret; -} - -void Tray::destroy_indicator() { - context_menu_id_ = -1; - - if (app_indicator_) { - g_object_unref(G_OBJECT(app_indicator_)); - app_indicator_ = nullptr; - } -} - -void Tray::hide_indicator() { - context_menu_id_ = -1; - - if (app_indicator_) { - app_indicator_set_status_(app_indicator_, APP_INDICATOR_STATUS_PASSIVE); - } -} - -void Tray::handle_method_call(FlMethodCall* method_call) { - g_autoptr(FlMethodResponse) response = nullptr; - - const gchar* method = fl_method_call_get_name(method_call); - FlValue* args = fl_method_call_get_args(method_call); - - // g_print("method call %s\n", method); - - if (strcmp(method, kInitSystemTray) == 0) { - response = init_tray(args); - } else if (strcmp(method, kSetSystemTrayInfo) == 0) { - response = set_tray_info(args); - } else if (strcmp(method, kSetContextMenu) == 0) { - response = set_context_menu(args); - } else if (strcmp(method, kPopupContextMenu) == 0) { - response = popup_context_menu(args); - } else if (strcmp(method, kGetTitle) == 0) { - response = get_title(args); - } else if (strcmp(method, kDestroySystemTray) == 0) { - response = destroy_system_tray(args); - } else { - response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); - } - - g_autoptr(GError) error = nullptr; - if (!fl_method_call_respond(method_call, response, &error)) { - g_warning("Failed to send method call response: %s", error->message); - } -} - -FlMethodResponse* Tray::init_tray(FlValue* args) { - g_autoptr(FlValue) result = fl_value_new_bool(FALSE); - FlMethodResponse* response = nullptr; - - do { - if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { - response = FL_METHOD_RESPONSE(fl_method_error_response_new( - errors::kBadArgumentsError, "", nullptr)); - break; - } - - const gchar* tray_id = nullptr; - - FlValue* tray_id_value = fl_value_lookup_string(args, kTrayIdKey); - if (tray_id_value && - fl_value_get_type(tray_id_value) == FL_VALUE_TYPE_STRING) { - tray_id = fl_value_get_string(tray_id_value); - } - - if (!init_tray(tray_id)) { - break; - } - - response = set_tray_info(args); - - } while (false); - - if (nullptr == response) { - response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - } - - return response; -} - -FlMethodResponse* Tray::set_tray_info(FlValue* args) { - g_autoptr(FlValue) result = fl_value_new_bool(FALSE); - FlMethodResponse* response = nullptr; - - do { - if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { - response = FL_METHOD_RESPONSE(fl_method_error_response_new( - errors::kBadArgumentsError, "", nullptr)); - break; - } - - const gchar* title = nullptr; - const gchar* icon_path = nullptr; - const gchar* tool_tip = nullptr; - - FlValue* title_value = fl_value_lookup_string(args, kTitleKey); - if (title_value && fl_value_get_type(title_value) == FL_VALUE_TYPE_STRING) { - title = fl_value_get_string(title_value); - } - - FlValue* icon_path_value = fl_value_lookup_string(args, kIconPathKey); - if (icon_path_value && - fl_value_get_type(icon_path_value) == FL_VALUE_TYPE_STRING) { - icon_path = fl_value_get_string(icon_path_value); - } - - FlValue* tooltip_value = fl_value_lookup_string(args, kToolTipKey); - if (tooltip_value && - fl_value_get_type(tooltip_value) == FL_VALUE_TYPE_STRING) { - tool_tip = fl_value_get_string(tooltip_value); - } - - result = fl_value_new_bool(set_tray_info(title, icon_path, tool_tip)); - - } while (false); - - if (nullptr == response) { - response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - } - - return response; -} - -FlMethodResponse* Tray::set_context_menu(FlValue* args) { - g_autoptr(FlValue) result = fl_value_new_bool(FALSE); - FlMethodResponse* response = nullptr; - - do { - if (fl_value_get_type(args) != FL_VALUE_TYPE_INT) { - response = FL_METHOD_RESPONSE(fl_method_error_response_new( - errors::kBadArgumentsError, "", nullptr)); - break; - } - - set_context_menu(fl_value_get_int(args)); - - result = fl_value_new_bool(TRUE); - - } while (false); - - if (nullptr == response) { - response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - } - return response; -} - -FlMethodResponse* Tray::popup_context_menu(FlValue* args) { - g_autoptr(FlValue) result = fl_value_new_bool(TRUE); - return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); -} - -FlMethodResponse* Tray::get_title(FlValue* args) { - g_autoptr(FlValue) result = fl_value_new_string(""); - FlMethodResponse* response = nullptr; - - do { - if (!app_indicator_) { - break; - } - - const gchar* title = app_indicator_get_label_(app_indicator_); - result = fl_value_new_string(title ? title : ""); - - } while (false); - - if (nullptr == response) { - response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - } - return response; -} - -FlMethodResponse* Tray::destroy_system_tray(FlValue* args) { - g_autoptr(FlValue) result = fl_value_new_string(FALSE); - FlMethodResponse* response = nullptr; - - do { - if (!app_indicator_) { - break; - } - - hide_indicator(); - - result = fl_value_new_bool(TRUE); - - } while (false); - - if (nullptr == response) { - response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - } - return response; -} - -bool Tray::init_tray(const char* tray_id) { - bool ret = false; - - do { - if (!init_indicator_api()) { - break; - } - - if (!create_indicator(tray_id)) { - break; - } - - ret = true; - } while (false); - - return ret; -} - -bool Tray::set_tray_info(const char* title, - const char* icon_path, - const char* toolTip) { - printf( - "SystemTray::set_system_tray_info title: %s, icon_path: %s, toolTip: " - "%s\n", - title, icon_path, toolTip); - - bool ret = false; - - do { - if (!app_indicator_) { - break; - } - - if (icon_path) { - if (strlen(icon_path)) { - app_indicator_set_status_(app_indicator_, APP_INDICATOR_STATUS_ACTIVE); - app_indicator_set_icon_full_(app_indicator_, icon_path, "icon"); - } else { - app_indicator_set_status_(app_indicator_, APP_INDICATOR_STATUS_PASSIVE); - } - } - - if (title) { - app_indicator_set_label_(app_indicator_, title, nullptr); - } - - ret = true; - } while (false); - - return ret; -} - -void Tray::set_context_menu(int64_t context_menu_id) { - context_menu_id_ = context_menu_id; - - do { - if (menu_manager_.expired()) { - break; - } - - std::shared_ptr menu_manager = menu_manager_.lock(); - std::shared_ptr menu = menu_manager->get_menu(get_context_menu_id()); - if (!menu) { - break; - } - - if (!app_indicator_) { - break; - } - - GtkWidget* system_menu = menu->get_menu(); - - gtk_widget_show_all(system_menu); - app_indicator_set_menu_(app_indicator_, GTK_MENU(system_menu)); - - } while (false); -} - -int64_t Tray::get_context_menu_id() const { - return context_menu_id_; -} - -#endif // NATIVE_C \ No newline at end of file diff --git a/linux/tray.h b/linux/tray.h deleted file mode 100644 index 2ce872a..0000000 --- a/linux/tray.h +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef __TRAY_H__ -#define __TRAY_H__ - -#include -#include -#ifdef HAVE_AYATANA -#include -#else -#include -#endif -#include - -typedef AppIndicator* (*app_indicator_new_fun)(const gchar*, - const gchar*, - AppIndicatorCategory); - -typedef void (*app_indicator_set_status_fun)(AppIndicator*, AppIndicatorStatus); -typedef void (*app_indicator_set_icon_full_func)(AppIndicator* self, - const gchar* icon_name, - const gchar* icon_desc); -typedef void (*app_indicator_set_attention_icon_full_fun)(AppIndicator*, - const gchar*, - const gchar*); -typedef void (*app_indicator_set_label_func)(AppIndicator* self, - const gchar* label, - const gchar* guide); - -typedef void (*app_indicator_set_title_func)(AppIndicator* self, - const gchar* title); - -typedef const gchar* (*app_indicator_get_label_func)(AppIndicator* self); - -typedef void (*app_indicator_set_menu_fun)(AppIndicator*, GtkMenu*); - -extern const char kInitSystemTray[]; -extern const char kSetSystemTrayInfo[]; -extern const char kSetContextMenu[]; -extern const char kPopupContextMenu[]; -extern const char kGetTitle[]; -extern const char kDestroySystemTray[]; - -class MenuManager; - -class Tray { - public: - Tray(FlMethodChannel* _channel, - std::weak_ptr menu_manager) noexcept; - ~Tray() noexcept; - - void handle_method_call(FlMethodCall* method_call); - - protected: - FlMethodResponse* init_tray(FlValue* args); - FlMethodResponse* set_tray_info(FlValue* args); - FlMethodResponse* set_context_menu(FlValue* args); - FlMethodResponse* popup_context_menu(FlValue* args); - FlMethodResponse* get_title(FlValue* args); - FlMethodResponse* destroy_system_tray(FlValue* args); - - bool init_tray(const char* tray_id); - bool set_tray_info(const char* title, - const char* icon_path, - const char* toolTip); - void set_context_menu(int64_t context_menu_id); - int64_t get_context_menu_id() const; - - bool init_indicator_api(); - bool create_indicator(const char* tray_id); - void destroy_indicator(); - void hide_indicator(); - - protected: - app_indicator_new_fun app_indicator_new_ = nullptr; - app_indicator_set_status_fun app_indicator_set_status_ = nullptr; - app_indicator_set_icon_full_func app_indicator_set_icon_full_ = nullptr; - app_indicator_set_attention_icon_full_fun - app_indicator_set_attention_icon_full_ = nullptr; - app_indicator_set_label_func app_indicator_set_label_ = nullptr; - app_indicator_set_title_func app_indicator_set_title_ = nullptr; - app_indicator_get_label_func app_indicator_get_label_ = nullptr; - app_indicator_set_menu_fun app_indicator_set_menu_ = nullptr; - - FlMethodChannel* channel_ = nullptr; - std::weak_ptr menu_manager_; - - bool indicator_api_inited_ = false; - - AppIndicator* app_indicator_ = nullptr; - - int context_menu_id_ = -1; -}; - -#endif // __TRAY_H__ \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 084d0f7..c596240 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,62 +1,94 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: name: async - url: "https://pub.flutter-io.cn" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.13.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.flutter-io.cn" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" characters: dependency: transitive description: name: characters - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.flutter-io.cn" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.4.0" clock: dependency: transitive description: name: clock - url: "https://pub.flutter-io.cn" + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" collection: dependency: transitive description: name: collection - url: "https://pub.flutter-io.cn" + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.19.1" crypto: dependency: transitive description: name: crypto - url: "https://pub.flutter-io.cn" + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.7" + dart_libayatana_appindicator: + dependency: "direct main" + description: + name: dart_libayatana_appindicator + sha256: "8eb0e7f96f9693a6156f70732c4ce85350107b8d183b700dad5047ae263ce2ba" + url: "https://pub.dev" + source: hosted + version: "0.1.5" + dbus: + dependency: "direct main" + description: + name: dbus + sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 + url: "https://pub.dev" + source: hosted + version: "0.7.12" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.flutter-io.cn" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" flutter: dependency: "direct main" description: flutter @@ -66,7 +98,8 @@ packages: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.flutter-io.cn" + sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493 + url: "https://pub.dev" source: hosted version: "1.0.4" flutter_test: @@ -74,109 +107,171 @@ packages: description: flutter source: sdk version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" lints: dependency: transitive description: name: lints - url: "https://pub.flutter-io.cn" + sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c + url: "https://pub.dev" source: hosted version: "1.0.1" matcher: dependency: transitive description: name: matcher - url: "https://pub.flutter-io.cn" + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" source: hosted - version: "0.12.11" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.flutter-io.cn" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" source: hosted - version: "0.1.4" + version: "0.11.1" meta: dependency: transitive description: name: meta - url: "https://pub.flutter-io.cn" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.17.0" path: dependency: "direct main" description: name: path - url: "https://pub.flutter-io.cn" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" source: hosted - version: "1.8.1" + version: "1.9.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - url: "https://pub.flutter-io.cn" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.10.2" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.flutter-io.cn" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.flutter-io.cn" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.flutter-io.cn" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.flutter-io.cn" + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - url: "https://pub.flutter-io.cn" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + url: "https://pub.dev" source: hosted - version: "0.4.9" + version: "0.7.7" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.flutter-io.cn" + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.4.0" uuid: dependency: "direct main" description: name: uuid - url: "https://pub.flutter-io.cn" + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.flutter-io.cn" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" + source: hosted + version: "15.0.2" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" sdks: - dart: ">=2.17.0-0 <3.0.0" - flutter: ">=1.20.0" + dart: ">=3.8.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index 94bbd20..7ba07c4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,10 +4,12 @@ version: 2.0.3 repository: https://github.com/antler119/system_tray.git environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.12.0 <4.0.0" flutter: ">=1.20.0" dependencies: + dart_libayatana_appindicator: ^0.1.5 + dbus: ^0.7.12 flutter: sdk: flutter path: ^1.8.0