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

Commit fa24f94

Browse files
authored
feat: add custom cursor interface on windows (#36143)
* feat: initial custom cursor and cache support * opt: add cursor validity check * opt: format code * opt: rename more suitable name * opt: key, method name * opt: code * feat: add unittests * opt: change to ref * opt: docs * opt: format code * opt: code * opt: correct code and comments * opt: rgba -> bgra * opt: name * opt: create valid/compatible mask of bitmap & mask * opt: null check * opt: split cursor functions into configurable ones * fix: EncodableValue type * opt: doc for custom cursor key * update: cursor doc * opt: docs * opt: refactor vector cache to unordered_map * opt: code * opt: unifrom key value * opt: uniform key_iter to name_iter * opt: docs * fix: get width out of range
1 parent abb68f4 commit fa24f94

File tree

9 files changed

+266
-1
lines changed

9 files changed

+266
-1
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3181,6 +3181,7 @@ FILE: ../../../flutter/shell/platform/windows/client_wrapper/include/flutter/plu
31813181
FILE: ../../../flutter/shell/platform/windows/client_wrapper/plugin_registrar_windows_unittests.cc
31823182
FILE: ../../../flutter/shell/platform/windows/cursor_handler.cc
31833183
FILE: ../../../flutter/shell/platform/windows/cursor_handler.h
3184+
FILE: ../../../flutter/shell/platform/windows/cursor_handler_unittests.cc
31843185
FILE: ../../../flutter/shell/platform/windows/direct_manipulation.cc
31853186
FILE: ../../../flutter/shell/platform/windows/direct_manipulation.h
31863187
FILE: ../../../flutter/shell/platform/windows/direct_manipulation_unittests.cc

shell/platform/windows/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ executable("flutter_windows_unittests") {
176176
# Common Windows test sources.
177177
sources = [
178178
"accessibility_bridge_windows_unittests.cc",
179+
"cursor_handler_unittests.cc",
179180
"direct_manipulation_unittests.cc",
180181
"dpi_utils_unittests.cc",
181182
"flutter_project_bundle_unittests.cc",

shell/platform/windows/cursor_handler.cc

Lines changed: 210 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,35 @@
1111
static constexpr char kChannelName[] = "flutter/mousecursor";
1212

1313
static constexpr char kActivateSystemCursorMethod[] = "activateSystemCursor";
14-
1514
static constexpr char kKindKey[] = "kind";
1615

16+
// This method allows creating a custom cursor with rawBGRA buffer, returns a
17+
// string to identify the cursor.
18+
static constexpr char kCreateCustomCursorMethod[] =
19+
"createCustomCursor/windows";
20+
// A string, the custom cursor's name.
21+
static constexpr char kCustomCursorNameKey[] = "name";
22+
// A list of bytes, the custom cursor's rawBGRA buffer.
23+
static constexpr char kCustomCursorBufferKey[] = "buffer";
24+
// A double, the x coordinate of the custom cursor's hotspot, starting from
25+
// left.
26+
static constexpr char kCustomCursorHotXKey[] = "hotX";
27+
// A double, the y coordinate of the custom cursor's hotspot, starting from top.
28+
static constexpr char kCustomCursorHotYKey[] = "hotY";
29+
// An int value for the width of the custom cursor.
30+
static constexpr char kCustomCursorWidthKey[] = "width";
31+
// An int value for the height of the custom cursor.
32+
static constexpr char kCustomCursorHeightKey[] = "height";
33+
34+
// This method also has an argument `kCustomCursorNameKey` for the name
35+
// of the cursor to activate.
36+
static constexpr char kSetCustomCursorMethod[] = "setCustomCursor/windows";
37+
38+
// This method also has an argument `kCustomCursorNameKey` for the name
39+
// of the cursor to delete.
40+
static constexpr char kDeleteCustomCursorMethod[] =
41+
"deleteCustomCursor/windows";
42+
1743
namespace flutter {
1844

1945
CursorHandler::CursorHandler(BinaryMessenger* messenger,
@@ -45,9 +71,192 @@ void CursorHandler::HandleMethodCall(
4571
const auto& kind = std::get<std::string>(kind_iter->second);
4672
delegate_->UpdateFlutterCursor(kind);
4773
result->Success();
74+
} else if (method.compare(kCreateCustomCursorMethod) == 0) {
75+
const auto& arguments = std::get<EncodableMap>(*method_call.arguments());
76+
auto name_iter =
77+
arguments.find(EncodableValue(std::string(kCustomCursorNameKey)));
78+
if (name_iter == arguments.end()) {
79+
result->Error(
80+
"Argument error",
81+
"Missing argument name while trying to customize system cursor");
82+
return;
83+
}
84+
auto name = std::get<std::string>(name_iter->second);
85+
auto buffer_iter =
86+
arguments.find(EncodableValue(std::string(kCustomCursorBufferKey)));
87+
if (buffer_iter == arguments.end()) {
88+
result->Error(
89+
"Argument error",
90+
"Missing argument buffer while trying to customize system cursor");
91+
return;
92+
}
93+
auto buffer = std::get<std::vector<uint8_t>>(buffer_iter->second);
94+
auto width_iter =
95+
arguments.find(EncodableValue(std::string(kCustomCursorWidthKey)));
96+
if (width_iter == arguments.end()) {
97+
result->Error(
98+
"Argument error",
99+
"Missing argument width while trying to customize system cursor");
100+
return;
101+
}
102+
auto width = std::get<int>(width_iter->second);
103+
auto height_iter =
104+
arguments.find(EncodableValue(std::string(kCustomCursorHeightKey)));
105+
if (height_iter == arguments.end()) {
106+
result->Error(
107+
"Argument error",
108+
"Missing argument height while trying to customize system cursor");
109+
return;
110+
}
111+
auto height = std::get<int>(height_iter->second);
112+
auto hot_x_iter =
113+
arguments.find(EncodableValue(std::string(kCustomCursorHotXKey)));
114+
if (hot_x_iter == arguments.end()) {
115+
result->Error(
116+
"Argument error",
117+
"Missing argument hotX while trying to customize system cursor");
118+
return;
119+
}
120+
auto hot_x = std::get<double>(hot_x_iter->second);
121+
auto hot_y_iter =
122+
arguments.find(EncodableValue(std::string(kCustomCursorHotYKey)));
123+
if (hot_y_iter == arguments.end()) {
124+
result->Error(
125+
"Argument error",
126+
"Missing argument hotY while trying to customize system cursor");
127+
return;
128+
}
129+
auto hot_y = std::get<double>(hot_y_iter->second);
130+
HCURSOR cursor = GetCursorFromBuffer(buffer, hot_x, hot_y, width, height);
131+
if (cursor == nullptr) {
132+
result->Error("Argument error",
133+
"Argument must contains a valid rawBGRA bitmap");
134+
return;
135+
}
136+
// Push the cursor into the cache map.
137+
custom_cursors_.emplace(name, std::move(cursor));
138+
result->Success(flutter::EncodableValue(std::move(name)));
139+
} else if (method.compare(kSetCustomCursorMethod) == 0) {
140+
const auto& arguments = std::get<EncodableMap>(*method_call.arguments());
141+
auto name_iter =
142+
arguments.find(EncodableValue(std::string(kCustomCursorNameKey)));
143+
if (name_iter == arguments.end()) {
144+
result->Error("Argument error",
145+
"Missing argument key while trying to set a custom cursor");
146+
return;
147+
}
148+
auto name = std::get<std::string>(name_iter->second);
149+
if (custom_cursors_.find(name) == custom_cursors_.end()) {
150+
result->Error(
151+
"Argument error",
152+
"The custom cursor identified by the argument key cannot be found");
153+
return;
154+
}
155+
HCURSOR cursor = custom_cursors_[name];
156+
delegate_->SetFlutterCursor(cursor);
157+
result->Success();
158+
} else if (method.compare(kDeleteCustomCursorMethod) == 0) {
159+
const auto& arguments = std::get<EncodableMap>(*method_call.arguments());
160+
auto name_iter =
161+
arguments.find(EncodableValue(std::string(kCustomCursorNameKey)));
162+
if (name_iter == arguments.end()) {
163+
result->Error(
164+
"Argument error",
165+
"Missing argument key while trying to delete a custom cursor");
166+
return;
167+
}
168+
auto name = std::get<std::string>(name_iter->second);
169+
auto it = custom_cursors_.find(name);
170+
// If the specified cursor name is not found, the deletion is a noop and
171+
// returns success.
172+
if (it != custom_cursors_.end()) {
173+
DeleteObject(it->second);
174+
custom_cursors_.erase(it);
175+
}
176+
result->Success();
48177
} else {
49178
result->NotImplemented();
50179
}
51180
}
52181

182+
HCURSOR GetCursorFromBuffer(const std::vector<uint8_t>& buffer,
183+
double hot_x,
184+
double hot_y,
185+
int width,
186+
int height) {
187+
HCURSOR cursor = nullptr;
188+
HDC display_dc = GetDC(NULL);
189+
// Flutter should returns rawBGRA, which has 8bits * 4channels.
190+
BITMAPINFO bmi;
191+
memset(&bmi, 0, sizeof(bmi));
192+
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
193+
bmi.bmiHeader.biWidth = width;
194+
bmi.bmiHeader.biHeight = -height;
195+
bmi.bmiHeader.biPlanes = 1;
196+
bmi.bmiHeader.biBitCount = 32;
197+
bmi.bmiHeader.biCompression = BI_RGB;
198+
bmi.bmiHeader.biSizeImage = width * height * 4;
199+
// Create the pixmap DIB section
200+
uint8_t* pixels = 0;
201+
HBITMAP bitmap =
202+
CreateDIBSection(display_dc, &bmi, DIB_RGB_COLORS, (void**)&pixels, 0, 0);
203+
ReleaseDC(0, display_dc);
204+
if (!bitmap || !pixels) {
205+
return nullptr;
206+
}
207+
int bytes_per_line = width * 4;
208+
for (int y = 0; y < height; ++y) {
209+
memcpy(pixels + y * bytes_per_line, &buffer[bytes_per_line * y],
210+
bytes_per_line);
211+
}
212+
HBITMAP mask;
213+
GetMaskBitmaps(bitmap, mask);
214+
ICONINFO icon_info;
215+
icon_info.fIcon = 0;
216+
icon_info.xHotspot = hot_x;
217+
icon_info.yHotspot = hot_y;
218+
icon_info.hbmMask = mask;
219+
icon_info.hbmColor = bitmap;
220+
cursor = CreateIconIndirect(&icon_info);
221+
DeleteObject(mask);
222+
DeleteObject(bitmap);
223+
return cursor;
224+
}
225+
226+
void GetMaskBitmaps(HBITMAP bitmap, HBITMAP& mask_bitmap) {
227+
HDC h_dc = ::GetDC(NULL);
228+
HDC h_main_dc = ::CreateCompatibleDC(h_dc);
229+
HDC h_and_mask_dc = ::CreateCompatibleDC(h_dc);
230+
231+
// Get the dimensions of the source bitmap
232+
BITMAP bm;
233+
::GetObject(bitmap, sizeof(BITMAP), &bm);
234+
mask_bitmap = ::CreateCompatibleBitmap(h_dc, bm.bmWidth, bm.bmHeight);
235+
236+
// Select the bitmaps to DC
237+
HBITMAP h_old_main_bitmap = (HBITMAP)::SelectObject(h_main_dc, bitmap);
238+
HBITMAP h_old_and_mask_bitmap =
239+
(HBITMAP)::SelectObject(h_and_mask_dc, mask_bitmap);
240+
241+
// Scan each pixel of the souce bitmap and create the masks
242+
COLORREF main_bit_pixel;
243+
for (int x = 0; x < bm.bmWidth; ++x) {
244+
for (int y = 0; y < bm.bmHeight; ++y) {
245+
main_bit_pixel = ::GetPixel(h_main_dc, x, y);
246+
if (main_bit_pixel == RGB(0, 0, 0)) {
247+
::SetPixel(h_and_mask_dc, x, y, RGB(255, 255, 255));
248+
} else {
249+
::SetPixel(h_and_mask_dc, x, y, RGB(0, 0, 0));
250+
}
251+
}
252+
}
253+
::SelectObject(h_main_dc, h_old_main_bitmap);
254+
::SelectObject(h_and_mask_dc, h_old_and_mask_bitmap);
255+
256+
::DeleteDC(h_and_mask_dc);
257+
::DeleteDC(h_main_dc);
258+
259+
::ReleaseDC(NULL, h_dc);
260+
}
261+
53262
} // namespace flutter

shell/platform/windows/cursor_handler.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_CURSOR_HANDLER_H_
66
#define FLUTTER_SHELL_PLATFORM_WINDOWS_CURSOR_HANDLER_H_
77

8+
#include <unordered_map>
9+
810
#include "flutter/shell/platform/common/client_wrapper/include/flutter/binary_messenger.h"
911
#include "flutter/shell/platform/common/client_wrapper/include/flutter/encodable_value.h"
1012
#include "flutter/shell/platform/common/client_wrapper/include/flutter/method_channel.h"
@@ -30,8 +32,21 @@ class CursorHandler {
3032

3133
// The delegate for cursor updates.
3234
WindowBindingHandler* delegate_;
35+
36+
// The cache map for custom cursors.
37+
std::unordered_map<std::string, HCURSOR> custom_cursors_;
3338
};
3439

40+
// Create a cursor from a rawBGRA buffer and the cursor info.
41+
HCURSOR GetCursorFromBuffer(const std::vector<uint8_t>& buffer,
42+
double hot_x,
43+
double hot_y,
44+
int width,
45+
int height);
46+
47+
// Get the corresponding mask bitmap from the source bitmap.
48+
void GetMaskBitmaps(HBITMAP bitmap, HBITMAP& mask_bitmap);
49+
3550
} // namespace flutter
3651

3752
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_CURSOR_HANDLER_H_
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
#include "flutter/shell/platform/windows/cursor_handler.h"
5+
6+
#include <memory>
7+
#include <vector>
8+
9+
#include "gmock/gmock.h"
10+
#include "gtest/gtest.h"
11+
12+
namespace flutter {
13+
namespace testing {
14+
TEST(CursorHandlerTest, CreateDummyCursor) {
15+
// Create a 4x4 rawBGRA dummy cursor buffer.
16+
std::vector<uint8_t> buffer;
17+
for (int i = 0; i < 4 * 4 * 4; i++) {
18+
buffer.push_back(0);
19+
}
20+
// Create the cursor from buffer provided above.
21+
auto cursor = GetCursorFromBuffer(buffer, 0.0, 0.0, 4, 4);
22+
// Expect cursor is not null.
23+
EXPECT_NE(cursor, nullptr);
24+
}
25+
26+
} // namespace testing
27+
} // namespace flutter

shell/platform/windows/flutter_window.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ void FlutterWindow::UpdateFlutterCursor(const std::string& cursor_name) {
105105
current_cursor_ = GetCursorByName(cursor_name);
106106
}
107107

108+
void FlutterWindow::SetFlutterCursor(HCURSOR cursor) {
109+
current_cursor_ = cursor;
110+
::SetCursor(current_cursor_);
111+
}
112+
108113
void FlutterWindow::OnWindowResized() {
109114
// Blocking the raster thread until DWM flushes alleviates glitches where
110115
// previous size surface is stretched over current size view.

shell/platform/windows/flutter_window.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ class FlutterWindow : public Window, public WindowBindingHandler {
131131
// |FlutterWindowBindingHandler|
132132
void UpdateFlutterCursor(const std::string& cursor_name) override;
133133

134+
// |FlutterWindowBindingHandler|
135+
void SetFlutterCursor(HCURSOR cursor) override;
136+
134137
// |FlutterWindowBindingHandler|
135138
void OnWindowResized() override;
136139

shell/platform/windows/testing/mock_window_binding_handler.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class MockWindowBindingHandler : public WindowBindingHandler {
3131
MOCK_METHOD0(OnWindowResized, void());
3232
MOCK_METHOD0(GetPhysicalWindowBounds, PhysicalWindowBounds());
3333
MOCK_METHOD1(UpdateFlutterCursor, void(const std::string& cursor_name));
34+
MOCK_METHOD1(SetFlutterCursor, void(HCURSOR cursor_name));
3435
MOCK_METHOD1(OnCursorRectUpdated, void(const Rect& rect));
3536
MOCK_METHOD0(OnResetImeComposing, void());
3637
MOCK_METHOD3(OnBitmapSurfaceUpdated,

shell/platform/windows/window_binding_handler.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ class WindowBindingHandler {
7272
// content. See mouse_cursor.dart for the values and meanings of cursor_name.
7373
virtual void UpdateFlutterCursor(const std::string& cursor_name) = 0;
7474

75+
// Sets the cursor directly from a cursor handle.
76+
virtual void SetFlutterCursor(HCURSOR cursor) = 0;
77+
7578
// Invoked when the cursor/composing rect has been updated in the framework.
7679
virtual void OnCursorRectUpdated(const Rect& rect) = 0;
7780

0 commit comments

Comments
 (0)