Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[Windows] Make the engine own the cursor plugin #38570

Merged
merged 1 commit into from
Jan 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions shell/platform/windows/cursor_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <windows.h>

#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";

Expand Down Expand Up @@ -40,15 +42,18 @@ static constexpr char kSetCustomCursorMethod[] = "setCustomCursor/windows";
static constexpr char kDeleteCustomCursorMethod[] =
"deleteCustomCursor/windows";

// Error codes used for responses.
static constexpr char kCursorError[] = "Cursor error";

namespace flutter {

CursorHandler::CursorHandler(BinaryMessenger* messenger,
WindowBindingHandler* delegate)
FlutterWindowsEngine* engine)
: channel_(std::make_unique<MethodChannel<EncodableValue>>(
messenger,
kChannelName,
&StandardMethodCodec::GetInstance())),
delegate_(delegate) {
engine_(engine) {
channel_->SetMethodCallHandler(
[this](const MethodCall<EncodableValue>& call,
std::unique_ptr<MethodResult<EncodableValue>> result) {
Expand All @@ -69,7 +74,13 @@ void CursorHandler::HandleMethodCall(
return;
}
const auto& kind = std::get<std::string>(kind_iter->second);
delegate_->UpdateFlutterCursor(kind);
FlutterWindowsView* view = engine_->view();
if (view == nullptr) {
result->Error(kCursorError,
"Cursor is not available in Windows headless mode");
return;
}
view->UpdateFlutterCursor(kind);
result->Success();
} else if (method.compare(kCreateCustomCursorMethod) == 0) {
const auto& arguments = std::get<EncodableMap>(*method_call.arguments());
Expand Down Expand Up @@ -153,7 +164,13 @@ void CursorHandler::HandleMethodCall(
return;
}
HCURSOR cursor = custom_cursors_[name];
delegate_->SetFlutterCursor(cursor);
FlutterWindowsView* view = engine_->view();
if (view == nullptr) {
result->Error(kCursorError,
"Cursor is not available in Windows headless mode");
return;
}
view->SetFlutterCursor(cursor);
result->Success();
} else if (method.compare(kDeleteCustomCursorMethod) == 0) {
const auto& arguments = std::get<EncodableMap>(*method_call.arguments());
Expand Down
8 changes: 5 additions & 3 deletions shell/platform/windows/cursor_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@

namespace flutter {

class FlutterWindowsEngine;

// Handler for the cursor system channel.
class CursorHandler {
public:
explicit CursorHandler(flutter::BinaryMessenger* messenger,
WindowBindingHandler* delegate);
flutter::FlutterWindowsEngine* engine);

private:
// Called when a method is called on |channel_|;
Expand All @@ -30,8 +32,8 @@ class CursorHandler {
// The MethodChannel used for communication with the Flutter engine.
std::unique_ptr<flutter::MethodChannel<EncodableValue>> channel_;

// The delegate for cursor updates.
WindowBindingHandler* delegate_;
// The Flutter engine that will be notified for cursor updates.
FlutterWindowsEngine* engine_;

// The cache map for custom cursors.
std::unordered_map<std::string, HCURSOR> custom_cursors_;
Expand Down
151 changes: 130 additions & 21 deletions shell/platform/windows/cursor_handler_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
#include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_message_codec.h"
#include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_method_codec.h"
#include "flutter/shell/platform/windows/flutter_windows_view.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/test_binary_messenger.h"
#include "flutter/shell/platform/windows/testing/windows_test.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

Expand Down Expand Up @@ -50,12 +52,46 @@ void SimulateCursorMessage(TestBinaryMessenger* messenger,

} // namespace

TEST(CursorHandlerTest, ActivateSystemCursor) {
class CursorHandlerTest : public WindowsTest {
protected:
FlutterWindowsEngine* engine() { return engine_.get(); }
FlutterWindowsView* view() { return view_.get(); }
MockWindowBindingHandler* window() { return window_; }

void use_headless_engine() {
FlutterWindowsEngineBuilder builder{GetContext()};

engine_ = builder.Build();
}

void use_engine_with_view() {
FlutterWindowsEngineBuilder builder{GetContext()};

auto window = std::make_unique<MockWindowBindingHandler>();

window_ = window.get();
EXPECT_CALL(*window_, SetView).Times(1);
EXPECT_CALL(*window_, GetRenderTarget).WillOnce(Return(nullptr));

engine_ = builder.Build();
view_ = std::make_unique<FlutterWindowsView>(std::move(window));

engine_->SetView(view_.get());
}

private:
std::unique_ptr<FlutterWindowsEngine> engine_;
std::unique_ptr<FlutterWindowsView> view_;
MockWindowBindingHandler* window_;
};

TEST_F(CursorHandlerTest, ActivateSystemCursor) {
use_engine_with_view();

TestBinaryMessenger messenger;
MockWindowBindingHandler window;
CursorHandler cursor_handler(&messenger, &window);
CursorHandler cursor_handler(&messenger, engine());

EXPECT_CALL(window, UpdateFlutterCursor("click")).Times(1);
EXPECT_CALL(*window(), UpdateFlutterCursor("click")).Times(1);

bool success = false;
MethodResultFunctions<> result_handler(
Expand All @@ -75,10 +111,38 @@ TEST(CursorHandlerTest, ActivateSystemCursor) {
EXPECT_TRUE(success);
}

TEST(CursorHandlerTest, CreateCustomCursor) {
TEST_F(CursorHandlerTest, ActivateSystemCursorRequiresView) {
use_headless_engine();

TestBinaryMessenger messenger;
MockWindowBindingHandler window;
CursorHandler cursor_handler(&messenger, &window);
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) {
use_engine_with_view();

TestBinaryMessenger messenger;
CursorHandler cursor_handler(&messenger, engine());

// Create a 4x4 raw BGRA test cursor buffer.
std::vector<uint8_t> buffer(4 * 4 * 4, 0);
Expand All @@ -105,10 +169,11 @@ TEST(CursorHandlerTest, CreateCustomCursor) {
EXPECT_TRUE(success);
}

TEST(CursorHandlerTest, SetCustomCursor) {
TEST_F(CursorHandlerTest, SetCustomCursor) {
use_engine_with_view();

TestBinaryMessenger messenger;
MockWindowBindingHandler window;
CursorHandler cursor_handler(&messenger, &window);
CursorHandler cursor_handler(&messenger, engine());

// Create a 4x4 raw BGRA test cursor buffer.
std::vector<uint8_t> buffer(4 * 4 * 4, 0);
Expand All @@ -122,7 +187,7 @@ TEST(CursorHandlerTest, SetCustomCursor) {
},
nullptr, nullptr);

EXPECT_CALL(window, SetFlutterCursor(/*cursor=*/NotNull())).Times(1);
EXPECT_CALL(*window(), SetFlutterCursor(/*cursor=*/NotNull())).Times(1);

SimulateCursorMessage(&messenger, kCreateCustomCursorMethod,
std::make_unique<EncodableValue>(EncodableMap{
Expand All @@ -144,10 +209,52 @@ TEST(CursorHandlerTest, SetCustomCursor) {
EXPECT_TRUE(success);
}

TEST(CursorHandlerTest, SetNonexistentCustomCursor) {
TEST_F(CursorHandlerTest, SetCustomCursorRequiresView) {
use_headless_engine();

TestBinaryMessenger messenger;
MockWindowBindingHandler window;
CursorHandler cursor_handler(&messenger, &window);
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) {
use_engine_with_view();

TestBinaryMessenger messenger;
CursorHandler cursor_handler(&messenger, engine());

bool error = false;
MethodResultFunctions<> result_handler(
Expand All @@ -161,7 +268,7 @@ TEST(CursorHandlerTest, SetNonexistentCustomCursor) {
},
nullptr);

EXPECT_CALL(window, SetFlutterCursor).Times(0);
EXPECT_CALL(*window(), SetFlutterCursor).Times(0);

SimulateCursorMessage(&messenger, kSetCustomCursorMethod,
std::make_unique<EncodableValue>(EncodableMap{
Expand All @@ -172,10 +279,11 @@ TEST(CursorHandlerTest, SetNonexistentCustomCursor) {
EXPECT_TRUE(error);
}

TEST(CursorHandlerTest, DeleteCustomCursor) {
TEST_F(CursorHandlerTest, DeleteCustomCursor) {
use_engine_with_view();

TestBinaryMessenger messenger;
MockWindowBindingHandler window;
CursorHandler cursor_handler(&messenger, &window);
CursorHandler cursor_handler(&messenger, engine());

// Create a 4x4 raw BGRA test cursor buffer.
std::vector<uint8_t> buffer(4 * 4 * 4, 0);
Expand Down Expand Up @@ -209,10 +317,11 @@ TEST(CursorHandlerTest, DeleteCustomCursor) {
EXPECT_TRUE(success);
}

TEST(CursorHandlerTest, DeleteNonexistentCustomCursor) {
TEST_F(CursorHandlerTest, DeleteNonexistentCustomCursor) {
use_engine_with_view();

TestBinaryMessenger messenger;
MockWindowBindingHandler handler;
CursorHandler cursor_handler(&messenger, &handler);
CursorHandler cursor_handler(&messenger, engine());

bool success = false;
MethodResultFunctions<> result_handler(
Expand Down
2 changes: 2 additions & 0 deletions shell/platform/windows/flutter_windows_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ FlutterWindowsEngine::FlutterWindowsEngine(
// Set up internal channels.
// TODO: Replace this with an embedder.h API. See
// https://github.com/flutter/flutter/issues/71099
cursor_handler_ =
std::make_unique<CursorHandler>(messenger_wrapper_.get(), this);
platform_handler_ =
std::make_unique<PlatformHandler>(messenger_wrapper_.get(), this);
settings_plugin_ = std::make_unique<SettingsPlugin>(messenger_wrapper_.get(),
Expand Down
4 changes: 4 additions & 0 deletions shell/platform/windows/flutter_windows_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "flutter/shell/platform/embedder/embedder.h"
#include "flutter/shell/platform/windows/accessibility_bridge_windows.h"
#include "flutter/shell/platform/windows/angle_surface_manager.h"
#include "flutter/shell/platform/windows/cursor_handler.h"
#include "flutter/shell/platform/windows/flutter_desktop_messenger.h"
#include "flutter/shell/platform/windows/flutter_project_bundle.h"
#include "flutter/shell/platform/windows/flutter_windows_texture_registrar.h"
Expand Down Expand Up @@ -308,6 +309,9 @@ class FlutterWindowsEngine {
// May be nullptr if ANGLE failed to initialize.
std::unique_ptr<AngleSurfaceManager> surface_manager_;

// Handler for cursor events.
std::unique_ptr<CursorHandler> cursor_handler_;

// Handler for the flutter/platform channel.
std::unique_ptr<PlatformHandler> platform_handler_;

Expand Down
10 changes: 8 additions & 2 deletions shell/platform/windows/flutter_windows_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ void FlutterWindowsView::SetEngine(
// Set up the system channel handlers.
auto internal_plugin_messenger = internal_plugin_registrar_->messenger();
InitializeKeyboard();
cursor_handler_ = std::make_unique<CursorHandler>(internal_plugin_messenger,
binding_handler_.get());

PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds();

Expand Down Expand Up @@ -114,6 +112,14 @@ uint32_t FlutterWindowsView::GetFrameBufferId(size_t width, size_t height) {
return kWindowFrameBufferID;
}

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.
Expand Down
11 changes: 7 additions & 4 deletions shell/platform/windows/flutter_windows_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
#include "flutter/shell/platform/common/geometry.h"
#include "flutter/shell/platform/embedder/embedder.h"
#include "flutter/shell/platform/windows/angle_surface_manager.h"
#include "flutter/shell/platform/windows/cursor_handler.h"
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
#include "flutter/shell/platform/windows/keyboard_handler_base.h"
#include "flutter/shell/platform/windows/keyboard_key_embedder_handler.h"
Expand Down Expand Up @@ -96,6 +95,13 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate,
// Returns the frame buffer id for the engine to render to.
uint32_t GetFrameBufferId(size_t width, size_t height);

// 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);

// Invoked by the engine right before the engine is restarted.
//
// This should reset necessary states to as if the view has just been
Expand Down Expand Up @@ -381,9 +387,6 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate,
// Handlers for text events from Windows.
std::unique_ptr<TextInputPlugin> text_input_plugin_;

// Handler for cursor events.
std::unique_ptr<CursorHandler> cursor_handler_;

// Currently configured WindowBindingHandler for view.
std::unique_ptr<WindowBindingHandler> binding_handler_;

Expand Down