6
6
#import " flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
7
7
8
8
#include < Carbon/Carbon.h>
9
+ #import < objc/message.h>
9
10
10
11
#import " flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
11
12
#import " flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
@@ -163,6 +164,8 @@ @interface FlutterViewWrapper : NSView
163
164
164
165
- (void )setBackgroundColor : (NSColor *)color ;
165
166
167
+ - (BOOL )performKeyEquivalent : (NSEvent *)event ;
168
+
166
169
@end
167
170
168
171
/* *
@@ -239,6 +242,37 @@ - (void)onKeyboardLayoutChanged;
239
242
240
243
@end
241
244
245
+ #pragma mark - NSEvent (KeyEquivalentMarker) protocol
246
+
247
+ @interface NSEvent (KeyEquivalentMarker)
248
+
249
+ // Internally marks that the event was received through performKeyEquivalent:.
250
+ // When text editing is active, keyboard events that have modifier keys pressed
251
+ // are received through performKeyEquivalent: instead of keyDown:. If such event
252
+ // is passed to TextInputContext but doesn't result in a text editing action it
253
+ // needs to be forwarded by FlutterKeyboardManager to the next responder.
254
+ - (void )markAsKeyEquivalent ;
255
+
256
+ // Returns YES if the event is marked as a key equivalent.
257
+ - (BOOL )isKeyEquivalent ;
258
+
259
+ @end
260
+
261
+ @implementation NSEvent (KeyEquivalentMarker)
262
+
263
+ // This field doesn't need a value because only its address is used as a unique identifier.
264
+ static char markerKey;
265
+
266
+ - (void )markAsKeyEquivalent {
267
+ objc_setAssociatedObject (self, &markerKey, @true , OBJC_ASSOCIATION_RETAIN );
268
+ }
269
+
270
+ - (BOOL )isKeyEquivalent {
271
+ return [objc_getAssociatedObject (self , &markerKey) boolValue ] == YES ;
272
+ }
273
+
274
+ @end
275
+
242
276
#pragma mark - Private dependant functions
243
277
244
278
namespace {
@@ -258,12 +292,15 @@ void OnKeyboardLayoutChanged(CFNotificationCenterRef center,
258
292
259
293
@implementation FlutterViewWrapper {
260
294
FlutterView* _flutterView;
295
+ FlutterViewController* _controller;
261
296
}
262
297
263
- - (instancetype )initWithFlutterView : (FlutterView*)view {
298
+ - (instancetype )initWithFlutterView : (FlutterView*)view
299
+ controller : (FlutterViewController*)controller {
264
300
self = [super initWithFrame: NSZeroRect ];
265
301
if (self) {
266
302
_flutterView = view;
303
+ _controller = controller;
267
304
view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
268
305
[self addSubview: view];
269
306
}
@@ -274,6 +311,24 @@ - (void)setBackgroundColor:(NSColor*)color {
274
311
[_flutterView setBackgroundColor: color];
275
312
}
276
313
314
+ - (BOOL )performKeyEquivalent : (NSEvent *)event {
315
+ if ([_controller isDispatchingKeyEvent: event]) {
316
+ // When NSWindow is nextResponder, keyboard manager will send to it
317
+ // unhandled events (through [NSWindow keyDown:]). If event has both
318
+ // control and cmd modifiers set (i.e. cmd+control+space - emoji picker)
319
+ // NSWindow will then send this event as performKeyEquivalent: to first
320
+ // responder, which might be FlutterTextInputPlugin. If that's the case, the
321
+ // plugin must not handle the event, otherwise the emoji picker would not
322
+ // work (due to first responder returning YES from performKeyEquivalent:)
323
+ // and there would be endless loop, because FlutterViewController will
324
+ // send the event back to [keyboardManager handleEvent:].
325
+ return NO ;
326
+ }
327
+ [event markAsKeyEquivalent ];
328
+ [_flutterView keyDown: event];
329
+ return YES ;
330
+ }
331
+
277
332
- (NSArray *)accessibilityChildren {
278
333
return @[ _flutterView ];
279
334
}
@@ -405,7 +460,8 @@ - (void)loadView {
405
460
if (_backgroundColor != nil ) {
406
461
[flutterView setBackgroundColor: _backgroundColor];
407
462
}
408
- FlutterViewWrapper* wrapperView = [[FlutterViewWrapper alloc ] initWithFlutterView: flutterView];
463
+ FlutterViewWrapper* wrapperView = [[FlutterViewWrapper alloc ] initWithFlutterView: flutterView
464
+ controller: self ];
409
465
self.view = wrapperView;
410
466
_flutterView = flutterView;
411
467
}
0 commit comments