1
1
Fork 0
mirror of https://github.com/QB64-Phoenix-Edition/QB64pe.git synced 2024-05-12 12:00:13 +00:00

Add cross-platform clipboard support

This commit is contained in:
Samuel Gomes 2024-03-26 23:34:54 +05:30
parent 11e40594ef
commit 47e3ec40f7
19 changed files with 3622 additions and 480 deletions

View file

@ -145,17 +145,17 @@ CXXFLAGS += -fno-strict-aliasing
CXXFLAGS += -Wno-conversion-null
ifeq ($(OS),lnx)
CXXLIBS += -lGL -lGLU -lX11 -lpthread -ldl -lrt
CXXLIBS += -lGL -lGLU -lX11 -lpthread -ldl -lrt -lxcb
CXXFLAGS += -DFREEGLUT_STATIC
endif
ifeq ($(OS),win)
CXXLIBS += -static-libgcc -static-libstdc++ -lcomdlg32 -lole32
CXXLIBS += -static-libgcc -static-libstdc++ -lcomdlg32 -lole32 -lshlwapi -lwindowscodecs
CXXFLAGS += -DGLEW_STATIC -DFREEGLUT_STATIC
endif
ifeq ($(OS),osx)
CXXLIBS += -framework OpenGL -framework IOKit -framework GLUT -framework Cocoa -framework ApplicationServices
CXXLIBS += -framework OpenGL -framework IOKit -framework GLUT -framework Cocoa -framework ApplicationServices -framework CoreFoundation
# OSX doesn't strip using objcopy, so we're using `-s` instead
ifneq ($(STRIP_SYMBOLS),n)
@ -204,6 +204,7 @@ include $(PATH_INTERNAL_C)/parts/video/image/build.mk
include $(PATH_INTERNAL_C)/parts/gui/build.mk
include $(PATH_INTERNAL_C)/parts/network/http/build.mk
include $(PATH_INTERNAL_C)/parts/compression/build.mk
include $(PATH_INTERNAL_C)/parts/os/clipboard/build.mk
.PHONY: all clean
@ -287,9 +288,7 @@ ifneq ($(filter y,$(DEP_DEVICEINPUT)),)
ifeq ($(OS),win)
CXXLIBS += -lwinmm -lxinput -ldinput8 -ldxguid -lwbemuuid -lole32 -loleaut32
endif
ifeq ($(OS),osx)
CXXLIBS += -framework CoreFoundation -framework IOKit
endif
QBLIB_NAME := $(addsuffix 1,$(QBLIB_NAME))
LICENSE_IN_USE += libstem_gamepad
@ -308,7 +307,7 @@ ifneq ($(filter y,$(DEP_AUDIO_MINIAUDIO)),)
CXXLIBS += -lwinmm -lksguid -ldxguid -lole32
endif
ifeq ($(OS),osx)
CXXLIBS += -lpthread -lm -framework CoreFoundation -framework CoreAudio -framework CoreMIDI -framework AudioUnit -framework AudioToolbox
CXXLIBS += -lpthread -lm -framework CoreAudio -framework CoreMIDI -framework AudioUnit -framework AudioToolbox
endif
QBLIB_NAME := $(addsuffix 1,$(QBLIB_NAME))

View file

@ -188,15 +188,6 @@ extern "C" int QB64_Resizable() { return ScreenResize; }
int32 sub_gl_called = 0;
extern "C" int qb64_custom_event(int event, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, void *p1, void *p2);
#ifdef QB64_WINDOWS
extern "C" LRESULT qb64_os_event_windows(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, int *qb64_os_event_info);
#endif
#if defined(QB64_LINUX) && defined(QB64_GUI)
extern "C" void qb64_os_event_linux(XEvent *event, Display *display, int *qb64_os_event_info);
#endif
#define QB64_EVENT_CLOSE 1
#define QB64_EVENT_KEY 2
#define QB64_EVENT_RELATIVE_MOUSE_MOVEMENT 3
@ -23910,448 +23901,6 @@ int32 func__exit() {
return x;
}
#if defined(QB64_LINUX) && defined(QB64_GUI)
// X11 clipboard interface for Linux
// SDL_SysWMinfo syswminfo;
Atom targets, utf8string, compoundtext, clipboard;
int x11filter(XEvent *x11event) {
static int i;
static char *cp;
static XSelectionRequestEvent *x11request;
static XSelectionEvent x11selectionevent;
static Atom mytargets[] = {XA_STRING, utf8string, compoundtext};
if (x11event->type == SelectionRequest) {
x11request = &x11event->xselectionrequest;
x11selectionevent.type = SelectionNotify;
x11selectionevent.serial = x11event->xany.send_event;
x11selectionevent.send_event = True;
x11selectionevent.display = X11_display;
x11selectionevent.requestor = x11request->requestor;
x11selectionevent.selection = x11request->selection;
x11selectionevent.target = None;
x11selectionevent.property = x11request->property;
x11selectionevent.time = x11request->time;
if (x11request->target == targets) {
XChangeProperty(X11_display, x11request->requestor, x11request->property, XA_ATOM, 32, PropModeReplace, (unsigned char *)mytargets, 3);
} else {
if (x11request->target == compoundtext || x11request->target == utf8string || x11request->target == XA_STRING) {
cp = XFetchBytes(X11_display, &i);
XChangeProperty(X11_display, x11request->requestor, x11request->property, x11request->target, 8, PropModeReplace, (unsigned char *)cp, i);
XFree(cp);
} else {
x11selectionevent.property = None;
}
}
XSendEvent(x11request->display, x11request->requestor, 0, NoEventMask, (XEvent *)&x11selectionevent);
XSync(X11_display, False);
}
return 1;
}
void setupx11clipboard() {
static int32 setup = 0;
if (!setup) {
setup = 1;
// SDL_GetWMInfo(&syswminfo);
// SDL_EventState(SDL_SYSWMEVENT,SDL_ENABLE);
// SDL_SetEventFilter(x11filter);
x11_lock();
targets = XInternAtom(X11_display, "TARGETS", False);
utf8string = XInternAtom(X11_display, "UTF8_STRING", False);
compoundtext = XInternAtom(X11_display, "COMPOUND_TEXT", False);
clipboard = XInternAtom(X11_display, "CLIPBOARD", False);
x11_unlock();
}
}
void x11clipboardcopy(const char *text) {
setupx11clipboard();
x11_lock();
XStoreBytes(X11_display, text, strlen(text) + 1);
XSetSelectionOwner(X11_display, clipboard, X11_window, CurrentTime);
x11_unlock();
return;
}
char *x11clipboardpaste() {
static int32 i;
static char *cp;
static unsigned char *cp2;
static Window x11selectionowner;
static XEvent x11event;
static unsigned long data_items, bytes_remaining, ignore;
static int format;
static Atom type;
cp = NULL;
cp2 = NULL;
setupx11clipboard();
// syswminfo.info.x11.lock_func();
x11_lock();
x11selectionowner = XGetSelectionOwner(X11_display, clipboard);
if (x11selectionowner != None) {
// The XGetSelectionOwner() function returns the window ID associated with the window
if (x11selectionowner == X11_window) { // we are the provider, so just return buffered content
x11_unlock();
int bytes;
cp = XFetchBytes(X11_display, &bytes);
return cp;
}
XConvertSelection(X11_display, clipboard, utf8string, clipboard, X11_window, CurrentTime);
XFlush(X11_display);
bool gotReply = false;
int timeoutMs = 10000; // 10sec
do {
XEvent event;
gotReply = XCheckTypedWindowEvent(X11_display, X11_window, SelectionNotify, &event);
if (gotReply) {
if (event.xselection.property == clipboard) {
XGetWindowProperty(X11_display, X11_window, clipboard, 0, 0, False, AnyPropertyType, &type, &format, &data_items, &bytes_remaining, &cp2);
if (cp2) {
XFree(cp2);
cp2 = NULL;
}
if (bytes_remaining) {
if (XGetWindowProperty(X11_display, X11_window, clipboard, 0, bytes_remaining, False, AnyPropertyType, &type, &format, &data_items,
&ignore, &cp2) == Success) {
cp = strdup((char *)cp2);
XFree(cp2);
XDeleteProperty(X11_display, X11_window, clipboard);
x11_unlock();
return cp;
}
}
x11_unlock();
return NULL;
} else {
x11_unlock();
return NULL;
}
}
Sleep(1);
timeoutMs -= 1;
} while (timeoutMs > 0);
} // x11selectionowner!=None
x11_unlock();
return NULL;
}
#elif defined(QB64_LINUX)
void x11clipboardcopy(const char *text) {}
char *x11clipboardpaste() { return NULL; }
#endif
qbs *internal_clipboard = NULL; // used only if clipboard services unavailable
int32 linux_clipboard_init = 0;
void sub__clipboard(qbs *text) {
#ifdef QB64_WINDOWS
static uint8 *textz;
static HGLOBAL h;
if (OpenClipboard(NULL)) {
EmptyClipboard();
h = GlobalAlloc(GMEM_MOVEABLE, text->len + 1);
if (h) {
textz = (uint8 *)GlobalLock(h);
if (textz) {
memcpy(textz, text->chr, text->len);
textz[text->len] = 0;
GlobalUnlock(h);
SetClipboardData(CF_TEXT, h);
}
}
CloseClipboard();
}
return;
#endif
#ifdef QB64_MACOSX
PasteboardRef clipboard;
if (PasteboardCreate(kPasteboardClipboard, &clipboard) != noErr) {
return;
}
if (PasteboardClear(clipboard) != noErr) {
CFRelease(clipboard);
return;
}
CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, text->chr, text->len, kCFAllocatorNull);
if (data == NULL) {
CFRelease(clipboard);
return;
}
OSStatus err;
err = PasteboardPutItemFlavor(clipboard, NULL, kUTTypeUTF8PlainText, data, 0);
CFRelease(clipboard);
CFRelease(data);
return;
#endif
#if defined(QB64_LINUX)
static qbs *textz = NULL;
if (!textz)
textz = qbs_new(0, 0);
qbs_set(textz, qbs_add(text, qbs_new_txt_len("\0", 1)));
x11clipboardcopy((char *)textz->chr);
return;
#endif
if (internal_clipboard == NULL)
internal_clipboard = qbs_new(0, 0);
qbs_set(internal_clipboard, text);
}
#ifdef DEPENDENCY_SCREENIMAGE
void sub__clipboardimage(int32 src) {
# ifdef QB64_WINDOWS
if (is_error_pending())
return;
static int32 i, i2, ii, w, h;
static uint32 *o, *o2;
static int32 x, y, n, c, i3, c2;
// validation
i = src;
if (i >= 0) { // validate i
validatepage(i);
i = page[i];
} else {
i = -i;
if (i >= nextimg) {
error(258);
return;
}
if (!img[i].valid) {
error(258);
return;
}
}
if (img[i].text) {
error(5);
return;
}
// end of validation
w = img[i].width;
h = img[i].height;
// source[http://support.microsoft.com/kb/318876]
HDC hdc;
BITMAPV5HEADER bi;
HBITMAP hBitmap;
void *lpBits;
ZeroMemory(&bi, sizeof(BITMAPV5HEADER));
bi.bV5Size = sizeof(BITMAPV5HEADER);
bi.bV5Width = w;
bi.bV5Height = h;
bi.bV5Planes = 1;
bi.bV5BitCount = 32;
bi.bV5Compression = BI_RGB;
hdc = GetDC(NULL);
// Create the DIB section with an alpha channel.
hBitmap = CreateDIBSection(hdc, (BITMAPINFO *)&bi, DIB_RGB_COLORS, (void **)&lpBits, NULL, (DWORD)0);
// Transfer the source image to a new 32-bit image to avoid incompatible formats)
i2 = func__newimage(w, h, 32, 1);
sub__putimage(NULL, NULL, NULL, NULL, -i, i2, NULL, NULL, NULL, NULL, 8 + 32);
o = img[-i2].offset32;
o2 = (uint32 *)lpBits;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
c = o[(h - 1 - y) * w + x];
o2[y * w + x] = c;
}
}
sub__freeimage(i2, 1);
// Create copy of hBitmap to send to the clipboard
HBITMAP bitmapCopy;
HDC hdc2, hdc3;
bitmapCopy = CreateCompatibleBitmap(hdc, w, h);
hdc2 = CreateCompatibleDC(hdc);
hdc3 = CreateCompatibleDC(hdc);
SelectObject(hdc2, bitmapCopy);
SelectObject(hdc3, hBitmap);
BitBlt(hdc2, 0, 0, w, h, hdc3, 0, 0, SRCCOPY);
ReleaseDC(NULL, hdc);
ReleaseDC(NULL, hdc2);
ReleaseDC(NULL, hdc3);
// Send bitmapCopy to the clipboard
if (OpenClipboard(NULL)) {
EmptyClipboard();
SetClipboardData(CF_BITMAP, bitmapCopy);
CloseClipboard();
}
DeleteObject(hBitmap);
DeleteObject(bitmapCopy);
# endif
}
#endif
#ifdef DEPENDENCY_SCREENIMAGE
int32 func__clipboardimage() {
# ifdef QB64_WINDOWS
if (is_error_pending())
return -1;
static HBITMAP bitmap;
static BITMAP bitmapInfo;
static HDC hdc;
static int32 w, h;
if (OpenClipboard(NULL)) {
if (IsClipboardFormatAvailable(CF_BITMAP) == 0) {
CloseClipboard();
return -1;
}
bitmap = (HBITMAP)GetClipboardData(CF_BITMAP);
CloseClipboard();
GetObject(bitmap, sizeof(BITMAP), &bitmapInfo);
h = bitmapInfo.bmHeight;
w = bitmapInfo.bmWidth;
static BITMAPFILEHEADER bmfHeader;
static BITMAPINFOHEADER bi;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = w;
bi.biHeight = -h;
bi.biPlanes = 1;
bi.biBitCount = 32;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
static int32 i, i2;
i2 = func__dest();
i = func__newimage(w, h, 32, 1);
sub__dest(i);
hdc = GetDC(NULL);
GetDIBits(hdc, bitmap, 0, h, write_page->offset, (BITMAPINFO *)&bi, DIB_RGB_COLORS);
sub__setalpha(255, NULL, NULL, NULL, 0); // required as some images come
// with alpha 0 from the clipboard
sub__dest(i2);
ReleaseDC(NULL, hdc);
DeleteObject(bitmap);
return i;
} else
return -1;
# endif
return -1;
}
#endif
qbs *func__clipboard() {
#ifdef QB64_WINDOWS
static qbs *text;
static uint8 *textz;
static HGLOBAL h;
if (OpenClipboard(NULL)) {
if (IsClipboardFormatAvailable(CF_TEXT)) {
h = GetClipboardData(CF_TEXT);
if (h) {
textz = (uint8 *)GlobalLock(h);
if (textz) {
text = qbs_new(strlen((char *)textz), 1);
memcpy(text->chr, textz, text->len);
GlobalUnlock(h);
CloseClipboard();
return text;
}
}
}
CloseClipboard();
}
text = qbs_new(0, 1);
return text;
#endif
#ifdef QB64_MACOSX
static qbs *text;
OSStatus err = noErr;
ItemCount itemCount;
PasteboardSyncFlags syncFlags;
static PasteboardRef inPasteboard = NULL;
PasteboardCreate(kPasteboardClipboard, &inPasteboard);
char *data;
data = "";
syncFlags = PasteboardSynchronize(inPasteboard);
err = badPasteboardSyncErr;
err = PasteboardGetItemCount(inPasteboard, &itemCount);
if ((err) != noErr)
goto CantGetPasteboardItemCount;
for (int itemIndex = 1; itemIndex <= itemCount; itemIndex++) {
PasteboardItemID itemID;
CFDataRef flavorData;
err = PasteboardGetItemIdentifier(inPasteboard, itemIndex, &itemID);
if ((err) != noErr)
goto CantGetPasteboardItemIdentifier;
err = PasteboardCopyItemFlavorData(inPasteboard, itemID, CFSTR("public.utf8-plain-text"), &flavorData);
if ((err) != noErr)
goto CantGetPasteboardItemCount;
data = (char *)CFDataGetBytePtr(flavorData);
uint32 size;
size = CFDataGetLength(flavorData);
text = qbs_new(size, 1);
memcpy(text->chr, data, text->len);
// CFRelease (flavorData);
// CFRelease (flavorTypeArray);
// CFRelease(inPasteboard);
return text;
CantGetPasteboardItemIdentifier:;
}
CantGetPasteboardItemCount:
text = qbs_new(0, 1);
return text;
return NULL;
#endif
#if defined(QB64_LINUX)
qbs *text;
char *cp = x11clipboardpaste();
cp = x11clipboardpaste();
if (!cp) {
text = qbs_new(0, 1);
} else {
text = qbs_new(strlen(cp), 1);
memcpy(text->chr, cp, text->len);
free(cp);
}
return text;
#endif
if (internal_clipboard == NULL)
internal_clipboard = qbs_new(0, 0);
return internal_clipboard;
}
int32 display_called = 0;
void display_now() {
if (autodisplay) {
@ -32392,8 +31941,6 @@ extern "C" void qb64_os_event_linux(XEvent *event, Display *display, int *qb64_o
X11_display = display;
X11_window = event->xexpose.window;
}
x11filter(event); // handles clipboard request events from other applications
}
if (*qb64_os_event_info == OS_EVENT_POST_PROCESSING) {

View file

@ -0,0 +1,15 @@
//----------------------------------------------------------------------------------------------------------------------
// QB64-PE cross-platform clipboard support
// Powered by clip (https://github.com/dacap/clip)
//----------------------------------------------------------------------------------------------------------------------
#pragma once
#include <stdint.h>
struct qbs;
qbs *func__clipboard();
void sub__clipboard(const qbs *qbsText);
int32_t func__clipboardimage();
void sub__clipboardimage(int32_t src);

View file

@ -1,4 +1,4 @@
//----------------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------
// ___ ___ __ _ _ ___ ___ ___ _ _ _
// / _ \| _ ) / /| | || _ \ __| |_ _|_ __ __ _ __ _ ___ | | (_) |__ _ _ __ _ _ _ _ _
// | (_) | _ \/ _ \_ _| _/ _| | || ' \/ _` / _` / -_) | |__| | '_ \ '_/ _` | '_| || |
@ -38,6 +38,9 @@
# define IMAGE_DEBUG_CHECK(_exp_) // Don't do anything in release builds
#endif
// This is returned to the caller if something goes wrong while loading the image
#define INVALID_IMAGE_HANDLE -1
// The byte ordering here are straight from libqb.cpp. So, if libqb.cpp is wrong, then we are wrong! ;)
#define IMAGE_GET_BGRA_RED(c) ((uint8_t)((uint32_t)(c) >> 16 & 0xFFu))
#define IMAGE_GET_BGRA_GREEN(c) ((uint8_t)((uint32_t)(c) >> 8 & 0xFFu))

View file

@ -1047,9 +1047,7 @@ void FGAPIENTRY glutMainLoopEvent( void )
#endif
// QB64-PE: custom code begin
int qb64_os_event_info = 0;
qb64_os_event_info = 1;
int qb64_os_event_info = 1;
qb64_os_event_linux(&event, fgDisplay.Display, &qb64_os_event_info);
if (qb64_os_event_info == 3)
return;

View file

@ -0,0 +1,49 @@
# clip Setup:
# Download the latest release from https://github.com/dacap/clip
# Copy all source files except clip_none.cpp to internal/c/parts/os/clipboard/clip
# Compile the source using -DCLIP_ENABLE_IMAGE=1 and -DHAVE_XCB_XLIB_H (Linux only)
CLIP_DEFS := -DCLIP_ENABLE_IMAGE=1
CLIP_SRCS := \
clip.cpp \
image.cpp
ifeq ($(OS),lnx)
CLIP_SRCS += clip_x11.cpp
CLIP_DEFS += -DHAVE_XCB_XLIB_H
endif
ifeq ($(OS),win)
CLIP_SRCS += clip_win.cpp
endif
ifeq ($(OS),osx)
CLIP_SRCS += clip_osx.mm
endif
CLIPBOARD_SRCS := clipboard.cpp
CLIP_OBJS := $(patsubst %.cpp,$(PATH_INTERNAL_C)/parts/os/clipboard/clip/%.o,$(CLIP_SRCS))
CLIP_OBJS += $(patsubst %.mm,$(PATH_INTERNAL_C)/parts/os/clipboard/clip/%.o,$(filter %.mm,$(CLIP_SRCS)))
CLIPBOARD_OBJS := $(patsubst %.cpp,$(PATH_INTERNAL_C)/parts/os/clipboard/%.o,$(CLIPBOARD_SRCS))
$(PATH_INTERNAL_C)/parts/os/clipboard/clip/%.o: $(PATH_INTERNAL_C)/parts/os/clipboard/clip/%.cpp
$(CXX) -O2 $(CXXFLAGS) $(CLIP_DEFS) -w $< -c -o $@
$(PATH_INTERNAL_C)/parts/os/clipboard/clip/%.o: $(PATH_INTERNAL_C)/parts/os/clipboard/clip/%.mm
$(CXX) -O2 $(CXXFLAGS) $(CLIP_DEFS) -w $< -c -o $@
$(PATH_INTERNAL_C)/parts/os/clipboard/%.o: $(PATH_INTERNAL_C)/parts/os/clipboard/%.cpp
$(CXX) -O2 $(CXXFLAGS) $(CLIP_DEFS) -Wall $< -c -o $@
CLIPBOARD_LIB := $(PATH_INTERNAL_C)/parts/os/clipboard/clipboard.a
$(CLIPBOARD_LIB): $(CLIP_OBJS) $(CLIPBOARD_OBJS)
$(AR) rcs $@ $(CLIP_OBJS) $(CLIPBOARD_OBJS)
EXE_LIBS += $(CLIPBOARD_LIB)
CLEAN_LIST += $(CLIPBOARD_LIB) $(CLIP_OBJS) $(CLIPBOARD_OBJS)

View file

@ -0,0 +1,184 @@
// Clip Library
// Copyright (c) 2015-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
#include "clip_lock_impl.h"
#include <vector>
#include <stdexcept>
namespace clip {
namespace {
void default_error_handler(ErrorCode code) {
static const char* err[] = {
"Cannot lock clipboard",
"Image format is not supported"
};
throw std::runtime_error(err[static_cast<int>(code)]);
}
} // anonymous namespace
error_handler g_error_handler = default_error_handler;
lock::lock(void* native_window_handle)
: p(new impl(native_window_handle)) {
}
lock::~lock() = default;
bool lock::locked() const {
return p->locked();
}
bool lock::clear() {
return p->clear();
}
bool lock::is_convertible(format f) const {
return p->is_convertible(f);
}
bool lock::set_data(format f, const char* buf, size_t length) {
return p->set_data(f, buf, length);
}
bool lock::get_data(format f, char* buf, size_t len) const {
return p->get_data(f, buf, len);
}
size_t lock::get_data_length(format f) const {
return p->get_data_length(f);
}
#if CLIP_ENABLE_IMAGE
bool lock::set_image(const image& img) {
return p->set_image(img);
}
bool lock::get_image(image& img) const {
return p->get_image(img);
}
bool lock::get_image_spec(image_spec& spec) const {
return p->get_image_spec(spec);
}
#endif // CLIP_ENABLE_IMAGE
format empty_format() { return 0; }
format text_format() { return 1; }
#if CLIP_ENABLE_IMAGE
format image_format() { return 2; }
#endif
bool has(format f) {
lock l;
if (l.locked())
return l.is_convertible(f);
else
return false;
}
bool clear() {
lock l;
if (l.locked())
return l.clear();
else
return false;
}
bool set_text(const std::string& value) {
lock l;
if (l.locked()) {
l.clear();
return l.set_data(text_format(), value.c_str(), value.size());
}
else
return false;
}
bool get_text(std::string& value) {
lock l;
if (!l.locked())
return false;
format f = text_format();
if (!l.is_convertible(f))
return false;
size_t len = l.get_data_length(f);
if (len > 0) {
std::vector<char> buf(len);
l.get_data(f, &buf[0], len);
value = &buf[0];
return true;
}
else {
value.clear();
return true;
}
}
#if CLIP_ENABLE_IMAGE
bool set_image(const image& img) {
lock l;
if (l.locked()) {
l.clear();
return l.set_image(img);
}
else
return false;
}
bool get_image(image& img) {
lock l;
if (!l.locked())
return false;
format f = image_format();
if (!l.is_convertible(f))
return false;
return l.get_image(img);
}
bool get_image_spec(image_spec& spec) {
lock l;
if (!l.locked())
return false;
format f = image_format();
if (!l.is_convertible(f))
return false;
return l.get_image_spec(spec);
}
#endif // CLIP_ENABLE_IMAGE
void set_error_handler(error_handler handler) {
g_error_handler = handler;
}
error_handler get_error_handler() {
return g_error_handler;
}
#ifdef HAVE_XCB_XLIB_H
static int g_x11_timeout = 1000;
void set_x11_wait_timeout(int msecs) { g_x11_timeout = msecs; }
int get_x11_wait_timeout() { return g_x11_timeout; }
#else
void set_x11_wait_timeout(int) { }
int get_x11_wait_timeout() { return 1000; }
#endif
} // namespace clip

View file

@ -0,0 +1,190 @@
// 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.
#ifndef CLIP_H_INCLUDED
#define CLIP_H_INCLUDED
#pragma once
#include <cassert>
#include <memory>
#include <string>
namespace clip {
// ======================================================================
// Low-level API to lock the clipboard/pasteboard and modify it
// ======================================================================
// Clipboard format identifier.
typedef size_t format;
class image;
struct image_spec;
class lock {
public:
// You can give your current HWND as the "native_window_handle."
// Windows clipboard functions use this handle to open/close
// (lock/unlock) the clipboard. From the MSDN documentation we
// need this handler so SetClipboardData() doesn't fail after a
// EmptyClipboard() call. Anyway it looks to work just fine if we
// call OpenClipboard() with a null HWND.
lock(void* native_window_handle = nullptr);
~lock();
// Returns true if we've locked the clipboard successfully in
// lock() constructor.
bool locked() const;
// Clears the clipboard content. If you don't clear the content,
// previous clipboard content (in unknown formats) could persist
// after the unlock.
bool clear();
// Returns true if the clipboard can be converted to the given
// format.
bool is_convertible(format f) const;
bool set_data(format f, const char* buf, size_t len);
bool get_data(format f, char* buf, size_t len) const;
size_t get_data_length(format f) const;
#if CLIP_ENABLE_IMAGE
// For images
bool set_image(const image& image);
bool get_image(image& image) const;
bool get_image_spec(image_spec& spec) const;
#endif // CLIP_ENABLE_IMAGE
private:
class impl;
std::unique_ptr<impl> p;
};
format register_format(const std::string& name);
// This format is when the clipboard has no content.
format empty_format();
// When the clipboard has UTF8 text.
format text_format();
#if CLIP_ENABLE_IMAGE
// When the clipboard has an image.
format image_format();
#endif
// Returns true if the clipboard has content of the given type.
bool has(format f);
// Clears the clipboard content.
bool clear();
// ======================================================================
// Error handling
// ======================================================================
enum class ErrorCode {
CannotLock,
#if CLIP_ENABLE_IMAGE
ImageNotSupported,
#endif
};
typedef void (*error_handler)(ErrorCode code);
void set_error_handler(error_handler f);
error_handler get_error_handler();
// ======================================================================
// Text
// ======================================================================
// High-level API to put/get UTF8 text in/from the clipboard. These
// functions returns false in case of error.
bool set_text(const std::string& value);
bool get_text(std::string& value);
// ======================================================================
// Image
// ======================================================================
#if CLIP_ENABLE_IMAGE
struct image_spec {
unsigned long width = 0;
unsigned long height = 0;
unsigned long bits_per_pixel = 0;
unsigned long bytes_per_row = 0;
unsigned long red_mask = 0;
unsigned long green_mask = 0;
unsigned long blue_mask = 0;
unsigned long alpha_mask = 0;
unsigned long red_shift = 0;
unsigned long green_shift = 0;
unsigned long blue_shift = 0;
unsigned long alpha_shift = 0;
unsigned long required_data_size() const;
};
// The image data must contain straight RGB values
// (non-premultiplied by alpha). The image retrieved from the
// clipboard will be non-premultiplied too. Basically you will be
// always dealing with straight alpha images.
//
// Details: Windows expects premultiplied images on its clipboard
// content, so the library code make the proper conversion
// automatically. macOS handles straight alpha directly, so there is
// no conversion at all. Linux/X11 images are transferred in
// image/png format which are specified in straight alpha.
class image {
public:
image();
image(const image_spec& spec);
image(const void* data, const image_spec& spec);
image(const image& image);
image(image&& image);
~image();
image& operator=(const image& image);
image& operator=(image&& image);
char* data() const { return m_data; }
const image_spec& spec() const { return m_spec; }
bool is_valid() const { return m_data != nullptr; }
void reset();
private:
void copy_image(const image& image);
void move_image(image&& image);
bool m_own_data;
char* m_data;
image_spec m_spec;
};
// High-level API to set/get an image in/from the clipboard. These
// functions returns false in case of error.
bool set_image(const image& img);
bool get_image(image& img);
bool get_image_spec(image_spec& spec);
#endif // CLIP_ENABLE_IMAGE
// ======================================================================
// Platform-specific
// ======================================================================
// Only for X11: Sets the time (in milliseconds) that we must wait
// for the selection/clipboard owner to receive the content. This
// value is 1000 (one second) by default.
void set_x11_wait_timeout(int msecs);
int get_x11_wait_timeout();
} // namespace clip
#endif // CLIP_H_INCLUDED

View file

@ -0,0 +1,80 @@
// Clip Library
// Copyright (C) 2020-2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef CLIP_COMMON_H_INCLUDED
#define CLIP_COMMON_H_INCLUDED
#pragma once
namespace clip {
namespace details {
#if CLIP_ENABLE_IMAGE
inline void divide_rgb_by_alpha(image& img,
bool hasAlphaGreaterThanZero = false) {
const image_spec& spec = img.spec();
bool hasValidPremultipliedAlpha = true;
for (unsigned long y=0; y<spec.height; ++y) {
const uint32_t* dst = (uint32_t*)(img.data()+y*spec.bytes_per_row);
for (unsigned long x=0; x<spec.width; ++x, ++dst) {
const uint32_t c = *dst;
const int r = ((c & spec.red_mask ) >> spec.red_shift );
const int g = ((c & spec.green_mask) >> spec.green_shift);
const int b = ((c & spec.blue_mask ) >> spec.blue_shift );
const int a = ((c & spec.alpha_mask) >> spec.alpha_shift);
if (a > 0)
hasAlphaGreaterThanZero = true;
if (r > a || g > a || b > a)
hasValidPremultipliedAlpha = false;
}
}
for (unsigned long y=0; y<spec.height; ++y) {
uint32_t* dst = (uint32_t*)(img.data()+y*spec.bytes_per_row);
for (unsigned long x=0; x<spec.width; ++x, ++dst) {
const uint32_t c = *dst;
int r = ((c & spec.red_mask ) >> spec.red_shift );
int g = ((c & spec.green_mask) >> spec.green_shift);
int b = ((c & spec.blue_mask ) >> spec.blue_shift );
int a = ((c & spec.alpha_mask) >> spec.alpha_shift);
// If all alpha values = 0, we make the image opaque.
if (!hasAlphaGreaterThanZero) {
a = 255;
// We cannot change the image spec (e.g. spec.alpha_mask=0) to
// make the image opaque, because the "spec" of the image is
// read-only. The image spec used by the client is the one
// returned by get_image_spec().
}
// If there is alpha information and it's pre-multiplied alpha
else if (hasValidPremultipliedAlpha) {
if (a > 0) {
// Convert it to straight alpha
r = r * 255 / a;
g = g * 255 / a;
b = b * 255 / a;
}
}
*dst =
(r << spec.red_shift ) |
(g << spec.green_shift) |
(b << spec.blue_shift ) |
(a << spec.alpha_shift);
}
}
}
#endif // CLIP_ENABLE_IMAGE
} // namespace details
} // namespace clip
#endif // CLIP_H_INCLUDED

View file

@ -0,0 +1,33 @@
// Clip Library
// Copyright (c) 2015-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef CLIP_LOCK_IMPL_H_INCLUDED
#define CLIP_LOCK_IMPL_H_INCLUDED
namespace clip {
class lock::impl {
public:
impl(void* native_window_handle);
~impl();
bool locked() const { return m_locked; }
bool clear();
bool is_convertible(format f) const;
bool set_data(format f, const char* buf, size_t len);
bool get_data(format f, char* buf, size_t len) const;
size_t get_data_length(format f) const;
bool set_image(const image& image);
bool get_image(image& image) const;
bool get_image_spec(image_spec& spec) const;
private:
bool m_locked;
};
} // namespace clip
#endif

View file

@ -0,0 +1,371 @@
// Clip Library
// Copyright (c) 2015-2023 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
#include "clip_common.h"
#include "clip_lock_impl.h"
#include <cassert>
#include <vector>
#include <map>
#include <CoreFoundation/CoreFoundation.h>
#include <Foundation/Foundation.h>
#include <AppKit/AppKit.h>
namespace clip {
namespace {
format g_last_format = 100;
std::map<std::string, format> g_name_to_format;
std::map<format, std::string> g_format_to_name;
#if CLIP_ENABLE_IMAGE
bool get_image_from_clipboard(image* output_img,
image_spec* output_spec)
{
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSString* result = [pasteboard availableTypeFromArray:
[NSArray arrayWithObjects:NSPasteboardTypeTIFF,NSPasteboardTypePNG,nil]];
if (!result)
return false;
NSData* data = [pasteboard dataForType:result];
if (!data)
return false;
NSBitmapImageRep* bitmap = [NSBitmapImageRep imageRepWithData:data];
if ((bitmap.bitmapFormat & NSBitmapFormatFloatingPointSamples) ||
(bitmap.planar)) {
error_handler e = get_error_handler();
if (e)
e(ErrorCode::ImageNotSupported);
return false;
}
image_spec spec;
spec.width = bitmap.pixelsWide;
spec.height = bitmap.pixelsHigh;
spec.bits_per_pixel = bitmap.bitsPerPixel;
spec.bytes_per_row = bitmap.bytesPerRow;
// We need three samples for Red/Green/Blue
if (bitmap.samplesPerPixel >= 3) {
// Here we are guessing the bits per sample (generally 8, not
// sure how many bits per sample macOS uses for 16bpp
// NSBitmapFormat or if this format is even used).
int bits_per_sample = (bitmap.bitsPerPixel == 16 ? 5: 8);
int bits_shift = 0;
// With alpha
if (bitmap.alpha) {
if (bitmap.bitmapFormat & NSBitmapFormatAlphaFirst) {
spec.alpha_shift = 0;
bits_shift += bits_per_sample;
}
else {
spec.alpha_shift = 3*bits_per_sample;
}
}
unsigned long* masks = &spec.red_mask;
unsigned long* shifts = &spec.red_shift;
// Red/green/blue shifts
for (unsigned long* shift=shifts; shift<shifts+3; ++shift) {
*shift = bits_shift;
bits_shift += bits_per_sample;
}
// With alpha
if (bitmap.alpha) {
if (bitmap.bitmapFormat & NSBitmapFormatSixteenBitBigEndian ||
bitmap.bitmapFormat & NSBitmapFormatThirtyTwoBitBigEndian) {
std::swap(spec.red_shift, spec.alpha_shift);
std::swap(spec.green_shift, spec.blue_shift);
}
}
// Without alpha
else {
if (bitmap.bitmapFormat & NSBitmapFormatSixteenBitBigEndian ||
bitmap.bitmapFormat & NSBitmapFormatThirtyTwoBitBigEndian) {
std::swap(spec.red_shift, spec.blue_shift);
}
}
// Calculate all masks
for (unsigned long* shift=shifts, *mask=masks; shift<shifts+4; ++shift, ++mask)
*mask = ((1ul<<bits_per_sample)-1ul) << (*shift);
// Without alpha
if (!bitmap.alpha)
spec.alpha_mask = 0;
}
if (output_spec) {
*output_spec = spec;
}
if (output_img) {
unsigned long size = spec.bytes_per_row*spec.height;
image img(spec);
std::copy(bitmap.bitmapData,
bitmap.bitmapData+size, img.data());
// Convert premultiplied data to unpremultiplied if needed.
if (bitmap.alpha &&
bitmap.samplesPerPixel >= 3 &&
!(bitmap.bitmapFormat & NSBitmapFormatAlphaNonpremultiplied)) {
details::divide_rgb_by_alpha(
img,
true); // hasAlphaGreaterThanZero=true because we have valid alpha information
}
std::swap(*output_img, img);
}
return true;
}
#endif // CLIP_ENABLE_IMAGE
}
lock::impl::impl(void*) : m_locked(true) {
}
lock::impl::~impl() {
}
bool lock::impl::clear() {
@autoreleasepool {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
[pasteboard clearContents];
return true;
}
}
bool lock::impl::is_convertible(format f) const {
@autoreleasepool {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSString* result = nil;
if (f == text_format()) {
result = [pasteboard availableTypeFromArray:[NSArray arrayWithObject:NSPasteboardTypeString]];
}
#if CLIP_ENABLE_IMAGE
else if (f == image_format()) {
result = [pasteboard availableTypeFromArray:
[NSArray arrayWithObjects:NSPasteboardTypeTIFF,NSPasteboardTypePNG,nil]];
}
#endif // CLIP_ENABLE_IMAGE
else {
auto it = g_format_to_name.find(f);
if (it != g_format_to_name.end()) {
const std::string& name = it->second;
NSString* string = [[NSString alloc] initWithBytesNoCopy:(void*)name.c_str()
length:name.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
result = [pasteboard availableTypeFromArray:[NSArray arrayWithObject:string]];
}
}
return (result ? true: false);
}
}
bool lock::impl::set_data(format f, const char* buf, size_t len) {
@autoreleasepool {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
if (f == text_format()) {
NSString* string = [[NSString alloc] initWithBytesNoCopy:(void*)buf
length:len
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
[pasteboard setString:string forType:NSPasteboardTypeString];
return true;
}
else {
auto it = g_format_to_name.find(f);
if (it != g_format_to_name.end()) {
const std::string& formatName = it->second;
NSString* typeString = [[NSString alloc]
initWithBytesNoCopy:(void*)formatName.c_str()
length:formatName.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
NSData* data = [NSData dataWithBytesNoCopy:(void*)buf
length:len
freeWhenDone:NO];
if ([pasteboard setData:data forType:typeString])
return true;
}
}
return false;
}
}
bool lock::impl::get_data(format f, char* buf, size_t len) const {
@autoreleasepool {
assert(buf);
if (!buf || !is_convertible(f))
return false;
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
if (f == text_format()) {
NSString* string = [pasteboard stringForType:NSPasteboardTypeString];
int reqsize = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]+1;
assert(reqsize <= len);
if (reqsize > len) {
// Buffer is too small
return false;
}
if (reqsize == 0)
return true;
memcpy(buf, [string UTF8String], reqsize);
return true;
}
auto it = g_format_to_name.find(f);
if (it == g_format_to_name.end())
return false;
const std::string& formatName = it->second;
NSString* typeString =
[[NSString alloc] initWithBytesNoCopy:(void*)formatName.c_str()
length:formatName.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
NSData* data = [pasteboard dataForType:typeString];
if (!data)
return false;
[data getBytes:buf length:len];
return true;
}
}
size_t lock::impl::get_data_length(format f) const {
@autoreleasepool {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
if (f == text_format()) {
NSString* string = [pasteboard stringForType:NSPasteboardTypeString];
return [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]+1;
}
auto it = g_format_to_name.find(f);
if (it == g_format_to_name.end())
return 0;
const std::string& formatName = it->second;
NSString* typeString =
[[NSString alloc] initWithBytesNoCopy:(void*)formatName.c_str()
length:formatName.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
NSData* data = [pasteboard dataForType:typeString];
if (!data)
return 0;
return data.length;
}
}
#if CLIP_ENABLE_IMAGE
bool lock::impl::set_image(const image& image) {
@autoreleasepool {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
const image_spec& spec = image.spec();
NSBitmapFormat bitmapFormat = 0;
int samples_per_pixel = 0;
if (spec.alpha_mask) {
samples_per_pixel = 4;
if (spec.alpha_shift == 0)
bitmapFormat |= NSBitmapFormatAlphaFirst;
bitmapFormat |= NSBitmapFormatAlphaNonpremultiplied;
}
else if (spec.red_mask || spec.green_mask || spec.blue_mask) {
samples_per_pixel = 3;
}
else {
samples_per_pixel = 1;
}
if (spec.bits_per_pixel == 32)
bitmapFormat |= NSBitmapFormatThirtyTwoBitLittleEndian;
else if (spec.bits_per_pixel == 16)
bitmapFormat |= NSBitmapFormatSixteenBitLittleEndian;
std::vector<unsigned char*> planes(1);
planes[0] = (unsigned char*)image.data();
NSBitmapImageRep* bitmap =
[[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:&planes[0]
pixelsWide:spec.width
pixelsHigh:spec.height
bitsPerSample:spec.bits_per_pixel / samples_per_pixel
samplesPerPixel:samples_per_pixel
hasAlpha:(spec.alpha_mask ? YES: NO)
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bitmapFormat:bitmapFormat
bytesPerRow:spec.bytes_per_row
bitsPerPixel:spec.bits_per_pixel];
if (!bitmap)
return false;
NSData* data = bitmap.TIFFRepresentation;
if (!data)
return false;
if ([pasteboard setData:data forType:NSPasteboardTypeTIFF])
return true;
return false;
}
}
bool lock::impl::get_image(image& img) const {
return get_image_from_clipboard(&img, nullptr);
}
bool lock::impl::get_image_spec(image_spec& spec) const {
return get_image_from_clipboard(nullptr, &spec);
}
#endif // CLIP_ENABLE_IMAGE
format register_format(const std::string& name) {
// Check if the format is already registered
auto it = g_name_to_format.find(name);
if (it != g_name_to_format.end())
return it->second;
format new_format = g_last_format++;
g_name_to_format[name] = new_format;
g_format_to_name[new_format] = name;
return new_format;
}
} // namespace clip

View file

@ -0,0 +1,659 @@
// Clip Library
// Copyright (C) 2015-2020 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
#include "clip_common.h"
#include "clip_lock_impl.h"
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <windows.h>
#if CLIP_ENABLE_IMAGE
#include "clip_win_wic.h"
#endif // CLIP_ENABLE_IMAGE
#ifndef LCS_WINDOWS_COLOR_SPACE
#define LCS_WINDOWS_COLOR_SPACE 'Win '
#endif
#ifndef CF_DIBV5
#define CF_DIBV5 17
#endif
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;
unsigned long get_shift_from_mask(unsigned long mask) {
unsigned long shift = 0;
for (shift=0; shift<sizeof(unsigned long)*8; ++shift)
if (mask & (1 << shift))
return shift;
return shift;
}
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;
};
#if CLIP_ENABLE_IMAGE
struct BitmapInfo {
BITMAPV5HEADER* b5 = nullptr;
BITMAPINFO* bi = nullptr;
int width = 0;
int height = 0;
uint16_t bit_count = 0;
uint32_t compression = 0;
uint32_t red_mask = 0;
uint32_t green_mask = 0;
uint32_t blue_mask = 0;
uint32_t alpha_mask = 0;
BitmapInfo() {
// Use DIBV5 only for 32 bpp uncompressed bitmaps and when all
// masks are valid.
if (IsClipboardFormatAvailable(CF_DIBV5)) {
b5 = (BITMAPV5HEADER*)GetClipboardData(CF_DIBV5);
if (b5 &&
b5->bV5BitCount == 32 &&
((b5->bV5Compression == BI_RGB) ||
(b5->bV5Compression == BI_BITFIELDS &&
b5->bV5RedMask && b5->bV5GreenMask &&
b5->bV5BlueMask && b5->bV5AlphaMask))) {
width = b5->bV5Width;
height = b5->bV5Height;
bit_count = b5->bV5BitCount;
compression = b5->bV5Compression;
if (compression == BI_BITFIELDS) {
red_mask = b5->bV5RedMask;
green_mask = b5->bV5GreenMask;
blue_mask = b5->bV5BlueMask;
alpha_mask = b5->bV5AlphaMask;
}
else {
red_mask = 0xff0000;
green_mask = 0xff00;
blue_mask = 0xff;
alpha_mask = 0xff000000;
}
return;
}
}
if (IsClipboardFormatAvailable(CF_DIB))
bi = (BITMAPINFO*)GetClipboardData(CF_DIB);
if (!bi)
return;
width = bi->bmiHeader.biWidth;
height = bi->bmiHeader.biHeight;
bit_count = bi->bmiHeader.biBitCount;
compression = bi->bmiHeader.biCompression;
if (compression == BI_BITFIELDS) {
red_mask = *((uint32_t*)&bi->bmiColors[0]);
green_mask = *((uint32_t*)&bi->bmiColors[1]);
blue_mask = *((uint32_t*)&bi->bmiColors[2]);
if (bit_count == 32)
alpha_mask = 0xff000000;
}
else if (compression == BI_RGB) {
switch (bit_count) {
case 32:
red_mask = 0xff0000;
green_mask = 0xff00;
blue_mask = 0xff;
alpha_mask = 0xff000000;
break;
case 24:
case 8: // We return 8bpp images as 24bpp
red_mask = 0xff0000;
green_mask = 0xff00;
blue_mask = 0xff;
break;
case 16:
red_mask = 0x7c00;
green_mask = 0x03e0;
blue_mask = 0x001f;
break;
}
}
}
bool is_valid() const {
return (b5 || bi);
}
void fill_spec(image_spec& spec) {
spec.width = width;
spec.height = (height >= 0 ? height: -height);
// We convert indexed to 24bpp RGB images to match the OS X behavior
spec.bits_per_pixel = bit_count;
if (spec.bits_per_pixel <= 8)
spec.bits_per_pixel = 24;
spec.bytes_per_row = width*((spec.bits_per_pixel+7)/8);
spec.red_mask = red_mask;
spec.green_mask = green_mask;
spec.blue_mask = blue_mask;
spec.alpha_mask = alpha_mask;
switch (spec.bits_per_pixel) {
case 24: {
// We need one extra byte to avoid a crash updating the last
// pixel on last row using:
//
// *((uint32_t*)ptr) = pixel24bpp;
//
++spec.bytes_per_row;
// Align each row to 32bpp
int padding = (4-(spec.bytes_per_row&3))&3;
spec.bytes_per_row += padding;
break;
}
case 16: {
int padding = (4-(spec.bytes_per_row&3))&3;
spec.bytes_per_row += padding;
break;
}
}
unsigned long* masks = &spec.red_mask;
unsigned long* shifts = &spec.red_shift;
for (unsigned long* shift=shifts, *mask=masks; shift<shifts+4; ++shift, ++mask) {
if (*mask)
*shift = get_shift_from_mask(*mask);
}
}
};
#endif // CLIP_ENABLE_IMAGE
}
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)
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) ? true: false);
}
#endif // CLIP_ENABLE_IMAGE
else if (IsClipboardFormatAvailable(f))
return true;
else
return false;
}
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_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);
}
}
image_spec out_spec = spec;
int palette_colors = 0;
int padding = 0;
switch (spec.bits_per_pixel) {
case 24: padding = (4-((spec.width*3)&3))&3; break;
case 16: padding = ((4-((spec.width*2)&3))&3)/2; break;
case 8: padding = (4-(spec.width&3))&3; break;
}
out_spec.bytes_per_row += padding;
// Create the BITMAPV5HEADER structure
Hglobal hmem(
GlobalAlloc(
GHND,
sizeof(BITMAPV5HEADER)
+ palette_colors*sizeof(RGBQUAD)
+ out_spec.bytes_per_row*out_spec.height));
if (!hmem)
return false;
out_spec.red_mask = 0x00ff0000;
out_spec.green_mask = 0xff00;
out_spec.blue_mask = 0xff;
out_spec.alpha_mask = 0xff000000;
out_spec.red_shift = 16;
out_spec.green_shift = 8;
out_spec.blue_shift = 0;
out_spec.alpha_shift = 24;
BITMAPV5HEADER* bi = (BITMAPV5HEADER*)GlobalLock(hmem);
bi->bV5Size = sizeof(BITMAPV5HEADER);
bi->bV5Width = out_spec.width;
bi->bV5Height = out_spec.height;
bi->bV5Planes = 1;
bi->bV5BitCount = (WORD)out_spec.bits_per_pixel;
bi->bV5Compression = BI_RGB;
bi->bV5SizeImage = out_spec.bytes_per_row*spec.height;
bi->bV5RedMask = out_spec.red_mask;
bi->bV5GreenMask = out_spec.green_mask;
bi->bV5BlueMask = out_spec.blue_mask;
bi->bV5AlphaMask = out_spec.alpha_mask;
bi->bV5CSType = LCS_WINDOWS_COLOR_SPACE;
bi->bV5Intent = LCS_GM_GRAPHICS;
bi->bV5ClrUsed = 0;
switch (spec.bits_per_pixel) {
case 32: {
const char* src = image.data();
char* dst = (((char*)bi)+bi->bV5Size) + (out_spec.height-1)*out_spec.bytes_per_row;
for (long y=spec.height-1; y>=0; --y) {
const uint32_t* src_x = (const uint32_t*)src;
uint32_t* dst_x = (uint32_t*)dst;
for (unsigned long x=0; x<spec.width; ++x, ++src_x, ++dst_x) {
uint32_t c = *src_x;
int r = ((c & spec.red_mask ) >> spec.red_shift );
int g = ((c & spec.green_mask) >> spec.green_shift);
int b = ((c & spec.blue_mask ) >> spec.blue_shift );
int a = ((c & spec.alpha_mask) >> spec.alpha_shift);
// Windows requires premultiplied RGBA values
r = r * a / 255;
g = g * a / 255;
b = b * a / 255;
*dst_x =
(r << out_spec.red_shift ) |
(g << out_spec.green_shift) |
(b << out_spec.blue_shift ) |
(a << out_spec.alpha_shift);
}
src += spec.bytes_per_row;
dst -= out_spec.bytes_per_row;
}
break;
}
default:
error_handler e = get_error_handler();
if (e)
e(ErrorCode::ImageNotSupported);
return false;
}
GlobalUnlock(hmem);
SetClipboardData(CF_DIBV5, hmem);
return true;
}
bool lock::impl::get_image(image& output_img) const {
// Get the "PNG" clipboard format (this is useful only for 32bpp
// images with alpha channel, in other case we can use the regular
// DIB format)
UINT png_format = RegisterClipboardFormatA("PNG");
if (png_format && IsClipboardFormatAvailable(png_format)) {
HANDLE png_handle = GetClipboardData(png_format);
if (png_handle) {
size_t png_size = GlobalSize(png_handle);
uint8_t* png_data = (uint8_t*)GlobalLock(png_handle);
bool result = win::read_png(png_data, png_size, &output_img, nullptr);
GlobalUnlock(png_handle);
if (result)
return true;
}
}
BitmapInfo bi;
if (!bi.is_valid()) {
// There is no image at all in the clipboard, no need to report
// this as an error, just return false.
return false;
}
image_spec spec;
bi.fill_spec(spec);
image img(spec);
switch (bi.bit_count) {
case 32:
case 24:
case 16: {
const uint8_t* src = nullptr;
if (bi.compression == BI_RGB ||
bi.compression == BI_BITFIELDS) {
if (bi.b5)
src = ((uint8_t*)bi.b5) + bi.b5->bV5Size;
else
src = ((uint8_t*)bi.bi) + bi.bi->bmiHeader.biSize;
if (bi.compression == BI_BITFIELDS)
src += sizeof(RGBQUAD)*3;
}
if (src) {
const int src_bytes_per_row = spec.width*((bi.bit_count+7)/8);
const int padding = (4-(src_bytes_per_row&3))&3;
for (long y=spec.height-1; y>=0; --y, src+=src_bytes_per_row+padding) {
char* dst = img.data()+y*spec.bytes_per_row;
std::copy(src, src+src_bytes_per_row, dst);
}
}
// Windows uses premultiplied RGB values, and we use straight
// alpha. So we have to divide all RGB values by its alpha.
if (bi.bit_count == 32 && spec.alpha_mask) {
details::divide_rgb_by_alpha(img);
}
break;
}
case 8: {
assert(bi.bi);
const int colors = (bi.bi->bmiHeader.biClrUsed > 0 ? bi.bi->bmiHeader.biClrUsed: 256);
std::vector<uint32_t> palette(colors);
for (int c=0; c<colors; ++c) {
palette[c] =
(bi.bi->bmiColors[c].rgbRed << spec.red_shift) |
(bi.bi->bmiColors[c].rgbGreen << spec.green_shift) |
(bi.bi->bmiColors[c].rgbBlue << spec.blue_shift);
}
const uint8_t* src = (((uint8_t*)bi.bi) + bi.bi->bmiHeader.biSize + sizeof(RGBQUAD)*colors);
const int padding = (4-(spec.width&3))&3;
for (long y=spec.height-1; y>=0; --y, src+=padding) {
char* dst = img.data()+y*spec.bytes_per_row;
for (unsigned long x=0; x<spec.width; ++x, ++src, dst+=3) {
int idx = *src;
if (idx < 0)
idx = 0;
else if (idx >= colors)
idx = colors-1;
*((uint32_t*)dst) = palette[idx];
}
}
break;
}
}
std::swap(output_img, img);
return true;
}
bool lock::impl::get_image_spec(image_spec& spec) const {
UINT png_format = RegisterClipboardFormatA("PNG");
if (png_format && IsClipboardFormatAvailable(png_format)) {
HANDLE png_handle = GetClipboardData(png_format);
if (png_handle) {
size_t png_size = GlobalSize(png_handle);
uint8_t* png_data = (uint8_t*)GlobalLock(png_handle);
bool result = win::read_png(png_data, png_size, nullptr, &spec);
GlobalUnlock(png_handle);
if (result)
return true;
}
}
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

View file

@ -0,0 +1,286 @@
// Clip Library
// Copyright (c) 2020-2022 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
#include <algorithm>
#include <vector>
#include <shlwapi.h>
#include <wincodec.h>
namespace clip {
namespace win {
// Successful calls to CoInitialize() (S_OK or S_FALSE) must match
// the calls to CoUninitialize().
// From: https://docs.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-couninitialize#remarks
struct coinit {
HRESULT hr;
coinit() {
hr = CoInitialize(nullptr);
}
~coinit() {
if (hr == S_OK || hr == S_FALSE)
CoUninitialize();
}
};
template<class T>
class comptr {
public:
comptr() { }
explicit comptr(T* ptr) : m_ptr(ptr) { }
comptr(const comptr&) = delete;
comptr& operator=(const comptr&) = delete;
~comptr() { reset(); }
T** operator&() { return &m_ptr; }
T* operator->() { return m_ptr; }
bool operator!() const { return !m_ptr; }
T* get() { return m_ptr; }
void reset() {
if (m_ptr) {
m_ptr->Release();
m_ptr = nullptr;
}
}
private:
T* m_ptr = nullptr;
};
#ifdef CLIP_SUPPORT_WINXP
class hmodule {
public:
hmodule(LPCWSTR name) : m_ptr(LoadLibraryW(name)) { }
hmodule(const hmodule&) = delete;
hmodule& operator=(const hmodule&) = delete;
~hmodule() {
if (m_ptr)
FreeLibrary(m_ptr);
}
operator HMODULE() { return m_ptr; }
bool operator!() const { return !m_ptr; }
private:
HMODULE m_ptr = nullptr;
};
#endif
//////////////////////////////////////////////////////////////////////
// Encode the image as PNG format
bool write_png_on_stream(const image& image,
IStream* stream) {
const image_spec& spec = image.spec();
comptr<IWICBitmapEncoder> encoder;
HRESULT hr = CoCreateInstance(CLSID_WICPngEncoder,
nullptr, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&encoder));
if (FAILED(hr))
return false;
hr = encoder->Initialize(stream, WICBitmapEncoderNoCache);
if (FAILED(hr))
return false;
comptr<IWICBitmapFrameEncode> frame;
comptr<IPropertyBag2> options;
hr = encoder->CreateNewFrame(&frame, &options);
if (FAILED(hr))
return false;
hr = frame->Initialize(options.get());
if (FAILED(hr))
return false;
// PNG encoder (and decoder) only supports GUID_WICPixelFormat32bppBGRA for 32bpp.
// See: https://docs.microsoft.com/en-us/windows/win32/wic/-wic-codec-native-pixel-formats#png-native-codec
WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppBGRA;
hr = frame->SetPixelFormat(&pixelFormat);
if (FAILED(hr))
return false;
hr = frame->SetSize(spec.width, spec.height);
if (FAILED(hr))
return false;
std::vector<uint32_t> buf;
uint8_t* ptr = (uint8_t*)image.data();
int bytes_per_row = spec.bytes_per_row;
// Convert to GUID_WICPixelFormat32bppBGRA if needed
if (spec.red_mask != 0xff0000 ||
spec.green_mask != 0xff00 ||
spec.blue_mask != 0xff ||
spec.alpha_mask != 0xff000000) {
buf.resize(spec.width * spec.height);
uint32_t* dst = (uint32_t*)&buf[0];
uint32_t* src = (uint32_t*)image.data();
for (int y=0; y<spec.height; ++y) {
auto src_line_start = src;
for (int x=0; x<spec.width; ++x) {
uint32_t c = *src;
*dst = ((((c & spec.red_mask ) >> spec.red_shift ) << 16) |
(((c & spec.green_mask) >> spec.green_shift) << 8) |
(((c & spec.blue_mask ) >> spec.blue_shift ) ) |
(((c & spec.alpha_mask) >> spec.alpha_shift) << 24));
++dst;
++src;
}
src = (uint32_t*)(((uint8_t*)src_line_start) + spec.bytes_per_row);
}
ptr = (uint8_t*)&buf[0];
bytes_per_row = 4 * spec.width;
}
hr = frame->WritePixels(spec.height,
bytes_per_row,
bytes_per_row * spec.height,
(BYTE*)ptr);
if (FAILED(hr))
return false;
hr = frame->Commit();
if (FAILED(hr))
return false;
hr = encoder->Commit();
if (FAILED(hr))
return false;
return true;
}
HGLOBAL write_png(const image& image) {
coinit com;
comptr<IStream> stream;
HRESULT hr = CreateStreamOnHGlobal(nullptr, false, &stream);
if (FAILED(hr))
return nullptr;
bool result = write_png_on_stream(image, stream.get());
HGLOBAL handle;
hr = GetHGlobalFromStream(stream.get(), &handle);
if (result)
return handle;
GlobalFree(handle);
return nullptr;
}
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from PNG format
bool read_png(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec) {
coinit com;
#ifdef CLIP_SUPPORT_WINXP
// Pull SHCreateMemStream from shlwapi.dll by ordinal 12
// for Windows XP support
// From: https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-shcreatememstream#remarks
typedef IStream* (WINAPI* SHCreateMemStreamPtr)(const BYTE* pInit, UINT cbInit);
hmodule shlwapiDll(L"shlwapi.dll");
if (!shlwapiDll)
return false;
auto SHCreateMemStream =
reinterpret_cast<SHCreateMemStreamPtr>(GetProcAddress(shlwapiDll, (LPCSTR)12));
if (!SHCreateMemStream)
return false;
#endif
comptr<IStream> stream(SHCreateMemStream(buf, len));
if (!stream)
return false;
comptr<IWICBitmapDecoder> decoder;
HRESULT hr = CoCreateInstance(CLSID_WICPngDecoder2,
nullptr, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&decoder));
if (FAILED(hr)) {
hr = CoCreateInstance(CLSID_WICPngDecoder1,
nullptr, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&decoder));
if (FAILED(hr))
return false;
}
// Can decoder be nullptr if hr is S_OK/successful? We've received
// some crash reports that might indicate this.
if (!decoder)
return false;
hr = decoder->Initialize(stream.get(), WICDecodeMetadataCacheOnDemand);
if (FAILED(hr))
return false;
comptr<IWICBitmapFrameDecode> frame;
hr = decoder->GetFrame(0, &frame);
if (FAILED(hr))
return false;
WICPixelFormatGUID pixelFormat;
hr = frame->GetPixelFormat(&pixelFormat);
if (FAILED(hr))
return false;
// Only support this pixel format
// TODO add support for more pixel formats
if (pixelFormat != GUID_WICPixelFormat32bppBGRA)
return false;
UINT width = 0, height = 0;
hr = frame->GetSize(&width, &height);
if (FAILED(hr))
return false;
image_spec spec;
spec.width = width;
spec.height = height;
spec.bits_per_pixel = 32;
spec.bytes_per_row = 4 * width;
spec.red_mask = 0xff0000;
spec.green_mask = 0xff00;
spec.blue_mask = 0xff;
spec.alpha_mask = 0xff000000;
spec.red_shift = 16;
spec.green_shift = 8;
spec.blue_shift = 0;
spec.alpha_shift = 24;
if (output_spec)
*output_spec = spec;
if (output_image) {
image img(spec);
hr = frame->CopyPixels(
nullptr, // Entire bitmap
spec.bytes_per_row,
spec.bytes_per_row * spec.height,
(BYTE*)img.data());
if (FAILED(hr)) {
return false;
}
std::swap(*output_image, img);
}
return true;
}
} // namespace win
} // namespace clip

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,230 @@
// Clip Library
// Copyright (c) 2018-2021 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
#include <algorithm>
#include <vector>
#include "png.h"
namespace clip {
namespace x11 {
//////////////////////////////////////////////////////////////////////
// Functions to convert clip::image into png data to store it in the
// clipboard.
void write_data_fn(png_structp png, png_bytep buf, png_size_t len) {
std::vector<uint8_t>& output = *(std::vector<uint8_t>*)png_get_io_ptr(png);
const size_t i = output.size();
output.resize(i+len);
std::copy(buf, buf+len, output.begin()+i);
}
bool write_png(const image& image,
std::vector<uint8_t>& output) {
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
nullptr, nullptr, nullptr);
if (!png)
return false;
png_infop info = png_create_info_struct(png);
if (!info) {
png_destroy_write_struct(&png, nullptr);
return false;
}
if (setjmp(png_jmpbuf(png))) {
png_destroy_write_struct(&png, &info);
return false;
}
png_set_write_fn(png,
(png_voidp)&output,
write_data_fn,
nullptr); // No need for a flush function
const image_spec& spec = image.spec();
int color_type = (spec.alpha_mask ?
PNG_COLOR_TYPE_RGB_ALPHA:
PNG_COLOR_TYPE_RGB);
png_set_IHDR(png, info,
spec.width, spec.height, 8, color_type,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_write_info(png, info);
png_set_packing(png);
png_bytep row =
(png_bytep)png_malloc(png, png_get_rowbytes(png, info));
for (png_uint_32 y=0; y<spec.height; ++y) {
const uint32_t* src =
(const uint32_t*)(((const uint8_t*)image.data())
+ y*spec.bytes_per_row);
uint8_t* dst = row;
unsigned int x, c;
for (x=0; x<spec.width; x++) {
c = *(src++);
*(dst++) = (c & spec.red_mask ) >> spec.red_shift;
*(dst++) = (c & spec.green_mask) >> spec.green_shift;
*(dst++) = (c & spec.blue_mask ) >> spec.blue_shift;
if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
*(dst++) = (c & spec.alpha_mask) >> spec.alpha_shift;
}
png_write_rows(png, &row, 1);
}
png_free(png, row);
png_write_end(png, info);
png_destroy_write_struct(&png, &info);
return true;
}
//////////////////////////////////////////////////////////////////////
// Functions to convert png data stored in the clipboard to a
// clip::image.
struct read_png_io {
const uint8_t* buf;
size_t len;
size_t pos;
};
void read_data_fn(png_structp png, png_bytep buf, png_size_t len) {
read_png_io& io = *(read_png_io*)png_get_io_ptr(png);
if (io.pos < io.len) {
size_t n = std::min(len, io.len-io.pos);
if (n > 0) {
std::copy(io.buf+io.pos,
io.buf+io.pos+n,
buf);
io.pos += n;
}
}
}
bool read_png(const uint8_t* buf,
const size_t len,
image* output_image,
image_spec* output_spec) {
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
nullptr, nullptr, nullptr);
if (!png)
return false;
png_infop info = png_create_info_struct(png);
if (!info) {
png_destroy_read_struct(&png, nullptr, nullptr);
return false;
}
if (setjmp(png_jmpbuf(png))) {
png_destroy_read_struct(&png, &info, nullptr);
return false;
}
read_png_io io = { buf, len, 0 };
png_set_read_fn(png, (png_voidp)&io, read_data_fn);
png_read_info(png, info);
png_uint_32 width, height;
int bit_depth, color_type, interlace_type;
png_get_IHDR(png, info, &width, &height,
&bit_depth, &color_type,
&interlace_type,
nullptr, nullptr);
image_spec spec;
spec.width = width;
spec.height = height;
spec.bits_per_pixel = 32;
// Don't use png_get_rowbytes(png, info) here because this is the
// bytes_per_row of the output clip::image (the png file could
// contain 24bpp but we want to return a 32bpp anyway with alpha=255
// in that case).
spec.bytes_per_row = 4*width;
spec.red_mask = 0x000000ff;
spec.green_mask = 0x0000ff00;
spec.blue_mask = 0x00ff0000;
spec.red_shift = 0;
spec.green_shift = 8;
spec.blue_shift = 16;
if ((color_type & PNG_COLOR_MASK_ALPHA) == PNG_COLOR_MASK_ALPHA) {
spec.alpha_mask = 0xff000000;
spec.alpha_shift = 24;
}
else {
spec.alpha_mask = 0;
spec.alpha_shift = 0;
}
if (output_spec)
*output_spec = spec;
if (output_image &&
width > 0 &&
height > 0) {
image img(spec);
// We want RGB 24-bit or RGBA 32-bit as a result
png_set_strip_16(png); // Down to 8-bit (TODO we might support 16-bit values)
png_set_packing(png); // Use one byte if color depth < 8-bit
png_set_expand_gray_1_2_4_to_8(png);
png_set_palette_to_rgb(png);
png_set_gray_to_rgb(png);
png_set_tRNS_to_alpha(png);
int number_passes = png_set_interlace_handling(png);
png_read_update_info(png, info);
const int src_bytes_per_row = png_get_rowbytes(png, info);
png_bytepp rows = (png_bytepp)png_malloc(png, sizeof(png_bytep)*height);
png_uint_32 y;
for (y=0; y<height; ++y)
rows[y] = (png_bytep)png_malloc(png, src_bytes_per_row);
for (int pass=0; pass<number_passes; ++pass)
for (y=0; y<height; ++y)
png_read_rows(png, rows+y, nullptr, 1);
for (y=0; y<height; ++y) {
const uint8_t* src = rows[y];
uint32_t* dst = (uint32_t*)(img.data() + y*spec.bytes_per_row);
unsigned int x, r, g, b, a = 0;
for (x=0; x<width; x++) {
r = *(src++);
g = *(src++);
b = *(src++);
if (spec.alpha_mask)
a = *(src++);
*(dst++) =
(r << spec.red_shift) |
(g << spec.green_shift) |
(b << spec.blue_shift) |
(a << spec.alpha_shift);
}
png_free(png, rows[y]);
}
png_free(png, rows);
std::swap(*output_image, img);
}
png_destroy_read_struct(&png, &info, nullptr);
return true;
}
} // namespace x11
} // namespace clip

View file

@ -0,0 +1,99 @@
// Clip Library
// Copyright (c) 2015-2022 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
namespace clip {
unsigned long image_spec::required_data_size() const
{
unsigned long n = (bytes_per_row * height);
// For 24bpp we add some extra space to access the last pixel (3
// bytes) as an uint32_t
if (bits_per_pixel == 24) {
if ((n % 4) > 0)
n += 4 - (n % 4);
else
++n;
}
return n;
}
image::image()
: m_own_data(false),
m_data(nullptr)
{
}
image::image(const image_spec& spec)
: m_own_data(true),
m_data(new char[spec.required_data_size()]),
m_spec(spec) {
}
image::image(const void* data, const image_spec& spec)
: m_own_data(false),
m_data((char*)data),
m_spec(spec) {
}
image::image(const image& image)
: m_own_data(false),
m_data(nullptr),
m_spec(image.m_spec) {
copy_image(image);
}
image::image(image&& image)
: m_own_data(false),
m_data(nullptr) {
move_image(std::move(image));
}
image::~image() {
reset();
}
image& image::operator=(const image& image) {
copy_image(image);
return *this;
}
image& image::operator=(image&& image) {
move_image(std::move(image));
return *this;
}
void image::reset() {
if (m_own_data) {
delete[] m_data;
m_own_data = false;
m_data = nullptr;
}
}
void image::copy_image(const image& image) {
reset();
m_spec = image.spec();
std::size_t n = m_spec.required_data_size();
m_own_data = true;
m_data = new char[n];
std::copy(image.data(),
image.data()+n,
m_data);
}
void image::move_image(image&& image) {
std::swap(m_own_data, image.m_own_data);
std::swap(m_data, image.m_data);
std::swap(m_spec, image.m_spec);
}
} // namespace clip

View file

@ -0,0 +1,282 @@
//----------------------------------------------------------------------------------------------------------------------
// 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>
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()
/// @brief Gets text (if present) in the OS clipboard.
/// @return A qbs string.
qbs *func__clipboard() {
std::string text;
qbs *qbsText;
if (clip::has(clip::text_format()) && clip::get_text(text)) {
qbsText = qbs_new(text.length(), 1);
if (qbsText->len)
memcpy(qbsText->chr, text.data(), qbsText->len);
} else {
qbsText = qbs_new(0, 1);
}
return qbsText;
}
/// @brief Sets text to the OS clipboard.
/// @param qbsText A qbs string.
void sub__clipboard(const qbs *qbsText) {
std::string text(reinterpret_cast<const char *>(qbsText->chr), qbsText->len);
if (qbsText->len)
clip::set_text(text);
}
static constexpr inline int clipboard_scale_5bits_to_8bits(const int v) { return (v << 3) | (v >> 2); }
static constexpr inline int clipboard_scale_6bits_to_8bits(const int v) { return (v << 2) | (v >> 4); }
/// @brief Retuns 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(clipboard_scale_5bits_to_8bits((c & spec.red_mask) >> spec.red_shift),
clipboard_scale_6bits_to_8bits((c & spec.green_mask) >> spec.green_shift),
clipboard_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
// We'll set this up like QB64's 32bpp BGRA to avoid conversions at our end
clip::image_spec spec;
spec.bits_per_pixel = 32;
spec.red_mask = 0xff0000;
spec.green_mask = 0xff00;
spec.blue_mask = 0xff;
spec.alpha_mask = 0xff000000;
spec.red_shift = 16;
spec.green_shift = 8;
spec.blue_shift = 0;
spec.alpha_shift = 24;
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);
std::vector<uint32_t> pixels(spec.width * spec.height); // this will hold our converted BGRA pixel data
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] = (*builtinFont ? srcImg.pal[fc] : srcImg.pal[bc]);
++builtinFont;
}
}
++c; // move to the next codepoint
}
}
IMAGE_DEBUG_PRINT("Setting clipboard image (rendered)");
clip::image clipImg(pixels.data(), spec);
clip::set_image(clipImg);
} else {
spec.width = srcImg.width;
spec.height = srcImg.height;
spec.bytes_per_row = spec.width * sizeof(uint32_t);
if (srcImg.bits_per_pixel == 32) {
// BGRA pixels
IMAGE_DEBUG_PRINT("Setting clipboard image (raw)");
clip::image clipImg(srcImg.offset32, spec);
clip::set_image(clipImg);
} else {
// Indexed pixels
IMAGE_DEBUG_PRINT("Converting BGRA indexed surface to BGRA32");
std::vector<uint32_t> pixels(spec.width * spec.height); // this will hold our converted BGRA pixel data
auto p = srcImg.offset;
for (size_t i = 0; i < pixels.size(); i++) {
pixels[i] = srcImg.pal[*p];
++p;
}
IMAGE_DEBUG_PRINT("Setting clipboard image (converted)");
clip::image clipImg(pixels.data(), spec);
clip::set_image(clipImg);
}
}
}

View file

@ -1,4 +1,4 @@
//----------------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------
// ___ ___ __ _ _ ___ ___ ___ _ _ _
// / _ \| _ ) / /| | || _ \ __| |_ _|_ __ __ _ __ _ ___ | | (_) |__ _ _ __ _ _ _ _ _
// | (_) | _ \/ _ \_ _| _/ _| | || ' \/ _` / _` / -_) | |__| | '_ \ '_/ _` | '_| || |
@ -46,9 +46,6 @@
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb/stb_image_write.h"
// This is returned to the caller if something goes wrong while loading the image
#define INVALID_IMAGE_HANDLE -1
#ifdef QB64_WINDOWS
# define ZERO_VARIABLE(_v_) ZeroMemory(&(_v_), sizeof(_v_))
#else
@ -866,7 +863,7 @@ void sub__saveimage(qbs *qbsFileName, int32_t imageHandle, qbs *qbsRequirements,
// Inner codepoint rendering loop
for (auto dy = y, py = 0; py < fontHeight; dy++, py++) {
for (auto dx = x, px = 0; px < fontWidth; dx++, px++) {
pixels[width * dy + dx] = image_swap_red_blue(*builtinFont ? palette_256[fc] : palette_256[bc]);
pixels[width * dy + dx] = image_swap_red_blue(*builtinFont ? img[imageHandle].pal[fc] : img[imageHandle].pal[bc]);
++builtinFont;
}
}

View file

@ -1,26 +1,27 @@
#include "audio.h"
#include "bitops.h"
#include "clipboard.h"
#include "command.h"
#include "common.h"
#include "compression.h"
#include "command.h"
#include "datetime.h"
#include "environ.h"
#include "error_handle.h"
#include "event.h"
#include "extended_math.h"
#include "filepath.h"
#include "file-fields.h"
#include "filepath.h"
#include "filesystem.h"
#include "font.h"
#include "gui.h"
#include "hexoctbin.h"
#include "image.h"
#include "qbmath.h"
#include "qbs.h"
#include "qbs-mk-cv.h"
#include "shell.h"
#include "error_handle.h"
#include "mem.h"
#include "qbmath.h"
#include "qbs-mk-cv.h"
#include "qbs.h"
#include "rounding.h"
#include "shell.h"
extern int32 func__cinp(int32 toggle,
int32 passed); // Console INP scan code reader
@ -194,10 +195,6 @@ extern void sub__fullscreen(int32 method, int32 passed);
extern void sub__allowfullscreen(int32 method, int32 smooth);
extern int32 func__fullscreen();
extern int32 func__fullscreensmooth();
extern void sub__clipboard(qbs *);
extern qbs *func__clipboard();
extern int32 func__clipboardimage();
extern void sub__clipboardimage(int32 src);
extern int32 func__exit();
extern void revert_input_check();
extern int32 func__openhost(qbs *);