mirror of
https://github.com/xiaolin-007/CloudflareSpeedTest-GUI.git
synced 2026-05-06 22:26:18 +08:00
Add files via upload
This commit is contained in:
375
CloudflareSpeedTest-GUI.py
Normal file
375
CloudflareSpeedTest-GUI.py
Normal file
@@ -0,0 +1,375 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import ctypes
|
||||
from functools import partial
|
||||
from PyQt5 import QtWidgets, QtGui, QtCore
|
||||
|
||||
def resource_path(relative_path):
|
||||
"""获取 PyInstaller 打包后资源文件路径"""
|
||||
if hasattr(sys, "_MEIPASS"):
|
||||
return os.path.join(sys._MEIPASS, relative_path)
|
||||
return os.path.join(os.path.abspath("."), relative_path)
|
||||
|
||||
APP_ICON = resource_path("app.ico")
|
||||
CFST_EXE = "cfst.exe"
|
||||
IP_FILE_NAME = "ip.txt"
|
||||
SAVED_SETTINGS_FILE = "saved_settings.json"
|
||||
APP_USER_MODEL_ID = "com.example.cloudflarespeedtest"
|
||||
|
||||
def _set_windows_appid(appid):
|
||||
try:
|
||||
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
_set_windows_appid(APP_USER_MODEL_ID)
|
||||
|
||||
class MainWin(QtWidgets.QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.setWindowTitle("CloudflareSpeedTest_GUI -- 小琳解说")
|
||||
|
||||
# 图标(修复:PyInstaller 下也能找到)
|
||||
if os.path.exists(APP_ICON):
|
||||
self.setWindowIcon(QtGui.QIcon(APP_ICON))
|
||||
else:
|
||||
self.setWindowIcon(QtGui.QIcon(resource_path("app.ico")))
|
||||
|
||||
self.setFont(QtGui.QFont("Microsoft YaHei", 10))
|
||||
self.resize(500, 520)
|
||||
|
||||
self._build_ui()
|
||||
self._load_saved_settings_list()
|
||||
|
||||
def _build_ui(self):
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
|
||||
params = [
|
||||
("-n", "200", "延迟线程 1-1000"),
|
||||
("-t", "4", "延迟次数"),
|
||||
("-dn", "10", "下载数量"),
|
||||
("-dt", "10", "下载时间(秒)"),
|
||||
("-tp", "443", "端口"),
|
||||
("-url", "https://cf.xiu2.xyz/url", "测速地址"),
|
||||
("-httping", "", "HTTPing 模式 (勾选启用)"),
|
||||
("-httping-code", "200", "HTTP 有效状态码"),
|
||||
("-cfcolo", "HKG,KHH,NRT,LAX", "地区码, HTTPing 模式可用"),
|
||||
("-tl", "9999", "平均延迟上限(ms)"),
|
||||
("-tll", "0", "平均延迟下限(ms)"),
|
||||
("-tlr", "1.00", "丢包上限 0.00-1.00"),
|
||||
("-sl", "0", "下载速度下限 MB/s"),
|
||||
("-p", "10", "显示结果数量"),
|
||||
("-f", "ip.txt", "IP 段文件"),
|
||||
("-ip", "", "指定 IP 段"),
|
||||
("-o", "result.csv", "输出文件"),
|
||||
("-dd", "", "禁用下载测速 (勾选启用)"),
|
||||
("-allip", "", "测速全部 IP (勾选启用)")
|
||||
]
|
||||
|
||||
grid = QtWidgets.QGridLayout()
|
||||
grid.setColumnStretch(1, 1)
|
||||
|
||||
self.controls = {}
|
||||
row = 0
|
||||
|
||||
for key, default, hint in params:
|
||||
cb = QtWidgets.QCheckBox(key)
|
||||
cb.setChecked(False)
|
||||
|
||||
if key == "-n":
|
||||
widget = QtWidgets.QSpinBox()
|
||||
widget.setRange(1, 1000)
|
||||
try:
|
||||
widget.setValue(int(default))
|
||||
except Exception:
|
||||
widget.setValue(200)
|
||||
widget.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
|
||||
widget.setEnabled(False)
|
||||
else:
|
||||
widget = QtWidgets.QLineEdit(default)
|
||||
if default == "":
|
||||
widget.setPlaceholderText(hint)
|
||||
widget.setEnabled(False)
|
||||
widget.setStyleSheet("QLineEdit:disabled { color: gray; }")
|
||||
|
||||
lbl = QtWidgets.QLabel(hint)
|
||||
cb.stateChanged.connect(partial(self._on_checkbox_toggled, key))
|
||||
|
||||
grid.addWidget(cb, row, 0)
|
||||
grid.addWidget(widget, row, 1)
|
||||
grid.addWidget(lbl, row, 2)
|
||||
|
||||
self.controls[key] = (cb, widget)
|
||||
row += 1
|
||||
|
||||
main_layout.addLayout(grid)
|
||||
|
||||
save_load_layout = QtWidgets.QGridLayout()
|
||||
save_load_layout.setColumnStretch(1, 1)
|
||||
|
||||
save_label = QtWidgets.QLabel("保存设置名称")
|
||||
self.save_name_edit = QtWidgets.QLineEdit()
|
||||
self.save_name_edit.setPlaceholderText("填写保存设置名称")
|
||||
self.save_btn = QtWidgets.QPushButton("保存设置")
|
||||
|
||||
load_label = QtWidgets.QLabel("已保存设置")
|
||||
self.load_combo = QtWidgets.QComboBox()
|
||||
self.load_combo.setEditable(False)
|
||||
|
||||
sp = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
self.save_name_edit.setSizePolicy(sp)
|
||||
self.load_combo.setSizePolicy(sp)
|
||||
|
||||
self.load_btn = QtWidgets.QPushButton("加载设置")
|
||||
self.delete_btn = QtWidgets.QPushButton("删除已保存")
|
||||
|
||||
load_btns_layout = QtWidgets.QHBoxLayout()
|
||||
load_btns_layout.addWidget(self.load_btn)
|
||||
load_btns_layout.addWidget(self.delete_btn)
|
||||
load_btns_layout.addStretch()
|
||||
|
||||
save_load_layout.addWidget(save_label, 0, 0)
|
||||
save_load_layout.addWidget(self.save_name_edit, 0, 1)
|
||||
save_load_layout.addWidget(self.save_btn, 0, 2)
|
||||
|
||||
save_load_layout.addWidget(load_label, 1, 0)
|
||||
save_load_layout.addWidget(self.load_combo, 1, 1)
|
||||
save_load_layout.addLayout(load_btns_layout, 1, 2)
|
||||
|
||||
# 运行按钮
|
||||
self.run_btn = QtWidgets.QPushButton("运行\n测速")
|
||||
btn_size = 88
|
||||
self.run_btn.setFixedSize(btn_size, btn_size)
|
||||
|
||||
font = QtGui.QFont("Microsoft YaHei", 14)
|
||||
font.setBold(True)
|
||||
self.run_btn.setFont(font)
|
||||
self.run_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #0078D7;
|
||||
color: white;
|
||||
border: none;
|
||||
font-weight: bold;
|
||||
border-radius: 8px;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #005A9E;
|
||||
}
|
||||
""")
|
||||
|
||||
save_load_layout.addWidget(
|
||||
self.run_btn, 0, 3, 2, 1, alignment=QtCore.Qt.AlignCenter
|
||||
)
|
||||
|
||||
main_layout.addLayout(save_load_layout)
|
||||
|
||||
self.save_btn.clicked.connect(self._on_save_clicked)
|
||||
self.load_btn.clicked.connect(self._on_load_clicked)
|
||||
self.delete_btn.clicked.connect(self._on_delete_clicked)
|
||||
self.run_btn.clicked.connect(self._on_run_clicked)
|
||||
|
||||
def _on_checkbox_toggled(self, key, state):
|
||||
cb, widget = self.controls[key]
|
||||
enabled = (state == 2)
|
||||
widget.setEnabled(enabled)
|
||||
if isinstance(widget, QtWidgets.QLineEdit):
|
||||
if enabled:
|
||||
widget.setStyleSheet("QLineEdit { color: black; }")
|
||||
else:
|
||||
widget.setStyleSheet("QLineEdit:disabled { color: gray; }")
|
||||
|
||||
def _load_saved_settings_list(self):
|
||||
self.load_combo.clear()
|
||||
if not os.path.exists(SAVED_SETTINGS_FILE):
|
||||
return
|
||||
try:
|
||||
with open(SAVED_SETTINGS_FILE, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
names = sorted(data.keys())
|
||||
self.load_combo.addItems(names)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _read_saved_settings(self):
|
||||
if not os.path.exists(SAVED_SETTINGS_FILE):
|
||||
return {}
|
||||
try:
|
||||
with open(SAVED_SETTINGS_FILE, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def _write_saved_settings(self, data):
|
||||
try:
|
||||
with open(SAVED_SETTINGS_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _on_save_clicked(self):
|
||||
name = self.save_name_edit.text().strip()
|
||||
if not name:
|
||||
QtWidgets.QMessageBox.warning(self, "保存失败", "请填写保存设置的名称。")
|
||||
return
|
||||
|
||||
settings = {}
|
||||
for k, (cb, widget) in self.controls.items():
|
||||
if isinstance(widget, QtWidgets.QSpinBox):
|
||||
val = widget.value()
|
||||
else:
|
||||
val = widget.text()
|
||||
settings[k] = [cb.isChecked(), val]
|
||||
|
||||
all_saved = self._read_saved_settings()
|
||||
all_saved[name] = settings
|
||||
|
||||
ok = self._write_saved_settings(all_saved)
|
||||
if ok:
|
||||
QtWidgets.QMessageBox.information(self, "保存成功", f"设置已保存为: {name}")
|
||||
self._load_saved_settings_list()
|
||||
idx = self.load_combo.findText(name)
|
||||
if idx >= 0:
|
||||
self.load_combo.setCurrentIndex(idx)
|
||||
else:
|
||||
QtWidgets.QMessageBox.warning(self, "保存失败", "写入保存文件失败。")
|
||||
|
||||
def _on_load_clicked(self):
|
||||
name = self.load_combo.currentText().strip()
|
||||
if not name:
|
||||
QtWidgets.QMessageBox.warning(self, "加载失败", "请先选择一个已保存的设置名称。")
|
||||
return
|
||||
|
||||
all_saved = self._read_saved_settings()
|
||||
if name not in all_saved:
|
||||
QtWidgets.QMessageBox.warning(self, "加载失败", "所选设置不存在或已被删除。")
|
||||
self._load_saved_settings_list()
|
||||
return
|
||||
|
||||
settings = all_saved[name]
|
||||
|
||||
for k, (cb, widget) in self.controls.items():
|
||||
if k in settings:
|
||||
checked, val = settings[k]
|
||||
cb.setChecked(bool(checked))
|
||||
if isinstance(widget, QtWidgets.QSpinBox):
|
||||
try:
|
||||
widget.setValue(int(val))
|
||||
except Exception:
|
||||
pass
|
||||
widget.setEnabled(bool(checked))
|
||||
else:
|
||||
widget.setText(str(val))
|
||||
widget.setEnabled(bool(checked))
|
||||
widget.setStyleSheet(
|
||||
"QLineEdit { color: black; }" if widget.isEnabled() else "QLineEdit:disabled { color: gray; }"
|
||||
)
|
||||
|
||||
QtWidgets.QMessageBox.information(self, "加载成功", f"已加载设置: {name}")
|
||||
|
||||
def _on_delete_clicked(self):
|
||||
name = self.load_combo.currentText().strip()
|
||||
if not name:
|
||||
QtWidgets.QMessageBox.warning(self, "删除失败", "请先选择一个已保存的设置名称。")
|
||||
return
|
||||
|
||||
all_saved = self._read_saved_settings()
|
||||
if name not in all_saved:
|
||||
QtWidgets.QMessageBox.warning(self, "删除失败", "所选设置不存在。")
|
||||
self._load_saved_settings_list()
|
||||
return
|
||||
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self, "确认删除",
|
||||
f"确定要删除已保存设置: {name} ?",
|
||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
|
||||
)
|
||||
|
||||
if reply != QtWidgets.QMessageBox.Yes:
|
||||
return
|
||||
|
||||
del all_saved[name]
|
||||
ok = self._write_saved_settings(all_saved)
|
||||
if ok:
|
||||
QtWidgets.QMessageBox.information(self, "删除成功", f"已删除: {name}")
|
||||
self._load_saved_settings_list()
|
||||
else:
|
||||
QtWidgets.QMessageBox.warning(self, "删除失败", "删除时写入文件失败。")
|
||||
|
||||
def _find_file_case_insensitive(self, target_name):
|
||||
target_lower = target_name.lower()
|
||||
for entry in os.listdir("."):
|
||||
if entry.lower() == target_lower:
|
||||
return entry
|
||||
return None
|
||||
|
||||
def _build_cmd_list(self, exe_name):
|
||||
cmd_list = [exe_name]
|
||||
|
||||
for k, (cb, widget) in self.controls.items():
|
||||
if not cb.isChecked():
|
||||
continue
|
||||
|
||||
if k == "-n":
|
||||
cmd_list.append(k)
|
||||
cmd_list.append(str(widget.value()))
|
||||
continue
|
||||
|
||||
if k in ("-httping", "-dd", "-allip"):
|
||||
cmd_list.append(k)
|
||||
continue
|
||||
|
||||
val = widget.text().strip()
|
||||
if val == "":
|
||||
continue
|
||||
|
||||
cmd_list.append(k)
|
||||
cmd_list.append(val)
|
||||
|
||||
return cmd_list
|
||||
|
||||
def _on_run_clicked(self):
|
||||
cfst_actual = self._find_file_case_insensitive(CFST_EXE)
|
||||
ip_actual = self._find_file_case_insensitive(IP_FILE_NAME)
|
||||
|
||||
missing = []
|
||||
if not cfst_actual:
|
||||
missing.append(CFST_EXE)
|
||||
if not ip_actual:
|
||||
missing.append(IP_FILE_NAME)
|
||||
|
||||
if missing:
|
||||
missing_str = ",".join(missing)
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self, "文件缺失",
|
||||
f"未找到必要文件: {missing_str}\n请将缺失文件放在程序同目录后重试。"
|
||||
)
|
||||
return
|
||||
|
||||
cmd_list = self._build_cmd_list(cfst_actual)
|
||||
if not cmd_list:
|
||||
cmd_list = [cfst_actual]
|
||||
|
||||
try:
|
||||
if os.name == "nt":
|
||||
CREATE_NEW_CONSOLE = 0x00000010
|
||||
subprocess.Popen(cmd_list, creationflags=CREATE_NEW_CONSOLE)
|
||||
else:
|
||||
subprocess.Popen(cmd_list)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
# 全局图标
|
||||
app.setWindowIcon(QtGui.QIcon(APP_ICON))
|
||||
|
||||
w = MainWin()
|
||||
w.show()
|
||||
sys.exit(app.exec_())
|
||||
Reference in New Issue
Block a user