From 67831c92c3957587095ab6b948b86317d95658c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=81=A5=E8=8E=B9?= Date: Sun, 18 Aug 2024 15:00:32 +0800 Subject: [PATCH] chore: Implement "display-added" and "display-removed" for macOS --- packages/screen_retriever/CHANGELOG.md | 1 + .../example/lib/pages/home.dart | 29 +++++++--- .../lib/src/screen_retriever.dart | 32 +++++++++++ .../test/screen_retriever_test.dart | 4 ++ .../Classes/ScreenRetrieverMacosPlugin.swift | 55 ++++++++++++++++++- .../lib/src/screen_listener.dart | 2 +- .../src/screen_retriever_method_channel.dart | 11 ++++ .../lib/src/screen_retriever_platform.dart | 6 ++ .../src/screen_retriever_platform_test.dart | 4 ++ 9 files changed, 133 insertions(+), 11 deletions(-) diff --git a/packages/screen_retriever/CHANGELOG.md b/packages/screen_retriever/CHANGELOG.md index 654d92e..5775804 100644 --- a/packages/screen_retriever/CHANGELOG.md +++ b/packages/screen_retriever/CHANGELOG.md @@ -1,6 +1,7 @@ ## 0.2.0 * Convert to federated plugin +* Implement "display-added" and "display-removed" for macOS #5 ## 0.1.9 diff --git a/packages/screen_retriever/example/lib/pages/home.dart b/packages/screen_retriever/example/lib/pages/home.dart index dd67f5e..d13ad99 100644 --- a/packages/screen_retriever/example/lib/pages/home.dart +++ b/packages/screen_retriever/example/lib/pages/home.dart @@ -4,9 +4,6 @@ import 'package:hotkey_manager/hotkey_manager.dart'; import 'package:screen_retriever/screen_retriever.dart'; import 'package:screen_retriever_example/widgets/display_card.dart'; -final hotKeyManager = HotKeyManager.instance; -final screenRetriever = ScreenRetriever.instance; - class _ListSection extends StatelessWidget { const _ListSection({required this.title, required this.children}); @@ -42,18 +39,14 @@ class HomePage extends StatefulWidget { State createState() => _HomePageState(); } -class _HomePageState extends State { +class _HomePageState extends State with ScreenListener { Display? _primaryDisplay; List _displayList = []; @override void initState() { + screenRetriever.addListener(this); super.initState(); - _init(); - } - - Future _init() async { - // 初始化快捷键 hotKeyManager.unregisterAll(); hotKeyManager.register( HotKey(KeyCode.keyD, modifiers: [KeyModifier.alt]), @@ -61,6 +54,24 @@ class _HomePageState extends State { _handleGetCursorScreenPoint(); }, ); + _getDisplays(); + } + + @override + void dispose() { + screenRetriever.removeListener(this); + super.dispose(); + } + + @override + void onScreenEvent(String eventName) { + BotToast.showText( + text: 'onScreenEvent: $eventName', + ); + _getDisplays(); + } + + Future _getDisplays() async { _primaryDisplay = await screenRetriever.getPrimaryDisplay(); _displayList = await screenRetriever.getAllDisplays(); setState(() {}); diff --git a/packages/screen_retriever/lib/src/screen_retriever.dart b/packages/screen_retriever/lib/src/screen_retriever.dart index 927df46..d2a093a 100644 --- a/packages/screen_retriever/lib/src/screen_retriever.dart +++ b/packages/screen_retriever/lib/src/screen_retriever.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:screen_retriever_platform_interface/screen_retriever_platform_interface.dart'; @@ -9,6 +10,35 @@ class ScreenRetriever { ScreenRetrieverPlatform get _platform => ScreenRetrieverPlatform.instance; + /// Handle screen events from the platform side. + void _handleScreenEvent(event) { + String type = event['type'] as String; + for (var listener in _listeners) { + listener.onScreenEvent(type); + } + } + + final ObserverList _listeners = + ObserverList(); + + bool get hasListeners { + return _listeners.isNotEmpty; + } + + void addListener(ScreenListener listener) { + if (!hasListeners) { + _platform.onScreenEventReceiver.listen(_handleScreenEvent); + } + _listeners.add(listener); + } + + void removeListener(ScreenListener listener) { + _listeners.remove(listener); + if (!hasListeners) { + _platform.onScreenEventReceiver.listen(null); + } + } + Future getCursorScreenPoint() { return _platform.getCursorScreenPoint(); } @@ -21,3 +51,5 @@ class ScreenRetriever { return _platform.getAllDisplays(); } } + +final ScreenRetriever screenRetriever = ScreenRetriever.instance; diff --git a/packages/screen_retriever/test/screen_retriever_test.dart b/packages/screen_retriever/test/screen_retriever_test.dart index 437a2af..cf94c05 100644 --- a/packages/screen_retriever/test/screen_retriever_test.dart +++ b/packages/screen_retriever/test/screen_retriever_test.dart @@ -16,6 +16,10 @@ class MockScreenRetrieverPlatform with MockPlatformInterfaceMixin implements ScreenRetrieverPlatform { @override + Stream> get onScreenEventReceiver => + throw UnimplementedError(); + + @override Future getCursorScreenPoint() { return Future(() => const Offset(10.0, 10.0)); } diff --git a/packages/screen_retriever_macos/macos/Classes/ScreenRetrieverMacosPlugin.swift b/packages/screen_retriever_macos/macos/Classes/ScreenRetrieverMacosPlugin.swift index 1b03d15..18c8847 100644 --- a/packages/screen_retriever_macos/macos/Classes/ScreenRetrieverMacosPlugin.swift +++ b/packages/screen_retriever_macos/macos/Classes/ScreenRetrieverMacosPlugin.swift @@ -48,11 +48,64 @@ extension NSRect { } } -public class ScreenRetrieverMacosPlugin: NSObject, FlutterPlugin { +public class ScreenRetrieverMacosPlugin: NSObject, FlutterPlugin,FlutterStreamHandler { + private var _eventSink: FlutterEventSink? + + var externalDisplayCount:Int = 0 + public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "dev.leanflutter.plugins/screen_retriever", binaryMessenger: registrar.messenger) let instance = ScreenRetrieverMacosPlugin() registrar.addMethodCallDelegate(instance, channel: channel) + let eventChannel = FlutterEventChannel(name: "dev.leanflutter.plugins/screen_retriever_event", binaryMessenger: registrar.messenger) + eventChannel.setStreamHandler(instance) + + instance.externalDisplayCount = NSScreen.screens.count + instance.setupNotificationCenter() + } + + public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { + self._eventSink = events + return nil; + } + + public func onCancel(withArguments arguments: Any?) -> FlutterError? { + self._eventSink = nil + return nil + } + +// public func handleDidFinishLaunching(_ notification: Notification) { +// externalDisplayCount = NSScreen.screens.count +// setupNotificationCenter() +// } + + func setupNotificationCenter() { + NotificationCenter.default.addObserver( + self, + selector: #selector(handleDisplayConnection), + name: NSApplication.didChangeScreenParametersNotification, + object: nil) + } + + + @objc func handleDisplayConnection(notification: Notification) { + if externalDisplayCount < NSScreen.screens.count { + _emitEvent("display-added") + externalDisplayCount = NSScreen.screens.count + } else if externalDisplayCount > NSScreen.screens.count { + _emitEvent("display-removed") + externalDisplayCount = NSScreen.screens.count + } + } + + public func _emitEvent(_ eventName: String) { + guard let eventSink = self._eventSink else { + return + } + let event: NSDictionary = [ + "type": eventName, + ] + eventSink(event) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { diff --git a/packages/screen_retriever_platform_interface/lib/src/screen_listener.dart b/packages/screen_retriever_platform_interface/lib/src/screen_listener.dart index 31b2cf6..04ceab5 100644 --- a/packages/screen_retriever_platform_interface/lib/src/screen_listener.dart +++ b/packages/screen_retriever_platform_interface/lib/src/screen_listener.dart @@ -1,5 +1,5 @@ /// Interface for listening to screen events. -abstract class ScreenListener { +abstract mixin class ScreenListener { /// Called when a screen event occurs. void onScreenEvent(String eventName) {} } diff --git a/packages/screen_retriever_platform_interface/lib/src/screen_retriever_method_channel.dart b/packages/screen_retriever_platform_interface/lib/src/screen_retriever_method_channel.dart index b76862f..154b562 100644 --- a/packages/screen_retriever_platform_interface/lib/src/screen_retriever_method_channel.dart +++ b/packages/screen_retriever_platform_interface/lib/src/screen_retriever_method_channel.dart @@ -13,6 +13,17 @@ class MethodChannelScreenRetriever extends ScreenRetrieverPlatform { 'dev.leanflutter.plugins/screen_retriever', ); + /// The event channel used to receive events from the native platform. + @visibleForTesting + final eventChannel = const EventChannel( + 'dev.leanflutter.plugins/screen_retriever_event', + ); + + @override + Stream> get onScreenEventReceiver { + return eventChannel.receiveBroadcastStream().cast>(); + } + // The default arguments used for [methodChannel.invokeMethod]. Map get _defaultArguments { MediaQueryData mediaQueryData = MediaQueryData.fromView( diff --git a/packages/screen_retriever_platform_interface/lib/src/screen_retriever_platform.dart b/packages/screen_retriever_platform_interface/lib/src/screen_retriever_platform.dart index d6e9370..8c6937b 100644 --- a/packages/screen_retriever_platform_interface/lib/src/screen_retriever_platform.dart +++ b/packages/screen_retriever_platform_interface/lib/src/screen_retriever_platform.dart @@ -24,6 +24,12 @@ abstract class ScreenRetrieverPlatform extends PlatformInterface { _instance = instance; } + Stream> get onScreenEventReceiver { + throw UnimplementedError( + 'onScreenEventReceiver() has not been implemented.', + ); + } + Future getCursorScreenPoint() { throw UnimplementedError( 'getCursorScreenPoint() has not been implemented.', diff --git a/packages/screen_retriever_platform_interface/test/src/screen_retriever_platform_test.dart b/packages/screen_retriever_platform_interface/test/src/screen_retriever_platform_test.dart index aaf7cfb..217d285 100644 --- a/packages/screen_retriever_platform_interface/test/src/screen_retriever_platform_test.dart +++ b/packages/screen_retriever_platform_interface/test/src/screen_retriever_platform_test.dart @@ -8,6 +8,10 @@ class MockScreenRetrieverPlatform with MockPlatformInterfaceMixin implements ScreenRetrieverPlatform { @override + Stream> get onScreenEventReceiver => + throw UnimplementedError(); + + @override Future getCursorScreenPoint() { return Future(() => Offset.zero); }