Move signal matching machinery into superclasses
[dbus-python-phuang.git] / dbus / bus.py
blobd98172656f6e20606ac4f275533499ce5e1e7524
1 # Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
3 # Licensed under the Academic Free License version 2.1
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published
7 # by the Free Software Foundation; either version 2.1 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 __all__ = ('BusConnection',)
20 __docformat__ = 'reStructuredText'
22 import logging
23 import weakref
25 from _dbus_bindings import validate_interface_name, validate_member_name,\
26 validate_bus_name, validate_object_path,\
27 validate_error_name,\
28 DBusException, \
29 BUS_SESSION, BUS_STARTER, BUS_SYSTEM, \
30 DBUS_START_REPLY_SUCCESS, \
31 DBUS_START_REPLY_ALREADY_RUNNING, \
32 BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE,\
33 HANDLER_RESULT_NOT_YET_HANDLED
34 from dbus.connection import Connection
36 _NAME_OWNER_CHANGE_MATCH = ("type='signal',sender='%s',"
37 "interface='%s',member='NameOwnerChanged',"
38 "path='%s',arg0='%%s'"
39 % (BUS_DAEMON_NAME, BUS_DAEMON_IFACE,
40 BUS_DAEMON_PATH))
41 """(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
42 messages"""
45 _logger = logging.getLogger('dbus.connection')
48 class BusConnection(Connection):
49 """A connection to a D-Bus daemon that implements the
50 ``org.freedesktop.DBus`` pseudo-service.
51 """
53 TYPE_SESSION = BUS_SESSION
54 """Represents a session bus (same as the global dbus.BUS_SESSION)"""
56 TYPE_SYSTEM = BUS_SYSTEM
57 """Represents the system bus (same as the global dbus.BUS_SYSTEM)"""
59 TYPE_STARTER = BUS_STARTER
60 """Represents the bus that started this service by activation (same as
61 the global dbus.BUS_STARTER)"""
63 START_REPLY_SUCCESS = DBUS_START_REPLY_SUCCESS
64 START_REPLY_ALREADY_RUNNING = DBUS_START_REPLY_ALREADY_RUNNING
66 def __new__(cls, address_or_type=TYPE_SESSION, mainloop=None):
67 bus = cls._new_for_bus(address_or_type, mainloop=mainloop)
69 # _bus_names is used by dbus.service.BusName!
70 bus._bus_names = weakref.WeakValueDictionary()
72 bus._signal_sender_matches = {}
73 """Map from sender well-known name to list of match rules for all
74 signal handlers that match on sender well-known name."""
76 bus.add_message_filter(bus.__class__._noc_signal_func)
78 return bus
80 def _noc_signal_func(self, message):
81 # If it's NameOwnerChanged, we'll need to update our
82 # sender well-known name -> sender unique name mappings
83 if (message.is_signal(BUS_DAEMON_IFACE, 'NameOwnerChanged')
84 and message.has_sender(BUS_DAEMON_NAME)
85 and message.has_path(BUS_DAEMON_PATH)):
86 name, unused, new = message.get_args_list()
87 for match in self._signal_sender_matches.get(name, (None,)):
88 match.sender_unique = new
90 return HANDLER_RESULT_NOT_YET_HANDLED
92 def add_signal_receiver(self, handler_function, signal_name=None,
93 dbus_interface=None, named_service=None,
94 path=None, **keywords):
95 match = super(BusConnection, self).add_signal_receiver(
96 handler_function, signal_name, dbus_interface, named_service,
97 path, **keywords)
99 # The bus daemon is special - its unique-name is org.freedesktop.DBus
100 # rather than starting with :
101 if (named_service is not None
102 and named_service[:1] != ':'
103 and named_service != BUS_DAEMON_NAME):
104 try:
105 match.sender_unique = self.get_name_owner(named_service)
106 except DBusException:
107 # if the desired sender isn't actually running, we'll get
108 # notified by NameOwnerChanged when it appears
109 pass
110 notification = self._signal_sender_matches.setdefault(
111 named_service, [])
112 if not notification:
113 self.add_match_string(_NAME_OWNER_CHANGE_MATCH % named_service)
114 notification.append(match)
116 self.add_match_string(str(match))
118 return match
120 def _clean_up_signal_match(self, match):
121 # The signals lock must be held.
122 self.remove_match_string(str(match))
123 notification = self._signal_sender_matches.get(match.sender, False)
124 if notification:
125 try:
126 notification.remove(match)
127 except LookupError:
128 pass
129 if not notification:
130 # nobody cares any more, so remove the match rule from the bus
131 self.remove_match_string(_NAME_OWNER_CHANGE_MATCH
132 % match.sender)
134 def activate_name_owner(self, bus_name):
135 if (bus_name is not None and bus_name[:1] != ':'
136 and bus_name != BUS_DAEMON_NAME):
137 try:
138 return self.get_name_owner(bus_name)
139 except DBusException, e:
140 # FIXME: detect whether it's NameHasNoOwner, but properly
141 #if not str(e).startswith('org.freedesktop.DBus.Error.NameHasNoOwner:'):
142 # raise
143 # it might not exist: try to start it
144 self.start_service_by_name(bus_name)
145 return self.get_name_owner(bus_name)
146 else:
147 # already unique
148 return bus_name
150 def get_object(self, named_service, object_path, introspect=True,
151 follow_name_owner_changes=False):
152 """Return a local proxy for the given remote object.
154 Method calls on the proxy are translated into method calls on the
155 remote object.
157 :Parameters:
158 `named_service` : str
159 A bus name (either the unique name or a well-known name)
160 of the application owning the object
161 `object_path` : str
162 The object path of the desired object
163 `introspect` : bool
164 If true (default), attempt to introspect the remote
165 object to find out supported methods and their signatures
166 `follow_name_owner_changes` : bool
167 If the object path is a well-known name and this parameter
168 is false (default), resolve the well-known name to the unique
169 name of its current owner and bind to that instead; if the
170 ownership of the well-known name changes in future,
171 keep communicating with the original owner.
172 This is necessary if the D-Bus API used is stateful.
174 If the object path is a well-known name and this parameter
175 is true, whenever the well-known name changes ownership in
176 future, bind to the new owner, if any.
178 If the given object path is a unique name, this parameter
179 has no effect.
181 :Returns: a `dbus.proxies.ProxyObject`
182 :Raises `DBusException`: if resolving the well-known name to a
183 unique name fails
185 if follow_name_owner_changes:
186 self._require_main_loop() # we don't get the signals otherwise
187 return self.ProxyObjectClass(self, named_service, object_path,
188 introspect=introspect,
189 follow_name_owner_changes=follow_name_owner_changes)
191 def get_unix_user(self, bus_name):
192 """Get the numeric uid of the process owning the given bus name.
194 :Parameters:
195 `bus_name` : str
196 A bus name, either unique or well-known
197 :Returns: a `dbus.UInt32`
199 validate_bus_name(bus_name)
200 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
201 BUS_DAEMON_IFACE, 'GetConnectionUnixUser',
202 's', (bus_name,))
204 def start_service_by_name(self, bus_name, flags=0):
205 """Start a service which will implement the given bus name on this Bus.
207 :Parameters:
208 `bus_name` : str
209 The well-known bus name to be activated.
210 `flags` : dbus.UInt32
211 Flags to pass to StartServiceByName (currently none are
212 defined)
214 :Returns: A tuple of 2 elements. The first is always True, the
215 second is either START_REPLY_SUCCESS or
216 START_REPLY_ALREADY_RUNNING.
218 :Raises DBusException: if the service could not be started.
220 validate_bus_name(bus_name)
221 return (True, self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
222 BUS_DAEMON_IFACE,
223 'StartServiceByName',
224 'su', (bus_name, flags)))
226 # XXX: it might be nice to signal IN_QUEUE, EXISTS by exception,
227 # but this would not be backwards-compatible
228 def request_name(self, name, flags=0):
229 """Request a bus name.
231 :Parameters:
232 `name` : str
233 The well-known name to be requested
234 `flags` : dbus.UInt32
235 A bitwise-OR of 0 or more of the flags
236 `DBUS_NAME_FLAG_ALLOW_REPLACEMENT`,
237 `DBUS_NAME_FLAG_REPLACE_EXISTING`
238 and `DBUS_NAME_FLAG_DO_NOT_QUEUE`
239 :Returns: `DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER`,
240 `DBUS_REQUEST_NAME_REPLY_IN_QUEUE`,
241 `DBUS_REQUEST_NAME_REPLY_EXISTS` or
242 `DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER`
243 :Raises DBusException: if the bus daemon cannot be contacted or
244 returns an error.
246 validate_bus_name(name, allow_unique=False)
247 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
248 BUS_DAEMON_IFACE, 'RequestName',
249 'su', (name, flags))
251 def release_name(self, name):
252 """Release a bus name.
254 :Parameters:
255 `name` : str
256 The well-known name to be released
257 :Returns: `DBUS_RELEASE_NAME_REPLY_RELEASED`,
258 `DBUS_RELEASE_NAME_REPLY_NON_EXISTENT`
259 or `DBUS_RELEASE_NAME_REPLY_NOT_OWNER`
260 :Raises DBusException: if the bus daemon cannot be contacted or
261 returns an error.
263 validate_bus_name(name, allow_unique=False)
264 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
265 BUS_DAEMON_IFACE, 'ReleaseName',
266 's', (name,))
268 def list_names(self):
269 """Return a list of all currently-owned names on the bus.
271 :Returns: a dbus.Array of dbus.UTF8String
273 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
274 BUS_DAEMON_IFACE, 'ListNames',
275 '', (), utf8_strings=True)
277 def list_activatable_names(self):
278 """Return a list of all names that can be activated on the bus.
280 :Returns: a dbus.Array of dbus.UTF8String
282 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
283 BUS_DAEMON_IFACE, 'ListNames',
284 '', (), utf8_strings=True)
286 def get_name_owner(self, bus_name):
287 """Return the unique connection name of the primary owner of the
288 given name.
290 :Raises DBusException: if the `bus_name` has no owner
292 validate_bus_name(bus_name, allow_unique=False)
293 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
294 BUS_DAEMON_IFACE, 'GetNameOwner',
295 's', (bus_name,), utf8_strings=True)
297 def name_has_owner(self, bus_name):
298 """Return True iff the given bus name has an owner on this bus.
300 :Parameters:
301 `name` : str
302 The bus name to look up
303 :Returns: a `bool`
305 return bool(self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
306 BUS_DAEMON_IFACE, 'NameHasOwner',
307 's', (bus_name,)))
309 def add_match_string(self, rule):
310 """Arrange for this application to receive messages on the bus that
311 match the given rule. This version will block.
313 :Parameters:
314 `rule` : str
315 The match rule
316 :Raises: `DBusException` on error.
318 self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
319 BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,))
321 # FIXME: add an async success/error handler capability?
322 # (and the same for remove_...)
323 def add_match_string_non_blocking(self, rule):
324 """Arrange for this application to receive messages on the bus that
325 match the given rule. This version will not block, but any errors
326 will be ignored.
329 :Parameters:
330 `rule` : str
331 The match rule
332 :Raises: `DBusException` on error.
334 self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
335 BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,),
336 None, None)
338 def remove_match_string(self, rule):
339 """Arrange for this application to receive messages on the bus that
340 match the given rule. This version will block.
342 :Parameters:
343 `rule` : str
344 The match rule
345 :Raises: `DBusException` on error.
347 self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
348 BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,))
350 def remove_match_string_non_blocking(self, rule):
351 """Arrange for this application to receive messages on the bus that
352 match the given rule. This version will not block, but any errors
353 will be ignored.
356 :Parameters:
357 `rule` : str
358 The match rule
359 :Raises: `DBusException` on error.
361 self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
362 BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,),
363 None, None)