Skip to content

Commit

Permalink
"writeImage" method support for windows (#145)
Browse files Browse the repository at this point in the history
* fix js version dependance

* writeImage method support for windows

---------

Co-authored-by: alexkmbk <[email protected]>
  • Loading branch information
alexkmbk and alexkmbk authored Jun 3, 2024
1 parent 22bf4e6 commit 05c6cb5
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 1 deletion.
2 changes: 1 addition & 1 deletion packages/pasteboard/lib/pasteboard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Pasteboard {
/// available on iOS, desktop and the web.
static Future<Uint8List?> 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<String?> get html => pasteboard.html;
Expand Down
24 changes: 24 additions & 0 deletions packages/pasteboard/lib/src/pasteboard_platform_io.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -63,6 +65,12 @@ class PasteboardPlatformIO implements PasteboardPlatform {
}
if (Platform.isIOS || Platform.isMacOS) {
await _channel.invokeMethod<void>('writeImage', image);
} else if (Platform.isWindows) {
final file = await File(GetTempFileName()).create();
file.writeAsBytesSync(image);
await _channel
.invokeMethod<Object>('writeImage', {'fileName': file.path});
file.delete();
}
}

Expand All @@ -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;
}
6 changes: 6 additions & 0 deletions packages/pasteboard/windows/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
$<$<CXX_COMPILER_ID:MSVC>:/W3 /WX>
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Wpedantic -Werror>
)


# List of absolute paths to libraries that should be bundled with the plugin
Expand Down
75 changes: 75 additions & 0 deletions packages/pasteboard/windows/pasteboard_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
#include <flutter/plugin_registrar_windows.h>
#include <flutter/standard_method_codec.h>


#include <Windows.h>
#include <ShlObj.h>
#include <gdiplus.h>

#include <map>
#include <memory>

#include "strconv.h"

#pragma comment(lib, "GdiPlus")

namespace {

constexpr STGMEDIUM kNullStorageMedium = {TYMED_NULL, nullptr, nullptr};
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<flutter::EncodableMap>(method_call.arguments());

std::string fileName;
if (arguments) {
auto it = arguments->find(flutter::EncodableValue("fileName"));
if (it != arguments->end()) {
if (std::holds_alternative<std::string>(it->second)) {
fileName = std::get<std::string>(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");
Expand Down

0 comments on commit 05c6cb5

Please sign in to comment.