...

Source file src/github.com/godbus/dbus/v5/prop/prop.go

Documentation: github.com/godbus/dbus/v5/prop

     1  // Package prop provides the Properties struct which can be used to implement
     2  // org.freedesktop.DBus.Properties.
     3  package prop
     4  
     5  import (
     6  	"reflect"
     7  	"sync"
     8  
     9  	"github.com/godbus/dbus/v5"
    10  	"github.com/godbus/dbus/v5/introspect"
    11  )
    12  
    13  // EmitType controls how org.freedesktop.DBus.Properties.PropertiesChanged is
    14  // emitted for a property. If it is EmitTrue, the signal is emitted. If it is
    15  // EmitInvalidates, the signal is also emitted, but the new value of the property
    16  // is not disclosed. If it is EmitConst, the property never changes value during
    17  // the lifetime of the object it belongs to, and hence the signal is never emitted
    18  // for it.
    19  type EmitType byte
    20  
    21  const (
    22  	EmitFalse EmitType = iota
    23  	EmitTrue
    24  	EmitInvalidates
    25  	EmitConst
    26  )
    27  
    28  func (e EmitType) String() (str string) {
    29  	switch e {
    30  	case EmitFalse:
    31  		str = "false"
    32  	case EmitTrue:
    33  		str = "true"
    34  	case EmitInvalidates:
    35  		str = "invalidates"
    36  	case EmitConst:
    37  		str = "const"
    38  	default:
    39  		panic("invalid value for EmitType")
    40  	}
    41  	return
    42  }
    43  
    44  // ErrIfaceNotFound is the error returned to peers who try to access properties
    45  // on interfaces that aren't found.
    46  var ErrIfaceNotFound = dbus.NewError("org.freedesktop.DBus.Properties.Error.InterfaceNotFound", nil)
    47  
    48  // ErrPropNotFound is the error returned to peers trying to access properties
    49  // that aren't found.
    50  var ErrPropNotFound = dbus.NewError("org.freedesktop.DBus.Properties.Error.PropertyNotFound", nil)
    51  
    52  // ErrReadOnly is the error returned to peers trying to set a read-only
    53  // property.
    54  var ErrReadOnly = dbus.NewError("org.freedesktop.DBus.Properties.Error.ReadOnly", nil)
    55  
    56  // ErrInvalidArg is returned to peers if the type of the property that is being
    57  // changed and the argument don't match.
    58  var ErrInvalidArg = dbus.NewError("org.freedesktop.DBus.Properties.Error.InvalidArg", nil)
    59  
    60  // The introspection data for the org.freedesktop.DBus.Properties interface.
    61  var IntrospectData = introspect.Interface{
    62  	Name: "org.freedesktop.DBus.Properties",
    63  	Methods: []introspect.Method{
    64  		{
    65  			Name: "Get",
    66  			Args: []introspect.Arg{
    67  				{Name: "interface", Type: "s", Direction: "in"},
    68  				{Name: "property", Type: "s", Direction: "in"},
    69  				{Name: "value", Type: "v", Direction: "out"},
    70  			},
    71  		},
    72  		{
    73  			Name: "GetAll",
    74  			Args: []introspect.Arg{
    75  				{Name: "interface", Type: "s", Direction: "in"},
    76  				{Name: "props", Type: "a{sv}", Direction: "out"},
    77  			},
    78  		},
    79  		{
    80  			Name: "Set",
    81  			Args: []introspect.Arg{
    82  				{Name: "interface", Type: "s", Direction: "in"},
    83  				{Name: "property", Type: "s", Direction: "in"},
    84  				{Name: "value", Type: "v", Direction: "in"},
    85  			},
    86  		},
    87  	},
    88  	Signals: []introspect.Signal{
    89  		{
    90  			Name: "PropertiesChanged",
    91  			Args: []introspect.Arg{
    92  				{Name: "interface", Type: "s", Direction: "out"},
    93  				{Name: "changed_properties", Type: "a{sv}", Direction: "out"},
    94  				{Name: "invalidates_properties", Type: "as", Direction: "out"},
    95  			},
    96  		},
    97  	},
    98  }
    99  
   100  // The introspection data for the org.freedesktop.DBus.Properties interface, as
   101  // a string.
   102  const IntrospectDataString = `
   103  	<interface name="org.freedesktop.DBus.Properties">
   104  		<method name="Get">
   105  			<arg name="interface" direction="in" type="s"/>
   106  			<arg name="property" direction="in" type="s"/>
   107  			<arg name="value" direction="out" type="v"/>
   108  		</method>
   109  		<method name="GetAll">
   110  			<arg name="interface" direction="in" type="s"/>
   111  			<arg name="props" direction="out" type="a{sv}"/>
   112  		</method>
   113  		<method name="Set">
   114  			<arg name="interface" direction="in" type="s"/>
   115  			<arg name="property" direction="in" type="s"/>
   116  			<arg name="value" direction="in" type="v"/>
   117  		</method>
   118  		<signal name="PropertiesChanged">
   119  			<arg name="interface" type="s"/>
   120  			<arg name="changed_properties" type="a{sv}"/>
   121  			<arg name="invalidates_properties" type="as"/>
   122  		</signal>
   123  	</interface>
   124  `
   125  
   126  // Prop represents a single property. It is used for creating a Properties
   127  // value.
   128  type Prop struct {
   129  	// Initial value. Must be a DBus-representable type. This is not modified
   130  	// after Properties has been initialized; use Get or GetMust to access the
   131  	// value.
   132  	Value interface{}
   133  
   134  	// If true, the value can be modified by calls to Set.
   135  	Writable bool
   136  
   137  	// Controls how org.freedesktop.DBus.Properties.PropertiesChanged is
   138  	// emitted if this property changes.
   139  	Emit EmitType
   140  
   141  	// If not nil, anytime this property is changed by Set, this function is
   142  	// called with an appropriate Change as its argument. If the returned error
   143  	// is not nil, it is sent back to the caller of Set and the property is not
   144  	// changed.
   145  	Callback func(*Change) *dbus.Error
   146  }
   147  
   148  // Introspection returns the introspection data for p.
   149  // The "name" argument is used as the property's name in the resulting data.
   150  func (p *Prop) Introspection(name string) introspect.Property {
   151  	var result = introspect.Property{Name: name, Type: dbus.SignatureOf(p.Value).String()}
   152  	if p.Writable {
   153  		result.Access = "readwrite"
   154  	} else {
   155  		result.Access = "read"
   156  	}
   157  	result.Annotations = []introspect.Annotation{
   158  		{
   159  			Name:  "org.freedesktop.DBus.Property.EmitsChangedSignal",
   160  			Value: p.Emit.String(),
   161  		},
   162  	}
   163  	return result
   164  }
   165  
   166  // Change represents a change of a property by a call to Set.
   167  type Change struct {
   168  	Props *Properties
   169  	Iface string
   170  	Name  string
   171  	Value interface{}
   172  }
   173  
   174  // Properties is a set of values that can be made available to the message bus
   175  // using the org.freedesktop.DBus.Properties interface. It is safe for
   176  // concurrent use by multiple goroutines.
   177  type Properties struct {
   178  	m    Map
   179  	mut  sync.RWMutex
   180  	conn *dbus.Conn
   181  	path dbus.ObjectPath
   182  }
   183  
   184  // New falls back to Export, but it returns nil if properties export fails,
   185  // swallowing the error, shouldn't be used.
   186  //
   187  // Deprecated: use Export instead.
   188  func New(conn *dbus.Conn, path dbus.ObjectPath, props Map) *Properties {
   189  	p, err := Export(conn, path, props)
   190  	if err != nil {
   191  		return nil
   192  	}
   193  	return p
   194  }
   195  
   196  // Export returns a new Properties structure that manages the given properties.
   197  // The key for the first-level map of props is the name of the interface; the
   198  // second-level key is the name of the property. The returned structure will be
   199  // exported as org.freedesktop.DBus.Properties on path.
   200  func Export(
   201  	conn *dbus.Conn, path dbus.ObjectPath, props Map,
   202  ) (*Properties, error) {
   203  	p := &Properties{m: copyProps(props), conn: conn, path: path}
   204  	if err := conn.Export(p, path, "org.freedesktop.DBus.Properties"); err != nil {
   205  		return nil, err
   206  	}
   207  	return p, nil
   208  }
   209  
   210  // Map is a helper type for supplying the configuration of properties to be handled.
   211  type Map = map[string]map[string]*Prop
   212  
   213  func copyProps(in Map) Map {
   214  	out := make(Map, len(in))
   215  	for intf, props := range in {
   216  		out[intf] = make(map[string]*Prop)
   217  		for name, prop := range props {
   218  			out[intf][name] = new(Prop)
   219  			*out[intf][name] = *prop
   220  			val := reflect.New(reflect.TypeOf(prop.Value))
   221  			val.Elem().Set(reflect.ValueOf(prop.Value))
   222  			out[intf][name].Value = val.Interface()
   223  		}
   224  	}
   225  	return out
   226  }
   227  
   228  // Get implements org.freedesktop.DBus.Properties.Get.
   229  func (p *Properties) Get(iface, property string) (dbus.Variant, *dbus.Error) {
   230  	p.mut.RLock()
   231  	defer p.mut.RUnlock()
   232  	m, ok := p.m[iface]
   233  	if !ok {
   234  		return dbus.Variant{}, ErrIfaceNotFound
   235  	}
   236  	prop, ok := m[property]
   237  	if !ok {
   238  		return dbus.Variant{}, ErrPropNotFound
   239  	}
   240  	return dbus.MakeVariant(reflect.ValueOf(prop.Value).Elem().Interface()), nil
   241  }
   242  
   243  // GetAll implements org.freedesktop.DBus.Properties.GetAll.
   244  func (p *Properties) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) {
   245  	p.mut.RLock()
   246  	defer p.mut.RUnlock()
   247  	m, ok := p.m[iface]
   248  	if !ok {
   249  		return nil, ErrIfaceNotFound
   250  	}
   251  	rm := make(map[string]dbus.Variant, len(m))
   252  	for k, v := range m {
   253  		rm[k] = dbus.MakeVariant(reflect.ValueOf(v.Value).Elem().Interface())
   254  	}
   255  	return rm, nil
   256  }
   257  
   258  // GetMust returns the value of the given property and panics if either the
   259  // interface or the property name are invalid.
   260  func (p *Properties) GetMust(iface, property string) interface{} {
   261  	p.mut.RLock()
   262  	defer p.mut.RUnlock()
   263  	return reflect.ValueOf(p.m[iface][property].Value).Elem().Interface()
   264  }
   265  
   266  // Introspection returns the introspection data that represents the properties
   267  // of iface.
   268  func (p *Properties) Introspection(iface string) []introspect.Property {
   269  	p.mut.RLock()
   270  	defer p.mut.RUnlock()
   271  	m := p.m[iface]
   272  	s := make([]introspect.Property, 0, len(m))
   273  	for name, prop := range m {
   274  		s = append(s, prop.Introspection(name))
   275  	}
   276  	return s
   277  }
   278  
   279  // set sets the given property and emits PropertyChanged if appropriate. p.mut
   280  // must already be locked.
   281  func (p *Properties) set(iface, property string, v interface{}) error {
   282  	prop := p.m[iface][property]
   283  	err := dbus.Store([]interface{}{v}, prop.Value)
   284  	if err != nil {
   285  		return err
   286  	}
   287  	return p.emitChange(iface, property)
   288  }
   289  
   290  func (p *Properties) emitChange(iface, property string) error {
   291  	prop := p.m[iface][property]
   292  	switch prop.Emit {
   293  	case EmitFalse:
   294  		return nil // do nothing
   295  	case EmitInvalidates:
   296  		return p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged",
   297  			iface, map[string]dbus.Variant{}, []string{property})
   298  	case EmitTrue:
   299  		return p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged",
   300  			iface, map[string]dbus.Variant{property: dbus.MakeVariant(prop.Value)},
   301  			[]string{})
   302  	case EmitConst:
   303  		return nil
   304  	default:
   305  		panic("invalid value for EmitType")
   306  	}
   307  }
   308  
   309  // Set implements org.freedesktop.Properties.Set.
   310  func (p *Properties) Set(iface, property string, newv dbus.Variant) *dbus.Error {
   311  	p.mut.Lock()
   312  	defer p.mut.Unlock()
   313  	m, ok := p.m[iface]
   314  	if !ok {
   315  		return ErrIfaceNotFound
   316  	}
   317  	prop, ok := m[property]
   318  	if !ok {
   319  		return ErrPropNotFound
   320  	}
   321  	if !prop.Writable {
   322  		return ErrReadOnly
   323  	}
   324  	if newv.Signature() != dbus.SignatureOf(prop.Value) {
   325  		return ErrInvalidArg
   326  	}
   327  	if prop.Callback != nil {
   328  		err := prop.Callback(&Change{p, iface, property, newv.Value()})
   329  		if err != nil {
   330  			return err
   331  		}
   332  	}
   333  	if err := p.set(iface, property, newv.Value()); err != nil {
   334  		return dbus.MakeFailedError(err)
   335  	}
   336  	return nil
   337  }
   338  
   339  // SetMust sets the value of the given property and panics if the interface or
   340  // the property name are invalid.
   341  func (p *Properties) SetMust(iface, property string, v interface{}) {
   342  	p.mut.Lock()
   343  	defer p.mut.Unlock() // unlock in case of panic
   344  	err := p.set(iface, property, v)
   345  	if err != nil {
   346  		panic(err)
   347  	}
   348  }
   349  

View as plain text