| 194 | |
| 195 | |
| 196 | class RelayedSignal(Signal): |
| 197 | """Base class for signals relayed from another source. |
| 198 | |
| 199 | Will attach to / detach from the source if there are |
| 200 | any / no listeners. |
| 201 | """ |
| 202 | |
| 203 | def __init__(self, *args, **kwargs): |
| 204 | Signal.__init__(self, *args, **kwargs) |
| 205 | self._attached = False |
| 206 | |
| 207 | def _attach(self): |
| 208 | """Override in subclass to attach to source.""" |
| 209 | raise NotImplementedError() |
| 210 | |
| 211 | def _detach(self): |
| 212 | """Override in subclass to detach from source.""" |
| 213 | raise NotImplementedError() |
| 214 | |
| 215 | def connect(self, *args, **kwargs): |
| 216 | Signal.connect(self, *args, **kwargs) |
| 217 | if not self._attached: |
| 218 | self._attach() |
| 219 | self._attached = True |
| 220 | |
| 221 | def disconnect(self, *args, **kwargs): |
| 222 | Signal.disconnect(self, *args, **kwargs) |
| 223 | if self._attached and not self.receivers: |
| 224 | self._detach() |
| 225 | self._attached = False |
| 226 | |
| 227 | |
| 228 | class DbusSignal(RelayedSignal): |
| 229 | """"Relay for DBus signals.""" |
| 230 | |
| 231 | def __init__(self, signal_name, signal_args, iface=None, bus=None, |
| 232 | iface_name=None, service_name=None, path=None): |
| 233 | """Set up a relay for given DBus signal. |
| 234 | |
| 235 | Mandatory arguments: |
| 236 | signal_name -- name of the DBus signal to relay |
| 237 | signal_args -- names of the arguments the signal passes |
| 238 | |
| 239 | Keyword arguments: |
| 240 | bus -- dbus.Bus instance |
| 241 | iface_name -- DBus interface name |
| 242 | service_name -- DBus service name |
| 243 | path -- DBus path |
| 244 | |
| 245 | Either iface or at least bus (and optionally iface_name, service_name |
| 246 | and path) must be given. |
| 247 | """ |
| 248 | if iface and (bus or iface_name or service_name or path): |
| 249 | raise ValueError('Ambiguous: both iface and' |
| 250 | ' bus/iface_name/service_name/path given.') |
| 251 | elif not (iface or bus): |
| 252 | raise ValueError('Either iface or bus must be given.') |
| 253 | |
| 254 | self._signal_name = signal_name |
| 255 | self._signal_args = signal_args |
| 256 | self._iface = iface |
| 257 | self._bus = bus |
| 258 | self._iface_name = iface_name |
| 259 | self._service_name = service_name |
| 260 | self._path = path |
| 261 | self._match = None |
| 262 | RelayedSignal.__init__(self, signal_args) |
| 263 | |
| 264 | def _attach(self): |
| 265 | if self._iface: |
| 266 | self._match = self._iface.connect_to_signal(self._signal_name, |
| 267 | self._relay) |
| 268 | else: |
| 269 | self._match = self._bus.add_signal_receiver(self._relay, |
| 270 | signal_name=self._signal_name, dbus_interface=self._iface_name, |
| 271 | bus_name=self._service_name, path=self._path) |
| 272 | |
| 273 | def _detach(self): |
| 274 | self._match.remove() |
| 275 | |
| 276 | def _relay(self, *args): |
| 277 | """Relay signal. |
| 278 | |
| 279 | Positional arguments are mapped to signal_args in order. |
| 280 | """ |
| 281 | self.send(None, **dict( |
| 282 | [(name, value) for (name, value) in zip(self._signal_args, args)])) |