1"""Implementation for dbus.Bus. Not to be imported directly."""
2
3# Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
4# Copyright (C) 2003 David Zeuthen
5# Copyright (C) 2004 Rob Taylor
6# Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
7#
8# SPDX-License-Identifier: MIT
9#
10# Permission is hereby granted, free of charge, to any person
11# obtaining a copy of this software and associated documentation
12# files (the "Software"), to deal in the Software without
13# restriction, including without limitation the rights to use, copy,
14# modify, merge, publish, distribute, sublicense, and/or sell copies
15# of the Software, and to permit persons to whom the Software is
16# furnished to do so, subject to the following conditions:
17#
18# The above copyright notice and this permission notice shall be
19# included in all copies or substantial portions of the Software.
20#
21# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
25# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
26# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28# DEALINGS IN THE SOFTWARE.
29
30from __future__ import generators
31
32__all__ = ('Bus', 'SystemBus', 'SessionBus', 'StarterBus')
33__docformat__ = 'reStructuredText'
34
35from dbus.exceptions import DBusException
36from _dbus_bindings import (
37    BUS_DAEMON_IFACE, BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_SESSION,
38    BUS_STARTER, BUS_SYSTEM, DBUS_START_REPLY_ALREADY_RUNNING,
39    DBUS_START_REPLY_SUCCESS, validate_bus_name,
40    validate_interface_name, validate_member_name, validate_object_path)
41from dbus.bus import BusConnection
42from dbus.lowlevel import SignalMessage
43from dbus._compat import is_py2
44
45if is_py2:
46    from _dbus_bindings import UTF8String
47
48
49class Bus(BusConnection):
50    """A connection to one of three possible standard buses, the SESSION,
51    SYSTEM, or STARTER bus. This class manages shared connections to those
52    buses.
53
54    If you're trying to subclass `Bus`, you may be better off subclassing
55    `BusConnection`, which doesn't have all this magic.
56    """
57
58    _shared_instances = {}
59
60    def __new__(cls, bus_type=BusConnection.TYPE_SESSION, private=False,
61                mainloop=None):
62        """Constructor, returning an existing instance where appropriate.
63
64        The returned instance is actually always an instance of `SessionBus`,
65        `SystemBus` or `StarterBus`.
66
67        :Parameters:
68            `bus_type` : cls.TYPE_SESSION, cls.TYPE_SYSTEM or cls.TYPE_STARTER
69                Connect to the appropriate bus
70            `private` : bool
71                If true, never return an existing shared instance, but instead
72                return a private connection.
73
74                :Deprecated: since 0.82.3. Use dbus.bus.BusConnection for
75                    private connections.
76
77            `mainloop` : dbus.mainloop.NativeMainLoop
78                The main loop to use. The default is to use the default
79                main loop if one has been set up, or raise an exception
80                if none has been.
81        :Changed: in dbus-python 0.80:
82            converted from a wrapper around a Connection to a Connection
83            subclass.
84        """
85        if (not private and bus_type in cls._shared_instances):
86            return cls._shared_instances[bus_type]
87
88        # this is a bit odd, but we create instances of the subtypes
89        # so we can return the shared instances if someone tries to
90        # construct one of them (otherwise we'd eg try and return an
91        # instance of Bus from __new__ in SessionBus). why are there
92        # three ways to construct this class? we just don't know.
93        if bus_type == BUS_SESSION:
94            subclass = SessionBus
95        elif bus_type == BUS_SYSTEM:
96            subclass = SystemBus
97        elif bus_type == BUS_STARTER:
98            subclass = StarterBus
99        else:
100            raise ValueError('invalid bus_type %s' % bus_type)
101
102        bus = BusConnection.__new__(subclass, bus_type, mainloop=mainloop)
103
104        bus._bus_type = bus_type
105
106        if not private:
107            cls._shared_instances[bus_type] = bus
108
109        return bus
110
111    def close(self):
112        t = self._bus_type
113        if self.__class__._shared_instances.get(t) is self:
114            del self.__class__._shared_instances[t]
115        super(Bus, self).close()
116
117    def get_connection(self):
118        """Return self, for backwards compatibility with earlier dbus-python
119        versions where Bus was not a subclass of Connection.
120
121        :Deprecated: since 0.80.0
122        """
123        return self
124    _connection = property(get_connection, None, None,
125                           """self._connection == self, for backwards
126                           compatibility with earlier dbus-python versions
127                           where Bus was not a subclass of Connection.""")
128
129    def get_session(private=False):
130        """Static method that returns a connection to the session bus.
131
132        :Parameters:
133            `private` : bool
134                If true, do not return a shared connection.
135        """
136        return SessionBus(private=private)
137
138    get_session = staticmethod(get_session)
139
140    def get_system(private=False):
141        """Static method that returns a connection to the system bus.
142
143        :Parameters:
144            `private` : bool
145                If true, do not return a shared connection.
146        """
147        return SystemBus(private=private)
148
149    get_system = staticmethod(get_system)
150
151
152    def get_starter(private=False):
153        """Static method that returns a connection to the starter bus.
154
155        :Parameters:
156            `private` : bool
157                If true, do not return a shared connection.
158        """
159        return StarterBus(private=private)
160
161    get_starter = staticmethod(get_starter)
162
163    def __repr__(self):
164        if self._bus_type == BUS_SESSION:
165            name = 'session'
166        elif self._bus_type == BUS_SYSTEM:
167            name = 'system'
168        elif self._bus_type == BUS_STARTER:
169            name = 'starter'
170        else:
171            name = 'unknown bus type'
172
173        return '<%s.%s (%s) at %#x>' % (self.__class__.__module__,
174                                        self.__class__.__name__,
175                                        name, id(self))
176    __str__ = __repr__
177
178
179# FIXME: Drop the subclasses here? I can't think why we'd ever want
180# polymorphism
181class SystemBus(Bus):
182    """The system-wide message bus."""
183    def __new__(cls, private=False, mainloop=None):
184        """Return a connection to the system bus.
185
186        :Parameters:
187            `private` : bool
188                If true, never return an existing shared instance, but instead
189                return a private connection.
190            `mainloop` : dbus.mainloop.NativeMainLoop
191                The main loop to use. The default is to use the default
192                main loop if one has been set up, or raise an exception
193                if none has been.
194        """
195        return Bus.__new__(cls, Bus.TYPE_SYSTEM, mainloop=mainloop,
196                           private=private)
197
198class SessionBus(Bus):
199    """The session (current login) message bus."""
200    def __new__(cls, private=False, mainloop=None):
201        """Return a connection to the session bus.
202
203        :Parameters:
204            `private` : bool
205                If true, never return an existing shared instance, but instead
206                return a private connection.
207            `mainloop` : dbus.mainloop.NativeMainLoop
208                The main loop to use. The default is to use the default
209                main loop if one has been set up, or raise an exception
210                if none has been.
211        """
212        return Bus.__new__(cls, Bus.TYPE_SESSION, private=private,
213                           mainloop=mainloop)
214
215class StarterBus(Bus):
216    """The bus that activated this process (only valid if
217    this process was launched by DBus activation).
218    """
219    def __new__(cls, private=False, mainloop=None):
220        """Return a connection to the bus that activated this process.
221
222        :Parameters:
223            `private` : bool
224                If true, never return an existing shared instance, but instead
225                return a private connection.
226            `mainloop` : dbus.mainloop.NativeMainLoop
227                The main loop to use. The default is to use the default
228                main loop if one has been set up, or raise an exception
229                if none has been.
230        """
231        return Bus.__new__(cls, Bus.TYPE_STARTER, private=private,
232                           mainloop=mainloop)
233