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 |
+
|