Setting clipboard contents from a script

I have a python script that needs to set the clipboard contents. I see that I can do this (with GTK 4.0):

Gdk.Display.get_default().get_clipboard().set('hello')
main_loop = GLib.MainLoop.new(None, True)
main_loop.run()

However, the drawback is that it runs forever. Without the main loop, it finishes, but the clipboard contents are not set correctly, which I assume is because the events are not processed.

Is this a correct assumption?

  • If so, is there a way to “drain” the main loop and make sure all events are consumed before exiting?
  • If not, if there are better ways to do this from a script than the above?

Maybe just connect to the changed signal and quit the loop when its emitted?

You meant something like this?

main_loop = GLib.MainLoop.new(None, False)

clipboard = Gdk.Display.get_default().get_clipboard()

def on_changed(self):
  print('changed before')
  main_loop.quit()
  print('changed after')

clipboard.connect('changed', on_changed)

print('before set')
clipboard.set('hello')
print('after set')

main_loop.run()

print('done')

Unfortunately, that prints:

before set
changed before
changed after
after set

i.e. it calls the on_change immediately after clipboard.set is called, so it doesn’t really give the indication whether the clipboard was actually updated, just that the update was triggered.

Any other suggestions?

Have you confirmed it wasn’t copied? Maybe you want to call gdk_clipboard_store_async() and wait for that to resolve?

That’s the whole problem though. When main_loop.run() gets called, eventually clipboard gets set. I’m trying to figure out how to determine when that happens, so I can main_loop.quit(). The above doesn’t ever quit because main_loop.quit() is called before main_loop.run(), but it’s not a solution for scripts which need to exit in reasonable time.

Adding this:

  def store_async_callback(self, res, user_data):
    print(res)
    try:
      res_finish = clipboard.store_finish(res)
      print(res_finish)
    except Exception as e:
      print(f'exception {e}')

    main_loop.quit()
    print('main_loop.quit')

  clipboard.store_async(0, None, store_async_callback, None)

results in:

main_loop.run
<Gio.Task object at 0x7f141507e180 (GTask at 0xf48c90)>
exception g-io-error-quark: Cannot store clipboard. No clipboard manager is active. (15)
main_loop.quit
done

so main_loop.run() happens before the callback (which is good) and main_loop.quit() works as expected (though there’s that “no clipboard manager” exception), but apparently that’s too soon as well - the actual clipboard contents do not get set.

Any other suggestions?

Okay, but how are you confirming that this is not working?

In a separate shell, I’m running this:

$ while true; do xclip -o -selection clipboard -t TARGETS; xclip -o -selection clipboard; sleep 1; echo; done
Error: target TARGETS not available
Error: target STRING not available

As you can see, it prints errors, which disappear when I leave the main loop running.

I guess that without a clipboard manager, which X11 presumably does not have, the providing process has to stay running to transfer clipboard data to clients.

No, that’s not the case. As long as the providing process says on long enough (I presume in order to process all the events that copy the data to the clipboard), the data is saved into the clipboard.

Stopping the process does not remove it from the clipboard or make it otherwise inaccessible to the requesting process, nor does the requesting process need to request it while the providing process is up.

I’m out of ideas then, sorry :person_shrugging:

1 Like

With Gtk.Application the clipboard contents is set correctly:

import sys
import gi

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

def on_activate(app):
    display = Gdk.Display.get_default()
    clipboard = Gdk.Display.get_clipboard(display)
    clipboard.set('hello')

app = Gtk.Application(application_id='org.Gtk.Example')
app.connect('activate', on_activate)
app.run(None)

(process exits immediately as the app does not have any windows)

So it looks like with plain GLib.MainLoop the “clipboard manager” (mentioned in gdk_clipboard_store_async documentation) does not get started (or connected)? Hopefully someone wiser than me could give some insight.

Hm… Interesting, that doesn’t work for me.

To confirm:

  • GLib.MainLoop examples (that quit quickly) from above don’t work for you as well?
  • You’re on Linux / X11 as well?
  • You used the same method (while + xclip one-liner I posted above) to confirm this is working?
  • Does it work if you change set to a random number (to confirm it actually changes on each invocation)?
      clipboard.set(f'hello-{random.randint(100, 999)}')

Interesting…

  • They didn’t work, I got the same error
  • Tested on Fedora 38 with X11 session
  • I tested by running the script and then used xsel -b to check clipboard content
  • Yes, the clipboard.set(f'hello-{random.randint(100, 999)}') works
    • Checked that clipboard content changes after each run with xsel -b.
1 Like

I’m on NixOS unstable. Wondering if there’s something fundamentally different between the two distros or maybe there’s something I need to do on my end for this to work.

Here are the versions of what may be relevant on my machine:

$ echo $XDG_SESSION_TYPE
x11
$ gnome-shell --version
GNOME Shell 43.3
$ python -c 'import gi; gi.require_version("Gtk", "4.0"); from gi.repository import Gtk; print([Gtk.MAJOR_VERSION, Gtk.MINOR_VERSION, Gtk.MICRO_VERSION])'
[4, 8, 3]
$ python --version
Python 3.10.10
$

Are yours similar?

Almost same:

$ echo $XDG_SESSION_TYPE
x11
$ gnome-shell --version                                                                                           
GNOME Shell 44.1
$ python -c 'import gi; gi.require_version("Gtk", "4.0"); from gi.repository import Gtk; print([Gtk.MAJOR_VERSION, Gtk.MINOR_VERSION, Gtk.MICRO_VERSION])'
[4, 10, 3]
$ python --version
Python 3.11.3
1 Like

Thanks! Well I’m out of ideas :slight_smile: I’ll have to wait in case someone else can provide a way to troubleshoot this.

1 Like

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