diff --git a/CHANGELOG.md b/CHANGELOG.md index ed27c7a..2c46acb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,13 @@ +## 0.0.9 + +* [Feature] add popUpContextMenu +* [Feature] add new system tray event (leftMouseDown / rightMouseDown) + ## 0.0.8 -* tray icon support high DPI on windows -* Fixed crash when minimize window, then click on menubar on macOS -* support simple appwindow on ubuntu +* [Feature] tray icon support high DPI on windows +* [Feature] support simple class appwindow for control window on all platforms +* [Fixed] fixed crash when minimize window, then click on menubar on macOS ## 0.0.7 diff --git a/README.md b/README.md index 4db33f0..462e0ba 100644 --- a/README.md +++ b/README.md @@ -3,17 +3,13 @@ A [Flutter package](https://github.com/antler119/system_tray.git) that that enables support for system tray menu for desktop flutter apps. **on Windows, macOS and Linux**. -## Features -* Modify system tray title/icon/tooltip -* Handle system tray event leftMouseUp/rightMouseUp (only for macos、windows) - ## Install In the pubspec.yaml of your flutter project, add the following dependency: ```yaml dependencies: ... - system_tray: ^0.0.8 + system_tray: ^0.0.9 ``` In your library add the following import: @@ -32,18 +28,94 @@ sudo apt-get install appindicator3-0.1 libappindicator3-dev ## Example App ### Windows - + ### macOS - + ### Linux - - - -## Usage: + + +## API + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodDescriptionWindowsmacOSLinux
initSystemTrayInitialize system tray✔️✔️✔️
setSystemTrayInfoModify the tray info +
    +
  • icon
  • +
  • toolTip
  • +
+
+
    +
  • title
  • +
  • icon
  • +
  • toolTip
  • +
+
+
    +
  • icon
  • +
+
setContextMenuSet the tray context menu✔️✔️✔️
popUpContextMenuPopup the tray context menu✔️✔️
registerSystemTrayEventHandlerRegister system tray event +
    +
  • leftMouseUp
  • +
  • leftMouseDown
  • +
  • leftMouseDblClk
  • +
  • rightMouseUp
  • +
  • rightMouseDown
  • +
+
+
    +
  • leftMouseUp
  • +
  • leftMouseDown
  • +
  • rightMouseUp
  • +
  • rightMouseDown
  • +
+
+ +## Usage Smallest example: ```dart @@ -60,16 +132,29 @@ Future initSystemTray() async { 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, ); 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(); + } + }); } ``` -Icon flashing effect example: +Flashing icon example: ```dart Future initSystemTray() async { @@ -113,27 +198,43 @@ Future initSystemTray() async { ), MenuSeparator(), SubMenu( - label: "SubMenu", + label: "Test API", children: [ - MenuItem( - label: 'SubItem1', - enabled: false, - onClicked: () { - debugPrint("click SubItem1"); - }, - ), - MenuItem( - label: 'SubItem2', - onClicked: () { - debugPrint("click SubItem2"); - }, - ), - MenuItem( - label: 'SubItem3', - onClicked: () { - debugPrint("click SubItem3"); - }, + 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(), @@ -155,7 +256,11 @@ Future initSystemTray() async { // handle system tray event _systemTray.registerSystemTrayEventHandler((eventName) { debugPrint("eventName: $eventName"); - if (eventName == "leftMouseUp") { + if (eventName == "leftMouseDown") { + } else if (eventName == "leftMouseUp") { + _systemTray.popUpContextMenu(); + } else if (eventName == "rightMouseDown") { + } else if (eventName == "rightMouseUp") { _appWindow.show(); } }); diff --git a/example/lib/main.dart b/example/lib/main.dart index 21c7114..26a9356 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,7 +4,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart'; - +import 'package:english_words/english_words.dart'; import 'package:system_tray/system_tray.dart'; void main() async { @@ -88,27 +88,43 @@ class _MyAppState extends State { ), MenuSeparator(), SubMenu( - label: "SubMenu", + label: "Test API", children: [ - MenuItem( - label: 'SubItem1', - enabled: false, - onClicked: () { - debugPrint("click SubItem1"); - }, - ), - MenuItem( - label: 'SubItem2', - onClicked: () { - debugPrint("click SubItem2"); - }, - ), - MenuItem( - label: 'SubItem3', - onClicked: () { - debugPrint("click SubItem3"); - }, + 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(), @@ -130,8 +146,12 @@ class _MyAppState extends State { // handle system tray event _systemTray.registerSystemTrayEventHandler((eventName) { debugPrint("eventName: $eventName"); - if (eventName == "leftMouseUp") { + if (eventName == "leftMouseDown") { + } else if (eventName == "leftMouseUp") { _appWindow.show(); + } else if (eventName == "rightMouseDown") { + } else if (eventName == "rightMouseUp") { + _systemTray.popUpContextMenu(); } }); } diff --git a/example/pubspec.lock b/example/pubspec.lock index 3311f63..a9a7e0a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -85,6 +85,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.4" + english_words: + dependency: "direct main" + description: + name: english_words + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.0" fake_async: dependency: transitive description: @@ -197,7 +204,7 @@ packages: path: ".." relative: true source: path - version: "0.0.8" + version: "0.0.9" term_glyph: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index ef44245..6ab64d0 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -30,6 +30,7 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 bitsdojo_window: 0.1.1+1 + english_words: ^4.0.0 dev_dependencies: flutter_test: diff --git a/lib/src/tray.dart b/lib/src/tray.dart index c4ed37a..523a630 100644 --- a/lib/src/tray.dart +++ b/lib/src/tray.dart @@ -12,6 +12,8 @@ const String _kChannelName = "flutter/system_tray"; const String _kInitSystemTray = "InitSystemTray"; const String _kSetSystemTrayInfo = "SetSystemTrayInfo"; const String _kSetContextMenu = "SetContextMenu"; +const String _kPopupContextMenu = "PopupContextMenu"; + const String _kMenuItemSelectedCallbackMethod = 'MenuItemSelectedCallback'; const String _kSystemTrayEventCallbackMethod = 'SystemTrayEventCallback'; @@ -51,7 +53,7 @@ class SystemTray { // SystemTrayEventCallback? _systemTrayEventCallback; - // Show a SystemTray icon + /// Show a SystemTray icon Future initSystemTray({ required String title, required String iconPath, @@ -68,6 +70,7 @@ class SystemTray { return value; } + /// Set system info info Future setSystemTrayInfo({ String? title, String? iconPath, @@ -84,6 +87,7 @@ class SystemTray { return value; } + /// register listener for system tray event. void registerSystemTrayEventHandler(SystemTrayEventCallback callback) { _systemTrayEventCallback = callback; } @@ -104,6 +108,12 @@ class SystemTray { } } + /// Pop up the context menu. + /// + Future popUpContextMenu() async { + await _platformChannel.invokeMethod(_kPopupContextMenu); + } + /// Converts [menus] to a representation that can be sent in the arguments to /// [_kMenuSetMethod]. /// diff --git a/macos/Classes/SystemTrayPlugin.swift b/macos/Classes/SystemTrayPlugin.swift index c0e2966..56e89a2 100644 --- a/macos/Classes/SystemTrayPlugin.swift +++ b/macos/Classes/SystemTrayPlugin.swift @@ -3,27 +3,29 @@ import FlutterMacOS let kChannelName = "flutter/system_tray" -let kInitSystemTray = "InitSystemTray"; -let kSetSystemTrayInfo = "SetSystemTrayInfo"; -let kSetContextMenu = "SetContextMenu"; -let kMenuItemSelectedCallbackMethod = "MenuItemSelectedCallback"; +let kInitSystemTray = "InitSystemTray" +let kSetSystemTrayInfo = "SetSystemTrayInfo" +let kSetContextMenu = "SetContextMenu" +let kPopupContextMenu = "PopupContextMenu" + +let kMenuItemSelectedCallbackMethod = "MenuItemSelectedCallback" let kTitleKey = "title" let kIconPathKey = "iconpath" let kToolTipKey = "tooltip" -let kIdKey = "id"; -let kTypeKey = "type"; -let kLabelKey = "label"; -let kSeparatorKey = "separator"; -let kSubMenuKey = "submenu"; -let kEnabledKey = "enabled"; +let kIdKey = "id" +let kTypeKey = "type" +let kLabelKey = "label" +let kSeparatorKey = "separator" +let kSubMenuKey = "submenu" +let kEnabledKey = "enabled" let kChannelAppWindowName = "flutter/system_tray/app_window" -let kInitAppWindow = "InitAppWindow"; -let kShowAppWindow = "ShowAppWindow"; -let kHideAppWindow = "HideAppWindow"; -let kCloseAppWindow = "CloseAppWindow"; +let kInitAppWindow = "InitAppWindow" +let kShowAppWindow = "ShowAppWindow" +let kHideAppWindow = "HideAppWindow" +let kCloseAppWindow = "CloseAppWindow" public class SystemTrayPlugin: NSObject, FlutterPlugin { var system_tray: SystemTray? @@ -34,15 +36,19 @@ public class SystemTrayPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: kChannelName, binaryMessenger: registrar.messenger) - let channel_app_window = FlutterMethodChannel(name: kChannelAppWindowName, binaryMessenger: registrar.messenger) + let channel_app_window = FlutterMethodChannel( + name: kChannelAppWindowName, binaryMessenger: registrar.messenger) let instance = SystemTrayPlugin(registrar, channel, channel_app_window) - + registrar.addMethodCallDelegate(instance, channel: channel) registrar.addMethodCallDelegate(instance, channel: channel_app_window) } - init(_ registrar: FlutterPluginRegistrar, _ channel: FlutterMethodChannel, _ channel_app_window: FlutterMethodChannel) { + init( + _ registrar: FlutterPluginRegistrar, _ channel: FlutterMethodChannel, + _ channel_app_window: FlutterMethodChannel) + { system_tray = SystemTray(channel) self.channel = channel self.channel_app_window = channel_app_window @@ -59,6 +65,8 @@ public class SystemTrayPlugin: NSObject, FlutterPlugin { setSystemTrayInfo(call, result) case kSetContextMenu: setContextMenu(call, result) + case kPopupContextMenu: + popUpContextMenu(call, result) case kInitAppWindow: initAppWindow(call, result) case kShowAppWindow: @@ -73,19 +81,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 iconPath = arguments[kIconPathKey] as? String - let toolTip = arguments[kToolTipKey] as? String - result(system_tray?.initSystemTray(title: title, iconPath: iconPath, toolTip: toolTip) ?? false) + let arguments = call.arguments as! [String: Any] + let title = arguments[kTitleKey] as? String + let iconPath = arguments[kIconPathKey] as? String + let toolTip = arguments[kToolTipKey] as? String + result(system_tray?.initSystemTray(title: title, iconPath: iconPath, toolTip: toolTip) ?? false) } func setSystemTrayInfo(_ call: FlutterMethodCall, _ result: FlutterResult) { - let arguments = call.arguments as! [String: Any] - let title = arguments[kTitleKey] as? String - let iconPath = arguments[kIconPathKey] as? String - let toolTip = arguments[kToolTipKey] as? String - result(system_tray?.setSystemTrayInfo(title: title, iconPath: iconPath, toolTip: toolTip) ?? false) + let arguments = call.arguments as! [String: Any] + let title = arguments[kTitleKey] as? String + let iconPath = arguments[kIconPathKey] as? String + let toolTip = arguments[kToolTipKey] as? String + result( + system_tray?.setSystemTrayInfo(title: title, iconPath: iconPath, toolTip: toolTip) ?? false) } func valueToMenuItem(menu: NSMenu, item: [String: Any]) -> Bool { @@ -99,9 +108,9 @@ public class SystemTrayPlugin: NSObject, FlutterPlugin { let isEnabled = item[kEnabledKey] as? Bool ?? false switch type! { - case kSeparatorKey: + case kSeparatorKey: menu.addItem(.separator()) - case kSubMenuKey: + case kSubMenuKey: let subMenu = NSMenu() let children = item[kSubMenuKey] as? [[String: Any]] ?? [[String: Any]]() if valueToMenu(menu: subMenu, items: children) { @@ -110,7 +119,7 @@ public class SystemTrayPlugin: NSObject, FlutterPlugin { menuItem.submenu = subMenu menu.addItem(menuItem) } - default: + default: let menuItem = NSMenuItem() menuItem.title = label menuItem.target = self @@ -123,103 +132,107 @@ public class SystemTrayPlugin: NSObject, FlutterPlugin { } func valueToMenu(menu: NSMenu, items: [[String: Any]]) -> Bool { - for item in items { - if !valueToMenuItem(menu: menu, item: item) { - return false - } + for item in items { + if !valueToMenuItem(menu: menu, item: item) { + return false } - return true + } + return true } func setContextMenu(_ call: FlutterMethodCall, _ result: FlutterResult) { - var popup_menu: NSMenu - repeat { - let items = call.arguments as? [[String: Any]] - if items == nil { - break - } - - popup_menu = NSMenu() - if !valueToMenu(menu: popup_menu, items: items!) { - break - } - - result(system_tray?.setContextMenu(menu: popup_menu) ?? false) - return - } while false - - result(false) + var popup_menu: NSMenu + repeat { + let items = call.arguments as? [[String: Any]] + if items == nil { + break + } + + popup_menu = NSMenu() + if !valueToMenu(menu: popup_menu, items: items!) { + break + } + + result(system_tray?.setContextMenu(menu: popup_menu) ?? false) + return + } while false + + result(false) + } + + func popUpContextMenu(_ call: FlutterMethodCall, _ result: FlutterResult) { + result(system_tray?.popUpContextMenu() ?? false) } - @objc func onMenuItemSelectedCallback(_ sender:Any) { + @objc func onMenuItemSelectedCallback(_ sender: Any) { let menuItem = sender as! NSMenuItem channel.invokeMethod(kMenuItemSelectedCallbackMethod, arguments: menuItem.tag, result: nil) } func initAppWindow(_ call: FlutterMethodCall, _ result: FlutterResult) { - repeat { - if self.app_window != nil { - result(true) - break; - } - - let view = registrar.view - if view == nil { - break - } - - let window = view!.window; - if window == nil { - break - } - - self.app_window = AppWindow(channel, window!) - result(true) - return - } while false - - result(false) + repeat { + if app_window != nil { + result(true) + break + } + + let view = registrar.view + if view == nil { + break + } + + let window = view!.window + if window == nil { + break + } + + app_window = AppWindow(channel, window!) + result(true) + return + } while false + + result(false) } func showAppWindow(_ call: FlutterMethodCall, _ result: FlutterResult) { - repeat { - if self.app_window == nil { - break; - } + repeat { + if app_window == nil { + break + } - self.app_window!.showAppWindow() - result(true) - return - } while false + app_window!.showAppWindow() + result(true) + return + } while false - result(false) + result(false) } func hideAppWindow(_ call: FlutterMethodCall, _ result: FlutterResult) { - repeat { - if self.app_window == nil { - break; - } + repeat { + if app_window == nil { + break + } - self.app_window!.hideAppWindow() - result(true) - return - } while false + app_window!.hideAppWindow() + result(true) + return + } while false - result(false) + result(false) } func closeAppWindow(_ call: FlutterMethodCall, _ result: FlutterResult) { - repeat { - if self.app_window == nil { - break; - } + repeat { + if app_window == nil { + break + } - self.app_window!.closeAppWindow() - result(true) - return - } while false + app_window!.closeAppWindow() + result(true) + return + } while false - result(false) + result(false) } } diff --git a/macos/Classes/Tray.swift b/macos/Classes/Tray.swift index ef25f80..84594a5 100644 --- a/macos/Classes/Tray.swift +++ b/macos/Classes/Tray.swift @@ -1,92 +1,104 @@ - import FlutterMacOS let kDefaultSizeWidth = 18 let kDefaultSizeHeight = 18 -let kSystemTrayEventCallbackMethod = "SystemTrayEventCallback"; +let kSystemTrayEventCallbackMethod = "SystemTrayEventCallback" + +let kSystemTrayEventLButtnUp = "leftMouseUp" +let kSystemTrayEventLButtnDown = "leftMouseDown" +let kSystemTrayEventRButtnUp = "rightMouseUp" +let kSystemTrayEventRButtnDown = "rightMouseDown" + +class SystemTray: NSObject, NSMenuDelegate { + var statusItem: NSStatusItem? + var statusItemMenu: NSMenu? + var channel: FlutterMethodChannel + + init(_ channel: FlutterMethodChannel) { + self.channel = channel + } + + func menuDidClose(_ menu: NSMenu) { + statusItem?.menu = nil + } + + @objc func onSystemTrayEventCallback(sender: NSStatusBarButton) { + if let event = NSApp.currentEvent { + switch event.type { + case .leftMouseUp: + channel.invokeMethod(kSystemTrayEventCallbackMethod, arguments: kSystemTrayEventLButtnUp) + case .leftMouseDown: + channel.invokeMethod(kSystemTrayEventCallbackMethod, arguments: kSystemTrayEventLButtnDown) + case .rightMouseUp: + channel.invokeMethod(kSystemTrayEventCallbackMethod, arguments: kSystemTrayEventRButtnUp) + case .rightMouseDown: + channel.invokeMethod(kSystemTrayEventCallbackMethod, arguments: kSystemTrayEventRButtnDown) + default: + break + } + } + } -let kSystemTrayEventLButtnUp = "leftMouseUp"; -let kSystemTrayEventLButtonDblClk = "leftMouseDblClk"; -let kSystemTrayEventRButtnUp = "rightMouseUp"; + func initSystemTray(title: String?, iconPath: String?, toolTip: String?) -> Bool { + statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) -class SystemTray : NSObject, NSMenuDelegate { - var statusItem: NSStatusItem? - var statusItemMenu: NSMenu? - var channel: FlutterMethodChannel + statusItem?.button?.action = #selector(onSystemTrayEventCallback(sender:)) + statusItem?.button?.target = self + statusItem?.button?.sendAction(on: [ + .leftMouseUp, .leftMouseDown, .rightMouseUp, .rightMouseDown, + ]) - init(_ channel: FlutterMethodChannel) { - self.channel = channel + if let toolTip = toolTip { + statusItem?.button?.toolTip = toolTip } - - func menuDidClose(_ menu: NSMenu) { - self.statusItem?.menu = nil + if let title = title { + statusItem?.button?.title = title } - @objc func onSystemTrayEventCallback(sender: NSStatusBarButton) { - if let event = NSApp.currentEvent { - switch event.type { - case .leftMouseUp: - channel.invokeMethod(kSystemTrayEventCallbackMethod, arguments: kSystemTrayEventLButtnUp) - default: - channel.invokeMethod(kSystemTrayEventCallbackMethod, arguments: kSystemTrayEventRButtnUp) - - statusItem?.menu = statusItemMenu - statusItem?.button?.performClick(nil) - } - } + if let iconPath = iconPath { + if let itemImage = NSImage(named: iconPath) { + let destSize = NSSize(width: kDefaultSizeWidth, height: kDefaultSizeHeight) + itemImage.size = destSize + statusItem?.button?.image = itemImage + statusItem?.button?.imagePosition = NSControl.ImagePosition.imageLeft + } } + return true + } - func initSystemTray(title: String?, iconPath: String?, toolTip: String?) -> Bool { - statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) - - statusItem?.button?.action = #selector(onSystemTrayEventCallback(sender:)) - statusItem?.button?.target = self - statusItem?.button?.sendAction(on: [.leftMouseUp, .rightMouseUp]) - - if let toolTip = toolTip { - statusItem?.button?.toolTip = toolTip - } - if let title = title { - statusItem?.button?.title = title - } - - if let iconPath = iconPath { - if let itemImage = NSImage(named: iconPath) { - let destSize = NSSize(width: kDefaultSizeWidth, height: kDefaultSizeHeight) - itemImage.size = destSize - statusItem?.button?.image = itemImage - statusItem?.button?.imagePosition = NSControl.ImagePosition.imageLeft - } - } - return true + func setSystemTrayInfo(title: String?, iconPath: String?, toolTip: String?) -> Bool { + if let toolTip = toolTip { + statusItem?.button?.toolTip = toolTip } - - func setSystemTrayInfo(title: String?, iconPath: String?, toolTip: String?) -> Bool { - if let toolTip = toolTip { - statusItem?.button?.toolTip = toolTip - } - if let title = title { - statusItem?.button?.title = title - } - if let iconPath = iconPath { - if let itemImage = NSImage(named: iconPath) { - let destSize = NSSize(width: kDefaultSizeWidth, height: kDefaultSizeHeight) - itemImage.size = destSize - statusItem?.button?.image = itemImage - statusItem?.button?.imagePosition = NSControl.ImagePosition.imageLeft - } else { - statusItem?.button?.image = nil - } - } - return true + if let title = title { + statusItem?.button?.title = title } - - func setContextMenu(menu: NSMenu) -> Bool { - statusItemMenu = menu - statusItemMenu?.delegate = self - return true + if let iconPath = iconPath { + if let itemImage = NSImage(named: iconPath) { + let destSize = NSSize(width: kDefaultSizeWidth, height: kDefaultSizeHeight) + itemImage.size = destSize + statusItem?.button?.image = itemImage + statusItem?.button?.imagePosition = NSControl.ImagePosition.imageLeft + } else { + statusItem?.button?.image = nil + } } - - + return true + } + + func setContextMenu(menu: NSMenu) -> Bool { + statusItemMenu = menu + statusItemMenu?.delegate = self + return true + } + + func popUpContextMenu() -> Bool { + if let statusItemMenu = statusItemMenu { + statusItem?.menu = statusItemMenu + statusItem?.button?.performClick(nil) + return true + } + return false + } } diff --git a/pubspec.yaml b/pubspec.yaml index 60700b5..ed903e2 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 window **on Windows, macOS and Linux**. -version: 0.0.8 +description: system_tray that makes it easy to customize tray and work with your Flutter desktop app. +version: 0.0.9 repository: https://github.com/antler119/system_tray.git environment: diff --git a/resources/screenshot_macos.jpg b/resources/screenshot_macos.jpg deleted file mode 100644 index 2039683..0000000 Binary files a/resources/screenshot_macos.jpg and /dev/null differ diff --git a/resources/screenshot_macos.png b/resources/screenshot_macos.png new file mode 100644 index 0000000..d3e0e84 Binary files /dev/null and b/resources/screenshot_macos.png differ diff --git a/resources/screenshot_ubuntu.jpg b/resources/screenshot_ubuntu.jpg deleted file mode 100644 index ba82ada..0000000 Binary files a/resources/screenshot_ubuntu.jpg and /dev/null differ diff --git a/resources/screenshot_ubuntu.png b/resources/screenshot_ubuntu.png new file mode 100644 index 0000000..b8aa1de Binary files /dev/null and b/resources/screenshot_ubuntu.png differ diff --git a/resources/screenshot_windows.jpg b/resources/screenshot_windows.jpg deleted file mode 100644 index b1c53f8..0000000 Binary files a/resources/screenshot_windows.jpg and /dev/null differ diff --git a/resources/screenshot_windows.png b/resources/screenshot_windows.png new file mode 100644 index 0000000..1af488e Binary files /dev/null and b/resources/screenshot_windows.png differ diff --git a/windows/app_window.cpp b/windows/app_window.cpp index 446d3aa..613f3b9 100644 --- a/windows/app_window.cpp +++ b/windows/app_window.cpp @@ -7,8 +7,9 @@ bool AppWindow::initAppWindow(HWND window, HWND flutter_window) { } bool AppWindow::showAppWindow(bool visible) { - if (!IsWindow(window_)) + if (!IsWindow(window_)) { return false; + } if (visible) { activeWindow(); @@ -20,19 +21,22 @@ bool AppWindow::showAppWindow(bool visible) { } bool AppWindow::closeAppWindow() { - if (!IsWindow(window_)) + if (!IsWindow(window_)) { return false; + } PostMessage(window_, WM_SYSCOMMAND, SC_CLOSE, 0); return true; } void AppWindow::activeWindow() { - if (!IsWindow(window_)) + if (!IsWindow(window_)) { return; + } - if (!::IsWindowVisible(window_)) + if (!::IsWindowVisible(window_)) { ShowWindow(window_, SW_SHOW); + } if (IsIconic(window_)) { SendMessage(window_, WM_SYSCOMMAND, SC_RESTORE | HTCAPTION, 0); @@ -43,8 +47,9 @@ void AppWindow::activeWindow() { } void AppWindow::refreshFlutterWindow() { - if (!IsWindow(flutter_window_)) + if (!IsWindow(flutter_window_)) { return; + } RECT rc = {}; GetClientRect(flutter_window_, &rc); diff --git a/windows/system_tray_plugin.cpp b/windows/system_tray_plugin.cpp index 2eadf9d..f4f4a96 100644 --- a/windows/system_tray_plugin.cpp +++ b/windows/system_tray_plugin.cpp @@ -27,6 +27,8 @@ const static char kChannelName[] = "flutter/system_tray"; const static char kInitSystemTray[] = "InitSystemTray"; const static char kSetSystemTrayInfo[] = "SetSystemTrayInfo"; const static char kSetContextMenu[] = "SetContextMenu"; +const static char kPopupContextMenu[] = "PopupContextMenu"; + const static char kMenuItemSelectedCallbackMethod[] = "MenuItemSelectedCallback"; const static char kSystemTrayEventCallbackMethod[] = "SystemTrayEventCallback"; @@ -122,6 +124,10 @@ class SystemTrayPlugin : public flutter::Plugin, public SystemTray::Delegate { const flutter::MethodCall& method_call, flutter::MethodResult& result); + void popupContextMenu( + const flutter::MethodCall& method_call, + flutter::MethodResult& result); + bool valueToMenu(HMENU menu, const flutter::EncodableList& representation); bool valueToMenuItem(HMENU menu, const flutter::EncodableMap& representation); @@ -222,6 +228,8 @@ void SystemTrayPlugin::HandleMethodCall( setSystemTrayInfo(method_call, *result); } else if (method_call.method_name().compare(kSetContextMenu) == 0) { setContextMenu(method_call, *result); + } else if (method_call.method_name().compare(kPopupContextMenu) == 0) { + popupContextMenu(method_call, *result); } else if (method_call.method_name().compare(kInitAppWindow) == 0) { initAppWindow(method_call, *result); } else if (method_call.method_name().compare(kShowAppWindow) == 0) { @@ -265,7 +273,8 @@ void SystemTrayPlugin::initSystemTray( const std::string* toolTip = std::get_if(ValueOrNull(*map, kToolTipKey)); - if (!system_tray_->initSystemTray(window, title, iconPath, toolTip)) { + if (!system_tray_ || + !system_tray_->initSystemTray(window, title, iconPath, toolTip)) { result.Error(kBadArgumentsError, "Unable to init system tray", flutter::EncodableValue(false)); break; @@ -297,7 +306,8 @@ void SystemTrayPlugin::setSystemTrayInfo( const std::string* toolTip = std::get_if(ValueOrNull(*map, kToolTipKey)); - if (!system_tray_->setSystemTrayInfo(title, iconPath, toolTip)) { + if (!system_tray_ || + !system_tray_->setSystemTrayInfo(title, iconPath, toolTip)) { result.Error(kBadArgumentsError, "Unable to set system tray info", flutter::EncodableValue(false)); break; @@ -389,7 +399,7 @@ void SystemTrayPlugin::setContextMenu( break; } - if (!system_tray_->setContextMenu(popup_menu)) { + if (!system_tray_ || !system_tray_->setContextMenu(popup_menu)) { result.Error(kBadArgumentsError, "Unable to set context menu", flutter::EncodableValue(false)); break; @@ -405,6 +415,22 @@ void SystemTrayPlugin::setContextMenu( } } +void SystemTrayPlugin::popupContextMenu( + const flutter::MethodCall& method_call, + flutter::MethodResult& result) { + do { + if (!system_tray_) { + result.Error(kBadArgumentsError, "Expected system tray", + flutter::EncodableValue(false)); + break; + } + + system_tray_->popUpContextMenu(); + + result.Success(flutter::EncodableValue(true)); + } while (false); +} + std::optional SystemTrayPlugin::HandleWindowProc(HWND hwnd, UINT message, WPARAM wparam, diff --git a/windows/tray.cpp b/windows/tray.cpp index 606fc9e..2fba534 100644 --- a/windows/tray.cpp +++ b/windows/tray.cpp @@ -12,8 +12,10 @@ constexpr const wchar_t kTrayWindowClassName[] = } const static char kSystemTrayEventLButtnUp[] = "leftMouseUp"; +const static char kSystemTrayEventLButtnDown[] = "leftMouseDown"; const static char kSystemTrayEventLButtonDblClk[] = "leftMouseDblClk"; const static char kSystemTrayEventRButtnUp[] = "rightMouseUp"; +const static char kSystemTrayEventRButtnDown[] = "rightMouseDown"; // Converts the given UTF-8 string to UTF-16. static std::wstring Utf16FromUtf8(const std::string& utf8_string) { @@ -121,6 +123,10 @@ bool SystemTray::setContextMenu(HMENU context_menu) { return true; } +void SystemTray::popUpContextMenu() { + ShowPopupMenu(); +} + bool SystemTray::installTrayIcon(HWND window, const std::string* title, const std::string* iconPath, @@ -210,6 +216,11 @@ std::optional SystemTray::OnTrayIconCallback(UINT id, const POINT& pt) { do { switch (notifyMsg) { + case WM_LBUTTONDOWN: { + if (delegate_) { + delegate_->OnSystemTrayEventCallback(kSystemTrayEventLButtnDown); + } + } break; case WM_LBUTTONUP: { if (delegate_) { delegate_->OnSystemTrayEventCallback(kSystemTrayEventLButtnUp); @@ -220,12 +231,21 @@ std::optional SystemTray::OnTrayIconCallback(UINT id, delegate_->OnSystemTrayEventCallback(kSystemTrayEventLButtonDblClk); } } break; + case WM_RBUTTONDOWN: { + if (delegate_) { + delegate_->OnSystemTrayEventCallback(kSystemTrayEventRButtnDown); + } + } break; case WM_RBUTTONUP: { if (delegate_) { delegate_->OnSystemTrayEventCallback(kSystemTrayEventRButtnUp); } - ShowPopupMenu(); } break; + // default: { + // printf("OnTrayIconCallback id:%d, 0x%x, pt[%d,%d]\n", id, + // notifyMsg, + // pt.x, pt.y); + // } break; } } while (false); diff --git a/windows/tray.h b/windows/tray.h index d07b1ea..0e2f47f 100644 --- a/windows/tray.h +++ b/windows/tray.h @@ -30,6 +30,8 @@ class SystemTray { bool setContextMenu(HMENU context_menu); + void popUpContextMenu(); + std::optional HandleWindowProc(HWND hwnd, UINT message, WPARAM wparam,