Initial commit: T14 System Settings GUI and Tray application
Features: - CPU governor control (performance/powersave) - Fan level control (L1-L7) with thinkfan integration - Battery charge threshold management (75-80%, 80-90%, 0-100%) - Quick charge to 100% option - Automated battery management (time-based and network-based) - System tray application with auto-start - Privilege elevation via pkexec helper script
This commit is contained in:
345
system-settings-gui.py
Executable file
345
system-settings-gui.py
Executable file
@@ -0,0 +1,345 @@
|
||||
#!/usr/bin/env python3
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
class SystemSettingsGUI:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("T14 System Settings")
|
||||
self.root.geometry("500x600")
|
||||
self.root.configure(bg='#f0f0f0')
|
||||
|
||||
# Title
|
||||
title = tk.Label(root, text="T14 System Settings", font=("Arial", 16, "bold"), bg='#f0f0f0')
|
||||
title.pack(pady=10)
|
||||
|
||||
# CPU Governor Section
|
||||
self.create_section(root, "CPU Governor")
|
||||
self.gov_var = tk.StringVar()
|
||||
|
||||
gov_frame = tk.Frame(root, bg='#f0f0f0')
|
||||
gov_frame.pack(pady=5)
|
||||
tk.Button(gov_frame, text="Performance", width=15, command=lambda: self.set_governor("performance")).pack(side=tk.LEFT, padx=5)
|
||||
tk.Button(gov_frame, text="Powersave", width=15, command=lambda: self.set_governor("powersave")).pack(side=tk.LEFT, padx=5)
|
||||
|
||||
self.gov_label = tk.Label(root, text="", font=("Arial", 10), bg='#f0f0f0', fg='#0066cc')
|
||||
self.gov_label.pack()
|
||||
|
||||
# Fan Level Section
|
||||
self.create_section(root, "Fan Level")
|
||||
fan_frame = tk.Frame(root, bg='#f0f0f0')
|
||||
fan_frame.pack(pady=5)
|
||||
|
||||
for level in range(1, 8):
|
||||
tk.Button(fan_frame, text=f"L{level}", width=5, command=lambda l=level: self.set_fan_level(l)).pack(side=tk.LEFT, padx=2)
|
||||
|
||||
self.fan_label = tk.Label(root, text="", font=("Arial", 10), bg='#f0f0f0', fg='#0066cc')
|
||||
self.fan_label.pack()
|
||||
|
||||
# Battery Thresholds Section
|
||||
self.create_section(root, "Battery Charging - Manual")
|
||||
|
||||
battery_frame = tk.Frame(root, bg='#f0f0f0')
|
||||
battery_frame.pack(pady=5)
|
||||
tk.Button(battery_frame, text="Conservative (75-80%)", width=20, command=lambda: self.set_battery(75, 80)).pack(pady=5)
|
||||
tk.Button(battery_frame, text="Moderate (80-90%)", width=20, command=lambda: self.set_battery(80, 90)).pack(pady=5)
|
||||
tk.Button(battery_frame, text="Full Charge (0-100%)", width=20, command=lambda: self.set_battery(0, 100)).pack(pady=5)
|
||||
tk.Button(battery_frame, text="⚡ Charge to 100% NOW", width=20, bg='#ffaa00', command=self.charge_full_now).pack(pady=5)
|
||||
|
||||
self.battery_label = tk.Label(root, text="", font=("Arial", 10), bg='#f0f0f0', fg='#0066cc')
|
||||
self.battery_label.pack()
|
||||
|
||||
# Automated Battery Management Section
|
||||
self.create_section(root, "Automated Battery Management")
|
||||
|
||||
auto_frame = tk.Frame(root, bg='#f0f0f0')
|
||||
auto_frame.pack(pady=5)
|
||||
tk.Button(auto_frame, text="Setup Time-Based (3:45 PM)", width=25, command=self.setup_time_based).pack(pady=3)
|
||||
tk.Button(auto_frame, text="Setup Network-Based", width=25, command=self.setup_network_based).pack(pady=3)
|
||||
|
||||
self.auto_label = tk.Label(root, text="Not configured", font=("Arial", 9), bg='#f0f0f0', fg='#666666')
|
||||
self.auto_label.pack()
|
||||
|
||||
# Status Section
|
||||
self.create_section(root, "Current Status")
|
||||
self.status_label = tk.Label(root, text="Loading...", font=("Arial", 9), bg='#f0f0f0', justify=tk.LEFT)
|
||||
self.status_label.pack(pady=10)
|
||||
|
||||
# Refresh button
|
||||
tk.Button(root, text="Refresh Status", command=self.refresh_all).pack(pady=10)
|
||||
|
||||
# Start refresh thread
|
||||
self.refresh_all()
|
||||
|
||||
def create_section(self, parent, title):
|
||||
sep = ttk.Separator(parent, orient='horizontal')
|
||||
sep.pack(fill='x', pady=10)
|
||||
label = tk.Label(parent, text=title, font=("Arial", 12, "bold"), bg='#f0f0f0')
|
||||
label.pack()
|
||||
|
||||
def run_command(self, cmd):
|
||||
try:
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=5)
|
||||
return result.stdout.strip()
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
def set_governor(self, gov):
|
||||
self.run_command(f"pkexec /usr/local/bin/system-settings-helper.sh cpu-governor {gov}")
|
||||
self.update_governor_label()
|
||||
|
||||
def update_governor_label(self):
|
||||
gov = self.run_command("cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor")
|
||||
self.gov_label.config(text=f"Current: {gov.upper()}")
|
||||
|
||||
def set_fan_level(self, level):
|
||||
if level == 2:
|
||||
# Level 2 = re-enable automatic control via thinkfan
|
||||
self.run_command("pkexec /usr/local/bin/system-settings-helper.sh fan-start-thinkfan")
|
||||
self.fan_label.config(text="Fan: Automatic (thinkfan managing)")
|
||||
else:
|
||||
# Other levels = manual control, stop thinkfan
|
||||
self.run_command("pkexec /usr/local/bin/system-settings-helper.sh fan-stop-thinkfan")
|
||||
self.run_command(f"pkexec /usr/local/bin/system-settings-helper.sh fan-level {level}")
|
||||
self.update_fan_label()
|
||||
|
||||
def update_fan_label(self):
|
||||
fan_info = self.run_command("grep -E 'speed|level' /proc/acpi/ibm/fan | head -2")
|
||||
self.fan_label.config(text=fan_info)
|
||||
|
||||
def set_battery(self, start, end):
|
||||
# Get current capacity and status
|
||||
capacity = int(self.run_command("cat /sys/class/power_supply/BAT0/capacity"))
|
||||
status = self.run_command("cat /sys/class/power_supply/BAT0/status")
|
||||
|
||||
# If currently charging and new end threshold is below current capacity, stop charging first
|
||||
if status == "Charging" and end < capacity:
|
||||
# Set end threshold below current to stop charging
|
||||
self.run_command(f"pkexec /usr/local/bin/system-settings-helper.sh battery-end {capacity - 1}")
|
||||
import time
|
||||
time.sleep(0.5)
|
||||
|
||||
# Now set the desired thresholds
|
||||
self.run_command(f"pkexec /usr/local/bin/system-settings-helper.sh battery-start {start}")
|
||||
self.run_command(f"pkexec /usr/local/bin/system-settings-helper.sh battery-end {end}")
|
||||
self.update_battery_label()
|
||||
|
||||
def update_battery_label(self):
|
||||
start = self.run_command("cat /sys/class/power_supply/BAT0/charge_control_start_threshold")
|
||||
end = self.run_command("cat /sys/class/power_supply/BAT0/charge_control_end_threshold")
|
||||
capacity = self.run_command("cat /sys/class/power_supply/BAT0/capacity")
|
||||
self.battery_label.config(text=f"Thresholds: {start}-{end}% | Current: {capacity}%")
|
||||
|
||||
def charge_full_now(self):
|
||||
# Set to full range to force charging
|
||||
self.run_command("pkexec /usr/local/bin/system-settings-helper.sh battery-start 0")
|
||||
self.run_command("pkexec /usr/local/bin/system-settings-helper.sh battery-end 100")
|
||||
|
||||
# Wait a moment and check
|
||||
import time
|
||||
time.sleep(0.5)
|
||||
|
||||
capacity = self.run_command("cat /sys/class/power_supply/BAT0/capacity")
|
||||
status = self.run_command("cat /sys/class/power_supply/BAT0/status")
|
||||
|
||||
# Visual feedback
|
||||
self.battery_label.config(text=f"⚡ Charging to 100% | Status: {status} | Current: {capacity}%", fg='#ff6600')
|
||||
|
||||
# Refresh after a second to show updated status
|
||||
self.root.after(2000, self.update_battery_label)
|
||||
|
||||
def refresh_all(self):
|
||||
thread = threading.Thread(target=self._refresh_status)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def _refresh_status(self):
|
||||
temp = self.run_command("cat /sys/class/hwmon/hwmon5/temp1_input | awk '{print int($1/1000)}'")
|
||||
gov = self.run_command("cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor")
|
||||
fan = self.run_command("grep '^speed' /proc/acpi/ibm/fan | awk '{print $NF}'")
|
||||
capacity = self.run_command("cat /sys/class/power_supply/BAT0/capacity")
|
||||
status = self.run_command("cat /sys/class/power_supply/BAT0/status")
|
||||
|
||||
status_text = f"""
|
||||
CPU Temperature: {temp}°C
|
||||
Governor: {gov.upper()}
|
||||
Fan Speed: {fan} RPM
|
||||
|
||||
Battery: {capacity}%
|
||||
Status: {status}
|
||||
"""
|
||||
self.status_label.config(text=status_text)
|
||||
self.update_governor_label()
|
||||
self.update_fan_label()
|
||||
self.update_battery_label()
|
||||
|
||||
def setup_time_based(self):
|
||||
# Create a dialog for time-based setup
|
||||
dialog = tk.Toplevel(self.root)
|
||||
dialog.title("Time-Based Battery Management")
|
||||
dialog.geometry("400x250")
|
||||
dialog.configure(bg='#f0f0f0')
|
||||
|
||||
tk.Label(dialog, text="Time-Based Battery Management", font=("Arial", 12, "bold"), bg='#f0f0f0').pack(pady=10)
|
||||
tk.Label(dialog, text="At 3:45 PM: Switch to 0-100% charging\nBefore 3:45 PM: Keep 75-80% charging", bg='#f0f0f0').pack(pady=10)
|
||||
|
||||
tk.Label(dialog, text="Time threshold (HH:MM):", bg='#f0f0f0').pack()
|
||||
time_entry = tk.Entry(dialog, width=10)
|
||||
time_entry.insert(0, "15:45")
|
||||
time_entry.pack(pady=5)
|
||||
|
||||
def confirm_time_based():
|
||||
threshold_time = time_entry.get()
|
||||
self.install_time_based_management(threshold_time)
|
||||
dialog.destroy()
|
||||
|
||||
tk.Button(dialog, text="Install", command=confirm_time_based).pack(pady=10)
|
||||
|
||||
def setup_network_based(self):
|
||||
# Create a dialog for network-based setup
|
||||
dialog = tk.Toplevel(self.root)
|
||||
dialog.title("Network-Based Battery Management")
|
||||
dialog.geometry("450x250")
|
||||
dialog.configure(bg='#f0f0f0')
|
||||
|
||||
tk.Label(dialog, text="Network-Based Battery Management", font=("Arial", 12, "bold"), bg='#f0f0f0').pack(pady=10)
|
||||
tk.Label(dialog, text="Logic:\n• At office before 3:45 PM → 75-80% (conservative)\n• At office after 3:45 PM → 0-100% (full charge)\n• At home (not on office WiFi) → 75-80% (conservative)",
|
||||
justify=tk.LEFT, bg='#f0f0f0').pack(pady=10)
|
||||
|
||||
tk.Label(dialog, text="Office WiFi SSID:", bg='#f0f0f0').pack()
|
||||
office_entry = tk.Entry(dialog, width=30)
|
||||
office_entry.pack(pady=5)
|
||||
|
||||
tk.Label(dialog, text="Time threshold (HH:MM):", bg='#f0f0f0').pack()
|
||||
time_entry = tk.Entry(dialog, width=10)
|
||||
time_entry.insert(0, "15:45")
|
||||
time_entry.pack(pady=5)
|
||||
|
||||
def confirm_network_based():
|
||||
office_ssid = office_entry.get()
|
||||
threshold_time = time_entry.get()
|
||||
if office_ssid:
|
||||
self.install_network_based_management(office_ssid, threshold_time)
|
||||
dialog.destroy()
|
||||
|
||||
tk.Button(dialog, text="Install", command=confirm_network_based).pack(pady=10)
|
||||
|
||||
def install_time_based_management(self, threshold_time):
|
||||
# Create systemd timer for time-based switching
|
||||
script_content = f"""#!/bin/bash
|
||||
# Switch to full charging at {threshold_time}
|
||||
echo 0 > /sys/class/power_supply/BAT0/charge_control_start_threshold
|
||||
echo 100 > /sys/class/power_supply/BAT0/charge_control_end_threshold
|
||||
echo "[$(date)] Switched to 0-100% charging (time-based)" >> /var/log/battery-thresholds.log
|
||||
"""
|
||||
self.run_command(f"sudo tee /usr/local/bin/battery-charge-full.sh > /dev/null << 'EOF'\n{script_content}EOF")
|
||||
self.run_command("sudo chmod +x /usr/local/bin/battery-charge-full.sh")
|
||||
|
||||
# Create systemd timer
|
||||
timer_unit = f"""[Unit]
|
||||
Description=Switch battery to full charge at {threshold_time}
|
||||
After=network.target
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* {threshold_time}:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
"""
|
||||
self.run_command(f"sudo tee /etc/systemd/system/battery-charge-full.timer > /dev/null << 'EOF'\n{timer_unit}EOF")
|
||||
|
||||
service_unit = """[Unit]
|
||||
Description=Battery full charge service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/battery-charge-full.sh
|
||||
"""
|
||||
self.run_command(f"sudo tee /etc/systemd/system/battery-charge-full.service > /dev/null << 'EOF'\n{service_unit}EOF")
|
||||
|
||||
self.run_command("sudo systemctl daemon-reload")
|
||||
self.run_command("sudo systemctl enable battery-charge-full.timer")
|
||||
self.run_command("sudo systemctl start battery-charge-full.timer")
|
||||
|
||||
self.auto_label.config(text=f"✓ Time-based enabled (switch at {threshold_time})", fg='#009900')
|
||||
|
||||
def install_network_based_management(self, office_ssid, threshold_time):
|
||||
# Create network monitoring script
|
||||
script_content = f"""#!/bin/bash
|
||||
# Network-based battery management
|
||||
|
||||
OFFICE_SSID="{office_ssid}"
|
||||
THRESHOLD_TIME="{threshold_time}"
|
||||
|
||||
CURRENT_SSID=$(nmcli -t -f active,ssid dev wifi | grep '^yes' | cut -d: -f2)
|
||||
CURRENT_TIME=$(date +%H:%M)
|
||||
|
||||
if [ "$CURRENT_SSID" = "$OFFICE_SSID" ]; then
|
||||
# At office - check time
|
||||
if [ "$CURRENT_TIME" \>= "$THRESHOLD_TIME" ]; then
|
||||
# After threshold time - charge to full before leaving
|
||||
echo 0 > /sys/class/power_supply/BAT0/charge_control_start_threshold
|
||||
echo 100 > /sys/class/power_supply/BAT0/charge_control_end_threshold
|
||||
echo "[$(date)] Office WiFi + after $THRESHOLD_TIME - 0-100% charging (full before leaving)" >> /var/log/battery-thresholds.log
|
||||
else
|
||||
# Before threshold time - conservative charging
|
||||
echo 75 > /sys/class/power_supply/BAT0/charge_control_start_threshold
|
||||
echo 80 > /sys/class/power_supply/BAT0/charge_control_end_threshold
|
||||
echo "[$(date)] Office WiFi + before $THRESHOLD_TIME - 75-80% charging (conservative)" >> /var/log/battery-thresholds.log
|
||||
fi
|
||||
else
|
||||
# Not on office WiFi - assume at home, conservative charging
|
||||
echo 75 > /sys/class/power_supply/BAT0/charge_control_start_threshold
|
||||
echo 80 > /sys/class/power_supply/BAT0/charge_control_end_threshold
|
||||
echo "[$(date)] Not on office WiFi (at home) - 75-80% charging (conservative)" >> /var/log/battery-thresholds.log
|
||||
fi
|
||||
"""
|
||||
self.run_command(f"sudo tee /usr/local/bin/battery-network-mgmt.sh > /dev/null << 'EOFSCRIPT'\n{script_content}EOFSCRIPT")
|
||||
self.run_command("sudo chmod +x /usr/local/bin/battery-network-mgmt.sh")
|
||||
|
||||
# Create dispatcher script for NetworkManager
|
||||
self.run_command(f"sudo tee /etc/NetworkManager/dispatcher.d/99-battery-management > /dev/null << 'EOF'\n#!/bin/bash\n/usr/local/bin/battery-network-mgmt.sh\nEOF")
|
||||
self.run_command("sudo chmod +x /etc/NetworkManager/dispatcher.d/99-battery-management")
|
||||
|
||||
# Also create systemd timer to check every 5 minutes as backup
|
||||
timer_content = """[Unit]
|
||||
Description=Battery Network Management Timer
|
||||
After=network.target
|
||||
|
||||
[Timer]
|
||||
OnBootSec=2min
|
||||
OnUnitActiveSec=5min
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
"""
|
||||
self.run_command(f"sudo tee /etc/systemd/system/battery-network-check.timer > /dev/null << 'EOF'\n{timer_content}EOF")
|
||||
|
||||
service_content = """[Unit]
|
||||
Description=Battery Network Management Check
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/battery-network-mgmt.sh
|
||||
"""
|
||||
self.run_command(f"sudo tee /etc/systemd/system/battery-network-check.service > /dev/null << 'EOF'\n{service_content}EOF")
|
||||
|
||||
self.run_command("sudo systemctl daemon-reload")
|
||||
self.run_command("sudo systemctl enable battery-network-check.timer")
|
||||
self.run_command("sudo systemctl start battery-network-check.timer")
|
||||
|
||||
# Run once immediately
|
||||
self.run_command("sudo /usr/local/bin/battery-network-mgmt.sh")
|
||||
|
||||
self.auto_label.config(text=f"✓ Network-based enabled (Office: {office_ssid}, at {threshold_time})", fg='#009900')
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = tk.Tk()
|
||||
app = SystemSettingsGUI(root)
|
||||
root.mainloop()
|
||||
151
system-settings-tray.py
Executable file
151
system-settings-tray.py
Executable file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python3
|
||||
import tkinter as tk
|
||||
from tkinter import Menu
|
||||
import subprocess
|
||||
import threading
|
||||
import sys
|
||||
|
||||
# Try to import pystray for system tray
|
||||
try:
|
||||
from PIL import Image, ImageDraw
|
||||
import pystray
|
||||
HAS_TRAY = True
|
||||
except ImportError:
|
||||
print("Installing required packages for system tray...")
|
||||
subprocess.run([sys.executable, "-m", "pip", "install", "pystray", "pillow"], check=True)
|
||||
from PIL import Image, ImageDraw
|
||||
import pystray
|
||||
HAS_TRAY = True
|
||||
|
||||
class SystemTrayApp:
|
||||
def __init__(self):
|
||||
self.icon = None
|
||||
self.current_status = {}
|
||||
self.update_status()
|
||||
|
||||
def run_command(self, cmd):
|
||||
try:
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=5)
|
||||
return result.stdout.strip()
|
||||
except Exception as e:
|
||||
return ""
|
||||
|
||||
def update_status(self):
|
||||
"""Get current system status"""
|
||||
self.current_status = {
|
||||
'temp': self.run_command("cat /sys/class/hwmon/hwmon5/temp1_input | awk '{print int($1/1000)}'"),
|
||||
'governor': self.run_command("cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"),
|
||||
'battery': self.run_command("cat /sys/class/power_supply/BAT0/capacity"),
|
||||
'charging': self.run_command("cat /sys/class/power_supply/BAT0/status"),
|
||||
'fan': self.run_command("grep '^level' /proc/acpi/ibm/fan | awk '{print $NF}'")
|
||||
}
|
||||
|
||||
def create_image(self):
|
||||
"""Create system tray icon"""
|
||||
# Create a simple icon
|
||||
image = Image.new('RGB', (64, 64), color='black')
|
||||
draw = ImageDraw.Draw(image)
|
||||
draw.ellipse([16, 16, 48, 48], fill='#0066cc', outline='white')
|
||||
return image
|
||||
|
||||
def open_full_gui(self, icon=None, item=None):
|
||||
"""Open the main GUI"""
|
||||
subprocess.Popen(['python3', '/home/rwiegand/Nextcloud/entwicklung/Werkzeuge/battery_management/system-settings-gui.py'])
|
||||
|
||||
def set_governor(self, gov):
|
||||
subprocess.run(f"pkexec /usr/local/bin/system-settings-helper.sh cpu-governor {gov}", shell=True)
|
||||
self.update_status()
|
||||
if self.icon:
|
||||
self.icon.title = self.get_tooltip()
|
||||
|
||||
def set_battery(self, start, end):
|
||||
subprocess.run(f"pkexec /usr/local/bin/system-settings-helper.sh battery-start {start}", shell=True)
|
||||
subprocess.run(f"pkexec /usr/local/bin/system-settings-helper.sh battery-end {end}", shell=True)
|
||||
self.update_status()
|
||||
if self.icon:
|
||||
self.icon.title = self.get_tooltip()
|
||||
|
||||
def charge_now(self):
|
||||
subprocess.run("pkexec /usr/local/bin/system-settings-helper.sh battery-start 0", shell=True)
|
||||
subprocess.run("pkexec /usr/local/bin/system-settings-helper.sh battery-end 100", shell=True)
|
||||
self.update_status()
|
||||
if self.icon:
|
||||
self.icon.title = self.get_tooltip()
|
||||
|
||||
def set_fan(self, level):
|
||||
if level == 2:
|
||||
# Level 2 = automatic control
|
||||
subprocess.run("pkexec /usr/local/bin/system-settings-helper.sh fan-start-thinkfan", shell=True)
|
||||
else:
|
||||
# Other levels = manual control
|
||||
subprocess.run("pkexec /usr/local/bin/system-settings-helper.sh fan-stop-thinkfan", shell=True)
|
||||
subprocess.run(f"pkexec /usr/local/bin/system-settings-helper.sh fan-level {level}", shell=True)
|
||||
self.update_status()
|
||||
if self.icon:
|
||||
self.icon.title = self.get_tooltip()
|
||||
|
||||
def get_tooltip(self):
|
||||
"""Generate tooltip text"""
|
||||
return f"T14: {self.current_status['temp']}°C | {self.current_status['governor']} | {self.current_status['battery']}% {self.current_status['charging']}"
|
||||
|
||||
def create_menu(self):
|
||||
"""Create system tray menu"""
|
||||
return pystray.Menu(
|
||||
pystray.MenuItem("T14 System Settings", pystray.Menu.SEPARATOR),
|
||||
pystray.MenuItem("Open Full GUI", self.open_full_gui, default=True),
|
||||
pystray.MenuItem("CPU Governor", pystray.Menu(
|
||||
pystray.MenuItem("Performance", lambda: self.set_governor("performance")),
|
||||
pystray.MenuItem("Powersave", lambda: self.set_governor("powersave"))
|
||||
)),
|
||||
pystray.MenuItem("Fan Level", pystray.Menu(
|
||||
pystray.MenuItem("Level 1", lambda: self.set_fan(1)),
|
||||
pystray.MenuItem("Level 2", lambda: self.set_fan(2)),
|
||||
pystray.MenuItem("Level 3", lambda: self.set_fan(3)),
|
||||
pystray.MenuItem("Level 4", lambda: self.set_fan(4)),
|
||||
pystray.MenuItem("Level 5", lambda: self.set_fan(5)),
|
||||
pystray.MenuItem("Level 6", lambda: self.set_fan(6)),
|
||||
pystray.MenuItem("Level 7", lambda: self.set_fan(7))
|
||||
)),
|
||||
pystray.MenuItem("Battery", pystray.Menu(
|
||||
pystray.MenuItem("⚡ Charge to 100% NOW", self.charge_now),
|
||||
pystray.MenuItem("Conservative (75-80%)", lambda: self.set_battery(75, 80)),
|
||||
pystray.MenuItem("Moderate (80-90%)", lambda: self.set_battery(80, 90)),
|
||||
pystray.MenuItem("Full (0-100%)", lambda: self.set_battery(0, 100))
|
||||
)),
|
||||
pystray.MenuItem("Refresh Status", lambda: self.refresh_status()),
|
||||
pystray.MenuItem("Quit", self.quit_app)
|
||||
)
|
||||
|
||||
def refresh_status(self):
|
||||
self.update_status()
|
||||
if self.icon:
|
||||
self.icon.title = self.get_tooltip()
|
||||
|
||||
def quit_app(self, icon=None, item=None):
|
||||
if self.icon:
|
||||
self.icon.stop()
|
||||
|
||||
def run(self):
|
||||
"""Run the system tray application"""
|
||||
self.icon = pystray.Icon(
|
||||
"t14_settings",
|
||||
self.create_image(),
|
||||
self.get_tooltip(),
|
||||
self.create_menu()
|
||||
)
|
||||
|
||||
# Update status every 30 seconds
|
||||
def update_loop():
|
||||
import time
|
||||
while True:
|
||||
time.sleep(30)
|
||||
self.refresh_status()
|
||||
|
||||
thread = threading.Thread(target=update_loop, daemon=True)
|
||||
thread.start()
|
||||
|
||||
self.icon.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = SystemTrayApp()
|
||||
app.run()
|
||||
Reference in New Issue
Block a user