| Flutter macOS Embedder |

FlutterTextInputPlugin.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/Source/FlutterTextInputPlugin.h"
6
7 #import <Foundation/Foundation.h>
8 #import <objc/message.h>
9
10 #include <algorithm>
11 #include <memory>
12
13 #include "flutter/common/constants.h"
14 #include "flutter/fml/platform/darwin/string_range_sanitization.h"
15 #include "flutter/shell/platform/common/text_editing_delta.h"
16 #include "flutter/shell/platform/common/text_input_model.h"
17 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
18 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputSemanticsObject.h"
19 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
20 #import "flutter/shell/platform/darwin/macos/framework/Source/NSView+ClipsToBounds.h"
21
22 static NSString* const kTextInputChannel = @"flutter/textinput";
23
24 #pragma mark - TextInput channel method names
25 // See https://api.flutter.dev/flutter/services/SystemChannels/textInput-constant.html
26 static NSString* const kSetClientMethod = @"TextInput.setClient";
27 static NSString* const kShowMethod = @"TextInput.show";
28 static NSString* const kHideMethod = @"TextInput.hide";
29 static NSString* const kClearClientMethod = @"TextInput.clearClient";
30 static NSString* const kSetEditingStateMethod = @"TextInput.setEditingState";
31 static NSString* const kSetEditableSizeAndTransform = @"TextInput.setEditableSizeAndTransform";
32 static NSString* const kSetCaretRect = @"TextInput.setCaretRect";
33 static NSString* const kUpdateEditStateResponseMethod = @"TextInputClient.updateEditingState";
34 static NSString* const kUpdateEditStateWithDeltasResponseMethod =
35 @"TextInputClient.updateEditingStateWithDeltas";
36 static NSString* const kPerformAction = @"TextInputClient.performAction";
37 static NSString* const kPerformSelectors = @"TextInputClient.performSelectors";
38 static NSString* const kMultilineInputType = @"TextInputType.multiline";
39
40 #pragma mark - TextInputConfiguration field names
41 static NSString* const kViewId = @"viewId";
42 static NSString* const kSecureTextEntry = @"obscureText";
43 static NSString* const kTextInputAction = @"inputAction";
44 static NSString* const kEnableDeltaModel = @"enableDeltaModel";
45 static NSString* const kTextInputType = @"inputType";
46 static NSString* const kTextInputTypeName = @"name";
47 static NSString* const kSelectionBaseKey = @"selectionBase";
48 static NSString* const kSelectionExtentKey = @"selectionExtent";
49 static NSString* const kSelectionAffinityKey = @"selectionAffinity";
50 static NSString* const kSelectionIsDirectionalKey = @"selectionIsDirectional";
51 static NSString* const kComposingBaseKey = @"composingBase";
52 static NSString* const kComposingExtentKey = @"composingExtent";
53 static NSString* const kTextKey = @"text";
54 static NSString* const kTransformKey = @"transform";
55 static NSString* const kAssociatedAutofillFields = @"fields";
56
57 // TextInputConfiguration.autofill and sub-field names
58 static NSString* const kAutofillProperties = @"autofill";
59 static NSString* const kAutofillId = @"uniqueIdentifier";
60 static NSString* const kAutofillEditingValue = @"editingValue";
61 static NSString* const kAutofillHints = @"hints";
62
63 // TextAffinity types
64 static NSString* const kTextAffinityDownstream = @"TextAffinity.downstream";
65 static NSString* const kTextAffinityUpstream = @"TextAffinity.upstream";
66
67 // TextInputAction types
68 static NSString* const kInputActionNewline = @"TextInputAction.newline";
69
70 #pragma mark - Enums
71 /**
72 * The affinity of the current cursor position. If the cursor is at a position
73 * representing a soft line break, the cursor may be drawn either at the end of
74 * the current line (upstream) or at the beginning of the next (downstream).
75 */
76 typedef NS_ENUM(NSUInteger, FlutterTextAffinity) {
77 kFlutterTextAffinityUpstream,
78 kFlutterTextAffinityDownstream
79 };
80
81 #pragma mark - Static functions
82
83 /*
84 * Updates a range given base and extent fields.
85 */
86 static flutter::TextRange RangeFromBaseExtent(NSNumber* base,
87 NSNumber* extent,
88 const flutter::TextRange& range) {
89 if (base == nil || extent == nil) {
90 return range;
91 }
92 if (base.intValue == -1 && extent.intValue == -1) {
93 return flutter::TextRange(0, 0);
94 }
95 return flutter::TextRange([base unsignedLongValue], [extent unsignedLongValue]);
96 }
97
98 // Returns the autofill hint content type, if specified; otherwise nil.
99 static NSString* GetAutofillHint(NSDictionary* autofill) {
100 NSArray<NSString*>* hints = autofill[kAutofillHints];
101 return hints.count > 0 ? hints[0] : nil;
102 }
103
104 // Returns the text content type for the specified TextInputConfiguration.
105 // NSTextContentType is only available for macOS 11.0 and later.
106 static NSTextContentType GetTextContentType(NSDictionary* configuration)
107 API_AVAILABLE(macos(11.0)) {
108 // Check autofill hints.
109 NSDictionary* autofill = configuration[kAutofillProperties];
110 if (autofill) {
111 NSString* hint = GetAutofillHint(autofill);
112 if ([hint isEqualToString:@"username"]) {
113 return NSTextContentTypeUsername;
114 }
115 if ([hint isEqualToString:@"password"]) {
116 return NSTextContentTypePassword;
117 }
118 if ([hint isEqualToString:@"oneTimeCode"]) {
119 return NSTextContentTypeOneTimeCode;
120 }
121 }
122 // If no autofill hints, guess based on other attributes.
123 if ([configuration[kSecureTextEntry] boolValue]) {
124 return NSTextContentTypePassword;
125 }
126 return nil;
127 }
128
129 // Returns YES if configuration describes a field for which autocomplete should be enabled for
130 // the specified TextInputConfiguration. Autocomplete is enabled by default, but will be disabled
131 // if the field is password-related, or if the configuration contains no autofill settings.
132 static BOOL EnableAutocompleteForTextInputConfiguration(NSDictionary* configuration) {
133 // Disable if obscureText is set.
134 if ([configuration[kSecureTextEntry] boolValue]) {
135 return NO;
136 }
137
138 // Disable if autofill properties are not set.
139 NSDictionary* autofill = configuration[kAutofillProperties];
140 if (autofill == nil) {
141 return NO;
142 }
143
144 // Disable if autofill properties indicate a username/password.
145 // See: https://github.com/flutter/flutter/issues/119824
146 NSString* hint = GetAutofillHint(autofill);
147 if ([hint isEqualToString:@"password"] || [hint isEqualToString:@"username"]) {
148 return NO;
149 }
150 return YES;
151 }
152
153 // Returns YES if configuration describes a field for which autocomplete should be enabled.
154 // Autocomplete is enabled by default, but will be disabled if the field is password-related, or if
155 // the configuration contains no autofill settings.
156 //
157 // In the case where the current field is part of an AutofillGroup, the configuration will have
158 // a fields attribute with a list of TextInputConfigurations, one for each field. In the case where
159 // any field in the group disables autocomplete, we disable it for all.
160 static BOOL EnableAutocomplete(NSDictionary* configuration) {
161 for (NSDictionary* field in configuration[kAssociatedAutofillFields]) {
162 if (!EnableAutocompleteForTextInputConfiguration(field)) {
163 return NO;
164 }
165 }
166
167 // Check the top-level TextInputConfiguration.
168 return EnableAutocompleteForTextInputConfiguration(configuration);
169 }
170
171 #pragma mark - NSEvent (KeyEquivalentMarker) protocol
172
173 @interface NSEvent (KeyEquivalentMarker)
174
175 // Internally marks that the event was received through performKeyEquivalent:.
176 // When text editing is active, keyboard events that have modifier keys pressed
177 // are received through performKeyEquivalent: instead of keyDown:. If such event
178 // is passed to TextInputContext but doesn't result in a text editing action it
179 // needs to be forwarded by FlutterKeyboardManager to the next responder.
180 - (void)markAsKeyEquivalent;
181
182 // Returns YES if the event is marked as a key equivalent.
183 - (BOOL)isKeyEquivalent;
184
185 @end
186
187 @implementation NSEvent (KeyEquivalentMarker)
188
189 // This field doesn't need a value because only its address is used as a unique identifier.
190 static char markerKey;
191
192 - (void)markAsKeyEquivalent {
193 objc_setAssociatedObject(self, &markerKey, @true, OBJC_ASSOCIATION_RETAIN);
194 }
195
196 - (BOOL)isKeyEquivalent {
197 return [objc_getAssociatedObject(self, &markerKey) boolValue] == YES;
198 }
199
200 @end
201
202 #pragma mark - FlutterTextInputPlugin private interface
203
204 /**
205 * Private properties of FlutterTextInputPlugin.
206 */
207 @interface FlutterTextInputPlugin () {
208 /**
209 * A text input context, representing a connection to the Cocoa text input system.
210 */
211 NSTextInputContext* _textInputContext;
212
213 /**
214 * The channel used to communicate with Flutter.
215 */
216 FlutterMethodChannel* _channel;
217
218 /**
219 * The FlutterViewController to manage input for.
220 */
221 __weak FlutterViewController* _currentViewController;
222
223 /**
224 * The originally requested view controller. This may be attached to a window
225 * that is a descendant of _currentViewController window in case the window
226 * can not become a key window (i.e. popup window).
227 */
228 __weak FlutterViewController* _originalViewController;
229
230 /**
231 * Used to obtain view controller on client creation
232 */
233 __weak id<FlutterTextInputPluginDelegate> _delegate;
234
235 /**
236 * Whether the text input is shown in the view.
237 *
238 * Defaults to TRUE on startup.
239 */
240 BOOL _shown;
241
242 /**
243 * The current state of the keyboard and pressed keys.
244 */
245 uint64_t _previouslyPressedFlags;
246
247 /**
248 * The affinity for the current cursor position.
249 */
250 FlutterTextAffinity _textAffinity;
251
252 /**
253 * ID of the text input client.
254 */
255 NSNumber* _clientID;
256
257 /**
258 * Keyboard type of the client. See available options:
259 * https://api.flutter.dev/flutter/services/TextInputType-class.html
260 */
261 NSString* _inputType;
262
263 /**
264 * An action requested by the user on the input client. See available options:
265 * https://api.flutter.dev/flutter/services/TextInputAction-class.html
266 */
267 NSString* _inputAction;
268
269 /**
270 * Set to true if the last event fed to the input context produced a text editing command
271 * or text output. It is reset to false at the beginning of every key event, and is only
272 * used while processing this event.
273 */
274 BOOL _eventProducedOutput;
275
276 /**
277 * Whether to enable the sending of text input updates from the engine to the
278 * framework as TextEditingDeltas rather than as one TextEditingValue.
279 * For more information on the delta model, see:
280 * https://master-api.flutter.dev/flutter/services/TextInputConfiguration/enableDeltaModel.html
281 */
282 BOOL _enableDeltaModel;
283
284 /**
285 * Used to gather multiple selectors performed in one run loop turn. These
286 * will be all sent in one platform channel call so that the framework can process
287 * them in single microtask.
288 */
289 NSMutableArray* _pendingSelectors;
290
291 /**
292 * The currently active text input model.
293 */
294 std::unique_ptr<flutter::TextInputModel> _activeModel;
295
296 /**
297 * Transform for current the editable. Used to determine position of accent selection menu.
298 */
299 CATransform3D _editableTransform;
300
301 /**
302 * Current position of caret in local (editable) coordinates.
303 */
304 CGRect _caretRect;
305 }
306
307 /**
308 * Handles a Flutter system message on the text input channel.
309 */
310 - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
311
312 /**
313 * Updates the text input model with state received from the framework via the
314 * TextInput.setEditingState message.
315 */
316 - (void)setEditingState:(NSDictionary*)state;
317
318 /**
319 * Informs the Flutter framework of changes to the text input model's state by
320 * sending the entire new state.
321 */
322 - (void)updateEditState;
323
324 /**
325 * Informs the Flutter framework of changes to the text input model's state by
326 * sending only the difference.
327 */
328 - (void)updateEditStateWithDelta:(const flutter::TextEditingDelta)delta;
329
330 /**
331 * Updates the stringValue and selectedRange that stored in the NSTextView interface
332 * that this plugin inherits from.
333 *
334 * If there is a FlutterTextField uses this plugin as its field editor, this method
335 * will update the stringValue and selectedRange through the API of the FlutterTextField.
336 */
337 - (void)updateTextAndSelection;
338
339 /**
340 * Return the string representation of the current textAffinity as it should be
341 * sent over the FlutterMethodChannel.
342 */
343 - (NSString*)textAffinityString;
344
345 /**
346 * Allow overriding run loop mode for test.
347 */
348 @property(readwrite, nonatomic) NSString* customRunLoopMode;
349 @property(nonatomic) NSTextInputContext* textInputContext;
350
351 @end
352
353 #pragma mark - FlutterTextInputPlugin
354
355 @implementation FlutterTextInputPlugin
356
357 - (instancetype)initWithDelegate:(id<FlutterTextInputPluginDelegate>)delegate {
358 // The view needs an empty frame otherwise it is visible on dark background.
359 // https://github.com/flutter/flutter/issues/118504
360 self = [super initWithFrame:NSZeroRect];
361 self.clipsToBounds = YES;
362 if (self != nil) {
363 _delegate = delegate;
364 _channel = [FlutterMethodChannel methodChannelWithName:kTextInputChannel
365 binaryMessenger:_delegate.binaryMessenger
366 codec:[FlutterJSONMethodCodec sharedInstance]];
367 _shown = FALSE;
368 // NSTextView does not support _weak reference, so this class has to
369 // use __unsafe_unretained and manage the reference by itself.
370 //
371 // Since the dealloc removes the handler, the pointer should
372 // be valid if the handler is ever called.
373 __unsafe_unretained FlutterTextInputPlugin* unsafeSelf = self;
374 [_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
375 [unsafeSelf handleMethodCall:call result:result];
376 }];
377 _textInputContext = [[NSTextInputContext alloc] initWithClient:unsafeSelf];
378 _previouslyPressedFlags = 0;
379
380 // Initialize with the zero matrix which is not
381 // an affine transform.
382 _editableTransform = CATransform3D();
383 _caretRect = CGRectNull;
384 }
385 return self;
386 }
387
388 - (BOOL)isFirstResponder {
389 if (!_currentViewController.viewLoaded) {
390 return false;
391 }
392 return [_currentViewController.view.window firstResponder] == self;
393 }
394
395 - (void)dealloc {
396 [_channel setMethodCallHandler:nil];
397 }
398
399 #pragma mark - Private
400
401 - (void)resignAndRemoveFromSuperview {
402 if (self.superview != nil) {
403 [self.window makeFirstResponder:_currentViewController.flutterView];
404 [self removeFromSuperview];
405 }
406 }
407
408 - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
409 BOOL handled = YES;
410 NSString* method = call.method;
411 if ([method isEqualToString:kSetClientMethod]) {
412 FML_DCHECK(_currentViewController == nil);
413 if (!call.arguments[0] || !call.arguments[1]) {
414 result([FlutterError
415 errorWithCode:@"error"
416 message:@"Missing arguments"
417 details:@"Missing arguments while trying to set a text input client"]);
418 return;
419 }
420 NSNumber* clientID = call.arguments[0];
421 if (clientID != nil) {
422 NSDictionary* config = call.arguments[1];
423
424 _clientID = clientID;
425 _inputAction = config[kTextInputAction];
426 _enableDeltaModel = [config[kEnableDeltaModel] boolValue];
427 NSDictionary* inputTypeInfo = config[kTextInputType];
428 _inputType = inputTypeInfo[kTextInputTypeName];
429 _textAffinity = kFlutterTextAffinityUpstream;
430 self.automaticTextCompletionEnabled = EnableAutocomplete(config);
431 if (@available(macOS 11.0, *)) {
432 self.contentType = GetTextContentType(config);
433 }
434
435 _activeModel = std::make_unique<flutter::TextInputModel>();
436 FlutterViewIdentifier viewId = flutter::kFlutterImplicitViewId;
437 NSObject* requestViewId = config[kViewId];
438 if ([requestViewId isKindOfClass:[NSNumber class]]) {
439 viewId = [(NSNumber*)requestViewId longLongValue];
440 }
441 _currentViewController = [_delegate viewControllerForIdentifier:viewId];
442 _originalViewController = _currentViewController;
443 while (!_currentViewController.view.window.canBecomeKeyWindow) {
444 NSWindow* parentWindow = [_currentViewController.view.window parentWindow];
445 if (parentWindow == nil) {
446 break;
447 }
448 NSViewController* controller = parentWindow.contentViewController;
449 if ([controller isKindOfClass:[FlutterViewController class]]) {
450 _currentViewController = (FlutterViewController*)controller;
451 } else {
452 break;
453 }
454 }
455 FML_DCHECK(_currentViewController != nil);
456 }
457 } else if ([method isEqualToString:kShowMethod]) {
458 FML_DCHECK(_currentViewController != nil);
459 // Ensure the plugin is in hierarchy. Only do this with accessibility disabled.
460 // When accessibility is enabled cocoa will reparent the plugin inside
461 // FlutterTextField in [FlutterTextField startEditing].
462 if (_client == nil) {
463 [_currentViewController.view addSubview:self];
464 }
465 [self.window makeFirstResponder:self];
466 _shown = TRUE;
467 } else if ([method isEqualToString:kHideMethod]) {
468 [self resignAndRemoveFromSuperview];
469 _shown = FALSE;
470 } else if ([method isEqualToString:kClearClientMethod]) {
471 FML_DCHECK(_currentViewController != nil);
472 [self resignAndRemoveFromSuperview];
473 // If there's an active mark region, commit it, end composing, and clear the IME's mark text.
474 if (_activeModel && _activeModel->composing()) {
475 _activeModel->CommitComposing();
476 _activeModel->EndComposing();
477 }
478 [_textInputContext discardMarkedText];
479
480 _clientID = nil;
481 _inputAction = nil;
482 _enableDeltaModel = NO;
483 _inputType = nil;
484 _activeModel = nullptr;
485 _currentViewController = nil;
486 _originalViewController = nil;
487 } else if ([method isEqualToString:kSetEditingStateMethod]) {
488 FML_DCHECK(_currentViewController != nil);
489 NSDictionary* state = call.arguments;
490 [self setEditingState:state];
491 } else if ([method isEqualToString:kSetEditableSizeAndTransform]) {
492 FML_DCHECK(_currentViewController != nil);
493 NSDictionary* state = call.arguments;
494 [self setEditableTransform:state[kTransformKey]];
495 } else if ([method isEqualToString:kSetCaretRect]) {
496 FML_DCHECK(_currentViewController != nil);
497 NSDictionary* rect = call.arguments;
498 [self updateCaretRect:rect];
499 } else {
500 handled = NO;
501 }
502 result(handled ? nil : FlutterMethodNotImplemented);
503 }
504
505 - (void)setEditableTransform:(NSArray*)matrix {
506 CATransform3D* transform = &_editableTransform;
507
508 transform->m11 = [matrix[0] doubleValue];
509 transform->m12 = [matrix[1] doubleValue];
510 transform->m13 = [matrix[2] doubleValue];
511 transform->m14 = [matrix[3] doubleValue];
512
513 transform->m21 = [matrix[4] doubleValue];
514 transform->m22 = [matrix[5] doubleValue];
515 transform->m23 = [matrix[6] doubleValue];
516 transform->m24 = [matrix[7] doubleValue];
517
518 transform->m31 = [matrix[8] doubleValue];
519 transform->m32 = [matrix[9] doubleValue];
520 transform->m33 = [matrix[10] doubleValue];
521 transform->m34 = [matrix[11] doubleValue];
522
523 transform->m41 = [matrix[12] doubleValue];
524 transform->m42 = [matrix[13] doubleValue];
525 transform->m43 = [matrix[14] doubleValue];
526 transform->m44 = [matrix[15] doubleValue];
527 }
528
529 - (void)updateCaretRect:(NSDictionary*)dictionary {
530 NSAssert(dictionary[@"x"] != nil && dictionary[@"y"] != nil && dictionary[@"width"] != nil &&
531 dictionary[@"height"] != nil,
532 @"Expected a dictionary representing a CGRect, got %@", dictionary);
533 _caretRect = CGRectMake([dictionary[@"x"] doubleValue], [dictionary[@"y"] doubleValue],
534 [dictionary[@"width"] doubleValue], [dictionary[@"height"] doubleValue]);
535 }
536
537 - (void)setEditingState:(NSDictionary*)state {
538 NSString* selectionAffinity = state[kSelectionAffinityKey];
539 if (selectionAffinity != nil) {
540 _textAffinity = [selectionAffinity isEqualToString:kTextAffinityUpstream]
541 ? kFlutterTextAffinityUpstream
542 : kFlutterTextAffinityDownstream;
543 }
544
545 NSString* text = state[kTextKey];
546
547 flutter::TextRange selected_range = RangeFromBaseExtent(
548 state[kSelectionBaseKey], state[kSelectionExtentKey], _activeModel->selection());
549 _activeModel->SetSelection(selected_range);
550
551 flutter::TextRange composing_range = RangeFromBaseExtent(
552 state[kComposingBaseKey], state[kComposingExtentKey], _activeModel->composing_range());
553
554 const bool wasComposing = _activeModel->composing();
555 _activeModel->SetText([text UTF8String], selected_range, composing_range);
556 if (composing_range.collapsed() && wasComposing) {
557 [_textInputContext discardMarkedText];
558 }
559 [_client startEditing];
560
561 [self updateTextAndSelection];
562 }
563
564 - (NSDictionary*)editingState {
565 if (_activeModel == nullptr) {
566 return nil;
567 }
568
569 NSString* const textAffinity = [self textAffinityString];
570
571 int composingBase = _activeModel->composing() ? _activeModel->composing_range().base() : -1;
572 int composingExtent = _activeModel->composing() ? _activeModel->composing_range().extent() : -1;
573
574 return @{
575 kSelectionBaseKey : @(_activeModel->selection().base()),
576 kSelectionExtentKey : @(_activeModel->selection().extent()),
577 kSelectionAffinityKey : textAffinity,
578 kSelectionIsDirectionalKey : @NO,
579 kComposingBaseKey : @(composingBase),
580 kComposingExtentKey : @(composingExtent),
581 kTextKey : [NSString stringWithUTF8String:_activeModel->GetText().c_str()] ?: [NSNull null],
582 };
583 }
584
585 - (void)updateEditState {
586 if (_activeModel == nullptr) {
587 return;
588 }
589
590 NSDictionary* state = [self editingState];
591 [_channel invokeMethod:kUpdateEditStateResponseMethod arguments:@[ _clientID, state ]];
592 [self updateTextAndSelection];
593 }
594
595 - (void)updateEditStateWithDelta:(const flutter::TextEditingDelta)delta {
596 NSUInteger selectionBase = _activeModel->selection().base();
597 NSUInteger selectionExtent = _activeModel->selection().extent();
598 int composingBase = _activeModel->composing() ? _activeModel->composing_range().base() : -1;
599 int composingExtent = _activeModel->composing() ? _activeModel->composing_range().extent() : -1;
600
601 NSString* const textAffinity = [self textAffinityString];
602
603 NSDictionary* deltaToFramework = @{
604 @"oldText" : @(delta.old_text().c_str()),
605 @"deltaText" : @(delta.delta_text().c_str()),
606 @"deltaStart" : @(delta.delta_start()),
607 @"deltaEnd" : @(delta.delta_end()),
608 @"selectionBase" : @(selectionBase),
609 @"selectionExtent" : @(selectionExtent),
610 @"selectionAffinity" : textAffinity,
611 @"selectionIsDirectional" : @(false),
612 @"composingBase" : @(composingBase),
613 @"composingExtent" : @(composingExtent),
614 };
615
616 NSDictionary* deltas = @{
617 @"deltas" : @[ deltaToFramework ],
618 };
619
620 [_channel invokeMethod:kUpdateEditStateWithDeltasResponseMethod arguments:@[ _clientID, deltas ]];
621 [self updateTextAndSelection];
622 }
623
624 - (void)updateTextAndSelection {
625 NSAssert(_activeModel != nullptr, @"Flutter text model must not be null.");
626 NSString* text = @(_activeModel->GetText().data());
627 int start = _activeModel->selection().base();
628 int extend = _activeModel->selection().extent();
629 NSRange selection = NSMakeRange(MIN(start, extend), ABS(start - extend));
630 // There may be a native text field client if VoiceOver is on.
631 // In this case, this plugin has to update text and selection through
632 // the client in order for VoiceOver to announce the text editing
633 // properly.
634 if (_client) {
635 [_client updateString:text withSelection:selection];
636 } else {
637 self.string = text;
638 [self setSelectedRange:selection];
639 }
640 }
641
642 - (NSString*)textAffinityString {
643 return (_textAffinity == kFlutterTextAffinityUpstream) ? kTextAffinityUpstream
644 : kTextAffinityDownstream;
645 }
646
647 - (BOOL)handleKeyEvent:(NSEvent*)event {
648 if (event.type == NSEventTypeKeyUp ||
649 (event.type == NSEventTypeFlagsChanged && event.modifierFlags < _previouslyPressedFlags)) {
650 return NO;
651 }
652 _previouslyPressedFlags = event.modifierFlags;
653 if (!_shown) {
654 return NO;
655 }
656
657 _eventProducedOutput = NO;
658 BOOL res = [_textInputContext handleEvent:event];
659 // NSTextInputContext#handleEvent returns YES if the context handles the event. One of the reasons
660 // the event is handled is because it's a key equivalent. But a key equivalent might produce a
661 // text command (indicated by calling doCommandBySelector) or might not (for example, Cmd+Q). In
662 // the latter case, this command somehow has not been executed yet and Flutter must dispatch it to
663 // the next responder. See https://github.com/flutter/flutter/issues/106354 .
664 // The event is also not redispatched if there is IME composition active, because it might be
665 // handled by the IME. See https://github.com/flutter/flutter/issues/134699
666
667 // both NSEventModifierFlagNumericPad and NSEventModifierFlagFunction are set for arrow keys.
668 bool is_navigation = event.modifierFlags & NSEventModifierFlagFunction &&
669 event.modifierFlags & NSEventModifierFlagNumericPad;
670 bool is_navigation_in_ime = is_navigation && self.hasMarkedText;
671
672 if (event.isKeyEquivalent && !is_navigation_in_ime && !_eventProducedOutput) {
673 return NO;
674 }
675 return res;
676 }
677
678 #pragma mark -
679 #pragma mark NSResponder
680
681 - (void)keyDown:(NSEvent*)event {
682 [_currentViewController keyDown:event];
683 }
684
685 - (void)keyUp:(NSEvent*)event {
686 [_currentViewController keyUp:event];
687 }
688
689 - (BOOL)performKeyEquivalent:(NSEvent*)event {
690 if ([_currentViewController isDispatchingKeyEvent:event]) {
691 // When NSWindow is nextResponder, keyboard manager will send to it
692 // unhandled events (through [NSWindow keyDown:]). If event has both
693 // control and cmd modifiers set (i.e. cmd+control+space - emoji picker)
694 // NSWindow will then send this event as performKeyEquivalent: to first
695 // responder, which is FlutterTextInputPlugin. If that's the case, the
696 // plugin must not handle the event, otherwise the emoji picker would not
697 // work (due to first responder returning YES from performKeyEquivalent:)
698 // and there would be endless loop, because FlutterViewController will
699 // send the event back to [keyboardManager handleEvent:].
700 return NO;
701 }
702 [event markAsKeyEquivalent];
703 [_currentViewController keyDown:event];
704 return YES;
705 }
706
707 - (void)flagsChanged:(NSEvent*)event {
708 [_currentViewController flagsChanged:event];
709 }
710
711 - (void)mouseDown:(NSEvent*)event {
712 [_currentViewController mouseDown:event];
713 }
714
715 - (void)mouseUp:(NSEvent*)event {
716 [_currentViewController mouseUp:event];
717 }
718
719 - (void)mouseDragged:(NSEvent*)event {
720 [_currentViewController mouseDragged:event];
721 }
722
723 - (void)rightMouseDown:(NSEvent*)event {
724 [_currentViewController rightMouseDown:event];
725 }
726
727 - (void)rightMouseUp:(NSEvent*)event {
728 [_currentViewController rightMouseUp:event];
729 }
730
731 - (void)rightMouseDragged:(NSEvent*)event {
732 [_currentViewController rightMouseDragged:event];
733 }
734
735 - (void)otherMouseDown:(NSEvent*)event {
736 [_currentViewController otherMouseDown:event];
737 }
738
739 - (void)otherMouseUp:(NSEvent*)event {
740 [_currentViewController otherMouseUp:event];
741 }
742
743 - (void)otherMouseDragged:(NSEvent*)event {
744 [_currentViewController otherMouseDragged:event];
745 }
746
747 - (void)mouseMoved:(NSEvent*)event {
748 [_currentViewController mouseMoved:event];
749 }
750
751 - (void)scrollWheel:(NSEvent*)event {
752 [_currentViewController scrollWheel:event];
753 }
754
755 - (NSTextInputContext*)inputContext {
756 return _textInputContext;
757 }
758
759 #pragma mark -
760 #pragma mark NSTextInputClient
761
762 - (void)insertTab:(id)sender {
763 // Implementing insertTab: makes AppKit send tab as command, instead of
764 // insertText with '\t'.
765 }
766
767 - (void)insertText:(id)string replacementRange:(NSRange)range {
768 if (_activeModel == nullptr) {
769 return;
770 }
771
772 _eventProducedOutput |= true;
773
774 if (range.location != NSNotFound) {
775 // The selected range can actually have negative numbers, since it can start
776 // at the end of the range if the user selected the text going backwards.
777 // Cast to a signed type to determine whether or not the selection is reversed.
778 long signedLength = static_cast<long>(range.length);
779 long location = range.location;
780 long textLength = _activeModel->text_range().end();
781
782 size_t base = std::clamp(location, 0L, textLength);
783 size_t extent = std::clamp(location + signedLength, 0L, textLength);
784
785 _activeModel->SetSelection(flutter::TextRange(base, extent));
786 } else if (_activeModel->composing() &&
787 !(_activeModel->composing_range() == _activeModel->selection())) {
788 // When confirmed by Japanese IME, string replaces range of composing_range.
789 // If selection == composing_range there is no problem.
790 // If selection ! = composing_range the range of selection is only a part of composing_range.
791 // Since _activeModel->AddText is processed first for selection, the finalization of the
792 // conversion cannot be processed correctly unless selection == composing_range or
793 // selection.collapsed(). Since _activeModel->SetSelection fails if (composing_ &&
794 // !range.collapsed()), selection == composing_range will failed. Therefore, the selection
795 // cursor should only be placed at the beginning of composing_range.
796 flutter::TextRange composing_range = _activeModel->composing_range();
797 _activeModel->SetSelection(flutter::TextRange(composing_range.start()));
798 }
799
800 flutter::TextRange oldSelection = _activeModel->selection();
801 flutter::TextRange composingBeforeChange = _activeModel->composing_range();
802 flutter::TextRange replacedRange(-1, -1);
803
804 std::string textBeforeChange = _activeModel->GetText().c_str();
805 // Input string may be NSString or NSAttributedString.
806 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
807 const NSString* rawString = isAttributedString ? [string string] : string;
808 std::string utf8String = rawString ? [rawString UTF8String] : "";
809 _activeModel->AddText(utf8String);
810 if (_activeModel->composing()) {
811 replacedRange = composingBeforeChange;
812 _activeModel->CommitComposing();
813 _activeModel->EndComposing();
814 } else {
815 replacedRange = range.location == NSNotFound
816 ? flutter::TextRange(oldSelection.base(), oldSelection.extent())
817 : flutter::TextRange(range.location, range.location + range.length);
818 }
819 if (_enableDeltaModel) {
820 [self updateEditStateWithDelta:flutter::TextEditingDelta(textBeforeChange, replacedRange,
821 utf8String)];
822 } else {
823 [self updateEditState];
824 }
825 }
826
827 - (void)doCommandBySelector:(SEL)selector {
828 _eventProducedOutput |= selector != NSSelectorFromString(@"noop:");
829 if ([self respondsToSelector:selector]) {
830 // Note: The more obvious [self performSelector...] doesn't give ARC enough information to
831 // handle retain semantics properly. See https://stackoverflow.com/questions/7017281/ for more
832 // information.
833 IMP imp = [self methodForSelector:selector];
834 void (*func)(id, SEL, id) = reinterpret_cast<void (*)(id, SEL, id)>(imp);
835 func(self, selector, nil);
836 }
837 if (_clientID == nil) {
838 // The macOS may still call selector even if it is no longer a first responder.
839 return;
840 }
841
842 if (selector == @selector(insertNewline:)) {
843 // Already handled through text insertion (multiline) or action.
844 return;
845 }
846
847 // Group multiple selectors received within a single run loop turn so that
848 // the framework can process them in single microtask.
849 NSString* name = NSStringFromSelector(selector);
850 if (_pendingSelectors == nil) {
851 _pendingSelectors = [NSMutableArray array];
852 }
853 [_pendingSelectors addObject:name];
854
855 if (_pendingSelectors.count == 1) {
856 __weak NSMutableArray* selectors = _pendingSelectors;
857 __weak FlutterMethodChannel* channel = _channel;
858 __weak NSNumber* clientID = _clientID;
859
860 CFStringRef runLoopMode = self.customRunLoopMode != nil
861 ? (__bridge CFStringRef)self.customRunLoopMode
862 : kCFRunLoopCommonModes;
863
864 CFRunLoopPerformBlock(CFRunLoopGetMain(), runLoopMode, ^{
865 if (selectors.count > 0) {
866 [channel invokeMethod:kPerformSelectors arguments:@[ clientID, selectors ]];
867 [selectors removeAllObjects];
868 }
869 });
870 }
871 }
872
873 - (void)insertNewline:(id)sender {
874 if (_activeModel == nullptr) {
875 return;
876 }
877 if (_activeModel->composing()) {
878 _activeModel->CommitComposing();
879 _activeModel->EndComposing();
880 }
881 if ([_inputType isEqualToString:kMultilineInputType] &&
882 [_inputAction isEqualToString:kInputActionNewline]) {
883 [self insertText:@"\n" replacementRange:self.selectedRange];
884 }
885 [_channel invokeMethod:kPerformAction arguments:@[ _clientID, _inputAction ]];
886 }
887
888 - (void)setMarkedText:(id)string
889 selectedRange:(NSRange)selectedRange
890 replacementRange:(NSRange)replacementRange {
891 if (_activeModel == nullptr) {
892 return;
893 }
894 std::string textBeforeChange = _activeModel->GetText().c_str();
895 if (!_activeModel->composing()) {
896 _activeModel->BeginComposing();
897 }
898
899 if (replacementRange.location != NSNotFound) {
900 // According to the NSTextInputClient documentation replacementRange is
901 // computed from the beginning of the marked text. That doesn't seem to be
902 // the case, because in situations where the replacementRange is actually
903 // specified (i.e. when switching between characters equivalent after long
904 // key press) the replacementRange is provided while there is no composition.
905 _activeModel->SetComposingRange(
906 flutter::TextRange(replacementRange.location,
907 replacementRange.location + replacementRange.length),
908 0);
909 }
910
911 flutter::TextRange composingBeforeChange = _activeModel->composing_range();
912 flutter::TextRange selectionBeforeChange = _activeModel->selection();
913
914 // Input string may be NSString or NSAttributedString.
915 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
916 const NSString* rawString = isAttributedString ? [string string] : string;
917 _activeModel->UpdateComposingText(
918 (const char16_t*)[rawString cStringUsingEncoding:NSUTF16StringEncoding],
919 flutter::TextRange(selectedRange.location, selectedRange.location + selectedRange.length));
920
921 if (_enableDeltaModel) {
922 std::string marked_text = [rawString UTF8String];
923 [self updateEditStateWithDelta:flutter::TextEditingDelta(textBeforeChange,
924 selectionBeforeChange.collapsed()
925 ? composingBeforeChange
926 : selectionBeforeChange,
927 marked_text)];
928 } else {
929 [self updateEditState];
930 }
931 }
932
933 - (void)unmarkText {
934 if (_activeModel == nullptr) {
935 return;
936 }
937 _activeModel->CommitComposing();
938 _activeModel->EndComposing();
939 if (_enableDeltaModel) {
940 [self updateEditStateWithDelta:flutter::TextEditingDelta(_activeModel->GetText().c_str())];
941 } else {
942 [self updateEditState];
943 }
944 }
945
946 - (NSRange)markedRange {
947 if (_activeModel == nullptr) {
948 return NSMakeRange(NSNotFound, 0);
949 }
950 return NSMakeRange(
951 _activeModel->composing_range().base(),
952 _activeModel->composing_range().extent() - _activeModel->composing_range().base());
953 }
954
955 - (BOOL)hasMarkedText {
956 return _activeModel != nullptr && _activeModel->composing_range().length() > 0;
957 }
958
959 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
960 actualRange:(NSRangePointer)actualRange {
961 if (_activeModel == nullptr) {
962 return nil;
963 }
964 NSString* text = [NSString stringWithUTF8String:_activeModel->GetText().c_str()];
965 if (range.location >= text.length) {
966 return nil;
967 }
968 range.length = std::min(range.length, text.length - range.location);
969 if (actualRange != nil) {
970 *actualRange = range;
971 }
972 NSString* substring = [text substringWithRange:range];
973 return [[NSAttributedString alloc] initWithString:substring attributes:nil];
974 }
975
976 - (NSArray<NSString*>*)validAttributesForMarkedText {
977 return @[];
978 }
979
980 // Returns the bounding CGRect of the transformed incomingRect, in screen
981 // coordinates.
982 - (CGRect)screenRectFromFrameworkTransform:(CGRect)incomingRect {
983 CGPoint points[] = {
984 incomingRect.origin,
985 CGPointMake(incomingRect.origin.x, incomingRect.origin.y + incomingRect.size.height),
986 CGPointMake(incomingRect.origin.x + incomingRect.size.width, incomingRect.origin.y),
987 CGPointMake(incomingRect.origin.x + incomingRect.size.width,
988 incomingRect.origin.y + incomingRect.size.height)};
989
990 CGPoint origin = CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX);
991 CGPoint farthest = CGPointMake(-CGFLOAT_MAX, -CGFLOAT_MAX);
992
993 for (int i = 0; i < 4; i++) {
994 const CGPoint point = points[i];
995
996 CGFloat x = _editableTransform.m11 * point.x + _editableTransform.m21 * point.y +
997 _editableTransform.m41;
998 CGFloat y = _editableTransform.m12 * point.x + _editableTransform.m22 * point.y +
999 _editableTransform.m42;
1000
1001 const CGFloat w = _editableTransform.m14 * point.x + _editableTransform.m24 * point.y +
1002 _editableTransform.m44;
1003
1004 if (w == 0.0) {
1005 return CGRectZero;
1006 } else if (w != 1.0) {
1007 x /= w;
1008 y /= w;
1009 }
1010
1011 origin.x = MIN(origin.x, x);
1012 origin.y = MIN(origin.y, y);
1013 farthest.x = MAX(farthest.x, x);
1014 farthest.y = MAX(farthest.y, y);
1015 }
1016
1017 const NSView* fromView = _originalViewController.flutterView;
1018 const CGRect rectInWindow = [fromView
1019 convertRect:CGRectMake(origin.x, origin.y, farthest.x - origin.x, farthest.y - origin.y)
1020 toView:nil];
1021 NSWindow* window = fromView.window;
1022 return window ? [window convertRectToScreen:rectInWindow] : rectInWindow;
1023 }
1024
1025 - (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange {
1026 // This only determines position of caret instead of any arbitrary range, but it's enough
1027 // to properly position accent selection popup
1028 return !_originalViewController.viewLoaded || CGRectEqualToRect(_caretRect, CGRectNull)
1029 ? CGRectZero
1030 : [self screenRectFromFrameworkTransform:_caretRect];
1031 }
1032
1033 - (NSUInteger)characterIndexForPoint:(NSPoint)point {
1034 // TODO(cbracken): Implement.
1035 // Note: This function can't easily be implemented under the system-message architecture.
1036 return 0;
1037 }
1038
1039 @end
FlutterResult
void(^ FlutterResult)(id _Nullable result)
Definition: FlutterChannels.h:194
FlutterMethodNotImplemented
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
FlutterCodecs.h
FlutterTextInputPlugin.h
NS_ENUM
typedef NS_ENUM(NSUInteger, FlutterTextAffinity)
Definition: FlutterTextInputPlugin.mm:76
kViewId
static NSString *const kViewId
Definition: FlutterTextInputPlugin.mm:41
kUpdateEditStateWithDeltasResponseMethod
static NSString *const kUpdateEditStateWithDeltasResponseMethod
Definition: FlutterTextInputPlugin.mm:34
kTextKey
static NSString *const kTextKey
Definition: FlutterTextInputPlugin.mm:53
kMultilineInputType
static NSString *const kMultilineInputType
Definition: FlutterTextInputPlugin.mm:38
kAutofillHints
static NSString *const kAutofillHints
Definition: FlutterTextInputPlugin.mm:61
kAutofillEditingValue
static NSString *const kAutofillEditingValue
Definition: FlutterTextInputPlugin.mm:60
kSecureTextEntry
static NSString *const kSecureTextEntry
Definition: FlutterTextInputPlugin.mm:42
kShowMethod
static NSString *const kShowMethod
Definition: FlutterTextInputPlugin.mm:27
kPerformSelectors
static NSString *const kPerformSelectors
Definition: FlutterTextInputPlugin.mm:37
kSetEditableSizeAndTransform
static NSString *const kSetEditableSizeAndTransform
Definition: FlutterTextInputPlugin.mm:31
GetTextContentType
static NSTextContentType GetTextContentType(NSDictionary *configuration) API_AVAILABLE(macos(11.0))
Definition: FlutterTextInputPlugin.mm:106
kSelectionBaseKey
static NSString *const kSelectionBaseKey
Definition: FlutterTextInputPlugin.mm:47
markerKey
static char markerKey
Definition: FlutterTextInputPlugin.mm:190
kSelectionAffinityKey
static NSString *const kSelectionAffinityKey
Definition: FlutterTextInputPlugin.mm:49
kAssociatedAutofillFields
static NSString *const kAssociatedAutofillFields
Definition: FlutterTextInputPlugin.mm:55
kSetEditingStateMethod
static NSString *const kSetEditingStateMethod
Definition: FlutterTextInputPlugin.mm:30
kComposingExtentKey
static NSString *const kComposingExtentKey
Definition: FlutterTextInputPlugin.mm:52
kAutofillId
static NSString *const kAutofillId
Definition: FlutterTextInputPlugin.mm:59
kSelectionExtentKey
static NSString *const kSelectionExtentKey
Definition: FlutterTextInputPlugin.mm:48
kClearClientMethod
static NSString *const kClearClientMethod
Definition: FlutterTextInputPlugin.mm:29
kEnableDeltaModel
static NSString *const kEnableDeltaModel
Definition: FlutterTextInputPlugin.mm:44
kPerformAction
static NSString *const kPerformAction
Definition: FlutterTextInputPlugin.mm:36
GetAutofillHint
static NSString * GetAutofillHint(NSDictionary *autofill)
Definition: FlutterTextInputPlugin.mm:99
kInputActionNewline
static NSString *const kInputActionNewline
Definition: FlutterTextInputPlugin.mm:68
kTextInputChannel
static NSString *const kTextInputChannel
Definition: FlutterTextInputPlugin.mm:22
kTextAffinityDownstream
static NSString *const kTextAffinityDownstream
Definition: FlutterTextInputPlugin.mm:64
kTextInputType
static NSString *const kTextInputType
Definition: FlutterTextInputPlugin.mm:45
kSetClientMethod
static NSString *const kSetClientMethod
Definition: FlutterTextInputPlugin.mm:26
kTextAffinityUpstream
static NSString *const kTextAffinityUpstream
Definition: FlutterTextInputPlugin.mm:65
kTransformKey
static NSString *const kTransformKey
Definition: FlutterTextInputPlugin.mm:54
kAutofillProperties
static NSString *const kAutofillProperties
Definition: FlutterTextInputPlugin.mm:58
kUpdateEditStateResponseMethod
static NSString *const kUpdateEditStateResponseMethod
Definition: FlutterTextInputPlugin.mm:33
EnableAutocompleteForTextInputConfiguration
static BOOL EnableAutocompleteForTextInputConfiguration(NSDictionary *configuration)
Definition: FlutterTextInputPlugin.mm:132
EnableAutocomplete
static BOOL EnableAutocomplete(NSDictionary *configuration)
Definition: FlutterTextInputPlugin.mm:160
kSetCaretRect
static NSString *const kSetCaretRect
Definition: FlutterTextInputPlugin.mm:32
kTextInputAction
static NSString *const kTextInputAction
Definition: FlutterTextInputPlugin.mm:43
RangeFromBaseExtent
static flutter::TextRange RangeFromBaseExtent(NSNumber *base, NSNumber *extent, const flutter::TextRange &range)
Definition: FlutterTextInputPlugin.mm:86
kComposingBaseKey
static NSString *const kComposingBaseKey
Definition: FlutterTextInputPlugin.mm:51
kHideMethod
static NSString *const kHideMethod
Definition: FlutterTextInputPlugin.mm:28
kTextInputTypeName
static NSString *const kTextInputTypeName
Definition: FlutterTextInputPlugin.mm:46
kSelectionIsDirectionalKey
static NSString *const kSelectionIsDirectionalKey
Definition: FlutterTextInputPlugin.mm:50
FlutterTextInputSemanticsObject.h
FlutterViewIdentifier
int64_t FlutterViewIdentifier
Definition: FlutterViewController.h:21
FlutterViewController_Internal.h
NSView+ClipsToBounds.h
FlutterTextInputPlugin()::_previouslyPressedFlags
uint64_t _previouslyPressedFlags
Definition: FlutterTextInputPlugin.mm:245
FlutterTextInputPlugin()::_textInputContext
NSTextInputContext * _textInputContext
Definition: FlutterTextInputPlugin.mm:211
FlutterTextInputPlugin()::_shown
BOOL _shown
Definition: FlutterTextInputPlugin.mm:240
FlutterTextInputPlugin()::_editableTransform
CATransform3D _editableTransform
Definition: FlutterTextInputPlugin.mm:299
FlutterTextInputPlugin()::_clientID
NSNumber * _clientID
Definition: FlutterTextInputPlugin.mm:255
FlutterTextInputPlugin()::_activeModel
std::unique_ptr< flutter::TextInputModel > _activeModel
Definition: FlutterTextInputPlugin.mm:294
FlutterTextInputPlugin()::_pendingSelectors
NSMutableArray * _pendingSelectors
Definition: FlutterTextInputPlugin.mm:289
FlutterTextInputPlugin()::_textAffinity
FlutterTextAffinity _textAffinity
Definition: FlutterTextInputPlugin.mm:250
FlutterTextInputPlugin()::_inputAction
NSString * _inputAction
Definition: FlutterTextInputPlugin.mm:267
FlutterTextInputPlugin()::_originalViewController
__weak FlutterViewController * _originalViewController
Definition: FlutterTextInputPlugin.mm:228
FlutterTextInputPlugin()::_currentViewController
__weak FlutterViewController * _currentViewController
Definition: FlutterTextInputPlugin.mm:221
FlutterTextInputPlugin()::_caretRect
CGRect _caretRect
Definition: FlutterTextInputPlugin.mm:304
FlutterTextInputPlugin()::_channel
FlutterMethodChannel * _channel
Definition: FlutterTextInputPlugin.mm:216
FlutterTextInputPlugin()::_delegate
__weak id< FlutterTextInputPluginDelegate > _delegate
Definition: FlutterTextInputPlugin.mm:233
FlutterTextInputPlugin()::_enableDeltaModel
BOOL _enableDeltaModel
Definition: FlutterTextInputPlugin.mm:282
FlutterTextInputPlugin()::_eventProducedOutput
BOOL _eventProducedOutput
Definition: FlutterTextInputPlugin.mm:274
FlutterTextInputPlugin()::_inputType
NSString * _inputType
Definition: FlutterTextInputPlugin.mm:261
FlutterTextInputPlugin(TestMethods)::customRunLoopMode
NSString * customRunLoopMode
Definition: FlutterTextInputPlugin.h:94
-[FlutterTextInputPlugin(TestMethods) editingState]
NSDictionary * editingState()
NSEvent(KeyEquivalentMarker)
Definition: FlutterTextInputPlugin.mm:173
-[NSEvent(KeyEquivalentMarker) markAsKeyEquivalent]
void markAsKeyEquivalent()
Definition: FlutterTextInputPlugin.mm:192
-[NSEvent(KeyEquivalentMarker) isKeyEquivalent]
BOOL isKeyEquivalent()
Definition: FlutterTextInputPlugin.mm:196
flutter::TextRange
Definition: text_range.h:19
flutter::TextRange::start
size_t start() const
Definition: text_range.h:42
flutter::TextRange::base
size_t base() const
Definition: text_range.h:30
flutter::TextRange::collapsed
bool collapsed() const
Definition: text_range.h:77
flutter::TextRange::extent
size_t extent() const
Definition: text_range.h:36
FlutterError
Definition: FlutterCodecs.h:247
FlutterJSONMethodCodec
Definition: FlutterCodecs.h:455
FlutterMethodCall
Definition: FlutterCodecs.h:221
FlutterMethodCall::method
NSString * method
Definition: FlutterCodecs.h:233
FlutterMethodCall::arguments
id arguments
Definition: FlutterCodecs.h:238
FlutterMethodChannel
Definition: FlutterChannels.h:221
+[FlutterMethodChannel methodChannelWithName:binaryMessenger:codec:]
instancetype methodChannelWithName:binaryMessenger:codec:(NSString *name,[binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger,[codec] NSObject< FlutterMethodCodec > *codec)
FlutterTextInputPlugin
Definition: FlutterTextInputPlugin.h:42
-[FlutterTextInputPlugin isFirstResponder]
BOOL isFirstResponder()
Definition: FlutterTextInputPlugin.mm:388
FlutterViewController
Definition: FlutterViewController.h:73
flutter
Definition: AccessibilityBridgeMac.h:16
FlutterTextInputPluginDelegate-p
Definition: FlutterTextInputPlugin.h:18
text_editing_delta.h
text_input_model.h