Skip to content

Instantly share code, notes, and snippets.

@bluecube
Created March 28, 2014 15:50

Revisions

  1. bluecube created this gist Mar 28, 2014.
    225 changes: 225 additions & 0 deletions XClipboard.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,225 @@
    #include "Agui/Clipboard/XClipboard.hpp"
    #include <X11/Xatom.h>
    #include <stdio.h>
    #include <sys/select.h>
    #include <unistd.h>
    #include <assert.h>
    #include <algorithm>
    #include <iostream>

    // Most of the code here is adapted from the example at
    // https://github.com/exebook/x11clipboard

    namespace agui
    {

    XClipboard XClipboard::instance;

    XClipboard::XClipboard()
    {
    assert(this == &instance);

    pipe(instance.fd);

    pthread_mutex_init(&instance.mutex, NULL);
    pthread_cond_init(&instance.condition, NULL);

    this->display = XOpenDisplay(0);
    int screenNumber = DefaultScreen(this->display);
    this->window = XCreateSimpleWindow(this->display,
    RootWindow(this->display, screenNumber),
    0, 0, 1, 1, 0,
    BlackPixel(this->display, screenNumber),
    WhitePixel(this->display, screenNumber));

    this->targetsAtom = XInternAtom(this->display, "TARGETS", 0);
    this->textAtom = XInternAtom(this->display, "TEXT", 0);
    this->utf8Atom = XInternAtom(this->display, "UTF8_STRING", 1);
    this->clipboardAtom = XInternAtom(this->display, "CLIPBOARD", 0);
    this->xselDataAtom = XInternAtom(this->display, "XSEL_DATA", 0);
    }

    XClipboard::~XClipboard()
    {
    pthread_mutex_lock(&instance.mutex);
    if (!instance.content.empty())
    killThread();
    pthread_mutex_unlock(&instance.mutex);

    pthread_cond_destroy(&instance.condition);
    pthread_mutex_destroy(&instance.mutex);

    XDestroyWindow(this->display, window);
    close(this->fd[0]);
    close(this->fd[1]);
    }

    void* XClipboard::serve(void*)
    {
    int displayFd = ConnectionNumber(instance.display);

    XSetSelectionOwner(instance.display, instance.clipboardAtom, instance.window, 0);
    if (XGetSelectionOwner(instance.display, instance.clipboardAtom) != instance.window)
    {
    pthread_mutex_lock(&instance.mutex);
    instance.content.clear();
    pthread_mutex_unlock(&instance.mutex);
    return NULL;
    }

    while (1) {
    // Our custom select call that waits for the wake up pipe
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(displayFd, &fds);
    FD_SET(instance.fd[0], &fds);

    XSync(instance.display, 0);
    select(std::max(instance.fd[0], displayFd) + 1, &fds, NULL, NULL, NULL);
    if (FD_ISSET(instance.fd[0], &fds))
    {
    std::cout << "pipe set" << std::endl;
    pthread_mutex_lock(&instance.mutex);
    instance.content.clear();
    pthread_mutex_unlock(&instance.mutex);
    return NULL;
    }

    XEvent event;
    XNextEvent(instance.display, &event);
    switch (event.type)
    {
    case SelectionRequest:
    {
    std::cout << "SelectionRequest" << std::endl;
    if (event.xselectionrequest.selection != instance.clipboardAtom)
    break;

    XSelectionEvent ev = {0};
    ev.type = SelectionNotify;
    ev.display = event.xselectionrequest.display;
    ev.requestor = event.xselectionrequest.requestor;
    ev.selection = event.xselectionrequest.selection;
    ev.time = event.xselectionrequest.time;
    ev.target = event.xselectionrequest.target;
    ev.property = event.xselectionrequest.property;

    std::cout << ev.requestor << " " << instance.window << std::endl;

    char* name = XGetAtomName(instance.display, ev.target);
    std::cout << "target " << name << std::endl;
    XFree(name);

    if (ev.target == instance.targetsAtom)
    {
    std::cout << "targets list" << std::endl;
    Atom supported[] = {instance.targetsAtom, XA_STRING, instance.utf8Atom};
    std::cout << supported << std::endl;
    XChangeProperty(instance.display, ev.requestor, ev.property, XA_ATOM, 32,
    PropModeReplace, (unsigned char*)(&supported), sizeof(supported)/sizeof(supported[0]));
    }
    else if (ev.target == XA_STRING || ev.target == instance.textAtom || ev.target == instance.utf8Atom)
    {
    pthread_mutex_lock(&instance.mutex);
    std::cout << "returning " << instance.content << std::endl;
    std::cout << XChangeProperty(instance.display, ev.requestor, ev.property, ev.target, 8,
    PropModeReplace,
    (unsigned char*)(instance.content.data()), instance.content.size()) << std::endl;
    pthread_mutex_unlock(&instance.mutex);
    }
    else
    {
    ev.property = None;
    }

    XSendEvent(instance.display, ev.requestor, 0, 0, (XEvent *)&ev);
    break;
    }
    case SelectionClear:
    if (event.xselectionrequest.selection != instance.clipboardAtom)
    break;

    std::cout << "selection clear" << std::endl;
    pthread_mutex_lock(&instance.mutex);
    instance.content.clear();
    pthread_mutex_unlock(&instance.mutex);
    return NULL;
    }
    }

    return NULL;
    }

    void XClipboard::copy(const std::string& input)
    {
    assert(!input.empty());
    if (input.empty())
    return;

    pthread_mutex_lock(&instance.mutex);

    if (!instance.content.empty())
    instance.content = input;
    else
    {
    instance.content = input;
    pthread_create(&instance.thread, NULL, serve, NULL);
    }

    pthread_mutex_unlock(&instance.mutex);
    }

    std::string XClipboard::paste()
    {
    pthread_mutex_lock(&instance.mutex);
    std::string ret;

    if (!instance.content.empty())
    ret = instance.content;
    else
    {
    // Now we're not holding the ownership -> the thread is not running -> we can call Xlib
    // functions however we like.
    XConvertSelection(instance.display, instance.clipboardAtom,
    (instance.utf8Atom == None ? XA_STRING : instance.utf8Atom),
    instance.xselDataAtom, instance.window, CurrentTime);
    XSync(instance.display, 0);
    XEvent event;
    XNextEvent(instance.display, &event);

    if (event.type == SelectionNotify &&
    event.xselection.selection == instance.clipboardAtom &&
    event.xselection.property)
    {
    Atom target;
    int format;
    unsigned long size;
    unsigned long N;
    char* data;
    XGetWindowProperty(event.xselection.display,
    event.xselection.requestor,
    event.xselection.property,
    0L,(~0L), 0, AnyPropertyType,
    &target, &format, &size, &N,(unsigned char**)&data);
    if(target == instance.utf8Atom || target == XA_STRING) {
    ret.assign(data, size);
    XFree(data);
    }
    XDeleteProperty(event.xselection.display, event.xselection.requestor,
    event.xselection.property);
    }
    }

    pthread_mutex_unlock(&instance.mutex);
    return ret;
    }

    void XClipboard::killThread()
    {
    write(instance.fd[1], "", 1); // write anything to stop the thread
    pthread_mutex_unlock(&instance.mutex);
    pthread_join(instance.thread, NULL);
    pthread_mutex_lock(&instance.mutex);
    }

    }
    40 changes: 40 additions & 0 deletions XClipboard.hpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,40 @@
    #pragma once
    #include <string>
    #include <pthread.h>
    #include <X11/Xlib.h>

    namespace agui
    {
    class XClipboard
    {
    public:
    static void copy(const std::string& input);
    static std::string paste();

    private:
    std::string content;

    Display* display;
    Window window;
    Atom targetsAtom;
    Atom textAtom;
    Atom utf8Atom;
    Atom clipboardAtom;
    Atom xselDataAtom;

    pthread_t thread;
    pthread_mutex_t mutex;
    pthread_cond_t condition;

    int fd[2];

    XClipboard();
    virtual ~XClipboard();
    // Kill the thread. Must be called with mutex locked.
    static void killThread();
    static void* serve(void*);

    static XClipboard instance;
    };
    }