Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,17 @@ jobs:
shell: bash
run: |
set -euo pipefail
helper="native/macos/.build/lanextend-vdisplay"
test -f "$helper"
lipo "$helper" -verify_arch arm64 x86_64
display_helper="native/macos/.build/lanextend-vdisplay"
input_helper="native/macos/.build/lanextend-input"
test -f "$display_helper"
test -f "$input_helper"
lipo "$display_helper" -verify_arch arm64 x86_64
lipo "$input_helper" -verify_arch arm64 x86_64

app_binary="$(find release -type f -path '*/LanExtend.app/Contents/MacOS/LanExtend' -print -quit)"
test -n "$app_binary"
lipo "$app_binary" -verify_arch arm64 x86_64
file "$helper" "$app_binary"
file "$display_helper" "$input_helper" "$app_binary"

- name: Upload macOS packages
uses: actions/upload-artifact@v4
Expand Down
26 changes: 22 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# LanExtend

LanExtend 是一个面向可信局域网的双端扩展屏 MVP:macOS 主端创建一块虚拟显示器,捕获该显示器后通过 WebRTC 将视频发送到 Windows 子端 GUI。主端发起连接并拥有会话控制权;子端负责被发现、接收信令和全屏显示
LanExtend 是一个面向局域网的 Mac/Windows 双端协作工具,包含两种独立模式:把 Windows 变成 Mac 无线扩展屏,或在不传输画面的情况下,让一套 Mac 键盘鼠标无缝控制两台设备并双向同步剪贴板

> 当前定位是可继续开发和双机验收的工程交付,不是已签名的正式产品。仓库内的自动化测试不等于真实 Mac/Windows、不同网卡、GPU 解码或长时间运行验证。

Expand All @@ -13,8 +13,9 @@ LanExtend 是一个面向可信局域网的双端扩展屏 MVP:macOS 主端创
| 拓扑 | 一台 Mac 主端连接一台 Windows 子端;子端同时只接受一个主端会话 |
| 网络 | 同一可信 IPv4 局域网;自动 UDP 广播发现,也可填写私有 IPv4 地址 |
| 画面 | 单路视频,默认建议 1920×1080、30 FPS、8 Mbps |
| 记忆 | 本机保存分辨率、帧率、码率、最近设备、已发现/手动添加设备和子端名称/端口 |
| 当前不支持 | 音频、键鼠/触控回传、HDR、多子端、IPv6、跨公网、账号/配对/认证 |
| 键鼠共享 | Mac 鼠标跨越屏幕边缘后控制 Windows;支持二维布局、按键/滚轮和纯文本剪贴板同步 |
| 记忆 | 本机保存画面参数、最近设备、二维设备布局、边缘停留时间和剪贴板开关 |
| 当前不支持 | 音频、触控、文件剪贴板、HDR、多子端、IPv6、跨公网、账号/配对/认证 |
| 分发 | 不支持 Mac App Store;CI 产物未配置 Developer ID 签名、公证或 Windows 代码签名 |

macOS 虚拟显示依赖未公开的 `CGVirtualDisplay` CoreGraphics API。它可能在系统更新后变化或失效,因此本项目把相关逻辑隔离在独立 helper 进程中,但无法消除兼容性风险。
Expand All @@ -27,6 +28,9 @@ macOS 虚拟显示依赖未公开的 `CGVirtualDisplay` CoreGraphics API。它
- Windows 子端通过 UDP 广播,Mac 主端监听并合并本机记忆列表。
- WebSocket 信令、协议版本/消息大小/字段校验、单主端占用保护。
- 主端主动连接、断开;子端可全屏并在会话期间阻止显示器休眠。
- 独立的键鼠共享模式:不创建虚拟屏、不传输画面,使用可拖拽二维布局实现跨边缘切换。
- Mac 全局键鼠捕获、Windows 本地输入注入、紧急返回快捷键 `⌃⌥⌘Esc`。
- 双向纯文本剪贴板同步,远端内容写入后不会被立即回传形成循环。
- 子端画面信息条默认隐藏,鼠标移动、触摸或键盘聚焦时短暂显示,避免遮挡扩展桌面。
- JSON 设置持久化,最多记忆 32 台设备;离线设备仍可显示和删除。
- 启动时及侧边栏手动检查 GitHub Releases;发现新版本后打开官方发布页,由用户下载并安装。
Expand Down Expand Up @@ -65,7 +69,9 @@ npm run build:native
npm run dev:host
```

按系统提示授予“屏幕与系统音频录制”(不同 macOS 版本名称可能略有不同)和本地网络访问权限;修改权限后完全退出并重新启动 LanExtend。
按系统提示授予“屏幕与系统音频录制”(不同 macOS 版本名称可能略有不同)和本地网络访问权限。安装包用户应只从 `/Applications/LanExtend.app` 启动;授权后返回应用会自动重新检测。源码目录或构建目录中的副本会被 macOS 当作不同的权限主体。

键鼠共享不需要屏幕录制,但首次启动时需要 macOS“辅助功能”权限;这是系统捕获全局键鼠事件的必要条件。

### 4. 创建并投放扩展屏

Expand All @@ -76,6 +82,15 @@ npm run dev:host
5. 如果私有虚拟显示不可用,GUI 会切到“已有显示器”兼容模式;此时才需要手动选择捕获源。选择已有屏会发送该屏全部内容,请先清除敏感信息。
6. 使用主端“断开扩展屏”结束投放;由本会话自动创建的虚拟显示器会随断开清理。

### 5. 启动键鼠和剪贴板共享

1. 在同一个设备列表选择 Windows 子端,然后滚动到“键鼠与剪贴板共享”。
2. 在布局画布中拖动橙色 Windows 屏幕,使其贴到对应 Mac 显示器的左、右、上或下边缘;“自动排列”会把它放到最上方 Mac 屏幕的右侧。
3. 根据需要开启“双向剪贴板同步”,并设置边缘停留时间。默认 `80 ms` 兼顾快速切换和减少误触。
4. 点击“启动键鼠共享”。此模式不会创建扩展屏,也不会发送任何屏幕画面。
5. 鼠标越过相邻边缘后,键盘、鼠标按键和滚轮会控制 Windows;从 Windows 对应边缘移回即可返回 Mac。
6. 任意时候按 `Control + Option + Command + Esc` 可强制把控制权返回 Mac。

完整操作和未签名产物说明见[用户指南](docs/user-guide.md)。公开安装包可从 [GitHub Releases](https://github.com/Modole/LanExtend/releases) 获取。

## 架构概览
Expand All @@ -84,19 +99,22 @@ npm run dev:host
flowchart LR
subgraph H["macOS 主端(最高控制权)"]
GUIH["Electron GUI"] --> VD["CGVirtualDisplay helper"]
GUIH --> IH["全局键鼠 helper"]
VD --> CAP["虚拟显示器 / 屏幕捕获"]
GUIH --> MEMH["本机 settings.json"]
CAP --> RTC1["WebRTC 发送端"]
end
subgraph R["Windows 子端"]
GUIR["Electron GUI"] --> RTCR["WebRTC 接收端"]
GUIR --> IW["Windows 输入 helper"]
GUIR --> MEMR["本机 settings.json"]
GUIR --> ADV["UDP 广播"]
GUIR --> SIG["WebSocket 信令服务"]
end
ADV -- "UDP/47771" --> GUIH
GUIH -- "ws://子端:47772" --> SIG
RTC1 -- "WebRTC 加密媒体(单路视频)" --> RTCR
IH -- "WebSocket 键鼠 / 剪贴板" --> IW
```

发现报文只用于定位子端;远端媒体不经过云服务。WebRTC 媒体本身使用其标准加密传输,但**设备身份、发现和 WebSocket 信令均未认证**,所以必须把整个二层/三层局域网视为信任边界。
Expand Down
2 changes: 1 addition & 1 deletion THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Third-Party Notices

This file describes direct runtime/build dependencies and implementation references for LanExtend 0.2.0. The authoritative resolved dependency graph is `package-lock.json`. A release maintainer must re-run license review whenever the lockfile or packaging inputs change.
This file describes direct runtime/build dependencies and implementation references for LanExtend 0.2.1. The authoritative resolved dependency graph is `package-lock.json`. A release maintainer must re-run license review whenever the lockfile or packaging inputs change.

LanExtend source code is licensed under the repository's MIT License. Third-party components remain under their respective licenses.

Expand Down
32 changes: 23 additions & 9 deletions docs/acceptance.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

- 主端:macOS 14+;至少一台 Apple Silicon Mac。若声明 Intel 支持,还需一台 Intel Mac 单独通过。
- 子端:Windows 10 22H2 64 位和 Windows 11 64 位应分别验收;如果只测其中一个,只能声明该系统已测。
- MVP:一主一子、单路视频、可信 IPv4 LAN、无认证。
- 明确排除:音频、键鼠/触控回传、HDR、多子端、IPv6、公网/NAT、App Store。
- MVP:一主一子、单路视频或独立键鼠共享、纯文本剪贴板、可信 IPv4 LAN、无认证。
- 明确排除:音频、触控、文件/图片剪贴板、HDR、多子端、IPv6、公网/NAT、App Store。

## 2. 验收记录模板

Expand Down Expand Up @@ -41,6 +41,7 @@ Windows 型号 / CPU / GPU / Windows build:
| A09 | 许可证 | 人工检查 | LICENSE、第三方声明、锁文件与打包依赖一致 | 执行时填写 |
| A10 | 文档范围 | 人工检查 | 明确 14+、Win10/11、MVP 限制、无认证和无签名 | 执行时填写 |
| A11 | 更新检查 | `tests/updates.test.js` + GUI | 语义版本比较正确,只接受本仓库 HTTPS Release 链接,失败可重试 | 执行时填写 |
| A12 | 输入助手探测 | `lanextend-input --probe` | Mac 返回 `trusted: true`;Windows 包含 PowerShell helper | **必须双机填写** |

## 4. 安装与首次启动(P0)

Expand Down Expand Up @@ -93,7 +94,20 @@ Windows 型号 / CPU / GPU / Windows build:
- [ ] R12 让 Mac/Windows 分别睡眠/唤醒,记录实际行为;若不能恢复,UI 至少允许干净断开重连。
- [ ] R13 子端视频出现后底部信息条默认隐藏;画面内移动鼠标/触摸/键盘聚焦后显示,无操作约 2.2 秒后隐藏;悬停按钮时不会在操作中消失。

## 8. 画质、性能与稳定性(P0/P1)
## 8. 键鼠与剪贴板共享(P0)

- [ ] K01 键鼠模式启动时不创建虚拟显示器、不请求屏幕录制、不建立 WebRTC 视频轨道。
- [ ] K02 GUI 正确显示全部 Mac 屏幕;Windows 节点可拖动并吸附四个方向,完全重启后布局保持。
- [ ] K03 从每条已配置相邻边缘进入 Windows,Windows 光标位置连续;从对应边缘返回正确 Mac 屏幕。
- [ ] K04 左/右/中键、拖拽、滚轮、字母数字、方向键、退格、回车和常用组合键工作。
- [ ] K05 `Command+C/V` 在 Windows 映射为 `Ctrl+C/V`;左右修饰键释放后没有粘键。
- [ ] K06 `Control+Option+Command+Esc` 总能返回 Mac,并释放 Windows 上全部键鼠按钮。
- [ ] K07 拔网、关闭 Windows、停止共享和 helper 异常退出后 Mac 本地输入立即恢复。
- [ ] K08 两端复制纯文本可双向同步且不回环;空文本、中文、emoji、128 KiB 边界按定义工作。
- [ ] K09 图片、文件和超过上限文本不造成崩溃,也不被误写为纯文本。
- [ ] K10 连续跨越边缘 100 次,无明显延迟累积、粘键或残留 helper。

## 9. 画质、性能与稳定性(P0/P1)

本 MVP 没有承诺固定延迟/FPS;以下是建议验收基线,不应在未测时写入产品宣传。

Expand All @@ -109,33 +123,33 @@ Windows 型号 / CPU / GPU / Windows build:

如果要声明延迟数值,必须说明测量方法(相机/时间码)、网络、分辨率、帧率、编码路径、样本数和 P50/P95;主观“很低”不能作为证据。

## 9. 安全负向验收(P0)
## 10. 安全负向验收(P0)

- [ ] S01 README/GUI 明确“仅可信内网、无认证”,没有“安全连接/受信设备”等误导文案。
- [ ] S02 Windows 信令端口未映射公网,防火墙不对公用网络开放。
- [ ] S03 伪造/超大/无效发现包不会导致主端崩溃或写入非法公网设备。
- [ ] S04 超 256 KiB、未知类型、版本错误或畸形信令被拒绝并关闭。
- [ ] S05 任意 HTTPS 之外页面不能在 Electron 内导航,渲染层没有 Node 集成。
- [ ] S06 配置文件不保存屏幕帧、SDP/ICE、密码或长期密钥;日志默认不输出敏感信令。
- [ ] S07 屏幕权限拒绝时失败关闭,不尝试 TCC 绕过;应用不申请辅助功能/麦克风
- [ ] S07 屏幕权限拒绝只影响扩展屏;辅助功能拒绝只影响键鼠共享;应用不尝试绕过 TCC。
- [ ] S08 明确记忆设备可伪造,不把 UUID/名称当认证。
- [ ] S09 未签名 artifacts 明确标记开发验收用途,没有建议全局关闭 Gatekeeper/SmartScreen。
- [ ] S10 第三方 GPL/AGPL 方案未复制进 MIT 实现;引用与许可证边界有记录。

## 10. 当前明确不验收为“支持”的能力
## 11. 当前明确不验收为“支持”的能力

以下项目若意外“看似可用”也不应纳入 v0.2.0 支持声明:
以下项目若意外“看似可用”也不应纳入 v0.3.0 支持声明:

- 音频播放/转发;
- Windows 到 Mac 的键盘、鼠标、触控、剪贴板或文件回传
- 触控、图片/文件剪贴板或剪贴板历史同步
- HDR、广色域、色彩校准保证;
- 一台 Mac 同时连接多个 Windows 子端或创建多块受管扩展屏;
- IPv6、DNS 名称、跨公网、NAT、TURN、云中继;
- 身份认证、配对、授权、受信设备安全列表;
- Mac App Store、后台下载/静默自动安装、正式签名/公证;
- 无人值守服务和企业集中管理。

## 11. 发布判定
## 12. 发布判定

### 可交付源码/开发预览

Expand Down
25 changes: 21 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

## 1. 目标与非目标

LanExtend 的 MVP 目标是让一台 macOS 14+ 电脑把一块“真正的扩展桌面”显示到同一可信局域网中的一台 Windows 10/11 电脑。主端决定创建/销毁显示器、选择捕获源、连接/断开和画质参数;子端只提供发现、信令和显示窗口
LanExtend 提供两个独立目标:让 macOS 14+ 把一块“真正的扩展桌面”显示到 Windows 10/11,或在两台设备各自显示本地内容时,让 Mac 的键盘、鼠标和纯文本剪贴板无缝切换到 Windows。主端决定连接、布局和功能开关

当前非目标包括音频、远端键鼠/触控、HDR、跨公网、NAT 穿透、IPv6、多子端并发、后台服务、账号体系、设备配对和企业级策略管理
当前非目标包括音频、触控、文件/图片剪贴板、HDR、跨公网、NAT 穿透、IPv6、多子端并发、后台服务、账号体系和企业级策略管理

## 2. 组件与职责

Expand All @@ -14,6 +14,8 @@ LanExtend 的 MVP 目标是让一台 macOS 14+ 电脑把一块“真正的扩展
| 沙箱渲染进程 | 两端 | GUI、WebRTC 协商、主端捕获、子端播放和状态展示 | 当前会话中断,可重启应用 |
| preload 桥 | 两端 | 暴露白名单 IPC,隔离 Node 能力 | GUI 无法调用系统能力 |
| `lanextend-vdisplay` | macOS | 探测/调用私有 `CGVirtualDisplay` API,拥有一块虚拟显示器 | 虚拟显示器被系统移除 |
| `lanextend-input` | macOS | 监听屏幕边缘、接管全局键鼠并把事件写为 JSONL | 键鼠共享停止,Mac 本地输入恢复 |
| `lanextend-input.ps1` | Windows | 常驻读取 JSONL,调用 Win32 API 注入键鼠 | Windows 不再接收远端输入 |
| UDP advertiser | Windows | 每 1.5 秒广播子端元数据 | 自动发现失效,手动地址仍可用 |
| UDP listener | macOS | 监听并维护在线设备,6 秒未见即离线 | 自动发现失效 |
| WebSocket server | Windows | 接受一个主端并转发 WebRTC 信令 | 无法建会话;已有媒体也可能终止 |
Expand Down Expand Up @@ -53,6 +55,7 @@ sequenceDiagram
- 主端必须先收到 `welcome` 并取得子端真实 UUID,再用该 UUID 生成稳定虚拟显示 serial、创建显示器和发起 offer;手动地址占位 ID 会在此时被真实 ID 替换。
- UDP 发现只携带定位和能力信息;收到报文时使用 UDP 数据包的来源 IPv4 作为子端地址,不信任报文自报地址。
- WebSocket 只传 `hello/offer/answer/ice/ping/pong/disconnect` 等 JSON 信令,不承载视频帧。
- 键鼠模式复用同一 WebSocket,增加 `control/input/clipboard` 消息;它不创建 WebRTC 或虚拟显示器。
- 子端拒绝第二个同时在线的主端,返回 WebSocket 关闭码 `1013`。

### 媒体面
Expand All @@ -78,6 +81,17 @@ sequenceDiagram

HiDPI 模式把 GUI 请求宽高视为**逻辑桌面尺寸**,物理帧缓冲宽高各为 2×。例如 1920×1080 HiDPI 对应 1920×1080 逻辑空间和 3840×2160 物理帧缓冲。像素数变为非 HiDPI 同逻辑尺寸的四倍,会显著增加 WindowServer、捕获和内存开销;发送端会尝试按所选逻辑分辨率约束/缩放 WebRTC,但最终编码尺寸必须以运行统计为准。

### 键鼠共享生命周期

1. GUI 读取 Mac 当前显示器坐标,并把 Windows 屏幕作为可拖拽矩形保存到同一逻辑坐标系。
2. 主端发出 `control/share-start`;Windows 启动常驻输入 helper 并返回主屏尺寸。
3. Mac 启动 `lanextend-input`。未跨边缘时事件原样交给 macOS,只观察鼠标位置。
4. 鼠标穿过与 Windows 矩形相邻的边缘后,helper 暂停向本机投递键鼠事件;主端把绝对鼠标位置、按键和滚轮通过 WebSocket 发送。
5. Windows helper 使用 Win32 API 执行输入。返回相邻边缘、断线或停止时会先释放全部按键。
6. `Control + Option + Command + Esc` 是本地紧急返回组合,不会发送给 Windows。

共享期间两端每 500 ms 检查纯文本剪贴板。启动共享时以 Mac 当前纯文本为初始值,之后任一端内容变化都会发送 `clipboard` 消息;应用远端内容时先更新本地去重值,避免形成回传循环。Windows 布局使用主显示器物理像素宽高,使高 DPI 缩放下的绝对光标坐标与 Win32 一致。

## 5. 配置与“记忆”模型

配置位于 Electron `app.getPath('userData')/settings.json`,写入时先创建同目录临时文件再原子重命名;类 Unix 平台创建文件时请求 `0600` 权限。数据模型版本当前为 `schemaVersion: 1`。
Expand All @@ -92,6 +106,9 @@ settings.json
│ ├── id(首次运行生成并保持)
│ ├── name / port
│ └── autoFullscreen
├── inputSharing
│ ├── lastDeviceId / clipboard / autoReconnect / edgeDelayMs
│ └── layouts[](每个设备的 x/y/width/height)
└── rememberedDevices[最多 32]
└── id / name / host / port / lastSeen / lastConnected
```
Expand All @@ -104,7 +121,7 @@ settings.json
| --- | --- | --- | --- |
| 屏幕录制 | 必需 | 不需要 | 缺失时无法可靠列出/捕获扩展屏,常见表现是黑屏 |
| 本地网络 | 必需 | Windows 防火墙放行 | 自动发现和局域网连接所需 |
| 辅助功能 | 不需要 | 不需要 | MVP 没有输入回传 |
| 辅助功能 | 键鼠共享必需 | 不需要 | macOS 全局事件 tap 的系统要求 |
| 麦克风/系统音频 | 不需要 | 不需要 | MVP 没有音频 |
| 管理员/root | 通常不需要 | 通常不需要 | 开放普通高位端口,不安装驱动/服务 |

Expand Down Expand Up @@ -140,4 +157,4 @@ MVP 没有认证和信令机密性,因此安全性依赖网络隔离、端点
3. 增加明确的会话确认弹窗、设备指纹、撤销和连接审计。
4. 增加端到端统计、崩溃恢复、编码器能力探测和分辨率/码率自适应。
5. 完成 Apple Silicon/Intel、Windows 10/11、Wi-Fi/以太网组合的长期真机矩阵。
6. 再评估音频、输入回传、多子端与 NAT 场景;每项都扩大权限和攻击面,应单独设计
6. 再评估音频、触控、文件剪贴板、多子端与 NAT 场景;每项都应单独设计和验收
Loading
Loading