186 lines
6.5 KiB
C++
186 lines
6.5 KiB
C++
#include "clipboard.h"
|
|
|
|
// GDI+ Headers
|
|
// WARNING: Must stay in this order to compile
|
|
#include <windows.h>
|
|
#include <objidl.h>
|
|
#include <gdiplus.h>
|
|
#ifdef __GNUC__
|
|
#include <gdiplus/gdiplusflat.h>
|
|
#else
|
|
#include <gdiplusflat.h>
|
|
#endif
|
|
|
|
#include <thread>
|
|
|
|
#include "util/libutils.h"
|
|
#include "util/logging.h"
|
|
#include "util/utils.h"
|
|
|
|
namespace clipboard {
|
|
namespace imports {
|
|
static bool ATTEMPTED_LOAD_LIBRARY = false;
|
|
|
|
static decltype(Gdiplus::GdiplusShutdown) *GdiplusShutdown = nullptr;
|
|
static decltype(Gdiplus::GdiplusStartup) *GdiplusStartup = nullptr;
|
|
static decltype(Gdiplus::DllExports::GdipCreateBitmapFromFile) *GdipCreateBitmapFromFile = nullptr;
|
|
static decltype(Gdiplus::DllExports::GdipCreateHBITMAPFromBitmap) *GdipCreateHBITMAPFromBitmap = nullptr;
|
|
static decltype(Gdiplus::DllExports::GdipDisposeImage) *GdipDisposeImage = nullptr;
|
|
}
|
|
|
|
void copy_image_handler(const std::filesystem::path &path) {
|
|
if (!imports::ATTEMPTED_LOAD_LIBRARY) {
|
|
imports::ATTEMPTED_LOAD_LIBRARY = true;
|
|
|
|
auto gdiplus = libutils::try_library("gdiplus.dll");
|
|
|
|
if (gdiplus) {
|
|
imports::GdiplusShutdown = (decltype(imports::GdiplusShutdown)) libutils::try_proc(
|
|
gdiplus, "GdiplusShutdown");
|
|
imports::GdiplusStartup = (decltype(imports::GdiplusStartup)) libutils::try_proc(
|
|
gdiplus, "GdiplusStartup");
|
|
imports::GdipCreateBitmapFromFile = (decltype(imports::GdipCreateBitmapFromFile)) libutils::try_proc(
|
|
gdiplus, "GdipCreateBitmapFromFile");
|
|
imports::GdipCreateHBITMAPFromBitmap = (decltype(imports::GdipCreateHBITMAPFromBitmap)) libutils::try_proc(
|
|
gdiplus, "GdipCreateHBITMAPFromBitmap");
|
|
imports::GdipDisposeImage = (decltype(imports::GdipDisposeImage)) libutils::try_proc(
|
|
gdiplus, "GdipDisposeImage");
|
|
} else {
|
|
log_warning("clipboard", "GDI+ library not found, disabling clipboard functionality");
|
|
}
|
|
}
|
|
if (!imports::GdiplusShutdown ||
|
|
!imports::GdiplusStartup ||
|
|
!imports::GdipCreateBitmapFromFile ||
|
|
!imports::GdipCreateHBITMAPFromBitmap ||
|
|
!imports::GdipDisposeImage)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If the screenshot button is print screen, the OpenClipboard call seems to fail often if we only
|
|
// call it once, probably due to a race condition. So, we can try calling a lot until we can open it.
|
|
bool clipboard_open = false;
|
|
for (int i = 0; i < 1000000; i++) {
|
|
if (OpenClipboard(nullptr)) {
|
|
clipboard_open = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!clipboard_open) {
|
|
log_warning("clipboard", "Failed to open clipboard");
|
|
return;
|
|
}
|
|
|
|
// Start gdiplus
|
|
Gdiplus::GdiplusStartupInput input {};
|
|
ULONG_PTR token;
|
|
imports::GdiplusStartup(&token, &input, nullptr);
|
|
|
|
// Convert the file path to a wstring and open the screenshot file
|
|
Gdiplus::GpBitmap *bitmap = nullptr;
|
|
auto status = imports::GdipCreateBitmapFromFile(path.c_str(), &bitmap);
|
|
if (status != Gdiplus::Ok) {
|
|
log_warning("clipboard", "failed to create GDI+ bitmap: {}", status);
|
|
|
|
imports::GdiplusShutdown(token);
|
|
CloseClipboard();
|
|
|
|
return;
|
|
}
|
|
|
|
// Retrieve the HBITMAP from the Bitmap object
|
|
HBITMAP hbitmap {};
|
|
status = imports::GdipCreateHBITMAPFromBitmap(bitmap, &hbitmap, 0);
|
|
if (status == Gdiplus::Ok) {
|
|
|
|
// Convert the HBITMAP to a DIB to copy to the clipboard
|
|
BITMAP bm;
|
|
GetObject(hbitmap, sizeof(bm), &bm);
|
|
|
|
BITMAPINFOHEADER info;
|
|
info.biSize = sizeof(info);
|
|
info.biWidth = bm.bmWidth;
|
|
info.biHeight = bm.bmHeight;
|
|
info.biPlanes = 1;
|
|
info.biBitCount = bm.bmBitsPixel;
|
|
info.biCompression = BI_RGB;
|
|
|
|
std::vector<BYTE> dimensions(bm.bmWidthBytes * bm.bmHeight);
|
|
auto hdc = GetDC(nullptr);
|
|
GetDIBits(hdc, hbitmap, 0, info.biHeight, dimensions.data(), reinterpret_cast<BITMAPINFO *>(&info), 0);
|
|
ReleaseDC(nullptr, hdc);
|
|
|
|
auto hmem = GlobalAlloc(GMEM_MOVEABLE, sizeof(info) + dimensions.size());
|
|
auto buffer = reinterpret_cast<BYTE *>(GlobalLock(hmem));
|
|
memcpy(buffer, &info, sizeof(info));
|
|
memcpy(&buffer[sizeof(info)], dimensions.data(), dimensions.size());
|
|
GlobalUnlock(hmem);
|
|
|
|
if (SetClipboardData(CF_DIB, hmem)) {
|
|
log_info("clipboard", "saved image to clipboard");
|
|
} else {
|
|
log_warning("clipboard", "failed to save image to clipboard");
|
|
}
|
|
} else {
|
|
log_warning("clipboard", "failed to retrieve HBITMAP from image bitmap: {}", status);
|
|
}
|
|
|
|
// Clean up after ourselves. hmem can't be deleted because it is owned by the clipboard now.
|
|
imports::GdipDisposeImage(bitmap);
|
|
|
|
imports::GdiplusShutdown(token);
|
|
|
|
CloseClipboard();
|
|
}
|
|
|
|
void copy_image(const std::filesystem::path path) {
|
|
|
|
// Create a new thread since we loop to open the clipboard
|
|
std::thread handle(copy_image_handler, std::move(path));
|
|
|
|
handle.detach();
|
|
}
|
|
|
|
void copy_text(const std::string str) {
|
|
if (!OpenClipboard(nullptr)) {
|
|
log_warning("clipboard", "Failed to open clipboard");
|
|
return;
|
|
}
|
|
|
|
HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, str.length());
|
|
memcpy(GlobalLock(mem), str.c_str(), str.length());
|
|
GlobalUnlock(mem);
|
|
|
|
EmptyClipboard();
|
|
SetClipboardData(CF_TEXT, mem);
|
|
CloseClipboard();
|
|
}
|
|
|
|
const std::string paste_text() {
|
|
HGLOBAL hglb;
|
|
LPSTR str;
|
|
std::string text;
|
|
|
|
// check if clipboard content is text
|
|
if (!IsClipboardFormatAvailable(CF_TEXT)) {
|
|
return text;
|
|
}
|
|
if (!OpenClipboard(nullptr)) {
|
|
log_warning("clipboard", "Failed to open clipboard");
|
|
return text;
|
|
}
|
|
|
|
hglb = GetClipboardData(CF_TEXT);
|
|
if (hglb != NULL) {
|
|
str = reinterpret_cast<LPSTR>(GlobalLock(hglb));
|
|
if (str != NULL) {
|
|
text = str;
|
|
GlobalUnlock(hglb);
|
|
}
|
|
}
|
|
CloseClipboard();
|
|
return text;
|
|
}
|
|
}
|