Reading text from GDK 4 Clipboard in Python

Hi,

I’m migrating a GTK3 application to GTK4, but I’m failing to get text from the clipboard. This is my current minimal script:

#!/usr/bin/env python

"""Display the current textual content from the clipboard.

Usage:

1. Select some text in any application and copy it (e.g. Ctrl+C).
2. Run this program in a terminal.

Result:

The program displays the text you copied on screen."""

import gi
gi.require_version("Gdk", "4.0")
gi.require_version("Gtk", "4.0")
from gi.repository import Gdk, GObject, Gtk


# FUNCTIONS
def get_text():
    """Return text from the clipboard."""
    def read_text(clipboard, result, user_data):
        value = clipboard.read_value_finish(result)
        print(value)
        return value.get_string()

    display = Gdk.Display.get_default()
    clipboard = display.get_clipboard()
    # clipboard.set("yellow")
    formats = clipboard.get_formats()  # FIXME: Always empty ↓ Unless set ↑
    print("Formats:", formats.to_string())  # Just checking...

    if formats.contain_gtype(GObject.TYPE_STRING):
        text = clipboard.read_value_async(
            GObject.TYPE_STRING,
            0,          # I/O Priority
            None,       # Cancellable
            read_text,  # Callback
            None        # User data
        )
    else:
        text = "N/A"

    return text


# RUN
Gtk.init()
print("TEXT:", get_text())

Running the program as is, it seems the clipboard is always empty, even though I have copied text from the web browser or another application.

Also, if I uncomment the line clipboard.set("yellow"), the clipboard formats report that there is plain text, but reading the value returns None instead of "yellow".


NOTE: The Clipboard example from gtk4-demo reads text from the clipboard and it actually works. That is, I can copy text from the web browser, for example, and when I click the Paste button in the demo, it pastes the right text. But the demo is written in C, and although the script above is actually a translation of the relevant parts of the Clipboard demo, it doesn’t work. I can’t see what I’m missing…

This is the relevant C code:

#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <string.h>
#include "demoimage.h"

static void
present_value (GtkStack     *dest_stack,
               const GValue *value)
{
  GtkWidget *child;

  // ...
  if (G_VALUE_HOLDS (value, G_TYPE_STRING))
    {
      gtk_stack_set_visible_child_name (dest_stack, "Text");
      child = gtk_stack_get_visible_child (dest_stack);

      gtk_label_set_label (GTK_LABEL (child), g_value_get_string (value));
    }
}

static void
paste_received (GObject      *source_object,
                GAsyncResult *result,
                gpointer      user_data)
{
  GtkStack *dest_stack = user_data;
  GdkClipboard *clipboard;
  const GValue *value;
  GError *error = NULL;

  clipboard = GDK_CLIPBOARD (source_object);

  value = gdk_clipboard_read_value_finish (clipboard, result, &error);
  if (value)
    {
      present_value (dest_stack, value);
    }
  else
    {
      g_print ("%s\n", error->message);
      g_error_free (error);
    }
}

static void
paste_button_clicked (GtkStack *dest_stack,
                      gpointer  user_data)
{
  GdkClipboard *clipboard;
  GdkContentFormats *formats;

  clipboard = gtk_widget_get_clipboard (GTK_WIDGET (dest_stack));
  formats = gdk_clipboard_get_formats (clipboard);

  // ...
  if (gdk_content_formats_contain_gtype (formats, G_TYPE_STRING))
    gdk_clipboard_read_value_async (clipboard, G_TYPE_STRING, 0, NULL, paste_received, dest_stack);
}

First of all you have some problems with the returns, what ever you return from the callbacks will go to the void, and read_value_async will always return None.

Second, you don’t need to call value.get_string(), you have already set GObject.TYPE_STRING as the type you’re expecting in read_value_async, so read_value_finish should return a string if available.

Here you have an example in Python, although it’s using read_text_async: Clipboard - PyGObject Guide.

PD: You can skip passing user data if you’re not going to use it.

2 Likes

:person_facepalming: Right.

Thanks a lot. The example works for me.

However, I was trying to use the GDK Clipboard without actually using GTK widgets. So, copying from your example, I was hoping something like the following would work:

import gi
gi.require_version('Gdk', '4.0')
gi.require_version('Gtk', '4.0')
from gi.repository import Gdk, Gtk


def paste_text():
    clipboard = Gdk.Display.get_default().get_clipboard()
    clipboard.read_text_async(None, on_paste_text)


def on_paste_text(_clipboard, result):
    text = _clipboard.read_text_finish(result)
    if text is not None:
        print(text)


Gtk.init()
paste_text()

But it doesn’t. It only works if I run the paste_text function from a Gtk.Application:

import gi
gi.require_version('Gdk', '4.0')
gi.require_version('Gtk', '4.0')
from gi.repository import Gdk, Gtk


def paste_text():
    clipboard = Gdk.Display.get_default().get_clipboard()
    clipboard.read_text_async(None, on_paste_text)


def on_paste_text(_clipboard, result):
    text = _clipboard.read_text_finish(result)
    if text is not None:
        print(text)


def on_activate(app):
    paste_text()


app = Gtk.Application(application_id='com.example.App')
app.connect('activate', on_activate)

app.run(None)

Do you happen to know why this is? What is the Gtk.Application initializing that allows the clipboard working as expected?

You don’t need an application, but you do need a main loop running.

1 Like

Aha!

I’m just typing things and hoping for the best, but this works:

import gi
gi.require_version('Gdk', '4.0')
gi.require_version('Gtk', '4.0')
from gi.repository import Gdk, GLib, Gtk


def paste_text():
    clipboard = Gdk.Display.get_default().get_clipboard()
    clipboard.read_text_async(None, on_paste_text)


def on_paste_text(clipboard, result):
    text = clipboard.read_text_finish(result)
    if text is not None:
        print(text)

    main_loop.quit()


main_loop = GLib.MainLoop.new(None, True)
paste_text()
main_loop.run()

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.