From 7162fa4f5656ad7dfe2d6fea02f9f33c5aa1b1cf Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 17 Aug 2019 20:50:20 +0200 Subject: Add clipboard support --- pugl/detail/implementation.c | 46 +++++++++++++++++++ pugl/detail/implementation.h | 15 +++++++ pugl/detail/mac.m | 40 +++++++++++++++++ pugl/detail/types.h | 7 +++ pugl/detail/win.c | 82 ++++++++++++++++++++++++++++++++++ pugl/detail/x11.c | 103 +++++++++++++++++++++++++++++++++++++++++++ pugl/detail/x11.h | 1 + 7 files changed, 294 insertions(+) (limited to 'pugl/detail') diff --git a/pugl/detail/implementation.c b/pugl/detail/implementation.c index 6ff71e2..f1fd57a 100644 --- a/pugl/detail/implementation.c +++ b/pugl/detail/implementation.c @@ -34,6 +34,20 @@ puglSetString(char** dest, const char* string) strncpy(*dest, string, len + 1); } +void +puglSetBlob(PuglBlob* const dest, const void* const data, const size_t len) +{ + if (data) { + dest->len = len; + dest->data = realloc(dest->data, len + 1); + memcpy(dest->data, data, len); + ((char*)dest->data)[len] = 0; + } else { + dest->len = 0; + dest->data = NULL; + } +} + static void puglSetDefaultHints(PuglHints hints) { @@ -126,6 +140,7 @@ puglFreeView(PuglView* view) } } + free(view->clipboard.data); puglFreeViewInternals(view); free(view); } @@ -267,3 +282,34 @@ puglDispatchEvent(PuglView* view, const PuglEvent* event) view->eventFunc(view, event); } } + +const void* +puglGetInternalClipboard(const PuglView* const view, + const char** const type, + size_t* const len) +{ + if (len) { + *len = view->clipboard.len; + } + + if (type) { + *type = "text/plain"; + } + + return view->clipboard.data; +} + +PuglStatus +puglSetInternalClipboard(PuglView* const view, + const char* const type, + const void* const data, + const size_t len) +{ + if (type && strcmp(type, "text/plain")) { + return PUGL_ERR_UNSUPPORTED_TYPE; + } + + puglSetBlob(&view->clipboard, data, len); + return PUGL_SUCCESS; +} + diff --git a/pugl/detail/implementation.h b/pugl/detail/implementation.h index 7f50ecf..874e7e1 100644 --- a/pugl/detail/implementation.h +++ b/pugl/detail/implementation.h @@ -24,12 +24,16 @@ #include "pugl/detail/types.h" #include "pugl/pugl.h" +#include #include #ifdef __cplusplus extern "C" { #endif +/** Set `blob` to `data` with length `len`, reallocating if necessary. */ +void puglSetBlob(PuglBlob* blob, const void* data, size_t len); + /** Reallocate and set `*dest` to `string`. */ void puglSetString(char** dest, const char* string); @@ -51,6 +55,17 @@ uint32_t puglDecodeUTF8(const uint8_t* buf); /** Dispatch `event` to `view`, optimising configure/expose if possible. */ void puglDispatchEvent(PuglView* view, const PuglEvent* event); +/** Set internal (stored in view) clipboard contents. */ +const void* +puglGetInternalClipboard(const PuglView* view, const char** type, size_t* len); + +/** Set internal (stored in view) clipboard contents. */ +PuglStatus +puglSetInternalClipboard(PuglView* view, + const char* type, + const void* data, + size_t len); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/pugl/detail/mac.m b/pugl/detail/mac.m index 8570709..ab13452 100644 --- a/pugl/detail/mac.m +++ b/pugl/detail/mac.m @@ -1024,3 +1024,43 @@ puglSetAspectRatio(PuglView* const view, return PUGL_SUCCESS; } + +const void* +puglGetClipboard(PuglView* const view, + const char** const type, + size_t* const len) +{ + NSPasteboard* const pasteboard = [NSPasteboard generalPasteboard]; + + if ([[pasteboard types] containsObject:NSStringPboardType]) { + const NSString* str = [pasteboard stringForType:NSStringPboardType]; + const char* utf8 = [str UTF8String]; + + puglSetBlob(&view->clipboard, utf8, strlen(utf8) + 1); + } + + return puglGetInternalClipboard(view, type, len); +} + +PuglStatus +puglSetClipboard(PuglView* const view, + const char* const type, + const void* const data, + const size_t len) +{ + NSPasteboard* const pasteboard = [NSPasteboard generalPasteboard]; + const char* const str = (const char*)data; + + PuglStatus st = puglSetInternalClipboard(view, type, data, len); + if (st) { + return st; + } + + [pasteboard declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] + owner:nil]; + + [pasteboard setString:[NSString stringWithUTF8String:str] + forType:NSStringPboardType]; + + return PUGL_SUCCESS; +} diff --git a/pugl/detail/types.h b/pugl/detail/types.h index 7c11f42..4ac224b 100644 --- a/pugl/detail/types.h +++ b/pugl/detail/types.h @@ -45,6 +45,12 @@ typedef struct PuglInternalsImpl PuglInternals; /** View hints. */ typedef int PuglHints[PUGL_NUM_WINDOW_HINTS]; +/** Blob of arbitrary data. */ +typedef struct { + void* data; //!< Dynamically allocated data + size_t len; //!< Length of data in bytes +} PuglBlob; + /** Cross-platform view definition. */ struct PuglViewImpl { PuglWorld* world; @@ -53,6 +59,7 @@ struct PuglViewImpl { PuglHandle handle; PuglEventFunc eventFunc; char* title; + PuglBlob clipboard; PuglNativeWindow parent; uintptr_t transientParent; PuglHints hints; diff --git a/pugl/detail/win.c b/pugl/detail/win.c index 9ed1e5d..25780e0 100644 --- a/pugl/detail/win.c +++ b/pugl/detail/win.c @@ -67,6 +67,20 @@ puglUtf8ToWideChar(const char* const utf8) return NULL; } +static char* +puglWideCharToUtf8(const wchar_t* const wstr, size_t* len) +{ + int n = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); + if (n > 0) { + char* result = (char*)calloc((size_t)n, sizeof(char)); + WideCharToMultiByte(CP_UTF8, 0, wstr, -1, result, n, NULL, NULL); + *len = (size_t)n; + return result; + } + + return NULL; +} + static bool puglRegisterWindowClass(const char* name) { @@ -842,3 +856,71 @@ puglSetAspectRatio(PuglView* const view, view->maxAspectY = maxY; return PUGL_SUCCESS; } + +const void* +puglGetClipboard(PuglView* const view, + const char** const type, + size_t* const len) +{ + PuglInternals* const impl = view->impl; + + if (!IsClipboardFormatAvailable(CF_UNICODETEXT) || + !OpenClipboard(impl->hwnd)) { + return NULL; + } + + HGLOBAL mem = GetClipboardData(CF_UNICODETEXT); + wchar_t* wstr = mem ? (wchar_t*)GlobalLock(mem) : NULL; + if (!wstr) { + CloseClipboard(); + return NULL; + } + + free(view->clipboard.data); + view->clipboard.data = puglWideCharToUtf8(wstr, &view->clipboard.len); + GlobalUnlock(mem); + CloseClipboard(); + + return puglGetInternalClipboard(view, type, len); +} + +PuglStatus +puglSetClipboard(PuglView* const view, + const char* const type, + const void* const data, + const size_t len) +{ + PuglInternals* const impl = view->impl; + + PuglStatus st = puglSetInternalClipboard(view, type, data, len); + if (st) { + return st; + } else if (!OpenClipboard(impl->hwnd)) { + return PUGL_ERR_UNKNOWN; + } + + // Measure string and allocate global memory for clipboard + const char* str = (const char*)data; + const int wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (wlen + 1) * sizeof(wchar_t)); + if (!mem) { + CloseClipboard(); + return PUGL_ERR_UNKNOWN; + } + + // Lock global memory + wchar_t* wstr = (wchar_t*)GlobalLock(mem); + if (!wstr) { + GlobalFree(mem); + CloseClipboard(); + return PUGL_ERR_UNKNOWN; + } + + // Convert string into global memory and set it as clipboard data + MultiByteToWideChar(CP_UTF8, 0, str, (int)len, wstr, wlen); + wstr[wlen] = 0; + GlobalUnlock(mem); + SetClipboardData(CF_UNICODETEXT, mem); + CloseClipboard(); + return PUGL_SUCCESS; +} diff --git a/pugl/detail/x11.c b/pugl/detail/x11.c index 43cfb18..adf8c9d 100644 --- a/pugl/detail/x11.c +++ b/pugl/detail/x11.c @@ -28,6 +28,7 @@ #include "pugl/pugl.h" #include +#include #include #include #include @@ -35,6 +36,7 @@ #include #include +#include #include #include #include @@ -75,6 +77,7 @@ puglInitWorldInternals(void) impl->display = display; // Intern the various atoms we will need + impl->atoms.CLIPBOARD = XInternAtom(display, "CLIPBOARD", 0); impl->atoms.UTF8_STRING = XInternAtom(display, "UTF8_STRING", 0); impl->atoms.WM_PROTOCOLS = XInternAtom(display, "WM_PROTOCOLS", 0); impl->atoms.WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", 0); @@ -600,6 +603,8 @@ sendRedisplayEvent(PuglView* view) PUGL_API PuglStatus puglDispatchEvents(PuglWorld* world) { + const PuglX11Atoms* const atoms = &world->impl->atoms; + // Send expose events for any views with pending redisplays for (size_t i = 0; i < world->numViews; ++i) { if (world->views[i]->redisplay) { @@ -636,6 +641,55 @@ puglDispatchEvents(PuglWorld* world) XSetICFocus(impl->xic); } else if (xevent.type == FocusOut) { XUnsetICFocus(impl->xic); + } else if (xevent.type == SelectionClear) { + puglSetBlob(&view->clipboard, NULL, 0); + continue; + } else if (xevent.type == SelectionNotify && + xevent.xselection.selection == atoms->CLIPBOARD && + xevent.xselection.target == atoms->UTF8_STRING && + xevent.xselection.property == XA_PRIMARY) { + + uint8_t* str = NULL; + Atom type = 0; + int fmt = 0; + unsigned long len = 0; + unsigned long left = 0; + XGetWindowProperty(impl->display, impl->win, XA_PRIMARY, + 0, 8, False, AnyPropertyType, + &type, &fmt, &len, &left, &str); + + if (str && fmt == 8 && type == atoms->UTF8_STRING && left == 0) { + puglSetBlob(&view->clipboard, str, len); + } + + XFree(str); + continue; + } else if (xevent.type == SelectionRequest) { + const XSelectionRequestEvent* request = &xevent.xselectionrequest; + + XSelectionEvent note = {0}; + note.type = SelectionNotify; + note.requestor = request->requestor; + note.selection = request->selection; + note.target = request->target; + note.time = request->time; + + const char* type = NULL; + size_t len = 0; + const void* data = puglGetInternalClipboard(view, &type, &len); + if (data && + request->selection == atoms->CLIPBOARD && + request->target == atoms->UTF8_STRING) { + note.property = request->property; + XChangeProperty(impl->display, note.requestor, + note.property, note.target, 8, PropModeReplace, + (const uint8_t*)data, len); + } else { + note.property = None; + } + + XSendEvent(impl->display, note.requestor, True, 0, (XEvent*)¬e); + continue; } // Translate X11 event to Pugl event @@ -797,3 +851,52 @@ puglSetTransientFor(PuglView* view, PuglNativeWindow parent) return PUGL_SUCCESS; } + +const void* +puglGetClipboard(PuglView* const view, + const char** const type, + size_t* const len) +{ + PuglInternals* const impl = view->impl; + const PuglX11Atoms* const atoms = &view->world->impl->atoms; + + const Window owner = XGetSelectionOwner(impl->display, atoms->CLIPBOARD); + if (owner != None && owner != impl->win) { + // Clear internal selection + puglSetBlob(&view->clipboard, NULL, 0); + + // Request selection from the owner + XConvertSelection(impl->display, + atoms->CLIPBOARD, + atoms->UTF8_STRING, + XA_PRIMARY, + impl->win, + CurrentTime); + + // Run event loop until data is received + while (!view->clipboard.data) { + puglPollEvents(view->world, -1); + puglDispatchEvents(view->world); + } + } + + return puglGetInternalClipboard(view, type, len); +} + +PuglStatus +puglSetClipboard(PuglView* const view, + const char* const type, + const void* const data, + const size_t len) +{ + PuglInternals* const impl = view->impl; + const PuglX11Atoms* const atoms = &view->world->impl->atoms; + + PuglStatus st = puglSetInternalClipboard(view, type, data, len); + if (st) { + return st; + } + + XSetSelectionOwner(impl->display, atoms->CLIPBOARD, impl->win, CurrentTime); + return PUGL_SUCCESS; +} diff --git a/pugl/detail/x11.h b/pugl/detail/x11.h index be5f3e0..bfdbf60 100644 --- a/pugl/detail/x11.h +++ b/pugl/detail/x11.h @@ -24,6 +24,7 @@ #include typedef struct { + Atom CLIPBOARD; Atom UTF8_STRING; Atom WM_PROTOCOLS; Atom WM_DELETE_WINDOW; -- cgit v1.2.1