Upload files to "/"

This commit is contained in:
2026-09-14 08:17:50 +07:00
commit 28f767a153
2 changed files with 890 additions and 0 deletions
+890
View File
@@ -0,0 +1,890 @@
import sys
import time
import socket
import threading
import subprocess
import json
import os
import urllib.request
import webbrowser
import winreg
import psutil
import ctypes
import ctypes.wintypes
from PyQt6.QtCore import QTimer, Qt, QRectF
from PyQt6.QtWidgets import (
QApplication, QLabel, QVBoxLayout, QWidget, QMenu, QMessageBox,
QColorDialog, QFontDialog, QSystemTrayIcon
)
from PyQt6.QtGui import QFont, QIcon, QPixmap, QPainter, QColor, QPen, QBrush, QRegion, QPainterPath
CONFIG_FILE = "litemon_config.json"
REG_PATH = r"Software\Microsoft\Windows\CurrentVersion\Run"
APP_REG_NAME = "LiteMonWidget"
def run_sys_cmd(cmd):
try:
if hasattr(os, "startfile"):
os.startfile(cmd)
else:
subprocess.Popen(cmd, shell=True)
except Exception:
pass
MENU_STYLE = """
QMenu {
background-color: #1e1e2e;
color: #cdd6f4;
border: 1px solid #45475a;
border-radius: 8px;
padding: 6px;
font-family: 'Segoe UI', sans-serif;
font-size: 12px;
}
QMenu::item {
background-color: transparent;
padding: 6px 24px 6px 12px;
border-radius: 4px;
color: #cdd6f4;
}
QMenu::item:selected {
background-color: #313244;
color: #89b4fa;
}
QMenu::separator {
height: 1px;
background-color: #45475a;
margin: 5px 2px;
}
"""
LANG = {
"ID": {
"layout": "📐 Mode Layout",
"horizontal_1": "Horizontal (1 Baris Memanjang)",
"horizontal_2": "Horizontal (2 Baris - Rapi di Toolbar)",
"vertical": "Vertikal (1 Kolom - Rata Kanan)",
"metrics": "📌 Opsi Tampilan Metrics",
"ref_ip": "🔄 Refresh IP & Jaringan Sekarang",
"net_if": "🌐 Pilih Interface Jaringan",
"basic": "⚙️ Pengaturan Dasar",
"unit": "🔤 Satuan Bandwidth",
"refresh": "⏱️ Auto Refresh Rate",
"autostart": "🚀 Auto-Start (Jalankan Saat Windows Boot)",
"taskbar": "📌 Tampilkan Ikon Taskbar Utama",
"tray": "🔔 Tampilkan Ikon System Tray (Kanan Jam)",
"lang": "🌐 Bahasa / Language",
"style": "🎨 Tampilan & Font",
"preset_size": "📏 Preset Ukuran",
"preset_theme": "🌈 Preset Tema Warna",
"dynamic_color": "⚠️ Dynamic Color (Merah Jika >85%)",
"rounded": "🔲 Sudut Melengkung (Rounded)",
"custom_font": "🔤 Pilih Custom Font...",
"custom_bg": "🎨 Pilih Warna Background...",
"custom_fg": "🎨 Pilih Warna Teks...",
"reset_style": "🔄 Reset Tampilan ke Default",
"opacity": "👁️ Transparansi Window",
"tools": "🛠️ Quick Utilities (Shortcut Windows)",
"about": "️ Tentang LiteMon (v1.0)",
"donate": "☕ Donasi / Dukung Creator",
"exit": "❌ Keluar LiteMon",
"about_title": "Tentang LiteMon Widget",
"free_tag": "🎁 Widget 100% GRATIS (Free Software)",
},
"EN": {
"layout": "📐 Layout Mode",
"horizontal_1": "Horizontal (1 Line Compact)",
"horizontal_2": "Horizontal (2 Rows - Toolbar Style)",
"vertical": "Vertical (1 Column - Aligned)",
"metrics": "📌 Display Metrics Options",
"ref_ip": "🔄 Refresh IP & Network Now",
"net_if": "🌐 Select Network Interface",
"basic": "⚙️ Basic Settings",
"unit": "🔤 Bandwidth Unit",
"refresh": "⏱️ Auto Refresh Rate",
"autostart": "🚀 Auto-Start (Run on Windows Boot)",
"taskbar": "📌 Show Main Taskbar Icon",
"tray": "🔔 Show System Tray Icon (Right/Notification)",
"lang": "🌐 Language / Bahasa",
"style": "🎨 Appearance & Fonts",
"preset_size": "📏 Size Presets",
"preset_theme": "🌈 Theme Presets",
"dynamic_color": "⚠️ Dynamic Colors (Red Alert >85%)",
"rounded": "🔲 Rounded Corners",
"custom_font": "🔤 Choose Custom Font...",
"custom_bg": "🎨 Choose Background Color...",
"custom_fg": "🎨 Choose Text Color...",
"reset_style": "🔄 Reset Appearance to Default",
"opacity": "👁️ Window Opacity",
"tools": "🛠️ Quick Utilities (Windows Shortcuts)",
"about": "️ About LiteMon (v1.0)",
"donate": "☕ Donate / Support Creator",
"exit": "❌ Exit LiteMon",
"about_title": "About LiteMon Widget",
"free_tag": "🎁 Widget is 100% FREE (Open Application)",
}
}
class SystemWidget(QWidget):
def __init__(self):
super().__init__()
self.drag_position = None
self.version = "1.0"
self.current_lang = "ID"
self.layout_mode = "horizontal_2"
self.scale_mode = "normal"
self.current_theme = "Catppuccin Dark"
self.selected_interface = "Auto (Overall)"
self.unit_mode = "bytes"
self.refresh_interval = 1
self.show_in_taskbar = False
self.show_in_tray = True
self.dynamic_color = True
self.is_rounded = True # OPSI ROUNDED BARU
self.custom_font_family = "Consolas"
self.custom_bg = ""
self.custom_fg = ""
self.custom_border = ""
self.show_metrics = {
"cpu": True, "ram": True, "uptime": True, "ping": True,
"up": True, "down": True, "local_ip": False, "public_ip": False
}
self.local_ip = self._get_local_ip()
self.public_ip = "Loading..."
self.ping_ms = 0
self.load_config()
self.last_net = self._get_net_counters()
self.last_time = time.time()
threading.Thread(target=self._async_fetch_loop, daemon=True).start()
self.themes_base = {
"Catppuccin Dark": {"bg": "rgba(30, 30, 46, 0.92)", "fg": "#cdd6f4", "border": "#45475a"},
"Tokyo Night": {"bg": "rgba(26, 27, 38, 0.95)", "fg": "#7aa2f7", "border": "#bb9af7"},
"Dracula Dark": {"bg": "rgba(40, 42, 54, 0.95)", "fg": "#f8f8f2", "border": "#bd93f9"},
"Nord Dark": {"bg": "rgba(46, 52, 64, 0.95)", "fg": "#eceff4", "border": "#88c0d0"},
"Cyberpunk Neon": {"bg": "rgba(13, 2, 33, 0.95)", "fg": "#00f5d4", "border": "#ff007f"},
"Matrix Green": {"bg": "rgba(0, 15, 0, 0.95)", "fg": "#00ff66", "border": "#00ff66"},
"Light Glass": {"bg": "rgba(250, 250, 250, 0.92)", "fg": "#1e1e2e", "border": "#aaaaaa"}
}
self.scales = {
"mini": {"font_size": 10, "radius": 5},
"normal": {"font_size": 11, "radius": 7},
"large": {"font_size": 13, "radius": 9}
}
self.initUI()
self.setup_tray()
self.timer = QTimer()
self.timer.timeout.connect(self.update_metrics)
self.timer.start(self.refresh_interval * 1000)
# Pengunci Z-Order untuk menempel ke Taskbar
self.topmost_timer = QTimer()
self.topmost_timer.timeout.connect(self.force_topmost)
self.topmost_timer.start(1000)
def force_topmost(self):
try:
hwnd = int(self.winId())
user32 = ctypes.windll.user32
taskbar_hwnd = user32.FindWindowW("Shell_TrayWnd", None)
if taskbar_hwnd:
if sys.maxsize > 2**32:
user32.SetWindowLongPtrW.argtypes = [ctypes.wintypes.HWND, ctypes.c_int, ctypes.wintypes.HWND]
user32.SetWindowLongPtrW(hwnd, -8, taskbar_hwnd)
else:
user32.SetWindowLongW.argtypes = [ctypes.wintypes.HWND, ctypes.c_int, ctypes.wintypes.HWND]
user32.SetWindowLongW(hwnd, -8, taskbar_hwnd)
user32.SetWindowPos(hwnd, -1, 0, 0, 0, 0, 0x0001 | 0x0002 | 0x0010)
except Exception:
pass
def apply_shape_mask(self):
scale = self.scales.get(self.scale_mode, self.scales["normal"])
# Set radius 0 jika is_rounded mati (bentuk kotak sempurna)
radius = float(scale["radius"]) if self.is_rounded else 0.0
path = QPainterPath()
path.addRoundedRect(QRectF(self.rect()), radius, radius)
region = QRegion(path.toFillPolygon().toPolygon())
self.setMask(region)
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
theme = self.themes_base.get(self.current_theme, self.themes_base["Catppuccin Dark"])
scale = self.scales.get(self.scale_mode, self.scales["normal"])
bg_str = self.custom_bg if self.custom_bg else theme["bg"]
border_str = self.custom_border if self.custom_border else theme["border"]
bg_color = self._parse_rgba(bg_str) if bg_str.startswith("rgba") else QColor(bg_str)
border_color = self._parse_rgba(border_str) if border_str.startswith("rgba") else QColor(border_str)
painter.setBrush(QBrush(bg_color))
painter.setPen(QPen(border_color, 1.5))
# Jika is_rounded False, set radius gambar jadi 0
radius = float(scale["radius"]) if self.is_rounded else 0.0
rect = QRectF(1.0, 1.0, self.width() - 2.0, self.height() - 2.0)
painter.drawRoundedRect(rect, radius, radius)
def _parse_rgba(self, rgba_str):
try:
vals = rgba_str.replace("rgba(", "").replace(")", "").split(",")
r, g, b = int(vals[0]), int(vals[1]), int(vals[2])
a = int(float(vals[3]) * 255)
return QColor(r, g, b, a)
except Exception:
return QColor(30, 30, 46, 235)
def _get_local_ip(self):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
return ip
except Exception:
return "127.0.0.1"
def _get_uptime(self):
boot_time = psutil.boot_time()
uptime_sec = time.time() - boot_time
m, s = divmod(uptime_sec, 60)
h, m = divmod(m, 60)
d, h = divmod(h, 24)
if d > 0:
return f"{int(d)}d {int(h)}h"
elif h > 0:
return f"{int(h)}h {int(m)}m"
else:
return f"{int(m)}m {int(s)}s"
def _async_fetch_loop(self):
loop_count = 0
while True:
if loop_count % 5 == 0 or self.public_ip in ["Loading...", "Offline"]:
self.local_ip = self._get_local_ip()
try:
req = urllib.request.urlopen("https://api.ipify.org", timeout=2.5)
self.public_ip = req.read().decode('utf-8')
except Exception:
self.public_ip = "Offline"
loop_count += 1
try:
start = time.perf_counter()
s = socket.create_connection(("1.1.1.1", 53), timeout=1.5)
s.close()
self.ping_ms = int((time.perf_counter() - start) * 1000)
except Exception:
self.ping_ms = -1
time.sleep(2.0)
def force_refresh_ip(self):
self.public_ip = "Refreshing..."
def _fetch():
self.local_ip = self._get_local_ip()
try:
req = urllib.request.urlopen("https://api.ipify.org", timeout=3.0)
self.public_ip = req.read().decode('utf-8')
except Exception:
self.public_ip = "Offline"
threading.Thread(target=_fetch, daemon=True).start()
def is_autostart_enabled(self):
try:
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, REG_PATH, 0, winreg.KEY_READ)
winreg.QueryValueEx(key, APP_REG_NAME)
winreg.CloseKey(key)
return True
except Exception:
return False
def toggle_autostart(self):
enabled = not self.is_autostart_enabled()
try:
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, REG_PATH, 0, winreg.KEY_ALL_ACCESS)
if enabled:
script_path = os.path.abspath(sys.argv[0])
cmd = f'"{sys.executable}" "{script_path}"' if script_path.endswith('.py') else f'"{script_path}"'
winreg.SetValueEx(key, APP_REG_NAME, 0, winreg.REG_SZ, cmd)
else:
try:
winreg.DeleteValue(key, APP_REG_NAME)
except FileNotFoundError:
pass
winreg.CloseKey(key)
except Exception:
pass
def setup_tray(self):
pix = QPixmap(32, 32)
pix.fill(Qt.GlobalColor.transparent)
p = QPainter(pix)
p.setBrush(QColor("#89b4fa"))
p.setPen(Qt.PenStyle.NoPen)
p.drawEllipse(2, 2, 28, 28)
p.end()
self.tray_icon = QSystemTrayIcon(QIcon(pix), self)
self.tray_icon.setToolTip("LiteMon v1.0")
self.tray_icon.activated.connect(self.on_tray_click)
if self.show_in_tray:
self.tray_icon.show()
else:
self.tray_icon.hide()
def on_tray_click(self, reason):
if reason == QSystemTrayIcon.ActivationReason.Trigger:
self.showNormal()
self.raise_()
self.activateWindow()
self.force_topmost()
def _get_net_counters(self):
if self.selected_interface == "Auto (Overall)":
return psutil.net_io_counters()
else:
counters = psutil.net_io_counters(pernic=True)
return counters.get(self.selected_interface, psutil.net_io_counters())
def update_window_flags(self):
flags = Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowStaysOnTopHint | Qt.WindowType.Tool
self.setWindowFlags(flags)
self.show()
self.force_topmost()
def initUI(self):
self.update_window_flags()
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
layout = QVBoxLayout()
layout.setContentsMargins(8, 3, 8, 3)
self.label = QLabel("Loading LiteMon v1.0...")
self.label.setWordWrap(False)
self.label.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
layout.addWidget(self.label)
self.setLayout(layout)
self.setWindowTitle("LiteMon v1.0")
self.apply_style()
def apply_style(self):
theme = self.themes_base.get(self.current_theme, self.themes_base["Catppuccin Dark"])
scale = self.scales.get(self.scale_mode, self.scales["normal"])
fg = self.custom_fg if self.custom_fg else theme["fg"]
font_family = self.custom_font_family
if self.layout_mode == "horizontal_2":
v_pad = 2 if self.scale_mode == "mini" else (3 if self.scale_mode == "normal" else 4)
h_pad = 6 if self.scale_mode == "mini" else (8 if self.scale_mode == "normal" else 10)
elif self.layout_mode == "horizontal_1":
v_pad = 3 if self.scale_mode == "mini" else (4 if self.scale_mode == "normal" else 5)
h_pad = 6 if self.scale_mode == "mini" else (8 if self.scale_mode == "normal" else 10)
else:
v_pad = 4 if self.scale_mode == "mini" else (6 if self.scale_mode == "normal" else 8)
h_pad = 6 if self.scale_mode == "mini" else (8 if self.scale_mode == "normal" else 10)
self.layout().setContentsMargins(h_pad, v_pad, h_pad, v_pad)
style = f"""
QLabel {{
background-color: transparent;
color: {fg};
font-family: '{font_family}', Consolas, monospace;
font-size: {scale['font_size']}px;
}}
"""
self.label.setStyleSheet(style)
self.adjustSize()
self.apply_shape_mask()
self.update()
def toggle_rounded(self):
self.is_rounded = not self.is_rounded
self.save_config()
self.apply_style()
def reset_custom_style(self):
self.current_theme = "Catppuccin Dark"
self.scale_mode = "normal"
self.layout_mode = "horizontal_2"
self.is_rounded = True
self.custom_bg = ""
self.custom_fg = ""
self.custom_border = ""
self.custom_font_family = "Consolas"
self.dynamic_color = True
self.setWindowOpacity(1.0)
self.save_config()
self.apply_style()
self.update_metrics()
def format_speed(self, b_sec):
if self.unit_mode == "bits":
bits = b_sec * 8
if bits < 1_000_000:
return f"{bits / 1000:>3.0f} Kbps".replace(" ", "&nbsp;")
else:
return f"{bits / 1_000_000:>4.1f} Mbps".replace(" ", "&nbsp;")
else:
if b_sec < 1048576:
return f"{b_sec / 1024:>3.0f} KB/s".replace(" ", "&nbsp;")
else:
return f"{b_sec / 1048576:>4.1f} MB/s".replace(" ", "&nbsp;")
def _cwrap(self, text, is_warning):
if self.dynamic_color and is_warning:
return f"<span style='color:#ff5555; font-weight:bold;'>{text}</span>"
return text
def update_metrics(self):
cpu = psutil.cpu_percent()
ram = psutil.virtual_memory().percent
uptime_str = self._get_uptime()
net_now = self._get_net_counters()
time_now = time.time()
dt = max(time_now - self.last_time, 0.001)
bytes_sent_sec = (net_now.bytes_sent - self.last_net.bytes_sent) / dt
bytes_recv_sec = (net_now.bytes_recv - self.last_net.bytes_recv) / dt
self.last_net = net_now
self.last_time = time_now
up_str = self.format_speed(bytes_sent_sec)
down_str = self.format_speed(bytes_recv_sec)
ping_str = f"{self.ping_ms:>3}ms".replace(" ", "&nbsp;")
w_cpu = cpu >= 85
w_ram = ram >= 85
w_ping = self.ping_ms >= 100 and self.ping_ms > 0
metrics_map = {
"cpu": ("CPU", f"{cpu:2.0f}%".replace(" ", "&nbsp;"), w_cpu),
"ram": ("RAM", f"{ram:2.0f}%".replace(" ", "&nbsp;"), w_ram),
"uptime": ("UPTIME", uptime_str, False),
"ping": ("PING", ping_str, w_ping),
"up": ("UP", up_str, False),
"down": ("DOWN", down_str, False),
"local_ip": ("L-IP", self.local_ip, False),
"public_ip": ("P-IP", self.public_ip, False)
}
r1_keys = ["cpu", "up", "ping", "local_ip"]
r2_keys = ["ram", "down", "uptime", "public_ip"]
r1_items = [metrics_map[k] for k in r1_keys if self.show_metrics.get(k, False)]
r2_items = [metrics_map[k] for k in r2_keys if self.show_metrics.get(k, False)]
if len(r1_items) == 0 and len(r2_items) > 1:
mid = (len(r2_items) + 1) // 2
r1_items = r2_items[:mid]
r2_items = r2_items[mid:]
elif len(r2_items) == 0 and len(r1_items) > 1:
mid = (len(r1_items) + 1) // 2
r2_items = r1_items[mid:]
r1_items = r1_items[:mid]
if self.layout_mode == "horizontal_2":
max_cols = max(len(r1_items), len(r2_items))
if max_cols > 0:
html = "<table cellpadding='0' cellspacing='0' style='border-collapse: collapse; margin: 0; padding: 0;'>"
html += "<tr>"
for c in range(max_cols):
if c > 0:
html += "<td width='12' align='center' style='color:#585b70;'>|</td>"
if c < len(r1_items):
lbl, val, warn = r1_items[c]
html += f"<td style='padding: 1px 3px; white-space: nowrap;'><b>{lbl}</b>&nbsp;{self._cwrap(val, warn)}</td>"
else:
html += "<td style='padding: 1px 3px;'></td>"
html += "</tr>"
html += "<tr>"
for c in range(max_cols):
if c > 0:
html += "<td width='12' align='center' style='color:#585b70;'>|</td>"
if c < len(r2_items):
lbl, val, warn = r2_items[c]
html += f"<td style='padding: 1px 3px; white-space: nowrap;'><b>{lbl}</b>&nbsp;{self._cwrap(val, warn)}</td>"
else:
html += "<td style='padding: 1px 3px;'></td>"
html += "</tr>"
html += "</table>"
rendered_text = html
else:
rendered_text = "LiteMon v1.0"
elif self.layout_mode == "horizontal_1":
all_active = r1_items + r2_items
if all_active:
items = [f"<b>{lbl}</b>&nbsp;{self._cwrap(val, warn)}" for lbl, val, warn in all_active]
rendered_text = "&nbsp;&nbsp;|&nbsp;&nbsp;".join(items)
else:
rendered_text = "LiteMon v1.0"
else:
all_active = r1_items + r2_items
if all_active:
html = "<table cellpadding='0' cellspacing='1' style='border-collapse: collapse;'>"
for lbl, val, warn in all_active:
html += f"<tr><td align='left' style='padding: 1px 4px;'><b>{lbl}</b></td><td width='12'></td><td align='right' style='padding: 1px 4px;'>{self._cwrap(val, warn)}</td></tr>"
html += "</table>"
rendered_text = html
else:
rendered_text = "LiteMon v1.0"
self.label.setText(rendered_text)
self.adjustSize()
self.apply_shape_mask()
screen = QApplication.primaryScreen()
if screen:
geom = screen.geometry()
new_x = self.x()
new_y = self.y()
if new_x + self.width() > geom.right():
new_x = max(geom.left(), geom.right() - self.width())
if new_y + self.height() > geom.bottom():
new_y = max(geom.top(), geom.bottom() - self.height())
if new_x != self.x() or new_y != self.y():
self.move(new_x, new_y)
self.update()
def show_about(self):
t = LANG[self.current_lang]
msg = QMessageBox(self)
msg.setWindowTitle(t["about_title"])
msg.setStyleSheet("""
QMessageBox { background-color: #1e1e2e; }
QLabel { color: #cdd6f4; font-family: 'Segoe UI', sans-serif; font-size: 13px; }
QPushButton { background-color: #313244; color: #cdd6f4; border: 1px solid #45475a; border-radius: 6px; padding: 6px 16px; font-weight: bold; }
QPushButton:hover { background-color: #45475a; color: #89b4fa; }
""")
msg.setText(
f"<b>LiteMon Widget v{self.version}</b><br>"
f"<font color='#a6e3a1'><b>{t['free_tag']}</b></font><br><br>"
"<b>Creator:</b> @masbudikusuma<br>"
"<b>Website:</b> <a href='https://litemon.masbudi.my.id' style='color:#89b4fa;'>litemon.masbudi.my.id</a><br>"
"<b>Donasi / Support:</b> <a href='https://donate.masbudi.my.id/LiteMon' style='color:#f9e2af;'>donate.masbudi.my.id/LiteMon</a><br><br>"
"Widget pemantau resource CPU, RAM & Traffic Jaringan Windows."
)
msg.exec()
def contextMenuEvent(self, event):
t = LANG[self.current_lang]
menu = QMenu(self)
menu.setStyleSheet(MENU_STYLE)
layout_menu = menu.addMenu(t["layout"])
act_h1 = layout_menu.addAction(f"{'👉 ' if self.layout_mode == 'horizontal_1' else ''}{t['horizontal_1']}")
act_h1.triggered.connect(lambda: self.set_layout_mode("horizontal_1"))
act_h2 = layout_menu.addAction(f"{'👉 ' if self.layout_mode == 'horizontal_2' else ''}{t['horizontal_2']}")
act_h2.triggered.connect(lambda: self.set_layout_mode("horizontal_2"))
act_v = layout_menu.addAction(f"{'👉 ' if self.layout_mode == 'vertical' else ''}{t['vertical']}")
act_v.triggered.connect(lambda: self.set_layout_mode("vertical"))
metrics_menu = menu.addMenu(t["metrics"])
labels_map = {
"cpu": "CPU Usage",
"ram": "RAM Usage",
"uptime": "System Uptime",
"ping": "Ping Latency",
"up": "Upload Speed (UP)",
"down": "Download Speed (DOWN)",
"local_ip": "IP Lokal",
"public_ip": "IP Publik"
}
for key, title in labels_map.items():
act = metrics_menu.addAction(f"{'' if self.show_metrics.get(key, False) else ''} {title}")
act.triggered.connect(lambda checked, k=key: self.toggle_metric(k))
metrics_menu.addSeparator()
act_ref_ip = metrics_menu.addAction(t["ref_ip"])
act_ref_ip.triggered.connect(self.force_refresh_ip)
net_menu = menu.addMenu(t["net_if"])
interfaces = ["Auto (Overall)"] + list(psutil.net_if_addrs().keys())
for iface in interfaces:
is_active = " (Active)" if iface == self.selected_interface else ""
act = net_menu.addAction(f"{iface}{is_active}")
act.triggered.connect(lambda checked, i=iface: self.set_interface(i))
settings_menu = menu.addMenu(t["basic"])
unit_menu = settings_menu.addMenu(t["unit"])
act_bytes = unit_menu.addAction(f"{'👉 ' if self.unit_mode == 'bytes' else ''}Bytes/s (KB/s, MB/s)")
act_bytes.triggered.connect(lambda: self.set_unit_mode("bytes"))
act_bits = unit_menu.addAction(f"{'👉 ' if self.unit_mode == 'bits' else ''}Bits/s (Kbps, Mbps)")
act_bits.triggered.connect(lambda: self.set_unit_mode("bits"))
refresh_menu = settings_menu.addMenu(t["refresh"])
for sec in [1, 2, 3, 5]:
act = refresh_menu.addAction(f"{'👉 ' if self.refresh_interval == sec else ''}{sec} s")
act.triggered.connect(lambda checked, s=sec: self.set_refresh_interval(s))
autostart_act = settings_menu.addAction(f"{'' if self.is_autostart_enabled() else ''} {t['autostart']}")
autostart_act.triggered.connect(self.toggle_autostart)
taskbar_act = settings_menu.addAction(f"{'' if self.show_in_taskbar else ''} {t['taskbar']}")
taskbar_act.triggered.connect(self.toggle_taskbar)
tray_act = settings_menu.addAction(f"{'' if self.show_in_tray else ''} {t['tray']}")
tray_act.triggered.connect(self.toggle_tray)
lang_menu = menu.addMenu(t["lang"])
act_id = lang_menu.addAction(f"{'👉 ' if self.current_lang == 'ID' else ''}🇮🇩 Bahasa Indonesia")
act_id.triggered.connect(lambda: self.set_language("ID"))
act_en = lang_menu.addAction(f"{'👉 ' if self.current_lang == 'EN' else ''}🇬🇧 English")
act_en.triggered.connect(lambda: self.set_language("EN"))
style_menu = menu.addMenu(t["style"])
size_menu = style_menu.addMenu(t["preset_size"])
for s_mode in ["mini", "normal", "large"]:
act = size_menu.addAction(f"{'👉 ' if self.scale_mode == s_mode else ''}{s_mode.capitalize()}")
act.triggered.connect(lambda checked, sm=s_mode: self.set_scale_mode(sm))
theme_menu = style_menu.addMenu(t["preset_theme"])
for t_name in self.themes_base.keys():
act = theme_menu.addAction(f"{'👉 ' if self.current_theme == t_name else ''}{t_name}")
act.triggered.connect(lambda checked, t=t_name: self.set_theme(t))
style_menu.addSeparator()
# OPSI ROUNDED ON/OFF
act_rounded = style_menu.addAction(f"{'' if self.is_rounded else ''} {t['rounded']}")
act_rounded.triggered.connect(self.toggle_rounded)
act_dyn_color = style_menu.addAction(f"{'' if self.dynamic_color else ''} {t['dynamic_color']}")
act_dyn_color.triggered.connect(self.toggle_dynamic_color)
style_menu.addSeparator()
act_font = style_menu.addAction(t["custom_font"])
act_font.triggered.connect(self.pick_custom_font)
act_color_bg = style_menu.addAction(t["custom_bg"])
act_color_bg.triggered.connect(self.pick_custom_bg)
act_color_fg = style_menu.addAction(t["custom_fg"])
act_color_fg.triggered.connect(self.pick_custom_fg)
act_reset_custom = style_menu.addAction(t["reset_style"])
act_reset_custom.triggered.connect(self.reset_custom_style)
opacity_menu = menu.addMenu(t["opacity"])
for op in [1.0, 0.85, 0.70, 0.50]:
act = opacity_menu.addAction(f"{int(op * 100)}%")
act.triggered.connect(lambda checked, o=op: self.setWindowOpacity(o))
tools_menu = menu.addMenu(t["tools"])
tools_menu.addAction("🚀 Task Manager", lambda: run_sys_cmd("taskmgr.exe"))
tools_menu.addAction("📊 Resource Monitor", lambda: run_sys_cmd("resmon.exe"))
tools_menu.addAction("⚙️ System Configuration (msconfig)", lambda: run_sys_cmd("msconfig.exe"))
tools_menu.addAction("🔍 DirectX Diagnostics (dxdiag)", lambda: run_sys_cmd("dxdiag.exe"))
tools_menu.addAction("💻 Device Manager (devmgmt.msc)", lambda: run_sys_cmd("devmgmt.msc"))
tools_menu.addAction("🌐 Network Connections (ncpa.cpl)", lambda: run_sys_cmd("ncpa.cpl"))
tools_menu.addAction("🛠️ Windows Services (services.msc)", lambda: run_sys_cmd("services.msc"))
tools_menu.addAction("💻 Command Prompt (CMD)", lambda: run_sys_cmd("cmd.exe"))
tools_menu.addAction("🎛️ Control Panel", lambda: run_sys_cmd("control.exe"))
menu.addSeparator()
donate_act = menu.addAction(t["donate"])
donate_act.triggered.connect(lambda: webbrowser.open("https://donate.masbudi.my.id/LiteMon"))
about_act = menu.addAction(t["about"])
about_act.triggered.connect(self.show_about)
menu.addSeparator()
exit_action = menu.addAction(t["exit"])
exit_action.triggered.connect(QApplication.instance().quit)
menu.exec(event.globalPos())
def toggle_metric(self, key):
self.show_metrics[key] = not self.show_metrics.get(key, False)
self.save_config()
self.update_metrics()
def set_interface(self, iface):
self.selected_interface = iface
self.last_net = self._get_net_counters()
self.save_config()
self.update_metrics()
def set_layout_mode(self, mode):
self.layout_mode = mode
self.save_config()
self.apply_style()
self.update_metrics()
def set_scale_mode(self, mode):
self.scale_mode = mode
self.save_config()
self.apply_style()
def set_theme(self, theme_name):
self.current_theme = theme_name
self.custom_bg = ""
self.custom_fg = ""
self.custom_border = ""
self.save_config()
self.apply_style()
def toggle_dynamic_color(self):
self.dynamic_color = not self.dynamic_color
self.save_config()
self.update_metrics()
def set_unit_mode(self, mode):
self.unit_mode = mode
self.save_config()
self.update_metrics()
def set_refresh_interval(self, sec):
self.refresh_interval = sec
self.timer.setInterval(sec * 1000)
self.save_config()
def set_language(self, lang_code):
self.current_lang = lang_code
self.save_config()
self.update_metrics()
def toggle_taskbar(self):
self.show_in_taskbar = not self.show_in_taskbar
self.update_window_flags()
self.save_config()
def toggle_tray(self):
self.show_in_tray = not self.show_in_tray
if self.show_in_tray:
self.tray_icon.show()
else:
self.tray_icon.hide()
self.save_config()
def pick_custom_font(self):
font, ok = QFontDialog.getFont(QFont(self.custom_font_family), self)
if ok:
self.custom_font_family = font.family()
self.save_config()
self.apply_style()
def pick_custom_bg(self):
color = QColorDialog.getColor(
parent=self,
title="Pilih Warna Background",
options=QColorDialog.ColorDialogOption.ShowAlphaChannel
)
if color.isValid():
self.custom_bg = f"rgba({color.red()}, {color.green()}, {color.blue()}, {color.alpha() / 255:.2f})"
self.save_config()
self.apply_style()
def pick_custom_fg(self):
color = QColorDialog.getColor(parent=self, title="Pilih Warna Teks")
if color.isValid():
self.custom_fg = color.name()
self.save_config()
self.apply_style()
def save_config(self):
config = {
"version": self.version,
"current_lang": self.current_lang,
"layout_mode": self.layout_mode,
"scale_mode": self.scale_mode,
"current_theme": self.current_theme,
"selected_interface": self.selected_interface,
"unit_mode": self.unit_mode,
"refresh_interval": self.refresh_interval,
"show_in_taskbar": self.show_in_taskbar,
"show_in_tray": self.show_in_tray,
"dynamic_color": self.dynamic_color,
"is_rounded": self.is_rounded,
"custom_font_family": self.custom_font_family,
"custom_bg": self.custom_bg,
"custom_fg": self.custom_fg,
"custom_border": self.custom_border,
"show_metrics": self.show_metrics,
"pos_x": self.x(),
"pos_y": self.y()
}
try:
with open(CONFIG_FILE, "w") as f:
json.dump(config, f, indent=4)
except Exception:
pass
def load_config(self):
if os.path.exists(CONFIG_FILE):
try:
with open(CONFIG_FILE, "r") as f:
c = json.load(f)
self.current_lang = c.get("current_lang", self.current_lang)
self.layout_mode = c.get("layout_mode", self.layout_mode)
if self.layout_mode == "grid_2":
self.layout_mode = "horizontal_2"
self.scale_mode = c.get("scale_mode", self.scale_mode)
self.current_theme = c.get("current_theme", self.current_theme)
self.selected_interface = c.get("selected_interface", self.selected_interface)
self.unit_mode = c.get("unit_mode", self.unit_mode)
self.refresh_interval = c.get("refresh_interval", self.refresh_interval)
self.show_in_taskbar = c.get("show_in_taskbar", self.show_in_taskbar)
self.show_in_tray = c.get("show_in_tray", self.show_in_tray)
self.dynamic_color = c.get("dynamic_color", self.dynamic_color)
self.is_rounded = c.get("is_rounded", self.is_rounded)
self.custom_font_family = c.get("custom_font_family", self.custom_font_family)
self.custom_bg = c.get("custom_bg", self.custom_bg)
self.custom_fg = c.get("custom_fg", self.custom_fg)
self.custom_border = c.get("custom_border", self.custom_border)
self.show_metrics.update(c.get("show_metrics", {}))
if "pos_x" in c and "pos_y" in c:
self.move(c["pos_x"], c["pos_y"])
except Exception:
pass
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
self.drag_position = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
event.accept()
def mouseMoveEvent(self, event):
if event.buttons() == Qt.MouseButton.LeftButton and self.drag_position is not None:
self.move(event.globalPosition().toPoint() - self.drag_position)
event.accept()
def mouseReleaseEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
self.drag_position = None
self.save_config()
event.accept()
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = SystemWidget()
widget.show()
sys.exit(app.exec())