Search code examples
xcb

Clipboard and XCB


I was trying to check if there are any files on the clipboard, and get the full file path to them if there were. I was helping this user here - http://forums.mozillazine.org/viewtopic.php?f=19&t=3003617 - I provided him with Windows and Mac solutions. But I couldn't find any docs on clipboard interaction with XCB. I apologize that this post has no code in it, but I just can't find any.

The only thing I found was here:

http://xpt.sourceforge.net/techdocs/nix/x/general/xwin12-Xclipboard/single/

Which has a paragraph that says:

Xcb provides access to the cut buffers built into every X server. It allows the buffers to be manipulated either via the command line, or with the mouse in a point and click manner. The buffers can be used as holding pens to store and retrieve arbitrary data fragments, so any number of different pieces of data can be saved and recalled later. The program is designed primarily for use with textual data.

And then it links to a broken link: ftp://ftp.x.org/contrib/utilities/xcb-2.3.README

Does anyone have any material on XCB and clipboard? Thank you


Solution

  • Not sure if this is the proper way to do it, but you gotta get 3 atoms: CLIPBOARD, STRING, and an arbitrary one. (awesomeWM seems to use XCB_ATOM_PRIMARY instead of CLIPBOARD, but it didn't work for me. Using UTF8_STRING instead of STRING doesn't work for me either.)

    typedef struct{
      xcb_atom_t selection;  // An `xcb_atom_t` is just an X11 ID, ie. a uint32!
      xcb_atom_t target;
      xcb_atom_t property;
    }xcb_clipboard_t;
    
    xcb_clipboard_t xcb_clipboard_ini(xcb_connection_t* connection){  // Getting these atoms is FAST! Still, do it the ASYNC way: first SEND all (atom) requests, Then FETCH them!
        xcb_intern_atom_cookie_t cookie_selection = xcb_intern_atom(connection, 0, 9,"CLIPBOARD");    // I think this MUST be a standardized name? "PRIMARY", "SECONDARY", "CLIPBOARD"
        xcb_intern_atom_cookie_t cookie_target    = xcb_intern_atom(connection, 0, 6,"STRING");       // can use XCB_ATOM_STRING at atom xid instead. "STRING", "UTF8_STRING"
        xcb_intern_atom_cookie_t cookie_property  = xcb_intern_atom(connection, 0, 9,"MATHISART");    // DUMMY ATOM where we store the *actual* CLIPBOARD data? So, create it if it doesn't exist!
        xcb_intern_atom_reply_t* reply_selection  = xcb_intern_atom_reply(connection, cookie_selection, NULL);
        xcb_intern_atom_reply_t* reply_target     = xcb_intern_atom_reply(connection, cookie_target,    NULL);
        xcb_intern_atom_reply_t* reply_property   = xcb_intern_atom_reply(connection, cookie_property,  NULL);
        xcb_clipboard_t clipboard;
        clipboard.selection = reply_selection->atom;  free(reply_selection);
        clipboard.target    = reply_target   ->atom;  free(reply_target);
        clipboard.property  = reply_property ->atom;  free(reply_property);
        return clipboard;
    }
    

    Then you get the clipboard in 3 steps:

    1. call xcb_convert_selection()
    2. wait for the suitable event
    3. call xcb_get_property()
    void xcb_clipboard_get_cookie(xcb_connection_t* connection, xcb_clipboard_t clipboard, xcb_window_t requestor){
        xcb_convert_selection(connection, requestor, clipboard.selection, clipboard.target, clipboard.property, XCB_CURRENT_TIME);  //  The library call to GET the current content of a SELECTION is called CONVERT...
        xcb_flush(connection);  // you MUST flush?
    }
    
    buf_t xcb_clipboard_get_reply(xcb_connection_t* connection, xcb_clipboard_t clipboard, xcb_window_t requestor){
        xcb_get_property_cookie_t get_property_cookie = xcb_get_property(connection, 0, requestor, clipboard.property, clipboard.target, 0, UINT_MAX/4);
        xcb_get_property_reply_t* get_property_reply  = xcb_get_property_reply(connection, get_property_cookie, NULL);
        buf_t clipboard_data;
        if(get_property_reply!=NULL){
            clipboard_data = bini(xcb_get_property_value_length(get_property_reply));
            memcpy(clipboard_data.data, xcb_get_property_value(get_property_reply), clipboard_data.bdim);  // printf("data \x1b[91m**\x1b[0m%.*s\x1b[91m**\x1b[0m\n", (int)clipboard.bdim, (u8*)clipboard.data);
        }else  clipboard_data = (buf_t){0x00};
        free(get_property_reply);
        xcb_delete_property(connection, requestor, clipboard.property);
        return clipboard_data;
    }
    
    int main(){
        // ...
        xcb_clipboard_get_cookie(xcb_connection, xcb_clipboard, xcb_window);
        free(xcb_wait_for_event(xcb_connection));
        buf_t clipboard = xcb_clipboard_get_reply(xcb_connection, xcb_clipboard, xcb_window);
    
        printf("%.*s\n", clipboard.bdim,clipboard.data);
        bend(&clipboard);
    

    where buf_t is a struct that looks like:

    typedef struct{
        i64 bdim;  // byte-dimension, ie. number of bytes
        u8* data;
    };
    

    (ie. just a buffer that knows its size), and initialized/deinitialized with the bini()/bend() functions, so that the buf_t gets populated with the clipboard contents.

    I think step 1) needs a dummy window for you to listen to events, since you can't listen to events on the root window (that I know of).

    xcb_screen_t* xcb_get_screen(xcb_connection_t* connection, int screen_idx){  // Return a screen from its number!
        const xcb_setup_t* setup = xcb_get_setup(connection);
        for(xcb_screen_iterator_t screen_it = xcb_setup_roots_iterator(setup);  screen_it.rem;  --screen_idx, xcb_screen_next(&screen_it))
            if(screen_idx==0) return screen_it.data;
        return NULL;
    }
    
    int xcb_screen_idx;
    xcb_connection_t*  xcb_connection = xcb_connect(NULL,&xcb_screen_idx);
    xcb_screen_t*      xcb_screen     = xcb_get_screen(xcb_connection,xcb_screen_idx);
    xcb_clipboard_t    xcb_clipboard  = xcb_clipboard_ini(xcb_connection);
    
    xcb_window_t xcb_window = xcb_generate_id(xcb_connection);
    xcb_create_window(xcb_connection, xcb_screen->root_depth, xcb_window, xcb_screen->root, 0,0,1,1, 0,XCB_WINDOW_CLASS_INPUT_OUTPUT, xcb_screen->root_visual, XCB_CW_BACK_PIXMAP, (u32[]){XCB_BACK_PIXMAP_NONE});
    

    This doesn't seem to work for UTF-8, though. When you try it on UTF-8 data, you get something like \u05ea\u05b4\u05bc\u05e9\u05b0\u05c2\u05de\u05b8\u0591\u05d7, which I guess you'd have to parse into binary UTF-8.

    The awesomeWM code did help. Another source.