From 67b94c79060b2844772377b25b2611b429140bb0 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 11 Sep 2026 16:02:52 +0800 Subject: [PATCH] feat(windows): measure the native system cursor size --- flutter/windows/runner/cursor_size.h | 122 ++++++++++++++++++++++ flutter/windows/runner/flutter_window.cpp | 3 + 2 files changed, 125 insertions(+) create mode 100644 flutter/windows/runner/cursor_size.h diff --git a/flutter/windows/runner/cursor_size.h b/flutter/windows/runner/cursor_size.h new file mode 100644 index 000000000..9edaf085a --- /dev/null +++ b/flutter/windows/runner/cursor_size.h @@ -0,0 +1,122 @@ +#ifndef RUSTDESK_CURSOR_SIZE_H_ +#define RUSTDESK_CURSOR_SIZE_H_ + +#include +#include +#include + +#include +#include +#include + +namespace cursor_size { + +struct IconBitmaps { + ICONINFO info{}; + ~IconBitmaps() { + if (info.hbmColor) DeleteObject(info.hbmColor); + if (info.hbmMask) DeleteObject(info.hbmMask); + } +}; + +struct Surface { + HDC dc = CreateCompatibleDC(nullptr); + HBITMAP bitmap = nullptr; + HGDIOBJ previous = nullptr; + uint32_t* pixels = nullptr; + int width = 0, height = 0; + + ~Surface() { + if (previous) SelectObject(dc, previous); + if (bitmap) DeleteObject(bitmap); + if (dc) DeleteDC(dc); + } + + void Init(const BITMAP& source, bool monochrome) { + width = source.bmWidth; + height = monochrome ? source.bmHeight / 2 : source.bmHeight; + if (!dc || width <= 0 || height <= 0) { + throw std::runtime_error("Could not create the cursor measurement surface"); + } + constexpr WORD kColorBits = 32; + BITMAPINFO format{}; + format.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + format.bmiHeader.biWidth = width; + format.bmiHeader.biHeight = -height; + format.bmiHeader.biPlanes = 1; + format.bmiHeader.biBitCount = kColorBits; + format.bmiHeader.biCompression = BI_RGB; + bitmap = CreateDIBSection(dc, &format, DIB_RGB_COLORS, + reinterpret_cast(&pixels), nullptr, 0); + if (!bitmap) throw std::runtime_error("Could not allocate the cursor bitmap"); + previous = SelectObject(dc, bitmap); + if (!previous || previous == HGDI_ERROR) { + previous = nullptr; + throw std::runtime_error("Could not select the cursor bitmap"); + } + } +}; + +inline void IncludeVisiblePixels(const Surface& surface, uint32_t background, + RECT& bounds) { + constexpr uint32_t kRgbMask = 0x00ffffff; + for (int y = 0; y < surface.height; ++y) { + for (int x = 0; x < surface.width; ++x) { + if ((surface.pixels[y * surface.width + x] & kRgbMask) == background) continue; + bounds.left = (std::min)(bounds.left, static_cast(x)); + bounds.right = (std::max)(bounds.right, static_cast(x + 1)); + bounds.top = (std::min)(bounds.top, static_cast(y)); + bounds.bottom = (std::max)(bounds.bottom, static_cast(y + 1)); + } + } +} + +inline double SystemSize() { + HCURSOR cursor = LoadCursorW(nullptr, IDC_ARROW); + IconBitmaps bitmaps; + if (!cursor || !GetIconInfo(cursor, &bitmaps.info)) { + throw std::runtime_error("Could not read the Windows system cursor"); + } + BITMAP source{}; + const auto bitmap = bitmaps.info.hbmColor ? bitmaps.info.hbmColor : bitmaps.info.hbmMask; + if (!GetObject(bitmap, sizeof(source), &source)) { + throw std::runtime_error("Could not read the system cursor bitmap size"); + } + Surface surface; + surface.Init(source, !bitmaps.info.hbmColor); + RECT bounds{surface.width, surface.height, 0, 0}; + // Drawing on both backgrounds measures alpha and legacy AND/XOR cursors. + constexpr uint32_t kBlack = 0x00000000, kWhite = 0x00ffffff; + for (const auto background : {kBlack, kWhite}) { + std::fill_n(surface.pixels, surface.width * surface.height, background); + if (!DrawIconEx(surface.dc, 0, 0, cursor, 0, 0, 0, nullptr, DI_NORMAL) || !GdiFlush()) { + throw std::runtime_error("Could not draw the Windows system cursor"); + } + IncludeVisiblePixels(surface, background, bounds); + } + if (bounds.right <= bounds.left) { + throw std::runtime_error("System cursor has no visible pixels"); + } + return (std::max)(bounds.right - bounds.left, bounds.bottom - bounds.top); +} + +inline void Register(flutter::BinaryMessenger* messenger) { + flutter::MethodChannel<> channel(messenger, "org.rustdesk.rustdesk/cursor", + &flutter::StandardMethodCodec::GetInstance()); + channel.SetMethodCallHandler([](const flutter::MethodCall<>& call, + std::unique_ptr> result) { + if (call.method_name() != "getSystemCursorSize") { + result->NotImplemented(); + return; + } + try { + result->Success(flutter::EncodableValue(SystemSize())); + } catch (const std::exception& error) { + result->Error("cursor_size", error.what()); + } + }); +} + +} // namespace cursor_size + +#endif // RUSTDESK_CURSOR_SIZE_H_ diff --git a/flutter/windows/runner/flutter_window.cpp b/flutter/windows/runner/flutter_window.cpp index 501436d71..705cb24e0 100644 --- a/flutter/windows/runner/flutter_window.cpp +++ b/flutter/windows/runner/flutter_window.cpp @@ -18,6 +18,7 @@ #include #include "win32_desktop.h" +#include "cursor_size.h" namespace { @@ -100,6 +101,7 @@ bool FlutterWindow::OnCreate() { return false; } RegisterPlugins(flutter_controller_->engine()); + cursor_size::Register(flutter_controller_->engine()->messenger()); flutter::MethodChannel<> channel( flutter_controller_->engine()->messenger(), @@ -146,6 +148,7 @@ bool FlutterWindow::OnCreate() { auto *flutter_view_controller = reinterpret_cast(controller); auto *registry = flutter_view_controller->engine(); + cursor_size::Register(registry->messenger()); TextureRgbaRendererPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("TextureRgbaRendererPlugin")); FlutterGpuTextureRendererPluginCApiRegisterWithRegistrar(