# Windows 兼容性分析与注意事项 LocalNetMsg 是跨平台应用 (Electron 32 / Node 20 / Chromium 128)。这份文档记录 **Windows 上能跑什么、哪些地方有坑、以及已经修过的地方**,避免以后改代码时把 Windows 弄坏。 测试环境:Ubuntu 22.04 (x64) + Electron 32.3.3。Windows 侧结论基于 依赖包源码与打包产物的静态验证,**未在真实 Windows 机器上跑过**(见文末"待验证")。 --- ## 1. 结论速览 | 功能 | Windows | 说明 | |---|---|---| | 聊天 / 文件传输 / 网盘 | ✅ | 纯 Node HTTP/WS,无平台依赖 | | 局域网发现 | ✅ | UDP 广播,多网卡 + 255.255.255.255 兜底 | | 远程终端 | ✅(修过) | 需 ConPTY (Win10 1809+),打包规则曾有坑,见 §3.1 | | 串口转发 | ✅ | serialport 有 win32 预编译;虚拟串口走 com0com | | USB (libusb 字节桥) | ⚠️ | node-usb 可用,但需 WinUSB 驱动 (Zadig) | | USB/IP 真透传 | ❌ | Linux 内核特性,代码里已明确拒绝 | | 开机自启 | ✅ | `app.setLoginItemSettings` | | 托盘 / 通知 | ✅ | 托盘图标走 `resources/icon.png` | --- ## 2. 原生模块:全是 N-API 预编译,不需要针对 Electron 重编 这是 Windows 打包能不能成的第一道关。`build.npmRebuild` 是 `false` (原生模块已在 Electron ABI 上,重编反而坏事),所以**所有原生模块必须自带 跨 ABI 的预编译二进制**。现状: | 模块 | Windows 产物 | ABI | 是否需要编译 | |---|---|---|---| | `@lydell/node-pty` | `@lydell/node-pty-win32-x64` | N-API (`napi_register_module_v1`) | 否 | | `node-hid` | `prebuilds/HID-win32-x64/node-napi-v4.node` | N-API | 否 | | `@serialport/bindings-cpp` | `prebuilds/win32-x64/*.node` | N-API | 否 | | `usb` (node-usb 3) | `@node-usb/usb-win32-x64-msvc` | napi-rs | 否 | | `better-sqlite3` | 源码编译 | **非 N-API** | 是,靠 `postinstall` 的 `electron-builder install-app-deps` | > **坑**:`better-sqlite3` 是唯一需要编译的。`npmRebuild: false` 只影响打包期, > `postinstall` 仍然会重编它。**如果用 `npm ci --ignore-scripts`,应用会启动即崩** > (sqlite 打开失败)。 node-pty 1.2 换成了"主包 + 平台包"拆包架构:主包只是 loader, 真正的二进制在 `@lydell/node-pty--` 里。**换平台时别忘了 平台包也要在 asarUnpack 名单里**(见 §3.1)。 --- ## 3. 已修复的 Windows 阻塞项 ### 3.1 打包:node-pty 平台包没被解包 → Windows 终端起不来 **现象**:Linux 上终端正常,Windows 上一点就报 spawn 失败。 **根因**:`build.asarUnpack` 里写的是 ```json "**/node_modules/@lydell/node-pty-*/*" // ❌ 只匹配一层 ``` `/*` 只匹配直接子项,于是 `prebuilds/` 目录本身匹配上了,但 `prebuilds/win32-x64/conpty.node` 这种**两层深**的文件没匹配上 → 留在 asar 里。 **为什么 Linux 上没暴露**:Linux 的 PTY 只需要一个 `pty.node`, Electron 的 asar 层能 dlopen 归档里的 `.node`(实测可用)。 Windows 就不行了: - `conpty.node` 要加载同目录的 **`conpty.dll`**(OS loader 从真实路径加载,asar 读不了) - 还要 **`OpenConsole.exe`**(`CreateProcess` 必须是磁盘上的真文件) - node-pty 自己就写了 `__dirname.replace('node_modules.asar', 'node_modules.asar.unpacked')` —— 它**明确假设**平台包在 unpacked 目录里,因为 `worker_threads.Worker` 的脚本路径也必须是真实文件 **修复**: ```json "**/node_modules/@lydell/node-pty-*/**/*" // ✅ 递归 ``` 自测 S7 会守住这条规则(防止以后又被改回单层)。 ### 3.2 文件名:Windows 保留设备名 + 尾部点空格 Win32 的两个坑,Linux 上完全不存在: - **保留设备名**:`CON` `PRN` `AUX` `NUL` `COM1-9` `LPT1-9` `CLOCK$`, **带扩展名也算**(`CON.txt` 依然是控制台设备)。对这类名字 `open()` 打开的是设备, 数据会写进控制台或者直接失败。 - **尾部点/空格被悄悄吞掉**:`report.` 实际落到 `report`, 于是 `existsSync('report.')` 为真,而我们以为写的是另一个文件 —— 重名判断和实际落盘对不上。 **修复**:新增 `src/main/safe-name.ts` 的 `safeFileName()`, 在两处落盘前调用: - `file-server.ts` 的 `targetPath()` —— 接收方保存上传文件 - `drive.ts` 的 `downloadDriveFile()` —— 名字来自**对端共享目录**,属于不可信输入 清洗策略是"最小必要":非法字符 `\ / : * ? " < > |` 全平台都清; 保留名和尾点尾空格**只在 win32 上处理**,避免无谓改掉 Linux 上合法的文件名。 ### 3.3 终端工作目录 `process.env.HOME` 在 Windows 上通常不存在,但装了 Git Bash / MSYS 之后 可能被设成 POSIX 路径(`/c/Users/xxx`)—— 那个路径在 Win32 下不存在,pty 起不来。 **修复**:`shell.ts` 新增 `defaultCwd()`,win32 优先 `USERPROFILE`。 --- ## 4. 已有 Windows 分支的地方(别改坏) | 文件 | Windows 处理 | |---|---| | `remote/shell.ts` | `defaultShell()` 优先 PowerShell 7 (`pwsh`),其次 Windows PowerShell 5;用 `where` 而不是 `command -v` | | `remote/vcom.ts` | 虚拟串口走 com0com(自动下载安装器 + `setupc.exe` 配对),Linux/macOS 走 socat | | `permissions.ts` | `fixWindowsHID()` 用 Zadig 装 WinUSB 驱动;`diagnoseUsb()` 给 Windows 提示 | | `notify.ts` | win32 分支单独处理通知 | | `discovery.ts` | UDP `setBroadcast(true)`,同时发 255.255.255.255 / 127.0.0.1 / 每接口子网广播 | Linux 专属逻辑(udev 规则、dialout 组、usbip、/sys/bus/usb 读取)都在 `platform === 'linux'` 判断内,Windows 不会走到。 --- ## 5. 已知限制与注意事项 1. **ConPTY 需要 Windows 10 1809+**。更老的 Windows(Server 2016 及以前) 没有 ConPTY,node-pty 1.2 的 Windows 实现直接依赖它 → 终端不可用。 2. **Windows 防火墙**首次运行会弹窗问是否允许 Electron 访问网络。 用户若选了"取消",UDP 发现和 WS 都连不上 —— 这是最常见的"装完连不上"原因。 3. **USB 真透传需要 WinUSB 驱动**。node-usb 走 libusb/WinUSB, Windows 上必须用 Zadig 把设备驱动换成 WinUSB,否则 `loadDevices()` 枚举不到 (代码里已能自动下载 Zadig 并提权安装)。 4. **虚拟串口要 admin**。com0com 装驱动必须管理员,代码走 `sudo-prompt` 提权。 5. **`i.family` 的取值**。`discovery.ts` 里判断 `i.family !== 'IPv4'`, Node 18+ 才把 `family` 从数字 4 改成字符串 `'IPv4'`。Electron 32 = Node 20 ✓, 但别把这行改成数字比较。 6. **长路径**。Windows 默认 260 字符上限,`safeFileName` 把文件名截到 200 留余量, 但"深目录 + 长文件名"仍可能超限,需要系统开启 LongPathsEnabled。 7. **路径大小写不敏感**。`existsSync` 在 Windows 上大小写不敏感, 所以 `A.apk` 和 `a.apk` 会被当成同一个文件 —— 重名自增逻辑能正确处理, 但 Linux/macOS 上会存成两个文件。两端行为不一致,属预期。 --- ## 6. 在 Windows 上验证的清单 代码改完,Windows 侧至少要过这一遍(本机无法替代): - [ ] 安装后能启动、图标正常(见 `docs/connection-status.md` 里的图标坑) - [ ] 侧边栏能发现到对端设备(UDP 广播 + 防火墙) - [ ] 收发文字消息正常 - [ ] 收发文件正常,文件名带中文/特殊字符/`CON` 也不出错 - [ ] 网盘能浏览对方目录、能下载(含整目录保持层级) - [ ] 远程终端能打开、能看到回显、能输入 - [ ] 串口列表能看到 COM 口(设备管理器里有) - [ ] USB 模式能枚举到设备(装了 WinUSB 的话) - [ ] 托盘图标正常、右键菜单能唤出窗口 - [ ] 开机自启开关生效 --- ## 7. 相关文件 | 文件 | 作用 | |---|---| | `src/main/safe-name.ts` | 跨平台文件名清洗(Windows 保留名 / 尾点空格) | | `src/main/remote/shell.ts` | 跨平台 shell 选择 + 默认工作目录 | | `src/main/remote/vcom.ts` | 虚拟串口(socat / com0com) | | `src/main/permissions.ts` | 权限诊断与一键修复(udev / dialout / WinUSB) | | `build.asarUnpack` (package.json) | 原生模块解包规则,**必须递归** |