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", ], )
