Files
ykxmz/_build/patch_more_features.py
T
xmz7891 b62ac865ea ykxmz 1.5.0:修复手表端 TLS 证书校验 + MTU 修复网络稳定性
被控端
- 修复 MTU 问题:夹 MSS + 路径 MTU 探测(公网可用率 48% -> 100%)
- 复用 HTTP 连接(免掉每次新建 TLS 的 30% 握手失败)
- 每分钟回头找局域网,自动切回快 400 倍的路径
- 画面心跳帧:静止时省流量,客户端仍知道流是活的
- 锁屏时给出可操作的提示

手表端
- 内置 GTS Root R4 / GlobalSign Root CA,修 SSLHandshakeException
- 连接错误翻译成人话(证书/时间/解析/超时分别提示)
- 经中转连上后用服务器报的地址刷新已配对记录
- 新增局域网连接页面、服务器延迟检测

服务器端
- 支持 chunked 请求体(修每 16 秒一次的连接错乱与大面积 502)
- 网络调优脚本 net-tune.sh(开机自动应用)
- 管理页:批量删除、网络体检、下载区
- 隧道改 HTTP/2 + 双连接

文档
- 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
2026-10-06 19:01:05 +08:00

698 lines
29 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.
# -*- coding: utf-8 -*-
"""再加 39 个功能(总数到 78)。
重点:
教学类(全屏大字 / 倒计时 / 随机点名 / 白板 / 投影设置)—— 老师的刚需
系统类(睡眠/休眠/注销/显示桌面/任务视图/重启显卡/时钟秒数/隐藏文件)
网络类(WiFi 密码 / 公网 IP / ping / 局域网设备 / 网卡列表)
文件类(复制/移动/压缩/解压/详情/回收站/文件夹大小/按类型统计)
工具类(随机密码 / 秒表 / 世界时钟 / 硬件信息 / 启动项 / 计划任务)
媒体类(定时停止 / 音量淡出 / 显卡信息)
"""
from __future__ import annotations
import sys
from pathlib import Path
FEATURES = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\windows"
r"\lanremote\features.py")
# ---------------------------------------------------------------- defs 追加
DEFS_OLD = ''' # ---- 文件(限定自己的目录)----'''
DEFS_NEW = ''' # ---- 教学(老师的刚需)----
add("big_text", "全屏大字", "input", "教学",
hint="要显示的文字", key="text")
add("big_text_clear", "关掉全屏大字", "action", "教学")
add("countdown_screen", "全屏倒计时", "input", "教学",
hint="几分钟", key="minutes")
add("roll_call", "随机点名", "action", "教学")
add("roll_call_set", "设置点名名单", "input", "教学",
hint="名字用逗号隔开", key="names")
add("whiteboard", "全屏白板", "action", "教学")
add("display_mode", "投影 / 显示设置", "action", "教学")
add("big_text_fixed", "全屏显示「安静」", "action", "教学")
# ---- 系统控制 ----
add("minimize_all", "显示桌面", "action", "系统")
add("task_view", "任务视图", "action", "系统")
add("close_windows", "关闭所有窗口", "action", "系统")
add("clock_seconds", "任务栏时钟显示秒", "toggle", "系统",
on=self._clock_seconds())
add("hidden_files", "显示隐藏文件", "toggle", "系统",
on=self._hidden_files())
add("sleep", "睡眠", "action", "系统")
add("hibernate", "休眠", "action", "系统")
add("lock_only", "只锁屏", "action", "系统")
add("sign_out", "注销当前用户", "action", "系统")
add("restart_graphics", "重启显卡驱动", "action", "系统")
# ---- 网络 ----
add("wifi_password", "查看 WiFi 密码", "action", "网络")
add("public_ip", "查公网 IP", "action", "网络")
add("ping_host", "Ping 一个地址", "input", "网络",
hint="如 192.168.2.1", key="host")
add("arp_devices", "局域网设备列表", "action", "网络")
add("net_adapters", "网卡列表", "action", "网络")
# ---- 文件(限定自己的目录)----'''
DEFS_TAIL_OLD = ''' add("open_file", "用默认程序打开", "action", "文件")
return items'''
DEFS_TAIL_NEW = ''' add("open_file", "用默认程序打开", "input", "文件",
hint="文件路径", key="path")
add("copy_file", "复制文件", "input", "文件",
hint="源路径", key="path", hint2="目标路径", key2="to")
add("move_file", "移动文件", "input", "文件",
hint="源路径", key="path", hint2="目标路径", key2="to")
add("zip_dir", "压缩为 zip", "input", "文件",
hint="文件夹路径", key="path")
add("unzip", "解压 zip", "input", "文件",
hint="zip 文件路径", key="path")
add("file_info", "文件详情", "input", "文件",
hint="文件路径", key="path")
add("folder_size", "文件夹大小", "input", "文件",
hint="文件夹路径", key="path")
add("open_recycle", "打开回收站", "action", "文件")
add("disk_by_type", "按类型统计磁盘", "action", "文件")
# ---- 工具 ----
add("password_gen", "生成随机密码", "action", "工具")
add("stopwatch", "秒表(电脑上显示)", "action", "工具")
add("world_clock", "世界时钟", "action", "工具")
add("hardware_info", "硬件信息", "action", "工具")
add("startup_items", "开机启动项", "action", "工具")
add("scheduled_tasks", "计划任务", "action", "工具")
add("system_detail", "系统详细信息", "action", "工具")
# ---- 媒体 ----
add("stop_after", "定时停止播放", "input", "媒体",
hint="几分钟后停", key="minutes")
add("fade_volume", "音量淡出到静音", "action", "媒体")
add("gpu_info", "显卡信息", "action", "媒体")
return items'''
# ---------------------------------------------------------------- 实现追加
IMPL_ANCHOR = ''' # ---------------- 持久化 ----------------'''
IMPL_NEW = ''' # ==================================================================
# 教学
# ==================================================================
def _fullscreen_window(self, builder, seconds: float = 0.0):
"""在电脑上开一个全屏窗口。builder(win) 负责往里放内容。"""
def build():
import tkinter as tk
root = getattr(self.actions.ui, "root", None)
if root is None:
return
win = tk.Toplevel(root)
win.attributes("-fullscreen", True)
win.attributes("-topmost", True)
def close(_event=None):
try:
win.destroy()
except Exception: # noqa: BLE001
pass
builder(win, close)
win.bind("<Escape>", close)
win.bind("<Button-1>", close)
win.protocol("WM_DELETE_WINDOW", close)
if seconds > 0:
win.after(int(seconds * 1000), close)
self.actions.ui.ui_call(build)
def _do_big_text(self, params) -> str:
text = str(params.get("text") or "").strip()
if not text:
raise ValueError("文字为空")
size = 96 if len(text) <= 8 else (64 if len(text) <= 20 else 40)
def builder(win, close):
import tkinter as tk
win.configure(bg="#0B1020")
tk.Label(win, text=text, bg="#0B1020", fg="#FFFFFF",
font=("Microsoft YaHei UI", size, "bold"),
wraplength=win.winfo_screenwidth() - 80,
justify="center").pack(expand=True, fill="both")
tk.Label(win, text="按 Esc 或点一下关闭", bg="#0B1020", fg="#55607A",
font=("Microsoft YaHei UI", 12)).place(
relx=0.5, rely=0.97, anchor="center")
self._fullscreen_window(builder)
return f"已全屏显示「{text[:20]}」"
def _do_big_text_fixed(self, params) -> str:
return self._do_big_text({"text": "安静"})
def _do_big_text_clear(self, params) -> str:
self.actions.close_image()
return "已关闭全屏内容"
def _do_countdown_screen(self, params) -> str:
try:
minutes = max(1, min(180, int(params.get("minutes") or 5)))
except (TypeError, ValueError):
minutes = 5
total = minutes * 60
def builder(win, close):
import tkinter as tk
win.configure(bg="#0B1020")
label = tk.Label(win, text="", bg="#0B1020", fg="#FFFFFF",
font=("Consolas", 160, "bold"))
label.pack(expand=True, fill="both")
hint = tk.Label(win, text=f"倒计时 {minutes} 分钟 · 按 Esc 结束",
bg="#0B1020", fg="#55607A",
font=("Microsoft YaHei UI", 14))
hint.place(relx=0.5, rely=0.95, anchor="center")
state = {"left": total}
def tick():
if state["left"] < 0:
return
mins, secs = divmod(max(0, state["left"]), 60)
label.configure(text=f"{mins:02d}:{secs:02d}",
fg="#FF6B6B" if state["left"] <= 10 else "#FFFFFF")
if state["left"] == 0:
try:
win.bell()
except Exception: # noqa: BLE001
pass
state["left"] -= 1
win.after(1000, tick)
tick()
self._fullscreen_window(builder)
return f"已开始 {minutes} 分钟倒计时"
def _do_whiteboard(self, params) -> str:
def builder(win, close):
import tkinter as tk
win.configure(bg="#FFFFFF")
canvas = tk.Canvas(win, bg="#FFFFFF", highlightthickness=0)
canvas.pack(fill="both", expand=True)
state = {"x": None, "y": None, "color": "#1F2328", "size": 4}
def press(event):
state["x"], state["y"] = event.x, event.y
def drag(event):
if state["x"] is None:
return
canvas.create_line(state["x"], state["y"], event.x, event.y,
fill=state["color"], width=state["size"],
capstyle="round")
state["x"], state["y"] = event.x, event.y
def release(_event):
state["x"] = state["y"] = None
canvas.bind("<Button-1>", press)
canvas.bind("<B1-Motion>", drag)
canvas.bind("<ButtonRelease-1>", release)
bar = tk.Frame(win, bg="#F5F7FA")
bar.place(relx=1.0, rely=0.0, anchor="ne")
def clear():
canvas.delete("all")
for text, color in (("黑", "#1F2328"), ("红", "#CF222E"),
("蓝", "#185CE8"), ("绿", "#1A7F37")):
tk.Button(bar, text=text, bg="#FFFFFF", fg=color, relief="flat",
font=("Microsoft YaHei UI", 11, "bold"), width=3,
command=lambda c=color: state.update(color=c)).pack(
side="left", padx=2, pady=4)
tk.Button(bar, text="清空", bg="#EAEEF2", relief="flat",
font=("Microsoft YaHei UI", 10),
command=clear).pack(side="left", padx=6, pady=4)
self._fullscreen_window(builder)
return "白板已打开(鼠标画,Esc 关闭)"
def _do_display_mode(self, params) -> str:
_run(["cmd", "/c", "start", "ms-settings:display"], shell=False)
return "已打开显示设置(投影模式在这里切)"
def _do_roll_call_set(self, params) -> str:
raw = str(params.get("names") or "").strip()
if not raw:
raise ValueError("名单为空")
parts = [p.strip() for p in raw.replace(",", ",").replace("、", ",")
.replace(" ", ",").split(",") if p.strip()]
if not parts:
raise ValueError("没解析出名字")
self.store.set("roll_call_names", parts)
return f"名单已存,共 {len(parts)} 人"
def _do_roll_call(self, params) -> str:
import random
names = self.store.get("roll_call_names") or []
if not names:
raise ValueError("还没有名单,先点「设置点名名单」")
picked = random.choice(names)
# 在电脑上全屏显示,让全班看到
self._do_big_text({"text": picked})
return f"点到:{picked}(共 {len(names)} 人)"
# ==================================================================
# 系统控制
# ==================================================================
def _do_minimize_all(self, params) -> str:
ctypes.windll.user32.keybd_event(0x5B, 0, 0, 0) # Win
ctypes.windll.user32.keybd_event(0x44, 0, 0, 0) # D
ctypes.windll.user32.keybd_event(0x44, 0, 2, 0)
ctypes.windll.user32.keybd_event(0x5B, 0, 2, 0)
return "已显示桌面"
def _do_task_view(self, params) -> str:
ctypes.windll.user32.keybd_event(0x5B, 0, 0, 0) # Win
ctypes.windll.user32.keybd_event(0x09, 0, 0, 0) # Tab
ctypes.windll.user32.keybd_event(0x09, 0, 2, 0)
ctypes.windll.user32.keybd_event(0x5B, 0, 2, 0)
return "已打开任务视图"
def _do_close_windows(self, params) -> str:
"""给所有可见的顶层窗口发关闭消息(相当于 Alt+F4 一轮)。"""
WM_CLOSE = 0x0010
closed = 0
user32 = ctypes.windll.user32
EnumProc = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_void_p,
ctypes.c_void_p)
own_pid = os.getpid()
def callback(hwnd, _lparam):
nonlocal closed
if not user32.IsWindowVisible(hwnd):
return True
length = user32.GetWindowTextLengthW(hwnd)
if length == 0:
return True
pid = ctypes.c_ulong()
user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
if pid.value == own_pid:
return True
cls = ctypes.create_unicode_buffer(256)
user32.GetClassNameW(hwnd, cls, 256)
if cls.value in ("Progman", "Shell_TrayWnd", "WorkerW"):
return True
user32.PostMessageW(hwnd, WM_CLOSE, 0, 0)
closed += 1
return True
user32.EnumWindows(EnumProc(callback), 0)
return f"已向 {closed} 个窗口发出关闭"
def _clock_seconds(self) -> bool:
code, out = _run(["reg", "query",
r"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced",
"/v", "ShowSecondsInSystemClock"])
return code == 0 and "0x1" in out
def _do_clock_seconds(self, params) -> str:
want = 1 if params.get("on") else 0
_run(["reg", "add",
r"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced",
"/v", "ShowSecondsInSystemClock", "/t", "REG_DWORD", "/d", str(want),
"/f"])
self._restart_explorer_quiet()
return "任务栏时钟已显示秒" if want else "任务栏时钟已恢复"
def _hidden_files(self) -> bool:
code, out = _run(["reg", "query",
r"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced",
"/v", "Hidden"])
return code == 0 and ("0x1" in out.split("REG_DWORD")[-1]
if "REG_DWORD" in out else False)
def _do_hidden_files(self, params) -> str:
want = 1 if params.get("on") else 2
_run(["reg", "add",
r"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced",
"/v", "Hidden", "/t", "REG_DWORD", "/d", str(want), "/f"])
self._restart_explorer_quiet()
return "已显示隐藏文件" if want == 1 else "已恢复不显示隐藏文件"
def _restart_explorer_quiet(self) -> None:
_run(["taskkill", "/F", "/IM", "explorer.exe"])
time.sleep(0.8)
subprocess.Popen("explorer.exe", shell=True, creationflags=_NO_WINDOW)
def _do_sleep(self, params) -> str:
_run(["rundll32.exe", "powrprof.dll,SetSuspendState", "0,1,0"])
return "已进入睡眠"
def _do_hibernate(self, params) -> str:
_run(["shutdown", "/h"])
return "已进入休眠(如果启用了休眠)"
def _do_lock_only(self, params) -> str:
ctypes.windll.user32.LockWorkStation()
return "已锁屏"
def _do_sign_out(self, params) -> str:
_run(["shutdown", "/l"])
return "已注销"
def _do_restart_graphics(self, params) -> str:
# Win + Ctrl + Shift + B
for vk in (0x5B, 0x11, 0x10, 0x42):
ctypes.windll.user32.keybd_event(vk, 0, 0, 0)
for vk in (0x42, 0x10, 0x11, 0x5B):
ctypes.windll.user32.keybd_event(vk, 0, 2, 0)
time.sleep(0.5)
return "已重启显卡驱动"
# ==================================================================
# 网络
# ==================================================================
def _do_wifi_password(self, params) -> str:
code, out = _run(["netsh", "wlan", "show", "profiles"])
if code != 0:
return "读不到 WiFi 配置"
names = []
for raw in out.splitlines():
text = raw.strip()
if text.startswith("All User Profile") or "所有用户配置文件" in text:
names.append(text.split(":", 1)[-1].strip())
if not names:
return "没有保存过 WiFi"
lines = []
for name in names[:6]:
code2, detail = _run(["netsh", "wlan", "show", "profile",
f"name={name}", "key=clear"])
pwd = ""
for raw in detail.splitlines():
text = raw.strip()
if text.startswith("Key Content") or "关键内容" in text:
pwd = text.split(":", 1)[-1].strip()
lines.append(f"{name}:{pwd or '(读不到,可能需要管理员)'}")
return "\\n".join(lines)
def _do_public_ip(self, params) -> str:
import urllib.request
for url in ("https://api.ipify.org", "https://ifconfig.me/ip"):
try:
with urllib.request.urlopen(url, timeout=6) as resp:
text = resp.read().decode("utf-8", errors="replace").strip()
if text:
return f"公网 IP:{text}"
except Exception: # noqa: BLE001
continue
return "查不到(可能没联网)"
def _do_ping_host(self, params) -> str:
host = str(params.get("host") or "").strip()
if not host:
raise ValueError("地址为空")
if not re.match(r"^[A-Za-z0-9_.:\\-]+$", host):
raise ValueError("地址含有不合法字符")
code, out = _run(["ping", "-n", "3", host], timeout=25)
lines = [l for l in out.splitlines() if l.strip()]
return "\\n".join(lines[-8:]) or "没有输出"
def _do_arp_devices(self, params) -> str:
code, out = _run(["arp", "-a"])
lines = [l.strip() for l in out.splitlines()
if l.strip() and "---" not in l and "Interface" not in l
and "接口" not in l]
return f"局域网设备 {len(lines)} 条:\\n" + "\\n".join(lines[:40])
def _do_net_adapters(self, params) -> str:
code, out = _run(["powershell", "-NoProfile", "-Command",
"Get-NetAdapter | ForEach-Object { "
"\\"$($_.Name) $($_.Status) $($_.LinkSpeed)\\" }"])
lines = [l for l in out.splitlines() if l.strip()]
return "\\n".join(lines) or "读不到网卡"
# ==================================================================
# 文件(继续限定在自己的目录内)
# ==================================================================
def _do_copy_file(self, params) -> str:
source = self.resolve_in_roots(str(params.get("path") or ""))
target = self.resolve_in_roots(str(params.get("to") or ""))
if not source.exists():
raise ValueError("源文件不存在")
import shutil as _shutil
if source.is_dir():
_shutil.copytree(source, target, dirs_exist_ok=True)
else:
target.parent.mkdir(parents=True, exist_ok=True)
_shutil.copy2(source, target)
return f"已复制到 {target}"
def _do_move_file(self, params) -> str:
source = self.resolve_in_roots(str(params.get("path") or ""))
target = self.resolve_in_roots(str(params.get("to") or ""))
if not source.exists():
raise ValueError("源文件不存在")
import shutil as _shutil
target.parent.mkdir(parents=True, exist_ok=True)
_shutil.move(str(source), str(target))
return f"已移动到 {target}"
def _do_zip_dir(self, params) -> str:
import zipfile
source = self.resolve_in_roots(str(params.get("path") or ""))
if not source.is_dir():
raise ValueError("不是文件夹")
out = source.parent / f"{source.name}.zip"
with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as zf:
for item in source.rglob("*"):
if item.is_file():
zf.write(item, item.relative_to(source.parent))
return f"已压缩为 {out.name}({out.stat().st_size // 1024} KB)"
def _do_unzip(self, params) -> str:
import zipfile
source = self.resolve_in_roots(str(params.get("path") or ""))
if not source.is_file() or source.suffix.lower() != ".zip":
raise ValueError("不是 zip 文件")
target = source.parent / source.stem
target.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(source) as zf:
zf.extractall(target)
return f"已解压到 {target.name}"
def _do_file_info(self, params) -> str:
target = self.resolve_in_roots(str(params.get("path") or ""))
if not target.exists():
raise ValueError("文件不存在")
stat = target.stat()
kind = "文件夹" if target.is_dir() else "文件"
size = stat.st_size
readable = (f"{size / 1048576:.2f} MB" if size > 1048576
else f"{size // 1024} KB")
return (f"{kind}:{target.name}\\n"
f"大小:{readable}\\n"
f"修改:{time.strftime('%Y-%m-%d %H:%M', time.localtime(stat.st_mtime))}\\n"
f"位置:{target.parent}")
def _do_folder_size(self, params) -> str:
target = self.resolve_in_roots(str(params.get("path") or ""))
if not target.is_dir():
raise ValueError("不是文件夹")
total = 0
count = 0
for item in target.rglob("*"):
try:
if item.is_file():
total += item.stat().st_size
count += 1
except OSError:
continue
return (f"{target.name}:{count} 个文件,"
f"共 {total / 1048576:.1f} MB")
def _do_open_recycle(self, params) -> str:
os.startfile("shell:RecycleBinFolder") # noqa: S606
return "已打开回收站"
def _do_disk_by_type(self, params) -> str:
buckets = {"图片": 0, "视频": 0, "音频": 0, "文档": 0, "压缩包": 0, "其他": 0}
exts = {
"图片": {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"},
"视频": {".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv"},
"音频": {".mp3", ".wav", ".flac", ".m4a", ".aac"},
"文档": {".doc", ".docx", ".pdf", ".ppt", ".pptx", ".xls",
".xlsx", ".txt", ".md"},
"压缩包": {".zip", ".rar", ".7z", ".tar", ".gz"},
}
for root in self.allowed_roots():
try:
for item in root.rglob("*"):
if not item.is_file():
continue
suffix = item.suffix.lower()
for name, group in exts.items():
if suffix in group:
buckets[name] += item.stat().st_size
break
else:
buckets["其他"] += item.stat().st_size
except OSError:
continue
parts = [f"{k} {v / 1048576:.0f}MB" for k, v in buckets.items() if v]
return " | ".join(parts) or "统计不到"
# ==================================================================
# 工具
# ==================================================================
def _do_password_gen(self, params) -> str:
import secrets
import string
alphabet = string.ascii_letters + string.digits + "!@#$%^&*-_"
pwd = "".join(secrets.choice(alphabet) for _ in range(16))
self.actions.clip_set(pwd)
return f"已生成并复制到剪贴板:\\n{pwd}"
def _do_stopwatch(self, params) -> str:
def builder(win, close):
import tkinter as tk
win.configure(bg="#0B1020")
label = tk.Label(win, text="00:00.0", bg="#0B1020", fg="#FFFFFF",
font=("Consolas", 140, "bold"))
label.pack(expand=True, fill="both")
win.configure(bg="#0B1020")
state = {"start": time.time()}
def tick():
elapsed = time.time() - state["start"]
mins, secs = divmod(int(elapsed), 60)
label.configure(text=f"{mins:02d}:{secs:02d}.{int(elapsed * 10) % 10}")
win.after(100, tick)
tick()
self._fullscreen_window(builder)
return "秒表已打开(Esc 关闭)"
def _do_world_clock(self, params) -> str:
now = time.time()
zones = [("北京", 8), ("伦敦", 0), ("纽约", -5), ("洛杉矶", -8),
("东京", 9), ("悉尼", 10)]
lines = []
for name, offset in zones:
local = time.gmtime(now + offset * 3600)
lines.append(f"{name} {time.strftime('%H:%M', local)}")
return "\\n".join(lines)
def _do_hardware_info(self, params) -> str:
script = (
"$c=Get-CimInstance Win32_Processor|Select-Object -First 1;"
"$m=(Get-CimInstance Win32_PhysicalMemory|Measure-Object -Property "
"Capacity -Sum).Sum/1GB;"
"$g=Get-CimInstance Win32_VideoController|Select-Object -First 1;"
"$b=Get-CimInstance Win32_BaseBoard|Select-Object -First 1;"
"\\"CPU: $($c.Name)\\";\\"核心: $($c.NumberOfCores) 核 "
"$($c.NumberOfLogicalProcessors) 线程\\";"
"\\"内存: $([math]::Round($m,1)) GB\\";"
"\\"显卡: $($g.Name)\\";\\"主板: $($b.Manufacturer) $($b.Product)\\"")
code, out = _run(["powershell", "-NoProfile", "-Command", script], timeout=30)
lines = [l for l in out.splitlines() if l.strip()]
return "\\n".join(lines) or "读不到硬件信息"
def _do_startup_items(self, params) -> str:
script = ("Get-CimInstance Win32_StartupCommand | "
"ForEach-Object { \\"$($_.Name) -> $($_.Command)\\" }")
code, out = _run(["powershell", "-NoProfile", "-Command", script], timeout=30)
lines = [l for l in out.splitlines() if l.strip()]
return f"启动项 {len(lines)} 个:\\n" + "\\n".join(
l[:100] for l in lines[:30]) if lines else "没有启动项"
def _do_scheduled_tasks(self, params) -> str:
script = ("Get-ScheduledTask | Where-Object {$_.State -ne 'Disabled'} | "
"Select-Object -First 30 -ExpandProperty TaskName")
code, out = _run(["powershell", "-NoProfile", "-Command", script], timeout=30)
lines = [l for l in out.splitlines() if l.strip()]
return f"计划任务 {len(lines)} 个:\\n" + "\\n".join(lines)
def _do_system_detail(self, params) -> str:
import platform
code, out = _run(["wmic", "os", "get", "Caption,Version,BuildNumber,"
"InstallDate,OSArchitecture", "/format:list"])
lines = [l.strip() for l in out.splitlines() if l.strip()]
return (f"{platform.system()} {platform.release()} "
f"{platform.machine()}\\n" + "\\n".join(lines))
# ==================================================================
# 媒体
# ==================================================================
def _do_stop_after(self, params) -> str:
try:
minutes = max(1, min(180, int(params.get("minutes") or 10)))
except (TypeError, ValueError):
minutes = 10
import threading
def fire():
self.actions.stop_audio()
timer = threading.Timer(minutes * 60, fire)
timer.daemon = True
timer.start()
self._timers.append(timer)
return f"{minutes} 分钟后停止播放"
def _do_fade_volume(self, params) -> str:
for _ in range(20):
self._volume_key(0xAE, 1)
time.sleep(0.05)
return "音量已淡出到最低"
def _do_gpu_info(self, params) -> str:
script = ("Get-CimInstance Win32_VideoController | ForEach-Object { "
"\\"$($_.Name) 驱动 $($_.DriverVersion) "
"显存 $([math]::Round($_.AdapterRAM/1GB,1))GB\\" }")
code, out = _run(["powershell", "-NoProfile", "-Command", script], timeout=25)
lines = [l for l in out.splitlines() if l.strip()]
return "\\n".join(lines) or "读不到显卡信息"
''' + IMPL_ANCHOR
def main() -> int:
ok = True
text = FEATURES.read_text(encoding="utf-8")
for old, new, tag in ((DEFS_OLD, DEFS_NEW, "defs 前半"),
(DEFS_TAIL_OLD, DEFS_TAIL_NEW, "defs 后半"),
(IMPL_ANCHOR, IMPL_NEW, "实现")):
if old not in text:
print(f" [失败] {tag} 锚点未匹配")
ok = False
else:
text = text.replace(old, new, 1)
print(f" features.py / {tag}: OK")
if ok:
FEATURES.write_text(text, encoding="utf-8")
print("\n语法检查:")
import ast
try:
ast.parse(FEATURES.read_text(encoding="utf-8"))
print(" features.py: OK")
except SyntaxError as exc:
print(f" features.py: 语法错误 {exc}")
ok = False
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())