diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c46acb..2e15a28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.0 + +* [Feature] add some helper functions (setImage / setTooltip / setTitle / getTitle) +* [Fixed] enable change icon image on macOS + ## 0.0.9 * [Feature] add popUpContextMenu diff --git a/README.md b/README.md index d6f5b52..3ff7e0d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ In the pubspec.yaml of your flutter project, add the following dependency: ```yaml dependencies: ... - system_tray: ^0.0.9 + system_tray: ^0.1.0 ``` In your library add the following import: @@ -77,6 +77,27 @@ sudo apt-get install appindicator3-0.1 libappindicator3-dev + + setImage + Modify the tray image + ✔️ + ✔️ + ✔️ + + + setTooltip + Modify the tray tooltip + ✔️ + ✔️ + ➖ + + + setTitle / getTitle + Set / Get the tray title + ➖ + ✔️ + ➖ + setContextMenu Set the tray context menu @@ -116,7 +137,6 @@ sudo apt-get install appindicator3-0.1 libappindicator3-dev ## Usage -Smallest example: ```dart Future initSystemTray() async { @@ -151,116 +171,6 @@ Future initSystemTray() async { } ``` -Flashing icon example: - -```dart -Future initSystemTray() async { - String path = - Platform.isWindows ? 'assets/app_icon.ico' : 'assets/app_icon.png'; - - final menu = [ - MenuItem(label: 'Show', onClicked: _appWindow.show), - MenuItem(label: 'Hide', onClicked: _appWindow.hide), - MenuItem( - label: 'Start flash tray icon', - onClicked: () { - debugPrint("Start flash tray icon"); - - _timer ??= Timer.periodic( - const Duration(milliseconds: 500), - (timer) { - _toogleTrayIcon = !_toogleTrayIcon; - _systemTray.setSystemTrayInfo( - iconPath: _toogleTrayIcon ? "" : path, - ); - }, - ); - }, - ), - MenuItem( - label: 'Stop flash tray icon', - onClicked: () { - debugPrint("Stop flash tray icon"); - - _timer?.cancel(); - _timer = null; - - _systemTray.setSystemTrayInfo( - iconPath: path, - ); - }, - ), - MenuSeparator(), - SubMenu( - label: "Test API", - children: [ - SubMenu( - label: "setSystemTrayInfo", - children: [ - MenuItem( - label: 'set title', - onClicked: () { - final String text = WordPair.random().asPascalCase; - debugPrint("click 'set title' : $text"); - _systemTray.setSystemTrayInfo( - title: text, - ); - }, - ), - MenuItem( - label: 'set icon path', - onClicked: () { - debugPrint("click 'set icon path' : $path"); - _systemTray.setSystemTrayInfo( - iconPath: path, - ); - }, - ), - MenuItem( - label: 'set tooltip', - onClicked: () { - final String text = WordPair.random().asPascalCase; - debugPrint("click 'set tooltip' : $text"); - _systemTray.setSystemTrayInfo( - toolTip: text, - ); - }, - ), - ], - ), - MenuItem(label: 'disabled Item', enabled: false), - ], - ), - MenuSeparator(), - MenuItem( - label: 'Exit', - onClicked: _appWindow.close, - ), - ]; - - // We first init the systray menu and then add the menu entries - await _systemTray.initSystemTray( - title: "system tray", - iconPath: path, - toolTip: "How to use system tray with Flutter", - ); - - await _systemTray.setContextMenu(menu); - - // handle system tray event - _systemTray.registerSystemTrayEventHandler((eventName) { - debugPrint("eventName: $eventName"); - if (eventName == "leftMouseDown") { - } else if (eventName == "leftMouseUp") { - _systemTray.popUpContextMenu(); - } else if (eventName == "rightMouseDown") { - } else if (eventName == "rightMouseUp") { - _appWindow.show(); - } - }); -} -``` - # Addition Recommended library that supports window control: diff --git a/example/assets/darts_icon.bmp b/example/assets/darts_icon.bmp new file mode 100644 index 0000000..320fd3e Binary files /dev/null and b/example/assets/darts_icon.bmp differ diff --git a/example/assets/darts_icon.ico b/example/assets/darts_icon.ico new file mode 100644 index 0000000..40d4e51 Binary files /dev/null and b/example/assets/darts_icon.ico differ diff --git a/example/assets/darts_icon.png b/example/assets/darts_icon.png new file mode 100644 index 0000000..dc0516d Binary files /dev/null and b/example/assets/darts_icon.png differ diff --git a/example/assets/gift_icon.ico b/example/assets/gift_icon.ico new file mode 100644 index 0000000..7043651 Binary files /dev/null and b/example/assets/gift_icon.ico differ diff --git a/example/assets/gift_icon.png b/example/assets/gift_icon.png new file mode 100644 index 0000000..53b56a5 Binary files /dev/null and b/example/assets/gift_icon.png differ diff --git a/example/lib/main.dart b/example/lib/main.dart index 2b2b543..e114d2f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:english_words/english_words.dart'; @@ -51,6 +52,8 @@ class _MyAppState extends State { String path = Platform.isWindows ? 'assets/app_icon.ico' : 'assets/app_icon.png'; + List iconList = ['darts_icon', 'gift_icon']; + final menu = [ MenuItem(label: 'Show', onClicked: _appWindow.show), MenuItem(label: 'Hide', onClicked: _appWindow.hide), @@ -63,9 +66,7 @@ class _MyAppState extends State { const Duration(milliseconds: 500), (timer) { _toogleTrayIcon = !_toogleTrayIcon; - _systemTray.setSystemTrayInfo( - iconPath: _toogleTrayIcon ? "" : path, - ); + _systemTray.setImage(_toogleTrayIcon ? "" : path); }, ); }, @@ -78,9 +79,7 @@ class _MyAppState extends State { _timer?.cancel(); _timer = null; - _systemTray.setSystemTrayInfo( - iconPath: path, - ); + _systemTray.setImage(path); }, ), MenuSeparator(), @@ -91,32 +90,38 @@ class _MyAppState extends State { label: "setSystemTrayInfo", children: [ MenuItem( - label: 'set title', + label: 'setTitle', onClicked: () { final String text = WordPair.random().asPascalCase; - debugPrint("click 'set title' : $text"); - _systemTray.setSystemTrayInfo( - title: text, - ); + debugPrint("click 'setTitle' : $text"); + _systemTray.setTitle(text); }, ), MenuItem( - label: 'set icon path', + label: 'setImage', onClicked: () { - debugPrint("click 'set icon path' : $path"); - _systemTray.setSystemTrayInfo( - iconPath: path, - ); + String iconName = iconList[Random().nextInt(iconList.length)]; + String path = Platform.isWindows + ? 'assets/$iconName.ico' + : 'assets/$iconName.png'; + + debugPrint("click 'setImage' : $path"); + _systemTray.setImage(path); }, ), MenuItem( - label: 'set toolTip', + label: 'setToolTip', onClicked: () { final String text = WordPair.random().asPascalCase; - debugPrint("click 'set toolTip' : $text"); - _systemTray.setSystemTrayInfo( - toolTip: text, - ); + debugPrint("click 'setToolTip' : $text"); + _systemTray.setToolTip(text); + }, + ), + MenuItem( + label: 'getTitle [macOS]', + onClicked: () async { + String title = await _systemTray.getTitle(); + debugPrint("click 'getTitle' : $title"); }, ), ], diff --git a/example/pubspec.lock b/example/pubspec.lock index a9a7e0a..416485a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -204,7 +204,7 @@ packages: path: ".." relative: true source: path - version: "0.0.9" + version: "0.1.0" term_glyph: dependency: transitive description: diff --git a/lib/src/app_window.dart b/lib/src/app_window.dart index 0255c8e..0a6a488 100644 --- a/lib/src/app_window.dart +++ b/lib/src/app_window.dart @@ -9,6 +9,7 @@ const String _kShowAppWindow = "ShowAppWindow"; const String _kHideAppWindow = "HideAppWindow"; const String _kCloseAppWindow = "CloseAppWindow"; +/// Representation of native window class AppWindow { AppWindow() { _platformChannel.setMethodCallHandler(_callbackHandler); @@ -17,14 +18,17 @@ class AppWindow { static const MethodChannel _platformChannel = MethodChannel(_kChannelName); + /// Show native window Future show() async { await _platformChannel.invokeMethod(_kShowAppWindow); } + /// Hide native window Future hide() async { await _platformChannel.invokeMethod(_kHideAppWindow); } + /// Close native window Future close() async { await _platformChannel.invokeMethod(_kCloseAppWindow); } diff --git a/lib/src/tray.dart b/lib/src/tray.dart index d4e466e..459f9e2 100644 --- a/lib/src/tray.dart +++ b/lib/src/tray.dart @@ -1,8 +1,6 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:ffi'; import 'dart:io'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -16,13 +14,13 @@ const String _kInitSystemTray = "InitSystemTray"; const String _kSetSystemTrayInfo = "SetSystemTrayInfo"; const String _kSetContextMenu = "SetContextMenu"; const String _kPopupContextMenu = "PopupContextMenu"; +const String _kGetTitle = "GetTitle"; const String _kMenuItemSelectedCallbackMethod = 'MenuItemSelectedCallback'; const String _kSystemTrayEventCallbackMethod = 'SystemTrayEventCallback'; const String _kTitleKey = "title"; const String _kIconPathKey = "iconpath"; -const String _kbase64IconKey = "base64icon"; const String _kToolTipKey = "tooltip"; const String _kIdKey = 'id'; const String _kTypeKey = 'type'; @@ -33,6 +31,7 @@ const String _kEnabledKey = 'enabled'; /// A callback provided to [SystemTray] to handle system tray click event. typedef SystemTrayEventCallback = void Function(String eventName); +/// Representation of system tray class SystemTray { SystemTray() { _platformChannel.setMethodCallHandler(_callbackHandler); @@ -67,8 +66,7 @@ class SystemTray { _kInitSystemTray, { _kTitleKey: title, - _kIconPathKey: _joinIconPath(iconPath), - _kbase64IconKey: await _base64Image(iconPath), + _kIconPathKey: await _getIcon(iconPath), _kToolTipKey: toolTip, }, ); @@ -85,22 +83,31 @@ class SystemTray { _kSetSystemTrayInfo, { _kTitleKey: title, - _kIconPathKey: iconPath == null ? null : _joinIconPath(iconPath), - _kbase64IconKey: await _base64Image(iconPath), + _kIconPathKey: iconPath == null ? null : await _getIcon(iconPath), _kToolTipKey: toolTip, }, ); return value; } - /// Returns Base64 Image - Future _base64Image(String? iconPath) async { - if (iconPath?.isNotEmpty != true) { - return null; - } + /// (Windows\macOS\Linux) Sets the image associated with this tray icon + Future setImage(String image) async { + await setSystemTrayInfo(iconPath: image); + } - ByteData imageData = await rootBundle.load(iconPath!); - return base64Encode(imageData.buffer.asUint8List()); + /// (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 _platformChannel.invokeMethod(_kGetTitle); } /// register listener for system tray event. @@ -227,9 +234,14 @@ class SystemTray { } } - String _joinIconPath(String assetPath) { + /// Get Icon + Future _getIcon(String assetPath) async { + if (assetPath.isEmpty == true) { + return ''; + } + if (Platform.isMacOS) { - return joinAll([assetPath]); + return await _base64Image(assetPath); } return joinAll([ @@ -238,4 +250,10 @@ class SystemTray { assetPath, ]); } + + /// Returns Base64 Image + Future _base64Image(String iconPath) async { + ByteData imageData = await rootBundle.load(iconPath); + return base64Encode(imageData.buffer.asUint8List()); + } } diff --git a/linux/system_tray_plugin.cc b/linux/system_tray_plugin.cc index 8307301..4fec3e5 100644 --- a/linux/system_tray_plugin.cc +++ b/linux/system_tray_plugin.cc @@ -63,7 +63,7 @@ SystemTrayPlugin* g_plugin = nullptr; static void tray_callback(GtkMenuItem* item, gpointer user_data) { int64_t id = GPOINTER_TO_INT(user_data); - g_print("tray_callback id:%ld\n", id); + // g_print("tray_callback id:%ld\n", id); g_autoptr(FlValue) result = fl_value_new_int(id); fl_method_channel_invoke_method(g_plugin->channel, @@ -424,7 +424,7 @@ static void system_tray_plugin_handle_method_call(SystemTrayPlugin* self, static void system_tray_plugin_dispose(GObject* object) { SystemTrayPlugin* self = SYSTEM_TRAY_PLUGIN(object); - g_print("system_tray_plugin_dispose self: %p\n", self); + // g_print("system_tray_plugin_dispose self: %p\n", self); g_clear_object(&self->registrar); g_clear_object(&self->channel); @@ -433,12 +433,12 @@ static void system_tray_plugin_dispose(GObject* object) { } static void system_tray_plugin_class_init(SystemTrayPluginClass* klass) { - g_print("system_tray_plugin_class_init klass: %p\n", klass); + // g_print("system_tray_plugin_class_init klass: %p\n", klass); G_OBJECT_CLASS(klass)->dispose = system_tray_plugin_dispose; } static void system_tray_plugin_init(SystemTrayPlugin* self) { - g_print("system_tray_plugin_init self: %p\n", self); + // g_print("system_tray_plugin_init self: %p\n", self); g_plugin = self; } diff --git a/linux/tray.cc b/linux/tray.cc index 23d6dc1..bd13515 100644 --- a/linux/tray.cc +++ b/linux/tray.cc @@ -16,7 +16,7 @@ bool SystemTray::init_system_tray(const char* title, const char* iconPath, const char* toolTip) { - printf("SystemTray::init_system_tray\n"); + // printf("SystemTray::init_system_tray\n"); bool ret = false; @@ -104,8 +104,8 @@ bool SystemTray::init_indicator_api() { bool SystemTray::create_indicator(const char* title, const char* iconPath, const char* toolTip) { - printf("SystemTray::create_indicator title: %s, iconPath: %s, toolTip: %s\n", - title, iconPath, toolTip); + // printf("SystemTray::create_indicator title: %s, iconPath: %s, toolTip: %s\n", + // title, iconPath, toolTip); bool ret = false; @@ -121,7 +121,7 @@ bool SystemTray::create_indicator(const char* title, } bool SystemTray::set_context_menu(GtkWidget* system_menu) { - printf("SystemTray::set_context_menu system_menu:%p\n", system_menu); + // printf("SystemTray::set_context_menu system_menu:%p\n", system_menu); bool ret = false; diff --git a/macos/Classes/AppWindow.swift b/macos/Classes/AppWindow.swift index fe6fe37..bd772da 100644 --- a/macos/Classes/AppWindow.swift +++ b/macos/Classes/AppWindow.swift @@ -10,7 +10,7 @@ class AppWindow: NSObject { } func showAppWindow() { - print("showAppWindow title:\(self.window.title) rect:\(self.window.contentLayoutRect)") + // print("showAppWindow title:\(self.window.title) rect:\(self.window.contentLayoutRect)") DispatchQueue.main.async { if self.window.isMiniaturized == true { self.window.deminiaturize(self) @@ -23,14 +23,14 @@ class AppWindow: NSObject { } func hideAppWindow() { - print("hideAppWindow") + // print("hideAppWindow") DispatchQueue.main.async { self.window.miniaturize(self) } } func closeAppWindow() { - print("closeAppWindow") + // print("closeAppWindow") self.window.performClose(self) } } diff --git a/macos/Classes/SystemTrayPlugin.swift b/macos/Classes/SystemTrayPlugin.swift index 87cc349..110f7ac 100644 --- a/macos/Classes/SystemTrayPlugin.swift +++ b/macos/Classes/SystemTrayPlugin.swift @@ -7,11 +7,13 @@ let kInitSystemTray = "InitSystemTray" let kSetSystemTrayInfo = "SetSystemTrayInfo" let kSetContextMenu = "SetContextMenu" let kPopupContextMenu = "PopupContextMenu" +let kSetPressedImage = "SetPressedImage" +let kGetTitle = "GetTitle" let kMenuItemSelectedCallbackMethod = "MenuItemSelectedCallback" let kTitleKey = "title" -let kBase64IconKey = "base64icon" +let kIconPathKey = "iconpath" let kToolTipKey = "tooltip" let kIdKey = "id" let kTypeKey = "type" @@ -47,8 +49,8 @@ public class SystemTrayPlugin: NSObject, FlutterPlugin { init( _ registrar: FlutterPluginRegistrar, _ channel: FlutterMethodChannel, - _ channel_app_window: FlutterMethodChannel) - { + _ channel_app_window: FlutterMethodChannel + ) { system_tray = SystemTray(channel) self.channel = channel self.channel_app_window = channel_app_window @@ -65,6 +67,10 @@ public class SystemTrayPlugin: NSObject, FlutterPlugin { setContextMenu(call, result) case kPopupContextMenu: popUpContextMenu(call, result) + case kSetPressedImage: + setPressedImage(call, result) + case kGetTitle: + getTitle(call, result) case kInitAppWindow: initAppWindow(call, result) case kShowAppWindow: @@ -81,18 +87,20 @@ public class SystemTrayPlugin: NSObject, FlutterPlugin { func initSystemTray(_ call: FlutterMethodCall, _ result: FlutterResult) { let arguments = call.arguments as! [String: Any] let title = arguments[kTitleKey] as? String - let base64Icon = arguments[kBase64IconKey] as? String + let base64Icon = arguments[kIconPathKey] as? String let toolTip = arguments[kToolTipKey] as? String - result(system_tray?.initSystemTray(title: title, base64Icon: base64Icon, toolTip: toolTip) ?? false) + result( + system_tray?.initSystemTray(title: title, base64Icon: base64Icon, toolTip: toolTip) ?? false) } func setSystemTrayInfo(_ call: FlutterMethodCall, _ result: FlutterResult) { let arguments = call.arguments as! [String: Any] let title = arguments[kTitleKey] as? String - let base64Icon = arguments[kBase64IconKey] as? String + let base64Icon = arguments[kIconPathKey] as? String let toolTip = arguments[kToolTipKey] as? String result( - system_tray?.setSystemTrayInfo(title: title, base64Icon: base64Icon, toolTip: toolTip) ?? false) + system_tray?.setSystemTrayInfo(title: title, base64Icon: base64Icon, toolTip: toolTip) + ?? false) } func valueToMenuItem(menu: NSMenu, item: [String: Any]) -> Bool { @@ -162,6 +170,16 @@ public class SystemTrayPlugin: NSObject, FlutterPlugin { result(system_tray?.popUpContextMenu() ?? false) } + func setPressedImage(_ call: FlutterMethodCall, _ result: FlutterResult) { + let base64Icon = call.arguments as? String + system_tray?.setPressedImage(base64Icon: base64Icon) + result(nil) + } + + func getTitle(_ call: FlutterMethodCall, _ result: FlutterResult) { + result(system_tray?.getTitle() ?? "") + } + @objc func onMenuItemSelectedCallback(_ sender: Any) { let menuItem = sender as! NSMenuItem channel.invokeMethod(kMenuItemSelectedCallbackMethod, arguments: menuItem.tag, result: nil) diff --git a/macos/Classes/Tray.swift b/macos/Classes/Tray.swift index 8e6d233..11cf085 100644 --- a/macos/Classes/Tray.swift +++ b/macos/Classes/Tray.swift @@ -52,19 +52,22 @@ class SystemTray: NSObject, NSMenuDelegate { if let toolTip = toolTip { statusItem?.button?.toolTip = toolTip } + if let title = title { statusItem?.button?.title = title } if let base64Icon = base64Icon { if let imageData = Data(base64Encoded: base64Icon, options: .ignoreUnknownCharacters), - let itemImage = NSImage(data: imageData) { + let itemImage = NSImage(data: imageData) + { let destSize = NSSize(width: kDefaultSizeWidth, height: kDefaultSizeHeight) itemImage.size = destSize statusItem?.button?.image = itemImage statusItem?.button?.imagePosition = NSControl.ImagePosition.imageLeft } } + return true } @@ -72,21 +75,24 @@ class SystemTray: NSObject, NSMenuDelegate { if let toolTip = toolTip { statusItem?.button?.toolTip = toolTip } + if let title = title { statusItem?.button?.title = title } + if let base64Icon = base64Icon { if let imageData = Data(base64Encoded: base64Icon, options: .ignoreUnknownCharacters), - let itemImage = NSImage(data: imageData) { + let itemImage = NSImage(data: imageData) + { let destSize = NSSize(width: kDefaultSizeWidth, height: kDefaultSizeHeight) itemImage.size = destSize statusItem?.button?.image = itemImage statusItem?.button?.imagePosition = NSControl.ImagePosition.imageLeft - return true + } else { + statusItem?.button?.image = nil } } - statusItem?.button?.image = nil return true } @@ -104,4 +110,25 @@ class SystemTray: NSObject, NSMenuDelegate { } return false } + + func setPressedImage(base64Icon: String?) { + if let base64Icon = base64Icon { + if let imageData = Data(base64Encoded: base64Icon, options: .ignoreUnknownCharacters), + let itemImage = NSImage(data: imageData) + { + let destSize = NSSize(width: kDefaultSizeWidth, height: kDefaultSizeHeight) + itemImage.size = destSize + statusItem?.button?.alternateImage = itemImage + statusItem?.button?.setButtonType(.toggle) + } else { + statusItem?.button?.alternateImage = nil + } + } else { + statusItem?.button?.alternateImage = nil + } + } + + func getTitle() -> String { + return statusItem?.button?.title ?? "" + } } diff --git a/pubspec.yaml b/pubspec.yaml index ed903e2..dfe5d28 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: system_tray description: system_tray that makes it easy to customize tray and work with your Flutter desktop app. -version: 0.0.9 +version: 0.1.0 repository: https://github.com/antler119/system_tray.git environment: diff --git a/windows/system_tray_plugin.cpp b/windows/system_tray_plugin.cpp index f4f4a96..3222d12 100644 --- a/windows/system_tray_plugin.cpp +++ b/windows/system_tray_plugin.cpp @@ -220,7 +220,7 @@ SystemTrayPlugin::~SystemTrayPlugin() { void SystemTrayPlugin::HandleMethodCall( const flutter::MethodCall& method_call, std::unique_ptr> result) { - printf("method call %s\n", method_call.method_name().c_str()); + // printf("method call %s\n", method_call.method_name().c_str()); if (method_call.method_name().compare(kInitSystemTray) == 0) { initSystemTray(method_call, *result);