2003-09-26 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     def __init__(self, bus_type=TYPE_SESSION, glib_mainloop=True):
57         self._connection = dbus_bindings.bus_get(bus_type)
58
59         self._connection.add_filter(self._signal_func)
60         self._match_rule_to_receivers = { }
61         if (glib_mainloop):
62             self._connection.setup_with_g_main()
63
64     def get_service(self, service_name="org.freedesktop.Broadcast"):
65         """Get one of the RemoteServices connected to this Bus. service_name
66         is just a string of the form 'com.widgetcorp.MyService'
67         """
68         return RemoteService(self._connection, service_name)
69
70     def add_signal_receiver(self, receiver, interface=None, service=None, path=None):
71         match_rule = self._get_match_rule(interface, service, path)
72         
73         if (not self._match_rule_to_receivers.has_key(match_rule)):
74             self._match_rule_to_receivers[match_rule] = [ ]
75         self._match_rule_to_receivers[match_rule].append(receiver)
76         
77         dbus_bindings.bus_add_match(self._connection, match_rule)
78         
79     def get_connection(self):
80         """Get the dbus_bindings.Connection object associated with this Bus"""
81         return self._connection
82
83     def _get_match_rule(self, interface, service, path):
84 ##        if (interface):
85 ##            match_rule = match_rule + ",interface='%s'" % (interface)
86 ##        if (service):
87 ##            match_rule = match_rule + ",service='%s'" % (service)
88 ##        if (path):
89 ##            match_rule = match_rule + ",path='%s'" % (path)
90         # FIXME: use the service here too!!!
91         return "type='signal',interface='%s',path='%s'" % (interface, path)
92     
93     def _signal_func(self, connection, message):
94         if (message.get_type() != dbus_bindings.MESSAGE_TYPE_SIGNAL):
95             return
96         
97         interface = message.get_interface()
98         service   = message.get_sender()
99         path      = message.get_path()
100         member    = message.get_member()
101
102         match_rule = self._get_match_rule(interface, service, path)
103
104         if (self._match_rule_to_receivers.has_key(match_rule)):
105             receivers = self._match_rule_to_receivers[match_rule]
106             args = [interface, member, service, path]
107             for receiver in receivers:
108                 receiver(*args)
109         
110
111 class RemoteObject:
112     """A remote Object.
113
114     A RemoteObject is provided by a RemoteService on a particular Bus. RemoteObjects
115     have member functions, and can be called like normal Python objects.
116     """
117     def __init__(self, connection, service_name, object_path, interface):
118         self._connection   = connection
119         self._service_name = service_name
120         self._object_path  = object_path
121         self._interface    = interface
122
123     def __getattr__(self, member):
124         if member == '__call__':
125             return object.__call__
126         else:
127             return RemoteMethod(self._connection, self._service_name,
128                                 self._object_path, self._interface, member)
129
130
131 class RemoteMethod:
132     """A remote Method.
133
134     Typically a member of a RemoteObject. Calls to the
135     method produce messages that travel over the Bus and are routed
136     to a specific Service.
137     """
138     def __init__(self, connection, service_name, object_path, interface, method_name):
139         self._connection   = connection
140         self._service_name = service_name
141         self._object_path  = object_path
142         self._interface    = interface
143         self._method_name  = method_name
144
145     def __call__(self, *args):
146         message = dbus_bindings.MethodCall(self._object_path, self._interface, self._method_name)
147         message.set_destination(self._service_name)
148         
149         # Add the arguments to the function
150         iter = message.get_iter()
151         for arg in args:
152             iter.append(arg)
153
154         reply_message = self._connection.send_with_reply_and_block(message, 5000)
155
156         args_tuple = reply_message.get_args_list()
157         if len(args_tuple) == 0:
158             return
159         elif len(args_tuple) == 1:
160             return args_tuple[0]
161         else:
162             return args_tuple
163
164 class Service:
165     """A base class for exporting your own Services across the Bus
166
167     Just inherit from Service, providing the name of your service
168     (e.g. org.designfu.SampleService).
169     """
170     def __init__(self, service_name, bus=None):
171         self._service_name = service_name
172                              
173         if bus == None:
174             # Get the default bus
175             self._bus = Bus()
176         else:
177             self._bus = bus
178
179         dbus_bindings.bus_acquire_service(self._bus.get_connection(), service_name)
180
181     def get_bus(self):
182         """Get the Bus this Service is on"""
183         return self._bus
184
185     def get_service_name(self):
186         """Get the name of this service"""
187         return self._service_name
188
189 class Object:
190     """A base class for exporting your own Objects across the Bus.
191
192     Just inherit from Object and provide a list of methods to share
193     across the Bus. These will appear as member functions of your
194     ServiceObject.
195     """
196     def __init__(self, object_path, methods_to_share, service):
197         self._object_path = object_path
198         self._service = service
199         self._bus = service.get_bus()
200         self._connection = self._bus.get_connection()
201
202         self._method_name_to_method = self._build_method_dictionary(methods_to_share)
203         
204         self._connection.register_object_path(object_path, self._unregister_cb, self._message_cb)
205
206     def broadcast_signal(self, interface, signal_name):
207         message = dbus_bindings.Signal(self._object_path, interface, signal_name)
208         #FIXME: need to set_sender, but it always disconnects when we do this
209         #message.set_sender(self._service.get_service_name())
210         self._connection.send(message)
211
212     def _unregister_cb(self, connection):
213         print ("Unregister")
214         
215     def _message_cb(self, connection, message):
216         target_method_name = message.get_member()
217         target_method = self._method_name_to_method[target_method_name]
218         args = message.get_args_list()
219         
220         try:
221             retval = target_method(*args)
222         except Exception, e:
223             if e.__module__ == '__main__':
224                 # FIXME: is it right to use .__name__ here?
225                 error_name = e.__class__.__name__
226             else:
227                 error_name = e.__module__ + '.' + str(e.__class__.__name__)
228             error_contents = str(e)
229             reply = dbus_bindings.Error(message, error_name, error_contents)
230         else:
231             reply = dbus_bindings.MethodReturn(message)
232             if retval != None:
233                 iter = reply.get_iter()
234                 iter.append(retval)
235                 
236         self._connection.send(reply)
237
238     def _build_method_dictionary(self, methods):
239         method_dict = {}
240         for method in methods:
241             if method_dict.has_key(method.__name__):
242                 print ('WARNING: registering DBus Object methods, already have a method named %s' % (method.__name__))
243             method_dict[method.__name__] = method
244         return method_dict
245
246 class RemoteService:
247     """A remote service providing objects.
248
249     A service is typically a process or application that provides
250     remote objects, but can also be the broadcast service that
251     receives signals from all applications on the Bus.
252     """
253     
254     def __init__(self, connection, service_name):
255         self._connection     = connection
256         self._service_name   = service_name
257
258     def get_object(self, object_path, interface):
259         """Get an object provided by this Service that implements a
260         particular interface. object_path is a string of the form
261         '/com/widgetcorp/MyService/MyObject1'. interface looks a lot
262         like a service_name (they're often the same) and is of the form,
263         'com.widgetcorp.MyInterface', and mostly just defines the
264         set of member functions that will be present in the object.
265         """
266         return RemoteObject(self._connection, self._service_name, object_path, interface)
267