| // 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. | |
| #include "flutter/shell/platform/windows/cursor_handler.h" | |
| #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"; | |
| static constexpr char kActivateSystemCursorMethod[] = "activateSystemCursor"; | |
| static constexpr char kKindKey[] = "kind"; | |
| // This method allows creating a custom cursor with rawBGRA buffer, returns a | |
| // string to identify the cursor. | |
| static constexpr char kCreateCustomCursorMethod[] = | |
| "createCustomCursor/windows"; | |
| // A string, the custom cursor's name. | |
| static constexpr char kCustomCursorNameKey[] = "name"; | |
| // A list of bytes, the custom cursor's rawBGRA buffer. | |
| static constexpr char kCustomCursorBufferKey[] = "buffer"; | |
| // A double, the x coordinate of the custom cursor's hotspot, starting from | |
| // left. | |
| static constexpr char kCustomCursorHotXKey[] = "hotX"; | |
| // A double, the y coordinate of the custom cursor's hotspot, starting from top. | |
| static constexpr char kCustomCursorHotYKey[] = "hotY"; | |
| // An int value for the width of the custom cursor. | |
| static constexpr char kCustomCursorWidthKey[] = "width"; | |
| // An int value for the height of the custom cursor. | |
| static constexpr char kCustomCursorHeightKey[] = "height"; | |
| // This method also has an argument `kCustomCursorNameKey` for the name | |
| // of the cursor to activate. | |
| static constexpr char kSetCustomCursorMethod[] = "setCustomCursor/windows"; | |
| // This method also has an argument `kCustomCursorNameKey` for the name | |
| // of the cursor to delete. | |
| static constexpr char kDeleteCustomCursorMethod[] = | |
| "deleteCustomCursor/windows"; | |
| // Error codes used for responses. | |
| static constexpr char kCursorError[] = "Cursor error"; | |
| namespace flutter { | |
| CursorHandler::CursorHandler(BinaryMessenger* messenger, | |
| FlutterWindowsEngine* engine) | |
| : channel_(std::make_unique<MethodChannel<EncodableValue>>( | |
| messenger, | |
| kChannelName, | |
| &StandardMethodCodec::GetInstance())), | |
| engine_(engine) { | |
| channel_->SetMethodCallHandler( | |
| [this](const MethodCall<EncodableValue>& call, | |
| std::unique_ptr<MethodResult<EncodableValue>> result) { | |
| HandleMethodCall(call, std::move(result)); | |
| }); | |
| } | |
| void CursorHandler::HandleMethodCall( | |
| const MethodCall<EncodableValue>& method_call, | |
| std::unique_ptr<MethodResult<EncodableValue>> result) { | |
| const std::string& method = method_call.method_name(); | |
| if (method.compare(kActivateSystemCursorMethod) == 0) { | |
| const auto& arguments = std::get<EncodableMap>(*method_call.arguments()); | |
| auto kind_iter = arguments.find(EncodableValue(std::string(kKindKey))); | |
| if (kind_iter == arguments.end()) { | |
| result->Error("Argument error", | |
| "Missing argument while trying to activate system cursor"); | |
| return; | |
| } | |
| const auto& kind = std::get<std::string>(kind_iter->second); | |
| 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()); | |
| auto name_iter = | |
| arguments.find(EncodableValue(std::string(kCustomCursorNameKey))); | |
| if (name_iter == arguments.end()) { | |
| result->Error( | |
| "Argument error", | |
| "Missing argument name while trying to customize system cursor"); | |
| return; | |
| } | |
| auto name = std::get<std::string>(name_iter->second); | |
| auto buffer_iter = | |
| arguments.find(EncodableValue(std::string(kCustomCursorBufferKey))); | |
| if (buffer_iter == arguments.end()) { | |
| result->Error( | |
| "Argument error", | |
| "Missing argument buffer while trying to customize system cursor"); | |
| return; | |
| } | |
| auto buffer = std::get<std::vector<uint8_t>>(buffer_iter->second); | |
| auto width_iter = | |
| arguments.find(EncodableValue(std::string(kCustomCursorWidthKey))); | |
| if (width_iter == arguments.end()) { | |
| result->Error( | |
| "Argument error", | |
| "Missing argument width while trying to customize system cursor"); | |
| return; | |
| } | |
| auto width = std::get<int>(width_iter->second); | |
| auto height_iter = | |
| arguments.find(EncodableValue(std::string(kCustomCursorHeightKey))); | |
| if (height_iter == arguments.end()) { | |
| result->Error( | |
| "Argument error", | |
| "Missing argument height while trying to customize system cursor"); | |
| return; | |
| } | |
| auto height = std::get<int>(height_iter->second); | |
| auto hot_x_iter = | |
| arguments.find(EncodableValue(std::string(kCustomCursorHotXKey))); | |
| if (hot_x_iter == arguments.end()) { | |
| result->Error( | |
| "Argument error", | |
| "Missing argument hotX while trying to customize system cursor"); | |
| return; | |
| } | |
| auto hot_x = std::get<double>(hot_x_iter->second); | |
| auto hot_y_iter = | |
| arguments.find(EncodableValue(std::string(kCustomCursorHotYKey))); | |
| if (hot_y_iter == arguments.end()) { | |
| result->Error( | |
| "Argument error", | |
| "Missing argument hotY while trying to customize system cursor"); | |
| return; | |
| } | |
| auto hot_y = std::get<double>(hot_y_iter->second); | |
| HCURSOR cursor = GetCursorFromBuffer(buffer, hot_x, hot_y, width, height); | |
| if (cursor == nullptr) { | |
| result->Error("Argument error", | |
| "Argument must contains a valid rawBGRA bitmap"); | |
| return; | |
| } | |
| // Push the cursor into the cache map. | |
| custom_cursors_.emplace(name, std::move(cursor)); | |
| result->Success(flutter::EncodableValue(std::move(name))); | |
| } else if (method.compare(kSetCustomCursorMethod) == 0) { | |
| const auto& arguments = std::get<EncodableMap>(*method_call.arguments()); | |
| auto name_iter = | |
| arguments.find(EncodableValue(std::string(kCustomCursorNameKey))); | |
| if (name_iter == arguments.end()) { | |
| result->Error("Argument error", | |
| "Missing argument key while trying to set a custom cursor"); | |
| return; | |
| } | |
| auto name = std::get<std::string>(name_iter->second); | |
| if (custom_cursors_.find(name) == custom_cursors_.end()) { | |
| result->Error( | |
| "Argument error", | |
| "The custom cursor identified by the argument key cannot be found"); | |
| return; | |
| } | |
| HCURSOR cursor = custom_cursors_[name]; | |
| 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()); | |
| auto name_iter = | |
| arguments.find(EncodableValue(std::string(kCustomCursorNameKey))); | |
| if (name_iter == arguments.end()) { | |
| result->Error( | |
| "Argument error", | |
| "Missing argument key while trying to delete a custom cursor"); | |
| return; | |
| } | |
| auto name = std::get<std::string>(name_iter->second); | |
| auto it = custom_cursors_.find(name); | |
| // If the specified cursor name is not found, the deletion is a noop and | |
| // returns success. | |
| if (it != custom_cursors_.end()) { | |
| DeleteObject(it->second); | |
| custom_cursors_.erase(it); | |
| } | |
| result->Success(); | |
| } else { | |
| result->NotImplemented(); | |
| } | |
| } | |
| HCURSOR GetCursorFromBuffer(const std::vector<uint8_t>& buffer, | |
| double hot_x, | |
| double hot_y, | |
| int width, | |
| int height) { | |
| HCURSOR cursor = nullptr; | |
| HDC display_dc = GetDC(NULL); | |
| // Flutter should returns rawBGRA, which has 8bits * 4channels. | |
| BITMAPINFO bmi; | |
| memset(&bmi, 0, sizeof(bmi)); | |
| bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); | |
| bmi.bmiHeader.biWidth = width; | |
| bmi.bmiHeader.biHeight = -height; | |
| bmi.bmiHeader.biPlanes = 1; | |
| bmi.bmiHeader.biBitCount = 32; | |
| bmi.bmiHeader.biCompression = BI_RGB; | |
| bmi.bmiHeader.biSizeImage = width * height * 4; | |
| // Create the pixmap DIB section | |
| uint8_t* pixels = 0; | |
| HBITMAP bitmap = | |
| CreateDIBSection(display_dc, &bmi, DIB_RGB_COLORS, (void**)&pixels, 0, 0); | |
| ReleaseDC(0, display_dc); | |
| if (!bitmap || !pixels) { | |
| return nullptr; | |
| } | |
| int bytes_per_line = width * 4; | |
| for (int y = 0; y < height; ++y) { | |
| memcpy(pixels + y * bytes_per_line, &buffer[bytes_per_line * y], | |
| bytes_per_line); | |
| } | |
| HBITMAP mask; | |
| GetMaskBitmaps(bitmap, mask); | |
| ICONINFO icon_info; | |
| icon_info.fIcon = 0; | |
| icon_info.xHotspot = hot_x; | |
| icon_info.yHotspot = hot_y; | |
| icon_info.hbmMask = mask; | |
| icon_info.hbmColor = bitmap; | |
| cursor = CreateIconIndirect(&icon_info); | |
| DeleteObject(mask); | |
| DeleteObject(bitmap); | |
| return cursor; | |
| } | |
| void GetMaskBitmaps(HBITMAP bitmap, HBITMAP& mask_bitmap) { | |
| HDC h_dc = ::GetDC(NULL); | |
| HDC h_main_dc = ::CreateCompatibleDC(h_dc); | |
| HDC h_and_mask_dc = ::CreateCompatibleDC(h_dc); | |
| // Get the dimensions of the source bitmap | |
| BITMAP bm; | |
| ::GetObject(bitmap, sizeof(BITMAP), &bm); | |
| mask_bitmap = ::CreateCompatibleBitmap(h_dc, bm.bmWidth, bm.bmHeight); | |
| // Select the bitmaps to DC | |
| HBITMAP h_old_main_bitmap = (HBITMAP)::SelectObject(h_main_dc, bitmap); | |
| HBITMAP h_old_and_mask_bitmap = | |
| (HBITMAP)::SelectObject(h_and_mask_dc, mask_bitmap); | |
| // Scan each pixel of the souce bitmap and create the masks | |
| COLORREF main_bit_pixel; | |
| for (int x = 0; x < bm.bmWidth; ++x) { | |
| for (int y = 0; y < bm.bmHeight; ++y) { | |
| main_bit_pixel = ::GetPixel(h_main_dc, x, y); | |
| if (main_bit_pixel == RGB(0, 0, 0)) { | |
| ::SetPixel(h_and_mask_dc, x, y, RGB(255, 255, 255)); | |
| } else { | |
| ::SetPixel(h_and_mask_dc, x, y, RGB(0, 0, 0)); | |
| } | |
| } | |
| } | |
| ::SelectObject(h_main_dc, h_old_main_bitmap); | |
| ::SelectObject(h_and_mask_dc, h_old_and_mask_bitmap); | |
| ::DeleteDC(h_and_mask_dc); | |
| ::DeleteDC(h_main_dc); | |
| ::ReleaseDC(NULL, h_dc); | |
| } | |
| } // namespace flutter |