Files

138 lines
5.3 KiB
Python

"""Windows 系统代理模式:注册表切换、原状态保存与异常退出恢复。"""
from __future__ import annotations
import ctypes
import re
from typing import Callable
PROXY_REG = r"HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings"
BACKUP_KEY = "prev_proxy_state"
_VALUE_NAMES = ("ProxyEnable", "ProxyServer", "ProxyOverride", "AutoConfigURL")
def is_our_pac_url(value: str) -> bool:
return bool(re.fullmatch(r"http://127\.0\.0\.1:\d+/proxy\.pac", str(value).strip()))
def notify_proxy_change() -> None:
"""通知 WinINET 和顶层窗口重新读取系统代理。"""
try:
ctypes.windll.Wininet.InternetSetOptionW(None, 39, None, 0)
ctypes.windll.Wininet.InternetSetOptionW(None, 37, None, 0)
result = ctypes.c_ulong()
ctypes.windll.user32.SendMessageTimeoutW(
0xFFFF, 0x001A, 0, ctypes.c_wchar_p("InternetSettings"),
0x0002, 5000, ctypes.byref(result))
except Exception:
pass
class SystemProxyMode:
def __init__(self, *, run: Callable, settings_load: Callable,
settings_save: Callable, log: Callable,
endpoint: Callable[[], tuple[str, int]],
proxy_override: Callable[[bool], str]):
self.run = run
self.settings_load = settings_load
self.settings_save = settings_save
self.log = log
self.endpoint = endpoint
self.proxy_override = proxy_override
self._previous = None
def reg_query(self, name: str):
result = self.run(["reg", "query", PROXY_REG, "/v", name], warn=False)
if result.returncode:
return None
for line in (result.stdout or "").splitlines():
parts = line.strip().split(None, 2)
if len(parts) >= 3 and parts[0].lower() == name.lower():
return parts[1], parts[2]
return None
def _backup(self):
if self._previous is not None:
return
state = {name: list(value) if (value := self.reg_query(name)) else None
for name in _VALUE_NAMES}
self._previous = state
try:
self.settings_save({**self.settings_load(), BACKUP_KEY: state})
except Exception:
pass
def _restore(self):
state = self._previous
if state is None:
state = self.settings_load().get(BACKUP_KEY)
if isinstance(state, dict):
for name in _VALUE_NAMES:
value = state.get(name)
if value:
self.run(["reg", "add", PROXY_REG, "/v", name, "/t", value[0],
"/d", value[1], "/f"], warn=False)
else:
self.run(["reg", "delete", PROXY_REG, "/v", name, "/f"], warn=False)
self.log("已恢复开启代理前的系统代理设置")
else:
for name in ("ProxyServer", "ProxyOverride", "AutoConfigURL"):
self.run(["reg", "delete", PROXY_REG, "/v", name, "/f"], warn=False)
self.run(["reg", "add", PROXY_REG, "/v", "ProxyEnable", "/t",
"REG_DWORD", "/d", "0", "/f"])
self._previous = None
try:
data = self.settings_load()
if BACKUP_KEY in data:
del data[BACKUP_KEY]
self.settings_save(data)
except Exception:
pass
def enable(self, pac_url=None, bypass_lan=False):
self._backup()
if pac_url:
self.run(["reg", "add", PROXY_REG, "/v", "ProxyEnable", "/t",
"REG_DWORD", "/d", "0", "/f"])
for name in ("ProxyServer", "ProxyOverride"):
self.run(["reg", "delete", PROXY_REG, "/v", name, "/f"], warn=False)
self.run(["reg", "add", PROXY_REG, "/v", "AutoConfigURL", "/t",
"REG_SZ", "/d", pac_url, "/f"])
self.log("系统代理已设置 (PAC)")
else:
host, port = self.endpoint()
address = f"{host}:{port}"
self.run(["reg", "add", PROXY_REG, "/v", "ProxyEnable", "/t",
"REG_DWORD", "/d", "1", "/f"])
self.run(["reg", "add", PROXY_REG, "/v", "ProxyServer", "/t",
"REG_SZ", "/d", address, "/f"])
self.run(["reg", "add", PROXY_REG, "/v", "ProxyOverride", "/t",
"REG_SZ", "/d", self.proxy_override(bypass_lan), "/f"])
self.log(f"系统代理已设置: {address}")
notify_proxy_change()
def disable(self):
self._restore()
notify_proxy_change()
def is_on(self):
host, port = self.endpoint()
enabled = self.reg_query("ProxyEnable")
try:
fixed_enabled = bool(enabled and int(str(enabled[1]).strip(), 0) == 1)
except (TypeError, ValueError):
fixed_enabled = False
if fixed_enabled:
server = self.reg_query("ProxyServer")
return bool(server and (server[1] or "").strip() == f"{host}:{port}")
pac = self.reg_query("AutoConfigURL")
return bool(pac) and is_our_pac_url(pac[1] or "")
def is_stale(self):
host, port = self.endpoint()
server = self.reg_query("ProxyServer")
pac = self.reg_query("AutoConfigURL")
return (bool(server) and (server[1] or "").strip() == f"{host}:{port}") or \
(bool(pac) and is_our_pac_url(pac[1] or ""))