| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- // Package prop provides the Properties struct which can be used to implement
- // org.freedesktop.DBus.Properties.
- package prop
- import (
- "reflect"
- "sync"
- "github.com/godbus/dbus/v5"
- "github.com/godbus/dbus/v5/introspect"
- )
- // EmitType controls how org.freedesktop.DBus.Properties.PropertiesChanged is
- // emitted for a property. If it is EmitTrue, the signal is emitted. If it is
- // EmitInvalidates, the signal is also emitted, but the new value of the property
- // is not disclosed. If it is EmitConst, the property never changes value during
- // the lifetime of the object it belongs to, and hence the signal is never emitted
- // for it.
- type EmitType byte
- const (
- EmitFalse EmitType = iota
- EmitTrue
- EmitInvalidates
- EmitConst
- )
- func (e EmitType) String() (str string) {
- switch e {
- case EmitFalse:
- str = "false"
- case EmitTrue:
- str = "true"
- case EmitInvalidates:
- str = "invalidates"
- case EmitConst:
- str = "const"
- default:
- panic("invalid value for EmitType")
- }
- return
- }
- // ErrIfaceNotFound is the error returned to peers who try to access properties
- // on interfaces that aren't found.
- var ErrIfaceNotFound = dbus.NewError("org.freedesktop.DBus.Properties.Error.InterfaceNotFound", nil)
- // ErrPropNotFound is the error returned to peers trying to access properties
- // that aren't found.
- var ErrPropNotFound = dbus.NewError("org.freedesktop.DBus.Properties.Error.PropertyNotFound", nil)
- // ErrReadOnly is the error returned to peers trying to set a read-only
- // property.
- var ErrReadOnly = dbus.NewError("org.freedesktop.DBus.Properties.Error.ReadOnly", nil)
- // ErrInvalidArg is returned to peers if the type of the property that is being
- // changed and the argument don't match.
- var ErrInvalidArg = dbus.NewError("org.freedesktop.DBus.Properties.Error.InvalidArg", nil)
- // The introspection data for the org.freedesktop.DBus.Properties interface.
- var IntrospectData = introspect.Interface{
- Name: "org.freedesktop.DBus.Properties",
- Methods: []introspect.Method{
- {
- Name: "Get",
- Args: []introspect.Arg{
- {Name: "interface", Type: "s", Direction: "in"},
- {Name: "property", Type: "s", Direction: "in"},
- {Name: "value", Type: "v", Direction: "out"},
- },
- },
- {
- Name: "GetAll",
- Args: []introspect.Arg{
- {Name: "interface", Type: "s", Direction: "in"},
- {Name: "props", Type: "a{sv}", Direction: "out"},
- },
- },
- {
- Name: "Set",
- Args: []introspect.Arg{
- {Name: "interface", Type: "s", Direction: "in"},
- {Name: "property", Type: "s", Direction: "in"},
- {Name: "value", Type: "v", Direction: "in"},
- },
- },
- },
- Signals: []introspect.Signal{
- {
- Name: "PropertiesChanged",
- Args: []introspect.Arg{
- {Name: "interface", Type: "s", Direction: "out"},
- {Name: "changed_properties", Type: "a{sv}", Direction: "out"},
- {Name: "invalidates_properties", Type: "as", Direction: "out"},
- },
- },
- },
- }
- // The introspection data for the org.freedesktop.DBus.Properties interface, as
- // a string.
- const IntrospectDataString = `
- <interface name="org.freedesktop.DBus.Properties">
- <method name="Get">
- <arg name="interface" direction="in" type="s"/>
- <arg name="property" direction="in" type="s"/>
- <arg name="value" direction="out" type="v"/>
- </method>
- <method name="GetAll">
- <arg name="interface" direction="in" type="s"/>
- <arg name="props" direction="out" type="a{sv}"/>
- </method>
- <method name="Set">
- <arg name="interface" direction="in" type="s"/>
- <arg name="property" direction="in" type="s"/>
- <arg name="value" direction="in" type="v"/>
- </method>
- <signal name="PropertiesChanged">
- <arg name="interface" type="s"/>
- <arg name="changed_properties" type="a{sv}"/>
- <arg name="invalidates_properties" type="as"/>
- </signal>
- </interface>
- `
- // Prop represents a single property. It is used for creating a Properties
- // value.
- type Prop struct {
- // Initial value. Must be a DBus-representable type. This is not modified
- // after Properties has been initialized; use Get or GetMust to access the
- // value.
- Value interface{}
- // If true, the value can be modified by calls to Set.
- Writable bool
- // Controls how org.freedesktop.DBus.Properties.PropertiesChanged is
- // emitted if this property changes.
- Emit EmitType
- // If not nil, anytime this property is changed by Set, this function is
- // called with an appropriate Change as its argument. If the returned error
- // is not nil, it is sent back to the caller of Set and the property is not
- // changed.
- Callback func(*Change) *dbus.Error
- }
- // Introspection returns the introspection data for p.
- // The "name" argument is used as the property's name in the resulting data.
- func (p *Prop) Introspection(name string) introspect.Property {
- var result = introspect.Property{Name: name, Type: dbus.SignatureOf(p.Value).String()}
- if p.Writable {
- result.Access = "readwrite"
- } else {
- result.Access = "read"
- }
- result.Annotations = []introspect.Annotation{
- {
- Name: "org.freedesktop.DBus.Property.EmitsChangedSignal",
- Value: p.Emit.String(),
- },
- }
- return result
- }
- // Change represents a change of a property by a call to Set.
- type Change struct {
- Props *Properties
- Iface string
- Name string
- Value interface{}
- }
- // Properties is a set of values that can be made available to the message bus
- // using the org.freedesktop.DBus.Properties interface. It is safe for
- // concurrent use by multiple goroutines.
- type Properties struct {
- m Map
- mut sync.RWMutex
- conn *dbus.Conn
- path dbus.ObjectPath
- }
- // New falls back to Export, but it returns nil if properties export fails,
- // swallowing the error, shouldn't be used.
- //
- // Deprecated: use Export instead.
- func New(conn *dbus.Conn, path dbus.ObjectPath, props Map) *Properties {
- p, err := Export(conn, path, props)
- if err != nil {
- return nil
- }
- return p
- }
- // Export returns a new Properties structure that manages the given properties.
- // The key for the first-level map of props is the name of the interface; the
- // second-level key is the name of the property. The returned structure will be
- // exported as org.freedesktop.DBus.Properties on path.
- func Export(
- conn *dbus.Conn, path dbus.ObjectPath, props Map,
- ) (*Properties, error) {
- p := &Properties{m: copyProps(props), conn: conn, path: path}
- if err := conn.Export(p, path, "org.freedesktop.DBus.Properties"); err != nil {
- return nil, err
- }
- return p, nil
- }
- // Map is a helper type for supplying the configuration of properties to be handled.
- type Map = map[string]map[string]*Prop
- func copyProps(in Map) Map {
- out := make(Map, len(in))
- for intf, props := range in {
- out[intf] = make(map[string]*Prop)
- for name, prop := range props {
- out[intf][name] = new(Prop)
- *out[intf][name] = *prop
- val := reflect.New(reflect.TypeOf(prop.Value))
- val.Elem().Set(reflect.ValueOf(prop.Value))
- out[intf][name].Value = val.Interface()
- }
- }
- return out
- }
- // Get implements org.freedesktop.DBus.Properties.Get.
- func (p *Properties) Get(iface, property string) (dbus.Variant, *dbus.Error) {
- p.mut.RLock()
- defer p.mut.RUnlock()
- m, ok := p.m[iface]
- if !ok {
- return dbus.Variant{}, ErrIfaceNotFound
- }
- prop, ok := m[property]
- if !ok {
- return dbus.Variant{}, ErrPropNotFound
- }
- return dbus.MakeVariant(reflect.ValueOf(prop.Value).Elem().Interface()), nil
- }
- // GetAll implements org.freedesktop.DBus.Properties.GetAll.
- func (p *Properties) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) {
- p.mut.RLock()
- defer p.mut.RUnlock()
- m, ok := p.m[iface]
- if !ok {
- return nil, ErrIfaceNotFound
- }
- rm := make(map[string]dbus.Variant, len(m))
- for k, v := range m {
- rm[k] = dbus.MakeVariant(reflect.ValueOf(v.Value).Elem().Interface())
- }
- return rm, nil
- }
- // GetMust returns the value of the given property and panics if either the
- // interface or the property name are invalid.
- func (p *Properties) GetMust(iface, property string) interface{} {
- p.mut.RLock()
- defer p.mut.RUnlock()
- return reflect.ValueOf(p.m[iface][property].Value).Elem().Interface()
- }
- // Introspection returns the introspection data that represents the properties
- // of iface.
- func (p *Properties) Introspection(iface string) []introspect.Property {
- p.mut.RLock()
- defer p.mut.RUnlock()
- m := p.m[iface]
- s := make([]introspect.Property, 0, len(m))
- for name, prop := range m {
- s = append(s, prop.Introspection(name))
- }
- return s
- }
- // 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)
- if err != nil {
- return err
- }
- return p.emitChange(iface, property)
- }
- 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")
- }
- }
- // Set implements org.freedesktop.Properties.Set.
- func (p *Properties) Set(iface, property string, newv dbus.Variant) *dbus.Error {
- p.mut.Lock()
- defer p.mut.Unlock()
- m, ok := p.m[iface]
- if !ok {
- return ErrIfaceNotFound
- }
- prop, ok := m[property]
- if !ok {
- return ErrPropNotFound
- }
- if !prop.Writable {
- return ErrReadOnly
- }
- if newv.Signature() != dbus.SignatureOf(prop.Value) {
- return ErrInvalidArg
- }
- if prop.Callback != nil {
- err := prop.Callback(&Change{p, iface, property, newv.Value()})
- if err != nil {
- return err
- }
- }
- if err := p.set(iface, property, newv.Value()); err != nil {
- return dbus.MakeFailedError(err)
- }
- return nil
- }
- // SetMust sets the value of the given property and panics if the interface or
- // the property name are invalid.
- 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)
- if err != nil {
- panic(err)
- }
- }
|