Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-py3status for openSUSE:Factory checked in at 2023-03-08 14:52:46 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-py3status (Old) and /work/SRC/openSUSE:Factory/.python-py3status.new.31432 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-py3status" Wed Mar 8 14:52:46 2023 rev:10 rq:1069990 version:3.49 Changes: -------- --- /work/SRC/openSUSE:Factory/python-py3status/python-py3status.changes 2022-10-03 20:08:11.061382268 +0200 +++ /work/SRC/openSUSE:Factory/.python-py3status.new.31432/python-py3status.changes 2023-03-08 14:52:50.250762600 +0100 @@ -1,0 +2,28 @@ +Tue Feb 28 10:14:23 UTC 2023 - Dawid Adam <nys...@gmail.com> + +- Update to 3.49: + * do_not_disturb module: use 'makoctl mode' to check current mode. (#2172), by Valdur Kana + * google_calendar module: add support to configure which google calendar will be used (#2174), by Alex Thomae + +------------------------------------------------------------------- +Tue Feb 28 10:14:23 UTC 2023 - Dawid Adam <nys...@gmail.com> + +- Update to 3.48: + * IMPORTANT: bluetooth module has been replaced by the bluetooth2 code, please migrate + * python: drop py 3.6 from CI and bump 3.11 (#2166) + * battery_level module: allow icon to not use charging_character (#2158), by Kevin Pulo + * bluetooth module: replaced by bluetooth2 as announced on 2022-10 + * check_tcp module: add IPv6 support (#2167), by Björn Busse + * clock module: migrate to standard zoneinfo with 3.7, 3.8 support. (#2155), by Valdur Kana + * events: change the reading timeout to infinity (#2153), by Austin Lund + * kdeconnector module: Active notifications were always 1. (#2170), by Valdur Kana + * kdeconnector module: refactor to use dbus signals to update module (#2168), by Valdur Kana + * kdeconnector module: show cell network type and strength. (#2162) (#2163), by Valdur Kana + * mpris module: fix error self.parent on Py3status module not found. (#2169), by Valdur Kana + * timewarrior module: remove dependency on dateutil (#2161), by Rasmus Rendal + * volume_status module: amixer scontrols uses device and card parameter. (#2152), by Valdur Kana + * volume_status module: deprecate start_delay parameter + * volume_status module: smarter initialization logic with retry, remove start_delay setting (#2165), by Joan Bruguera + * window module: window without title will not produce error on i3msg mode, by Valdur Kana* core: add inhibition timer on misbehaving signals + +------------------------------------------------------------------- Old: ---- py3status-3.47.tar.gz New: ---- py3status-3.49.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-py3status.spec ++++++ --- /var/tmp/diff_new_pack.gbZQr1/_old 2023-03-08 14:52:50.738765258 +0100 +++ /var/tmp/diff_new_pack.gbZQr1/_new 2023-03-08 14:52:50.742765279 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-py3status # -# Copyright (c) 2022 SUSE LLC +# Copyright (c) 2023 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,7 +19,7 @@ %define skip_python2 1 %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-py3status -Version: 3.47 +Version: 3.49 Release: 0 Summary: Python extensible i3status wrapper License: BSD-3-Clause @@ -32,6 +32,8 @@ BuildRequires: fdupes BuildRequires: python-rpm-macros Requires: python-setuptools +Requires(post): update-alternatives +Requires(postun):update-alternatives Recommends: i3status Recommends: python-gevent >= 1.1 Recommends: python-pyudev >= 0.21.0 @@ -73,7 +75,7 @@ %python_install_alternative py3status %python_install_alternative py3-cmd -%preun +%postun %python_uninstall_alternative py3status %python_uninstall_alternative py3-cmd @@ -82,6 +84,6 @@ %doc CHANGELOG README.md %python_alternative %{_bindir}/py3status %python_alternative %{_bindir}/py3-cmd -%{python_sitelib}/* +%{python_sitelib}/py3status* %changelog ++++++ py3status-3.47.tar.gz -> py3status-3.49.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/.github/workflows/ci.yml new/py3status-3.49/.github/workflows/ci.yml --- old/py3status-3.47/.github/workflows/ci.yml 2022-03-14 10:30:28.000000000 +0100 +++ new/py3status-3.49/.github/workflows/ci.yml 2022-12-12 08:07:52.000000000 +0100 @@ -8,7 +8,7 @@ strategy: max-parallel: 5 matrix: - python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] + python-version: [3.7, 3.8, 3.9, "3.10", "3.11"] steps: - uses: actions/checkout@v1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/CHANGELOG new/py3status-3.49/CHANGELOG --- old/py3status-3.47/CHANGELOG 2022-10-02 17:52:20.000000000 +0200 +++ new/py3status-3.49/CHANGELOG 2023-02-17 13:40:27.000000000 +0100 @@ -1,3 +1,25 @@ +version 3.49 (2023-02-17) +* do_not_disturb module: use 'makoctl mode' to check current mode. (#2172), by Valdur Kana +* google_calendar module: add support to configure which google calendar will be used (#2174), by Alex Thomae + +version 3.48 (2023-01-14) +* IMPORTANT: bluetooth module has been replaced by the bluetooth2 code, please migrate +* python: drop py 3.6 from CI and bump 3.11 (#2166) +* battery_level module: allow icon to not use charging_character (#2158), by Kevin Pulo +* bluetooth module: replaced by bluetooth2 as announced on 2022-10 +* check_tcp module: add IPv6 support (#2167), by Björn Busse +* clock module: migrate to standard zoneinfo with 3.7, 3.8 support. (#2155), by Valdur Kana +* events: change the reading timeout to infinity (#2153), by Austin Lund +* kdeconnector module: Active notifications were always 1. (#2170), by Valdur Kana +* kdeconnector module: refactor to use dbus signals to update module (#2168), by Valdur Kana +* kdeconnector module: show cell network type and strength. (#2162) (#2163), by Valdur Kana +* mpris module: fix error self.parent on Py3status module not found. (#2169), by Valdur Kana +* timewarrior module: remove dependency on dateutil (#2161), by Rasmus Rendal +* volume_status module: amixer scontrols uses device and card parameter. (#2152), by Valdur Kana +* volume_status module: deprecate start_delay parameter +* volume_status module: smarter initialization logic with retry, remove start_delay setting (#2165), by Joan Bruguera +* window module: window without title will not produce error on i3msg mode, by Valdur Kana* core: add inhibition timer on misbehaving signals + version 3.47 (2022-10-02) * INFORMATION: the upcoming bluetooth module has been merged as bluetooth2, users are encouraged to switch * core: simpler logic to inhibit out of order signaling (#2147) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/PKG-INFO new/py3status-3.49/PKG-INFO --- old/py3status-3.47/PKG-INFO 2022-10-02 17:54:02.083479200 +0200 +++ new/py3status-3.49/PKG-INFO 2023-02-17 13:41:23.554134000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: py3status -Version: 3.47 +Version: 3.49 Summary: py3status: an extensible i3status wrapper written in python Home-page: https://github.com/ultrabug/py3status Author: Ultrabug diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status/core.py new/py3status-3.49/py3status/core.py --- old/py3status-3.47/py3status/core.py 2022-10-02 17:50:25.000000000 +0200 +++ new/py3status-3.49/py3status/core.py 2022-10-27 12:10:18.000000000 +0200 @@ -251,6 +251,7 @@ """ self.config = vars(options) self.i3bar_running = True + self.inhibit_signal_ts = time.monotonic() self.last_refresh_ts = time.monotonic() self.lock = Event() self.modules = {} @@ -1018,7 +1019,10 @@ return ",".join(dumps(x) for x in outputs) def i3bar_stop(self, signum, frame): - if self.next_allowed_signal == signum: + if ( + self.next_allowed_signal == signum + and time.monotonic() > self.inhibit_signal_ts + ): self.log(f"received stop_signal {Signals(signum).name}") self.i3bar_running = False # i3status should be stopped @@ -1027,15 +1031,20 @@ self.next_allowed_signal = SIGCONT else: self.log(f"inhibited stop_signal {Signals(signum).name}", level="warning") + self.inhibit_signal_ts = time.monotonic() + 0.1 def i3bar_start(self, signum, frame): - if self.next_allowed_signal == signum: + if ( + self.next_allowed_signal == signum + and time.monotonic() > self.inhibit_signal_ts + ): self.log(f"received resume signal {Signals(signum).name}") self.i3bar_running = True self.wake_modules() self.next_allowed_signal = self.stop_signal else: self.log(f"inhibited start_signal {Signals(signum).name}", level="warning") + self.inhibit_signal_ts = time.monotonic() + 0.1 def sleep_modules(self): # Put all py3modules to sleep so they stop updating diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status/events.py new/py3status-3.49/py3status/events.py --- old/py3status-3.47/py3status/events.py 2022-02-17 12:08:39.000000000 +0100 +++ new/py3status-3.49/py3status/events.py 2022-10-17 13:35:26.000000000 +0200 @@ -272,7 +272,7 @@ """ try: while self.py3_wrapper.running: - event_str = self.poller_inp.readline() + event_str = self.poller_inp.readline(timeout=None) if not event_str: continue try: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status/module.py new/py3status-3.49/py3status/module.py --- old/py3status-3.47/py3status/module.py 2022-10-02 17:37:33.000000000 +0200 +++ new/py3status-3.49/py3status/module.py 2023-01-09 18:39:16.000000000 +0100 @@ -5,6 +5,7 @@ from importlib.machinery import SourceFileLoader from pathlib import Path from random import randint +from types import FunctionType from py3status.composite import Composite from py3status.constants import MARKUP_LANGUAGES, ON_ERROR_VALUES, POSITIONS @@ -845,8 +846,8 @@ if method.startswith("_"): continue else: - m_type = type(getattr(class_inst, method)) - if "method" in str(m_type): + m_attr = inspect.getattr_static(class_inst, method) + if isinstance(m_attr, FunctionType): params_type = self._params_type(method, class_inst) if method == "on_click": self.click_events = params_type diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status/modules/battery_level.py new/py3status-3.49/py3status/modules/battery_level.py --- old/py3status-3.47/py3status/modules/battery_level.py 2022-10-02 16:25:48.000000000 +0200 +++ new/py3status-3.49/py3status/modules/battery_level.py 2023-01-09 18:41:34.000000000 +0100 @@ -12,6 +12,7 @@ (default 60) charging_character: a character to represent charging battery especially useful when using icon fonts (e.g. FontAwesome) + set to 'None' if you want to hide the charging state of your battery (default "â¡") format: string that formats the output. See placeholders below. (default "{icon}") @@ -477,7 +478,7 @@ self.status = self.format_status_discharging def _update_icon(self): - if self.charging: + if self.charging and self.charging_character is not None: self.icon = self.charging_character else: self.icon = self.blocks[ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status/modules/bluetooth.py new/py3status-3.49/py3status/modules/bluetooth.py --- old/py3status-3.47/py3status/modules/bluetooth.py 2022-10-02 16:25:48.000000000 +0200 +++ new/py3status-3.49/py3status/modules/bluetooth.py 2023-01-10 13:36:52.000000000 +0100 @@ -3,54 +3,92 @@ Configuration parameters: cache_timeout: refresh interval for this module (default 10) - format: display format for this module (default 'BT[: {format_device}]') - format_device: display format for bluetooth devices (default '{name}') - format_separator: show separator if more than one (default '\|') + format: display format for this module (default "{format_adapter}") + format_adapter: display format for adapters (default "{format_device}") + format_adapter_separator: show separator if more than one (default " ") + format_device: display format for devices + (default "\?if=connected&color=connected {alias}") + format_device_separator: show separator if more than one (default " ") + thresholds: specify color thresholds to use + (default [(False, "bad"), (True, "good")]) Format placeholders: - {format_device} format for bluetooth devices + {format_adapter} format for adapters + {adapter} number of adapters, eg 1 + +format_adapter placeholders: + {format_device} format for devices + {device} number of devices, eg 5 + {address} eg, 00:00:00:00:00:00 + {addresstype} eg, public + {alias} eg, thinkpad + {class} eg, 123456 + {discoverable} eg, False + {discoverabletimeout} eg, 0 + {discovering} eg, False + {modalias} eg, usb:v1D68234ABCDEF5 + {name} eg, z420 + {pairable} eg, True + {pairabletimeout} eg, 0 + {path} eg, /org/bluez/hci0 + {powered} eg, True + {uuids} eg, [] format_device placeholders: - {mac} bluetooth device address - {name} bluetooth device name + {adapter} eg, /org/bluez/hci0 + {address} eg, 00:00:00:00:00:00 + {addresstype} eg, public + {alias} eg, MSFT Mouse + {class} eg, 1234 + {connected} eg, False + {icon} eg, input-mouse + {legacypairing} eg, False + {modalias} eg, usb:v1D68234ABCDEF5 + {name} eg, Microsoft Bluetooth Notebook Mouse 5000 + {paired} eg, True + {servicesresolved} eg, False + {trusted} eg, True + {uuids} eg, [] -Color options: - color_bad: No connection - color_good: Active connection +Color thresholds: + xxx: print a color based on the value of `xxx` placeholder Requires: - pydbus: pythonic dbus library pygobject: Python bindings for GObject Introspection -@author jmdana <https://github.com/jmdana> -@license GPLv3 <https://www.gnu.org/licenses/gpl-3.0.txt> +Examples: +``` +# always display devices +bluetooth { + format_device = "\?color=connected {alias}" +} + +# set an alias via blueman-manager or bluetoothctl +# $ bluetoothctl +# [bluetooth] # devices +# [bluetooth] # connect 00:00:00:00:00:00 +# [bluetooth] # set-alias "MSFT Mouse" + +# display missing adapter (feat. request) +bluetooth { + format = "\?if=adapter {format_adapter}|\?color=darkgray No Adapter" +} + +# legacy default +bluetooth { + format = "\?color=good BT: {format_adapter}|\?color=bad BT" + format_device_separator = "\|" +} +``` -SAMPLE OUTPUT -{'color': '#00FF00', 'full_text': u'BT: Microsoft Bluetooth Notebook Mouse 5000'} +@author jmdana, lasers +@license BSD -off -{'color': '#FF0000', 'full_text': u'BT'} +SAMPLE OUTPUT +{'color': '#00FF00', 'full_text': u'Microsoft Bluetooth Notebook Mouse 5000'} """ -from pydbus import SystemBus - -DEFAULT_FORMAT = "BT[: {format_device}]" - - -def get_connected_devices(): - bus = SystemBus() - manager = bus.get("org.bluez", "/")["org.freedesktop.DBus.ObjectManager"] - objects = manager.GetManagedObjects() - devices = [] - - for dev_path, interfaces in objects.items(): - if "org.bluez.Device1" in interfaces: - properties = objects[dev_path]["org.bluez.Device1"] - - if properties["Connected"] == 1: - devices.append((properties["Address"], properties["Name"])) - - return devices +from gi.repository import Gio class Py3status: @@ -58,87 +96,93 @@ # available configuration parameters cache_timeout = 10 - format = DEFAULT_FORMAT - format_device = "{name}" - format_separator = r"\|" - - class Meta: - def deprecate_function(config): - if not config.get("format_separator") and config.get("device_separator"): - sep = config.get("device_separator") - sep = sep.replace("\\", "\\\\") - sep = sep.replace("[", r"\[") - sep = sep.replace("]", r"\]") - sep = sep.replace("|", r"\|") - - return {"format_separator": sep} - else: - return {} - - deprecated = { - "function": [{"function": deprecate_function}], - "remove": [ - { - "param": "device_separator", - "msg": "obsolete set using `format_separator`", - } - ], - } + format = "{format_adapter}" + format_adapter = "{format_device}" + format_adapter_separator = " " + format_device = "\?if=connected&color=connected {alias}" + format_device_separator = " " + thresholds = [(False, "bad"), (True, "good")] def post_config_hook(self): - # DEPRECATION WARNING. SPECIAL CASE. DO NOT USE THIS AS EXAMPLE. - format_prefix = getattr(self, "format_prefix", None) - format_no_conn = getattr(self, "format_no_conn", None) - format_no_conn_prefix = getattr(self, "format_no_conn_prefix", None) - - placeholders = set(self.py3.get_placeholders_list(self.format)) - if {"name", "mac"} & placeholders: - # this is an old format so should be format_device - self.format_device = self.format - self.format = DEFAULT_FORMAT - msg = "DEPRECATION WARNING: your format is using invalid " - msg += "placeholders you should update your configuration." - self.py3.log(msg) - - if self.format != DEFAULT_FORMAT: - # The user has set a format using the new style format so we are - # done here. - return - - if format_prefix or format_no_conn_prefix or format_no_conn: - # create a format that will give the expected output - self.format = r"[\?if=format_device {}{{format_device}}|{}{}]".format( - format_prefix or "BT: ", - format_no_conn_prefix or "BT: ", - format_no_conn or "OFF", + bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) + iface = "org.freedesktop.DBus.ObjectManager" + self.bluez_manager = Gio.DBusProxy.new_sync( + bus, Gio.DBusProxyFlags.NONE, None, "org.bluez", "/", iface, None + ) + + self.names_and_matches = [ + ("adapters", "org.bluez.Adapter1"), + ("devices", "org.bluez.Device1"), + ] + + self.thresholds_init = {} + for name in ["format", "format_adapter", "format_device"]: + self.thresholds_init[name] = self.py3.get_color_names_list( + getattr(self, name) ) - msg = "DEPRECATION WARNING: you are using old style configuration " - msg += "parameters you should update to use the new format." - self.py3.log(msg) + + def _get_bluez_data(self): + objects = self.bluez_manager.GetManagedObjects() + temporary = {} + + for path, interfaces in sorted(objects.items()): + interface_keys = interfaces.keys() + for name, match in self.names_and_matches: + if match in interface_keys: + interface = {k.lower(): v for k, v in interfaces[match].items()} + interface.update({"path": path, "uuids": []}) + temporary.setdefault(name, []).append(interface) + break + + for device in temporary.pop("devices", []): + for index, adapter in enumerate(temporary["adapters"]): + if device["adapter"] == adapter["path"]: + temporary["adapters"][index].setdefault("devices", []).append( + device + ) + break + + return temporary def bluetooth(self): - devices = get_connected_devices() + bluez_data = self._get_bluez_data() + adapters = bluez_data.pop("adapters", []) + new_adapter = [] + + for adapter in adapters: + devices = adapter.pop("devices", []) + new_device = [] + + for device in devices: + for x in self.thresholds_init["format_device"]: + if x in device: + self.py3.threshold_get_color(device[x], x) + + new_device.append(self.py3.safe_format(self.format_device, device)) + + format_device_separator = self.py3.safe_format(self.format_device_separator) + format_device = self.py3.composite_join(format_device_separator, new_device) + + adapter.update({"format_device": format_device, "device": len(devices)}) + + for x in self.thresholds_init["format_adapter"]: + if x in adapter: + self.py3.threshold_get_color(adapter[x], x) + + new_adapter.append(self.py3.safe_format(self.format_adapter, adapter)) + + format_adapter_separator = self.py3.safe_format(self.format_adapter_separator) + format_adapter = self.py3.composite_join(format_adapter_separator, new_adapter) + + bluetooth_data = {"format_adapter": format_adapter, "adapter": len(adapters)} - if devices: - data = [] - for mac, name in devices: - data.append( - self.py3.safe_format(self.format_device, {"name": name, "mac": mac}) - ) - - format_separator = self.py3.safe_format(self.format_separator) - format_device = self.py3.composite_join(format_separator, data) - color = self.py3.COLOR_GOOD - else: - format_device = None - color = self.py3.COLOR_BAD + for x in self.thresholds_init["format"]: + if x in bluetooth_data: + self.py3.threshold_get_color(bluetooth_data[x], x) return { "cached_until": self.py3.time_in(self.cache_timeout), - "full_text": self.py3.safe_format( - self.format, {"format_device": format_device} - ), - "color": color, + "full_text": self.py3.safe_format(self.format, bluetooth_data), } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status/modules/bluetooth2.py new/py3status-3.49/py3status/modules/bluetooth2.py --- old/py3status-3.47/py3status/modules/bluetooth2.py 2022-10-02 15:59:23.000000000 +0200 +++ new/py3status-3.49/py3status/modules/bluetooth2.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,195 +0,0 @@ -r""" -Display bluetooth status. - -Configuration parameters: - cache_timeout: refresh interval for this module (default 10) - format: display format for this module (default "{format_adapter}") - format_adapter: display format for adapters (default "{format_device}") - format_adapter_separator: show separator if more than one (default " ") - format_device: display format for devices - (default "\?if=connected&color=connected {alias}") - format_device_separator: show separator if more than one (default " ") - thresholds: specify color thresholds to use - (default [(False, "bad"), (True, "good")]) - -Format placeholders: - {format_adapter} format for adapters - {adapter} number of adapters, eg 1 - -format_adapter placeholders: - {format_device} format for devices - {device} number of devices, eg 5 - {address} eg, 00:00:00:00:00:00 - {addresstype} eg, public - {alias} eg, thinkpad - {class} eg, 123456 - {discoverable} eg, False - {discoverabletimeout} eg, 0 - {discovering} eg, False - {modalias} eg, usb:v1D68234ABCDEF5 - {name} eg, z420 - {pairable} eg, True - {pairabletimeout} eg, 0 - {path} eg, /org/bluez/hci0 - {powered} eg, True - {uuids} eg, [] - -format_device placeholders: - {adapter} eg, /org/bluez/hci0 - {address} eg, 00:00:00:00:00:00 - {addresstype} eg, public - {alias} eg, MSFT Mouse - {class} eg, 1234 - {connected} eg, False - {icon} eg, input-mouse - {legacypairing} eg, False - {modalias} eg, usb:v1D68234ABCDEF5 - {name} eg, Microsoft Bluetooth Notebook Mouse 5000 - {paired} eg, True - {servicesresolved} eg, False - {trusted} eg, True - {uuids} eg, [] - -Color thresholds: - xxx: print a color based on the value of `xxx` placeholder - -Requires: - pygobject: Python bindings for GObject Introspection - -Examples: -``` -# always display devices -bluetooth { - format_device = "\?color=connected {alias}" -} - -# set an alias via blueman-manager or bluetoothctl -# $ bluetoothctl -# [bluetooth] # devices -# [bluetooth] # connect 00:00:00:00:00:00 -# [bluetooth] # set-alias "MSFT Mouse" - -# display missing adapter (feat. request) -bluetooth { - format = "\?if=adapter {format_adapter}|\?color=darkgray No Adapter" -} - -# legacy default -bluetooth { - format = "\?color=good BT: {format_adapter}|\?color=bad BT" - format_device_separator = "\|" -} -``` - -@author jmdana, lasers -@license BSD - -SAMPLE OUTPUT -{'color': '#00FF00', 'full_text': u'Microsoft Bluetooth Notebook Mouse 5000'} -""" - -from gi.repository import Gio - - -class Py3status: - """ """ - - # available configuration parameters - cache_timeout = 10 - format = "{format_adapter}" - format_adapter = "{format_device}" - format_adapter_separator = " " - format_device = "\?if=connected&color=connected {alias}" - format_device_separator = " " - thresholds = [(False, "bad"), (True, "good")] - - def post_config_hook(self): - bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) - iface = "org.freedesktop.DBus.ObjectManager" - self.bluez_manager = Gio.DBusProxy.new_sync( - bus, Gio.DBusProxyFlags.NONE, None, "org.bluez", "/", iface, None - ) - - self.names_and_matches = [ - ("adapters", "org.bluez.Adapter1"), - ("devices", "org.bluez.Device1"), - ] - - self.thresholds_init = {} - for name in ["format", "format_adapter", "format_device"]: - self.thresholds_init[name] = self.py3.get_color_names_list( - getattr(self, name) - ) - - def _get_bluez_data(self): - objects = self.bluez_manager.GetManagedObjects() - temporary = {} - - for path, interfaces in sorted(objects.items()): - interface_keys = interfaces.keys() - for name, match in self.names_and_matches: - if match in interface_keys: - interface = {k.lower(): v for k, v in interfaces[match].items()} - interface.update({"path": path, "uuids": []}) - temporary.setdefault(name, []).append(interface) - break - - for device in temporary.pop("devices", []): - for index, adapter in enumerate(temporary["adapters"]): - if device["adapter"] == adapter["path"]: - temporary["adapters"][index].setdefault("devices", []).append( - device - ) - break - - return temporary - - def bluetooth2(self): - bluez_data = self._get_bluez_data() - adapters = bluez_data.pop("adapters", []) - new_adapter = [] - - for adapter in adapters: - devices = adapter.pop("devices", []) - new_device = [] - - for device in devices: - for x in self.thresholds_init["format_device"]: - if x in device: - self.py3.threshold_get_color(device[x], x) - - new_device.append(self.py3.safe_format(self.format_device, device)) - - format_device_separator = self.py3.safe_format(self.format_device_separator) - format_device = self.py3.composite_join(format_device_separator, new_device) - - adapter.update({"format_device": format_device, "device": len(devices)}) - - for x in self.thresholds_init["format_adapter"]: - if x in adapter: - self.py3.threshold_get_color(adapter[x], x) - - new_adapter.append(self.py3.safe_format(self.format_adapter, adapter)) - - format_adapter_separator = self.py3.safe_format(self.format_adapter_separator) - format_adapter = self.py3.composite_join(format_adapter_separator, new_adapter) - - bluetooth_data = {"format_adapter": format_adapter, "adapter": len(adapters)} - - for x in self.thresholds_init["format"]: - if x in bluetooth_data: - self.py3.threshold_get_color(bluetooth_data[x], x) - - return { - "cached_until": self.py3.time_in(self.cache_timeout), - "full_text": self.py3.safe_format(self.format, bluetooth_data), - } - - -if __name__ == "__main__": - """ - Run module in test mode. - """ - from py3status.module_test import module_test - - module_test(Py3status) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status/modules/check_tcp.py new/py3status-3.49/py3status/modules/check_tcp.py --- old/py3status-3.47/py3status/modules/check_tcp.py 2022-10-02 16:25:48.000000000 +0200 +++ new/py3status-3.49/py3status/modules/check_tcp.py 2023-01-09 18:39:16.000000000 +0100 @@ -44,7 +44,12 @@ def check_tcp(self): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - result = sock.connect_ex((self.host, self.port)) + + try: + result = sock.connect_ex((self.host, self.port)) + except socket.gaierror: + sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + result = sock.connect_ex((self.host, self.port)) if result: color = self.color_off diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status/modules/clock.py new/py3status-3.49/py3status/modules/clock.py --- old/py3status-3.47/py3status/modules/clock.py 2022-10-02 16:25:48.000000000 +0200 +++ new/py3status-3.49/py3status/modules/clock.py 2022-10-27 10:35:49.000000000 +0200 @@ -6,12 +6,11 @@ Timezones are defined in the `format` using the TZ name in squiggly brackets eg `{GMT}`, `{Portugal}`, `{Europe/Paris}`, `{America/Argentina/Buenos_Aires}`. -ISO-3166 two letter country codes eg `{de}` can also be used but if more than -one timezone exists for the country eg `{us}` the first one will be selected. +See https://docs.python.org/3/library/zoneinfo.html for supported formats. `{Local}` can be used for the local settings of your computer. -Note: Timezones are case sensitive +Note: Timezones are case sensitive! A full list of timezones can be found at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones @@ -54,10 +53,6 @@ {timezone_unclear} full timezone name eg `America/Argentina/Buenos_Aires` but is empty if only one timezone is provided -Requires: - pytz: cross platform time zone library for python - tzlocal: tzinfo object for the local timezone - Examples: ``` # cycling through London, Warsaw, Tokyo @@ -80,7 +75,7 @@ } ``` -@author tobes +@author tobes ultrabug @license BSD SAMPLE OUTPUT @@ -95,8 +90,11 @@ import time from datetime import datetime -import pytz -import tzlocal +try: + import zoneinfo +# Fall back for python 3.7 and python 3.8 +except ImportError: + from backports import zoneinfo CLOCK_BLOCKS = "ðð§ðððððððððð ðð¡ðð¢ðð£ðð¤ðð¥ðð¦" @@ -192,24 +190,13 @@ # special Local timezone if tz == "Local": try: - return tzlocal.get_localzone() - except pytz.UnknownTimeZoneError: + return zoneinfo.ZoneInfo("localtime") + except zoneinfo.ZoneInfoNotFoundError: return "?" - - # we can use a country code to get tz - # FIXME this is broken for multi-timezone countries eg US - # for now we just grab the first one - if len(tz) == 2: - try: - zones = pytz.country_timezones(tz) - except KeyError: - return "?" - tz = zones[0] - # get the timezone try: - zone = pytz.timezone(tz) - except pytz.UnknownTimeZoneError: + zone = zoneinfo.ZoneInfo(tz) + except zoneinfo.ZoneInfoNotFoundError: return "?" return zone @@ -264,12 +251,7 @@ idx = int(h / self.block_hours * len(self.blocks)) icon = self.blocks[idx] - try: - # tzlocal < 3.0 - timezone = zone.zone - except AttributeError: - # tzlocal >= 3.0 - timezone = zone.key + timezone = zone.key tzname = timezone.split("/")[-1].replace("_", " ") if self.multiple_tz: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status/modules/do_not_disturb.py new/py3status-3.49/py3status/modules/do_not_disturb.py --- old/py3status-3.47/py3status/modules/do_not_disturb.py 2022-10-02 16:25:48.000000000 +0200 +++ new/py3status-3.49/py3status/modules/do_not_disturb.py 2023-02-17 13:39:22.000000000 +0100 @@ -129,8 +129,18 @@ Mako Notification. """ + # From version mako 1.7 we can use "makoctl mode" def setup(self, parent): - self.toggle(parent.state) + self.has_makoctl_mode = bool( + self.parent.py3.check_commands(["makoctl"]) + ) and not self.parent.py3.command_run("makoctl mode") + + def get_state(self): + if self.has_makoctl_mode: + state = self.parent.py3.command_output("makoctl mode") + return state.strip() == "do-not-disturb" + else: + return self.parent.state def toggle(self, state): if state is True: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status/modules/google_calendar.py new/py3status-3.49/py3status/modules/google_calendar.py --- old/py3status-3.47/py3status/modules/google_calendar.py 2022-10-02 17:08:43.000000000 +0200 +++ new/py3status-3.49/py3status/modules/google_calendar.py 2023-02-17 13:39:22.000000000 +0100 @@ -25,6 +25,8 @@ (default 1) cache_timeout: How often the module is refreshed in seconds (default 60) + calendar_id: The ID of the calendar to display. + (default "primary") client_secret: the path to your client_secret file which contains your OAuth 2.0 credentials. (default '~/.config/py3status/google_calendar.client_secret') @@ -182,6 +184,7 @@ button_refresh = 2 button_toggle = 1 cache_timeout = 60 + calendar_id = "primary" client_secret = "~/.config/py3status/google_calendar.client_secret" events_within_hours = 12 force_lowercase = False @@ -284,7 +287,7 @@ eventsResult = ( self.service.events() .list( - calendarId="primary", + calendarId=self.calendar_id, timeMax=time_max.isoformat() + "Z", # 'Z' indicates UTC time timeMin=time_min.isoformat() + "Z", # 'Z' indicates UTC time singleEvents=True, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status/modules/kdeconnector.py new/py3status-3.49/py3status/modules/kdeconnector.py --- old/py3status-3.47/py3status/modules/kdeconnector.py 2022-10-02 16:25:48.000000000 +0200 +++ new/py3status-3.49/py3status/modules/kdeconnector.py 2023-01-09 18:39:16.000000000 +0100 @@ -25,6 +25,8 @@ {name} name of the device {notif_size} number of notifications {notif_status} shows if a notification is available or not + {net_type} shows cell network type + {net_strength} shows cell network strength Color options: color_bad: Device unknown, unavailable @@ -40,12 +42,12 @@ ``` kdeconnector { device_id = "aa0844d33ac6ca03" - format = "{name} {battery} â¡ {state}" + format = "{name} {charge} {bat_status}" low_battery = "10" } ``` -@author Moritz Lüdecke +@author Moritz Lüdecke, valdur55 SAMPLE OUTPUT {'color': '#00FF00', 'full_text': u'Samsung Galaxy S6 \u2709 \u2B06 97%'} @@ -66,17 +68,24 @@ {'color': '#FF0000', 'full_text': u'unknown device'} """ -from pydbus import SessionBus +import sys +from threading import Thread +from dbus.mainloop.glib import DBusGMainLoop +from gi.repository import GLib +from pydbus import SessionBus +STRING_GEVENT = "this module does not work with gevent" SERVICE_BUS = "org.kde.kdeconnect" INTERFACE = SERVICE_BUS + ".device" INTERFACE_DAEMON = SERVICE_BUS + ".daemon" INTERFACE_BATTERY = INTERFACE + ".battery" INTERFACE_NOTIFICATIONS = INTERFACE + ".notifications" +INTERFACE_CONN_REPORT = INTERFACE + ".connectivity_report" PATH = "/modules/kdeconnect" DEVICE_PATH = PATH + "/devices" BATTERY_SUBPATH = "/battery" +CONN_REPORT_SUBPATH = "/connectivity_report" NOTIFICATIONS_SUBPATH = "/notifications" UNKNOWN = "Unknown" UNKNOWN_DEVICE = "unknown device" @@ -100,52 +109,213 @@ status_notif = " â" def post_config_hook(self): + if self.py3.is_gevent(): + raise Exception(STRING_GEVENT) + self._bat = None + self._con = None self._dev = None self._not = None + self._result = {} + + self._signal_reachable_changed = None + self._signal_battery = None + self._signal_notifications = None + self._signal_conn_report = None + + self._format_contains_notifications = self.py3.format_contains( + self.format, ["notif_size", "notif_status"] + ) + + self._format_contains_connection_status = self.py3.format_contains( + self.format, ["net_type", "net_strength"] + ) + + # start last + self._kill = False + self._dbus_loop = DBusGMainLoop() + self._dbus = SessionBus() + self._bus_initialized = self._init_dbus() + self._start_listener() + + if self._bus_initialized: + self._update_conn_info() + self._update_notif_info() + self._update_battery_info() + + def _start_loop(self): + self._loop = GLib.MainLoop() + GLib.timeout_add(1000, self._timeout) + try: + self._loop.run() + except KeyboardInterrupt: + # This branch is only needed for the test mode + self._kill = True + + def _start_listener(self): + self._signal_reachable_changed = self._dbus.con.signal_subscribe( + sender=None, + interface_name=INTERFACE, + member="reachableChanged", + object_path=None, + arg0=None, + flags=0, + callback=self._reachable_on_change, + ) + + self._signal_battery = self._dbus.con.signal_subscribe( + sender=None, + interface_name=INTERFACE_BATTERY, + member=None, + object_path=None, + arg0=None, + flags=0, + callback=self._battery_on_change, + ) + + if self._format_contains_notifications: + self._signal_notifications = self._dbus.con.signal_subscribe( + sender=None, + interface_name=INTERFACE_NOTIFICATIONS, + member=None, + object_path=None, + arg0=None, + flags=0, + callback=self._notifications_on_change, + ) + + if self._format_contains_connection_status: + self._signal_conn_report = self._dbus.con.signal_subscribe( + sender=None, + interface_name=INTERFACE_CONN_REPORT, + member=None, + object_path=None, + arg0=None, + flags=0, + callback=self._conn_report_on_change, + ) + + t = Thread(target=self._start_loop) + t.daemon = True + t.start() + + def _notifications_on_change( + self, connection, owner, object_path, interface_name, event, new_value + ): + if self._is_current_device(object_path): + self._update_notif_info() + self.py3.update() + + def _reachable_on_change( + self, connection, owner, object_path, interface_name, event, new_value + ): + if self._is_current_device(object_path): + # Update only when device is connected + if new_value[0]: + self._update_battery_info() + self._update_notif_info() + self._update_conn_info() + self.py3.update() + + def _battery_on_change( + self, connection, owner, object_path, interface_name, event, new_value + ): + if self._is_current_device(object_path): + if event == "refreshed": + if new_value[1] != -1: + self._set_battery_status( + isCharging=new_value[0], charge=new_value[1] + ) + elif event == "stateChanged": + self._set_battery_status(isCharging=new_value[0], charge=None) + elif event == "chargeChanged": + self._set_battery_status(isCharging=None, charge=new_value[0]) + else: + self._update_battery_info() + self.py3.update() + + def _conn_report_on_change( + self, connection, owner, object_path, interface_name, event, new_value + ): + if self._is_current_device(object_path): + if event == "refreshed": + if ( + self._result["net_type"] != new_value[0] + or self._result["net_strength_raw"] != new_value[1] + ): + self._set_conn_status( + net_type=new_value[0], net_strength=new_value[1] + ) + self.py3.update() + else: + self._update_conn_info() + self.py3.update() + + def _is_current_device(self, object_path): + return self.device_id in object_path + + def _timeout(self): + if self._kill: + self._loop.quit() + sys.exit(0) + def _init_dbus(self): """ Get the device id """ - _bus = SessionBus() - if self.device_id is None: - self.device_id = self._get_device_id(_bus) + self.device_id = self._get_device_id() if self.device_id is None: return False try: - self._dev = _bus.get(SERVICE_BUS, DEVICE_PATH + f"/{self.device_id}") + self._dev = self._dbus.get(SERVICE_BUS, DEVICE_PATH + f"/{self.device_id}") try: - self._bat = _bus.get( + self._bat = self._dbus.get( SERVICE_BUS, DEVICE_PATH + f"/{self.device_id}" + BATTERY_SUBPATH ) - self._not = _bus.get( - SERVICE_BUS, - DEVICE_PATH + f"/{self.device_id}" + NOTIFICATIONS_SUBPATH, - ) + + if self._format_contains_notifications: + self._not = self._dbus.get( + SERVICE_BUS, + DEVICE_PATH + f"/{self.device_id}" + NOTIFICATIONS_SUBPATH, + ) + else: + self._not = None except Exception: # Fallback to the old version self._bat = None self._not = None + + try: # This plugin is released after kdeconnect version Mar 13, 2021 + if self._format_contains_connection_status: + self._con = self._dbus.get( + SERVICE_BUS, + DEVICE_PATH + f"/{self.device_id}" + CONN_REPORT_SUBPATH, + ) + else: + self._con = None + except Exception: + self._con = None + except Exception: return False return True - def _get_device_id(self, bus): + def _get_device_id(self): """ Find the device id """ - _dbus = bus.get(SERVICE_BUS, PATH) - devices = _dbus.devices() + _bus = self._dbus.get(SERVICE_BUS, PATH) + devices = _bus.devices() if self.device is None and self.device_id is None and len(devices) == 1: return devices[0] for id in devices: - self._dev = bus.get(SERVICE_BUS, DEVICE_PATH + f"/{id}") + self._dev = self._dbus.get(SERVICE_BUS, DEVICE_PATH + f"/{id}") if self.device == self._dev.name: return id @@ -196,58 +366,105 @@ "isCharging": isCharging == 1, } except Exception: - return None + return { + "charge": -1, + "isCharging": None, + } return battery + def _get_conn(self): + """ + Get the connection report + """ + try: + if self._con: + # Possible values are -1 - 4 + strength = self._con.cellularNetworkStrength + type = self._con.cellularNetworkType + + con_info = { + "strength": strength, + "type": type, + } + else: + con_info = { + "strength": -1, + "type": "", + } + except Exception: + return { + "strength": -1, + "type": "", + } + + return con_info + def _get_notifications(self): """ Get notifications """ try: if self._not: - notifications = {"activeNotifications": self._not.activeNotifications()} + notifications = self._not.activeNotifications() else: - notifications = {"activeNotifications": self._dev.activeNotifications()} - notifications = {"activeNotifications": notifications} + notifications = self._dev.activeNotifications() except Exception: - return None + return [] return notifications - def _get_battery_status(self, battery): + def _set_battery_status(self, isCharging, charge): """ Get the battery status """ - if not battery or battery["charge"] == -1: - return (UNKNOWN_SYMBOL, UNKNOWN, "#FFFFFF") - - if battery["isCharging"]: - status = self.status_chr - color = self.py3.COLOR_GOOD - else: - status = self.status_bat - color = self.py3.COLOR_DEGRADED - - if not battery["isCharging"] and battery["charge"] <= self.low_threshold: - color = self.py3.COLOR_BAD - - if battery["charge"] > 99: - status = self.status_full + if charge == -1: + self._result["charge"] = UNKNOWN_SYMBOL + self._result["bat_status"] = UNKNOWN + self._result["color"] = "#FFFFFF" + return + + if charge is not None: + self._result["charge"] = charge + + if isCharging is not None: + if isCharging: + self._result["bat_status"] = self.status_chr + self._result["color"] = self.py3.COLOR_GOOD + else: + self._result["bat_status"] = self.status_bat + self._result["color"] = self.py3.COLOR_DEGRADED - return (battery["charge"], status, color) + if ( + not isCharging + and isinstance(self._result["charge"], int) + and self._result["charge"] <= self.low_threshold + ): + self._result["color"] = self.py3.COLOR_BAD + + if charge is not None: + if charge > 99: + self._result["bat_status"] = self.status_full - def _get_notifications_status(self, notifications): + def _set_notifications_status(self, activeNotifications): """ Get the notifications status """ - if notifications: - size = len(notifications["activeNotifications"]) - else: - size = 0 - status = self.status_notif if size > 0 else self.status_no_notif + size = len(activeNotifications) + self._result["notif_status"] = ( + self.status_notif if size > 0 else self.status_no_notif + ) + self._result["notif_size"] = size - return (size, status) + def _set_conn_status(self, net_type, net_strength): + """ + Get the conn status + """ + self._result["net_strength_raw"] = net_strength + self._result["net_strength"] = ( + net_strength * 25 if net_strength > -1 else UNKNOWN_SYMBOL + ) + self._result["net_type"] = net_type def _get_text(self): """ @@ -265,38 +482,68 @@ self.py3.COLOR_BAD, ) - battery = self._get_battery() - (charge, bat_status, color) = self._get_battery_status(battery) - - notif = self._get_notifications() - (notif_size, notif_status) = self._get_notifications_status(notif) - return ( self.py3.safe_format( self.format, - dict( - name=device["name"], - charge=charge, - bat_status=bat_status, - notif_size=notif_size, - notif_status=notif_status, - ), + dict(name=device["name"], **self._result), ), - color, + self._result.get("color"), + ) + + def _update_conn_info(self): + if self._format_contains_connection_status: + conn = self._get_conn() + self._set_conn_status(net_type=conn["type"], net_strength=conn["strength"]) + + def _update_notif_info(self): + if self._format_contains_notifications: + notif = self._get_notifications() + self._set_notifications_status(notif) + + def _update_battery_info(self): + battery = self._get_battery() + self._set_battery_status( + isCharging=battery["isCharging"], charge=battery["charge"] ) + def kill(self): + self._kill = True + if self._signal_reachable_changed: + self._dbus.con.signal_unsubscribe(self._signal_reachable_changed) + + if self._signal_battery: + self._dbus.con.signal_unsubscribe(self._signal_battery) + + if self._signal_notifications: + self._dbus.con.signal_unsubscribe(self._signal_notifications) + + if self._signal_conn_report: + self._dbus.con.signal_unsubscribe(self._signal_conn_report) + def kdeconnector(self): """ Get the current state and return it. """ - if self._init_dbus(): + if self._kill: + raise KeyboardInterrupt + + if self._bus_initialized: (text, color) = self._get_text() + cached_until = self.py3.CACHE_FOREVER + else: text = UNKNOWN_DEVICE color = self.py3.COLOR_BAD + cached_until = self.py3.time_in(self.cache_timeout) + self._bus_initialized = self._init_dbus() + if self._bus_initialized: + self._update_conn_info() + self._update_notif_info() + self._update_battery_info() + self.py3.update() response = { - "cached_until": self.py3.time_in(self.cache_timeout), + "cached_until": cached_until, "full_text": text, "color": color, } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status/modules/mpris.py new/py3status-3.49/py3status/modules/mpris.py --- old/py3status-3.47/py3status/modules/mpris.py 2022-10-02 15:59:23.000000000 +0200 +++ new/py3status-3.49/py3status/modules/mpris.py 2023-01-09 18:39:16.000000000 +0100 @@ -668,7 +668,7 @@ def kill(self): self._kill = True if self._name_owner_change_match: - self.parent._dbus._clean_up_signal_match(self._name_owner_change_match) + self._dbus._clean_up_signal_match(self._name_owner_change_match) def mpris(self): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status/modules/timewarrior.py new/py3status-3.49/py3status/modules/timewarrior.py --- old/py3status-3.47/py3status/modules/timewarrior.py 2021-08-30 10:15:50.000000000 +0200 +++ new/py3status-3.49/py3status/modules/timewarrior.py 2022-11-20 12:12:25.000000000 +0100 @@ -140,7 +140,6 @@ from json import loads as json_loads import datetime as dt -from dateutil.relativedelta import relativedelta STRING_NOT_INSTALLED = "not installed" DATETIME = "%Y%m%dT%H%M%SZ" @@ -244,15 +243,15 @@ end = dt.datetime.strptime(time["end"], DATETIME) start = dt.datetime.strptime(time["start"], DATETIME) - duration = relativedelta(end, start) + duration = end - start time["format_duration"] = self.py3.safe_format( self.format_duration, { "days": duration.days, - "hours": duration.hours, - "minutes": duration.minutes, - "seconds": duration.seconds, + "hours": duration.seconds // (60 * 60), + "minutes": (duration.seconds // 60) % 60, + "seconds": duration.seconds % 60, }, ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status/modules/volume_status.py new/py3status-3.49/py3status/modules/volume_status.py --- old/py3status-3.47/py3status/modules/volume_status.py 2022-10-02 16:25:48.000000000 +0200 +++ new/py3status-3.49/py3status/modules/volume_status.py 2023-01-10 13:37:26.000000000 +0100 @@ -26,10 +26,6 @@ (default False) max_volume: Allow the volume to be increased past 100% if available. pactl and pamixer supports this. (default 120) - start_delay: Number of seconds to wait before starting this module. - This allows some systems to start the audio backend before we - try picking it up. - (default 0) thresholds: Threshold for percent volume. (default [(0, 'bad'), (20, 'degraded'), (50, 'good')]) volume_delta: Percentage amount that the volume is increased or @@ -118,13 +114,13 @@ def setup(self, parent): if self.card is None: self.card = "0" + if self.device is None: + self.device = "default" if self.channel is None: controls = self.parent.py3.command_output( - ["amixer", "scontrols"] + ["amixer", "-c", self.card, "-D", self.device, "scontrols"] ).splitlines() self.channel = controls[-abs(int(self.is_input))].split("'")[1::2][0] - if self.device is None: - self.device = "default" self.cmd = [ "amixer", "-M", @@ -332,7 +328,6 @@ format_muted = r"[\?if=is_input ð¶|âª]: muted" is_input = False max_volume = 120 - start_delay = 0 thresholds = [(0, "bad"), (20, "degraded"), (50, "good")] volume_delta = 5 @@ -358,12 +353,14 @@ "param": "threshold_degraded", "msg": "obsolete set using thresholds parameter", }, + { + "param": "start_delay", + "msg": "obsolete parameter", + }, ], } def post_config_hook(self): - if self.start_delay: - sleep(int(self.start_delay)) if not self.command: commands = ["pamixer", "pactl", "amixer"] # pamixer, pactl requires pulseaudio to work @@ -383,9 +380,21 @@ if self.device is not None: self.device = str(self.device) - self.backend = globals()[self.command.capitalize()](self) + self._init_backend() self.color_muted = self.py3.COLOR_MUTED or self.py3.COLOR_BAD + def _init_backend(self): + # attempt it a few times since the audio service may still be loading during startup + for i in range(6): + try: + self.backend = globals()[self.command.capitalize()](self) + return + except Exception: # noqa e722 + # try again later (exponential backoff) + sleep(0.5 * 2**i) + + self.backend = globals()[self.command.capitalize()](self) + def volume_status(self): perc, muted = self.backend.get_volume() color = None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status/modules/window.py new/py3status-3.49/py3status/modules/window.py --- old/py3status-3.47/py3status/modules/window.py 2022-10-02 16:25:48.000000000 +0200 +++ new/py3status-3.49/py3status/modules/window.py 2022-10-05 17:35:50.000000000 +0200 @@ -45,10 +45,12 @@ def compatibility(self, window_properties): # specify width to truncate title with ellipsis - if self.parent.max_width: - title = window_properties["title"] - if title and len(title) > self.parent.max_width: + title = window_properties.get("title", "") + if title: + if self.parent.max_width and len(title) > self.parent.max_width: window_properties["title"] = title[: self.parent.max_width - 1] + "â¦" + else: + window_properties["title"] = "" return window_properties diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status/version.py new/py3status-3.49/py3status/version.py --- old/py3status-3.47/py3status/version.py 2022-10-02 17:50:36.000000000 +0200 +++ new/py3status-3.49/py3status/version.py 2023-02-17 13:40:42.000000000 +0100 @@ -1 +1 @@ -version = "3.47" +version = "3.49" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status.egg-info/PKG-INFO new/py3status-3.49/py3status.egg-info/PKG-INFO --- old/py3status-3.47/py3status.egg-info/PKG-INFO 2022-10-02 17:54:02.000000000 +0200 +++ new/py3status-3.49/py3status.egg-info/PKG-INFO 2023-02-17 13:41:23.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: py3status -Version: 3.47 +Version: 3.49 Summary: py3status: an extensible i3status wrapper written in python Home-page: https://github.com/ultrabug/py3status Author: Ultrabug diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/py3status.egg-info/SOURCES.txt new/py3status-3.49/py3status.egg-info/SOURCES.txt --- old/py3status-3.47/py3status.egg-info/SOURCES.txt 2022-10-02 17:54:02.000000000 +0200 +++ new/py3status-3.49/py3status.egg-info/SOURCES.txt 2023-02-17 13:41:23.000000000 +0100 @@ -57,7 +57,6 @@ py3status/modules/backlight.py py3status/modules/battery_level.py py3status/modules/bluetooth.py -py3status/modules/bluetooth2.py py3status/modules/check_tcp.py py3status/modules/clementine.py py3status/modules/clock.py @@ -166,5 +165,6 @@ tests/test_consistency.py tests/test_formatter.py tests/test_module_doc.py +tests/test_module_load.py tests/test_py3.py tests/test_user_modules.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/tests/test_module_load.py new/py3status-3.49/tests/test_module_load.py --- old/py3status-3.47/tests/test_module_load.py 1970-01-01 01:00:00.000000000 +0100 +++ new/py3status-3.49/tests/test_module_load.py 2023-01-09 18:39:16.000000000 +0100 @@ -0,0 +1,45 @@ +from py3status.module_test import MockPy3statusWrapper +from py3status.module import Module + + +class TestModule: + static_variable = 123 + + def __init__(self): + self.instance_variable = 321 + + def post_config_hook(self): + pass + + @staticmethod + def static_method(self): + raise Exception("I don't want to be called!") + + @property + def property(self): + raise Exception("I don't want to be called!") + + def instance_method(self): + raise Exception("I don't want to be called!") + + def _private_instance_method(self): + raise Exception("I don't want to be called!") + + def on_click(self, event): + raise Exception("I don't want to be called!") + + +def test_module_load(): + mock = MockPy3statusWrapper( + { + "general": {}, + "py3status": {}, + ".module_groups": {}, + "test_module": {}, + } + ) + + module = TestModule() + m = Module("test_module", {}, mock, module) + m.prepare_module() + assert list(m.methods) == ["instance_method"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/py3status-3.47/tox.ini new/py3status-3.49/tox.ini --- old/py3status-3.47/tox.ini 2022-08-25 16:23:38.000000000 +0200 +++ new/py3status-3.49/tox.ini 2022-12-12 08:07:52.000000000 +0100 @@ -1,5 +1,5 @@ [tox] -envlist = py37, py38, py39, py310, mypy +envlist = py37, py38, py39, py310, py311, mypy skip_missing_interpreters = True [testenv] @@ -29,6 +29,7 @@ [gh-actions] python = 3.7: py37 - 3.8: py38, mypy - 3.9: py39 + 3.8: py38 + 3.9: py39, mypy 3.10: py310 + 3.11: py311