使用 GtkApplication

GtkApplication 是 GTK 应用程序的基类。

GtkApplication 的理念是,应用程序应该关注在响应用户操作时需要发生什么以及何时发生。操作系统启动应用程序的确切机制并不重要。

为此,GtkApplication 公开了一组应用程序应该响应的信号(或虚函数):

  • startup: 在应用程序首次启动时进行设置。

  • shutdown: 执行关闭任务。

  • activate: 显示应用程序的默认第一个窗口(例如一个新文档)。这对应于应用程序被桌面环境启动的情况。

  • open: 打开文件并在新窗口中显示它们。这对应于有人试图从文件浏览器或其他类似方式使用应用程序打开一个或多个文档的情况。

当您的应用程序启动时,将触发 startup 信号。这为您提供了一个执行与显示新窗口没有直接关系的初始化任务的机会。之后,根据应用程序的启动方式,将调用 activateopen

GtkApplication 默认应用程序为单实例。如果用户尝试启动单实例应用程序的第二个实例,则 GtkApplication 将向第一个实例发送信号,您将收到额外的 activate 或 open 信号。在这种情况下,第二个实例将立即退出,而不会调用 startupshutdown

所有启动初始化都应在 startup 中完成。这避免了在第二个实例情况下浪费工作,因为程序会立即退出。

应用程序将持续运行,直到不再需要运行为止。这通常是只要有任何打开的窗口存在。您还可以使用 g_application_hold() 强制应用程序保持活动状态。

在关闭时,您会收到 shutdown 信号,您可以在其中执行任何必要的清理任务(例如将文件保存到磁盘)。

应用程序的主入口点只应创建一个 GtkApplication 实例,设置信号处理程序,然后调用 g_application_run()

主实例与本地实例

应用程序的“主实例”是第一个运行的实例。“远程实例”是启动的另一个实例,它不是主实例。“本地实例”一词用于指代当前进程,它可能是也可能不是主实例。

GtkApplication 仅在主实例中发出信号。可以(并从“本地实例”的角度)在主实例或远程实例中调用 GtkApplication API。如果本地实例是主实例,则对 GtkApplication 的函数调用将导致在本地主实例中发出信号。如果本地实例是远程实例,则函数调用会导致消息发送到主实例并在那里发出信号。

例如,在主实例上调用 g_application_activate() 将发出 activate 信号。在远程实例上调用它将导致消息发送到主实例,主实例将发出 activate

您很少需要知道本地实例是主实例还是远程实例。在几乎所有情况下,您都应该调用您感兴趣的 GtkApplication 方法,它会根据需要转发或在本地处理。

操作

除了默认的 activate 和 open 之外,应用程序可以注册一组它支持的操作。使用 GActionMap 接口将操作添加到应用程序,并使用 GActionGroup 接口调用或查询。

activateopen 一样,在主实例上调用 g_action_group_activate_action() 将在当前进程中激活命名操作。在远程实例上调用 g_action_group_activate_action() 将向主实例发送消息,导致操作在那里被激活。

处理命令行

通常,GtkApplication 会假定命令行上传递的参数是要打开的文件。如果没有传递参数,则假定应用程序正在启动以显示其主窗口或一个空文档。如果给定了文件,您将通过 open 信号收到这些文件(以 GFile 的形式)。否则,您将收到一个 activate 信号。建议新应用程序利用这种默认的命令行参数处理方式。

如果您想以更高级的方式处理命令行参数,有几种(互补的)机制可以实现:

首先,将发出 handle-local-options 信号,信号处理程序将获得一个包含已解析选项的字典。要使用此功能,您需要使用 g_application_add_main_option_entries() 注册您的选项。信号处理程序可以返回一个非负值来以该退出代码结束进程,或者返回一个负值来继续正常的命令行选项处理。此信号的一个常见用途是实现一个 --version 参数,该参数无需与远程实例通信即可工作。

如果 handle-local-options 的灵活性不足以满足您的需求,您可以覆盖 local_command_line 虚函数以完全接管本地实例中的命令行参数处理。如果这样做,您将负责注册应用程序以及处理 --help 参数(默认的 local_command_line 函数会为您完成此操作)。

也可以在 handle-local-optionslocal_command_line 中响应命令行参数调用操作。例如,邮件客户端可以选择将 --compose 命令行参数映射到其 compose 操作的调用。这是通过从 local_command_line 实现中调用 g_action_group_activate_action() 来完成的。如果正在处理的命令行位于主实例中,则 compose 操作在本地调用。如果它是一个远程实例,则操作调用被转发到主实例。

注意

可以使用操作调用来代替 activateopen。应用程序完全有可能在没有发出 activate 信号的情况下启动。activate 信号仅被视为默认的“无选项启动”信号。操作旨在用于其他所有情况。

某些应用程序可能希望执行更高级的命令行处理,包括控制远程实例的生命周期及其退出状态,以及转发整个命令行参数、环境甚至标准输入、输出和错误流的内容。这可以通过使用 G_APPLICATION_HANDLES_COMMAND_LINEcommand-line 信号来实现。

添加自定义命令行选项

GApplication 支持解析使用 g_application_add_main_option_entries() 指定的附加命令行选项。调用此函数的理想位置是应用程序类的实例初始化函数中,或者如果您没有定义自己的类,则是在 g_application_new() 返回之后。

g_application_add_main_option_entries() 接受一个指向 GOptionEntry 结构体数组的指针。当遇到特定的命令行选项时,相应的 GOptionEntryarg_data 字段将被设置为解析此选项的结果。如果 arg_dataNULL,则该选项将存储在传递给 handle-local-options 信号处理程序(或虚函数)的 options 字典中。

您还可以使用 g_application_add_main_option()g_application_add_option_group() 指定附加命令行选项。

handle-local-options 处理程序预计会处理命令行选项。此处理程序可以执行以下几项操作:

  • 在本地处理选项并退出(成功或错误状态);典型的例子是 --version 选项。

  • 将选项视为对主实例执行操作的请求。

  • 将选项视为在主实例上打开一个或多个文件的请求。

  • 检查选项,发现它们不感兴趣,然后恢复正常处理。

handle-local-options 信号处理程序的返回值将决定 GApplication 接下来做什么。如果返回值为 -1,则继续默认处理(如上所述)。如果返回非负值,则表示选项已在本地处理,进程应退出(退出状态为返回值)。

使用 G_APPLICATION_HANDLES_COMMAND_LINE

通常情况下,当应用程序第二次启动时,本地实例与主实例之间的通信简短而简单——通常只是请求显示新窗口或打开一些文件。本地实例通常会立即退出。

G_APPLICATION_HANDLES_COMMAND_LINE 允许双方之间进行更复杂的交互。如有疑问,您不应使用 G_APPLICATION_HANDLES_COMMAND_LINE。在某些情况下可能需要使用它:

  • 您的应用程序需要将数据从主实例打印到远程实例的 stdout/stderr。

  • 应用程序的主实例需要控制远程实例的持续时间。

  • 应用程序的主实例需要从远程实例返回特定的退出状态。

  • 应用程序的主实例需要访问远程实例的环境变量或文件描述符(例如 stdin)。

  • 您可能会发现以这种方式将预解析的命令行选项传递给主实例更方便(尽管操作参数应该提供一种足够方便的方法,以更少的开销完成相同的任务)。

  • 将应用程序移植到 GApplication 时,您发现暂时以这种方式进行更容易。

这种应用程序的一个很好的例子是文本编辑器,它可能从 EDITOR 环境变量中使用。如果从 git commit 调用,远程实例必须在用户完成编辑文件后才能退出,即使用户已经打开了其他窗口并且仍然保持打开状态,它仍然需要退出。

如果设置了 G_APPLICATION_HANDLES_COMMAND_LINE,则在 handle-local-options 处理程序返回后,而不是将剩余的命令行参数解释为文件列表,这些参数通过 command-line 信号传递给主实例。在命令行选项解析过程中构建并可能由 handle-local-options 修改的选项数组会传递给主实例,可以在其中使用 g_application_command_line_get_options_dict() 访问它。

可以(通过在 command-line 处理程序内部使用 GOptionContext)在主实例中完成所有处理,但强烈不鼓励这样做。GOptionContext 的设计非常基于它只会运行一次、作用于 argcargv 的假设,这与 command-line 可以多次调用的事实不符。此外,直接从本地实例报告命令行解析中的错误更优雅,无需与主实例通信。最后,最好将选项注册到本地实例,以便 --help 输出会列出它们。尽管如此,如果您不使用 g_application_add_main_option_entries() 并且设置了 G_APPLICATION_HANDLES_COMMAND_LINE,那么任何未知选项都将被忽略并转发到主实例上的 command-line 信号。

警告

虽然 GOptionEntry 允许指定回调函数,以便在找到参数时调用(当与 GOptionContext 一起使用以手动解析命令行参数时),但在使用 GApplication 解析应用程序的命令行参数时,不允许使用此类选项条目。