Update the mouse cursor handler to work with multi-view on Windows (#163855)

Partially fixes https://github.com/flutter/flutter/issues/142845.

This PR removes implicit view assumptions from the
`'flutter/mousecursor'` channel handler on Windows.

The cursor is now set for all existing views, ensuring that the current
`FlutterWindow` with the cursor in its client area uses the correct
cursor. The method arguments remain unchanged.

Cursor handler tests have also been updated to avoid assuming an
implicit view.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
Harlen Batagelo 2025-03-13 19:10:02 -03:00 committed by GitHub
parent 71010953aa
commit 02f3ddf989
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 110 additions and 205 deletions

View File

@ -8,7 +8,6 @@
#include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_method_codec.h"
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
#include "flutter/shell/platform/windows/flutter_windows_view.h"
static constexpr char kChannelName[] = "flutter/mousecursor";
@ -74,16 +73,7 @@ void CursorHandler::HandleMethodCall(
return;
}
const auto& kind = std::get<std::string>(kind_iter->second);
// TODO(loicsharma): Remove implicit view assumption.
// https://github.com/flutter/flutter/issues/142845
FlutterWindowsView* view = engine_->view(kImplicitViewId);
if (view == nullptr) {
result->Error(kCursorError,
"Cursor is not available in Windows headless mode");
return;
}
view->UpdateFlutterCursor(kind);
engine_->UpdateFlutterCursor(kind);
result->Success();
} else if (method.compare(kCreateCustomCursorMethod) == 0) {
const auto& arguments = std::get<EncodableMap>(*method_call.arguments());
@ -167,16 +157,7 @@ void CursorHandler::HandleMethodCall(
return;
}
HCURSOR cursor = custom_cursors_[name];
// TODO(loicsharma): Remove implicit view assumption.
// https://github.com/flutter/flutter/issues/142845
FlutterWindowsView* view = engine_->view(kImplicitViewId);
if (view == nullptr) {
result->Error(kCursorError,
"Cursor is not available in Windows headless mode");
return;
}
view->SetFlutterCursor(cursor);
engine_->SetFlutterCursor(cursor);
result->Success();
} else if (method.compare(kDeleteCustomCursorMethod) == 0) {
const auto& arguments = std::get<EncodableMap>(*method_call.arguments());

View File

@ -14,6 +14,7 @@
#include "flutter/shell/platform/windows/testing/engine_modifier.h"
#include "flutter/shell/platform/windows/testing/flutter_windows_engine_builder.h"
#include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h"
#include "flutter/shell/platform/windows/testing/mock_windows_proc_table.h"
#include "flutter/shell/platform/windows/testing/test_binary_messenger.h"
#include "flutter/shell/platform/windows/testing/windows_test.h"
#include "gmock/gmock.h"
@ -24,6 +25,7 @@ namespace testing {
namespace {
using ::testing::_;
using ::testing::IsNull;
using ::testing::NotNull;
using ::testing::Return;
@ -63,6 +65,7 @@ class CursorHandlerTest : public WindowsTest {
FlutterWindowsEngine* engine() { return engine_.get(); }
FlutterWindowsView* view() { return view_.get(); }
MockWindowBindingHandler* window() { return window_; }
MockWindowsProcTable* proc_table() { return windows_proc_table_.get(); }
void UseHeadlessEngine() {
FlutterWindowsEngineBuilder builder{GetContext()};
@ -71,7 +74,10 @@ class CursorHandlerTest : public WindowsTest {
}
void UseEngineWithView() {
windows_proc_table_ = std::make_shared<MockWindowsProcTable>();
FlutterWindowsEngineBuilder builder{GetContext()};
builder.SetWindowsProcTable(windows_proc_table_);
auto window = std::make_unique<MockWindowBindingHandler>();
EXPECT_CALL(*window.get(), SetView).Times(1);
@ -79,17 +85,14 @@ class CursorHandlerTest : public WindowsTest {
window_ = window.get();
engine_ = builder.Build();
view_ = std::make_unique<FlutterWindowsView>(kImplicitViewId, engine_.get(),
std::move(window));
EngineModifier modifier{engine_.get()};
modifier.SetImplicitView(view_.get());
view_ = engine_->CreateView(std::move(window));
}
private:
std::unique_ptr<FlutterWindowsEngine> engine_;
std::unique_ptr<FlutterWindowsView> view_;
MockWindowBindingHandler* window_;
std::shared_ptr<MockWindowsProcTable> windows_proc_table_;
FML_DISALLOW_COPY_AND_ASSIGN(CursorHandlerTest);
};
@ -100,7 +103,8 @@ TEST_F(CursorHandlerTest, ActivateSystemCursor) {
TestBinaryMessenger messenger;
CursorHandler cursor_handler(&messenger, engine());
EXPECT_CALL(*window(), UpdateFlutterCursor("click")).Times(1);
EXPECT_CALL(*proc_table(), LoadCursor(IsNull(), IDC_HAND)).Times(1);
EXPECT_CALL(*proc_table(), SetCursor).Times(1);
bool success = false;
MethodResultFunctions<> result_handler(
@ -120,33 +124,6 @@ TEST_F(CursorHandlerTest, ActivateSystemCursor) {
EXPECT_TRUE(success);
}
TEST_F(CursorHandlerTest, ActivateSystemCursorRequiresView) {
UseHeadlessEngine();
TestBinaryMessenger messenger;
CursorHandler cursor_handler(&messenger, engine());
bool error = false;
MethodResultFunctions<> result_handler(
nullptr,
[&error](const std::string& error_code, const std::string& error_message,
const EncodableValue* value) {
error = true;
EXPECT_EQ(error_message,
"Cursor is not available in Windows headless mode");
},
nullptr);
SimulateCursorMessage(&messenger, kActivateSystemCursorMethod,
std::make_unique<EncodableValue>(EncodableMap{
{EncodableValue("device"), EncodableValue(0)},
{EncodableValue("kind"), EncodableValue("click")},
}),
&result_handler);
EXPECT_TRUE(error);
}
TEST_F(CursorHandlerTest, CreateCustomCursor) {
UseEngineWithView();
@ -196,7 +173,8 @@ TEST_F(CursorHandlerTest, SetCustomCursor) {
},
nullptr, nullptr);
EXPECT_CALL(*window(), SetFlutterCursor(/*cursor=*/NotNull())).Times(1);
EXPECT_CALL(*proc_table(), LoadCursor).Times(0);
EXPECT_CALL(*proc_table(), SetCursor(NotNull())).Times(1);
SimulateCursorMessage(&messenger, kCreateCustomCursorMethod,
std::make_unique<EncodableValue>(EncodableMap{
@ -218,47 +196,6 @@ TEST_F(CursorHandlerTest, SetCustomCursor) {
EXPECT_TRUE(success);
}
TEST_F(CursorHandlerTest, SetCustomCursorRequiresView) {
UseHeadlessEngine();
TestBinaryMessenger messenger;
CursorHandler cursor_handler(&messenger, engine());
// Create a 4x4 raw BGRA test cursor buffer.
std::vector<uint8_t> buffer(4 * 4 * 4, 0);
bool error = false;
MethodResultFunctions<> create_result_handler(nullptr, nullptr, nullptr);
MethodResultFunctions<> set_result_handler(
nullptr,
[&error](const std::string& error_code, const std::string& error_message,
const EncodableValue* value) {
error = true;
EXPECT_EQ(error_message,
"Cursor is not available in Windows headless mode");
},
nullptr);
SimulateCursorMessage(&messenger, kCreateCustomCursorMethod,
std::make_unique<EncodableValue>(EncodableMap{
{EncodableValue("name"), EncodableValue("hello")},
{EncodableValue("buffer"), EncodableValue(buffer)},
{EncodableValue("width"), EncodableValue(4)},
{EncodableValue("height"), EncodableValue(4)},
{EncodableValue("hotX"), EncodableValue(0.0)},
{EncodableValue("hotY"), EncodableValue(0.0)},
}),
&create_result_handler);
SimulateCursorMessage(&messenger, kSetCustomCursorMethod,
std::make_unique<EncodableValue>(EncodableMap{
{EncodableValue("name"), EncodableValue("hello")},
}),
&set_result_handler);
EXPECT_TRUE(error);
}
TEST_F(CursorHandlerTest, SetNonexistentCustomCursor) {
UseEngineWithView();
@ -277,7 +214,8 @@ TEST_F(CursorHandlerTest, SetNonexistentCustomCursor) {
},
nullptr);
EXPECT_CALL(*window(), SetFlutterCursor).Times(0);
EXPECT_CALL(*proc_table(), LoadCursor).Times(0);
EXPECT_CALL(*proc_table(), SetCursor).Times(0);
SimulateCursorMessage(&messenger, kSetCustomCursorMethod,
std::make_unique<EncodableValue>(EncodableMap{

View File

@ -30,49 +30,6 @@ static const int kMaxTouchDeviceId = 128;
static const int kLinesPerScrollWindowsDefault = 3;
// Maps a Flutter cursor name to an HCURSOR.
//
// Returns the arrow cursor for unknown constants.
//
// This map must be kept in sync with Flutter framework's
// services/mouse_cursor.dart.
static HCURSOR GetCursorByName(const std::string& cursor_name) {
static auto* cursors = new std::map<std::string, const wchar_t*>{
{"allScroll", IDC_SIZEALL},
{"basic", IDC_ARROW},
{"click", IDC_HAND},
{"forbidden", IDC_NO},
{"help", IDC_HELP},
{"move", IDC_SIZEALL},
{"none", nullptr},
{"noDrop", IDC_NO},
{"precise", IDC_CROSS},
{"progress", IDC_APPSTARTING},
{"text", IDC_IBEAM},
{"resizeColumn", IDC_SIZEWE},
{"resizeDown", IDC_SIZENS},
{"resizeDownLeft", IDC_SIZENESW},
{"resizeDownRight", IDC_SIZENWSE},
{"resizeLeft", IDC_SIZEWE},
{"resizeLeftRight", IDC_SIZEWE},
{"resizeRight", IDC_SIZEWE},
{"resizeRow", IDC_SIZENS},
{"resizeUp", IDC_SIZENS},
{"resizeUpDown", IDC_SIZENS},
{"resizeUpLeft", IDC_SIZENWSE},
{"resizeUpRight", IDC_SIZENESW},
{"resizeUpLeftDownRight", IDC_SIZENWSE},
{"resizeUpRightDownLeft", IDC_SIZENESW},
{"wait", IDC_WAIT},
};
const wchar_t* idc_name = IDC_ARROW;
auto it = cursors->find(cursor_name);
if (it != cursors->end()) {
idc_name = it->second;
}
return ::LoadCursor(nullptr, idc_name);
}
static constexpr int32_t kDefaultPointerDeviceId = 0;
// This method is only valid during a window message related to mouse/touch
@ -144,7 +101,6 @@ FlutterWindow::FlutterWindow(
keyboard_manager_ = std::make_unique<KeyboardManager>(this);
InitializeChild("FLUTTERVIEW", width, height);
current_cursor_ = ::LoadCursor(nullptr, IDC_ARROW);
}
// Base constructor for mocks
@ -176,15 +132,6 @@ PhysicalWindowBounds FlutterWindow::GetPhysicalWindowBounds() {
return {GetCurrentWidth(), GetCurrentHeight()};
}
void FlutterWindow::UpdateFlutterCursor(const std::string& cursor_name) {
SetFlutterCursor(GetCursorByName(cursor_name));
}
void FlutterWindow::SetFlutterCursor(HCURSOR cursor) {
current_cursor_ = cursor;
::SetCursor(current_cursor_);
}
bool FlutterWindow::Focus() {
auto hwnd = GetWindowHandle();
if (hwnd == nullptr) {
@ -257,10 +204,6 @@ void FlutterWindow::OnPointerLeave(double x,
binding_handler_delegate_->OnPointerLeave(x, y, device_kind, device_id);
}
void FlutterWindow::OnSetCursor() {
::SetCursor(current_cursor_);
}
void FlutterWindow::OnText(const std::u16string& text) {
binding_handler_delegate_->OnText(text);
}
@ -649,7 +592,8 @@ FlutterWindow::HandleMessage(UINT const message,
case WM_SETCURSOR: {
UINT hit_test_result = LOWORD(lparam);
if (hit_test_result == HTCLIENT) {
OnSetCursor();
// Halt further processing to prevent DefWindowProc from setting the
// cursor back to the registered class cursor.
return TRUE;
}
break;

View File

@ -104,9 +104,6 @@ class FlutterWindow : public KeyboardManager::WindowDelegate,
FlutterPointerDeviceKind device_kind,
int32_t device_id);
// Called when the cursor should be set for the client area.
virtual void OnSetCursor();
// |WindowBindingHandlerDelegate|
virtual void OnText(const std::u16string& text) override;
@ -161,12 +158,6 @@ class FlutterWindow : public KeyboardManager::WindowDelegate,
// |FlutterWindowBindingHandler|
virtual PhysicalWindowBounds GetPhysicalWindowBounds() override;
// |FlutterWindowBindingHandler|
virtual void UpdateFlutterCursor(const std::string& cursor_name) override;
// |FlutterWindowBindingHandler|
virtual void SetFlutterCursor(HCURSOR cursor) override;
// |FlutterWindowBindingHandler|
virtual bool OnBitmapSurfaceCleared() override;
@ -329,9 +320,6 @@ class FlutterWindow : public KeyboardManager::WindowDelegate,
// windowing and input state.
WindowBindingHandlerDelegate* binding_handler_delegate_ = nullptr;
// The last cursor set by Flutter. Defaults to the arrow cursor.
HCURSOR current_cursor_;
// The cursor rect set by Flutter.
RECT cursor_rect_;

View File

@ -66,7 +66,6 @@ class MockFlutterWindow : public FlutterWindow {
OnPointerLeave,
(double, double, FlutterPointerDeviceKind, int32_t),
(override));
MOCK_METHOD(void, OnSetCursor, (), (override));
MOCK_METHOD(float, GetScrollOffsetMultiplier, (), (override));
MOCK_METHOD(float, GetDpiScale, (), (override));
MOCK_METHOD(void, UpdateCursorRect, (const Rect&), (override));
@ -422,12 +421,5 @@ TEST_F(FlutterWindowTest, CachedLifecycleMessage) {
EXPECT_TRUE(restored);
}
TEST_F(FlutterWindowTest, UpdateCursor) {
FlutterWindow win32window(100, 100);
win32window.UpdateFlutterCursor("text");
HCURSOR cursor = ::GetCursor();
EXPECT_EQ(cursor, ::LoadCursor(nullptr, IDC_IBEAM));
}
} // namespace testing
} // namespace flutter

View File

@ -801,6 +801,44 @@ void FlutterWindowsEngine::SetNextFrameCallback(fml::closure callback) {
this);
}
HCURSOR FlutterWindowsEngine::GetCursorByName(
const std::string& cursor_name) const {
static auto* cursors = new std::map<std::string, const wchar_t*>{
{"allScroll", IDC_SIZEALL},
{"basic", IDC_ARROW},
{"click", IDC_HAND},
{"forbidden", IDC_NO},
{"help", IDC_HELP},
{"move", IDC_SIZEALL},
{"none", nullptr},
{"noDrop", IDC_NO},
{"precise", IDC_CROSS},
{"progress", IDC_APPSTARTING},
{"text", IDC_IBEAM},
{"resizeColumn", IDC_SIZEWE},
{"resizeDown", IDC_SIZENS},
{"resizeDownLeft", IDC_SIZENESW},
{"resizeDownRight", IDC_SIZENWSE},
{"resizeLeft", IDC_SIZEWE},
{"resizeLeftRight", IDC_SIZEWE},
{"resizeRight", IDC_SIZEWE},
{"resizeRow", IDC_SIZENS},
{"resizeUp", IDC_SIZENS},
{"resizeUpDown", IDC_SIZENS},
{"resizeUpLeft", IDC_SIZENWSE},
{"resizeUpRight", IDC_SIZENESW},
{"resizeUpLeftDownRight", IDC_SIZENWSE},
{"resizeUpRightDownLeft", IDC_SIZENESW},
{"wait", IDC_WAIT},
};
const wchar_t* idc_name = IDC_ARROW;
auto it = cursors->find(cursor_name);
if (it != cursors->end()) {
idc_name = it->second;
}
return windows_proc_table_->LoadCursor(nullptr, idc_name);
}
void FlutterWindowsEngine::SendSystemLocales() {
std::vector<LanguageInfo> languages =
GetPreferredLanguageInfo(*windows_proc_table_);
@ -995,6 +1033,15 @@ std::optional<LRESULT> FlutterWindowsEngine::ProcessExternalWindowMessage(
return std::nullopt;
}
void FlutterWindowsEngine::UpdateFlutterCursor(
const std::string& cursor_name) const {
SetFlutterCursor(GetCursorByName(cursor_name));
}
void FlutterWindowsEngine::SetFlutterCursor(HCURSOR cursor) const {
windows_proc_table_->SetCursor(cursor);
}
void FlutterWindowsEngine::OnChannelUpdate(std::string name, bool listening) {
if (name == "flutter/platform" && listening) {
lifecycle_manager_->BeginProcessingExit();

View File

@ -307,6 +307,13 @@ class FlutterWindowsEngine {
return windows_proc_table_;
}
// Sets the cursor that should be used when the mouse is over the Flutter
// content. See mouse_cursor.dart for the values and meanings of cursor_name.
void UpdateFlutterCursor(const std::string& cursor_name) const;
// Sets the cursor directly from a cursor handle.
void SetFlutterCursor(HCURSOR cursor) const;
protected:
// Creates the keyboard key handler.
//
@ -341,6 +348,14 @@ class FlutterWindowsEngine {
// Allows swapping out embedder_api_ calls in tests.
friend class EngineModifier;
// Maps a Flutter cursor name to an HCURSOR.
//
// Returns the arrow cursor for unknown constants.
//
// This map must be kept in sync with Flutter framework's
// services/mouse_cursor.dart.
HCURSOR GetCursorByName(const std::string& cursor_name) const;
// Sends system locales to the engine.
//
// Should be called just after the engine is run, and after any relevant

View File

@ -176,14 +176,6 @@ bool FlutterWindowsView::OnFrameGenerated(size_t width, size_t height) {
return true;
}
void FlutterWindowsView::UpdateFlutterCursor(const std::string& cursor_name) {
binding_handler_->UpdateFlutterCursor(cursor_name);
}
void FlutterWindowsView::SetFlutterCursor(HCURSOR cursor) {
binding_handler_->SetFlutterCursor(cursor);
}
void FlutterWindowsView::ForceRedraw() {
if (resize_status_ == ResizeState::kDone) {
// Request new frame.

View File

@ -125,13 +125,6 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate {
// This completes a view resize if one is pending.
virtual void OnFramePresented();
// Sets the cursor that should be used when the mouse is over the Flutter
// content. See mouse_cursor.dart for the values and meanings of cursor_name.
void UpdateFlutterCursor(const std::string& cursor_name);
// Sets the cursor directly from a cursor handle.
void SetFlutterCursor(HCURSOR cursor);
// |WindowBindingHandlerDelegate|
bool OnWindowSizeChanged(size_t width, size_t height) override;

View File

@ -54,7 +54,6 @@ class MockWindow : public FlutterWindow {
OnPointerLeave,
(double, double, FlutterPointerDeviceKind, int32_t),
(override));
MOCK_METHOD(void, OnSetCursor, (), (override));
MOCK_METHOD(void, OnText, (const std::u16string&), (override));
MOCK_METHOD(void,
OnKey,

View File

@ -23,11 +23,6 @@ class MockWindowBindingHandler : public WindowBindingHandler {
MOCK_METHOD(HWND, GetWindowHandle, (), (override));
MOCK_METHOD(float, GetDpiScale, (), (override));
MOCK_METHOD(PhysicalWindowBounds, GetPhysicalWindowBounds, (), (override));
MOCK_METHOD(void,
UpdateFlutterCursor,
(const std::string& cursor_name),
(override));
MOCK_METHOD(void, SetFlutterCursor, (HCURSOR cursor_name), (override));
MOCK_METHOD(void, OnCursorRectUpdated, (const Rect& rect), (override));
MOCK_METHOD(void, OnResetImeComposing, (), (override));
MOCK_METHOD(bool, OnBitmapSurfaceCleared, (), (override));

View File

@ -34,6 +34,13 @@ class MockWindowsProcTable : public WindowsProcTable {
MOCK_METHOD(HRESULT, DwmFlush, (), (const, override));
MOCK_METHOD(HCURSOR,
LoadCursor,
(HINSTANCE instance, LPCWSTR cursor_name),
(const, override));
MOCK_METHOD(HCURSOR, SetCursor, (HCURSOR cursor), (const, override));
private:
FML_DISALLOW_COPY_AND_ASSIGN(MockWindowsProcTable);
};

View File

@ -54,13 +54,6 @@ class WindowBindingHandler {
// Returns the bounds of the backing window in physical pixels.
virtual PhysicalWindowBounds GetPhysicalWindowBounds() = 0;
// Sets the cursor that should be used when the mouse is over the Flutter
// content. See mouse_cursor.dart for the values and meanings of cursor_name.
virtual void UpdateFlutterCursor(const std::string& cursor_name) = 0;
// Sets the cursor directly from a cursor handle.
virtual void SetFlutterCursor(HCURSOR cursor) = 0;
// Invoked when the cursor/composing rect has been updated in the framework.
virtual void OnCursorRectUpdated(const Rect& rect) = 0;

View File

@ -58,4 +58,13 @@ HRESULT WindowsProcTable::DwmFlush() const {
return ::DwmFlush();
}
HCURSOR WindowsProcTable::LoadCursor(HINSTANCE instance,
LPCWSTR cursor_name) const {
return ::LoadCursorW(instance, cursor_name);
}
HCURSOR WindowsProcTable::SetCursor(HCURSOR cursor) const {
return ::SetCursor(cursor);
}
} // namespace flutter

View File

@ -57,6 +57,19 @@ class WindowsProcTable {
// https://learn.microsoft.com/windows/win32/api/dwmapi/nf-dwmapi-dwmflush
virtual HRESULT DwmFlush() const;
// Loads the specified cursor resource from the executable (.exe) file
// associated with an application instance.
//
// See:
// https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-loadcursorw
virtual HCURSOR LoadCursor(HINSTANCE instance, LPCWSTR cursor_name) const;
// Sets the cursor shape.
//
// See:
// https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-setcursor
virtual HCURSOR SetCursor(HCURSOR cursor) const;
private:
using GetPointerType_ = BOOL __stdcall(UINT32 pointerId,
POINTER_INPUT_TYPE* pointerType);

View File

@ -408,8 +408,7 @@ abstract final class SystemMouseCursors {
//
// * Android: shell/platform/android/io/flutter/plugin/mouse/MouseCursorPlugin.java
// * Web: lib/web_ui/lib/src/engine/mouse_cursor.dart
// * Windows: shell/platform/windows/win32_flutter_window_win32.cc
// * Windows UWP: shell/platform/windows/win32_flutter_window_winuwp.cc
// * Windows: shell/platform/windows/flutter_windows_engine.cc
// * Linux: shell/platform/linux/fl_mouse_cursor_plugin.cc
// * macOS: shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm