Major changes: - TUN mode: tun2socks + xray SOCKS architecture (replaced sing-box) - Hot-swap: node switching keeps TUN alive, only restarts xray - Traffic graph: live QPainter chart on dashboard with detail dialog - Settings: stock qfluentwidgets cards (SpinBox, ColorPicker, PushSettingCard) - Navigation: fixed icon/text overlap in expanded menu - Ctrl+V import, copy link from context menu - System proxy cleanup on exit (atexit safety net) - Build: data/ backup/restore during PyInstaller rebuild - Log throttling in TUN mode to prevent UI freeze Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
45 lines
1.3 KiB
Python
45 lines
1.3 KiB
Python
from __future__ import annotations
|
|
|
|
import socket
|
|
|
|
from PyQt6.QtCore import QObject, QTimer, pyqtSignal
|
|
|
|
|
|
class NetworkMonitor(QObject):
|
|
network_changed = pyqtSignal(str, str)
|
|
|
|
def __init__(self, interval_ms: int = 5000, parent: QObject | None = None):
|
|
super().__init__(parent)
|
|
self._timer = QTimer(self)
|
|
self._timer.setInterval(interval_ms)
|
|
self._timer.timeout.connect(self._check)
|
|
self._last_fingerprint = self._fingerprint()
|
|
|
|
def start(self) -> None:
|
|
self._last_fingerprint = self._fingerprint()
|
|
self._timer.start()
|
|
|
|
def stop(self) -> None:
|
|
self._timer.stop()
|
|
|
|
def _check(self) -> None:
|
|
current = self._fingerprint()
|
|
if current != self._last_fingerprint:
|
|
previous = self._last_fingerprint
|
|
self._last_fingerprint = current
|
|
self.network_changed.emit(previous, current)
|
|
|
|
@staticmethod
|
|
def _fingerprint() -> str:
|
|
local_ip = "0.0.0.0"
|
|
try:
|
|
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
|
|
sock.connect(("8.8.8.8", 80))
|
|
local_ip = sock.getsockname()[0]
|
|
except OSError:
|
|
try:
|
|
local_ip = socket.gethostbyname(socket.gethostname())
|
|
except OSError:
|
|
local_ip = "0.0.0.0"
|
|
|
|
return local_ip
|