Skip to content

Commit 66d97ae

Browse files
mechmindjsouthworth
authored andcommitted
always invoke (Add|Remove)MatchSignal on bus object, add additional filter options
1 parent 22e5df4 commit 66d97ae

File tree

2 files changed

+145
-10
lines changed

2 files changed

+145
-10
lines changed

object.go

+54-10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ type BusObject interface {
1313
CallWithContext(ctx context.Context, method string, flags Flags, args ...interface{}) *Call
1414
Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call
1515
GoWithContext(ctx context.Context, method string, flags Flags, ch chan *Call, args ...interface{}) *Call
16+
AddMatchSignal(iface, member string, options ...MatchOption) *Call
17+
RemoveMatchSignal(iface, member string, options ...MatchOption) *Call
1618
GetProperty(p string) (Variant, error)
1719
Destination() string
1820
Path() ObjectPath
@@ -35,23 +37,65 @@ func (o *Object) CallWithContext(ctx context.Context, method string, flags Flags
3537
return <-o.createCall(ctx, method, flags, make(chan *Call, 1), args...).Done
3638
}
3739

38-
// AddMatchSignal subscribes BusObject to signals from specified interface and
39-
// method (member).
40-
func (o *Object) AddMatchSignal(iface, member string) *Call {
41-
return o.Call(
40+
// MatchOption specifies option for dbus routing match rule. Options can be constructed with WithMatch* helpers.
41+
// For full list of available options consult
42+
// https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules
43+
type MatchOption struct {
44+
key string
45+
value string
46+
}
47+
48+
// WithMatchOption creates match option with given key and value
49+
func WithMatchOption(key, value string) MatchOption {
50+
return MatchOption{key, value}
51+
}
52+
53+
// WithMatchObjectPath creates match option that filters events based on given path
54+
func WithMatchObjectPath(path ObjectPath) MatchOption {
55+
return MatchOption{"path", string(path)}
56+
}
57+
58+
func formatMatchOptions(options []MatchOption) string {
59+
items := make([]string, 0, len(options))
60+
for _, option := range options {
61+
items = append(items, option.key+"='"+option.value+"'")
62+
}
63+
64+
return strings.Join(items, ",")
65+
}
66+
67+
// AddMatchSignal subscribes BusObject to signals from specified interface,
68+
// method (member). Additional filter rules can be added via WithMatch* option constructors.
69+
// Note: To filter events by object path you have to specify this path via an option.
70+
func (o *Object) AddMatchSignal(iface, member string, options ...MatchOption) *Call {
71+
base := []MatchOption{
72+
{"type", "signal"},
73+
{"interface", iface},
74+
{"member", member},
75+
}
76+
77+
options = append(base, options...)
78+
return o.conn.BusObject().Call(
4279
"org.freedesktop.DBus.AddMatch",
4380
0,
44-
"type='signal',interface='"+iface+"',member='"+member+"'",
81+
formatMatchOptions(options),
4582
)
4683
}
4784

48-
// RemoveMatchSignal unsubscribes BusObject to signals from specified interface and
49-
// method (member).
50-
func (o *Object) RemoveMatchSignal(iface, member string) *Call {
51-
return o.Call(
85+
// RemoveMatchSignal unsubscribes BusObject from signals from specified interface,
86+
// method (member). Additional filter rules can be added via WithMatch* option constructors
87+
func (o *Object) RemoveMatchSignal(iface, member string, options ...MatchOption) *Call {
88+
base := []MatchOption{
89+
{"type", "signal"},
90+
{"interface", iface},
91+
{"member", member},
92+
}
93+
94+
options = append(base, options...)
95+
return o.conn.BusObject().Call(
5296
"org.freedesktop.DBus.RemoveMatch",
5397
0,
54-
"type='signal',interface='"+iface+"',member='"+member+"'",
98+
formatMatchOptions(options),
5599
)
56100
}
57101

object_test.go

+91
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,94 @@ func TestObjectGoWithContext(t *testing.T) {
5757
t.Fatal("Expected call to respond in 1 Millisecond")
5858
}
5959
}
60+
61+
type nopServer struct{}
62+
63+
func (_ nopServer) Nop() *Error {
64+
return nil
65+
}
66+
67+
func fetchSignal(t *testing.T, ch chan *Signal, timeout time.Duration) *Signal {
68+
select {
69+
case sig := <-ch:
70+
return sig
71+
case <-time.After(timeout):
72+
t.Fatalf("Failed to fetch signal in specified timeout %s", timeout)
73+
}
74+
return nil
75+
}
76+
77+
func TestObjectSignalHandling(t *testing.T) {
78+
bus, err := SessionBus()
79+
if err != nil {
80+
t.Fatalf("Unexpected error connecting to session bus: %s", err)
81+
}
82+
83+
name := bus.Names()[0]
84+
path := ObjectPath("/org/godbus/DBus/TestSignals")
85+
otherPath := ObjectPath("/org/other-godbus/DBus/TestSignals")
86+
iface := "org.godbus.DBus.TestSignals"
87+
otherIface := "org.godbus.DBus.OtherTestSignals"
88+
err = bus.Export(nopServer{}, path, iface)
89+
if err != nil {
90+
t.Fatalf("Unexpected error registering nop server: %v", err)
91+
}
92+
93+
obj := bus.Object(name, path)
94+
obj.AddMatchSignal(iface, "Heartbeat", WithMatchObjectPath(obj.Path()))
95+
96+
ch := make(chan *Signal, 5)
97+
bus.Signal(ch)
98+
99+
go func() {
100+
defer func() {
101+
if err := recover(); err != nil {
102+
t.Errorf("Catched panic in emitter goroutine: %v", err)
103+
}
104+
}()
105+
106+
// desired signals
107+
bus.Emit(path, iface+".Heartbeat", uint32(1))
108+
bus.Emit(path, iface+".Heartbeat", uint32(2))
109+
// undesired signals
110+
bus.Emit(otherPath, iface+".Heartbeat", uint32(3))
111+
bus.Emit(otherPath, otherIface+".Heartbeat", uint32(4))
112+
bus.Emit(path, iface+".Updated", false)
113+
// sentinel
114+
bus.Emit(path, iface+".Heartbeat", uint32(5))
115+
116+
time.Sleep(100 * time.Millisecond)
117+
bus.Emit(path, iface+".Heartbeat", uint32(6))
118+
}()
119+
120+
checkSignal := func(sig *Signal, value uint32) {
121+
if sig.Path != path {
122+
t.Errorf("signal.Path mismatch: %s != %s", path, sig.Path)
123+
}
124+
125+
name := iface + ".Heartbeat"
126+
if sig.Name != name {
127+
t.Errorf("signal.Name mismatch: %s != %s", name, sig.Name)
128+
}
129+
130+
if len(sig.Body) != 1 {
131+
t.Errorf("Invalid signal body length: %d", len(sig.Body))
132+
return
133+
}
134+
135+
if sig.Body[0] != interface{}(value) {
136+
t.Errorf("signal value mismatch: %d != %d", value, sig.Body[0])
137+
}
138+
}
139+
140+
checkSignal(fetchSignal(t, ch, 50*time.Millisecond), 1)
141+
checkSignal(fetchSignal(t, ch, 50*time.Millisecond), 2)
142+
checkSignal(fetchSignal(t, ch, 50*time.Millisecond), 5)
143+
144+
obj.RemoveMatchSignal(iface, "Heartbeat", WithMatchObjectPath(obj.Path()))
145+
select {
146+
case sig := <-ch:
147+
t.Errorf("Got signal after removing subscription: %v", sig)
148+
case <-time.After(200 * time.Millisecond):
149+
}
150+
}

0 commit comments

Comments
 (0)