Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package s-tui for openSUSE:Factory checked 
in at 2026-01-13 21:27:01
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/s-tui (Old)
 and      /work/SRC/openSUSE:Factory/.s-tui.new.1928 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "s-tui"

Tue Jan 13 21:27:01 2026 rev:10 rq:1326816 version:1.3.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/s-tui/s-tui.changes      2024-02-20 
21:14:37.946455297 +0100
+++ /work/SRC/openSUSE:Factory/.s-tui.new.1928/s-tui.changes    2026-01-13 
21:27:38.730808544 +0100
@@ -1,0 +2,16 @@
+Mon Jan 12 17:29:09 UTC 2026 - Martin Hauke <[email protected]>
+
+- Update to version 1.3.0
+  * A fix for #246 which as caused by a breaking change in urwid.
+  * Add a new feature, changing the colors of temperatures graphs
+    only if the specific sensor reaches alert temperature, instead
+    of all temperature graphs.
+  * load_config: do not treat ':' as delimiter.
+  * Fix unsucceful import of curses_display.
+  * display alert colors only for affected graph.
+  * Consier manually set temperature threshold for color change.
+- Update to version 1.2.0
+  * Add check/uncheck all buttons for sensor and graph menus.
+  * Some minor bug fixes and version bumps.
+
+-------------------------------------------------------------------

Old:
----
  s-tui-1.1.6.tar.gz

New:
----
  s-tui-1.3.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ s-tui.spec ++++++
--- /var/tmp/diff_new_pack.87gRFC/_old  2026-01-13 21:27:39.238829506 +0100
+++ /var/tmp/diff_new_pack.87gRFC/_new  2026-01-13 21:27:39.242829671 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package s-tui
 #
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
 # Copyright (c) 2019 Malcolm J Lewis <[email protected]>
 #
 # All modifications and additions to the file contributed by third parties
@@ -18,7 +18,7 @@
 
 
 Name:           s-tui
-Version:        1.1.6
+Version:        1.3.0
 Release:        0
 Summary:        Terminal based CPU stress and monitoring utility
 License:        GPL-2.0-or-later

++++++ s-tui-1.1.6.tar.gz -> s-tui-1.3.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/.github/workflows/pythonpackage.yml 
new/s-tui-1.3.0/.github/workflows/pythonpackage.yml
--- old/s-tui-1.1.6/.github/workflows/pythonpackage.yml 2024-01-28 
04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/.github/workflows/pythonpackage.yml 2026-01-12 
13:28:46.000000000 +0100
@@ -1,6 +1,8 @@
 name: Python package
 
-on: [push]
+on:
+  push:
+  pull_request:
 
 jobs:
   build:
@@ -9,7 +11,7 @@
     strategy:
       max-parallel: 4
       matrix:
-        python-version: [3.8, 3.9, '3.10', 3.11]
+        python-version: [3.9, '3.10', 3.11, 3.12, 3.13]
 
     steps:
     - uses: actions/checkout@v1
@@ -20,7 +22,7 @@
     - name: Install dependencies
       run: |
         python -m pip install --upgrade pip
-        python setup.py install
+        pip install .
     - name: Lint with flake8
       run: |
         pip install flake8
@@ -28,8 +30,17 @@
         flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
         # exit-zero treats all errors as warnings. The GitHub editor is 127 
chars wide
         flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 
--statistics
+
+    - name: Run Black -- check
+      run: |
+        pip install black==25.1.0
+        # check that code is formatted with black
+        black --check .
+
     - name: Test with pytest
       run: |
         pip install pytest
         pytest
         python -m s_tui.s_tui -j
+
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/.gitignore new/s-tui-1.3.0/.gitignore
--- old/s-tui-1.1.6/.gitignore  2024-01-28 04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/.gitignore  2026-01-12 13:28:46.000000000 +0100
@@ -23,5 +23,5 @@
 pyenv*
 *.loop
 *.spec
-venv/
-.venv/
+venv*/
+.venv*/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/CONTRIBUTING.md 
new/s-tui-1.3.0/CONTRIBUTING.md
--- old/s-tui-1.1.6/CONTRIBUTING.md     2024-01-28 04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/CONTRIBUTING.md     2026-01-12 13:28:46.000000000 +0100
@@ -1,8 +1,9 @@
 1. Fork the `s-tui` repository. Work on your fork.
 2. Create a new branch on which to make your change, e.g. `git checkout -b 
my_feature`.
-3. Please work on one feature at a time, to make it easy to test and discuss 
pull requests. 
-4. Commit your change. Include a commit message describing you contribution.
-5. Submit a pull request to the main repository.
+3. Please work on one feature at a time, to make it easy to test and discuss 
pull requests.
+4. Format your code with [black](https://github.com/psf/black) before 
committing.
+5. Commit your change. Include a commit message describing you contribution.
+6. Submit a pull request to the main repository.
 
 This repository follows PEP8 coding conventions (or at least makes an effort 
to do so)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/Makefile new/s-tui-1.3.0/Makefile
--- old/s-tui-1.1.6/Makefile    2024-01-28 04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/Makefile    2026-01-12 13:28:46.000000000 +0100
@@ -1,7 +1,7 @@
 # A make file for creating a s-tui executable.
 # This requires pandoc and pyinstaller
 
-all: stui del readme
+all: stui del
 
 # Create s-tui executable
 stui:
@@ -16,3 +16,6 @@
 clean:
        pyinstaller --clean s-tui
        rm -rf ./s_tui/dist/ ./s_tui/build/ ./s_tui/s*.spec ./s_tui/*.pyc 
./s_tui/*.log s-tui.spec dist/
+
+debug:
+       python -m s_tui.s_tui
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/README.md new/s-tui-1.3.0/README.md
--- old/s-tui-1.1.6/README.md   2024-01-28 04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/README.md   2026-01-12 13:28:46.000000000 +0100
@@ -22,6 +22,7 @@
   - [More installation methods](#more-installation-methods)
     - [Ubuntu (18.10 and newer)](#ubuntu-1810-and-newer)
     - [Ubuntu (18.04, 16.04)](#ubuntu-1804-1604)
+    - [Debian (10 and newer)](#debian-10-and-newer)
     - [Arch Linux, Manjaro](#arch-linux-manjaro)
     - [OpenSUSE](#opensuse)
     - [Fedora](#fedora)
@@ -96,6 +97,13 @@
 sudo apt-get install python3-s-tui
 ```
 
+### Debian (10 and newer)
+
+```
+sudo apt install s-tui
+```
+
+
 ### Arch Linux, Manjaro
 
 `s-tui` is in the Arch repository:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/about_menu.py 
new/s-tui-1.3.0/s_tui/about_menu.py
--- old/s-tui-1.1.6/s_tui/about_menu.py 2024-01-28 04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/s_tui/about_menu.py 2026-01-12 13:28:46.000000000 +0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2017-2020 Alex Manuskin, Gil Tsuker
+# Copyright (C) 2017-2025 Alex Manuskin, Gil Tsuker
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -15,7 +15,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-"""Displays the About message menu """
+"""Displays the About message menu"""
 
 from __future__ import print_function
 from __future__ import absolute_import
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/help_menu.py 
new/s-tui-1.3.0/s_tui/help_menu.py
--- old/s-tui-1.1.6/s_tui/help_menu.py  2024-01-28 04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/s_tui/help_menu.py  2026-01-12 13:28:46.000000000 +0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2017-2020 Alex Manuskin, Gil Tsuker
+# Copyright (C) 2017-2025 Alex Manuskin, Gil Tsuker
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -16,8 +16,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 
-"""A class display the help message menu
-"""
+"""A class display the help message menu"""
 
 from __future__ import print_function
 from __future__ import absolute_import
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/helper_functions.py 
new/s-tui-1.3.0/s_tui/helper_functions.py
--- old/s-tui-1.1.6/s_tui/helper_functions.py   2024-01-28 04:24:31.000000000 
+0100
+++ new/s-tui-1.3.0/s_tui/helper_functions.py   2026-01-12 13:28:46.000000000 
+0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2017-2020 Alex Manuskin, Gil Tsuker
+# Copyright (C) 2017-2025 Alex Manuskin, Gil Tsuker
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -15,7 +15,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-""" Helper functions module with common useful functions """
+"""Helper functions module with common useful functions"""
 
 
 import os
@@ -30,7 +30,7 @@
 
 from collections import OrderedDict
 
-__version__ = "1.1.6"
+__version__ = "1.3.0"
 
 _DEFAULT = object()
 PY3 = sys.version_info[0] == 3
@@ -52,7 +52,7 @@
             all_info = cpuinfo.readlines()
             for line in all_info:
                 if b"model name" in line:
-                    return re.sub(b".*model name.*:", b"", line, 1)
+                    return re.sub(b".*model name.*:", b"", line, count=1)
     elif platform.system() == "FreeBSD":
         cmd = ["sysctl", "-n", "hw.model"]
         process = subprocess.Popen(
@@ -139,45 +139,33 @@
     sys.exit()
 
 
-def get_user_config_dir():
-    """
-    Return the path to the user s-tui config directory
-    """
+def _get_xdg_config_home():
+    """Return the XDG config home directory, with fallback to ~/.config"""
     user_home = os.getenv("XDG_CONFIG_HOME")
-    if user_home is None or not user_home:
-        config_path = os.path.expanduser(os.path.join("~", ".config", "s-tui"))
-    else:
-        config_path = os.path.join(user_home, "s-tui")
-
-    return config_path
+    if user_home:
+        return user_home
+    return os.path.expanduser(os.path.join("~", ".config"))
 
 
 def get_config_dir():
     """
     Return the path to the user home config directory
     """
-    user_home = os.getenv("XDG_CONFIG_HOME")
-    if user_home is None or not user_home:
-        config_path = os.path.expanduser(os.path.join("~", ".config"))
-    else:
-        config_path = user_home
+    return _get_xdg_config_home()
 
-    return config_path
 
-
-def get_user_config_file():
+def get_user_config_dir():
     """
     Return the path to the user s-tui config directory
     """
-    user_home = os.getenv("XDG_CONFIG_HOME")
-    if user_home is None or not user_home:
-        config_path = os.path.expanduser(
-            os.path.join("~", ".config", "s-tui", "s-tui.conf")
-        )
-    else:
-        config_path = os.path.join(user_home, "s-tui", "s-tui.conf")
+    return os.path.join(_get_xdg_config_home(), "s-tui")
+
 
-    return config_path
+def get_user_config_file():
+    """
+    Return the path to the user s-tui config file
+    """
+    return os.path.join(get_user_config_dir(), "s-tui.conf")
 
 
 def user_config_dir_exists():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/s_tui.py 
new/s-tui-1.3.0/s_tui/s_tui.py
--- old/s-tui-1.1.6/s_tui/s_tui.py      2024-01-28 04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/s_tui/s_tui.py      2026-01-12 13:28:46.000000000 +0100
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 
-# Copyright (C) 2017-2020 Alex Manuskin, Gil Tsuker
+# Copyright (C) 2017-2025 Alex Manuskin, Gil Tsuker
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -24,7 +24,6 @@
 
 import argparse
 import signal
-import itertools
 import logging
 import os
 import subprocess
@@ -36,7 +35,6 @@
 
 import psutil
 import urwid
-import urwid.curses_display
 
 try:
     import configparser
@@ -94,7 +92,7 @@
 VERSION_MESSAGE = (
     "s-tui "
     + __version__
-    + " - (C) 2017-2020 Alex Manuskin, Gil Tsuker\n\
+    + " - (C) 2017-2025 Alex Manuskin, Gil Tsuker\n\
     Released under GNU GPLv2"
 )
 
@@ -265,7 +263,6 @@
                 graph.update()
             except IndexError:
                 logging.debug("Graph update failed")
-                pass
 
         # update graph summery
         for summary in self.visible_summaries.values():
@@ -273,7 +270,6 @@
                 summary.update()
             except IndexError:
                 logging.debug("Summary update failed")
-                pass
 
         # Only update clock if not is stress mode
         if self.controller.stress_controller.get_current_mode() != "Monitor":
@@ -308,14 +304,11 @@
         if update:
             for sensor, visible_sensors in 
self.graphs_menu.active_sensors.items():
                 self.graphs[sensor].set_visible_graphs(visible_sensors)
-                # If not sensor is selected, do not display the graph
-                if sensor in self.visible_graphs and not any(visible_sensors):
-                    del self.visible_graphs[sensor]
-                elif not any(visible_sensors):
-                    pass
-                # Update visible graphs if a sensor was selected
-                else:
+                # Update visible_graphs based on sensor selection
+                if any(visible_sensors):
                     self.visible_graphs[sensor] = self.graphs[sensor]
+                elif sensor in self.visible_graphs:
+                    del self.visible_graphs[sensor]
             self.show_graphs()
 
         self.original_widget = self.main_window_w
@@ -334,60 +327,37 @@
 
         self.original_widget = self.main_window_w
 
-    def on_stress_menu_open(self, widget):
-        """Open stress options"""
+    def _open_menu_overlay(self, menu):
+        """Helper to open a menu overlay with cached size"""
+        height, width = menu.get_size()
         self.original_widget = urwid.Overlay(
-            self.stress_menu.main_window,
+            menu.main_window,
             self.original_widget,
             ("fixed left", 1),
-            self.stress_menu.get_size()[1],
+            width,
             "top",
-            self.stress_menu.get_size()[0],
+            height,
         )
 
+    def on_stress_menu_open(self, widget):
+        """Open stress options"""
+        self._open_menu_overlay(self.stress_menu)
+
     def on_help_menu_open(self, widget):
         """Open Help menu"""
-        self.original_widget = urwid.Overlay(
-            self.help_menu.main_window,
-            self.original_widget,
-            ("fixed left", 1),
-            self.help_menu.get_size()[1],
-            "top",
-            self.help_menu.get_size()[0],
-        )
+        self._open_menu_overlay(self.help_menu)
 
     def on_about_menu_open(self, widget):
         """Open About menu"""
-        self.original_widget = urwid.Overlay(
-            self.about_menu.main_window,
-            self.original_widget,
-            ("fixed left", 1),
-            self.about_menu.get_size()[1],
-            "top",
-            self.about_menu.get_size()[0],
-        )
+        self._open_menu_overlay(self.about_menu)
 
     def on_graphs_menu_open(self, widget):
         """Open Sensor menu on top of existing frame"""
-        self.original_widget = urwid.Overlay(
-            self.graphs_menu.main_window,
-            self.original_widget,
-            ("fixed left", 1),
-            self.graphs_menu.get_size()[1],
-            "top",
-            self.graphs_menu.get_size()[0],
-        )
+        self._open_menu_overlay(self.graphs_menu)
 
     def on_summary_menu_open(self, widget):
         """Open Sensor menu on top of existing frame"""
-        self.original_widget = urwid.Overlay(
-            self.summary_menu.main_window,
-            self.original_widget,
-            ("fixed left", 1),
-            self.summary_menu.get_size()[1],
-            "top",
-            self.summary_menu.get_size()[0],
-        )
+        self._open_menu_overlay(self.summary_menu)
 
     def on_mode_button(self, my_button, state):
         """Notify the controller of a new mode setting."""
@@ -515,10 +485,9 @@
 
     def show_graphs(self):
         """Show a pile of the graph selected for display"""
-        elements = itertools.chain.from_iterable(
-            ([graph] for graph in self.visible_graphs.values())
+        self.graph_place_holder.original_widget = urwid.Pile(
+            list(self.visible_graphs.values())
         )
-        self.graph_place_holder.original_widget = urwid.Pile(elements)
 
     def main_window(self):
         # initiating the graphs
@@ -548,18 +517,14 @@
                 self.summary_menu.active_sensors[source_name],
             )
 
-        # Check if source is available and add it to a dict of visible graphs
-        # and summaries.
-        # All available summaries are always visible
+        # Check if source is available and has selected sensors
+        # Combine availability check and sensor selection in one pass
         self.visible_graphs = OrderedDict(
-            (key, val) for key, val in self.graphs.items() if 
val.get_is_available()
+            (key, val)
+            for key, val in self.graphs.items()
+            if val.get_is_available() and 
any(self.graphs_menu.active_sensors[key])
         )
 
-        # Do not show the graph if there is no selected sensors
-        for key in self.graphs.keys():
-            if not any(self.graphs_menu.active_sensors[key]):
-                del self.visible_graphs[key]
-
         self.visible_summaries = OrderedDict(
             (key, val) for key, val in self.summaries.items() if 
val.get_is_available()
         )
@@ -634,7 +599,7 @@
         # Use user config file if one was saved before
         self.conf = None
         if user_config_file_exists():
-            self.conf = configparser.ConfigParser()
+            self.conf = configparser.ConfigParser(delimiters="=")
             self.conf.read(get_user_config_file())
         else:
             logging.debug("Config file not found")
@@ -838,6 +803,8 @@
 
     def save_settings(self):
         """Save the current configuration to a user config file"""
+        # Build source lookup dict once for O(1) access
+        sources_by_name = {s.get_source_name(): s for s in self.sources}
 
         def _save_displayed_setting(conf, submenu):
             items = []
@@ -850,11 +817,11 @@
                 section = source + "," + submenu
                 conf.add_section(section)
 
-                sources = self.sources
                 logging.debug("Saving settings for %s", source)
                 logging.debug("Visible sensors %s", visible_sensors)
-                # TODO: consider changing sensors_list to dict
-                curr_sensor = [x for x in sources if x.get_source_name() == 
source][0]
+                curr_sensor = sources_by_name.get(source)
+                if curr_sensor is None:
+                    continue
                 sensor_list = curr_sensor.get_sensor_list()
                 for sensor_id, sensor in enumerate(sensor_list):
                     try:
@@ -920,25 +887,22 @@
     log_file = DEFAULT_LOG_FILE
     if args.debug_run:
         args.debug = True
+
+    log_formatter = logging.Formatter(
+        "%(asctime)s [%(funcName)s()] [%(levelname)-5.5s]  %(message)s"
+    )
+    root_logger = logging.getLogger()
+
     if args.debug or args.debug_file is not None:
         level = logging.DEBUG
         if args.debug_file is not None:
             log_file = args.debug_file
-        log_formatter = logging.Formatter(
-            "%(asctime)s [%(funcName)s()] [%(levelname)-5.5s]  %(message)s"
-        )
-        root_logger = logging.getLogger()
         file_handler = logging.FileHandler(log_file)
         file_handler.setFormatter(log_formatter)
         root_logger.addHandler(file_handler)
-        root_logger.setLevel(level)
     else:
         level = logging.ERROR
-        log_formatter = logging.Formatter(
-            "%(asctime)s [%(funcName)s()] [%(levelname)-5.5s]  %(message)s"
-        )
-        root_logger = logging.getLogger()
-        root_logger.setLevel(level)
+    root_logger.setLevel(level)
 
     if args.terminal or args.json:
         logging.info("Printing single line to terminal")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/sensors_menu.py 
new/s-tui-1.3.0/s_tui/sensors_menu.py
--- old/s-tui-1.1.6/s_tui/sensors_menu.py       2024-01-28 04:24:31.000000000 
+0100
+++ new/s-tui-1.3.0/s_tui/sensors_menu.py       2026-01-12 13:28:46.000000000 
+0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2017-2020 Alex Manuskin, Gil Tsuker
+# Copyright (C) 2017-2025 Alex Manuskin, Gil Tsuker
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -42,11 +42,11 @@
         cancel_button._label.align = "center"
         apply_button = urwid.Button("Apply", on_press=self.on_apply)
         apply_button._label.align = "center"
-
         if_buttons = urwid.Columns([apply_button, cancel_button])
 
         self.sensor_status_dict = {}
         sensor_column_list = []
+        selector_buttons_column_list = []
         self.sensor_button_dict = {}
         self.active_sensors = {}
         for source in source_list:
@@ -71,23 +71,47 @@
             sensor_title = urwid.Text(("bold text", sensor_title_str), 
"center")
 
             # create the checkbox buttons with the saved visibility
+            current_col_nr = 0
             for sensor, s_tatus in zip(
                 source.get_sensor_list(), self.sensor_status_dict[source_name]
             ):
                 cb = urwid.CheckBox(sensor, s_tatus)
                 self.sensor_button_dict[source_name].append(cb)
                 self.active_sensors[source_name].append(s_tatus)
+                current_col_nr += 1
+
+            col_selector_buttons = []
+            if current_col_nr > 0:
+                checkall_button = urwid.Button(
+                    "Check all",
+                    on_press=self.on_checkall_col,
+                    user_data=sensor_title_str,
+                )
+                # checkall_button._label.align = "center"
+                col_selector_buttons.append(checkall_button)
+                uncheckall_button = urwid.Button(
+                    "Uncheck all",
+                    on_press=self.on_uncheckall_col,
+                    user_data=sensor_title_str,
+                )
+                # uncheckall_button._label.align = "center"
+                col_selector_buttons.append(uncheckall_button)
 
             sensor_title_and_buttons = [sensor_title] + 
self.sensor_button_dict[
                 source_name
             ]
             listw = urwid.SimpleFocusListWalker(sensor_title_and_buttons)
-
             sensor_column_list.append(urwid.Pile(listw))
 
+            listw = urwid.SimpleFocusListWalker(col_selector_buttons)
+            selector_buttons_column_list.append(urwid.Pile(listw))
+
         sensor_select_widget = urwid.Columns(sensor_column_list)
+        selector_buttons_widget = urwid.Columns(
+            selector_buttons_column_list, box_columns=[0, 1]
+        )
 
-        list_temp = [sensor_select_widget, if_buttons]
+        list_temp = [sensor_select_widget, selector_buttons_widget, if_buttons]
         listw = urwid.SimpleFocusListWalker(list_temp)
         self.main_window = urwid.LineBox(ViListBox(listw))
 
@@ -113,14 +137,26 @@
     def on_apply(self, w):
         update_sensor_visibility = False
         for s_name, sensor_buttons in self.sensor_button_dict.items():
-            cb_sensor_visibility = []
-            for sensor_cb in sensor_buttons:
-                cb_sensor_visibility.append(sensor_cb.get_state())
+            cb_sensor_visibility = [
+                sensor_cb.get_state() for sensor_cb in sensor_buttons
+            ]
 
-                changed_state = cb_sensor_visibility != 
self.active_sensors[s_name]
-                update_sensor_visibility |= changed_state
+            if cb_sensor_visibility != self.active_sensors[s_name]:
+                update_sensor_visibility = True
 
             self.active_sensors[s_name] = cb_sensor_visibility
 
         self.set_checkbox_value()
         self.return_fn(update=update_sensor_visibility)
+
+    def setall_cb_col(self, w, col, state):
+        for s_name, sensor_buttons in self.sensor_button_dict.items():
+            if s_name == col:
+                for checkbox in sensor_buttons:
+                    checkbox.set_state(state)
+
+    def on_uncheckall_col(self, w, col):
+        self.setall_cb_col(self, col, False)
+
+    def on_checkall_col(self, w, col):
+        self.setall_cb_col(self, col, True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/sources/fan_source.py 
new/s-tui-1.3.0/s_tui/sources/fan_source.py
--- old/s-tui-1.1.6/s_tui/sources/fan_source.py 2024-01-28 04:24:31.000000000 
+0100
+++ new/s-tui-1.3.0/s_tui/sources/fan_source.py 2026-01-12 13:28:46.000000000 
+0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Copyright (C) 2017-2020 Alex Manuskin, Maor Veitsman
+# Copyright (C) 2017-2025 Alex Manuskin, Maor Veitsman
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -15,7 +15,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-""" This module implements a fan source """
+"""This module implements a fan source"""
 
 
 from __future__ import absolute_import
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/sources/freq_source.py 
new/s-tui-1.3.0/s_tui/sources/freq_source.py
--- old/s-tui-1.1.6/s_tui/sources/freq_source.py        2024-01-28 
04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/s_tui/sources/freq_source.py        2026-01-12 
13:28:46.000000000 +0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Copyright (C) 2017-2020 Alex Manuskin, Maor Veitsman
+# Copyright (C) 2017-2025 Alex Manuskin, Maor Veitsman
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -45,13 +45,16 @@
             "freq dark smooth",
         )
 
-        # Check if psutil.cpu_freq is available.
+        # Get per-cpu and overall freq in minimal calls
+        per_cpu_freq = psutil.cpu_freq(True)
+        overall_freq = psutil.cpu_freq(False)
+
         # +1 for average frequency
-        self.last_measurement = [0] * len(psutil.cpu_freq(True))
-        if psutil.cpu_freq(False):
+        self.last_measurement = [0] * len(per_cpu_freq)
+        if overall_freq:
             self.last_measurement.append(0)
 
-        self.top_freq = psutil.cpu_freq().max
+        self.top_freq = overall_freq.max if overall_freq else 0.0
         self.max_freq = self.top_freq
 
         if self.top_freq == 0.0:
@@ -60,12 +63,20 @@
                 self.max_freq = max(self.last_measurement)
 
         self.available_sensors = ["Avg"]
-        for core_id, _ in enumerate(psutil.cpu_freq(True)):
+        for core_id in range(len(per_cpu_freq)):
             self.available_sensors.append("Core " + str(core_id))
 
     def update(self):
-        self.last_measurement = [psutil.cpu_freq(False).current]
-        for core in psutil.cpu_freq(True):
+        # Get per-cpu frequencies in a single call
+        per_cpu_freq = psutil.cpu_freq(True)
+        # Compute average from per-CPU values
+        avg_freq = (
+            sum(core.current for core in per_cpu_freq) / len(per_cpu_freq)
+            if per_cpu_freq
+            else 0.0
+        )
+        self.last_measurement = [avg_freq]
+        for core in per_cpu_freq:
             self.last_measurement.append(core.current)
 
     def get_maximum(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/sources/hook.py 
new/s-tui-1.3.0/s_tui/sources/hook.py
--- old/s-tui-1.1.6/s_tui/sources/hook.py       2024-01-28 04:24:31.000000000 
+0100
+++ new/s-tui-1.3.0/s_tui/sources/hook.py       2026-01-12 13:28:46.000000000 
+0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2017-2020 Alex Manuskin, Gil Tsuker
+# Copyright (C) 2017-2025 Alex Manuskin, Gil Tsuker
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/sources/hook_script.py 
new/s-tui-1.3.0/s_tui/sources/hook_script.py
--- old/s-tui-1.1.6/s_tui/sources/hook_script.py        2024-01-28 
04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/s_tui/sources/hook_script.py        2026-01-12 
13:28:46.000000000 +0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2017-2020 Alex Manuskin, Gil Tsuker
+# Copyright (C) 2017-2025 Alex Manuskin, Gil Tsuker
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/sources/rapl_power_source.py 
new/s-tui-1.3.0/s_tui/sources/rapl_power_source.py
--- old/s-tui-1.1.6/s_tui/sources/rapl_power_source.py  2024-01-28 
04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/s_tui/sources/rapl_power_source.py  2026-01-12 
13:28:46.000000000 +0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Copyright (C) 2017-2020 Alex Manuskin, Maor Veitsman
+# Copyright (C) 2017-2025 Alex Manuskin, Maor Veitsman
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -15,7 +15,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-""" RaplPowerSource is a s-tui Source, used to gather power usage
+"""RaplPowerSource is a s-tui Source, used to gather power usage
 information
 """
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/sources/rapl_read.py 
new/s-tui-1.3.0/s_tui/sources/rapl_read.py
--- old/s-tui-1.1.6/s_tui/sources/rapl_read.py  2024-01-28 04:24:31.000000000 
+0100
+++ new/s-tui-1.3.0/s_tui/sources/rapl_read.py  2026-01-12 13:28:46.000000000 
+0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Copyright (C) 2017-2020 Alex Manuskin, Maor Veitsman
+# Copyright (C) 2017-2025 Alex Manuskin, Maor Veitsman
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -15,7 +15,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-""" This module reads intel power measurements"""
+"""This module reads intel power measurements"""
 
 from __future__ import absolute_import
 
@@ -46,7 +46,7 @@
 class RaplReader:
     def __init__(self):
         basenames = glob.glob("/sys/class/powercap/intel-rapl:*/")
-        self.basenames = sorted(set({x for x in basenames}))
+        self.basenames = sorted(set(basenames))
 
     def read_power(self):
         """Read power stats and return dictionary"""
@@ -153,9 +153,12 @@
 
     def read_power(self):
         ret = []
+        # Read UNIT_MSR once - it's the same for all cores/packages
+        first_msr_file = next(iter(self.package_msr_files.values()))
+        unit_msr = self.read_msr(first_msr_file, UNIT_MSR)
+        energy_factor = 0.5 ** ((unit_msr & ENERGY_UNIT_MASK) >> 8)
+
         for i, filename in self.package_msr_files.items():
-            unit_msr = self.read_msr(filename, UNIT_MSR)
-            energy_factor = 0.5 ** ((unit_msr & ENERGY_UNIT_MASK) >> 8)
             package_msr = self.read_msr(filename, PACKAGE_MSR)
             ret.append(
                 RaplStats(
@@ -166,8 +169,6 @@
             )
 
         for i, filename in self.core_msr_files.items():
-            unit_msr = self.read_msr(filename, UNIT_MSR)
-            energy_factor = 0.5 ** ((unit_msr & ENERGY_UNIT_MASK) >> 8)
             core_msr = self.read_msr(filename, CORE_MSR)
             ret.append(
                 RaplStats(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/sources/script_hook_loader.py 
new/s-tui-1.3.0/s_tui/sources/script_hook_loader.py
--- old/s-tui-1.1.6/s_tui/sources/script_hook_loader.py 2024-01-28 
04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/s_tui/sources/script_hook_loader.py 2026-01-12 
13:28:46.000000000 +0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2017-2020 Alex Manuskin, Gil Tsuker
+# Copyright (C) 2017-2025 Alex Manuskin, Gil Tsuker
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/sources/source.py 
new/s-tui-1.3.0/s_tui/sources/source.py
--- old/s-tui-1.1.6/s_tui/sources/source.py     2024-01-28 04:24:31.000000000 
+0100
+++ new/s-tui-1.3.0/s_tui/sources/source.py     2026-01-12 13:28:46.000000000 
+0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2017-2020 Alex Manuskin, Gil Tsuker
+# Copyright (C) 2017-2025 Alex Manuskin, Gil Tsuker
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -15,7 +15,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-""" This module implements a parent source class for s-tui"""
+"""This module implements a parent source class for s-tui"""
 
 from collections import OrderedDict
 import logging
@@ -28,6 +28,7 @@
         self.edge_hooks = []
         self.measurement_unit = ""
         self.last_measurement = []
+        self.last_thresholds = []
         self.is_available = True
         self.available_sensors = []
         self.name = ""
@@ -105,6 +106,10 @@
         """Returns a list of the last measurement"""
         return self.last_measurement
 
+    def get_threshold_list(self):
+        """Returns a list of the last threshold values"""
+        return self.last_thresholds
+
     def add_edge_hook(self, hook):
         """
         Add hook to be triggered when the threshold of this Source is surpassed
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/sources/temp_source.py 
new/s-tui-1.3.0/s_tui/sources/temp_source.py
--- old/s-tui-1.1.6/s_tui/sources/temp_source.py        2024-01-28 
04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/s_tui/sources/temp_source.py        2026-01-12 
13:28:46.000000000 +0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Copyright (C) 2017-2020 Alex Manuskin, Gil Tzuker, Maor Veitsman
+# Copyright (C) 2017-2025 Alex Manuskin, Gil Tzuker, Maor Veitsman
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -15,7 +15,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-""" This module implements a Temperature source """
+"""This module implements a Temperature source"""
 
 from __future__ import absolute_import
 
@@ -50,6 +50,7 @@
         self.name = "Temp"
         self.measurement_unit = "C"
         self.max_last_temp = 0
+        self.temp_thresh_is_set = False
         self.pallet = (
             "temp light",
             "temp dark",
@@ -96,18 +97,32 @@
         # Set temperature threshold if a custom one is set
         self.temp_thresh = self.THRESHOLD_TEMP
         if temp_thresh is not None:
+            self.temp_thresh_is_set = True
             if int(temp_thresh) > 0:
                 self.temp_thresh = int(temp_thresh)
                 logging.debug("Updated custom threshold to %s", 
self.temp_thresh)
 
+        # Initialize individual thresholds
+        self.last_thresholds = [self.temp_thresh] * len(self.available_sensors)
+
     def update(self):
         sample = OrderedDict(sorted(psutil.sensors_temperatures().items()))
         self.last_measurement = []
+        self.last_thresholds = []
         for sensor in sample:
             for minor_sensor in sample[sensor]:
                 if minor_sensor.current <= 1.0 or minor_sensor.current >= 
127.0:
                     continue
                 self.last_measurement.append(minor_sensor.current)
+                if (
+                    minor_sensor.high is not None
+                    and minor_sensor.high
+                    and minor_sensor.high < 127.0
+                    and self.temp_thresh_is_set is False
+                ):
+                    self.last_thresholds.append(minor_sensor.high)
+                else:
+                    self.last_thresholds.append(self.temp_thresh)
 
         if self.last_measurement:
             self.max_last_temp = max(self.last_measurement)
@@ -128,6 +143,10 @@
         raise NotImplementedError("Get maximum is not implemented")
 
     def get_top(self):
+        # Cache the top temperature after first calculation
+        if hasattr(self, "_cached_top_temp"):
+            return self._cached_top_temp
+
         top_temp = 10
         available_temps = psutil.sensors_temperatures().values()
         for temp in available_temps:
@@ -137,4 +156,5 @@
                         top_temp = temp_minor.critical
                 except TypeError:
                     continue
-        return min(top_temp, 99)
+        self._cached_top_temp = min(top_temp, 99)
+        return self._cached_top_temp
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/sources/util_source.py 
new/s-tui-1.3.0/s_tui/sources/util_source.py
--- old/s-tui-1.1.6/s_tui/sources/util_source.py        2024-01-28 
04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/s_tui/sources/util_source.py        2026-01-12 
13:28:46.000000000 +0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Copyright (C) 2017-2020 Alex Manuskin, Maor Veitsman
+# Copyright (C) 2017-2025 Alex Manuskin, Maor Veitsman
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -49,8 +49,12 @@
             self.available_sensors.append("Core " + str(core_id))
 
     def update(self):
-        self.last_measurement = [psutil.cpu_percent(interval=0.0, 
percpu=False)]
-        for util in psutil.cpu_percent(interval=0.0, percpu=True):
+        # Get per-CPU utilization in a single call
+        per_cpu_util = psutil.cpu_percent(interval=0.0, percpu=True)
+        # Compute average from per-CPU values instead of making a second call
+        avg_util = sum(per_cpu_util) / len(per_cpu_util) if per_cpu_util else 
0.0
+        self.last_measurement = [avg_util]
+        for util in per_cpu_util:
             logging.info("Core id util %s", util)
             self.last_measurement.append(float(util))
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/stress_menu.py 
new/s-tui-1.3.0/s_tui/stress_menu.py
--- old/s-tui-1.1.6/s_tui/stress_menu.py        2024-01-28 04:24:31.000000000 
+0100
+++ new/s-tui-1.3.0/s_tui/stress_menu.py        2026-01-12 13:28:46.000000000 
+0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2017-2020 Alex Manuskin, Gil Tsuker
+# Copyright (C) 2017-2025 Alex Manuskin, Gil Tsuker
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -16,8 +16,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 
-"""A class to control the options of stress in a menu
-"""
+"""A class to control the options of stress in a menu"""
 
 from __future__ import print_function
 import re
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/sturwid/bar_graph_vector.py 
new/s-tui-1.3.0/s_tui/sturwid/bar_graph_vector.py
--- old/s-tui-1.1.6/s_tui/sturwid/bar_graph_vector.py   2024-01-28 
04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/s_tui/sturwid/bar_graph_vector.py   2026-01-12 
13:28:46.000000000 +0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Copyright (C) 2017-2020 Alex Manuskin, Gil Tsuker
+# Copyright (C) 2017-2025 Alex Manuskin, Gil Tsuker
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -32,7 +32,7 @@
         values.append(new_val)
         return values[1:]
 
-    MAX_SAMPLES = 300
+    MAX_SAMPLES = 1000
     SCALE_DENSITY = 5
 
     def __init__(
@@ -77,12 +77,11 @@
         graph_title = self.graph_name + " [" + self.measurement_unit + "]"
         sub_title_list = self.source.get_sensor_list()
 
-        # create several different instances of salable bar graph
+        # create several different instances of scalable bar graph
         w = []
         for _ in range(graph_count):
             graph = ScalableBarGraph(["bg background", self.color_a, 
self.color_b])
             w.append(graph)
-
         super(BarGraphVector, self).__init__(
             graph_title, sub_title_list, y_label, w, visible_graph_list
         )
@@ -92,7 +91,7 @@
 
         self.color_counter_vector = [0] * graph_count
 
-    def _set_colors(self, colors):
+    def _set_colors(self, graph, colors):
         self.color_a = colors[0]
         self.color_b = colors[1]
         self.smooth_a = colors[2]
@@ -100,10 +99,9 @@
         if self.satt:
             self.satt = {(1, 0): self.smooth_a, (2, 0): self.smooth_b}
 
-        for graph in self.bar_graph_vector:
-            graph.set_segment_attributes(
-                ["bg background", self.color_a, self.color_b], satt=self.satt
-            )
+        graph.set_segment_attributes(
+            ["bg background", self.color_a, self.color_b], satt=self.satt
+        )
 
     def get_graph_name(self):
         return self.graph_name
@@ -152,39 +150,63 @@
             return
 
         # NOTE setting edge trigger causes overhead
+        triggered = False
         try:
-            if self.source.get_edge_triggered():
-                self._set_colors(self.alert_colors)
-            else:
-                self._set_colors(self.regular_colors)
+            triggered = self.source.get_edge_triggered()
         except NotImplementedError:
             pass
 
         current_reading = self.source.get_reading_list()
+        current_thresholds = self.source.get_threshold_list()
         logging.info("Reading %s", current_reading)
 
         y_label_size_max = 0
         local_top_value = []
+        # Store per-graph data for the second pass
+        graph_display_data = []
 
-        # update visible graph data, and maximum
+        # First pass: update graph data, collect local maximums, and prepare 
display data
         for graph_idx, graph in enumerate(self.bar_graph_vector):
+            # Check if this graph has a threshold defined
+            has_threshold = (
+                graph_idx < len(current_thresholds)
+                and current_thresholds[graph_idx] is not None
+            )
+            if (
+                # Case 1: no per-sensor threshold, fall back to global trigger
+                (not has_threshold and triggered)
+                # Case 2: per-sensor threshold exists and this sensor exceeds 
it,
+                #         even if `triggered` is False
+                or (
+                    has_threshold
+                    and current_reading[graph_idx] > 
current_thresholds[graph_idx]
+                )
+            ):
+                self._set_colors(graph, self.alert_colors)
+            else:
+                self._set_colors(graph, self.regular_colors)
             try:
                 _ = self.visible_graph_list[graph_idx]
             except IndexError:
                 # If a new graph "Appears", append it to visible
                 self.visible_graph_list.append(True)
-            bars = []
+
             if self.visible_graph_list[graph_idx]:
                 self.graph_data[graph_idx] = self.append_latest_value(
                     self.graph_data[graph_idx], current_reading[graph_idx]
                 )
 
-                # Get the graph width (dimension 1)
+                # Get the graph width (dimension 1) - cache for reuse
                 num_displayed_bars = graph.get_size()[1]
-                visible_id = self.MAX_SAMPLES - num_displayed_bars - 1
+                start_idx = self.MAX_SAMPLES - num_displayed_bars
 
-                visible_graph_data = self.graph_data[graph_idx][visible_id:]
+                visible_graph_data = self.graph_data[graph_idx][start_idx - 1 
:]
                 local_top_value.append(max(visible_graph_data))
+                graph_display_data.append(
+                    (graph_idx, graph, start_idx, num_displayed_bars)
+                )
+            else:
+                graph_display_data.append(None)
 
         update_max = False
         if self.graph_max == 1:
@@ -196,36 +218,26 @@
             update_max = True
             self.graph_max = local_max
 
-        # update the graph bars
-        for graph_idx, graph in enumerate(self.bar_graph_vector):
+        # Second pass: generate bars using cached data
+        for entry in graph_display_data:
+            if entry is None:
+                continue
+            graph_idx, graph, start_idx, num_displayed_bars = entry
             bars = []
-            if self.visible_graph_list[graph_idx]:
-                # Get the graph width (dimension 1)
-                num_displayed_bars = graph.get_size()[1]
-                # Iterate over all the information in the graph
-                if self.color_counter_vector[graph_idx] % 2 == 0:
-                    for n in range(
-                        self.MAX_SAMPLES - num_displayed_bars, self.MAX_SAMPLES
-                    ):
-                        value = round(self.graph_data[graph_idx][n], 1)
-                        # toggle between two bar types
-                        if n & 1:
-                            bars.append([0, value])
-                        else:
-                            bars.append([value, 0])
+            # Determine bar color alternation pattern
+            swap = self.color_counter_vector[graph_idx] % 2 == 1
+            for n in range(start_idx, self.MAX_SAMPLES):
+                value = round(self.graph_data[graph_idx][n], 1)
+                # toggle between two bar types
+                first = (n & 1) ^ swap
+                if first:
+                    bars.append([0, value])
                 else:
-                    for n in range(
-                        self.MAX_SAMPLES - num_displayed_bars, self.MAX_SAMPLES
-                    ):
-                        value = round(self.graph_data[graph_idx][n], 1)
-                        if n & 1:
-                            bars.append([value, 0])
-                        else:
-                            bars.append([0, value])
-                self.color_counter_vector[graph_idx] += 1
+                    bars.append([value, 0])
+            self.color_counter_vector[graph_idx] += 1
 
-                graph.set_data(bars, float(self.graph_max))
-                y_label_size_max = max(y_label_size_max, graph.get_size()[0])
+            graph.set_data(bars, float(self.graph_max))
+            y_label_size_max = max(y_label_size_max, graph.get_size()[0])
 
         self.set_y_label(
             self.get_label_scale(0, self.graph_max, float(y_label_size_max))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/sturwid/complex_bar_graph.py 
new/s-tui-1.3.0/s_tui/sturwid/complex_bar_graph.py
--- old/s-tui-1.1.6/s_tui/sturwid/complex_bar_graph.py  2024-01-28 
04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/s_tui/sturwid/complex_bar_graph.py  2026-01-12 
13:28:46.000000000 +0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Copyright (C) 2017-2020 Alex Manuskin, Gil Tsuker
+# Copyright (C) 2017-2025 Alex Manuskin, Gil Tsuker
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/sturwid/summary_text_list.py 
new/s-tui-1.3.0/s_tui/sturwid/summary_text_list.py
--- old/s-tui-1.1.6/s_tui/sturwid/summary_text_list.py  2024-01-28 
04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/s_tui/sturwid/summary_text_list.py  2026-01-12 
13:28:46.000000000 +0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Copyright (C) 2017-2020 Alex Manuskin, Gil Tsuker
+# Copyright (C) 2017-2025 Alex Manuskin, Gil Tsuker
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -44,12 +44,9 @@
             # This can be accessed by the update method
             self.summary_text_items[key] = value_w
             col_w = urwid.Columns([("weight", 1.5, label_w), value_w])
-            try:
-                _ = self.visible_summaries[key]
-            except KeyError:
-                # If an unknown key appears, add it to list
-                self.visible_summaries[key] = True
-            if self.visible_summaries[key]:
+            # Use setdefault for atomic check-and-set (faster than try/except)
+            is_visible = self.visible_summaries.setdefault(key, True)
+            if is_visible:
                 summery_text_list.append(col_w)
 
         return summery_text_list
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/s_tui/sturwid/ui_elements.py 
new/s-tui-1.3.0/s_tui/sturwid/ui_elements.py
--- old/s-tui-1.1.6/s_tui/sturwid/ui_elements.py        2024-01-28 
04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/s_tui/sturwid/ui_elements.py        2026-01-12 
13:28:46.000000000 +0100
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2017-2020 Alex Manuskin, Gil Tsuker
+# Copyright (C) 2017-2025 Alex Manuskin, Gil Tsuker
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s-tui-1.1.6/setup.py new/s-tui-1.3.0/setup.py
--- old/s-tui-1.1.6/setup.py    2024-01-28 04:24:31.000000000 +0100
+++ new/s-tui-1.3.0/setup.py    2026-01-12 13:28:46.000000000 +0100
@@ -40,7 +40,7 @@
         "Topic :: System :: Monitoring",
     ],
     install_requires=[
-        "urwid>=2.0.1",
-        "psutil>=5.9.1",
+        "urwid>=3.0.2",
+        "psutil>=7.0.0",
     ],
 )

Reply via email to