# -*- coding: utf-8 -*- """应用内网络自检(原来是一个独立的控制台脚本,现在做进界面里)。 和独立脚本最大的区别: 被控端自己就在监听 8765/8766,所以**不需要再去绑端口**, 直接读它自己的计数器就知道手表的包有没有到: * 自动发现请求收到几个 * TCP 连接来过几次 这比再开一个进程抢端口靠谱得多,也正是"连不上"时最想知道的事。 """ from __future__ import annotations import os import platform import socket import subprocess import time from .config import app_dir from .discovery import DISCOVERY_PORT from .firewall import check as firewall_check _NO_WINDOW = getattr(subprocess, "CREATE_NO_WINDOW", 0) def _run(cmd: list[str], timeout: int = 12) -> str: try: proc = subprocess.run(cmd, capture_output=True, timeout=timeout, creationflags=_NO_WINDOW, encoding="mbcs" if os.name == "nt" else "utf-8", errors="replace") return (proc.stdout or "") + (proc.stderr or "") except Exception: # noqa: BLE001 return "" def local_ips() -> list[tuple[str, str]]: """本机所有可用的局域网 IPv4。""" out: list[tuple[str, str]] = [] try: probe = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) probe.connect(("8.8.8.8", 80)) addr = probe.getsockname()[0] probe.close() if addr and not addr.startswith("127."): out.append((addr, "默认路由")) except OSError: pass try: for info in socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET): addr = info[4][0] if addr and not addr.startswith("127.") and addr not in [a for a, _ in out]: out.append((addr, "主机名")) except OSError: pass return out def wifi_name() -> str: text = _run(["netsh", "wlan", "show", "interfaces"]) for raw in text.splitlines(): line = raw.strip() if line.startswith("SSID") and "BSSID" not in line: return line.split(":", 1)[-1].strip() return "" def collect(server, store) -> dict: """把自检需要的东西一次性收集出来。""" data: dict = { "time": time.strftime("%Y-%m-%d %H:%M:%S"), "os": f"{platform.system()} {platform.release()}", "ips": local_ips(), "wifi": wifi_name(), "port": getattr(server, "port", 0), "discovery_port": DISCOVERY_PORT, "server_running": bool(getattr(server, "running", False)), "probes": 0, "selfcheck_requests": 0, "tcp_connections": 0, "connected": [], "firewall": {}, "app_dir": str(app_dir()), } # 服务端状态(含自动发现计数、累计 TCP 连接数) try: status = server.status() data["connected"] = status.get("connected", []) data["pairing_code"] = status.get("pairing_code", "") data["screen_sharing"] = status.get("screen_sharing", False) data["tcp_connections"] = int(status.get("total_connections", 0) or 0) info = status.get("discovery") or {} data["probes"] = int(info.get("answered", 0) or 0) data["selfcheck_requests"] = int(info.get("selfcheck_requests", 0) or 0) data["discovery_running"] = bool(info.get("running")) data["discovery_error"] = info.get("error", "") except Exception as exc: # noqa: BLE001 data["discovery_running"] = False data["discovery_error"] = str(exc) # 防火墙 try: data["firewall"] = firewall_check(data["port"] or 8765, DISCOVERY_PORT) except Exception as exc: # noqa: BLE001 data["firewall"] = {"supported": False, "message": str(exc)} return data def verdict(data: dict) -> tuple[str, str]: """给出结论:(级别, 文字)。级别是 ok / warn / error。""" fw = data.get("firewall") or {} if not data.get("server_running"): return "error", "被控端没有在运行 —— 先启动它,否则手表一定连不上。" if not fw.get("supported"): return "warn", "防火墙状态读不到(可能不是 Windows),无法判断。" if fw.get("blocked_count"): return "error", (f"有 {fw['blocked_count']} 条「阻止」规则压着," "手表能搜到但连不上。去「设置/修复」点一键修复。") if not (fw.get("tcp") and fw.get("udp")): return "error", "端口放行规则缺失,其他设备连不上。点「一键修复」。" if not fw.get("app_rule"): return "warn", ("现在能连上,但程序级放行规则不完整," "以后系统可能再生成阻止规则。建议修一次。") probes = data.get("probes", 0) conns = data.get("tcp_connections", 0) if probes == 0 and conns == 0: return "warn", ("防火墙没问题,但**一个包都没收到**。" "手表可能不在同一个 WiFi,或路由器开了 AP 隔离。") return "ok", "一切正常,手表应该能连上。" def render(data: dict) -> str: """把自检结果渲染成可复制的文本。""" level, text = verdict(data) lines: list[str] = [] lines.append("===== 电脑端自检 =====") lines.append(f"时间: {data.get('time')}") lines.append(f"系统: {data.get('os')}") lines.append("") lines.append("--- 本机网络 ---") ips = data.get("ips") or [] if ips: for addr, tag in ips: lines.append(f" {addr} [{tag}]") lines.append(f" >>> 手表上填这个 IP:{ips[0][0]}") else: lines.append(" 没读到 IP(网络没连上?)") if data.get("wifi"): lines.append(f" WiFi: {data['wifi']}") lines.append("") lines.append("--- 监听 ---") lines.append(f" 控制端口 TCP {data.get('port')}: " f"{'监听中' if data.get('server_running') else '未监听'}") lines.append(f" 发现端口 UDP {data.get('discovery_port')}: " f"{'监听中' if data.get('discovery_running') else '未监听'}") if data.get("discovery_error"): lines.append(f" 错误: {data['discovery_error']}") lines.append("") lines.append("--- 收到过多少(关键)---") lines.append(f" 自动发现请求: {data.get('probes', 0)} 个") lines.append(f" 自检请求: {data.get('selfcheck_requests', 0)} 个") lines.append(f" TCP 连接: {data.get('tcp_connections', 0)} 次") if data.get("probes", 0) == 0 and data.get("tcp_connections", 0) == 0: lines.append(" !! 一直是 0 说明手表的包根本没到这台电脑") else: lines.append(" 有数字说明手表的包能到达这台电脑") lines.append("") lines.append("--- 防火墙 ---") fw = data.get("firewall") or {} lines.append(f" 端口放行 TCP: {'是' if fw.get('tcp') else '否'}") lines.append(f" 端口放行 UDP: {'是' if fw.get('udp') else '否'}") lines.append(f" 程序级放行: {'是' if fw.get('app_rule') else '否'} " f"({os.path.basename(str(fw.get('app_rule_program') or '')) or '-'})") lines.append(f" 阻止规则: {fw.get('blocked_count', 0)} 条(针对本程序)") if fw.get("blocked_others"): lines.append(f" 另外还有 {fw['blocked_others']} 条是针对别的程序的,不影响") lines.append("") lines.append("--- 已连接设备 ---") connected = data.get("connected") or [] if connected: for item in connected: lines.append(f" {item.get('name', '?')} {item.get('addr', '')}") else: lines.append(" (没有设备连着)") lines.append("") lines.append("--- 结论 ---") lines.append(f" [{level.upper()}] {text}") lines.append("") lines.append("===== 结束 =====") return "\n".join(lines)