编写搜索提供程序

搜索是 GNOME 用户体验的核心概念。Shell 概览中的搜索条目是进行快速搜索的地方。

搜索提供程序是一种机制,应用程序可以通过它向 GNOME Shell 暴露其搜索能力。当用户在 Shell 的搜索条目中输入任何内容时,文本会转发给所有已知的搜索提供程序,并将结果返回以供显示。

在 Shell 概览中,搜索结果会按其各自的应用程序分组,并且每个应用程序最多显示三个结果。用户可以选择单个结果,在这种情况下,应用程序应该打开它;或者,用户可以选择应用程序图标,在这种情况下,应用程序可以显示来自该特定应用程序的所有结果的应用程序内视图,而没有任何限制。

“打开”的确切含义取决于具体的应用程序。文件和文档提供项目内容的预览;软件显示安装应用程序的 UI;终端窗口则简单地被带入焦点。如果可能,应用程序应该提供一种“返回”到其搜索视图的方式,该视图应该预先填充在 Shell 中执行的相同搜索。这样可以让用户继续在应用程序内部完善其搜索。

应用程序应该准备好处理重复查询,因为用户在 Shell 搜索条目中输入更多字符。

基础

为了成为搜索提供程序,应用程序应该实现以下 D-Bus 接口

<node>
  <interface name="org.gnome.Shell.SearchProvider2">

    <method name="GetInitialResultSet">
      <arg type="as" name="terms" direction="in" />
      <arg type="as" name="results" direction="out" />
    </method>

    <method name="GetSubsearchResultSet">
      <arg type="as" name="previous_results" direction="in" />
      <arg type="as" name="terms" direction="in" />
      <arg type="as" name="results" direction="out" />
    </method>

    <method name="GetResultMetas">
      <arg type="as" name="identifiers" direction="in" />
      <arg type="aa{sv}" name="metas" direction="out" />
    </method>

    <method name="ActivateResult">
      <arg type="s" name="identifier" direction="in" />
      <arg type="as" name="terms" direction="in" />
      <arg type="u" name="timestamp" direction="in" />
    </method>

    <method name="LaunchSearch">
      <arg type="as" name="terms" direction="in" />
      <arg type="u" name="timestamp" direction="in" />
    </method>

  </interface>
</node>

注意

您还可以找到此 D-Bus 接口定义的副本,位于 $(datadir)/dbus-1/interfaces/org.gnome.ShellSearchProvider2.xml

注册新的搜索提供程序

为了将搜索提供程序注册到 GNOME Shell,您必须在 $(datadir)/gnome-shell/search-providers 中为您的提供程序提供一个键/值文件。

假设我们有一个名为“Foo Bar”的应用程序,它是 D-Bus 可激活的,其中“Foo”是项目范围内的命名空间,“Bar”是应用程序的名称。那么我们可以创建一个名为 foo.bar.search-provider.ini 的文件,内容如下

[Shell Search Provider]
DesktopId=foo.Bar.desktop
BusName=foo.Bar
ObjectPath=/foo/Bar/SearchProvider
Version=2

在您重新启动 Shell 后,查询现在将使用给定的 D-Bus 名称转发到搜索提供程序。

配置

GNOME 控制中心有一个设置面板,允许用户配置在 Shell 中使用哪些搜索提供程序以及以何种顺序显示其结果。这完全由控制中心和 Shell 处理,您的应用程序不参与其中,只需在 DesktopId 键中提供其桌面文件中的名称即可。Shell 和控制中心都使用此键来查找在 UI 中用于表示您的搜索提供程序的图标和名称。

细节

请记住,通过在 Shell 搜索条目中输入文本激活搜索提供程序不应明显启动应用程序。仍然可以在与应用程序相同的二进制文件中实现搜索提供程序(这具有共享搜索实现之间的优势);只需确保激活应用程序以“服务”模式启动它即可。startup() 虚拟函数不应打开任何窗口,而应仅在 activate()open() 中执行此操作。

另一个需要记住的要点是,Shell 中的搜索不应影响应用程序的 UI,除非它已经运行,并且用户明确选择使用该应用程序打开(一个或所有)搜索结果。一旦用户这样做,预计您将重用已经打开的窗口并将其切换到搜索视图。

SearchProvider 接口的所有方法都应异步实现,特别是,您应该处理重叠的子搜索请求,因为用户继续在 Shell 搜索条目中输入。处理此问题的一种常见方法是在收到新的请求时取消先前的搜索请求。

SearchProvider 接口

GetInitialResultSet :: (as)(as)

GetInitialResultSet 在启动新的搜索时调用。它将搜索词数组作为参数获取,并应返回结果 ID 数组。gnome-shell 将为这些结果 ID 中的一些调用 GetResultMetas,以获取可以在结果列表中显示的有关结果的详细信息。

GetSubsearchResultSet :: (as,as)(as)

GetSubsearchResultSet 在用户在搜索条目中输入更多字符时调用,以完善初始搜索结果。它将先前的搜索结果和当前的搜索词作为参数获取,并应返回结果 ID 数组,就像 GetInitialResulSet 一样。

GetResultMetas :: (as)(aa{sv})

GetResultMetas 用于获取结果的详细信息。它将结果 ID 数组作为参数获取,并应返回匹配的字典数组(即,为每个传入的结果 ID 一个 a{sv})。应为每个结果提供以下信息

  • “id”:结果 ID

  • “name”:结果的显示名称

  • “icon”:序列化的 GIcon(参见 g_icon_serialize()),或者,

  • “gicon”:GIcon 的文本表示形式(参见 g_icon_to_string()),或者,

  • “icon-data”:一个类型为 (iiibiiay) 的元组,描述具有宽度、高度、行步长、是否具有 alpha、每样本位数和通道数的像素图以及图像数据

  • “description”:可选的简短描述(1-2 行)

  • “clipboardText”:激活时可选的发送到剪贴板的文本

ActivateResult :: (s,as,u)()

ActivateResult 在用户单击单个结果以在应用程序中打开它时调用。参数是结果 ID、当前的搜索词和时间戳。

LaunchSearch :: (as,u)()

LaunchSearch 在用户单击提供程序图标以在应用程序中显示更多搜索结果时调用。参数是当前的搜索词和时间戳。

实现

您可以通过在 dbus_register() vfunc 中导出实现搜索提供程序接口的 GDBusInterfaceSkeleton,将搜索提供程序添加到使用 GtkApplication 的应用程序中。

假设我们有一个名为 Foo Bar 的应用程序,其中 Foo 是项目范围内的命名空间,Bar 是应用程序的名称。

您应该使用“gnome”Meson 模块通过 gdbus-codegen 生成 GDBusInterfaceSkeleton

gnome = import('gnome')

sp_sources = gnome.gdbus_codegen(
  'shell-search-provider-generated',
  sources: 'org.gnome.Shell.SearchProvider2.xml',
  interface_prefix : 'org.gnome.',
  namespace : 'Bar',
)

然后,您需要覆盖 GtkApplicationdbus_register()dbus_unregister() 虚拟函数,以便在给定的路径上导出和取消导出接口

G_DEFINE_TYPE (BarApplication, bar_application, GTK_TYPE_APPLICATION);

static void
bar_application_class_init (BarApplicationClass *class)
{
  GApplicationClass *application_class = G_APPLICATION_CLASS (class);

  application_class->dbus_register = bar_application_dbus_register;
  application_class->dbus_unregister = bar_application_dbus_unregister;
}

GtkApplication *
bar_application_new (void)
{
  return g_object_new (BAR_TYPE_APPLICATION,
                       "application-id", "foo.bar",
                       NULL);
}

static gboolean
bar_application_dbus_register (GApplication *application,
                               GDBusConnection *connection,
                               const gchar *object_path,
                               GError **error)
{
  BarApplication *self = BAR_APPLICATION (application);

  // Chain up to the parent's implementation
  if (!G_APPLICATION_CLASS (bar_application_parent_class)
         ->dbus_register (application,
                          connection,
                          object_path,
                          error))
    return FALSE;

  g_autofree search_provider_path =
    g_strconcat (object_path, "/SearchProvider", NULL);

  // Export the SearchProvider interface to the given path
  if (!bar_search_provider_dbus_export (self->search_provider,
                                        connection,
                                        search_provider_path,
                                        error))
    return FALSE;

  return TRUE;
}

static void
bar_application_dbus_unregister (GApplication *application,
                                 GDBusConnection *connection,
                                 const gchar *object_path)
{
  BarApplication *self = BAR_APPLICATION (application);
  g_autofree char *search_provider_path =
    g_strconcat (object_path, "/SearchProvider", NULL);

  bar_search_provider_dbus_unexport (self->search_provider,
                                     connection,
                                     search_provider_path);

  G_APPLICATION_CLASS (bar_application_parent_class)
    ->dbus_unregister (application, connection, object_path);
}

您需要创建搜索提供程序对象作为接口的实现

G_DECLARE_FINAL_TYPE (BarSearchProvider, bar_search_provider, BAR, SEARCH_PROVIDER, GObject)

struct _BarSearchProvider {
  GObject parent_instance;
  ShellSearchProvider2 *skeleton;
};

G_DEFINE_TYPE (BarSearchProvider, bar_search_provider, G_TYPE_OBJECT)

static void
bar_search_provider_init (BarSearchProvider *self)
{
  self->skeleton = shell_search_provider2_skeleton_new ();

  g_signal_connect_swapped (self->skeleton,
                            "handle-activate-result",
                            G_CALLBACK (bar_search_provider_activate_result),
                            self);
  g_signal_connect_swapped (self->skeleton,
                            "handle-get-initial-result-set",
                            G_CALLBACK (bar_search_provider_get_initial_result_set),
                            self);
  g_signal_connect_swapped (self->skeleton,
                            "handle-get-subsearch-result-set",
                            G_CALLBACK (bar_search_provider_get_subsearch_result_set),
                            self);
  g_signal_connect_swapped (self->skeleton,
                            "handle-get-result-metas",
                            G_CALLBACK (bar_search_provider_get_result_metas),
                            self);
  g_signal_connect_swapped (self->skeleton,
                            "handle-launch-search",
                            G_CALLBACK (bar_search_provider_launch_search),
                            self);
}

重要提示

如果您的 D-Bus 方法处理程序是异步的,那么您应该使用 g_application_hold() 保留对 BarApplication 的引用,然后在完成操作后使用 g_application_release() 释放引用。使用相应的 shell_search_provider2_complete* 方法来指示完成并返回结果(如果有)。

这是一对用于导出和取消导出骨架的便利包装方法

gboolean
bar_search_provider_dbus_export (BarSearchProvider *self,
                                 GDBusConnection *connection,
                                 const gchar *object_path,
                                 GError **error)
{
  return g_dbus_interface_skeleton_export (
    G_DBUS_INTERFACE_SKELETON (self->skeleton),
    connection,
    object_path,
    error);
}

void
bar_search_provider_dbus_unexport (BarSearchProvider *self,
                                   GDBusConnection *connection,
                                   const gchar *object_path)
{
  if (g_dbus_interface_skeleton_has_connection (
        G_DBUS_INTERFACE_SKELETON (self->skeleton),
        connection))
    {
      g_dbus_interface_skeleton_unexport_from_connection (
        G_DBUS_INTERFACE_SKELETON (self->skeleton),
        connection);
    }
}