Skip to content

Instantly share code, notes, and snippets.

@bluecube
Created March 28, 2014 15:50
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save bluecube/9835979 to your computer and use it in GitHub Desktop.
Save bluecube/9835979 to your computer and use it in GitHub Desktop.
Clipboard under X11
#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);
}
}
#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;
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment