Initial import of FDE macOS framework (#7642)

Merges the current flutter-desktop-embedding macOS framework into the
engine. Notable changes:
- All channel/codec related code is eliminated in favor of using the
  existing iOS implementations.
- All .m files renamed to .mm for consistency with the iOS code.
  - Some minor code changes to fix new warnings in Objective-C++ mode.
- License headers, basic format (e.g., clang-format changes) updated to
  use repo style.
- Xcode project is not included; instead adds GN build rules to create
  an integrated framework that combines what was the FDE library with
  what is present in FlutterEmbedder.framework.

Other changes are left as follow-ups, including:
- Moving shared code out of ios/ into common/.
- Potentially improving sharing between iOS and macOS BUILD.gn.
- Class renaming; the FLE prefix will be eliminated, but that API
  surface isn't stable yet, so that can be changed later.
This commit is contained in:
stuartmorgan
2019-01-31 14:14:43 -08:00
committed by GitHub
parent 54f5467155
commit 93452747fc
22 changed files with 1613 additions and 3 deletions
+16
View File
@@ -557,6 +557,22 @@ FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_software.h
FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_software.mm
FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.h
FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FLEOpenGLContextHandling.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FLEPlugin.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FLEPluginRegistrar.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FLEReshapeListener.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FLEView.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FLEViewController.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/Flutter.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Info.plist
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLETextInputModel.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLETextInputModel.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLETextInputPlugin.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLETextInputPlugin.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLEView.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLEViewController.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLEViewController_Internal.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/module.modulemap
FILE: ../../../flutter/shell/platform/embedder/assets/EmbedderInfo.plist
FILE: ../../../flutter/shell/platform/embedder/assets/embedder.modulemap
FILE: ../../../flutter/shell/platform/embedder/embedder.cc
+23
View File
@@ -4,12 +4,19 @@
assert(is_mac || is_ios)
import("framework_shared.gni")
group("darwin") {
if (is_ios) {
deps = [
"ios:flutter_framework",
]
}
if (is_mac) {
deps = [
"macos:flutter_framework",
]
}
}
source_set("flutter_channels") {
@@ -40,6 +47,22 @@ source_set("flutter_channels") {
public_configs = [ "$flutter_root:config" ]
}
# Framework code shared between iOS and macOS.
source_set("framework_shared") {
set_sources_assignment_filter([])
sources = [
"ios/framework/Source/FlutterChannels.mm",
"ios/framework/Source/FlutterCodecs.mm",
"ios/framework/Source/FlutterStandardCodec.mm",
"ios/framework/Source/FlutterStandardCodec_Internal.h",
]
public = framework_shared_headers
set_sources_assignment_filter(sources_assignment_filter)
defines = [ "FLUTTER_FRAMEWORK" ]
}
executable("flutter_channels_unittests") {
testonly = true
@@ -0,0 +1,14 @@
# Copyright 2013 The Flutter Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
framework_shared_headers = get_path_info(
[
# TODO: Move these files, and their implementations, to a shared
# location.
"ios/framework/Headers/FlutterMacros.h",
"ios/framework/Headers/FlutterBinaryMessenger.h",
"ios/framework/Headers/FlutterChannels.h",
"ios/framework/Headers/FlutterCodecs.h",
],
"abspath")
@@ -61,7 +61,10 @@ FLUTTER_EXPORT
*/
- (void)sendOnChannel:(NSString*)channel
message:(NSData* _Nullable)message
binaryReply:(FlutterBinaryReply _Nullable)callback;
binaryReply:(FlutterBinaryReply _Nullable)callback
// TODO: Add macOS support for replies once
// https://github.com/flutter/flutter/issues/18852 is fixed.
API_UNAVAILABLE(macos);
/**
* Registers a message handler for incoming binary messages from the Flutter side
@@ -103,7 +103,11 @@ FLUTTER_EXPORT
* @param message The message. Must be supported by the codec of this channel.
* @param callback A callback to be invoked with the message reply from Flutter.
*/
- (void)sendMessage:(id _Nullable)message reply:(FlutterReply _Nullable)callback;
- (void)sendMessage:(id _Nullable)message
reply:(FlutterReply _Nullable)callback
// TODO: Add macOS support for replies once
// https://github.com/flutter/flutter/issues/18852 is fixed.
API_UNAVAILABLE(macos);
/**
* Registers a message handler with this channel.
@@ -234,7 +238,10 @@ FLUTTER_EXPORT
*/
- (void)invokeMethod:(NSString*)method
arguments:(id _Nullable)arguments
result:(FlutterResult _Nullable)callback;
result:(FlutterResult _Nullable)callback
// TODO: Add macOS support for replies once
// https://github.com/flutter/flutter/issues/18852 is fixed.
API_UNAVAILABLE(macos);
/**
* Registers a handler for method calls from the Flutter side.
+196
View File
@@ -0,0 +1,196 @@
# Copyright 2013 The Flutter Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
assert(is_mac)
import("//build/config/mac/mac_sdk.gni")
import("$flutter_root/common/config.gni")
import("$flutter_root/shell/platform/darwin/framework_shared.gni")
_flutter_framework_name = "Flutter"
_flutter_framework_out_dir_subpath = "macos/$_flutter_framework_name.framework"
_flutter_framework_dir = "$root_out_dir/$_flutter_framework_out_dir_subpath"
# The headers that will be copied to the Flutter.framework and be accessed
# from outside the Flutter engine source root.
_flutter_framework_headers = [
"framework/Headers/Flutter.h",
"framework/Headers/FLEOpenGLContextHandling.h",
"framework/Headers/FLEPlugin.h",
"framework/Headers/FLEPluginRegistrar.h",
"framework/Headers/FLEReshapeListener.h",
"framework/Headers/FLEView.h",
"framework/Headers/FLEViewController.h",
]
_flutter_framework_headers_copy_dir =
"$_flutter_framework_dir/Versions/A/Headers"
shared_library("create_flutter_framework_dylib") {
visibility = [ ":*" ]
output_name = "$_flutter_framework_name"
sources = [
"framework/Source/FLETextInputModel.h",
"framework/Source/FLETextInputModel.mm",
"framework/Source/FLETextInputPlugin.h",
"framework/Source/FLETextInputPlugin.mm",
"framework/Source/FLEView.mm",
"framework/Source/FLEViewController.mm",
"framework/Source/FLEViewController_Internal.h",
]
sources += _flutter_framework_headers
deps = [
"$flutter_root/shell/platform/darwin:framework_shared",
"$flutter_root/shell/platform/embedder:embedder",
]
public_configs = [ "$flutter_root:config" ]
defines = [ "FLUTTER_FRAMEWORK" ]
cflags_objcc = [ "-fobjc-arc" ]
libs = [ "Cocoa.framework" ]
}
copy("copy_framework_dylib") {
visibility = [ ":*" ]
sources = [
"$root_out_dir/lib$_flutter_framework_name.dylib",
]
outputs = [
"$_flutter_framework_dir/Versions/A/$_flutter_framework_name",
]
deps = [
":create_flutter_framework_dylib",
]
}
action("copy_dylib_and_update_framework_install_name") {
visibility = [ ":*" ]
stamp_file = "$root_out_dir/flutter_install_name_stamp"
script = "$flutter_root/sky/tools/change_install_name.py"
inputs = [
"$_flutter_framework_dir/Versions/A/$_flutter_framework_name",
]
outputs = [
stamp_file,
]
args = [
"--dylib",
rebase_path("$_flutter_framework_dir/Versions/A/$_flutter_framework_name"),
"--install_name",
"@rpath/$_flutter_framework_name.framework/Versions/A/$_flutter_framework_name",
"--stamp",
rebase_path(stamp_file),
]
deps = [
":copy_framework_dylib",
]
}
copy("copy_framework_info_plist") {
visibility = [ ":*" ]
sources = [
"framework/Info.plist",
]
outputs = [
"$_flutter_framework_dir/Versions/A/Resources/Info.plist",
]
}
copy("copy_framework_module_map") {
visibility = [ ":*" ]
sources = [
"framework/module.modulemap",
]
outputs = [
"$_flutter_framework_dir/Versions/A/Modules/module.modulemap",
]
}
action("copy_framework_headers") {
script = "$flutter_root/sky/tools/install_framework_headers.py"
visibility = [ ":*" ]
set_sources_assignment_filter([])
sources = get_path_info(_flutter_framework_headers, "abspath") +
framework_shared_headers
outputs = []
foreach(header, sources) {
header_basename = get_path_info(header, "file")
outputs += [ "$_flutter_framework_headers_copy_dir/$header_basename" ]
}
args = [
"--location",
rebase_path("$_flutter_framework_headers_copy_dir"),
"--headers",
] + rebase_path(sources, "", "//")
set_sources_assignment_filter(sources_assignment_filter)
}
copy("copy_framework_icu") {
visibility = [ ":*" ]
set_sources_assignment_filter([])
sources = [
"//third_party/icu/flutter/icudtl.dat",
]
set_sources_assignment_filter(sources_assignment_filter)
outputs = [
"$_flutter_framework_dir/Versions/A/Resources/{{source_file_part}}",
]
}
copy("copy_license") {
visibility = [ ":*" ]
sources = [
"//LICENSE",
]
outputs = [
"$root_out_dir/LICENSE",
]
}
action("_generate_symlinks") {
visibility = [ ":*" ]
script = "//build/config/mac/package_framework.py"
outputs = [
"$root_build_dir/$_flutter_framework_name.stamp",
]
args = [
"--framework",
"$_flutter_framework_out_dir_subpath",
"--version",
"A",
"--contents",
"$_flutter_framework_name",
"Resources",
"Headers",
"Modules",
"--stamp",
"$_flutter_framework_name.stamp",
]
deps = [
":copy_dylib_and_update_framework_install_name",
":copy_framework_headers",
":copy_framework_icu",
":copy_framework_info_plist",
":copy_framework_module_map",
":copy_license",
]
}
group("flutter_framework") {
deps = [
":_generate_symlinks",
]
}
@@ -0,0 +1,29 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#if defined(FLUTTER_FRAMEWORK)
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterMacros.h"
#else
#import "FlutterMacros.h"
#endif
/**
* Protocol for views owned by FLEViewController to handle context changes, specifically relating to
* OpenGL context changes.
*/
FLUTTER_EXPORT
@protocol FLEOpenGLContextHandling
/**
* Sets the receiver as the current context object.
*/
- (void)makeCurrentContext;
/**
* Called when the display is updated. In an NSOpenGLView this is best handled via a flushBuffer
* call.
*/
- (void)onPresent;
@end
@@ -0,0 +1,52 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Foundation/Foundation.h>
#if defined(FLUTTER_FRAMEWORK)
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterMacros.h"
#else
#import "FlutterChannels.h"
#import "FlutterCodecs.h"
#import "FlutterMacros.h"
#endif
@protocol FLEPluginRegistrar;
/**
* Implemented by the platform side of a Flutter plugin.
*
* Defines a set of optional callback methods and a method to set up the plugin
* and register it to be called by other application components.
*
* Currently FLEPlugin has very limited functionality, but is expected to expand over time to
* more closely match the functionality of FlutterPlugin.
*/
FLUTTER_EXPORT
@protocol FLEPlugin <NSObject>
/**
* Creates an instance of the plugin to register with |registrar| using the desired
* FLEPluginRegistrar methods.
*/
+ (void)registerWithRegistrar:(nonnull id<FLEPluginRegistrar>)registrar;
@optional
/**
* Called when a message is sent from Flutter on a channel that a plugin instance has subscribed
* to via -[FLEPluginRegistrar addMethodCallDelegate:channel:].
*
* The |result| callback must be called exactly once, with one of:
* - FlutterMethodNotImplemented, if the method call is unknown.
* - A FlutterError, if the method call was understood but there was a
* problem handling it.
* - Any other value (including nil) to indicate success. The value will
* be returned to the Flutter caller, and must be serializable to JSON.
*/
- (void)handleMethodCall:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result;
@end
@@ -0,0 +1,48 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
#import "FLEPlugin.h"
#if defined(FLUTTER_FRAMEWORK)
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterBinaryMessenger.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterMacros.h"
#else
#import "FlutterBinaryMessenger.h"
#import "FlutterChannels.h"
#import "FlutterMacros.h"
#endif
/**
* The protocol for an object managing registration for a plugin. It provides access to application
* context, as as allowing registering for callbacks for handling various conditions.
*
* Currently FLEPluginRegistrar has very limited functionality, but is expected to expand over time
* to more closely match the functionality of FlutterPluginRegistrar.
*/
FLUTTER_EXPORT
@protocol FLEPluginRegistrar <NSObject>
/**
* The binary messenger used for creating channels to communicate with the Flutter engine.
*/
@property(nonnull, readonly) id<FlutterBinaryMessenger> messenger;
/**
* The view displaying Flutter content.
*
* WARNING: If/when multiple Flutter views within the same application are supported (#98), this
* API will change.
*/
@property(nullable, readonly) NSView* view;
/**
* Registers |delegate| to receive handleMethodCall:result: callbacks for the given |channel|.
*/
- (void)addMethodCallDelegate:(nonnull id<FLEPlugin>)delegate
channel:(nonnull FlutterMethodChannel*)channel;
@end
@@ -0,0 +1,23 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
#if defined(FLUTTER_FRAMEWORK)
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterMacros.h"
#else
#import "FlutterMacros.h"
#endif
/**
* Protocol for listening to reshape events on this FlutterView.
* Used to notify the underlying Flutter engine of the new screen dimensions.
* Reflected from [NSOpenGLView.reshape].
*/
FLUTTER_EXPORT
@protocol FLEReshapeListener
- (void)viewDidReshape:(nonnull NSOpenGLView*)view;
@end
@@ -0,0 +1,28 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
#import "FLEOpenGLContextHandling.h"
#import "FLEReshapeListener.h"
#if defined(FLUTTER_FRAMEWORK)
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterMacros.h"
#else
#import "FlutterMacros.h"
#endif
/**
* View capable of acting as a rendering target and input source for the Flutter
* engine.
*/
FLUTTER_EXPORT
@interface FLEView : NSOpenGLView <FLEOpenGLContextHandling>
/**
* Listener for reshape events. See protocol description.
*/
@property(nonatomic, weak, nullable) IBOutlet id<FLEReshapeListener> reshapeListener;
@end
@@ -0,0 +1,64 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
#import "FLEOpenGLContextHandling.h"
#import "FLEPluginRegistrar.h"
#import "FLEReshapeListener.h"
#if defined(FLUTTER_FRAMEWORK)
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterBinaryMessenger.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterMacros.h"
#else
#import "FlutterBinaryMessenger.h"
#import "FlutterMacros.h"
#endif
/**
* Controls embedder plugins and communication with the underlying Flutter engine, managing a view
* intended to handle key inputs and drawing protocols (see |view|).
*
* Can be launched headless (no managed view), at which point a Dart executable will be run on the
* Flutter engine in non-interactive mode, or with a drawable Flutter canvas.
*/
FLUTTER_EXPORT
@interface FLEViewController
: NSViewController <FlutterBinaryMessenger, FLEPluginRegistrar, FLEReshapeListener>
/**
* The view this controller manages when launched in interactive mode (headless set to false). Must
* be capable of handling text input events, and the OpenGL context handling protocols.
*/
@property(nullable) NSView<FLEOpenGLContextHandling>* view;
/**
* Launches the Flutter engine with the provided configuration.
*
* @param assets The path to the flutter_assets folder for the Flutter application to be run.
* @param arguments Arguments to pass to the Flutter engine. See
* https://github.com/flutter/engine/blob/master/shell/common/switches.h
* for details. Not all arguments will apply to embedding mode.
* Note: This API layer will likely abstract arguments in the future, instead of
* providing a direct passthrough.
* @return YES if the engine launched successfully.
*/
- (BOOL)launchEngineWithAssetsPath:(nonnull NSURL*)assets
commandLineArguments:(nullable NSArray<NSString*>*)arguments;
/**
* Launches the Flutter engine in headless mode with the provided configuration. In headless mode,
* this controller's view should not be displayed.
*
* See launcheEngineWithAssetsPath:commandLineArguments: for details.
*/
- (BOOL)launchHeadlessEngineWithAssetsPath:(nonnull NSURL*)assets
commandLineArguments:(nullable NSArray<NSString*>*)arguments;
/**
* Returns the FLEPluginRegistrar that should be used to register the plugin with the given name.
*/
- (nonnull id<FLEPluginRegistrar>)registrarForPlugin:(nonnull NSString*)pluginName;
@end
@@ -0,0 +1,14 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "FLEOpenGLContextHandling.h"
#import "FLEPlugin.h"
#import "FLEPluginRegistrar.h"
#import "FLEReshapeListener.h"
#import "FLEView.h"
#import "FLEViewController.h"
#import "FlutterBinaryMessenger.h"
#import "FlutterChannels.h"
#import "FlutterCodecs.h"
#import "FlutterMacros.h"
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>Flutter</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Flutter</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
@@ -0,0 +1,69 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Foundation/Foundation.h>
/**
* The affinity of the current cursor position. If the cursor is at a position representing
* a line break, the cursor may be drawn either at the end of the current line (upstream)
* or at the beginning of the next (downstream).
*/
typedef NS_ENUM(NSUInteger, FLETextAffinity) { FLETextAffinityUpstream, FLETextAffinityDownstream };
/**
* Data model representing text input state during an editing session.
*/
@interface FLETextInputModel : NSObject
/**
* The full text being edited.
*/
@property(nonnull, copy) NSMutableString* text;
/**
* The range of text currently selected. This may have length zero to represent a single
* cursor position.
*/
@property NSRange selectedRange;
/**
* The affinity for the current cursor position.
*/
@property FLETextAffinity textAffinity;
/**
* The range of text that is marked for edit, i.e. under the effects of a multi-keystroke input
* combination.
*/
@property NSRange markedRange;
/**
* Representation of the model's data as a state dictionary suitable for interchange with the
* Flutter Dart layer.
*/
@property(nonnull) NSDictionary* state;
/**
* ID of the text input client.
*/
@property(nonatomic, readonly, nonnull) NSNumber* clientID;
/**
* Keyboard type of the client. See available options:
* https://docs.flutter.io/flutter/services/TextInputType-class.html
*/
@property(nonatomic, readonly, nonnull) NSString* inputType;
/**
* An action requested by the user on the input client. See available options:
* https://docs.flutter.io/flutter/services/TextInputAction-class.html
*/
@property(nonatomic, readonly, nonnull) NSString* inputAction;
- (nullable instancetype)init NS_UNAVAILABLE;
/**
* Initializes a text input model with a [clientId] and [config] arguments. [config] arguments
* provide information on the text input connection.
*/
- (nullable instancetype)initWithClientID:(nonnull NSNumber*)clientID
configuration:(nonnull NSDictionary*)config;
@end
@@ -0,0 +1,123 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "flutter/shell/platform/darwin/macos/framework/Source/FLETextInputModel.h"
static NSString* const kTextAffinityDownstream = @"TextAffinity.downstream";
static NSString* const kTextAffinityUpstream = @"TextAffinity.upstream";
static NSString* const kTextInputAction = @"inputAction";
static NSString* const kTextInputType = @"inputType";
static NSString* const kTextInputTypeName = @"name";
static NSString* const kSelectionBaseKey = @"selectionBase";
static NSString* const kSelectionExtentKey = @"selectionExtent";
static NSString* const kSelectionAffinityKey = @"selectionAffinity";
static NSString* const kSelectionIsDirectionalKey = @"selectionIsDirectional";
static NSString* const kComposingBaseKey = @"composingBase";
static NSString* const kComposingExtentKey = @"composingExtent";
static NSString* const kTextKey = @"text";
/**
* These three static methods are necessary because Cocoa and Flutter have different idioms for
* signalling an empty range: Flutter uses {-1, -1} while Cocoa uses {NSNotFound, 0}. Also,
* despite the name, the "extent" fields are actually end indices, not lengths.
*/
/**
* Updates a range given base and extent fields.
*/
static NSRange UpdateRangeFromBaseExtent(NSNumber* base, NSNumber* extent, NSRange range) {
if (base == nil || extent == nil) {
return range;
}
if (base.intValue == -1 && extent.intValue == -1) {
range.location = NSNotFound;
range.length = 0;
} else {
range.location = [base unsignedLongValue];
range.length = [extent unsignedLongValue] - range.location;
}
return range;
}
/**
* Returns the appropriate base field for a given range.
*/
static long GetBaseForRange(NSRange range) {
if (range.location == NSNotFound) {
return -1;
}
return range.location;
}
/**
* Returns the appropriate extent field for a given range.
*/
static long GetExtentForRange(NSRange range) {
if (range.location == NSNotFound) {
return -1;
}
return range.location + range.length;
}
@implementation FLETextInputModel
- (instancetype)initWithClientID:(NSNumber*)clientID configuration:(NSDictionary*)config {
self = [super init];
if (self != nil) {
_clientID = clientID;
_inputAction = config[kTextInputAction];
// There's more information that can be used from this dictionary.
// Add more as needed.
NSDictionary* inputTypeInfo = config[kTextInputType];
_inputType = inputTypeInfo[kTextInputTypeName];
if (!_clientID || !_inputAction || !_inputType) {
NSLog(@"Missing arguments for %@ init.", [self class]);
return nil;
}
_text = [[NSMutableString alloc] init];
_selectedRange = NSMakeRange(NSNotFound, 0);
_markedRange = NSMakeRange(NSNotFound, 0);
_textAffinity = FLETextAffinityUpstream;
}
return self;
}
- (NSDictionary*)state {
NSString* const textAffinity =
(_textAffinity == FLETextAffinityUpstream) ? kTextAffinityUpstream : kTextAffinityDownstream;
NSDictionary* state = @{
kSelectionBaseKey : @(GetBaseForRange(_selectedRange)),
kSelectionExtentKey : @(GetExtentForRange(_selectedRange)),
kSelectionAffinityKey : textAffinity,
kSelectionIsDirectionalKey : @NO,
kComposingBaseKey : @(GetBaseForRange(_markedRange)),
kComposingExtentKey : @(GetExtentForRange(_markedRange)),
kTextKey : _text
};
return state;
}
- (void)setState:(NSDictionary*)state {
if (state == nil)
return;
_selectedRange = UpdateRangeFromBaseExtent(state[kSelectionBaseKey], state[kSelectionExtentKey],
_selectedRange);
NSString* selectionAffinity = state[kSelectionAffinityKey];
if (selectionAffinity != nil) {
_textAffinity = [selectionAffinity isEqualToString:kTextAffinityUpstream]
? FLETextAffinityUpstream
: FLETextAffinityDownstream;
}
_markedRange =
UpdateRangeFromBaseExtent(state[kComposingBaseKey], state[kComposingExtentKey], _markedRange);
NSString* text = state[kTextKey];
if (text != nil)
[_text setString:text];
}
@end
@@ -0,0 +1,26 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterBinaryMessenger.h"
#import "flutter/shell/platform/darwin/macos/framework/Headers/FLEViewController.h"
/**
* A plugin to handle text input.
*
* Responsible for bridging the native macOS text input system with the Flutter framework text
* editing classes, via system channels.
*
* This is not an FLEPlugin since it needs access to FLEViewController internals, so needs to be
* managed differently.
*/
@interface FLETextInputPlugin : NSResponder
/**
* Initializes a text input plugin that coordinates key event handling with |viewController|.
*/
- (instancetype)initWithViewController:(FLEViewController*)viewController;
@end
@@ -0,0 +1,309 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "flutter/shell/platform/darwin/macos/framework/Source/FLETextInputPlugin.h"
#import <objc/message.h>
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FLETextInputModel.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FLEViewController_Internal.h"
static NSString* const kTextInputChannel = @"flutter/textinput";
// See https://docs.flutter.io/flutter/services/SystemChannels/textInput-constant.html
static NSString* const kSetClientMethod = @"TextInput.setClient";
static NSString* const kShowMethod = @"TextInput.show";
static NSString* const kHideMethod = @"TextInput.hide";
static NSString* const kClearClientMethod = @"TextInput.clearClient";
static NSString* const kSetEditingStateMethod = @"TextInput.setEditingState";
static NSString* const kUpdateEditStateResponseMethod = @"TextInputClient.updateEditingState";
static NSString* const kPerformAction = @"TextInputClient.performAction";
static NSString* const kMultilineInputType = @"TextInputType.multiline";
/**
* Private properties of FlutterTextInputPlugin.
*/
@interface FLETextInputPlugin () <NSTextInputClient>
/**
* A text input context, representing a connection to the Cocoa text input system.
*/
@property(nonatomic) NSTextInputContext* textInputContext;
/**
* A dictionary of text input models, one per client connection, keyed
* by the client connection ID.
*/
@property(nonatomic) NSMutableDictionary<NSNumber*, FLETextInputModel*>* textInputModels;
/**
* The currently active client connection ID.
*/
@property(nonatomic, nullable) NSNumber* activeClientID;
/**
* The currently active text input model.
*/
@property(nonatomic, readonly, nullable) FLETextInputModel* activeModel;
/**
* The channel used to communicate with Flutter.
*/
@property(nonatomic) FlutterMethodChannel* channel;
/**
* The FLEViewController to manage input for.
*/
@property(nonatomic, weak) FLEViewController* flutterViewController;
/**
* Handles a Flutter system message on the text input channel.
*/
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
@end
@implementation FLETextInputPlugin
- (instancetype)initWithViewController:(FLEViewController*)viewController {
self = [super init];
if (self != nil) {
_flutterViewController = viewController;
_channel = [FlutterMethodChannel methodChannelWithName:kTextInputChannel
binaryMessenger:viewController
codec:[FlutterJSONMethodCodec sharedInstance]];
__weak FLETextInputPlugin* weakSelf = self;
[_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
[weakSelf handleMethodCall:call result:result];
}];
_textInputModels = [[NSMutableDictionary alloc] init];
_textInputContext = [[NSTextInputContext alloc] initWithClient:self];
}
return self;
}
#pragma mark - Private
- (FLETextInputModel*)activeModel {
return (_activeClientID == nil) ? nil : _textInputModels[_activeClientID];
}
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
BOOL handled = YES;
NSString* method = call.method;
if ([method isEqualToString:kSetClientMethod]) {
if (!call.arguments[0] || !call.arguments[1]) {
result([FlutterError
errorWithCode:@"error"
message:@"Missing arguments"
details:@"Missing arguments while trying to set a text input client"]);
return;
}
NSNumber* clientID = call.arguments[0];
if (clientID != nil &&
(_activeClientID == nil || ![_activeClientID isEqualToNumber:clientID])) {
_activeClientID = clientID;
// TODO: Do we need to preserve state across setClient calls?
FLETextInputModel* inputModel =
[[FLETextInputModel alloc] initWithClientID:clientID configuration:call.arguments[1]];
if (!inputModel) {
result([FlutterError errorWithCode:@"error"
message:@"Failed to create an input model"
details:@"Configuration arguments might be missing"]);
return;
}
_textInputModels[_activeClientID] = inputModel;
}
} else if ([method isEqualToString:kShowMethod]) {
[self.flutterViewController addKeyResponder:self];
[_textInputContext activate];
} else if ([method isEqualToString:kHideMethod]) {
[self.flutterViewController removeKeyResponder:self];
[_textInputContext deactivate];
} else if ([method isEqualToString:kClearClientMethod]) {
_activeClientID = nil;
} else if ([method isEqualToString:kSetEditingStateMethod]) {
NSDictionary* state = call.arguments;
self.activeModel.state = state;
} else {
handled = NO;
NSLog(@"Unhandled text input method '%@'", method);
}
result(handled ? nil : FlutterMethodNotImplemented);
}
/**
* Informs the Flutter framework of changes to the text input model's state.
*/
- (void)updateEditState {
if (self.activeModel == nil) {
return;
}
[_channel invokeMethod:kUpdateEditStateResponseMethod
arguments:@[ _activeClientID, _textInputModels[_activeClientID].state ]];
}
#pragma mark -
#pragma mark NSResponder
/**
* Note, the Apple docs suggest that clients should override essentially all the
* mouse and keyboard event-handling methods of NSResponder. However, experimentation
* indicates that only key events are processed by the native layer; Flutter processes
* mouse events. Additionally, processing both keyUp and keyDown results in duplicate
* processing of the same keys. So for now, limit processing to just keyDown.
*/
- (void)keyDown:(NSEvent*)event {
[_textInputContext handleEvent:event];
}
#pragma mark -
#pragma mark NSStandardKeyBindingMethods
/**
* Note, experimentation indicates that moveRight and moveLeft are called rather
* than the supposedly more RTL-friendly moveForward and moveBackward.
*/
- (void)moveLeft:(nullable id)sender {
NSRange selection = self.activeModel.selectedRange;
if (selection.length == 0) {
if (selection.location > 0) {
// Move to previous location
self.activeModel.selectedRange = NSMakeRange(selection.location - 1, 0);
[self updateEditState];
}
} else {
// Collapse current selection
self.activeModel.selectedRange = NSMakeRange(selection.location, 0);
[self updateEditState];
}
}
- (void)moveRight:(nullable id)sender {
NSRange selection = self.activeModel.selectedRange;
if (selection.length == 0) {
if (selection.location < self.activeModel.text.length) {
// Move to next location
self.activeModel.selectedRange = NSMakeRange(selection.location + 1, 0);
[self updateEditState];
}
} else {
// Collapse current selection
self.activeModel.selectedRange = NSMakeRange(selection.location + selection.length, 0);
[self updateEditState];
}
}
- (void)deleteBackward:(id)sender {
NSRange selection = self.activeModel.selectedRange;
if (selection.location == 0)
return;
NSRange range = selection;
if (selection.length == 0) {
NSUInteger location = (selection.location == NSNotFound) ? self.activeModel.text.length - 1
: selection.location - 1;
range = NSMakeRange(location, 1);
}
self.activeModel.selectedRange = NSMakeRange(range.location, 0);
[self insertText:@"" replacementRange:range]; // Updates edit state
}
#pragma mark -
#pragma mark NSTextInputClient
- (void)insertText:(id)string replacementRange:(NSRange)range {
if (self.activeModel != nil) {
if (range.location == NSNotFound && range.length == 0) {
// Use selection
range = self.activeModel.selectedRange;
}
if (range.location > self.activeModel.text.length)
range.location = self.activeModel.text.length;
if (range.length > (self.activeModel.text.length - range.location))
range.length = self.activeModel.text.length - range.location;
[self.activeModel.text replaceCharactersInRange:range withString:string];
self.activeModel.selectedRange = NSMakeRange(range.location + ((NSString*)string).length, 0);
[self updateEditState];
}
}
- (void)doCommandBySelector:(SEL)selector {
if ([self respondsToSelector:selector]) {
// Note: The more obvious [self performSelector...] doesn't give ARC enough information to
// handle retain semantics properly. See https://stackoverflow.com/questions/7017281/ for more
// information.
IMP imp = [self methodForSelector:selector];
void (*func)(id, SEL, id) = reinterpret_cast<void (*)(id, SEL, id)>(imp);
func(self, selector, nil);
}
}
- (void)insertNewline:(id)sender {
if ([self.activeModel.inputType isEqualToString:kMultilineInputType]) {
[self insertText:@"\n" replacementRange:self.activeModel.selectedRange];
}
[_channel invokeMethod:kPerformAction
arguments:@[ _activeClientID, self.activeModel.inputAction ]];
}
- (void)setMarkedText:(id)string
selectedRange:(NSRange)selectedRange
replacementRange:(NSRange)replacementRange {
if (self.activeModel != nil) {
[self.activeModel.text replaceCharactersInRange:replacementRange withString:string];
self.activeModel.selectedRange = selectedRange;
[self updateEditState];
}
}
- (void)unmarkText {
if (self.activeModel != nil) {
self.activeModel.markedRange = NSMakeRange(NSNotFound, 0);
[self updateEditState];
}
}
- (NSRange)selectedRange {
return (self.activeModel == nil) ? NSMakeRange(NSNotFound, 0) : self.activeModel.selectedRange;
}
- (NSRange)markedRange {
return (self.activeModel == nil) ? NSMakeRange(NSNotFound, 0) : self.activeModel.markedRange;
}
- (BOOL)hasMarkedText {
return (self.activeModel == nil) ? NO : self.activeModel.markedRange.location != NSNotFound;
}
- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
actualRange:(NSRangePointer)actualRange {
if (self.activeModel) {
if (actualRange != nil)
*actualRange = range;
NSString* substring = [self.activeModel.text substringWithRange:range];
return [[NSAttributedString alloc] initWithString:substring attributes:nil];
} else {
return nil;
}
}
- (NSArray<NSString*>*)validAttributesForMarkedText {
return @[];
}
- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange {
// TODO: Implement.
// Note: This function can't easily be implemented under the system-message architecture.
return CGRectZero;
}
- (NSUInteger)characterIndexForPoint:(NSPoint)point {
// TODO: Implement.
// Note: This function can't easily be implemented under the system-message architecture.
return 0;
}
@end
@@ -0,0 +1,42 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "flutter/shell/platform/darwin/macos/framework/Headers/FLEView.h"
@implementation FLEView
#pragma mark -
#pragma mark FLEContextHandlingProtocol
- (void)makeCurrentContext {
[self.openGLContext makeCurrentContext];
}
- (void)onPresent {
[self.openGLContext flushBuffer];
}
#pragma mark -
#pragma mark Implementation
/**
* Declares that the view uses a flipped coordinate system, consistent with Flutter conventions.
*/
- (BOOL)isFlipped {
return YES;
}
- (BOOL)isOpaque {
return YES;
}
- (void)reshape {
[_reshapeListener viewDidReshape:self];
}
- (BOOL)acceptsFirstResponder {
return YES;
}
@end
@@ -0,0 +1,474 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "flutter/shell/platform/darwin/macos/framework/Headers/FLEViewController.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FLEViewController_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h"
#import "flutter/shell/platform/darwin/macos/framework/Headers/FLEReshapeListener.h"
#import "flutter/shell/platform/darwin/macos/framework/Headers/FLEView.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FLETextInputPlugin.h"
#import "flutter/shell/platform/embedder/embedder.h"
static NSString* const kICUBundlePath = @"icudtl.dat";
static const int kDefaultWindowFramebuffer = 0;
// Android KeyEvent constants from https://developer.android.com/reference/android/view/KeyEvent
static const int kAndroidMetaStateShift = 1 << 0;
static const int kAndroidMetaStateAlt = 1 << 1;
static const int kAndroidMetaStateCtrl = 1 << 12;
static const int kAndroidMetaStateMeta = 1 << 16;
#pragma mark - Private interface declaration.
/**
* Private interface declaration for FLEViewController.
*/
@interface FLEViewController ()
/**
* A list of additional responders to keyboard events. Keybord events are forwarded to all of them.
*/
@property NSMutableOrderedSet<NSResponder*>* additionalKeyResponders;
/**
* Creates and registers plugins used by this view controller.
*/
- (void)addInternalPlugins;
/**
* Shared implementation of the regular and headless public APIs.
*/
- (BOOL)launchEngineInternalWithAssetsPath:(nonnull NSURL*)assets
headless:(BOOL)headless
commandLineArguments:(nullable NSArray<NSString*>*)arguments;
/**
* Creates a render config with callbacks based on whether the embedder is being run as a headless
* server.
*/
+ (FlutterRendererConfig)createRenderConfigHeadless:(BOOL)headless;
/**
* Creates the OpenGL context used as the resource context by the engine.
*/
- (void)createResourceContext;
/**
* Makes the OpenGL context used by the engine for rendering optimization the
* current context.
*/
- (void)makeResourceContextCurrent;
/**
* Responds to system messages sent to this controller from the Flutter engine.
*/
- (void)handlePlatformMessage:(const FlutterPlatformMessage*)message;
/**
* Converts |event| to a FlutterPointerEvent with the given phase, and sends it to the engine.
*/
- (void)dispatchMouseEvent:(nonnull NSEvent*)event phase:(FlutterPointerPhase)phase;
/**
* Converts |event| to a key event channel message, and sends it to the engine.
*/
- (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type;
@end
#pragma mark - Static methods provided to engine configuration
/**
* Makes the owned FlutterView the current context.
*/
static bool OnMakeCurrent(FLEViewController* controller) {
[controller.view makeCurrentContext];
return true;
}
/**
* Clears the current context.
*/
static bool OnClearCurrent(FLEViewController* controller) {
[NSOpenGLContext clearCurrentContext];
return true;
}
/**
* Flushes the GL context as part of the Flutter rendering pipeline.
*/
static bool OnPresent(FLEViewController* controller) {
[controller.view onPresent];
return true;
}
/**
* Returns the framebuffer object whose color attachment the engine should render into.
*/
static uint32_t OnFBO(FLEViewController* controller) {
return kDefaultWindowFramebuffer;
}
/**
* Handles the given platform message by dispatching to the controller.
*/
static void OnPlatformMessage(const FlutterPlatformMessage* message,
FLEViewController* controller) {
[controller handlePlatformMessage:message];
}
/**
* Makes the resource context the current context.
*/
static bool OnMakeResourceCurrent(FLEViewController* controller) {
[controller makeResourceContextCurrent];
return true;
}
#pragma mark Static methods provided for headless engine configuration
static bool HeadlessOnMakeCurrent(FLEViewController* controller) {
return false;
}
static bool HeadlessOnClearCurrent(FLEViewController* controller) {
return false;
}
static bool HeadlessOnPresent(FLEViewController* controller) {
return false;
}
static uint32_t HeadlessOnFBO(FLEViewController* controller) {
return kDefaultWindowFramebuffer;
}
static bool HeadlessOnMakeResourceCurrent(FLEViewController* controller) {
return false;
}
#pragma mark - FLEViewController implementation.
@implementation FLEViewController {
FlutterEngine _engine;
// The additional context provided to the Flutter engine for resource loading.
NSOpenGLContext* _resourceContext;
// A mapping of channel names to the registered handlers for those channels.
NSMutableDictionary<NSString*, FlutterBinaryMessageHandler>* _messageHandlers;
// The plugin used to handle text input. This is not an FLEPlugin, so must be owned separately.
FLETextInputPlugin* _textInputPlugin;
// A message channel for passing key events to the Flutter engine. This should be replaced with
// an embedding API; see Issue #47.
FlutterBasicMessageChannel* _keyEventChannel;
}
@dynamic view;
/**
* Performs initialization that's common between the different init paths.
*/
static void CommonInit(FLEViewController* controller) {
controller->_messageHandlers = [[NSMutableDictionary alloc] init];
controller->_additionalKeyResponders = [[NSMutableOrderedSet alloc] init];
}
- (instancetype)initWithCoder:(NSCoder*)coder {
self = [super initWithCoder:coder];
if (self != nil) {
CommonInit(self);
}
return self;
}
- (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self != nil) {
CommonInit(self);
}
return self;
}
- (void)dealloc {
if (FlutterEngineShutdown(_engine) == kSuccess) {
_engine = NULL;
}
}
- (void)loadView {
self.view = [[FLEView alloc] init];
}
#pragma mark - Public methods
- (BOOL)launchEngineWithAssetsPath:(NSURL*)assets
commandLineArguments:(NSArray<NSString*>*)arguments {
return [self launchEngineInternalWithAssetsPath:assets
headless:NO
commandLineArguments:arguments];
}
- (BOOL)launchHeadlessEngineWithAssetsPath:(NSURL*)assets
commandLineArguments:(NSArray<NSString*>*)arguments {
return [self launchEngineInternalWithAssetsPath:assets
headless:YES
commandLineArguments:arguments];
}
- (id<FLEPluginRegistrar>)registrarForPlugin:(NSString*)pluginName {
// Currently, the view controller acts as the registrar for all plugins, so the
// name is ignored. It is part of the API to reduce churn in the future when
// aligning more closely with the Flutter registrar system.
return self;
}
#pragma mark - Framework-internal methods
- (void)addKeyResponder:(NSResponder*)responder {
[self.additionalKeyResponders addObject:responder];
}
- (void)removeKeyResponder:(NSResponder*)responder {
[self.additionalKeyResponders removeObject:responder];
}
#pragma mark - Private methods
- (void)addInternalPlugins {
_textInputPlugin = [[FLETextInputPlugin alloc] initWithViewController:self];
_keyEventChannel =
[FlutterBasicMessageChannel messageChannelWithName:@"flutter/keyevent"
binaryMessenger:self
codec:[FlutterJSONMessageCodec sharedInstance]];
}
- (BOOL)launchEngineInternalWithAssetsPath:(NSURL*)assets
headless:(BOOL)headless
commandLineArguments:(NSArray<NSString*>*)arguments {
if (_engine != NULL) {
return NO;
}
// Set up the resource context. This is done here rather than in viewDidLoad as there's no
// guarantee that viewDidLoad will be called before the engine is started, and the context must
// be valid by that point.
[self createResourceContext];
const FlutterRendererConfig config = [FLEViewController createRenderConfigHeadless:headless];
// Register internal plugins before starting the engine.
[self addInternalPlugins];
// FlutterProjectArgs is expecting a full argv, so when processing it for flags the first
// item is treated as the executable and ignored. Add a dummy value so that all provided arguments
// are used.
const unsigned long argc = arguments.count + 1;
const char** argv = (const char**)malloc(argc * sizeof(const char*));
argv[0] = "placeholder";
for (NSUInteger i = 0; i < arguments.count; ++i) {
argv[i + 1] = [arguments[i] UTF8String];
}
NSString* icuData = [[NSBundle bundleForClass:[self class]] pathForResource:kICUBundlePath
ofType:nil];
FlutterProjectArgs flutterArguments = {};
flutterArguments.struct_size = sizeof(FlutterProjectArgs);
flutterArguments.assets_path = assets.fileSystemRepresentation;
flutterArguments.icu_data_path = icuData.UTF8String;
flutterArguments.command_line_argc = (int)(argc);
flutterArguments.command_line_argv = argv;
flutterArguments.platform_message_callback = (FlutterPlatformMessageCallback)OnPlatformMessage;
FlutterEngineResult result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &flutterArguments,
(__bridge void*)(self), &_engine);
free(argv);
if (result != kSuccess) {
NSLog(@"Failed to start Flutter engine: error %d", result);
return NO;
}
return YES;
}
+ (FlutterRendererConfig)createRenderConfigHeadless:(BOOL)headless {
if (headless) {
const FlutterRendererConfig config = {
.type = kOpenGL,
.open_gl.struct_size = sizeof(FlutterOpenGLRendererConfig),
.open_gl.make_current = (BoolCallback)HeadlessOnMakeCurrent,
.open_gl.clear_current = (BoolCallback)HeadlessOnClearCurrent,
.open_gl.present = (BoolCallback)HeadlessOnPresent,
.open_gl.fbo_callback = (UIntCallback)HeadlessOnFBO,
.open_gl.make_resource_current = (BoolCallback)HeadlessOnMakeResourceCurrent};
return config;
} else {
const FlutterRendererConfig config = {
.type = kOpenGL,
.open_gl.struct_size = sizeof(FlutterOpenGLRendererConfig),
.open_gl.make_current = (BoolCallback)OnMakeCurrent,
.open_gl.clear_current = (BoolCallback)OnClearCurrent,
.open_gl.present = (BoolCallback)OnPresent,
.open_gl.fbo_callback = (UIntCallback)OnFBO,
.open_gl.make_resource_current = (BoolCallback)OnMakeResourceCurrent};
return config;
}
}
- (void)createResourceContext {
NSOpenGLContext* viewContext = ((NSOpenGLView*)self.view).openGLContext;
_resourceContext = [[NSOpenGLContext alloc] initWithFormat:viewContext.pixelFormat
shareContext:viewContext];
}
- (void)makeResourceContextCurrent {
[_resourceContext makeCurrentContext];
}
- (void)handlePlatformMessage:(const FlutterPlatformMessage*)message {
NSData* messageData = [NSData dataWithBytesNoCopy:(void*)message->message
length:message->message_size
freeWhenDone:NO];
NSString* channel = @(message->channel);
__block const FlutterPlatformMessageResponseHandle* responseHandle = message->response_handle;
FlutterBinaryReply binaryResponseHandler = ^(NSData* response) {
if (responseHandle) {
FlutterEngineSendPlatformMessageResponse(self->_engine, responseHandle,
static_cast<const uint8_t*>(response.bytes),
response.length);
responseHandle = NULL;
} else {
NSLog(@"Error: Message responses can be sent only once. Ignoring duplicate response "
"on channel '%@'.",
channel);
}
};
FlutterBinaryMessageHandler channelHandler = _messageHandlers[channel];
if (channelHandler) {
channelHandler(messageData, binaryResponseHandler);
} else {
binaryResponseHandler(nil);
}
}
- (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase {
NSPoint locationInView = [self.view convertPoint:event.locationInWindow fromView:nil];
NSPoint locationInBackingCoordinates = [self.view convertPointToBacking:locationInView];
const FlutterPointerEvent flutterEvent = {
.struct_size = sizeof(flutterEvent),
.phase = phase,
.x = locationInBackingCoordinates.x,
.y = -locationInBackingCoordinates.y, // convertPointToBacking makes this negative.
.timestamp = static_cast<size_t>(event.timestamp * NSEC_PER_MSEC),
};
FlutterEngineSendPointerEvent(_engine, &flutterEvent, 1);
}
- (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type {
[_keyEventChannel sendMessage:@{
@"keymap" : @"android",
@"type" : type,
@"keyCode" : @(event.keyCode),
@"metaState" :
@(((event.modifierFlags & NSEventModifierFlagShift) ? kAndroidMetaStateShift : 0) |
((event.modifierFlags & NSEventModifierFlagOption) ? kAndroidMetaStateAlt : 0) |
((event.modifierFlags & NSEventModifierFlagControl) ? kAndroidMetaStateCtrl : 0) |
((event.modifierFlags & NSEventModifierFlagCommand) ? kAndroidMetaStateMeta : 0))
}];
}
#pragma mark - FLEReshapeListener
/**
* Responds to view reshape by notifying the engine of the change in dimensions.
*/
- (void)viewDidReshape:(NSOpenGLView*)view {
CGRect scaledBounds = [view convertRectToBacking:view.bounds];
const FlutterWindowMetricsEvent event = {
.struct_size = sizeof(event),
.width = static_cast<size_t>(scaledBounds.size.width),
.height = static_cast<size_t>(scaledBounds.size.height),
.pixel_ratio = scaledBounds.size.width / view.bounds.size.width,
};
FlutterEngineSendWindowMetricsEvent(_engine, &event);
}
#pragma mark - FlutterBinaryMessenger
- (void)sendOnChannel:(nonnull NSString*)channel message:(nullable NSData*)message {
FlutterPlatformMessage platformMessage = {
.struct_size = sizeof(FlutterPlatformMessage),
.channel = [channel UTF8String],
.message = static_cast<const uint8_t*>(message.bytes),
.message_size = message.length,
};
FlutterEngineResult result = FlutterEngineSendPlatformMessage(_engine, &platformMessage);
if (result != kSuccess) {
NSLog(@"Failed to send message to Flutter engine on channel '%@' (%d).", channel, result);
}
}
- (void)setMessageHandlerOnChannel:(nonnull NSString*)channel
binaryMessageHandler:(nullable FlutterBinaryMessageHandler)handler {
_messageHandlers[channel] = [handler copy];
}
#pragma mark - FLEPluginRegistrar
- (id<FlutterBinaryMessenger>)messenger {
return self;
}
- (void)addMethodCallDelegate:(nonnull id<FLEPlugin>)delegate
channel:(nonnull FlutterMethodChannel*)channel {
[channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
[delegate handleMethodCall:call result:result];
}];
}
#pragma mark - NSResponder
- (BOOL)acceptsFirstResponder {
return YES;
}
- (void)keyDown:(NSEvent*)event {
[self dispatchKeyEvent:event ofType:@"keydown"];
for (NSResponder* responder in self.additionalKeyResponders) {
if ([responder respondsToSelector:@selector(keyDown:)]) {
[responder keyDown:event];
}
}
}
- (void)keyUp:(NSEvent*)event {
[self dispatchKeyEvent:event ofType:@"keyup"];
for (NSResponder* responder in self.additionalKeyResponders) {
if ([responder respondsToSelector:@selector(keyUp:)]) {
[responder keyUp:event];
}
}
}
- (void)mouseDown:(NSEvent*)event {
[self dispatchMouseEvent:event phase:kDown];
}
- (void)mouseUp:(NSEvent*)event {
[self dispatchMouseEvent:event phase:kUp];
}
- (void)mouseDragged:(NSEvent*)event {
[self dispatchMouseEvent:event phase:kMove];
}
@end

Some files were not shown because too many files have changed in this diff Show More