python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python系统代理智能开关

基于Python实现Windows系统代理智能开关工具

作者:创客白泽

在日常开发和学习中,我们经常需要使用代理访问国际资源,本文将基于PyQt5开发一个可视化代理开关工具,一键解决使用代理后无法访问浏览器网页问题

概述:代理访问的痛点与解决方案

在日常开发和学习中,我们经常需要使用代理访问国际资源。但Windows系统代理设置存在几个典型痛点:

本文介绍一款基于PyQt5开发的可视化代理开关工具,具有以下核心优势:

功能特性

核心功能

增强特性

效果展示

图1:工具运行效果(开启/关闭状态对比对应的网络代理开启变化)

开发环境准备

基础环境

安装依赖

pip install pyqt5 win32ctypes ctypes

使用说明

基础使用

高级配置

修改代理服务器地址(需编辑注册表):

# 在setProxyState方法中添加以下代码
proxy_server = "127.0.0.1:1080"
winreg.SetValueEx(key, "ProxyServer", 0, winreg.REG_SZ, proxy_server)

代码深度解析

1. 自定义SwitchButton组件

class SwitchButton(QWidget):
    toggled = pyqtSignal(bool)
    
    def paintEvent(self, event):
        # 使用QLinearGradient实现渐变色效果
        gradient = QLinearGradient(bg_rect.topLeft(), bg_rect.bottomRight())
        if self._checked:
            gradient.setColorAt(0, QColor("#66BB6A"))  # 开启状态渐变色
        else:
            gradient.setColorAt(0, QColor("#E0E0E0"))  # 关闭状态渐变色

关键点分析:

2. 注册表操作核心逻辑

def setProxyState(self, enabled):
    try:
        with winreg.OpenKey(winreg.HKEY_CURRENT_USER, 
                          r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", 
                          0, winreg.KEY_WRITE) as key:
            winreg.SetValueEx(key, "ProxyEnable", 0, winreg.REG_DWORD, 1 if enabled else 0)
        
        # 关键!通知系统设置变更
        self.notifySystem()

注册表路径解析:

HKEY_CURRENT_USER
└── Software
    └── Microsoft
        └── Windows
            └── CurrentVersion
                └── Internet Settings
                    ├── ProxyEnable (DWORD)
                    └── ProxyServer (STRING)

3. 系统通知机制

def notifySystem(self):
    INTERNET_OPTION_SETTINGS_CHANGED = 39
    INTERNET_OPTION_REFRESH = 37
    ctypes.windll.Wininet.InternetSetOptionW(0, INTERNET_OPTION_SETTINGS_CHANGED, 0, 0)
    ctypes.windll.Wininet.InternetSetOptionW(0, INTERNET_OPTION_REFRESH, 0, 0)

技术原理:

调用WinINet API通知系统代理设置已变更,避免需要重启浏览器才能生效

完整源码下载

import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                             QHBoxLayout, QLabel, QPushButton, QFrame)
from PyQt5.QtCore import (Qt, QPropertyAnimation, QEasingCurve, 
                         QPoint, pyqtProperty, pyqtSignal)
from PyQt5.QtGui import QColor, QFont, QPainter, QBrush, QPen, QLinearGradient
import winreg
import ctypes
import platform

class SwitchButton(QWidget):
    toggled = pyqtSignal(bool)
    
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFixedSize(80, 40)
        self._checked = False
        self._circle_position = QPoint(5, 5)  # 明确初始化位置
        self._bg_color = QColor("#cccccc")
        self._circle_color = QColor("#ffffff")
        self._active_color = QColor("#4CAF50")
        self._animation = QPropertyAnimation(self, b"circle_position")
        self._animation.setDuration(200)
        self._animation.setEasingCurve(QEasingCurve.OutQuad)
        
    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        
        # 绘制背景
        bg_rect = self.rect()
        gradient = QLinearGradient(bg_rect.topLeft(), bg_rect.bottomRight())
        if self._checked:
            gradient.setColorAt(0, QColor("#66BB6A"))
            gradient.setColorAt(1, QColor("#81C784"))
        else:
            gradient.setColorAt(0, QColor("#E0E0E0"))
            gradient.setColorAt(1, QColor("#BDBDBD"))
            
        painter.setBrush(QBrush(gradient))
        painter.setPen(Qt.NoPen)
        painter.drawRoundedRect(bg_rect, 20, 20)
        
        # 绘制圆形滑块
        painter.setBrush(QBrush(self._circle_color))
        painter.setPen(QPen(QColor("#999999"), 1))
        painter.drawEllipse(self._circle_position.x(), 5, 30, 30)
        
    @pyqtProperty(QPoint)
    def circle_position(self):
        return self._circle_position
    
    @circle_position.setter
    def circle_position(self, pos):
        self._circle_position = pos
        self.update()
        
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.toggle()
            
    def toggle(self, checked=None):
        if checked is None:
            self._checked = not self._checked
        else:
            self._checked = checked
            
        # 动画效果
        start_pos = QPoint(5, 5) if not self._checked else QPoint(45, 5)
        end_pos = QPoint(45, 5) if not self._checked else QPoint(5, 5)
        
        self._animation.stop()
        self._animation.setStartValue(start_pos)
        self._animation.setEndValue(end_pos)
        self._animation.start()
        
        self.update()
        self.toggled.emit(self._checked)
        
    def isChecked(self):
        return self._checked
    
    def setChecked(self, checked):
        self.toggle(checked)

class ProxySwitchApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("网络代理开关")
        self.setFixedSize(180, 210)
        self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
        self.setAttribute(Qt.WA_TranslucentBackground)
        
        self.initUI()
        self.initProxyState()
        
    def initUI(self):
        # 主窗口容器
        self.main_widget = QWidget()
        self.main_widget.setObjectName("mainWidget")
        self.setCentralWidget(self.main_widget)
        
        # 主布局
        layout = QVBoxLayout(self.main_widget)
        layout.setContentsMargins(20, 20, 20, 20)
        layout.setSpacing(20)
        
        # 标题栏
        title_bar = QWidget()
        title_bar.setFixedHeight(30)
        title_bar_layout = QHBoxLayout(title_bar)
        title_bar_layout.setContentsMargins(0, 0, 0, 0)
        
        self.title_label = QLabel("网络代理开关")
        self.title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #333333;")
        
        self.close_btn = QPushButton("×")
        self.close_btn.setFixedSize(30, 30)
        self.close_btn.setStyleSheet("""
            QPushButton {
                border: none;
                font-size: 18px;
                color: #777777;
            }
            QPushButton:hover {
                color: #ffffff;
                background-color: #E81123;
                border-radius: 15px;
            }
        """)
        self.close_btn.clicked.connect(self.close)
        
        title_bar_layout.addWidget(self.title_label)
        title_bar_layout.addStretch()
        title_bar_layout.addWidget(self.close_btn)
        
        # 开关控件
        switch_container = QWidget()
        switch_layout = QHBoxLayout(switch_container)
        switch_layout.setContentsMargins(0, 0, 0, 0)
        
        self.switch = SwitchButton()
        self.switch.toggled.connect(self.onSwitchToggled)
        
        self.status_label = QLabel()
        self.status_label.setStyleSheet("font-size: 14px; color: #555555;")
        self.status_label.setAlignment(Qt.AlignCenter)
        
        switch_layout.addStretch()
        switch_layout.addWidget(self.switch)
        switch_layout.addStretch()
        
        # 底部信息
        info_label = QLabel("点击滑块切换代理状态")
        info_label.setStyleSheet("font-size: 12px; color: #888888;")
        info_label.setAlignment(Qt.AlignCenter)
        
        # 添加到主布局
        layout.addWidget(title_bar)
        layout.addWidget(switch_container)
        layout.addWidget(self.status_label)
        layout.addWidget(info_label)
        layout.addStretch()
        
        # 设置样式
        self.main_widget.setStyleSheet("""
            #mainWidget {
                background-color: #ffffff;
                border-radius: 10px;
                border: 1px solid #dddddd;
            }
        """)
        
    def initProxyState(self):
        try:
            with winreg.OpenKey(winreg.HKEY_CURRENT_USER, 
                              r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", 
                              0, winreg.KEY_READ) as key:
                value, _ = winreg.QueryValueEx(key, "ProxyEnable")
                self.switch.setChecked(value == 1)
                self.updateStatusLabel(value == 1)
        except WindowsError:
            self.switch.setChecked(False)
            self.updateStatusLabel(False)
    
    def onSwitchToggled(self, checked):
        self.setProxyState(checked)
        self.updateStatusLabel(checked)
        
    def setProxyState(self, enabled):
        try:
            with winreg.OpenKey(winreg.HKEY_CURRENT_USER, 
                              r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", 
                              0, winreg.KEY_WRITE) as key:
                winreg.SetValueEx(key, "ProxyEnable", 0, winreg.REG_DWORD, 1 if enabled else 0)
                
            # 通知系统设置已更改
            self.notifySystem()
            return True
        except WindowsError as e:
            print(f"修改注册表失败: {e}")
            # 尝试以管理员身份运行
            if not self.isAdmin():
                self.showMessage("需要管理员权限", "请以管理员身份运行此程序")
            return False
    
    def updateStatusLabel(self, enabled):
        if enabled:
            self.status_label.setText("代理状态: <span style='color:#4CAF50;'>已开启</span>")
        else:
            self.status_label.setText("代理状态: <span style='color:#F44336;'>已关闭</span>")
    
    def isAdmin(self):
        try:
            return ctypes.windll.shell32.IsUserAnAdmin()
        except:
            return False
    
    def notifySystem(self):
        """通知系统代理设置已更改"""
        if platform.system() == "Windows":
            INTERNET_OPTION_SETTINGS_CHANGED = 39
            INTERNET_OPTION_REFRESH = 37
            ctypes.windll.Wininet.InternetSetOptionW(0, INTERNET_OPTION_SETTINGS_CHANGED, 0, 0)
            ctypes.windll.Wininet.InternetSetOptionW(0, INTERNET_OPTION_REFRESH, 0, 0)
    
    def showMessage(self, title, message):
        from PyQt5.QtWidgets import QMessageBox
        QMessageBox.information(self, title, message)
    
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.drag_pos = event.globalPos()
            event.accept()
    
    def mouseMoveEvent(self, event):
        if event.buttons() == Qt.LeftButton and hasattr(self, 'drag_pos'):
            self.move(self.pos() + event.globalPos() - self.drag_pos)
            self.drag_pos = event.globalPos()
            event.accept()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    
    # 设置全局字体
    font = QFont("Microsoft YaHei" if platform.system() == "Windows" else "PingFang SC")
    app.setFont(font)
    
    window = ProxySwitchApp()
    window.show()
    
    sys.exit(app.exec_())

进阶优化方向

1. 多代理配置预设

self.proxy_presets = {
    "SSR": "127.0.0.1:1080",
    "Clash": "127.0.0.1:7890",
    "V2Ray": "127.0.0.1:10808"
}

2. 系统托盘支持

from PyQt5.QtWidgets import QSystemTrayIcon, QMenu

def createTrayIcon(self):
    tray = QSystemTrayIcon(self)
    menu = QMenu()
    # 添加菜单项...
    tray.setContextMenu(menu)
    tray.show()

3. 自动代理配置(PAC)支持

winreg.SetValueEx(key, "AutoConfigURL", 0, winreg.REG_SZ, pac_url)

技术栈深度

PyQt5动画系统

QPropertyAnimation:属性动画核心类

QEasingCurve:提供30+种缓动曲线

属性绑定机制:@pyqtProperty装饰器

Windows注册表安全

权限管理:KEY_WRITE需要管理员权限

异常处理:WindowsError捕获

64/32位兼容:KEY_WOW64_64KEY标志

常见问题解答

Q:为什么修改后代理不立即生效?

A:需要调用InternetSetOptionW通知系统,部分浏览器可能需要手动刷新

Q:如何支持 socks5 代理?

A:需修改注册表ProxyServer值为socks=127.0.0.1:1080

Q:程序无法修改注册表?

A:右键以管理员身份运行,或添加UAC清单文件

总结与展望

本文开发的代理开关工具解决了以下核心问题:

未来扩展方向:

到此这篇关于基于Python实现Windows系统代理智能开关工具的文章就介绍到这了,更多相关Python系统代理智能开关内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文