Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/themes-screenshot-on-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ jobs:
cp screencap.png "screenshot-$theme.png"

# Compare with original theme preview to detect changes, generate a diff
python3 tools/compare-images.py "screenshot-$theme.png" "$dir/preview.png" "diff-$theme.png"
if [ -f "diff-$theme.png" ]; then
echo "::warning::$theme theme rendering has changed, check if it is intentional or a side-effect. A diff is included in the job artifacts."
if [ -f "$dir/preview.png" ]; then
python3 tools/compare-images.py "screenshot-$theme.png" "$dir/preview.png" "diff-$theme.png"
if [ -f "diff-$theme.png" ]; then
echo "::warning::$theme theme rendering has changed, check if it is intentional or a side-effect. A diff is included in the job artifacts."
fi
fi
fi
done
Expand Down
7 changes: 7 additions & 0 deletions library/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ def DateStats():
stats.Date.stats()


@async_job("Custom_Stats")
@schedule(timedelta(seconds=config.THEME_DATA['STATS']['CUSTOM'].get("INTERVAL", None)).total_seconds())
def CustomStats():
# print("Refresh custom stats")
stats.Custom.stats()


@async_job("Queue_Handler")
@schedule(timedelta(milliseconds=1).total_seconds())
def QueueHandler():
Expand Down
69 changes: 69 additions & 0 deletions library/sensors/sensors_custom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# turing-smart-screen-python - a Python system monitor and library for USB-C displays like Turing Smart Screen or XuanFang
# https://github.com/mathoudebine/turing-smart-screen-python/

# Copyright (C) 2021-2023 Matthieu Houdebine (mathoudebine)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

# This file allows to add custom data source as sensors and display them in System Monitor themes
# There is no limitation on how much custom data source classes can be added to this file
# See CustomDataExample theme for the theme implementation part

import platform
from abc import ABC, abstractmethod


# Custom data classes must be implemented in this file, inherit the CustomDataSource and implement its 2 methods
class CustomDataSource(ABC):
@abstractmethod
def as_numeric(self) -> float:
# Numeric value will be used for graph and radial progress bars
# If there is no numeric value, keep this function empty
pass

@abstractmethod
def as_string(self) -> str:
# Text value will be used for text display and radial progress bar inner text
# Numeric value can be formatted here to be displayed as expected
# It is also possible to return a text unrelated to the numeric value
# If this function is empty, the numeric value will be used as string without formatting
pass


# Example for a custom data class that has numeric and text values
class ExampleCustomNumericData(CustomDataSource):
def as_numeric(self) -> float:
# Numeric value will be used for graph and radial progress bars
# Here a Python function from another module can be called to get data
# Example: return my_module.get_rgb_led_brightness() / return audio.system_volume() ...
return 75.845

def as_string(self) -> str:
# Text value will be used for text display and radial progress bar inner text.
# Numeric value can be formatted here to be displayed as expected
# It is also possible to return a text unrelated to the numeric value
# If this function is empty, the numeric value will be used as string without formatting
# Example here: format numeric value: add unit as a suffix, and keep 1 digit decimal precision
return f'{self.as_numeric(): .1f}%'


# Example for a custom data class that only has text values
class ExampleCustomTextOnlyData(CustomDataSource):
def as_numeric(self) -> float:
# If there is no numeric value, keep this function empty
pass

def as_string(self) -> str:
# If a custom data class only has text values, it won't be possible to display graph or radial bars
return "Python version: " + platform.python_version()
51 changes: 47 additions & 4 deletions library/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
except:
os._exit(0)

import library.sensors.sensors_custom as sensors_custom


def get_theme_file_path(name):
if name:
Expand Down Expand Up @@ -118,14 +120,17 @@ def display_themed_progress_bar(theme_data, value):
)


def display_themed_radial_bar(theme_data, value, min_size=0, unit=''):
def display_themed_radial_bar(theme_data, value, min_size=0, unit='', custom_text=None):
if not theme_data.get("SHOW", False):
return

if theme_data.get("SHOW_TEXT", False):
text = f"{{:>{min_size}}}".format(value)
if theme_data.get("SHOW_UNIT", True) and unit:
text += str(unit)
if custom_text:
text = custom_text
else:
text = f"{{:>{min_size}}}".format(value)
if theme_data.get("SHOW_UNIT", True) and unit:
text += str(unit)
else:
text = ""

Expand Down Expand Up @@ -490,3 +495,41 @@ def stats():
theme_data=hour_theme_data,
value=f"{babel.dates.format_time(date_now, format=time_format, locale=lc_time)}"
)


class Custom:
@staticmethod
def stats():
for custom_stat in config.THEME_DATA['STATS']['CUSTOM']:
if custom_stat != "INTERVAL":

# Load the custom sensor class from sensors_custom.py based on the class name
try:
custom_stat_class = getattr(sensors_custom, str(custom_stat))()
string_value = custom_stat_class.as_string()
numeric_value = custom_stat_class.as_numeric()
except:
logger.error("Custom sensor class " + str(custom_stat) + " not found in sensors_custom.py")
return

if not string_value:
string_value = str(numeric_value)

# Display text
theme_data = config.THEME_DATA['STATS']['CUSTOM'][custom_stat].get("TEXT", None)
if theme_data and string_value:
display_themed_value(theme_data=theme_data, value=string_value)

# Display graph from numeric value
theme_data = config.THEME_DATA['STATS']['CUSTOM'][custom_stat].get("GRAPH", None)
if theme_data and numeric_value:
display_themed_progress_bar(theme_data=theme_data, value=numeric_value)

# Display radial from numeric and text value
theme_data = config.THEME_DATA['STATS']['CUSTOM'][custom_stat].get("RADIAL", None)
if theme_data and numeric_value and string_value:
display_themed_radial_bar(
theme_data=theme_data,
value=numeric_value,
custom_text=string_value
)
1 change: 1 addition & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ def on_win32_wm_event(hWnd, msg, wParam, lParam):
scheduler.DiskStats()
scheduler.NetStats()
scheduler.DateStats()
scheduler.CustomStats()
scheduler.QueueHandler()

if tray_icon and platform.system() == "Darwin": # macOS-specific
Expand Down
Binary file added res/themes/CustomDataExample/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added res/themes/CustomDataExample/preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
104 changes: 104 additions & 0 deletions res/themes/CustomDataExample/theme.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# This theme is an example of how to implement and display custom data in System Monitor
# It is possible to add external sensor sources that come from custom Python code or external modules, and integrate them in an existing theme
# This file is the theme part to display the custom data, custom data gathering must first be implemented in Python into the sensors_custom.py file
# Names of the custom sensor classes listed here must be an exact match to class names in sensors_custom.py
---
display:
DISPLAY_ORIENTATION: landscape
DISPLAY_RGB_LED: 61, 184, 225

static_images:
BACKGROUND:
PATH: background.png
X: 0
Y: 0
WIDTH: 480
HEIGHT: 320

STATS:
DATE:
INTERVAL: 1
HOUR:
TEXT:
SHOW: True
X: 20
Y: 30
FONT: roboto-mono/RobotoMono-Bold.ttf
FONT_SIZE: 20
FONT_COLOR: 200, 200, 200
# BACKGROUND_COLOR: 50, 0, 0
BACKGROUND_IMAGE: background.png

# All custom sensor classes are listed under CUSTOM
CUSTOM:

# For now the refresh interval (in seconds) is the same for all custom data classes
INTERVAL: 3



# The name of the class must be an exact match to the class name in sensors_custom.py
ExampleCustomNumericData:

# There are 3 ways of displaying a custom sensor containing numeric values: as text, as graph progress bar, as radial progress bar
TEXT:
SHOW: True
X: 50
Y: 80
FONT: roboto-mono/RobotoMono-Bold.ttf
FONT_SIZE: 40
FONT_COLOR: 200, 200, 200
# BACKGROUND_COLOR: 50, 0, 0
BACKGROUND_IMAGE: background.png

GRAPH:
SHOW: True
X: 50
Y: 150
WIDTH: 180
HEIGHT: 30
MIN_VALUE: 0
MAX_VALUE: 100
BAR_COLOR: 255, 135, 0
BAR_OUTLINE: True
# BACKGROUND_COLOR: 0, 0, 0
BACKGROUND_IMAGE: background.png

RADIAL:
SHOW: True
X: 350
Y: 130
RADIUS: 68
WIDTH: 18
MIN_VALUE: 0
MAX_VALUE: 100
ANGLE_START: 110
ANGLE_END: 70
ANGLE_STEPS: 1
ANGLE_SEP: 25
CLOCKWISE: True
BAR_COLOR: 61, 184, 225
SHOW_TEXT: True
SHOW_UNIT: True
FONT: roboto-mono/RobotoMono-Bold.ttf
FONT_SIZE: 25
FONT_COLOR: 255, 135, 0
# BACKGROUND_COLOR: 0, 0, 0
BACKGROUND_IMAGE: background.png




# The name of the class must be an exact match to the class name in sensors_custom.py
ExampleCustomTextOnlyData:

# There are only 1 way of displaying a custom sensor containing text-only values: as text
TEXT:
SHOW: True
X: 34
Y: 250
FONT: roboto-mono/RobotoMono-Bold.ttf
FONT_SIZE: 28
FONT_COLOR: 61, 184, 225
# BACKGROUND_COLOR: 50, 0, 0
BACKGROUND_IMAGE: background.png
2 changes: 2 additions & 0 deletions res/themes/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,5 @@ STATS:
HOUR:
TEXT:
SHOW: False
CUSTOM:
INTERVAL: 3
1 change: 1 addition & 0 deletions theme-editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def refresh_theme():
stats.Disk.stats()
stats.Net.stats()
stats.Date.stats()
stats.Custom.stats()


if __name__ == "__main__":
Expand Down