Bundle pydbus library
This commit is contained in:
parent
3cf21b1ff6
commit
cfdbc82b40
|
|
@ -37,6 +37,7 @@ mkdir -p $PACKAGE_APPS_DIR
|
||||||
mkdir -p $PACKAGE_SCHEMAS_DIR
|
mkdir -p $PACKAGE_SCHEMAS_DIR
|
||||||
|
|
||||||
cp src/*.py $PACKAGE_BREEZY_SRC_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 -L modules/PyXRLinuxDriverIPC/xrdriveripc.py $PACKAGE_BREEZY_SRC_DIR
|
||||||
cp $UI_BUILD_PATH/src/breezydesktop.gresource $PACKAGE_BREEZY_DIR
|
cp $UI_BUILD_PATH/src/breezydesktop.gresource $PACKAGE_BREEZY_DIR
|
||||||
cp -r po/mo/* $PACKAGE_LOCALE_DIR
|
cp -r po/mo/* $PACKAGE_LOCALE_DIR
|
||||||
|
|
|
||||||
|
|
@ -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 <linus@lew21.net>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
pip
|
||||||
|
|
@ -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 <linus@lew21.net>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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"}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
pydbus
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
from .bus import SystemBus, SessionBus, connect
|
||||||
|
from gi.repository.GLib import Variant
|
||||||
|
|
||||||
|
__all__ = ["SystemBus", "SessionBus", "connect", "Variant"]
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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 "<bound signal " + self.__signal__.__qualname__ + " of " + repr(self.__self__) + ">"
|
||||||
|
|
||||||
|
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__ = "<anonymous signal>" # function uses <lambda> ;)
|
||||||
|
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 "<signal " + self.__qualname__ + " at 0x" + format(id(self), "x") + ">"
|
||||||
|
|
||||||
|
bound_method = type(signal().emit) # TODO find a prettier way to get this type
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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>"
|
||||||
|
CompositeObject.__qualname__ = "<CompositeObject>(" + "+".join(x.__name__ for x in CompositeObject.__bases__) + ")"
|
||||||
|
CompositeObject.__module__ = "DBUS"
|
||||||
|
return CompositeObject
|
||||||
|
|
@ -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 "<function " + self.__qualname__ + " at 0x" + format(id(self), "x") + ">"
|
||||||
|
|
@ -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 "<property " + self.__qualname__ + " at 0x" + format(id(self), "x") + ">"
|
||||||
|
|
@ -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 "<signal " + self.__qualname__ + " at 0x" + format(id(self), "x") + ">"
|
||||||
|
|
||||||
|
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 "<descriptor " + self.__qualname__ + " at 0x" + format(id(self), "x") + ">"
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -17,12 +17,16 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# 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 gettext
|
||||||
import gi
|
import gi
|
||||||
import locale
|
import locale
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from logging.handlers import TimedRotatingFileHandler
|
from logging.handlers import TimedRotatingFileHandler
|
||||||
|
|
@ -41,7 +45,6 @@ locale.bindtextdomain('breezydesktop', locale_dir)
|
||||||
gettext.bindtextdomain('breezydesktop', locale_dir)
|
gettext.bindtextdomain('breezydesktop', locale_dir)
|
||||||
gettext.textdomain('breezydesktop')
|
gettext.textdomain('breezydesktop')
|
||||||
|
|
||||||
|
|
||||||
from gi.repository import Adw, Gtk, Gio
|
from gi.repository import Adw, Gtk, Gio
|
||||||
from .licensedialog import LicenseDialog
|
from .licensedialog import LicenseDialog
|
||||||
from .statemanager import StateManager
|
from .statemanager import StateManager
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue