go-tg-screenshot-bot
463 строки · 14.2 Кб
1package dbus
2
3import (
4"errors"
5"fmt"
6"os"
7"reflect"
8"strings"
9)
10
11var (
12ErrMsgInvalidArg = Error{
13"org.freedesktop.DBus.Error.InvalidArgs",
14[]interface{}{"Invalid type / number of args"},
15}
16ErrMsgNoObject = Error{
17"org.freedesktop.DBus.Error.NoSuchObject",
18[]interface{}{"No such object"},
19}
20ErrMsgUnknownMethod = Error{
21"org.freedesktop.DBus.Error.UnknownMethod",
22[]interface{}{"Unknown / invalid method"},
23}
24ErrMsgUnknownInterface = Error{
25"org.freedesktop.DBus.Error.UnknownInterface",
26[]interface{}{"Object does not implement the interface"},
27}
28)
29
30func MakeNoObjectError(path ObjectPath) Error {
31return Error{
32"org.freedesktop.DBus.Error.NoSuchObject",
33[]interface{}{fmt.Sprintf("No such object '%s'", string(path))},
34}
35}
36
37func MakeUnknownMethodError(methodName string) Error {
38return Error{
39"org.freedesktop.DBus.Error.UnknownMethod",
40[]interface{}{fmt.Sprintf("Unknown / invalid method '%s'", methodName)},
41}
42}
43
44func MakeUnknownInterfaceError(ifaceName string) Error {
45return Error{
46"org.freedesktop.DBus.Error.UnknownInterface",
47[]interface{}{fmt.Sprintf("Object does not implement the interface '%s'", ifaceName)},
48}
49}
50
51func MakeFailedError(err error) *Error {
52return &Error{
53"org.freedesktop.DBus.Error.Failed",
54[]interface{}{err.Error()},
55}
56}
57
58// Sender is a type which can be used in exported methods to receive the message
59// sender.
60type Sender string
61
62func computeMethodName(name string, mapping map[string]string) string {
63newname, ok := mapping[name]
64if ok {
65name = newname
66}
67return name
68}
69
70func getMethods(in interface{}, mapping map[string]string) map[string]reflect.Value {
71if in == nil {
72return nil
73}
74methods := make(map[string]reflect.Value)
75val := reflect.ValueOf(in)
76typ := val.Type()
77for i := 0; i < typ.NumMethod(); i++ {
78methtype := typ.Method(i)
79method := val.Method(i)
80t := method.Type()
81// only track valid methods must return *Error as last arg
82// and must be exported
83if t.NumOut() == 0 ||
84t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) ||
85methtype.PkgPath != "" {
86continue
87}
88// map names while building table
89methods[computeMethodName(methtype.Name, mapping)] = method
90}
91return methods
92}
93
94func getAllMethods(in interface{}, mapping map[string]string) map[string]reflect.Value {
95if in == nil {
96return nil
97}
98methods := make(map[string]reflect.Value)
99val := reflect.ValueOf(in)
100typ := val.Type()
101for i := 0; i < typ.NumMethod(); i++ {
102methtype := typ.Method(i)
103method := val.Method(i)
104// map names while building table
105methods[computeMethodName(methtype.Name, mapping)] = method
106}
107return methods
108}
109
110func standardMethodArgumentDecode(m Method, sender string, msg *Message, body []interface{}) ([]interface{}, error) {
111pointers := make([]interface{}, m.NumArguments())
112decode := make([]interface{}, 0, len(body))
113
114for i := 0; i < m.NumArguments(); i++ {
115tp := reflect.TypeOf(m.ArgumentValue(i))
116val := reflect.New(tp)
117pointers[i] = val.Interface()
118if tp == reflect.TypeOf((*Sender)(nil)).Elem() {
119val.Elem().SetString(sender)
120} else if tp == reflect.TypeOf((*Message)(nil)).Elem() {
121val.Elem().Set(reflect.ValueOf(*msg))
122} else {
123decode = append(decode, pointers[i])
124}
125}
126
127if len(decode) != len(body) {
128return nil, ErrMsgInvalidArg
129}
130
131if err := Store(body, decode...); err != nil {
132return nil, ErrMsgInvalidArg
133}
134
135return pointers, nil
136}
137
138func (conn *Conn) decodeArguments(m Method, sender string, msg *Message) ([]interface{}, error) {
139if decoder, ok := m.(ArgumentDecoder); ok {
140return decoder.DecodeArguments(conn, sender, msg, msg.Body)
141}
142return standardMethodArgumentDecode(m, sender, msg, msg.Body)
143}
144
145// handleCall handles the given method call (i.e. looks if it's one of the
146// pre-implemented ones and searches for a corresponding handler if not).
147func (conn *Conn) handleCall(msg *Message) {
148name := msg.Headers[FieldMember].value.(string)
149path := msg.Headers[FieldPath].value.(ObjectPath)
150ifaceName, _ := msg.Headers[FieldInterface].value.(string)
151sender, hasSender := msg.Headers[FieldSender].value.(string)
152serial := msg.serial
153
154if len(name) == 0 {
155conn.sendError(ErrMsgUnknownMethod, sender, serial)
156}
157
158if ifaceName == "org.freedesktop.DBus.Peer" {
159switch name {
160case "Ping":
161conn.sendReply(sender, serial)
162case "GetMachineId":
163conn.sendReply(sender, serial, conn.uuid)
164default:
165conn.sendError(MakeUnknownMethodError(name), sender, serial)
166}
167return
168}
169
170object, ok := conn.handler.LookupObject(path)
171if !ok {
172conn.sendError(MakeNoObjectError(path), sender, serial)
173return
174}
175
176iface, exists := object.LookupInterface(ifaceName)
177if !exists {
178conn.sendError(MakeUnknownInterfaceError(ifaceName), sender, serial)
179return
180}
181
182m, exists := iface.LookupMethod(name)
183if !exists {
184conn.sendError(MakeUnknownMethodError(name), sender, serial)
185return
186}
187args, err := conn.decodeArguments(m, sender, msg)
188if err != nil {
189conn.sendError(err, sender, serial)
190return
191}
192
193ret, err := m.Call(args...)
194if err != nil {
195conn.sendError(err, sender, serial)
196return
197}
198
199if msg.Flags&FlagNoReplyExpected == 0 {
200reply := new(Message)
201reply.Type = TypeMethodReply
202reply.Headers = make(map[HeaderField]Variant)
203if hasSender {
204reply.Headers[FieldDestination] = msg.Headers[FieldSender]
205}
206reply.Headers[FieldReplySerial] = MakeVariant(msg.serial)
207reply.Body = make([]interface{}, len(ret))
208for i := 0; i < len(ret); i++ {
209reply.Body[i] = ret[i]
210}
211reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...))
212
213if err := reply.IsValid(); err != nil {
214fmt.Fprintf(os.Stderr, "dbus: dropping invalid reply to %s.%s on obj %s: %s\n", ifaceName, name, path, err)
215} else {
216conn.sendMessageAndIfClosed(reply, nil)
217}
218}
219}
220
221// Emit emits the given signal on the message bus. The name parameter must be
222// formatted as "interface.member", e.g., "org.freedesktop.DBus.NameLost".
223func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) error {
224i := strings.LastIndex(name, ".")
225if i == -1 {
226return errors.New("dbus: invalid method name")
227}
228iface := name[:i]
229member := name[i+1:]
230msg := new(Message)
231msg.Type = TypeSignal
232msg.Headers = make(map[HeaderField]Variant)
233msg.Headers[FieldInterface] = MakeVariant(iface)
234msg.Headers[FieldMember] = MakeVariant(member)
235msg.Headers[FieldPath] = MakeVariant(path)
236msg.Body = values
237if len(values) > 0 {
238msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
239}
240if err := msg.IsValid(); err != nil {
241return err
242}
243
244var closed bool
245conn.sendMessageAndIfClosed(msg, func() {
246closed = true
247})
248if closed {
249return ErrClosed
250}
251return nil
252}
253
254// Export registers the given value to be exported as an object on the
255// message bus.
256//
257// If a method call on the given path and interface is received, an exported
258// method with the same name is called with v as the receiver if the
259// parameters match and the last return value is of type *Error. If this
260// *Error is not nil, it is sent back to the caller as an error.
261// Otherwise, a method reply is sent with the other return values as its body.
262//
263// Any parameters with the special type Sender are set to the sender of the
264// dbus message when the method is called. Parameters of this type do not
265// contribute to the dbus signature of the method (i.e. the method is exposed
266// as if the parameters of type Sender were not there).
267//
268// Similarly, any parameters with the type Message are set to the raw message
269// received on the bus. Again, parameters of this type do not contribute to the
270// dbus signature of the method.
271//
272// Every method call is executed in a new goroutine, so the method may be called
273// in multiple goroutines at once.
274//
275// Method calls on the interface org.freedesktop.DBus.Peer will be automatically
276// handled for every object.
277//
278// Passing nil as the first parameter will cause conn to cease handling calls on
279// the given combination of path and interface.
280//
281// Export returns an error if path is not a valid path name.
282func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
283return conn.ExportWithMap(v, nil, path, iface)
284}
285
286// ExportAll registers all exported methods defined by the given object on
287// the message bus.
288//
289// Unlike Export there is no requirement to have the last parameter as type
290// *Error. If you want to be able to return error then you can append an error
291// type parameter to your method signature. If the error returned is not nil,
292// it is sent back to the caller as an error. Otherwise, a method reply is
293// sent with the other return values as its body.
294func (conn *Conn) ExportAll(v interface{}, path ObjectPath, iface string) error {
295return conn.export(getAllMethods(v, nil), path, iface, false)
296}
297
298// ExportWithMap works exactly like Export but provides the ability to remap
299// method names (e.g. export a lower-case method).
300//
301// The keys in the map are the real method names (exported on the struct), and
302// the values are the method names to be exported on DBus.
303func (conn *Conn) ExportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
304return conn.export(getMethods(v, mapping), path, iface, false)
305}
306
307// ExportSubtree works exactly like Export but registers the given value for
308// an entire subtree rather under the root path provided.
309//
310// In order to make this useful, one parameter in each of the value's exported
311// methods should be a Message, in which case it will contain the raw message
312// (allowing one to get access to the path that caused the method to be called).
313//
314// Note that more specific export paths take precedence over less specific. For
315// example, a method call using the ObjectPath /foo/bar/baz will call a method
316// exported on /foo/bar before a method exported on /foo.
317func (conn *Conn) ExportSubtree(v interface{}, path ObjectPath, iface string) error {
318return conn.ExportSubtreeWithMap(v, nil, path, iface)
319}
320
321// ExportSubtreeWithMap works exactly like ExportSubtree but provides the
322// ability to remap method names (e.g. export a lower-case method).
323//
324// The keys in the map are the real method names (exported on the struct), and
325// the values are the method names to be exported on DBus.
326func (conn *Conn) ExportSubtreeWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
327return conn.export(getMethods(v, mapping), path, iface, true)
328}
329
330// ExportMethodTable like Export registers the given methods as an object
331// on the message bus. Unlike Export the it uses a method table to define
332// the object instead of a native go object.
333//
334// The method table is a map from method name to function closure
335// representing the method. This allows an object exported on the bus to not
336// necessarily be a native go object. It can be useful for generating exposed
337// methods on the fly.
338//
339// Any non-function objects in the method table are ignored.
340func (conn *Conn) ExportMethodTable(methods map[string]interface{}, path ObjectPath, iface string) error {
341return conn.exportMethodTable(methods, path, iface, false)
342}
343
344// Like ExportSubtree, but with the same caveats as ExportMethodTable.
345func (conn *Conn) ExportSubtreeMethodTable(methods map[string]interface{}, path ObjectPath, iface string) error {
346return conn.exportMethodTable(methods, path, iface, true)
347}
348
349func (conn *Conn) exportMethodTable(methods map[string]interface{}, path ObjectPath, iface string, includeSubtree bool) error {
350var out map[string]reflect.Value
351if methods != nil {
352out = make(map[string]reflect.Value)
353for name, method := range methods {
354rval := reflect.ValueOf(method)
355if rval.Kind() != reflect.Func {
356continue
357}
358t := rval.Type()
359// only track valid methods must return *Error as last arg
360if t.NumOut() == 0 ||
361t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) {
362continue
363}
364out[name] = rval
365}
366}
367return conn.export(out, path, iface, includeSubtree)
368}
369
370func (conn *Conn) unexport(h *defaultHandler, path ObjectPath, iface string) error {
371if h.PathExists(path) {
372obj := h.objects[path]
373obj.DeleteInterface(iface)
374if len(obj.interfaces) == 0 {
375h.DeleteObject(path)
376}
377}
378return nil
379}
380
381// export is the worker function for all exports/registrations.
382func (conn *Conn) export(methods map[string]reflect.Value, path ObjectPath, iface string, includeSubtree bool) error {
383h, ok := conn.handler.(*defaultHandler)
384if !ok {
385return fmt.Errorf(
386`dbus: export only allowed on the default handler. Received: %T"`,
387conn.handler)
388}
389
390if !path.IsValid() {
391return fmt.Errorf(`dbus: Invalid path name: "%s"`, path)
392}
393
394// Remove a previous export if the interface is nil
395if methods == nil {
396return conn.unexport(h, path, iface)
397}
398
399// If this is the first handler for this path, make a new map to hold all
400// handlers for this path.
401if !h.PathExists(path) {
402h.AddObject(path, newExportedObject())
403}
404
405exportedMethods := make(map[string]Method)
406for name, method := range methods {
407exportedMethods[name] = exportedMethod{method}
408}
409
410// Finally, save this handler
411obj := h.objects[path]
412obj.AddInterface(iface, newExportedIntf(exportedMethods, includeSubtree))
413
414return nil
415}
416
417// ReleaseName calls org.freedesktop.DBus.ReleaseName and awaits a response.
418func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) {
419var r uint32
420err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r)
421if err != nil {
422return 0, err
423}
424return ReleaseNameReply(r), nil
425}
426
427// RequestName calls org.freedesktop.DBus.RequestName and awaits a response.
428func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) {
429var r uint32
430err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r)
431if err != nil {
432return 0, err
433}
434return RequestNameReply(r), nil
435}
436
437// ReleaseNameReply is the reply to a ReleaseName call.
438type ReleaseNameReply uint32
439
440const (
441ReleaseNameReplyReleased ReleaseNameReply = 1 + iota
442ReleaseNameReplyNonExistent
443ReleaseNameReplyNotOwner
444)
445
446// RequestNameFlags represents the possible flags for a RequestName call.
447type RequestNameFlags uint32
448
449const (
450NameFlagAllowReplacement RequestNameFlags = 1 << iota
451NameFlagReplaceExisting
452NameFlagDoNotQueue
453)
454
455// RequestNameReply is the reply to a RequestName call.
456type RequestNameReply uint32
457
458const (
459RequestNameReplyPrimaryOwner RequestNameReply = 1 + iota
460RequestNameReplyInQueue
461RequestNameReplyExists
462RequestNameReplyAlreadyOwner
463)
464