commit e87649773f91525453edae1e56d0240aed0b62d1
parent 99d5d6e8a4bee2253331e98a2276d9c645b0713e
Author: Michael Savage <mikejsavage@gmail.com>
Date: Sun, 3 May 2020 12:15:05 +0300
Add libclipboard
Diffstat:
4 files changed, 1005 insertions(+), 0 deletions(-)
diff --git a/libs/libclipboard.lua b/libs/libclipboard.lua
@@ -0,0 +1 @@
+lib( "libclipboard", { "libs/libclipboard/clipboard_x11.cpp" } )
diff --git a/libs/libclipboard/clipboard_x11.cpp b/libs/libclipboard/clipboard_x11.cpp
@@ -0,0 +1,713 @@
+/**
+ * \file clipboard_x11.c
+ * \brief X11 implementation of the clipboard.
+ *
+ * \copyright Copyright (C) 2016 Jeremy Tan.
+ * This file is released under the MIT license.
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016 Jeremy Tan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define _POSIX_C_SOURCE 199309L
+
+#include "libclipboard.h"
+
+#ifdef LIBCLIPBOARD_BUILD_X11
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/time.h>
+
+#include <xcb/xcb.h>
+#include <pthread.h>
+
+/* X11 headers are borked */
+#if defined(__MINGW32__) && defined(gettimeofday)
+# undef gettimeofday
+#endif
+
+#define VALID_MODE(x) ((x) >= LCB_CLIPBOARD && (x) < LCB_MODE_END)
+
+/**
+ * Enumeration of standard X11 atom identifiers
+ */
+typedef enum std_x_atoms {
+ /** The TARGETS atom identifier **/
+ X_ATOM_TARGETS = 0,
+ /** The MULTIPLE atom identifier **/
+ X_ATOM_MULTIPLE,
+ /** The TIMESTAMP atom identifier **/
+ X_ATOM_TIMESTAMP,
+ /** The INCR atom identifier **/
+ X_ATOM_INCR,
+ /** The CLIPBOARD atom identifier **/
+ X_ATOM_CLIPBOARD,
+ /** The UTF8_STRING atom identifier **/
+ X_ATOM_UTF8_STRING,
+ /** End marker sentinel **/
+ X_ATOM_END
+} std_x_atoms;
+
+/**
+ * Union to simplify getting interned atoms from XCB
+ */
+typedef union atom_c {
+ /** The atom **/
+ xcb_atom_t atom;
+ /** The cookie returned by xcb_intern_atom **/
+ xcb_intern_atom_cookie_t cookie;
+} atom_c;
+
+/**
+ * Contains selection data
+ */
+typedef struct selection_c {
+ /** Determines if we currently own the given selection **/
+ bool has_ownership;
+ /** The pointer to the data of the selection **/
+ unsigned char *data;
+ /** The length (in bytes) of the selection data **/
+ size_t length;
+ /** The type of data held in this selection **/
+ xcb_atom_t target;
+ /** The X11 atom for the selection mode e.g. XA_PRIMARY **/
+ xcb_atom_t xmode;
+} selection_c;
+
+/** X11 Implementation of the clipboard context **/
+struct clipboard_c {
+ /** XCB Display connection **/
+ xcb_connection_t *xc;
+ /** XCB Default screen **/
+ xcb_screen_t *xs;
+ /** Standard atoms **/
+ atom_c std_atoms[X_ATOM_END];
+ /** Our window to use for messages **/
+ xcb_window_t xw;
+ /** Action timeout (ms) **/
+ int action_timeout;
+ /** Transfer size (bytes) **/
+ uint32_t transfer_size;
+
+ /** Event loop thread **/
+ pthread_t event_loop;
+ /** Indicates true iff event_loop is initted **/
+ bool event_loop_initted;
+ /** Mutex for access to context data **/
+ pthread_mutex_t mu;
+ /** Indicates true iff mu is initted **/
+ bool mu_initted;
+ /** Condition variable to notify when action is complete **/
+ pthread_cond_t cond;
+ /** Indicates true iff cond is initted **/
+ bool cond_initted;
+
+ /** Selection data **/
+ selection_c selections[LCB_MODE_END];
+
+ /** malloc **/
+ clipboard_malloc_fn malloc;
+ /** calloc **/
+ clipboard_calloc_fn calloc;
+ /** realloc **/
+ clipboard_realloc_fn realloc;
+ /** free **/
+ clipboard_free_fn free;
+};
+
+/**
+ * The standard atom names. These values should match the
+ * std_x_atoms enumeration.
+ */
+const char *g_std_atom_names[X_ATOM_END] = {
+ "TARGETS", "MULTIPLE", "TIMESTAMP", "INCR",
+ "CLIPBOARD", "UTF8_STRING",
+};
+
+/**
+ * \brief Interns the list of atoms
+ *
+ * \param [in] xc The XCB connection.
+ * \param [out] atoms The location to store interned atoms.
+ * \param [in] atom_names The names of the atoms to intern.
+ * \param [in] number The number of atoms to intern.
+ * \return true iff all atoms were interned.
+ */
+static bool x11_intern_atoms(xcb_connection_t *xc, atom_c *atoms, const char **atom_names, int number) {
+ for (int i = 0; i < number; i++) {
+ atoms[i].cookie = xcb_intern_atom(xc, 0,
+ strlen(atom_names[i]), atom_names[i]);
+ }
+
+ for (int i = 0; i < number; i++) {
+ xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(xc,
+ atoms[i].cookie, NULL);
+ if (reply == NULL) {
+ return false;
+ }
+
+ atoms[i].atom = reply->atom;
+ free(reply); /* XCB: Do not use custom allocators */
+ }
+
+ return true;
+}
+
+/**
+ * \brief Retrieves the screen from the given connection.
+ *
+ * \param [in] xc The XCB connection.
+ * \param [in] screen The screen number to retrieve.
+ * \return The screen, or NULL on error.
+ *
+ * Based on https://xcb.freedesktop.org/xlibtoxcbtranslationguide/
+ */
+static xcb_screen_t *x11_get_screen(xcb_connection_t *xc, int screen) {
+ xcb_screen_iterator_t iter;
+
+ iter = xcb_setup_roots_iterator(xcb_get_setup(xc));
+ for (; iter.rem; screen--, xcb_screen_next(&iter)) {
+ if (screen == 0) {
+ return iter.data;
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * \brief Clears the selection data held in our cache on SelectionClear.
+ *
+ * \param [in] cb The clipboard context.
+ * \param [in] e The selection clear event.
+ */
+static void x11_clear_selection(clipboard_c *cb, xcb_selection_clear_event_t *e) {
+ if (e->owner != cb->xw) {
+ return;
+ }
+
+ for (int i = 0; i < LCB_MODE_END; i++) {
+ selection_c *sel = &cb->selections[i];
+ if (sel->xmode == e->selection && (pthread_mutex_lock(&cb->mu) == 0)) {
+ cb->free(sel->data);
+ sel->data = NULL;
+ sel->length = 0;
+ sel->has_ownership = false;
+ sel->target = XCB_NONE;
+ pthread_mutex_unlock(&cb->mu);
+ break;
+ }
+ }
+}
+
+/**
+ * \brief Retrieves the selection into our cache on SelectionNotify
+ *
+ * \param [in] cb The clipboard context.
+ * \param [in] e The selection notify event.
+ *
+ * The INCR format is not supported.
+ */
+static void x11_retrieve_selection(clipboard_c *cb, xcb_selection_notify_event_t *e) {
+ unsigned char *buf = NULL;
+ size_t bufsiz = 0, bytes_after = 1;
+ xcb_get_property_reply_t *reply = NULL;
+ xcb_atom_t actual_type;
+ uint8_t actual_format;
+
+ if (e->property != XCB_ATOM_PRIMARY && e->property != XCB_ATOM_SECONDARY && e->property != cb->std_atoms[X_ATOM_CLIPBOARD].atom) {
+ fprintf(stderr, "x11_retrieve_selection: [Warn] Unknown selection property returned: %d\n", e->property);
+ return;
+ }
+
+ while (bytes_after > 0) {
+ free(reply); /* XCB: Do not use custom allocators */
+ xcb_get_property_cookie_t ck = xcb_get_property(cb->xc, true, cb->xw,
+ e->property, XCB_ATOM_ANY,
+ bufsiz / 4, cb->transfer_size / 4);
+ reply = xcb_get_property_reply(cb->xc, ck, NULL);
+ /* reply->format should be 8, 16 or 32. */
+ if (reply == NULL || (bufsiz > 0 && (reply->format != actual_format || reply->type != actual_type)) || ((reply->format % 8) != 0)) {
+ fprintf(stderr, "x11_retrieve_selection: [Err] Invalid return value from xcb_get_property_reply\n");
+ cb->free(buf);
+ buf = NULL;
+ break;
+ }
+
+ if (bufsiz == 0) {
+ actual_type = reply->type;
+ actual_format = reply->format;
+ }
+
+ /* Todo: Check for INCR */
+
+ int nitems = xcb_get_property_value_length(reply);
+ if (nitems > 0) {
+ if ((bufsiz % 4) != 0) {
+ fprintf(stderr, "x11_retrieve_selection: [Err] Got more data but read data size is not a multiple of 4\n");
+ cb->free(buf);
+ buf = NULL;
+ break;
+ }
+
+ size_t unit_size = (reply->format / 8);
+ buf = (unsigned char *)cb->realloc(buf, unit_size * (bufsiz + nitems));
+ if (buf == NULL) {
+ fprintf(stderr, "x11_retrieve_selection: [Err] realloc failed\n");
+ break;
+ }
+
+ memcpy(buf + bufsiz, xcb_get_property_value(reply), nitems * unit_size);
+ bufsiz += nitems * unit_size;
+ }
+
+ bytes_after = reply->bytes_after;
+ }
+ free(reply); /* XCB: Do not use custom allocators */
+
+ if (buf != NULL && (pthread_mutex_lock(&cb->mu) == 0)) {
+ selection_c *sel = NULL;
+ for (int i = 0; i < LCB_MODE_END; i++) {
+ if (cb->selections[i].xmode == e->property) {
+ sel = &cb->selections[i];
+ break;
+ }
+ }
+
+ if (sel != NULL && sel->target == actual_type) {
+ cb->free(sel->data);
+ sel->data = buf;
+ sel->length = bufsiz;
+ buf = NULL;
+ } else {
+ fprintf(stderr, "x11_retrieve_selection: [Warn] Mismatched selection: actual_type=%d\n", actual_type);
+ }
+
+ pthread_cond_broadcast(&cb->cond);
+ pthread_mutex_unlock(&cb->mu);
+ }
+ cb->free(buf);
+}
+
+/**
+ * \brief Sends the selection data to the requestor on SelectionRequest
+ *
+ * \param [in] cb The clipboard context.
+ * \param [in] e The selection request event.
+ * \return true iff the data was sent (requestor's property was changed)
+ *
+ * Not currently ICCCM compliant as MULTIPLE target is unsupported. Also
+ * not compliant because we're not supplying a proper TIMESTAMP value.
+ */
+static bool x11_transmit_selection(clipboard_c *cb, xcb_selection_request_event_t *e) {
+ /* Default location to store data if none specified */
+ if (e->property == XCB_NONE) {
+ e->property = e->target;
+ }
+
+ if (e->target == cb->std_atoms[X_ATOM_TARGETS].atom) {
+ xcb_atom_t targets[] = {
+ cb->std_atoms[X_ATOM_TIMESTAMP].atom,
+ cb->std_atoms[X_ATOM_TARGETS].atom,
+ cb->std_atoms[X_ATOM_UTF8_STRING].atom
+ };
+ xcb_change_property(cb->xc, XCB_PROP_MODE_REPLACE, e->requestor,
+ e->property, XCB_ATOM_ATOM,
+ sizeof(xcb_atom_t) * 8,
+ sizeof(targets) / sizeof(xcb_atom_t), targets);
+ } else if (e->target == cb->std_atoms[X_ATOM_TIMESTAMP].atom) {
+ xcb_timestamp_t cur = XCB_CURRENT_TIME;
+ xcb_change_property(cb->xc, XCB_PROP_MODE_REPLACE, e->requestor,
+ e->property, XCB_ATOM_INTEGER, sizeof(cur) * 8,
+ 1, &cur);
+ } else if (e->target == cb->std_atoms[X_ATOM_UTF8_STRING].atom) {
+ selection_c *sel = NULL;
+ if (pthread_mutex_lock(&cb->mu) != 0) {
+ return false;
+ }
+
+ for (int i = 0; i < LCB_MODE_END; i++) {
+ if (cb->selections[i].xmode == e->selection) {
+ sel = &cb->selections[i];
+ break;
+ }
+ }
+
+ if (sel == NULL || !sel->has_ownership || sel->data == NULL || sel->target != e->target) {
+ pthread_mutex_unlock(&cb->mu);
+ return false;
+ }
+
+ xcb_change_property(cb->xc, XCB_PROP_MODE_REPLACE, e->requestor,
+ e->property, e->target, 8, sel->length, sel->data);
+ pthread_mutex_unlock(&cb->mu);
+ } else {
+ /* Unknown target */
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * \brief The main event loop to process window messages.
+ *
+ * \param [in] arg The clipboard context.
+ *
+ * This thread will run indefinitely until the clipboard context's window
+ * is destroyed. It *must* receive a DestroyNotify message to end.
+ */
+static void *x11_event_loop(void *arg) {
+ clipboard_c *cb = (clipboard_c *)arg;
+ xcb_generic_event_t *e;
+
+ while ((e = xcb_wait_for_event(cb->xc))) {
+ if (e->response_type == 0) {
+ /* I think this cast is appropriate... */
+ xcb_generic_error_t *err = (xcb_generic_error_t *) e;
+ fprintf(stderr, "x11_event_loop: [Warn] Received X11 error: %d\n", err->error_code);
+ free(e); /* XCB: Do not use custom allocators */
+ continue;
+ }
+
+ switch (e->response_type & ~0x80) {
+ case XCB_DESTROY_NOTIFY: {
+ xcb_destroy_notify_event_t *evt = (xcb_destroy_notify_event_t *)e;
+ if (evt->window == cb->xw) {
+ free(e); /* XCB: Do not use custom allocators */
+ return NULL;
+ }
+ }
+ break;
+ case XCB_SELECTION_CLEAR: {
+ x11_clear_selection(cb, (xcb_selection_clear_event_t *)e);
+ }
+ break;
+ case XCB_SELECTION_NOTIFY: {
+ x11_retrieve_selection(cb, (xcb_selection_notify_event_t *)e);
+ }
+ break;
+ case XCB_SELECTION_REQUEST: {
+ xcb_selection_request_event_t *req = (xcb_selection_request_event_t *)e;
+ xcb_selection_notify_event_t notify = {0};
+ notify.response_type = XCB_SELECTION_NOTIFY;
+ notify.time = XCB_CURRENT_TIME;
+ notify.requestor = req->requestor;
+ notify.selection = req->selection;
+ notify.target = req->target;
+ notify.property = x11_transmit_selection(cb, req) ? req->property : XCB_NONE;
+ xcb_send_event(cb->xc, false, req->requestor, XCB_EVENT_MASK_PROPERTY_CHANGE, (char *)¬ify);
+ xcb_flush(cb->xc);
+ }
+ break;
+ case XCB_PROPERTY_NOTIFY: {
+ }
+ break;
+ default: {
+ /* Ignore unknown messages */
+ }
+ }
+ free(e); /* XCB: Do not use custom allocators */
+ }
+
+ fprintf(stderr, "x11_event_loop: [Warn] xcb_wait_for_event returned NULL\n");
+ return NULL;
+}
+
+LCB_API clipboard_c *LCB_CC clipboard_new(clipboard_opts *cb_opts) {
+ clipboard_opts defaults = { };
+ defaults.x11.display_name = NULL;
+ defaults.x11.action_timeout = LCB_X11_ACTION_TIMEOUT_DEFAULT;
+ defaults.x11.transfer_size = LCB_X11_TRANSFER_SIZE_DEFAULT;
+
+ if (cb_opts == NULL) {
+ cb_opts = &defaults;
+ }
+
+ clipboard_calloc_fn calloc_fn = cb_opts->user_calloc_fn ? cb_opts->user_calloc_fn : calloc;
+ clipboard_c *cb = (clipboard_c *)calloc_fn(1, sizeof(clipboard_c));
+ if (cb == NULL) {
+ return NULL;
+ }
+ LCB_SET_ALLOCATORS(cb, cb_opts);
+
+ cb->action_timeout = cb_opts->x11.action_timeout > 0 ?
+ cb_opts->x11.action_timeout : LCB_X11_ACTION_TIMEOUT_DEFAULT;
+ /* Round down to nearest multiple of 4 */
+ cb->transfer_size = (cb_opts->x11.transfer_size / 4) * 4;
+ if (cb->transfer_size == 0) {
+ cb->transfer_size = LCB_X11_TRANSFER_SIZE_DEFAULT;
+ }
+
+ cb->mu_initted = pthread_mutex_init(&cb->mu, NULL) == 0;
+ if (!cb->mu_initted) {
+ clipboard_free(cb);
+ return NULL;
+ }
+
+ cb->cond_initted = pthread_cond_init(&cb->cond, NULL) == 0;
+ if (!cb->cond_initted) {
+ clipboard_free(cb);
+ return NULL;
+ }
+
+ int preferred_screen;
+ cb->xc = xcb_connect(cb_opts->x11.display_name, &preferred_screen);
+ assert(cb->xc != NULL); /* Docs say return is never NULL */
+ if (xcb_connection_has_error(cb->xc) != 0) {
+ clipboard_free(cb);
+ return NULL;
+ }
+ cb->xs = x11_get_screen(cb->xc, preferred_screen);
+ assert(cb->xs != NULL);
+
+ if (!x11_intern_atoms(cb->xc, cb->std_atoms, g_std_atom_names, X_ATOM_END)) {
+ clipboard_free(cb);
+ return NULL;
+ }
+
+ cb->selections[LCB_CLIPBOARD].xmode = cb->std_atoms[X_ATOM_CLIPBOARD].atom;
+ cb->selections[LCB_PRIMARY].xmode = XCB_ATOM_PRIMARY;
+ cb->selections[LCB_SECONDARY].xmode = XCB_ATOM_SECONDARY;
+
+ /* Structure notify mask to get DestroyNotify messages */
+ /* Property change mask for PropertyChange messages */
+ uint32_t event_mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE;
+ cb->xw = xcb_generate_id(cb->xc);
+ xcb_generic_error_t *err = xcb_request_check(cb->xc,
+ xcb_create_window_checked(cb->xc,
+ XCB_COPY_FROM_PARENT, cb->xw, cb->xs->root,
+ 0, 0, 10, 10, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ cb->xs->root_visual,
+ XCB_CW_EVENT_MASK, &event_mask));
+ if (err != NULL) {
+ cb->xw = 0;
+ /* Am I meant to free this? */
+ free(err); /* XCB: Do not use custom allocators */
+ clipboard_free(cb);
+ return NULL;
+ }
+
+ cb->event_loop_initted = pthread_create(&cb->event_loop, NULL,
+ x11_event_loop, (void *)cb) == 0;
+ if (!cb->event_loop_initted) {
+ clipboard_free(cb);
+ return NULL;
+ }
+
+ return cb;
+}
+
+LCB_API void LCB_CC clipboard_free(clipboard_c *cb) {
+ if (cb == NULL) {
+ return;
+ }
+
+ if (cb->event_loop_initted) {
+ /* This should send a DestroyNotify message as the termination condition */
+ xcb_destroy_window(cb->xc, cb->xw);
+ xcb_flush(cb->xc);
+ pthread_join(cb->event_loop, NULL);
+ } else if (cb->xw != 0) {
+ xcb_destroy_window(cb->xc, cb->xw);
+ }
+
+ if (cb->xc != NULL) {
+ xcb_disconnect(cb->xc);
+ }
+
+ if (cb->cond_initted) {
+ pthread_cond_destroy(&cb->cond);
+ }
+ if (cb->mu_initted) {
+ pthread_mutex_destroy(&cb->mu);
+ }
+
+ /* Free selection data */
+ for (int i = 0; i < LCB_MODE_END; i++) {
+ if (cb->selections[i].data != NULL) {
+ cb->free(cb->selections[i].data);
+ }
+ }
+
+ cb->free(cb);
+}
+
+LCB_API void LCB_CC clipboard_clear(clipboard_c *cb, clipboard_mode mode) {
+ if (cb == NULL || cb->xc == NULL) {
+ return;
+ }
+
+ xcb_atom_t sel;
+
+ switch (mode) {
+ case LCB_CLIPBOARD:
+ sel = cb->std_atoms[X_ATOM_CLIPBOARD].atom;
+ break;
+ case LCB_PRIMARY:
+ sel = XCB_ATOM_PRIMARY;
+ break;
+ case LCB_SECONDARY:
+ sel = XCB_ATOM_SECONDARY;
+ break;
+ default:
+ return;
+ }
+
+ xcb_set_selection_owner(cb->xc, XCB_NONE, sel, XCB_CURRENT_TIME);
+ xcb_flush(cb->xc);
+}
+
+LCB_API bool LCB_CC clipboard_has_ownership(clipboard_c *cb, clipboard_mode mode) {
+ bool ret = false;
+
+ if (!VALID_MODE(mode)) {
+ return false;
+ }
+
+ if (cb && (pthread_mutex_lock(&cb->mu) == 0)) {
+ ret = cb->selections[mode].has_ownership;
+ pthread_mutex_unlock(&cb->mu);
+ }
+ return ret;
+}
+
+/**
+ * \brief Copies the selection data into a newly allocated buffer
+ *
+ * \param [in] cb The clipboard context
+ * \param [in] sel The selection context
+ * \param [out] ret The return location
+ * \param [out] length The length of the returned data (optional)
+ */
+static void retrieve_text_selection(clipboard_c *cb, selection_c *sel, char **ret, int *length) {
+ if (sel->data != NULL && sel->target == cb->std_atoms[X_ATOM_UTF8_STRING].atom) {
+ *ret = (char *)cb->malloc(sizeof(char) * (sel->length + 1));
+ if (*ret != NULL) {
+ memcpy(*ret, sel->data, sel->length);
+ (*ret)[sel->length] = '\0';
+
+ if (length != NULL) {
+ *length = sel->length;
+ }
+ }
+ }
+}
+
+LCB_API char LCB_CC *clipboard_text_ex(clipboard_c *cb, int *length, clipboard_mode mode) {
+ char *ret = NULL;
+
+ if (cb == NULL || !VALID_MODE(mode)) {
+ return NULL;
+ }
+
+ if (pthread_mutex_lock(&cb->mu) == 0) {
+ selection_c *sel = &cb->selections[mode];
+ if (sel->has_ownership) {
+ retrieve_text_selection(cb, sel, &ret, length);
+ } else {
+ /* Convert selection & wait for reply */
+ struct timeval now;
+ struct timespec timeout;
+ int pret = 0;
+
+ xcb_get_selection_owner_reply_t *owner = xcb_get_selection_owner_reply(cb->xc,
+ xcb_get_selection_owner(cb->xc, sel->xmode), NULL);
+ if (owner == NULL || owner->owner == 0) {
+ /* No selection owner; no data available */
+ pthread_mutex_unlock(&cb->mu);
+ free(owner); /* XCB: Do not use custom allocators */
+ return NULL;
+ }
+ free(owner); /* XCB: Do not use custom allocators */
+
+ /* Unset any old value */
+ cb->free(sel->data);
+ sel->data = NULL;
+ sel->length = 0;
+
+ sel->target = cb->std_atoms[X_ATOM_UTF8_STRING].atom;
+ xcb_convert_selection(cb->xc, cb->xw, sel->xmode,
+ sel->target, sel->xmode, XCB_CURRENT_TIME);
+ xcb_flush(cb->xc);
+
+ /* Calculate timeout */
+ gettimeofday(&now, NULL);
+ timeout.tv_sec = now.tv_sec + (cb->action_timeout / 1000);
+ timeout.tv_nsec = (now.tv_usec * 1000UL) + ((cb->action_timeout % 1000) * 1000000UL);
+ if (timeout.tv_nsec >= 1000000000UL) {
+ timeout.tv_sec += timeout.tv_nsec / 1000000000UL;
+ timeout.tv_nsec = timeout.tv_nsec % 1000000000UL;
+ }
+
+ while (pret == 0 && sel->data == NULL) {
+ pret = pthread_cond_timedwait(&cb->cond, &cb->mu, &timeout);
+ }
+
+ retrieve_text_selection(cb, sel, &ret, length);
+ }
+
+ pthread_mutex_unlock(&cb->mu);
+ }
+
+ return ret;
+}
+
+LCB_API bool LCB_CC clipboard_set_text_ex(clipboard_c *cb, const char *src, int length, clipboard_mode mode) {
+ bool ret = false;
+
+ if (cb == NULL || src == NULL || length == 0 || !VALID_MODE(mode)) {
+ return false;
+ }
+
+ if (pthread_mutex_lock(&cb->mu) == 0) {
+ selection_c *sel = &cb->selections[mode];
+ if (sel->data != NULL) {
+ cb->free(sel->data);
+ }
+ if (length < 0) {
+ length = strlen(src);
+ }
+
+ sel->data = (unsigned char *)cb->malloc(sizeof(char) * (length + 1));
+ if (sel->data != NULL) {
+ memcpy(sel->data, src, length);
+ sel->data[length] = '\0';
+ sel->length = length;
+ sel->has_ownership = true;
+ sel->target = cb->std_atoms[X_ATOM_UTF8_STRING].atom;
+ xcb_set_selection_owner(cb->xc, cb->xw, sel->xmode, XCB_CURRENT_TIME);
+ xcb_flush(cb->xc);
+ ret = true;
+ }
+
+ pthread_mutex_unlock(&cb->mu);
+ }
+
+ return ret;
+}
+
+#endif /* LIBCLIPBOARD_BUILD_X11 */
diff --git a/libs/libclipboard/libclipboard-config.h b/libs/libclipboard/libclipboard-config.h
@@ -0,0 +1,36 @@
+/**
+ * \file libclipboard-config.h
+ * \brief Configuration specific options
+ *
+ * \copyright Copyright (C) 2016 Jeremy Tan.
+ * This file is released under the MIT license.
+ * See LICENSE for details.
+ *
+ * \details NOTE: This file is automatically generated.
+ */
+
+#ifndef _LIBCLIPBOARD_CONFIG_H
+#define _LIBCLIPBOARD_CONFIG_H
+
+/** Major version of libclipboard **/
+#define LIBCLIPBOARD_VERSION_MAJOR 1
+
+/** Minor version of libclipboard **/
+#define LIBCLIPBOARD_VERSION_MINOR 0
+
+/** Are we building the Win32 backend? **/
+/* #undef LIBCLIPBOARD_BUILD_WIN32 */
+
+/** Are we building the X11 backend? **/
+#define LIBCLIPBOARD_BUILD_X11
+
+/** Are we building the Cocoa (OS X) backend? **/
+/* #undef LIBCLIPBOARD_BUILD_COCOA */
+
+/** Are we building a shared library? **/
+/* #undef LIBCLIPBOARD_BUILD_SHARED */
+
+/** Are we using the stdcall calling convention? **/
+/* #undef LIBCLIPBOARD_USE_STDCALL */
+
+#endif /* _LIBCLIPBOARD_CONFIG_H */
diff --git a/libs/libclipboard/libclipboard.h b/libs/libclipboard/libclipboard.h
@@ -0,0 +1,255 @@
+/**
+ * \file libclipboard.h
+ * \brief Main header file
+ *
+ * \copyright Copyright (C) 2016 Jeremy Tan.
+ * This file is released under the MIT license.
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016 Jeremy Tan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _LIBCLIPBOARD_H_
+#define _LIBCLIPBOARD_H_
+
+#include "libclipboard-config.h"
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/* Symbol exports and calling convention logic */
+#if defined(_WIN32) || defined(__CYGWIN__)
+# define LIBCLIPBOARD_DLL_IMPORT __declspec(dllimport)
+# define LIBCLIPBOARD_DLL_EXPORT __declspec(dllexport)
+# define LIBCLIPBOARD_DLL_LOCAL
+# if defined(LIBCLIPBOARD_USE_STDCALL) && !defined(_WIN64)
+# define LCB_CC __stdcall
+# else
+# define LCB_CC
+# endif
+#else
+# if __GNUC__ >= 4
+# define LIBCLIPBOARD_DLL_IMPORT __attribute__ ((visibility ("default")))
+# define LIBCLIPBOARD_DLL_EXPORT __attribute__ ((visibility ("default")))
+# define LIBCLIPBOARD_DLL_LOCAL __attribute__ ((visibility ("hidden")))
+# else
+# define LIBCLIPBOARD_DLL_IMPORT
+# define LIBCLIPBOARD_DLL_EXPORT
+# define LIBCLIPBOARD_DLL_LOCAL
+# endif
+# define LCB_CC
+#endif
+
+#ifdef LIBCLIPBOARD_BUILD_SHARED
+# ifdef clipboard_EXPORTS /* Defined by CMake when we're compiling the shared library */
+# define LCB_API LIBCLIPBOARD_DLL_EXPORT
+# else
+# define LCB_API LIBCLIPBOARD_DLL_IMPORT
+# endif
+# define LCB_LOCAL LIBCLIPBOARD_DLL_LOCAL
+#else
+# define LCB_API
+# define LCB_LOCAL
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Default action timeout of 1500ms **/
+#define LCB_X11_ACTION_TIMEOUT_DEFAULT 1500
+/** Default transfer size (X11 only), default 1MB (must be multiple of 4) **/
+#define LCB_X11_TRANSFER_SIZE_DEFAULT 1048576
+/** Default max number of retries to try to obtain clipboard lock **/
+#define LCB_WIN32_MAX_RETRIES_DEFAULT 5
+/** Default delay in ms between retries to obtain clipboard lock **/
+#define LCB_WIN32_RETRY_DELAY_DEFAULT 5
+
+/**
+ * \brief For internal use only. Initialises custom allocators.
+ *
+ * \param [out] cb Clipboard context
+ * \param [in] opts Clipboard options
+ */
+#define LCB_SET_ALLOCATORS(cb, opts) do { \
+ (cb)->malloc = (opts) && (opts)->user_malloc_fn ? (opts)->user_malloc_fn : malloc; \
+ (cb)->calloc = (opts) && (opts)->user_calloc_fn ? (opts)->user_calloc_fn : calloc; \
+ (cb)->realloc = (opts) && (opts)->user_realloc_fn ? (opts)->user_realloc_fn : realloc; \
+ (cb)->free = (opts) && (opts)->user_free_fn ? (opts)->user_free_fn : free; \
+} while(0)
+
+/** Custom malloc function signature **/
+typedef void *(*clipboard_malloc_fn)(size_t size);
+/** Custom calloc function signature **/
+typedef void *(*clipboard_calloc_fn)(size_t nmemb, size_t size);
+/** Custom realloc function signature **/
+typedef void *(*clipboard_realloc_fn)(void *ptr, size_t size);
+/** Custom free function signature **/
+typedef void (*clipboard_free_fn)(void *ptr);
+
+/**
+ * Determines which clipboard is used in called functions.
+ */
+typedef enum clipboard_mode {
+ /** The primary (global) clipboard **/
+ LCB_CLIPBOARD = 0,
+ /** The (global) mouse selection clipboard **/
+ LCB_PRIMARY,
+ /** The (global) mouse selection clipboard; for backwards compatibility **/
+ LCB_SELECTION = LCB_PRIMARY,
+ /** The largely unused (global) secondary selection clipboard **/
+ LCB_SECONDARY,
+ /** Sentinel value for end of clipboard modes **/
+ LCB_MODE_END
+} clipboard_mode;
+
+/**
+ * Options to be passed on instantiation.
+ */
+typedef struct clipboard_opts {
+ /* I would put the OS specific opts in a union, but anonymous unions are non-standard */
+ /* Typing out union names is too much effort */
+
+ /** X11 specific options **/
+ struct clipboard_opts_x11 {
+ /** Max time (ms) to wait for action to complete **/
+ int action_timeout;
+ /** Transfer size, in bytes. Must be a multiple of 4. **/
+ uint32_t transfer_size;
+ /** The name of the X11 display (NULL for default - DISPLAY env. var.) **/
+ const char *display_name;
+ } x11;
+
+ /** Win32 specific options **/
+ struct clipboard_opts_win32 {
+ /**
+ * Max number of retries to try to obtain clipboard lock.
+ * If max_retries is zero, the default value will be used.
+ * Specify a negative value for zero retries.
+ */
+ int max_retries;
+ /**
+ * Delay in ms between retries to obtain clipboard lock.
+ * If retry_delay is zero, the default value will be used.
+ * Specify a negative value for no (zero) delay.
+ */
+ int retry_delay;
+ } win32;
+
+ /** User specified malloc (NULL for default) **/
+ clipboard_malloc_fn user_malloc_fn;
+ /** User specified calloc (NULL for default) **/
+ clipboard_calloc_fn user_calloc_fn;
+ /** User specified realloc (NULL for default) **/
+ clipboard_realloc_fn user_realloc_fn;
+ /** User specified free (NULL for default) **/
+ clipboard_free_fn user_free_fn;
+} clipboard_opts;
+
+/** Opaque data structure for a clipboard context/instance **/
+typedef struct clipboard_c clipboard_c;
+
+/**
+ * \brief Instantiates a new clipboard instance of the given type.
+ *
+ * \param [in] cb_opts Implementation specific options (optional).
+ * \return The new clipboard instance, or NULL on failure.
+ */
+LCB_API clipboard_c *LCB_CC clipboard_new(clipboard_opts *cb_opts);
+
+/**
+ * \brief Frees associated clipboard data from the provided structure.
+ *
+ * \param [in] cb The clipboard to be freed.
+ */
+LCB_API void LCB_CC clipboard_free(clipboard_c *cb);
+
+/**
+ * \brief Clears the contents of the given clipboard.
+ *
+ * \param [in] cb The clipboard to clear.
+ * \param [in] mode Which clipboard to clear (platform dependent)
+ */
+LCB_API void LCB_CC clipboard_clear(clipboard_c *cb, clipboard_mode mode);
+
+/**
+ * \brief Determines if the clipboard is currently owned
+ *
+ * \param [in] cb The clipboard to check
+ * \param [in] mode Which clipboard to clear (platform dependent)
+ * \return true iff the clipboard data is owned by the provided instance.
+ */
+LCB_API bool LCB_CC clipboard_has_ownership(clipboard_c *cb, clipboard_mode mode);
+
+/**
+ * \brief Retrieves the text currently held on the clipboard.
+ *
+ * \param [in] cb The clipboard to retrieve from
+ * \param [out] length Returns the length of the retrieved data, excluding
+ * the NULL terminator (optional).
+ * \param [in] mode Which clipboard to clear (platform dependent)
+ * \return A copy to the retrieved text. This must be free()'d by the user.
+ * Note that the text is encoded in UTF-8 format.
+ */
+LCB_API char *LCB_CC clipboard_text_ex(clipboard_c *cb, int *length, clipboard_mode mode);
+
+/**
+ * \brief Simplified version of clipboard_text_ex
+ *
+ * \param [in] cb The clipboard to retrieve from
+ * \return As per clipboard_text_ex.
+ *
+ * \details This function assumes LCB_CLIPBOARD as the clipboard mode.
+ */
+LCB_API char *LCB_CC clipboard_text(clipboard_c *cb);
+
+/**
+ * \brief Sets the text for the provided clipboard.
+ *
+ * \param [in] cb The clipboard to set the text.
+ * \param [in] src The UTF-8 encoded text to be set in the clipboard.
+ * \param [in] length The length of text to be set (excluding the NULL
+ * terminator).
+ * \param [in] mode Which clipboard to clear (platform dependent)
+ * \return true iff the clipboard was set (false on error)
+ *
+ * \details If the length parameter is -1, src is treated as a NULL-terminated
+ * string and its length will be determined automatically.
+ */
+LCB_API bool LCB_CC clipboard_set_text_ex(clipboard_c *cb, const char *src, int length, clipboard_mode mode);
+
+/**
+ * \brief Simplified version of clipboard_set_text_ex
+ *
+ * \param [in] cb The clipboard to set the text.
+ * \param [in] src The UTF-8 encoded NULL terminated string to be set.
+ * \return true iff the clipboard was set (false on error)
+ *
+ * \details This function assumes LCB_CLIPBOARD as the clipboard mode.
+ */
+LCB_API bool LCB_CC clipboard_set_text(clipboard_c *cb, const char *src);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBCLIPBOARD_H_ */