# -*- 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(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 labels = new ArrayList(); 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 labels = new ArrayList(); 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 reply = client.call(Json.obj("cmd", "proc_list"), 20000); final List names = stringList(reply, "names"); final List 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 request, final String field) { appendLog("正在获取" + title + "…"); io.execute(new Runnable() { @Override public void run() { try { final Map 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 reply = client.call(Json.obj("cmd", "clip_history"), 12000); final List texts = stringList(reply, "texts"); final List times = stringList(reply, "times"); ui.post(new Runnable() { @Override public void run() { if (texts.isEmpty()) { toast("电脑剪贴板还没有历史"); return; } List labels = new ArrayList(); 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())