Files
ykxmz/_build/add_lan_and_latency.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

530 lines
20 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 -*-
"""安卓端新增:局域网连接页面 + 服务器延迟检测。
用户要求:
"在首页没连之前可以加一个进入局域网连接页面,
就可以用局域网进行连接,不经过中转服务器"
"加个东西就是可以检查延迟检查连接服务器"
设计:
首页(未连接时)三个按钮:
[ 局域网连接(同一个 WiFi) ] <- 新增,走局域网直连
[ 输入配对码连接 ] <- 原来的,经中转
[ 中转设置(跨网络用) ]
外加一个延迟状态行:
[ 检查服务器 ] 显示"延迟 83 毫秒 / 连不上"
局域网页面:
* 自动搜索(UDP 广播,被控端监听 8766)
* 搜到的电脑列出来,点一下就连
* 也能手动填 IP:端口
* 配对码
* 全部走 RemoteClient.connect()(直连),不碰中转
两条路并存:局域网友好、中转兜底。
"""
from __future__ import annotations
import ast
import sys
from pathlib import Path
MAIN = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\android\src"
r"\com\lanremote\watch\MainActivity.java")
# ===================== 1. 首页加按钮 =====================
HOME_ANCHOR = ''' Button codeButton = bigButton("输入配对码连接", C_OK, 15);'''
HOME_NEW = ''' // 局域网连接:手表和电脑在同一个 WiFi 时走这条,不经过中转。
// 用户要求:"在首页没连之前可以加一个进入局域网连接页面,
// 就可以用局域网进行连接,不经过中转服务器"
Button lanButton = bigButton("局域网连接(同一个 WiFi)", C_ACCENT, 15);
LinearLayout.LayoutParams lanParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, dp(52));
lanParams.setMargins(0, 0, 0, dp(8));
lanButton.setLayoutParams(lanParams);
lanButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showLanPanel();
}
});
box.addView(lanButton);
Button codeButton = bigButton("输入配对码连接", C_OK, 15);'''
HOME_LATENCY_ANCHOR = ''' statusView = new TextView(this);
statusView.setTextColor(C_DIM);
statusView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
statusView.setPadding(0, 0, 0, dp(6));
box.addView(statusView);'''
HOME_LATENCY_NEW = ''' // 服务器延迟:连不上中转的时候,这一条最有用
LinearLayout serverRow = new LinearLayout(this);
serverRow.setOrientation(LinearLayout.HORIZONTAL);
serverRow.setGravity(Gravity.CENTER_VERTICAL);
serverStatus = new TextView(this);
serverStatus.setTextColor(C_DIM);
serverStatus.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
serverStatus.setText("服务器:还没测");
LinearLayout.LayoutParams ssParams = new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f);
serverStatus.setLayoutParams(ssParams);
serverRow.addView(serverStatus);
Button pingButton = smallButton("检查服务器", C_CARD, new Runnable() {
@Override
public void run() {
checkServer();
}
});
serverRow.addView(pingButton);
box.addView(serverRow);
statusView = new TextView(this);
statusView.setTextColor(C_DIM);
statusView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
statusView.setPadding(0, dp(6), 0, dp(6));
box.addView(statusView);'''
# ===================== 2. 字段 =====================
FIELD_ANCHOR = ''' private TextView statusView;'''
FIELD_NEW = ''' // ---- 局域网连接页面 ----
private FrameLayout lanPanel;
private LinearLayout lanResultBox;
private EditText lanIpInput;
private EditText lanPortInput;
private EditText lanCodeInput;
private TextView lanStatus;
private volatile boolean lanSearching;
// ---- 服务器延迟 ----
private TextView serverStatus;
private TextView statusView;'''
# ===================== 3. onCreate 里建这个面板 =====================
CREATE_ANCHOR = ''' buildConnectPanel();
buildControlPanel();
buildMirrorPanel();'''
CREATE_NEW = ''' buildConnectPanel();
buildControlPanel();
buildMirrorPanel();
buildLanPanel();'''
CREATE_ADD_ANCHOR = ''' root.addView(connectPanel, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));'''
CREATE_ADD_NEW = ''' root.addView(connectPanel, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
root.addView(lanPanel, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));'''
CREATE_HIDE_ANCHOR = ''' controlPanel.setVisibility(View.GONE);
mirrorPanel.setVisibility(View.GONE);
connectPanel.setVisibility(View.VISIBLE);'''
CREATE_HIDE_NEW = ''' controlPanel.setVisibility(View.GONE);
mirrorPanel.setVisibility(View.GONE);
lanPanel.setVisibility(View.GONE);
connectPanel.setVisibility(View.VISIBLE);'''
# ===================== 4. 新方法(插在 showConnectPanel 前) =====================
METHOD_ANCHOR = ''' private void showConnectPanel() {'''
METHOD_NEW = ''' // ==================================================================
// 局域网连接(不经过中转)
// ==================================================================
private void buildLanPanel() {
lanPanel = new FrameLayout(this);
lanPanel.setBackgroundColor(C_BG);
ScrollView scroll = new ScrollView(this);
scroll.setBackgroundColor(C_BG);
LinearLayout box = new LinearLayout(this);
box.setOrientation(LinearLayout.VERTICAL);
box.setPadding(dp(10), dp(12), dp(10), dp(12));
scroll.addView(box, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
lanPanel.addView(scroll, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
// ---- 标题 + 返回 ----
LinearLayout top = new LinearLayout(this);
top.setOrientation(LinearLayout.HORIZONTAL);
top.setGravity(Gravity.CENTER_VERTICAL);
TextView title = new TextView(this);
title.setText("局域网连接");
title.setTextColor(C_FG);
title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 17);
LinearLayout.LayoutParams tParams = new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f);
title.setLayoutParams(tParams);
top.addView(title);
top.addView(smallButton("返回", C_CARD, new Runnable() {
@Override
public void run() {
hideLanPanel();
}
}));
box.addView(top);
TextView hint = new TextView(this);
hint.setText("手表和电脑连同一个 WiFi 时用这个,速度更快、不占流量。\\n"
+ "电脑上要开着 ykxmz。");
hint.setTextColor(C_DIM);
hint.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
hint.setPadding(0, dp(6), 0, dp(10));
box.addView(hint);
// ---- 自动搜索 ----
Button search = bigButton("自动搜索电脑", C_ACCENT, 14);
LinearLayout.LayoutParams sParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, dp(50));
sParams.setMargins(0, 0, 0, dp(8));
search.setLayoutParams(sParams);
search.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startLanSearch();
}
});
box.addView(search);
lanStatus = new TextView(this);
lanStatus.setTextColor(C_DIM);
lanStatus.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
lanStatus.setPadding(0, 0, 0, dp(6));
box.addView(lanStatus);
lanResultBox = new LinearLayout(this);
lanResultBox.setOrientation(LinearLayout.VERTICAL);
box.addView(lanResultBox);
// ---- 手动填地址 ----
TextView manualTip = new TextView(this);
manualTip.setText("搜不到就自己填(电脑上 ykxmz 会显示 IP)");
manualTip.setTextColor(C_DIM);
manualTip.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
manualTip.setPadding(0, dp(10), 0, dp(4));
box.addView(manualTip);
lanIpInput = makeInput("电脑 IP,如 192.168.10.105",
InputType.TYPE_CLASS_TEXT);
box.addView(labeled("电脑 IP", lanIpInput));
lanPortInput = makeInput("端口(默认 8765)",
InputType.TYPE_CLASS_NUMBER);
box.addView(labeled("端口", lanPortInput));
lanCodeInput = makeInput("6 位配对码", InputType.TYPE_CLASS_NUMBER);
box.addView(labeled("配对码", lanCodeInput));
Button connect = bigButton("连上这台电脑", C_OK, 15);
LinearLayout.LayoutParams cParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, dp(52));
cParams.setMargins(0, dp(10), 0, 0);
connect.setLayoutParams(cParams);
connect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
connectLanTyped();
}
});
box.addView(connect);
}
private void showLanPanel() {
connectPanel.setVisibility(View.GONE);
controlPanel.setVisibility(View.GONE);
if (mirrorPanel != null) {
mirrorPanel.setVisibility(View.GONE);
}
lanPanel.setVisibility(View.VISIBLE);
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
// 进来先自动搜一次,省得用户还要自己点
String savedCode = savedPairingCode();
if (lanCodeInput != null && savedCode != null && !savedCode.isEmpty()
&& lanCodeInput.getText().length() == 0) {
lanCodeInput.setText(savedCode);
}
startLanSearch();
}
private void hideLanPanel() {
lanPanel.setVisibility(View.GONE);
showConnectPanel();
}
/** 广播搜一圈,找到的电脑列出来。 */
private void startLanSearch() {
if (lanSearching) {
return;
}
lanSearching = true;
if (lanResultBox != null) {
lanResultBox.removeAllViews();
}
setLanStatus("正在搜索…(最多 6 秒)", C_DIM);
appendLog("局域网搜索开始");
io.execute(new Runnable() {
@Override
public void run() {
java.util.List<DiscoveryCore.Found> found;
try {
found = Discovery.search(MainActivity.this, 4000);
} catch (Throwable error) {
found = new java.util.ArrayList<DiscoveryCore.Found>();
appendLog("搜索失败:" + error);
}
final java.util.List<DiscoveryCore.Found> result = found;
runOnUiThread(new Runnable() {
@Override
public void run() {
lanSearching = false;
if (lanResultBox == null) {
return;
}
lanResultBox.removeAllViews();
if (result.isEmpty()) {
setLanStatus("没搜到电脑。确认:① 电脑开着 ykxmz ② "
+ "同一个 WiFi ③ 路由器没开 AP 隔离。"
+ "\\n也可以直接填下面的 IP。", C_WARN);
appendLog("局域网没搜到电脑");
return;
}
setLanStatus("搜到 " + result.size() + " 台,点一下就连",
C_OK);
appendLog("搜到 " + result.size() + " 台电脑");
for (final DiscoveryCore.Found item : result) {
Button b = bigButton(
item.name + " · " + item.host,
C_CARD, 14);
LinearLayout.LayoutParams lp =
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
dp(50));
lp.setMargins(0, 0, 0, dp(6));
b.setLayoutParams(lp);
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (lanIpInput != null) {
lanIpInput.setText(item.host);
}
if (lanPortInput != null) {
lanPortInput.setText(
String.valueOf(item.port));
}
connectLanTyped();
}
});
lanResultBox.addView(b);
}
}
});
}
});
}
private void setLanStatus(String text, int color) {
if (lanStatus != null) {
lanStatus.setText(text);
lanStatus.setTextColor(color);
}
}
/** 用手填/点选的地址直连,不走中转。 */
private void connectLanTyped() {
String host = lanIpInput == null ? ""
: lanIpInput.getText().toString().trim();
String portText = lanPortInput == null ? ""
: lanPortInput.getText().toString().trim();
String code = lanCodeInput == null ? ""
: lanCodeInput.getText().toString().trim();
if (host.isEmpty()) {
setLanStatus("先填电脑 IP,或者上面搜一下", C_WARN);
toast("还没填电脑 IP");
return;
}
int port = RemoteClient.DEFAULT_PORT;
if (!portText.isEmpty()) {
try {
port = Integer.parseInt(portText);
} catch (NumberFormatException error) {
setLanStatus("端口要是数字", C_WARN);
return;
}
}
if (code.isEmpty()) {
setLanStatus("要填 6 位配对码", C_WARN);
toast("还没填配对码");
return;
}
rememberPairingCode(code);
lastHost = host;
lastPort = port;
lastCode = code;
lanDirect = true; // 断了自动切回中转
appendLog("局域网直连 " + host + ":" + port);
connect(host, port, code, null,
DeviceStore.deviceKey(host, port, store.deviceId()));
}
// ==================================================================
// 服务器延迟检测
// ==================================================================
private void checkServer() {
final String relay = relayUrl();
if (relay.isEmpty()) {
if (serverStatus != null) {
serverStatus.setText("服务器:还没设中转地址");
serverStatus.setTextColor(C_WARN);
}
return;
}
if (serverStatus != null) {
serverStatus.setText("服务器:正在测…");
serverStatus.setTextColor(C_DIM);
}
appendLog("开始检查服务器 " + relay);
io.execute(new Runnable() {
@Override
public void run() {
final long started = System.currentTimeMillis();
String text;
int color = C_DIM;
try {
java.net.URL url = new java.net.URL(relay + "/v1/health");
java.net.HttpURLConnection conn =
(java.net.HttpURLConnection) url.openConnection();
conn.setRequestProperty("User-Agent", "LanRemote/1.4.0");
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
conn.setRequestMethod("GET");
int code = conn.getResponseCode();
long ms = System.currentTimeMillis() - started;
if (code == 200) {
text = "服务器:延迟 " + ms + " 毫秒";
color = ms < 300 ? C_OK : (ms < 900 ? C_WARN : C_DANGER);
} else {
text = "服务器:HTTP " + code;
color = C_DANGER;
}
try {
conn.disconnect();
} catch (Throwable ignored) {
// 关不掉也无所谓
}
} catch (Throwable error) {
text = "服务器:连不上(" + shortReason(error) + ")";
color = C_DANGER;
}
final String shown = text;
final int tint = color;
runOnUiThread(new Runnable() {
@Override
public void run() {
if (serverStatus != null) {
serverStatus.setText(shown);
serverStatus.setTextColor(tint);
}
appendLog(shown);
}
});
}
});
}
/** 异常名压短,免得界面上一长串。 */
private static String shortReason(Throwable error) {
String name = error.getClass().getSimpleName();
if (name.isEmpty()) {
name = "错误";
}
String message = String.valueOf(error.getMessage());
if (message.length() > 40) {
message = message.substring(0, 40);
}
return message.isEmpty() || "null".equals(message)
? name : (name + " " + message);
}
private void showConnectPanel() {'''
# ===================== 5. 返回键处理 =====================
BACK_ANCHOR = ''' protected void onDestroy() {'''
BACK_NEW = ''' private boolean closeLanIfOpen() {
if (lanPanel != null && lanPanel.getVisibility() == View.VISIBLE) {
hideLanPanel();
return true;
}
return false;
}
@Override
public void onBackPressed() {
// 局域网页面开着的话,先退回首页
if (closeLanIfOpen()) {
return;
}
super.onBackPressed();
}
@Override
protected void onDestroy() {'''
def main() -> int:
text = MAIN.read_text(encoding="utf-8")
if "buildLanPanel" in text:
print(" 已经加过")
return 0
ok = True
for old, new, label in (
(HOME_ANCHOR, HOME_NEW, "首页加局域网按钮"),
(HOME_LATENCY_ANCHOR, HOME_LATENCY_NEW, "首页加延迟状态"),
(FIELD_ANCHOR, FIELD_NEW, "加字段"),
(CREATE_ANCHOR, CREATE_NEW, "onCreate 建面板"),
(CREATE_ADD_ANCHOR, CREATE_ADD_NEW, "onCreate 挂面板"),
(CREATE_HIDE_ANCHOR, CREATE_HIDE_NEW, "onCreate 默认隐藏"),
(METHOD_ANCHOR, METHOD_NEW, "加局域网页面和延迟检测"),
(BACK_ANCHOR, BACK_NEW, "加返回键处理")):
if old not in text:
print(f" [失败] {label} —— 锚点没找到")
ok = False
continue
if text.count(old) != 1:
print(f" [警告] {label} 锚点出现 {text.count(old)} 次")
text = text.replace(old, new, 1)
print(f" {label}")
MAIN.write_text(text, encoding="utf-8", newline="\n")
try:
ast.parse(text)
except SyntaxError:
pass # Java 不是 Python,这里只占位
print(" 已写入")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())