System mouse cursor: macOS (#18131)

* Untested macOS impl

* Workable mac

* Modernize

* Simplify

* Address comments

* Refactor with FlutterPlugin

* Cached dict

* Update per comments

* format

* Remove device arg

* Format

* Update license

* Update licences

* Doc update
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index 1ef5628..7ed3bbe 100755
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -959,6 +959,8 @@
 FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h
 FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.h
 FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.mm
+FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h
+FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm
 FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputModel.h
 FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputModel.mm
 FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h
diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn
index dccf1ff..2e5a9cc 100644
--- a/shell/platform/darwin/macos/BUILD.gn
+++ b/shell/platform/darwin/macos/BUILD.gn
@@ -54,6 +54,8 @@
     "framework/Source/FlutterEngine_Internal.h",
     "framework/Source/FlutterExternalTextureGL.h",
     "framework/Source/FlutterExternalTextureGL.mm",
+    "framework/Source/FlutterMouseCursorPlugin.h",
+    "framework/Source/FlutterMouseCursorPlugin.mm",
     "framework/Source/FlutterTextInputModel.h",
     "framework/Source/FlutterTextInputModel.mm",
     "framework/Source/FlutterTextInputPlugin.h",
diff --git a/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h b/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h
new file mode 100644
index 0000000..73b1d63
--- /dev/null
+++ b/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h
@@ -0,0 +1,18 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h"
+#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h"
+
+/**
+ * A plugin to handle mouse cursor.
+ *
+ * Responsible for bridging the native macOS mouse cursor system with the
+ * Flutter framework mouse cursor classes, via system channels.
+ */
+@interface FlutterMouseCursorPlugin : NSObject <FlutterPlugin>
+
+@end
diff --git a/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm
new file mode 100644
index 0000000..6bf1972
--- /dev/null
+++ b/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm
@@ -0,0 +1,153 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <objc/message.h>
+
+#import "FlutterMouseCursorPlugin.h"
+#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
+
+static NSString* const kMouseCursorChannel = @"flutter/mousecursor";
+
+static NSString* const kActivateSystemCursorMethod = @"activateSystemCursor";
+static NSString* const kKindKey = @"kind";
+
+static NSString* const kKindValueNone = @"none";
+
+/**
+ * Maps a Flutter's constant to a platform's cursor object.
+ *
+ * Returns the arrow cursor for unknown constants, including kSystemShapeNone.
+ */
+static NSCursor* GetCursorForKind(NSString* kind) {
+  // The following mapping must be kept in sync with Flutter framework's
+  // mouse_cursor.dart
+  if ([kind isEqualToString:@"basic"])
+    return [NSCursor arrowCursor];
+  else if ([kind isEqualToString:@"click"])
+    return [NSCursor pointingHandCursor];
+  else if ([kind isEqualToString:@"text"])
+    return [NSCursor IBeamCursor];
+  else if ([kind isEqualToString:@"forbidden"])
+    return [NSCursor operationNotAllowedCursor];
+  else if ([kind isEqualToString:@"grab"])
+    return [NSCursor openHandCursor];
+  else if ([kind isEqualToString:@"grabbing"])
+    return [NSCursor closedHandCursor];
+  else
+    return [NSCursor arrowCursor];
+}
+
+@interface FlutterMouseCursorPlugin ()
+/**
+ * Whether the cursor is currently hidden.
+ */
+@property(nonatomic) BOOL hidden;
+
+/**
+ * Handles the method call that activates a system cursor.
+ *
+ * Returns a FlutterError if the arguments can not be recognized. Otherwise
+ * returns nil.
+ */
+- (FlutterError*)activateSystemCursor:(nonnull NSDictionary*)arguments;
+
+/**
+ * Displays the specified cursor.
+ *
+ * Unhides the cursor before displaying the cursor, and updates
+ * internal states.
+ */
+- (void)displayCursorObject:(nonnull NSCursor*)cursorObject;
+
+/**
+ * Hides the cursor.
+ */
+- (void)hide;
+
+/**
+ * Handles all method calls from Flutter.
+ */
+- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
+
+@end
+
+@implementation FlutterMouseCursorPlugin
+
+#pragma mark - Private
+
+NSMutableDictionary* cachedSystemCursors;
+
+- (instancetype)init {
+  self = [super init];
+  if (self) {
+    cachedSystemCursors = [NSMutableDictionary dictionary];
+  }
+  return self;
+}
+
+- (void)dealloc {
+  if (_hidden) {
+    [NSCursor unhide];
+  }
+}
+
+- (FlutterError*)activateSystemCursor:(nonnull NSDictionary*)arguments {
+  NSString* kindArg = arguments[kKindKey];
+  if (!kindArg) {
+    return [FlutterError errorWithCode:@"error"
+                               message:@"Missing argument"
+                               details:@"Missing argument while trying to activate system cursor"];
+  }
+  if ([kindArg isEqualToString:kKindValueNone]) {
+    [self hide];
+    return nil;
+  }
+  NSCursor* cursorObject = [FlutterMouseCursorPlugin cursorFromKind:kindArg];
+  [self displayCursorObject:cursorObject];
+  return nil;
+}
+
+- (void)displayCursorObject:(nonnull NSCursor*)cursorObject {
+  [cursorObject set];
+  if (_hidden) {
+    [NSCursor unhide];
+  }
+  _hidden = NO;
+}
+
+- (void)hide {
+  if (!_hidden) {
+    [NSCursor hide];
+  }
+  _hidden = YES;
+}
+
++ (NSCursor*)cursorFromKind:(NSString*)kind {
+  NSCursor* cachedValue = [cachedSystemCursors objectForKey:kind];
+  if (!cachedValue) {
+    cachedValue = GetCursorForKind(kind);
+    [cachedSystemCursors setValue:cachedValue forKey:kind];
+  }
+  return cachedValue;
+}
+
+#pragma mark - FlutterPlugin implementation
+
++ (void)registerWithRegistrar:(id<FlutterPluginRegistrar>)registrar {
+  FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:kMouseCursorChannel
+                                                              binaryMessenger:registrar.messenger];
+  FlutterMouseCursorPlugin* instance = [[FlutterMouseCursorPlugin alloc] init];
+  [registrar addMethodCallDelegate:instance channel:channel];
+}
+
+- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
+  NSString* method = call.method;
+  if ([method isEqualToString:kActivateSystemCursorMethod]) {
+    result([self activateSystemCursor:call.arguments]);
+  } else {
+    result(FlutterMethodNotImplemented);
+  }
+}
+
+@end
diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm
index 4813ab8..31708f2 100644
--- a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm
+++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm
@@ -9,6 +9,7 @@
 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
 #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
+#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h"
 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h"
 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
 #import "flutter/shell/platform/embedder/embedder.h"
@@ -350,6 +351,7 @@
 }
 
 - (void)addInternalPlugins {
+  [FlutterMouseCursorPlugin registerWithRegistrar:[self registrarForPlugin:@"mousecursor"]];
   _textInputPlugin = [[FlutterTextInputPlugin alloc] initWithViewController:self];
   _keyEventChannel =
       [FlutterBasicMessageChannel messageChannelWithName:@"flutter/keyevent"