mirror of
https://github.com/QB64-Phoenix-Edition/QB64pe.git
synced 2024-09-20 09:04:43 +00:00
392 lines
15 KiB
C++
392 lines
15 KiB
C++
//----------------------------------------------------------------------------------------------------------------------
|
|
// QB64-PE cross-platform clipboard support
|
|
// Powered by clip (https://github.com/dacap/clip)
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
#include "libqb-common.h"
|
|
|
|
// We need 'qbs' and 'image' structs stuff from here. Stop using this when image and friends are refactored
|
|
#include "../../../libqb.h"
|
|
|
|
// This is not strictly needed. But we'll leave it here for VSCode to do it's magic
|
|
#define CLIP_ENABLE_IMAGE 1
|
|
#include "clip/clip.h"
|
|
#include "clipboard.h"
|
|
#include "error_handle.h"
|
|
#define IMAGE_DEBUG 0
|
|
#include "image.h"
|
|
#include "qbs.h"
|
|
#include <vector>
|
|
|
|
#ifdef QB64_MACOSX
|
|
# include <ApplicationServices/ApplicationServices.h>
|
|
#endif
|
|
|
|
extern const img_struct *img; // used by sub__clipboardimage()
|
|
extern const img_struct *write_page; // used by func__clipboardimage()
|
|
extern const int32_t *page; // used by sub__clipboardimage()
|
|
extern const int32_t nextimg; // used by sub__clipboardimage()
|
|
extern const uint8_t charset8x8[256][8][8]; // used by sub__clipboardimage()
|
|
extern const uint8_t charset8x16[256][16][8]; // used by sub__clipboardimage()
|
|
|
|
// This is used as a fallback internal clipboard should any text clipboard functions below fail
|
|
static std::string g_InternalClipboard;
|
|
|
|
/// @brief Gets text (if present) in the OS clipboard.
|
|
/// @return A qbs string.
|
|
qbs *func__clipboard() {
|
|
#if defined(QB64_MACOSX)
|
|
|
|
// We'll use our own clipboard get code on macOS since our requirements are different than what clip supports
|
|
PasteboardRef clipboard = nullptr;
|
|
OSStatus err = PasteboardCreate(kPasteboardClipboard, &clipboard);
|
|
|
|
if (err == noErr) {
|
|
PasteboardSynchronize(clipboard);
|
|
ItemCount itemCount = 0;
|
|
|
|
err = PasteboardGetItemCount(clipboard, &itemCount);
|
|
if (err == noErr) {
|
|
for (ItemCount itemIndex = 1; itemIndex <= itemCount; itemIndex++) {
|
|
PasteboardItemID itemID = nullptr;
|
|
err = PasteboardGetItemIdentifier(clipboard, itemIndex, &itemID);
|
|
if (err != noErr)
|
|
continue;
|
|
|
|
CFDataRef flavorData = nullptr;
|
|
err = PasteboardCopyItemFlavorData(clipboard, itemID, CFSTR("public.utf8-plain-text"), &flavorData);
|
|
if (err == noErr) {
|
|
g_InternalClipboard.assign(reinterpret_cast<const char *>(CFDataGetBytePtr(flavorData)), CFDataGetLength(flavorData));
|
|
|
|
CFRelease(flavorData);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
CFRelease(clipboard);
|
|
}
|
|
|
|
#elif defined(QB64_WINDOWS)
|
|
|
|
// We'll need custom code for Windows because clip does automatic UTF-8 conversions that leads to some undesired behavior when copying extended ASCII
|
|
if (OpenClipboard(NULL)) {
|
|
if (IsClipboardFormatAvailable(CF_TEXT)) {
|
|
HANDLE hClipboardData = GetClipboardData(CF_TEXT);
|
|
|
|
if (hClipboardData) {
|
|
auto pchData = reinterpret_cast<const char *>(GlobalLock(hClipboardData));
|
|
|
|
if (pchData) {
|
|
g_InternalClipboard.assign(pchData, strlen(pchData));
|
|
|
|
GlobalUnlock(hClipboardData);
|
|
}
|
|
}
|
|
}
|
|
|
|
CloseClipboard();
|
|
}
|
|
|
|
#else
|
|
|
|
// clip works like we want on Linux
|
|
if (clip::has(clip::text_format()))
|
|
clip::get_text(g_InternalClipboard);
|
|
|
|
#endif
|
|
|
|
auto qbsText = qbs_new(g_InternalClipboard.length(), 1);
|
|
if (qbsText->len)
|
|
memcpy(qbsText->chr, g_InternalClipboard.data(), qbsText->len);
|
|
|
|
return qbsText;
|
|
}
|
|
|
|
/// @brief Sets text to the OS clipboard.
|
|
/// @param qbsText A qbs string.
|
|
void sub__clipboard(const qbs *qbsText) {
|
|
g_InternalClipboard.assign(reinterpret_cast<const char *>(qbsText->chr), qbsText->len);
|
|
|
|
if (qbsText->len) {
|
|
#if defined(QB64_MACOSX)
|
|
|
|
// We'll use our own clipboard set code on macOS since our requirements are different than what clip supports
|
|
PasteboardRef clipboard;
|
|
if (PasteboardCreate(kPasteboardClipboard, &clipboard) == noErr) {
|
|
if (PasteboardClear(clipboard) == noErr) {
|
|
CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, qbsText->chr, qbsText->len, kCFAllocatorNull);
|
|
|
|
if (data) {
|
|
PasteboardPutItemFlavor(clipboard, nullptr, kUTTypeUTF8PlainText, data, 0);
|
|
|
|
CFRelease(data);
|
|
}
|
|
}
|
|
|
|
CFRelease(clipboard);
|
|
}
|
|
|
|
#elif defined(QB64_WINDOWS)
|
|
|
|
// We'll need custom code for Windows because clip does automatic UTF-8 conversions that leads to some undesired behavior when copying extended ASCII
|
|
if (OpenClipboard(NULL)) {
|
|
if (EmptyClipboard()) {
|
|
HGLOBAL hClipboardData = GlobalAlloc(GMEM_MOVEABLE, qbsText->len + 1);
|
|
|
|
if (hClipboardData) {
|
|
auto pchData = reinterpret_cast<uint8_t *>(GlobalLock(hClipboardData));
|
|
|
|
if (pchData) {
|
|
memcpy(pchData, qbsText->chr, qbsText->len);
|
|
pchData[qbsText->len] = '\0'; // null terminate
|
|
|
|
GlobalUnlock(hClipboardData);
|
|
|
|
SetClipboardData(CF_TEXT, hClipboardData);
|
|
}
|
|
|
|
GlobalFree(hClipboardData);
|
|
}
|
|
}
|
|
|
|
CloseClipboard();
|
|
}
|
|
|
|
#else
|
|
|
|
// clip works like we want on Linux
|
|
clip::set_text(g_InternalClipboard);
|
|
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// @brief Returns an image handle of an image from the clipboard (if present).
|
|
/// @return A valid image handle. Returns -1 if clipboard format is not supported or if there is nothing.
|
|
int32_t func__clipboardimage() {
|
|
int32_t qb64Img = INVALID_IMAGE_HANDLE; // assume failure
|
|
|
|
if (is_error_pending())
|
|
return qb64Img;
|
|
|
|
if (clip::has(clip::image_format())) {
|
|
clip::image clipImg;
|
|
|
|
IMAGE_DEBUG_PRINT("Clipboard image found");
|
|
|
|
if (clip::get_image(clipImg)) {
|
|
auto spec = clipImg.spec();
|
|
|
|
IMAGE_DEBUG_PRINT("Image (%lu x %lu) @ %lubpp", spec.width, spec.height, spec.bits_per_pixel);
|
|
|
|
if (spec.width && spec.height) {
|
|
auto oldDest = func__dest();
|
|
|
|
// We only support 32bpp images. Images in other formats are converted to 32bpp BGRA
|
|
qb64Img = func__newimage(spec.width, spec.height, 32, 1);
|
|
|
|
if (qb64Img < INVALID_IMAGE_HANDLE) {
|
|
sub__dest(qb64Img);
|
|
auto dst = write_page->offset32;
|
|
|
|
IMAGE_DEBUG_PRINT("Converting and copying image");
|
|
|
|
// Convert and copy the image based on the bpp
|
|
switch (spec.bits_per_pixel) {
|
|
case 64:
|
|
for (uint32_t y = 0; y < spec.height; y++) {
|
|
auto src = reinterpret_cast<const uint64_t *>(clipImg.data() + spec.bytes_per_row * y);
|
|
for (uint32_t x = 0; x < spec.width; x++, src++) {
|
|
auto c = *src;
|
|
*dst = image_make_bgra((c & spec.red_mask) >> spec.red_shift >> 8, (c & spec.green_mask) >> spec.green_shift >> 8,
|
|
(c & spec.blue_mask) >> spec.blue_shift >> 8, (c & spec.alpha_mask) >> spec.alpha_shift >> 8);
|
|
|
|
++dst;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 32:
|
|
// The alpha mask can be zero (which means that the image is just RGB)
|
|
if (spec.alpha_mask) {
|
|
for (uint32_t y = 0; y < spec.height; y++) {
|
|
auto src = reinterpret_cast<const uint32_t *>(clipImg.data() + spec.bytes_per_row * y);
|
|
for (uint32_t x = 0; x < spec.width; x++, src++) {
|
|
auto c = *src;
|
|
*dst = image_make_bgra((c & spec.red_mask) >> spec.red_shift, (c & spec.green_mask) >> spec.green_shift,
|
|
(c & spec.blue_mask) >> spec.blue_shift, (c & spec.alpha_mask) >> spec.alpha_shift);
|
|
|
|
++dst;
|
|
}
|
|
}
|
|
} else {
|
|
for (uint32_t y = 0; y < spec.height; y++) {
|
|
auto src = reinterpret_cast<const uint32_t *>(clipImg.data() + spec.bytes_per_row * y);
|
|
for (uint32_t x = 0; x < spec.width; x++, src++) {
|
|
auto c = *src;
|
|
*dst = image_make_bgra((c & spec.red_mask) >> spec.red_shift, (c & spec.green_mask) >> spec.green_shift,
|
|
(c & spec.blue_mask) >> spec.blue_shift, 255u);
|
|
|
|
++dst;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 24:
|
|
for (uint32_t y = 0; y < spec.height; y++) {
|
|
auto src = reinterpret_cast<const uint8_t *>(clipImg.data() + spec.bytes_per_row * y);
|
|
for (uint32_t x = 0; x < spec.width; x++, src += 3) {
|
|
auto c = *reinterpret_cast<const uint32_t *>(src);
|
|
*dst = image_make_bgra((c & spec.red_mask) >> spec.red_shift, (c & spec.green_mask) >> spec.green_shift,
|
|
(c & spec.blue_mask) >> spec.blue_shift, 255u);
|
|
|
|
++dst;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 16:
|
|
for (uint32_t y = 0; y < spec.height; y++) {
|
|
auto src = reinterpret_cast<const uint16_t *>(clipImg.data() + spec.bytes_per_row * y);
|
|
for (uint32_t x = 0; x < spec.width; x++, src++) {
|
|
auto c = *src;
|
|
*dst = image_make_bgra(image_scale_5bits_to_8bits((c & spec.red_mask) >> spec.red_shift),
|
|
image_scale_6bits_to_8bits((c & spec.green_mask) >> spec.green_shift),
|
|
image_scale_5bits_to_8bits((c & spec.blue_mask) >> spec.blue_shift), 255u);
|
|
|
|
++dst;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sub__dest(oldDest);
|
|
}
|
|
}
|
|
}
|
|
|
|
return qb64Img;
|
|
}
|
|
|
|
/// @brief Set the clipboard image using a QB64 image handle.
|
|
/// @param src A valid QB64 image handle.
|
|
void sub__clipboardimage(int32_t src) {
|
|
if (is_error_pending())
|
|
return;
|
|
|
|
// Validation
|
|
if (src >= 0) {
|
|
validatepage(src);
|
|
src = page[src];
|
|
} else {
|
|
src = -src;
|
|
if (src >= nextimg) {
|
|
error(QB_ERROR_INVALID_HANDLE);
|
|
return;
|
|
}
|
|
if (!img[src].valid) {
|
|
error(QB_ERROR_INVALID_HANDLE);
|
|
return;
|
|
}
|
|
}
|
|
// End of validation
|
|
|
|
// Even though we have color mask and shift support, clip needs the RGBA order :(
|
|
clip::image_spec spec;
|
|
spec.bits_per_pixel = 32;
|
|
spec.red_mask = 0x000000ff;
|
|
spec.green_mask = 0x0000ff00;
|
|
spec.blue_mask = 0x00ff0000;
|
|
spec.alpha_mask = 0xff000000;
|
|
spec.red_shift = 0;
|
|
spec.green_shift = 8;
|
|
spec.blue_shift = 16;
|
|
spec.alpha_shift = 24;
|
|
|
|
std::vector<uint32_t> pixels; // this will hold our converted BGRA32 pixel data
|
|
auto srcImg = img[src];
|
|
|
|
if (srcImg.text) {
|
|
IMAGE_DEBUG_PRINT("Rendering text surface to BGRA32");
|
|
|
|
uint32_t const fontWidth = 8;
|
|
uint32_t fontHeight = 16;
|
|
if (srcImg.font == 8 || srcImg.font == 14)
|
|
fontHeight = srcImg.font;
|
|
|
|
spec.width = fontWidth * srcImg.width;
|
|
spec.height = fontHeight * srcImg.height;
|
|
spec.bytes_per_row = spec.width * sizeof(uint32_t);
|
|
pixels.resize(spec.width * spec.height);
|
|
|
|
uint8_t fc, bc, *c = srcImg.offset; // set to the first codepoint
|
|
uint8_t const *builtinFont = nullptr;
|
|
|
|
// Render all text to the raw pixel array
|
|
for (uint32_t y = 0; y < spec.height; y += fontHeight) {
|
|
for (uint32_t x = 0; x < spec.width; x += fontWidth) {
|
|
switch (fontHeight) {
|
|
case 8:
|
|
builtinFont = &charset8x8[*c][0][0];
|
|
break;
|
|
|
|
case 14:
|
|
builtinFont = &charset8x16[*c][1][0];
|
|
break;
|
|
|
|
default: // 16
|
|
builtinFont = &charset8x16[*c][0][0];
|
|
}
|
|
|
|
++c; // move to the attribute
|
|
fc = *c & 0x0F;
|
|
bc = ((*c >> 4) & 7) + ((*c >> 7) << 3);
|
|
|
|
// Inner codepoint rendering loop
|
|
for (uint32_t dy = y, py = 0; py < fontHeight; dy++, py++) {
|
|
for (uint32_t dx = x, px = 0; px < fontWidth; dx++, px++) {
|
|
pixels[spec.width * dy + dx] = image_swap_red_blue(*builtinFont ? srcImg.pal[fc] : srcImg.pal[bc]);
|
|
++builtinFont;
|
|
}
|
|
}
|
|
|
|
++c; // move to the next codepoint
|
|
}
|
|
}
|
|
} else {
|
|
spec.width = srcImg.width;
|
|
spec.height = srcImg.height;
|
|
spec.bytes_per_row = spec.width * sizeof(uint32_t);
|
|
pixels.resize(spec.width * spec.height);
|
|
|
|
if (srcImg.bits_per_pixel == 32) {
|
|
// BGRA32 pixels
|
|
IMAGE_DEBUG_PRINT("Converting BGRA32 image to RGBA32");
|
|
|
|
auto p = srcImg.offset32;
|
|
|
|
for (size_t i = 0; i < pixels.size(); i++) {
|
|
pixels[i] = image_swap_red_blue(*p);
|
|
++p;
|
|
}
|
|
} else {
|
|
// Indexed pixels
|
|
IMAGE_DEBUG_PRINT("Converting BGRA32 indexed image to RGBA32");
|
|
|
|
auto p = srcImg.offset;
|
|
|
|
for (size_t i = 0; i < pixels.size(); i++) {
|
|
pixels[i] = image_swap_red_blue(srcImg.pal[*p]);
|
|
++p;
|
|
}
|
|
}
|
|
}
|
|
|
|
IMAGE_DEBUG_PRINT("Setting clipboard image");
|
|
|
|
// Send the image off to the OS clipboard
|
|
clip::image clipImg(pixels.data(), spec);
|
|
clip::set_image(clipImg);
|
|
}
|