From 05c6cb567d9cafb09e2a39e1d8fea5f692582bbb Mon Sep 17 00:00:00 2001 From: Alexey Kostromin Date: Mon, 3 Jun 2024 10:55:50 +0500 Subject: [PATCH] "writeImage" method support for windows (#145) * fix js version dependance * writeImage method support for windows --------- Co-authored-by: alexkmbk --- packages/pasteboard/lib/pasteboard.dart | 2 +- .../lib/src/pasteboard_platform_io.dart | 24 ++++++ packages/pasteboard/windows/CMakeLists.txt | 6 ++ .../pasteboard/windows/pasteboard_plugin.cpp | 75 +++++++++++++++++++ 4 files changed, 106 insertions(+), 1 deletion(-) diff --git a/packages/pasteboard/lib/pasteboard.dart b/packages/pasteboard/lib/pasteboard.dart index 0abac27e..e3cc42a7 100644 --- a/packages/pasteboard/lib/pasteboard.dart +++ b/packages/pasteboard/lib/pasteboard.dart @@ -10,7 +10,7 @@ class Pasteboard { /// available on iOS, desktop and the web. static Future get image => pasteboard.image; - /// only available on Windows + /// only available on Windows and the web. /// Get "HTML format" from system pasteboard. /// HTML format: https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767917(v=vs.85) static Future get html => pasteboard.html; diff --git a/packages/pasteboard/lib/src/pasteboard_platform_io.dart b/packages/pasteboard/lib/src/pasteboard_platform_io.dart index 279c6abf..b1451fd8 100644 --- a/packages/pasteboard/lib/src/pasteboard_platform_io.dart +++ b/packages/pasteboard/lib/src/pasteboard_platform_io.dart @@ -1,5 +1,7 @@ import 'dart:io'; import 'dart:typed_data'; +import 'package:path/path.dart' as Path; +import 'package:uuid/uuid.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; @@ -63,6 +65,12 @@ class PasteboardPlatformIO implements PasteboardPlatform { } if (Platform.isIOS || Platform.isMacOS) { await _channel.invokeMethod('writeImage', image); + } else if (Platform.isWindows) { + final file = await File(GetTempFileName()).create(); + file.writeAsBytesSync(image); + await _channel + .invokeMethod('writeImage', {'fileName': file.path}); + file.delete(); } } @@ -77,3 +85,19 @@ class PasteboardPlatformIO implements PasteboardPlatform { Clipboard.setData(ClipboardData(text: value)); } } + +String GetTempFileName() { + final dir = Directory.systemTemp; + String tempFileName; + + var uuid = Uuid(); + + while (true) { + tempFileName = Path.join(dir.path, uuid.v1().toString()); + if (!File(tempFileName).existsSync()) { + break; + } + } + + return tempFileName; +} diff --git a/packages/pasteboard/windows/CMakeLists.txt b/packages/pasteboard/windows/CMakeLists.txt index 6829ed50..39b6a6df 100644 --- a/packages/pasteboard/windows/CMakeLists.txt +++ b/packages/pasteboard/windows/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.14) set(PROJECT_NAME "pasteboard") project(${PROJECT_NAME} LANGUAGES CXX) + # This value is used when generating builds using this plugin, so it must # not be changed set(PLUGIN_NAME "pasteboard_plugin") @@ -17,6 +18,11 @@ target_include_directories(${PLUGIN_NAME} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) +# GdiPlus doesn't compile with /W4 warning level +target_compile_options(${PLUGIN_NAME} PRIVATE + $<$:/W3 /WX> + $<$>:-Wall -Wextra -Wpedantic -Werror> +) # List of absolute paths to libraries that should be bundled with the plugin diff --git a/packages/pasteboard/windows/pasteboard_plugin.cpp b/packages/pasteboard/windows/pasteboard_plugin.cpp index 82fc3cb9..b235ab5d 100644 --- a/packages/pasteboard/windows/pasteboard_plugin.cpp +++ b/packages/pasteboard/windows/pasteboard_plugin.cpp @@ -4,14 +4,18 @@ #include #include + #include #include +#include #include #include #include "strconv.h" +#pragma comment(lib, "GdiPlus") + namespace { constexpr STGMEDIUM kNullStorageMedium = {TYMED_NULL, nullptr, nullptr}; @@ -131,6 +135,49 @@ PBITMAPINFO CreateBitmapInfoStruct(HBITMAP hBmp) { return pbmi; } +// The code was taken from this answer: https://stackoverflow.com/a/39201008/2134488 +// +bool CopyImageToClipboard(const wchar_t* filename) +{ + //initialize Gdiplus once: + Gdiplus::GdiplusStartupInput gdiplusStartupInput; + ULONG_PTR gdiplusToken; + Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); + + bool result = false; + Gdiplus::Bitmap *gdibmp = Gdiplus::Bitmap::FromFile(filename); + if (gdibmp) + { + HBITMAP hbitmap; + gdibmp->GetHBITMAP(0, &hbitmap); + if (OpenClipboard(NULL)) + { + EmptyClipboard(); + DIBSECTION ds; + if (GetObject(hbitmap, sizeof(DIBSECTION), &ds)) + { + HDC hdc = GetDC(HWND_DESKTOP); + //create compatible bitmap (get DDB from DIB) + HBITMAP hbitmap_ddb = CreateDIBitmap(hdc, &ds.dsBmih, CBM_INIT, + ds.dsBm.bmBits, (BITMAPINFO*)&ds.dsBmih, DIB_RGB_COLORS); + ReleaseDC(HWND_DESKTOP, hdc); + SetClipboardData(CF_BITMAP, hbitmap_ddb); + DeleteObject(hbitmap_ddb); + result = true; + } + CloseClipboard(); + } + + //cleanup: + DeleteObject(hbitmap); + delete gdibmp; + } + + Gdiplus::GdiplusShutdown(gdiplusToken); + + return result; +} + void CreateBMPFile(LPCTSTR pszFile, HBITMAP hBMP) { HANDLE hf; // file handle BITMAPFILEHEADER hdr; // bitmap file-header @@ -431,6 +478,34 @@ void PasteboardPlugin::HandleMethodCall( SetClipboardData(CF_HDROP, storage.hGlobal); result->Success(); } + else if (method_call.method_name() == "writeImage") { + + + const auto* arguments = std::get_if(method_call.arguments()); + + std::string fileName; + if (arguments) { + auto it = arguments->find(flutter::EncodableValue("fileName")); + if (it != arguments->end()) { + if (std::holds_alternative(it->second)) { + fileName = std::get(it->second); + } + } + } + + if (fileName.size() == 0) { + result->Error("0", "File name is empty"); + return; + } + std::wstring wsFileName = cp_to_wide(fileName, CP_UTF8); + + if (!CopyImageToClipboard(wsFileName.c_str())) { + result->Error("0", "Failed to copy image to clipboard"); + return; + } + + result->Success(); +} else if (method_call.method_name() == "html") { UINT CF_HTML = RegisterClipboardFormatA("HTML Format");