Upload 5 files
Browse files- build.py +49 -0
- gui.py +283 -0
- hardware.py +142 -0
- main.py +115 -0
- tray.py +830 -0
build.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import PyInstaller.__main__
|
2 |
+
import shutil
|
3 |
+
import os
|
4 |
+
|
5 |
+
# Aufräumen vorheriger Builds
|
6 |
+
for folder in ['build', 'dist', '__pycache__']:
|
7 |
+
if os.path.exists(folder):
|
8 |
+
shutil.rmtree(folder)
|
9 |
+
|
10 |
+
# PyInstaller Optionen ohne --icon
|
11 |
+
opts = [
|
12 |
+
'main.py',
|
13 |
+
'--name=SmartTaskTool_by_Sevenof9',
|
14 |
+
'--onefile',
|
15 |
+
#'--console',
|
16 |
+
'--noconsole',
|
17 |
+
'--windowed',
|
18 |
+
'--clean',
|
19 |
+
'--log-level=WARN',
|
20 |
+
'--add-data=gui.py;.', # gui.py in dist/ kopieren
|
21 |
+
'--add-data=tray.py;.', # tray.py in dist/ kopieren
|
22 |
+
'--add-data=hardware.py;.', # hardware.py in dist/ kopieren
|
23 |
+
'--add-data=restart_helper.py;.', # restart_helper.py in dist/ kopieren
|
24 |
+
'--add-data=DePixelSchmal.otf;.',
|
25 |
+
]
|
26 |
+
|
27 |
+
# Hidden-Imports
|
28 |
+
hidden_imports = [
|
29 |
+
'win32com',
|
30 |
+
'win32com.client',
|
31 |
+
'pythoncom',
|
32 |
+
'pystray._win32',
|
33 |
+
'pystray._base',
|
34 |
+
'wmi',
|
35 |
+
'pynvml',
|
36 |
+
'pystray',
|
37 |
+
'PIL.Image',
|
38 |
+
'PIL.ImageDraw',
|
39 |
+
'PIL.ImageFont',
|
40 |
+
'pythoncom',
|
41 |
+
'wx',
|
42 |
+
'difflib',
|
43 |
+
'psutil'
|
44 |
+
]
|
45 |
+
|
46 |
+
for hidden in hidden_imports:
|
47 |
+
opts.append(f'--hidden-import={hidden}')
|
48 |
+
|
49 |
+
PyInstaller.__main__.run(opts)
|
gui.py
ADDED
@@ -0,0 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# gui.py
|
2 |
+
|
3 |
+
import wx
|
4 |
+
|
5 |
+
class MainFrame(wx.Frame):
|
6 |
+
|
7 |
+
MAX_WIDTH = 1000
|
8 |
+
MAX_HEIGHT = 1000
|
9 |
+
MIN_WIDTH = 300
|
10 |
+
MIN_HEIGHT = 200
|
11 |
+
|
12 |
+
def __init__(self, *args, hardware_info=None, result_queue=None, **kwargs):
|
13 |
+
super().__init__(*args, **kwargs)
|
14 |
+
self.hardware_info = hardware_info or {}
|
15 |
+
self.result_queue = result_queue
|
16 |
+
|
17 |
+
self.selected_components = {
|
18 |
+
'cpu': True,
|
19 |
+
'ram': True,
|
20 |
+
'gpu': True,
|
21 |
+
'network': True,
|
22 |
+
'drives': []
|
23 |
+
}
|
24 |
+
|
25 |
+
# EVT_CLOSE binden
|
26 |
+
self.Bind(wx.EVT_CLOSE, self.on_close)
|
27 |
+
|
28 |
+
# Checkboxen speichern: Dictionary mit Kategorie als Key und Liste von Checkboxes als Value
|
29 |
+
self.checkboxes = {}
|
30 |
+
|
31 |
+
self.panel = wx.Panel(self)
|
32 |
+
vbox = wx.BoxSizer(wx.VERTICAL)
|
33 |
+
|
34 |
+
info = wx.StaticText(self.panel, label="Window will self-close in 10 sec")
|
35 |
+
vbox.Add(info, 0, wx.ALL, 10)
|
36 |
+
|
37 |
+
auto_close_info = wx.StaticText(
|
38 |
+
self.panel,
|
39 |
+
label="Drive monitoring (read/write) every second\nThreshold 2MB"
|
40 |
+
)
|
41 |
+
vbox.Add(auto_close_info, 0, wx.LEFT | wx.BOTTOM, 10)
|
42 |
+
|
43 |
+
tray_info = wx.StaticText(
|
44 |
+
self.panel,
|
45 |
+
label="(Hover over tray icon to see further information)"
|
46 |
+
)
|
47 |
+
vbox.Add(tray_info, 0, wx.LEFT | wx.BOTTOM, 10)
|
48 |
+
|
49 |
+
# CPU-Info: eine Checkbox mit zusammengesetzten Infos
|
50 |
+
if hardware_info.get('cpu_info'):
|
51 |
+
cpu_info = hardware_info['cpu_info']
|
52 |
+
cpu_label = (f"CPU Info: Logical cores: {cpu_info.get('logical_cores')}, "
|
53 |
+
f"Physical cores: {cpu_info.get('physical_cores')}, "
|
54 |
+
f"Frequency: {cpu_info.get('frequency', 'N/A')} MHz")
|
55 |
+
checkbox = wx.CheckBox(self.panel, label=cpu_label)
|
56 |
+
checkbox.SetValue(True)
|
57 |
+
vbox.Add(checkbox, 0, wx.LEFT | wx.BOTTOM, 10)
|
58 |
+
self.checkboxes['cpu'] = [checkbox]
|
59 |
+
|
60 |
+
# RAM-Info: eine Checkbox mit zusammengesetzten Infos
|
61 |
+
if hardware_info.get('ram_info'):
|
62 |
+
ram_info = hardware_info['ram_info']
|
63 |
+
ram_label = (f"RAM Info: Total: {ram_info.get('total_gb', 'N/A')} GB, "
|
64 |
+
f"Available: {ram_info.get('available_gb', 'N/A')} GB")
|
65 |
+
checkbox = wx.CheckBox(self.panel, label=ram_label)
|
66 |
+
checkbox.SetValue(True)
|
67 |
+
vbox.Add(checkbox, 0, wx.LEFT | wx.BOTTOM, 10)
|
68 |
+
self.checkboxes['ram'] = [checkbox]
|
69 |
+
|
70 |
+
# GPU-Info
|
71 |
+
if hardware_info.get('gpu_info'):
|
72 |
+
self.add_section(vbox, "GPU Info:", [
|
73 |
+
f"{gpu['name']} ({gpu['memory_total_mb']} MB VRAM / MaxTemp: {gpu['max_temp']} °C)"
|
74 |
+
for gpu in hardware_info['gpu_info']
|
75 |
+
], category="gpu")
|
76 |
+
|
77 |
+
# Netzwerkadapter
|
78 |
+
if hardware_info.get('network_adapters'):
|
79 |
+
self.add_section(vbox, "Active Network Adapters:", hardware_info['network_adapters'], category="network")
|
80 |
+
|
81 |
+
# Drive-Info
|
82 |
+
if hardware_info.get('drive_map'):
|
83 |
+
self.add_drive_section(vbox)
|
84 |
+
|
85 |
+
# Submit Button
|
86 |
+
self.submit_button = wx.Button(self.panel, label="Submit and Close (10)")
|
87 |
+
self.submit_button.Bind(wx.EVT_BUTTON, self.on_submit)
|
88 |
+
vbox.Add(self.submit_button, 0, wx.ALL | wx.CENTER, 10)
|
89 |
+
|
90 |
+
self.panel.SetSizer(vbox)
|
91 |
+
self.panel.Layout()
|
92 |
+
self.submit_button.SetFocus()
|
93 |
+
self.SetDefaultItem(self.submit_button)
|
94 |
+
|
95 |
+
best_size = self.panel.GetBestSize()
|
96 |
+
width = min(max(best_size.width, self.MIN_WIDTH), self.MAX_WIDTH)
|
97 |
+
height = min(max(best_size.height, self.MIN_HEIGHT), self.MAX_HEIGHT)
|
98 |
+
self.SetClientSize((width, height))
|
99 |
+
self.SetPosition((200,100))
|
100 |
+
self.SetTitle("SmartTaskTool by Sevenof9")
|
101 |
+
|
102 |
+
self.countdown_timer = 10
|
103 |
+
self.timer = wx.Timer(self)
|
104 |
+
self.Bind(wx.EVT_TIMER, self.update_countdown, self.timer)
|
105 |
+
self.timer.Start(1000) # Update every second
|
106 |
+
|
107 |
+
def update_countdown(self, event):
|
108 |
+
self.countdown_timer -= 1
|
109 |
+
if self.countdown_timer <= 0:
|
110 |
+
self.submit_values()
|
111 |
+
self.timer.Stop()
|
112 |
+
else:
|
113 |
+
self.submit_button.SetLabel(f"Submit and Close ({self.countdown_timer})")
|
114 |
+
|
115 |
+
def add_drive_section(self, vbox):
|
116 |
+
drive_map = self.hardware_info.get('drive_map', {})
|
117 |
+
vbox.Add(wx.StaticText(self.panel, label="Detected Drives:"), 0, wx.LEFT | wx.TOP, 10)
|
118 |
+
for dev in sorted(drive_map.keys()):
|
119 |
+
parts = drive_map[dev]
|
120 |
+
for part_info in sorted(parts, key=lambda x: x['letter']):
|
121 |
+
letter = part_info.get('letter', 'N/A')
|
122 |
+
label = part_info.get('label', 'N/A')
|
123 |
+
checkbox_label = f"{dev} - {letter} ({label})"
|
124 |
+
checkbox = wx.CheckBox(self.panel, label=checkbox_label)
|
125 |
+
checkbox.SetValue(True)
|
126 |
+
vbox.Add(checkbox, 0, wx.LEFT | wx.BOTTOM, 10)
|
127 |
+
|
128 |
+
if 'drives' not in self.checkboxes:
|
129 |
+
self.checkboxes['drives'] = []
|
130 |
+
self.checkboxes['drives'].append((dev, letter, checkbox))
|
131 |
+
|
132 |
+
def submit_values(self):
|
133 |
+
selected_components = {
|
134 |
+
'cpu': False,
|
135 |
+
'ram': False,
|
136 |
+
'gpu': False,
|
137 |
+
'network': False,
|
138 |
+
'drives': []
|
139 |
+
}
|
140 |
+
|
141 |
+
checkbox_categories = ['cpu', 'ram', 'gpu', 'network']
|
142 |
+
for category in checkbox_categories:
|
143 |
+
if category in self.checkboxes:
|
144 |
+
selected_components[category] = any(checkbox.GetValue() for checkbox in self.checkboxes[category])
|
145 |
+
|
146 |
+
if 'drives' in self.checkboxes:
|
147 |
+
for dev, part, checkbox in self.checkboxes['drives']:
|
148 |
+
if checkbox.GetValue():
|
149 |
+
selected_components['drives'].append((dev, part))
|
150 |
+
|
151 |
+
# Hardwareinfo ergänzen
|
152 |
+
selected_components['cpu_info'] = self.hardware_info.get('cpu_info', {})
|
153 |
+
selected_components['ram_info'] = self.hardware_info.get('ram_info', {})
|
154 |
+
selected_components['gpu_info'] = self.hardware_info.get('gpu_info', [])
|
155 |
+
selected_components['network_adapters'] = self.hardware_info.get('network_adapters', [])
|
156 |
+
selected_components['drive_map'] = self.hardware_info.get('drive_map', {})
|
157 |
+
|
158 |
+
print("[DEBUG] Auswahl:", selected_components)
|
159 |
+
if self.result_queue:
|
160 |
+
self.result_queue.put(selected_components)
|
161 |
+
self.Close()
|
162 |
+
|
163 |
+
def on_submit(self, event):
|
164 |
+
print("[DEBUG] Submit-Button geklickt")
|
165 |
+
self.submit_values()
|
166 |
+
|
167 |
+
def add_section(self, vbox, title, items, category):
|
168 |
+
vbox.Add(wx.StaticText(self.panel, label=title), 0, wx.LEFT | wx.TOP, 10)
|
169 |
+
for item in items:
|
170 |
+
checkbox = wx.CheckBox(self.panel, label=item)
|
171 |
+
checkbox.SetValue(True)
|
172 |
+
vbox.Add(checkbox, 0, wx.LEFT | wx.BOTTOM, 10)
|
173 |
+
|
174 |
+
if category not in self.checkboxes:
|
175 |
+
self.checkboxes[category] = []
|
176 |
+
|
177 |
+
if category == "drives":
|
178 |
+
# Assuming dev and part are extracted from item
|
179 |
+
parts = item.split(":")
|
180 |
+
if len(parts) == 2:
|
181 |
+
dev, part = parts
|
182 |
+
self.checkboxes[category].append((dev.strip(), part.strip(), checkbox))
|
183 |
+
else:
|
184 |
+
self.checkboxes[category].append(checkbox)
|
185 |
+
|
186 |
+
def get_selected_components(self):
|
187 |
+
selected = {
|
188 |
+
'cpu': False,
|
189 |
+
'ram': False,
|
190 |
+
'gpu': False,
|
191 |
+
'network': False,
|
192 |
+
'drives': []
|
193 |
+
}
|
194 |
+
|
195 |
+
for category, checkboxes in self.checkboxes.items():
|
196 |
+
if category == 'drives':
|
197 |
+
# drives ist eine Liste von Tupeln: (dev, part, checkbox)
|
198 |
+
drive_map = self.hardware_info.get('drive_map', {})
|
199 |
+
selected_drives = []
|
200 |
+
for entry in self.checkboxes:
|
201 |
+
if len(entry) == 3:
|
202 |
+
category, checkbox, item = entry
|
203 |
+
if category == "drives" and checkbox.GetValue():
|
204 |
+
if ":" in item:
|
205 |
+
dev, part = item.split(":", 1)
|
206 |
+
selected_drives.append((dev.strip(), part.strip()))
|
207 |
+
|
208 |
+
selected['drives'] = selected_drives
|
209 |
+
else:
|
210 |
+
# normale checkbox listen
|
211 |
+
if any(checkbox.GetValue() for checkbox in checkboxes):
|
212 |
+
selected[category] = True
|
213 |
+
|
214 |
+
# Hardwareinfos ergänzen
|
215 |
+
selected['cpu_info'] = self.hardware_info.get('cpu_info', {})
|
216 |
+
selected['ram_info'] = self.hardware_info.get('ram_info', {})
|
217 |
+
selected['gpu_info'] = self.hardware_info.get('gpu_info', [])
|
218 |
+
selected['network_adapters'] = self.hardware_info.get('network_adapters', [])
|
219 |
+
selected_components['drive_map'] = self.hardware_info.get('drive_map', {})
|
220 |
+
|
221 |
+
return selected
|
222 |
+
|
223 |
+
def on_close(self, event):
|
224 |
+
print("[DEBUG] Fenster wird geschlossen")
|
225 |
+
|
226 |
+
if hasattr(self, 'timer') and self.timer.IsRunning():
|
227 |
+
self.timer.Stop()
|
228 |
+
|
229 |
+
if self.result_queue:
|
230 |
+
# Leere Auswahl übermitteln
|
231 |
+
selected_components = {
|
232 |
+
'cpu': False,
|
233 |
+
'ram': False,
|
234 |
+
'gpu': False,
|
235 |
+
'network': False,
|
236 |
+
'drives': [],
|
237 |
+
'cpu_info': self.hardware_info.get('cpu_info', {}),
|
238 |
+
'ram_info': self.hardware_info.get('ram_info', {}),
|
239 |
+
'gpu_info': self.hardware_info.get('gpu_info', []),
|
240 |
+
'network_adapters': self.hardware_info.get('network_adapters', []),
|
241 |
+
'drive_map': self.hardware_info.get('drive_map', {})
|
242 |
+
}
|
243 |
+
self.result_queue.put(selected_components)
|
244 |
+
|
245 |
+
self.Destroy()
|
246 |
+
|
247 |
+
app = wx.GetApp()
|
248 |
+
if app:
|
249 |
+
app.ExitMainLoop()
|
250 |
+
|
251 |
+
|
252 |
+
|
253 |
+
|
254 |
+
|
255 |
+
if __name__ == "__main__":
|
256 |
+
app = wx.App(False)
|
257 |
+
|
258 |
+
# Layout
|
259 |
+
hardware_info = {
|
260 |
+
'cpu_info': {
|
261 |
+
'logical_cores': 8,
|
262 |
+
'physical_cores': 4,
|
263 |
+
'frequency': 3200,
|
264 |
+
},
|
265 |
+
'ram_info': {
|
266 |
+
'total_gb': 16,
|
267 |
+
'available_gb': 10
|
268 |
+
},
|
269 |
+
'gpu_info': [
|
270 |
+
{'name': 'NVIDIA GTX 1080', 'memory_total_mb': 8192, 'max_temp': 84}
|
271 |
+
],
|
272 |
+
'network_adapters': ['Ethernet', 'Wi-Fi'],
|
273 |
+
'drive_map': {
|
274 |
+
'Disk0': [{'letter': 'C:', 'label': 'System'}, {'letter': 'D:', 'label': 'Recovery'}],
|
275 |
+
'Disk1': [{'letter': 'E:', 'label': 'Data'}]
|
276 |
+
}
|
277 |
+
|
278 |
+
|
279 |
+
}
|
280 |
+
|
281 |
+
frame = MainFrame(None, hardware_info=hardware_info)
|
282 |
+
frame.Show()
|
283 |
+
app.MainLoop()
|
hardware.py
ADDED
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# hardware.py
|
2 |
+
import psutil
|
3 |
+
import wmi
|
4 |
+
import pynvml
|
5 |
+
import threading
|
6 |
+
import time
|
7 |
+
|
8 |
+
|
9 |
+
def safe_call(func, name):
|
10 |
+
try:
|
11 |
+
return func()
|
12 |
+
except Exception as e:
|
13 |
+
print(f"[WARN] {name} konnte nicht geladen werden: {e}")
|
14 |
+
return None
|
15 |
+
|
16 |
+
|
17 |
+
def get_physical_drives_with_partitions_and_labels():
|
18 |
+
c = wmi.WMI()
|
19 |
+
drive_map = {}
|
20 |
+
|
21 |
+
for disk in c.Win32_DiskDrive():
|
22 |
+
disk_id = disk.DeviceID.split("\\")[-1].upper()
|
23 |
+
if disk_id not in drive_map:
|
24 |
+
drive_map[disk_id] = []
|
25 |
+
|
26 |
+
partitions = disk.associators("Win32_DiskDriveToDiskPartition")
|
27 |
+
for partition in partitions:
|
28 |
+
logical_disks = partition.associators("Win32_LogicalDiskToPartition")
|
29 |
+
for logical_disk in logical_disks:
|
30 |
+
letter = logical_disk.DeviceID.upper().strip()
|
31 |
+
volume_name = logical_disk.VolumeName or "Kein Name"
|
32 |
+
if not any(d["letter"] == letter for d in drive_map[disk_id]):
|
33 |
+
drive_map[disk_id].append({
|
34 |
+
"letter": letter,
|
35 |
+
"label": volume_name
|
36 |
+
})
|
37 |
+
print("[DEBUG] Drive Info:", drive_map)
|
38 |
+
return drive_map
|
39 |
+
|
40 |
+
|
41 |
+
|
42 |
+
def get_cpu_info():
|
43 |
+
cpu_freq = psutil.cpu_freq()
|
44 |
+
cpu_info = {
|
45 |
+
"logical_cores": psutil.cpu_count(logical=True),
|
46 |
+
"physical_cores": psutil.cpu_count(logical=False),
|
47 |
+
"frequency": round(cpu_freq.max) if cpu_freq else None,
|
48 |
+
}
|
49 |
+
return cpu_info
|
50 |
+
|
51 |
+
|
52 |
+
def get_ram_info():
|
53 |
+
mem = psutil.virtual_memory()
|
54 |
+
ram_info = {
|
55 |
+
"total_gb": round(mem.total / (1024 ** 3)),
|
56 |
+
"available_gb": round(mem.available / (1024 ** 3)),
|
57 |
+
}
|
58 |
+
print("[DEBUG] RAM Info:", ram_info)
|
59 |
+
return ram_info
|
60 |
+
|
61 |
+
|
62 |
+
|
63 |
+
def get_gpu_info():
|
64 |
+
gpu_info = []
|
65 |
+
pynvml.nvmlInit()
|
66 |
+
try:
|
67 |
+
device_count = pynvml.nvmlDeviceGetCount()
|
68 |
+
for i in range(device_count):
|
69 |
+
handle = pynvml.nvmlDeviceGetHandleByIndex(i)
|
70 |
+
name_raw = pynvml.nvmlDeviceGetName(handle)
|
71 |
+
name = name_raw.decode() if isinstance(name_raw, bytes) else name_raw
|
72 |
+
mem_info = pynvml.nvmlDeviceGetMemoryInfo(handle)
|
73 |
+
|
74 |
+
try:
|
75 |
+
max_temp = pynvml.nvmlDeviceGetTemperatureThreshold(
|
76 |
+
handle,
|
77 |
+
pynvml.NVML_TEMPERATURE_THRESHOLD_GPU_MAX
|
78 |
+
)
|
79 |
+
except Exception:
|
80 |
+
max_temp = 90
|
81 |
+
|
82 |
+
gpu_info.append({
|
83 |
+
"name": name,
|
84 |
+
"memory_total_mb": int(mem_info.total / 1024**2),
|
85 |
+
"max_temp": max_temp
|
86 |
+
})
|
87 |
+
except Exception as e:
|
88 |
+
print(f"[WARN] GPU-Info konnte nicht geladen werden: {e}")
|
89 |
+
finally:
|
90 |
+
pynvml.nvmlShutdown()
|
91 |
+
print("[DEBUG] RAM Info:", gpu_info)
|
92 |
+
return gpu_info
|
93 |
+
|
94 |
+
|
95 |
+
def get_network_adapters():
|
96 |
+
c = wmi.WMI()
|
97 |
+
adapters = []
|
98 |
+
for nic in c.Win32_NetworkAdapterConfiguration(IPEnabled=True):
|
99 |
+
if hasattr(nic, 'Description'):
|
100 |
+
adapters.append(nic.Description)
|
101 |
+
print("[DEBUG] RAM Info:", adapters)
|
102 |
+
return adapters
|
103 |
+
|
104 |
+
|
105 |
+
def detect_hardware():
|
106 |
+
drive_map = safe_call(get_physical_drives_with_partitions_and_labels, "Laufwerke") or {}
|
107 |
+
cpu_info = safe_call(get_cpu_info, "CPU") or {}
|
108 |
+
gpu_info = safe_call(get_gpu_info, "GPU") or []
|
109 |
+
ram_info = safe_call(get_ram_info, "RAM") or {}
|
110 |
+
network_adapters = safe_call(get_network_adapters, "Netzwerkadapter") or []
|
111 |
+
'''
|
112 |
+
device_partitions = [
|
113 |
+
(dev, part)
|
114 |
+
for dev, parts in drive_map.items()
|
115 |
+
for part in parts
|
116 |
+
]
|
117 |
+
'''
|
118 |
+
return {
|
119 |
+
'cpu_info': cpu_info,
|
120 |
+
'ram_info': ram_info,
|
121 |
+
'gpu_info': gpu_info,
|
122 |
+
'network_adapters': network_adapters,
|
123 |
+
'drive_map': drive_map
|
124 |
+
}
|
125 |
+
|
126 |
+
|
127 |
+
|
128 |
+
def main():
|
129 |
+
print("[INFO] Starte Hardware-Erkennung...\n")
|
130 |
+
hardware_info = detect_hardware()
|
131 |
+
|
132 |
+
# Optional: Ausgabe der erkannten Hardware (kann auskommentiert werden)
|
133 |
+
#for key, value in hardware_info.items():
|
134 |
+
# print(f"[RESULT] {key}: {value}")
|
135 |
+
|
136 |
+
print("\n[INFO] Warte 5 Sekunden...")
|
137 |
+
time.sleep(5)
|
138 |
+
print("[INFO] Hardware Erkennung beendet.")
|
139 |
+
|
140 |
+
if __name__ == "__main__":
|
141 |
+
main()
|
142 |
+
|
main.py
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import wx
|
2 |
+
import sys
|
3 |
+
import threading
|
4 |
+
import queue
|
5 |
+
import time
|
6 |
+
import os
|
7 |
+
from gui import MainFrame
|
8 |
+
from tray import start_tray_monitoring, shutdown_requested
|
9 |
+
from hardware import detect_hardware
|
10 |
+
|
11 |
+
# main.py
|
12 |
+
|
13 |
+
def start_gui_and_get_selection(hardware_info, result_queue):
|
14 |
+
class App(wx.App):
|
15 |
+
def OnInit(self):
|
16 |
+
self.frame = MainFrame(None, hardware_info=hardware_info, result_queue=result_queue)
|
17 |
+
self.frame.Show()
|
18 |
+
return True
|
19 |
+
|
20 |
+
app = App(False)
|
21 |
+
app.MainLoop()
|
22 |
+
|
23 |
+
|
24 |
+
def save_exe_dir_to_meipass():
|
25 |
+
try:
|
26 |
+
# Ermittlung des MEIPASS-Pfads (nur wenn als .exe via PyInstaller gestartet)
|
27 |
+
if hasattr(sys, '_MEIPASS'):
|
28 |
+
meipass_dir = sys._MEIPASS
|
29 |
+
else:
|
30 |
+
print("[WARN] Kein MEIPASS gefunden (nicht als EXE gestartet). Überspringe Speichern.")
|
31 |
+
return
|
32 |
+
|
33 |
+
# Pfad zur laufenden .exe
|
34 |
+
if getattr(sys, 'frozen', False):
|
35 |
+
#exe_dir = os.path.dirname(sys.executable)
|
36 |
+
exe_path = sys.executable # <- vollständiger Pfad zur exe inkl. Dateiname
|
37 |
+
else:
|
38 |
+
#exe_dir = os.path.dirname(os.path.abspath(__file__))
|
39 |
+
exe_path = os.path.abspath(__file__) # <- vollständiger Pfad zur .py Datei
|
40 |
+
|
41 |
+
# Zieldatei im MEIPASS-Verzeichnis
|
42 |
+
output_file = os.path.join(meipass_dir, "startdir.txt")
|
43 |
+
|
44 |
+
with open(output_file, "w", encoding="utf-8") as f:
|
45 |
+
#f.write(f"Startverzeichnis: {exe_dir}\n")
|
46 |
+
f.write(f"{exe_path}\n")
|
47 |
+
|
48 |
+
print(f"[INFO] Startverzeichnis und Startdatei gespeichert in MEIPASS: {output_file}")
|
49 |
+
except Exception as e:
|
50 |
+
print(f"[ERROR] Fehler beim Schreiben der startdir.txt: {e}")
|
51 |
+
|
52 |
+
|
53 |
+
if __name__ == "__main__":
|
54 |
+
try:
|
55 |
+
time.sleep(0.5)
|
56 |
+
|
57 |
+
# Schreibe das Startverzeichnis in den MEIPASS-Ordner
|
58 |
+
save_exe_dir_to_meipass()
|
59 |
+
|
60 |
+
print("[DEBUG] Starte hardware.py...")
|
61 |
+
hardware_info = detect_hardware()
|
62 |
+
print("[DEBUG] hardware.py exit...")
|
63 |
+
|
64 |
+
result_queue = queue.Queue()
|
65 |
+
|
66 |
+
# GUI im MainThread starten!
|
67 |
+
start_gui_and_get_selection(hardware_info, result_queue)
|
68 |
+
|
69 |
+
print("[INFO] GUI beendet.")
|
70 |
+
|
71 |
+
try:
|
72 |
+
selected_components = result_queue.get(timeout=11)
|
73 |
+
tray_should_start = any([
|
74 |
+
selected_components.get('cpu'),
|
75 |
+
selected_components.get('ram'),
|
76 |
+
selected_components.get('gpu'),
|
77 |
+
selected_components.get('network'),
|
78 |
+
bool(selected_components.get('drives'))
|
79 |
+
])
|
80 |
+
except queue.Empty:
|
81 |
+
print("[WARN] Keine Rückgabe durch GUI. Traymonitor wird nicht gestartet.")
|
82 |
+
tray_should_start = False
|
83 |
+
|
84 |
+
if tray_should_start:
|
85 |
+
print("[INFO] Auswahl empfangen:", selected_components)
|
86 |
+
time.sleep(1)
|
87 |
+
|
88 |
+
tray_thread = threading.Thread(
|
89 |
+
target=start_tray_monitoring,
|
90 |
+
args=(hardware_info, selected_components),
|
91 |
+
daemon=False
|
92 |
+
)
|
93 |
+
tray_thread.start()
|
94 |
+
|
95 |
+
# Haupt-Exit-Überwachung
|
96 |
+
while not shutdown_requested.wait(timeout=0.1):
|
97 |
+
pass
|
98 |
+
|
99 |
+
print("[INFO] Tray-Exit wurde erkannt – beende main.py.")
|
100 |
+
sys.exit(0)
|
101 |
+
|
102 |
+
|
103 |
+
print("[INFO] Tray-Monitoring gestartet. GUI ist geschlossen.")
|
104 |
+
tray_thread.join()
|
105 |
+
else:
|
106 |
+
print("[INFO] Programm wird beendet, da keine Auswahl getroffen wurde (GUI geschlossen).")
|
107 |
+
sys.exit(0) # Sauber beenden, wenn kein Traymonitor starten soll
|
108 |
+
|
109 |
+
except KeyboardInterrupt:
|
110 |
+
print("[INFO] Manuell beendet.")
|
111 |
+
sys.exit(0)
|
112 |
+
except Exception as e:
|
113 |
+
print(f"[ERROR] Unerwarteter Fehler: {e}", file=sys.stderr)
|
114 |
+
sys.exit(1)
|
115 |
+
|
tray.py
ADDED
@@ -0,0 +1,830 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# tray.py
|
2 |
+
import threading
|
3 |
+
from threading import Event
|
4 |
+
from pystray import Icon, MenuItem, Menu
|
5 |
+
from PIL import Image, ImageDraw, ImageFont
|
6 |
+
#from functools import partial
|
7 |
+
import os
|
8 |
+
import sys
|
9 |
+
import time
|
10 |
+
import psutil
|
11 |
+
import pythoncom
|
12 |
+
import wmi
|
13 |
+
#import ctypes
|
14 |
+
import pynvml
|
15 |
+
from pynvml import *
|
16 |
+
# from pynvml import nvmlDeviceGetTemperature, nvmlDeviceGetTemperatureThreshold, NVML_TEMPERATURE_GPU, NVML_TEMPERATURE_THRESHOLD_GPU_MAX, nvmlInit, nvmlShutdown, nvmlDeviceGetHandleByIndex, nvmlDeviceGetUtilizationRates, nvmlDeviceGetMemoryInfo, nvmlDeviceGetCount, nvmlDeviceGetName
|
17 |
+
from difflib import get_close_matches
|
18 |
+
import subprocess # Importieren Sie subprocess
|
19 |
+
import tempfile
|
20 |
+
import shutil
|
21 |
+
|
22 |
+
icons = {}
|
23 |
+
current_colors = {}
|
24 |
+
last_colors = {}
|
25 |
+
stop_events = {}
|
26 |
+
color_lock = threading.Lock()
|
27 |
+
icon_lock = threading.Lock()
|
28 |
+
|
29 |
+
|
30 |
+
COLOR_MAP = {
|
31 |
+
"gray": (160, 160, 160, 255),
|
32 |
+
"green": (0, 128, 0, 255),
|
33 |
+
"red": (128, 0, 0, 255),
|
34 |
+
"yellow": (220, 220, 0, 255)
|
35 |
+
}
|
36 |
+
|
37 |
+
|
38 |
+
def managed_thread(target, *args, **kwargs):
|
39 |
+
"""
|
40 |
+
Führt target(*args, **kwargs) in Schleife aus, bis shutdown_event gesetzt ist.
|
41 |
+
Ideal für Monitoring-Loops.
|
42 |
+
"""
|
43 |
+
def wrapper():
|
44 |
+
while not shutdown_event.is_set():
|
45 |
+
target(*args, **kwargs)
|
46 |
+
time.sleep(2.1) # oder individuell einstellbar
|
47 |
+
t = threading.Thread(target=wrapper, daemon=True)
|
48 |
+
thread_refs.append(t)
|
49 |
+
t.start()
|
50 |
+
|
51 |
+
shutdown_event = Event()
|
52 |
+
thread_refs = []
|
53 |
+
shutdown_requested = threading.Event()
|
54 |
+
|
55 |
+
|
56 |
+
def resource_path(relative_path):
|
57 |
+
try:
|
58 |
+
base_path = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__)))
|
59 |
+
full_path = os.path.join(base_path, relative_path)
|
60 |
+
if not os.path.exists(full_path) and hasattr(sys, 'frozen'):
|
61 |
+
raise FileNotFoundError # Trigger fallback
|
62 |
+
return full_path
|
63 |
+
except (AttributeError, FileNotFoundError):
|
64 |
+
try:
|
65 |
+
import pkgutil
|
66 |
+
data = pkgutil.get_data(__name__, relative_path)
|
67 |
+
if data:
|
68 |
+
temp_dir = tempfile.gettempdir()
|
69 |
+
temp_file = os.path.join(temp_dir, os.path.basename(relative_path))
|
70 |
+
with open(temp_file, 'wb') as f:
|
71 |
+
f.write(data)
|
72 |
+
return temp_file
|
73 |
+
except Exception as e:
|
74 |
+
print(f"[ERROR] Fallback-Resource-Pfad fehlgeschlagen: {e}")
|
75 |
+
|
76 |
+
# Last resort: use relative path from package root
|
77 |
+
pkg_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "your_package_name"))
|
78 |
+
fallback_path = os.path.join(pkg_root, relative_path)
|
79 |
+
if os.path.exists(fallback_path):
|
80 |
+
return fallback_path
|
81 |
+
|
82 |
+
raise FileNotFoundError(f"Resource '{relative_path}' not found in any path.")
|
83 |
+
|
84 |
+
|
85 |
+
font_path = resource_path("DePixelSchmal.otf")
|
86 |
+
|
87 |
+
def round_to_nearest_five(value):
|
88 |
+
return int(round(value / 5.0) * 5)
|
89 |
+
|
90 |
+
def get_active_network_adapters():
|
91 |
+
c = wmi.WMI()
|
92 |
+
adapters = []
|
93 |
+
for nic in c.Win32_NetworkAdapterConfiguration(IPEnabled=True):
|
94 |
+
if hasattr(nic, 'Description'):
|
95 |
+
adapters.append(nic.Description)
|
96 |
+
return adapters
|
97 |
+
|
98 |
+
def get_adapter_speeds():
|
99 |
+
c = wmi.WMI()
|
100 |
+
speeds = {}
|
101 |
+
for nic in c.Win32_NetworkAdapter():
|
102 |
+
if nic.NetEnabled and nic.Speed:
|
103 |
+
speeds[nic.Name] = int(nic.Speed) # Bits per second
|
104 |
+
return speeds
|
105 |
+
|
106 |
+
def find_best_match(name, candidates):
|
107 |
+
matches = get_close_matches(name, candidates, n=1, cutoff=0.6)
|
108 |
+
return matches[0] if matches else None
|
109 |
+
|
110 |
+
def create_text_icon(text, color=(255, 255, 255, 255), bg_color=(0, 0, 0, 0)):
|
111 |
+
size = 77
|
112 |
+
image = Image.new("RGBA", (size, size), bg_color)
|
113 |
+
draw = ImageDraw.Draw(image)
|
114 |
+
|
115 |
+
try:
|
116 |
+
font = ImageFont.truetype(font_path, 29)
|
117 |
+
except Exception:
|
118 |
+
font = ImageFont.load_default()
|
119 |
+
|
120 |
+
bbox = draw.textbbox((0, 0), text, font=font) if hasattr(draw, 'textbbox') else font.getsize(text)
|
121 |
+
text_w, text_h = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
122 |
+
text_x = (size - text_w) // 2 - bbox[0]
|
123 |
+
text_y = (size - text_h) // 2 - bbox[1]
|
124 |
+
|
125 |
+
draw.text((text_x, text_y), text, font=font, fill=color)
|
126 |
+
return image
|
127 |
+
|
128 |
+
def format_speed_custom(value_kb):
|
129 |
+
units = ['kB/s', 'MB/s', 'GB/s']
|
130 |
+
speed = value_kb
|
131 |
+
unit_index = 0
|
132 |
+
|
133 |
+
while speed >= 100 and unit_index < len(units) - 1:
|
134 |
+
speed /= 1024
|
135 |
+
unit_index += 1
|
136 |
+
|
137 |
+
if speed < 10:
|
138 |
+
display = f"{speed:.1f}"
|
139 |
+
else:
|
140 |
+
display = f"{min(round(speed), 99)}"
|
141 |
+
|
142 |
+
return f"{display}\n{units[unit_index]}"
|
143 |
+
|
144 |
+
# drive icon
|
145 |
+
def get_color(read_active, write_active, read_mb=0, write_mb=0):
|
146 |
+
if read_mb < 2 and write_mb < 2:
|
147 |
+
return "gray"
|
148 |
+
elif read_mb >= 2 and write_mb >= 2:
|
149 |
+
ratio = read_mb / write_mb if write_mb != 0 else float('inf')
|
150 |
+
if 1/5 <= ratio <= 5:
|
151 |
+
return "yellow"
|
152 |
+
elif write_mb > read_mb:
|
153 |
+
return "red"
|
154 |
+
else:
|
155 |
+
return "green"
|
156 |
+
elif write_mb >= 2:
|
157 |
+
return "red"
|
158 |
+
elif read_mb >= 2:
|
159 |
+
return "green"
|
160 |
+
return "gray"
|
161 |
+
|
162 |
+
|
163 |
+
def _set_icon_color(key, color):
|
164 |
+
with icon_lock:
|
165 |
+
icon_data = icons.get(key)
|
166 |
+
if icon_data:
|
167 |
+
icon = icon_data["icon"]
|
168 |
+
label = icon_data["label"]
|
169 |
+
new_icon = create_icon(COLOR_MAP.get(color, (128, 128, 128)), label)
|
170 |
+
try:
|
171 |
+
icon.icon = new_icon
|
172 |
+
except Exception as e:
|
173 |
+
print(f"[WARN] Could not update icon for {key}: {e}")
|
174 |
+
finally:
|
175 |
+
with color_lock:
|
176 |
+
last_colors[key] = color
|
177 |
+
|
178 |
+
# Drive letter ICONS
|
179 |
+
def create_icon(color_rgb, label):
|
180 |
+
size = 77
|
181 |
+
image = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
182 |
+
draw = ImageDraw.Draw(image)
|
183 |
+
|
184 |
+
draw.ellipse((0, 0, size, size), fill=color_rgb)
|
185 |
+
|
186 |
+
try:
|
187 |
+
font = ImageFont.truetype(font_path, 55)
|
188 |
+
except Exception:
|
189 |
+
font = ImageFont.load_default()
|
190 |
+
|
191 |
+
if hasattr(draw, 'textbbox'):
|
192 |
+
bbox = draw.textbbox((0, 0), label, font=font)
|
193 |
+
else:
|
194 |
+
width, height = font.getsize(label)
|
195 |
+
bbox = (0, 0, width, height)
|
196 |
+
|
197 |
+
text_w = bbox[2] - bbox[0]
|
198 |
+
text_h = bbox[3] - bbox[1]
|
199 |
+
text_x = (size - text_w) / 2
|
200 |
+
text_y = (size - text_h) / 2 - bbox[1]
|
201 |
+
|
202 |
+
brightness = sum(color_rgb) / 3
|
203 |
+
text_color = (0, 0, 0, 255) if brightness > 130 else (255, 255, 255, 255)
|
204 |
+
|
205 |
+
draw.text((text_x, text_y), label, font=font, fill=text_color)
|
206 |
+
return image
|
207 |
+
|
208 |
+
def update_tray_color(key, color):
|
209 |
+
with color_lock:
|
210 |
+
old_color = current_colors.get(key)
|
211 |
+
if old_color == color:
|
212 |
+
return
|
213 |
+
current_colors[key] = color
|
214 |
+
|
215 |
+
|
216 |
+
|
217 |
+
def _icon_updater(key, stop_event):
|
218 |
+
while not stop_event.is_set():
|
219 |
+
with color_lock:
|
220 |
+
color = current_colors.get(key, "gray")
|
221 |
+
last_color = last_colors.get(key)
|
222 |
+
|
223 |
+
if color != last_color:
|
224 |
+
try:
|
225 |
+
_set_icon_color(key, color)
|
226 |
+
except Exception as e:
|
227 |
+
print(f"[WARN] Icon update failed for {key}: {e}")
|
228 |
+
|
229 |
+
stop_event.wait(0.2)
|
230 |
+
|
231 |
+
|
232 |
+
|
233 |
+
# gradient color bar ICON: cpu, ram, gpu, vram, temp
|
234 |
+
def get_gradient_color(percent):
|
235 |
+
"""
|
236 |
+
Gibt eine Farbe für den gegebenen Prozentwert aus einem Regenbogenverlauf zurück:
|
237 |
+
0% -> grün, 50% -> gelb, 100% -> rot
|
238 |
+
"""
|
239 |
+
if percent <= 50:
|
240 |
+
# Grün → Gelb
|
241 |
+
ratio = percent / 50.0
|
242 |
+
r = int(COLOR_MAP["green"][0] + ratio * (COLOR_MAP["yellow"][0] - COLOR_MAP["green"][0]))
|
243 |
+
g = int(COLOR_MAP["green"][1] + ratio * (COLOR_MAP["yellow"][1] - COLOR_MAP["green"][1]))
|
244 |
+
b = int(COLOR_MAP["green"][2] + ratio * (COLOR_MAP["yellow"][2] - COLOR_MAP["green"][2]))
|
245 |
+
else:
|
246 |
+
# Gelb → Rot
|
247 |
+
ratio = (percent - 50) / 50.0
|
248 |
+
r = int(COLOR_MAP["yellow"][0] + ratio * (COLOR_MAP["red"][0] - COLOR_MAP["yellow"][0]))
|
249 |
+
g = int(COLOR_MAP["yellow"][1] + ratio * (COLOR_MAP["red"][1] - COLOR_MAP["yellow"][1]))
|
250 |
+
b = int(COLOR_MAP["yellow"][2] + ratio * (COLOR_MAP["red"][2] - COLOR_MAP["yellow"][2]))
|
251 |
+
|
252 |
+
return (r, g, b, 255)
|
253 |
+
|
254 |
+
|
255 |
+
def create_bar_icon(percent, label, color=None):
|
256 |
+
"""
|
257 |
+
Erstellt ein Balken-Icon mit Regenbogen-Farbverlauf.
|
258 |
+
0% → unten grün, 50% → mitte gelb, 100% → oben rot
|
259 |
+
"""
|
260 |
+
size = 77
|
261 |
+
margin = 4
|
262 |
+
image = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
263 |
+
draw = ImageDraw.Draw(image)
|
264 |
+
|
265 |
+
# Balkengröße
|
266 |
+
bar_width = size - 2 * margin
|
267 |
+
bar_height = int((percent / 100.0) * size)
|
268 |
+
bar_x0 = margin
|
269 |
+
bar_x1 = size - margin
|
270 |
+
bar_y_bottom = size - 1
|
271 |
+
bar_y_top = bar_y_bottom - bar_height + 1
|
272 |
+
|
273 |
+
# Farbverlauf zeichnen
|
274 |
+
for i in range(bar_height):
|
275 |
+
rel_percent = (i / bar_height) * percent
|
276 |
+
line_color = get_gradient_color(rel_percent)
|
277 |
+
y = bar_y_bottom - i
|
278 |
+
draw.line([(bar_x0, y), (bar_x1, y)], fill=line_color)
|
279 |
+
|
280 |
+
# Label zeichnen
|
281 |
+
try:
|
282 |
+
font = ImageFont.truetype(font_path, 30)
|
283 |
+
except:
|
284 |
+
font = ImageFont.load_default()
|
285 |
+
|
286 |
+
draw.text((2, 2), label, font=font, fill=(255, 255, 255, 255))
|
287 |
+
|
288 |
+
return image
|
289 |
+
|
290 |
+
|
291 |
+
|
292 |
+
def _on_quit(icon_inst=None, item=None):
|
293 |
+
print("[INFO] Beenden eingeleitet...")
|
294 |
+
|
295 |
+
# 1. Stop-Flag setzen und Threads sauber beenden
|
296 |
+
shutdown_event.set()
|
297 |
+
for t in thread_refs:
|
298 |
+
t.join(timeout=2.1)
|
299 |
+
print("[INFO] Alle Threads beendet.")
|
300 |
+
|
301 |
+
# 2. Tray-Icons stoppen
|
302 |
+
stop_all_tray_icons()
|
303 |
+
print("[INFO] Alle Trays beendet.")
|
304 |
+
# 3. pynvml sauber beenden
|
305 |
+
try:
|
306 |
+
pynvml.nvmlShutdown()
|
307 |
+
except Exception:
|
308 |
+
pass
|
309 |
+
print("[INFO] Nvidia shut down.")
|
310 |
+
time.sleep(0.1)
|
311 |
+
shutdown_requested.set()
|
312 |
+
|
313 |
+
|
314 |
+
def _on_restart(icon_inst=None, item=None):
|
315 |
+
print("[INFO] Neustart eingeleitet...")
|
316 |
+
|
317 |
+
# 1. Stop-Flag setzen und Threads sauber beenden
|
318 |
+
shutdown_event.set()
|
319 |
+
for t in thread_refs:
|
320 |
+
t.join(timeout=2.1)
|
321 |
+
print("[INFO] Alle Threads beendet.")
|
322 |
+
|
323 |
+
# 2. Tray-Icons stoppen
|
324 |
+
stop_all_tray_icons()
|
325 |
+
|
326 |
+
# 3. pynvml sauber beenden
|
327 |
+
try:
|
328 |
+
pynvml.nvmlShutdown()
|
329 |
+
except Exception:
|
330 |
+
pass
|
331 |
+
|
332 |
+
time.sleep(0.1)
|
333 |
+
|
334 |
+
base_dir = os.path.dirname(os.path.abspath(__file__))
|
335 |
+
startdir_path = os.path.join(base_dir, "startdir.txt")
|
336 |
+
print("[INFO] Start Folder lesen")
|
337 |
+
try:
|
338 |
+
with open(startdir_path, "r", encoding="utf-8") as f:
|
339 |
+
executable_path = f.readline().strip()
|
340 |
+
if not os.path.isfile(executable_path):
|
341 |
+
raise FileNotFoundError(f"EXE nicht gefunden: {executable_path}")
|
342 |
+
except Exception as e:
|
343 |
+
print(f"[ERROR] Fehler beim Lesen der startdir.txt: {e}")
|
344 |
+
if icon_inst:
|
345 |
+
icon_inst.stop()
|
346 |
+
return
|
347 |
+
|
348 |
+
exe_dir = os.path.dirname(executable_path)
|
349 |
+
|
350 |
+
time.sleep(0.1)
|
351 |
+
try:
|
352 |
+
print(f"[INFO] Starte EXE erneut (via subprocess.Popen): {executable_path}")
|
353 |
+
|
354 |
+
env = os.environ.copy()
|
355 |
+
env["RESTART_COUNT"] = str(int(env.get("RESTART_COUNT", "0")) + 1)
|
356 |
+
env["PYINSTALLER_RESET_ENVIRONMENT"] = "1"
|
357 |
+
|
358 |
+
subprocess.Popen(
|
359 |
+
[executable_path],
|
360 |
+
cwd=exe_dir,
|
361 |
+
env=env,
|
362 |
+
close_fds=True,
|
363 |
+
shell=False
|
364 |
+
)
|
365 |
+
|
366 |
+
print(f"[INFO] EXE gestartet!")
|
367 |
+
except Exception as e:
|
368 |
+
print(f"[ERROR] Fehler beim Start via Popen: {e}")
|
369 |
+
return
|
370 |
+
print("[INFO] Old Instance EXIT")
|
371 |
+
time.sleep(0.5)
|
372 |
+
shutdown_requested.set()
|
373 |
+
|
374 |
+
|
375 |
+
|
376 |
+
|
377 |
+
def stop_all_tray_icons():
|
378 |
+
for icon in icons.values():
|
379 |
+
try:
|
380 |
+
if icon["icon"].visible:
|
381 |
+
icon["icon"].visible = False
|
382 |
+
icon["icon"].stop()
|
383 |
+
except Exception as e:
|
384 |
+
print(f"[WARN] Icon-Stop fehlgeschlagen: {e}")
|
385 |
+
for event in stop_events.values():
|
386 |
+
event.set()
|
387 |
+
|
388 |
+
def update_tray_tooltip(key, tooltip_text):
|
389 |
+
icon_data = icons.get(key)
|
390 |
+
if icon_data:
|
391 |
+
icon = icon_data["icon"]
|
392 |
+
try:
|
393 |
+
icon.title = tooltip_text[:127]
|
394 |
+
except Exception as e:
|
395 |
+
print(f"[WARN] Tooltip konnte nicht gesetzt werden für {key}: {e}")
|
396 |
+
|
397 |
+
def update_net_icons(adapter_name, send_kb, recv_kb, selected_components):
|
398 |
+
menu = Menu(
|
399 |
+
MenuItem("Restart", lambda icon_inst, item: _on_restart(icon_inst, item)),
|
400 |
+
MenuItem("Exit", lambda icon_inst, item: _on_quit(icon_inst, item))
|
401 |
+
)
|
402 |
+
|
403 |
+
|
404 |
+
def update_icon(direction, value_kb):
|
405 |
+
if not selected_components['network']:
|
406 |
+
return
|
407 |
+
|
408 |
+
if value_kb < 10:
|
409 |
+
value_kb = 0
|
410 |
+
|
411 |
+
key = f"NET_{adapter_name}, Direction: {direction}"
|
412 |
+
text_with_linebreak = f"{'U ' if direction == 'SEND' else 'D '}{format_speed_custom(value_kb)}"
|
413 |
+
speed_parts = format_speed_custom(value_kb).split("\n")
|
414 |
+
text_no_linebreak = f"{speed_parts[0]} {speed_parts[1]}" if len(speed_parts) == 2 else format_speed_custom(value_kb)
|
415 |
+
|
416 |
+
image = create_text_icon(text_with_linebreak)
|
417 |
+
if key not in icons:
|
418 |
+
icon = Icon(key, image, menu=menu)
|
419 |
+
icons[key] = {"icon": icon, "label": key}
|
420 |
+
print(f"Created Network icon {key}")
|
421 |
+
icon.run_detached()
|
422 |
+
else:
|
423 |
+
icons[key]["icon"].icon = image
|
424 |
+
# Set tooltip using the update_tray_tooltip function
|
425 |
+
tooltip = f"{adapter_name} {'Upload' if direction == 'SEND' else 'Download'}: {text_no_linebreak}"
|
426 |
+
tooltip = tooltip[:127] # falls percpu=True (tooltips max128 zeichen)
|
427 |
+
update_tray_tooltip(key, tooltip)
|
428 |
+
|
429 |
+
threading.Thread(target=update_icon, args=("SEND", send_kb), daemon=True).start()
|
430 |
+
threading.Thread(target=update_icon, args=("RECV", recv_kb), daemon=True).start()
|
431 |
+
|
432 |
+
|
433 |
+
def sort_selected_drives(drive_selections, device_map):
|
434 |
+
items = [(dev, part) for dev, parts in device_map.items() for part in parts]
|
435 |
+
|
436 |
+
# Zugriff auf 'letter' für Sortierung
|
437 |
+
sorted_items = sorted(items, key=lambda x: x[1]['letter'].upper(), reverse=True)
|
438 |
+
|
439 |
+
# Auch drive_selections sortieren anhand des zweiten Elements (Laufwerksbuchstabe)
|
440 |
+
return sorted(drive_selections, key=lambda x: x[1].upper(), reverse=True)
|
441 |
+
|
442 |
+
|
443 |
+
def start_drive_icons(hardware_info, stop_all_tray_icons, device_map, drive_selections):
|
444 |
+
print("Starting tray icons for selected drives...")
|
445 |
+
menu = Menu(
|
446 |
+
MenuItem("Restart", lambda icon_inst, item: _on_restart(icon_inst, item)),
|
447 |
+
MenuItem("Exit", lambda icon_inst, item: _on_quit(icon_inst, item))
|
448 |
+
)
|
449 |
+
|
450 |
+
|
451 |
+
sorted_drive_selections = sort_selected_drives(drive_selections, device_map)
|
452 |
+
|
453 |
+
for index, (dev, part) in enumerate(sorted_drive_selections):
|
454 |
+
icon_label = part.strip(":")
|
455 |
+
icon_key = f"{dev}_{part}"
|
456 |
+
icon_title = f"{index}_SmartTaskTool_{icon_label}"
|
457 |
+
|
458 |
+
image = create_icon(COLOR_MAP["gray"], icon_label)
|
459 |
+
icon = Icon(icon_title, image, menu=menu)
|
460 |
+
icons[icon_key] = {"icon": icon, "label": icon_label}
|
461 |
+
current_colors[icon_key] = "gray"
|
462 |
+
last_colors[icon_key] = None
|
463 |
+
stop_event = threading.Event()
|
464 |
+
stop_events[icon_key] = stop_event
|
465 |
+
|
466 |
+
icon.run_detached()
|
467 |
+
|
468 |
+
threading.Thread(target=_icon_updater, args=(icon_key, stop_event), daemon=True).start()
|
469 |
+
time.sleep(0.2)
|
470 |
+
|
471 |
+
print("Started Icons:")
|
472 |
+
for key, value in icons.items():
|
473 |
+
print(f" {key}: {value}")
|
474 |
+
|
475 |
+
|
476 |
+
def start_tray_monitoring(hardware_info, selected_components):
|
477 |
+
if not isinstance(selected_components, dict):
|
478 |
+
raise ValueError("selected_components muss ein Dictionary sein")
|
479 |
+
|
480 |
+
expected_keys = ['cpu', 'ram', 'gpu', 'network', 'drives']
|
481 |
+
|
482 |
+
# Check if all expected keys are present and have the correct type
|
483 |
+
for key in expected_keys:
|
484 |
+
if key not in selected_components:
|
485 |
+
raise KeyError(f"selected_components fehlt: '{key}'")
|
486 |
+
|
487 |
+
value = selected_components[key]
|
488 |
+
|
489 |
+
if key == 'drives':
|
490 |
+
if not isinstance(value, list):
|
491 |
+
raise TypeError(f"'{key}' muss eine Liste sein.")
|
492 |
+
if not all(isinstance(item, tuple) and len(item) == 2 for item in value):
|
493 |
+
raise ValueError(f"Jedes Element in '{key}' muss ein Tuple mit zwei Elementen sein.")
|
494 |
+
else:
|
495 |
+
if not isinstance(value, bool):
|
496 |
+
raise TypeError(f"'{key}' muss ein Boolean sein.")
|
497 |
+
|
498 |
+
print("Starting tray monitoring...")
|
499 |
+
|
500 |
+
menu = Menu(
|
501 |
+
MenuItem("Restart", lambda icon_inst, item: _on_restart(icon_inst, item)),
|
502 |
+
MenuItem("Exit", lambda icon_inst, item: _on_quit(icon_inst, item))
|
503 |
+
)
|
504 |
+
|
505 |
+
def update_cpu(percent): # Dummy-Wert, tatsächliche Auswertung erfolgt kontrolliert
|
506 |
+
if not selected_components['cpu']:
|
507 |
+
return
|
508 |
+
|
509 |
+
# CPU-Werte kontrolliert ermitteln
|
510 |
+
try:
|
511 |
+
logical = psutil.cpu_count(logical=True)
|
512 |
+
physical = psutil.cpu_count(logical=False)
|
513 |
+
cpu_percentages = psutil.cpu_percent(interval=None, percpu=True)
|
514 |
+
|
515 |
+
num_cores = logical if logical else physical
|
516 |
+
core_usages = cpu_percentages[:num_cores]
|
517 |
+
|
518 |
+
if not core_usages:
|
519 |
+
raise ValueError("Keine CPU-Werte erhalten.")
|
520 |
+
|
521 |
+
avg_cpu_percent = sum(core_usages) / len(core_usages)
|
522 |
+
percent = round_to_nearest_five(avg_cpu_percent)
|
523 |
+
except Exception as e:
|
524 |
+
print(f"[ERROR] CPU-Auswertung fehlgeschlagen: {e}")
|
525 |
+
percent = 0
|
526 |
+
core_usages = []
|
527 |
+
num_cores = 0
|
528 |
+
|
529 |
+
# Icon-Update
|
530 |
+
image = create_bar_icon(percent, "CPU")
|
531 |
+
key = "CPU_USAGE"
|
532 |
+
if key not in icons:
|
533 |
+
icon = Icon(key, image, menu=menu)
|
534 |
+
icons[key] = {"icon": icon, "label": "CPU"}
|
535 |
+
print(f"Created CPU icon")
|
536 |
+
threading.Thread(target=icon.run, daemon=True).start()
|
537 |
+
else:
|
538 |
+
icons[key]["icon"].icon = image
|
539 |
+
|
540 |
+
# Tooltip
|
541 |
+
try:
|
542 |
+
cores_str = " | ".join([f"{int(p)}%" for p in core_usages])
|
543 |
+
tooltip = f"{num_cores} Cores: {cores_str}"
|
544 |
+
tooltip = tooltip[:127]
|
545 |
+
update_tray_tooltip(key, tooltip)
|
546 |
+
except Exception:
|
547 |
+
tooltip = f"CPU {percent}%"
|
548 |
+
|
549 |
+
#icons[key]["icon"].title = tooltip
|
550 |
+
|
551 |
+
|
552 |
+
def update_ram(percent):
|
553 |
+
if not selected_components['ram']:
|
554 |
+
return
|
555 |
+
percent = round_to_nearest_five(percent)
|
556 |
+
image = create_bar_icon(percent, "RAM")
|
557 |
+
key = "RAM_USAGE"
|
558 |
+
if key not in icons:
|
559 |
+
icon = Icon(key, image, menu=menu)
|
560 |
+
icons[key] = {"icon": icon, "label": "RAM"}
|
561 |
+
print(f"Created RAM icon")
|
562 |
+
threading.Thread(target=icon.run, daemon=True).start()
|
563 |
+
else:
|
564 |
+
icons[key]["icon"].icon = image
|
565 |
+
|
566 |
+
try:
|
567 |
+
mem = psutil.virtual_memory()
|
568 |
+
used_gb = round(mem.used / (1024 ** 3))
|
569 |
+
total_gb = round(mem.total / (1024 ** 3))
|
570 |
+
tooltip = f"RAM {used_gb} / {total_gb} GB"
|
571 |
+
update_tray_tooltip(key, tooltip)
|
572 |
+
except Exception:
|
573 |
+
tooltip = f"RAM: {percent}%"
|
574 |
+
#icons[key]["icon"].title = tooltip
|
575 |
+
|
576 |
+
|
577 |
+
def update_gpu_vram_temp(idx, util, mem_used, mem_total, temp, max_temp):
|
578 |
+
if not selected_components['gpu']:
|
579 |
+
return
|
580 |
+
|
581 |
+
# === Temperatur ===
|
582 |
+
key_temp = f"GPU{idx}_TEMP"
|
583 |
+
label_temp = f"T{idx}"
|
584 |
+
temp_rounded = round(temp)
|
585 |
+
# Temperatur in Prozent (dynamisch zu max_temp)
|
586 |
+
clamped = max(35, min(temp, max_temp)) # untere Grenze: 35°C
|
587 |
+
pct = round((clamped - 35) / (max_temp - 35) * 100)
|
588 |
+
image_temp = create_bar_icon(pct, label_temp)
|
589 |
+
|
590 |
+
if key_temp not in icons:
|
591 |
+
try:
|
592 |
+
icon = Icon(key_temp, image_temp, menu=menu)
|
593 |
+
icons[key_temp] = {"icon": icon, "label": label_temp}
|
594 |
+
print(f"Created GPU{idx} temperature icon.")
|
595 |
+
threading.Thread(target=icon.run, daemon=True).start()
|
596 |
+
except Exception as e:
|
597 |
+
print(f"Error creating GPU{idx} temperature icon: {e}")
|
598 |
+
else:
|
599 |
+
icons[key_temp]["icon"].icon = image_temp
|
600 |
+
|
601 |
+
|
602 |
+
tooltip = f"{label_temp}: {temp_rounded} °C / {max_temp} °C"
|
603 |
+
tooltip = tooltip[:127]
|
604 |
+
update_tray_tooltip(key_temp, tooltip)
|
605 |
+
#icons[key_temp]["icon"].title = f"{label_temp}: {temp_rounded} °C / {max_temp} °C"
|
606 |
+
time.sleep(0.01)
|
607 |
+
|
608 |
+
|
609 |
+
# === VRAM-Nutzung ===
|
610 |
+
key_vram = f"VRAM{idx}_USAGE"
|
611 |
+
label_vram = f"VR{idx}"
|
612 |
+
vram_util = round_to_nearest_five(mem_used / mem_total * 100)
|
613 |
+
image_vram = create_bar_icon(vram_util, label_vram)
|
614 |
+
|
615 |
+
if key_vram not in icons:
|
616 |
+
try:
|
617 |
+
icon = Icon(key_vram, image_vram, menu=menu)
|
618 |
+
icons[key_vram] = {"icon": icon, "label": label_vram}
|
619 |
+
print(f"Created VRAM{idx} usage icon.")
|
620 |
+
threading.Thread(target=icon.run, daemon=True).start()
|
621 |
+
except Exception as e:
|
622 |
+
print(f"Error creating VRAM{idx} usage icon: {e}")
|
623 |
+
else:
|
624 |
+
icons[key_vram]["icon"].icon = image_vram
|
625 |
+
|
626 |
+
used_gb = round(mem_used / (1024**3))
|
627 |
+
total_gb = round(mem_total / (1024**3))
|
628 |
+
# Set tooltip using the update_tray_tooltip function
|
629 |
+
tooltip = f"{label_vram}: {used_gb}/{total_gb} GB"
|
630 |
+
tooltip = tooltip[:127]
|
631 |
+
update_tray_tooltip(key_vram, tooltip)
|
632 |
+
|
633 |
+
#icons[key_vram]["icon"].title = f"{label_vram}: {used_gb}/{total_gb} GB"
|
634 |
+
time.sleep(0.01)
|
635 |
+
|
636 |
+
|
637 |
+
# === GPU-Nutzung ===
|
638 |
+
key_gpu = f"GPU{idx}_USAGE"
|
639 |
+
label_gpu = f"GPU{idx}"
|
640 |
+
image_gpu = create_bar_icon(util, label_gpu)
|
641 |
+
|
642 |
+
if key_gpu not in icons:
|
643 |
+
try:
|
644 |
+
icon = Icon(key_gpu, image_gpu, menu=menu)
|
645 |
+
icons[key_gpu] = {"icon": icon, "label": label_gpu}
|
646 |
+
print(f"Created GPU{idx} usage icon.")
|
647 |
+
threading.Thread(target=icon.run, daemon=True).start()
|
648 |
+
except Exception as e:
|
649 |
+
print(f"Error creating GPU{idx} usage icon: {e}")
|
650 |
+
else:
|
651 |
+
icons[key_gpu]["icon"].icon = image_gpu
|
652 |
+
#icons[key_gpu]["icon"].title = f"{label_gpu}: {util}%"
|
653 |
+
|
654 |
+
# Set tooltip using the update_tray_tooltip function
|
655 |
+
tooltip = f"{label_gpu}: {util}%"
|
656 |
+
tooltip = tooltip[:127]
|
657 |
+
update_tray_tooltip(key_gpu, tooltip)
|
658 |
+
|
659 |
+
|
660 |
+
def gpu_monitor():
|
661 |
+
try:
|
662 |
+
pynvml.nvmlInit()
|
663 |
+
gpu_data = []
|
664 |
+
|
665 |
+
for idx in range(pynvml.nvmlDeviceGetCount()):
|
666 |
+
handle = pynvml.nvmlDeviceGetHandleByIndex(idx)
|
667 |
+
try:
|
668 |
+
max_temp = pynvml.nvmlDeviceGetTemperatureThreshold(
|
669 |
+
handle, pynvml.NVML_TEMPERATURE_THRESHOLD_GPU_MAX
|
670 |
+
)
|
671 |
+
except pynvml.NVMLError:
|
672 |
+
max_temp = 90 # Fallback
|
673 |
+
|
674 |
+
gpu_data.append((idx, handle, max_temp))
|
675 |
+
|
676 |
+
while not shutdown_event.is_set():
|
677 |
+
#while True:
|
678 |
+
for idx, handle, max_temp in gpu_data:
|
679 |
+
try:
|
680 |
+
util = pynvml.nvmlDeviceGetUtilizationRates(handle).gpu
|
681 |
+
mem = pynvml.nvmlDeviceGetMemoryInfo(handle)
|
682 |
+
temp = pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU)
|
683 |
+
|
684 |
+
update_gpu_vram_temp(idx, util, mem.used, mem.total, temp, max_temp)
|
685 |
+
|
686 |
+
except pynvml.NVMLError as e:
|
687 |
+
print(f"[ERROR] GPU{idx} Fehler: {e}")
|
688 |
+
|
689 |
+
time.sleep(0.5)
|
690 |
+
|
691 |
+
except pynvml.NVMLError as e:
|
692 |
+
print(f"[ERROR] NVML Initialisierung fehlgeschlagen: {e}")
|
693 |
+
|
694 |
+
|
695 |
+
|
696 |
+
def cpu_monitor():
|
697 |
+
while not shutdown_event.is_set():
|
698 |
+
try:
|
699 |
+
update_cpu(psutil.cpu_percent(interval=None))
|
700 |
+
except Exception as e:
|
701 |
+
print(f"[ERROR] CPU Monitoring Fehler: {e}")
|
702 |
+
time.sleep(0.5)
|
703 |
+
|
704 |
+
def ram_monitor():
|
705 |
+
while not shutdown_event.is_set():
|
706 |
+
try:
|
707 |
+
mem = psutil.virtual_memory()
|
708 |
+
update_ram(mem.percent)
|
709 |
+
except Exception as e:
|
710 |
+
print(f"[ERROR] RAM Monitoring Fehler: {e}")
|
711 |
+
time.sleep(0.5)
|
712 |
+
|
713 |
+
def wmi_monitor(poll_interval=2):
|
714 |
+
# if polinterval change -> set MB/s and kb/s
|
715 |
+
pythoncom.CoInitialize()
|
716 |
+
c = wmi.WMI(namespace="root\\CIMV2")
|
717 |
+
prev_disk_counters = {}
|
718 |
+
prev_net_stats = {}
|
719 |
+
speeds = get_adapter_speeds()
|
720 |
+
|
721 |
+
active_adapters = get_active_network_adapters()
|
722 |
+
adapter_map = {active: find_best_match(active, list(speeds.keys())) for active in active_adapters}
|
723 |
+
|
724 |
+
try:
|
725 |
+
next_time = time.perf_counter()
|
726 |
+
while not shutdown_event.is_set():
|
727 |
+
#while True:
|
728 |
+
try:
|
729 |
+
|
730 |
+
# Drives
|
731 |
+
perf_logical_disks = {
|
732 |
+
disk.Name.upper(): disk
|
733 |
+
for disk in c.Win32_PerfRawData_PerfDisk_LogicalDisk()
|
734 |
+
}
|
735 |
+
|
736 |
+
for dev, part in selected_components.get('drives', []):
|
737 |
+
if not selected_components['drives']:
|
738 |
+
continue
|
739 |
+
|
740 |
+
perf_disk = perf_logical_disks.get(part)
|
741 |
+
if not perf_disk:
|
742 |
+
continue
|
743 |
+
|
744 |
+
read_bytes = int(perf_disk.DiskReadBytesPerSec)
|
745 |
+
write_bytes = int(perf_disk.DiskWriteBytesPerSec)
|
746 |
+
prev_read, prev_write = prev_disk_counters.get(part, (0, 0))
|
747 |
+
read_diff = max(0, read_bytes - prev_read)
|
748 |
+
write_diff = max(0, write_bytes - prev_write)
|
749 |
+
prev_disk_counters[part] = (read_bytes, write_bytes)
|
750 |
+
|
751 |
+
mb_read = read_diff / 1024 / 1024 / 2
|
752 |
+
mb_write = write_diff / 1024 / 1024 / 2
|
753 |
+
|
754 |
+
key = f"{dev}_{part}"
|
755 |
+
color = get_color(mb_read > 2.0, mb_write > 2.0, mb_read, mb_write)
|
756 |
+
|
757 |
+
update_tray_color(key, color)
|
758 |
+
update_tray_tooltip(key, f"{part} R {int(mb_read)} MB/s | W {int(mb_write)} MB/s")
|
759 |
+
|
760 |
+
# Network
|
761 |
+
|
762 |
+
perf_net_ifaces = c.Win32_PerfRawData_Tcpip_NetworkInterface()
|
763 |
+
|
764 |
+
for iface in perf_net_ifaces:
|
765 |
+
iface_name = getattr(iface, 'Name', None)
|
766 |
+
if not iface_name:
|
767 |
+
continue
|
768 |
+
|
769 |
+
adapter_name = find_best_match(iface_name, list(adapter_map.keys()))
|
770 |
+
if not adapter_name or not selected_components['network']:
|
771 |
+
continue
|
772 |
+
|
773 |
+
send_raw = int(iface.BytesSentPersec)
|
774 |
+
recv_raw = int(iface.BytesReceivedPerSec)
|
775 |
+
|
776 |
+
#print(f"Raw Network Data - Send: {send_raw}, Recv: {recv_raw}")
|
777 |
+
|
778 |
+
prev_send, prev_recv = prev_net_stats.get(adapter_name, (0, 0))
|
779 |
+
send_diff = max(0, send_raw - prev_send)
|
780 |
+
recv_diff = max(0, recv_raw - prev_recv)
|
781 |
+
|
782 |
+
#print(f"Network Diff Data - Send: {send_diff}, Recv: {recv_diff}")
|
783 |
+
|
784 |
+
prev_net_stats[adapter_name] = (send_raw, recv_raw)
|
785 |
+
|
786 |
+
# Convert bytes to KB
|
787 |
+
send_kb = send_diff / 1024 / 2
|
788 |
+
recv_kb = recv_diff / 1024 / 2
|
789 |
+
|
790 |
+
#print(f"Converted Network Data - Send: {send_kb} KB/s, Recv: {recv_kb} KB/s")
|
791 |
+
|
792 |
+
update_net_icons(adapter_name, send_kb, recv_kb, selected_components)
|
793 |
+
|
794 |
+
except Exception as e:
|
795 |
+
print(f"[ERROR] Unified WMI Monitor: {e}")
|
796 |
+
|
797 |
+
next_time += poll_interval
|
798 |
+
time.sleep(max(0, next_time - time.perf_counter()))
|
799 |
+
|
800 |
+
finally:
|
801 |
+
pythoncom.CoUninitialize()
|
802 |
+
|
803 |
+
|
804 |
+
|
805 |
+
if selected_components['cpu']:
|
806 |
+
managed_thread(cpu_monitor)
|
807 |
+
time.sleep(0.3)
|
808 |
+
|
809 |
+
if selected_components['ram']:
|
810 |
+
managed_thread(ram_monitor)
|
811 |
+
time.sleep(0.3)
|
812 |
+
|
813 |
+
if selected_components['gpu']:
|
814 |
+
managed_thread(gpu_monitor)
|
815 |
+
time.sleep(0.3)
|
816 |
+
|
817 |
+
if selected_components['network']:
|
818 |
+
managed_thread(wmi_monitor)
|
819 |
+
time.sleep(2)
|
820 |
+
|
821 |
+
# Start tray icons for drives based on user selection
|
822 |
+
if selected_components['drives']:
|
823 |
+
device_map = hardware_info.get('drive_map', {})
|
824 |
+
drive_selections = selected_components['drives']
|
825 |
+
start_drive_icons(hardware_info, stop_all_tray_icons, device_map, drive_selections)
|
826 |
+
|
827 |
+
|
828 |
+
print("All tray monitoring components started.")
|
829 |
+
|
830 |
+
|