1
2
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
14
15
16
17
18
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
45
46 var ErrIfaceNotFound = dbus.NewError("org.freedesktop.DBus.Properties.Error.InterfaceNotFound", nil)
47
48
49
50 var ErrPropNotFound = dbus.NewError("org.freedesktop.DBus.Properties.Error.PropertyNotFound", nil)
51
52
53
54 var ErrReadOnly = dbus.NewError("org.freedesktop.DBus.Properties.Error.ReadOnly", nil)
55
56
57
58 var ErrInvalidArg = dbus.NewError("org.freedesktop.DBus.Properties.Error.InvalidArg", nil)
59
60
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
101
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
127
128 type Prop struct {
129
130
131
132 Value interface{}
133
134
135 Writable bool
136
137
138
139 Emit EmitType
140
141
142
143
144
145 Callback func(*Change) *dbus.Error
146 }
147
148
149
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
167 type Change struct {
168 Props *Properties
169 Iface string
170 Name string
171 Value interface{}
172 }
173
174
175
176
177 type Properties struct {
178 m Map
179 mut sync.RWMutex
180 conn *dbus.Conn
181 path dbus.ObjectPath
182 }
183
184
185
186
187
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
197
198
199
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
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
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
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
259
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
267
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
280
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
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
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
340
341 func (p *Properties) SetMust(iface, property string, v interface{}) {
342 p.mut.Lock()
343 defer p.mut.Unlock()
344 err := p.set(iface, property, v)
345 if err != nil {
346 panic(err)
347 }
348 }
349
View as plain text