21

I'm trying to add to the "recently used" files list from Python 3 on Ubuntu.

I am able to successfully read the recently used file list like this:

from gi.repository import Gtk
recent_mgr = Gtk.RecentManager.get_default()
for item in recent_mgr.get_items():
    print(item.get_uri())

This prints out the same list of files I see when I look at "Recent" in Nautilus, or look at the "Recently Used" place in the file dialog of apps like GIMP.

However, when I tried adding an item like this (where /home/laurence/foo/bar.txt is an existing text file)...

recent_mgr.add_item('file:///home/laurence/foo/bar.txt')

...the file does not show up in the Recent section of Nautilus or in file dialogs. It doesn't even show up in the results returned by get_items().

How can I add a file to GTK's recently used file list from Python?

Laurence Gonsalves
  • 137,896
  • 35
  • 246
  • 299
  • 1
    @oldtechaa The list of recent files seen by Nautilus and the file dialog in other apps (including GIMP, Inkscape, and even Chrome) seem to be identical. Many apps default to filtering the list to their own file type, but if you change the filter to "All Files" you see exactly the same list that Nautilus shows, and that is displayed by the code in the question. – Laurence Gonsalves Oct 06 '16 at 05:59
  • What does `add_item()` return? – andlabs Oct 06 '16 at 18:13
  • @andlabs Calling `add_item()` returns `True`. (Actually, is seems to always return `True`, even if the URI I pass to it is nonexistant or complete nonsense. The only exception is if I pass it a non-`str`. Then it raises `TypeError`.) – Laurence Gonsalves Oct 07 '16 at 06:21
  • Maybe not though seeing that `add_item()` returns true would suggest it is something else. Could it be picking up the wrong history file some how? If it returns true it suggests it succeeded in writing it somewhere. – OrderAndChaos Oct 07 '16 at 07:05
  • The real question I have, which is unrelated to your code, is why `add_item()` doesn't use GError while every other method of GtkRecentManager does. I'll ask that later; maybe one of the GTK+ developers can guess what's going on... – andlabs Oct 07 '16 at 07:39
  • 1
    @andlabs This is due to the use of asynchronous code to avoid `g_content_type_guess` to block the flow of the program I think, see my answer below. – Jacques Gaudin Oct 08 '16 at 08:00
  • @JacquesGaudin good to know then; thanks. And it seems `add_full()` is written in a way that even though it is synchronous, it cannot fail unless the input parameters are wrong. I'm not sure if this is smart, but oh well :/ – andlabs Oct 08 '16 at 15:36
  • @andlabs Not sure about the smartness of it either, especially when you realise that `g_content_type_guess` worst guess is the first mimetype in the system's mimetype list. Not a very smart guess ! – Jacques Gaudin Oct 08 '16 at 16:21
  • Actually I should rephrase that: "it will hide failures of any of its underlying functions as it doesn't call the versions of those functions that could potentially report failures (if they even exist)"; I'd have to look at the GBookmarkFile API. Also, what would you suggest as a better worst-case, `application/octet-stream`? – andlabs Oct 08 '16 at 16:34
  • @andlabs I may be a bit naive but I was thinking that there was a mimetype for unknown data file. The content type guess returns an uncertainty boolean which could be replaced with an unknown data mimetype linking to a hexadecimal editor maybe... – Jacques Gaudin Oct 09 '16 at 09:28

2 Answers2

18

A Gtk.RecentManager needs to emit the changed signal for the update to be written in a private attribute of the C++ class. To use a RecentManager object in an application, you need to start the event loop by calling Gtk.main:

from gi.repository import Gtk

recent_mgr = Gtk.RecentManager.get_default()
uri = r'file:/path/to/my/file'
recent_mgr.add_item(uri)
Gtk.main()

If you don't call Gtk.main(), the changed signal is not emitted and nothing happens.

To answer @andlabs query, the reason why RecentManager.add_item returns a boolean is because the g_file_query_info_async function is called. The callback function gtk_recent_manager_add_item_query_info then gathers the mimetype, application name and command into a GtkRecentData struct and finally calls gtk_recent_manager_add_full. The source is here.

If anything goes wrong, it is well after add_item has finished, so the method just returns True if the object it is called from is a RecentManager and if the uri is not NULL; and False otherwise.

The documentation is inaccurate in saying:

Returns TRUE if the new item was successfully added to the recently used resources list

as returning TRUE only means that an asynchronous function was called to deal with the addition of a new item.

As suggested by Laurence Gonsalves, the following runs pseudo-synchronously:

from gi.repository import Gtk, GObject

recent_mgr = Gtk.RecentManager.get_default()
uri = r'file:/path/to/my/file'
recent_mgr.add_item(uri)
GObject.idle_add(Gtk.main_quit)
Gtk.main()
Cactus
  • 27,075
  • 9
  • 69
  • 149
Jacques Gaudin
  • 15,779
  • 10
  • 54
  • 75
1

This is my solution (complet script) with timer for quit GTK.main() loop

#!/usr/bin/env python3

import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib
import sys
import os
import subprocess

recent_mgr = Gtk.RecentManager.get_default()

if len(sys.argv) <= 1:
    paths = (os.getcwd(),)
else:
    paths = sys.argv[1:]

for path in paths:
    if os.path.exists(path):
        if path[0] != "/":
            path = os.getcwd() + "/" + path
        subprocess.call(["touch", "-a", path])
        uri = r"file:" + path
        recent_mgr.add_item(uri)

GLib.timeout_add(22, Gtk.main_quit, None)
Gtk.main()

MarrekNožka
  • 343
  • 2
  • 6