-
-
Save huytd/313a5a8cde8b8fe458616d33a7a58b45 to your computer and use it in GitHub Desktop.
Clipboard under X11
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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