如何阅读dbus-monitor的输出?

我正在玩弄dbus-monitor,试图了解在Ubuntu环境中dbus的工作原理。关于这一点,我有几个问题:
1. 请问你能告诉我如何正确阅读以下内容吗?我理解大概的意思,但细节还不太清楚。
信号发送者=:1.1948 -> 目标=(空目标) 序列号=1829990 路径=/org/ayatana/menu/DA00003; 接口=org.ayatana.dbusmenu; 成员=ItemPropertyUpdated 整数32位 23 字符串 "enabled" 变量 布尔值 true 方法调用发送者=:1.6 -> 目标=org.freedesktop.Notifications 序列号=1399 路径=/org/freedesktop/Notifications; 接口=org.freedesktop.Notifications; 成员=GetCapabilities
我知道第一个是一个信号,而第二个是一个方法。"目标"是否意味着信号可以有一个特定的接收者/插槽?"成员"是什么意思?列表中跟随信号的项目是传递给信号的参数吗?"发送者"和"序列号"又是什么?
2. 我注意到音量控制和通知之间的关系。从我从dbus-monitor输出中读到的内容来看,
方法调用发送者=:1.6 -> 目标=org.freedesktop.Notifications 序列号=1400 路径=/org/freedesktop/Notifications; 接口=org.freedesktop.Notifications; 成员=Notify 字符串 "gnome-settings-daemon" 无符号整数32位 0 字符串 "notification-audio-volume-medium" 字符串 " " 字符串 "" 数组 [ ] 数组 [ 字典项( 字符串 "value" 变量 整数32位 38 ) 字典项( 字符串 "x-canonical-private-synchronous" 变量 字符串 "volume" ) ] 整数32位 -1
似乎通知是由它的方法触发的。我只是不太明白为什么会这样工作。在我看来,如果有一个信号被发出"notification-audio-volume-medium",而通知监听该信号并相应地做出反应,那将更有意义。如果发送/接收是公开的而不是私有的,是否会提供更多的灵活性和效率?例如,如果有一个公共信号"notification-audio-volume-medium",那么几个应用程序可以监听该信号(这将允许竞争的通知应用程序出现),而开发人员只需关注发送信号,而接收和处理信号将成为通知应用程序(或任何其他需要这些信号的程序)的责任。
3. 我刚接触DBus,并希望学到更多,因为我正在使用Python上的DBus,主要是为了开发一些小部件。我看过dbus-python教程,它教如何监听所有信号(不指定接口或路径等)。但是如何像dbus-monitor一样跟踪方法调用呢?
如果你有耐心教我怎么做,那就欢迎。
2个回答

D-Bus介绍

D-Bus提供了在服务之间进行通信的方式。服务可以是匿名的(仅通过总线地址来标识,例如:1.6),也可以获得众所周知的名称,例如org.freedesktop.Notifications或org.freedesktop.NetworkManager。您在日志中看到的发送者和目标都是服务。"Null destination"表示广播:将消息传递给所有服务。
一个服务可以向总线导出一个或多个对象。对象被赋予对象路径,例如/org/freedesktop/NetworkManager/ActiveConnection/1或/org/ayatana/menu/DA00003。对象路径使用斜杠作为分隔符,类似于文件系统路径。
每个对象可以支持一个或多个接口。接口只是一组方法和信号,俗称成员(与面向对象编程中的接口非常相似)。方法和信号具有固定的签名。成员始终在众所周知的接口名称中进行命名空间划分。
一旦发布,众所周知的名称将永远不会更改。
任何服务都可以连接到另一个服务的信号并异步调用其方法。任何服务都可以发出信号。

信号

现在回答你的具体问题。

信号发送者=:1.1948 -> 目标=(空目标) 序列号=1829990 路径=/org/ayatana/menu/DA00003; 接口=org.ayatana.dbusmenu; 成员=ItemPropertyUpdated
整数32 23
字符串 "enabled"
变量 布尔值 true

是的,你说得对,这是一个信号。它由服务:1.1948广播,并且"self"对象是/org/ayatana/menu/DA00003。该信号的名称是ItemPropertyUpdated,在接口org.ayatana.dbusmenu中定义(类似于C++中的org.ayatana.dbusmenu::ItemPropertyUpdated)。序列号,我猜想,是总线上事件的一种唯一标识符。

然后我们看到信号参数。根据接口文档,第一个int32参数是项目的id,第二个字符串是其属性名称,第三个变量是属性值。所以,/org/ayatana/menu/DA00003对象通知我们,项目id为23的项将其enabled属性更改为true。
另一个信号的例子:
信号发送者=:1.1602 -> 目标=(空目标) 序列号=20408 路径=/im/pidgin/purple/PurpleObject; 接口=im.pidgin.purple.PurpleInterface; 成员=SendingChatMsg int32 47893 string "test" uint32 1 信号发送者=:1.1602 -> 目标=(空目标) 序列号=20409 路径=/im/pidgin/purple/PurpleObject; 接口=im.pidgin.purple.PurpleInterface; 成员=IrcSendingText int32 64170 string "PRIVMSG #chat :test
我使用Pidgin向IRC频道发送了一条文本消息"test",并且/im/pidgin/purple/PurpleObject在im.pidgin.purple.PurpleInterface接口下发出了两个信号:首先是一个通用的SendingChatMsg,然后是一个更具体的IrcSendingText。
方法
现在讲解方法。方法是一种请求D-Bus对象执行某些操作或查询并返回数据的方式。它们与经典面向对象编程中的方法非常相似,只是D-Bus方法是异步调用的。
让我们以编程方式调用一个 D-Bus 方法。
import dbus, dbus.proxies

#-- connect to the session bus (as opposed to the system bus)
session = dbus.SessionBus()

#-- create proxy object of D-Bus object
obj_proxy = dbus.proxies.ProxyObject(conn=session,
         bus_name="org.freedesktop.Notifications",     #-- name of the service we are retrieving object from
         object_path="/org/freedesktop/Notifications") #-- the object path

#-- create proxy object of the D-Bus object wrapped into specific interface
intf_proxy = dbus.proxies.Interface(obj_proxy, "org.freedesktop.Notifications")

#-- lastly, create proxy object of the D-Bus method
method_proxy = intf_proxy.get_dbus_method("Notify")

#-- ... and call the method
method_proxy("test from python",
             dbus.UInt32(0),
             "bluetooth",     #-- icon name
             "Notification summary",
             "Here goes notification body",
             [], {},
             5) #-- timeout

注意参数,特别是图标名称。在你的例子中,"notification-audio-volume-medium" 是中音量扬声器的图标。

自定义服务

完全可以运行自己的 D-Bus 服务,导出自己的 D-Bus 对象,并定义自己的 D-Bus 接口,包括自己的方法和信号。一旦掌握了整体概念并阅读了 dbus 模块的文档,这些都可以在 Python 中很容易地完成。:)


欢迎讨论,不过我可能在一两天内不能回复。 - ulidtko
1谢谢 :) 这解释了很多问题。有点有趣的是,发送者可以匿名,当我使用 DFeet 时,每个发送者都有一个对应的进程名称,但这在 dbus-monitor 的输出中并没有体现出来。进程能被追踪吗? 现在用 Python 我看到我可以发送信号或提供方法或触发其他方的方法。是否也可以拦截方法?假设我想要查看程序 A 是否触发了 B 的 Dbus 方法,并对其进行处理,这可行吗? - neydroydrec
关于通知:notify-osd是被其他应用程序被动触发的,而不是主动寻找信号。这样做是否不切实际,或者我对Dbus有什么误解?我想制作一个能够替代notify-osd并将通知收集到某种收件箱中的应用程序。我可以通过监听信号来拦截通知吗? - neydroydrec
@Benjamin,嗯,当你想要拦截针对外部服务的方法调用时,很可能会认为这是一个有缺陷的设计。你应该做的是编写一个程序,提供 org.freedesktop.Notifications 服务。这样,所有对该服务的方法调用都将由你的代码处理。 - ulidtko
"Self" object是什么东西? - kawing-chiu
@kawing-chiu 你是什么意思?我了解在Python函数和方法中self-命名参数的约定,但我不明白你问题的背景。 - ulidtko
在你的回答中,有一行是...,"self"对象是/org/ayatana/menu/DA00003。我想知道那行中的self对象是什么意思。 - kawing-chiu
@kawing-chiu 啊...抱歉,我写这个已经有一段时间了。重点是D-Bus显然尝试了一个“面向对象”的API建模。在接收信号的情况下,我们不能真正说/org/ayatana/menu/DA00003 对象 是“发射器”或“发送者”,因为发送者是 服务,即 :1.1948。所以,如果我们要遵循传统的面向对象命名法,我们可能希望将那个 /org... 对象 称为 self。如果你知道JavaScript中的this是如何工作的,这非常类似(基本上是同样的情况)。 - ulidtko

我也在寻找通过dbus和Python脚本收集桌面通知的解决方案。这个问题是我在谷歌搜索中找到的最接近的答案,但是编写一个替代notify-osd的程序似乎有点过头了 :)
通过查看recent-notifications小程序的源代码,我得到了一些关于如何监控dbus消息的提示,下面是我想出来的Python实现:
import gtk
import dbus
from dbus.mainloop.glib import DBusGMainLoop

def filter_cb(bus, message):
    # the NameAcquired message comes through before match string gets applied
    if message.get_member() != "Notify":
        return
    args = message.get_args_list()
    # args are
    # (app_name, notification_id, icon, summary, body, actions, hints, timeout)
    print("Notification from app '%s'" % args[0])
    print("Summary: %s" % args[3])
    print("Body: %s", args[4])


DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()
bus.add_match_string(
    "type='method_call',interface='org.freedesktop.Notifications',member='Notify'")
bus.add_message_filter(filter_cb)
gtk.main()

希望这对某人有所帮助,因为似乎没有太多与监控dbus消息相关的简单Python示例。

1这确实帮了我很多!非常感谢!对你有几个建议:type='method_call'是不必要的,因为通知只使用方法调用。规范中没有信号。另外,member='Notify'也是不必要的,因为你已经在函数中过滤掉了它(正如你正确地说的那样,由于第一个NameAquired消息,你无法避免这种情况)。 - MestreLion