mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 23:00:44 +00:00
被控端 - 修复 MTU 问题:夹 MSS + 路径 MTU 探测(公网可用率 48% -> 100%) - 复用 HTTP 连接(免掉每次新建 TLS 的 30% 握手失败) - 每分钟回头找局域网,自动切回快 400 倍的路径 - 画面心跳帧:静止时省流量,客户端仍知道流是活的 - 锁屏时给出可操作的提示 手表端 - 内置 GTS Root R4 / GlobalSign Root CA,修 SSLHandshakeException - 连接错误翻译成人话(证书/时间/解析/超时分别提示) - 经中转连上后用服务器报的地址刷新已配对记录 - 新增局域网连接页面、服务器延迟检测 服务器端 - 支持 chunked 请求体(修每 16 秒一次的连接错乱与大面积 502) - 网络调优脚本 net-tune.sh(开机自动应用) - 管理页:批量删除、网络体检、下载区 - 隧道改 HTTP/2 + 双连接 文档 - 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
560 lines
23 KiB
Python
560 lines
23 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""手表端 UI 彻底重做:分页 + 37 个功能。
|
||
|
||
原来是一长条 28 个按钮的网格,要滚 274dp。
|
||
现在分成 6 页,每页最多 7 个按钮 —— **完全不用滚动**。
|
||
|
||
媒体 (7) 屏幕 (6) 文件 (6) 语音 (5) 电脑 (6) 系统 (7)
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
MAIN = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\android\src"
|
||
r"\com\lanremote\watch\MainActivity.java")
|
||
|
||
START = " // 按钮网格\n"
|
||
END = " flushGrid(grid); // 按当前列数把上面入队的按钮切成行\n"
|
||
|
||
NEW_PANEL = ''' // ---- 分页标签 ----
|
||
HorizontalScrollView tabScroll = new HorizontalScrollView(this);
|
||
tabScroll.setHorizontalScrollBarEnabled(false);
|
||
tabBar = new LinearLayout(this);
|
||
tabBar.setOrientation(LinearLayout.HORIZONTAL);
|
||
tabBar.setPadding(0, 0, 0, 0);
|
||
tabScroll.addView(tabBar);
|
||
controlPanel.addView(tabScroll, new LinearLayout.LayoutParams(
|
||
ViewGroup.LayoutParams.MATCH_PARENT, dp(42)));
|
||
|
||
// ---- 分页内容 ----
|
||
tabContent = new LinearLayout(this);
|
||
tabContent.setOrientation(LinearLayout.VERTICAL);
|
||
tabContent.setPadding(0, dp(6), 0, dp(6));
|
||
ScrollView contentScroll = new ScrollView(this);
|
||
contentScroll.addView(tabContent, new LinearLayout.LayoutParams(
|
||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||
controlPanel.addView(contentScroll, new LinearLayout.LayoutParams(
|
||
ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||
'''
|
||
|
||
# ---------------------------------------------------------------- tabs 机制
|
||
|
||
TAB_METHODS_ANCHOR = ''' private Button bigButton(String text, int color, float sizeSp) {'''
|
||
|
||
TAB_METHODS = ''' // ==================================================================
|
||
// 分页
|
||
// ==================================================================
|
||
|
||
private static final class Act {
|
||
final String label;
|
||
final Runnable task;
|
||
|
||
Act(String label, Runnable task) {
|
||
this.label = label;
|
||
this.task = task;
|
||
}
|
||
}
|
||
|
||
private static final class TabDef {
|
||
final String name;
|
||
final Act[] acts;
|
||
|
||
TabDef(String name, Act[] acts) {
|
||
this.name = name;
|
||
this.acts = acts;
|
||
}
|
||
}
|
||
|
||
private TabDef[] tabDefs;
|
||
private LinearLayout tabBar;
|
||
private LinearLayout tabContent;
|
||
private int currentTab = 0;
|
||
|
||
private Act act(String label, Runnable task) {
|
||
return new Act(label, task);
|
||
}
|
||
|
||
/** 定义所有分页。每页最多 7 个按钮,一屏放得下,不用滚。 */
|
||
private void initTabs() {
|
||
tabDefs = new TabDef[]{
|
||
new TabDef("媒体", new Act[]{
|
||
act("播放/暂停", new Runnable() {
|
||
public void run() { media("play_pause"); }
|
||
}),
|
||
act("停止", new Runnable() {
|
||
public void run() { media("stop"); }
|
||
}),
|
||
act("上一曲", new Runnable() {
|
||
public void run() { media("prev"); }
|
||
}),
|
||
act("下一曲", new Runnable() {
|
||
public void run() { media("next"); }
|
||
}),
|
||
act("音量 +", new Runnable() {
|
||
public void run() { media("vol_up"); }
|
||
}),
|
||
act("音量 -", new Runnable() {
|
||
public void run() { media("vol_down"); }
|
||
}),
|
||
act("静音", new Runnable() {
|
||
public void run() { media("mute"); }
|
||
}),
|
||
}),
|
||
new TabDef("屏幕", new Act[]{
|
||
act("屏幕镜像", new Runnable() {
|
||
public void run() { openMirrorPanel(); }
|
||
}),
|
||
act("截屏", new Runnable() {
|
||
public void run() { doScreenshot(); }
|
||
}),
|
||
act("投图到电脑", new Runnable() {
|
||
public void run() { pickFile(REQ_IMAGE, "image/*"); }
|
||
}),
|
||
act("放音到电脑", new Runnable() {
|
||
public void run() { pickFile(REQ_AUDIO, "audio/*"); }
|
||
}),
|
||
act("关显示器", new Runnable() {
|
||
public void run() { confirmMonitor(); }
|
||
}),
|
||
act("设为壁纸", new Runnable() {
|
||
public void run() { confirmWallpaper(); }
|
||
}),
|
||
}),
|
||
new TabDef("文件", new Act[]{
|
||
act("发送文件", new Runnable() {
|
||
public void run() { pickFile(REQ_FILE, "*/*"); }
|
||
}),
|
||
act("接收文件", new Runnable() {
|
||
public void run() { showReceivedFiles(); }
|
||
}),
|
||
act("剪贴板→电脑", new Runnable() {
|
||
public void run() { promptClipSet(); }
|
||
}),
|
||
act("取电脑剪贴板", new Runnable() {
|
||
public void run() { fetchClip(); }
|
||
}),
|
||
act("剪贴板历史", new Runnable() {
|
||
public void run() { pickClipHistory(); }
|
||
}),
|
||
act("打开文件夹", new Runnable() {
|
||
public void run() { pickFolder(); }
|
||
}),
|
||
}),
|
||
new TabDef("语音", new Act[]{
|
||
act("语音播报", new Runnable() {
|
||
public void run() { promptSay(); }
|
||
}),
|
||
act("音色", new Runnable() {
|
||
public void run() { pickVoice(); }
|
||
}),
|
||
act("朗读剪贴板", new Runnable() {
|
||
public void run() {
|
||
sendCommand(Json.obj("cmd", "say_clip"), "朗读剪贴板");
|
||
}
|
||
}),
|
||
act("电脑音乐", new Runnable() {
|
||
public void run() { pickLocalAudio(); }
|
||
}),
|
||
act("弹通知", new Runnable() {
|
||
public void run() { promptNotify(); }
|
||
}),
|
||
}),
|
||
new TabDef("电脑", new Act[]{
|
||
act("快捷命令", new Runnable() {
|
||
public void run() { pickQuickApp(); }
|
||
}),
|
||
act("打开网址", new Runnable() {
|
||
public void run() { promptOpenUrl(); }
|
||
}),
|
||
act("进程管理", new Runnable() {
|
||
public void run() { pickProcess(); }
|
||
}),
|
||
act("性能", new Runnable() {
|
||
public void run() { showPerf(); }
|
||
}),
|
||
act("磁盘", new Runnable() {
|
||
public void run() { showDisk(); }
|
||
}),
|
||
act("电量", new Runnable() {
|
||
public void run() { showBattery(); }
|
||
}),
|
||
}),
|
||
new TabDef("系统", new Act[]{
|
||
act("锁屏", new Runnable() {
|
||
public void run() { confirmPower("lock", "锁屏"); }
|
||
}),
|
||
act("睡眠", new Runnable() {
|
||
public void run() { confirmPower("sleep", "睡眠"); }
|
||
}),
|
||
act("关机", new Runnable() {
|
||
public void run() { confirmPower("shutdown", "关机"); }
|
||
}),
|
||
act("重启", new Runnable() {
|
||
public void run() { confirmPower("reboot", "重启"); }
|
||
}),
|
||
act("取消定时关机", new Runnable() {
|
||
public void run() { cancelShutdown(); }
|
||
}),
|
||
act("定时关机", new Runnable() {
|
||
public void run() { pickShutdownTimer(); }
|
||
}),
|
||
act("设备诊断", new Runnable() {
|
||
public void run() { openDiagPanel(); }
|
||
}),
|
||
}),
|
||
};
|
||
|
||
tabBar.removeAllViews();
|
||
for (int i = 0; i < tabDefs.length; i++) {
|
||
final int index = i;
|
||
TextView tab = new TextView(this);
|
||
tab.setText(tabDefs[i].name);
|
||
tab.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13);
|
||
tab.setGravity(Gravity.CENTER);
|
||
tab.setPadding(dp(14), dp(8), dp(14), dp(8));
|
||
tab.setOnClickListener(new View.OnClickListener() {
|
||
@Override
|
||
public void onClick(View view) {
|
||
selectTab(index);
|
||
}
|
||
});
|
||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||
ViewGroup.LayoutParams.MATCH_PARENT);
|
||
params.setMargins(0, 0, dp(6), 0);
|
||
tab.setLayoutParams(params);
|
||
tabBar.addView(tab);
|
||
}
|
||
selectTab(0);
|
||
}
|
||
|
||
private void selectTab(int index) {
|
||
if (tabDefs == null || index < 0 || index >= tabDefs.length) {
|
||
return;
|
||
}
|
||
currentTab = index;
|
||
for (int i = 0; i < tabBar.getChildCount(); i++) {
|
||
View child = tabBar.getChildAt(i);
|
||
boolean on = (i == index);
|
||
child.setBackground(rounded(on ? C_ACCENT : C_CARD, 8));
|
||
if (child instanceof TextView) {
|
||
((TextView) child).setTextColor(on ? 0xFFFFFFFF : C_FG);
|
||
}
|
||
}
|
||
tabContent.removeAllViews();
|
||
cellQueue.clear();
|
||
for (Act item : tabDefs[index].acts) {
|
||
addRow(tabContent, action(item.label, item.task), null);
|
||
}
|
||
flushGrid(tabContent);
|
||
}
|
||
|
||
// ==================================================================
|
||
// 新功能
|
||
// ==================================================================
|
||
|
||
private void media(String what) {
|
||
sendCommand(Json.obj("cmd", "media", "action", what), "媒体");
|
||
}
|
||
|
||
private void confirmMonitor() {
|
||
new AlertDialog.Builder(this)
|
||
.setTitle("关闭显示器")
|
||
.setMessage("要关掉电脑显示器吗?(动一下鼠标或按键就能唤醒)")
|
||
.setPositiveButton("关掉", new android.content.DialogInterface.OnClickListener() {
|
||
@Override
|
||
public void onClick(android.content.DialogInterface d, int w) {
|
||
sendCommand(Json.obj("cmd", "monitor", "on", false), "关显示器");
|
||
}
|
||
})
|
||
.setNegativeButton("取消", null)
|
||
.show();
|
||
}
|
||
|
||
private void confirmWallpaper() {
|
||
new AlertDialog.Builder(this)
|
||
.setTitle("设为壁纸")
|
||
.setMessage("把电脑上最近收到的那张图片设为桌面壁纸?")
|
||
.setPositiveButton("设置", new android.content.DialogInterface.OnClickListener() {
|
||
@Override
|
||
public void onClick(android.content.DialogInterface d, int w) {
|
||
sendCommand(Json.obj("cmd", "set_wallpaper"), "设置壁纸");
|
||
}
|
||
})
|
||
.setNegativeButton("取消", null)
|
||
.show();
|
||
}
|
||
|
||
private void pickShutdownTimer() {
|
||
final int[] minutes = {15, 30, 60, 120};
|
||
final String[] labels = {"15 分钟后", "30 分钟后", "1 小时后", "2 小时后"};
|
||
showChooser("定时关机", new ArrayList<String>(java.util.Arrays.asList(labels)),
|
||
new Chooser() {
|
||
@Override
|
||
public void onPick(int index) {
|
||
sendCommand(Json.obj("cmd", "shutdown_timer",
|
||
"minutes", minutes[index]), "定时关机");
|
||
}
|
||
});
|
||
}
|
||
|
||
private void cancelShutdown() {
|
||
sendCommand(Json.obj("cmd", "shutdown_timer", "cancel", true), "取消定时关机");
|
||
}
|
||
|
||
private void promptOpenUrl() {
|
||
promptInput("在电脑上打开网址", "如 baidu.com", "https://", new InputHandler() {
|
||
@Override
|
||
public void onInput(String text) {
|
||
if (!text.isEmpty()) {
|
||
sendCommand(Json.obj("cmd", "open_url", "url", text), "打开网址");
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
private void pickQuickApp() {
|
||
final String[][] apps = {
|
||
{"任务管理器", "taskmgr"},
|
||
{"资源管理器", "explorer"},
|
||
{"命令提示符", "cmd"},
|
||
{"计算器", "calc"},
|
||
{"记事本", "notepad"},
|
||
{"画图", "mspaint"},
|
||
{"控制面板", "control"},
|
||
{"设备管理器", "devmgmt"},
|
||
};
|
||
List<String> labels = new ArrayList<String>();
|
||
for (String[] item : apps) {
|
||
labels.add(item[0]);
|
||
}
|
||
showChooser("在电脑上打开", labels, new Chooser() {
|
||
@Override
|
||
public void onPick(int index) {
|
||
sendCommand(Json.obj("cmd", "quick", "app", apps[index][1]),
|
||
"打开" + apps[index][0]);
|
||
}
|
||
});
|
||
}
|
||
|
||
private void pickFolder() {
|
||
final String[][] folders = {
|
||
{"下载", "downloads"},
|
||
{"文档", "documents"},
|
||
{"桌面", "desktop"},
|
||
{"图片", "pictures"},
|
||
{"音乐", "music"},
|
||
{"接收目录", "received"},
|
||
{"程序目录", "app"},
|
||
};
|
||
List<String> labels = new ArrayList<String>();
|
||
for (String[] item : folders) {
|
||
labels.add(item[0]);
|
||
}
|
||
showChooser("打开电脑文件夹", labels, new Chooser() {
|
||
@Override
|
||
public void onPick(int index) {
|
||
sendCommand(Json.obj("cmd", "open_folder", "folder", folders[index][1]),
|
||
"打开" + folders[index][0]);
|
||
}
|
||
});
|
||
}
|
||
|
||
private void pickProcess() {
|
||
appendLog("正在获取电脑进程…");
|
||
io.execute(new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
try {
|
||
final Map<String, Object> reply =
|
||
client.call(Json.obj("cmd", "proc_list"), 20000);
|
||
final List<String> names = stringList(reply, "names");
|
||
final List<String> pids = stringList(reply, "pids");
|
||
ui.post(new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
if (names.isEmpty()) {
|
||
toast("没读到进程,或没连上");
|
||
return;
|
||
}
|
||
showChooser("占内存最多的进程", names, new Chooser() {
|
||
@Override
|
||
public void onPick(int index) {
|
||
confirmKill(names.get(index), pids.get(index));
|
||
}
|
||
});
|
||
}
|
||
});
|
||
} catch (final IOException e) {
|
||
postError("获取进程", e);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
private void confirmKill(String label, String pid) {
|
||
final int target;
|
||
try {
|
||
target = Integer.parseInt(pid);
|
||
} catch (NumberFormatException e) {
|
||
toast("进程号不对");
|
||
return;
|
||
}
|
||
new AlertDialog.Builder(this)
|
||
.setTitle("结束进程")
|
||
.setMessage("确定要结束这个进程吗?\n\n" + label)
|
||
.setPositiveButton("结束", new android.content.DialogInterface.OnClickListener() {
|
||
@Override
|
||
public void onClick(android.content.DialogInterface d, int w) {
|
||
sendCommand(Json.obj("cmd", "proc_kill", "pid", target), "结束进程");
|
||
}
|
||
})
|
||
.setNegativeButton("取消", null)
|
||
.show();
|
||
}
|
||
|
||
private void showPerf() {
|
||
fetchAndShow("性能", Json.obj("cmd", "perf"), "note");
|
||
}
|
||
|
||
private void showDisk() {
|
||
fetchAndShow("磁盘占用", Json.obj("cmd", "disk"), "note");
|
||
}
|
||
|
||
private void showBattery() {
|
||
fetchAndShow("电脑电量", Json.obj("cmd", "battery"), "note");
|
||
}
|
||
|
||
/** 拉一条命令,把 note 用对话框显示出来。 */
|
||
private void fetchAndShow(final String title, final Map<String, Object> request,
|
||
final String field) {
|
||
appendLog("正在获取" + title + "…");
|
||
io.execute(new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
try {
|
||
final Map<String, Object> reply = client.call(request, 15000);
|
||
final boolean ok = Json.bool(reply, "ok", false);
|
||
final String note = ok
|
||
? Json.str(reply, field, "(无内容)")
|
||
: Json.str(reply, "error", "读取失败");
|
||
ui.post(new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
appendLog(title + ":" + note);
|
||
new AlertDialog.Builder(MainActivity.this)
|
||
.setTitle(title)
|
||
.setMessage(note)
|
||
.setPositiveButton("知道了", null)
|
||
.show();
|
||
}
|
||
});
|
||
} catch (final IOException e) {
|
||
postError(title, e);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
private void pickClipHistory() {
|
||
appendLog("正在获取剪贴板历史…");
|
||
io.execute(new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
try {
|
||
final Map<String, Object> reply =
|
||
client.call(Json.obj("cmd", "clip_history"), 12000);
|
||
final List<String> texts = stringList(reply, "texts");
|
||
final List<String> times = stringList(reply, "times");
|
||
ui.post(new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
if (texts.isEmpty()) {
|
||
toast("电脑剪贴板还没有历史");
|
||
return;
|
||
}
|
||
List<String> labels = new ArrayList<String>();
|
||
for (int i = 0; i < texts.size(); i++) {
|
||
String preview = texts.get(i).replace("\\n", " ");
|
||
if (preview.length() > 22) {
|
||
preview = preview.substring(0, 22) + "…";
|
||
}
|
||
String when = i < times.size() ? times.get(i) + " " : "";
|
||
labels.add(when + preview);
|
||
}
|
||
showChooser("剪贴板历史(点一条取到手表)", labels, new Chooser() {
|
||
@Override
|
||
public void onPick(int index) {
|
||
copyToWatchClipboard(texts.get(index));
|
||
toast("已复制到手表剪贴板");
|
||
appendLog("取回一条剪贴板历史");
|
||
}
|
||
});
|
||
}
|
||
});
|
||
} catch (final IOException e) {
|
||
postError("剪贴板历史", e);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
'''
|
||
|
||
|
||
def main() -> int:
|
||
text = MAIN.read_text(encoding="utf-8")
|
||
start = text.find(START)
|
||
end = text.find(END)
|
||
if start < 0 or end < 0:
|
||
print(f" [失败] 找不到网格区块 (start={start}, end={end})")
|
||
return 1
|
||
end += len(END)
|
||
text = text[:start] + NEW_PANEL + text[end:]
|
||
print(" 分页面板: OK")
|
||
|
||
if TAB_METHODS_ANCHOR not in text:
|
||
print(" [失败] bigButton 锚点未匹配")
|
||
return 1
|
||
text = text.replace(TAB_METHODS_ANCHOR, TAB_METHODS + TAB_METHODS_ANCHOR, 1)
|
||
print(" 分页机制 + 新功能: OK")
|
||
|
||
# addRow 要能接受 null(一页里按钮数可能是奇数)
|
||
old_add = ''' private void addRow(LinearLayout parent, View left, View right) {
|
||
// 只入队,真正的行切分在 flushGrid() 里按 gridColumns 统一做。
|
||
// 这样上层那串 addRow(a, b) 不用改,列数就能自适应。
|
||
cellQueue.add(left);
|
||
cellQueue.add(right);
|
||
}'''
|
||
new_add = ''' private void addRow(LinearLayout parent, View left, View right) {
|
||
// 只入队,真正的行切分在 flushGrid() 里按 gridColumns 统一做。
|
||
// 允许传 null:分页里某一页的按钮数可能是奇数。
|
||
if (left != null) {
|
||
cellQueue.add(left);
|
||
}
|
||
if (right != null) {
|
||
cellQueue.add(right);
|
||
}
|
||
}'''
|
||
if old_add in text:
|
||
text = text.replace(old_add, new_add, 1)
|
||
print(" addRow 支持 null: OK")
|
||
else:
|
||
print(" [警告] addRow 未替换")
|
||
|
||
# 初始化分页
|
||
old_init = " showConnectPanel();"
|
||
new_init = " initTabs();\n showConnectPanel();"
|
||
if old_init in text:
|
||
text = text.replace(old_init, new_init, 1)
|
||
print(" initTabs 调用: OK")
|
||
|
||
MAIN.write_text(text, encoding="utf-8")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|