aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2019-08-17 20:50:20 +0200
committerDavid Robillard <d@drobilla.net>2019-09-07 09:51:48 +0200
commit7162fa4f5656ad7dfe2d6fea02f9f33c5aa1b1cf (patch)
tree0f885354b2d75875003401379d5aaf9aadda226f
parent27e43183d89aad98f6000ee187b05547776ae4c2 (diff)
downloadpugl-7162fa4f5656ad7dfe2d6fea02f9f33c5aa1b1cf.tar.gz
pugl-7162fa4f5656ad7dfe2d6fea02f9f33c5aa1b1cf.tar.bz2
pugl-7162fa4f5656ad7dfe2d6fea02f9f33c5aa1b1cf.zip
Add clipboard support
-rw-r--r--pugl/detail/implementation.c46
-rw-r--r--pugl/detail/implementation.h15
-rw-r--r--pugl/detail/mac.m40
-rw-r--r--pugl/detail/types.h7
-rw-r--r--pugl/detail/win.c82
-rw-r--r--pugl/detail/x11.c103
-rw-r--r--pugl/detail/x11.h1
-rw-r--r--pugl/pugl.h27
-rw-r--r--test/pugl_test.c14
9 files changed, 332 insertions, 3 deletions
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 <stddef.h>
#include <stdint.h>
#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 <X11/X.h>
+#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
@@ -35,6 +36,7 @@
#include <sys/select.h>
#include <sys/time.h>
+#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
@@ -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*)&note);
+ 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 <X11/Xutil.h>
typedef struct {
+ Atom CLIPBOARD;
Atom UTF8_STRING;
Atom WM_PROTOCOLS;
Atom WM_DELETE_WINDOW;
diff --git a/pugl/pugl.h b/pugl/pugl.h
index 4182225..580e9dc 100644
--- a/pugl/pugl.h
+++ b/pugl/pugl.h
@@ -22,6 +22,7 @@
#define PUGL_H_INCLUDED
#include <stdbool.h>
+#include <stddef.h>
#include <stdint.h>
#ifdef PUGL_SHARED
@@ -85,6 +86,7 @@ typedef enum {
PUGL_ERR_CREATE_WINDOW,
PUGL_ERR_SET_FORMAT,
PUGL_ERR_CREATE_CONTEXT,
+ PUGL_ERR_UNSUPPORTED_TYPE,
} PuglStatus;
/**
@@ -761,6 +763,31 @@ PUGL_API PuglStatus
puglGrabFocus(PuglView* view);
/**
+ Get clipboard contents.
+
+ @param view The view.
+ @param[out] type Set to the MIME type of the data.
+ @param[out] len Set to the length of the data in bytes.
+ @return The clipboard contents.
+*/
+PUGL_API const void*
+puglGetClipboard(PuglView* view, const char** type, size_t* len);
+
+/**
+ Set clipboard contents.
+
+ @param view The view.
+ @param type The MIME type of the data, "text/plain" is assumed if NULL.
+ @param data The data to copy to the clipboard.
+ @param len The length of data in bytes (including terminator if necessary).
+*/
+PUGL_API PuglStatus
+puglSetClipboard(PuglView* view,
+ const char* type,
+ const void* data,
+ size_t len);
+
+/**
Request user attention.
This hints to the system that the window or application requires attention
diff --git a/test/pugl_test.c b/test/pugl_test.c
index ee58cf8..4bd5f80 100644
--- a/test/pugl_test.c
+++ b/test/pugl_test.c
@@ -147,7 +147,7 @@ swapFocus(PuglTestApp* app)
}
static void
-onKeyPress(PuglView* view, const PuglEventKey* event)
+onKeyPress(PuglView* view, const PuglEventKey* event, const char* prefix)
{
PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
PuglRect frame = puglGetFrame(view);
@@ -156,6 +156,14 @@ onKeyPress(PuglView* view, const PuglEventKey* event)
swapFocus(app);
} else if (event->key == 'q' || event->key == PUGL_KEY_ESCAPE) {
app->quit = 1;
+ } else if (event->state & PUGL_MOD_CTRL && event->key == 'c') {
+ puglSetClipboard(view, NULL, "Pugl test", strlen("Pugl test") + 1);
+ fprintf(stderr, "%sCopy \"Pugl test\"\n", prefix);
+ } else if (event->state & PUGL_MOD_CTRL && event->key == 'v') {
+ const char* type = NULL;
+ size_t len = 0;
+ const char* text = (const char*)puglGetClipboard(view, &type, &len);
+ fprintf(stderr, "%sPaste \"%s\"\n", prefix, text);
} else if (event->state & PUGL_MOD_SHIFT) {
if (event->key == PUGL_KEY_UP) {
frame.height += 10;
@@ -220,7 +228,7 @@ onParentEvent(PuglView* view, const PuglEvent* event)
}
break;
case PUGL_KEY_PRESS:
- onKeyPress(view, &event->key);
+ onKeyPress(view, &event->key, "Parent: ");
break;
case PUGL_MOTION_NOTIFY:
break;
@@ -252,7 +260,7 @@ onEvent(PuglView* view, const PuglEvent* event)
app->quit = 1;
break;
case PUGL_KEY_PRESS:
- onKeyPress(view, &event->key);
+ onKeyPress(view, &event->key, "Child: ");
break;
case PUGL_MOTION_NOTIFY:
app->xAngle = fmodf(app->xAngle - (float)(event->motion.x - app->lastMouseX), 360.0f);