linuxgtkuriclipboardvala

Inserting uris into Gtk.Clipboard with vala


I am currently trying to implement copy and paste for my application. The problem is that I can only write plain text or images to the clipboard according to the documentation of Gtk.Clipboard: set_text()/set_image().

There is also set_with_data(), which I think can be used to add a URI or an array of URIs, but I can't figure out; I didn't find any good examples either.

Using the given answer I can fill the clipboard with an array of URIs, but I can read them. When I try, it just calls get_func() again and refills it.

CTRL C pressed
clipboard get_func called
Received: file:///home/marcel/Downloads/.gitignore

CTRL V pressd
clipboard get_func called
Received: file:///home/marcel/Downloads
Try Pasting: file:///home/marcel/Downloads

This is the code I use for testing CTRL + V.

print ("\nCTRL V pressd\n");
clipboard.request_uris ((clipboard, uris) => {
    foreach ( string content in uris ) {
        print ("Try Pasting: ");
        print (content);
        print ("\n");
    }
});

This is the relevant part of get_func() for CTRL + C.

clipboard.set_with_owner (
    clipboard_targets,
    (clipboard, selection_data, info, user_data_or_owner) => {
    print ("clipboard get_func called\n");
    var w = user_data_or_owner as Window;
    File[] files = { w.get_selected_file () };

    switch ( info ) {
        case ClipboardProtocol.TEXT_URI_LIST:
            print ("Received: ");
            string[] uris = {};
            foreach ( var file in files ) {
                print (file.get_uri ());
                print ("\n");
                uris += file.get_uri ();
            }
            selection_data.set_uris (uris);
            break;
    }
}

As you can see in the terminal output above, it just refills the clipboard, throwing away its previous values.


Solution

  • As requested I am providing both an example for writing URIs to clipboard and getting URIs from clipboard. These examples are basically command line programs that get / set the clipboard immediately. In an actual GUI application you would probably react to a button press or, to catch CtrlC / CtrlV events, use Gtk.Widget.add_events() and get / set the clipboard when handling the Gtk.Widget.event signal.

    Getting the clipboard

    You can request URIs from the X11 clipboard using Gtk.Clipboard.request_uris (). This function takes a callback that will be called once the URIs are available.

    Example:

    public void main (string[] args) {
        Gtk.init (ref args);
    
        Gdk.Display display = Gdk.Display.get_default ();
        Gtk.Clipboard clipboard = Gtk.Clipboard.get_for_display (display, Gdk.SELECTION_CLIPBOARD);
    
        clipboard.request_uris (recieved_func);
        Gtk.main ();
    }
    
    /* Gtk.ClipboardURIRecievedFunc */
    private void recieved_func (Gtk.Clipboard clipboard, string[] uris) {
        foreach (var uri in uris) {
            print (uri + "\n");
        }
        Gtk.main_quit ();
    }
    

    To be compiled with valac clipget.vala --pkg=gtk+-3.0

    Setting the clipboard

    Theory:

    From the Qt4 documentation:

    Since there is no standard way to copy and paste files between applications on X11, various MIME types and conventions are currently in use. For instance, Nautilus expects files to be supplied with a x-special/gnome-copied-files MIME type with data beginning with the cut/copy action, a newline character, and the URL of the file.

    Gtk.Clipboard does not pre-implement setting the clipboard for copying / cutting files. As you said, there is no such Gtk.Clipboard.set_uris().

    Instead, you should set the clipboard by providing a callback that X11 gets the clipboard contents from once requested.

    These are the steps required:

    Example:

    enum ClipboardProtocol {
        TEXT_URI_LIST,
        GNOME_COPIED_FILES,
        UTF8_STRING
    }
    
    public void main (string[] args) {
        Gtk.init (ref args);
    
        Gdk.Display display = Gdk.Display.get_default ();
        Gtk.Clipboard clipboard = Gtk.Clipboard.get_for_display (display, Gdk.SELECTION_CLIPBOARD);
    
        var clipboard_targets = new Gtk.TargetEntry[3];
    
        Gtk.TargetEntry target_entry = { "text/uri-list", 0, ClipboardProtocol.TEXT_URI_LIST };
        clipboard_targets[0] = target_entry;
    
        target_entry = { "x-special/gnome-copied-files", 0, ClipboardProtocol.GNOME_COPIED_FILES };
        clipboard_targets[1] = target_entry;
    
        target_entry = { "UTF8_STRING", 0, ClipboardProtocol.UTF8_STRING };
        clipboard_targets[2] = target_entry;
    
        var owner = new Object ();
    
        var rc = clipboard.set_with_owner (
            clipboard_targets,
            get_func,
            clear_func,
            owner
        );
        assert (rc);
        clipboard.store ();
    
        Gtk.main ();
    }
    
    /* Gtk.ClipboardGetFunc */
    private void get_func (
        Gtk.Clipboard clipboard,
        Gtk.SelectionData selection_data,
        uint info, 
        void* user_data_or_owner
    ) {
        print ("GET FUNC!\n");
    
        File my_file = File.new_for_path ("/home/lukas/tmp/test.txt");
        File my_2nd_file = File.new_for_path ("/home/lukas/tmp/test2.txt");
        File[] files = { my_file, my_2nd_file };
    
        switch (info) {
            case ClipboardProtocol.TEXT_URI_LIST:
                string[] uris = {};
                foreach (var file in files) {
                    uris += file.get_uri ();
                }
                selection_data.set_uris (uris);
                break;
    
            case ClipboardProtocol.GNOME_COPIED_FILES:
                var prefix = "copy\n"; 
                //var prefix = "cut\n";
                /* use one of the above */
    
                var builder = new StringBuilder (prefix);
                for (int i = 0; i < files.length; i++) {
                    builder.append (files[i].get_uri ()); 
                    /* dont put the newline if this is the last file */
                    if (i != files.length - 1)
                        builder.append_c ('\n');
                }
                selection_data.set (
                    selection_data.get_target (),
                    8,
                    builder.data
                );
                break;
    
            case ClipboardProtocol.UTF8_STRING:
                var builder = new StringBuilder ();
                foreach (var file in files) {
                    builder.append (file.get_parse_name ());
                }
                builder.append_c ('\n');
                selection_data.set_text (builder.str, -1);
                break;
            default:
                assert_not_reached ();
        }
        Gtk.main_quit ();
    }
    
    /* Gtk.ClipboardClearFunc */
    private void clear_func (Gtk.Clipboard clipboard, void* data) {
        ;
    }
    

    To be compiled with valac clipset.vala --pkg=gtk+-3.0

    A couple of notes:

    Sources: