使用 D-Bus 控制您的 Linux 桌面

作者:Koen Vervloesem

每个现代 Linux 桌面环境都使用 D-Bus,这是一种允许软件应用程序相互通信的系统。 借助 D-Bus,您可以让您的桌面按照您想要的方式工作。 在本文中,我将演示 D-Bus 可以实现的一些功能。 准备好进行一些桌面调整吧。

D-Bus (桌面总线) 是一种进程间通信 (IPC) 系统,它为应用程序相互对话提供了一种机制。 D-Bus 设计者从头开始构建它,但深受 KDE 的 DCOP (桌面通信协议) 系统的影响。 目前,D-Bus 无处不在——KDE 4 已经放弃 DCOP 转而使用 D-Bus,而 GNOME 也在朝着 D-Bus 而不是其自身的 Bonobo 系统发展。 因此,D-Bus 已经成为一种与桌面无关的 IPC 机制。 使用 D-Bus 的软件可以无缝地与您的桌面集成,无论您使用哪种桌面环境。 D-Bus 是跨桌面项目 freedesktop.org 的一部分,Red Hat 是主要的贡献者。

借助 D-Bus,每个向其他程序提供服务的程序都会注册自己。 然后,其他程序可以查找哪些服务可用。 程序还可以注册自己以接收事件,例如某些系统服务会这样做,以检测热插拔硬件。

D-Bus 不允许直接的进程到进程通信,而是通过提供“总线”来工作。 总线守护进程在总线上的进程之间路由消息,这样,进程可以同时与一个或多个应用程序对话。 每个应用程序都可以向总线发送消息或侦听总线上的事件。

通常,D-Bus 创建两条总线:一条特权系统总线和一条会话总线。 系统总线允许在具有正确访问权限的进程之间进行系统范围的通信,其主要用途是将来自 HAL (硬件抽象层) 的事件传递给对硬件事件感兴趣的进程。 一些可能的硬件事件可能是通知已添加新的硬件设备或打印机队列已更改。 第二条总线,即会话总线,在您登录时创建,它允许您的应用程序进行通信。

D-Bus 对话

每个使用 D-Bus 的应用程序都会公开一些对象,这些对象通常映射到内部 GObject、C++ 或 Python 对象。 一个应用程序可以向另一个应用程序中的特定对象发送消息。 您可以使用唯一的路径名来寻址每个 D-Bus 对象,该路径名看起来像文件系统路径名。 为了确保每个应用程序都使用唯一的路径名,D-Bus 对象路径名通常以开发人员的域名为前缀,例如 /org/kde 或 /com/redhat。 Java 编程语言使用相同的系统和包名(例如,org.sun)。 D-Bus 路径由三部分组成:服务名称、对象路径和接口(我在本文后面会给出一些示例)。

那么,如何在您自己的应用程序中支持或使用 D-Bus 呢? 核心 API 是用 C 语言编写的,并且相当底层。 它实际上不是为应用程序程序员设计的。 不同的编程语言和环境在此外 API 之上构建了绑定层,例如 GLib、Qt、Python、Ruby、Perl 和 Mono。 我在这里不深入探讨 C 或 GLib(GNOME 的基础库)编程,但我会给出一些用脚本语言(如 Python 和 Ruby)以及 shell 脚本编写的示例。

哪些应用程序正在使用 D-Bus?

freedesktop.org 项目在其网站上有一个不完整的使用 D-Bus 的应用程序列表,并且每个应用程序的总线名称也列在那里。 但是,您可以使用一些有趣的工具来帮助您探索自己系统上的 D-Bus,从而自行找到总线名称。 例如,Qt 有一个图形化的 D-Bus 浏览器,名为 qdbusviewer(图 1)。 在 Ubuntu 中,您可以在 qt4-dev-tools 软件包中找到该应用程序。 虽然它是 KDE 的一部分,但该应用程序在包括 GNOME 在内的其他桌面环境中也能完美运行。

Control Your Linux Desktop with D-Bus

图 1. 在 GNOME 上运行的 QDBusViewer

当您运行 qdbusviewer 时,它会显示两个选项卡:会话总线和系统总线。 在每个选项卡中,左侧窗格显示服务名称列表。 如果您单击服务名称,右侧窗格会显示有关该服务的信息,例如可用的方法和信号。 例如,如果您单击服务 org.freedesktop.PowerManagement,然后在右侧窗格中依次单击 org/、freedesktop/ 和 PowerManagement/ 层次结构,您将导航到 D-Bus 路径的两个部分:左侧窗格中的 org.freedesktop.PowerManagement 是服务名称,右侧窗格中的 org/freedesktop/PowerManagement 是对象路径。

对象路径在右侧窗格中还有最后一部分,即三个所谓的接口,它们具有点分隔的名称:org.freedesktop.DBus.Introspectable、org.freedesktop.DBus.Properties 和 org.freedesktop.PowerManagement。 每个接口都实现了一些方法和信号。 这些是您可以与之交互的内容。 在这里,我们对接口 org.freedesktop.PowerManagement 感兴趣,因为这个接口实现了具体的电源管理操作。 当您单击它时,您可以看到列表中所有已实现的方法和信号。 如果您单击 Suspend 方法,您的计算机将挂起,并且仅当您按下电源按钮时才会唤醒。

某些方法(例如 Shutdown、Reboot、Hibernate 和 Suspend)实现操作,而其他方法则为您提供有关系统的一些信息。 例如,org.freedesktop.PowerManagement 对象实现了一些方法,如 GetLowBattery、GetOnBattery、CanShutdown 等。 如果您的系统(笔记本电脑)正在电池供电但剩余电池时间足够,则单击 GetOnBattery 会在下面的窗格中给出答复“Arguments: true”,但如果您单击 GetLowBattery,则会给出答复“Arguments: false”。

值得指出的是,qdbusviewer 只能显示当前已注册的服务名称。 例如,如果您尚未启动 Pidgin,则查看器将不会列出 Pidgin 服务。 当您探索可以在系统上使用的 D-Bus 服务时,请考虑到这一点。

如果您更喜欢命令行方式,则不必启动 qdbusviewer。 命令行应用程序 qdbus 公开了相同的信息。 如果您在终端中运行qdbus您将获得会话总线上可用的服务名称列表。 如果您使用--system标志运行它,则会显示系统总线已知的服务。 如果您想知道服务公开的不同对象,请运行例如

$ qdbus org.freedesktop.PowerManagement
/
/org
/org/freedesktop
/org/freedesktop/PowerManagement
/org/freedesktop/PowerManagement/Backlight
/org/freedesktop/PowerManagement/Inhibit
/org/freedesktop/PowerManagement/Statistics

现在,如果您想知道 /org/freedesktop/PowerManagement 对象实现了哪些接口,请使用

$ qdbus org.freedesktop.PowerManagement \
            /org/freedesktop/PowerManagement

这将给出与您在 qdbusviewer 中看到的相同的方法和接口列表。 例如,行

method bool org.freedesktop.PowerManagement.GetOnBattery()

这个bool意味着此方法返回一个布尔值,该值可以是 true 或 false。 如果该方法不返回值,例如 org.freedesktop.PowerManagement.Suspend(),则该行会列出void而不是bool.

qdbus 还允许您直接调用这些方法。 例如,如果您想调用 Suspend 方法,请执行

$ qdbus org.freedesktop.PowerManagement \
            /org/freedesktop/PowerManagement \
            org.freedesktop.PowerManagement.Suspend
在命令行上玩转 D-Bus

在本文的其余部分,我将展示一些流行的应用程序公开的 D-Bus 功能,并编写一些脚本来与这些应用程序通信并自动化一些任务。 希望这能为您提供一些灵感,让您与自己喜欢的应用程序进行通信。 我使用不同的 D-Bus 工具和脚本语言来展示您可以使用 D-Bus 的不同方式。

我已经提到了使用 D-Bus 的第一种方法:使用 KDE 程序 qdbusviewer 和 qdbus。 但是,如果您不喜欢 KDE,则可以使用命令行程序 dbus-send 和 dbus-monitor 分别发送和监视 D-Bus 消息。 例如,您可以使用以下命令将系统置于挂起模式

$ dbus-send --dest=org.freedesktop.PowerManagement \
                /org/freedesktop/PowerManagement \
                org.freedesktop.PowerManagement.Suspend

如您所见,dbus-send 调用与 qdbus 的调用几乎相同。 唯一的区别是您必须为服务名称使用 --dest 参数。 但是,让我们看看一些新的东西。 如果您正在浏览器中观看长时间的 YouTube 视频,屏幕保护程序可能会启动,因为 Flash 插件不与系统的其余部分通信。 借助 D-Bus,您可以阻止这种烦人的行为。 神奇的命令是这个

$ dbus-send --print-reply \
            --dest=org.gnome.ScreenSaver / \
            org.gnome.ScreenSaver.Inhibit \
                string:"YouTube" \
                string:"Inhibit Screensaver"

使用此命令,您可以使用两个参数调用 org.gnome.ScreenSaver 接口的 Inhibit 方法。 第一个是应用程序的名称。 我在这里使用“YouTube”,但它可以是任意名称。 第二个参数是禁止屏幕保护程序的原因。 dbus-send 期望每个参数都以其类型为前缀,例如 string、boolean、int16 等。 这里的两个参数都是字符串。 我还使用了参数 --print-reply,因为我需要该命令的回复:Inhibit 方法返回一个 uint32 数字,这是一个标识禁止请求的“cookie”。 如果您想取消禁止屏幕保护程序,则必须将此 cookie 作为参数发送

$ dbus-send --dest=org.gnome.ScreenSaver / \
            org.gnome.ScreenSaver.UnInhibit \
                uint32:1234567890

使用这两个命令,您可以破解您自己的个人屏幕保护程序禁止 shell 脚本。 注意:您需要在第一个命令运行时将 cookie 保存到变量或文件中,然后在上面的命令中替换实际的 cookie 值。

如果您正在调试 D-Bus 脚本或观察其他 D-Bus 应用程序的方法和信号,则命令行程序 dbus-monitor 非常方便。 只需在终端中启动它,您就会看到所有 D-Bus 活动滚动显示。 dbus-monitor 可用于实时查看所有 D-Bus 活动。 因此,如果您的系统上发生某些事情,例如,您的网络中断,您可以在 dbus-monitor 的输出中看到此消息是如何发送到 D-Bus 总线的。 这样,您就知道要侦听哪些信号或调用哪些方法来利用相同的事件。

dbus-monitor 还允许您指定要监视的一组表达式——例如

$ dbus-monitor --system "interface='org.freedesktop.NetworkManager'"

这将监视所有 NetworkManager 事件。 我使用 --system 参数是因为 NetworkManager 使用系统总线。

编写 Liferea Feed 阅读器的脚本

Liferea Feed 阅读器有一小部分但很有趣的 D-Bus 方法。 最有趣的方法是 Subscribe,它允许您从另一个应用程序向 Liferea 添加 Feed。 使用此方法的一个应用程序是 FeedBag,这是一个 Firefox 扩展,它修改浏览器地址栏中的 Feed 按钮:如果您单击该按钮,它会将订阅添加到 Liferea 而不是 Live Bookmarks。 在幕后,这是因为 FeedBag 调用了 org.gnome.feed.Reader.Subscribe 方法。 您可以从终端执行相同的操作

$ feed="http://feeds2.feedburner.com/linuxjournalcom?format=xml"
$ dbus-send --dest=org.gnome.feed.Reader \
            /org/gnome/feed/Reader \
            org.gnome.feed.Reader.Subscribe \
                string:"$feed"

Liferea 提供了一个脚本 liferea-add-feed,它完全做到了这一点,但添加了一些错误处理。

Liferea 还通过 D-Bus 公开了一些信息,如果您有不使用 Liferea 通知区域的替代窗口管理器,这将非常有趣。 然后,您可以自行构建通知系统——只需询问 Liferea 中新的和未读的项目数量并显示输出

$ dbus-send --print-reply \
             --dest=org.gnome.feed.Reader \
             /org/gnome/feed/Reader \
             org.gnome.feed.Reader.GetNewItems

$ dbus-send --print-reply \
            --dest=org.gnome.feed.Reader \
            /org/gnome/feed/Reader \
            org.gnome.feed.Reader.GetUnreadItems
离开键盘

如果您想执行比调用单个方法更复杂的任务,您可以使用 dbus-send 命令编写 shell 脚本,或使用更高级的语言来简化任务。 有用于 Python、Ruby 和 Java 等语言的 D-Bus 绑定。

在下一个示例中,我将实现一个 Python 脚本,如果您的屏幕保护程序激活,该脚本会将您在 Pidgin 上的状态设置为“离开键盘”。 这展示了 D-Bus 的两个方面:脚本等待来自屏幕保护程序的信号,然后它调用 Pidgin 中的一个方法。 脚本如清单 1 所示。

清单 1. pidgin_screensaver.py

#!/usr/bin/env python

def pidgin_status_func(state):
    obj = bus.get_object("im.pidgin.purple.PurpleService",
                         "/im/pidgin/purple/PurpleObject")
    pidgin = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface")
    status = pidgin.PurpleSavedstatusFind("afk")
    if status == 0:
        status = pidgin.PurpleSavedstatusNew("afk", 5)
    if state:
        pidgin.PurpleSavedstatusSetMessage(status,
                                           "Away from keyboard")
        pidgin.PurpleSavedstatusActivate(status)

import dbus, gobject
from dbus.mainloop.glib import DBusGMainLoop

dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()

bus.add_signal_receiver(pidgin_status_func,
                        dbus_interface="org.gnome.ScreenSaver",
                        signal_name="ActiveChanged")

loop = gobject.MainLoop()
loop.run()

让我们剖析一下这个脚本。 函数 pidgin_status_func 设置您在 Pidgin 中的状态。 它从会话总线获取 im/pidgin/purple/PurpleObject 对象,然后获取 im.pidgin.purple.PurpleInterface 接口。 然后,它调用此接口的方法。 它首先检查名称为“afk”的状态类型是否存在来创建新的“已保存状态”类型,如果不存在,则创建它(“afk”代表“Away From Keyboard”,5 是“离开”状态类型)。

然后,该函数检查 state 变量,该变量是 pidgin_status_func 函数调用的参数(我稍后会解释此参数的含义)。 如果参数为 true,则它将新“afk”状态的状态消息设置为“Away from keyboard”并激活新状态。 效果是 Pidgin 将您的状态显示为“afk”,状态消息为“Away from keyboard”。

现在您需要在屏幕保护程序激活时调用此函数。 因此,启动 dbus 主循环并连接到会话总线。 然后,添加一个信号接收器,该接收器侦听来自 org.gnome.ScreenSaver 接口的信号 ActiveChanged。 如果/当信号触发时,它会调用 pidgin_status_func 函数。 由于 ActiveChanged 信号具有一个布尔参数,该参数表示屏幕保护程序的当前状态(1 表示活动,0 表示非活动),因此您在 pidgin_status_func 函数中定义了一个名为 state 的参数。 为了保持侦听,只要脚本正在运行,就让循环无限期地运行。

Pidgin 具有极其丰富的 D-Bus 接口; 您几乎可以用它做任何事情。 因此,让这个示例为您提供一些灵感,在 Pidgin 中完成一些创造性的任务!

玩转 D-Bus

让我们看另一个示例,这次是用 Ruby 编写的。 我们将创建一个脚本,将 Rhythmbox 中当前播放的歌曲显示为您在 Pidgin 中的状态(清单 2)。

清单 2. pidgin_rhythmbox.rb

#!/usr/bin/env ruby

require 'dbus'

bus = DBus::SessionBus.instance
rhythmbox = bus.service("org.gnome.Rhythmbox")
player = rhythmbox.object("/org/gnome/Rhythmbox/Player")
player.introspect
player.default_iface = "org.gnome.Rhythmbox.Player"

pidgin = bus.service("im.pidgin.purple.PurpleService")
purple = pidgin.object("/im/pidgin/purple/PurpleObject")
purple.introspect
purple.default_iface = "im.pidgin.purple.PurpleInterface"

player.on_signal("playingUriChanged") do |uri|
  status = purple.PurpleSavedstatusFind("rhythmbox").first
  if status == 0
    status = purple.PurpleSavedstatusNew("rhythmbox", 2).first
  end
  purple.PurpleSavedstatusSetMessage(status, uri.to_s)
  purple.PurpleSavedstatusActivate(status)
end

在这里,您会看到与我在 Python 脚本中使用的相同类型的命令:打开 D-Bus 会话,定义 D-Bus 服务、对象和接口,并且我定义了一个信号接收器。 并且,循环无限期地运行以保持侦听 D-Bus 信号。

当然,这可以稍微整理一下。 例如,您现在仅将歌曲的文件路径显示为状态消息。 我将留给读者从文件中提取相关的 ID3 标签并显示它们,而不是文件路径。

结论

现在您已经了解了如何执行 D-Bus 调用以及如何处理 D-Bus 信号,您可以开始自动化桌面上的任务了。 如果您是 Linux 高级用户,D-Bus 绝对应该在您的词汇表中。

关于 D-Bus 还有很多我无法在本文中向您展示的内容,但是借助 qdbusviewer、qdbus、dbus-send 和 dbus-monitor,您可以自行探索各种可能性。 如果您想使用 D-Bus 创建一些更复杂的自动化任务,Python 和 Ruby 编程语言是不错的选择。 考虑一下“资源”中提到的教程,然后就让您的想象力自由驰骋吧。

如果您是一名软件开发人员,那么我在这里没有涵盖的部分是注册服务。 这是 D-Bus 故事的另一面。 如果您注册服务,则可以通过 D-Bus 向其他应用程序提供对象。

Koen Vervloesem 自 2000 年以来一直是一名自由记者,主要撰写关于自由和开源软件的文章。 他拥有计算机科学和哲学硕士学位,无法决定哪个领域更有趣。 与此同时,他喜欢思考“我编码,故我在。”

加载 Disqus 评论