go中使用dbus和gsettings
目录
dbus in go
使用 dbus 包
连接 dbus
dbus.SessionBus() dbus.SystemBus()
调用方法
比如调用 dbus-daemon 提供的对象的一个方法,服务名: org.freedesktop.DBus
, 路径: /org/freedesktop/DBus
,接口名: org.freedesktop.DBus
, 方法名: GetNameOwner
, 此方法输入参数是一个字符串,返回参数也是一个字符串。
sessionBus, err := dbus.SessionBus() obj := sessionBus.Object("org.freedesktop.DBus", "/org/freedesktop/DBus") var ret string err = obj.Call("org.freedesktop.DBus.GetNameOwner", 0, "com.deepin.dde.daemon.Dock").Store(&ret)
需要注意传给 obj.Call
方法的第一个参数 method
的值是接口名 + "." + 方法名。
在处理没有返回值的方法时,可以不调用 Store
方法,而直接检查 Err
字段。
获取和设置属性
属性的获取和设置本质也是方法调用。
获取属性
获取单个属性是调用对象的 org.freedesktop.DBus.Properties
接口的 Get
方法,此方法传入参数是接口名和属性名,返回值是属性值,类型为 dbus.Variant
。
下面的代码是要获取 org.freedesktop.DBus
接口的 Features
属性,此属性的类型是 []string
。
sessionBus, err := dbus.SessionBus() obj := sessionBus.Object("org.freedesktop.DBus", "/org/freedesktop/DBus") var ret dbus.Variant err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.DBus", "Features").Store(&ret) v := ret.Value().([]string)
从 ret
中获取内部的值,需要调用 Value
方法,获取 interface{}
类型的内部值,再使用类型断言 .([]string)
。
一次获取对象在某个接口下的全部属性,可调用 Get
方法旁边的 GetAll
方法。
设置属性
设置属性是调用对象的 org.freedesktop.DBus.Properties
接口的 Set
方法,此方法的传入参数是接口名、属性名、属性值,没有返回值。
下面的代码是要设置 org.freedesktop.DBus
接口的 Features
属性。
sessionBus, err := dbus.SessionBus() obj := sessionBus.Object("org.freedesktop.DBus", "/org/freedesktop/DBus") propVal := dbus.MakeVariant([]string{"abc"}) err = obj.Call("org.freedesktop.DBus.Properties.Set", 0, "org.freedesktop.DBus", "Features", propVal).Err
传入参数中属性值是 dbus.Variant
类型,制造这种类型可用 dbus.MakeVariant
方法。
监控信号
调用 AddMatch
方法添加监控规则,如果不需要监控了,就调用 RemoveMatch
方法去掉监控规则。
go 的 dbus 库提供了方便的方法: AddMatchSignal
和 RemoveMatchSignal
。
下面的代码是要监控 Session Bus
上的 /org/freedesktop/Bus
对象的 org.freedesktop.DBus
接口的 NameOwnerChanged
信号。
sessionBus, err := dbus.SessionBus() err = sessionBus.BusObject().AddMatchSignal("org.freedesktop.DBus", "NameOwnerChanged", dbus.WithMatchObjectPath("/org/freedesktop/DBus")).Err signalCh := make(chan *dbus.Signal, 10) sessionBus.Signal(signalCh) go func() { for { select { case sig := <-signalCh: log.Printf("sig: %#v\n", sig) if sig.Path == "/org/freedesktop/DBus" && sig.Name == "org.freedesktop.DBus.NameOwnerChanged" { var name string var oldOwner string var newOwner string err = dbus.Store(sig.Body, &name, &oldOwner, &newOwner) log.Printf("%s %s %s\n", name, oldOwner, newOwner) } } } }() time.Sleep(100*time.Second) err = sessionBus.BusObject().RemoveMatchSignal("org.freedesktop.DBus", "NameOwnerChanged", dbus.WithMatchObjectPath("/org/freedesktop/Bus")).Err sessionBus.RemoveSignal(signalCh)
将调用 sessionBus.Signal
方法注册 signalCh
通道,以此来通过该通道接收信号, go func ()
中的代码是循环接收 signalCh
通道传来的数据并处理。如果不需要再从这个通道接收信号了,可以调用 sessionBus.RemoveSignal
取消注册 signalCh
通道,这样信号就不会再被发送到 signalCh
通道中了。
在处理信号时,可以先验证信号( *dbus.Signal
类型)的 Path
和 Name
字段,不必验证 Sender
字段。 处理信号的 Body
字段,它是 []interface{}
类型,就可以用 dbus.Store
方法了。
导出对象
将自己代码中的一个变量作为对象的一个接口导出在 DBus 上,然后供其他人调用。
下面的代码是要把类型为 Obj
结构的 obj
变量作为对象的一个接口导出,它有一个 GetString
方法,路径 /p1/p2/p3
,接口名 p1.p2.p3
。
type Obj struct { } func (o *Obj) GetString() (string, *dbus.Error) { return "object", nil } func main() { sessionBus, err := dbus.SessionBus() obj := &Obj{} err = sessionBus.Export(obj, "/p1/p2/p3", "p1.p2.p3") log.Print("names:", sessionBus.Names()) select { } }
程序运行后会打印类似的输出:
names:[:1.4239 :1.4239]
这是此程序目前在 DBus 上的名字,冒号 :
开头的是唯一名。
导出对象接口,可以调用 sessionBus.Export
方法,要停止导出,并没有一个专门的的方法,依旧要使用 Export
方法, 但是第一个参数(之前放 obj
的位置)要换成 nil
。
自动导出的方法有如下要求:
- 公开的,方法名第一字母是大写的。
- 最后一个返回值是 *dbus.Error 类型。
注册服务名
下面的代码是要申请一个服务名 p1.p2.p3
, 不是完整代码,可以插在前一份代码的 sessionBus.Export
方法调用之后。现有的各个项目里也一般是先导出主要对象,然后再申请服务名。
_, err = sessionBus.RequestName("p1.p2.p3", 0)
发送信号
下面的代码是要发送信号,路径 /p1/p2/p3
, 接口名 p1.p2.p3
, 信号名 Signal1
, 参数是字符串和数字。
sessionBus, err := dbus.SessionBus() for { err = sessionBus.Emit("/p1/p2/p3", "p1.p2.p3.Signal1", "arg1", 2) time.Sleep(2*time.Second) }
程序每 2 秒发送一次信号,发送信号时,不要求对象是否已经导出。
使用 go-dbus-factory 项目
此项目实现了根据 dbus 服务的自省 XML 文件和一点配置文件,自动生成访问 dbus 服务的 go 代码。
项目地址:https://gitlab.deepin.io/dde-v20/go-dbus-factory ,可以打成 deb 包,软件包名 golang-github-linuxdeepin-go-dbus-factory-dev
,每个服务放在不同的包中,包名的前缀是 github.com/linuxdeepin/go-dbus-factory/
。
方法调用
下面的代码要调用 com.deepin.dde.daemon.Dock
服务的 /com/deepin/dde/daemon/Dock
对象的 RequestDock
方法。
sessionBus, err := dbus.SessionBus() dockObj := dock.NewDock(sessionBus) ok, err := dockObj.RequestDock(0, "/usr/share/applications/deepin-editor.desktop", -1) log.Println("ok:", ok)
需要注意传给 obj.Call
方法的第一个参数 method
的值是接口名 + "." + 方法名。
在处理没有返回值的方法时,可以不调用 Store
方法,而直接检查 Err
字段。
获取和设置属性
属性的获取和设置本质也是方法调用。
获取属性
获取单个属性是调用对象的 org.freedesktop.DBus.Properties
接口的 Get
方法,此方法传入参数是接口名和属性名,返回值是属性值,类型为 dbus.Variant
。
下面的代码是要获取 org.freedesktop.DBus
接口的 Features
属性,此属性的类型是 []string
。
sessionBus, err := dbus.SessionBus() obj := sessionBus.Object("org.freedesktop.DBus", "/org/freedesktop/DBus") var ret dbus.Variant err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.DBus", "Features").Store(&ret) v := ret.Value().([]string) log.Println(v)
从 ret
中获取内部的值,需要调用 Value
方法,获取 interface{}
类型的内部值,再使用类型断言 .([]string)
。
一次获取对象在某个接口下的全部属性,可调用 Get
方法旁边的 GetAll
方法。
设置属性
设置属性是调用对象的 org.freedesktop.DBus.Properties
接口的 Set
方法,此方法的传入参数是接口名、属性名、属性值,没有返回值。
下面的代码是要设置 org.freedesktop.DBus
接口的 Features
属性。
sessionBus, err := dbus.SessionBus() obj := sessionBus.Object("org.freedesktop.DBus", "/org/freedesktop/DBus") propVal := dbus.MakeVariant([]string{"abc"}) err = obj.Call("org.freedesktop.DBus.Properties.Set", 0, "org.freedesktop.DBus", "Features", propVal).Err
监控信号
下面的代码是要监控之前说过的 Dock
对象的 EntryAdded
信号。
sessionBus, err := dbus.SessionBus() dockObj := dock.NewDock(sessionBus) sigLoop := dbusutil.NewSignalLoop(sessionBus, 10) sigLoop.Start() dockObj.InitSignalExt(sigLoop, true) sigHid, err := dockObj.ConnectEntryAdded(func(entryPath dbus.ObjectPath, index int32) { log.Println(entryPath, index) }) time.Sleep(100*time.Second) sigLoop.Stop() dockObj.RemoveHandler(sigHid)
程序运行后,从启动器打开一个任务栏上之前没有的应用,然后程序就能收到 EntryAdded
信号并打印参数值出来。
这部分代码用到了新的包 pkg.deepin.io/lib/dbusutil
包,使用它的 SignalLoop
功能来处理信号。调用 dbusutil.NewSignalLoop
创建一个信号循环,然后调用 Start
方法开始,当不需要时调用 Stop
结束。
dockObj
默认是不支持监听信号的,直接调用 dockObj.ConnectXXX
方法会崩溃,必须先调用 InitSignalExt
方法,传入 SignalLoop。
使用 dbusutil 包
之前介绍了使用 dbus1 包,现在介绍使用更多的 dbusutil 包,它是对 dbus1 包的功能的一次包装,更加偏向于导出服务。
导出对象
type Obj struct { methods *struct { GetString func() `out:"result"` } } func (o *Obj) GetString() (string, *dbus.Error) { return "object", nil } func (o *Obj) GetInterfaceName() string { return "p1.p2.p3" } func main() { service, err := dbusutil.NewSessionService() obj := &Obj{ } err = service.Export("/p1/p2/p3", obj) err = service.RequestName("p1.p2.p3") service.Wait() }
在 main
方法中,使用 dbusutil.NewSessionService
创建 *dbusutil.Service
类型的变量 service
,然后调用 service.Export
方法导出 obj
变量。要导出 obj
变量,就得让它的类型 *Obj
实现 GetInterfaceName
方法,此方法要返回接口名。
定义属性
定义一个属性的简单方式是,给导出的结构增加公开字段,比如想要导出 Obj
结构,导出的接口 p1.p2.p3
中会有一个 Name
属性,类型为字符串,就要有如下定义:
type Obj struct { Name string }
这个 Name
属性是只读的,如果要让它可以写、设置,就要给 Name
字段加上结构字段 tag
: prop:"access:rw"
, 如下:
type Obj struct { Name string `prop:"access:rw"` }
发送信号
下面的代码是要发送信号,路径 /p1/p2/p3
, 接口名 p1.p2.p3
, 信号名 Signal1
, 参数是字符串和数字。
type Obj struct { signals *struct { Added struct { arg0 string arg1 int32 } } } func (o *Obj) GetInterfaceName() string { return "p1.p2.p3" } func main() { service, err := dbusutil.NewSessionService() obj := &Obj{ } err = service.Export("/p1/p2/p3", obj) err = service.RequestName("p1.p2.p3") for { err = service.Emit(obj, "Added", "val0", 11) time.Sleep(2*time.Second) } }
要点是在定义 Obj 结构时,加上 signals
字段,并在 signals
字段类型定义结构中加上 Added
字段 ,然后调用 service.Emit
方法发送信号。
发送属性改变信号
下面的代码是要发送属性改变信号,路径 /p1/p2/p3
, 接口名 p1.p2.p3
, 属性名 Name
,类型字符串。
type Obj struct { Name string } func (o *Obj) GetInterfaceName() string { return "p1.p2.p3" } func main() { service, err := dbusutil.NewSessionService() obj := &Obj{ } err = service.Export("/p1/p2/p3", obj) err = service.RequestName("p1.p2.p3") for { err = service.EmitPropertyChanged(obj, "Name", "name1") time.Sleep(2*time.Second) } }
要点是先在 Obj 结构中定义一个属性 Name
,然后调用 service.EmitPropertyChanged
方法发送属性改变信号。
gsettings in go
gsettings 是 gio 提供的功能,go 语言要用就得用 cgo 包装一下,这部分代码是由 go-gir-generator
工具自动生成的,软件包 golang-gir-gio-2.0-dev
提供了源码文件,安装之后放在 /usr/share/gocode/src/pkg.deepin.io/gir/gio-2.0
文件夹下。
项目地址: https://gitlab.deepin.io/github-linuxdeepin-mirror/go-gir-generator
简单使用
下面的代码是要获取和设置 gsettings ca.desrt.dconf-editor.Demo
的几个设置项。
gs := gio.NewSettings("ca.desrt.dconf-editor.Demo") str := gs.GetString("string") gs.SetString("string", str) int0 := gs.GetInt("integer-32-signed") gs.SetInt("integer-32-signed", int0) gs.Unref()
NewSettings
方法,创建 Settings 对象。
GetString
方法获取字符串类型的设置项的值,=SetString= 方法设置字符串类型的设置项的值。
GetInt
方法获取整数 int32 类型的设置项的值,=SetInt= 方法设置整数 int32 类型的设置项的值。
Unref
方法,在不再需要 gs 对象时,减少引用计数的值。
监听改变信号(原生)
下面的代码是要监听 gsettings ca.desrt.dconf-editor.Demo
的 boolean
设置项的值改变事件。
gs := gio.NewSettings("ca.desrt.dconf-editor.Demo") gs.Connect("changed::boolean", func(gs *gio.Settings, key string) { boolean := gs.GetBoolean(key) log.Println(boolean) }) glib.StartLoop()
调用 Connect
方法注册事件监听处理函数,第一个参数为事件名,第二个参数为事件处理函数。
glib.StartLoop()
开始一个 Main Loop 事件循环。
监听改变信号(dbus 实现)
由于原生的信号监听方式之前在一种特殊架构上偶尔出现发生了改变事件但不触发处理函数的问题,于是写了另外一种信号监听方式,来规避这种问题。
下面的代码使用了另外一个包 pkg.deepin.io/lib/gsettings
也实现了监听 boolean
设置项的值改变事件。
gs := gio.NewSettings("ca.desrt.dconf-editor.Demo") gsettings.ConnectChanged("ca.desrt.dconf-editor.Demo", "boolean", func(key string) { boolean := gs.GetBoolean(key) log.Println(boolean) }) err := gsettings.StartMonitor() select { }
ConnectChanged
方法注册改变事件的处理函数,第一个参数是 schema id
, 第二个参数是设置项名,第三个参数是事件处理函数。
StartMonitor
方法开始监听循环。
dbus 服务中作为属性
下面的代码是要导出 DBus 服务,服务名 p1.p2.p3
, 对象路径 /p1/p2/p3
, 接口名 p1.p2.p3
,把 gsettings ca.desrt.dconf-editor.Demo
的 boolean
设置项当作接口的属性。
type Obj struct { Boolean gsprop.Bool `prop:"access:rw"` } func (o *Obj) GetInterfaceName() string { return "p1.p2.p3" } func main() { gs := gio.NewSettings("ca.desrt.dconf-editor.Demo") service, err := dbusutil.NewSessionService() obj := &Obj{} obj.Boolean.Bind(gs, "boolean") log.Println("get result:", obj.Boolean.Get()) err = service.Export("/p1/p2/p3", obj) err = service.RequestName("p1.p2.p3") err = gsettings.StartMonitor() service.Wait() }
这算是 gsettings 与 dbusutil 包知识相结合的内容。
要点是 Obj 结构定义了一个公开字段 Name
,类型为 gsprop.Bool
,然后调用 Bind
方法绑定了 gs
的 boolean
设置项和 obj
的 Name
属性的关联,使得对 Name
属性的读写即是对 gs
的 boolean
设置项的读写, 如果 gs
的 boolean
设置项的值改变会自动发出 Name
属性改变信号。
gsprop 包中还有其他的类型,来支持各种数据类型,比如 String=,=Int=,=Uint
等。
同样要记得调用 StartMonitor
方法开始监听循环。