diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..ece61c6 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.6 \ No newline at end of file diff --git a/gnome-44-max.patch b/gnome-44-max.patch index 286d8bb..2df4770 100644 --- a/gnome-44-max.patch +++ b/gnome-44-max.patch @@ -1,18 +1,10 @@ diff --git a/gnome-44-max/bin/setup b/gnome-44-max/bin/setup -index 7ee291a..3bd001b 100755 +index e34efb5..ee5e694 100755 --- a/gnome-44-max/bin/setup +++ b/gnome-44-max/bin/setup -@@ -11,6 +11,7 @@ check_command() { - - check_command "flatpak" - check_command "gnome-extensions" -+check_command "glib-compile-schemas" - - # This script gets packaged with the release and should do the bulk of the setup work. This allows this setup to be tied - # to a specific release of the code, and guarantees it will never run along-side newer or older binaries. -@@ -78,8 +79,10 @@ echo "Copying the manifest file to ${DATA_DIR}" - mkdir -p $DATA_DIR - cp manifest $DATA_DIR +@@ -79,8 +79,10 @@ echo "Copying the manifest file to ${BREEZY_GNOME_DATA_DIR}" + mkdir -p $BREEZY_GNOME_DATA_DIR + cp manifest $BREEZY_GNOME_DATA_DIR -echo "Installing the breezydesktop@xronlinux.com GNOME extension" -gnome-extensions install --force breezydesktop@xronlinux.com.shell-extension.zip @@ -21,8 +13,8 @@ index 7ee291a..3bd001b 100755 +gnome-extensions install --force "$EXTENSION_UUID.shell-extension.zip" +glib-compile-schemas "$GNOME_SHELL_DATA_DIR/extensions/$EXTENSION_UUID/schemas" - echo "Installing the Breezy Desktop UI Flatpak (this may take a couple minutes the first time)" - flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + echo "Installing the Breezy Desktop UI application" + cp -r breezy_ui/data/* $XDG_DATA_HOME diff --git a/gnome-44-max/src/cursor.js b/gnome-44-max/src/cursor.js index 36ad7ee..41102a0 100644 --- a/gnome-44-max/src/cursor.js @@ -78,7 +70,7 @@ index 44b3f5f..fa65a4a 100644 } diff --git a/gnome-44-max/src/extension.js b/gnome-44-max/src/extension.js -index cd17162..435154d 100644 +index 5f62bfd..d06f91c 100644 --- a/gnome-44-max/src/extension.js +++ b/gnome-44-max/src/extension.js @@ -1,19 +1,21 @@ @@ -135,7 +127,7 @@ index cd17162..435154d 100644 // Set/destroyed by enable/disable this._cursor_manager = null; -@@ -593,6 +594,6 @@ export default class BreezyDesktopExtension extends Extension { +@@ -619,6 +620,6 @@ export default class BreezyDesktopExtension extends Extension { } } @@ -264,7 +256,7 @@ index 497274e..6c98cdb 100644 } \ No newline at end of file diff --git a/gnome-44-max/src/metadata.json b/gnome-44-max/src/metadata.json -index b9b5ebf..c888f94 100644 +index 125954e..c888f94 100644 --- a/gnome-44-max/src/metadata.json +++ b/gnome-44-max/src/metadata.json @@ -5,7 +5,7 @@ @@ -353,7 +345,7 @@ index 7883b9b..5478d2a 100644 } \ No newline at end of file diff --git a/gnome-44-max/src/xrEffect.js b/gnome-44-max/src/xrEffect.js -index aab805c..922ec0f 100644 +index 6b1421b..7d36c46 100644 --- a/gnome-44-max/src/xrEffect.js +++ b/gnome-44-max/src/xrEffect.js @@ -1,13 +1,15 @@ @@ -378,7 +370,7 @@ index aab805c..922ec0f 100644 +const Me = ExtensionUtils.getCurrentExtension(); + +const Globals = Me.imports.globals; -+const { ++const { dataViewEnd, dataViewUint8, dataViewBigUint, @@ -400,7 +392,7 @@ index aab805c..922ec0f 100644 // the driver should be using the same data layout version const DATA_LAYOUT_VERSION = 3; -@@ -221,7 +223,7 @@ function checkParityByte(dataView) { +@@ -232,7 +234,7 @@ function checkParityByte(dataView) { return parityByte === parity; } @@ -409,7 +401,7 @@ index aab805c..922ec0f 100644 Properties: { 'supported-device-detected': GObject.ParamSpec.boolean( 'supported-device-detected', -@@ -361,8 +363,13 @@ export const XREffect = GObject.registerClass({ +@@ -372,8 +374,13 @@ export const XREffect = GObject.registerClass({ if (!this._initialized) { this.set_uniform_float(this.get_uniform_location('screenTexture'), 1, [0]); diff --git a/gnome/src/extension.js b/gnome/src/extension.js index d06f91c..0a7e9ae 100644 --- a/gnome/src/extension.js +++ b/gnome/src/extension.js @@ -14,6 +14,7 @@ const Globals = Me.imports.globals; const { CursorManager } = Me.imports.cursormanager; const { Logger } = Me.imports.logger; const { MonitorManager } = Me.imports.monitormanager; +const { SystemBackground } = Me.imports.systembackground; const { isValidKeepAlive } = Me.imports.time; const { IPC_FILE_PATH, XREffect } = Me.imports.xrEffect; @@ -251,24 +252,25 @@ class BreezyDesktopExtension { this._cursor_manager = new CursorManager(Main.layoutManager.uiGroup, refreshRate); this._cursor_manager.enable(); - this._overlay = new St.Bin(); - this._overlay.opacity = 255; + const overlayContent = new Clutter.Actor({clip_to_allocation: true}); + + this._overlay = new St.Bin({ + child: overlayContent + }); this._overlay.set_position(targetMonitor.x, targetMonitor.y); this._overlay.set_size(targetMonitor.width, targetMonitor.height); - Globals.logger.log_debug(`BreezyDesktopExtension _effect_enable overlay size: \ - ${targetMonitor.width}x${targetMonitor.height} at ${targetMonitor.x},${targetMonitor.y}`); - const overlayContent = new Clutter.Actor({clip_to_allocation: true}); + global.stage.add_child(this._overlay); + Shell.util_set_hidden_from_pick(this._overlay, true); + + this._background = new SystemBackground(); + overlayContent.add_child(this._background); + const uiClone = new Clutter.Clone({ source: Main.layoutManager.uiGroup, clip_to_allocation: true }); uiClone.x = -targetMonitor.x; uiClone.y = -targetMonitor.y; overlayContent.add_child(uiClone); - this._overlay.set_child(overlayContent); - - Shell.util_set_hidden_from_pick(this._overlay, true); - global.stage.add_child(this._overlay); - // In GS 45, use of "actor" was renamed to "child". const clutterContainer = Clutter.Container !== undefined; this._actor_added_connection = global.stage.connect( diff --git a/gnome/src/monitormanager.js b/gnome/src/monitormanager.js index 075ba63..580925c 100644 --- a/gnome/src/monitormanager.js +++ b/gnome/src/monitormanager.js @@ -283,7 +283,6 @@ var MonitorManager = GObject.registerClass({ this._monitorsChangedConnection = null; this._displayConfigProxy = null; - this._backendManager = null; this._monitorProperties = null; this._changeHookFn = null; this._needsConfigCheck = this.use_optimal_monitor_config; @@ -295,7 +294,6 @@ var MonitorManager = GObject.registerClass({ enable() { Globals.logger.log_debug('MonitorManager enable'); - this._backendManager = global.backend.get_monitor_manager(); newDisplayConfig(this.extension_path, ((proxy, error) => { if (error) { return; @@ -313,7 +311,6 @@ var MonitorManager = GObject.registerClass({ this._monitorsChangedConnection = null; this._displayConfigProxy = null; - this._backendManager = null; this._monitorProperties = null; this._changeHookFn = null; } @@ -405,7 +402,7 @@ var MonitorManager = GObject.registerClass({ const monitorProperties = []; for (let i = 0; i < result.length; i++) { const [monitorName, connectorName, vendor, product, serial, refreshRate] = result[i]; - const monitorIndex = this._backendManager.get_monitor_for_connector(connectorName); + const monitorIndex = global.backend.get_monitor_manager().get_monitor_for_connector(connectorName); Globals.logger.log_debug(`Found monitor ${monitorName}, vendor ${vendor}, product ${product}, serial ${serial}, connector ${connectorName}, index ${monitorIndex}`); if (monitorIndex >= 0) { monitorProperties[monitorIndex] = { diff --git a/gnome/src/systembackground.js b/gnome/src/systembackground.js new file mode 100644 index 0000000..4d7186f --- /dev/null +++ b/gnome/src/systembackground.js @@ -0,0 +1,31 @@ +const Cogl = imports.gi.Cogl; +const GLib = imports.gi.GLib; +const GObject = imports.gi.GObject; +const Meta = imports.gi.Meta; + +const DEFAULT_BACKGROUND_COLOR = new Cogl.Color({red: 40, green: 40, blue: 40, alpha: 255}); + +let _systemBackground; + +var SystemBackground = GObject.registerClass({ + Signals: {'loaded': {}}, +}, class SystemBackground extends Meta.BackgroundActor { + _init() { + if (_systemBackground == null) { + _systemBackground = new Meta.Background({meta_display: global.display}); + _systemBackground.set_color(DEFAULT_BACKGROUND_COLOR); + } + + super._init({ + meta_display: global.display, + monitor: 0, + }); + this.content.background = _systemBackground; + + let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + this.emit('loaded'); + return GLib.SOURCE_REMOVE; + }); + GLib.Source.set_name_by_id(id, '[gnome-shell] SystemBackground.loaded'); + } +}); diff --git a/gnome/src/xrEffect.js b/gnome/src/xrEffect.js index 7d36c46..4394f43 100644 --- a/gnome/src/xrEffect.js +++ b/gnome/src/xrEffect.js @@ -326,8 +326,11 @@ var XREffect = GObject.registerClass({ constructor(params = {}) { super(params); - this._is_display_distance_at_end = false; this._distance_ease_timeline = null; + this.connect('notify::toggle-display-distance-start', this._handle_display_distance_properties_change.bind(this)); + this.connect('notify::toggle-display-distance-end', this._handle_display_distance_properties_change.bind(this)); + this.connect('notify::display-distance', this._handle_display_distance_properties_change.bind(this)); + this._handle_display_distance_properties_change(); const calibrating = GdkPixbuf.Pixbuf.new_from_file(`${Globals.extension_dir}/textures/calibrating.png`); this.calibratingImage = new Clutter.Image(); @@ -340,6 +343,12 @@ var XREffect = GObject.registerClass({ customBanner.width, customBanner.height, customBanner.rowstride); } + _handle_display_distance_properties_change() { + const distance_from_end = Math.abs(this.display_distance - this.toggle_display_distance_end); + const distance_from_start = Math.abs(this.display_distance - this.toggle_display_distance_start); + this._is_display_distance_at_end = distance_from_end < distance_from_start; + } + _change_distance() { if (this._distance_ease_timeline?.is_playing()) this._distance_ease_timeline.stop(); @@ -353,7 +362,6 @@ var XREffect = GObject.registerClass({ this._distance_ease_timeline.get_progress() * (toggle_display_distance_target - this._distance_ease_start); }); - this._is_display_distance_at_end = !this._is_display_distance_at_end; this._distance_ease_timeline.start(); } diff --git a/modules/sombrero b/modules/sombrero index 5a7ddc2..d270ebf 160000 --- a/modules/sombrero +++ b/modules/sombrero @@ -1 +1 @@ -Subproject commit 5a7ddc2c18df268476dd123b9af84091e3bf49bb +Subproject commit d270ebfd2e3202133fea75e1513f1571960bdafd diff --git a/ui/bin/dev/use_local_ui.sh b/ui/bin/dev/use_local_ui.sh new file mode 100755 index 0000000..c641c6f --- /dev/null +++ b/ui/bin/dev/use_local_ui.sh @@ -0,0 +1,38 @@ +USER_HOME=$(realpath ~) +ARCH=$(uname -m) + +# https://stackoverflow.com/a/246128 +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) + +if [ -z "$XDG_DATA_HOME" ]; then + XDG_DATA_HOME="$USER_HOME/.local/share" +fi + +if [ -z "$XDG_BIN_HOME" ]; then + XDG_BIN_HOME="$USER_HOME/.local/bin" +fi + + +# create temp directory +tmp_dir=$(mktemp -d -t breezy-gnome-XXXXXXXXXX) +pushd $tmp_dir > /dev/null +echo "Created temp directory: ${tmp_dir}" + +echo "Extracting to: ${tmp_dir}/breezy_ui" +tar -xf $SCRIPT_DIR/../../out/breezyUI-${ARCH}.tar.gz + +echo "Installing the Breezy Desktop UI application" +cp -r breezy_ui/data/* $XDG_DATA_HOME +cp -r breezy_ui/bin/* $XDG_BIN_HOME + +# update copied files to use the local XDG paths +ESCAPED_XDG_DATA_HOME=$(printf '%s\n' "$XDG_DATA_HOME" | sed -e 's/[\/&]/\\&/g') +sed -i -e "s/\/usr\/local\/share/$ESCAPED_XDG_DATA_HOME/g" $XDG_BIN_HOME/breezydesktop +sed -i "/Exec/c\Exec=$XDG_BIN_HOME/breezydesktop" $XDG_DATA_HOME/applications/com.xronlinux.BreezyDesktop.desktop + +glib-compile-schemas $XDG_DATA_HOME/glib-2.0/schemas +update-desktop-database $XDG_DATA_HOME/applications +gtk-update-icon-cache + +popd > /dev/null +rm -rf $tmp_dir \ No newline at end of file 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/meson.build b/ui/meson.build index 4d147ed..05d6f6d 100644 --- a/ui/meson.build +++ b/ui/meson.build @@ -1,5 +1,5 @@ project('breezydesktop', - version: '1.0.0', + version: run_command('cat', join_paths('..', 'VERSION'), check: true).stdout().strip(), meson_version: '>= 0.62.0', default_options: [ 'warning_level=2', 'werror=false', ], ) diff --git a/ui/po/breezydesktop.pot b/ui/po/breezydesktop.pot index e12cbf3..5408e5d 100644 --- a/ui/po/breezydesktop.pot +++ b/ui/po/breezydesktop.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-02 09:15-0700\n" +"POT-Creation-Date: 2024-10-03 21:35-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -27,11 +27,11 @@ msgstr "" msgid "This feature is not currently supported for your device." msgstr "" -#: src/licensedialog.py:57 +#: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "" -#: src/licensedialog.py:63 +#: src/licensedialogcontent.py:71 msgid "Feature Availability" msgstr "" @@ -331,22 +331,22 @@ msgid "" "script. Report this issue if it persists." msgstr "" -#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:55 -msgid "License Details" -msgstr "" - -#: src/gtk/license-dialog.ui:27 +#: src/gtk/license-dialog-content.ui:15 msgid "Donate" msgstr "" -#: src/gtk/license-dialog.ui:44 +#: src/gtk/license-dialog-content.ui:31 msgid "Request a token" msgstr "" -#: src/gtk/license-dialog.ui:52 +#: src/gtk/license-dialog-content.ui:39 msgid "Verify token" msgstr "" +#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:91 +msgid "License Details" +msgstr "" + #: src/gtk/no-device.ui:13 msgid "No device connected" msgstr "" @@ -421,22 +421,22 @@ msgstr "" msgid "Menu" msgstr "" -#: src/gtk/window.ui:35 +#: src/gtk/window.ui:43 msgid "Some features expire soon" msgstr "" -#: src/gtk/window.ui:36 src/gtk/window.ui:43 +#: src/gtk/window.ui:51 src/gtk/window.ui:76 msgid "View details" msgstr "" -#: src/gtk/window.ui:42 +#: src/gtk/window.ui:68 msgid "Productivity features are disabled" msgstr "" -#: src/gtk/window.ui:59 +#: src/gtk/window.ui:95 msgid "Force Reset" msgstr "" -#: src/gtk/window.ui:63 +#: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "" diff --git a/ui/po/de.po b/ui/po/de.po index cbe688b..06f17ff 100644 --- a/ui/po/de.po +++ b/ui/po/de.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-01 13:21-0700\n" +"POT-Creation-Date: 2024-10-03 21:26-0700\n" "PO-Revision-Date: 2024-08-02 20:54-0700\n" "Last-Translator: \n" "Language-Team: German \n" @@ -29,11 +29,11 @@ msgstr "" msgid "This feature is not currently supported for your device." msgstr "Diese Funktion wird von Ihrem Gerät derzeit nicht unterstützt." -#: src/licensedialog.py:57 +#: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "Bezahlter Tarifstatus" -#: src/licensedialog.py:63 +#: src/licensedialogcontent.py:71 msgid "Feature Availability" msgstr "Funktionsverfügbarkeit" @@ -354,22 +354,22 @@ msgstr "" "das Setup-Skript bitte erneut aus. Melden Sie dieses Problem, falls es " "beständig ist." -#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:55 -msgid "License Details" -msgstr "Lizenzdetails" - -#: src/gtk/license-dialog.ui:27 +#: src/gtk/license-dialog-content.ui:15 msgid "Donate" msgstr "Spenden" -#: src/gtk/license-dialog.ui:44 +#: src/gtk/license-dialog-content.ui:31 msgid "Request a token" msgstr "Ein Token anfordern" -#: src/gtk/license-dialog.ui:52 +#: src/gtk/license-dialog-content.ui:39 msgid "Verify token" msgstr "Token verifizieren" +#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:91 +msgid "License Details" +msgstr "Lizenzdetails" + #: src/gtk/no-device.ui:13 msgid "No device connected" msgstr "Kein Gerät verbunden" @@ -470,22 +470,22 @@ msgstr "Breezy Desktop" msgid "Menu" msgstr "Menü" -#: src/gtk/window.ui:35 +#: src/gtk/window.ui:43 msgid "Some features expire soon" msgstr "Einige Funktionen laufen bald ab" -#: src/gtk/window.ui:36 src/gtk/window.ui:43 +#: src/gtk/window.ui:51 src/gtk/window.ui:76 msgid "View details" msgstr "Details anzeigen" -#: src/gtk/window.ui:42 +#: src/gtk/window.ui:68 msgid "Productivity features are disabled" msgstr "Produktivitätsfunktionen sind deaktiviert" -#: src/gtk/window.ui:59 +#: src/gtk/window.ui:95 msgid "Force Reset" msgstr "Erzwingt Zurrücksetzung" -#: src/gtk/window.ui:63 +#: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "Über BreezyDesktop" diff --git a/ui/po/es.po b/ui/po/es.po index f72cbce..99ff603 100644 --- a/ui/po/es.po +++ b/ui/po/es.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-01 13:21-0700\n" +"POT-Creation-Date: 2024-10-03 21:26-0700\n" "PO-Revision-Date: 2024-08-02 20:55-0700\n" "Last-Translator: \n" "Language-Team: Spanish \n" @@ -28,11 +28,11 @@ msgstr "" msgid "This feature is not currently supported for your device." msgstr "Esta función no es compatible con tu dispositivo en este momento." -#: src/licensedialog.py:57 +#: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "Estado del Nivel de Membresía Pagada" -#: src/licensedialog.py:63 +#: src/licensedialogcontent.py:71 msgid "Feature Availability" msgstr "Disponibilidad de Características" @@ -353,22 +353,22 @@ msgstr "" "Su configuración de Breezy GNOME es inválida o incompleta. Vuelva a ejecutar " "el script de configuración. Informe sobre este problema si persiste." -#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:55 -msgid "License Details" -msgstr "Detalles de la Licencia" - -#: src/gtk/license-dialog.ui:27 +#: src/gtk/license-dialog-content.ui:15 msgid "Donate" msgstr "Donar" -#: src/gtk/license-dialog.ui:44 +#: src/gtk/license-dialog-content.ui:31 msgid "Request a token" msgstr "Solicitar un token" -#: src/gtk/license-dialog.ui:52 +#: src/gtk/license-dialog-content.ui:39 msgid "Verify token" msgstr "Verificar token" +#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:91 +msgid "License Details" +msgstr "Detalles de la Licencia" + #: src/gtk/no-device.ui:13 msgid "No device connected" msgstr "No hay dispositivo conectado" @@ -467,22 +467,22 @@ msgstr "Breezy Desktop" msgid "Menu" msgstr "Menú" -#: src/gtk/window.ui:35 +#: src/gtk/window.ui:43 msgid "Some features expire soon" msgstr "Algunas funciones expirarán pronto" -#: src/gtk/window.ui:36 src/gtk/window.ui:43 +#: src/gtk/window.ui:51 src/gtk/window.ui:76 msgid "View details" msgstr "Ver detalles" -#: src/gtk/window.ui:42 +#: src/gtk/window.ui:68 msgid "Productivity features are disabled" msgstr "Las funciones de productividad están deshabilitadas" -#: src/gtk/window.ui:59 +#: src/gtk/window.ui:95 msgid "Force Reset" msgstr "Reinicio forzoso" -#: src/gtk/window.ui:63 +#: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "Acerca de BreezyDesktop" diff --git a/ui/po/fr.po b/ui/po/fr.po index 8a3c638..30f4be6 100644 --- a/ui/po/fr.po +++ b/ui/po/fr.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-01 13:21-0700\n" +"POT-Creation-Date: 2024-10-03 21:26-0700\n" "PO-Revision-Date: 2024-08-02 20:54-0700\n" "Last-Translator: \n" "Language-Team: French \n" @@ -31,11 +31,11 @@ msgstr "" "Cette fonctionnalité n'est actuellement pas prise en charge par votre " "appareil." -#: src/licensedialog.py:57 +#: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "Statut de l'abonnement payant" -#: src/licensedialog.py:63 +#: src/licensedialogcontent.py:71 msgid "Feature Availability" msgstr "Disponibilité des fonctionnalités" @@ -337,11 +337,11 @@ msgstr "Par défaut" #: src/gtk/connected-device.ui:396 msgid "Text Scaling" -msgstr "" +msgstr "Mise à l'échelle du texte" #: src/gtk/connected-device.ui:397 msgid "Scaling text below 1.0 will simulate a higher resolution display" -msgstr "" +msgstr "Une mise à l'échelle du texte en dessous de 1.0 simulera un affichage de plus haute résolution" #: src/gtk/failed-verification.ui:13 msgid "Breezy Desktop GNOME invalid setup" @@ -356,22 +356,22 @@ msgstr "" "exécuter à nouveau le script de configuration. Signalez ce problème s'il " "persiste." -#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:55 -msgid "License Details" -msgstr "Détails de la licence" - -#: src/gtk/license-dialog.ui:27 +#: src/gtk/license-dialog-content.ui:15 msgid "Donate" msgstr "Faire un don" -#: src/gtk/license-dialog.ui:44 +#: src/gtk/license-dialog-content.ui:31 msgid "Request a token" msgstr "Demander un jeton d'authentification" -#: src/gtk/license-dialog.ui:52 +#: src/gtk/license-dialog-content.ui:39 msgid "Verify token" msgstr "Vérifier le jeton d'authentification" +#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:91 +msgid "License Details" +msgstr "Détails de la licence" + #: src/gtk/no-device.ui:13 msgid "No device connected" msgstr "Aucun appareil connecté" @@ -470,22 +470,22 @@ msgstr "Breezy Desktop" msgid "Menu" msgstr "Menu" -#: src/gtk/window.ui:35 +#: src/gtk/window.ui:43 msgid "Some features expire soon" msgstr "Certaines fonctionnalités expirent bientôt" -#: src/gtk/window.ui:36 src/gtk/window.ui:43 +#: src/gtk/window.ui:51 src/gtk/window.ui:76 msgid "View details" msgstr "Afficher les détails" -#: src/gtk/window.ui:42 +#: src/gtk/window.ui:68 msgid "Productivity features are disabled" msgstr "Les fonctionnalités de productivité sont désactivées" -#: src/gtk/window.ui:59 +#: src/gtk/window.ui:95 msgid "Force Reset" msgstr "Réinitialiser" -#: src/gtk/window.ui:63 +#: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "À propos de BreezyDesktop" diff --git a/ui/po/it.po b/ui/po/it.po index f3c95a4..fb59a2c 100644 --- a/ui/po/it.po +++ b/ui/po/it.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-01 13:21-0700\n" +"POT-Creation-Date: 2024-10-03 21:26-0700\n" "PO-Revision-Date: 2024-08-02 21:14-0700\n" "Last-Translator: \n" "Language-Team: Italian \n" @@ -27,11 +27,11 @@ msgstr "" msgid "This feature is not currently supported for your device." msgstr "" -#: src/licensedialog.py:57 +#: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "" -#: src/licensedialog.py:63 +#: src/licensedialogcontent.py:71 msgid "Feature Availability" msgstr "" @@ -331,22 +331,22 @@ msgid "" "script. Report this issue if it persists." msgstr "" -#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:55 -msgid "License Details" -msgstr "" - -#: src/gtk/license-dialog.ui:27 +#: src/gtk/license-dialog-content.ui:15 msgid "Donate" msgstr "" -#: src/gtk/license-dialog.ui:44 +#: src/gtk/license-dialog-content.ui:31 msgid "Request a token" msgstr "" -#: src/gtk/license-dialog.ui:52 +#: src/gtk/license-dialog-content.ui:39 msgid "Verify token" msgstr "" +#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:91 +msgid "License Details" +msgstr "" + #: src/gtk/no-device.ui:13 msgid "No device connected" msgstr "" @@ -421,22 +421,22 @@ msgstr "" msgid "Menu" msgstr "" -#: src/gtk/window.ui:35 +#: src/gtk/window.ui:43 msgid "Some features expire soon" msgstr "" -#: src/gtk/window.ui:36 src/gtk/window.ui:43 +#: src/gtk/window.ui:51 src/gtk/window.ui:76 msgid "View details" msgstr "" -#: src/gtk/window.ui:42 +#: src/gtk/window.ui:68 msgid "Productivity features are disabled" msgstr "" -#: src/gtk/window.ui:59 +#: src/gtk/window.ui:95 msgid "Force Reset" msgstr "" -#: src/gtk/window.ui:63 +#: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "" diff --git a/ui/po/ja.po b/ui/po/ja.po index c5ef84f..e5825b7 100644 --- a/ui/po/ja.po +++ b/ui/po/ja.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-01 13:21-0700\n" +"POT-Creation-Date: 2024-10-03 21:26-0700\n" "PO-Revision-Date: 2024-08-02 20:55-0700\n" "Last-Translator: \n" "Language-Team: Japanese \n" @@ -31,11 +31,11 @@ msgstr "メガネを3Dモードに切り替え、表示の幅を2倍にします msgid "This feature is not currently supported for your device." msgstr "この機能は現在接続されているデバイスではサポートされていません。" -#: src/licensedialog.py:57 +#: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "有料ティアの状態" -#: src/licensedialog.py:63 +#: src/licensedialogcontent.py:71 msgid "Feature Availability" msgstr "利用できる機能" @@ -338,7 +338,9 @@ msgstr "テキストスケーリング" #: src/gtk/connected-device.ui:397 msgid "Scaling text below 1.0 will simulate a higher resolution display" -msgstr "テキストを1.0未満にスケーリングすると、高解像度ディスプレイをシミュレートします。" +msgstr "" +"テキストを1.0未満にスケーリングすると、高解像度ディスプレイをシミュレートしま" +"す。" #: src/gtk/failed-verification.ui:13 msgid "Breezy Desktop GNOME invalid setup" @@ -352,22 +354,22 @@ msgstr "" "Breezy GNOMEのセットアップが無効または不完全です。セットアップスクリプトを再" "実行してください。問題が解決しない場合は、この問題を報告してください。" -#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:55 -msgid "License Details" -msgstr "ライセンスの詳細" - -#: src/gtk/license-dialog.ui:27 +#: src/gtk/license-dialog-content.ui:15 msgid "Donate" msgstr "寄付" -#: src/gtk/license-dialog.ui:44 +#: src/gtk/license-dialog-content.ui:31 msgid "Request a token" msgstr "トークンをリクエストする" -#: src/gtk/license-dialog.ui:52 +#: src/gtk/license-dialog-content.ui:39 msgid "Verify token" msgstr "トークンを検証する" +#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:91 +msgid "License Details" +msgstr "ライセンスの詳細" + #: src/gtk/no-device.ui:13 msgid "No device connected" msgstr "デバイスが接続されていません" @@ -466,22 +468,22 @@ msgstr "Breezy Desktop" msgid "Menu" msgstr "メニュー" -#: src/gtk/window.ui:35 +#: src/gtk/window.ui:43 msgid "Some features expire soon" msgstr "一部の機能はもうすぐ期限が切れます" -#: src/gtk/window.ui:36 src/gtk/window.ui:43 +#: src/gtk/window.ui:51 src/gtk/window.ui:76 msgid "View details" msgstr "詳細を表示" -#: src/gtk/window.ui:42 +#: src/gtk/window.ui:68 msgid "Productivity features are disabled" msgstr "プロダクティビティ機能が無効になっています" -#: src/gtk/window.ui:59 +#: src/gtk/window.ui:95 msgid "Force Reset" msgstr "強制リセット" -#: src/gtk/window.ui:63 +#: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "Breezy Desktopについて" diff --git a/ui/po/mo/ja/LC_MESSAGES/breezydesktop.mo b/ui/po/mo/ja/LC_MESSAGES/breezydesktop.mo index 9566ea2..69a3de7 100644 Binary files a/ui/po/mo/ja/LC_MESSAGES/breezydesktop.mo and b/ui/po/mo/ja/LC_MESSAGES/breezydesktop.mo differ diff --git a/ui/po/mo/pt_BR/LC_MESSAGES/breezydesktop.mo b/ui/po/mo/pt_BR/LC_MESSAGES/breezydesktop.mo index 1794ccb..18c9d92 100644 Binary files a/ui/po/mo/pt_BR/LC_MESSAGES/breezydesktop.mo and b/ui/po/mo/pt_BR/LC_MESSAGES/breezydesktop.mo differ diff --git a/ui/po/pl.po b/ui/po/pl.po index c570a94..3034319 100644 --- a/ui/po/pl.po +++ b/ui/po/pl.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-01 13:21-0700\n" +"POT-Creation-Date: 2024-10-03 21:26-0700\n" "PO-Revision-Date: 2024-08-16 10:26-0700\n" "Last-Translator: \n" "Language-Team: Polish \n" @@ -28,11 +28,11 @@ msgstr "" msgid "This feature is not currently supported for your device." msgstr "" -#: src/licensedialog.py:57 +#: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "" -#: src/licensedialog.py:63 +#: src/licensedialogcontent.py:71 msgid "Feature Availability" msgstr "" @@ -332,22 +332,22 @@ msgid "" "script. Report this issue if it persists." msgstr "" -#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:55 -msgid "License Details" -msgstr "" - -#: src/gtk/license-dialog.ui:27 +#: src/gtk/license-dialog-content.ui:15 msgid "Donate" msgstr "" -#: src/gtk/license-dialog.ui:44 +#: src/gtk/license-dialog-content.ui:31 msgid "Request a token" msgstr "" -#: src/gtk/license-dialog.ui:52 +#: src/gtk/license-dialog-content.ui:39 msgid "Verify token" msgstr "" +#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:91 +msgid "License Details" +msgstr "" + #: src/gtk/no-device.ui:13 msgid "No device connected" msgstr "" @@ -422,22 +422,22 @@ msgstr "" msgid "Menu" msgstr "" -#: src/gtk/window.ui:35 +#: src/gtk/window.ui:43 msgid "Some features expire soon" msgstr "" -#: src/gtk/window.ui:36 src/gtk/window.ui:43 +#: src/gtk/window.ui:51 src/gtk/window.ui:76 msgid "View details" msgstr "" -#: src/gtk/window.ui:42 +#: src/gtk/window.ui:68 msgid "Productivity features are disabled" msgstr "" -#: src/gtk/window.ui:59 +#: src/gtk/window.ui:95 msgid "Force Reset" msgstr "" -#: src/gtk/window.ui:63 +#: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "" diff --git a/ui/po/pt_BR.po b/ui/po/pt_BR.po index 9f116dd..339645c 100644 --- a/ui/po/pt_BR.po +++ b/ui/po/pt_BR.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-01 13:21-0700\n" +"POT-Creation-Date: 2024-10-03 21:26-0700\n" "PO-Revision-Date: 2024-08-19 09:39-0700\n" "Last-Translator: \n" "Language-Team: Brazilian Portuguese \n" "Language-Team: Russian \n" @@ -29,11 +29,11 @@ msgstr "" msgid "This feature is not currently supported for your device." msgstr "Эта функция в настоящее время не поддерживается для вашего устройства." -#: src/licensedialog.py:57 +#: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "Статус платного уровня" -#: src/licensedialog.py:63 +#: src/licensedialogcontent.py:71 msgid "Feature Availability" msgstr "Статус функций" @@ -355,22 +355,22 @@ msgstr "" "перезапустите скрипт настройки. Сообщите об этой проблеме, если она " "сохраняется." -#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:55 -msgid "License Details" -msgstr "Подробности лицензии" - -#: src/gtk/license-dialog.ui:27 +#: src/gtk/license-dialog-content.ui:15 msgid "Donate" msgstr "Донатить" -#: src/gtk/license-dialog.ui:44 +#: src/gtk/license-dialog-content.ui:31 msgid "Request a token" msgstr "Запросить токен" -#: src/gtk/license-dialog.ui:52 +#: src/gtk/license-dialog-content.ui:39 msgid "Verify token" msgstr "Проверить токен" +#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:91 +msgid "License Details" +msgstr "Подробности лицензии" + #: src/gtk/no-device.ui:13 msgid "No device connected" msgstr "Устройство не подключено" @@ -468,22 +468,22 @@ msgstr "Breezy Desktop" msgid "Menu" msgstr "Меню" -#: src/gtk/window.ui:35 +#: src/gtk/window.ui:43 msgid "Some features expire soon" msgstr "Некоторые функции скоро истекут" -#: src/gtk/window.ui:36 src/gtk/window.ui:43 +#: src/gtk/window.ui:51 src/gtk/window.ui:76 msgid "View details" msgstr "Просмотреть детали" -#: src/gtk/window.ui:42 +#: src/gtk/window.ui:68 msgid "Productivity features are disabled" msgstr "Функции повышения производительности отключены" -#: src/gtk/window.ui:59 +#: src/gtk/window.ui:95 msgid "Force Reset" msgstr "Сброс" -#: src/gtk/window.ui:63 +#: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "О BreezyDesktop" diff --git a/ui/po/sv.po b/ui/po/sv.po index b1d598d..9a6d458 100644 --- a/ui/po/sv.po +++ b/ui/po/sv.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-01 13:21-0700\n" +"POT-Creation-Date: 2024-10-03 21:26-0700\n" "PO-Revision-Date: 2024-08-16 10:31-0700\n" "Last-Translator: \n" "Language-Team: Swedish \n" @@ -29,11 +29,11 @@ msgstr "" msgid "This feature is not currently supported for your device." msgstr "Din enhet stöder inte den här funktionen för tillfället." -#: src/licensedialog.py:57 +#: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "Betalningsstatus" -#: src/licensedialog.py:63 +#: src/licensedialogcontent.py:71 msgid "Feature Availability" msgstr "Funktions tillgänglighet" @@ -349,22 +349,22 @@ msgstr "" "Din Breezy GNOME inställning är ogiltig eller ofullständig. Var god kör " "inställning skriptet igen. Rapportera detta problem om det fortsätter." -#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:55 -msgid "License Details" -msgstr "Licensdetaljer" - -#: src/gtk/license-dialog.ui:27 +#: src/gtk/license-dialog-content.ui:15 msgid "Donate" msgstr "Donera" -#: src/gtk/license-dialog.ui:44 +#: src/gtk/license-dialog-content.ui:31 msgid "Request a token" msgstr "Begär en token" -#: src/gtk/license-dialog.ui:52 +#: src/gtk/license-dialog-content.ui:39 msgid "Verify token" msgstr "Verifiera token" +#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:91 +msgid "License Details" +msgstr "Licensdetaljer" + #: src/gtk/no-device.ui:13 msgid "No device connected" msgstr "Inget enhet ansluten" @@ -462,22 +462,22 @@ msgstr "Breezy Desktop" msgid "Menu" msgstr "Meny" -#: src/gtk/window.ui:35 +#: src/gtk/window.ui:43 msgid "Some features expire soon" msgstr "Vissa funktioner upphör snart" -#: src/gtk/window.ui:36 src/gtk/window.ui:43 +#: src/gtk/window.ui:51 src/gtk/window.ui:76 msgid "View details" msgstr "Se detaljer" -#: src/gtk/window.ui:42 +#: src/gtk/window.ui:68 msgid "Productivity features are disabled" msgstr "Produktivitets funktioner är inaktiverade" -#: src/gtk/window.ui:59 +#: src/gtk/window.ui:95 msgid "Force Reset" msgstr "Tvinga Reset" -#: src/gtk/window.ui:63 +#: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "Om BreezyDesktop" diff --git a/ui/po/uk_UA.po b/ui/po/uk_UA.po index b5820d6..c729347 100644 --- a/ui/po/uk_UA.po +++ b/ui/po/uk_UA.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-01 13:21-0700\n" +"POT-Creation-Date: 2024-10-03 21:26-0700\n" "PO-Revision-Date: 2024-08-17 10:08-0700\n" "Last-Translator: \n" "Language-Team: Ukrainian \n" @@ -28,11 +28,11 @@ msgstr "Переключає окуляри в режим «бок о бок» msgid "This feature is not currently supported for your device." msgstr "Ця функція наразі не підтримується на вашому пристрої." -#: src/licensedialog.py:57 +#: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "Статус платного рівня" -#: src/licensedialog.py:63 +#: src/licensedialogcontent.py:71 msgid "Feature Availability" msgstr "Статус функцій" @@ -352,22 +352,22 @@ msgstr "" "Ваша настройка Breezy GNOME є невірною або неповною. Будь ласка, запустіть " "скрипт настройки повторно. Повідомте про цю проблему, якщо вона не зникає." -#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:55 -msgid "License Details" -msgstr "Деталі ліцензії" - -#: src/gtk/license-dialog.ui:27 +#: src/gtk/license-dialog-content.ui:15 msgid "Donate" msgstr "Донатити" -#: src/gtk/license-dialog.ui:44 +#: src/gtk/license-dialog-content.ui:31 msgid "Request a token" msgstr "Запитати токен" -#: src/gtk/license-dialog.ui:52 +#: src/gtk/license-dialog-content.ui:39 msgid "Verify token" msgstr "Перевірити токен" +#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:91 +msgid "License Details" +msgstr "Деталі ліцензії" + #: src/gtk/no-device.ui:13 msgid "No device connected" msgstr "Жоден пристрій не підключено" @@ -465,22 +465,22 @@ msgstr "Breezy Desktop" msgid "Menu" msgstr "Меню" -#: src/gtk/window.ui:35 +#: src/gtk/window.ui:43 msgid "Some features expire soon" msgstr "Деякі функції закінчуються незабаром" -#: src/gtk/window.ui:36 src/gtk/window.ui:43 +#: src/gtk/window.ui:51 src/gtk/window.ui:76 msgid "View details" msgstr "Переглянути деталі" -#: src/gtk/window.ui:42 +#: src/gtk/window.ui:68 msgid "Productivity features are disabled" msgstr "Функції продуктивного режиму відключені" -#: src/gtk/window.ui:59 +#: src/gtk/window.ui:95 msgid "Force Reset" msgstr "Скинути" -#: src/gtk/window.ui:63 +#: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "Про BreezyDesktop" diff --git a/ui/po/zh_CN.po b/ui/po/zh_CN.po index 45e6462..a4fa7ca 100644 --- a/ui/po/zh_CN.po +++ b/ui/po/zh_CN.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-01 13:21-0700\n" +"POT-Creation-Date: 2024-10-03 21:26-0700\n" "PO-Revision-Date: 2024-08-02 20:55-0700\n" "Last-Translator: \n" "Language-Team: Chinese (simplified) \n" @@ -26,11 +26,11 @@ msgstr "" msgid "This feature is not currently supported for your device." msgstr "" -#: src/licensedialog.py:57 +#: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "" -#: src/licensedialog.py:63 +#: src/licensedialogcontent.py:71 msgid "Feature Availability" msgstr "" @@ -330,22 +330,22 @@ msgid "" "script. Report this issue if it persists." msgstr "" -#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:55 -msgid "License Details" -msgstr "" - -#: src/gtk/license-dialog.ui:27 +#: src/gtk/license-dialog-content.ui:15 msgid "Donate" msgstr "" -#: src/gtk/license-dialog.ui:44 +#: src/gtk/license-dialog-content.ui:31 msgid "Request a token" msgstr "" -#: src/gtk/license-dialog.ui:52 +#: src/gtk/license-dialog-content.ui:39 msgid "Verify token" msgstr "" +#: src/gtk/license-dialog.ui:5 src/gtk/window.ui:91 +msgid "License Details" +msgstr "" + #: src/gtk/no-device.ui:13 msgid "No device connected" msgstr "" @@ -420,22 +420,22 @@ msgstr "" msgid "Menu" msgstr "" -#: src/gtk/window.ui:35 +#: src/gtk/window.ui:43 msgid "Some features expire soon" msgstr "" -#: src/gtk/window.ui:36 src/gtk/window.ui:43 +#: src/gtk/window.ui:51 src/gtk/window.ui:76 msgid "View details" msgstr "" -#: src/gtk/window.ui:42 +#: src/gtk/window.ui:68 msgid "Productivity features are disabled" msgstr "" -#: src/gtk/window.ui:59 +#: src/gtk/window.ui:95 msgid "Force Reset" msgstr "" -#: src/gtk/window.ui:63 +#: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "" diff --git a/ui/src/breezydesktop.gresource.xml b/ui/src/breezydesktop.gresource.xml index 99a5a5d..843487c 100644 --- a/ui/src/breezydesktop.gresource.xml +++ b/ui/src/breezydesktop.gresource.xml @@ -4,6 +4,7 @@ gtk/connected-device.ui gtk/failed-verification.ui gtk/license-dialog.ui + gtk/license-dialog-content.ui gtk/no-device.ui gtk/no-driver.ui gtk/no-extension.ui diff --git a/ui/src/breezydesktop.in b/ui/src/breezydesktop.in index f25bbe3..bbd44d3 100755 --- a/ui/src/breezydesktop.in +++ b/ui/src/breezydesktop.in @@ -26,14 +26,20 @@ import locale import gettext VERSION = '@VERSION@' -pkgdatadir = '@pkgdatadir@' -localedir = '@localedir@' +xdg_data_home = os.getenv('XDG_DATA_HOME', os.path.join(os.path.expanduser('~'), '.local', 'share')) +appdir = os.getenv('APPDIR', xdg_data_home) +locale_dir = os.path.join(appdir, 'locale') +pkgdatadir = os.path.join(appdir, 'breezydesktop') sys.path.insert(1, pkgdatadir) + signal.signal(signal.SIGINT, signal.SIG_DFL) -locale.bindtextdomain('breezydesktop', localedir) locale.textdomain('breezydesktop') -gettext.install('breezydesktop', localedir) +locale.setlocale(locale.LC_ALL, locale.getlocale()) +locale.bindtextdomain('breezydesktop', locale_dir) +gettext.install('breezydesktop', locale_dir) +gettext.bindtextdomain('breezydesktop', locale_dir) +gettext.textdomain('breezydesktop') if __name__ == '__main__': import gi diff --git a/ui/src/gtk/license-dialog-content.ui b/ui/src/gtk/license-dialog-content.ui new file mode 100644 index 0000000..96545c5 --- /dev/null +++ b/ui/src/gtk/license-dialog-content.ui @@ -0,0 +1,55 @@ + + + + + diff --git a/ui/src/gtk/license-dialog.ui b/ui/src/gtk/license-dialog.ui index 70cc2bb..bf7c6d1 100644 --- a/ui/src/gtk/license-dialog.ui +++ b/ui/src/gtk/license-dialog.ui @@ -12,59 +12,5 @@ view-refresh-symbolic - - - vertical - 5 - 5 - 20 - 20 - - - 10 - - - Donate - ko-fi.com/wheaney - True - - - go-next-symbolic - https://ko-fi.com/wheaney - - - - - - - - 0 - Request a token - 6 - 1 - - - - - 0 - Verify token - 16 - 1 - - - - - - - - - - - - - - diff --git a/ui/src/gtk/window.ui b/ui/src/gtk/window.ui index cd98eb9..044f36f 100644 --- a/ui/src/gtk/window.ui +++ b/ui/src/gtk/window.ui @@ -30,17 +30,53 @@ vertical - + 0 - Some features expire soon - View details + False + warning + + + True + + + True + Some features expire soon + True + + + + + + + View details + True + + - + 0 - Productivity features are disabled - View details + False + error + + + True + + + True + Productivity features are disabled + True + + + + + + + View details + True + + diff --git a/ui/src/licensedialog.py b/ui/src/licensedialog.py index 52a2c64..7c9cbc6 100644 --- a/ui/src/licensedialog.py +++ b/ui/src/licensedialog.py @@ -1,87 +1,14 @@ -from gi.repository import Adw, Gtk, GLib -from .nolicense import NoLicense -from .statemanager import StateManager -from .licensetierrow import LicenseTierRow -from .licensefeaturerow import LicenseFeatureRow -from .xrdriveripc import XRDriverIPC -import gettext - -_ = gettext.gettext +from gi.repository import Gtk +from .licensedialogcontent import LicenseDialogContent @Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/license-dialog.ui') class LicenseDialog(Gtk.Dialog): __gtype_name__ = 'LicenseDialog' - tiers = Gtk.Template.Child() - features = Gtk.Template.Child() - request_token = Gtk.Template.Child() - verify_token = Gtk.Template.Child() refresh_license_button = Gtk.Template.Child() def __init__(self): super(Gtk.Dialog, self).__init__() self.init_template() - - self.ipc = XRDriverIPC.get_instance() - StateManager.get_instance().connect('notify::license-action-needed', self._handle_license) - self._handle_license(StateManager.get_instance()) - - self.request_token.connect('apply', self._on_request_token) - self.verify_token.connect('apply', self._on_verify_token) - self.refresh_license_button.connect('clicked', self._refresh_license) - - self.no_license = NoLicense(hide_refresh_button = True) - - def _refresh_license(self, widget): - self.refresh_license_button.set_sensitive(False) - self.ipc.write_control_flags({'refresh_device_license': True}) - GLib.timeout_add_seconds(3, self._handle_license) - - def _handle_license(self, state_manager = None, val = None): - GLib.idle_add(self._handle_license_idle, state_manager or StateManager.get_instance()) - - def _handle_license_idle(self, state_manager): - self.refresh_license_button.set_sensitive(False) - - license_view = state_manager.state['ui_view'].get('license', {}) - self.request_token.set_visible(not state_manager.confirmed_token) - self.verify_token.set_visible(not state_manager.confirmed_token) - - for child in self.tiers: - self.tiers.remove(child) - - for child in self.features: - self.features.remove(child) - - if license_view: - tiers_group = Adw.PreferencesGroup(title=_("Paid Tier Status"), margin_top=20) - self.tiers.append(tiers_group) - - for tier_name, tier_details in license_view['tiers'].items(): - tiers_group.add(LicenseTierRow(tier_name, tier_details)) - - features_group = Adw.PreferencesGroup(title=_("Feature Availability"), margin_top=20) - self.features.append(features_group) - - for feature_name, feature_details in license_view['features'].items(): - features_group.add(LicenseFeatureRow(feature_name, feature_details)) - else: - self.tiers.append(self.no_license) - - self.refresh_license_button.set_sensitive(True) - - def _on_request_token(self, widget): - email_address = self.request_token.get_text() - self.request_token.set_editable(False) - if not self.ipc.request_token(email_address): - self.request_token.set_editable(True) - - def _on_verify_token(self, widget): - token = self.verify_token.get_text() - self.request_token.set_editable(False) - self.verify_token.set_editable(False) - if self.ipc.verify_token(token): - self.ipc.write_control_flags({'refresh_device_license': True}) - else: - self.request_token.set_editable(True) - self.verify_token.set_editable(True) \ No newline at end of file + self.content = LicenseDialogContent(self.refresh_license_button) + self.get_content_area().append(self.content) \ No newline at end of file diff --git a/ui/src/licensedialogcontent.py b/ui/src/licensedialogcontent.py new file mode 100644 index 0000000..72b8aba --- /dev/null +++ b/ui/src/licensedialogcontent.py @@ -0,0 +1,95 @@ +from gi.repository import Adw, Gtk, GLib +from .nolicense import NoLicense +from .statemanager import StateManager +from .licensetierrow import LicenseTierRow +from .licensefeaturerow import LicenseFeatureRow +from .xrdriveripc import XRDriverIPC +import gettext + +_ = gettext.gettext + +@Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/license-dialog-content.ui') +class LicenseDialogContent(Gtk.Box): + __gtype_name__ = 'LicenseDialogContent' + + tiers = Gtk.Template.Child() + features = Gtk.Template.Child() + request_token = Gtk.Template.Child() + verify_token = Gtk.Template.Child() + donation_info = Gtk.Template.Child() + + def __init__(self, refresh_license_button): + super(Gtk.Box, self).__init__() + self.init_template() + + # check if it has a set_subtitle_selectable method + if hasattr(self.donation_info, 'set_subtitle_selectable'): + self.donation_info.set_subtitle_selectable(True) + + self.refresh_license_button = refresh_license_button + self.refresh_license_button.connect('clicked', self._refresh_license) + + self.ipc = XRDriverIPC.get_instance() + StateManager.get_instance().connect('notify::license-action-needed', self._handle_license) + self._handle_license(StateManager.get_instance()) + + self.request_token.connect('apply', self._on_request_token) + self.verify_token.connect('apply', self._on_verify_token) + + self.no_license = NoLicense(hide_refresh_button = True) + + def _refresh_license(self, widget): + self.refresh_license_button.set_sensitive(False) + self.ipc.write_control_flags({'refresh_device_license': True}) + GLib.timeout_add_seconds(3, self._handle_license) + + def _handle_license(self, state_manager = None, val = None): + GLib.idle_add(self._handle_license_idle, state_manager or StateManager.get_instance()) + + def _handle_license_idle(self, state_manager): + self.refresh_license_button.set_sensitive(False) + + license_view = state_manager.state['ui_view'].get('license', {}) + self.request_token.set_visible(not state_manager.confirmed_token) + self.verify_token.set_visible(not state_manager.confirmed_token) + + for child in self.tiers: + self.tiers.remove(child) + + for child in self.features: + self.features.remove(child) + + if license_view: + tiers_group = Adw.PreferencesGroup(title=_("Paid Tier Status"), margin_top=20) + self.tiers.append(tiers_group) + + for tier_name, tier_details in license_view['tiers'].items(): + row = LicenseTierRow(tier_name, tier_details) + if row.get_title() != "": + tiers_group.add(row) + + features_group = Adw.PreferencesGroup(title=_("Feature Availability"), margin_top=20) + self.features.append(features_group) + + for feature_name, feature_details in license_view['features'].items(): + features_group.add(LicenseFeatureRow(feature_name, feature_details)) + else: + self.tiers.append(self.no_license) + + self.refresh_license_button.set_sensitive(True) + + def _on_request_token(self, widget): + email_address = self.request_token.get_text() + self.request_token.set_editable(False) + if not self.ipc.request_token(email_address): + self.request_token.set_editable(True) + + def _on_verify_token(self, widget): + token = self.verify_token.get_text() + self.request_token.set_editable(False) + self.verify_token.set_editable(False) + if self.ipc.verify_token(token): + self.ipc.write_control_flags({'refresh_device_license': True}) + else: + self.request_token.set_editable(True) + self.verify_token.set_editable(True) \ No newline at end of file diff --git a/ui/src/licensetierrow.py b/ui/src/licensetierrow.py index 0b0d1a1..46cc218 100644 --- a/ui/src/licensetierrow.py +++ b/ui/src/licensetierrow.py @@ -56,7 +56,7 @@ class LicenseTierRow(Adw.ExpanderRow): 'supporter': _('Gaming'), 'subscriber': _('Productivity') } - return tier_names[tier] + return tier_names.get(tier) or "" def _period_description(self, period): period_descriptions = { diff --git a/ui/src/main.py b/ui/src/main.py index 710d3ce..d6543e4 100644 --- a/ui/src/main.py +++ b/ui/src/main.py @@ -17,12 +17,14 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -import gettext -import gi -import locale -import logging import os import sys + +lib_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lib') +sys.path.insert(0, lib_dir) + +import gi +import logging import argparse from logging.handlers import TimedRotatingFileHandler @@ -32,16 +34,6 @@ gi.require_version('Adw', '1') gi.require_version('Gio', '2.0') gi.require_version('GLib', '2.0') -user_home = os.path.expanduser('~') -xdg_data_home = os.environ.get('XDG_DATA_HOME') or os.path.join(user_home, '.local', 'share') -locale_dir = os.environ.get('LOCALE_DIR', os.path.join(xdg_data_home, 'locale')) - -locale.setlocale(locale.LC_ALL, locale.getdefaultlocale()) -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 @@ -75,14 +67,16 @@ XRDriverIPC.set_instance(XRDriverIPC(logger, config_dir)) class BreezydesktopApplication(Adw.Application): """The main application singleton class.""" - def __init__(self, skip_verification): + def __init__(self, version, skip_verification): super().__init__(application_id='com.xronlinux.BreezyDesktop', flags=Gio.ApplicationFlags.DEFAULT_FLAGS) + self.version = version + self.create_action('quit', self.on_quit_action, ['q']) self.create_action('about', self.on_about_action) self.create_action('license', self.on_license_action) self.create_action('reset_driver', self.on_reset_driver_action) - self._skip_verification = skip_verification + self._skip_verification = skip_verification or False # always do this on start-up since the driver sometimes fails to update the license on boot, # prevent showing a license warning unnecessarily @@ -107,7 +101,7 @@ class BreezydesktopApplication(Adw.Application): modal=True, program_name='Breezy Desktop', logo_icon_name='com.xronlinux.BreezyDesktop', - version='1.0.0', + version=self.version, authors=['Wayne Heaney'], copyright='© 2024 Wayne Heaney') about.present() @@ -151,5 +145,5 @@ def main(version): parser.add_argument("-sv", "--skip-verification", action="store_true") args = parser.parse_args() - app = BreezydesktopApplication(args.skip_verification) + app = BreezydesktopApplication(version, args.skip_verification) return app.run(None) diff --git a/ui/src/meson.build b/ui/src/meson.build index 9917b2a..cb1ecbc 100644 --- a/ui/src/meson.build +++ b/ui/src/meson.build @@ -34,6 +34,7 @@ breezydesktop_sources = [ 'failedverification.py', 'license.py', 'licensedialog.py', + 'licensedialogcontent.py', 'licensefeaturerow.py', 'licensetierrow.py', 'main.py', diff --git a/ui/src/window.py b/ui/src/window.py index aeddeb4..502f7dc 100644 --- a/ui/src/window.py +++ b/ui/src/window.py @@ -36,10 +36,14 @@ class BreezydesktopWindow(Gtk.ApplicationWindow): main_content = Gtk.Template.Child() license_action_needed_banner = Gtk.Template.Child() + license_action_needed_button = Gtk.Template.Child() missing_breezy_features_banner = Gtk.Template.Child() + missing_breezy_features_button = Gtk.Template.Child() def __init__(self, skip_verification, **kwargs): super().__init__(**kwargs) + + self._skip_verification = skip_verification self.state_manager = StateManager.get_instance() self.state_manager.connect('device-update', self._handle_state_update) @@ -54,8 +58,8 @@ class BreezydesktopWindow(Gtk.ApplicationWindow): self.no_extension = NoExtension() self.no_license = NoLicense() - self.license_action_needed_banner.connect('button-clicked', self._on_license_button_clicked) - self.missing_breezy_features_banner.connect('button-clicked', self._on_license_button_clicked) + self.license_action_needed_button.connect('clicked', self._on_license_button_clicked) + self.missing_breezy_features_button.connect('clicked', self._on_license_button_clicked) self._handle_state_update(self.state_manager, None) @@ -77,14 +81,14 @@ class BreezydesktopWindow(Gtk.ApplicationWindow): if not self._skip_verification and not verify_installation(): self.main_content.append(self.failed_verification) + elif not ExtensionsManager.get_instance().is_installed(): + self.main_content.append(self.no_extension) elif not self.state_manager.driver_running: self.main_content.append(self.no_driver) elif not self.state_manager.license_present: self.main_content.append(self.no_license) elif not state_manager.connected_device_name: self.main_content.append(self.no_device) - elif not ExtensionsManager.get_instance().is_installed(): - self.main_content.append(self.no_extension) else: self.main_content.append(self.connected_device) self.connected_device.set_device_name(state_manager.connected_device_name)