diff --git a/prop/prop.go b/prop/prop.go index 3a80d82..a42fbae 100644 --- a/prop/prop.go +++ b/prop/prop.go @@ -278,32 +278,40 @@ func (p *Properties) Introspection(iface string) []introspect.Property { // set sets the given property and emits PropertyChanged if appropriate. p.mut // must already be locked. -func (p *Properties) set(iface, property string, v interface{}) error { - prop := p.m[iface][property] - err := dbus.Store([]interface{}{v}, prop.Value) +func (p *Properties) set(iface string, properties []string, v ...interface{}) error { + var props []interface{} + for _, property := range properties { + props = append(props, p.m[iface][property].Value) + } + err := dbus.Store(v, props...) if err != nil { return err } - return p.emitChange(iface, property) + return p.emitChange(iface, properties...) } -func (p *Properties) emitChange(iface, property string) error { - prop := p.m[iface][property] - switch prop.Emit { - case EmitFalse: - return nil // do nothing - case EmitInvalidates: - return p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged", - iface, map[string]dbus.Variant{}, []string{property}) - case EmitTrue: - return p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged", - iface, map[string]dbus.Variant{property: dbus.MakeVariant(prop.Value)}, - []string{}) - case EmitConst: - return nil - default: - panic("invalid value for EmitType") +func (p *Properties) emitChange(iface string, properties ...string) error { + changes := make(map[string]dbus.Variant) + invalidates := []string{} + + for _, property := range properties { + prop := p.m[iface][property] + switch prop.Emit { + case EmitFalse: + continue + case EmitInvalidates: + invalidates = append(invalidates, property) + case EmitTrue: + changes[property] = dbus.MakeVariant(prop.Value) + case EmitConst: + continue + default: + panic("invalid value for EmitType") + } } + + return p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged", + iface, changes, invalidates) } // Set implements org.freedesktop.Properties.Set. @@ -330,7 +338,7 @@ func (p *Properties) Set(iface, property string, newv dbus.Variant) *dbus.Error return err } } - if err := p.set(iface, property, newv.Value()); err != nil { + if err := p.set(iface, []string{property}, newv.Value()); err != nil { return dbus.MakeFailedError(err) } return nil @@ -341,7 +349,25 @@ func (p *Properties) Set(iface, property string, newv dbus.Variant) *dbus.Error func (p *Properties) SetMust(iface, property string, v interface{}) { p.mut.Lock() defer p.mut.Unlock() // unlock in case of panic - err := p.set(iface, property, v) + err := p.set(iface, []string{property}, v) + if err != nil { + panic(err) + } +} + +// SetMustMany sets the values of the given property value map and panics if any interface or +// property name are invalid. +func (p *Properties) SetMustMany(iface string, newValues map[string]interface{}) { + p.mut.Lock() + defer p.mut.Unlock() + + properties := make([]string, 0, len(newValues)) + values := make([]interface{}, 0, len(newValues)) + for key, val := range newValues { + properties = append(properties, key) + values = append(values, val) + } + err := p.set(iface, properties, values...) if err != nil { panic(err) } diff --git a/prop/prop_test.go b/prop/prop_test.go index 8d4cd9d..5e77366 100644 --- a/prop/prop_test.go +++ b/prop/prop_test.go @@ -159,3 +159,171 @@ func TestInt32(t *testing.T) { t.Errorf("expected r to be int32(101), but was %#v", r) } } + +func TestMany(t *testing.T) { + srv, err := dbus.SessionBus() + if err != nil { + t.Fatal(err) + } + defer srv.Close() + + cli, err := dbus.SessionBus() + if err != nil { + t.Fatal(err) + } + defer cli.Close() + + propsSpec := map[string]map[string]*Prop{ + "org.guelfey.DBus.Test": { + "one": { + "oneValue", + true, + EmitTrue, + nil, + }, + "two": { + 0, + true, + EmitInvalidates, + nil, + }, + }, + } + props := New(srv, "/org/guelfey/DBus/Test", propsSpec) + + r := props.GetMust("org.guelfey.DBus.Test", "one") + if r != "oneValue" { + t.Errorf("expected r to be 'oneValue', but was %#v", r) + } + r = props.GetMust("org.guelfey.DBus.Test", "two") + if r != 0 { + t.Errorf("expected r to be 0, but was %#v", r) + } + + ready := make(chan struct{}) + c := make(chan *dbus.Signal, 1) + go func() { + err := cli.AddMatchSignal(dbus.WithMatchMember("PropertiesChanged")) + if err != nil { + t.Error(err) + } + + sigChan := make(chan *dbus.Signal, 1) + cli.Signal(sigChan) + close(ready) + sig := <-sigChan + + err = cli.RemoveMatchSignal(dbus.WithMatchMember("PropertiesChanged")) + if err != nil { + t.Error(err) + } + + if sig.Name == "org.freedesktop.DBus.Properties.PropertiesChanged" { + c <- sig + } else { + t.Errorf("Got wrong signal: %v\n", sig.Name) + } + }() + + <-ready + props.SetMustMany("org.guelfey.DBus.Test", map[string]interface{}{"one": "otherValue", "two": 1}) + sig := <-c + + changed := sig.Body[1].(map[string]dbus.Variant) + invalidated := sig.Body[2].([]string) + if len(changed) != 1 || len(invalidated) != 1 { + t.Fatalf("changed len or invalidated len mismatch") + } + if changed["one"].Value() != "otherValue" { + t.Fatalf("changed value mismatch") + } +} + +func TestManyEmitFalseAndConst(t *testing.T) { + srv, err := dbus.SessionBus() + if err != nil { + t.Fatal(err) + } + defer srv.Close() + + cli, err := dbus.SessionBus() + if err != nil { + t.Fatal(err) + } + defer cli.Close() + + propsSpec := map[string]map[string]*Prop{ + "org.guelfey.DBus.Test": { + "emit": { + "emitValue", + true, + EmitTrue, + nil, + }, + "const": { + 0, + false, + EmitConst, + nil, + }, + "noEmit": { + "no", + true, + EmitFalse, + nil, + }, + }, + } + props := New(srv, "/org/guelfey/DBus/Test", propsSpec) + + r := props.GetMust("org.guelfey.DBus.Test", "emit") + if r != "emitValue" { + t.Errorf("expected r to be 'emitValue', but was %#v", r) + } + r = props.GetMust("org.guelfey.DBus.Test", "const") + if r != 0 { + t.Errorf("expected r to be 0, but was %#v", r) + } + r = props.GetMust("org.guelfey.DBus.Test", "noEmit") + if r != "no" { + t.Errorf("expected r to be 'no', but was %#v", r) + } + + ready := make(chan struct{}) + c := make(chan *dbus.Signal, 1) + go func() { + err := cli.AddMatchSignal(dbus.WithMatchMember("PropertiesChanged")) + if err != nil { + t.Error(err) + } + + sigChan := make(chan *dbus.Signal, 1) + cli.Signal(sigChan) + close(ready) + sig := <-sigChan + + err = cli.RemoveMatchSignal(dbus.WithMatchMember("PropertiesChanged")) + if err != nil { + t.Error(err) + } + + if sig.Name == "org.freedesktop.DBus.Properties.PropertiesChanged" { + c <- sig + } else { + t.Errorf("Got wrong signal: %v\n", sig.Name) + } + }() + + <-ready + props.SetMustMany("org.guelfey.DBus.Test", map[string]interface{}{"emit": "otherEmitValue", "const": 1, "noEmit": "otherNoEmitValue"}) + sig := <-c + + changed := sig.Body[1].(map[string]dbus.Variant) + invalidated := sig.Body[2].([]string) + if len(changed) != 1 || len(invalidated) != 0 { + t.Fatalf("changed len or invalidated len mismatch") + } + if changed["emit"].Value() != "otherEmitValue" { + t.Fatalf("changed value mismatch for emit") + } +}