Files
ykxmz/_build/fix_watch_lan_panel.py
T
xmz7891 b76014709e 修用户报的三个问题 + 揪出我自己检查工具的盲区
用户报:
  1. 官网 502
  2. 局域网连接:输入配对码提示连上了但没反应,得推出去点设备才能连
  3. 被控端安装时要能设中转服务器;之后双击 exe 也该能改配对码 /
     开关新设备 / 中转地址 —— 但现在双击没反应

────────────────────────────────────────
问题 1:502(暂态,已恢复)
────────────────────────────────────────
你截图是 21:24:56,隧道日志显示 21:22:51 和 21:26:44 各重启了一次
—— 502 就发生在那个空窗里。现在三个服务都 active、10 条连接、
本机 200(3ms)、公网 200(2.6s)。

(重启是谁触发的还没查清,watchdog 的嫌疑最大,下一轮跟进。)

────────────────────────────────────────
问题 2:局域网连上后没反应(真 bug,已修)
────────────────────────────────────────
手表上有**四个**面板,靠手工 setVisibility 切换:

    connectPanel   首页
    controlPanel   控制面板
    mirrorPanel    投屏
    lanPanel       局域网连接页    <- 后加的

而 showControlPanel() 只藏了前两个:

    connectPanel.setVisibility(GONE);
    controlPanel.setVisibility(VISIBLE);
    if (mirrorPanel != null) mirrorPanel.setVisibility(GONE);
    // lanPanel 没藏!

于是从「局域网连接」页填完码、连接成功之后:
    connect() 成功 -> showControlPanel() -> 控制面板显示了
    但 lanPanel 还盖在最上层 -> 用户看到的还是局域网页面

表现就是用户说的「提示连上了,但没反应,得推出去到设备列表
点击设备才能连接」—— 推出去把 lanPanel 收掉,才看到底下。

**根子在于"每处切换都手写一串 setVisibility"**,加第四个面板时
必然漏。改成统一入口:

    private void showOnly(View keep)   // 藏掉所有面板,只留一个

6 个切换点全部走它。以后加面板只要在 showOnly 里加一行。

────────────────────────────────────────
问题 3:双击 exe 没反应(功能缺失,已补)
────────────────────────────────────────
原来是两件事叠在一起:

**3a. 装完之后双击永远静默**(main() 最后几行)

    if not store.get("setup_done", False):
        return run_setup(store)
    return run_silent(store, None)      # <- 什么都不弹

改成:没参数 + 已设置过 -> **打开设置窗口**。
后台运行走显式 --silent(开机自启和安装包都用它,不受影响)。

**3b. 设置窗口里没有中转服务器**

SetupWindow 原来只有 配对码 / 开机自启 / 放行防火墙。
现在加了:
    * 中转服务器地址(可不填;只在一个 WiFi 里用就不需要)
    * 允许新设备接入(开关,只在"设置"模式给)

并且给 SetupWindow 加了 mode:
    "first"     第一次设置(防火墙 + 自启 + 转静默,老行为不变)
    "settings"  已装好,只改配置

**3c. 顺带修了一个会静默失败的东西**

改完发现打包出来的 exe 一跑就 ModuleNotFoundError:
No module named 'lanremote.ui' —— **PyInstaller 的 --hidden-import
里没有 ui**。它是 tkinter 那套设置窗口的实现,而静默那条路
不需要它,静态分析就找不到。源码跑得好好的,只有 exe 会崩。

已加 --hidden-import "lanremote.ui",并写了
_tests/test_double_click_window.py 针对**打包好的 exe** 验:
    ✅ 进程没崩 / 窗口弹出来了 / 标题是「ykxmz · 设置」

────────────────────────────────────────
额外揪出来的:我的检查工具一直在骗我
────────────────────────────────────────
反复被同一个问题咬了三次:补丁脚本为了加 import os / import re,
把它插到了 `from __future__` 前面,整个文件语法错。

而**我前面几次说的「全仓语法检查 0 个问题」是假绿**:

    ast.parse(src)      -> 通过(只解析,不做编译期检查)
    compile(src, ...)   -> SyntaxError: from __future__ imports
                           must occur at the beginning of the file

`__future__` 的位置是**编译期**检查,ast.parse 根本不管。
所以改成用 compile(),并写成
_tests/check_python_syntax.py(回归阶段 3a7),
它还会专门把"ast.parse 会放过、compile 会拦"的文件标出来。

实测:全仓 533 个文件,用 compile() 扫出 1 个坏的(ui.py),
而它正是让"双击没反应"的最后一个原因。

────────────────────────────────────────
还有一个重要发现
────────────────────────────────────────
**你一直在跑的是 10-06 的旧 exe**:

    C:\Users\shu\Desktop\ykxmz\ykxmz.exe    10-06 17:43  <- 你双击的这个
    LanRemote\dist_exe\ykxmz\ykxmz.exe      10-07 21:5x  <- 修复都在这

它的 silent.log 正在反复报:
    [21:15:59] 连接断了,重开一次再试(The read operation timed out)
    [21:16:42] 中转连接出问题:RelayError: 请求失败: ...
    [21:18:34] 已登记到中转:配对码 111111

这就是我修的那个 126 秒长轮询问题 —— 旧版超时太短反复掉线。
新版把客户端超时改成 180 秒了。

**所以这三个修复要装上新版才生效。**
2026-10-07 21:38:22 +08:00

177 lines
6.5 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 -*-
r'''修「局域网连上后没反应,得推出去点设备才能连」。
== 根因 ==
手表上有**四个**面板,靠手工 setVisibility 切换:
connectPanel 首页(连接页)
controlPanel 控制面板
mirrorPanel 投屏
lanPanel 局域网连接页 ← 后加的
而 showControlPanel() 只藏了前两个:
private void showControlPanel() {
connectPanel.setVisibility(View.GONE);
controlPanel.setVisibility(View.VISIBLE);
if (mirrorPanel != null) mirrorPanel.setVisibility(View.GONE);
...
}
**没藏 lanPanel。** 于是用户从「局域网连接」页面填完配对码、连接成功之后:
connect() 成功 -> showControlPanel() -> 控制面板显示了
但 lanPanel 还在最上层 -> 用户看到的还是局域网页面
表现就是用户说的:「提示连上了,但没反应,得推出去到设备列表
点击设备才能连接」—— 推出去(返回)把 lanPanel 收掉,才看到底下
的控制面板/设备列表。
同样的漏藏在另外两处(切投屏的时候也忘了藏 lanPanel)。
== 修法 ==
不逐处补 setVisibility —— 那样每加一个面板就会再漏一次
(这次就是加 lanPanel 时漏的)。改成**一个统一入口**:
private void showOnly(View keep) // 藏掉所有面板,只留一个
所有切换都走它。以后再加面板,只要在 showOnly 的列表里加一行,
所有切换点自动都对。
'''
from __future__ import annotations
import re
import sys
from pathlib import Path
SRC = (Path(__file__).resolve().parent.parent / "android" / "src"
/ "com" / "lanremote" / "watch" / "MainActivity.java")
HELPER = ''' /**
* 只显示一个面板,其余全部隐藏。
*
* <p>为什么要有这个:原来每个切换点都手写一串 setVisibility,
* 加第四个面板(lanPanel)时漏补了几处 —— 于是「局域网连接页」
* 填完码连上了,{@link #showControlPanel()} 也执行了,
* 但局域网页还盖在最上层,用户看到的是"连上了但没反应",
* 得推出去才看到底下的控制面板。
*
* <p>以后加面板只需要往这个方法里加一行,所有切换点自动都对。
*/
private void showOnly(View keep) {
View[] all = {connectPanel, controlPanel, mirrorPanel, lanPanel};
for (View v : all) {
if (v != null) {
v.setVisibility(v == keep ? View.VISIBLE : View.GONE);
}
}
}
'''
def main() -> int:
text = SRC.read_text(encoding="utf-8")
ok = True
# ---- 1. 插入 showOnly 帮助方法(放在 showControlPanel 之前)----
anchor = " private void showControlPanel() {"
if "private void showOnly(View keep)" in text:
print(" • showOnly 已经存在")
elif anchor not in text:
print(" ✗ 找不到 showControlPanel")
return 1
else:
text = text.replace(anchor, HELPER + anchor, 1)
print(" ✓ 插入 showOnly()")
# ---- 2. showControlPanel 改用 showOnly ----
old_ctrl = """ private void showControlPanel() {
connectPanel.setVisibility(View.GONE);
controlPanel.setVisibility(View.VISIBLE);
if (mirrorPanel != null) {
mirrorPanel.setVisibility(View.GONE);
}
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
}"""
new_ctrl = """ private void showControlPanel() {
// ⚠ 必须走 showOnly —— 原来这里手写 setVisibility,漏了 lanPanel,
// 导致"局域网连上后没反应、要推出去点设备"。
showOnly(controlPanel);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
}"""
if old_ctrl in text:
text = text.replace(old_ctrl, new_ctrl, 1)
print(" ✓ showControlPanel 改用 showOnly")
else:
print(" ! showControlPanel 的形状和预期不一样,用逐行方式改")
# 退化:把它函数体里的四行拆掉,换成 showOnly
m = re.search(r"( private void showControlPanel\(\) \{\n)(.*?)(\n \})",
text, re.S)
if m:
body = m.group(2)
# 保留 setSoftInputMode 那行
keep = [ln for ln in body.splitlines()
if "setSoftInputMode" in ln]
newbody = (" showOnly(controlPanel);\n"
+ "\n".join(keep))
text = text[:m.start(2)] + newbody + text[m.end(2):]
print(" ✓ 已替换(退化方式)")
else:
ok = False
# ---- 3. 另外两处切投屏的地方也要藏 lanPanel ----
# 形态:connectPanel.GONE / controlPanel.GONE / mirrorPanel.VISIBLE
pat_mirror = re.compile(
r" connectPanel\.setVisibility\(View\.GONE\);\n"
r" controlPanel\.setVisibility\(View\.GONE\);\n"
r" mirrorPanel\.setVisibility\(View\.VISIBLE\);")
def repl_mirror(m):
return " showOnly(mirrorPanel);"
n_mirror = len(pat_mirror.findall(text))
if n_mirror:
text = pat_mirror.sub(repl_mirror, text)
print(" ✓ %d 处切投屏改用 showOnly" % n_mirror)
else:
print(" • 没找到切投屏的那两处(可能已经改过)")
# ---- 4. showLanPanel 也走 showOnly ----
pat_lan = re.compile(
r" connectPanel\.setVisibility\(View\.GONE\);\n"
r" controlPanel\.setVisibility\(View\.GONE\);\n"
r"(\s*)mirrorPanel\.setVisibility\(View\.GONE\);\n"
r"(\s*)lanPanel\.setVisibility\(View\.VISIBLE\);")
if pat_lan.search(text):
text = pat_lan.sub(" showOnly(lanPanel);", text)
print(" ✓ showLanPanel 改用 showOnly")
if not ok:
print(" 有地方没匹配上,没有写回")
return 1
SRC.write_text(text, encoding="utf-8", newline="\n")
print()
print(" 已写回 MainActivity.java")
# 自检:还有没有裸的 panel setVisibility(除了 showOnly 内部和首页初始化)
leftovers = []
for i, line in enumerate(text.splitlines(), 1):
s = line.strip()
if re.match(r"^(connectPanel|controlPanel|mirrorPanel|lanPanel)"
r"\.setVisibility", s):
leftovers.append((i, s))
print()
print(" ── 剩下的裸 setVisibility(%d 处)──" % len(leftovers))
for ln, s in leftovers:
print(" 第 %d 行: %s" % (ln, s))
return 0
if __name__ == "__main__":
sys.exit(main())