添加 main.py
This commit is contained in:
parent
6d886b8cca
commit
781909e0a3
447
main.py
Normal file
447
main.py
Normal file
@ -0,0 +1,447 @@
|
||||
import tkinter as tk
|
||||
import webbrowser
|
||||
from tkinter import messagebox, ttk, filedialog
|
||||
import re
|
||||
import threading
|
||||
import requests
|
||||
import json
|
||||
from pynput import keyboard
|
||||
|
||||
DEFAULT_STRENGTH = 20
|
||||
DEFAULT_TIME = 5000
|
||||
MAX_TIME = 30000
|
||||
MAX_STRENGTH = 40
|
||||
|
||||
root = tk.Tk()
|
||||
root.title("按键监听一键开火工具")
|
||||
|
||||
connection_code_var = tk.StringVar()
|
||||
key_settings = []
|
||||
|
||||
monitoring = False
|
||||
monitor_thread = None
|
||||
hotkey_infos = []
|
||||
pulse_list_cache = []
|
||||
|
||||
|
||||
def center_window(window, width, height):
|
||||
window.update_idletasks()
|
||||
x = (window.winfo_screenwidth() - width) // 2
|
||||
y = (window.winfo_screenheight() - height) // 2
|
||||
window.geometry(f"{width}x{height}+{x}+{y}")
|
||||
|
||||
|
||||
def parse_connection_code(code):
|
||||
m = re.match(r"^([a-fA-F0-9\-]+)@(.+)$", code.strip())
|
||||
if not m:
|
||||
return None, None
|
||||
client_id, api_host = m.group(1), m.group(2)
|
||||
return client_id, api_host
|
||||
|
||||
|
||||
def send_fire_request(client_id, api_host, strength, time_ms, override, pulseid):
|
||||
url = f"{api_host}/api/v2/game/{client_id}/action/fire"
|
||||
data = {
|
||||
"strength": strength,
|
||||
"time": time_ms,
|
||||
"override": override,
|
||||
}
|
||||
if pulseid:
|
||||
data["pulseId"] = pulseid
|
||||
try:
|
||||
resp = requests.post(url, json=data, timeout=5)
|
||||
if resp.status_code == 200:
|
||||
return True, resp.text
|
||||
return False, f"HTTP {resp.status_code}: {resp.text}"
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
|
||||
def get_pulse_list(client_id, api_host):
|
||||
global pulse_list_cache
|
||||
url = f"{api_host}/api/v2/game/{client_id}/pulse_list"
|
||||
try:
|
||||
resp = requests.get(url, timeout=5)
|
||||
if resp.status_code == 200:
|
||||
d = resp.json()
|
||||
if d.get("status") == 1 and "pulseList" in d:
|
||||
pulse_list_cache = d["pulseList"]
|
||||
return pulse_list_cache
|
||||
return []
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def parse_hotkey(hotkey_str):
|
||||
key_map = {
|
||||
"ctrl": keyboard.Key.ctrl,
|
||||
"alt": keyboard.Key.alt,
|
||||
"shift": keyboard.Key.shift,
|
||||
"cmd": keyboard.Key.cmd,
|
||||
"win": keyboard.Key.cmd,
|
||||
"super": keyboard.Key.cmd,
|
||||
"enter": keyboard.Key.enter,
|
||||
"tab": keyboard.Key.tab,
|
||||
"esc": keyboard.Key.esc,
|
||||
"space": keyboard.Key.space,
|
||||
"up": keyboard.Key.up,
|
||||
"down": keyboard.Key.down,
|
||||
"left": keyboard.Key.left,
|
||||
"right": keyboard.Key.right,
|
||||
"delete": keyboard.Key.delete,
|
||||
"backspace": keyboard.Key.backspace,
|
||||
}
|
||||
parts = [p.strip().lower() for p in hotkey_str.split('+')]
|
||||
keys = set()
|
||||
for p in parts:
|
||||
if p in key_map:
|
||||
keys.add(key_map[p])
|
||||
elif len(p) == 1:
|
||||
keys.add(keyboard.KeyCode.from_char(p))
|
||||
else:
|
||||
raise ValueError(f"无法识别的按键: {p}")
|
||||
return keys
|
||||
|
||||
|
||||
def hotkey_setting_dialog(edit_idx=None):
|
||||
if edit_idx is not None:
|
||||
setting = key_settings[edit_idx]
|
||||
initial_key_combo = setting["key_combo"]
|
||||
initial_strength = str(setting["strength"])
|
||||
initial_time = str(setting["time"])
|
||||
initial_pulseid = setting["pulseid"]
|
||||
initial_override = setting["override"]
|
||||
else:
|
||||
initial_key_combo = ""
|
||||
initial_strength = str(DEFAULT_STRENGTH)
|
||||
initial_time = str(DEFAULT_TIME)
|
||||
initial_pulseid = ""
|
||||
initial_override = False
|
||||
|
||||
dialog = tk.Toplevel(root)
|
||||
dialog.title("编辑按键配置" if edit_idx is not None else "添加按键配置")
|
||||
dialog.transient(root)
|
||||
dialog.grab_set()
|
||||
|
||||
frm_d = tk.Frame(dialog, padx=10, pady=10)
|
||||
frm_d.pack()
|
||||
|
||||
tk.Label(frm_d, text="按键组合:").grid(row=0, column=0, sticky="e")
|
||||
key_combo_var = tk.StringVar(value=initial_key_combo)
|
||||
key_combo_entry = tk.Entry(frm_d, textvariable=key_combo_var, width=20)
|
||||
key_combo_entry.grid(row=0, column=1, sticky="w")
|
||||
|
||||
tk.Label(frm_d, text="开火强度:").grid(row=1, column=0, sticky="e")
|
||||
strength_var = tk.StringVar(value=initial_strength)
|
||||
tk.Entry(frm_d, textvariable=strength_var, width=20).grid(row=1, column=1, sticky="w")
|
||||
|
||||
tk.Label(frm_d, text="开火时长:").grid(row=2, column=0, sticky="e")
|
||||
time_var = tk.StringVar(value=initial_time)
|
||||
tk.Entry(frm_d, textvariable=time_var, width=20).grid(row=2, column=1, sticky="w")
|
||||
|
||||
tk.Label(frm_d, text="开火波形:").grid(row=3, column=0, sticky="e")
|
||||
pulseid_cmb = ttk.Combobox(frm_d, width=18, state="readonly")
|
||||
pulseid_cmb.grid(row=3, column=1, sticky="w")
|
||||
|
||||
def fetch_and_set_pulse_list():
|
||||
code = connection_code_var.get().strip()
|
||||
if not code:
|
||||
pulseid_cmb["values"] = ["(不指定)"]
|
||||
pulseid_cmb.current(0)
|
||||
return
|
||||
client_id, api_host = parse_connection_code(code)
|
||||
if not client_id or not api_host:
|
||||
pulseid_cmb["values"] = ["(不指定)"]
|
||||
pulseid_cmb.current(0)
|
||||
return
|
||||
pulse_list = get_pulse_list(client_id, api_host)
|
||||
if not pulse_list:
|
||||
pulseid_cmb["values"] = ["(不指定)"]
|
||||
pulseid_cmb.current(0)
|
||||
else:
|
||||
vals = ["(不指定)"] + [
|
||||
f"{p['name']} ({p['id']})" for p in pulse_list
|
||||
]
|
||||
pulseid_cmb["values"] = vals
|
||||
# pulseid_cmb.current(0)
|
||||
if edit_idx is not None and initial_pulseid:
|
||||
select_idx = 0
|
||||
for i, p in enumerate(pulse_list):
|
||||
if p["id"] == initial_pulseid:
|
||||
select_idx = i + 1
|
||||
break
|
||||
pulseid_cmb.current(select_idx)
|
||||
else:
|
||||
pulseid_cmb.current(0)
|
||||
|
||||
fetch_and_set_pulse_list()
|
||||
|
||||
override_var = tk.BooleanVar(value=initial_override)
|
||||
tk.Checkbutton(frm_d, text="重置开火时间", variable=override_var).grid(row=4, column=1, sticky="w")
|
||||
|
||||
def on_ok():
|
||||
key_combo = key_combo_var.get().strip()
|
||||
strength = strength_var.get().strip()
|
||||
time_ms = time_var.get().strip()
|
||||
override = override_var.get()
|
||||
pulseid_sel = pulseid_cmb.current()
|
||||
pulseid = ""
|
||||
if pulseid_sel > 0 and pulse_list_cache:
|
||||
pulseid = pulse_list_cache[pulseid_sel - 1]["id"]
|
||||
if not key_combo:
|
||||
messagebox.showerror("错误", "必须填写按键组合", parent=dialog)
|
||||
return
|
||||
try:
|
||||
hotkey_keys = parse_hotkey(key_combo)
|
||||
except ValueError as e:
|
||||
messagebox.showerror("错误", str(e), parent=dialog)
|
||||
return
|
||||
try:
|
||||
strength = int(strength)
|
||||
if not (1 <= strength <= MAX_STRENGTH):
|
||||
raise ValueError()
|
||||
except:
|
||||
messagebox.showerror("错误", f"强度必须为 1~{MAX_STRENGTH} 的整数", parent=dialog)
|
||||
return
|
||||
try:
|
||||
time_ms = int(time_ms)
|
||||
if not (1 <= time_ms <= MAX_TIME):
|
||||
raise ValueError()
|
||||
except:
|
||||
messagebox.showerror("错误", f"时间必须为 1~{MAX_TIME} 的整数", parent=dialog)
|
||||
return
|
||||
obj = {
|
||||
"key_combo": key_combo,
|
||||
"hotkey_keys": hotkey_keys,
|
||||
"strength": strength,
|
||||
"time": time_ms,
|
||||
"pulseid": pulseid,
|
||||
"override": override,
|
||||
}
|
||||
if edit_idx is None:
|
||||
key_settings.append(obj)
|
||||
else:
|
||||
key_settings[edit_idx] = obj
|
||||
update_hotkey_list()
|
||||
dialog.destroy()
|
||||
|
||||
tk.Button(frm_d, text="刷新", command=fetch_and_set_pulse_list, width=6).grid(row=3, column=2, sticky="w", padx=8)
|
||||
tk.Button(frm_d, text="确定", command=on_ok, width=10).grid(row=5, column=1, sticky="w", pady=8)
|
||||
center_window(dialog, 300, 180)
|
||||
dialog.wait_window(dialog)
|
||||
|
||||
|
||||
def add_hotkey_setting():
|
||||
hotkey_setting_dialog(edit_idx=None)
|
||||
|
||||
|
||||
def edit_hotkey_setting():
|
||||
sel = hotkey_list.curselection()
|
||||
if not sel:
|
||||
messagebox.showerror("错误", "请先选择一个配置项")
|
||||
return
|
||||
idx = sel[0]
|
||||
hotkey_setting_dialog(edit_idx=idx)
|
||||
|
||||
|
||||
def del_hotkey_setting():
|
||||
sel = hotkey_list.curselection()
|
||||
if not sel:
|
||||
return
|
||||
idx = sel[0]
|
||||
del key_settings[idx]
|
||||
update_hotkey_list()
|
||||
|
||||
|
||||
def update_hotkey_list():
|
||||
hotkey_list.delete(0, tk.END)
|
||||
for i, st in enumerate(key_settings):
|
||||
pulselabel = st['pulseid'] if st['pulseid'] else "(无)"
|
||||
desc = f"<{st['key_combo']}> 强度:{st['strength']} 时间:{st['time']}ms 波形:{pulselabel} 重置时间:{'是' if st['override'] else '否'}"
|
||||
hotkey_list.insert(tk.END, desc)
|
||||
|
||||
|
||||
class MultiHotkeyMonitor(threading.Thread):
|
||||
def __init__(self, hotkey_infos):
|
||||
super().__init__(daemon=True)
|
||||
self.hotkey_infos = hotkey_infos
|
||||
self.pressed_keys = set()
|
||||
self.lock = threading.Lock()
|
||||
self.running = True
|
||||
|
||||
def run(self):
|
||||
def on_press(key):
|
||||
with self.lock:
|
||||
self.pressed_keys.add(key)
|
||||
for info in self.hotkey_infos:
|
||||
if all(k in self.pressed_keys for k in info['hotkey_keys']):
|
||||
ok, msg = send_fire_request(
|
||||
info['client_id'], info['api_host'],
|
||||
info['strength'], info['time'],
|
||||
info['override'], info['pulseid'])
|
||||
if ok:
|
||||
print(f"[{info['key_combo']}] 已发送一键开火请求")
|
||||
else:
|
||||
print(f"[{info['key_combo']}] 发送失败: {msg}")
|
||||
|
||||
def on_release(key):
|
||||
with self.lock:
|
||||
if key in self.pressed_keys:
|
||||
self.pressed_keys.remove(key)
|
||||
|
||||
with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
|
||||
while self.running:
|
||||
listener.join(0.1)
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
|
||||
def start_monitor():
|
||||
global monitor_thread, monitoring, hotkey_infos
|
||||
code = connection_code_var.get().strip()
|
||||
if not code:
|
||||
messagebox.showerror("错误", "请填写 CGH 连接码")
|
||||
return
|
||||
client_id, api_host = parse_connection_code(code)
|
||||
if not client_id or not api_host:
|
||||
messagebox.showerror("错误", "CGH 连接码格式错误")
|
||||
return
|
||||
if not key_settings:
|
||||
messagebox.showerror("错误", "请至少添加一个按键监听配置")
|
||||
return
|
||||
|
||||
hotkey_infos.clear()
|
||||
for st in key_settings:
|
||||
hotkey_infos.append({
|
||||
"hotkey_keys": st["hotkey_keys"],
|
||||
"key_combo": st["key_combo"],
|
||||
"client_id": client_id,
|
||||
"api_host": api_host,
|
||||
"strength": st["strength"],
|
||||
"time": st["time"],
|
||||
"pulseid": st["pulseid"],
|
||||
"override": st["override"]
|
||||
})
|
||||
|
||||
monitoring = True
|
||||
monitor_thread = MultiHotkeyMonitor(hotkey_infos)
|
||||
monitor_thread.start()
|
||||
btn_start["state"] = "disabled"
|
||||
btn_stop["state"] = "normal"
|
||||
lbl_status["text"] = "监听按键事件中..."
|
||||
|
||||
|
||||
def stop_monitor():
|
||||
global monitoring, monitor_thread
|
||||
monitoring = False
|
||||
if monitor_thread:
|
||||
monitor_thread.stop()
|
||||
monitor_thread = None
|
||||
btn_start["state"] = "disabled"
|
||||
btn_stop["state"] = "disabled"
|
||||
lbl_status["text"] = "等待关闭程序,关闭程序后才可彻底取消监听..."
|
||||
messagebox.showinfo("提示", "必须手动关闭本程序才可取消监听!")
|
||||
|
||||
|
||||
def save_config():
|
||||
config = {
|
||||
"connection_code": connection_code_var.get(),
|
||||
"key_settings": [
|
||||
{
|
||||
"key_combo": st["key_combo"],
|
||||
"strength": st["strength"],
|
||||
"time": st["time"],
|
||||
"pulseid": st["pulseid"],
|
||||
"override": st["override"],
|
||||
}
|
||||
for st in key_settings
|
||||
]
|
||||
}
|
||||
filename = filedialog.asksaveasfilename(
|
||||
defaultextension=".json",
|
||||
filetypes=[("JSON 配置文件", "*.json")],
|
||||
title="保存配置文件"
|
||||
)
|
||||
if filename:
|
||||
try:
|
||||
with open(filename, "w", encoding="utf-8") as f:
|
||||
json.dump(config, f, ensure_ascii=False, indent=2)
|
||||
messagebox.showinfo("提示", f"配置已保存到 {filename}")
|
||||
except Exception as e:
|
||||
messagebox.showerror("保存失败", str(e))
|
||||
|
||||
|
||||
def load_config():
|
||||
filename = filedialog.askopenfilename(
|
||||
defaultextension=".json",
|
||||
filetypes=[("JSON 配置文件", "*.json")],
|
||||
title="打开配置文件"
|
||||
)
|
||||
if filename:
|
||||
try:
|
||||
with open(filename, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
connection_code_var.set(config.get("connection_code", ""))
|
||||
key_settings.clear()
|
||||
for st in config.get("key_settings", []):
|
||||
try:
|
||||
hotkey_keys = parse_hotkey(st["key_combo"])
|
||||
except Exception:
|
||||
hotkey_keys = set()
|
||||
key_settings.append({
|
||||
"key_combo": st["key_combo"],
|
||||
"hotkey_keys": hotkey_keys,
|
||||
"strength": st.get("strength", DEFAULT_STRENGTH),
|
||||
"time": st.get("time", DEFAULT_TIME),
|
||||
"pulseid": st.get("pulseid", ""),
|
||||
"override": st.get("override", False),
|
||||
})
|
||||
update_hotkey_list()
|
||||
messagebox.showinfo("提示", f"配置已读取: {filename}")
|
||||
except Exception as e:
|
||||
messagebox.showerror("读取失败", str(e))
|
||||
|
||||
|
||||
frm = tk.Frame(root, padx=15, pady=15)
|
||||
frm.pack()
|
||||
|
||||
menubar = tk.Menu(root)
|
||||
filemenu = tk.Menu(menubar, tearoff=0)
|
||||
filemenu.add_command(label="保存配置", command=save_config)
|
||||
filemenu.add_command(label="读取配置", command=load_config)
|
||||
menubar.add_cascade(label="文件", menu=filemenu)
|
||||
aboutmenu = tk.Menu(menubar, tearoff=0)
|
||||
aboutmenu.add_command(label="开源",
|
||||
command=lambda: webbrowser.open("https://gitea.miri.site/Mr_Fang/cgh_keyboard_fire_tool"))
|
||||
menubar.add_cascade(label="帮助", menu=aboutmenu)
|
||||
root.config(menu=menubar)
|
||||
|
||||
tk.Label(frm, text="连接码:").grid(row=0, column=0, sticky="e")
|
||||
tk.Entry(frm, textvariable=connection_code_var, width=60).grid(row=0, column=1, columnspan=3, sticky="w")
|
||||
|
||||
tk.Label(frm, text="按键配置:").grid(row=1, column=0, sticky="ne", pady=4)
|
||||
hotkey_list = tk.Listbox(frm, height=6, width=60)
|
||||
hotkey_list.grid(row=1, column=1, columnspan=2, sticky="w")
|
||||
btn_container = tk.Frame(frm)
|
||||
btn_container.grid(row=1, column=3, sticky="nw", padx=8)
|
||||
btn_add = tk.Button(btn_container, text="添加", width=8, command=add_hotkey_setting)
|
||||
btn_add.pack(pady=4)
|
||||
btn_edit = tk.Button(btn_container, text="编辑", width=8, command=edit_hotkey_setting)
|
||||
btn_edit.pack(pady=4)
|
||||
btn_del = tk.Button(btn_container, text="删除", width=8, command=del_hotkey_setting)
|
||||
btn_del.pack(pady=4)
|
||||
|
||||
btn_start = tk.Button(frm, text="开始监听", command=start_monitor, width=16, fg="green")
|
||||
btn_start.grid(row=2, column=1, sticky="w", pady=10)
|
||||
btn_stop = tk.Button(frm, text="停止监听", command=stop_monitor, width=16, fg="darkred", state="disabled")
|
||||
btn_stop.grid(row=2, column=2, sticky="w", pady=10)
|
||||
|
||||
lbl_status = tk.Label(frm, text="等待开始监听...", fg="blue")
|
||||
lbl_status.grid(row=3, column=1, sticky="w")
|
||||
|
||||
tk.Label(frm, text="Tips: 支持添加多个不同配置的按键组合,可用“文件”菜单保存/读取配置").grid(row=4, column=0,
|
||||
columnspan=4, sticky="w")
|
||||
center_window(root, 600, 250)
|
||||
|
||||
root.mainloop()
|
Loading…
x
Reference in New Issue
Block a user