mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-01 11:22:38 +08:00
[feat] improve CLI commands, controls and npm packaging
This commit is contained in:
@@ -29,14 +29,8 @@ jobs:
|
|||||||
- runner: windows-2022
|
- runner: windows-2022
|
||||||
target: win32-x64
|
target: win32-x64
|
||||||
arch: x64
|
arch: x64
|
||||||
- runner: windows-11-arm
|
|
||||||
target: win32-arm64
|
|
||||||
arch: arm64
|
|
||||||
runs-on: ${{ matrix.runner }}
|
runs-on: ${{ matrix.runner }}
|
||||||
timeout-minutes: 120
|
timeout-minutes: 120
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -58,12 +52,18 @@ jobs:
|
|||||||
run: sh apps/cli/scripts/prepare-deps.sh
|
run: sh apps/cli/scripts/prepare-deps.sh
|
||||||
- name: Configure macOS
|
- name: Configure macOS
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
run: xmake f -y -a ${{ matrix.arch }} -m release --target_minver=13.0 --CROSSDESK_BUILD_DESKTOP=n --CROSSDESK_BUILD_CLI=y --USE_CUDA=n
|
run: xmake f -y -p macosx -a ${{ matrix.arch }} -m release --target_minver=13.0 --CROSSDESK_BUILD_DESKTOP=n --CROSSDESK_BUILD_CLI=y --USE_CUDA=n
|
||||||
- name: Configure Linux or Windows
|
- name: Configure Linux
|
||||||
if: runner.os != 'macOS'
|
if: runner.os == 'Linux'
|
||||||
run: xmake f -y -a ${{ matrix.arch }} -m release --CROSSDESK_BUILD_DESKTOP=n --CROSSDESK_BUILD_CLI=y --USE_CUDA=n
|
run: xmake f -y -p linux -a ${{ matrix.arch }} -m release --CROSSDESK_BUILD_DESKTOP=n --CROSSDESK_BUILD_CLI=y --USE_CUDA=n
|
||||||
|
- name: Configure Windows
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
run: xmake f -y -p windows -a ${{ matrix.arch }} -m release --CROSSDESK_BUILD_DESKTOP=n --CROSSDESK_BUILD_CLI=y --USE_CUDA=n
|
||||||
- name: Build
|
- name: Build
|
||||||
run: xmake build crossdesk-cli
|
run: xmake build crossdesk-cli
|
||||||
|
- name: Sign macOS CLI
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
run: codesign --force --sign - build/macosx/${{ matrix.arch }}/release/crossdesk-cli
|
||||||
- name: Report native environment
|
- name: Report native environment
|
||||||
run: xmake run crossdesk-cli doctor --json
|
run: xmake run crossdesk-cli doctor --json
|
||||||
- name: Package native program and npm entry
|
- name: Package native program and npm entry
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="docs/images/crossdesk-banner.svg" width="420" alt="CrossDesk 标志:图标在左,双色文字在右" />
|
<img src="docs/images/crossdesk-banner.svg" width="420" alt="CrossDesk 标志:图标在左,双色文字在右" />
|
||||||
<p>轻量、跨平台的远程桌面,支持电脑、浏览器和移动端连接,用桌面墙集中查看与操控多台设备。</p>
|
<p>轻量、跨平台的远程桌面,支持电脑、浏览器、移动端和终端连接,用桌面墙集中查看与操控多台设备。</p>
|
||||||
<p><a href="https://www.crossdesk.cn/">官方网站</a> · <a href="https://github.com/kunkundi/crossdesk/releases">下载客户端</a> · <a href="https://web.crossdesk.cn/">打开 Web 客户端</a> · <a href="README_EN.md">English</a></p>
|
<p><a href="https://www.crossdesk.cn/">官方网站</a> · <a href="https://github.com/kunkundi/crossdesk/releases">下载客户端</a> · <a href="https://web.crossdesk.cn/">打开 Web 客户端</a> · <a href="README_EN.md">English</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -19,9 +19,9 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
CrossDesk 让你从电脑、浏览器或手机连接远端电脑,也能通过 **桌面墙(DeskHub)** 集中查看与操控多台设备。项目基于 [MiniRTC](https://github.com/kunkundi/minirtc),支持自托管。
|
CrossDesk 让你从电脑、浏览器、手机或终端连接远端电脑,也能通过 **桌面墙(DeskHub)** 集中查看与操控多台设备。项目基于 [MiniRTC](https://github.com/kunkundi/minirtc),支持自托管。
|
||||||
|
|
||||||
[主要功能](#features) · [下载与平台支持](#download) · [快速连接](#quick-start) · [IP 直连](#lan-direct) · [界面预览](#preview) · [使用指南](docs/USER_GUIDE.md) · [自托管](#self-hosting) · [构建与反馈](#development) · [赞助](#sponsor)
|
[主要功能](#features) · [下载与平台支持](#download) · [快速连接](#quick-start) · [终端 CLI](#cli) · [IP 直连](#lan-direct) · [界面预览](#preview) · [使用指南](docs/USER_GUIDE.md) · [自托管](#self-hosting) · [构建与反馈](#development) · [赞助](#sponsor)
|
||||||
|
|
||||||
<a id="features"></a>
|
<a id="features"></a>
|
||||||
|
|
||||||
@@ -29,6 +29,7 @@ CrossDesk 让你从电脑、浏览器或手机连接远端电脑,也能通过
|
|||||||
|
|
||||||
- **跨平台远控:** Windows、macOS、Linux 桌面互控,浏览器及原生 iOS / Android 客户端访问电脑;鸿蒙 PC 与移动端处于开发阶段。
|
- **跨平台远控:** Windows、macOS、Linux 桌面互控,浏览器及原生 iOS / Android 客户端访问电脑;鸿蒙 PC 与移动端处于开发阶段。
|
||||||
- **局域网 IP 直连:** 桌面客户端通过 IP、端口和六位密码连接,无需云端信令服务;支持独立端口、记住密码及按模式显示近期设备。
|
- **局域网 IP 直连:** 桌面客户端通过 IP、端口和六位密码连接,无需云端信令服务;支持独立端口、记住密码及按模式显示近期设备。
|
||||||
|
- **终端主控:** 在终端中通过设备 ID 观看和操控远程桌面,支持 ANSI / Kitty 画面、局部缩放与平移、持续本地命令模式及 Pi 扩展,可单独构建。
|
||||||
- **桌面墙:** 多设备实时预览、分组、放大操控、群控和批量文件分发。
|
- **桌面墙:** 多设备实时预览、分组、放大操控、群控和批量文件分发。
|
||||||
- **画面与声音:** H.264 / AV1、硬件编解码、远端音频,以及会话内画质和帧率调整;可用能力随平台与设备而异。
|
- **画面与声音:** H.264 / AV1、硬件编解码、远端音频,以及会话内画质和帧率调整;可用能力随平台与设备而异。
|
||||||
- **日常协作:** 多显示器切换、键鼠与组合键、文本剪贴板、文件传输。
|
- **日常协作:** 多显示器切换、键鼠与组合键、文本剪贴板、文件传输。
|
||||||
@@ -50,8 +51,9 @@ CrossDesk 让你从电脑、浏览器或手机连接远端电脑,也能通过
|
|||||||
| 鸿蒙 PC(开发版) | **控制 / 被控**;API 26,arm64;使用 DevEco 构建并签名 HAP,仍在进行真机验收 |
|
| 鸿蒙 PC(开发版) | **控制 / 被控**;API 26,arm64;使用 DevEco 构建并签名 HAP,仍在进行真机验收 |
|
||||||
| 鸿蒙手机 / 平板(开发版) | **仅控制**;API 20+,arm64;使用 DevEco 构建并签名 HAP,仍在进行真机验收 |
|
| 鸿蒙手机 / 平板(开发版) | **仅控制**;API 20+,arm64;使用 DevEco 构建并签名 HAP,仍在进行真机验收 |
|
||||||
| Web | **仅控制**;支持 WebRTC 的浏览器;无需在控制端安装客户端 |
|
| Web | **仅控制**;支持 WebRTC 的浏览器;无需在控制端安装客户端 |
|
||||||
|
| 终端 CLI(开发版) | **仅控制**;构建目标为 macOS / Linux(x64、arm64)和 Windows(x64);从源码构建,平台验收范围见 [CLI 说明](apps/cli/README.md) |
|
||||||
|
|
||||||
系统要求按当前源码与构建配置整理,具体安装包以对应 Release 说明为准。各客户端的功能范围见 [移动端与鸿蒙](#mobile)。
|
系统要求按当前源码与构建配置整理,具体安装包以对应 Release 说明为准。各客户端的功能范围见 [终端 CLI](#cli) 与 [移动端与鸿蒙](#mobile)。
|
||||||
|
|
||||||
- **Linux:** 已有桌面选普通版 `crossdesk-linux-…deb`;需要独立 Xfce 桌面的服务器选 `crossdesk-virtual-desktop-xfce-…deb`。两者选择其一,安装与启动方法见 [使用指南](docs/USER_GUIDE.md#installation) 和 [无头运行](docs/HEADLESS.md)。
|
- **Linux:** 已有桌面选普通版 `crossdesk-linux-…deb`;需要独立 Xfce 桌面的服务器选 `crossdesk-virtual-desktop-xfce-…deb`。两者选择其一,安装与启动方法见 [使用指南](docs/USER_GUIDE.md#installation) 和 [无头运行](docs/HEADLESS.md)。
|
||||||
- **macOS:** 首次运行时按提示授予屏幕录制及辅助功能权限;前者用于桌面采集,后者用于远程键鼠输入。[权限说明](docs/USER_GUIDE.md#installation)
|
- **macOS:** 首次运行时按提示授予屏幕录制及辅助功能权限;前者用于桌面采集,后者用于远程键鼠输入。[权限说明](docs/USER_GUIDE.md#installation)
|
||||||
@@ -122,6 +124,59 @@ CrossDesk 让你从电脑、浏览器或手机连接远端电脑,也能通过
|
|||||||
- [客户端设置与更新](docs/USER_GUIDE.md#settings):常规设置、桌面墙传输及版本检查。
|
- [客户端设置与更新](docs/USER_GUIDE.md#settings):常规设置、桌面墙传输及版本检查。
|
||||||
- [桌面墙技术说明](docs/deskhub.md):预览订阅、带宽分配、协议与兼容性。
|
- [桌面墙技术说明](docs/deskhub.md):预览订阅、带宽分配、协议与兼容性。
|
||||||
|
|
||||||
|
<a id="cli"></a>
|
||||||
|
|
||||||
|
## 终端客户端 CLI
|
||||||
|
|
||||||
|
`crossdesk-cli` 在终端中通过 9 位设备 ID 连接被控电脑,默认只读、静音。普通终端使用 ANSI 字符画面,支持 Kitty 图形协议的终端可显示像素画面;可切换显示器、调整画质、放大细节和平移,也可明确开启键鼠控制。CLI 可以独立于桌面 GUI 构建,被控电脑仍需运行 CrossDesk 并提供可捕获的图形会话,信令服务需支持 `cli` 临时身份注册。
|
||||||
|
|
||||||
|
### 从源码构建并连接
|
||||||
|
|
||||||
|
准备 Git、Xmake 3.1.1 或以上、C++17 编译工具链及对应平台依赖。以下以 macOS / Linux shell 为例;Windows 的 Git Bash / 开发者 PowerShell 步骤见 [CLI 构建说明](apps/cli/docs/terminal-client.md#构建与安装)。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone --recurse-submodules https://github.com/kunkundi/crossdesk.git
|
||||||
|
cd crossdesk
|
||||||
|
./apps/cli/scripts/prepare-deps.sh
|
||||||
|
xmake f -y --CROSSDESK_BUILD_DESKTOP=n --CROSSDESK_BUILD_CLI=y --USE_CUDA=n -m release
|
||||||
|
xmake build crossdesk-cli
|
||||||
|
xmake run crossdesk-cli doctor --json
|
||||||
|
xmake run crossdesk-cli 123456789
|
||||||
|
```
|
||||||
|
|
||||||
|
已有仓库时从仓库根目录执行依赖准备及后续命令。将 `123456789` 换成被控电脑的 ID,密码在连接时隐藏输入。`doctor` 只检查本机环境,不发起连接。仓库默认构建桌面端;以上配置只构建 CLI,需要同时构建桌面端时将 `CROSSDESK_BUILD_DESKTOP` 设为 `y`。产物位于 `build/<platform>/<arch>/release/`。
|
||||||
|
|
||||||
|
ANSI 全景受终端字符网格限制。阅读小字可添加 `--quality high --zoom native`,或进入画面后按 `d` 查看细节;`--renderer kitty` 需要终端支持对应协议。显示、鼠标报告、SSH 和终端兼容性见 [使用说明](apps/cli/docs/terminal-client.md)。
|
||||||
|
|
||||||
|
### 控制、查看与退出
|
||||||
|
|
||||||
|
| 模式 / 按键 | 行为 |
|
||||||
|
| --- | --- |
|
||||||
|
| 只读模式 | `c` 开启远端控制;`q` 或 `Ctrl+C` 退出 CLI |
|
||||||
|
| 控制模式 | 键鼠输入传给远端;`Ctrl+]` 进入本地命令模式并暂停远端输入 |
|
||||||
|
| 本地命令模式 | 执行命令后持续保持;`c` 选择手动返回后是否启用控制,`q` 退出 CLI |
|
||||||
|
| `Esc` / `Ctrl+C` | 在本地命令模式中手动返回所选的控制或只读模式 |
|
||||||
|
| `+` / `=` / `-` | 在只读或本地命令模式中放大 / 缩小 |
|
||||||
|
| `d` / `f` | 在只读或本地命令模式中切换原始细节 / 完整桌面;只读时也可点击目标进入细节 |
|
||||||
|
| 方向键 / `i j k l` | 在只读或本地命令模式中平移;`i` 上、`j` 左、`k` 下、`l` 右 |
|
||||||
|
|
||||||
|
`Ctrl+]` 模式不会因为缩放、平移或按 `c` 自动结束。状态栏中的 `CONTROL PAUSED` / `READ ONLY` 表示返回后是否启用控制。控制模式中的普通 `c`、`q` 和 `Ctrl+C` 会发送到远端,需要先按 `Ctrl+]` 执行本地操作;再次按 `Ctrl+]` 也可返回,若返回控制模式则向远端发送一次该组合键。
|
||||||
|
|
||||||
|
### Pi 扩展与功能范围
|
||||||
|
|
||||||
|
CLI 附带 Pi 自定义 TUI 扩展,使用同样的输入模式。在仓库根目录启动,例如 macOS Apple Silicon 的 release 构建:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export CROSSDESK_CLI="$PWD/build/macosx/arm64/release/crossdesk-cli"
|
||||||
|
pi -e ./apps/cli/plugins/pi/index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
其他平台将 `CROSSDESK_CLI` 改为原生可执行文件的绝对路径。在 Pi 中输入 `/crossdesk` 或 `/crossdesk 123456789`,按提示输入密码。安装要求与验证范围见 [Pi 扩展说明](apps/cli/docs/terminal-client.md#pi-扩展)。
|
||||||
|
|
||||||
|
CLI 当前仅提供 ID 连接与远程观看 / 输入,不提供 IP 直连、音频播放、文件传输或剪贴板同步。macOS arm64 已完成本地构建和连接验证;Linux / Windows 完整运行、SSH / Kitty 实机显示及真实 Pi TUI 的验收范围见 [验证记录](apps/cli/docs/validation.md)。平台构建配置与 npm 打包流程不代表全部平台已验收或包已发布。
|
||||||
|
|
||||||
|
[CLI 文档入口](apps/cli/README.md) · [终端使用与故障排查](apps/cli/docs/terminal-client.md) · [桥接协议](apps/cli/docs/bridge-protocol.md) · [npm 打包与分发](apps/cli/docs/npm-distribution.md)
|
||||||
|
|
||||||
<a id="mobile"></a>
|
<a id="mobile"></a>
|
||||||
|
|
||||||
## 移动端与鸿蒙
|
## 移动端与鸿蒙
|
||||||
@@ -169,8 +224,7 @@ PC 端可控制和被控制,复用桌面界面与桌面墙;手机和平板
|
|||||||
|
|
||||||
## 构建与反馈
|
## 构建与反馈
|
||||||
|
|
||||||
- [终端客户端 CLI](apps/cli/README.md):ANSI / Kitty 远程桌面观看、可选键鼠控制和 Pi 扩展;支持独立于桌面 GUI 的构建,npm 分发尚未发布。
|
- [终端客户端 CLI](apps/cli/README.md):独立构建、终端使用、Pi 扩展、桥接协议和 npm 打包。
|
||||||
|
|
||||||
- [源码构建](docs/BUILD.md):桌面端依赖、编译参数与打包;移动端请使用上方各平台的构建指南。
|
- [源码构建](docs/BUILD.md):桌面端依赖、编译参数与打包;移动端请使用上方各平台的构建指南。
|
||||||
- [GUI 架构](docs/gui-architecture.md):桌面界面、平台实现与工程结构。
|
- [GUI 架构](docs/gui-architecture.md):桌面界面、平台实现与工程结构。
|
||||||
- [连接模式架构](docs/connection-architecture.md):ID 连接与 IP 直连的寻址、认证、协商、数据路径及生命周期。
|
- [连接模式架构](docs/connection-architecture.md):ID 连接与 IP 直连的寻址、认证、协商、数据路径及生命周期。
|
||||||
|
|||||||
+60
-6
@@ -1,6 +1,6 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="docs/images/crossdesk-banner.svg" width="420" alt="CrossDesk logo with the icon on the left and a two-tone wordmark on the right" />
|
<img src="docs/images/crossdesk-banner.svg" width="420" alt="CrossDesk logo with the icon on the left and a two-tone wordmark on the right" />
|
||||||
<p>A lightweight, cross-platform remote desktop for computers, browsers, and mobile devices, with DeskHub for viewing and controlling multiple desktops.</p>
|
<p>A lightweight, cross-platform remote desktop for computers, browsers, mobile devices, and terminals, with DeskHub for viewing and controlling multiple desktops.</p>
|
||||||
<p><a href="https://www.crossdesk.cn/">Website</a> · <a href="https://github.com/kunkundi/crossdesk/releases">Download</a> · <a href="https://web.crossdesk.cn/">Open Web Client</a> · <a href="README.md">中文</a></p>
|
<p><a href="https://www.crossdesk.cn/">Website</a> · <a href="https://github.com/kunkundi/crossdesk/releases">Download</a> · <a href="https://web.crossdesk.cn/">Open Web Client</a> · <a href="README.md">中文</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -19,9 +19,9 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
CrossDesk connects your computers from desktop, browser, and mobile clients, with **DeskHub** for viewing and controlling multiple devices together. It is built on [MiniRTC](https://github.com/kunkundi/minirtc) and supports self-hosting.
|
CrossDesk connects your computers from desktop, browser, mobile, and terminal clients, with **DeskHub** for viewing and controlling multiple devices together. It is built on [MiniRTC](https://github.com/kunkundi/minirtc) and supports self-hosting.
|
||||||
|
|
||||||
[Features](#features) · [Download and platforms](#download) · [Quick start](#quick-start) · [Direct IP](#lan-direct) · [Preview](#preview) · [User guide](docs/USER_GUIDE_EN.md) · [Self-hosting](#self-hosting) · [Build and feedback](#development) · [Sponsor](#sponsor)
|
[Features](#features) · [Download and platforms](#download) · [Quick start](#quick-start) · [Terminal CLI](#cli) · [Direct IP](#lan-direct) · [Preview](#preview) · [User guide](docs/USER_GUIDE_EN.md) · [Self-hosting](#self-hosting) · [Build and feedback](#development) · [Sponsor](#sponsor)
|
||||||
|
|
||||||
<a id="features"></a>
|
<a id="features"></a>
|
||||||
|
|
||||||
@@ -29,6 +29,7 @@ CrossDesk connects your computers from desktop, browser, and mobile clients, wit
|
|||||||
|
|
||||||
- **Cross-platform access:** control Windows, macOS, and Linux desktops from computers, browsers, or native iOS / Android clients. HarmonyOS PC and mobile clients are in development.
|
- **Cross-platform access:** control Windows, macOS, and Linux desktops from computers, browsers, or native iOS / Android clients. HarmonyOS PC and mobile clients are in development.
|
||||||
- **Direct IP over LAN:** connect desktop clients using an IP address, port, and six-character password without a cloud signaling service. Each local IP has its own port; passwords can be remembered and recent devices are separated by mode.
|
- **Direct IP over LAN:** connect desktop clients using an IP address, port, and six-character password without a cloud signaling service. Each local IP has its own port; passwords can be remembered and recent devices are separated by mode.
|
||||||
|
- **Terminal controller:** view and control remote desktops by device ID, with ANSI / Kitty rendering, zoom and pan, persistent local command mode, a Pi extension, and a build independent of the desktop GUI.
|
||||||
- **DeskHub:** live multi-device previews, groups, enlarged control, group input, and batch file distribution.
|
- **DeskHub:** live multi-device previews, groups, enlarged control, group input, and batch file distribution.
|
||||||
- **Video and audio:** H.264 / AV1, hardware codecs, remote audio, and in-session quality and frame-rate controls. Capabilities depend on the platform and device.
|
- **Video and audio:** H.264 / AV1, hardware codecs, remote audio, and in-session quality and frame-rate controls. Capabilities depend on the platform and device.
|
||||||
- **Everyday controls:** monitor switching, keyboard and mouse input, shortcuts, text clipboard, and file transfer.
|
- **Everyday controls:** monitor switching, keyboard and mouse input, shortcuts, text clipboard, and file transfer.
|
||||||
@@ -50,8 +51,9 @@ Get a package matching your OS and CPU architecture from the [website](https://w
|
|||||||
| HarmonyOS PC (development) | **Control + host**. API 26, arm64; build and sign a HAP with DevEco; physical-device validation is ongoing |
|
| HarmonyOS PC (development) | **Control + host**. API 26, arm64; build and sign a HAP with DevEco; physical-device validation is ongoing |
|
||||||
| HarmonyOS phones / tablets (development) | **Control only**. API 20+, arm64; build and sign a HAP with DevEco; physical-device validation is ongoing |
|
| HarmonyOS phones / tablets (development) | **Control only**. API 20+, arm64; build and sign a HAP with DevEco; physical-device validation is ongoing |
|
||||||
| Web | **Control only**. A WebRTC-capable browser; no client installation on the controlling device |
|
| Web | **Control only**. A WebRTC-capable browser; no client installation on the controlling device |
|
||||||
|
| Terminal CLI (development) | **Control only**. Build targets: macOS / Linux (x64, arm64) and Windows (x64). Build from source; see the [CLI guide](apps/cli/README.md) (Chinese) for platform validation status |
|
||||||
|
|
||||||
Requirements follow the current source and build configuration. Check the release notes for the downloaded package. Client capabilities are summarized under [Mobile and HarmonyOS](#mobile).
|
Requirements follow the current source and build configuration. Check the release notes for the downloaded package. Client capabilities are summarized under [Terminal CLI](#cli) and [Mobile and HarmonyOS](#mobile).
|
||||||
|
|
||||||
- **Linux:** use `crossdesk-linux-…deb` for an existing desktop, or `crossdesk-virtual-desktop-xfce-…deb` for a server needing a separate Xfce desktop. Choose one. See [installation details](docs/USER_GUIDE_EN.md#installation) and [headless operation](docs/HEADLESS_EN.md).
|
- **Linux:** use `crossdesk-linux-…deb` for an existing desktop, or `crossdesk-virtual-desktop-xfce-…deb` for a server needing a separate Xfce desktop. Choose one. See [installation details](docs/USER_GUIDE_EN.md#installation) and [headless operation](docs/HEADLESS_EN.md).
|
||||||
- **macOS:** grant Screen Recording and Accessibility when prompted on first launch. These enable desktop capture and remote keyboard/mouse input. See [permissions](docs/USER_GUIDE_EN.md#installation).
|
- **macOS:** grant Screen Recording and Accessibility when prompted on first launch. These enable desktop capture and remote keyboard/mouse input. See [permissions](docs/USER_GUIDE_EN.md#installation).
|
||||||
@@ -122,6 +124,59 @@ Remote sessions support monitor switching, video quality and frame-rate controls
|
|||||||
- [Client settings and updates](docs/USER_GUIDE_EN.md#settings): general options, DeskHub video settings, and version checks.
|
- [Client settings and updates](docs/USER_GUIDE_EN.md#settings): general options, DeskHub video settings, and version checks.
|
||||||
- [DeskHub technical guide](docs/deskhub.md) (Chinese): preview subscriptions, bandwidth allocation, protocol, and compatibility.
|
- [DeskHub technical guide](docs/deskhub.md) (Chinese): preview subscriptions, bandwidth allocation, protocol, and compatibility.
|
||||||
|
|
||||||
|
<a id="cli"></a>
|
||||||
|
|
||||||
|
## Terminal client CLI
|
||||||
|
|
||||||
|
`crossdesk-cli` connects to a host by its 9-digit device ID and starts in read-only, silent mode. Standard terminals use ANSI character rendering; terminals supporting the Kitty graphics protocol can display pixel graphics. The CLI supports monitor selection, quality settings, detail zoom, panning, and optional keyboard/mouse control. It builds independently of the desktop GUI. The host still needs CrossDesk and a capturable graphical session, and the signaling service must support temporary `cli` identities.
|
||||||
|
|
||||||
|
### Build and connect
|
||||||
|
|
||||||
|
Install Git, Xmake 3.1.1 or later, a C++17 toolchain, and the platform dependencies. The following example uses a macOS / Linux shell; Windows Git Bash / Developer PowerShell steps are in the [CLI build guide](apps/cli/docs/terminal-client.md#构建与安装) (Chinese).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone --recurse-submodules https://github.com/kunkundi/crossdesk.git
|
||||||
|
cd crossdesk
|
||||||
|
./apps/cli/scripts/prepare-deps.sh
|
||||||
|
xmake f -y --CROSSDESK_BUILD_DESKTOP=n --CROSSDESK_BUILD_CLI=y --USE_CUDA=n -m release
|
||||||
|
xmake build crossdesk-cli
|
||||||
|
xmake run crossdesk-cli doctor --json
|
||||||
|
xmake run crossdesk-cli 123456789
|
||||||
|
```
|
||||||
|
|
||||||
|
For an existing checkout, run the dependency preparation and subsequent commands from the repository root. Replace `123456789` with the host's ID; the password prompt hides your input. `doctor` reports the local environment without connecting. The repository builds the desktop client by default; this configuration builds only the CLI. Set `CROSSDESK_BUILD_DESKTOP=y` to also build the desktop client. Binaries are placed in `build/<platform>/<arch>/release/`.
|
||||||
|
|
||||||
|
An ANSI overview is limited by the terminal's character grid. Add `--quality high --zoom native`, or press `d` in the viewer, to inspect small text. `--renderer kitty` requires a terminal supporting that protocol. See the [terminal guide](apps/cli/docs/terminal-client.md) (Chinese) for rendering, mouse reporting, SSH, and terminal compatibility.
|
||||||
|
|
||||||
|
### Control, navigation, and exit
|
||||||
|
|
||||||
|
| Mode / key | Behavior |
|
||||||
|
| --- | --- |
|
||||||
|
| Read-only mode | `c` enables remote control; `q` or `Ctrl+C` exits the CLI |
|
||||||
|
| Control mode | Keyboard/mouse input goes to the host; `Ctrl+]` enters local command mode and pauses remote input |
|
||||||
|
| Local command mode | Stays active after commands; `c` selects whether control resumes when you leave; `q` exits the CLI |
|
||||||
|
| `Esc` / `Ctrl+C` | Manually leave local command mode and return to the selected control or read-only mode |
|
||||||
|
| `+` / `=` / `-` | Zoom in / out in read-only or local command mode |
|
||||||
|
| `d` / `f` | Switch to native detail / full-desktop view in read-only or local command mode; clicking in read-only mode also opens detail at that point |
|
||||||
|
| Arrow keys / `i j k l` | Pan in read-only or local command mode: `i` up, `j` left, `k` down, `l` right |
|
||||||
|
|
||||||
|
Zooming, panning, or pressing `c` does not end `Ctrl+]` mode. The status bar shows `CONTROL PAUSED` / `READ ONLY` to indicate whether control will resume. In control mode, ordinary `c`, `q`, and `Ctrl+C` go to the host, so enter local command mode first for local actions. Pressing `Ctrl+]` again also leaves local mode and forwards that shortcut if control resumes.
|
||||||
|
|
||||||
|
### Pi extension and feature scope
|
||||||
|
|
||||||
|
The CLI includes a Pi custom TUI extension with the same input modes. Launch it from the repository root; for example, with a macOS Apple Silicon release build:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export CROSSDESK_CLI="$PWD/build/macosx/arm64/release/crossdesk-cli"
|
||||||
|
pi -e ./apps/cli/plugins/pi/index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
On other platforms, set `CROSSDESK_CLI` to the absolute path of the native executable. Enter `/crossdesk` or `/crossdesk 123456789` in Pi and follow the password prompt. See the [Pi extension guide](apps/cli/docs/terminal-client.md#pi-扩展) (Chinese) for requirements and validation scope.
|
||||||
|
|
||||||
|
The CLI currently provides ID-based viewing and input, without direct IP connections, audio playback, file transfer, or clipboard synchronization. Local builds and remote connections have been validated on macOS arm64. See the [validation record](apps/cli/docs/validation.md) (Chinese) for the remaining Linux / Windows, SSH / Kitty terminal, and actual Pi TUI checks. Platform build configurations and npm packaging do not establish that every platform is validated or that packages have been published.
|
||||||
|
|
||||||
|
[CLI documentation](apps/cli/README.md) · [Terminal usage and troubleshooting](apps/cli/docs/terminal-client.md) · [Bridge protocol](apps/cli/docs/bridge-protocol.md) · [npm packaging and distribution](apps/cli/docs/npm-distribution.md) (detailed guides in Chinese)
|
||||||
|
|
||||||
<a id="mobile"></a>
|
<a id="mobile"></a>
|
||||||
|
|
||||||
## Mobile and HarmonyOS
|
## Mobile and HarmonyOS
|
||||||
@@ -169,8 +224,7 @@ Host your own signaling and TURN services, then enable them under **Settings →
|
|||||||
|
|
||||||
## Build and feedback
|
## Build and feedback
|
||||||
|
|
||||||
- [Terminal client CLI](apps/cli/README.md) (Chinese): ANSI / Kitty remote viewing, optional keyboard and mouse control, and a Pi extension. The CLI can be built without the desktop GUI; npm packages are not published yet.
|
- [Terminal client CLI](apps/cli/README.md) (Chinese): independent builds, terminal usage, Pi extension, bridge protocol, and npm packaging.
|
||||||
|
|
||||||
- [Build from source](docs/BUILD_EN.md): desktop dependencies, build options, and packaging. Use the platform guides above for mobile builds.
|
- [Build from source](docs/BUILD_EN.md): desktop dependencies, build options, and packaging. Use the platform guides above for mobile builds.
|
||||||
- [GUI architecture](docs/gui-architecture.md) (Chinese): desktop UI, platform implementation, and project structure.
|
- [GUI architecture](docs/gui-architecture.md) (Chinese): desktop UI, platform implementation, and project structure.
|
||||||
- [Connection architecture](docs/connection-architecture.md) (Chinese): discovery, authentication, negotiation, data paths, and lifecycle for ID and direct IP modes.
|
- [Connection architecture](docs/connection-architecture.md) (Chinese): discovery, authentication, negotiation, data paths, and lifecycle for ID and direct IP modes.
|
||||||
|
|||||||
+5
-5
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
CrossDesk 的终端主控客户端,源码位于本仓库的 `apps/cli`。通过真实 MiniRTC ID 会话观看远程桌面;默认只读、静音。包含 ANSI 象限字符画面、局部放大和平移、Kitty 图形后端,以及 Pi 自定义 TUI 扩展。
|
CrossDesk 的终端主控客户端,源码位于本仓库的 `apps/cli`。通过真实 MiniRTC ID 会话观看远程桌面;默认只读、静音。包含 ANSI 象限字符画面、局部放大和平移、Kitty 图形后端,以及 Pi 自定义 TUI 扩展。
|
||||||
|
|
||||||
分发目标为 macOS、Linux 和 Windows 的原生终端客户端,包含 x64 / arm64。npm 包发布后可统一使用:
|
分发目标为 macOS / Linux(x64、arm64)和 Windows(x64)的原生终端客户端。npm 包发布后可统一使用:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npx crossdesk-cli@latest doctor --json
|
npx crossdesk-cli@latest doctor --json
|
||||||
npx crossdesk-cli@latest view 123456789
|
npx crossdesk-cli@latest 123456789
|
||||||
```
|
```
|
||||||
|
|
||||||
**当前尚未发布到 npm,也尚未完成三端实机验收。** 已加入平台包生成、自动选包入口和六个平台的 CI 构建配置;运行要求、打包与本地安装见 [npm 分发](docs/npm-distribution.md)。
|
**当前尚未发布到 npm,也尚未完成三端实机验收。** 已加入平台包生成、自动选包入口和五个平台的 CI 构建配置;运行要求、打包与本地安装见 [npm 分发](docs/npm-distribution.md)。
|
||||||
|
|
||||||
在 CrossDesk 仓库根目录执行以下命令构建 CLI:
|
在 CrossDesk 仓库根目录执行以下命令构建 CLI:
|
||||||
|
|
||||||
@@ -18,10 +18,10 @@ npx crossdesk-cli@latest view 123456789
|
|||||||
xmake f -y --CROSSDESK_BUILD_DESKTOP=n --CROSSDESK_BUILD_CLI=y --USE_CUDA=n -m release
|
xmake f -y --CROSSDESK_BUILD_DESKTOP=n --CROSSDESK_BUILD_CLI=y --USE_CUDA=n -m release
|
||||||
xmake build crossdesk-cli
|
xmake build crossdesk-cli
|
||||||
xmake run crossdesk-cli doctor --json
|
xmake run crossdesk-cli doctor --json
|
||||||
xmake run crossdesk-cli view 123456789 --quality high --zoom native
|
xmake run crossdesk-cli 123456789 --quality high --zoom native
|
||||||
```
|
```
|
||||||
|
|
||||||
最后一个设备 ID 是格式示例,使用时替换为你有权访问的设备。密码通过隐藏提示输入。只读时 `c` 开启控制、`q` 或 `Ctrl+C` 退出;点击目标或按 `d` 进入原始细节模式,`+/-` 或滚轮缩放,方向键或 `i` 上、`j` 左、`k` 下、`l` 右平移,`f` 回到完整桌面。任何模式下先按 `Ctrl+]` 打开本地命令提示,再按 `c` 切换控制、`q` 退出或视图按键调整一次画面,`Esc` 取消。控制模式中的 `Ctrl+T`、`Ctrl+C` 和普通按键均传给远端。ANSI 全屏缩略图仍受字符网格限制,默认请求 high 画质,阅读小字建议使用细节模式;支持 Kitty 协议的终端可使用 `--renderer kitty` 获得像素画面。
|
`crossdesk-cli <设备ID> [选项]` 为默认连接方式,`view <设备ID>` 保留为兼容别名。最后一个设备 ID 是格式示例,使用时替换为你有权访问的设备。密码通过隐藏提示输入。只读时 `c` 开启控制、`q` 或 `Ctrl+C` 退出;点击目标或按 `d` 进入原始细节模式,`+/-` 或滚轮缩放,方向键或 `i` 上、`j` 左、`k` 下、`l` 右平移,`f` 回到完整桌面。任何模式下先按 `Ctrl+]` 进入持续本地命令模式,再按 `c` 选择返回后的控制状态、`q` 退出或视图按键连续调整画面,`Esc` / `Ctrl+C` 手动返回。控制模式中的 `Ctrl+T`、`Ctrl+C` 和普通按键均传给远端。ANSI 全屏缩略图仍受字符网格限制,默认请求 high 画质,阅读小字建议使用细节模式;支持 Kitty 协议的终端可使用 `--renderer kitty` 获得像素画面。
|
||||||
|
|
||||||
- [构建、使用、Pi 安装和故障排查](docs/terminal-client.md)
|
- [构建、使用、Pi 安装和故障排查](docs/terminal-client.md)
|
||||||
- [私有桥接协议](docs/bridge-protocol.md)
|
- [私有桥接协议](docs/bridge-protocol.md)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
| connect | device:9位数字字符串, display:0..63(默认0) | 从专用 password fd 读取至换行/EOF(最多256字节、等待5秒),开始异步注册/连接;ok不是远端已连接 |
|
| connect | device:9位数字字符串, display:0..63(默认0) | 从专用 password fd 读取至换行/EOF(最多256字节、等待5秒),开始异步注册/连接;ok不是远端已连接 |
|
||||||
| status | 无 | 返回当前状态 |
|
| status | 无 | 返回当前状态 |
|
||||||
| control | enabled:boolean | 显式启停控制;关闭时释放输入 |
|
| control | enabled:boolean | 显式启停控制;关闭时释放输入 |
|
||||||
| input | data:string,最多16KiB | 终端原始按键/UTF-8/bracketed paste/SGR鼠标;只读时 c 开启控制、q/Ctrl+C 退出,d/鼠标点击进入细节,+/-/滚轮缩放,方向键或 i 上、j 左、k 下、l 右平移,f 全景;Ctrl+] 打开本地命令,随后 c 切换控制、q 退出或视图键调整一次画面,Esc/Ctrl+C 取消;控制模式中 Ctrl+T/Ctrl+C 传给远端,Ctrl+] 两次传递远端 Ctrl+] |
|
| input | data:string,最多16KiB | 终端原始按键/UTF-8/bracketed paste/SGR鼠标;只读时 c 开启控制、q/Ctrl+C 退出,d/鼠标点击进入细节,+/-/滚轮缩放,方向键或 i 上、j 左、k 下、l 右平移,f 全景;Ctrl+] 打开本地命令,随后 c 选择返回后的控制状态、q 退出或视图键连续调整画面,Esc/Ctrl+C 手动返回;控制模式中 Ctrl+T/Ctrl+C 传给远端,本地模式中再按 Ctrl+] 手动返回,并在返回控制模式时传递远端 Ctrl+] |
|
||||||
| display | index:0..63 | 选择已广告的零起始显示器,提升 generation |
|
| display | index:0..63 | 选择已广告的零起始显示器,提升 generation |
|
||||||
| ack | frame_id、generation:非负整数 | 确认宿主消费帧;匹配当前等待帧才开放下一帧 |
|
| ack | frame_id、generation:非负整数 | 确认宿主消费帧;匹配当前等待帧才开放下一帧 |
|
||||||
| disconnect | 无 | 释放输入、结束当前媒体会话 |
|
| disconnect | 无 | 释放输入、结束当前媒体会话 |
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
状态事件每秒最多一次:`{"event":"state","status":{...},"output_bytes":123,"submitted_frames":3,"elapsed_seconds":1.5}`。status 含 registering/connecting/connected/failed/closed/idle、client_id(当前或刚结束会话的 `cli-xxxxxxxxx`,注册前为空)、error、control、generation、显示器、文本/预览能力、预览确认状态、请求的 preview_width/height/quality/bitrate_bps、帧计数与网络统计。接收/提交 FPS 由相应计数的增量除以时间增量计算。
|
状态事件每秒最多一次:`{"event":"state","status":{...},"output_bytes":123,"submitted_frames":3,"elapsed_seconds":1.5}`。status 含 registering/connecting/connected/failed/closed/idle、client_id(当前或刚结束会话的 `cli-xxxxxxxxx`,注册前为空)、error、control、generation、显示器、文本/预览能力、预览确认状态、请求的 preview_width/height/quality/bitrate_bps、帧计数与网络统计。接收/提交 FPS 由相应计数的增量除以时间增量计算。
|
||||||
|
|
||||||
状态和成功响应中的 `status.command_mode` 表示正在等待本地命令,此时 `control=false`,远端输入暂停;执行或取消命令后恢复此前模式。`status.exit_requested` 表示用户按键请求退出,宿主可据此关闭组件。宿主应从 `input` 响应立即更新命令提示,不必等待下一次状态事件;不要自行截获 `Ctrl+]` 退出。显式 `control` 请求会结束命令状态。具体快捷键见 [终端客户端](terminal-client.md)。
|
状态和成功响应中的 `status.command_mode` 表示处于持续本地命令模式,此时 `control=false`,远端输入暂停;`status.resume_control` 表示手动返回后是否启用控制。执行命令、未知按键或粘贴不会退出此模式,失焦或输入溢出只会将返回模式设为只读。`Esc` / `Ctrl+C` 或再次 `Ctrl+]` 手动返回;`status.exit_requested` 表示用户按键请求退出,宿主可据此关闭组件。宿主应从 `input` 响应立即更新命令提示,不必等待下一次状态事件;不要自行截获 `Ctrl+]` 退出。显式 `control` 请求只更新所选控制状态,在本地模式中仍暂停远端输入。新建连接或断开会重置本地模式。具体快捷键见 [终端客户端](terminal-client.md)。
|
||||||
|
|
||||||
边界:输入每条及当前解析缓冲上限64KiB、嵌套32层;响应队列64条;输出一条在途消息上限2MiB;1MiB/s;最多一条待 ACK 帧;一个最新待渲染 NV12 加一个最近画面的重绘缓存。协议错误明确响应;超限缓冲后结束。EOF 清理会话,完整的已收请求按序处理,不完整末尾报错;最多两秒排空输出后退出。宿主应持续消费响应,不能无限发送而不读取。失去连接后的释放只能尽力发送。
|
边界:输入每条及当前解析缓冲上限64KiB、嵌套32层;响应队列64条;输出一条在途消息上限2MiB;1MiB/s;最多一条待 ACK 帧;一个最新待渲染 NV12 加一个最近画面的重绘缓存。协议错误明确响应;超限缓冲后结束。EOF 清理会话,完整的已收请求按序处理,不完整末尾报错;最多两秒排空输出后退出。宿主应持续消费响应,不能无限发送而不读取。失去连接后的释放只能尽力发送。
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
npx crossdesk-cli@latest doctor --json
|
npx crossdesk-cli@latest doctor --json
|
||||||
npx crossdesk-cli@latest view 123456789
|
npx crossdesk-cli@latest 123456789
|
||||||
npm install -g crossdesk-cli
|
npm install -g crossdesk-cli
|
||||||
crossdesk-cli view 123456789
|
crossdesk-cli 123456789
|
||||||
```
|
```
|
||||||
|
|
||||||
需要 Node.js 22 或以上和 npm。启动器根据 Node 的 `process.platform` / `process.arch` 选择同版本的平台包,直接启动原生 C++ 程序,继承终端、工作目录、环境变量和命令参数。安装过程不下载外部可执行文件、不执行 postinstall、不在用户机器编译。不要使用 `--omit=optional`,否则对应平台包不会安装。
|
需要 Node.js 22 或以上和 npm。启动器根据 Node 的 `process.platform` / `process.arch` 选择同版本的平台包,直接启动原生 C++ 程序,继承终端、工作目录、环境变量和命令参数。安装过程不下载外部可执行文件、不执行 postinstall、不在用户机器编译。不要使用 `--omit=optional`,否则对应平台包不会安装。
|
||||||
@@ -22,9 +22,8 @@ crossdesk-cli view 123456789
|
|||||||
| Linux x64 | `crossdesk-cli-linux-x64` | glibc、libstdc++、zlib;所需 GLIBC / GLIBCXX 版本写入 `build-info.json` |
|
| Linux x64 | `crossdesk-cli-linux-x64` | glibc、libstdc++、zlib;所需 GLIBC / GLIBCXX 版本写入 `build-info.json` |
|
||||||
| Linux arm64 | `crossdesk-cli-linux-arm64` | 同上 |
|
| Linux arm64 | `crossdesk-cli-linux-arm64` | 同上 |
|
||||||
| Windows x64 | `crossdesk-cli-win32-x64` | Windows 10 或以上,原生 Console / ConPTY 与 VT 输出 |
|
| Windows x64 | `crossdesk-cli-win32-x64` | Windows 10 或以上,原生 Console / ConPTY 与 VT 输出 |
|
||||||
| Windows arm64 | `crossdesk-cli-win32-arm64` | Windows 11 ARM,原生 Console / ConPTY |
|
|
||||||
|
|
||||||
这些是构建与分发目标,不代表均已实机通过。当前完整构建与 npm 运行验证只覆盖 macOS arm64;CLI 主程序与平台 I/O 已通过 Windows / Linux x64、arm64 的交叉编译检查。其他平台的完整 MiniRTC 链接、实际视频连接、键鼠、终端恢复仍需对应机器验收。
|
这些是构建与分发目标,不代表均已实机通过。当前完整构建与 npm 运行验证只覆盖 macOS arm64;CLI 主程序与平台 I/O 已通过 Windows x64 与 Linux x64、arm64 的交叉编译检查。其他平台的完整 MiniRTC 链接、实际视频连接、键鼠、终端恢复仍需对应机器验收。
|
||||||
|
|
||||||
普通终端使用 ANSI 渲染,按能力选择 TrueColor / 256 色 / 基础色;Kitty 图形协议是可选增强。macOS Terminal、iTerm2、Linux 常用终端、Windows Terminal 和 VS Code 集成终端属于目标场景。SSH 应分配 PTY(`ssh -t`);tmux / screen 使用 ANSI。不支持 `TERM=dumb` 或把交互观看输出重定向到文件;非交互宿主使用 `bridge --stdio`。Alpine / musl 当前没有预编译包。Windows 独立 mintty 窗口可能不提供 Console / ConPTY,应使用 Windows Terminal 承载 Git Bash。
|
普通终端使用 ANSI 渲染,按能力选择 TrueColor / 256 色 / 基础色;Kitty 图形协议是可选增强。macOS Terminal、iTerm2、Linux 常用终端、Windows Terminal 和 VS Code 集成终端属于目标场景。SSH 应分配 PTY(`ssh -t`);tmux / screen 使用 ANSI。不支持 `TERM=dumb` 或把交互观看输出重定向到文件;非交互宿主使用 `bridge --stdio`。Alpine / musl 当前没有预编译包。Windows 独立 mintty 窗口可能不提供 Console / ConPTY,应使用 Windows Terminal 承载 Git Bash。
|
||||||
|
|
||||||
@@ -32,7 +31,13 @@ CI 的 macOS 构建配置指定 `--target_minver=13.0`,但最终必须检查
|
|||||||
|
|
||||||
## 生成包
|
## 生成包
|
||||||
|
|
||||||
先按 [构建说明](terminal-client.md) 构建 release,再执行:
|
先按 [构建说明](terminal-client.md) 构建 release。macOS 打包前需要签名,CI 使用 ad-hoc 签名;本地可执行以下命令(Intel 将 `arm64` 换成 `x86_64`):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
codesign --force --sign - build/macosx/arm64/release/crossdesk-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
然后生成 npm 包:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm --prefix apps/cli run pack:npm
|
npm --prefix apps/cli run pack:npm
|
||||||
@@ -46,7 +51,7 @@ npm --prefix apps/cli run pack:npm
|
|||||||
npm --prefix apps/cli run pack:npm -- --binary /absolute/path/crossdesk-cli --out-dir /absolute/path/output
|
npm --prefix apps/cli run pack:npm -- --binary /absolute/path/crossdesk-cli --out-dir /absolute/path/output
|
||||||
```
|
```
|
||||||
|
|
||||||
每台构建机生成当前平台的原生包与相同版本的通用启动包。`apps/cli/npm/platforms.json` 集中定义六个平台;启动包通过精确版本的 `optionalDependencies` 让 npm 仅安装匹配系统和 CPU 的原生包。原生包包含二进制、运行要求、SHA-256,以及当前控制端和已应用修补的 MiniRTC 源码快照。外部 Xmake 依赖的许可证、对应源码和重链接材料需要随正式版本一并提供,参见 [源码来源](source-provenance.md)。源码快照已含 MiniRTC,解包后无需运行 `prepare-deps.sh`;在 `source/` 目录按上述构建说明显式关闭桌面、开启 CLI,再构建 `crossdesk-cli`。源码包仅包含 CLI 及其依赖,不包含桌面或移动应用。
|
每台构建机生成当前平台的原生包与相同版本的通用启动包。`apps/cli/npm/platforms.json` 集中定义五个平台;启动包通过精确版本的 `optionalDependencies` 让 npm 仅安装匹配系统和 CPU 的原生包。原生包包含二进制、运行要求、SHA-256,以及当前控制端和已应用修补的 MiniRTC 源码快照。外部 Xmake 依赖的许可证、对应源码和重链接材料需要随正式版本一并提供,参见 [源码来源](source-provenance.md)。源码快照已含 MiniRTC,解包后无需运行 `prepare-deps.sh`;在 `source/` 目录按上述构建说明显式关闭桌面、开启 CLI,再构建 `crossdesk-cli`。源码包仅包含 CLI 及其依赖,不包含桌面或移动应用。
|
||||||
|
|
||||||
`apps/cli/package.json` 的 `private: true` 表示源码目录不是发布产物。只发布打包脚本生成的 tarball;生成的包不带 private 标记。
|
`apps/cli/package.json` 的 `private: true` 表示源码目录不是发布产物。只发布打包脚本生成的 tarball;生成的包不带 private 标记。
|
||||||
|
|
||||||
@@ -67,13 +72,13 @@ Pi 等宿主应直接启动原生程序以可靠控制进程和专用密码描
|
|||||||
|
|
||||||
## CI 与发布
|
## CI 与发布
|
||||||
|
|
||||||
`.github/workflows/build-cli.yml` 配置六个平台的原生 release 构建、doctor 和打包,并上传 tarball;该工作流不执行 npm publish。工作流随 CLI 整合到 CrossDesk;本次本地整合未触发远程 CI。
|
`.github/workflows/build-cli.yml` 配置五个平台的原生 release 构建、doctor 和打包,并上传 tarball;该工作流不执行 npm publish。更新 MiniRTC 引用时,必须先将对应提交推送到子模块远端,再推送 CrossDesk,否则全新检出和 CI 会在拉取子模块时失败。
|
||||||
|
|
||||||
正式发布时,先发布全部对应版本的原生包,再发布通用 `crossdesk-cli` 包。例如:
|
正式发布时,先发布全部对应版本的原生包,再发布通用 `crossdesk-cli` 包。例如:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm publish crossdesk-cli-darwin-arm64-0.1.0.tgz --access public
|
npm publish crossdesk-cli-darwin-arm64-0.1.0.tgz --access public
|
||||||
# 同样发布其余五个平台的原生包后,再发布入口包。
|
# 同样发布其余四个平台的原生包后,再发布入口包。
|
||||||
npm publish crossdesk-cli-0.1.0.tgz --access public
|
npm publish crossdesk-cli-0.1.0.tgz --access public
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -17,21 +17,21 @@ xmake run crossdesk-cli doctor --json
|
|||||||
|
|
||||||
macOS 的 MiniRTC 依赖仍链接 VideoToolbox/CoreMedia 和 GLib 声明的 AppKit 等系统框架;CLI 不创建 GUI、采集器或音频设备。硬件解码参数显式关闭。Linux 无需 DISPLAY/WAYLAND_DISPLAY;本轮尚未在 Linux 主机实际构建验证。
|
macOS 的 MiniRTC 依赖仍链接 VideoToolbox/CoreMedia 和 GLib 声明的 AppKit 等系统框架;CLI 不创建 GUI、采集器或音频设备。硬件解码参数显式关闭。Linux 无需 DISPLAY/WAYLAND_DISPLAY;本轮尚未在 Linux 主机实际构建验证。
|
||||||
|
|
||||||
Windows 产物为 `build/windows/<arch>/release/crossdesk-cli.exe`;先在 Git Bash 中执行 `sh apps/cli/scripts/prepare-deps.sh`,然后可在开发者 PowerShell 中执行上述 Xmake 命令。Windows 使用 Console / ConPTY 的原生键盘、鼠标事件和 VT 输出,保存并恢复控制台模式与代码页;无需 WSL。Windows Terminal、现代 PowerShell / CMD 和 VS Code 的 ConPTY 终端属于目标运行环境,仍需实机验收。Git Bash 的独立 mintty 窗口没有原生控制台时,应从 Windows Terminal 的 Git Bash 配置启动。
|
Windows 仅支持 x64,产物为 `build/windows/x64/release/crossdesk-cli.exe`;先在 Git Bash 中执行 `sh apps/cli/scripts/prepare-deps.sh`,然后在开发者 PowerShell 中执行上述 Xmake 命令,配置时额外指定 `-p windows -a x64`。在 Git Bash 中构建时同样必须指定 `-p windows`,避免被识别为 MSYS 平台。Windows 使用 Console / ConPTY 的原生键盘、鼠标事件和 VT 输出,保存并恢复控制台模式与代码页;无需 WSL。Windows Terminal、现代 PowerShell / CMD 和 VS Code 的 ConPTY 终端属于目标运行环境,仍需实机验收。Git Bash 的独立 mintty 窗口没有原生控制台时,应从 Windows Terminal 的 Git Bash 配置启动。
|
||||||
|
|
||||||
预编译 npm 包的用户无需构建工具,见 [npm 分发](npm-distribution.md)。
|
预编译 npm 包的用户无需构建工具,见 [npm 分发](npm-distribution.md)。
|
||||||
|
|
||||||
## 连接与身份
|
## 连接与身份
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
crossdesk-cli view 123456789
|
crossdesk-cli 123456789
|
||||||
crossdesk-cli view 123456789 --renderer ansi --fps 8 --quality high --zoom native
|
crossdesk-cli 123456789 --renderer ansi --fps 8 --quality high --zoom native
|
||||||
crossdesk-cli view 123456789 --renderer kitty --quality ultra --zoom native --display 0
|
crossdesk-cli 123456789 --renderer kitty --quality ultra --zoom native --display 0
|
||||||
crossdesk-cli view 123456789 --control
|
crossdesk-cli 123456789 --control
|
||||||
crossdesk-cli view 123456789 --server your-server.example --port 9099
|
crossdesk-cli 123456789 --server your-server.example --port 9099
|
||||||
```
|
```
|
||||||
|
|
||||||
设备 ID 必须是 9 位数字,以上数字仅示例。默认信令地址沿用当前 CrossDesk:`api.crossdesk.cn:9099`。密码从本地终端隐藏输入(POSIX `/dev/tty`、Windows `CONIN$` / `CONOUT$`),不提供密码命令行参数。CLI 不读取、不修改桌面配置。每次连接直接用 `cli` 请求服务端分配临时 `cli-xxxxxxxxx` ID(后缀为 9 位数字),注册、Join 和媒体使用同一个 Peer。CLI 仅作为控制端,不申请普通数字身份或 `C-` 身份,也不保存本机密码或重连凭据。每次启动或重新连接都会申请新的 ID;断开后服务端按 Web 临时身份的规则清理注册记录。
|
直接将设备 ID 作为第一个参数即可连接;`crossdesk-cli view <设备ID> [选项]` 保留为兼容别名,支持相同选项。设备 ID 必须是 9 位数字,保留前导零,以上数字仅示例。默认信令地址沿用当前 CrossDesk:`api.crossdesk.cn:9099`。密码从本地终端隐藏输入(POSIX `/dev/tty`、Windows `CONIN$` / `CONOUT$`),不提供密码命令行参数。CLI 不读取、不修改桌面配置。每次连接直接用 `cli` 请求服务端分配临时 `cli-xxxxxxxxx` ID(后缀为 9 位数字),注册、Join 和媒体使用同一个 Peer。CLI 仅作为控制端,不申请普通数字身份或 `C-` 身份,也不保存本机密码或重连凭据。每次启动或重新连接都会申请新的 ID;断开后服务端按 Web 临时身份的规则清理注册记录。
|
||||||
|
|
||||||
需先部署支持 `cli` 注册的新版 crossdesk-server;旧服务端会登录失败,本客户端不会回退为普通设备身份。桥接 `status.client_id` 返回服务端分配的 ID。
|
需先部署支持 `cli` 注册的新版 crossdesk-server;旧服务端会登录失败,本客户端不会回退为普通设备身份。桥接 `status.client_id` 返回服务端分配的 ID。
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ crossdesk-cli view 123456789 --server your-server.example --port 9099
|
|||||||
|
|
||||||
## ANSI、Kitty 与 SSH
|
## ANSI、Kitty 与 SSH
|
||||||
|
|
||||||
ANSI 默认使用四象限字符:每个字符从 2×2 样本选择误差最小的两个颜色和象限形状,比原先上下半方块多一倍水平采样点;每个字符仍只有前景/背景两色。使用 `--ansi-detail half` 可回到上下半方块以兼容字体。缩小时采用面积加权滤波,避免最近邻采样漏掉细线。支持 TrueColor、256 色和基础 8 色,256 色选择实际颜色立方体或灰阶条中更接近的颜色。环境的 COLORTERM/TERM 决定颜色模式;非 UTF-8 locale 使用背景色空格。TERM=dumb、非 TTY 输出明确拒绝 view,不输出全屏转义序列。
|
ANSI 默认使用四象限字符:每个字符从 2×2 样本选择误差最小的两个颜色和象限形状,比原先上下半方块多一倍水平采样点;每个字符仍只有前景/背景两色。使用 `--ansi-detail half` 可回到上下半方块以兼容字体。缩小时采用面积加权滤波,避免最近邻采样漏掉细线。支持 TrueColor、256 色和基础 8 色,256 色选择实际颜色立方体或灰阶条中更接近的颜色。环境的 COLORTERM/TERM 决定颜色模式;非 UTF-8 locale 使用背景色空格。TERM=dumb、非 TTY 输出明确拒绝交互连接,不输出全屏转义序列。
|
||||||
|
|
||||||
画面按字符单元比例等比缩放,留黑边及状态栏,默认假定字符宽高比 0.5;TTY 提供像素尺寸时使用其比例。每次只输出变化行,合并连续颜色设置;缩放在完整输出边界清理旧画面。上限 240 列、100 画面行;默认预留两行及一列,避免自动换行。
|
画面按字符单元比例等比缩放,留黑边及状态栏,默认假定字符宽高比 0.5;TTY 提供像素尺寸时使用其比例。每次只输出变化行,合并连续颜色设置;缩放在完整输出边界清理旧画面。上限 240 列、100 画面行;默认预留两行及一列,避免自动换行。
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ Kitty 通过终端字节流发送 zlib 压缩的内联 RGB/Base64(每块至多
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
ssh -t your-server
|
ssh -t your-server
|
||||||
crossdesk-cli view 123456789 --renderer ansi --fps 8
|
crossdesk-cli 123456789 --renderer ansi --fps 8
|
||||||
```
|
```
|
||||||
|
|
||||||
用户本地终端负责显示 SSH 字节流。ANSI、Kitty over SSH 和 SSH+tmux 的实机互通尚未验证。无 GUI 指主控客户端;被控端仍需要可捕获的图形会话。
|
用户本地终端负责显示 SSH 字节流。ANSI、Kitty over SSH 和 SSH+tmux 的实机互通尚未验证。无 GUI 指主控客户端;被控端仍需要可捕获的图形会话。
|
||||||
@@ -75,24 +75,26 @@ crossdesk-cli view 123456789 --renderer ansi --fps 8
|
|||||||
|
|
||||||
转换与输出在拥有会话的事件循环,媒体回调独立。输出至多一个完整消息(2MiB),以 1MiB/s 令牌预算分块非阻塞写入;不能开始新画面时保留最新解码帧。ANSI 差分基准只在整帧输出完成后提交。Pi 桥接另要求消费帧 ACK,最多一帧等待 ACK,输入映射只在对应 ACK 后切换到新网格。状态栏优先在完整画面输出之间更新,避免大图持续刷新时状态饥饿;可写事件唤醒短写重试,仍遵守字节预算。控制事件至多 64 条,协议输入 64KiB/嵌套 32 层,输入粘贴缓冲 32KiB,文本单次最多 16KiB。
|
转换与输出在拥有会话的事件循环,媒体回调独立。输出至多一个完整消息(2MiB),以 1MiB/s 令牌预算分块非阻塞写入;不能开始新画面时保留最新解码帧。ANSI 差分基准只在整帧输出完成后提交。Pi 桥接另要求消费帧 ACK,最多一帧等待 ACK,输入映射只在对应 ACK 后切换到新网格。状态栏优先在完整画面输出之间更新,避免大图持续刷新时状态饥饿;可写事件唤醒短写重试,仍遵守字节预算。控制事件至多 64 条,协议输入 64KiB/嵌套 32 层,输入粘贴缓冲 32KiB,文本单次最多 16KiB。
|
||||||
|
|
||||||
统计包括收到帧数、被替换帧数、非法帧、等待帧数、实际完整提交次数、输出字节数、运行秒数、最近本地排队时间、网络码率/RTT/SRTP。通过计数差/时间差可得到区间 FPS;view 结束日志另给平均提交 FPS。写入 TTY 完成不等于屏幕显示完成,本地排队时间不是端到端延迟。
|
统计包括收到帧数、被替换帧数、非法帧、等待帧数、实际完整提交次数、输出字节数、运行秒数、最近本地排队时间、网络码率/RTT/SRTP。通过计数差/时间差可得到区间 FPS;交互连接结束日志另给平均提交 FPS。写入 TTY 完成不等于屏幕显示完成,本地排队时间不是端到端延迟。
|
||||||
|
|
||||||
## 输入与退出
|
## 输入与退出
|
||||||
|
|
||||||
默认只读、静音。`--control` 或只读时按 `c` 明确启用输入。`Ctrl+]` 是本地命令前缀:先按下并松开,再按下表中的命令键。状态栏即时显示当前模式与可用按键,单按前缀不会退出。
|
默认只读、静音。`--control` 或只读时按 `c` 明确启用输入。`Ctrl+]` 进入持续的本地命令模式:先按下并松开,再按下表中的命令键,可以连续执行多个命令。状态栏即时显示当前模式与可用按键。
|
||||||
|
|
||||||
|
macOS 自带 Terminal 还需要在当前窗口勾选“显示 → 允许鼠标报告”(View → Allow Mouse Reporting)。CLI 会启用鼠标报告,但无法覆盖 Terminal 关闭此选项的设置;未勾选时,点击、拖动和滚轮事件不会传给 CLI。参见 [Apple 官方说明](https://support.apple.com/zh-cn/guide/terminal/trmlc69728a5/mac)。
|
||||||
|
|
||||||
| 操作 | 只读模式 | 控制模式 |
|
| 操作 | 只读模式 | 控制模式 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 开启/关闭远端控制 | `c` | `Ctrl+]`,再按 `c` |
|
| 开启/关闭远端控制 | `c` | `Ctrl+]`,再按 `c` |
|
||||||
| 退出 CLI | `q` 或 `Ctrl+C` | `Ctrl+]`,再按 `q` |
|
| 退出 CLI | `q` 或 `Ctrl+C` | `Ctrl+]`,再按 `q` |
|
||||||
| 显示本地命令提示 | `?` 或 `Ctrl+]` | `Ctrl+]` |
|
| 进入本地命令模式 | `?` 或 `Ctrl+]` | `Ctrl+]` |
|
||||||
| 放大/缩小 | `+` / `=` / `-` | `Ctrl+]`,再按对应键 |
|
| 放大/缩小 | `+` / `=` / `-` | `Ctrl+]`,再按对应键 |
|
||||||
| 原始细节/完整画面 | `d`(detail)/ `f`(fit),相邻字母键 | `Ctrl+]`,再按对应键 |
|
| 原始细节/完整画面 | `d`(detail)/ `f`(fit),相邻字母键 | `Ctrl+]`,再按对应键 |
|
||||||
| 平移 | 方向键或 `i` 上、`j` 左、`k` 下、`l` 右 | `Ctrl+]`,再按对应键 |
|
| 平移 | 方向键或 `i` 上、`j` 左、`k` 下、`l` 右 | `Ctrl+]`,再按对应键 |
|
||||||
|
|
||||||
进入本地命令状态时暂停远端输入,并释放已记录按键/鼠标;执行一个命令后恢复原模式,`c` 则切换模式。`Esc` 或 `Ctrl+C` 取消命令并恢复原模式;未知命令显示提示,不发送给远端。命令状态中的鼠标事件被忽略,bracketed paste 内容被丢弃并取消命令,不会触发快捷键。控制模式下普通 `c/q/?`、`Ctrl+T`、`Ctrl+C` 和单独 `Esc` 均传给远端;连续按两次 `Ctrl+]` 可向远端发送一次 `Ctrl+]`。目前不支持重绑定。
|
进入本地命令模式时暂停远端输入,并释放已记录按键/鼠标。执行缩放、平移、`c` 或未知命令后均保持此模式;`c` 切换手动返回后是否启用远端控制,状态栏显示 `CONTROL PAUSED` 或 `READ ONLY`。`Esc` 或 `Ctrl+C` 手动返回所选模式;`q` 退出 CLI。再次按 `Ctrl+]` 也会返回,并在所选模式为控制时向远端发送一次 `Ctrl+]`。命令模式中的鼠标事件被忽略,bracketed paste 内容被丢弃,均不会退出模式或触发快捷键。窗口失焦或输入缓冲区溢出时继续保持本地命令模式,但将返回模式设为只读。控制模式下普通 `c/q/?`、`Ctrl+T`、`Ctrl+C` 和单独 `Esc` 均传给远端。目前不支持重绑定。
|
||||||
|
|
||||||
只读时,`d` 或在画面中单击进入该位置的原始细节模式;`+` / `=` 放大、`-` 缩小(1~32倍),滚轮以鼠标指向为中心调整倍率。方向键或 `i/j/k/l` 分别向上/左/下/右平移,`f` 恢复完整画面。黑边点击被忽略。这些按键和鼠标在控制模式下仍传给远端;使用本地命令前缀可调整一次视图,持续查看时可先切回只读。放大、平移和只读鼠标定位不会向远端发送操作,也适用于静止画面。
|
只读时,`d` 或在画面中单击进入该位置的原始细节模式;`+` / `=` 放大、`-` 缩小(1~32倍),滚轮以鼠标指向为中心调整倍率。方向键或 `i/j/k/l` 分别向上/左/下/右平移,`f` 恢复完整画面。黑边点击被忽略。这些按键和鼠标在控制模式下仍传给远端;进入本地命令模式后可连续调整视图,直至手动返回。放大、平移和只读鼠标定位不会向远端发送操作,也适用于静止画面。
|
||||||
|
|
||||||
支持 SGR 鼠标移动、左右中键、拖动和滚轮;黑边不发送新点击,黑边处松开仍释放旧按钮。输入坐标映射到所选显示器,`--display` 是 HostInformation 列表的零起始索引,视频流为 Display1、Display2……
|
支持 SGR 鼠标移动、左右中键、拖动和滚轮;黑边不发送新点击,黑边处松开仍释放旧按钮。输入坐标映射到所选显示器,`--display` 是 HostInformation 列表的零起始索引,视频流为 Display1、Display2……
|
||||||
|
|
||||||
@@ -113,9 +115,9 @@ pi -e ./apps/cli/plugins/pi/index.ts
|
|||||||
|
|
||||||
在 Pi 输入 `/crossdesk` 或 `/crossdesk 123456789`。先输入 ID,再在自定义遮蔽组件输入密码。扩展使用独立 fd 3 写密码,随后关闭;不写配置、聊天、工具结果或模型上下文。查看组件与独立 CLI 使用相同的 `Ctrl+]` 命令前缀和上述模式按键,由 bridge 统一解析。插件默认请求 high 预览并显示 ANSI 象限字符行,支持同样的只读细节/放大/平移键,颜色深度依据终端环境回退,由 Pi 自身布局和刷新;不直接占用 TTY,不发送 Kitty 图形或自行打开鼠标报告。
|
在 Pi 输入 `/crossdesk` 或 `/crossdesk 123456789`。先输入 ID,再在自定义遮蔽组件输入密码。扩展使用独立 fd 3 写密码,随后关闭;不写配置、聊天、工具结果或模型上下文。查看组件与独立 CLI 使用相同的 `Ctrl+]` 命令前缀和上述模式按键,由 bridge 统一解析。插件默认请求 high 预览并显示 ANSI 象限字符行,支持同样的只读细节/放大/平移键,颜色深度依据终端环境回退,由 Pi 自身布局和刷新;不直接占用 TTY,不发送 Kitty 图形或自行打开鼠标报告。
|
||||||
|
|
||||||
需要全局发现时,把整个 `apps/cli/plugins/pi` 目录复制到 `~/.pi/agent/extensions/crossdesk/` 后重启 Pi 或执行 `/reload`。卸载时先关闭视图,删除该目录并 `/reload`;使用 `-e` 时从启动参数移除即可。无需修改包中的密码或设备配置。默认连接官方信令;自建信令当前通过独立 view 命令使用,插件尚无服务器设置界面。
|
需要全局发现时,把整个 `apps/cli/plugins/pi` 目录复制到 `~/.pi/agent/extensions/crossdesk/` 后重启 Pi 或执行 `/reload`。卸载时先关闭视图,删除该目录并 `/reload`;使用 `-e` 时从启动参数移除即可。无需修改包中的密码或设备配置。默认连接官方信令;自建信令当前通过独立 CLI 连接命令使用,插件尚无服务器设置界面。
|
||||||
|
|
||||||
仅在 `ctx.mode === "tui"`、hasUI 且进程双向 TTY 时打开组件。RPC/print/json 返回独立播放器提示,不声称内嵌视频。UI 关闭/会话退出先关闭 bridge stdin,超时 SIGTERM,再超时 SIGKILL。bridge 尚未就绪或已经停止时,组件可直接用 `q`、`Esc` 或 `Ctrl+C` 关闭。SDK 类型和模拟 UI 生命周期已验证,真实 Pi TUI 视觉刷新、增强按键与鼠标传递未实机验收。真实设备的 bridge 与独立 view 验证见 [验证记录](validation.md)。
|
仅在 `ctx.mode === "tui"`、hasUI 且进程双向 TTY 时打开组件。RPC/print/json 返回独立播放器提示,不声称内嵌视频。UI 关闭/会话退出先关闭 bridge stdin,超时 SIGTERM,再超时 SIGKILL。bridge 尚未就绪或已经停止时,组件可直接用 `q`、`Esc` 或 `Ctrl+C` 关闭。SDK 类型和模拟 UI 生命周期已验证,真实 Pi TUI 视觉刷新、增强按键与鼠标传递未实机验收。真实设备的 bridge 与独立 CLI 连接验证见 [验证记录](validation.md)。
|
||||||
|
|
||||||
官方依据:[扩展文档](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/extensions.md)、[TUI 文档](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/tui.md)、[Kitty 图形协议](https://sw.kovidgoyal.net/kitty/graphics-protocol/)。
|
官方依据:[扩展文档](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/extensions.md)、[TUI 文档](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/tui.md)、[Kitty 图形协议](https://sw.kovidgoyal.net/kitty/graphics-protocol/)。
|
||||||
|
|
||||||
@@ -130,8 +132,10 @@ pi -e ./apps/cli/plugins/pi/index.ts
|
|||||||
- `display_not_available`:换成远端声明的索引;单会话一次只显示一个屏幕。
|
- `display_not_available`:换成远端声明的索引;单会话一次只显示一个屏幕。
|
||||||
- `media_initialization_failed`:检查编译依赖和本地媒体运行环境。
|
- `media_initialization_failed`:检查编译依赖和本地媒体运行环境。
|
||||||
- 只读下按键不会发送到远端;远端未声明文本协议时文本输入不可用。
|
- 只读下按键不会发送到远端;远端未声明文本协议时文本输入不可用。
|
||||||
- 日志在进程创建的私有临时目录(POSIX `/tmp/crossdesk-cli-*`、Windows `%TEMP%\crossdesk-cli-*`)。view 的摘要在 client.log;底层诊断默认关闭且敏感 Join 日志已修补。目录不保存密码;使用完可删除。错误退出信息在恢复终端后显示。
|
- 日志在进程创建的私有临时目录(POSIX `/tmp/crossdesk-cli-*`、Windows `%TEMP%\crossdesk-cli-*`)。交互连接的摘要在 client.log,包含收到的终端鼠标事件数 `mouse_reports`;transport.log 只记录白名单中的 ICE/DTLS 状态及本地控制开关,不记录密码、SDP 或原始信令。使用完可删除。错误退出信息在恢复终端后显示,并给出本次日志目录。
|
||||||
- `doctor --json` 是环境报告,不会联网登录、主动探测 Kitty 或证明远端可连接。
|
- `connection_failed` 表示连接建立失败;`connection_lost` 表示连接成功后传输失效。xmake 随后的 `execv ... failed(1)` 表示程序以错误码 1 退出,本身不代表进程崩溃。遇到控制失效时,对照 transport.log 中的控制开关和连接状态;`mouse_reports` 为 0 表示终端没有向 CLI 提供可识别的 SGR 鼠标事件。
|
||||||
|
- 如果整个 macOS Terminal 窗口退出且 CLI 记录 `output closed`,还应检查系统 DiagnosticReports 中的 Terminal 报告。已观察到 Terminal 2.15 在创建标签页并调整窗口尺寸时发生 `TTLogicalScreen expandUnicharOffsetCacheToLine:` 行缓存越界;这与 CLI 的 `connection_failed` 不同。临时可在独立窗口运行,避开该标签页操作,不能据此认为 Terminal 缺陷已修复。
|
||||||
|
- `doctor --json` 是环境报告,不会联网登录、主动探测 Kitty 或证明远端可连接。 `input_shortcuts=local_commands_v2` 表示支持本文的持续本地命令模式;启动日志同时记录实际执行路径,以便区分不同工程的构建产物。
|
||||||
|
|
||||||
第一轮无音频播放或音频订阅 API,SSH 服务器上的声音不会传回本地。无后台守护进程、共享媒体 Broker、IP 直连、MCP 或自动截图。其他宿主可以使用 [私有桥接](bridge-protocol.md),没有可靠自定义 UI 时打开独立终端或给出播放器命令。
|
第一轮无音频播放或音频订阅 API,SSH 服务器上的声音不会传回本地。无后台守护进程、共享媒体 Broker、IP 直连、MCP 或自动截图。其他宿主可以使用 [私有桥接](bridge-protocol.md),没有可靠自定义 UI 时打开独立终端或给出播放器命令。
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,13 @@
|
|||||||
|
|
||||||
此次没有连接真实远端设备,也没有进行 Linux/Windows 或真实 Pi TUI 实机验证。新增测试代码与产物均保存在工程外系统临时目录。
|
此次没有连接真实远端设备,也没有进行 Linux/Windows 或真实 Pi TUI 实机验证。新增测试代码与产物均保存在工程外系统临时目录。
|
||||||
|
|
||||||
|
## 直接 ID 入口与 npm 源码包(2026-10-30)
|
||||||
|
|
||||||
|
- 直接输入 9 位设备 ID 和 `view` 兼容别名均通过参数检查,保留前导零,支持相同的连接选项;无效 ID 在密码提示前拒绝。macOS arm64 终端中的密码提示与 Ctrl+C 取消通过。
|
||||||
|
- 本地 npm 包离线安装、启动器帮助和 `doctor --json` 通过;源码包包含完整的 `libs/controller/include`,补齐诊断头文件后,从安装包内源码独立配置、release 构建及 `doctor` 通过。
|
||||||
|
- 原有仓库结构、wire 和键盘状态测试通过;未新增或修改工程内测试,额外验证和产物均位于工程外临时目录。
|
||||||
|
- Build and Release #899 和 CLI native builds and npm packages #1 的首次运行均因 MiniRTC 提交 `b9ee1fa7` 尚未推送而在检出阶段失败。将该提交推送到 MiniRTC 远端后,全新递归浅克隆通过。
|
||||||
|
|
||||||
## 独立开发阶段的验证范围
|
## 独立开发阶段的验证范围
|
||||||
|
|
||||||
以下为迁入前的验证记录摘要,不表示本次重新连接了远程设备:
|
以下为迁入前的验证记录摘要,不表示本次重新连接了远程设备:
|
||||||
@@ -46,6 +53,6 @@
|
|||||||
|
|
||||||
## 实测边界
|
## 实测边界
|
||||||
|
|
||||||
本地检查不涵盖 Linux / Windows 完整构建、跨平台远程控制、Kitty/SSH 实际终端显示、真实 Pi TUI 或长时间资源占用。六个平台 CI 配置与 npm 包名均不代表已发布或已验收。
|
本地检查不涵盖 Linux / Windows 完整构建、跨平台远程控制、Kitty/SSH 实际终端显示、真实 Pi TUI 或长时间资源占用。五个平台 CI 配置与 npm 包名均不代表已发布或已验收。
|
||||||
|
|
||||||
CLI 需要支持 `cli` 临时注册的服务端。`doctor --json` 仅报告本机环境,不会联网登录或证明远端可连接。
|
CLI 需要支持 `cli` 临时注册的服务端。`doctor --json` 仅报告本机环境,不会联网登录或证明远端可连接。
|
||||||
|
|||||||
+44
-20
@@ -201,7 +201,7 @@ int View(SessionOptions options, const std::string &device,
|
|||||||
const std::string &renderer, bool control, int fd, AnsiDetail detail,
|
const std::string &renderer, bool control, int fd, AnsiDetail detail,
|
||||||
double zoom, bool native) {
|
double zoom, bool native) {
|
||||||
if (!IsTty(0) || !IsTty(fd) || Env("TERM") == "dumb")
|
if (!IsTty(0) || !IsTty(fd) || Env("TERM") == "dumb")
|
||||||
throw std::runtime_error("view needs an interactive ANSI terminal; use "
|
throw std::runtime_error("connecting needs an interactive ANSI terminal; use "
|
||||||
"bridge --stdio for a host");
|
"bridge --stdio for a host");
|
||||||
auto password = Password();
|
auto password = Password();
|
||||||
if (interrupted)
|
if (interrupted)
|
||||||
@@ -288,9 +288,10 @@ int View(SessionOptions options, const std::string &device,
|
|||||||
auto st = s.Status();
|
auto st = s.Status();
|
||||||
const auto last_state = st["state"].get<std::string>();
|
const auto last_state = st["state"].get<std::string>();
|
||||||
auto line = (input.error.empty() ? last_state : input.error) + " " +
|
auto line = (input.error.empty() ? last_state : input.error) + " " +
|
||||||
(input.command_mode ? "LOCAL COMMAND"
|
(input.command_mode
|
||||||
: input.control ? "CONTROL"
|
? (input.control ? "LOCAL COMMAND / CONTROL PAUSED"
|
||||||
: "READ ONLY") +
|
: "LOCAL COMMAND / READ ONLY")
|
||||||
|
: input.control ? "CONTROL" : "READ ONLY") +
|
||||||
" " +
|
" " +
|
||||||
(kitty ? "KITTY"
|
(kitty ? "KITTY"
|
||||||
: detail == AnsiDetail::Quadrant ? "QUAD"
|
: detail == AnsiDetail::Quadrant ? "QUAD"
|
||||||
@@ -302,8 +303,8 @@ int View(SessionOptions options, const std::string &device,
|
|||||||
: "") +
|
: "") +
|
||||||
" | Ctrl+] commands";
|
" | Ctrl+] commands";
|
||||||
const std::string hint =
|
const std::string hint =
|
||||||
input.command_mode ? "c control q quit Esc cancel | i up j left k down "
|
input.command_mode ? "Esc/Ctrl+C return | c toggle q quit | "
|
||||||
"l right | +/- zoom f fit d detail"
|
"arrows/ijkl pan +/- zoom f fit d detail"
|
||||||
: input.control
|
: input.control
|
||||||
? "Keys/mouse -> remote | Ctrl+] then c release, q quit"
|
? "Keys/mouse -> remote | Ctrl+] then c release, q quit"
|
||||||
: "c control q quit ? help | i up j left k down l right | "
|
: "c control q quit ? help | i up j left k down l right | "
|
||||||
@@ -355,6 +356,7 @@ int View(SessionOptions options, const std::string &device,
|
|||||||
// These diagnostics go to the private file, never through the active screen.
|
// These diagnostics go to the private file, never through the active screen.
|
||||||
std::cerr
|
std::cerr
|
||||||
<< json{{"session", final},
|
<< json{{"session", final},
|
||||||
|
{"mouse_reports", input.mouse_reports},
|
||||||
{"submitted", submitted},
|
{"submitted", submitted},
|
||||||
{"output_bytes", output.bytes},
|
{"output_bytes", output.bytes},
|
||||||
{"last_queue_ms", queue_ms},
|
{"last_queue_ms", queue_ms},
|
||||||
@@ -377,6 +379,7 @@ int Bridge(int fd, SessionOptions options, int password_fd) {
|
|||||||
auto status = [&] {
|
auto status = [&] {
|
||||||
auto value = s.Status();
|
auto value = s.Status();
|
||||||
value["command_mode"] = input.command_mode;
|
value["command_mode"] = input.command_mode;
|
||||||
|
value["resume_control"] = input.control;
|
||||||
value["exit_requested"] = input.exit;
|
value["exit_requested"] = input.exit;
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
@@ -487,8 +490,7 @@ int Bridge(int fd, SessionOptions options, int password_fd) {
|
|||||||
awaiting = 0;
|
awaiting = 0;
|
||||||
} else if (method == "control") {
|
} else if (method == "control") {
|
||||||
input.control = Boolean(j, "enabled", false);
|
input.control = Boolean(j, "enabled", false);
|
||||||
input.command_mode = false;
|
s.SetControl(input.control && !input.command_mode);
|
||||||
s.SetControl(input.control);
|
|
||||||
} else if (method == "input") {
|
} else if (method == "input") {
|
||||||
auto text = j.at("data").get<std::string>();
|
auto text = j.at("data").get<std::string>();
|
||||||
if (text.size() > 16384)
|
if (text.size() > 16384)
|
||||||
@@ -621,18 +623,19 @@ int Bridge(int fd, SessionOptions options, int password_fd) {
|
|||||||
s.Disconnect();
|
s.Disconnect();
|
||||||
return interrupted ? 130 : 0;
|
return interrupted ? 130 : 0;
|
||||||
}
|
}
|
||||||
const char *help = R"(crossdesk-cli doctor [--json]
|
const char *help = R"(crossdesk-cli <9-digit-device-id> [--renderer auto|ansi|kitty] [--fps 1..30]
|
||||||
crossdesk-cli view <9-digit-device-id> [--renderer auto|ansi|kitty] [--fps 1..30]
|
[--display 0..63] [--control] [--server host] [--port 9099]
|
||||||
[--display 0..63] [--control] [--server host] [--port 9099]
|
[--quality low|balanced|high|ultra] [--ansi-detail quadrant|half] [--zoom 1..32|native]
|
||||||
[--quality low|balanced|high|ultra] [--ansi-detail quadrant|half] [--zoom 1..32|native]
|
crossdesk-cli view <9-digit-device-id> [options] (compatibility alias)
|
||||||
|
crossdesk-cli doctor [--json]
|
||||||
crossdesk-cli bridge --stdio [--password-fd 3] [--server host] [--port 9099]
|
crossdesk-cli bridge --stdio [--password-fd 3] [--server host] [--port 9099]
|
||||||
|
|
||||||
view: read-only and silent by default; hidden password prompt on the local terminal.
|
Connect by device ID: read-only and silent by default; hidden password prompt on the local terminal.
|
||||||
Read-only: c enables control; q or Ctrl+C quits; ? shows local commands.
|
Read-only: c enables control; q or Ctrl+C quits; ? shows local commands.
|
||||||
Ctrl+] opens local commands: c toggles control, q quits, Esc/Ctrl+C cancels.
|
Ctrl+] enters persistent local mode: c selects control on return; q quits; Esc/Ctrl+C returns.
|
||||||
View keys: click/d native detail, wheel/+/- zoom, arrows/ijkl pan (i up, j left, k down, l right), f fit.
|
View keys: click/d native detail, wheel/+/- zoom, arrows/ijkl pan (i up, j left, k down, l right), f fit.
|
||||||
In control mode, Ctrl+] then a view key adjusts the view once; ordinary keys go to the remote.
|
Local mode stays active after commands, paste or focus loss; remote input remains paused.
|
||||||
Ctrl+T and Ctrl+C pass through in control mode; Ctrl+] twice sends remote Ctrl+].
|
Ctrl+T and Ctrl+C pass through in control mode; another Ctrl+] leaves local mode and forwards Ctrl+] if control is enabled.
|
||||||
Default ANSI detail: quadrant. Default quality: high (1920x1080); ultra requests up to 3840x2160.
|
Default ANSI detail: quadrant. Default quality: high (1920x1080); ultra requests up to 3840x2160.
|
||||||
Display index is zero-based. ANSI cannot reproduce full desktop text at fit scale.
|
Display index is zero-based. ANSI cannot reproduce full desktop text at fit scale.
|
||||||
--fps controls local output; negotiated previews use supported 1/8/15 FPS.
|
--fps controls local output; negotiated previews use supported 1/8/15 FPS.
|
||||||
@@ -658,6 +661,9 @@ int main(int argc, char **argv) {
|
|||||||
{"stdin_tty", IsTty(0)},
|
{"stdin_tty", IsTty(0)},
|
||||||
{"stdout_tty", IsTty(1)},
|
{"stdout_tty", IsTty(1)},
|
||||||
{"term", SafeText(Env("TERM"))},
|
{"term", SafeText(Env("TERM"))},
|
||||||
|
{"terminal_program", SafeText(Env("TERM_PROGRAM"))},
|
||||||
|
{"input_shortcuts", "local_commands_v2"},
|
||||||
|
{"transport_diagnostics", true},
|
||||||
{"utf8", v.utf8},
|
{"utf8", v.utf8},
|
||||||
{"colors", v.colors},
|
{"colors", v.colors},
|
||||||
{"multiplexer", !Env("TMUX").empty() || !Env("STY").empty()},
|
{"multiplexer", !Env("TMUX").empty() || !Env("STY").empty()},
|
||||||
@@ -668,19 +674,27 @@ int main(int argc, char **argv) {
|
|||||||
std::cout << d.dump(argc == 3 ? -1 : 2) << "\n";
|
std::cout << d.dump(argc == 3 ? -1 : 2) << "\n";
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (command != "view" && command != "bridge")
|
int i = 2;
|
||||||
throw std::invalid_argument("unknown command; use --help");
|
if (command != "view" && command != "bridge") {
|
||||||
|
if (command.find_first_not_of("0123456789") != std::string::npos)
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"expected a 9-digit device ID or a command; use --help");
|
||||||
|
command = "view";
|
||||||
|
i = 1;
|
||||||
|
}
|
||||||
SessionOptions options;
|
SessionOptions options;
|
||||||
std::string renderer = "auto", device;
|
std::string renderer = "auto", device;
|
||||||
AnsiDetail detail = AnsiDetail::Quadrant;
|
AnsiDetail detail = AnsiDetail::Quadrant;
|
||||||
double zoom = 1;
|
double zoom = 1;
|
||||||
bool control = false, stdio = false, native = false;
|
bool control = false, stdio = false, native = false;
|
||||||
int secret_fd = 3;
|
int secret_fd = 3;
|
||||||
int i = 2;
|
|
||||||
if (command == "view") {
|
if (command == "view") {
|
||||||
if (argc <= 2)
|
if (i >= argc)
|
||||||
throw std::invalid_argument("device ID required");
|
throw std::invalid_argument("device ID required");
|
||||||
device = argv[i++];
|
device = argv[i++];
|
||||||
|
if (device.size() != 9 ||
|
||||||
|
device.find_first_not_of("0123456789") != std::string::npos)
|
||||||
|
throw std::invalid_argument("device ID must contain 9 digits");
|
||||||
}
|
}
|
||||||
auto number = [](const std::string &s, int lo, int hi) {
|
auto number = [](const std::string &s, int lo, int hi) {
|
||||||
size_t used = 0;
|
size_t used = 0;
|
||||||
@@ -739,6 +753,13 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
if (Redirect(2, 1) < 0)
|
if (Redirect(2, 1) < 0)
|
||||||
throw std::runtime_error("cannot isolate protocol output");
|
throw std::runtime_error("cannot isolate protocol output");
|
||||||
|
std::cerr << json{{"event", "startup"},
|
||||||
|
{"executable", SafeText(argv[0], 4096)},
|
||||||
|
{"terminal_program", SafeText(Env("TERM_PROGRAM"))},
|
||||||
|
{"input_shortcuts", "local_commands_v2"},
|
||||||
|
{"log_directory", options.log_directory}}
|
||||||
|
.dump()
|
||||||
|
<< "\n";
|
||||||
try {
|
try {
|
||||||
return command == "view" ? View(options, device, renderer, control,
|
return command == "view" ? View(options, device, renderer, control,
|
||||||
output.value, detail, zoom, native)
|
output.value, detail, zoom, native)
|
||||||
@@ -746,6 +767,9 @@ int main(int argc, char **argv) {
|
|||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
if (command == "view") {
|
if (command == "view") {
|
||||||
std::string msg = "crossdesk-cli: " + SafeText(e.what()) + "\r\n";
|
std::string msg = "crossdesk-cli: " + SafeText(e.what()) + "\r\n";
|
||||||
|
if (std::string(e.what()) == "connection_lost")
|
||||||
|
msg += "The remote connection was lost after connecting.\r\n";
|
||||||
|
msg += "Diagnostics: " + SafeText(options.log_directory) + "\r\n";
|
||||||
Write(output.value, msg.data(), msg.size());
|
Write(output.value, msg.data(), msg.size());
|
||||||
}
|
}
|
||||||
throw;
|
throw;
|
||||||
|
|||||||
@@ -3,6 +3,5 @@
|
|||||||
"darwin-x64": { "os": "darwin", "cpu": "x64", "xmakePlatform": "macosx", "xmakeArch": "x86_64" },
|
"darwin-x64": { "os": "darwin", "cpu": "x64", "xmakePlatform": "macosx", "xmakeArch": "x86_64" },
|
||||||
"linux-arm64": { "os": "linux", "cpu": "arm64", "libc": "glibc", "xmakePlatform": "linux", "xmakeArch": "arm64" },
|
"linux-arm64": { "os": "linux", "cpu": "arm64", "libc": "glibc", "xmakePlatform": "linux", "xmakeArch": "arm64" },
|
||||||
"linux-x64": { "os": "linux", "cpu": "x64", "libc": "glibc", "xmakePlatform": "linux", "xmakeArch": "x86_64" },
|
"linux-x64": { "os": "linux", "cpu": "x64", "libc": "glibc", "xmakePlatform": "linux", "xmakeArch": "x86_64" },
|
||||||
"win32-arm64": { "os": "win32", "cpu": "arm64", "xmakePlatform": "windows", "xmakeArch": "arm64" },
|
|
||||||
"win32-x64": { "os": "win32", "cpu": "x64", "xmakePlatform": "windows", "xmakeArch": "x64" }
|
"win32-x64": { "os": "win32", "cpu": "x64", "xmakePlatform": "windows", "xmakeArch": "x64" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default function crossdesk(pi: ExtensionAPI) {
|
|||||||
description: "Open a human-only remote desktop view (Ctrl+] for commands)",
|
description: "Open a human-only remote desktop view (Ctrl+] for commands)",
|
||||||
handler: async (args, ctx) => {
|
handler: async (args, ctx) => {
|
||||||
if (ctx.mode !== "tui" || !ctx.hasUI || !process.stdin.isTTY || !process.stdout.isTTY) {
|
if (ctx.mode !== "tui" || !ctx.hasUI || !process.stdin.isTTY || !process.stdout.isTTY) {
|
||||||
ctx.ui.notify("CrossDesk embedded view requires Pi interactive TUI. Run crossdesk-cli view <device-id> in a terminal.", "warning");
|
ctx.ui.notify("CrossDesk embedded view requires Pi interactive TUI. Run crossdesk-cli <device-id> in a terminal.", "warning");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (stop) { ctx.ui.notify("A CrossDesk view is already open", "warning"); return; }
|
if (stop) { ctx.ui.notify("A CrossDesk view is already open", "warning"); return; }
|
||||||
@@ -118,7 +118,9 @@ export default function crossdesk(pi: ExtensionAPI) {
|
|||||||
if (Number.isSafeInteger(message.status.generation)) generation = message.status.generation;
|
if (Number.isSafeInteger(message.status.generation)) generation = message.status.generation;
|
||||||
control = message.status.control === true;
|
control = message.status.control === true;
|
||||||
commandMode = message.status.command_mode === true;
|
commandMode = message.status.command_mode === true;
|
||||||
status = clean(message.status.state) + (commandMode ? " LOCAL COMMAND" : control ? " CONTROL" : " READ ONLY");
|
status = clean(message.status.state) + (commandMode
|
||||||
|
? " LOCAL COMMAND / " + (message.status.resume_control === true ? "CONTROL PAUSED" : "READ ONLY")
|
||||||
|
: control ? " CONTROL" : " READ ONLY");
|
||||||
if (message.status.error) error = clean(message.status.error);
|
if (message.status.error) error = clean(message.status.error);
|
||||||
if (message.status.state !== "connected") lines = [];
|
if (message.status.state !== "connected") lines = [];
|
||||||
}
|
}
|
||||||
@@ -157,7 +159,7 @@ export default function crossdesk(pi: ExtensionAPI) {
|
|||||||
const ack = frame; frame = undefined;
|
const ack = frame; frame = undefined;
|
||||||
if (ack) queueMicrotask(() => send("ack", ack)); // Host consumed the frame; not a physical display timestamp.
|
if (ack) queueMicrotask(() => send("ack", ack)); // Host consumed the frame; not a physical display timestamp.
|
||||||
const hint = bridgeStopped ? "q / Esc / Ctrl+C close"
|
const hint = bridgeStopped ? "q / Esc / Ctrl+C close"
|
||||||
: commandMode ? "c control q quit Esc cancel | i up j left k down l right | +/- zoom f fit d detail"
|
: commandMode ? "Esc/Ctrl+C return | c toggle q quit | arrows/ijkl pan +/- zoom f fit d detail"
|
||||||
: control ? "Keys -> remote | Ctrl+] then c release, q quit"
|
: control ? "Keys -> remote | Ctrl+] then c release, q quit"
|
||||||
: "c control q quit ? help | i up j left k down l right | +/- zoom f fit d detail";
|
: "c control q quit ? help | i up j left k down l right | +/- zoom f fit d detail";
|
||||||
return [truncateToWidth(`CrossDesk ${device} | ${status} ${native ? "detail" : "fit"} x${zoom} | Ctrl+] commands`, width),
|
return [truncateToWidth(`CrossDesk ${device} | ${status} ${native ? "detail" : "fit"} x${zoom} | Ctrl+] commands`, width),
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ function inspect(binary, target) {
|
|||||||
.sort((a, b) => a.localeCompare(b, undefined, { numeric: true })).at(-1);
|
.sort((a, b) => a.localeCompare(b, undefined, { numeric: true })).at(-1);
|
||||||
} else {
|
} else {
|
||||||
const offset = data.length >= 64 ? data.readUInt32LE(60) : data.length;
|
const offset = data.length >= 64 ? data.readUInt32LE(60) : data.length;
|
||||||
const machine = process.arch === 'arm64' ? 0xaa64 : 0x8664;
|
const machine = 0x8664;
|
||||||
if (data.toString('ascii', 0, 2) !== 'MZ' || offset + 6 > data.length ||
|
if (data.toString('ascii', 0, 2) !== 'MZ' || offset + 6 > data.length ||
|
||||||
data.readUInt32LE(offset) !== 0x4550 || data.readUInt16LE(offset + 4) !== machine)
|
data.readUInt32LE(offset) !== 0x4550 || data.readUInt16LE(offset + 4) !== machine)
|
||||||
throw new Error('Expected a Windows PE executable matching the current CPU.');
|
throw new Error('Expected a Windows PE executable matching the current CPU.');
|
||||||
@@ -76,7 +76,7 @@ function inspect(binary, target) {
|
|||||||
!fs.existsSync(path.join(process.env.SystemRoot, 'System32', dependency))))
|
!fs.existsSync(path.join(process.env.SystemRoot, 'System32', dependency))))
|
||||||
throw new Error(`Unbundled Windows runtime: ${dependency}. Build with static runtimes (MT).`);
|
throw new Error(`Unbundled Windows runtime: ${dependency}. Build with static runtimes (MT).`);
|
||||||
}
|
}
|
||||||
info.minimumOS = process.arch === 'arm64' ? 'Windows 11' : 'Windows 10 (VT console)';
|
info.minimumOS = 'Windows 10 (VT console)';
|
||||||
}
|
}
|
||||||
const doctor = JSON.parse(run(binary, ['doctor', '--json'], { timeout: 15000 }));
|
const doctor = JSON.parse(run(binary, ['doctor', '--json'], { timeout: 15000 }));
|
||||||
if (doctor.bridge_version !== 1) throw new Error('Native binary does not report the expected bridge version.');
|
if (doctor.bridge_version !== 1) throw new Error('Native binary does not report the expected bridge version.');
|
||||||
@@ -135,7 +135,7 @@ try {
|
|||||||
// Keep desktop build definitions for Xmake's initial option discovery;
|
// Keep desktop build definitions for Xmake's initial option discovery;
|
||||||
// desktop application sources and resources are intentionally not bundled.
|
// desktop application sources and resources are intentionally not bundled.
|
||||||
for (const entry of ['apps/cli', 'apps/desktop/xmake',
|
for (const entry of ['apps/cli', 'apps/desktop/xmake',
|
||||||
'libs/controller/include/controller_session.h',
|
'libs/controller/include',
|
||||||
'libs/controller/src', 'libs/terminal', 'libs/wire/include',
|
'libs/controller/src', 'libs/terminal', 'libs/wire/include',
|
||||||
'libs/wire/src', 'deps/submodules/minirtc',
|
'libs/wire/src', 'deps/submodules/minirtc',
|
||||||
'deps/submodules/xmake.lua', 'deps/thirdparty', 'deps/patches', 'xmake.lua',
|
'deps/submodules/xmake.lua', 'deps/thirdparty', 'deps/patches', 'xmake.lua',
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ local credential_patch = path.join(source_dir, "deps/patches/minirtc-credential-
|
|||||||
|
|
||||||
add_requires("spdlog 1.14.1", {system = false})
|
add_requires("spdlog 1.14.1", {system = false})
|
||||||
if is_plat("windows") then
|
if is_plat("windows") then
|
||||||
set_runtimes("MT")
|
|
||||||
add_requires("zlib 1.3.1", {system = false, configs = {shared = false}})
|
add_requires("zlib 1.3.1", {system = false, configs = {shared = false}})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2026-10-30
|
||||||
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _CONTROLLER_DIAGNOSTICS_H_
|
||||||
|
#define _CONTROLLER_DIAGNOSTICS_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
void InitializeControllerDiagnostics(const std::string &directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
#include "controller_diagnostics.h"
|
||||||
|
#include <log.h>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
namespace {
|
||||||
|
// Signaling errors and SDP can contain credentials. Only audited transport
|
||||||
|
// events reach this sink; arbitrary MiniRTC messages remain suppressed.
|
||||||
|
std::string DiagnosticEvent(const spdlog::details::log_msg &message) {
|
||||||
|
std::string_view source = message.source.filename ? message.source.filename : "";
|
||||||
|
const auto slash = source.find_last_of("/\\");
|
||||||
|
if (slash != std::string_view::npos)
|
||||||
|
source.remove_prefix(slash + 1);
|
||||||
|
const std::string_view text(message.payload.data(), message.payload.size());
|
||||||
|
if (source == "controller_session.cpp")
|
||||||
|
return std::string(text); // Local states/counters only at these call sites.
|
||||||
|
if (source == "ice_transport.cpp") {
|
||||||
|
const auto marker = text.rfind("state_change: ");
|
||||||
|
if (marker != std::string_view::npos) {
|
||||||
|
const auto state = text.substr(marker + 14);
|
||||||
|
for (const auto allowed : {"disconnected", "gathering", "connecting",
|
||||||
|
"connected", "ready", "failed"})
|
||||||
|
if (state == allowed)
|
||||||
|
return "ICE state: " + std::string(state);
|
||||||
|
}
|
||||||
|
for (const auto allowed : {"Traversal using relay server", "Traversal using p2p",
|
||||||
|
"Selected ICE pair is not available yet"})
|
||||||
|
if (text == allowed)
|
||||||
|
return std::string(text);
|
||||||
|
}
|
||||||
|
if (source == "ice_agent.cpp") {
|
||||||
|
for (const auto allowed : {"DTLS handshake started, awaiting peer packets...",
|
||||||
|
"DTLS handshake retransmission failed",
|
||||||
|
"DTLS peer certificate missing",
|
||||||
|
"DTLS peer fingerprint mismatch",
|
||||||
|
"DTLS peer fingerprint verified",
|
||||||
|
"DTLS peer not verified", "No SRTP profile selected",
|
||||||
|
"SSL_export_keying_material failed", "StartDtls failed",
|
||||||
|
"libnice has no relay upgrade extension; rebuild dependencies"})
|
||||||
|
if (text == allowed)
|
||||||
|
return std::string(text);
|
||||||
|
for (const auto prefix : {"DTLS handshake timed out after ",
|
||||||
|
"DTLS handshake start failed, err=",
|
||||||
|
"DTLS packet processing failed, err="})
|
||||||
|
if (text.substr(0, std::char_traits<char>::length(prefix)) == prefix)
|
||||||
|
return std::string(prefix); // Omit variable payload entirely.
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
class DiagnosticSink final : public spdlog::sinks::base_sink<std::mutex> {
|
||||||
|
public:
|
||||||
|
explicit DiagnosticSink(const std::string &directory)
|
||||||
|
: file_(directory + "/transport.log", 1024 * 1024, 2) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void sink_it_(const spdlog::details::log_msg &message) override {
|
||||||
|
const auto event = DiagnosticEvent(message);
|
||||||
|
if (event.empty())
|
||||||
|
return;
|
||||||
|
auto sanitized = message;
|
||||||
|
sanitized.payload = spdlog::string_view_t(event.data(), event.size());
|
||||||
|
file_.log(sanitized);
|
||||||
|
file_.flush();
|
||||||
|
}
|
||||||
|
void flush_() override { file_.flush(); }
|
||||||
|
spdlog::sinks::rotating_file_sink_mt file_;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void InitializeControllerDiagnostics(const std::string &directory) {
|
||||||
|
auto sink = std::make_shared<DiagnosticSink>(directory);
|
||||||
|
minirtc::InitLogger(directory);
|
||||||
|
auto logger = minirtc::get_logger();
|
||||||
|
// Connect calls this after the previous peer and its callbacks stop.
|
||||||
|
logger->sinks() = {std::move(sink)};
|
||||||
|
logger->set_level(spdlog::level::info);
|
||||||
|
}
|
||||||
|
} // namespace crossdesk
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "controller_session.h"
|
#include "controller_session.h"
|
||||||
|
#include "controller_diagnostics.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@@ -136,8 +137,8 @@ void ControllerSession::Connect(SessionOptions options, std::string device,
|
|||||||
// The server assigns a temporary controller identity on this connection.
|
// The server assigns a temporary controller identity on this connection.
|
||||||
// Never register a desktop identity or persist local credentials.
|
// Never register a desktop identity or persist local credentials.
|
||||||
client_id_.clear();
|
client_id_.clear();
|
||||||
minirtc::InitLogger(options_.log_directory);
|
InitializeControllerDiagnostics(options_.log_directory);
|
||||||
minirtc::get_logger()->set_level(spdlog::level::off);
|
SPDLOG_LOGGER_INFO(minirtc::get_logger(), "Session registering");
|
||||||
auto p = Parameters();
|
auto p = Parameters();
|
||||||
peer_ = CreatePeer(&p);
|
peer_ = CreatePeer(&p);
|
||||||
if (!peer_) {
|
if (!peer_) {
|
||||||
@@ -169,6 +170,7 @@ void ControllerSession::Tick() {
|
|||||||
if (Finished())
|
if (Finished())
|
||||||
break;
|
break;
|
||||||
if (e.kind == 0) {
|
if (e.kind == 0) {
|
||||||
|
SPDLOG_LOGGER_INFO(minirtc::get_logger(), "Signal status: {}", e.code);
|
||||||
if (e.code == SignalTlsCertError) {
|
if (e.code == SignalTlsCertError) {
|
||||||
Fail("tls_certificate_error");
|
Fail("tls_certificate_error");
|
||||||
break;
|
break;
|
||||||
@@ -199,6 +201,8 @@ void ControllerSession::Tick() {
|
|||||||
password_.clear();
|
password_.clear();
|
||||||
}
|
}
|
||||||
} else if (e.kind == 1) {
|
} else if (e.kind == 1) {
|
||||||
|
SPDLOG_LOGGER_INFO(minirtc::get_logger(), "Connection status: {} (session {})",
|
||||||
|
e.code, state_);
|
||||||
if (e.code == ::Connected && !Connected()) {
|
if (e.code == ::Connected && !Connected()) {
|
||||||
state_ = "connected";
|
state_ = "connected";
|
||||||
auto hello = MakeHostInformation("CrossDesk CLI", {}, false);
|
auto hello = MakeHostInformation("CrossDesk CLI", {}, false);
|
||||||
@@ -215,7 +219,7 @@ void ControllerSession::Tick() {
|
|||||||
else if (e.code == RemoteUnavailable)
|
else if (e.code == RemoteUnavailable)
|
||||||
Fail("device_offline_or_unavailable");
|
Fail("device_offline_or_unavailable");
|
||||||
else if (e.code == Failed)
|
else if (e.code == Failed)
|
||||||
Fail("connection_failed");
|
Fail(Connected() ? "connection_lost" : "connection_failed");
|
||||||
else if (e.code == Closed || e.code == Disconnected)
|
else if (e.code == Closed || e.code == Disconnected)
|
||||||
Fail("remote_disconnected");
|
Fail("remote_disconnected");
|
||||||
} else if (e.kind == 2 && Connected()) {
|
} else if (e.kind == 2 && Connected()) {
|
||||||
@@ -275,6 +279,7 @@ void ControllerSession::ClosePeers() {
|
|||||||
password_.clear();
|
password_.clear();
|
||||||
}
|
}
|
||||||
void ControllerSession::Fail(const char *reason) {
|
void ControllerSession::Fail(const char *reason) {
|
||||||
|
SPDLOG_LOGGER_INFO(minirtc::get_logger(), "Session failed: {}", reason);
|
||||||
ReleaseInput();
|
ReleaseInput();
|
||||||
error_ = reason;
|
error_ = reason;
|
||||||
state_ = "failed";
|
state_ = "failed";
|
||||||
@@ -345,6 +350,8 @@ void ControllerSession::Preview() {
|
|||||||
Send(kControlStream, s.ToJson().dump());
|
Send(kControlStream, s.ToJson().dump());
|
||||||
}
|
}
|
||||||
void ControllerSession::SetControl(bool enabled) {
|
void ControllerSession::SetControl(bool enabled) {
|
||||||
|
if (control_ != enabled)
|
||||||
|
SPDLOG_LOGGER_INFO(minirtc::get_logger(), "Remote control: {}", enabled);
|
||||||
if (!enabled)
|
if (!enabled)
|
||||||
ReleaseInput();
|
ReleaseInput();
|
||||||
control_ = enabled;
|
control_ = enabled;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ bool KittyReply(const std::string &response);
|
|||||||
// Incremental parser: protocol replies and unknown escapes are consumed
|
// Incremental parser: protocol replies and unknown escapes are consumed
|
||||||
// locally. Key taps always release immediately; paste uses the UTF-8 text
|
// locally. Key taps always release immediately; paste uses the UTF-8 text
|
||||||
// protocol. In command_mode, control records the mode to restore while the
|
// protocol. In command_mode, control records the mode to restore while the
|
||||||
// session's remote input is suspended.
|
// session's remote input is suspended until the user explicitly leaves the mode.
|
||||||
class Input {
|
class Input {
|
||||||
public:
|
public:
|
||||||
void Feed(const std::string &bytes, ControllerSession &, const Grid *grid);
|
void Feed(const std::string &bytes, ControllerSession &, const Grid *grid);
|
||||||
@@ -62,6 +62,7 @@ public:
|
|||||||
std::string error;
|
std::string error;
|
||||||
double zoom = 1, center_x = .5, center_y = .5;
|
double zoom = 1, center_x = .5, center_y = .5;
|
||||||
uint64_t view_revision = 0;
|
uint64_t view_revision = 0;
|
||||||
|
uint64_t mouse_reports = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string pending_;
|
std::string pending_;
|
||||||
|
|||||||
+15
-14
@@ -31,10 +31,14 @@ bool Input::LocalKey(int key, ControllerSession &s, const Grid *grid) {
|
|||||||
s.SetControl(false);
|
s.SetControl(false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool command = command_mode;
|
if (command_mode && (key == 27 || key == 3)) {
|
||||||
command_mode = false;
|
command_mode = false;
|
||||||
if (key == 'q' || (key == 3 && !command)) {
|
s.SetControl(control);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (key == 'q' || key == 3) {
|
||||||
exit = true;
|
exit = true;
|
||||||
|
command_mode = false;
|
||||||
control = false;
|
control = false;
|
||||||
} else if (key == 'c') {
|
} else if (key == 'c') {
|
||||||
control = !control;
|
control = !control;
|
||||||
@@ -42,12 +46,12 @@ bool Input::LocalKey(int key, ControllerSession &s, const Grid *grid) {
|
|||||||
key == 'd' || key == 'i' || key == 'j' || key == 'k' ||
|
key == 'd' || key == 'i' || key == 'j' || key == 'k' ||
|
||||||
key == 'l') {
|
key == 'l') {
|
||||||
Navigate(char(key), grid);
|
Navigate(char(key), grid);
|
||||||
} else if (!command) {
|
} else if (!command_mode) {
|
||||||
return false;
|
return false;
|
||||||
} else if (key != 27 && key != 3) {
|
} else {
|
||||||
error = "Unknown local command; Ctrl+] for help";
|
error = "Unknown local command; ? for help";
|
||||||
}
|
}
|
||||||
s.SetControl(control);
|
s.SetControl(control && !command_mode);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
void Input::Navigate(char key, const Grid *grid) {
|
void Input::Navigate(char key, const Grid *grid) {
|
||||||
@@ -94,7 +98,6 @@ void Input::Feed(const std::string &bytes, ControllerSession &s,
|
|||||||
if (pending_.size() + bytes.size() > 32768) {
|
if (pending_.size() + bytes.size() > 32768) {
|
||||||
pending_.clear();
|
pending_.clear();
|
||||||
pasting_ = false;
|
pasting_ = false;
|
||||||
command_mode = false;
|
|
||||||
s.SetControl(false);
|
s.SetControl(false);
|
||||||
control = false;
|
control = false;
|
||||||
return;
|
return;
|
||||||
@@ -117,11 +120,8 @@ void Input::Feed(const std::string &bytes, ControllerSession &s,
|
|||||||
auto end = pending_.find("\033[201~");
|
auto end = pending_.find("\033[201~");
|
||||||
if (end == std::string::npos)
|
if (end == std::string::npos)
|
||||||
return; // bounded by 32 KiB; one UTF-8 transaction
|
return; // bounded by 32 KiB; one UTF-8 transaction
|
||||||
if (command_mode) {
|
// Drop local-mode paste without leaving the mode or running commands.
|
||||||
// Pasted text must never become local commands or leak to the remote.
|
if (control && !command_mode && !s.Text(pending_.substr(0, end)))
|
||||||
command_mode = false;
|
|
||||||
s.SetControl(control);
|
|
||||||
} else if (control && !s.Text(pending_.substr(0, end)))
|
|
||||||
error = "Remote UTF-8 text input unavailable";
|
error = "Remote UTF-8 text input unavailable";
|
||||||
pending_.erase(0, end + 6);
|
pending_.erase(0, end + 6);
|
||||||
pasting_ = false;
|
pasting_ = false;
|
||||||
@@ -159,7 +159,6 @@ void Input::Feed(const std::string &bytes, ControllerSession &s,
|
|||||||
pending_.erase(0, end + 1);
|
pending_.erase(0, end + 1);
|
||||||
if (seq == "\033[O") {
|
if (seq == "\033[O") {
|
||||||
control = false;
|
control = false;
|
||||||
command_mode = false;
|
|
||||||
s.SetControl(false);
|
s.SetControl(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -224,6 +223,8 @@ void Input::Feed(const std::string &bytes, ControllerSession &s,
|
|||||||
} else
|
} else
|
||||||
mouse = mouse && cur == limit;
|
mouse = mouse && cur == limit;
|
||||||
}
|
}
|
||||||
|
if (mouse)
|
||||||
|
++mouse_reports;
|
||||||
if (grid && mouse && !command_mode) {
|
if (grid && mouse && !command_mode) {
|
||||||
auto pos = MapPointer(*grid, x - 1, y - 1);
|
auto pos = MapPointer(*grid, x - 1, y - 1);
|
||||||
if (!control) {
|
if (!control) {
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ local desktop_platform = is_plat("windows", "macosx", "linux") or
|
|||||||
add_rules("mode.release", "mode.debug")
|
add_rules("mode.release", "mode.debug")
|
||||||
set_languages("c++17")
|
set_languages("c++17")
|
||||||
set_encodings("utf-8")
|
set_encodings("utf-8")
|
||||||
|
if is_plat("windows") then
|
||||||
|
-- Shared wire targets and third-party packages must use the same CRT as
|
||||||
|
-- the native applications; a setting inside apps/cli does not reach them.
|
||||||
|
set_runtimes("MT")
|
||||||
|
end
|
||||||
add_requires("nlohmann_json 3.11.3")
|
add_requires("nlohmann_json 3.11.3")
|
||||||
|
|
||||||
option("CROSSDESK_BUILD_DESKTOP")
|
option("CROSSDESK_BUILD_DESKTOP")
|
||||||
|
|||||||
Reference in New Issue
Block a user