Flutter Engine
FlutterViewController.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h"
6 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
7 
8 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
9 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
10 #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
11 #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
12 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h"
13 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h"
14 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
15 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h"
16 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h"
17 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalRenderer.h"
18 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h"
19 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterOpenGLRenderer.h"
20 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderingBackend.h"
21 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputSemanticsObject.h"
22 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
23 #import "flutter/shell/platform/embedder/embedder.h"
24 
25 namespace {
26 
27 /// Clipboard plain text format.
28 constexpr char kTextPlainFormat[] = "text/plain";
29 
30 /// The private notification for voice over.
31 static NSString* const EnhancedUserInterfaceNotification =
32  @"NSApplicationDidChangeAccessibilityEnhancedUserInterfaceNotification";
33 static NSString* const EnhancedUserInterfaceKey = @"AXEnhancedUserInterface";
34 
35 /**
36  * State tracking for mouse events, to adapt between the events coming from the system and the
37  * events that the embedding API expects.
38  */
39 struct MouseState {
40  /**
41  * Whether or not a kAdd event has been sent (or sent again since the last kRemove if tracking is
42  * enabled). Used to determine whether to send a kAdd event before sending an incoming mouse
43  * event, since Flutter expects pointers to be added before events are sent for them.
44  */
45  bool flutter_state_is_added = false;
46 
47  /**
48  * Whether or not a kDown has been sent since the last kAdd/kUp.
49  */
50  bool flutter_state_is_down = false;
51 
52  /**
53  * Whether or not mouseExited: was received while a button was down. Cocoa's behavior when
54  * dragging out of a tracked area is to send an exit, then keep sending drag events until the last
55  * button is released. Flutter doesn't expect to receive events after a kRemove, so the kRemove
56  * for the exit needs to be delayed until after the last mouse button is released. If cursor
57  * returns back to the window while still dragging, the flag is cleared in mouseEntered:.
58  */
59  bool has_pending_exit = false;
60 
61  /**
62  * The currently pressed buttons, as represented in FlutterPointerEvent.
63  */
64  int64_t buttons = 0;
65 
66  /**
67  * Resets all state to default values.
68  */
69  void Reset() {
70  flutter_state_is_added = false;
71  flutter_state_is_down = false;
72  has_pending_exit = false;
73  buttons = 0;
74  }
75 };
76 
77 } // namespace
78 
79 #pragma mark - Private interface declaration.
80 
81 /**
82  * FlutterViewWrapper is a convenience class that wraps a FlutterView and provides
83  * a mechanism to attach AppKit views such as FlutterTextField without affecting
84  * the accessibility subtree of the wrapped FlutterView itself.
85  *
86  * The FlutterViewController uses this class to create its content view. When
87  * any of the accessibility services (e.g. VoiceOver) is turned on, the accessibility
88  * bridge creates FlutterTextFields that interact with the service. The bridge has to
89  * attach the FlutterTextField somewhere in the view hierarchy in order for the
90  * FlutterTextField to interact correctly with VoiceOver. Those FlutterTextFields
91  * will be attached to this view so that they won't affect the accessibility subtree
92  * of FlutterView.
93  */
94 @interface FlutterViewWrapper : NSView
95 
96 @end
97 
98 /**
99  * Private interface declaration for FlutterViewController.
100  */
101 @interface FlutterViewController () <FlutterViewReshapeListener>
102 
103 /**
104  * The tracking area used to generate hover events, if enabled.
105  */
106 @property(nonatomic) NSTrackingArea* trackingArea;
107 
108 /**
109  * The current state of the mouse and the sent mouse events.
110  */
111 @property(nonatomic) MouseState mouseState;
112 
113 /**
114  * Event monitor for keyUp events.
115  */
116 @property(nonatomic) id keyUpMonitor;
117 
118 /**
119  * Pointer to a keyboard manager, a hub that manages how key events are
120  * dispatched to various Flutter key responders, and whether the event is
121  * propagated to the next NSResponder.
122  */
123 @property(nonatomic) FlutterKeyboardManager* keyboardManager;
124 
125 /**
126  * Starts running |engine|, including any initial setup.
127  */
128 - (BOOL)launchEngine;
129 
130 /**
131  * Updates |trackingArea| for the current tracking settings, creating it with
132  * the correct mode if tracking is enabled, or removing it if not.
133  */
134 - (void)configureTrackingArea;
135 
136 /**
137  * Creates and registers plugins used by this view controller.
138  */
139 - (void)addInternalPlugins;
140 
141 /**
142  * Calls dispatchMouseEvent:phase: with a phase determined by self.mouseState.
143  *
144  * mouseState.buttons should be updated before calling this method.
145  */
146 - (void)dispatchMouseEvent:(nonnull NSEvent*)event;
147 
148 /**
149  * Converts |event| to a FlutterPointerEvent with the given phase, and sends it to the engine.
150  */
151 - (void)dispatchMouseEvent:(nonnull NSEvent*)event phase:(FlutterPointerPhase)phase;
152 
153 /**
154  * Initializes the KVO for user settings and passes the initial user settings to the engine.
155  */
156 - (void)sendInitialSettings;
157 
158 /**
159  * Responds to updates in the user settings and passes this data to the engine.
160  */
161 - (void)onSettingsChanged:(NSNotification*)notification;
162 
163 /**
164  * Responds to updates in accessibility.
165  */
166 - (void)onAccessibilityStatusChanged:(NSNotification*)notification;
167 
168 /**
169  * Handles messages received from the Flutter engine on the _*Channel channels.
170  */
171 - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
172 
173 /**
174  * Plays a system sound. |soundType| specifies which system sound to play. Valid
175  * values can be found in the SystemSoundType enum in the services SDK package.
176  */
177 - (void)playSystemSound:(NSString*)soundType;
178 
179 /**
180  * Reads the data from the clipboard. |format| specifies the media type of the
181  * data to obtain.
182  */
183 - (NSDictionary*)getClipboardData:(NSString*)format;
184 
185 /**
186  * Clears contents and writes new data into clipboard. |data| is a dictionary where
187  * the keys are the type of data, and tervalue the data to be stored.
188  */
189 - (void)setClipboardData:(NSDictionary*)data;
190 
191 /**
192  * Returns true iff the clipboard contains nonempty string data.
193  */
194 - (BOOL)clipboardHasStrings;
195 
196 @end
197 
198 #pragma mark - FlutterViewWrapper implementation.
199 
200 @implementation FlutterViewWrapper {
202 }
203 
204 - (instancetype)initWithFlutterView:(FlutterView*)view {
205  self = [super initWithFrame:NSZeroRect];
206  if (self) {
207  _flutterView = view;
208  view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
209  [self addSubview:view];
210  }
211  return self;
212 }
213 
214 - (NSArray*)accessibilityChildren {
215  return @[ _flutterView ];
216 }
217 
218 @end
219 
220 #pragma mark - FlutterViewController implementation.
221 
222 @implementation FlutterViewController {
223  // The project to run in this controller's engine.
225 
226  // A message channel for sending user settings to the flutter engine.
228 
229  // A method channel for miscellaneous platform functionality.
231 }
232 
233 @dynamic view;
234 
235 /**
236  * Performs initialization that's common between the different init paths.
237  */
238 static void CommonInit(FlutterViewController* controller) {
239  if (!controller->_engine) {
240  controller->_engine = [[FlutterEngine alloc] initWithName:@"io.flutter"
241  project:controller->_project
242  allowHeadlessExecution:NO];
243  }
244  controller->_mouseTrackingMode = FlutterMouseTrackingModeInKeyWindow;
245  controller->_textInputPlugin = [[FlutterTextInputPlugin alloc] initWithViewController:controller];
246  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
247  // macOS fires this private message when VoiceOver turns on or off.
248  [center addObserver:controller
249  selector:@selector(onAccessibilityStatusChanged:)
250  name:EnhancedUserInterfaceNotification
251  object:nil];
252 }
253 
254 - (instancetype)initWithCoder:(NSCoder*)coder {
255  self = [super initWithCoder:coder];
256  NSAssert(self, @"Super init cannot be nil");
257 
258  CommonInit(self);
259  return self;
260 }
261 
262 - (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
263  self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
264  NSAssert(self, @"Super init cannot be nil");
265 
266  CommonInit(self);
267  return self;
268 }
269 
270 - (instancetype)initWithProject:(nullable FlutterDartProject*)project {
271  self = [super initWithNibName:nil bundle:nil];
272  NSAssert(self, @"Super init cannot be nil");
273 
274  _project = project;
275  CommonInit(self);
276  return self;
277 }
278 
279 - (instancetype)initWithEngine:(nonnull FlutterEngine*)engine
280  nibName:(nullable NSString*)nibName
281  bundle:(nullable NSBundle*)nibBundle {
282  NSAssert(engine != nil, @"Engine is required");
283  self = [super initWithNibName:nibName bundle:nibBundle];
284  if (self) {
285  if (engine.viewController) {
286  NSLog(@"The supplied FlutterEngine %@ is already used with FlutterViewController "
287  "instance %@. One instance of the FlutterEngine can only be attached to one "
288  "FlutterViewController at a time. Set FlutterEngine.viewController "
289  "to nil before attaching it to another FlutterViewController.",
290  [engine description], [engine.viewController description]);
291  }
292  _engine = engine;
293  CommonInit(self);
294  [engine setViewController:self];
295  }
296 
297  return self;
298 }
299 
300 - (void)loadView {
301  FlutterView* flutterView;
302  if ([FlutterRenderingBackend renderUsingMetal]) {
303  FlutterMetalRenderer* metalRenderer = reinterpret_cast<FlutterMetalRenderer*>(_engine.renderer);
304  id<MTLDevice> device = metalRenderer.device;
305  id<MTLCommandQueue> commandQueue = metalRenderer.commandQueue;
306  if (!device || !commandQueue) {
307  NSLog(@"Unable to create FlutterView; no MTLDevice or MTLCommandQueue available.");
308  return;
309  }
310  flutterView = [[FlutterView alloc] initWithMTLDevice:device
311  commandQueue:commandQueue
312  reshapeListener:self];
313  } else {
314  FlutterOpenGLRenderer* openGLRenderer =
315  reinterpret_cast<FlutterOpenGLRenderer*>(_engine.renderer);
316  NSOpenGLContext* mainContext = openGLRenderer.openGLContext;
317  if (!mainContext) {
318  NSLog(@"Unable to create FlutterView; no GL context available.");
319  return;
320  }
321  flutterView = [[FlutterView alloc] initWithMainContext:mainContext reshapeListener:self];
322  }
323  FlutterViewWrapper* wrapperView = [[FlutterViewWrapper alloc] initWithFlutterView:flutterView];
324  self.view = wrapperView;
325  _flutterView = flutterView;
326 }
327 
328 - (void)viewDidLoad {
329  [self configureTrackingArea];
330 }
331 
332 - (void)viewWillAppear {
333  [super viewWillAppear];
334  if (!_engine.running) {
335  [self launchEngine];
336  }
337  [self listenForMetaModifiedKeyUpEvents];
338 }
339 
340 - (void)viewWillDisappear {
341  // Per Apple's documentation, it is discouraged to call removeMonitor: in dealloc, and it's
342  // recommended to be called earlier in the lifecycle.
343  [NSEvent removeMonitor:_keyUpMonitor];
344  _keyUpMonitor = nil;
345 }
346 
347 - (void)dealloc {
348  _engine.viewController = nil;
349 }
350 
351 #pragma mark - Public methods
352 
353 - (void)setMouseTrackingMode:(FlutterMouseTrackingMode)mode {
354  if (_mouseTrackingMode == mode) {
355  return;
356  }
357  _mouseTrackingMode = mode;
358  [self configureTrackingArea];
359 }
360 
361 #pragma mark - Private methods
362 
363 - (BOOL)launchEngine {
364  // Register internal plugins before starting the engine.
365  [self addInternalPlugins];
366 
367  _engine.viewController = self;
368  if (![_engine runWithEntrypoint:nil]) {
369  return NO;
370  }
371  // Send the initial user settings such as brightness and text scale factor
372  // to the engine.
373  // TODO(stuartmorgan): Move this logic to FlutterEngine.
374  [self sendInitialSettings];
375  return YES;
376 }
377 
378 // macOS does not call keyUp: on a key while the command key is pressed. This results in a loss
379 // of a key event once the modified key is released. This method registers the
380 // ViewController as a listener for a keyUp event before it's handled by NSApplication, and should
381 // NOT modify the event to avoid any unexpected behavior.
382 - (void)listenForMetaModifiedKeyUpEvents {
383  NSAssert(_keyUpMonitor == nil, @"_keyUpMonitor was already created");
384  FlutterViewController* __weak weakSelf = self;
385  _keyUpMonitor = [NSEvent
386  addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp
387  handler:^NSEvent*(NSEvent* event) {
388  // Intercept keyUp only for events triggered on the current
389  // view.
390  if (weakSelf.viewLoaded && weakSelf.flutterView &&
391  ([[event window] firstResponder] ==
392  weakSelf.flutterView) &&
393  ([event modifierFlags] & NSEventModifierFlagCommand) &&
394  ([event type] == NSEventTypeKeyUp))
395  [weakSelf keyUp:event];
396  return event;
397  }];
398 }
399 
400 - (void)configureTrackingArea {
401  if (!self.viewLoaded) {
402  // The viewDidLoad will call configureTrackingArea again when
403  // the view is actually loaded.
404  return;
405  }
406  if (_mouseTrackingMode != FlutterMouseTrackingModeNone && self.flutterView) {
407  NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |
408  NSTrackingInVisibleRect | NSTrackingEnabledDuringMouseDrag;
409  switch (_mouseTrackingMode) {
410  case FlutterMouseTrackingModeInKeyWindow:
411  options |= NSTrackingActiveInKeyWindow;
412  break;
413  case FlutterMouseTrackingModeInActiveApp:
414  options |= NSTrackingActiveInActiveApp;
415  break;
416  case FlutterMouseTrackingModeAlways:
417  options |= NSTrackingActiveAlways;
418  break;
419  default:
420  NSLog(@"Error: Unrecognized mouse tracking mode: %ld", _mouseTrackingMode);
421  return;
422  }
423  _trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect
424  options:options
425  owner:self
426  userInfo:nil];
427  [self.flutterView addTrackingArea:_trackingArea];
428  } else if (_trackingArea) {
429  [self.flutterView removeTrackingArea:_trackingArea];
430  _trackingArea = nil;
431  }
432 }
433 
434 - (void)addInternalPlugins {
435  __weak FlutterViewController* weakSelf = self;
436  [FlutterMouseCursorPlugin registerWithRegistrar:[self registrarForPlugin:@"mousecursor"]];
437  _keyboardManager = [[FlutterKeyboardManager alloc] initWithOwner:weakSelf];
438  [_keyboardManager addPrimaryResponder:[[FlutterEmbedderKeyResponder alloc]
439  initWithSendEvent:^(const FlutterKeyEvent& event,
440  FlutterKeyEventCallback callback,
441  void* userData) {
442  [weakSelf.engine sendKeyEvent:event
443  callback:callback
444  userData:userData];
445  }]];
446  [_keyboardManager
447  addPrimaryResponder:[[FlutterChannelKeyResponder alloc]
448  initWithChannel:[FlutterBasicMessageChannel
449  messageChannelWithName:@"flutter/keyevent"
450  binaryMessenger:_engine.binaryMessenger
452  sharedInstance]]]];
453  [_keyboardManager addSecondaryResponder:_textInputPlugin];
456  binaryMessenger:_engine.binaryMessenger
459  [FlutterMethodChannel methodChannelWithName:@"flutter/platform"
460  binaryMessenger:_engine.binaryMessenger
461  codec:[FlutterJSONMethodCodec sharedInstance]];
462  [_platformChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
463  [weakSelf handleMethodCall:call result:result];
464  }];
465 }
466 
467 - (void)dispatchMouseEvent:(nonnull NSEvent*)event {
471  [self dispatchMouseEvent:event phase:phase];
472 }
473 
474 - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase {
475  NSAssert(self.viewLoaded, @"View must be loaded before it handles the mouse event");
476  // There are edge cases where the system will deliver enter out of order relative to other
477  // events (e.g., drag out and back in, release, then click; mouseDown: will be called before
478  // mouseEntered:). Discard those events, since the add will already have been synthesized.
479  if (_mouseState.flutter_state_is_added && phase == kAdd) {
480  return;
481  }
482 
483  // If a pointer added event hasn't been sent, synthesize one using this event for the basic
484  // information.
485  if (!_mouseState.flutter_state_is_added && phase != kAdd) {
486  // Only the values extracted for use in flutterEvent below matter, the rest are dummy values.
487  NSEvent* addEvent = [NSEvent enterExitEventWithType:NSEventTypeMouseEntered
488  location:event.locationInWindow
489  modifierFlags:0
490  timestamp:event.timestamp
491  windowNumber:event.windowNumber
492  context:nil
493  eventNumber:0
494  trackingNumber:0
495  userData:NULL];
496  [self dispatchMouseEvent:addEvent phase:kAdd];
497  }
498 
499  NSPoint locationInView = [self.flutterView convertPoint:event.locationInWindow fromView:nil];
500  NSPoint locationInBackingCoordinates = [self.flutterView convertPointToBacking:locationInView];
501  FlutterPointerEvent flutterEvent = {
502  .struct_size = sizeof(flutterEvent),
503  .phase = phase,
504  .timestamp = static_cast<size_t>(event.timestamp * USEC_PER_SEC),
505  .x = locationInBackingCoordinates.x,
506  .y = -locationInBackingCoordinates.y, // convertPointToBacking makes this negative.
507  .device_kind = kFlutterPointerDeviceKindMouse,
508  // If a click triggered a synthesized kAdd, don't pass the buttons in that event.
509  .buttons = phase == kAdd ? 0 : _mouseState.buttons,
510  };
511 
512  if (event.type == NSEventTypeScrollWheel) {
514 
515  double pixelsPerLine = 1.0;
516  if (!event.hasPreciseScrollingDeltas) {
517  CGEventSourceRef source = CGEventCreateSourceFromEvent(event.CGEvent);
518  pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
519  if (source) {
520  CFRelease(source);
521  }
522  }
523  double scaleFactor = self.flutterView.layer.contentsScale;
524  flutterEvent.scroll_delta_x = -event.scrollingDeltaX * pixelsPerLine * scaleFactor;
525  flutterEvent.scroll_delta_y = -event.scrollingDeltaY * pixelsPerLine * scaleFactor;
526  }
527  [_engine sendPointerEvent:flutterEvent];
528 
529  // Update tracking of state as reported to Flutter.
530  if (phase == kDown) {
532  } else if (phase == kUp) {
534  if (_mouseState.has_pending_exit) {
535  [self dispatchMouseEvent:event phase:kRemove];
536  _mouseState.has_pending_exit = false;
537  }
538  } else if (phase == kAdd) {
540  } else if (phase == kRemove) {
541  _mouseState.Reset();
542  }
543 }
544 
545 - (void)onAccessibilityStatusChanged:(NSNotification*)notification {
546  if (!_engine) {
547  return;
548  }
549  BOOL enabled = [notification.userInfo[EnhancedUserInterfaceKey] boolValue];
550  if (!enabled && self.viewLoaded && [_textInputPlugin isFirstResponder]) {
551  // The client (i.e. the FlutterTextField) of the textInputPlugin is a sibling
552  // of the FlutterView. macOS will pick the ancestor to be the next responder
553  // when the client is removed from the view hierarchy, which is the result of
554  // turning off semantics. This will cause the keyboard focus to stick at the
555  // NSWindow.
556  //
557  // Since the view controller creates the illustion that the FlutterTextField is
558  // below the FlutterView in accessibility (See FlutterViewWrapper), it has to
559  // manually pick the next responder.
560  [self.view.window makeFirstResponder:_flutterView];
561  }
562  _engine.semanticsEnabled = [notification.userInfo[EnhancedUserInterfaceKey] boolValue];
563 }
564 
565 - (void)onSettingsChanged:(NSNotification*)notification {
566  // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/32015.
567  NSString* brightness =
568  [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
569  [_settingsChannel sendMessage:@{
570  @"platformBrightness" : [brightness isEqualToString:@"Dark"] ? @"dark" : @"light",
571  // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/32006.
572  @"textScaleFactor" : @1.0,
573  @"alwaysUse24HourFormat" : @false
574  }];
575 }
576 
577 - (void)sendInitialSettings {
578  // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/32015.
579  [[NSDistributedNotificationCenter defaultCenter]
580  addObserver:self
581  selector:@selector(onSettingsChanged:)
582  name:@"AppleInterfaceThemeChangedNotification"
583  object:nil];
584  [self onSettingsChanged:nil];
585 }
586 
587 - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
588  if ([call.method isEqualToString:@"SystemNavigator.pop"]) {
589  [NSApp terminate:self];
590  result(nil);
591  } else if ([call.method isEqualToString:@"SystemSound.play"]) {
592  [self playSystemSound:call.arguments];
593  result(nil);
594  } else if ([call.method isEqualToString:@"Clipboard.getData"]) {
595  result([self getClipboardData:call.arguments]);
596  } else if ([call.method isEqualToString:@"Clipboard.setData"]) {
597  [self setClipboardData:call.arguments];
598  result(nil);
599  } else if ([call.method isEqualToString:@"Clipboard.hasStrings"]) {
600  result(@{@"value" : @([self clipboardHasStrings])});
601  } else {
603  }
604 }
605 
606 - (void)playSystemSound:(NSString*)soundType {
607  if ([soundType isEqualToString:@"SystemSoundType.alert"]) {
608  NSBeep();
609  }
610 }
611 
612 - (NSDictionary*)getClipboardData:(NSString*)format {
613  NSPasteboard* pasteboard = self.pasteboard;
614  if ([format isEqualToString:@(kTextPlainFormat)]) {
615  NSString* stringInPasteboard = [pasteboard stringForType:NSPasteboardTypeString];
616  return stringInPasteboard == nil ? nil : @{@"text" : stringInPasteboard};
617  }
618  return nil;
619 }
620 
621 - (void)setClipboardData:(NSDictionary*)data {
622  NSPasteboard* pasteboard = self.pasteboard;
623  NSString* text = data[@"text"];
624  [pasteboard clearContents];
625  if (text && ![text isEqual:[NSNull null]]) {
626  [pasteboard setString:text forType:NSPasteboardTypeString];
627  }
628 }
629 
630 - (BOOL)clipboardHasStrings {
631  return [self.pasteboard stringForType:NSPasteboardTypeString].length > 0;
632 }
633 
634 - (NSPasteboard*)pasteboard {
635  return [NSPasteboard generalPasteboard];
636 }
637 
638 #pragma mark - FlutterViewReshapeListener
639 
640 /**
641  * Responds to view reshape by notifying the engine of the change in dimensions.
642  */
643 - (void)viewDidReshape:(NSView*)view {
644  [_engine updateWindowMetrics];
645 }
646 
647 #pragma mark - FlutterPluginRegistry
648 
649 - (id<FlutterPluginRegistrar>)registrarForPlugin:(NSString*)pluginName {
650  return [_engine registrarForPlugin:pluginName];
651 }
652 
653 #pragma mark - NSResponder
654 
655 - (BOOL)acceptsFirstResponder {
656  return YES;
657 }
658 
659 - (void)keyDown:(NSEvent*)event {
660  [_keyboardManager handleEvent:event];
661 }
662 
663 - (void)keyUp:(NSEvent*)event {
664  [_keyboardManager handleEvent:event];
665 }
666 
667 - (BOOL)performKeyEquivalent:(NSEvent*)event {
668  [_keyboardManager handleEvent:event];
669  if (event.type == NSEventTypeKeyDown) {
670  // macOS only sends keydown for performKeyEquivalent, but the Flutter framework
671  // always expects a keyup for every keydown. Synthesizes a key up event so that
672  // the Flutter framework continues to work.
673  NSEvent* synthesizedUp = [NSEvent keyEventWithType:NSEventTypeKeyUp
674  location:event.locationInWindow
675  modifierFlags:event.modifierFlags
676  timestamp:event.timestamp
677  windowNumber:event.windowNumber
678  context:event.context
679  characters:event.characters
680  charactersIgnoringModifiers:event.charactersIgnoringModifiers
681  isARepeat:event.isARepeat
682  keyCode:event.keyCode];
683  [_keyboardManager handleEvent:synthesizedUp];
684  }
685  return YES;
686 }
687 
688 - (void)flagsChanged:(NSEvent*)event {
689  [_keyboardManager handleEvent:event];
690 }
691 
692 - (void)mouseEntered:(NSEvent*)event {
693  if (_mouseState.has_pending_exit) {
694  _mouseState.has_pending_exit = false;
695  } else {
696  [self dispatchMouseEvent:event phase:kAdd];
697  }
698 }
699 
700 - (void)mouseExited:(NSEvent*)event {
701  if (_mouseState.buttons != 0) {
702  _mouseState.has_pending_exit = true;
703  return;
704  }
705  [self dispatchMouseEvent:event phase:kRemove];
706 }
707 
708 - (void)mouseDown:(NSEvent*)event {
710  [self dispatchMouseEvent:event];
711 }
712 
713 - (void)mouseUp:(NSEvent*)event {
714  _mouseState.buttons &= ~static_cast<uint64_t>(kFlutterPointerButtonMousePrimary);
715  [self dispatchMouseEvent:event];
716 }
717 
718 - (void)mouseDragged:(NSEvent*)event {
719  [self dispatchMouseEvent:event];
720 }
721 
722 - (void)rightMouseDown:(NSEvent*)event {
724  [self dispatchMouseEvent:event];
725 }
726 
727 - (void)rightMouseUp:(NSEvent*)event {
728  _mouseState.buttons &= ~static_cast<uint64_t>(kFlutterPointerButtonMouseSecondary);
729  [self dispatchMouseEvent:event];
730 }
731 
732 - (void)rightMouseDragged:(NSEvent*)event {
733  [self dispatchMouseEvent:event];
734 }
735 
736 - (void)otherMouseDown:(NSEvent*)event {
737  _mouseState.buttons |= (1 << event.buttonNumber);
738  [self dispatchMouseEvent:event];
739 }
740 
741 - (void)otherMouseUp:(NSEvent*)event {
742  _mouseState.buttons &= ~static_cast<uint64_t>(1 << event.buttonNumber);
743  [self dispatchMouseEvent:event];
744 }
745 
746 - (void)otherMouseDragged:(NSEvent*)event {
747  [self dispatchMouseEvent:event];
748 }
749 
750 - (void)mouseMoved:(NSEvent*)event {
751  [self dispatchMouseEvent:event];
752 }
753 
754 - (void)scrollWheel:(NSEvent*)event {
755  // TODO: Add gesture-based (trackpad) scroll support once it's supported by the engine rather
756  // than always using kHover.
757  [self dispatchMouseEvent:event phase:kHover];
758 }
759 
760 @end
fml::scoped_nsobject< FlutterTextInputPlugin > _textInputPlugin
Definition: embedder.h:595
double scroll_delta_y
The y offset of the scroll in physical pixels.
Definition: embedder.h:666
instancetype methodChannelWithName:binaryMessenger:codec:(NSString *name, [binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger, [codec] NSObject< FlutterMethodCodec > *codec)
instancetype messageChannelWithName:binaryMessenger:codec:(NSString *name, [binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger, [codec] NSObject< FlutterMessageCodec > *codec)
fml::scoped_nsobject< FlutterView > _flutterView
FlutterBasicMessageChannel * _settingsChannel
FlutterPointerPhase
The phase of the pointer event.
Definition: embedder.h:587
MouseState _mouseState
double scroll_delta_x
The x offset of the scroll in physical pixels.
Definition: embedder.h:664
GAsyncResult * result
uint32_t uint32_t * format
NSOpenGLContext * openGLContext
FlKeyEvent * event
instancetype sharedInstance()
fml::scoped_nsobject< FlutterEngine > _engine
FlutterDartProject * _project
FlutterMethodChannel * _platformChannel
size_t struct_size
The size of this struct. Must be sizeof(FlutterPointerEvent).
Definition: embedder.h:649
static constexpr char kTextPlainFormat[]
The pointer moved while up.
Definition: embedder.h:619
void(^ FlutterResult)(id _Nullable result)
Definition: embedder.h:612
id< MTLCommandQueue > commandQueue
int BOOL
Definition: windows_types.h:37
FlView * view
std::u16string text
FlutterPointerSignalKind signal_kind
Definition: embedder.h:662
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented