-
-
Notifications
You must be signed in to change notification settings - Fork 196
/
theme-editor.py
executable file
·299 lines (240 loc) · 10.8 KB
/
theme-editor.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
#!/usr/bin/env python
# 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/>.
# theme-editor.py: Allow to easily edit themes for System Monitor (main.py) in a preview window on the computer
# The preview window is refreshed as soon as the theme file is modified
import locale
import logging
import os
import platform
import subprocess
import sys
import time
MIN_PYTHON = (3, 8)
if sys.version_info < MIN_PYTHON:
print("[ERROR] Python %s.%s or later is required." % MIN_PYTHON)
try:
sys.exit(0)
except:
os._exit(0)
try:
import tkinter
from PIL import ImageTk
except:
print(
"[ERROR] Tkinter dependency not installed. Please follow troubleshooting page: https://github.com/mathoudebine/turing-smart-screen-python/wiki/Troubleshooting#all-os-tkinter-dependency-not-installed")
try:
sys.exit(0)
except:
os._exit(0)
if len(sys.argv) != 2:
print("Usage :")
print(" theme-editor.py theme-name")
print("Examples : ")
print(" theme-editor.py 3.5inchTheme2")
print(" theme-editor.py Landscape6Grid")
print(" theme-editor.py Cyberpunk")
try:
sys.exit(0)
except:
os._exit(0)
import library.log
library.log.logger.setLevel(logging.NOTSET) # Disable system monitor logging for the editor
# Create a logger for the editor
logger = logging.getLogger('turing-editor')
logger.setLevel(logging.DEBUG)
# Hardcode specific configuration for theme editor
from library import config
config.CONFIG_DATA["config"]["HW_SENSORS"] = "STATIC" # For theme editor always use stub data
config.CONFIG_DATA["config"]["THEME"] = sys.argv[1] # Theme is given as argument
config.load_theme()
# For theme editor, always use simulated LCD
if config.THEME_DATA["display"].get("DISPLAY_SIZE", '3.5"') == '5"':
config.CONFIG_DATA["display"]["REVISION"] = "SIMU5"
else:
config.CONFIG_DATA["display"]["REVISION"] = "SIMU"
from library.display import display # Only import display after hardcoded config is set
RGB_LED_MARGIN = 12
def refresh_theme():
config.load_theme()
# Initialize the display
display.initialize_display()
# Create all static images
display.display_static_images()
# Create all static texts
display.display_static_text()
# Display all data on screen once
import library.stats as stats
if config.THEME_DATA['STATS']['CPU']['PERCENTAGE'].get("INTERVAL", 0) > 0:
stats.CPU.percentage()
if config.THEME_DATA['STATS']['CPU']['FREQUENCY'].get("INTERVAL", 0) > 0:
stats.CPU.frequency()
if config.THEME_DATA['STATS']['CPU']['LOAD'].get("INTERVAL", 0) > 0:
stats.CPU.load()
if config.THEME_DATA['STATS']['CPU']['TEMPERATURE'].get("INTERVAL", 0) > 0:
stats.CPU.temperature()
if config.THEME_DATA['STATS']['CPU']['FAN_SPEED'].get("INTERVAL", 0) > 0:
stats.CPU.fan_speed()
if config.THEME_DATA['STATS']['GPU'].get("INTERVAL", 0) > 0:
stats.Gpu.stats()
if config.THEME_DATA['STATS']['MEMORY'].get("INTERVAL", 0) > 0:
stats.Memory.stats()
if config.THEME_DATA['STATS']['DISK'].get("INTERVAL", 0) > 0:
stats.Disk.stats()
if config.THEME_DATA['STATS']['NET'].get("INTERVAL", 0) > 0:
stats.Net.stats()
if config.THEME_DATA['STATS']['DATE'].get("INTERVAL", 0) > 0:
stats.Date.stats()
if config.THEME_DATA['STATS']['UPTIME'].get("INTERVAL", 0) > 0:
stats.SystemUptime.stats()
if config.THEME_DATA['STATS']['CUSTOM'].get("INTERVAL", 0) > 0:
stats.Custom.stats()
if __name__ == "__main__":
def on_closing():
logger.debug("Exit Theme Editor...")
try:
sys.exit(0)
except:
os._exit(0)
x0 = 0
y0 = 0
def draw_zone(x0, y0, x1, y1):
x = min(x0, x1)
y = min(y0, y1)
width = max(x0, x1) - min(x0, x1)
height = max(y0, y1) - min(y0, y1)
if width > 0 and height > 0:
label_zone.place(x=x + RGB_LED_MARGIN, y=y + RGB_LED_MARGIN, width=width, height=height)
else:
label_zone.place_forget()
def on_button1_press(event):
global x0, y0
x0, y0 = event.x, event.y
label_zone.place_forget()
def on_button1_press_and_drag(event):
global x0, y0
x1, y1 = event.x, event.y
# Do not draw zone outside of theme preview
if x1 < 0:
x1 = 0
elif x1 >= display.lcd.get_width():
x1 = display.lcd.get_width() - 1
if y1 < 0:
y1 = 0
elif y1 >= display.lcd.get_height():
y1 = display.lcd.get_height() - 1
label_coord.config(text='Drawing zone from [{},{}] to [{},{}]'.format(x0, y0, x1, y1))
draw_zone(x0, y0, x1, y1)
def on_button1_release(event):
global x0, y0
x1, y1 = event.x, event.y
if x1 != x0 or y1 != y0:
# Do not draw zone outside of theme preview
if x1 < 0:
x1 = 0
elif x1 >= display.lcd.get_width():
x1 = display.lcd.get_width() - 1
if y1 < 0:
y1 = 0
elif y1 >= display.lcd.get_height():
y1 = display.lcd.get_height() - 1
# Display drawn zone and coordinates
draw_zone(x0, y0, x1, y1)
# Display relative zone coordinates, to set in theme
x = min(x0, x1)
y = min(y0, y1)
width = max(x0, x1) - min(x0, x1)
height = max(y0, y1) - min(y0, y1)
label_coord.config(text='Zone: X={}, Y={}, width={} height={}'.format(x, y, width, height))
else:
# Display click coordinates
label_coord.config(
text='X={}, Y={} (click and drag to draw a zone)'.format(x0, y0, abs(x1 - x0), abs(y1 - y0)))
def on_zone_click(event):
label_zone.place_forget()
# Apply system locale to this program
locale.setlocale(locale.LC_ALL, '')
logger.debug("Starting Theme Editor...")
# Get theme file to edit
theme_file = config.THEME_DATA['PATH'] + "theme.yaml"
last_edit_time = os.path.getmtime(theme_file)
logger.debug("Using theme file " + theme_file)
# Open theme in default editor. You can also open the file manually in another program
logger.debug("Opening theme file in your default editor. If it does not work, open it manually in the "
"editor of your choice")
if platform.system() == 'Darwin': # macOS
subprocess.call(('open', "./" + theme_file))
elif platform.system() == 'Windows': # Windows
os.startfile(".\\" + theme_file)
else: # linux variants
subprocess.call(('xdg-open', "./" + theme_file))
# Load theme file and generate first preview
refresh_theme()
# Create preview window
logger.debug("Opening theme preview window with static data")
viewer = tkinter.Tk()
viewer.title("Turing SysMon Theme Editor")
viewer.iconphoto(True, tkinter.PhotoImage(file="res/icons/monitor-icon-17865/64.png"))
viewer.geometry(str(display.lcd.get_width() + 2 * RGB_LED_MARGIN) + "x" + str(
display.lcd.get_height() + 2 * RGB_LED_MARGIN + 40))
viewer.protocol("WM_DELETE_WINDOW", on_closing)
viewer.call('wm', 'attributes', '.', '-topmost', '1') # Preview window always on top
viewer.config(cursor="cross")
# Display RGB backplate LEDs color as background color
led_color = config.THEME_DATA['display'].get("DISPLAY_RGB_LED", (255, 255, 255))
if isinstance(led_color, str):
led_color = tuple(map(int, led_color.split(', ')))
viewer.configure(bg='#%02x%02x%02x' % led_color)
# Display preview in the window
display_image = ImageTk.PhotoImage(display.lcd.screen_image)
viewer_picture = tkinter.Label(viewer, image=display_image, borderwidth=0)
viewer_picture.place(x=RGB_LED_MARGIN, y=RGB_LED_MARGIN)
# Allow to click on preview to show coordinates and draw zones
viewer_picture.bind("<ButtonPress-1>", on_button1_press)
viewer_picture.bind("<B1-Motion>", on_button1_press_and_drag)
viewer_picture.bind("<ButtonRelease-1>", on_button1_release)
label_coord = tkinter.Label(viewer, text="Click or draw a zone to show coordinates")
label_coord.place(x=0, y=display.lcd.get_height() + 2 * RGB_LED_MARGIN,
width=display.lcd.get_width() + 2 * RGB_LED_MARGIN)
label_info = tkinter.Label(viewer, text="This preview will reload when theme file is updated")
label_info.place(x=0, y=display.lcd.get_height() + 2 * RGB_LED_MARGIN + 20,
width=display.lcd.get_width() + 2 * RGB_LED_MARGIN)
label_zone = tkinter.Label(viewer, bg='#%02x%02x%02x' % tuple(map(lambda x: 255 - x, led_color)))
label_zone.bind("<ButtonRelease-1>", on_zone_click)
viewer.update()
logger.debug("You can now edit the theme file in the editor. When you save your changes, the preview window will "
"update automatically")
# Every time the theme file is modified: reload preview
while True:
if os.path.exists(theme_file) and os.path.getmtime(theme_file) > last_edit_time:
logger.debug("The theme file has been updated, the preview window will refresh")
refresh_theme()
last_edit_time = os.path.getmtime(theme_file)
# Update the preview.png that is in the theme folder
display.lcd.screen_image.save(config.THEME_DATA['PATH'] + "preview.png", "PNG")
# Display new picture
display_image = ImageTk.PhotoImage(display.lcd.screen_image)
viewer_picture.config(image=display_image)
# Refresh RGB backplate LEDs color
led_color = config.THEME_DATA['display'].get("DISPLAY_RGB_LED", (255, 255, 255))
if isinstance(led_color, str):
led_color = tuple(map(int, led_color.split(', ')))
viewer.configure(bg='#%02x%02x%02x' % led_color)
label_zone.configure(bg='#%02x%02x%02x' % tuple(map(lambda x: 255 - x, led_color)))
# Regularly update the viewer window even if content unchanged
viewer.update()
time.sleep(0.1)