mudgangster

Tiny, scriptable MUD client
Log | Files | Refs | README

clipboard_x11.cpp (23400B)


      1 /**
      2  *  \file clipboard_x11.c
      3  *  \brief X11 implementation of the clipboard.
      4  *
      5  *  \copyright Copyright (C) 2016 Jeremy Tan.
      6  *             This file is released under the MIT license.
      7  *
      8  *  The MIT License (MIT)
      9  *
     10  *  Copyright (c) 2016 Jeremy Tan
     11  *
     12  *  Permission is hereby granted, free of charge, to any person obtaining a copy
     13  *  of this software and associated documentation files (the "Software"), to deal
     14  *  in the Software without restriction, including without limitation the rights
     15  *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     16  *  copies of the Software, and to permit persons to whom the Software is
     17  *  furnished to do so, subject to the following conditions:
     18  *
     19  *  The above copyright notice and this permission notice shall be included in all
     20  *  copies or substantial portions of the Software.
     21  *
     22  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     23  *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     24  *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     25  *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     26  *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     27  *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     28  *  SOFTWARE.
     29  */
     30 
     31 #define _POSIX_C_SOURCE 199309L
     32 
     33 #include "libclipboard.h"
     34 
     35 #ifdef LIBCLIPBOARD_BUILD_X11
     36 
     37 #include <assert.h>
     38 #include <stdlib.h>
     39 #include <string.h>
     40 #include <stdint.h>
     41 #include <stdio.h>
     42 #include <sys/time.h>
     43 
     44 #include <xcb/xcb.h>
     45 #include <pthread.h>
     46 
     47 /* X11 headers are borked */
     48 #if defined(__MINGW32__) && defined(gettimeofday)
     49 #  undef gettimeofday
     50 #endif
     51 
     52 #define VALID_MODE(x) ((x) >= LCB_CLIPBOARD && (x) < LCB_MODE_END)
     53 
     54 /**
     55  *  Enumeration of standard X11 atom identifiers
     56  */
     57 typedef enum std_x_atoms {
     58     /** The TARGETS atom identifier **/
     59     X_ATOM_TARGETS = 0,
     60     /** The MULTIPLE atom identifier **/
     61     X_ATOM_MULTIPLE,
     62     /** The TIMESTAMP atom identifier **/
     63     X_ATOM_TIMESTAMP,
     64     /** The INCR atom identifier **/
     65     X_ATOM_INCR,
     66     /** The CLIPBOARD atom identifier **/
     67     X_ATOM_CLIPBOARD,
     68     /** The UTF8_STRING atom identifier **/
     69     X_ATOM_UTF8_STRING,
     70     /** End marker sentinel **/
     71     X_ATOM_END
     72 } std_x_atoms;
     73 
     74 /**
     75  *  Union to simplify getting interned atoms from XCB
     76  */
     77 typedef union atom_c {
     78     /** The atom **/
     79     xcb_atom_t atom;
     80     /** The cookie returned by xcb_intern_atom **/
     81     xcb_intern_atom_cookie_t cookie;
     82 } atom_c;
     83 
     84 /**
     85  *  Contains selection data
     86  */
     87 typedef struct selection_c {
     88     /** Determines if we currently own the given selection **/
     89     bool has_ownership;
     90     /** The pointer to the data of the selection **/
     91     unsigned char *data;
     92     /** The length (in bytes) of the selection data **/
     93     size_t length;
     94     /** The type of data held in this selection **/
     95     xcb_atom_t target;
     96     /** The X11 atom for the selection mode e.g. XA_PRIMARY **/
     97     xcb_atom_t xmode;
     98 } selection_c;
     99 
    100 /** X11 Implementation of the clipboard context **/
    101 struct clipboard_c {
    102     /** XCB Display connection **/
    103     xcb_connection_t *xc;
    104     /** XCB Default screen **/
    105     xcb_screen_t *xs;
    106     /** Standard atoms **/
    107     atom_c std_atoms[X_ATOM_END];
    108     /** Our window to use for messages **/
    109     xcb_window_t xw;
    110     /** Action timeout (ms) **/
    111     int action_timeout;
    112     /** Transfer size (bytes) **/
    113     uint32_t transfer_size;
    114 
    115     /** Event loop thread **/
    116     pthread_t event_loop;
    117     /** Indicates true iff event_loop is initted **/
    118     bool event_loop_initted;
    119     /** Mutex for access to context data **/
    120     pthread_mutex_t mu;
    121     /** Indicates true iff mu is initted **/
    122     bool mu_initted;
    123     /** Condition variable to notify when action is complete **/
    124     pthread_cond_t cond;
    125     /** Indicates true iff cond is initted **/
    126     bool cond_initted;
    127 
    128     /** Selection data **/
    129     selection_c selections[LCB_MODE_END];
    130 
    131     /** malloc **/
    132     clipboard_malloc_fn malloc;
    133     /** calloc **/
    134     clipboard_calloc_fn calloc;
    135     /** realloc **/
    136     clipboard_realloc_fn realloc;
    137     /** free **/
    138     clipboard_free_fn free;
    139 };
    140 
    141 /**
    142  *  The standard atom names. These values should match the
    143  *  std_x_atoms enumeration.
    144  */
    145 const char *g_std_atom_names[X_ATOM_END] = {
    146     "TARGETS", "MULTIPLE", "TIMESTAMP", "INCR",
    147     "CLIPBOARD", "UTF8_STRING",
    148 };
    149 
    150 /**
    151  *  \brief Interns the list of atoms
    152  *
    153  *  \param [in] xc The XCB connection.
    154  *  \param [out] atoms The location to store interned atoms.
    155  *  \param [in] atom_names The names of the atoms to intern.
    156  *  \param [in] number The number of atoms to intern.
    157  *  \return true iff all atoms were interned.
    158  */
    159 static bool x11_intern_atoms(xcb_connection_t *xc, atom_c *atoms, const char **atom_names, int number) {
    160     for (int i = 0; i < number; i++) {
    161         atoms[i].cookie = xcb_intern_atom(xc, 0,
    162                                           strlen(atom_names[i]), atom_names[i]);
    163     }
    164 
    165     for (int i  = 0; i < number; i++) {
    166         xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(xc,
    167                                          atoms[i].cookie, NULL);
    168         if (reply == NULL) {
    169             return false;
    170         }
    171 
    172         atoms[i].atom = reply->atom;
    173         free(reply); /* XCB: Do not use custom allocators */
    174     }
    175 
    176     return true;
    177 }
    178 
    179 /**
    180  *  \brief Retrieves the screen from the given connection.
    181  *
    182  *  \param [in] xc The XCB connection.
    183  *  \param [in] screen The screen number to retrieve.
    184  *  \return The screen, or NULL on error.
    185  *
    186  *  Based on https://xcb.freedesktop.org/xlibtoxcbtranslationguide/
    187  */
    188 static xcb_screen_t *x11_get_screen(xcb_connection_t *xc, int screen) {
    189     xcb_screen_iterator_t iter;
    190 
    191     iter = xcb_setup_roots_iterator(xcb_get_setup(xc));
    192     for (; iter.rem; screen--, xcb_screen_next(&iter)) {
    193         if (screen == 0) {
    194             return iter.data;
    195         }
    196     }
    197 
    198     return NULL;
    199 }
    200 
    201 /**
    202  *  \brief Clears the selection data held in our cache on SelectionClear.
    203  *
    204  *  \param [in] cb The clipboard context.
    205  *  \param [in] e The selection clear event.
    206  */
    207 static void x11_clear_selection(clipboard_c *cb, xcb_selection_clear_event_t *e) {
    208     if (e->owner != cb->xw) {
    209         return;
    210     }
    211 
    212     for (int i = 0; i < LCB_MODE_END; i++) {
    213         selection_c *sel = &cb->selections[i];
    214         if (sel->xmode == e->selection && (pthread_mutex_lock(&cb->mu) == 0)) {
    215             cb->free(sel->data);
    216             sel->data = NULL;
    217             sel->length = 0;
    218             sel->has_ownership = false;
    219             sel->target = XCB_NONE;
    220             pthread_mutex_unlock(&cb->mu);
    221             break;
    222         }
    223     }
    224 }
    225 
    226 /**
    227  *  \brief Retrieves the selection into our cache on SelectionNotify
    228  *
    229  *  \param [in] cb The clipboard context.
    230  *  \param [in] e The selection notify event.
    231  *
    232  *  The INCR format is not supported.
    233  */
    234 static void x11_retrieve_selection(clipboard_c *cb, xcb_selection_notify_event_t *e) {
    235     unsigned char *buf = NULL;
    236     size_t bufsiz = 0, bytes_after = 1;
    237     xcb_get_property_reply_t *reply = NULL;
    238     xcb_atom_t actual_type;
    239     uint8_t actual_format;
    240 
    241     if (e->property != XCB_ATOM_PRIMARY && e->property != XCB_ATOM_SECONDARY && e->property != cb->std_atoms[X_ATOM_CLIPBOARD].atom) {
    242         fprintf(stderr, "x11_retrieve_selection: [Warn] Unknown selection property returned: %d\n", e->property);
    243         return;
    244     }
    245 
    246     while (bytes_after > 0) {
    247         free(reply); /* XCB: Do not use custom allocators */
    248         xcb_get_property_cookie_t ck = xcb_get_property(cb->xc, true, cb->xw,
    249                                        e->property, XCB_ATOM_ANY,
    250                                        bufsiz / 4, cb->transfer_size / 4);
    251         reply = xcb_get_property_reply(cb->xc, ck, NULL);
    252         /* reply->format should be 8, 16 or 32. */
    253         if (reply == NULL || (bufsiz > 0 && (reply->format != actual_format || reply->type != actual_type)) || ((reply->format % 8) != 0)) {
    254             fprintf(stderr, "x11_retrieve_selection: [Err] Invalid return value from xcb_get_property_reply\n");
    255             cb->free(buf);
    256             buf = NULL;
    257             break;
    258         }
    259 
    260         if (bufsiz == 0) {
    261             actual_type = reply->type;
    262             actual_format = reply->format;
    263         }
    264 
    265         /* Todo: Check for INCR */
    266 
    267         int nitems = xcb_get_property_value_length(reply);
    268         if (nitems > 0) {
    269             if ((bufsiz % 4) != 0) {
    270                 fprintf(stderr, "x11_retrieve_selection: [Err] Got more data but read data size is not a multiple of 4\n");
    271                 cb->free(buf);
    272                 buf = NULL;
    273                 break;
    274             }
    275 
    276             size_t unit_size = (reply->format / 8);
    277             buf = (unsigned char *)cb->realloc(buf, unit_size * (bufsiz + nitems));
    278             if (buf == NULL) {
    279                 fprintf(stderr, "x11_retrieve_selection: [Err] realloc failed\n");
    280                 break;
    281             }
    282 
    283             memcpy(buf + bufsiz, xcb_get_property_value(reply), nitems * unit_size);
    284             bufsiz += nitems * unit_size;
    285         }
    286 
    287         bytes_after = reply->bytes_after;
    288     }
    289     free(reply); /* XCB: Do not use custom allocators */
    290 
    291     if (buf != NULL && (pthread_mutex_lock(&cb->mu) == 0)) {
    292         selection_c *sel = NULL;
    293         for (int i = 0; i < LCB_MODE_END; i++) {
    294             if (cb->selections[i].xmode == e->property) {
    295                 sel = &cb->selections[i];
    296                 break;
    297             }
    298         }
    299 
    300         if (sel != NULL && sel->target == actual_type) {
    301             cb->free(sel->data);
    302             sel->data = buf;
    303             sel->length = bufsiz;
    304             buf = NULL;
    305         } else {
    306             fprintf(stderr, "x11_retrieve_selection: [Warn] Mismatched selection: actual_type=%d\n", actual_type);
    307         }
    308 
    309         pthread_cond_broadcast(&cb->cond);
    310         pthread_mutex_unlock(&cb->mu);
    311     }
    312     cb->free(buf);
    313 }
    314 
    315 /**
    316  *  \brief Sends the selection data to the requestor on SelectionRequest
    317  *
    318  *  \param [in] cb The clipboard context.
    319  *  \param [in] e The selection request event.
    320  *  \return true iff the data was sent (requestor's property was changed)
    321  *
    322  *  Not currently ICCCM compliant as MULTIPLE target is unsupported. Also
    323  *  not compliant because we're not supplying a proper TIMESTAMP value.
    324  */
    325 static bool x11_transmit_selection(clipboard_c *cb, xcb_selection_request_event_t *e) {
    326     /* Default location to store data if none specified */
    327     if (e->property == XCB_NONE) {
    328         e->property = e->target;
    329     }
    330 
    331     if (e->target == cb->std_atoms[X_ATOM_TARGETS].atom) {
    332         xcb_atom_t targets[] = {
    333             cb->std_atoms[X_ATOM_TIMESTAMP].atom,
    334             cb->std_atoms[X_ATOM_TARGETS].atom,
    335             cb->std_atoms[X_ATOM_UTF8_STRING].atom
    336         };
    337         xcb_change_property(cb->xc, XCB_PROP_MODE_REPLACE, e->requestor,
    338                             e->property, XCB_ATOM_ATOM,
    339                             sizeof(xcb_atom_t) * 8,
    340                             sizeof(targets) / sizeof(xcb_atom_t), targets);
    341     } else if (e->target == cb->std_atoms[X_ATOM_TIMESTAMP].atom) {
    342         xcb_timestamp_t cur = XCB_CURRENT_TIME;
    343         xcb_change_property(cb->xc, XCB_PROP_MODE_REPLACE, e->requestor,
    344                             e->property, XCB_ATOM_INTEGER, sizeof(cur) * 8,
    345                             1, &cur);
    346     } else if (e->target == cb->std_atoms[X_ATOM_UTF8_STRING].atom) {
    347         selection_c *sel = NULL;
    348         if (pthread_mutex_lock(&cb->mu) != 0) {
    349             return false;
    350         }
    351 
    352         for (int i = 0; i < LCB_MODE_END; i++) {
    353             if (cb->selections[i].xmode == e->selection) {
    354                 sel = &cb->selections[i];
    355                 break;
    356             }
    357         }
    358 
    359         if (sel == NULL || !sel->has_ownership || sel->data == NULL || sel->target != e->target) {
    360             pthread_mutex_unlock(&cb->mu);
    361             return false;
    362         }
    363 
    364         xcb_change_property(cb->xc, XCB_PROP_MODE_REPLACE, e->requestor,
    365                             e->property, e->target, 8, sel->length, sel->data);
    366         pthread_mutex_unlock(&cb->mu);
    367     } else {
    368         /* Unknown target */
    369         return false;
    370     }
    371 
    372     return true;
    373 }
    374 
    375 /**
    376  *  \brief The main event loop to process window messages.
    377  *
    378  *  \param [in] arg The clipboard context.
    379  *
    380  *  This thread will run indefinitely until the clipboard context's window
    381  *  is destroyed. It *must* receive a DestroyNotify message to end.
    382  */
    383 static void *x11_event_loop(void *arg) {
    384     clipboard_c *cb = (clipboard_c *)arg;
    385     xcb_generic_event_t *e;
    386 
    387     while ((e = xcb_wait_for_event(cb->xc))) {
    388         if (e->response_type == 0) {
    389             /* I think this cast is appropriate... */
    390             xcb_generic_error_t *err = (xcb_generic_error_t *) e;
    391             fprintf(stderr, "x11_event_loop: [Warn] Received X11 error: %d\n", err->error_code);
    392             free(e); /* XCB: Do not use custom allocators */
    393             continue;
    394         }
    395 
    396         switch (e->response_type & ~0x80) {
    397             case XCB_DESTROY_NOTIFY: {
    398                 xcb_destroy_notify_event_t *evt = (xcb_destroy_notify_event_t *)e;
    399                 if (evt->window == cb->xw) {
    400                     free(e); /* XCB: Do not use custom allocators */
    401                     return NULL;
    402                 }
    403             }
    404             break;
    405             case XCB_SELECTION_CLEAR: {
    406                 x11_clear_selection(cb, (xcb_selection_clear_event_t *)e);
    407             }
    408             break;
    409             case XCB_SELECTION_NOTIFY: {
    410                 x11_retrieve_selection(cb, (xcb_selection_notify_event_t *)e);
    411             }
    412             break;
    413             case XCB_SELECTION_REQUEST: {
    414                 xcb_selection_request_event_t *req = (xcb_selection_request_event_t *)e;
    415                 xcb_selection_notify_event_t notify = {0};
    416                 notify.response_type = XCB_SELECTION_NOTIFY;
    417                 notify.time = XCB_CURRENT_TIME;
    418                 notify.requestor = req->requestor;
    419                 notify.selection = req->selection;
    420                 notify.target = req->target;
    421                 notify.property = x11_transmit_selection(cb, req) ? req->property : XCB_NONE;
    422                 xcb_send_event(cb->xc, false, req->requestor, XCB_EVENT_MASK_PROPERTY_CHANGE, (char *)&notify);
    423                 xcb_flush(cb->xc);
    424             }
    425             break;
    426             case XCB_PROPERTY_NOTIFY: {
    427             }
    428             break;
    429             default: {
    430                 /* Ignore unknown messages */
    431             }
    432         }
    433         free(e); /* XCB: Do not use custom allocators */
    434     }
    435 
    436     fprintf(stderr, "x11_event_loop: [Warn] xcb_wait_for_event returned NULL\n");
    437     return NULL;
    438 }
    439 
    440 LCB_API clipboard_c *LCB_CC clipboard_new(clipboard_opts *cb_opts) {
    441     clipboard_opts defaults = { };
    442     defaults.x11.display_name = NULL;
    443     defaults.x11.action_timeout = LCB_X11_ACTION_TIMEOUT_DEFAULT;
    444     defaults.x11.transfer_size = LCB_X11_TRANSFER_SIZE_DEFAULT;
    445 
    446     if (cb_opts == NULL) {
    447         cb_opts = &defaults;
    448     }
    449 
    450     clipboard_calloc_fn calloc_fn = cb_opts->user_calloc_fn ? cb_opts->user_calloc_fn : calloc;
    451     clipboard_c *cb = (clipboard_c *)calloc_fn(1, sizeof(clipboard_c));
    452     if (cb == NULL) {
    453         return NULL;
    454     }
    455     LCB_SET_ALLOCATORS(cb, cb_opts);
    456 
    457     cb->action_timeout = cb_opts->x11.action_timeout > 0 ?
    458                          cb_opts->x11.action_timeout : LCB_X11_ACTION_TIMEOUT_DEFAULT;
    459     /* Round down to nearest multiple of 4 */
    460     cb->transfer_size = (cb_opts->x11.transfer_size / 4) * 4;
    461     if (cb->transfer_size == 0) {
    462         cb->transfer_size = LCB_X11_TRANSFER_SIZE_DEFAULT;
    463     }
    464 
    465     cb->mu_initted = pthread_mutex_init(&cb->mu, NULL) == 0;
    466     if (!cb->mu_initted) {
    467         clipboard_free(cb);
    468         return NULL;
    469     }
    470 
    471     cb->cond_initted = pthread_cond_init(&cb->cond, NULL) == 0;
    472     if (!cb->cond_initted) {
    473         clipboard_free(cb);
    474         return NULL;
    475     }
    476 
    477     int preferred_screen;
    478     cb->xc = xcb_connect(cb_opts->x11.display_name, &preferred_screen);
    479     assert(cb->xc != NULL); /* Docs say return is never NULL */
    480     if (xcb_connection_has_error(cb->xc) != 0) {
    481         clipboard_free(cb);
    482         return NULL;
    483     }
    484     cb->xs = x11_get_screen(cb->xc, preferred_screen);
    485     assert(cb->xs != NULL);
    486 
    487     if (!x11_intern_atoms(cb->xc, cb->std_atoms, g_std_atom_names, X_ATOM_END)) {
    488         clipboard_free(cb);
    489         return NULL;
    490     }
    491 
    492     cb->selections[LCB_CLIPBOARD].xmode = cb->std_atoms[X_ATOM_CLIPBOARD].atom;
    493     cb->selections[LCB_PRIMARY].xmode   = XCB_ATOM_PRIMARY;
    494     cb->selections[LCB_SECONDARY].xmode = XCB_ATOM_SECONDARY;
    495 
    496     /* Structure notify mask to get DestroyNotify messages */
    497     /* Property change mask for PropertyChange messages */
    498     uint32_t event_mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE;
    499     cb->xw = xcb_generate_id(cb->xc);
    500     xcb_generic_error_t *err = xcb_request_check(cb->xc,
    501                                xcb_create_window_checked(cb->xc,
    502                                        XCB_COPY_FROM_PARENT, cb->xw, cb->xs->root,
    503                                        0, 0, 10, 10, 0,  XCB_WINDOW_CLASS_INPUT_OUTPUT,
    504                                        cb->xs->root_visual,
    505                                        XCB_CW_EVENT_MASK, &event_mask));
    506     if (err != NULL) {
    507         cb->xw = 0;
    508         /* Am I meant to free this? */
    509         free(err); /* XCB: Do not use custom allocators */
    510         clipboard_free(cb);
    511         return NULL;
    512     }
    513 
    514     cb->event_loop_initted = pthread_create(&cb->event_loop, NULL,
    515                                             x11_event_loop, (void *)cb) == 0;
    516     if (!cb->event_loop_initted) {
    517         clipboard_free(cb);
    518         return NULL;
    519     }
    520 
    521     return cb;
    522 }
    523 
    524 LCB_API void LCB_CC clipboard_free(clipboard_c *cb) {
    525     if (cb == NULL) {
    526         return;
    527     }
    528 
    529     if (cb->event_loop_initted) {
    530         /* This should send a DestroyNotify message as the termination condition */
    531         xcb_destroy_window(cb->xc, cb->xw);
    532         xcb_flush(cb->xc);
    533         pthread_join(cb->event_loop, NULL);
    534     } else if (cb->xw != 0) {
    535         xcb_destroy_window(cb->xc, cb->xw);
    536     }
    537 
    538     if (cb->xc != NULL) {
    539         xcb_disconnect(cb->xc);
    540     }
    541 
    542     if (cb->cond_initted) {
    543         pthread_cond_destroy(&cb->cond);
    544     }
    545     if (cb->mu_initted) {
    546         pthread_mutex_destroy(&cb->mu);
    547     }
    548 
    549     /* Free selection data */
    550     for (int i = 0; i < LCB_MODE_END; i++) {
    551         if (cb->selections[i].data != NULL) {
    552             cb->free(cb->selections[i].data);
    553         }
    554     }
    555 
    556     cb->free(cb);
    557 }
    558 
    559 LCB_API void LCB_CC clipboard_clear(clipboard_c *cb, clipboard_mode mode) {
    560     if (cb == NULL || cb->xc == NULL) {
    561         return;
    562     }
    563 
    564     xcb_atom_t sel;
    565 
    566     switch (mode) {
    567         case LCB_CLIPBOARD:
    568             sel = cb->std_atoms[X_ATOM_CLIPBOARD].atom;
    569             break;
    570         case LCB_PRIMARY:
    571             sel = XCB_ATOM_PRIMARY;
    572             break;
    573         case LCB_SECONDARY:
    574             sel = XCB_ATOM_SECONDARY;
    575             break;
    576         default:
    577             return;
    578     }
    579 
    580     xcb_set_selection_owner(cb->xc, XCB_NONE, sel, XCB_CURRENT_TIME);
    581     xcb_flush(cb->xc);
    582 }
    583 
    584 LCB_API bool LCB_CC clipboard_has_ownership(clipboard_c *cb, clipboard_mode mode) {
    585     bool ret = false;
    586 
    587     if (!VALID_MODE(mode)) {
    588         return false;
    589     }
    590 
    591     if (cb && (pthread_mutex_lock(&cb->mu) == 0)) {
    592         ret = cb->selections[mode].has_ownership;
    593         pthread_mutex_unlock(&cb->mu);
    594     }
    595     return ret;
    596 }
    597 
    598 /**
    599  *  \brief Copies the selection data into a newly allocated buffer
    600  *
    601  *  \param [in] cb The clipboard context
    602  *  \param [in] sel The selection context
    603  *  \param [out] ret The return location
    604  *  \param [out] length The length of the returned data (optional)
    605  */
    606 static void retrieve_text_selection(clipboard_c *cb, selection_c *sel, char **ret, int *length) {
    607     if (sel->data != NULL && sel->target == cb->std_atoms[X_ATOM_UTF8_STRING].atom) {
    608         *ret = (char *)cb->malloc(sizeof(char) * (sel->length + 1));
    609         if (*ret != NULL) {
    610             memcpy(*ret, sel->data, sel->length);
    611             (*ret)[sel->length] = '\0';
    612 
    613             if (length != NULL) {
    614                 *length = sel->length;
    615             }
    616         }
    617     }
    618 }
    619 
    620 LCB_API char LCB_CC *clipboard_text_ex(clipboard_c *cb, int *length, clipboard_mode mode) {
    621     char *ret = NULL;
    622 
    623     if (cb == NULL || !VALID_MODE(mode)) {
    624         return NULL;
    625     }
    626 
    627     if (pthread_mutex_lock(&cb->mu) == 0) {
    628         selection_c *sel = &cb->selections[mode];
    629         if (sel->has_ownership) {
    630             retrieve_text_selection(cb, sel, &ret, length);
    631         } else {
    632             /* Convert selection & wait for reply */
    633             struct timeval now;
    634             struct timespec timeout;
    635             int pret = 0;
    636 
    637             xcb_get_selection_owner_reply_t *owner = xcb_get_selection_owner_reply(cb->xc,
    638                     xcb_get_selection_owner(cb->xc, sel->xmode), NULL);
    639             if (owner == NULL || owner->owner == 0) {
    640                 /* No selection owner; no data available */
    641                 pthread_mutex_unlock(&cb->mu);
    642                 free(owner); /* XCB: Do not use custom allocators */
    643                 return NULL;
    644             }
    645             free(owner); /* XCB: Do not use custom allocators */
    646 
    647             /* Unset any old value */
    648             cb->free(sel->data);
    649             sel->data = NULL;
    650             sel->length = 0;
    651 
    652             sel->target = cb->std_atoms[X_ATOM_UTF8_STRING].atom;
    653             xcb_convert_selection(cb->xc, cb->xw, sel->xmode,
    654                                   sel->target, sel->xmode, XCB_CURRENT_TIME);
    655             xcb_flush(cb->xc);
    656 
    657             /* Calculate timeout */
    658             gettimeofday(&now, NULL);
    659             timeout.tv_sec = now.tv_sec + (cb->action_timeout / 1000);
    660             timeout.tv_nsec = (now.tv_usec * 1000UL) + ((cb->action_timeout % 1000) * 1000000UL);
    661             if (timeout.tv_nsec >= 1000000000UL) {
    662                 timeout.tv_sec += timeout.tv_nsec / 1000000000UL;
    663                 timeout.tv_nsec = timeout.tv_nsec % 1000000000UL;
    664             }
    665 
    666             while (pret == 0 && sel->data == NULL) {
    667                 pret = pthread_cond_timedwait(&cb->cond, &cb->mu, &timeout);
    668             }
    669 
    670             retrieve_text_selection(cb, sel, &ret, length);
    671         }
    672 
    673         pthread_mutex_unlock(&cb->mu);
    674     }
    675 
    676     return ret;
    677 }
    678 
    679 LCB_API bool LCB_CC clipboard_set_text_ex(clipboard_c *cb, const char *src, int length, clipboard_mode mode) {
    680     bool ret = false;
    681 
    682     if (cb == NULL || src == NULL || length == 0 || !VALID_MODE(mode)) {
    683         return false;
    684     }
    685 
    686     if (pthread_mutex_lock(&cb->mu) == 0) {
    687         selection_c *sel = &cb->selections[mode];
    688         if (sel->data != NULL) {
    689             cb->free(sel->data);
    690         }
    691         if (length < 0) {
    692             length = strlen(src);
    693         }
    694 
    695         sel->data = (unsigned char *)cb->malloc(sizeof(char) * (length + 1));
    696         if (sel->data != NULL) {
    697             memcpy(sel->data, src, length);
    698             sel->data[length] = '\0';
    699             sel->length = length;
    700             sel->has_ownership = true;
    701             sel->target = cb->std_atoms[X_ATOM_UTF8_STRING].atom;
    702             xcb_set_selection_owner(cb->xc, cb->xw, sel->xmode, XCB_CURRENT_TIME);
    703             xcb_flush(cb->xc);
    704             ret = true;
    705         }
    706 
    707         pthread_mutex_unlock(&cb->mu);
    708     }
    709 
    710     return ret;
    711 }
    712 
    713 #endif /* LIBCLIPBOARD_BUILD_X11 */