2004-05-30 Seth Nickell <seth@gnome.org>
[platform/upstream/dbus.git] / python / dbus.py
1
2 """Module for high-level communication over the FreeDesktop.org Bus (DBus)
3
4 DBus allows you to share and access remote objects between processes
5 running on the desktop, and also to access system services (such as
6 the print spool).
7
8 To use DBus, first get a Bus object, which provides a connection to one
9 of a few standard dbus-daemon instances that might be running. From the
10 Bus you can get a RemoteService. A service is provided by an application or
11 process connected to the Bus, and represents a set of objects. Once you
12 have a RemoteService you can get a RemoteObject that implements a specific interface
13 (an interface is just a standard group of member functions). Then you can call
14 those member functions directly.
15
16 You can think of a complete method call as looking something like:
17
18 Bus:SESSION -> Service:org.gnome.Evolution -> Object:/org/gnome/Evolution/Inbox -> Interface: org.gnome.Evolution.MailFolder -> Method: Forward('message1', 'seth@gnome.org')
19
20 This communicates over the SESSION Bus to the org.gnome.Evolution process to call the
21 Forward method of the /org/gnome/Evolution/Inbox object (which provides the
22 org.gnome.Evolution.MailFolder interface) with two string arguments.
23
24 For example, the dbus-daemon itself provides a service and some objects:
25
26 # Get a connection to the desktop-wide SESSION bus
27 bus = dbus.Bus(dbus.Bus.TYPE_SESSION)
28
29 # Get the service provided by the dbus-daemon named org.freedesktop.DBus
30 dbus_service = bus.get_service('org.freedesktop.DBus')
31
32 # Get a reference to the desktop bus' standard object, denoted
33 # by the path /org/freedesktop/DBus. The object /org/freedesktop/DBus
34 # implements the 'org.freedesktop.DBus' interface
35 dbus_object = dbus_service.get_object('/org/freedesktop/DBus',
36                                        'org.freedesktop.DBus')
37
38 # One of the member functions in the org.freedesktop.DBus interface
39 # is ListServices(), which provides a list of all the other services
40 # registered on this bus. Call it, and print the list.
41 print(dbus_object.ListServices())
42 """
43
44 import dbus_bindings
45
46 class Bus:
47     """A connection to a DBus daemon.
48
49     One of three possible standard buses, the SESSION, SYSTEM,
50     or ACTIVATION bus
51     """
52     TYPE_SESSION    = dbus_bindings.BUS_SESSION
53     TYPE_SYSTEM     = dbus_bindings.BUS_SYSTEM
54     TYPE_ACTIVATION = dbus_bindings.BUS_ACTIVATION
55
56     """bus_type=[Bus.TYPE_SESSION | Bus.TYPE_SYSTEM | Bus.TYPE_ACTIVATION]
57     """
58     def __init__(self, bus_type=TYPE_SESSION, glib_mainloop=True):
59         self._connection = dbus_bindings.bus_get(bus_type)
60
61         self._connection.add_filter(self._signal_func)
62         self._match_rule_to_receivers = { }
63         if (glib_mainloop):
64             self._connection.setup_with_g_main()
65
66     def get_service(self, service_name="org.freedesktop.Broadcast"):
67         """Get one of the RemoteServices connected to this Bus. service_name
68         is just a string of the form 'com.widgetcorp.MyService'
69         """
70         return RemoteService(self._connection, service_name)
71
72     def add_signal_receiver(self, receiver, interface=None, service=None, path=None):
73         match_rule = self._get_match_rule(interface, service, path)
74         
75         if (not self._match_rule_to_receivers.has_key(match_rule)):
76             self._match_rule_to_receivers[match_rule] = [ ]
77         self._match_rule_to_receivers[match_rule].append(receiver)
78         
79         dbus_bindings.bus_add_match(self._connection, match_rule)
80
81     def remove_signal_receiver(self, receiver, interface=None, service=None, path=None):
82         match_rule = self._get_match_rule(interface, service, path)
83
84         if self._match_rule_to_receivers.has_key(match_rule):
85             if self._match_rule_to_receivers[match_rule].__contains__(receiver):
86                 self._match_rule_to_receivers[match_rule].remove(receiver)
87                 dbus_bindings.bus_remove_match(self._connection, match_rule)
88
89     def get_connection(self):
90         """Get the dbus_bindings.Connection object associated with this Bus"""
91         return self._connection
92
93     def _get_match_rule(self, interface, service, path):
94 ##        if (interface):
95 ##            match_rule = match_rule + ",interface='%s'" % (interface)
96 ##        if (service):
97 ##            match_rule = match_rule + ",service='%s'" % (service)
98 ##        if (path):
99 ##            match_rule = match_rule + ",path='%s'" % (path)
100         # FIXME: use the service here too!!!
101         return "type='signal',interface='%s',path='%s'" % (interface, path)
102     
103     def _signal_func(self, connection, message):
104         if (message.get_type() != dbus_bindings.MESSAGE_TYPE_SIGNAL):
105             return dbus_bindings.HANDLER_RESULT_NOT_YET_HANDLED
106         
107         interface = message.get_interface()
108         service   = message.get_sender()
109         path      = message.get_path()
110         member    = message.get_member()
111
112         match_rule = self._get_match_rule(interface, service, path)
113
114         if (self._match_rule_to_receivers.has_key(match_rule)):
115             receivers = self._match_rule_to_receivers[match_rule]
116             args = [interface, member, service, path, message]
117             for receiver in receivers:
118                 receiver(*args)
119
120 class SystemBus(Bus):
121     """The system-wide message bus
122     """
123     def __init__(self):
124         Bus.__init__(self, Bus.TYPE_SYSTEM)
125
126 class SessionBus(Bus):
127     """The session (current login) message bus
128     """
129     def __init__(self):
130         Bus.__init__(self, Bus.TYPE_SESSION)
131
132 class ActivationBus(Bus):
133     """The bus that activated this process (if
134     this process was launched by DBus activation)
135     """
136     def __init__(self):
137         Bus.__init__(self, Bus.TYPE_ACTIVATION)
138
139
140 class RemoteObject:
141     """A remote Object.
142
143     A RemoteObject is provided by a RemoteService on a particular Bus. RemoteObjects
144     have member functions, and can be called like normal Python objects.
145     """
146     def __init__(self, connection, service_name, object_path, interface):
147         self._connection   = connection
148         self._service_name = service_name
149         self._object_path  = object_path
150         self._interface    = interface
151
152     def __getattr__(self, member):
153         if member == '__call__':
154             return object.__call__
155         else:
156             return RemoteMethod(self._connection, self._service_name,
157                                 self._object_path, self._interface, member)
158
159
160 class RemoteMethod:
161     """A remote Method.
162
163     Typically a member of a RemoteObject. Calls to the
164     method produce messages that travel over the Bus and are routed
165     to a specific Service.
166     """
167     def __init__(self, connection, service_name, object_path, interface, method_name):
168         self._connection   = connection
169         self._service_name = service_name
170         self._object_path  = object_path
171         self._interface    = interface
172         self._method_name  = method_name
173
174     def __call__(self, *args):
175         message = dbus_bindings.MethodCall(self._object_path, self._interface, self._method_name)
176         message.set_destination(self._service_name)
177         
178         # Add the arguments to the function
179         iter = message.get_iter()
180         for arg in args:
181             iter.append(arg)
182
183         reply_message = self._connection.send_with_reply_and_block(message, 5000)
184
185         args_tuple = reply_message.get_args_list()
186         if len(args_tuple) == 0:
187             return
188         elif len(args_tuple) == 1:
189             return args_tuple[0]
190         else:
191             return args_tuple
192
193 class Service:
194     """A base class for exporting your own Services across the Bus
195
196     Just inherit from Service, providing the name of your service
197     (e.g. org.designfu.SampleService).
198     """
199     def __init__(self, service_name, bus=None):
200         self._service_name = service_name
201                              
202         if bus == None:
203             # Get the default bus
204             self._bus = Bus()
205         else:
206             self._bus = bus
207
208         dbus_bindings.bus_acquire_service(self._bus.get_connection(), service_name)
209
210     def get_bus(self):
211         """Get the Bus this Service is on"""
212         return self._bus
213
214     def get_service_name(self):
215         """Get the name of this service"""
216         return self._service_name
217
218 def _dispatch_dbus_method_call(target_method, argument_list, message):
219     """Calls method_to_call using argument_list, but handles
220     exceptions, etc, and generates a reply to the DBus Message message
221     """
222     try:
223         retval = target_method(*argument_list)
224     except Exception, e:
225         if e.__module__ == '__main__':
226             # FIXME: is it right to use .__name__ here?
227             error_name = e.__class__.__name__
228         else:
229             error_name = e.__module__ + '.' + str(e.__class__.__name__)
230             error_contents = str(e)
231             reply = dbus_bindings.Error(message, error_name, error_contents)
232     else:
233         reply = dbus_bindings.MethodReturn(message)
234         if retval != None:
235             iter = reply.get_iter()
236             iter.append(retval)
237
238     return reply
239
240 def _build_method_dictionary(methods):
241     method_dict = {}
242     for method in methods:
243         if method_dict.has_key(method.__name__):
244             print ('WARNING: registering DBus Object methods, already have a method named %s' % (method.__name__))
245         method_dict[method.__name__] = method
246     return method_dict
247
248 class Object:
249     """A base class for exporting your own Objects across the Bus.
250
251     Just inherit from Object and provide a list of methods to share
252     across the Bus. These will appear as member functions of your
253     ServiceObject.
254     """
255     def __init__(self, object_path, service, dbus_methods=[]):
256         # Reversed constructor argument order. Add a temporary
257         # check to help people get things straightened out with minimal pain.
258         if type(service) == list:
259             raise TypeError, "dbus.Object.__init__(): the order of the 'service' and 'dbus_methods' arguments has been reversed (for consistency with dbus.ObjectTree)."
260         
261         self._object_path = object_path
262         self._service = service
263         self._bus = service.get_bus()
264         self._connection = self._bus.get_connection()
265
266         self._method_name_to_method = _build_method_dictionary(dbus_methods)
267         
268         self._connection.register_object_path(object_path, self._unregister_cb, self._message_cb)
269
270     def broadcast_signal(self, interface, signal_name):
271         message = dbus_bindings.Signal(self._object_path, interface, signal_name)
272         #FIXME: need to set_sender, but it always disconnects when we do this
273         #message.set_sender(self._service.get_service_name())
274         self._connection.send(message)
275
276     def _unregister_cb(self, connection):
277         print ("Unregister")
278
279     def _message_cb(self, connection, message):
280         target_method_name = message.get_member()
281         target_method = self._method_name_to_method[target_method_name]
282         args = message.get_args_list()
283
284         reply = _dispatch_dbus_method_call(target_method, args, message)
285         
286         self._connection.send(reply)
287
288
289
290 class ObjectTree:
291     """An object tree allows you to register a handler for a tree of object paths.
292     This means that literal Python objects do not need to be created for each object
293     over the bus, but you can have a virtual tree of objects handled by a single
294     Python object. There are two ways to handle method calls on virtual objects:
295
296     1) Pass a list of dbus_methods in to __init__. This works just like dbus.Object,
297     except an object_path is passed as the first argument to each method, denoting which
298     virtual object the call was made on. If all the objects in the tree support the same
299     methods, this is the best approach.
300
301     2) Override object_method_called. This allows you to define the valid methods dynamically
302     on an object by object basis. For example, if providing an object tree that represented
303     a filesystem heirarchy, you'd only want an ls method on directory objects, not file objects.
304     """
305
306     def __init__(self, base_path, service, dbus_methods=[]):
307         self._base_path = base_path
308         self._service = service
309         self._bus = service.get_bus()
310         self._connection = self._bus.get_connection()
311
312         self._method_name_to_method = _build_method_dictionary(dbus_methods)
313         
314         self._connection.register_fallback(base_path, self._unregister_cb, self._message_cb)
315         
316     def object_method_called(self, object_path, method_name, argument_list):
317         """Override this method. Called with, object_path, the relative path of the object
318         under the base_path, the name of the method invoked, and a list of arguments
319         """
320         raise NotImplementedException, "object_method_called() must be overriden"
321
322     def _unregister_cb(self, connection):
323         print ("Unregister")
324
325     def _message_cb(self, connection, message):
326         target_object_full_path = message.get_path()
327         assert(self._base_path == target_object_full_path[:len(self._base_path)])
328         target_object_path = target_object_full_path[len(self._base_path):]
329         target_method_name = message.get_member()        
330         message_args = message.get_args_list()
331
332         try:
333             target_method = self._method_name_to_method[target_method_name]
334             args = [target_object_path] + message_args
335         except KeyError:
336             target_method = self.object_method_called
337             args = [target_object_path, target_method_name, message_args]
338
339         reply = _dispatch_dbus_method_call(target_method, args, message)
340
341         self._connection.send(reply)
342         
343 class RemoteService:
344     """A remote service providing objects.
345
346     A service is typically a process or application that provides
347     remote objects, but can also be the broadcast service that
348     receives signals from all applications on the Bus.
349     """
350     
351     def __init__(self, connection, service_name):
352         self._connection     = connection
353         self._service_name   = service_name
354
355     def get_object(self, object_path, interface):
356         """Get an object provided by this Service that implements a
357         particular interface. object_path is a string of the form
358         '/com/widgetcorp/MyService/MyObject1'. interface looks a lot
359         like a service_name (they're often the same) and is of the form,
360         'com.widgetcorp.MyInterface', and mostly just defines the
361         set of member functions that will be present in the object.
362         """
363         return RemoteObject(self._connection, self._service_name, object_path, interface)
364