5#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h"
7#import <Foundation/Foundation.h>
8#import <objc/message.h>
13#include "flutter/fml/platform/darwin/string_range_sanitization.h"
14#include "flutter/shell/platform/common/text_editing_delta.h"
15#include "flutter/shell/platform/common/text_input_model.h"
16#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
17#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputSemanticsObject.h"
18#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
19#import "flutter/shell/platform/darwin/macos/framework/Source/NSView+ClipsToBounds.h"
23#pragma mark - TextInput channel method names
34 @"TextInputClient.updateEditingStateWithDeltas";
39#pragma mark - TextInputConfiguration field names
74typedef NS_ENUM(NSUInteger, FlutterTextAffinity) {
75 kFlutterTextAffinityUpstream,
76 kFlutterTextAffinityDownstream
79#pragma mark - Static functions
87 if (
base == nil || extent == nil) {
90 if (
base.intValue == -1 && extent.intValue == -1) {
99 return hints.count > 0 ? hints[0] : nil;
110 if ([hint isEqualToString:
@"username"]) {
111 return NSTextContentTypeUsername;
113 if ([hint isEqualToString:
@"password"]) {
114 return NSTextContentTypePassword;
116 if ([hint isEqualToString:
@"oneTimeCode"]) {
117 return NSTextContentTypeOneTimeCode;
122 return NSTextContentTypePassword;
138 if (autofill == nil) {
145 if ([hint isEqualToString:
@"password"] || [hint isEqualToString:
@"username"]) {
169#pragma mark - NSEvent (KeyEquivalentMarker) protocol
191 objc_setAssociatedObject(
self, &
markerKey, @
true, OBJC_ASSOCIATION_RETAIN);
195 return [objc_getAssociatedObject(self, &markerKey) boolValue] == YES;
200#pragma mark - FlutterTextInputPlugin private interface
287- (void)setEditingState:(NSDictionary*)state;
299- (void)updateEditStateWithDelta:(const
flutter::TextEditingDelta)delta;
323#pragma mark - FlutterTextInputPlugin
329 std::unique_ptr<flutter::TextInputModel> _activeModel;
345 self = [
super initWithFrame:NSZeroRect];
346 self.clipsToBounds = YES;
359 [_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
360 [unsafeSelf handleMethodCall:call result:result];
362 _textInputContext = [[NSTextInputContext alloc] initWithClient:unsafeSelf];
363 _previouslyPressedFlags = 0;
373- (
BOOL)isFirstResponder {
374 if (!
self.flutterViewController.viewLoaded) {
377 return [
self.flutterViewController.view.window firstResponder] ==
self;
381 [_channel setMethodCallHandler:nil];
384#pragma mark - Private
386- (void)resignAndRemoveFromSuperview {
387 if (
self.superview != nil) {
388 [
self.window makeFirstResponder:_flutterViewController.flutterView];
389 [
self removeFromSuperview];
395 NSString* method =
call.method;
397 if (!
call.arguments[0] || !
call.arguments[1]) {
399 errorWithCode:
@"error"
401 details:
@"Missing arguments while trying to set a text input client"]);
404 NSNumber* clientID =
call.arguments[0];
405 if (clientID != nil) {
406 NSDictionary* config =
call.arguments[1];
408 _clientID = clientID;
409 _inputAction = config[kTextInputAction];
410 _enableDeltaModel = [config[kEnableDeltaModel] boolValue];
411 NSDictionary* inputTypeInfo = config[kTextInputType];
412 _inputType = inputTypeInfo[kTextInputTypeName];
413 self.textAffinity = kFlutterTextAffinityUpstream;
415 if (@available(macOS 11.0, *)) {
419 _activeModel = std::make_unique<flutter::TextInputModel>();
425 if (_client == nil) {
426 [_flutterViewController.view addSubview:self];
428 [
self.window makeFirstResponder:self];
431 [
self resignAndRemoveFromSuperview];
434 [
self resignAndRemoveFromSuperview];
436 if (_activeModel && _activeModel->composing()) {
437 _activeModel->CommitComposing();
438 _activeModel->EndComposing();
440 [_textInputContext discardMarkedText];
444 _enableDeltaModel = NO;
446 _activeModel =
nullptr;
449 [
self setEditingState:state];
452 [
self setEditableTransform:state[kTransformKey]];
454 NSDictionary*
rect =
call.arguments;
455 [
self updateCaretRect:rect];
462- (void)setEditableTransform:(NSArray*)matrix {
465 transform->m11 = [matrix[0] doubleValue];
466 transform->m12 = [matrix[1] doubleValue];
467 transform->m13 = [matrix[2] doubleValue];
468 transform->m14 = [matrix[3] doubleValue];
470 transform->m21 = [matrix[4] doubleValue];
471 transform->m22 = [matrix[5] doubleValue];
472 transform->m23 = [matrix[6] doubleValue];
473 transform->m24 = [matrix[7] doubleValue];
475 transform->m31 = [matrix[8] doubleValue];
476 transform->m32 = [matrix[9] doubleValue];
477 transform->m33 = [matrix[10] doubleValue];
478 transform->m34 = [matrix[11] doubleValue];
480 transform->m41 = [matrix[12] doubleValue];
481 transform->m42 = [matrix[13] doubleValue];
482 transform->m43 = [matrix[14] doubleValue];
483 transform->m44 = [matrix[15] doubleValue];
486- (void)updateCaretRect:(NSDictionary*)dictionary {
487 NSAssert(dictionary[
@"x"] != nil && dictionary[
@"y"] != nil && dictionary[
@"width"] != nil &&
488 dictionary[
@"height"] != nil,
489 @"Expected a dictionary representing a CGRect, got %@", dictionary);
490 _caretRect = CGRectMake([dictionary[
@"x"] doubleValue], [dictionary[
@"y"] doubleValue],
491 [dictionary[
@"width"] doubleValue], [dictionary[
@"height"] doubleValue]);
494- (void)setEditingState:(NSDictionary*)state {
495 NSString* selectionAffinity =
state[kSelectionAffinityKey];
496 if (selectionAffinity != nil) {
497 _textAffinity = [selectionAffinity isEqualToString:kTextAffinityUpstream]
498 ? kFlutterTextAffinityUpstream
499 : kFlutterTextAffinityDownstream;
506 _activeModel->SetSelection(selected_range);
511 const bool wasComposing = _activeModel->composing();
512 _activeModel->SetText([
text UTF8String], selected_range, composing_range);
513 if (composing_range.
collapsed() && wasComposing) {
514 [_textInputContext discardMarkedText];
516 [_client startEditing];
518 [
self updateTextAndSelection];
521- (NSDictionary*)editingState {
522 if (_activeModel ==
nullptr) {
526 NSString*
const textAffinity = [
self textAffinityString];
528 int composingBase = _activeModel->composing() ? _activeModel->composing_range().base() : -1;
529 int composingExtent = _activeModel->composing() ? _activeModel->composing_range().extent() : -1;
538 kTextKey : [NSString stringWithUTF8String:_activeModel->GetText().c_str()] ?: [NSNull null],
542- (void)updateEditState {
543 if (_activeModel ==
nullptr) {
547 NSDictionary*
state = [
self editingState];
548 [_channel invokeMethod:kUpdateEditStateResponseMethod arguments:@[
self.clientID, state ]];
549 [
self updateTextAndSelection];
552- (void)updateEditStateWithDelta:(const
flutter::TextEditingDelta)delta {
553 NSUInteger selectionBase = _activeModel->selection().base();
554 NSUInteger selectionExtent = _activeModel->selection().extent();
555 int composingBase = _activeModel->composing() ? _activeModel->composing_range().base() : -1;
556 int composingExtent = _activeModel->composing() ? _activeModel->composing_range().extent() : -1;
558 NSString*
const textAffinity = [
self textAffinityString];
560 NSDictionary* deltaToFramework = @{
561 @"oldText" : @(
delta.old_text().c_str()),
562 @"deltaText" : @(
delta.delta_text().c_str()),
563 @"deltaStart" : @(
delta.delta_start()),
564 @"deltaEnd" : @(
delta.delta_end()),
565 @"selectionBase" : @(selectionBase),
566 @"selectionExtent" : @(selectionExtent),
567 @"selectionAffinity" : textAffinity,
568 @"selectionIsDirectional" : @(
false),
569 @"composingBase" : @(composingBase),
570 @"composingExtent" : @(composingExtent),
573 NSDictionary* deltas = @{
574 @"deltas" : @[ deltaToFramework ],
577 [_channel invokeMethod:kUpdateEditStateWithDeltasResponseMethod
578 arguments:@[
self.clientID, deltas ]];
579 [
self updateTextAndSelection];
582- (void)updateTextAndSelection {
583 NSAssert(_activeModel !=
nullptr,
@"Flutter text model must not be null.");
584 NSString*
text = @(_activeModel->GetText().data());
585 int start = _activeModel->selection().base();
586 int extend = _activeModel->selection().extent();
587 NSRange selection = NSMakeRange(
MIN(
start, extend), ABS(
start - extend));
593 [_client updateString:text withSelection:selection];
596 [
self setSelectedRange:selection];
600- (NSString*)textAffinityString {
605- (
BOOL)handleKeyEvent:(NSEvent*)event {
606 if (
event.type == NSEventTypeKeyUp ||
607 (
event.type == NSEventTypeFlagsChanged &&
event.modifierFlags < _previouslyPressedFlags)) {
610 _previouslyPressedFlags =
event.modifierFlags;
615 _eventProducedOutput = NO;
616 BOOL res = [_textInputContext handleEvent:event];
626 bool is_navigation =
event.modifierFlags & NSEventModifierFlagFunction &&
627 event.modifierFlags & NSEventModifierFlagNumericPad;
628 bool is_navigation_in_ime = is_navigation &&
self.hasMarkedText;
630 if (
event.isKeyEquivalent && !is_navigation_in_ime && !_eventProducedOutput) {
637#pragma mark NSResponder
639- (void)keyDown:(NSEvent*)event {
640 [
self.flutterViewController keyDown:event];
643- (void)keyUp:(NSEvent*)event {
644 [
self.flutterViewController keyUp:event];
647- (
BOOL)performKeyEquivalent:(NSEvent*)event {
660 [event markAsKeyEquivalent];
661 [
self.flutterViewController keyDown:event];
665- (void)flagsChanged:(NSEvent*)event {
666 [
self.flutterViewController flagsChanged:event];
669- (void)mouseDown:(NSEvent*)event {
670 [
self.flutterViewController mouseDown:event];
673- (void)mouseUp:(NSEvent*)event {
674 [
self.flutterViewController mouseUp:event];
677- (void)mouseDragged:(NSEvent*)event {
678 [
self.flutterViewController mouseDragged:event];
681- (void)rightMouseDown:(NSEvent*)event {
682 [
self.flutterViewController rightMouseDown:event];
685- (void)rightMouseUp:(NSEvent*)event {
686 [
self.flutterViewController rightMouseUp:event];
689- (void)rightMouseDragged:(NSEvent*)event {
690 [
self.flutterViewController rightMouseDragged:event];
693- (void)otherMouseDown:(NSEvent*)event {
694 [
self.flutterViewController otherMouseDown:event];
697- (void)otherMouseUp:(NSEvent*)event {
698 [
self.flutterViewController otherMouseUp:event];
701- (void)otherMouseDragged:(NSEvent*)event {
702 [
self.flutterViewController otherMouseDragged:event];
705- (void)mouseMoved:(NSEvent*)event {
706 [
self.flutterViewController mouseMoved:event];
709- (void)scrollWheel:(NSEvent*)event {
710 [
self.flutterViewController scrollWheel:event];
713- (NSTextInputContext*)inputContext {
714 return _textInputContext;
718#pragma mark NSTextInputClient
720- (void)insertTab:(
id)sender {
725- (void)insertText:(
id)string replacementRange:(NSRange)range {
726 if (_activeModel ==
nullptr) {
730 _eventProducedOutput |=
true;
732 if (range.location != NSNotFound) {
736 long signedLength =
static_cast<long>(range.length);
737 long location = range.location;
738 long textLength = _activeModel->text_range().end();
741 size_t extent =
std::clamp(location + signedLength, 0
L, textLength);
750 std::string textBeforeChange = _activeModel->GetText().c_str();
751 std::string utf8String = [string UTF8String];
752 _activeModel->AddText(utf8String);
753 if (_activeModel->composing()) {
754 replacedRange = composingBeforeChange;
755 _activeModel->CommitComposing();
756 _activeModel->EndComposing();
758 replacedRange = range.location == NSNotFound
762 if (_enableDeltaModel) {
763 [
self updateEditStateWithDelta:flutter::TextEditingDelta(textBeforeChange, replacedRange,
766 [
self updateEditState];
770- (void)doCommandBySelector:(
SEL)selector {
771 _eventProducedOutput |= selector != NSSelectorFromString(
@"noop:");
772 if ([
self respondsToSelector:selector]) {
776 IMP imp = [
self methodForSelector:selector];
777 void (*func)(
id, SEL,
id) =
reinterpret_cast<void (*)(
id,
SEL,
id)
>(imp);
778 func(
self, selector, nil);
780 if (
self.clientID == nil) {
785 if (selector ==
@selector(insertNewline:)) {
792 NSString*
name = NSStringFromSelector(selector);
793 if (_pendingSelectors == nil) {
794 _pendingSelectors = [NSMutableArray array];
796 [_pendingSelectors addObject:name];
798 if (_pendingSelectors.count == 1) {
799 __weak NSMutableArray* selectors = _pendingSelectors;
801 __weak NSNumber* clientID =
self.clientID;
803 CFStringRef runLoopMode =
self.customRunLoopMode != nil
804 ? (__bridge CFStringRef)
self.customRunLoopMode
805 : kCFRunLoopCommonModes;
807 CFRunLoopPerformBlock(CFRunLoopGetMain(), runLoopMode, ^{
808 if (selectors.count > 0) {
809 [channel invokeMethod:kPerformSelectors arguments:@[ clientID, selectors ]];
810 [selectors removeAllObjects];
816- (void)insertNewline:(
id)sender {
817 if (_activeModel ==
nullptr) {
820 if (_activeModel->composing()) {
821 _activeModel->CommitComposing();
822 _activeModel->EndComposing();
826 [
self insertText:@"\n" replacementRange:self.selectedRange];
828 [_channel invokeMethod:kPerformAction arguments:@[
self.clientID, self.inputAction ]];
831- (void)setMarkedText:(
id)string
832 selectedRange:(NSRange)selectedRange
833 replacementRange:(NSRange)replacementRange {
834 if (_activeModel ==
nullptr) {
837 std::string textBeforeChange = _activeModel->GetText().c_str();
838 if (!_activeModel->composing()) {
839 _activeModel->BeginComposing();
842 if (replacementRange.location != NSNotFound) {
848 _activeModel->SetComposingRange(
850 replacementRange.location + replacementRange.length),
858 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
859 const NSString* rawString = isAttributedString ? [string string] : string;
860 _activeModel->UpdateComposingText(
861 (
const char16_t*)[rawString cStringUsingEncoding:NSUTF16StringEncoding],
862 flutter::TextRange(selectedRange.location, selectedRange.location + selectedRange.length));
864 if (_enableDeltaModel) {
865 std::string marked_text = [rawString UTF8String];
866 [
self updateEditStateWithDelta:flutter::TextEditingDelta(textBeforeChange,
867 selectionBeforeChange.collapsed()
868 ? composingBeforeChange
869 : selectionBeforeChange,
872 [
self updateEditState];
877 if (_activeModel ==
nullptr) {
880 _activeModel->CommitComposing();
881 _activeModel->EndComposing();
882 if (_enableDeltaModel) {
883 [
self updateEditStateWithDelta:flutter::TextEditingDelta(_activeModel->GetText().c_str())];
885 [
self updateEditState];
889- (NSRange)markedRange {
890 if (_activeModel ==
nullptr) {
891 return NSMakeRange(NSNotFound, 0);
894 _activeModel->composing_range().base(),
895 _activeModel->composing_range().extent() - _activeModel->composing_range().base());
898- (
BOOL)hasMarkedText {
899 return _activeModel !=
nullptr && _activeModel->composing_range().length() > 0;
902- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
903 actualRange:(NSRangePointer)actualRange {
904 if (_activeModel ==
nullptr) {
907 if (actualRange != nil) {
908 *actualRange = range;
910 NSString*
text = [NSString stringWithUTF8String:_activeModel->GetText().c_str()];
911 NSString* substring = [text substringWithRange:range];
912 return [[NSAttributedString alloc] initWithString:substring attributes:nil];
915- (NSArray<NSString*>*)validAttributesForMarkedText {
921- (CGRect)screenRectFromFrameworkTransform:(CGRect)incomingRect {
924 CGPointMake(incomingRect.origin.x, incomingRect.origin.y + incomingRect.size.height),
925 CGPointMake(incomingRect.origin.x + incomingRect.size.width, incomingRect.origin.y),
926 CGPointMake(incomingRect.origin.x + incomingRect.size.width,
927 incomingRect.origin.y + incomingRect.size.height)};
929 CGPoint origin = CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX);
930 CGPoint farthest = CGPointMake(-CGFLOAT_MAX, -CGFLOAT_MAX);
932 for (
int i = 0;
i < 4;
i++) {
933 const CGPoint point =
points[i];
945 }
else if (
w != 1.0) {
950 origin.x =
MIN(origin.x,
x);
951 origin.y =
MIN(origin.y,
y);
952 farthest.x =
MAX(farthest.x,
x);
953 farthest.y =
MAX(farthest.y,
y);
956 const NSView* fromView =
self.flutterViewController.flutterView;
957 const CGRect rectInWindow = [fromView
958 convertRect:CGRectMake(origin.x, origin.y, farthest.x - origin.x, farthest.y - origin.y)
960 NSWindow*
window = fromView.window;
961 return window ? [window convertRectToScreen:rectInWindow] : rectInWindow;
964- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange {
967 return !
self.flutterViewController.viewLoaded || CGRectEqualToRect(
_caretRect, CGRectNull)
969 : [
self screenRectFromFrameworkTransform:_caretRect];
972- (NSUInteger)characterIndexForPoint:(NSPoint)point {
void(^ FlutterResult)(id _Nullable result)
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
static const int points[]
static unsigned clamp(SkFixed fx, int max)
FlutterMethodChannel * _channel
NSString * customRunLoopMode
NSTextInputContext * textInputContext
void markAsKeyEquivalent()
instancetype sharedInstance()
instancetype methodChannelWithName:binaryMessenger:codec:(NSString *name,[binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger,[codec] NSObject< FlutterMethodCodec > *codec)
FlutterViewController * flutterViewController
void updateTextAndSelection()
FlutterTextAffinity textAffinity
NSString * textAffinityString()
uint64_t previouslyPressedFlags
NSMutableArray * pendingSelectors
FlutterMethodChannel * channel
FlutterViewController * viewController
typedef NS_ENUM(NSInteger, FlutterAutofillType)
static NSString *const kUpdateEditStateWithDeltasResponseMethod
static NSString *const kTextKey
static NSString *const kMultilineInputType
static NSString *const kAutofillHints
static NSString *const kAutofillEditingValue
static NSString *const kSecureTextEntry
static NSString *const kShowMethod
static NSString *const kPerformSelectors
CATransform3D _editableTransform
static NSString * GetAutofillHint(NSDictionary *autofill)
static NSString *const kSetEditableSizeAndTransform
static NSTextContentType GetTextContentType(NSDictionary *configuration) API_AVAILABLE(macos(11.0))
static NSString *const kSelectionBaseKey
static NSString *const kSelectionAffinityKey
static NSString *const kAssociatedAutofillFields
static NSString *const kSetEditingStateMethod
static NSString *const kComposingExtentKey
static NSString *const kAutofillId
static NSString *const kSelectionExtentKey
static NSString *const kClearClientMethod
static NSString *const kEnableDeltaModel
static NSString *const kPerformAction
static NSString *const kInputActionNewline
static NSString *const kTextInputChannel
static NSString *const kTextAffinityDownstream
static NSString *const kTextInputType
static NSString *const kSetClientMethod
static NSString *const kTextAffinityUpstream
static NSString *const kTransformKey
static NSString *const kAutofillProperties
static NSString *const kUpdateEditStateResponseMethod
static BOOL EnableAutocompleteForTextInputConfiguration(NSDictionary *configuration)
static BOOL EnableAutocomplete(NSDictionary *configuration)
static NSString *const kSetCaretRect
static NSString *const kTextInputAction
static flutter::TextRange RangeFromBaseExtent(NSNumber *base, NSNumber *extent, const flutter::TextRange &range)
static NSString *const kComposingBaseKey
static NSString *const kHideMethod
static NSString *const kTextInputTypeName
static NSString *const kSelectionIsDirectionalKey
sk_sp< SkBlender > blender SkRect rect
DEF_SWITCHES_START aot vmservice shared library name
API_AVAILABLE(ios(14.0), macos(11.0)) static NSString *MTLCommandEncoderErrorStateToString(MTLCommandEncoderErrorState state)
SkRange< size_t > TextRange
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)