5#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h"
6#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
8#include <Carbon/Carbon.h>
9#import <objc/message.h>
11#include "flutter/common/constants.h"
12#include "flutter/shell/platform/embedder/embedder.h"
14#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
15#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
16#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
17#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
18#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h"
19#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h"
20#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h"
21#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputSemanticsObject.h"
22#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
24#pragma mark - Static types and data.
38static constexpr double kTrackpadTouchInertiaCancelWindowMs = 0.050;
71 bool flutter_state_is_added =
false;
76 bool flutter_state_is_down =
false;
85 bool has_pending_exit =
false;
90 bool flutter_state_is_pan_zoom_started =
false;
95 NSEventPhase pan_gesture_phase = NSEventPhaseNone;
100 NSEventPhase scale_gesture_phase = NSEventPhaseNone;
105 NSEventPhase rotate_gesture_phase = NSEventPhaseNone;
110 NSTimeInterval last_scroll_momentum_changed_time = 0;
115 void GestureReset() {
120 flutter_state_is_pan_zoom_started =
false;
121 pan_gesture_phase = NSEventPhaseNone;
122 scale_gesture_phase = NSEventPhaseNone;
123 rotate_gesture_phase = NSEventPhaseNone;
130 flutter_state_is_added =
false;
131 flutter_state_is_down =
false;
132 has_pending_exit =
false;
139#pragma mark - Private interface declaration.
156- (void)setBackgroundColor:(NSColor*)color;
212- (void)dispatchMouseEvent:(nonnull NSEvent*)event;
217- (void)dispatchGestureEvent:(nonnull NSEvent*)event;
234#pragma mark - FlutterViewWrapper implementation.
243 CFDictionaryRef userInfo) {
245 if (controller != nil) {
255- (instancetype)initWithFlutterView:(
FlutterView*)view
257 self = [
super initWithFrame:NSZeroRect];
261 view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
262 [
self addSubview:view];
267- (void)setBackgroundColor:(NSColor*)color {
268 [_flutterView setBackgroundColor:color];
271- (
BOOL)performKeyEquivalent:(NSEvent*)event {
279 return [
super performKeyEquivalent:event];
281 [_flutterView keyDown:event];
285- (NSArray*)accessibilityChildren {
286 return @[ _flutterView ];
289- (void)mouseDown:(NSEvent*)event {
300 [
self.nextResponder mouseDown:event];
303- (void)mouseUp:(NSEvent*)event {
314 [
self.nextResponder mouseUp:event];
319#pragma mark - FlutterViewController implementation.
325 std::shared_ptr<flutter::AccessibilityBridgeMac>
_bridge;
332@synthesize viewIdentifier = _viewIdentifier;
333@dynamic accessibilityBridge;
341 project:controller->_project
342 allowHeadlessExecution:NO];
344 NSCAssert(controller.
engine == nil,
345 @"The FlutterViewController is unexpectedly attached to "
346 @"engine %@ before initialization.",
349 NSCAssert(controller.
engine != nil,
350 @"The FlutterViewController unexpectedly stays unattached after initialization. "
351 @"In unit tests, this is likely because either the FlutterViewController or "
352 @"the FlutterEngine is mocked. Please subclass these classes instead.",
354 controller->_mouseTrackingMode = kFlutterMouseTrackingModeInKeyWindow;
359 CFNotificationCenterRef cfCenter = CFNotificationCenterGetDistributedCenter();
362 kTISNotifySelectedKeyboardInputSourceChanged, NULL,
363 CFNotificationSuspensionBehaviorDeliverImmediately);
367 self = [
super initWithCoder:coder];
368 NSAssert(
self,
@"Super init cannot be nil");
370 CommonInit(
self, nil);
374- (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
375 self = [
super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
376 NSAssert(
self,
@"Super init cannot be nil");
378 CommonInit(
self, nil);
383 self = [
super initWithNibName:nil bundle:nil];
384 NSAssert(
self,
@"Super init cannot be nil");
387 CommonInit(
self, nil);
392 nibName:(nullable NSString*)nibName
393 bundle:(nullable NSBundle*)nibBundle {
394 NSAssert(
engine != nil,
@"Engine is required");
396 self = [
super initWithNibName:nibName bundle:nibBundle];
404- (
BOOL)isDispatchingKeyEvent:(NSEvent*)event {
405 return [_keyboardManager isDispatchingKeyEvent:event];
411 id<MTLCommandQueue> commandQueue =
_engine.renderer.commandQueue;
412 if (!
device || !commandQueue) {
413 NSLog(
@"Unable to create FlutterView; no MTLDevice or MTLCommandQueue available.");
416 flutterView = [
self createFlutterViewWithMTLDevice:device commandQueue:commandQueue];
417 if (_backgroundColor != nil) {
422 self.view = wrapperView;
427 [
self configureTrackingArea];
428 [
self.view setAllowedTouchTypes:NSTouchTypeMaskIndirect];
429 [
self.view setWantsRestingTouches:YES];
430 [_engine viewControllerViewDidLoad:self];
433- (void)viewWillAppear {
434 [
super viewWillAppear];
438 [
self listenForMetaModifiedKeyUpEvents];
441- (void)viewWillDisappear {
444 [NSEvent removeMonitor:_keyUpMonitor];
449 if ([
self attached]) {
450 [_engine removeViewController:self];
452 CFNotificationCenterRef cfCenter = CFNotificationCenterGetDistributedCenter();
453 CFNotificationCenterRemoveEveryObserver(cfCenter, (__bridge
void*)
self);
456#pragma mark - Public methods
458- (void)setMouseTrackingMode:(FlutterMouseTrackingMode)mode {
459 if (_mouseTrackingMode ==
mode) {
462 _mouseTrackingMode =
mode;
463 [
self configureTrackingArea];
466- (void)setBackgroundColor:(NSColor*)color {
467 _backgroundColor =
color;
468 [_flutterView setBackgroundColor:_backgroundColor];
472 NSAssert([
self attached],
@"This view controller is not attached.");
473 return _viewIdentifier;
476- (void)onPreEngineRestart {
477 [
self initializeKeyboard];
480- (void)notifySemanticsEnabledChanged {
482 BOOL newSemanticsEnabled =
_engine.semanticsEnabled;
483 if (newSemanticsEnabled == mySemanticsEnabled) {
486 if (newSemanticsEnabled) {
487 _bridge = [
self createAccessibilityBridgeWithEngine:_engine];
493 NSAssert(newSemanticsEnabled == !!
_bridge,
@"Failed to update semantics for the view.");
496- (
std::weak_ptr<flutter::AccessibilityBridgeMac>)accessibilityBridge {
503 NSAssert(
_engine == nil,
@"Already attached to an engine %@.",
_engine);
505 _viewIdentifier = viewIdentifier;
507 [_threadSynchronizer registerView:_viewIdentifier];
510- (void)detachFromEngine {
511 NSAssert(
_engine != nil,
@"Not attached to any engine.");
512 [_threadSynchronizer deregisterView:_viewIdentifier];
522 NSAssert(
_engine.semanticsEnabled,
@"Semantics must be enabled.");
523 if (!
_engine.semanticsEnabled) {
526 for (
size_t i = 0;
i <
update->node_count;
i++) {
528 _bridge->AddFlutterSemanticsNodeUpdate(*node);
531 for (
size_t i = 0;
i <
update->custom_action_count;
i++) {
539 if (!
self.viewLoaded) {
543 auto root =
_bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
545 if ([
self.flutterView.accessibilityChildren
count] == 0) {
546 NSAccessibilityElement* native_root = root->GetNativeViewAccessible();
547 self.flutterView.accessibilityChildren = @[ native_root ];
550 self.flutterView.accessibilityChildren = nil;
554#pragma mark - Private methods
556- (
BOOL)launchEngine {
557 if (![
_engine runWithEntrypoint:nil]) {
567- (void)listenForMetaModifiedKeyUpEvents {
568 if (_keyUpMonitor != nil) {
574 _keyUpMonitor = [NSEvent
575 addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp
576 handler:^NSEvent*(NSEvent* event) {
579 NSResponder* firstResponder = [[event window] firstResponder];
580 if (weakSelf.viewLoaded && weakSelf.flutterView &&
581 (firstResponder == weakSelf.flutterView ||
582 firstResponder == weakSelf.textInputPlugin) &&
583 ([event modifierFlags] & NSEventModifierFlagCommand) &&
584 ([event type] == NSEventTypeKeyUp)) {
585 [weakSelf keyUp:event];
591- (void)configureTrackingArea {
592 if (!
self.viewLoaded) {
597 if (_mouseTrackingMode != kFlutterMouseTrackingModeNone &&
self.flutterView) {
598 NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |
599 NSTrackingInVisibleRect | NSTrackingEnabledDuringMouseDrag;
600 switch (_mouseTrackingMode) {
601 case kFlutterMouseTrackingModeInKeyWindow:
602 options |= NSTrackingActiveInKeyWindow;
604 case kFlutterMouseTrackingModeInActiveApp:
605 options |= NSTrackingActiveInActiveApp;
607 case kFlutterMouseTrackingModeAlways:
608 options |= NSTrackingActiveAlways;
611 NSLog(
@"Error: Unrecognized mouse tracking mode: %ld", _mouseTrackingMode);
614 _trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect
618 [
self.flutterView addTrackingArea:_trackingArea];
619 }
else if (_trackingArea) {
620 [
self.flutterView removeTrackingArea:_trackingArea];
625- (void)initializeKeyboard {
631- (void)dispatchMouseEvent:(nonnull NSEvent*)event {
635 [
self dispatchMouseEvent:
event phase:phase];
638- (void)dispatchGestureEvent:(nonnull NSEvent*)event {
639 if (
event.phase == NSEventPhaseBegan ||
event.phase == NSEventPhaseMayBegin) {
640 [self dispatchMouseEvent:event phase:kPanZoomStart];
641 }
else if (
event.phase == NSEventPhaseChanged) {
642 [self dispatchMouseEvent:event phase:kPanZoomUpdate];
643 }
else if (
event.phase == NSEventPhaseEnded ||
event.phase == NSEventPhaseCancelled) {
644 [self dispatchMouseEvent:event phase:kPanZoomEnd];
645 }
else if (
event.phase == NSEventPhaseNone &&
event.momentumPhase == NSEventPhaseNone) {
646 [self dispatchMouseEvent:event phase:kHover];
651 if (event.momentumPhase == NSEventPhaseChanged) {
652 _mouseState.last_scroll_momentum_changed_time = event.timestamp;
655 NSAssert(
event.momentumPhase != NSEventPhaseNone,
656 @"Received gesture event with unexpected phase");
661 NSAssert(
self.viewLoaded,
@"View must be loaded before it handles the mouse event");
672 if (
event.type == NSEventTypeScrollWheel) {
674 }
else if (
event.type == NSEventTypeMagnify) {
676 }
else if (
event.type == NSEventTypeRotate) {
681 if (
event.type == NSEventTypeScrollWheel) {
685 if (
_mouseState.flutter_state_is_pan_zoom_started) {
689 _mouseState.flutter_state_is_pan_zoom_started =
true;
692 if (!
_mouseState.flutter_state_is_pan_zoom_started) {
696 NSAssert(
event.phase == NSEventPhaseCancelled,
697 @"Received gesture event with unexpected phase");
701 NSEventPhase all_gestures_fields =
_mouseState.pan_gesture_phase |
704 NSEventPhase active_mask = NSEventPhaseBegan | NSEventPhaseChanged;
705 if ((all_gestures_fields & active_mask) != 0) {
715 NSEvent* addEvent = [NSEvent enterExitEventWithType:NSEventTypeMouseEntered
716 location:event.locationInWindow
718 timestamp:event.timestamp
719 windowNumber:event.windowNumber
724 [
self dispatchMouseEvent:addEvent phase:kAdd];
727 NSPoint locationInView = [
self.flutterView convertPoint:event.locationInWindow fromView:nil];
728 NSPoint locationInBackingCoordinates = [
self.flutterView convertPointToBacking:locationInView];
738 .timestamp =
static_cast<size_t>(
event.timestamp * USEC_PER_SEC),
739 .x = locationInBackingCoordinates.x,
740 .y = -locationInBackingCoordinates.
y,
742 .device_kind = deviceKind,
744 .buttons = phase ==
kAdd ? 0 : _mouseState.buttons,
745 .view_id = static_cast<FlutterViewIdentifier>(_viewIdentifier),
749 if (
event.type == NSEventTypeScrollWheel) {
752 }
else if (
event.type == NSEventTypeMagnify) {
754 }
else if (
event.type == NSEventTypeRotate) {
767 double pixelsPerLine = 1.0;
768 if (!
event.hasPreciseScrollingDeltas) {
774 pixelsPerLine = 40.0;
776 double scaleFactor =
self.flutterView.layer.contentsScale;
785 double scaledDeltaX = -
event.scrollingDeltaX * pixelsPerLine * scaleFactor;
786 double scaledDeltaY = -
event.scrollingDeltaY * pixelsPerLine * scaleFactor;
787 if (
event.modifierFlags & NSShiftKeyMask) {
796 [_keyboardManager syncModifiersIfNeeded:event.modifierFlags timestamp:event.timestamp];
797 [_engine sendPointerEvent:flutterEvent];
800 if (phase ==
kDown) {
802 }
else if (phase ==
kUp) {
805 [
self dispatchMouseEvent:event phase:kRemove];
808 }
else if (phase ==
kAdd) {
815- (void)onAccessibilityStatusChanged:(
BOOL)enabled {
821 [
self.view addSubview:_textInputPlugin];
825- (
std::shared_ptr<flutter::AccessibilityBridgeMac>)createAccessibilityBridgeWithEngine:
827 return std::make_shared<flutter::AccessibilityBridgeMac>(
engine,
self);
830- (nonnull
FlutterView*)createFlutterViewWithMTLDevice:(
id<MTLDevice>)device
831 commandQueue:(
id<MTLCommandQueue>)commandQueue {
832 return [[
FlutterView alloc] initWithMTLDevice:device
833 commandQueue:commandQueue
835 threadSynchronizer:_threadSynchronizer
836 viewIdentifier:_viewIdentifier];
839- (void)onKeyboardLayoutChanged {
840 _keyboardLayoutData = nil;
846- (NSString*)lookupKeyForAsset:(NSString*)asset {
850- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
854#pragma mark - FlutterViewDelegate
859- (void)viewDidReshape:(NSView*)view {
861 [_engine updateWindowMetricsForViewController:self];
864- (
BOOL)viewShouldAcceptFirstResponder:(NSView*)view {
872#pragma mark - FlutterPluginRegistry
875 return [_engine registrarForPlugin:pluginName];
878- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
879 return [_engine valuePublishedByPlugin:pluginKey];
882#pragma mark - FlutterKeyboardViewDelegate
891static NSData* CurrentKeyboardLayoutData() {
892 TISInputSourceRef
source = TISCopyCurrentKeyboardInputSource();
893 CFTypeRef layout_data = TISGetInputSourceProperty(
source, kTISPropertyUnicodeKeyLayoutData);
894 if (layout_data == nil) {
899 source = TISCopyCurrentKeyboardLayoutInputSource();
900 layout_data = TISGetInputSourceProperty(
source, kTISPropertyUnicodeKeyLayoutData);
902 return (__bridge_transfer NSData*)CFRetain(layout_data);
907 userData:(nullable
void*)userData {
908 [_engine sendKeyEvent:event callback:callback userData:userData];
912 return _engine.binaryMessenger;
915- (
BOOL)onTextInputKeyEvent:(nonnull NSEvent*)event {
916 return [_textInputPlugin handleKeyEvent:event];
923- (LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(
BOOL)shift {
924 if (_keyboardLayoutData == nil) {
925 _keyboardLayoutData = CurrentKeyboardLayoutData();
927 const UCKeyboardLayout* layout =
reinterpret_cast<const UCKeyboardLayout*
>(
928 CFDataGetBytePtr((__bridge CFDataRef)_keyboardLayoutData));
930 UInt32 deadKeyState = 0;
931 UniCharCount stringLength = 0;
934 UInt32 modifierState = ((shift ? shiftKey : 0) >> 8) & 0xFF;
937 bool isDeadKey =
false;
939 UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierState,
keyboardType,
940 kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &stringLength, &resultChar);
942 if (status == noErr && stringLength == 0 && deadKeyState != 0) {
945 UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierState,
keyboardType,
946 kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &stringLength, &resultChar);
949 if (status == noErr && stringLength == 1 && !std::iscntrl(resultChar)) {
950 return LayoutClue{resultChar, isDeadKey};
952 return LayoutClue{0,
false};
955- (nonnull NSDictionary*)getPressedState {
956 return [_keyboardManager getPressedState];
959#pragma mark - NSResponder
961- (
BOOL)acceptsFirstResponder {
965- (void)keyDown:(NSEvent*)event {
966 [_keyboardManager handleEvent:event];
969- (void)keyUp:(NSEvent*)event {
970 [_keyboardManager handleEvent:event];
973- (void)flagsChanged:(NSEvent*)event {
974 [_keyboardManager handleEvent:event];
977- (void)mouseEntered:(NSEvent*)event {
981 [
self dispatchMouseEvent:event phase:kAdd];
985- (void)mouseExited:(NSEvent*)event {
990 [
self dispatchMouseEvent:event phase:kRemove];
993- (void)mouseDown:(NSEvent*)event {
995 [
self dispatchMouseEvent:event];
998- (void)mouseUp:(NSEvent*)event {
1000 [
self dispatchMouseEvent:event];
1003- (void)mouseDragged:(NSEvent*)event {
1004 [
self dispatchMouseEvent:event];
1007- (void)rightMouseDown:(NSEvent*)event {
1009 [
self dispatchMouseEvent:event];
1012- (void)rightMouseUp:(NSEvent*)event {
1014 [
self dispatchMouseEvent:event];
1017- (void)rightMouseDragged:(NSEvent*)event {
1018 [
self dispatchMouseEvent:event];
1021- (void)otherMouseDown:(NSEvent*)event {
1023 [
self dispatchMouseEvent:event];
1026- (void)otherMouseUp:(NSEvent*)event {
1028 [
self dispatchMouseEvent:event];
1031- (void)otherMouseDragged:(NSEvent*)event {
1032 [
self dispatchMouseEvent:event];
1035- (void)mouseMoved:(NSEvent*)event {
1036 [
self dispatchMouseEvent:event];
1039- (void)scrollWheel:(NSEvent*)event {
1040 [
self dispatchGestureEvent:event];
1043- (void)magnifyWithEvent:(NSEvent*)event {
1044 [
self dispatchGestureEvent:event];
1047- (void)rotateWithEvent:(NSEvent*)event {
1048 [
self dispatchGestureEvent:event];
1051- (void)swipeWithEvent:(NSEvent*)event {
1055- (void)touchesBeganWithEvent:(NSEvent*)event {
1056 NSTouch* touch =
event.allTouches.anyObject;
1059 kTrackpadTouchInertiaCancelWindowMs) {
1062 NSPoint locationInView = [
self.flutterView convertPoint:event.locationInWindow fromView:nil];
1063 NSPoint locationInBackingCoordinates =
1064 [
self.flutterView convertPointToBacking:locationInView];
1067 .timestamp =
static_cast<size_t>(
event.timestamp * USEC_PER_SEC),
1068 .x = locationInBackingCoordinates.x,
1069 .y = -locationInBackingCoordinates.
y,
1076 [_engine sendPointerEvent:flutterEvent];
1078 _mouseState.last_scroll_momentum_changed_time = 0;
static SkScalar center(float pos0, float pos1)
FlutterPointerPhase
The phase of the pointer event.
@ kPanZoomUpdate
The pan/zoom updated.
@ kHover
The pointer moved while up.
@ kPanZoomStart
A pan/zoom started on this pointer.
@ kPanZoomEnd
The pan/zoom ended.
@ kFlutterPointerButtonMousePrimary
@ kFlutterPointerButtonMouseSecondary
@ kFlutterPointerSignalKindScrollInertiaCancel
@ kFlutterPointerSignalKindScroll
void(* FlutterKeyEventCallback)(bool, void *)
FlutterPointerDeviceKind
The device type that created a pointer event.
@ kFlutterPointerDeviceKindTrackpad
@ kFlutterPointerDeviceKindMouse
static constexpr int32_t kPointerPanZoomDeviceId
static constexpr int32_t kMousePointerDeviceId
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
std::function< void()> KeyboardLayoutNotifier
#define FML_DCHECK(condition)
NSString * lookupKeyForAsset:fromPackage:(NSString *asset,[fromPackage] NSString *package)
NSString * lookupKeyForAsset:(NSString *asset)
void addViewController:(FlutterViewController *controller)
KeyboardLayoutNotifier keyboardLayoutNotifier
void notifySemanticsEnabledChanged()
void onKeyboardLayoutChanged()
FlutterKeyboardManager * keyboardManager
void initializeKeyboard()
NSTrackingArea * trackingArea
FlutterViewIdentifier viewIdentifier
void configureTrackingArea()
NSData * keyboardLayoutData
void setBackgroundColor:(nonnull NSColor *color)
fml::scoped_nsobject< FlutterTextInputPlugin > _textInputPlugin
UIKeyboardType keyboardType
instancetype initWithCoder
fml::scoped_nsobject< FlutterView > _flutterView
fml::scoped_nsobject< FlutterEngine > _engine
FlutterDartProject * _project
flutter::KeyboardLayoutNotifier _keyboardLayoutNotifier
FlutterThreadSynchronizer * _threadSynchronizer
__weak FlutterViewController * _controller
std::shared_ptr< flutter::AccessibilityBridgeMac > _bridge
static void OnKeyboardLayoutChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
void(^ KeyboardLayoutNotifier)()
DEF_SWITCHES_START aot vmservice shared library name
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive mode
double scroll_delta_y
The y offset of the scroll in physical pixels.
size_t struct_size
The size of this struct. Must be sizeof(FlutterPointerEvent).
double scale
The scale of the pan/zoom, where 1.0 is the initial scale.
FlutterPointerSignalKind signal_kind
double rotation
The rotation of the pan/zoom in radians, where 0.0 is the initial angle.
double scroll_delta_x
The x offset of the scroll in physical pixels.
double pan_x
The x offset of the pan/zoom in physical pixels.
double pan_y
The y offset of the pan/zoom in physical pixels.
A batch of updates to semantics nodes and custom actions.