#include "Agui/Clipboard/XClipboard.hpp" #include #include #include #include #include #include #include // 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); } }