为什么 gtk_recent_manager_add_full () 默默地失败了?

Why does gtk_recent_manager_add_full () silently fail?

主要目标

我写了一个 python 简单的程序,可以将一个文件添加到 GTK3 中的最近文件列表中。它在 vim 打开文件时被调用。它工作正常,但是 vim 的启动时间乘以 10。现在我试图将它移植到 C 以改善这个缺陷。这是我尝试移植的 python 脚本的演示:

from gi import require_version
require_version('Gtk', '3.0')
from gi.repository import Gtk

manager = Gtk.RecentManager()
recent_data = Gtk.RecentData()

recent_data.app_name = "vim"
recent_data.mime_type = "text/plain"
recent_data.app_exec = "/usr/bin/vim"
manager.add_full("file:///home/lafleur/tweaks.txt", recent_data)

我的尝试

请参阅下面的代码示例。它编译得很好,但是当我 运行 它时我收到严重警告,然后过程成功,但该文件没有出现在 Nautilus 的最近文件中。

这是回溯:

$ ./a.out
adding file:///home/lafleur/tweaks.txt to recent files

(process:17646): GLib-GObject-CRITICAL **: 12:37:32.034: g_object_get: assertion 'G_IS_OBJECT (object)' failed
file added to recent files.

我不知道哪里出了问题。我关注了 GNOME's GTK3 documentation. Those docs state that the mandatory arguments to gtk_recent_manager_add_full () are the gtk_recent_manager, a uri and a GtkRecentData object holding the file's MIME type, the application name and its callback. When compiled, the process complains that it needs an application description, which I added in the sample (see below). I found g_object_get ()'s definition here 中的文档,但这并没有给我任何线索。

问题

我的问题是:我如何知道发生了什么以及为什么该过程无法将现有 /home/lafleur/tweaks.txt 添加到 Nautilus 的最近文件列表中?我怎么知道我的代码中有什么不是有效的 GObject ?我是否错过了一些初始化,如 this SO answer 中所述?

这是代码示例:

#include <gtk/gtk.h>

int main (int argc, char **argv)
{
  GtkRecentData recent_data;
  GtkRecentManager *manager;
  GError *error = NULL;
  gchar *uri;
  gboolean retval;

  uri = g_filename_to_uri ("/home/lafleur/tweaks.txt", NULL, &error);
  manager = gtk_recent_manager_get_default ();
  if (error) {
      g_warning ("%s", error->message);
      g_error_free (error);
  } else {
    recent_data.mime_type = "text/plain";
    recent_data.app_name = "vim";
    recent_data.app_exec = "/usr/bin/vim";
    recent_data.description = "the vim editor";
    g_print ("adding %s to recent files\n", uri);
    retval = gtk_recent_manager_add_full (
        manager,
        uri,
        &recent_data
    );
    if (retval == TRUE) {
      g_print ("file added to recent files.\n");
    } else {
      g_warning ("there was a problem.\n");
    }
    g_free (uri);
  }
  return retval;

当从 C 中使用 GTK API 时,您需要初始化 GTK 本身,例如:

  gtk_init (&argc, &argv);

其中 argcargv 是指向您在 main 中获得的参数计数器和向量的指针。如果您不这样做,那么接下来的任何行为都是未定义的行为。

Python 绑定,出于 GTK 1 和 2 时代的向后兼容性原因,导入时会自动调用 gtk_init()

此外,GtkRecentManager 在 GTK 主循环中对更新进行排队,以将多个写入合并为一个;这在 GUI 应用程序中通常不是问题,但如果您正在编写 CLI 工具,您还需要旋转主循环,直到 GtkRecentManager 发出“已更改”信号。

一旦您将程序修改为先调用 gtk_init(),然后旋转主循环直到写入完成,您的程序就会运行,例如:

#include <stdlib.h>
#include <gtk/gtk.h>

int main (int argc, char **argv)
{
  GError *error = NULL;
  char *uri = g_filename_to_uri ("/home/lafleur/tweaks.txt", NULL, &error);

  // Bail out early in case of error
  if (error != NULL) {
      g_warning ("%s", error->message);
      g_error_free (error);
      return EXIT_FAILURE;
  }

  // You can pass (NULL, NULL) if you don't have arguments; if you
  // want to deal with the possibility of not having a display
  // connection, you can use gtk_init_check() instead.
  gtk_init (&argc, &argv);

  // Create the recent manager
  GtkRecentManager *manager = gtk_recent_manager_get_default ();

  // Create a main loop; the recent manager will schedule writes
  // within the loop, so it can coalesce multiple operations.
  GMainLoop *loop = g_main_loop_new (NULL, FALSE);

  // Once we receive the "changed" signal from the recent manager
  // we stop the main loop; we use the "swapped" variant so that
  // the callback will be invoked with the signal's user data
  // as the first argument; in this case, we're going to call
  // g_main_loop_quit() on the loop object
  g_signal_connect_swapped (manager, "changed",
                            G_CALLBACK (g_main_loop_quit),
                            loop);

  GtkRecentData recent_data = {
    .mime_type = "text/plain",
    .app_name = "vim",

    // The "app_exec" key should be the command line needed to open
    // the file you're adding; the %f escape sequence means "use the
    // path of the file", whereas the %U escape sequence means "use
    // the URL of the file".
    .app_exec = "/usr/bin/vim %f",
    .description = "the vim editor",
  };

  g_print ("adding %s to recent files\n", uri);

  gboolean retval = gtk_recent_manager_add_full (
    manager,
    uri,
    &recent_data
  );

  // Never compare boolean values for equality; gboolean is an int-sized
  // type, which means anything that is not FALSE/0 will have a TRUE value
  if (retval) {
    g_print ("file added to recent files.\n");
  } else {
    g_warning ("there was a problem.\n");
  }

  g_free (uri);

  // Start the loop; this will block until GtkRecentManager emits the
  // "changed" signal, telling us the the recently used files list has
  // been updated
  g_main_loop_run (loop);

  // Use standard exit codes
  return retval ? EXIT_SUCCESS : EXIT_FAILURE;
}

由于您必须调用 gtk_init(),因此您 需要连接到您的显示服务器;这意味着您不能在图形会话之外的纯虚拟终端下 运行 该程序。如果您需要这样做,您将需要使用 GBookmarkFile API 编写自己的“最近的管理器”,这与 GTK 内部使用的 API 相同。但是,GBookmarkFile API 肯定是更底层的,它需要您了解最近使用的文件所使用的文件格式和位置。