408 lines
10 KiB
C++
408 lines
10 KiB
C++
|
// Clip Library
|
||
|
// Copyright (C) 2015-2024 David Capello
|
||
|
//
|
||
|
// This file is released under the terms of the MIT license.
|
||
|
// Read LICENSE.txt for more information.
|
||
|
|
||
|
#include "clip_win.h"
|
||
|
|
||
|
#include "clip.h"
|
||
|
#include "clip_lock_impl.h"
|
||
|
|
||
|
#include <algorithm>
|
||
|
#include <cstdlib>
|
||
|
#include <cstring>
|
||
|
#include <string>
|
||
|
#include <vector>
|
||
|
|
||
|
namespace clip {
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
// Data type used as header for custom formats to indicate the exact
|
||
|
// size of the user custom data. This is necessary because it looks
|
||
|
// like GlobalSize() might not return the exact size, but a greater
|
||
|
// value.
|
||
|
typedef uint64_t CustomSizeT;
|
||
|
|
||
|
class Hglobal {
|
||
|
public:
|
||
|
Hglobal() : m_handle(nullptr) {
|
||
|
}
|
||
|
|
||
|
explicit Hglobal(HGLOBAL handle) : m_handle(handle) {
|
||
|
}
|
||
|
|
||
|
explicit Hglobal(size_t len) : m_handle(GlobalAlloc(GHND, len)) {
|
||
|
}
|
||
|
|
||
|
~Hglobal() {
|
||
|
if (m_handle)
|
||
|
GlobalFree(m_handle);
|
||
|
}
|
||
|
|
||
|
void release() {
|
||
|
m_handle = nullptr;
|
||
|
}
|
||
|
|
||
|
operator HGLOBAL() {
|
||
|
return m_handle;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
HGLOBAL m_handle;
|
||
|
};
|
||
|
|
||
|
// From: https://issues.chromium.org/issues/40080988#comment8
|
||
|
//
|
||
|
// "Adds impersonation of the anonymous token around calls to the
|
||
|
// CloseClipboard() system call. On Windows 8+ the win32k driver
|
||
|
// captures the access token of the caller and makes it available to
|
||
|
// other users on the desktop through the system call
|
||
|
// GetClipboardAccessToken(). This introduces a risk of privilege
|
||
|
// escalation in sandboxed processes. By performing the
|
||
|
// impersonation then whenever Chrome writes data to the clipboard
|
||
|
// only the anonymous token is available."
|
||
|
//
|
||
|
class AnonymousTokenImpersonator {
|
||
|
public:
|
||
|
AnonymousTokenImpersonator()
|
||
|
: m_must_revert(ImpersonateAnonymousToken(GetCurrentThread()))
|
||
|
{}
|
||
|
|
||
|
~AnonymousTokenImpersonator() {
|
||
|
if (m_must_revert)
|
||
|
RevertToSelf();
|
||
|
}
|
||
|
private:
|
||
|
const bool m_must_revert;
|
||
|
};
|
||
|
|
||
|
} // anonymous namespace
|
||
|
|
||
|
lock::impl::impl(void* hwnd) : m_locked(false) {
|
||
|
for (int i=0; i<5; ++i) {
|
||
|
if (OpenClipboard((HWND)hwnd)) {
|
||
|
m_locked = true;
|
||
|
break;
|
||
|
}
|
||
|
Sleep(20);
|
||
|
}
|
||
|
|
||
|
if (!m_locked) {
|
||
|
error_handler e = get_error_handler();
|
||
|
if (e)
|
||
|
e(ErrorCode::CannotLock);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
lock::impl::~impl() {
|
||
|
if (m_locked) {
|
||
|
AnonymousTokenImpersonator guard;
|
||
|
CloseClipboard();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool lock::impl::clear() {
|
||
|
return (EmptyClipboard() ? true: false);
|
||
|
}
|
||
|
|
||
|
bool lock::impl::is_convertible(format f) const {
|
||
|
if (f == text_format()) {
|
||
|
return
|
||
|
(IsClipboardFormatAvailable(CF_TEXT) ||
|
||
|
IsClipboardFormatAvailable(CF_UNICODETEXT) ||
|
||
|
IsClipboardFormatAvailable(CF_OEMTEXT));
|
||
|
}
|
||
|
#if CLIP_ENABLE_IMAGE
|
||
|
else if (f == image_format()) {
|
||
|
return (IsClipboardFormatAvailable(CF_DIB) ||
|
||
|
win::wic_image_format_available(nullptr) != nullptr);
|
||
|
}
|
||
|
#endif // CLIP_ENABLE_IMAGE
|
||
|
else
|
||
|
return IsClipboardFormatAvailable(f);
|
||
|
}
|
||
|
|
||
|
bool lock::impl::set_data(format f, const char* buf, size_t len) {
|
||
|
bool result = false;
|
||
|
|
||
|
if (f == text_format()) {
|
||
|
if (len > 0) {
|
||
|
int reqsize = MultiByteToWideChar(CP_UTF8, 0, buf, len, NULL, 0);
|
||
|
if (reqsize > 0) {
|
||
|
++reqsize;
|
||
|
|
||
|
Hglobal hglobal(sizeof(WCHAR)*reqsize);
|
||
|
LPWSTR lpstr = static_cast<LPWSTR>(GlobalLock(hglobal));
|
||
|
MultiByteToWideChar(CP_UTF8, 0, buf, len, lpstr, reqsize);
|
||
|
GlobalUnlock(hglobal);
|
||
|
|
||
|
result = (SetClipboardData(CF_UNICODETEXT, hglobal)) ? true: false;
|
||
|
if (result)
|
||
|
hglobal.release();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
Hglobal hglobal(len+sizeof(CustomSizeT));
|
||
|
if (hglobal) {
|
||
|
auto dst = (uint8_t*)GlobalLock(hglobal);
|
||
|
if (dst) {
|
||
|
*((CustomSizeT*)dst) = len;
|
||
|
memcpy(dst+sizeof(CustomSizeT), buf, len);
|
||
|
GlobalUnlock(hglobal);
|
||
|
result = (SetClipboardData(f, hglobal) ? true: false);
|
||
|
if (result)
|
||
|
hglobal.release();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
bool lock::impl::get_data(format f, char* buf, size_t len) const {
|
||
|
assert(buf);
|
||
|
|
||
|
if (!buf || !is_convertible(f))
|
||
|
return false;
|
||
|
|
||
|
bool result = false;
|
||
|
|
||
|
if (f == text_format()) {
|
||
|
if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
|
||
|
HGLOBAL hglobal = GetClipboardData(CF_UNICODETEXT);
|
||
|
if (hglobal) {
|
||
|
LPWSTR lpstr = static_cast<LPWSTR>(GlobalLock(hglobal));
|
||
|
if (lpstr) {
|
||
|
size_t reqsize =
|
||
|
WideCharToMultiByte(CP_UTF8, 0, lpstr, -1,
|
||
|
nullptr, 0, nullptr, nullptr);
|
||
|
|
||
|
assert(reqsize <= len);
|
||
|
if (reqsize <= len) {
|
||
|
WideCharToMultiByte(CP_UTF8, 0, lpstr, -1,
|
||
|
buf, reqsize, nullptr, nullptr);
|
||
|
result = true;
|
||
|
}
|
||
|
GlobalUnlock(hglobal);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (IsClipboardFormatAvailable(CF_TEXT)) {
|
||
|
HGLOBAL hglobal = GetClipboardData(CF_TEXT);
|
||
|
if (hglobal) {
|
||
|
LPSTR lpstr = static_cast<LPSTR>(GlobalLock(hglobal));
|
||
|
if (lpstr) {
|
||
|
// TODO check length
|
||
|
memcpy(buf, lpstr, len);
|
||
|
result = true;
|
||
|
GlobalUnlock(hglobal);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
if (IsClipboardFormatAvailable(f)) {
|
||
|
HGLOBAL hglobal = GetClipboardData(f);
|
||
|
if (hglobal) {
|
||
|
const SIZE_T total_size = GlobalSize(hglobal);
|
||
|
auto ptr = (const uint8_t*)GlobalLock(hglobal);
|
||
|
if (ptr) {
|
||
|
CustomSizeT reqsize = *((CustomSizeT*)ptr);
|
||
|
|
||
|
// If the registered length of data in the first CustomSizeT
|
||
|
// number of bytes of the hglobal data is greater than the
|
||
|
// GlobalSize(hglobal), something is wrong, it should not
|
||
|
// happen.
|
||
|
assert(reqsize <= total_size);
|
||
|
if (reqsize > total_size)
|
||
|
reqsize = total_size - sizeof(CustomSizeT);
|
||
|
|
||
|
if (reqsize <= len) {
|
||
|
memcpy(buf, ptr+sizeof(CustomSizeT), reqsize);
|
||
|
result = true;
|
||
|
}
|
||
|
GlobalUnlock(hglobal);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
size_t lock::impl::get_data_length(format f) const {
|
||
|
size_t len = 0;
|
||
|
|
||
|
if (f == text_format()) {
|
||
|
if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
|
||
|
HGLOBAL hglobal = GetClipboardData(CF_UNICODETEXT);
|
||
|
if (hglobal) {
|
||
|
LPWSTR lpstr = static_cast<LPWSTR>(GlobalLock(hglobal));
|
||
|
if (lpstr) {
|
||
|
len =
|
||
|
WideCharToMultiByte(CP_UTF8, 0, lpstr, -1,
|
||
|
nullptr, 0, nullptr, nullptr);
|
||
|
GlobalUnlock(hglobal);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (IsClipboardFormatAvailable(CF_TEXT)) {
|
||
|
HGLOBAL hglobal = GetClipboardData(CF_TEXT);
|
||
|
if (hglobal) {
|
||
|
LPSTR lpstr = (LPSTR)GlobalLock(hglobal);
|
||
|
if (lpstr) {
|
||
|
len = strlen(lpstr) + 1;
|
||
|
GlobalUnlock(hglobal);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (f != empty_format()) {
|
||
|
if (IsClipboardFormatAvailable(f)) {
|
||
|
HGLOBAL hglobal = GetClipboardData(f);
|
||
|
if (hglobal) {
|
||
|
const SIZE_T total_size = GlobalSize(hglobal);
|
||
|
auto ptr = (const uint8_t*)GlobalLock(hglobal);
|
||
|
if (ptr) {
|
||
|
len = *((CustomSizeT*)ptr);
|
||
|
|
||
|
assert(len <= total_size);
|
||
|
if (len > total_size)
|
||
|
len = total_size - sizeof(CustomSizeT);
|
||
|
|
||
|
GlobalUnlock(hglobal);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
#if CLIP_ENABLE_LIST_FORMATS
|
||
|
|
||
|
std::vector<format_info> lock::impl::list_formats() const {
|
||
|
static const char* standard_formats[CF_MAX] = {
|
||
|
"", "CF_TEXT", "CF_BITMAP", "CF_METAFILEPICT",
|
||
|
"CF_SYLK", "CF_DIF", "CF_TIFF", "CF_OEMTEXT",
|
||
|
"CF_DIB", "CF_PALETTE", "CF_PENDATA", "CF_RIFF",
|
||
|
"CF_WAVE", "CF_UNICODETEXT", "CF_ENHMETAFILE", "CF_HDROP",
|
||
|
"CF_LOCALE", "CF_DIBV5"
|
||
|
};
|
||
|
|
||
|
std::vector<format_info> formats;
|
||
|
std::vector<char> format_name(512);
|
||
|
|
||
|
formats.reserve(CountClipboardFormats());
|
||
|
|
||
|
UINT format_id = EnumClipboardFormats(0);
|
||
|
while (format_id != 0) {
|
||
|
if (format_id >= CF_TEXT && format_id < CF_MAX) {
|
||
|
// Standard clipboard format
|
||
|
formats.emplace_back(format_id, standard_formats[format_id]);
|
||
|
}
|
||
|
// Get user-defined format name
|
||
|
else {
|
||
|
int size = GetClipboardFormatNameA(
|
||
|
format_id,
|
||
|
format_name.data(),
|
||
|
format_name.size());
|
||
|
|
||
|
formats.emplace_back(format_id, std::string(format_name.data(), size));
|
||
|
}
|
||
|
|
||
|
format_id = EnumClipboardFormats(format_id);
|
||
|
}
|
||
|
|
||
|
return formats;
|
||
|
}
|
||
|
|
||
|
#endif // CLIP_ENABLE_LIST_FORMATS
|
||
|
|
||
|
#if CLIP_ENABLE_IMAGE
|
||
|
|
||
|
bool lock::impl::set_image(const image& image) {
|
||
|
const image_spec& spec = image.spec();
|
||
|
|
||
|
// Add the PNG clipboard format for images with alpha channel
|
||
|
// (useful to communicate with some Windows programs that only use
|
||
|
// alpha data from PNG clipboard format)
|
||
|
if (spec.bits_per_pixel == 32 &&
|
||
|
spec.alpha_mask) {
|
||
|
UINT png_format = RegisterClipboardFormatA("PNG");
|
||
|
if (png_format) {
|
||
|
Hglobal png_handle(win::write_png(image));
|
||
|
if (png_handle)
|
||
|
SetClipboardData(png_format, png_handle);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Hglobal hmem(clip::win::create_dibv5(image));
|
||
|
if (!hmem)
|
||
|
return false;
|
||
|
|
||
|
SetClipboardData(CF_DIBV5, hmem);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool lock::impl::get_image(image& output_img) const {
|
||
|
// Tries to get the first image format that can be read using WIC
|
||
|
// ("PNG", "JPG", "GIF", etc).
|
||
|
UINT cbformat;
|
||
|
if (auto read_img = win::wic_image_format_available(&cbformat)) {
|
||
|
HANDLE handle = GetClipboardData(cbformat);
|
||
|
if (handle) {
|
||
|
size_t size = GlobalSize(handle);
|
||
|
uint8_t* data = (uint8_t*)GlobalLock(handle);
|
||
|
bool result = read_img(data, size, &output_img, nullptr);
|
||
|
GlobalUnlock(handle);
|
||
|
if (result)
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we couldn't find any, we try to use the regular DIB format.
|
||
|
win::BitmapInfo bi;
|
||
|
return bi.to_image(output_img);
|
||
|
}
|
||
|
|
||
|
bool lock::impl::get_image_spec(image_spec& spec) const {
|
||
|
UINT cbformat;
|
||
|
if (auto read_img = win::wic_image_format_available(&cbformat)) {
|
||
|
HANDLE handle = GetClipboardData(cbformat);
|
||
|
if (handle) {
|
||
|
size_t size = GlobalSize(handle);
|
||
|
uint8_t* data = (uint8_t*)GlobalLock(handle);
|
||
|
bool result = read_img(data, size, nullptr, &spec);
|
||
|
GlobalUnlock(handle);
|
||
|
if (result)
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
win::BitmapInfo bi;
|
||
|
if (!bi.is_valid())
|
||
|
return false;
|
||
|
bi.fill_spec(spec);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
#endif // CLIP_ENABLE_IMAGE
|
||
|
|
||
|
format register_format(const std::string& name) {
|
||
|
int reqsize = 1+MultiByteToWideChar(CP_UTF8, 0,
|
||
|
name.c_str(), name.size(), NULL, 0);
|
||
|
std::vector<WCHAR> buf(reqsize);
|
||
|
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), name.size(),
|
||
|
&buf[0], reqsize);
|
||
|
|
||
|
// From MSDN, registered clipboard formats are identified by values
|
||
|
// in the range 0xC000 through 0xFFFF.
|
||
|
return (format)RegisterClipboardFormatW(&buf[0]);
|
||
|
}
|
||
|
|
||
|
} // namespace clip
|