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(" ", " ") else: return f"{bits / 1_000_000:>4.1f} Mbps".replace(" ", " ") else: if b_sec < 1048576: return f"{b_sec / 1024:>3.0f} KB/s".replace(" ", " ") else: return f"{b_sec / 1048576:>4.1f} MB/s".replace(" ", " ") def _cwrap(self, text, is_warning): if self.dynamic_color and is_warning: return f"{text}" 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(" ", " ") 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(" ", " "), w_cpu), "ram": ("RAM", f"{ram:2.0f}%".replace(" ", " "), 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 = "" html += "" for c in range(max_cols): if c > 0: html += "" if c < len(r1_items): lbl, val, warn = r1_items[c] html += f"" else: html += "" html += "" html += "" for c in range(max_cols): if c > 0: html += "" if c < len(r2_items): lbl, val, warn = r2_items[c] html += f"" else: html += "" html += "" html += "
|{lbl} {self._cwrap(val, warn)}
|{lbl} {self._cwrap(val, warn)}
" 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"{lbl} {self._cwrap(val, warn)}" for lbl, val, warn in all_active] rendered_text = "  |  ".join(items) else: rendered_text = "LiteMon v1.0" else: all_active = r1_items + r2_items if all_active: html = "" for lbl, val, warn in all_active: html += f"" html += "
{lbl}{self._cwrap(val, warn)}
" 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"LiteMon Widget v{self.version}
" f"{t['free_tag']}

" "Creator: @masbudikusuma
" "Website: litemon.masbudi.my.id
" "Donasi / Support: donate.masbudi.my.id/LiteMon

" "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())