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

Reply via email to