From 72aff8f25c1aab315b58a94faeb3d141632b641b Mon Sep 17 00:00:00 2001 From: George Sladkovsky Date: Tue, 22 Oct 2024 11:53:19 +0200 Subject: [PATCH 1/4] feat(widget): Libre Hardware Monitor Widget This commit introduces the Libre Hardware Monitor widget that connects to the local LHM web server and fetches the selected sensor data using SensorId. The post request is done asynchronously using QNetworkAccessManager to avoid locking the UI. Possible errors are handled and will be displayed in the widget instead of the value. Auth is supported. Histogram view is also available. --- docs/_Sidebar.md | 1 + docs/widgets/(Widget)-Libre-HW-Monitor.md | 96 ++++++++ src/build.py | 3 +- src/config.yaml | 48 +++- .../validation/widgets/yasb/libre_monitor.py | 143 ++++++++++++ src/core/widgets/yasb/libre_monitor.py | 214 ++++++++++++++++++ src/styles.css | 10 +- 7 files changed, 512 insertions(+), 3 deletions(-) create mode 100644 docs/widgets/(Widget)-Libre-HW-Monitor.md create mode 100644 src/core/validation/widgets/yasb/libre_monitor.py create mode 100644 src/core/widgets/yasb/libre_monitor.py diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md index 3c9497a..c6951ff 100644 --- a/docs/_Sidebar.md +++ b/docs/_Sidebar.md @@ -14,6 +14,7 @@ - [Github](./(Widget)-Github) - [Disk](./(Widget)-Disk) - [Language](./(Widget)-Language) + - [Libre Hardware Monitor](./(Widget)-Libre-HW-Monitor) - [Media](./(Widget)-Media) - [Memory](./(Widget)-Memory) - [OBS](./(Widget)-Obs) diff --git a/docs/widgets/(Widget)-Libre-HW-Monitor.md b/docs/widgets/(Widget)-Libre-HW-Monitor.md new file mode 100644 index 0000000..51829ab --- /dev/null +++ b/docs/widgets/(Widget)-Libre-HW-Monitor.md @@ -0,0 +1,96 @@ +# Libre Hardware Monitor Widget Configuration + +| Option | Type | Default | Description | +|-------------------------|---------|------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| +| `label` | string | `"\udb82\udcae {info[value]}{info[unit]}"` | The primary label format. | +| `label_alt` | string | `"\uf4bc {info[histogram]} {info[value]} ({info[min]}/{info[max]}) {info[unit]}"` | Histograms. The alternative label format. | +| `sensor_id` | string | `"/amdcpu/0/load/0"` | Libre Hardware Monitor SensorId from http://localhost:8085/data.json | +| `class_name` | string | `"libre-monitor-widget"` | CSS class name for styling of different widget instances. | +| `update_interval` | integer | `1000` | The interval in milliseconds to update the widget. | +| `precision` | integer | `1` | Floating point precision of the info[value]. | +| `history_size` | integer | `60` | The size of the min/max history. | +| `histogram_num_columns` | integer | `10` | The number of columns in the histogram. | +| `histogram_fixed_min` | integer | `None` | Histogram minimum value. If None - set as history minimum value. | +| `histogram_fixed_max` | integer | `None` | Histogram maximum value. If None - set as history maximum value. | +| `server_host` | string | `"localhost"` | Libre Hardware Monitor server host. | +| `server_port` | integer | `8085` | Libre Hardware Monitor server port. | +| `server_username` | string | `""` | Libre Hardware Monitor username. Only needed if auth is enabled. | +| `server_password` | string | `""` | Libre Hardware Monitor password. Only needed if auth is enabled. | +| `histogram_icons` | list | `['\u2581', '\u2581', '\u2582', '\u2583', '\u2584', '\u2585', '\u2586', '\u2587', '\u2588']` | Icons representing CPU usage histograms. | +| `callbacks` | dict | `{'on_left': 'toggle_label', 'on_middle': 'do_nothing', 'on_right': 'do_nothing'}` | Callback functions for different mouse button actions. | + +## Example Configuration (GPU Temperature) + +```yaml + libre_gpu: + type: "yasb.libre_monitor.LibreHardwareMonitorWidget" + options: + label: "\udb82\udcae {info[value]}{info[unit]}" + label_alt: "\uf437 {info[histogram]} {info[value]} ({info[min]}/{info[max]}) {info[unit]}" + sensor_id: "/gpu-nvidia/0/temperature/0" + update_interval: 1000 + precision: 1 + histogram_num_columns: 10 + class_name: "libre-monitor-widget" + + history_size: 60 + histogram_icons: + - '\u2581' # 0% + - '\u2581' # 10% + - '\u2582' # 20% + - '\u2583' # 30% + - '\u2584' # 40% + - '\u2585' # 50% + - '\u2586' # 60% + - '\u2587' # 70% + - '\u2588' # 80%+ + + # histogram_fixed_min: 0.0 + # histogram_fixed_max: 100.0 + + # server_host: "localhost" + # server_port: 8085 + # server_username: "admin" + # server_password: "password" + + callbacks: + on_left: "toggle_label" + on_middle: "do_nothing" + on_right: "do_nothing" +``` +## Set up instructions +1. Install Libre Hardware Monitor https://github.com/LibreHardwareMonitor/LibreHardwareMonitor +2. Run Libre Hardware Monitor. +3. Start the Remote Web Server (Options -> Remote Web Server -> Run). +4. Find the required SensorId in the http://localhost:8085/data.json. +5. Update the widget configuration with the required SensorId. + +**Note**: Libre Hardware Monitor and its web server must be running in the background for the widget to work. Autostart is recommended. + +## Description of Options + +- **label**: The format string for the Libre Monitor label. You can use placeholders like `{info[value]} {info[unit]}` to dynamically insert required information. +- **label_alt**: The alternative format string for the Libre Monitor label. Useful for displaying additional details like histogram `{info[histogram]}` or min/max values `{info[min]} {info[max]}`. +- **class_name**: Custom CSS class name for the widget instance. Useful when having multiple widgets with different styling. +- **sensor_id**: The sensor ID of the Libre Hardware Monitor server. All the SensorIds can be found in the http://localhost:8085/data.json when the server is running (Options->Remote Web Server->Run). +- **update_interval**: The interval in milliseconds at which the widget updates its information. Limited by the Libre Hardware Monitor update interval. +- **precision**: Floating point precision of the `{info[value]}`. +- **history_size**: The size of the min/max history. The history is reset when the widget/yasb is reloaded. +- **histogram_fixed_min**: Set the fixed minimum value of the histogram. Actual sensor min value from the history is not changed. If not set manually it will be set as history minimum value. +- **histogram_fixed_max**: Set the fixed maximum value of the histogram. Actual sensor max value from the history is not changed. If not set manually it will be set as history maximum value. +- **histogram_icons**: A list of icons representing different values of the histogram. +- **histogram_num_columns**: The number of columns to display in the histogram. +- **server_host**: The host of the Libre Hardware Monitor server. +- **server_port**: The port of the Libre Hardware Monitor server. +- **server_username**: The username of the Libre Hardware Monitor server. Required if auth is enabled. +- **server_password**: The password of the Libre Hardware Monitor server. Required if auth is enabled. +- **callbacks**: A dictionary specifying the callbacks for mouse events. The keys are `on_left`, `on_middle`, and `on_right`, and the values are the names of the callback functions. + +## Example Style +```css +.libre-monitor-widget {} +.libre-monitor-widget .widget-container {} +.libre-monitor-widget .widget-container .label {} +.libre-monitor-widget .widget-container .label.alt {} +.libre-monitor-widget .widget-container .icon {} +``` diff --git a/src/build.py b/src/build.py index 45ec30a..af680af 100644 --- a/src/build.py +++ b/src/build.py @@ -10,6 +10,7 @@ 'core.widgets.yasb.weather', 'core.widgets.yasb.memory', 'core.widgets.yasb.cpu', + 'core.widgets.yasb.libre_monitor', 'core.widgets.yasb.active_window', 'core.widgets.yasb.applications', 'core.widgets.yasb.battery', @@ -100,4 +101,4 @@ "build_exe": build_options, "bdist_msi": bdist_msi_options, }, -) \ No newline at end of file +) diff --git a/src/config.yaml b/src/config.yaml index 0c3c83f..8919ff8 100644 --- a/src/config.yaml +++ b/src/config.yaml @@ -374,4 +374,50 @@ widgets: label_alt: "\ue62a" class_name: "system-widget" callbacks: - on_left: "exec start_menu" \ No newline at end of file + on_left: "exec start_menu" + + libre_monitor_widget: + type: "yasb.libre_monitor.LibreHardwareMonitorWidget" + options: + # Available "info" keys: value, unit, min, max, histogram + label: "\udb82\udcae {info[value]}{info[unit]}" + label_alt: "\uf437 {info[histogram]} {info[value]} ({info[min]}/{info[max]}) {info[unit]}" + sensor_id: "/gpu-nvidia/0/temperature/0" # "SensorId" from http://localhost:8085/data.json when the Libre HWM server is running. + update_interval: 1000 # Update interval in milliseconds. Limited by the Libre HWM update interval. + precision: 0 + histogram_num_columns: 10 + + # Optional. Custom widget class name to customize multiple widgets. Default is libre-monitor-widget. + class_name: "libre-monitor-widget" + + # Optional. A history of min and max values. + # history_size: 50 + + # Optional. Custom histogram icons. Must have exactly 9 icons. + # histogram_icons: + # - '\u2581' + # - '\u2581' + # - '\u2582' + # - '\u2583' + # - '\u2584' + # - '\u2585' + # - '\u2586' + # - '\u2587' + # - '\u2588' + + # Optional. Fixed min/max value range for the histogram. Actual {info[min]} and {info[max]} values are untouched. + # histogram_fixed_min: 0.0 + # histogram_fixed_max: 100.0 + + # Optional. Custom Libre HWM server host and port. + # server_host: "localhost" + # server_port: 8085 + + # Optional. If you enable auth in Libre HWM, set username and password and restart the server. + # server_username: "admin" + # server_password: "password" + + callbacks: + on_left: "toggle_label" + on_middle: "do_nothing" + on_right: "do_nothing" diff --git a/src/core/validation/widgets/yasb/libre_monitor.py b/src/core/validation/widgets/yasb/libre_monitor.py new file mode 100644 index 0000000..12fb36f --- /dev/null +++ b/src/core/validation/widgets/yasb/libre_monitor.py @@ -0,0 +1,143 @@ +DEFAULTS = { + 'class_name': 'libre-monitor-widget', + 'label': '\udb82\udcae {info[value]}{info[unit]}', + 'label_alt': '\uf437 {info[histogram]} {info[value]} ({info[min]}/{info[max]}) {info[unit]}', + 'update_interval': 1000, + 'sensor_id': '/amdcpu/0/load/0', + 'histogram_icons': [ + r'\u2581', + r'\u2581', + r'\u2582', + r'\u2583', + r'\u2584', + r'\u2585', + r'\u2586', + r'\u2587', + r'\u2588' + ], + 'histogram_num_columns': 10, + 'precision': 2, + 'history_size': 60, + 'histogram_fixed_min': None, + 'histogram_fixed_max': None, + 'server_host': 'localhost', + 'server_port': 8085, + 'server_username': '', + 'server_password': '', + 'callbacks': { + 'on_left': 'toggle_label', + 'on_middle': 'do_nothing', + 'on_right': 'do_nothing' + }, +} + +VALIDATION_SCHEMA = { + 'class_name': { + 'type': 'string', + 'default': DEFAULTS['class_name'], + 'required': False, + }, + 'label': { + 'type': 'string', + 'default': DEFAULTS['label'] + }, + 'label_alt': { + 'type': 'string', + 'default': DEFAULTS['label_alt'] + }, + 'update_interval': { + 'type': 'integer', + 'default': DEFAULTS['update_interval'], + 'min': 0, + 'max': 60000 + }, + 'sensor_id': { + 'type': 'string', + 'default': DEFAULTS['sensor_id'], + }, + 'histogram_icons': { + 'type': 'list', + 'default': DEFAULTS['histogram_icons'], + 'minlength': 9, + 'maxlength': 9, + "schema": { + 'type': 'string' + } + }, + 'histogram_num_columns': { + 'type': 'integer', + 'default': DEFAULTS['histogram_num_columns'], + 'min': 0, + 'max': 128 + }, + 'precision': { + 'type': 'integer', + 'default': DEFAULTS['precision'], + 'min': 0, + 'max': 30, + 'required': False, + }, + 'history_size': { + 'type': 'integer', + 'default': DEFAULTS['history_size'], + 'min': DEFAULTS["histogram_num_columns"], + 'max': 50000, + 'required': False, + }, + 'histogram_fixed_min': { + 'type': 'float', + 'default': DEFAULTS['histogram_fixed_min'], + 'min': -10000.0, + 'max': 10000.0, + 'required': False, + 'nullable': True + }, + 'histogram_fixed_max': { + 'type': 'float', + 'default': DEFAULTS['histogram_fixed_max'], + 'min': -10000.0, + 'max': 10000.0, + 'required': False, + 'nullable': True + }, + 'server_host': { + 'type': 'string', + 'default': DEFAULTS['server_host'], + 'required': False, + }, + 'server_port': { + 'type': 'integer', + 'default': DEFAULTS['server_port'], + 'min': 0, + 'max': 65535, + 'required': False, + }, + 'server_username': { + 'type': 'string', + 'default': DEFAULTS['server_username'], + 'required': False, + }, + 'server_password': { + 'type': 'string', + 'default': DEFAULTS['server_password'], + 'required': False, + }, + 'callbacks': { + 'type': 'dict', + 'schema': { + 'on_left': { + 'type': 'string', + 'default': DEFAULTS['callbacks']['on_left'], + }, + 'on_middle': { + 'type': 'string', + 'default': DEFAULTS['callbacks']['on_middle'], + }, + 'on_right': { + 'type': 'string', + 'default': DEFAULTS['callbacks']['on_right'], + } + }, + 'default': DEFAULTS['callbacks'] + } +} diff --git a/src/core/widgets/yasb/libre_monitor.py b/src/core/widgets/yasb/libre_monitor.py new file mode 100644 index 0000000..c749537 --- /dev/null +++ b/src/core/widgets/yasb/libre_monitor.py @@ -0,0 +1,214 @@ +import re +import json +from collections import deque + +from PyQt6.QtWidgets import QHBoxLayout, QLabel, QWidget +from PyQt6.QtCore import Qt, QUrl +from PyQt6.QtNetwork import QAuthenticator, QNetworkAccessManager, QNetworkRequest, QNetworkReply + +from core.validation.widgets.yasb.libre_monitor import VALIDATION_SCHEMA +from core.widgets.base import BaseWidget + + +class LibreHardwareMonitorWidget(BaseWidget): + validation_schema = VALIDATION_SCHEMA + + def __init__( + self, + class_name: str, + label: str, + label_alt: str, + update_interval: int, + sensor_id: str, + histogram_icons: list[str], + histogram_num_columns: int, + precision: int, + history_size: int, + histogram_fixed_min: float | None, + histogram_fixed_max: float | None, + server_host: str, + server_port: int, + server_username: str, + server_password: str, + callbacks: dict, + ): + super().__init__(update_interval, class_name=class_name) + self._show_alt_label = False + self._label_content = label + self._label_alt_content = label_alt + self._sensor_id = sensor_id + self._precision = precision + self._history = deque([0.0] * histogram_num_columns, maxlen=histogram_num_columns) + self._history_long: deque[float] = deque([], maxlen=history_size) + self._histogram_fixed_min = histogram_fixed_min + self._histogram_fixed_max = histogram_fixed_max + self._histogram_icons = histogram_icons + self._histogram_num_columns = histogram_num_columns + self._server_host = server_host + self._server_port = server_port + self._server_username = server_username + self._server_password = server_password + + # UI + self._widget_container_layout = QHBoxLayout() + self._widget_container_layout.setSpacing(0) + self._widget_container_layout.setContentsMargins(0, 0, 0, 0) + + self._widget_container = QWidget() + self._widget_container.setLayout(self._widget_container_layout) + self._widget_container.setProperty("class", "widget-container") + self.widget_layout.addWidget(self._widget_container) + + self._create_dynamically_label(self._label_content, self._label_alt_content) + + self.register_callback("toggle_label", self._toggle_label) + self.register_callback("update_label", self._update_label) + + self._data = None + # Create a network manager to handle the LHM connection asynchronously + self._network_manager = QNetworkAccessManager() + # Called when the request is finished + self._network_manager.finished.connect(self._handle_network_response) + # Called if the server requests authentication + self._network_manager.authenticationRequired.connect(self._handle_authentication) + + # Callbacks + self.callback_left = callbacks["on_left"] + self.callback_right = callbacks["on_right"] + self.callback_middle = callbacks["on_middle"] + self.callback_timer = "update_label" + + # Timer + self.start_timer() + + def _toggle_label(self): + """Toggle between main and alt labels""" + self._show_alt_label = not self._show_alt_label + for widget in self._widgets: + widget.setVisible(not self._show_alt_label) + for widget in self._widgets_alt: + widget.setVisible(self._show_alt_label) + self._update_label() + + def _create_dynamically_label(self, content: str, content_alt: str): + """Label initialization""" + + def process_content(content: str, is_alt=False): + label_parts = re.split("(.*?)", content) + label_parts = [part for part in label_parts if part] + widgets: list[QLabel] = [] + for part in label_parts: + part = part.strip() + if not part: + continue + if "" in part: + class_name = re.search(r'class=(["\'])([^"\']+?)\1', part) + class_result = class_name.group(2) if class_name else "icon" + icon = re.sub(r"|", "", part).strip() + label = QLabel(icon) + label.setProperty("class", class_result) + else: + label = QLabel(part) + label.setProperty("class", "label") + label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self._widget_container_layout.addWidget(label) + widgets.append(label) + if is_alt: + label.hide() + else: + label.show() + return widgets + + self._widgets = process_content(content) + self._widgets_alt = process_content(content_alt, is_alt=True) + + def _get_histogram_bar(self, value: float, value_min: float, value_max: float): + """Gets the appropriate histogram element from the icons list based on the value and min/max""" + bar_index = int((value - value_min) / max((value_max - value_min), 0.00001) * 10) + bar_index = min(abs(bar_index), 8) + return self._histogram_icons[bar_index] + + def _update_label(self): + """Make a request and update the label with the received data""" + self._make_request() + info = { + "status": "No Connection...", + "value": 0.0, + "unit": "", + } + if self._data and self._data.get("result") == "ok": + value = self._data.get("value", 0.0) + + self._history.append(float(value)) + self._history_long.append(float(value)) + history_min_value = min(self._history_long) + history_max_value = max(self._history_long) + min_val = history_min_value if self._histogram_fixed_min is None else self._histogram_fixed_min + max_val = history_max_value if self._histogram_fixed_max is None else self._histogram_fixed_max + + info["value"] = f"{value:.{self._precision}f}" + info["min"] = f"{history_min_value:.{self._precision}f}" + info["max"] = f"{history_max_value:.{self._precision}f}" + info["unit"] = self._data.get("format", "Error Error").split(" ")[-1] + info["histogram"] = ( + "".join([self._get_histogram_bar(val, min_val, max_val) for val in self._history]) + .encode("utf-8") + .decode("unicode_escape") + ) + elif self._data: + info["value"] = self._data.get("status", "") + + active_widgets = self._widgets_alt if self._show_alt_label else self._widgets + active_label_content = self._label_alt_content if self._show_alt_label else self._label_content + label_parts = re.split("(.*?)", active_label_content) + label_parts = [part for part in label_parts if part] + widget_index = 0 + for part in label_parts: + part = part.strip() + if part and widget_index < len(active_widgets) and isinstance(active_widgets[widget_index], QLabel): + if "" in part: + icon = re.sub(r"|", "", part).strip() + active_widgets[widget_index].setText(icon) + else: + formatted_text = part.format(info=info) if info else part + active_widgets[widget_index].setText(formatted_text) + widget_index += 1 + + def _make_request(self): + """Makes a post request to LibreHardwareMonitor""" + url = QUrl(f"http://{self._server_host}:{self._server_port}/Sensor?action=Get&id={self._sensor_id}") + request = QNetworkRequest(url) + request.setHeader(QNetworkRequest.KnownHeaders.ContentTypeHeader, "application/x-www-form-urlencoded") + self._network_manager.post(request, b"") + + def _handle_network_response(self, reply: QNetworkReply): + """ + Handles the network response from the QNetworkAccessManager. + Handles the potential error codes and populates the data dict with the result. + """ + if reply.error() == QNetworkReply.NetworkError.NoError: + bytes_string = reply.readAll().data() + self._data = json.loads(bytes_string.decode("utf-8")) + if self._data.get("result") == "ok": + self._data["status"] = "Connected..." + else: + self._data["status"] = "Invalid sensor id..." + elif reply.error() == QNetworkReply.NetworkError.AuthenticationRequiredError: + self._data = { + "status": "Authentication Failed...", + "result": "fail", + "value": 0.0, + } + else: + self._data = { + "status": "Connection Error...", + "result": "fail", + "value": 0.0, + } + reply.deleteLater() + + def _handle_authentication(self, _: QNetworkReply, auth: QAuthenticator): + """If server requests auth, this will be called and username and password will be set""" + if self._server_username and self._server_password: + auth.setUser(self._server_username) + auth.setPassword(self._server_password) diff --git a/src/styles.css b/src/styles.css index a7ca378..e695267 100644 --- a/src/styles.css +++ b/src/styles.css @@ -19,6 +19,7 @@ .language-widget {} -> Styles specific to the language widget .disk-widget {} -> Styles specific to the disk widget .taskbar-widget {} -> Styles specific to the taskbar widget +.libre-monitor-widget {} -> Styles specific to the Libre Hardware Monitor widget */ * { font-size: 12px; @@ -308,4 +309,11 @@ } .taskbar-widget .app-icon { padding: 0 6px; -} \ No newline at end of file +} + +/* LIBRE MONITOR WIDGET */ +.libre-monitor-widget .icon { + font-size: 14px; + color: #cba6f7; + margin: 0 2px 1px 0; +} From eefde3510960c0cb7903cdc25408c715001e33a9 Mon Sep 17 00:00:00 2001 From: George Sladkovsky Date: Tue, 22 Oct 2024 14:10:39 +0200 Subject: [PATCH 2/4] feat(widget): Readme update --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e744176..5661489 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ for more themes visit [yasb-themes](https://github.com/amnweb/yasb-themes) - **[Github](https://github.com/amnweb/yasb/wiki/(Widget)-Github)**: Shows notifications from GitHub. - **[Disk](https://github.com/amnweb/yasb/wiki/(Widget)-Disk)**: Displays disk usage information. - **[Language](https://github.com/amnweb/yasb/wiki/(Widget)-Language)**: Shows the current input language. +- **[Libre Hardware Monitor](https://github.com/amnweb/yasb/wiki/(Widget)-Libre-HW-Monitor)**: Connects to Libre Hardware Monitor to get sensor data. - **[Media](https://github.com/amnweb/yasb/wiki/(Widget)-Media)**: Displays media controls and information. - **[Memory](https://github.com/amnweb/yasb/wiki/(Widget)-Memory)**: Shows current memory usage. - **[OBS](https://github.com/amnweb/yasb/wiki/(Widget)-Obs)**: Integrates with OBS Studio to show recording status. From 04930185dc5d72bffaa59fd75cfe463445737714 Mon Sep 17 00:00:00 2001 From: George Sladkovsky Date: Tue, 22 Oct 2024 17:19:33 +0200 Subject: [PATCH 3/4] fix(widget): added default values for info dict --- src/core/widgets/yasb/libre_monitor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/widgets/yasb/libre_monitor.py b/src/core/widgets/yasb/libre_monitor.py index c749537..4e1ca02 100644 --- a/src/core/widgets/yasb/libre_monitor.py +++ b/src/core/widgets/yasb/libre_monitor.py @@ -135,6 +135,9 @@ def _update_label(self): "status": "No Connection...", "value": 0.0, "unit": "", + "min": 0.0, + "max": 0.0, + "histogram": "" } if self._data and self._data.get("result") == "ok": value = self._data.get("value", 0.0) From b71b92f72975d0d1cca01656ea7d554ccb3ee9bc Mon Sep 17 00:00:00 2001 From: George Sladkovsky Date: Tue, 22 Oct 2024 17:27:09 +0200 Subject: [PATCH 4/4] fix(widget): No connection message for histogram --- src/core/widgets/yasb/libre_monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/widgets/yasb/libre_monitor.py b/src/core/widgets/yasb/libre_monitor.py index 4e1ca02..9a9c520 100644 --- a/src/core/widgets/yasb/libre_monitor.py +++ b/src/core/widgets/yasb/libre_monitor.py @@ -137,7 +137,7 @@ def _update_label(self): "unit": "", "min": 0.0, "max": 0.0, - "histogram": "" + "histogram": "No Connection..." } if self._data and self._data.get("result") == "ok": value = self._data.get("value", 0.0)