fox_utils 0.1.7: clipboard, drag & drop, Android file saving

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
FoxyzandClaude Opus 5.5 committed 2026-10-09 06:31:15 +03:00
commit dc16bba054
46 files changed
+4738

No files matched your search

+36
View File
@@ -0,0 +1,36 @@
cmake_minimum_required(VERSION 3.14)
set(PROJECT_NAME "fox_utils")
project(${PROJECT_NAME} LANGUAGES CXX)
# This value is used when generating builds using this plugin, so it must
# not be changed
set(PLUGIN_NAME "fox_utils_plugin")
add_library(${PLUGIN_NAME} SHARED
"fox_utils_plugin.cpp"
"pasteboard_plugin.cpp"
"desktop_drop_plugin.cpp"
)
apply_standard_settings(${PLUGIN_NAME})
set_target_properties(${PLUGIN_NAME} PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
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; strict flags are scoped to
# this package's own sources. The vendored desktop_drop engine is compiled
# as upstream ships it (no warnings-as-errors) since it is not ours to churn.
set_source_files_properties("fox_utils_plugin.cpp" "pasteboard_plugin.cpp"
PROPERTIES COMPILE_OPTIONS
"$<$<CXX_COMPILER_ID:MSVC>:/W3 /WX>;$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Wpedantic -Werror>")
set_source_files_properties("desktop_drop_plugin.cpp"
PROPERTIES COMPILE_OPTIONS
"$<$<CXX_COMPILER_ID:MSVC>:/W3>")
# List of absolute paths to libraries that should be bundled with the plugin
set(fox_utils_bundled_libraries
""
PARENT_SCOPE
)
+285
View File
@@ -0,0 +1,285 @@
#include "include/fox_utils/desktop_drop_plugin.h"
#include <windows.h>
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar_windows.h>
#include <flutter/standard_method_codec.h>
#include <memory>
#include <sstream>
#include <algorithm>
namespace {
using FlutterMethodChannel = std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>>;
using namespace std::literals::string_literals;
std::string ws2s(const std::wstring &wstr) {
if (wstr.empty()) {
return {};
}
size_t pos;
size_t begin = 0;
std::string ret;
int size;
pos = wstr.find(static_cast<wchar_t>(0), begin);
while (pos != std::wstring::npos && begin < wstr.length()) {
std::wstring segment = std::wstring(&wstr[begin], pos - begin);
size = WideCharToMultiByte(CP_UTF8,
WC_ERR_INVALID_CHARS,
&segment[0],
(int) segment.size(),
nullptr,
0,
nullptr,
nullptr);
std::string converted = std::string(size, 0);
WideCharToMultiByte(CP_UTF8,
WC_ERR_INVALID_CHARS,
&segment[0],
(int) segment.size(),
&converted[0],
(int) converted.size(),
nullptr,
nullptr);
ret.append(converted);
ret.append({0});
begin = pos + 1;
pos = wstr.find(static_cast<wchar_t>(0), begin);
}
if (begin <= wstr.length()) {
std::wstring segment = std::wstring(&wstr[begin], wstr.length() - begin);
size =
WideCharToMultiByte(CP_UTF8,
WC_ERR_INVALID_CHARS,
&segment[0],
(int) segment.size(),
nullptr,
0,
nullptr,
nullptr);
std::string converted = std::string(size, 0);
WideCharToMultiByte(CP_UTF8,
WC_ERR_INVALID_CHARS,
&segment[0],
(int) segment.size(),
&converted[0],
(int) converted.size(),
nullptr,
nullptr);
ret.append(converted);
}
return ret;
}
class DesktopDropTarget : public IDropTarget {
public:
DesktopDropTarget(FlutterMethodChannel channel, HWND window_handle);
HRESULT DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) override;
HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) override;
HRESULT DragLeave() override;
HRESULT Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) override;
HRESULT QueryInterface(const IID &riid, void **ppvObject) override;
ULONG AddRef() override;
ULONG Release() override;
virtual ~DesktopDropTarget();
private:
FlutterMethodChannel channel_;
HWND window_handle_;
LONG ref_count_;
bool need_revoke_ole_initialize_;
};
class DesktopDropPlugin : public flutter::Plugin {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);
explicit DesktopDropPlugin(DesktopDropTarget *target);
~DesktopDropPlugin() override;
private:
DesktopDropTarget *target_;
};
DesktopDropPlugin::DesktopDropPlugin(DesktopDropTarget *target) : target_(target) {
target_->AddRef();
}
DesktopDropPlugin::~DesktopDropPlugin() {
target_->Release();
}
// static
void DesktopDropPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarWindows *registrar) {
auto channel =
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
registrar->messenger(), "desktop_drop",
&flutter::StandardMethodCodec::GetInstance());
HWND hwnd = nullptr;
if (registrar->GetView()) {
hwnd = registrar->GetView()->GetNativeWindow();
}
if (hwnd == nullptr) {
// no window, no drop.
return;
}
channel->SetMethodCallHandler([](const auto &call, auto result) {
result->NotImplemented();
});
auto drop_target = new DesktopDropTarget(std::move(channel), hwnd);
auto plugin = std::make_unique<DesktopDropPlugin>(drop_target);
registrar->AddPlugin(std::move(plugin));
}
DesktopDropTarget::DesktopDropTarget(FlutterMethodChannel channel, HWND window_handle) : channel_(
std::move(channel)), window_handle_(window_handle), ref_count_(0), need_revoke_ole_initialize_(false) {
auto ret = RegisterDragDrop(window_handle_, this);
if (ret == E_OUTOFMEMORY) {
OleInitialize(nullptr);
ret = RegisterDragDrop(window_handle_, this);
if (ret == 0) {
need_revoke_ole_initialize_ = true;
}
}
if (ret != 0) {
std::cout << "RegisterDragDrop failed: " << ret << std::endl;
}
}
HRESULT DesktopDropTarget::DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
POINT point = {pt.x, pt.y};
ScreenToClient(window_handle_, &point);
channel_->InvokeMethod("entered", std::make_unique<flutter::EncodableValue>(
flutter::EncodableList{
flutter::EncodableValue(double(point.x)),
flutter::EncodableValue(double(point.y))
}
));
return 0;
}
HRESULT DesktopDropTarget::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
POINT point = {pt.x, pt.y};
ScreenToClient(window_handle_, &point);
channel_->InvokeMethod("updated", std::make_unique<flutter::EncodableValue>(
flutter::EncodableList{
flutter::EncodableValue(double(point.x)),
flutter::EncodableValue(double(point.y))
}
));
return 0;
}
HRESULT DesktopDropTarget::DragLeave() {
channel_->InvokeMethod("exited", std::make_unique<flutter::EncodableValue>());
return 0;
}
HRESULT DesktopDropTarget::Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
flutter::EncodableList list = {};
// construct a FORMATETC object
FORMATETC fmtetc = {CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
STGMEDIUM stgmed;
// See if the dataobject contains any TEXT stored as a HGLOBAL
if (pDataObj->QueryGetData(&fmtetc) == S_OK) {
// Yippie! the data is there, so go get it!
if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK) {
// we asked for the data as a HGLOBAL, so access it appropriately
PVOID data = GlobalLock(stgmed.hGlobal);
if (data != nullptr) {
auto files = DragQueryFile(reinterpret_cast<HDROP>(data), 0xFFFFFFFF, nullptr, 0);
for (unsigned int i = 0; i < files; ++i) {
TCHAR filename[MAX_PATH];
DragQueryFile(reinterpret_cast<HDROP>(data), i, filename, sizeof(TCHAR) * MAX_PATH);
std::wstring wide(filename);
std::string path = ws2s(wide);
std::cout << "done: " << path << std::endl;
list.push_back(flutter::EncodableValue(path));
}
GlobalUnlock(stgmed.hGlobal);
}
// release the data using the COM API
ReleaseStgMedium(&stgmed);
}
}
channel_->InvokeMethod("performOperation", std::make_unique<flutter::EncodableValue>(list));
return 0;
}
HRESULT DesktopDropTarget::QueryInterface(const IID &iid, void **ppvObject) {
if (iid == IID_IDropTarget || iid == IID_IUnknown) {
AddRef();
*ppvObject = this;
return S_OK;
} else {
*ppvObject = nullptr;
return E_NOINTERFACE;
}
}
ULONG DesktopDropTarget::AddRef() {
return InterlockedIncrement(&ref_count_);
}
ULONG DesktopDropTarget::Release() {
LONG count = InterlockedDecrement(&ref_count_);
if (count == 0) {
delete this;
return 0;
} else {
return count;
}
}
DesktopDropTarget::~DesktopDropTarget() {
RevokeDragDrop(window_handle_);
if (need_revoke_ole_initialize_) {
OleUninitialize();
}
}
} // namespace
void DesktopDropPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar) {
DesktopDropPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarManager::GetInstance()
->GetRegistrar<flutter::PluginRegistrarWindows>(registrar));
}
+9
View File
@@ -0,0 +1,9 @@
#include "include/fox_utils/fox_utils_plugin.h"
#include "include/fox_utils/pasteboard_plugin.h"
#include "include/fox_utils/desktop_drop_plugin.h"
void FoxUtilsPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar) {
PasteboardPluginRegisterWithRegistrar(registrar);
DesktopDropPluginRegisterWithRegistrar(registrar);
}
@@ -0,0 +1,23 @@
#ifndef FLUTTER_PLUGIN_DESKTOP_DROP_PLUGIN_H_
#define FLUTTER_PLUGIN_DESKTOP_DROP_PLUGIN_H_
#include <flutter_plugin_registrar.h>
#ifdef FLUTTER_PLUGIN_IMPL
#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport)
#else
#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport)
#endif
#if defined(__cplusplus)
extern "C" {
#endif
FLUTTER_PLUGIN_EXPORT void DesktopDropPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar);
#if defined(__cplusplus)
} // extern "C"
#endif
#endif // FLUTTER_PLUGIN_DESKTOP_DROP_PLUGIN_H_
@@ -0,0 +1,26 @@
#ifndef FLUTTER_PLUGIN_FOX_UTILS_PLUGIN_H_
#define FLUTTER_PLUGIN_FOX_UTILS_PLUGIN_H_
#include <flutter_plugin_registrar.h>
#ifdef FLUTTER_PLUGIN_IMPL
#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport)
#else
#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport)
#endif
#if defined(__cplusplus)
extern "C" {
#endif
// Single native registration entry for the fox_utils package on Windows.
// The generated plugin registrant calls this function; it in turn registers
// every vendored engine in this package (pasteboard, desktop_drop).
FLUTTER_PLUGIN_EXPORT void FoxUtilsPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar);
#if defined(__cplusplus)
} // extern "C"
#endif
#endif // FLUTTER_PLUGIN_FOX_UTILS_PLUGIN_H_
@@ -0,0 +1,23 @@
#ifndef FLUTTER_PLUGIN_PASTEBOARD_PLUGIN_H_
#define FLUTTER_PLUGIN_PASTEBOARD_PLUGIN_H_
#include <flutter_plugin_registrar.h>
#ifdef FLUTTER_PLUGIN_IMPL
#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport)
#else
#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport)
#endif
#if defined(__cplusplus)
extern "C" {
#endif
FLUTTER_PLUGIN_EXPORT void PasteboardPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar);
#if defined(__cplusplus)
} // extern "C"
#endif
#endif // FLUTTER_PLUGIN_PASTEBOARD_PLUGIN_H_
+555
View File
@@ -0,0 +1,555 @@
#include "include/fox_utils/pasteboard_plugin.h"
#include <flutter/method_channel.h>
#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};
STGMEDIUM CreateStorageForFileNames(const std::vector<std::string> &filenames) {
// CF_HDROP clipboard format consists of DROPFILES structure, a series of file
// names including the terminating null character and the additional null
// character at the tail to terminate the array.
// For example,
//| DROPFILES | FILENAME 1 | NULL | ... | FILENAME n | NULL | NULL |
// For more details, please refer to
// https://docs.microsoft.com/en-us/windows/desktop/shell/clipboard#cf_hdrop
if (filenames.empty())
return kNullStorageMedium;
const size_t kDropFilesHeaderSizeInBytes = sizeof(DROPFILES);
size_t total_bytes = kDropFilesHeaderSizeInBytes;
for (const auto &filename: filenames) {
// Allocate memory of the filename's length including the null
// character.
total_bytes += (filename.length() + 1) * sizeof(wchar_t);
}
// |data| needs to be terminated by an additional null character.
total_bytes += sizeof(wchar_t);
// GHND combines GMEM_MOVEABLE and GMEM_ZEROINIT, and GMEM_ZEROINIT
// initializes memory contents to zero.
HANDLE hdata = GlobalAlloc(GHND, total_bytes);
auto *drop_files = (DROPFILES *) GlobalLock(hdata);
drop_files->pFiles = sizeof(DROPFILES);
drop_files->fWide = TRUE;
auto *data = reinterpret_cast<wchar_t *>(
reinterpret_cast<BYTE *>(drop_files) + kDropFilesHeaderSizeInBytes);
size_t next_filename_offset = 0;
for (const auto &filename: filenames) {
auto wide_filename = utf8_to_wide(filename);
wcsncpy_s(data + next_filename_offset,
wide_filename.length() + 1,
wide_filename.c_str(),
wide_filename.length() + 1);
// Skip the terminating null character of the filename.
next_filename_offset += wide_filename.length() + 1;
}
STGMEDIUM storage;
storage.tymed = TYMED_HGLOBAL;
storage.hGlobal = hdata;
storage.pUnkForRelease = nullptr;
GlobalUnlock(hdata);
return storage;
}
PBITMAPINFO CreateBitmapInfoStruct(HBITMAP hBmp) {
BITMAP bmp;
PBITMAPINFO pbmi;
WORD cClrBits;
// Retrieve the bitmap color format, width, and height.
GetObject(hBmp, sizeof(BITMAP), (LPSTR) &bmp);
// Convert the color format to a count of bits.
cClrBits = (WORD) (bmp.bmPlanes * bmp.bmBitsPixel);
if (cClrBits == 1)
cClrBits = 1;
else if (cClrBits <= 4)
cClrBits = 4;
else if (cClrBits <= 8)
cClrBits = 8;
else if (cClrBits <= 16)
cClrBits = 16;
else if (cClrBits <= 24)
cClrBits = 24;
else
cClrBits = 32;
// Allocate memory for the BITMAPINFO structure. (This structure
// contains a BITMAPINFOHEADER structure and an array of RGBQUAD
// data structures.)
if (cClrBits < 24)
pbmi = (PBITMAPINFO) LocalAlloc(
LPTR, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * int(1 << cClrBits));
// There is no RGBQUAD array for these formats: 24-bit-per-pixel or
// 32-bit-per-pixel
else
pbmi = (PBITMAPINFO) LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER));
// Initialize the fields in the BITMAPINFO structure.
pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbmi->bmiHeader.biWidth = bmp.bmWidth;
pbmi->bmiHeader.biHeight = bmp.bmHeight;
pbmi->bmiHeader.biPlanes = bmp.bmPlanes;
pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;
if (cClrBits < 24) pbmi->bmiHeader.biClrUsed = (1 << cClrBits);
// If the bitmap is not compressed, set the BI_RGB flag.
pbmi->bmiHeader.biCompression = BI_RGB;
// Compute the number of bytes in the array of color
// indices and store the result in biSizeImage.
// The width must be DWORD aligned unless the bitmap is RLE
// compressed.
pbmi->bmiHeader.biSizeImage =
((pbmi->bmiHeader.biWidth * cClrBits + 31) & ~31) / 8 *
pbmi->bmiHeader.biHeight;
// Set biClrImportant to 0, indicating that all of the
// device colors are important.
pbmi->bmiHeader.biClrImportant = 0;
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
PBITMAPINFOHEADER pbih; // bitmap info-header
LPBYTE lpBits; // memory pointer
DWORD cb; // incremental count of bytes
BYTE *hp; // byte pointer
DWORD dwTmp;
PBITMAPINFO pbi;
HDC hDC;
hDC = CreateCompatibleDC(GetWindowDC(GetDesktopWindow()));
SelectObject(hDC, hBMP);
pbi = CreateBitmapInfoStruct(hBMP);
pbih = (PBITMAPINFOHEADER) pbi;
lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
assert(lpBits);
// Retrieve the color table (RGBQUAD array) and the bits
// (array of palette indices) from the DIB.
GetDIBits(hDC, hBMP, 0, (WORD) pbih->biHeight, lpBits, pbi,
DIB_RGB_COLORS);
// Create the .BMP file.
hf = CreateFile(pszFile, GENERIC_READ | GENERIC_WRITE, (DWORD) 0, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
assert(hf != INVALID_HANDLE_VALUE);
hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M"
// Compute the size of the entire file.
hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) + pbih->biSize +
pbih->biClrUsed * sizeof(RGBQUAD) + pbih->biSizeImage);
hdr.bfReserved1 = 0;
hdr.bfReserved2 = 0;
// Compute the offset to the array of color indices.
hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + pbih->biSize +
pbih->biClrUsed * sizeof(RGBQUAD);
// Copy the BITMAPFILEHEADER into the .BMP file.
WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER), (LPDWORD) &dwTmp,
nullptr);
// Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
WriteFile(hf, (LPVOID) pbih,
sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof(RGBQUAD),
(LPDWORD) &dwTmp, (nullptr));
// Copy the array of color indices into the .BMP file.
cb = pbih->biSizeImage;
hp = lpBits;
WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp, nullptr);
// Close the .BMP file.
CloseHandle(hf);
// Free memory.
GlobalFree((HGLOBAL) lpBits);
}
void CreateBitmapHeaderWithColorDepth(LONG width, LONG height, WORD color_depth,
BITMAPINFOHEADER *hdr) {
// These values are shared with gfx::PlatformDevice.
hdr->biSize = sizeof(BITMAPINFOHEADER);
hdr->biWidth = width;
hdr->biHeight = -height; // Minus means top-down bitmap.
hdr->biPlanes = 1;
hdr->biBitCount = color_depth;
hdr->biCompression = BI_RGB; // No compression.
hdr->biSizeImage = 0;
hdr->biXPelsPerMeter = 1;
hdr->biYPelsPerMeter = 1;
hdr->biClrUsed = 0;
hdr->biClrImportant = 0;
}
HBITMAP CreateHBitmapXRGB8888(int width, int height, HANDLE shared_section,
void **data) {
if (width == 0 || height == 0) {
width = 1;
height = 1;
}
BITMAPINFOHEADER hdr = {0};
CreateBitmapHeaderWithColorDepth(width, height, 32, &hdr);
HBITMAP hbitmap =
CreateDIBSection(nullptr, reinterpret_cast<const BITMAPINFO *>(&hdr), 0,
data, shared_section, 0);
return hbitmap;
}
// A scoper to manage acquiring and automatically releasing the clipboard.
class ScopedClipboard {
public:
ScopedClipboard() : opened_(false) {}
~ScopedClipboard() {
if (opened_) Release();
}
bool Acquire(HWND owner) {
const int kMaxAttemptsToOpenClipboard = 5;
if (opened_) {
return false;
}
for (int attempts = 0; attempts < kMaxAttemptsToOpenClipboard; ++attempts) {
if (::OpenClipboard(owner)) {
opened_ = true;
return true;
}
// If we didn't manage to open the clipboard, sleep a bit and be hopeful.
::Sleep(5);
}
// We failed to acquire the clipboard.
return false;
}
void Release() {
if (opened_) {
::CloseClipboard();
opened_ = false;
}
}
private:
bool opened_;
};
class PasteboardPlugin : public flutter::Plugin {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);
PasteboardPlugin();
virtual ~PasteboardPlugin();
private:
// Called when a method is called on this plugin's channel from Dart.
void HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
};
// static
void PasteboardPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarWindows *registrar) {
auto channel =
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
registrar->messenger(), "pasteboard",
&flutter::StandardMethodCodec::GetInstance());
auto plugin = std::make_unique<PasteboardPlugin>();
channel->SetMethodCallHandler(
[plugin_pointer = plugin.get()](const auto &call, auto result) {
plugin_pointer->HandleMethodCall(call, std::move(result));
});
registrar->AddPlugin(std::move(plugin));
}
PasteboardPlugin::PasteboardPlugin() {}
PasteboardPlugin::~PasteboardPlugin() {}
void PasteboardPlugin::HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
if (method_call.method_name() == "image") {
if (!IsClipboardFormatAvailable(CF_DIB)) {
result->Success();
return;
}
ScopedClipboard clipboard;
if (!clipboard.Acquire(nullptr)) {
result->Error("0", "open clipboard failed");
return;
}
// We use a DIB rather than a DDB here since ::GetObject() with the
// HBITMAP returned from ::GetClipboardData(CF_BITMAP) always reports a
// color depth of 32bpp.
auto *bitmap = static_cast<BITMAPINFO *>(::GetClipboardData(CF_DIB));
if (!bitmap) {
result->Success();
return;
}
int color_table_length = 0;
// For more information on BITMAPINFOHEADER and biBitCount definition,
// see https://docs.microsoft.com/en-us/windows/win32/wmdm/-bitmapinfoheader
switch (bitmap->bmiHeader.biBitCount) {
case 1:
case 4:
case 8:
color_table_length = bitmap->bmiHeader.biClrUsed
? int(bitmap->bmiHeader.biClrUsed)
: int(1 << bitmap->bmiHeader.biBitCount);
break;
case 16:
case 32:
if (bitmap->bmiHeader.biCompression == BI_BITFIELDS)
color_table_length = 3;
break;
case 24:break;
default:result->Success();
return;
}
const void *bitmap_bits = reinterpret_cast<const char *>(bitmap) +
bitmap->bmiHeader.biSize +
color_table_length * sizeof(RGBQUAD);
void *dst_bits;
auto dst_hbitmap =
CreateHBitmapXRGB8888(bitmap->bmiHeader.biWidth,
bitmap->bmiHeader.biHeight, nullptr, &dst_bits);
auto hdc = CreateCompatibleDC(nullptr);
auto old_hbitmap = static_cast<HBITMAP>(SelectObject(hdc, dst_hbitmap));
::SetDIBitsToDevice(
hdc, 0, 0, bitmap->bmiHeader.biWidth, bitmap->bmiHeader.biHeight, 0, 0,
0, bitmap->bmiHeader.biHeight, bitmap_bits, bitmap, DIB_RGB_COLORS);
SelectObject(hdc, old_hbitmap);
DeleteDC(hdc);
TCHAR path[MAX_PATH];
GetTempPath(MAX_PATH, path);
TCHAR name[MAX_PATH];
GetTempFileName(path, L"pasteboard", false, name);
CreateBMPFile(name, dst_hbitmap);
DeleteObject(dst_hbitmap);
result->Success(flutter::EncodableValue(wide_to_utf8(name)));
} else if (method_call.method_name() == "files") {
if (!IsClipboardFormatAvailable(CF_HDROP)) {
result->Success();
return;
}
if (!OpenClipboard(nullptr)) {
result->Error("0", "open clipboard failed");
return;
}
auto handle = GetClipboardData(CF_HDROP);
flutter::EncodableList file_list;
if (handle) {
auto data = reinterpret_cast<HDROP>(GlobalLock(handle));
if (data) {
auto files = DragQueryFile(data, 0xFFFFFFFF, nullptr, 0);
for (unsigned int i = 0; i < files; ++i) {
TCHAR filename[MAX_PATH];
DragQueryFile(data, i, filename, sizeof(TCHAR) * MAX_PATH);
std::wstring wide_filename(filename);
file_list.emplace_back(wide_to_utf8(wide_filename));
}
}
}
CloseClipboard();
result->Success(flutter::EncodableValue(file_list));
} else if (method_call.method_name() == "writeFiles") {
auto *arguments = method_call.arguments();
auto files = std::get_if<std::vector<flutter::EncodableValue>>(arguments);
if (!files) {
result->Error("0", "files is empty");
return;
}
std::vector<std::string> paths;
for (const auto &item: *files) {
if (std::holds_alternative<std::string>(item)) {
paths.push_back(std::get<std::string>(item));
}
}
if (paths.empty()) {
result->Error("0", "files is empty");
return;
}
ScopedClipboard clipboard;
if (!clipboard.Acquire(nullptr)) {
result->Error("0", "failed to open clipboard");
return;
}
auto storage = CreateStorageForFileNames(paths);
if (storage.tymed == TYMED_NULL) {
result->Error("0", "create storage failed");
return;
}
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");
bool isHTMLFormatAvailable = false;
if (IsClipboardFormatAvailable(CF_HTML)) {
isHTMLFormatAvailable = true;
}
if (!isHTMLFormatAvailable) {
result->Success();
return;
}
if (!OpenClipboard(nullptr)) {
result->Error("0", "open clipboard failed");
return;
}
HANDLE hClipboardData = GetClipboardData(CF_HTML);
if (hClipboardData == NULL) {
result->Success();
CloseClipboard();
return;
}
else {
char* p = (char*)GlobalLock(hClipboardData);
SIZE_T size = GlobalSize(hClipboardData);
std::string str;
str.assign(p, size);
result->Success(flutter::EncodableValue(str));
GlobalUnlock(hClipboardData);
}
CloseClipboard();
}
else {
result->NotImplemented();
}
}
} // namespace
void PasteboardPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar) {
PasteboardPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarManager::GetInstance()
->GetRegistrar<flutter::PluginRegistrarWindows>(registrar));
}
+565
View File
@@ -0,0 +1,565 @@
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2019-2021 JavaCommons
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-----------------------------------------------------------------------------
*/
/* strconv.h v1.8.10 */
/* Last Modified: 2021/08/30 21:53 */
#ifndef STRCONV_H
#define STRCONV_H
#include <windows.h>
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#if __cplusplus >= 201103L && !defined(STRCONV_CPP98)
static inline std::wstring cp_to_wide(const std::string &s, UINT codepage)
{
int in_length = (int)s.length();
int out_length = MultiByteToWideChar(codepage, 0, s.c_str(), in_length, 0, 0);
std::wstring result(out_length, L'\0');
if (out_length)
MultiByteToWideChar(codepage, 0, s.c_str(), in_length, &result[0], out_length);
return result;
}
static inline std::string wide_to_cp(const std::wstring &s, UINT codepage)
{
int in_length = (int)s.length();
int out_length = WideCharToMultiByte(codepage, 0, s.c_str(), in_length, 0, 0, 0, 0);
std::string result(out_length, '\0');
if (out_length)
WideCharToMultiByte(codepage, 0, s.c_str(), in_length, &result[0], out_length, 0, 0);
return result;
}
#else /* __cplusplus < 201103L */
static inline std::wstring cp_to_wide(const std::string &s, UINT codepage)
{
int in_length = (int)s.length();
int out_length = MultiByteToWideChar(codepage, 0, s.c_str(), in_length, 0, 0);
std::vector<wchar_t> buffer(out_length);
if (out_length)
MultiByteToWideChar(codepage, 0, s.c_str(), in_length, &buffer[0], out_length);
std::wstring result(buffer.begin(), buffer.end());
return result;
}
static inline std::string wide_to_cp(const std::wstring &s, UINT codepage)
{
int in_length = (int)s.length();
int out_length = WideCharToMultiByte(codepage, 0, s.c_str(), in_length, 0, 0, 0, 0);
std::vector<char> buffer(out_length);
if (out_length)
WideCharToMultiByte(codepage, 0, s.c_str(), in_length, &buffer[0], out_length, 0, 0);
std::string result(buffer.begin(), buffer.end());
return result;
}
#endif
static inline std::string cp_to_utf8(const std::string &s, UINT codepage)
{
if (codepage == CP_UTF8)
return s;
std::wstring wide = cp_to_wide(s, codepage);
return wide_to_cp(wide, CP_UTF8);
}
static inline std::string utf8_to_cp(const std::string &s, UINT codepage)
{
if (codepage == CP_UTF8)
return s;
std::wstring wide = cp_to_wide(s, CP_UTF8);
return wide_to_cp(wide, codepage);
}
static inline std::wstring ansi_to_wide(const std::string &s)
{
return cp_to_wide(s, CP_ACP);
}
static inline std::string wide_to_ansi(const std::wstring &s)
{
return wide_to_cp(s, CP_ACP);
}
static inline std::wstring sjis_to_wide(const std::string &s)
{
return cp_to_wide(s, 932);
}
static inline std::string wide_to_sjis(const std::wstring &s)
{
return wide_to_cp(s, 932);
}
static inline std::wstring utf8_to_wide(const std::string &s)
{
return cp_to_wide(s, CP_UTF8);
}
static inline std::string wide_to_utf8(const std::wstring &s)
{
return wide_to_cp(s, CP_UTF8);
}
static inline std::string ansi_to_utf8(const std::string &s)
{
return cp_to_utf8(s, CP_ACP);
}
static inline std::string utf8_to_ansi(const std::string &s)
{
return utf8_to_cp(s, CP_ACP);
}
static inline std::string sjis_to_utf8(const std::string &s)
{
return cp_to_utf8(s, 932);
}
static inline std::string utf8_to_sjis(const std::string &s)
{
return utf8_to_cp(s, 932);
}
#ifdef __cpp_char8_t
static inline std::u8string utf8_to_char8(const std::string &s)
{
return std::u8string(s.begin(), s.end());
}
static inline std::string char8_to_utf8(const std::u8string &s)
{
return std::string(s.begin(), s.end());
}
static inline std::wstring char8_to_wide(const std::u8string &s)
{
return cp_to_wide(char8_to_utf8(s), CP_UTF8);
}
static inline std::u8string wide_to_char8(const std::wstring &s)
{
return utf8_to_char8(wide_to_cp(s, CP_UTF8));
}
static inline std::u8string cp_to_char8(const std::string &s, UINT codepage)
{
return utf8_to_char8(cp_to_utf8(s, codepage));
}
static inline std::string char8_to_cp(const std::u8string &s, UINT codepage)
{
return utf8_to_cp(char8_to_utf8(s), codepage);
}
static inline std::u8string ansi_to_char8(const std::string &s)
{
return cp_to_char8(s, CP_ACP);
}
static inline std::string char8_to_ansi(const std::u8string &s)
{
return char8_to_cp(s, CP_ACP);
}
static inline std::u8string sjis_to_char8(const std::string &s)
{
return cp_to_char8(s, 932);
}
static inline std::string char8_to_sjis(const std::u8string &s)
{
return char8_to_cp(s, 932);
}
#endif
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4996)
#endif
static inline std::wstring vformat(const wchar_t *format, va_list args)
{
int len = _vsnwprintf(0, 0, format, args);
if (len < 0)
return L"";
std::vector<wchar_t> buffer(len + 1);
len = _vsnwprintf(&buffer[0], len, format, args);
if (len < 0)
return L"";
buffer[len] = L'\0';
return &buffer[0];
}
static inline std::string vformat(const char *format, va_list args)
{
int len = _vsnprintf(0, 0, format, args);
if (len < 0)
return "";
std::vector<char> buffer(len + 1);
len = _vsnprintf(&buffer[0], len, format, args);
if (len < 0)
return "";
buffer[len] = '\0';
return &buffer[0];
}
#ifdef __cpp_char8_t
static inline std::u8string vformat(const char8_t *format, va_list args)
{
int len = _vsnprintf(0, 0, (const char *)format, args);
if (len < 0)
return u8"";
std::vector<char> buffer(len + 1);
len = _vsnprintf(&buffer[0], len, (const char *)format, args);
if (len < 0)
return u8"";
buffer[len] = '\0';
return (char8_t *)&buffer[0];
}
#endif
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
static inline std::wstring format(const wchar_t *format, ...)
{
va_list args;
va_start(args, format);
std::wstring s = vformat(format, args);
va_end(args);
return s;
}
static inline std::string format(const char *format, ...)
{
va_list args;
va_start(args, format);
std::string s = vformat(format, args);
va_end(args);
return s;
}
#ifdef __cpp_char8_t
static inline std::u8string format(const char8_t *format, ...)
{
va_list args;
va_start(args, format);
std::u8string s = vformat(format, args);
va_end(args);
return s;
}
#endif
static inline void format(std::ostream &ostrm, const wchar_t *format, ...)
{
va_list args;
va_start(args, format);
std::wstring s = vformat(format, args);
va_end(args);
ostrm << wide_to_utf8(s) << std::flush;
}
static inline void format(std::ostream &ostrm, const char *format, ...)
{
va_list args;
va_start(args, format);
std::string s = vformat(format, args);
va_end(args);
ostrm << s << std::flush;
}
#ifdef __cpp_char8_t
static inline void format(std::ostream &ostrm, const char8_t *format, ...)
{
va_list args;
va_start(args, format);
std::u8string s = vformat(format, args);
va_end(args);
ostrm << char8_to_utf8(s) << std::flush;
}
#endif
static inline std::string formatA(const wchar_t *format, ...)
{
va_list args;
va_start(args, format);
std::wstring s = vformat(format, args);
va_end(args);
return wide_to_ansi(s);
}
static inline std::string formatA(const char *format, ...)
{
va_list args;
va_start(args, format);
std::string s = vformat(format, args);
va_end(args);
return utf8_to_ansi(s);
}
#ifdef __cpp_char8_t
static inline std::string formatA(const char8_t *format, ...)
{
va_list args;
va_start(args, format);
std::u8string s = vformat(format, args);
va_end(args);
return char8_to_ansi(s);
}
#endif
static inline void formatA(std::ostream &ostrm, const wchar_t *format, ...)
{
va_list args;
va_start(args, format);
std::wstring s = vformat(format, args);
va_end(args);
ostrm << wide_to_ansi(s) << std::flush;
}
static inline void formatA(std::ostream &ostrm, const char *format, ...)
{
va_list args;
va_start(args, format);
std::string s = vformat(format, args);
va_end(args);
ostrm << utf8_to_ansi(s) << std::flush;
}
#ifdef __cpp_char8_t
static inline void formatA(std::ostream &ostrm, const char8_t *format, ...)
{
va_list args;
va_start(args, format);
std::u8string s = vformat(format, args);
va_end(args);
ostrm << char8_to_ansi(s) << std::flush;
}
#endif
static inline void dbgmsg(const wchar_t *title, const wchar_t *format, ...)
{
va_list args;
va_start(args, format);
std::wstring s = vformat(format, args);
va_end(args);
MessageBoxW(0, s.c_str(), title, MB_OK);
}
static inline void dbgmsg(const char *title, const char *format, ...)
{
va_list args;
va_start(args, format);
std::string s = vformat(format, args);
va_end(args);
MessageBoxW(0, utf8_to_wide(s).c_str(), utf8_to_wide(title).c_str(), MB_OK);
}
#ifdef __cpp_char8_t
static inline void dbgmsg(const char8_t *title, const char8_t *format, ...)
{
va_list args;
va_start(args, format);
std::u8string s = vformat(format, args);
va_end(args);
MessageBoxW(0, char8_to_wide(s).c_str(), char8_to_wide(title).c_str(), MB_OK);
}
#endif
static inline HANDLE handle_for_ostream(std::ostream &ostrm)
{
if (&ostrm == &std::cout)
{
return GetStdHandle(STD_OUTPUT_HANDLE);
}
else if (&ostrm == &std::cerr)
{
return GetStdHandle(STD_ERROR_HANDLE);
}
return INVALID_HANDLE_VALUE;
}
static inline void dbgout(std::ostream &ostrm, const wchar_t *format, ...)
{
va_list args;
va_start(args, format);
std::wstring ws = vformat(format, args);
va_end(args);
HANDLE h = handle_for_ostream(ostrm);
if (h == INVALID_HANDLE_VALUE)
{
return;
}
DWORD dwNumberOfCharsWrite;
if (GetFileType(h) != FILE_TYPE_CHAR)
{
std::string s = wide_to_cp(ws, GetConsoleOutputCP());
WriteFile(h, s.c_str(), (DWORD)s.size(), &dwNumberOfCharsWrite, NULL);
}
else
{
WriteConsoleW(h,
ws.c_str(),
(DWORD)ws.size(),
&dwNumberOfCharsWrite,
NULL);
}
}
static inline void dbgout(std::ostream &ostrm, const char *format, ...)
{
va_list args;
va_start(args, format);
std::string s = vformat(format, args);
va_end(args);
HANDLE h = handle_for_ostream(ostrm);
if (h == INVALID_HANDLE_VALUE)
{
return;
}
DWORD dwNumberOfCharsWrite;
if (GetFileType(h) != FILE_TYPE_CHAR)
{
s = utf8_to_cp(s, GetConsoleOutputCP());
WriteFile(h, s.c_str(), (DWORD)s.size(), &dwNumberOfCharsWrite, NULL);
}
else
{
std::wstring ws = utf8_to_wide(s);
WriteConsoleW(h,
ws.c_str(),
(DWORD)ws.size(),
&dwNumberOfCharsWrite,
NULL);
}
}
#ifdef __cpp_char8_t
static inline void dbgout(std::ostream &ostrm, const char8_t *format, ...)
{
va_list args;
va_start(args, format);
std::u8string s = vformat(format, args);
va_end(args);
HANDLE h = handle_for_ostream(ostrm);
if (h == INVALID_HANDLE_VALUE)
{
return;
}
DWORD dwNumberOfCharsWrite;
if (GetFileType(h) != FILE_TYPE_CHAR)
{
std::string str = char8_to_cp(s, GetConsoleOutputCP());
WriteFile(h, (const char *)str.c_str(), (DWORD)str.size(), &dwNumberOfCharsWrite, NULL);
}
else
{
std::wstring ws = char8_to_wide(s);
WriteConsoleW(h,
ws.c_str(),
(DWORD)ws.size(),
&dwNumberOfCharsWrite,
NULL);
}
}
#endif
class unicode_ostream
{
private:
std::ostream *m_ostrm;
UINT m_target_cp;
bool is_ascii(const std::string &s)
{
for (std::size_t i = 0; i < s.size(); i++)
{
unsigned char c = (unsigned char)s[i];
if (c > 0x7f)
return false;
}
return true;
}
public:
unicode_ostream(std::ostream &ostrm, UINT target_cp = CP_ACP) : m_ostrm(&ostrm), m_target_cp(target_cp) {}
std::ostream &stream() { return *m_ostrm; }
void stream(std::ostream &ostrm) { m_ostrm = &ostrm; }
UINT target_cp() { return m_target_cp; }
void target_cp(UINT cp) { m_target_cp = cp; }
template <typename T>
unicode_ostream &operator<<(const T &x)
{
std::ostringstream oss;
oss << x;
std::string output = oss.str();
if (is_ascii(output))
{
(*m_ostrm) << x;
}
else
{
(*m_ostrm) << utf8_to_cp(output, m_target_cp);
}
return *this;
}
unicode_ostream &operator<<(const std::wstring &x)
{
(*m_ostrm) << wide_to_cp(x, m_target_cp);
return *this;
}
unicode_ostream &operator<<(const wchar_t *x)
{
(*m_ostrm) << wide_to_cp(x, m_target_cp);
return *this;
}
unicode_ostream &operator<<(const std::string &x)
{
(*m_ostrm) << utf8_to_cp(x, m_target_cp);
return *this;
}
unicode_ostream &operator<<(const char *x)
{
(*m_ostrm) << utf8_to_cp(x, m_target_cp);
return *this;
}
#ifdef __cpp_char8_t
unicode_ostream &operator<<(const std::u8string &x)
{
(*m_ostrm) << char8_to_cp(x, m_target_cp);
return *this;
}
unicode_ostream &operator<<(const char8_t *x)
{
(*m_ostrm) << char8_to_cp(x, m_target_cp);
return *this;
}
#endif
unicode_ostream &operator<<(std::ostream &(*pf)(std::ostream &)) // For manipulators...
{
(*m_ostrm) << pf;
return *this;
}
unicode_ostream &operator<<(std::basic_ios<char> &(*pf)(std::basic_ios<char> &)) // For manipulators...
{
(*m_ostrm) << pf;
return *this;
}
};
#define U8(X) ((const char *)u8##X)
#define WIDE(X) (L##X)
#endif /* STRCONV_H */