# -*- coding: utf-8 -*- """手表端「连不上?」:广播一个请求,让电脑把网络自检窗口弹出来。 放在**未连接**那个界面上(连不上时看到的就是它),不是控制页。 """ from __future__ import annotations import sys from pathlib import Path ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\android\src" r"\com\lanremote\watch") CORE = ROOT / "DiscoveryCore.java" WRAP = ROOT / "Discovery.java" MAIN = ROOT / "MainActivity.java" # ------------------------------------------------------------ DiscoveryCore CORE_CONST_OLD = ''' private static final String REPLY_MAGIC = "LANREMOTE_REPLY"; ''' CORE_CONST_NEW = ''' private static final String REPLY_MAGIC = "LANREMOTE_REPLY"; private static final String SELFCHECK_MAGIC = "LANREMOTE_SELFCHECK"; private static final String ACK_MAGIC = "LANREMOTE_ACK"; ''' CORE_ANCHOR = ''' /** 直接探测某个已知地址的 TCP 端口。 */''' CORE_NEW = ''' /** * 请电脑打开「网络自检」。 * * 走 UDP 广播是有意的:连不上的典型原因就是 TCP 被防火墙拦了, * 而 UDP 广播往往仍然通(那正是「能搜到但连不上」的原因)。 * 所以只要能广播出去,就能让电脑把自检窗口弹出来。 * * @return 电脑名;没有回应返回 null */ public static String requestSelfCheck(String extraBroadcast, int timeoutMs) { DatagramSocket socket = null; try { socket = new DatagramSocket(); socket.setBroadcast(true); socket.setSoTimeout(500); byte[] payload = ("{\\"lanremote\\":\\"" + SELFCHECK_MAGIC + "\\",\\"proto\\":1}").getBytes("UTF-8"); List targets = new ArrayList(); targets.add("255.255.255.255"); if (extraBroadcast != null && extraBroadcast.length() > 0) { targets.add(extraBroadcast); } // 连发三轮,丢一两个包也能到 for (int round = 0; round < 3; round++) { for (String target : targets) { try { socket.send(new DatagramPacket(payload, payload.length, InetAddress.getByName(target), PORT)); } catch (Exception ignored) { // 某个广播地址不被允许时跳过 } } try { Thread.sleep(120); } catch (InterruptedException stop) { Thread.currentThread().interrupt(); break; } } long deadline = System.currentTimeMillis() + timeoutMs; byte[] buffer = new byte[2048]; while (System.currentTimeMillis() < deadline) { DatagramPacket packet = new DatagramPacket(buffer, buffer.length); try { socket.receive(packet); } catch (SocketTimeoutException timeout) { continue; } catch (Exception broken) { break; } String text = new String(packet.getData(), 0, packet.getLength(), "UTF-8"); if (text.indexOf(ACK_MAGIC) < 0) { continue; } try { Map obj = Json.parseObject(text); String name = Json.str(obj, "name", ""); if (name != null && name.length() > 0) { return name; } } catch (Exception ignored) { // 解析失败就用来源 IP } return packet.getAddress().getHostAddress(); } } catch (Exception ignored) { // 网络不可用 } finally { if (socket != null) { socket.close(); } } return null; } /** 直接探测某个已知地址的 TCP 端口。 */''' # --------------------------------------------------------------- Discovery WRAP_ANCHOR = ''' public static DiscoveryCore.Found probeHost(String host, int timeoutMs) {''' WRAP_NEW = ''' /** * 请电脑打开「网络自检」。 * 返回电脑名;没收到回应返回 null。 */ public static String requestSelfCheck(Context context, int timeoutMs) { WifiManager.MulticastLock lock = acquireLock(context); try { return DiscoveryCore.requestSelfCheck(directedBroadcast(), timeoutMs); } finally { releaseLock(lock); } } public static DiscoveryCore.Found probeHost(String host, int timeoutMs) {''' # -------------------------------------------------------------- MainActivity MAIN_BTN_OLD = ''' statusView = new TextView(this); statusView.setTextColor(C_DIM);''' MAIN_BTN_NEW = ''' // 连不上?让电脑端把网络自检窗口弹出来(走 UDP,TCP 被拦时也能到) Button selfCheck = bigButton("连不上?让电脑开自检", C_WARN, 14); LinearLayout.LayoutParams scParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, dp(52)); scParams.setMargins(0, 0, 0, dp(8)); selfCheck.setLayoutParams(scParams); selfCheck.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { requestPcSelfCheck(); } }); box.addView(selfCheck); statusView = new TextView(this); statusView.setTextColor(C_DIM);''' MAIN_METHOD_ANCHOR = ''' // 自动搜索电脑(不用输 IP)''' MAIN_METHOD_NEW = ''' /** 连不上时:广播请求,让电脑把网络自检窗口打开。 */ private void requestPcSelfCheck() { statusView.setText("正在请电脑打开网络自检…"); appendLog("请求电脑打开网络自检"); io.execute(new Runnable() { @Override public void run() { final String answer = Discovery.requestSelfCheck(MainActivity.this, 5000); runOnUiThread(new Runnable() { @Override public void run() { if (answer != null) { statusView.setText("电脑 " + answer + " 已打开网络自检"); toast("电脑 " + answer + " 已打开自检窗口"); appendLog("电脑 " + answer + " 已打开网络自检"); } else { statusView.setText("没收到电脑回应"); toast("没收到回应:广播可能也没通"); appendLog("自检请求无回应 —— 广播没到电脑"); } } }); } }); } // 自动搜索电脑(不用输 IP)''' def main() -> int: ok = True def patch(path: Path, pairs, label: str) -> None: nonlocal ok text = path.read_text(encoding="utf-8") for old, new, tag in pairs: if old not in text: print(f" [失败] {label} / {tag} 锚点未匹配") ok = False return text = text.replace(old, new, 1) path.write_text(text, encoding="utf-8") print(f" {label}: OK") patch(CORE, [(CORE_CONST_OLD, CORE_CONST_NEW, "常量"), (CORE_ANCHOR, CORE_NEW, "requestSelfCheck")], "DiscoveryCore.java") patch(WRAP, [(WRAP_ANCHOR, WRAP_NEW, "包装")], "Discovery.java") patch(MAIN, [(MAIN_BTN_OLD, MAIN_BTN_NEW, "按钮"), (MAIN_METHOD_ANCHOR, MAIN_METHOD_NEW, "处理方法")], "MainActivity.java") return 0 if ok else 1 if __name__ == "__main__": sys.exit(main())