|
@@ -2,9 +2,9 @@ package dbus
|
|
|
|
|
|
import (
|
|
import (
|
|
"errors"
|
|
"errors"
|
|
|
|
+ "fmt"
|
|
"reflect"
|
|
"reflect"
|
|
"strings"
|
|
"strings"
|
|
- "unicode"
|
|
|
|
)
|
|
)
|
|
|
|
|
|
var (
|
|
var (
|
|
@@ -22,16 +22,52 @@ var (
|
|
}
|
|
}
|
|
)
|
|
)
|
|
|
|
|
|
|
|
+// exportWithMapping represents an exported struct along with a method name
|
|
|
|
+// mapping to allow for exporting lower-case methods, etc.
|
|
|
|
+type exportWithMapping struct {
|
|
|
|
+ export interface{}
|
|
|
|
+
|
|
|
|
+ // Method name mapping; key -> struct method, value -> dbus method.
|
|
|
|
+ mapping map[string]string
|
|
|
|
+
|
|
|
|
+ // Whether or not this export is for the entire subtree
|
|
|
|
+ includeSubtree bool
|
|
|
|
+}
|
|
|
|
+
|
|
// Sender is a type which can be used in exported methods to receive the message
|
|
// Sender is a type which can be used in exported methods to receive the message
|
|
// sender.
|
|
// sender.
|
|
type Sender string
|
|
type Sender string
|
|
|
|
|
|
-func exportedMethod(v interface{}, name string) reflect.Value {
|
|
|
|
- if v == nil {
|
|
|
|
|
|
+func exportedMethod(export exportWithMapping, name string) reflect.Value {
|
|
|
|
+ if export.export == nil {
|
|
return reflect.Value{}
|
|
return reflect.Value{}
|
|
}
|
|
}
|
|
- m := reflect.ValueOf(v).MethodByName(name)
|
|
|
|
- if !m.IsValid() {
|
|
|
|
|
|
+
|
|
|
|
+ // If a mapping was included in the export, check the map to see if we
|
|
|
|
+ // should be looking for a different method in the export.
|
|
|
|
+ if export.mapping != nil {
|
|
|
|
+ for key, value := range export.mapping {
|
|
|
|
+ if value == name {
|
|
|
|
+ name = key
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Catch the case where a method is aliased but the client is calling
|
|
|
|
+ // the original, e.g. the "Foo" method was exported mapped to
|
|
|
|
+ // "foo," and dbus client called the original "Foo."
|
|
|
|
+ if key == name {
|
|
|
|
+ return reflect.Value{}
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ value := reflect.ValueOf(export.export)
|
|
|
|
+ m := value.MethodByName(name)
|
|
|
|
+
|
|
|
|
+ // Catch the case of attempting to call an unexported method
|
|
|
|
+ method, ok := value.Type().MethodByName(name)
|
|
|
|
+
|
|
|
|
+ if !m.IsValid() || !ok || method.PkgPath != "" {
|
|
return reflect.Value{}
|
|
return reflect.Value{}
|
|
}
|
|
}
|
|
t := m.Type()
|
|
t := m.Type()
|
|
@@ -43,6 +79,42 @@ func exportedMethod(v interface{}, name string) reflect.Value {
|
|
return m
|
|
return m
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// searchHandlers will look through all registered handlers looking for one
|
|
|
|
+// to handle the given path. If a verbatim one isn't found, it will check for
|
|
|
|
+// a subtree registration for the path as well.
|
|
|
|
+func (conn *Conn) searchHandlers(path ObjectPath) (map[string]exportWithMapping, bool) {
|
|
|
|
+ conn.handlersLck.RLock()
|
|
|
|
+ defer conn.handlersLck.RUnlock()
|
|
|
|
+
|
|
|
|
+ handlers, ok := conn.handlers[path]
|
|
|
|
+ if ok {
|
|
|
|
+ return handlers, ok
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // If handlers weren't found for this exact path, look for a matching subtree
|
|
|
|
+ // registration
|
|
|
|
+ handlers = make(map[string]exportWithMapping)
|
|
|
|
+ path = path[:strings.LastIndex(string(path), "/")]
|
|
|
|
+ for len(path) > 0 {
|
|
|
|
+ var subtreeHandlers map[string]exportWithMapping
|
|
|
|
+ subtreeHandlers, ok = conn.handlers[path]
|
|
|
|
+ if ok {
|
|
|
|
+ for iface, handler := range subtreeHandlers {
|
|
|
|
+ // Only include this handler if it registered for the subtree
|
|
|
|
+ if handler.includeSubtree {
|
|
|
|
+ handlers[iface] = handler
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ path = path[:strings.LastIndex(string(path), "/")]
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return handlers, ok
|
|
|
|
+}
|
|
|
|
+
|
|
// handleCall handles the given method call (i.e. looks if it's one of the
|
|
// handleCall handles the given method call (i.e. looks if it's one of the
|
|
// pre-implemented ones and searches for a corresponding handler if not).
|
|
// pre-implemented ones and searches for a corresponding handler if not).
|
|
func (conn *Conn) handleCall(msg *Message) {
|
|
func (conn *Conn) handleCall(msg *Message) {
|
|
@@ -62,40 +134,35 @@ func (conn *Conn) handleCall(msg *Message) {
|
|
}
|
|
}
|
|
return
|
|
return
|
|
}
|
|
}
|
|
- if len(name) == 0 || unicode.IsLower([]rune(name)[0]) {
|
|
|
|
|
|
+ if len(name) == 0 {
|
|
conn.sendError(errmsgUnknownMethod, sender, serial)
|
|
conn.sendError(errmsgUnknownMethod, sender, serial)
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // Find the exported handler (if any) for this path
|
|
|
|
+ handlers, ok := conn.searchHandlers(path)
|
|
|
|
+ if !ok {
|
|
|
|
+ conn.sendError(errmsgNoObject, sender, serial)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
var m reflect.Value
|
|
var m reflect.Value
|
|
if hasIface {
|
|
if hasIface {
|
|
- conn.handlersLck.RLock()
|
|
|
|
- obj, ok := conn.handlers[path]
|
|
|
|
- if !ok {
|
|
|
|
- conn.sendError(errmsgNoObject, sender, serial)
|
|
|
|
- conn.handlersLck.RUnlock()
|
|
|
|
- return
|
|
|
|
- }
|
|
|
|
- iface := obj[ifaceName]
|
|
|
|
- conn.handlersLck.RUnlock()
|
|
|
|
|
|
+ iface := handlers[ifaceName]
|
|
m = exportedMethod(iface, name)
|
|
m = exportedMethod(iface, name)
|
|
} else {
|
|
} else {
|
|
- conn.handlersLck.RLock()
|
|
|
|
- if _, ok := conn.handlers[path]; !ok {
|
|
|
|
- conn.sendError(errmsgNoObject, sender, serial)
|
|
|
|
- conn.handlersLck.RUnlock()
|
|
|
|
- return
|
|
|
|
- }
|
|
|
|
- for _, v := range conn.handlers[path] {
|
|
|
|
|
|
+ for _, v := range handlers {
|
|
m = exportedMethod(v, name)
|
|
m = exportedMethod(v, name)
|
|
if m.IsValid() {
|
|
if m.IsValid() {
|
|
break
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- conn.handlersLck.RUnlock()
|
|
|
|
}
|
|
}
|
|
|
|
+
|
|
if !m.IsValid() {
|
|
if !m.IsValid() {
|
|
conn.sendError(errmsgUnknownMethod, sender, serial)
|
|
conn.sendError(errmsgUnknownMethod, sender, serial)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
+
|
|
t := m.Type()
|
|
t := m.Type()
|
|
vs := msg.Body
|
|
vs := msg.Body
|
|
pointers := make([]interface{}, t.NumIn())
|
|
pointers := make([]interface{}, t.NumIn())
|
|
@@ -106,27 +173,36 @@ func (conn *Conn) handleCall(msg *Message) {
|
|
pointers[i] = val.Interface()
|
|
pointers[i] = val.Interface()
|
|
if tp == reflect.TypeOf((*Sender)(nil)).Elem() {
|
|
if tp == reflect.TypeOf((*Sender)(nil)).Elem() {
|
|
val.Elem().SetString(sender)
|
|
val.Elem().SetString(sender)
|
|
|
|
+ } else if tp == reflect.TypeOf((*Message)(nil)).Elem() {
|
|
|
|
+ val.Elem().Set(reflect.ValueOf(*msg))
|
|
} else {
|
|
} else {
|
|
decode = append(decode, pointers[i])
|
|
decode = append(decode, pointers[i])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
if len(decode) != len(vs) {
|
|
if len(decode) != len(vs) {
|
|
conn.sendError(errmsgInvalidArg, sender, serial)
|
|
conn.sendError(errmsgInvalidArg, sender, serial)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
+
|
|
if err := Store(vs, decode...); err != nil {
|
|
if err := Store(vs, decode...); err != nil {
|
|
conn.sendError(errmsgInvalidArg, sender, serial)
|
|
conn.sendError(errmsgInvalidArg, sender, serial)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // Extract parameters
|
|
params := make([]reflect.Value, len(pointers))
|
|
params := make([]reflect.Value, len(pointers))
|
|
for i := 0; i < len(pointers); i++ {
|
|
for i := 0; i < len(pointers); i++ {
|
|
params[i] = reflect.ValueOf(pointers[i]).Elem()
|
|
params[i] = reflect.ValueOf(pointers[i]).Elem()
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // Call method
|
|
ret := m.Call(params)
|
|
ret := m.Call(params)
|
|
if em := ret[t.NumOut()-1].Interface().(*Error); em != nil {
|
|
if em := ret[t.NumOut()-1].Interface().(*Error); em != nil {
|
|
conn.sendError(*em, sender, serial)
|
|
conn.sendError(*em, sender, serial)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
+
|
|
if msg.Flags&FlagNoReplyExpected == 0 {
|
|
if msg.Flags&FlagNoReplyExpected == 0 {
|
|
reply := new(Message)
|
|
reply := new(Message)
|
|
reply.Type = TypeMethodReply
|
|
reply.Type = TypeMethodReply
|
|
@@ -203,6 +279,10 @@ func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) erro
|
|
// contribute to the dbus signature of the method (i.e. the method is exposed
|
|
// contribute to the dbus signature of the method (i.e. the method is exposed
|
|
// as if the parameters of type Sender were not there).
|
|
// as if the parameters of type Sender were not there).
|
|
//
|
|
//
|
|
|
|
+// Similarly, any parameters with the type Message are set to the raw message
|
|
|
|
+// received on the bus. Again, parameters of this type do not contribute to the
|
|
|
|
+// dbus signature of the method.
|
|
|
|
+//
|
|
// Every method call is executed in a new goroutine, so the method may be called
|
|
// Every method call is executed in a new goroutine, so the method may be called
|
|
// in multiple goroutines at once.
|
|
// in multiple goroutines at once.
|
|
//
|
|
//
|
|
@@ -214,10 +294,51 @@ func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) erro
|
|
//
|
|
//
|
|
// Export returns an error if path is not a valid path name.
|
|
// Export returns an error if path is not a valid path name.
|
|
func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
|
|
func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
|
|
|
|
+ return conn.ExportWithMap(v, nil, path, iface)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// ExportWithMap works exactly like Export but provides the ability to remap
|
|
|
|
+// method names (e.g. export a lower-case method).
|
|
|
|
+//
|
|
|
|
+// The keys in the map are the real method names (exported on the struct), and
|
|
|
|
+// the values are the method names to be exported on DBus.
|
|
|
|
+func (conn *Conn) ExportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
|
|
|
|
+ return conn.exportWithMap(v, mapping, path, iface, false)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// ExportSubtree works exactly like Export but registers the given value for
|
|
|
|
+// an entire subtree rather under the root path provided.
|
|
|
|
+//
|
|
|
|
+// In order to make this useful, one parameter in each of the value's exported
|
|
|
|
+// methods should be a Message, in which case it will contain the raw message
|
|
|
|
+// (allowing one to get access to the path that caused the method to be called).
|
|
|
|
+//
|
|
|
|
+// Note that more specific export paths take precedence over less specific. For
|
|
|
|
+// example, a method call using the ObjectPath /foo/bar/baz will call a method
|
|
|
|
+// exported on /foo/bar before a method exported on /foo.
|
|
|
|
+func (conn *Conn) ExportSubtree(v interface{}, path ObjectPath, iface string) error {
|
|
|
|
+ return conn.ExportSubtreeWithMap(v, nil, path, iface)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// ExportSubtreeWithMap works exactly like ExportSubtree but provides the
|
|
|
|
+// ability to remap method names (e.g. export a lower-case method).
|
|
|
|
+//
|
|
|
|
+// The keys in the map are the real method names (exported on the struct), and
|
|
|
|
+// the values are the method names to be exported on DBus.
|
|
|
|
+func (conn *Conn) ExportSubtreeWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
|
|
|
|
+ return conn.exportWithMap(v, mapping, path, iface, true)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// exportWithMap is the worker function for all exports/registrations.
|
|
|
|
+func (conn *Conn) exportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string, includeSubtree bool) error {
|
|
if !path.IsValid() {
|
|
if !path.IsValid() {
|
|
- return errors.New("dbus: invalid path name")
|
|
|
|
|
|
+ return fmt.Errorf(`dbus: Invalid path name: "%s"`, path)
|
|
}
|
|
}
|
|
|
|
+
|
|
conn.handlersLck.Lock()
|
|
conn.handlersLck.Lock()
|
|
|
|
+ defer conn.handlersLck.Unlock()
|
|
|
|
+
|
|
|
|
+ // Remove a previous export if the interface is nil
|
|
if v == nil {
|
|
if v == nil {
|
|
if _, ok := conn.handlers[path]; ok {
|
|
if _, ok := conn.handlers[path]; ok {
|
|
delete(conn.handlers[path], iface)
|
|
delete(conn.handlers[path], iface)
|
|
@@ -225,51 +346,39 @@ func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
|
|
delete(conn.handlers, path)
|
|
delete(conn.handlers, path)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // If this is the first handler for this path, make a new map to hold all
|
|
|
|
+ // handlers for this path.
|
|
if _, ok := conn.handlers[path]; !ok {
|
|
if _, ok := conn.handlers[path]; !ok {
|
|
- conn.handlers[path] = make(map[string]interface{})
|
|
|
|
|
|
+ conn.handlers[path] = make(map[string]exportWithMapping)
|
|
}
|
|
}
|
|
- conn.handlers[path][iface] = v
|
|
|
|
- conn.handlersLck.Unlock()
|
|
|
|
|
|
+
|
|
|
|
+ // Finally, save this handler
|
|
|
|
+ conn.handlers[path][iface] = exportWithMapping{export: v, mapping: mapping, includeSubtree: includeSubtree}
|
|
|
|
+
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
-// ReleaseName calls org.freedesktop.DBus.ReleaseName. You should use only this
|
|
|
|
-// method to release a name (see below).
|
|
|
|
|
|
+// ReleaseName calls org.freedesktop.DBus.ReleaseName and awaits a response.
|
|
func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) {
|
|
func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) {
|
|
var r uint32
|
|
var r uint32
|
|
err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r)
|
|
err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r)
|
|
if err != nil {
|
|
if err != nil {
|
|
return 0, err
|
|
return 0, err
|
|
}
|
|
}
|
|
- if r == uint32(ReleaseNameReplyReleased) {
|
|
|
|
- conn.namesLck.Lock()
|
|
|
|
- for i, v := range conn.names {
|
|
|
|
- if v == name {
|
|
|
|
- copy(conn.names[i:], conn.names[i+1:])
|
|
|
|
- conn.names = conn.names[:len(conn.names)-1]
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- conn.namesLck.Unlock()
|
|
|
|
- }
|
|
|
|
return ReleaseNameReply(r), nil
|
|
return ReleaseNameReply(r), nil
|
|
}
|
|
}
|
|
|
|
|
|
-// RequestName calls org.freedesktop.DBus.RequestName. You should use only this
|
|
|
|
-// method to request a name because package dbus needs to keep track of all
|
|
|
|
-// names that the connection has.
|
|
|
|
|
|
+// RequestName calls org.freedesktop.DBus.RequestName and awaits a response.
|
|
func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) {
|
|
func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) {
|
|
var r uint32
|
|
var r uint32
|
|
err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r)
|
|
err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r)
|
|
if err != nil {
|
|
if err != nil {
|
|
return 0, err
|
|
return 0, err
|
|
}
|
|
}
|
|
- if r == uint32(RequestNameReplyPrimaryOwner) {
|
|
|
|
- conn.namesLck.Lock()
|
|
|
|
- conn.names = append(conn.names, name)
|
|
|
|
- conn.namesLck.Unlock()
|
|
|
|
- }
|
|
|
|
return RequestNameReply(r), nil
|
|
return RequestNameReply(r), nil
|
|
}
|
|
}
|
|
|
|
|