1

I want to put a string or bytes to the X11 selection so that when I switch to other applications, I can directly do a ctrl+p paste.

I try to follow the documentation of the X11 clipboard mechanism. If I understand correctly, I need to use XSetSelectionOwner to obtain the XA_CLIPBOARD selection and then use XChangeProperty to put my data to the clipboard.

Here is a simple snippet, but unfortunately it does not work:

// main.c
#include <stdlib.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xmu/Atoms.h>

int main() {
    // try to write `hello` to the clipboard
    const char *in = "hello\0";
    const int n = 5;

    Display* d = XOpenDisplay(0);
    Window w = XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, 1, 1, 0, 0, 0);
    Atom XA_CLIPBOARD = XInternAtom(d, "CLIPBOARD", True);
    XSetSelectionOwner(d, XA_CLIPBOARD, w, CurrentTime);

    XEvent event;
    XNextEvent(d, &event);
    if (event.type != SelectionRequest) {
        XCloseDisplay(d);
        return 0;
    }
    if (event.xselectionrequest.selection != XA_CLIPBOARD) {
        XCloseDisplay(d);
        return 0;
    }

    XSelectionRequestEvent* req = &event.xselectionrequest;
    XChangeProperty(d, req->requestor, req->property, XA_STRING, 8, PropModeReplace, (unsigned char *)in, n);

    XEvent re = {0};
    re.xselection.type = SelectionNotify;
    re.xselection.display = req->display;
    re.xselection.requestor = req->requestor;
    re.xselection.selection = req->selection;
    re.xselection.property = req->property;
    re.xselection.target = req->target;
    XSendEvent(d, req->requestor, 0, 0, &re); // event is sent, but data is not in my clipboard
    XFlush(d);

    XCloseDisplay(d);
    return 0;
}

Compile: clang -o main main.c -lX11 -lXmu

What did I do wrong, and how to fix it?

Changkun
  • 1,502
  • 1
  • 14
  • 29

2 Answers2

0

XNextEvent retrieves any event that occured (input, pointer, configuration, property changes, ...).

So it is very unlikely that the first event, will be your desired event.

You have to iterate over the event queue to check if a SelectionRequest event occured.

Therefore you have to call XNextEvent in a loop and check everytime the event.type for the desired event.

Edit:

If you retrieve a ClientMessage event and the client data equals the Atom WM_DELETE, there was a request by the user to close the window (pressing the X). Other than that, you can quit whenever you want.

XEvent evt;

while (i_want_to_receive_and_process_events) {

    XNextEvent(dpy, &evt);
    
    switch (evt.type) {
    
    case SelectionRequest:
        if (evt.xselectionrequest.selection == XA_CLIPBOARD) {
            // what you were looking for, do your thing
            // i_want_to_receive_and_process_events = false (?)
        }
    break;
    
    case ClientMessage:
        if (evt.xclient.data.l[0] == WM_DELETE) {
            i_want_to_receive_and_process_events = false;
        }
    break;

    }

}
Erdal Küçük
  • 4,810
  • 1
  • 6
  • 11
  • Then what's the condition to break the loop? Could you maybe show me how to modify the given snippet and turn it into a working snippet? – Changkun Nov 20 '20 at 11:12
  • How about that? – Erdal Küçük Nov 20 '20 at 12:33
  • Sorry, it does not work. If you can come up with a full working example, it would be great. – Changkun Nov 20 '20 at 12:45
  • I think you know how to do that. Opening a window, setting/getting an Atom, sending events, your example shows that. You just have to try it. If something you have done is not working (as expected), run your tests, find out where the problem could be and after all, show us what you have done, which tests you have run and i bet, there are plenty of people, willing to help you out. But you cannot expect from others to do your work. And what gives you the impression that i would know how to do what you are trying to do? I know a bit of C and a bit of X, maybe just not enough bits. – Erdal Küçük Nov 20 '20 at 13:44
  • @Jakob You want a working example, see [GLFW - Clipboard input and output](https://www.glfw.org/docs/latest/input_guide.html#clipboard), have a look at their sources and learn how they have done it. – Erdal Küçük Nov 20 '20 at 14:00
  • 1
    Thanks, I've figured out myself with the `xclip`'s source code. – Changkun Nov 20 '20 at 15:08
0

I've done some digging around trying to get a bear minimum program that lets me set whatever value that I want in the clipboard via X11. From my limited understanding X11 is a client-server setup. And much like how some other technology functions, such as MySql, to get the data structures you want you need to loop through a queue of sorts.

Admittedly I don't fully grasp what's happening behind the scenes and this code is most likely is unreliable; but you can still use it to study and figure it out.

Tested on Linux Mint Cinnamon

// g++ -o clipboard test.cc -lX11 -lXmu
//                               |-->libxmu-dev
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xmu/Atoms.h>
#include <string>

bool writeX11Clipboard(std::string dest){
    if(dest.length() <= 0){
        return false;
    }
    // init X11 display and window.
    Display *display = XOpenDisplay(NULL);
    unsigned long color = BlackPixel(display, DefaultScreen(display));
    Window window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0,0, 1,1, 0, color, color);

    // Copy string into an unsigned char array, may be able to just typecast.
    size_t destSize = dest.length();
    unsigned char *copyData = new unsigned char[destSize];
    int copyDataLen = destSize;
    for(int i=0; i<copyDataLen; i++){
        copyData[i] = (unsigned char)dest[i];
    }
    
    // Pre-iteration preperations...
    XEvent event;
    Atom selection = XA_CLIPBOARD(display);
    Atom target = XA_UTF8_STRING(display);

    XSelectInput(display, window, PropertyChangeMask);
    XSetSelectionOwner(display, selection, window, CurrentTime);

    Window requestor_id;

    /*
     * Loop over a few events.
     * The first 3 appears to require sending information regarding the previous event to the X11 Server.
     * On the 4th iteration the event that allows you to write into the clipboard appears to become usable. 
     * */
    for(int i=0; i<4; i++){
        XNextEvent(display, &event);
        if(event.type == SelectionRequest){
            requestor_id = event.xselectionrequest.requestor;
        }else{
            XDestroyWindow(display, window);
            XCloseDisplay(display);
            return false;
        }
    
        XEvent eventResponse;
            Atom inc;
            Atom targets;
        targets = XInternAtom(display, "TARGETS", False);
        inc = XInternAtom(display, "INCR", False);
    
        if (event.xselectionrequest.target == targets) {
            // These operations will allow the code to continue iterating through events.
            Atom types[2] = { targets, XA_UTF8_STRING(display) };
            XChangeProperty(display,
                                event.xselectionrequest.requestor,
                                event.xselectionrequest.property,
                                XA_ATOM,
                                32, PropModeReplace, (unsigned char *) types,
                                (int) (sizeof(types) / sizeof(Atom))
                );
        }else{
            // This is what actually sets the clipboard data.
            XChangeProperty(display,
                                event.xselectionrequest.requestor,
                                event.xselectionrequest.property,
                                XA_UTF8_STRING(display),
                                8, PropModeReplace, (unsigned char *) copyData,
                                (int) copyDataLen);
        }
    
        // Create a X11 event packet and send it to the server.
        eventResponse.xselection.property = event.xselectionrequest.property;
            eventResponse.xselection.type = SelectionNotify;
            eventResponse.xselection.display = event.xselectionrequest.display;
            eventResponse.xselection.requestor = event.xselectionrequest.requestor;
            eventResponse.xselection.selection = event.xselectionrequest.selection;
            eventResponse.xselection.target = event.xselectionrequest.target;
            eventResponse.xselection.time = event.xselectionrequest.time;
    
        XSendEvent(display, event.xselectionrequest.requestor, 0, 0, &eventResponse);
            XFlush(display);
    }
    // Clean up.
    XDestroyWindow(display, window);
    XCloseDisplay(display);
    return true;
}

int main(int argc, char *argv[]){
    if(argc != 2)
        return 1;
    writeX11Clipboard(argv[1]);
    return 0;
}

Usage:

morningstar@starmorning:: ~/Documents/tests 8=D~ ./clipboard "my test"
Ctrl+Shift+V
morningstar@starmorning:: ~/Documents/tests 8=D~ my test