diff --git a/ui/bin/package b/ui/bin/package index 71c13ab..d66e731 100755 --- a/ui/bin/package +++ b/ui/bin/package @@ -37,6 +37,7 @@ mkdir -p $PACKAGE_APPS_DIR mkdir -p $PACKAGE_SCHEMAS_DIR cp src/*.py $PACKAGE_BREEZY_SRC_DIR +cp -r lib $PACKAGE_BREEZY_SRC_DIR cp -L modules/PyXRLinuxDriverIPC/xrdriveripc.py $PACKAGE_BREEZY_SRC_DIR cp $UI_BUILD_PATH/src/breezydesktop.gresource $PACKAGE_BREEZY_DIR cp -r po/mo/* $PACKAGE_LOCALE_DIR diff --git a/ui/lib/pydbus-0.6.0.dist-info/DESCRIPTION.rst b/ui/lib/pydbus-0.6.0.dist-info/DESCRIPTION.rst new file mode 100644 index 0000000..935d652 --- /dev/null +++ b/ui/lib/pydbus-0.6.0.dist-info/DESCRIPTION.rst @@ -0,0 +1,115 @@ +pydbus +====== +.. image:: https://travis-ci.org/LEW21/pydbus.svg?branch=master + :target: https://travis-ci.org/LEW21/pydbus +.. image:: https://badge.fury.io/py/pydbus.svg + :target: https://badge.fury.io/py/pydbus + +Pythonic DBus library. + +Changelog: https://github.com/LEW21/pydbus/releases + +Requirements +------------ +* Python 2.7+ - but works best on 3.4+ (help system is nicer there) +* PyGI_ (not packaged on pypi, you need to install it from your distribution's repository - it's usually called python-gi, python-gobject or pygobject) +* GLib_ 2.46+ and girepository_ 1.46+ (Ubuntu 16.04+) - for object publication support + +.. _PyGI: https://wiki.gnome.org/Projects/PyGObject +.. _GLib: https://developer.gnome.org/glib/ +.. _girepository: https://wiki.gnome.org/Projects/GObjectIntrospection + +Examples +-------- + +Send a desktop notification +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. code-block:: python + + from pydbus import SessionBus + + bus = SessionBus() + notifications = bus.get('.Notifications') + + notifications.Notify('test', 0, 'dialog-information', "Hello World!", "pydbus works :)", [], {}, 5000) + +List systemd units +~~~~~~~~~~~~~~~~~~ +.. code-block:: python + + from pydbus import SystemBus + + bus = SystemBus() + systemd = bus.get(".systemd1") + + for unit in systemd.ListUnits(): + print(unit) + +Start or stop systemd unit +~~~~~~~~~~~~~~~~~~ +.. code-block:: python + + from pydbus import SystemBus + + bus = SystemBus() + systemd = bus.get(".systemd1") + + job1 = systemd.StopUnit("ssh.service", "fail") + job2 = systemd.StartUnit("ssh.service", "fail") + +Watch for new systemd jobs +~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. code-block:: python + + from pydbus import SystemBus + from gi.repository import GLib + + bus = SystemBus() + systemd = bus.get(".systemd1") + + systemd.JobNew.connect(print) + GLib.MainLoop().run() + + # or + + systemd.onJobNew = print + GLib.MainLoop().run() + +View object's API +~~~~~~~~~~~~~~~~~ +.. code-block:: python + + from pydbus import SessionBus + + bus = SessionBus() + notifications = bus.get('.Notifications') + + help(notifications) + +More examples & documentation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Tutorial_ contains more examples and docs. + +.. _Tutorial: https://github.com/LEW21/pydbus/blob/master/doc/tutorial.rst + +Copyright Information +--------------------- + +Copyright (C) 2014, 2015, 2016 Linus Lewandowski + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + diff --git a/ui/lib/pydbus-0.6.0.dist-info/INSTALLER b/ui/lib/pydbus-0.6.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/ui/lib/pydbus-0.6.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/ui/lib/pydbus-0.6.0.dist-info/METADATA b/ui/lib/pydbus-0.6.0.dist-info/METADATA new file mode 100644 index 0000000..83f96fb --- /dev/null +++ b/ui/lib/pydbus-0.6.0.dist-info/METADATA @@ -0,0 +1,137 @@ +Metadata-Version: 2.0 +Name: pydbus +Version: 0.6.0 +Summary: Pythonic DBus library +Home-page: https://github.com/LEW21/pydbus +Author: Linus Lewandowski +Author-email: linus@lew21.net +License: LGPLv2+ +Keywords: dbus +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+) +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 + +pydbus +====== +.. image:: https://travis-ci.org/LEW21/pydbus.svg?branch=master + :target: https://travis-ci.org/LEW21/pydbus +.. image:: https://badge.fury.io/py/pydbus.svg + :target: https://badge.fury.io/py/pydbus + +Pythonic DBus library. + +Changelog: https://github.com/LEW21/pydbus/releases + +Requirements +------------ +* Python 2.7+ - but works best on 3.4+ (help system is nicer there) +* PyGI_ (not packaged on pypi, you need to install it from your distribution's repository - it's usually called python-gi, python-gobject or pygobject) +* GLib_ 2.46+ and girepository_ 1.46+ (Ubuntu 16.04+) - for object publication support + +.. _PyGI: https://wiki.gnome.org/Projects/PyGObject +.. _GLib: https://developer.gnome.org/glib/ +.. _girepository: https://wiki.gnome.org/Projects/GObjectIntrospection + +Examples +-------- + +Send a desktop notification +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. code-block:: python + + from pydbus import SessionBus + + bus = SessionBus() + notifications = bus.get('.Notifications') + + notifications.Notify('test', 0, 'dialog-information', "Hello World!", "pydbus works :)", [], {}, 5000) + +List systemd units +~~~~~~~~~~~~~~~~~~ +.. code-block:: python + + from pydbus import SystemBus + + bus = SystemBus() + systemd = bus.get(".systemd1") + + for unit in systemd.ListUnits(): + print(unit) + +Start or stop systemd unit +~~~~~~~~~~~~~~~~~~ +.. code-block:: python + + from pydbus import SystemBus + + bus = SystemBus() + systemd = bus.get(".systemd1") + + job1 = systemd.StopUnit("ssh.service", "fail") + job2 = systemd.StartUnit("ssh.service", "fail") + +Watch for new systemd jobs +~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. code-block:: python + + from pydbus import SystemBus + from gi.repository import GLib + + bus = SystemBus() + systemd = bus.get(".systemd1") + + systemd.JobNew.connect(print) + GLib.MainLoop().run() + + # or + + systemd.onJobNew = print + GLib.MainLoop().run() + +View object's API +~~~~~~~~~~~~~~~~~ +.. code-block:: python + + from pydbus import SessionBus + + bus = SessionBus() + notifications = bus.get('.Notifications') + + help(notifications) + +More examples & documentation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Tutorial_ contains more examples and docs. + +.. _Tutorial: https://github.com/LEW21/pydbus/blob/master/doc/tutorial.rst + +Copyright Information +--------------------- + +Copyright (C) 2014, 2015, 2016 Linus Lewandowski + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + diff --git a/ui/lib/pydbus-0.6.0.dist-info/RECORD b/ui/lib/pydbus-0.6.0.dist-info/RECORD new file mode 100644 index 0000000..a194108 --- /dev/null +++ b/ui/lib/pydbus-0.6.0.dist-info/RECORD @@ -0,0 +1,45 @@ +pydbus-0.6.0.dist-info/DESCRIPTION.rst,sha256=pKEYrpPtLiD4ksnqzDs6ZepA9c3uoy5Mjvuxoi1_pEU,3033 +pydbus-0.6.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pydbus-0.6.0.dist-info/METADATA,sha256=JKSoIIJKdCLkuWiXdKtv4bSyMt9rA7BM3_RJbxqk5y4,3838 +pydbus-0.6.0.dist-info/RECORD,, +pydbus-0.6.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pydbus-0.6.0.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110 +pydbus-0.6.0.dist-info/metadata.json,sha256=fouQjd0oTDJrVRvQO8dwqvMZbNib8Ui4TgArvHoxzmQ,916 +pydbus-0.6.0.dist-info/top_level.txt,sha256=X1ybDik1ZA7yilKlt-MUQCPJBNlAW9Jp6bHoAYgAgN0,7 +pydbus-0.6.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 +pydbus/__init__.py,sha256=5ouUtYT15bJ-LaEFea3ZccXkhlZHyf1ykwKLNnFMlIs,148 +pydbus/__pycache__/__init__.cpython-312.pyc,, +pydbus/__pycache__/_inspect3.cpython-312.pyc,, +pydbus/__pycache__/auto_names.cpython-312.pyc,, +pydbus/__pycache__/bus.cpython-312.pyc,, +pydbus/__pycache__/bus_names.cpython-312.pyc,, +pydbus/__pycache__/exitable.cpython-312.pyc,, +pydbus/__pycache__/generic.cpython-312.pyc,, +pydbus/__pycache__/identifier.cpython-312.pyc,, +pydbus/__pycache__/method_call_context.cpython-312.pyc,, +pydbus/__pycache__/proxy.cpython-312.pyc,, +pydbus/__pycache__/proxy_method.cpython-312.pyc,, +pydbus/__pycache__/proxy_property.cpython-312.pyc,, +pydbus/__pycache__/proxy_signal.cpython-312.pyc,, +pydbus/__pycache__/publication.cpython-312.pyc,, +pydbus/__pycache__/registration.cpython-312.pyc,, +pydbus/__pycache__/request_name.cpython-312.pyc,, +pydbus/__pycache__/subscription.cpython-312.pyc,, +pydbus/__pycache__/timeout.cpython-312.pyc,, +pydbus/_inspect3.py,sha256=lDzzSThwvbMQ3PwY-SVpWByu___QPhbSvPY0xwQScc8,708 +pydbus/auto_names.py,sha256=elJqIT2dIhioBYUMvIqtH6Pt_tFCf29QSzp0QXbndy8,519 +pydbus/bus.py,sha256=dMJAqRq85TJAz8iDZSg8COhjy8sabrofZ2jjSe6Pxds,1565 +pydbus/bus_names.py,sha256=vdkfHdQy_9FzMWkJaXJZpDpvktV9N2TFyRzLE8X1cDI,3533 +pydbus/exitable.py,sha256=FblBVEUjz6DyqgJpOVlE8-TMeVaC1ZPf9wyi7ejQ-gI,982 +pydbus/generic.py,sha256=2K9VNwu-TkMWws818H2BiyLsoX99fZuJgbmoNtVLzjE,2589 +pydbus/identifier.py,sha256=iVrw6rDMyNiQouNUAMpvEhGCz1Bog-R0HwzmvsHs0LY,383 +pydbus/method_call_context.py,sha256=ilIh0jJmmdoJX26UF1uV9eYvdQXXgYoyxzNHKvIJcJA,1039 +pydbus/proxy.py,sha256=Og9VbKJ4sZvr4dI7cBwVKsklWAqfnEmAlSpoaPJIs-g,4203 +pydbus/proxy_method.py,sha256=I741zD_vBixhpeKArTVLaUDTQsZGJ98xaX2SGORJkb0,3089 +pydbus/proxy_property.py,sha256=WUkOV4V8DuvmN1-yAD54IJklZqgJex2eTV2KZ20PJ1k,1066 +pydbus/proxy_signal.py,sha256=D60OxO635erbIknziTzX3QDChhImmjzgUpwjVe8gy6A,2112 +pydbus/publication.py,sha256=oJKPraVVv0YPLWN2-y0KjjjW1Lu4NNTg9lNoVneYLlY,1391 +pydbus/registration.py,sha256=GWBv2lDDLrkYCr9WsaEFS1hZstuXB7OgHdEnvC0Shc4,5565 +pydbus/request_name.py,sha256=jpWNd8H8rlRdJQiNAud-di1jU6gE31JvLOtoXQm_lHE,883 +pydbus/subscription.py,sha256=MBXKRIvLvSgYLIT7A0a3Qn6aAvT6Rcx1b6xudTsr7mQ,2166 +pydbus/timeout.py,sha256=G4o9dVwN7hb4MaPUHlcGpQKtlUSaTGjBTBC7nuza9mI,303 diff --git a/ui/lib/pydbus-0.6.0.dist-info/REQUESTED b/ui/lib/pydbus-0.6.0.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/ui/lib/pydbus-0.6.0.dist-info/WHEEL b/ui/lib/pydbus-0.6.0.dist-info/WHEEL new file mode 100644 index 0000000..8b6dd1b --- /dev/null +++ b/ui/lib/pydbus-0.6.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.29.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/ui/lib/pydbus-0.6.0.dist-info/metadata.json b/ui/lib/pydbus-0.6.0.dist-info/metadata.json new file mode 100644 index 0000000..898cdff --- /dev/null +++ b/ui/lib/pydbus-0.6.0.dist-info/metadata.json @@ -0,0 +1 @@ +{"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Natural Language :: English", "License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7"], "extensions": {"python.details": {"contacts": [{"email": "linus@lew21.net", "name": "Linus Lewandowski", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/LEW21/pydbus"}}}, "generator": "bdist_wheel (0.29.0)", "keywords": ["dbus"], "license": "LGPLv2+", "metadata_version": "2.0", "name": "pydbus", "summary": "Pythonic DBus library", "version": "0.6.0"} \ No newline at end of file diff --git a/ui/lib/pydbus-0.6.0.dist-info/top_level.txt b/ui/lib/pydbus-0.6.0.dist-info/top_level.txt new file mode 100644 index 0000000..d2fe79b --- /dev/null +++ b/ui/lib/pydbus-0.6.0.dist-info/top_level.txt @@ -0,0 +1 @@ +pydbus diff --git a/ui/lib/pydbus-0.6.0.dist-info/zip-safe b/ui/lib/pydbus-0.6.0.dist-info/zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ui/lib/pydbus-0.6.0.dist-info/zip-safe @@ -0,0 +1 @@ + diff --git a/ui/lib/pydbus/__init__.py b/ui/lib/pydbus/__init__.py new file mode 100644 index 0000000..5576517 --- /dev/null +++ b/ui/lib/pydbus/__init__.py @@ -0,0 +1,4 @@ +from .bus import SystemBus, SessionBus, connect +from gi.repository.GLib import Variant + +__all__ = ["SystemBus", "SessionBus", "connect", "Variant"] diff --git a/ui/lib/pydbus/_inspect3.py b/ui/lib/pydbus/_inspect3.py new file mode 100644 index 0000000..b0a2f22 --- /dev/null +++ b/ui/lib/pydbus/_inspect3.py @@ -0,0 +1,28 @@ +from collections import OrderedDict +from inspect import getargspec + +class _empty: + pass + +class Signature: + empty = _empty + + def __init__(self, parameters=None, return_annotation=_empty): + self.parameters = OrderedDict(((param.name, param) for param in parameters)) + self.return_annotation = return_annotation + +class Parameter: + empty = _empty + + POSITIONAL_ONLY = 0 + POSITIONAL_OR_KEYWORD = 1 + KEYWORD_ONLY = 999 + + def __init__(self, name, kind, default=_empty, annotation=_empty): + self.name = name + self.kind = kind + self.annotation = annotation + +def signature(f): + parameters = [Parameter(arg, Parameter.POSITIONAL_OR_KEYWORD) for arg in getargspec(f).args] + return Signature(parameters = parameters) diff --git a/ui/lib/pydbus/auto_names.py b/ui/lib/pydbus/auto_names.py new file mode 100644 index 0000000..06c6c18 --- /dev/null +++ b/ui/lib/pydbus/auto_names.py @@ -0,0 +1,21 @@ +from gi.repository import Gio + +def auto_bus_name(bus_name): + if bus_name[0] == ".": + #Default namespace + bus_name = "org.freedesktop" + bus_name + + if not Gio.dbus_is_name(bus_name): + raise ValueError("invalid bus name") + + return bus_name + +def auto_object_path(bus_name, object_path=None): + if object_path is None: + # They always name it like that. + object_path = "/" + bus_name.replace(".", "/") + + if object_path[0] != "/": + object_path = "/" + bus_name.replace(".", "/") + "/" + object_path + + return object_path diff --git a/ui/lib/pydbus/bus.py b/ui/lib/pydbus/bus.py new file mode 100644 index 0000000..6726390 --- /dev/null +++ b/ui/lib/pydbus/bus.py @@ -0,0 +1,60 @@ +from gi.repository import Gio +from .proxy import ProxyMixin +from .request_name import RequestNameMixin +from .bus_names import OwnMixin, WatchMixin +from .subscription import SubscriptionMixin +from .registration import RegistrationMixin +from .publication import PublicationMixin + +def pydbus_property(self): + try: + return self._pydbus + except AttributeError: + self._pydbus = Bus(self) + return self._pydbus + +Gio.DBusConnection.pydbus = property(pydbus_property) + +def bus_get(type): + return Gio.bus_get_sync(type, None).pydbus + +def connect(address): + c = Gio.DBusConnection.new_for_address_sync(address, Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT | Gio.DBusConnectionFlags.MESSAGE_BUS_CONNECTION, None, None) + c.pydbus.autoclose = True + return c.pydbus + +class Bus(ProxyMixin, RequestNameMixin, OwnMixin, WatchMixin, SubscriptionMixin, RegistrationMixin, PublicationMixin): + Type = Gio.BusType + + def __init__(self, gio_con): + self.con = gio_con + self.autoclose = False + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + if self.autoclose: + self.con.close_sync(None) + + @property + def dbus(self): + try: + return self._dbus + except AttributeError: + self._dbus = self.get(".DBus")[""] + return self._dbus + + @property + def polkit_authority(self): + try: + return self._polkit_authority + except AttributeError: + self._polkit_authority = self.get(".PolicyKit1", "Authority")[""] + return self._polkit_authority + +def SystemBus(): + return bus_get(Bus.Type.SYSTEM) + +def SessionBus(): + return bus_get(Bus.Type.SESSION) diff --git a/ui/lib/pydbus/bus_names.py b/ui/lib/pydbus/bus_names.py new file mode 100644 index 0000000..3b27431 --- /dev/null +++ b/ui/lib/pydbus/bus_names.py @@ -0,0 +1,97 @@ +from gi.repository import Gio +from .exitable import ExitableWithAliases +import warnings + +class NameOwner(ExitableWithAliases("unown")): + Flags = Gio.BusNameOwnerFlags + __slots__ = () + + def __init__(self, con, name, flags, name_aquired_handler, name_lost_handler): + id = Gio.bus_own_name_on_connection(con, name, flags, name_aquired_handler, name_lost_handler) + self._at_exit(lambda: Gio.bus_unown_name(id)) + +class NameWatcher(ExitableWithAliases("unwatch")): + Flags = Gio.BusNameWatcherFlags + __slots__ = () + + def __init__(self, con, name, flags, name_appeared_handler, name_vanished_handler): + id = Gio.bus_watch_name_on_connection(con, name, flags, name_appeared_handler, name_vanished_handler) + self._at_exit(lambda: Gio.bus_unwatch_name(id)) + +class OwnMixin(object): + __slots__ = () + NameOwnerFlags = NameOwner.Flags + + def own_name(self, name, flags=0, name_aquired=None, name_lost=None): + """[DEPRECATED] Asynchronously aquires a bus name. + + Starts acquiring name on the bus specified by bus_type and calls + name_acquired and name_lost when the name is acquired respectively lost. + + To receive name_aquired and name_lost callbacks, you need an event loop. + https://github.com/LEW21/pydbus/blob/master/doc/tutorial.rst#setting-up-an-event-loop + + Parameters + ---------- + name : string + Bus name to aquire + flags : NameOwnerFlags, optional + name_aquired : callable, optional + Invoked when name is acquired + name_lost : callable, optional + Invoked when name is lost + + Returns + ------- + NameOwner + An object you can use as a context manager to unown the name later. + + See Also + -------- + See https://developer.gnome.org/gio/2.44/gio-Owning-Bus-Names.html#g-bus-own-name + for more information. + """ + warnings.warn("own_name() is deprecated, use request_name() instead.", DeprecationWarning) + + name_aquired_handler = (lambda con, name: name_aquired()) if name_aquired is not None else None + name_lost_handler = (lambda con, name: name_lost()) if name_lost is not None else None + return NameOwner(self.con, name, flags, name_aquired_handler, name_lost_handler) + +class WatchMixin(object): + __slots__ = () + NameWatcherFlags = NameWatcher.Flags + + def watch_name(self, name, flags=0, name_appeared=None, name_vanished=None): + """Asynchronously watches a bus name. + + Starts watching name on the bus specified by bus_type and calls + name_appeared and name_vanished when the name is known to have a owner + respectively known to lose its owner. + + To receive name_appeared and name_vanished callbacks, you need an event loop. + https://github.com/LEW21/pydbus/blob/master/doc/tutorial.rst#setting-up-an-event-loop + + Parameters + ---------- + name : string + Bus name to watch + flags : NameWatcherFlags, optional + name_appeared : callable, optional + Invoked when name is known to exist + Called as name_appeared(name_owner). + name_vanished : callable, optional + Invoked when name is known to not exist + + Returns + ------- + NameWatcher + An object you can use as a context manager to unwatch the name later. + + See Also + -------- + See https://developer.gnome.org/gio/2.44/gio-Watching-Bus-Names.html#g-bus-watch-name + for more information. + """ + name_appeared_handler = (lambda con, name, name_owner: name_appeared(name_owner)) if name_appeared is not None else None + name_vanished_handler = (lambda con, name: name_vanished()) if name_vanished is not None else None + return NameWatcher(self.con, name, flags, name_appeared_handler, name_vanished_handler) diff --git a/ui/lib/pydbus/exitable.py b/ui/lib/pydbus/exitable.py new file mode 100644 index 0000000..0349043 --- /dev/null +++ b/ui/lib/pydbus/exitable.py @@ -0,0 +1,52 @@ +import inspect + +class Exitable(object): + __slots__ = ("_at_exit_cbs") + + def _at_exit(self, cb): + try: + self._at_exit_cbs + except AttributeError: + self._at_exit_cbs = [] + + self._at_exit_cbs.append(cb) + + def __enter__(self): + return self + + def __exit__(self, exc_type = None, exc_value = None, traceback = None): + if self._exited: + return + + for cb in reversed(self._at_exit_cbs): + call_with_exc = True + try: + inspect.getcallargs(cb, exc_type, exc_value, traceback) + except TypeError: + call_with_exc = False + + if call_with_exc: + cb(exc_type, exc_value, traceback) + else: + cb() + + self._at_exit_cbs = None + + @property + def _exited(self): + try: + return self._at_exit_cbs is None + except AttributeError: + return True + +def ExitableWithAliases(*exit_methods): + class CustomExitable(Exitable): + pass + + def exit(self): + self.__exit__() + + for exit_method_name in exit_methods: + setattr(CustomExitable, exit_method_name, exit) + + return CustomExitable diff --git a/ui/lib/pydbus/generic.py b/ui/lib/pydbus/generic.py new file mode 100644 index 0000000..abeb7ce --- /dev/null +++ b/ui/lib/pydbus/generic.py @@ -0,0 +1,105 @@ +"""Generic programming utilities. + +Utilities implemented in this file are not dependent +on dbus, they can be used everywhere. +""" + +class subscription(object): + __slots__ = ("callback_list", "callback") + + def __init__(self, callback_list, callback): + self.callback_list = callback_list + self.callback = callback + self.callback_list.append(callback) + + def unsubscribe(self): + self.callback_list.remove(self.callback) + self.callback_list = None + self.callback = None + + def disconnect(self): + """An alias for unsubscribe()""" + self.unsubscribe() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + if not self.callback is None: + self.unsubscribe() + +class bound_signal(object): + __slots__ = ("__signal__", "__self__") # bound method uses ("__func__", "__self__") + + def __init__(self, signal, instance): + self.__signal__ = signal + self.__self__ = instance + + @property + def callbacks(self): + return self.__signal__.map[self.__self__] + + def connect(self, callback): + """Subscribe to the signal.""" + return self.__signal__.connect(self.__self__, callback) + + def emit(self, *args): + """Emit the signal.""" + self.__signal__.emit(self.__self__, *args) + + def __call__(self, *args): + """Emit the signal.""" + self.emit(*args) + + def __repr__(self): + return "" + +class signal(object): + """Static signal object + + You're expected to set it as a class property:: + + class A: + SomethingHappened = signal() + + Declared this way, it can be used on class instances + to connect signal observers:: + + a = A() + a.SomethingHappened.connect(func) + + and emit the signal:: + + a.SomethingHappened() + + You may pass any parameters to the emiting function + - they will be forwarded to all subscribed callbacks. + """ + + def __init__(self): + self.map = {} + self.__qualname__ = "" # function uses ;) + self.__doc__ = "Signal." + + def connect(self, object, callback): + """Subscribe to the signal.""" + return subscription(self.map.setdefault(object, []), callback) + + def emit(self, object, *args): + """Emit the signal.""" + for cb in self.map.get(object, []): + cb(*args) + + def __get__(self, instance, owner): + if instance is None: + return self + + return bound_signal(self, instance) + + def __set__(self, instance, value): + raise AttributeError("can't set attribute") + + def __repr__(self): + return "" + +bound_method = type(signal().emit) # TODO find a prettier way to get this type diff --git a/ui/lib/pydbus/identifier.py b/ui/lib/pydbus/identifier.py new file mode 100644 index 0000000..f7d4f31 --- /dev/null +++ b/ui/lib/pydbus/identifier.py @@ -0,0 +1,22 @@ + +try: + isident = str.isidentifier +except: + import re + + isidentre = re.compile("^[a-zA-Z_][a-zA-Z0-9_]*$") + def isident(s): + return isidentre.match(s) is not None + +def filter_identifier(name): + name = name.replace("-", "_") + + safe_name = "" + for c in name: + if not safe_name: + if isident(c): + safe_name += c + else: + if isident("a" + c): + safe_name += c + return safe_name diff --git a/ui/lib/pydbus/method_call_context.py b/ui/lib/pydbus/method_call_context.py new file mode 100644 index 0000000..a565631 --- /dev/null +++ b/ui/lib/pydbus/method_call_context.py @@ -0,0 +1,34 @@ +from gi.repository import GLib +from collections import namedtuple + +AuthorizationResult = namedtuple("AuthorizationResult", "is_authorized is_challenge details") + +class MethodCallContext(object): + def __init__(self, gdbus_method_invocation): + self._mi = gdbus_method_invocation + + @property + def bus(self): + return self._mi.get_connection().pydbus + + @property + def sender(self): + return self._mi.get_sender() + + @property + def object_path(self): + return self._mi.get_object_path() + + @property + def interface_name(self): + return self._mi.get_interface_name() + + @property + def method_name(self): + return self._mi.get_method_name() + + def check_authorization(self, action_id, details, interactive=False): + return AuthorizationResult(*self.bus.polkit_authority.CheckAuthorization(('system-bus-name', {'name': GLib.Variant("s", self.sender)}), action_id, details, 1 if interactive else 0, '')) + + def is_authorized(self, action_id, details, interactive=False): + return self.check_authorization(action_id, details, interactive).is_authorized diff --git a/ui/lib/pydbus/proxy.py b/ui/lib/pydbus/proxy.py new file mode 100644 index 0000000..c081113 --- /dev/null +++ b/ui/lib/pydbus/proxy.py @@ -0,0 +1,123 @@ +from gi.repository import GLib +from xml.etree import ElementTree as ET +from .auto_names import * + +from .proxy_method import ProxyMethod +from .proxy_property import ProxyProperty +from .proxy_signal import ProxySignal, OnSignal +from .timeout import timeout_to_glib + +class ProxyMixin(object): + __slots__ = () + + def get(self, bus_name, object_path=None, **kwargs): + """Get a remote object. + + Parameters + ---------- + bus_name : string + Name of the service that exposes this object. + You may start with "." - then org.freedesktop will be automatically prepended. + object_path : string, optional + Path of the object. If not provided, bus_name translated to path format is used. + + Returns + ------- + ProxyObject implementing all the Interfaces exposed by the remote object. + Note that it inherits from multiple Interfaces, so the method you want to use + may be shadowed by another one, eg. from a newer version of the interface. + Therefore, to interact with only a single interface, use: + >>> bus.get("org.freedesktop.systemd1")["org.freedesktop.systemd1.Manager"] + or simply + >>> bus.get(".systemd1")[".Manager"] + which will give you access to the one specific interface. + """ + # Python 2 sux + for kwarg in kwargs: + if kwarg not in ("timeout",): + raise TypeError(self.__qualname__ + " got an unexpected keyword argument '{}'".format(kwarg)) + timeout = kwargs.get("timeout", None) + + bus_name = auto_bus_name(bus_name) + object_path = auto_object_path(bus_name, object_path) + + ret = self.con.call_sync( + bus_name, object_path, + 'org.freedesktop.DBus.Introspectable', "Introspect", None, GLib.VariantType.new("(s)"), + 0, timeout_to_glib(timeout), None) + + if not ret: + raise KeyError("no such object; you might need to pass object path as the 2nd argument for get()") + + xml, = ret.unpack() + + try: + introspection = ET.fromstring(xml) + except: + raise KeyError("object provides invalid introspection XML") + + return CompositeInterface(introspection)(self, bus_name, object_path) + +class ProxyObject(object): + def __init__(self, bus, bus_name, path, object=None): + self._bus = bus + self._bus_name = bus_name + self._path = path + self._object = object if object else self + +def Interface(iface): + + class interface(ProxyObject): + @staticmethod + def _Introspect(): + print(iface.attrib["name"] + ":") + for member in iface: + print("\t" + member.tag + " " + member.attrib["name"]) + print() + + interface.__qualname__ = interface.__name__ = iface.attrib["name"] + interface.__module__ = "DBUS" + + for member in iface: + member_name = member.attrib["name"] + if member.tag == "method": + setattr(interface, member_name, ProxyMethod(interface.__name__, member)) + elif member.tag == "property": + setattr(interface, member_name, ProxyProperty(interface.__name__, member)) + elif member.tag == "signal": + signal = ProxySignal(interface.__name__, member) + setattr(interface, member_name, signal) + setattr(interface, "on" + member_name, OnSignal(signal)) + + return interface + +def CompositeInterface(introspection): + class CompositeObject(ProxyObject): + def __getitem__(self, iface): + if iface == "" or iface[0] == ".": + iface = self._path.replace("/", ".")[1:] + iface + matching_bases = [base for base in type(self).__bases__ if base.__name__ == iface] + + if len(matching_bases) == 0: + raise KeyError(iface) + assert(len(matching_bases) == 1) + + iface_class = matching_bases[0] + return iface_class(self._bus, self._bus_name, self._path, self) + + @classmethod + def _Introspect(cls): + for iface in cls.__bases__: + try: + iface._Introspect() + except: + pass + + ifaces = sorted([x for x in introspection if x.tag == "interface"], key=lambda x: int(x.attrib["name"].startswith("org.freedesktop.DBus."))) + if not ifaces: + raise KeyError("object does not export any interfaces; you might need to pass object path as the 2nd argument for get()") + CompositeObject.__bases__ = tuple(Interface(iface) for iface in ifaces) + CompositeObject.__name__ = "" + CompositeObject.__qualname__ = "(" + "+".join(x.__name__ for x in CompositeObject.__bases__) + ")" + CompositeObject.__module__ = "DBUS" + return CompositeObject diff --git a/ui/lib/pydbus/proxy_method.py b/ui/lib/pydbus/proxy_method.py new file mode 100644 index 0000000..8798edd --- /dev/null +++ b/ui/lib/pydbus/proxy_method.py @@ -0,0 +1,91 @@ +from gi.repository import GLib +from .generic import bound_method +from .identifier import filter_identifier +from .timeout import timeout_to_glib + +try: + from inspect import Signature, Parameter + put_signature_in_doc = False +except: + from ._inspect3 import Signature, Parameter + put_signature_in_doc = True + +class DBUSSignature(Signature): + + def __str__(self): + result = [] + for param in self.parameters.values(): + p = param.name if not param.name.startswith("arg") else "" + if type(param.annotation) == str: + p += ":" + param.annotation + result.append(p) + + rendered = '({})'.format(', '.join(result)) + + if self.return_annotation is not Signature.empty: + rendered += ' -> {}'.format(self.return_annotation) + + return rendered + +class ProxyMethod(object): + def __init__(self, iface_name, method): + self._iface_name = iface_name + self.__name__ = method.attrib["name"] + self.__qualname__ = self._iface_name + "." + self.__name__ + + self._inargs = [(arg.attrib.get("name", ""), arg.attrib["type"]) for arg in method if arg.tag == "arg" and arg.attrib["direction"] == "in"] + self._outargs = [arg.attrib["type"] for arg in method if arg.tag == "arg" and arg.attrib["direction"] == "out"] + self._sinargs = "(" + "".join(x[1] for x in self._inargs) + ")" + self._soutargs = "(" + "".join(self._outargs) + ")" + + self_param = Parameter("self", Parameter.POSITIONAL_ONLY) + pos_params = [] + for i, a in enumerate(self._inargs): + name = filter_identifier(a[0]) + + if not name: + name = "arg" + str(i) + + param = Parameter(name, Parameter.POSITIONAL_ONLY, annotation=a[1]) + + pos_params.append(param) + ret_type = Signature.empty if len(self._outargs) == 0 else self._outargs[0] if len(self._outargs) == 1 else "(" + ", ".join(self._outargs) + ")" + + self.__signature__ = DBUSSignature([self_param] + pos_params, return_annotation=ret_type) + + if put_signature_in_doc: + self.__doc__ = self.__name__ + str(self.__signature__) + + def __call__(self, instance, *args, **kwargs): + argdiff = len(args) - len(self._inargs) + if argdiff < 0: + raise TypeError(self.__qualname__ + " missing {} required positional argument(s)".format(-argdiff)) + elif argdiff > 0: + raise TypeError(self.__qualname__ + " takes {} positional argument(s) but {} was/were given".format(len(self._inargs), len(args))) + + # Python 2 sux + for kwarg in kwargs: + if kwarg not in ("timeout",): + raise TypeError(self.__qualname__ + " got an unexpected keyword argument '{}'".format(kwarg)) + timeout = kwargs.get("timeout", None) + + ret = instance._bus.con.call_sync( + instance._bus_name, instance._path, + self._iface_name, self.__name__, GLib.Variant(self._sinargs, args), GLib.VariantType.new(self._soutargs), + 0, timeout_to_glib(timeout), None).unpack() + + if len(self._outargs) == 0: + return None + elif len(self._outargs) == 1: + return ret[0] + else: + return ret + + def __get__(self, instance, owner): + if instance is None: + return self + + return bound_method(self, instance) + + def __repr__(self): + return "" diff --git a/ui/lib/pydbus/proxy_property.py b/ui/lib/pydbus/proxy_property.py new file mode 100644 index 0000000..e06a17d --- /dev/null +++ b/ui/lib/pydbus/proxy_property.py @@ -0,0 +1,31 @@ +from gi.repository import GLib + +class ProxyProperty(object): + def __init__(self, iface_name, property): + self._iface_name = iface_name + self.__name__ = property.attrib["name"] + self.__qualname__ = self._iface_name + "." + self.__name__ + + self._type = property.attrib["type"] + access = property.attrib["access"] + self._readable = access.startswith("read") + self._writeable = access.endswith("write") + self.__doc__ = "(" + self._type + ") " + access + + def __get__(self, instance, owner): + if instance is None: + return self + + if not self._readable: + raise AttributeError("unreadable attribute") + + return instance._object["org.freedesktop.DBus.Properties"].Get(self._iface_name, self.__name__) + + def __set__(self, instance, value): + if instance is None or not self._writeable: + raise AttributeError("can't set attribute") + + instance._object["org.freedesktop.DBus.Properties"].Set(self._iface_name, self.__name__, GLib.Variant(self._type, value)) + + def __repr__(self): + return "" diff --git a/ui/lib/pydbus/proxy_signal.py b/ui/lib/pydbus/proxy_signal.py new file mode 100644 index 0000000..c9a0169 --- /dev/null +++ b/ui/lib/pydbus/proxy_signal.py @@ -0,0 +1,66 @@ +from .generic import bound_signal + +class ProxySignal(object): + def __init__(self, iface_name, signal): + self._iface_name = iface_name + self.__name__ = signal.attrib["name"] + self.__qualname__ = self._iface_name + "." + self.__name__ + + self._args = [arg.attrib["type"] for arg in signal if arg.tag == "arg"] + self.__doc__ = "Signal. Callback: (" + ", ".join(self._args) + ")" + + def connect(self, object, callback): + """Subscribe to the signal.""" + def signal_fired(sender, object, iface, signal, params): + callback(*params) + return object._bus.subscribe(sender=object._bus_name, object=object._path, iface=self._iface_name, signal=self.__name__, signal_fired=signal_fired) + + def __get__(self, instance, owner): + if instance is None: + return self + + return bound_signal(self, instance) + + def __set__(self, instance, value): + raise AttributeError("can't set attribute") + + def __repr__(self): + return "" + +class OnSignal(object): + def __init__(self, signal): + self.signal = signal + self.__name__ = "on" + signal.__name__ + self.__qualname__ = signal._iface_name + "." + self.__name__ + self.__doc__ = "Assign a callback to subscribe to the signal. Assing None to unsubscribe. Callback: (" + ", ".join(signal._args) + ")" + + def __get__(self, instance, owner): + if instance is None: + return self + + try: + return getattr(instance, "_on" + self.signal.__name__) + except AttributeError: + return None + + def __set__(self, instance, value): + if instance is None: + raise AttributeError("can't set attribute") + + try: + old = getattr(instance, "_sub" + self.signal.__name__) + old.unsubscribe() + except AttributeError: + pass + + if value is None: + delattr(instance, "_on" + self.signal.__name__) + delattr(instance, "_sub" + self.signal.__name__) + return + + sub = self.signal.connect(instance, value) + setattr(instance, "_on" + self.signal.__name__, value) + setattr(instance, "_sub" + self.signal.__name__, sub) + + def __repr__(self): + return "" diff --git a/ui/lib/pydbus/publication.py b/ui/lib/pydbus/publication.py new file mode 100644 index 0000000..ef03825 --- /dev/null +++ b/ui/lib/pydbus/publication.py @@ -0,0 +1,42 @@ +from gi.repository import Gio +from .exitable import ExitableWithAliases +from .auto_names import * + +class Publication(ExitableWithAliases("unpublish")): + __slots__ = () + + def __init__(self, bus, bus_name, *objects, **kwargs): # allow_replacement=True, replace=False + # Python 2 sux + for kwarg in kwargs: + if kwarg not in ("allow_replacement", "replace",): + raise TypeError(self.__qualname__ + " got an unexpected keyword argument '{}'".format(kwarg)) + allow_replacement = kwargs.get("allow_replacement", True) + replace = kwargs.get("replace", False) + + bus_name = auto_bus_name(bus_name) + + for object_info in objects: + path, object, node_info = (None, None, None) + + if type(object_info) == tuple: + if len(object_info) == 3: + path, object, node_info = object_info + if len(object_info) == 2: + path, object = object_info + if len(object_info) == 1: + object = object_info[0] + else: + object = object_info + + path = auto_object_path(bus_name, path) + self._at_exit(bus.register_object(path, object, node_info).__exit__) + + # Request name only after registering all the objects. + self._at_exit(bus.request_name(bus_name, allow_replacement=allow_replacement, replace=replace).__exit__) + +class PublicationMixin(object): + __slots__ = () + + def publish(self, bus_name, *objects): + """Expose objects on the bus.""" + return Publication(self, bus_name, *objects) diff --git a/ui/lib/pydbus/registration.py b/ui/lib/pydbus/registration.py new file mode 100644 index 0000000..f531539 --- /dev/null +++ b/ui/lib/pydbus/registration.py @@ -0,0 +1,156 @@ +from __future__ import print_function +import sys, traceback +from gi.repository import GLib, Gio +from . import generic +from .exitable import ExitableWithAliases +from functools import partial +from .method_call_context import MethodCallContext +import logging + +try: + from inspect import signature, Parameter +except: + from ._inspect3 import signature, Parameter + +class ObjectWrapper(ExitableWithAliases("unwrap")): + __slots__ = ["object", "outargs", "readable_properties", "writable_properties"] + + def __init__(self, object, interfaces): + self.object = object + + self.outargs = {} + for iface in interfaces: + for method in iface.methods: + self.outargs[iface.name + "." + method.name] = [arg.signature for arg in method.out_args] + + self.readable_properties = {} + self.writable_properties = {} + for iface in interfaces: + for prop in iface.properties: + if prop.flags & Gio.DBusPropertyInfoFlags.READABLE: + self.readable_properties[iface.name + "." + prop.name] = prop.signature + if prop.flags & Gio.DBusPropertyInfoFlags.WRITABLE: + self.writable_properties[iface.name + "." + prop.name] = prop.signature + + for iface in interfaces: + for signal in iface.signals: + s_name = signal.name + def EmitSignal(iface, signal): + return lambda *args: self.SignalEmitted(iface.name, signal.name, GLib.Variant("(" + "".join(s.signature for s in signal.args) + ")", args)) + self._at_exit(getattr(object, signal.name).connect(EmitSignal(iface, signal)).__exit__) + + if "org.freedesktop.DBus.Properties" not in (iface.name for iface in interfaces): + try: + def onPropertiesChanged(iface, changed, invalidated): + changed = {key: GLib.Variant(self.readable_properties[iface + "." + key], val) for key, val in changed.items()} + args = GLib.Variant("(sa{sv}as)", (iface, changed, invalidated)) + self.SignalEmitted("org.freedesktop.DBus.Properties", "PropertiesChanged", args) + self._at_exit(object.PropertiesChanged.connect(onPropertiesChanged).__exit__) + except AttributeError: + pass + + SignalEmitted = generic.signal() + + def call_method(self, connection, sender, object_path, interface_name, method_name, parameters, invocation): + try: + try: + outargs = self.outargs[interface_name + "." + method_name] + method = getattr(self.object, method_name) + except KeyError: + if interface_name == "org.freedesktop.DBus.Properties": + if method_name == "Get": + method = self.Get + outargs = ["v"] + elif method_name == "GetAll": + method = self.GetAll + outargs = ["a{sv}"] + elif method_name == "Set": + method = self.Set + outargs = [] + else: + raise + else: + raise + + sig = signature(method) + + kwargs = {} + if "dbus_context" in sig.parameters and sig.parameters["dbus_context"].kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY): + kwargs["dbus_context"] = MethodCallContext(invocation) + + result = method(*parameters, **kwargs) + + if len(outargs) == 0: + invocation.return_value(None) + elif len(outargs) == 1: + invocation.return_value(GLib.Variant("(" + "".join(outargs) + ")", (result,))) + else: + invocation.return_value(GLib.Variant("(" + "".join(outargs) + ")", result)) + + except Exception as e: + logger = logging.getLogger(__name__) + logger.exception("Exception while handling %s.%s()", interface_name, method_name) + + #TODO Think of a better way to translate Python exception types to DBus error types. + e_type = type(e).__name__ + if not "." in e_type: + e_type = "unknown." + e_type + invocation.return_dbus_error(e_type, str(e)) + + def Get(self, interface_name, property_name): + type = self.readable_properties[interface_name + "." + property_name] + result = getattr(self.object, property_name) + return GLib.Variant(type, result) + + def GetAll(self, interface_name): + ret = {} + for name, type in self.readable_properties.items(): + ns, local = name.rsplit(".", 1) + if ns == interface_name: + ret[local] = GLib.Variant(type, getattr(self.object, local)) + return ret + + def Set(self, interface_name, property_name, value): + self.writable_properties[interface_name + "." + property_name] + setattr(self.object, property_name, value) + +class ObjectRegistration(ExitableWithAliases("unregister")): + __slots__ = () + + def __init__(self, bus, path, interfaces, wrapper, own_wrapper=False): + if own_wrapper: + self._at_exit(wrapper.__exit__) + + def func(interface_name, signal_name, parameters): + bus.con.emit_signal(None, path, interface_name, signal_name, parameters) + + self._at_exit(wrapper.SignalEmitted.connect(func).__exit__) + + try: + ids = [bus.con.register_object(path, interface, wrapper.call_method, None, None) for interface in interfaces] + except TypeError as e: + if str(e).startswith("argument vtable: Expected Gio.DBusInterfaceVTable"): + raise Exception("GLib 2.46 is required to publish objects; it is impossible in older versions.") + else: + raise + + self._at_exit(lambda: [bus.con.unregister_object(id) for id in ids]) + +class RegistrationMixin: + __slots__ = () + + def register_object(self, path, object, node_info): + if node_info is None: + try: + node_info = type(object).dbus + except AttributeError: + node_info = type(object).__doc__ + + if type(node_info) != list and type(node_info) != tuple: + node_info = [node_info] + + node_info = [Gio.DBusNodeInfo.new_for_xml(ni) for ni in node_info] + interfaces = sum((ni.interfaces for ni in node_info), []) + + wrapper = ObjectWrapper(object, interfaces) + return ObjectRegistration(self, path, interfaces, wrapper, own_wrapper=True) diff --git a/ui/lib/pydbus/request_name.py b/ui/lib/pydbus/request_name.py new file mode 100644 index 0000000..96d856c --- /dev/null +++ b/ui/lib/pydbus/request_name.py @@ -0,0 +1,29 @@ +from .exitable import ExitableWithAliases + +class NameOwner(ExitableWithAliases("unown")): + __slots__ = () + + def __init__(self, bus, name, allow_replacement, replace): + flags = 4 | (1 if allow_replacement else 0) | (2 if replace else 0) + res = bus.dbus.RequestName(name, flags) + if res == 1: + self._at_exit(lambda: bus.dbus.ReleaseName(name)) + return # OK + if res == 3: + raise RuntimeError("name already exists on the bus") + if res == 4: + raise RuntimeError("you're already the owner of this name") + raise RuntimeError("cannot take ownership of the name") + +class RequestNameMixin(object): + __slots__ = () + + def request_name(self, name, allow_replacement=True, replace=False): + """Aquires a bus name. + + Returns + ------- + NameOwner + An object you can use as a context manager to unown the name later. + """ + return NameOwner(self, name, allow_replacement, replace) diff --git a/ui/lib/pydbus/subscription.py b/ui/lib/pydbus/subscription.py new file mode 100644 index 0000000..93b0cbb --- /dev/null +++ b/ui/lib/pydbus/subscription.py @@ -0,0 +1,53 @@ +from gi.repository import Gio +from .exitable import ExitableWithAliases + +class Subscription(ExitableWithAliases("unsubscribe", "disconnect")): + Flags = Gio.DBusSignalFlags + __slots__ = () + + def __init__(self, con, sender, iface, member, object, arg0, flags, callback): + id = con.signal_subscribe(sender, iface, member, object, arg0, flags, callback) + self._at_exit(lambda: con.signal_unsubscribe(id)) + +class SubscriptionMixin(object): + __slots__ = () + SubscriptionFlags = Subscription.Flags + + def subscribe(self, sender=None, iface=None, signal=None, object=None, arg0=None, flags=0, signal_fired=None): + """Subscribes to matching signals. + + Subscribes to signals on connection and invokes signal_fired callback + whenever the signal is received. + + To receive signal_fired callback, you need an event loop. + https://github.com/LEW21/pydbus/blob/master/doc/tutorial.rst#setting-up-an-event-loop + + Parameters + ---------- + sender : string, optional + Sender name to match on (unique or well-known name) or None to listen from all senders. + iface : string, optional + Interface name to match on or None to match on all interfaces. + signal : string, optional + Signal name to match on or None to match on all signals. + object : string, optional + Object path to match on or None to match on all object paths. + arg0 : string, optional + Contents of first string argument to match on or None to match on all kinds of arguments. + flags : SubscriptionFlags, optional + signal_fired : callable, optional + Invoked when there is a signal matching the requested data. + Parameters: sender, object, iface, signal, params + + Returns + ------- + Subscription + An object you can use as a context manager to unsubscribe from the signal later. + + See Also + -------- + See https://developer.gnome.org/gio/2.44/GDBusConnection.html#g-dbus-connection-signal-subscribe + for more information. + """ + callback = (lambda con, sender, object, iface, signal, params: signal_fired(sender, object, iface, signal, params.unpack())) if signal_fired is not None else lambda *args: None + return Subscription(self.con, sender, iface, signal, object, arg0, flags, callback) diff --git a/ui/lib/pydbus/timeout.py b/ui/lib/pydbus/timeout.py new file mode 100644 index 0000000..0af65c1 --- /dev/null +++ b/ui/lib/pydbus/timeout.py @@ -0,0 +1,15 @@ +from gi.repository import GLib, GObject + +def timeout_to_glib(timeout): + if timeout is None: + try: + return GLib.MAXINT + except AttributeError: + # GLib < 2.46 + return GObject.G_MAXINT + else: + try: + timeout = timeout.total_seconds() + except AttributeError: + pass + return int(timeout * 1000) diff --git a/ui/src/main.py b/ui/src/main.py index 710d3ce..74213cf 100644 --- a/ui/src/main.py +++ b/ui/src/main.py @@ -17,12 +17,16 @@ # # SPDX-License-Identifier: GPL-3.0-or-later +import os +import sys + +lib_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lib') +sys.path.insert(0, lib_dir) + import gettext import gi import locale import logging -import os -import sys import argparse from logging.handlers import TimedRotatingFileHandler @@ -41,7 +45,6 @@ locale.bindtextdomain('breezydesktop', locale_dir) gettext.bindtextdomain('breezydesktop', locale_dir) gettext.textdomain('breezydesktop') - from gi.repository import Adw, Gtk, Gio from .licensedialog import LicenseDialog from .statemanager import StateManager