ClashTray/main/main.py
2023-12-24 20:45:07 +08:00

267 lines
9.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys
import platform
import requests
from PyQt5.QtWidgets import QApplication, QMainWindow, QSystemTrayIcon, QMenu, QAction,QActionGroup
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QTimer,pyqtSignal,QThread
import json
import aiohttp
import asyncio
class TrayIconExample(QMainWindow):
speed_data = pyqtSignal(dict)
def __init__(self):
super(TrayIconExample, self).__init__()
self.init_ui()
# 启动异步任务
self.worker_thread = WorkerThread(self)
self.worker_thread.data_ready.connect(self.update_speed_data)
self.worker_thread.start()
def init_ui(self):
self.setWindowTitle('Clash Tray')
# 创建系统托盘图标
tray_icon = QSystemTrayIcon(QIcon('img/icon.png'), self)
tray_icon.setToolTip('Clash Tray')
# 创建右键菜单
context_menu = QMenu(self)
proxy_mode = QMenu('代理模式', self)
exit_action = QAction('退出', self)
self.tun_mode = QAction('TUN模式', self)
self.tun_mode.setCheckable(True)
# 获取环境变量
self.copy_env=QAction('复制环境变量',self)
self.more_menu=QMenu('更多',self)
self.upload_speed=QAction('⬆️上传速度',self)
self.download_speed=QAction('⬇️下载速度',self)
self.upload_speed.setCheckable(False)
self.upload_speed.setEnabled(False)
self.download_speed.setCheckable(False)
self.download_speed.setEnabled(False)
# self.timer = QTimer(self)
# self.timer.timeout.connect(self.update_data)
# self.timer.start(1000) # 定时器间隔为1000毫秒1秒
# 更多 子菜单项设置
self.version=self.get_version()
self.show_version = QAction('内核版本:'+self.version, self)
self.show_version.setCheckable(False)
self.show_version.setEnabled(False)
self.restart_core=QAction('重启Clash',self)
self.more_menu.addAction(self.restart_core)
self.more_menu.addAction(self.show_version)
self.restart_core.triggered.connect(self.restart_clash)
# 创建代理模式 QActionGroup
proxy_mode_group = QActionGroup(self)
proxy_mode_group.setExclusive(True) # 设置为单选模式
# 设置代理模式菜单
self.direct_mode=QAction('直连模式',self)
self.rule_mode=QAction('规则模式',self)
self.global_mode=QAction('全局模式',self)
self.direct_mode.setCheckable(True)
self.rule_mode.setCheckable(True)
self.global_mode.setCheckable(True)
proxy_mode_group.addAction(self.direct_mode)
proxy_mode_group.addAction(self.rule_mode)
proxy_mode_group.addAction(self.global_mode)
self.tun_mode.triggered.connect(self.change_tun_mode)
exit_action.triggered.connect(self.exit_application)
# 将菜单添加到托盘图标
context_menu.addAction(self.tun_mode)
context_menu.addMenu(proxy_mode)
context_menu.addAction(self.copy_env)
context_menu.addMenu(self.more_menu)
context_menu.addActions([self.upload_speed,self.download_speed,exit_action])
proxy_mode.addActions([self.direct_mode,self.rule_mode,self.global_mode])
self.copy_env.triggered.connect(self.copy_env_var)
self.direct_mode.triggered.connect(lambda:self.set_proxy_mode(0))
self.rule_mode.triggered.connect(lambda:self.set_proxy_mode(1))
self.global_mode.triggered.connect(lambda:self.set_proxy_mode(2))
# 将右键菜单设置给托盘图标
tray_icon.setContextMenu(context_menu)
# 显示托盘图标
tray_icon.show()
self.get_config()
self.setGeometry(200, 200, 1200, 800)
def restart_clash(self):
url,port=self.get_connect()
data={'path': '','payload': ''}
response=requests.post(url=url+':'+port+'/restart').json()
if response['status']=='ok':
print('重启成功')
def get_connect(self,url=None,port=None):
url='http://127.0.0.1'
port=str(9090)
return url,port
def get_config(self):
url,port=self.get_connect(self)
response=requests.get(url=url+':'+port+'/configs')
# print(response.json['mode'])
if response.status_code==200:
res=response.json()
mode=res['mode']
if mode=='direct':
self.direct_mode.setChecked(True)
elif mode=='rule':
self.rule_mode.setChecked(True)
else:
self.global_mode.setChecked(True)
tun=res['tun']['enable']
print('tun:',tun)
if tun:
self.tun_mode.setChecked(True)
else:
self.tun_mode.setChecked(False)
return response
def get_version(self):
url,port=self.get_connect()
response=requests.get(url=url+':'+port+'/version')
if response.status_code==200:
res=response.json()
print('version',res)
return res['version']
def set_proxy_mode(self,type):
mode=None
if type==0:
mode='direct'
elif type==1:
mode='rule'
elif type==2:
mode='global'
url,port=self.get_connect()
data = {"mode": mode}
response=requests.patch(url=url+':'+port+'/configs?force=true',json=data)
if response.status_code==204:
self.get_config()
def change_tun_mode(self):
url,port=self.get_connect()
print(self.tun_mode.isChecked())
data = {"tun": {"enable": False if self.tun_mode.isChecked() else True}}
print(data)
response=requests.patch(url=url+':'+port+'/configs?force=true',json=data)
if response.status_code==204:
self.get_config()
def copy_env_var(self):
sys_info=platform.uname()
clipboard = QApplication.clipboard()
env_var=''
# print(sys_info.system)
if sys_info.system == ('Linux' or 'Darwin'):
env_var='export https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 all_proxy=socks5://127.0.0.1:7890'
elif sys_info.system == 'Windows':
env_var='setx https_proxy=http://127.0.0.1:7890\nsetx http_proxy=http://127.0.0.1:7890\nsetx all_proxy=socks5://127.0.0.1:7890'
# print(env_var)
clipboard.setText(env_var)
def update_speed_data(self,data):
print(data)
upload=data['up']
down=data['down']
if upload /1024<=1024 or down /1024<=1024:
upload=round(upload/1024,2)
down=round(down/1024,2)
self.upload_speed.setText('上传速度:'+str(upload)+'KB/s')
self.download_speed.setText('下载速度:'+str(down)+'KB/s')
else:
upload=round(upload/1024/1024,2)
down=round(down/1024/1024,2)
self.upload_speed.setText('上传速度:'+str(upload)+'MB/s')
self.download_speed.setText('下载速度:'+str(down)+'MB/s')
pass
def exit_application(self):
QApplication.quit()
# async def fetch_and_output_data(self):
# url = "http://127.0.0.1:9090/traffic" # 替换为实际的 API 地址
# async with aiohttp.ClientSession() as session:
# async with session.get(url, timeout=None) as response: # timeout=None 表示不设置超时
# while True:
# partial_data = await response.content.read(100) # 每次读取100字节的数据示例
# if not partial_data:
# break # 如果没有更多数据,退出循环
# # 在此处可以进行异步加载部分数据的操作,例如使用 Qt 的异步加载机制
# # print("Partial data:", partial_data)
# # 将字节数据转换为字符串
# partial_data_str = partial_data.decode('utf-8')
# print(partial_data_str)
# # 将字符串解析为 JSON 对象
# try:
# partial_data_json = json.loads(partial_data_str)
# print("Partial data:", partial_data_json)
# self.speed_data.emit(partial_data_json)
# except json.JSONDecodeError as e:
# print(f"Error decoding JSON: {e}")
# await asyncio.sleep(1) # 休眠1秒模拟每秒输出一部分数据
class WorkerThread(QThread):
data_ready = pyqtSignal(dict)
def run(self):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(self.fetch_and_output_data())
async def fetch_and_output_data(self):
url = "http://127.0.0.1:9090/traffic"
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=None) as response:
while True:
partial_data = await response.content.read(100)
if not partial_data:
break
partial_data_str = partial_data.decode('utf-8')
try:
partial_data_json = json.loads(partial_data_str)
self.data_ready.emit(partial_data_json)
except json.JSONDecodeError as e:
print(f"解析 JSON 时出错:{e}")
await asyncio.sleep(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = TrayIconExample()
sys.exit(app.exec_())