@ponydev/dsh-phone 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +7 -1
- package/CHANGELOG.md +16 -2
- package/CONTRIBUTING.md +4 -3
- package/README.md +6 -10
- package/docs/DESIGN.md +6 -8
- package/lib/adb.js +4 -4
- package/lib/client.js +44 -18
- package/lib/index.js +2 -2
- package/package.json +3 -3
- package/docs/mockups/device.png +0 -0
- package/docs/mockups/list.png +0 -0
- package/docs/mockups/mockup.html +0 -384
- package/docs/screenshots/01-devices.png +0 -0
- package/docs/screenshots/02-preview.png +0 -0
- package/docs/screenshots/03-overview.png +0 -0
package/AGENTS.md
CHANGED
|
@@ -36,13 +36,19 @@ docs/DESIGN.md 架构与布局;docs/mockups 原型;docs/scree
|
|
|
36
36
|
## 3. 提交前必须通过
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
npm run check # 语法 + i18n
|
|
39
|
+
npm run check # 语法 + i18n + 隐私/截图门禁
|
|
40
40
|
npm run smoke # 解析/白名单/targets/尺寸(无 adb 自动 SKIP)
|
|
41
41
|
npm pack --dry-run # 发布内容检查
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
CI 重复执行以上检查;失败不合并。
|
|
45
45
|
|
|
46
|
+
## 3.1 隐私与截图门禁(不可绕过)
|
|
47
|
+
|
|
48
|
+
1. `docs/screenshots/`、`docs/mockups/` 下的图**只能**由 `scripts/render-test.mjs` + **假 fixtures** 生成,禁止提交真机 / 真实账户截图;
|
|
49
|
+
2. docs 截图必须在 `docs/screenshots/MANIFEST.md` 登记来源(`- \`路径\` ← \`scripts/xxx.mjs\``),由 `scripts/check-shots.mjs` 强制;
|
|
50
|
+
3. 提交前 `npm run check` 已包含 `scripts/check-privacy.mjs` + `scripts/check-shots.mjs` 隐私门禁;命中即失败,不要为通过而放宽规则。
|
|
51
|
+
|
|
46
52
|
## 4. 安全不变量(不可破坏)
|
|
47
53
|
|
|
48
54
|
1. **serial 白名单**:`shot` / `input` / `stream` / `screen-info` 前,serial 必须出现在最新 `adb devices -l` 里;
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.2 — 2026-10-26
|
|
4
|
+
|
|
5
|
+
### 变更
|
|
6
|
+
- 隐私清理:文档、mockup、冒烟测试与离线 harness 里的真实设备序列号 / Tailscale CGNAT IP / 私有主机名统一替换为示例值
|
|
7
|
+
- 新增 `scripts/check-privacy.mjs` 与 `scripts/check-shots.mjs` 门禁,接入 `npm run check`、CI 与 release workflow
|
|
8
|
+
- npm `files` 的 `docs` 收紧为 `docs/*.md`,不再把 docs 截图 / mockup 图片打包进发布物
|
|
9
|
+
- 截图:删除真实截图 `01-devices` / `02-preview` 与 mockups 图片,只保留由 `scripts/render-test.mjs` + 假 fixtures 重生成的 `03-overview`,并在 `docs/screenshots/MANIFEST.md` 登记来源
|
|
10
|
+
|
|
11
|
+
## 0.3.1 — 2026-10-25
|
|
12
|
+
|
|
13
|
+
### 修复
|
|
14
|
+
- 云手机在平台侧切换分辨率后画面比例不跟随:预览每 3s 用 `screen-info?fresh` 检测 `wm size`,变化时自动重连流并按新尺寸重启 screenrecord
|
|
15
|
+
- 顶部新增当前流分辨率显示(如 `540×960 · 58fps · 20ms`)
|
|
16
|
+
|
|
3
17
|
## 0.3.0 — 2026-10-24
|
|
4
18
|
|
|
5
19
|
### 新增
|
|
@@ -52,8 +66,8 @@
|
|
|
52
66
|
- 常用连接目标:连接成功自动记录(`$DSH_HOME/phone/targets.json`,最近 12 条),未连接目标也显示在列表,点击直接 `adb connect`
|
|
53
67
|
- `⋯ → adb 诊断`:adb 路径/版本、每台设备状态,可复制
|
|
54
68
|
- 列表改为平铺,不再按「云手机」分组;只保留连接方式标签(USB / Wi-Fi / Tailscale / emulator)
|
|
55
|
-
- Tailscale
|
|
56
|
-
- 真机验证:
|
|
69
|
+
- Tailscale 判定:`*.ts.net`、`100.64.0.0/10`
|
|
70
|
+
- 真机验证:Android 真机上 tap / swipe / 返回 / 主页 均已实测(点开「网络和互联网」「关于本机」,列表滚动)
|
|
57
71
|
- H.264 实时预览:Host `POST /phone/stream` 直接 pipe `screenrecord --output-format=h264`;客户端 Annex-B → avcC → WebCodecs VideoDecoder → canvas,约 20–30fps
|
|
58
72
|
- 不支持 WebCodecs 的浏览器自动回退截图轮询;`screen-info` 提供设备真实分辨率做坐标换算
|
|
59
73
|
- 操作栏改为底部悬浮胶囊(HarmonyOS 风格三键 + 截图),不占画面高度;顶部设备下拉/清晰度/旋转/更多;断开连接收进设备菜单并二次确认
|
package/CONTRIBUTING.md
CHANGED
|
@@ -24,7 +24,7 @@ launchctl kickstart -k gui/$(id -u)/local.deepseek-harness.web
|
|
|
24
24
|
离线 UI 验证(不依赖宿主,也不需要会话):
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
#
|
|
27
|
+
# 先准备一张画面占位图(本地调试可用真机截图;提交进 docs 的截图必须用合成占位图)
|
|
28
28
|
adb -s <serial> exec-out screencap -p > /tmp/dsh-phone-shot.png
|
|
29
29
|
npm run test-ui # 输出 /tmp/dsh-phone-render.png
|
|
30
30
|
open /tmp/dsh-phone-render.html
|
|
@@ -38,7 +38,7 @@ open '/tmp/dsh-phone-render.html#stream'
|
|
|
38
38
|
## 提交前检查
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
npm run check # 语法 + i18n
|
|
41
|
+
npm run check # 语法 + i18n + 隐私/截图门禁
|
|
42
42
|
npm run smoke # 解析/白名单/targets/尺寸(无 adb 自动 SKIP)
|
|
43
43
|
npm pack --dry-run # 发布内容检查
|
|
44
44
|
```
|
|
@@ -49,5 +49,6 @@ npm pack --dry-run # 发布内容检查
|
|
|
49
49
|
- serial 必须先过 `adb devices` 列表校验;不要假设云手机型号/分辨率(用 `wm size`)。
|
|
50
50
|
- 不新增运行时依赖;不引入构建工具。
|
|
51
51
|
- 颜色只用 `--dsw-*` token,文案走 zh/en 字典;不做元数据栏、不做文字输入。
|
|
52
|
-
-
|
|
52
|
+
- **截图只能由 `scripts/render-test.mjs` + 假 fixtures 生成**;不要把真机 / 真实账户截图提交到仓库。
|
|
53
|
+
- docs 截图必须在 `docs/screenshots/MANIFEST.md` 登记来源;提交前 `npm run check` 含隐私门禁(`check-privacy` + `check-shots`),命中即失败。
|
|
53
54
|
- Conventional Commits:`feat:` / `fix:` / `docs:` / `ci:` / `chore:` / `refactor:`;不写 AI 署名。
|
package/README.md
CHANGED
|
@@ -5,10 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
DSH Web 插件:在右侧栏预览已连接的 Android 设备,点画面就是点手机。
|
|
7
7
|
|
|
8
|
-
| 设备列表 | 预览画面 |
|
|
9
|
-
|---|---|
|
|
10
|
-
|  |  |
|
|
11
|
-
|
|
12
8
|

|
|
13
9
|
|
|
14
10
|
## 功能
|
|
@@ -21,7 +17,7 @@ DSH Web 插件:在右侧栏预览已连接的 Android 设备,点画面就是
|
|
|
21
17
|
- **连接**:`adb connect <ip>:<port>`(无线调试、Tailscale 云手机);连接过的目标自动记到 `$DSH_HOME/phone/targets.json`,未连接时也显示在列表里,点一下就直接连。
|
|
22
18
|
- **诊断**:`⋯ → adb 诊断` 看 adb 路径/版本、每台设备状态,一键复制。
|
|
23
19
|
- **更多**:电源、旋转、重新连接、adb 诊断、浮窗打开、复制 serial、断开。
|
|
24
|
-
- 连接方式由 serial 判断:`usb:` 属性 → USB
|
|
20
|
+
- 连接方式由 serial 判断:`usb:` 属性 → USB;`*.ts.net` 或 `100.64.0.0/10` → Tailscale;其余带端口 → Wi-Fi。
|
|
25
21
|
- 系统平台由 `getprop` 判断(`ro.build.version.harmony*` / `hw_sc.build.platform.version` → HarmonyOS,其余 Android);iOS 图标等接入 WDA transport 后生效。
|
|
26
22
|
- 没有分辨率/网速元数据栏,没有文字输入框。
|
|
27
23
|
|
|
@@ -29,7 +25,7 @@ DSH Web 插件:在右侧栏预览已连接的 Android 设备,点画面就是
|
|
|
29
25
|
|
|
30
26
|
```sh
|
|
31
27
|
# 本地开发(link 到当前 clone)
|
|
32
|
-
dsh plugin --profile web add link
|
|
28
|
+
dsh plugin --profile web add link:~/deepseek/plugins/dsh-phone
|
|
33
29
|
|
|
34
30
|
# 从 npm(发布后)
|
|
35
31
|
dsh plugin --profile web add @ponydev/dsh-phone
|
|
@@ -46,7 +42,7 @@ dsh plugin --profile web add @ponydev/dsh-phone
|
|
|
46
42
|
- id: phone
|
|
47
43
|
name: "@ponydev/dsh-phone"
|
|
48
44
|
config:
|
|
49
|
-
adbPath: adb # 或
|
|
45
|
+
adbPath: adb # 或 ~/Library/Android/sdk/platform-tools/adb
|
|
50
46
|
pollMs: 700
|
|
51
47
|
streamTimeLimit: 180 # screenrecord 单次上限,到点自动续
|
|
52
48
|
streamBitRate: 6000000 # 客户端清晰度会覆盖这两项
|
|
@@ -128,14 +124,14 @@ DSH 右侧栏第一次打开是**视口宽度的 45%**(1440 屏 ≈ 648px)
|
|
|
128
124
|
## 开发
|
|
129
125
|
|
|
130
126
|
```sh
|
|
131
|
-
npm run check #
|
|
127
|
+
npm run check # 语法 + i18n + 隐私/截图门禁
|
|
132
128
|
npm run smoke # 解析/白名单/真机 adb 冒烟
|
|
133
129
|
node scripts/render-test.mjs [screenshot.png]
|
|
134
130
|
# 离线渲染列表 + 预览,输出 /tmp/dsh-phone-render.html/.png
|
|
135
131
|
```
|
|
136
132
|
|
|
137
|
-
`scripts/render-test.mjs` 默认用 `/tmp/dsh-phone-shot.png`
|
|
138
|
-
|
|
133
|
+
`scripts/render-test.mjs` 默认用 `/tmp/dsh-phone-shot.png` 作为画面占位。本地调试可以用真机截图,
|
|
134
|
+
但**提交进 docs 的截图必须用合成占位图**(真机截图只放 /tmp,不进仓库)。
|
|
139
135
|
|
|
140
136
|
## 结构
|
|
141
137
|
|
package/docs/DESIGN.md
CHANGED
|
@@ -4,9 +4,7 @@
|
|
|
4
4
|
> 参考 Codex Mobile Dev 的右侧设备面板:**顶上只有一行「设备下拉 + 几个图标」,下面整块留给设备画面**。
|
|
5
5
|
> 本文是布局、命名与技术方案,**尚未开始写代码**。
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-

|
|
7
|
+
原型(离线 HTML,假数据,可用浏览器直接打开):[`docs/mockups/mockup.html`](mockups/mockup.html)
|
|
10
8
|
|
|
11
9
|
---
|
|
12
10
|
|
|
@@ -65,12 +63,12 @@
|
|
|
65
63
|
[ 选择设备 ▾ ] [刷新] [+]
|
|
66
64
|
──────────────────────────────────────────────────────
|
|
67
65
|
已连接
|
|
68
|
-
▢ Pixel 7a [USB]
|
|
69
|
-
▢ Pixel 7a [Wi-Fi] 100.
|
|
70
|
-
▢ 小米 13 [thor] 100.
|
|
66
|
+
▢ Pixel 7a [USB] SERIAL0123456789AB ›
|
|
67
|
+
▢ Pixel 7a [Wi-Fi] 100.64.0.11:5555 ›
|
|
68
|
+
▢ 小米 13 [thor] 100.64.0.12:5555 ›
|
|
71
69
|
云手机
|
|
72
|
-
☁ 云手机
|
|
73
|
-
☁ 云手机
|
|
70
|
+
☁ 云手机 A node-a.example-tailnet.ts.net:5555 ›
|
|
71
|
+
☁ 云手机 B node-b.example-tailnet.ts.net:5555 ›
|
|
74
72
|
──────────────────────────────────────────────────────
|
|
75
73
|
● 5 台在线
|
|
76
74
|
```
|
package/lib/adb.js
CHANGED
|
@@ -88,7 +88,7 @@ function isCgnatSerial(serial) {
|
|
|
88
88
|
export function classifyDevice(serial, props = {}) {
|
|
89
89
|
if (props.usb) return 'usb'
|
|
90
90
|
if (/^emulator-\d+$/.test(serial)) return 'emulator'
|
|
91
|
-
if (
|
|
91
|
+
if (/\.ts\.net/.test(serial)) return 'tailscale'
|
|
92
92
|
if (isCgnatSerial(serial)) return 'tailscale'
|
|
93
93
|
if (serial.includes(':')) return 'wifi'
|
|
94
94
|
return 'usb'
|
|
@@ -309,11 +309,11 @@ export class Adb {
|
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
/** 设备物理分辨率(wm size),客户端用它把画面坐标换算成输入坐标;缓存 60s。 */
|
|
312
|
-
async screenInfo(serial) {
|
|
312
|
+
async screenInfo(serial, options = {}) {
|
|
313
313
|
await this.assertReady(serial)
|
|
314
314
|
const cached = this._screens.get(serial)
|
|
315
315
|
const now = Date.now()
|
|
316
|
-
if (cached && now - cached.at < 60_000) return cached.info
|
|
316
|
+
if (!options.fresh && cached && now - cached.at < 60_000) return cached.info
|
|
317
317
|
const { stdout } = await this._run(['-s', serial, 'shell', 'wm', 'size'], { timeout: 5000 })
|
|
318
318
|
const info = parseWmSize(stdout.toString('utf8'))
|
|
319
319
|
if (!info) throw fail('bad-screen-info', '无法读取设备分辨率')
|
|
@@ -323,7 +323,7 @@ export class Adb {
|
|
|
323
323
|
|
|
324
324
|
async connect(address) {
|
|
325
325
|
if (typeof address !== 'string' || !ADDRESS_RE.test(address.trim())) {
|
|
326
|
-
throw fail('bad-address', '地址格式应为 host:port,例如
|
|
326
|
+
throw fail('bad-address', '地址格式应为 host:port,例如 192.0.2.53:5555')
|
|
327
327
|
}
|
|
328
328
|
const { stdout } = await this._run(['connect', address.trim()], { timeout: 8000 })
|
|
329
329
|
this._devices.at = 0
|
package/lib/client.js
CHANGED
|
@@ -392,7 +392,7 @@ window.__ModuleLoader__.load({
|
|
|
392
392
|
connect: (address) => unwrap('connect', { address }),
|
|
393
393
|
disconnect: (serial) => unwrap('disconnect', { serial }),
|
|
394
394
|
input: (serial, event) => unwrap('input', { serial, event }),
|
|
395
|
-
screenInfo: (serial) => unwrap('screen-info', { serial }),
|
|
395
|
+
screenInfo: (serial, options) => unwrap('screen-info', { serial, fresh: Boolean(options && options.fresh) }),
|
|
396
396
|
ping: (serial) => unwrap('ping', { serial }),
|
|
397
397
|
targets: () => unwrap('targets'),
|
|
398
398
|
removeTarget: (address) => unwrap('targets/remove', { address }),
|
|
@@ -437,7 +437,7 @@ window.__ModuleLoader__.load({
|
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
function kindForAddress(address) {
|
|
440
|
-
if (
|
|
440
|
+
if (/\.ts\.net/.test(address)) return 'tailscale'
|
|
441
441
|
if (/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./.test(address)) return 'tailscale'
|
|
442
442
|
if (address.includes(':')) return 'wifi'
|
|
443
443
|
return 'usb'
|
|
@@ -663,7 +663,8 @@ window.__ModuleLoader__.load({
|
|
|
663
663
|
let pps = null
|
|
664
664
|
let frames = 0
|
|
665
665
|
let waitingKey = true
|
|
666
|
-
let
|
|
666
|
+
let lastWidth = 0
|
|
667
|
+
let lastHeight = 0
|
|
667
668
|
|
|
668
669
|
const resetDecoder = () => {
|
|
669
670
|
if (decoder) {
|
|
@@ -694,9 +695,10 @@ window.__ModuleLoader__.load({
|
|
|
694
695
|
frames += 1
|
|
695
696
|
waitingKey = false
|
|
696
697
|
if (onFrame) onFrame()
|
|
697
|
-
if (
|
|
698
|
-
|
|
699
|
-
|
|
698
|
+
if (frame.displayWidth !== lastWidth || frame.displayHeight !== lastHeight) {
|
|
699
|
+
lastWidth = frame.displayWidth
|
|
700
|
+
lastHeight = frame.displayHeight
|
|
701
|
+
if (onSize) onSize(lastWidth, lastHeight)
|
|
700
702
|
}
|
|
701
703
|
} finally {
|
|
702
704
|
frame.close()
|
|
@@ -811,6 +813,7 @@ window.__ModuleLoader__.load({
|
|
|
811
813
|
const [attempt, setAttempt] = useState(0)
|
|
812
814
|
const [toast, setToast] = useState(null)
|
|
813
815
|
const [fps, setFps] = useState(0)
|
|
816
|
+
const [streamSize, setStreamSize] = useState(null)
|
|
814
817
|
const [rtt, setRtt] = useState(null)
|
|
815
818
|
const frameCounter = useRef({ count: 0, at: typeof performance !== 'undefined' ? performance.now() : Date.now() })
|
|
816
819
|
const streamStatus = useStream(
|
|
@@ -821,7 +824,10 @@ window.__ModuleLoader__.load({
|
|
|
821
824
|
attempt,
|
|
822
825
|
quality,
|
|
823
826
|
(w, h) => {
|
|
824
|
-
if (w > 0 && h > 0)
|
|
827
|
+
if (w > 0 && h > 0) {
|
|
828
|
+
setRatio(w / h)
|
|
829
|
+
setStreamSize({ width: w, height: h })
|
|
830
|
+
}
|
|
825
831
|
},
|
|
826
832
|
() => {
|
|
827
833
|
frameCounter.current.count += 1
|
|
@@ -831,17 +837,27 @@ window.__ModuleLoader__.load({
|
|
|
831
837
|
useEffect(() => {
|
|
832
838
|
if (!serial) return undefined
|
|
833
839
|
let alive = true
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
840
|
+
let last = null
|
|
841
|
+
const apply = (info) => {
|
|
842
|
+
if (!info || !info.width || !info.height || info.width < 2 || info.height < 2) return
|
|
843
|
+
const changed = last && (last.width !== info.width || last.height !== info.height)
|
|
844
|
+
last = { width: info.width, height: info.height }
|
|
845
|
+
setDeviceSize(last)
|
|
846
|
+
setRatio(info.width / info.height)
|
|
847
|
+
// 设备分辨率变了(云手机平台切换):重连流,让 Host 按新尺寸重启 screenrecord
|
|
848
|
+
if (changed) setAttempt((value) => value + 1)
|
|
849
|
+
}
|
|
850
|
+
const check = async () => {
|
|
851
|
+
try {
|
|
852
|
+
const info = await api.screenInfo(serial, { fresh: true })
|
|
853
|
+
if (alive) apply(info)
|
|
854
|
+
} catch {}
|
|
855
|
+
}
|
|
856
|
+
check()
|
|
857
|
+
const timer = setInterval(check, 3000)
|
|
843
858
|
return () => {
|
|
844
859
|
alive = false
|
|
860
|
+
clearInterval(timer)
|
|
845
861
|
}
|
|
846
862
|
}, [serial])
|
|
847
863
|
|
|
@@ -1038,6 +1054,7 @@ window.__ModuleLoader__.load({
|
|
|
1038
1054
|
ratio,
|
|
1039
1055
|
immersive,
|
|
1040
1056
|
deviceSize,
|
|
1057
|
+
streamSize,
|
|
1041
1058
|
frameUrl,
|
|
1042
1059
|
error: errorNow,
|
|
1043
1060
|
loading: loadingNow,
|
|
@@ -1389,7 +1406,7 @@ window.__ModuleLoader__.load({
|
|
|
1389
1406
|
h('input', {
|
|
1390
1407
|
className: 'dph-input',
|
|
1391
1408
|
value: address,
|
|
1392
|
-
placeholder: '
|
|
1409
|
+
placeholder: '192.0.2.53:5555',
|
|
1393
1410
|
onChange: (event) => setAddress(event.target.value),
|
|
1394
1411
|
autoFocus: true,
|
|
1395
1412
|
spellCheck: false,
|
|
@@ -1657,7 +1674,16 @@ window.__ModuleLoader__.load({
|
|
|
1657
1674
|
qualityLabel(quality, t),
|
|
1658
1675
|
)
|
|
1659
1676
|
: null,
|
|
1660
|
-
selected
|
|
1677
|
+
selected
|
|
1678
|
+
? h(
|
|
1679
|
+
'span',
|
|
1680
|
+
{ className: 'dph-perf', title: t('quality') },
|
|
1681
|
+
(preview.streamSize ? preview.streamSize.width + '×' + preview.streamSize.height + ' · ' : '') +
|
|
1682
|
+
preview.fps +
|
|
1683
|
+
'fps · ' +
|
|
1684
|
+
(preview.rtt == null ? '—' : preview.rtt + 'ms'),
|
|
1685
|
+
)
|
|
1686
|
+
: null,
|
|
1661
1687
|
h('div', { className: 'dph-grow' }),
|
|
1662
1688
|
rightButtons,
|
|
1663
1689
|
),
|
package/lib/index.js
CHANGED
|
@@ -95,7 +95,7 @@ export function apply(ctx, rawConfig) {
|
|
|
95
95
|
case 'ping':
|
|
96
96
|
return ok(await adb.ping(String(payload.serial || '')))
|
|
97
97
|
case 'screen-info':
|
|
98
|
-
return ok(await adb.screenInfo(String(payload.serial || '')))
|
|
98
|
+
return ok(await adb.screenInfo(String(payload.serial || ''), { fresh: payload.fresh === true }))
|
|
99
99
|
case 'disconnect':
|
|
100
100
|
return ok(await adb.disconnect(String(payload.serial || '')))
|
|
101
101
|
case 'input':
|
|
@@ -151,7 +151,7 @@ export function apply(ctx, rawConfig) {
|
|
|
151
151
|
if (preset) {
|
|
152
152
|
openOptions = { bitRate: preset.bitRate }
|
|
153
153
|
try {
|
|
154
|
-
const info = await adb.screenInfo(serial)
|
|
154
|
+
const info = await adb.screenInfo(serial, { fresh: true })
|
|
155
155
|
const dims = streamSizeFor(info, preset.shortSide)
|
|
156
156
|
if (dims) openOptions.size = dims.width + 'x' + dims.height
|
|
157
157
|
} catch {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ponydev/dsh-phone",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "DSH Web 插件:右侧栏预览并点按控制已连接的 Android 设备(adb)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"type": "module",
|
|
26
26
|
"files": [
|
|
27
27
|
"lib",
|
|
28
|
-
"docs",
|
|
28
|
+
"docs/*.md",
|
|
29
29
|
"cordis.patch.yml",
|
|
30
30
|
"README.md",
|
|
31
31
|
"CHANGELOG.md",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"node": ">=22.5"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
|
-
"check": "node --check lib/adb.js && node --check lib/targets.js && node --check lib/stream.js && node --check lib/transport.js && node --check lib/index.js && node --check lib/client.js && node scripts/check-i18n.mjs",
|
|
46
|
+
"check": "node --check lib/adb.js && node --check lib/targets.js && node --check lib/stream.js && node --check lib/transport.js && node --check lib/index.js && node --check lib/client.js && node scripts/check-i18n.mjs && node scripts/check-privacy.mjs && node scripts/check-shots.mjs",
|
|
47
47
|
"smoke": "node scripts/smoke.mjs",
|
|
48
48
|
"check-i18n": "node scripts/check-i18n.mjs",
|
|
49
49
|
"release-notes": "node scripts/release-notes.mjs",
|
package/docs/mockups/device.png
DELETED
|
Binary file
|
package/docs/mockups/list.png
DELETED
|
Binary file
|
package/docs/mockups/mockup.html
DELETED
|
@@ -1,384 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<title>dsh-phone · 布局草案 v2</title>
|
|
6
|
-
<style>
|
|
7
|
-
:root {
|
|
8
|
-
--bg: #ffffff;
|
|
9
|
-
--bg-side: #f7f7f8;
|
|
10
|
-
--bg-soft: #f4f4f5;
|
|
11
|
-
--border: #e9e9ec;
|
|
12
|
-
--border-strong: #dededf;
|
|
13
|
-
--text: #17171a;
|
|
14
|
-
--text-2: #6f6f76;
|
|
15
|
-
--text-3: #9a9aa1;
|
|
16
|
-
--accent: #3b6cff;
|
|
17
|
-
--accent-soft: #eef2ff;
|
|
18
|
-
--green: #22c55e;
|
|
19
|
-
--radius: 14px;
|
|
20
|
-
--font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC", "Helvetica Neue", Arial, sans-serif;
|
|
21
|
-
}
|
|
22
|
-
* { box-sizing: border-box; }
|
|
23
|
-
html, body { margin: 0; height: 100%; }
|
|
24
|
-
body { font-family: var(--font); color: var(--text); background: #ececee; -webkit-font-smoothing: antialiased; }
|
|
25
|
-
.frame { width: 1440px; height: 900px; display: flex; background: var(--bg); margin: 0 auto; overflow: hidden; }
|
|
26
|
-
svg { display: block; }
|
|
27
|
-
|
|
28
|
-
/* ---------------- left sidebar ---------------- */
|
|
29
|
-
.sidebar { width: 268px; flex: 0 0 268px; background: var(--bg-side); border-right: 1px solid var(--border); display: flex; flex-direction: column; padding: 14px 10px 10px; }
|
|
30
|
-
.brand { display: flex; align-items: center; gap: 8px; padding: 2px 8px 12px; }
|
|
31
|
-
.brand .logo { width: 24px; height: 24px; border-radius: 7px; background: #17171a; color: #fff; display: grid; place-items: center; font-size: 12px; font-weight: 700; }
|
|
32
|
-
.brand .name { font-size: 13.5px; font-weight: 600; letter-spacing: .2px; }
|
|
33
|
-
.newchat { margin: 0 2px 10px; height: 34px; border-radius: 10px; background: #fff; border: 1px solid var(--border); display: flex; align-items: center; gap: 8px; padding: 0 11px; font-size: 13px; }
|
|
34
|
-
.newchat kbd { margin-left: auto; font: 11px var(--font); color: var(--text-3); }
|
|
35
|
-
.search { margin: 0 2px 12px; height: 30px; border-radius: 9px; background: #efeff1; display: flex; align-items: center; gap: 7px; padding: 0 9px; color: var(--text-3); font-size: 12.5px; }
|
|
36
|
-
.sec { font-size: 11px; color: var(--text-3); padding: 4px 10px 6px; letter-spacing: .3px; }
|
|
37
|
-
.sess { height: 34px; border-radius: 9px; display: flex; align-items: center; gap: 8px; padding: 0 9px; font-size: 13px; color: #3c3c42; margin-bottom: 1px; }
|
|
38
|
-
.sess .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--text-3); flex: none; }
|
|
39
|
-
.sess.active { background: #fff; box-shadow: 0 0 0 1px var(--border); font-weight: 500; color: var(--text); }
|
|
40
|
-
.sess.active .dot { background: var(--accent); }
|
|
41
|
-
.sess .t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
42
|
-
.side-foot { margin-top: auto; display: flex; flex-direction: column; gap: 2px; }
|
|
43
|
-
.foot-row { height: 38px; border-radius: 10px; display: flex; align-items: center; gap: 9px; padding: 0 9px; font-size: 12.5px; color: var(--text-2); }
|
|
44
|
-
.foot-row .badge { margin-left: auto; font-size: 12px; color: var(--text); font-weight: 600; }
|
|
45
|
-
.foot-row .pill { width: 7px; height: 7px; border-radius: 50%; background: var(--green); }
|
|
46
|
-
|
|
47
|
-
/* ---------------- center ---------------- */
|
|
48
|
-
.main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; background: #fff; }
|
|
49
|
-
.conv-head { height: 52px; flex: none; display: flex; align-items: center; gap: 8px; padding: 0 14px 0 20px; }
|
|
50
|
-
.conv-title { font-size: 13.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
51
|
-
.conv-sub { font-size: 12px; color: var(--text-3); }
|
|
52
|
-
.head-right { margin-left: auto; display: flex; align-items: center; gap: 6px; }
|
|
53
|
-
.iconbtn { width: 30px; height: 30px; border-radius: 8px; display: grid; place-items: center; color: var(--text-2); background: transparent; border: 0; position: relative; }
|
|
54
|
-
.iconbtn.active { background: var(--accent-soft); color: var(--accent); }
|
|
55
|
-
.iconbtn .badge { position: absolute; top: 3px; right: 3px; min-width: 13px; height: 13px; border-radius: 7px; background: var(--accent); color: #fff; font-size: 9px; font-weight: 600; display: grid; place-items: center; padding: 0 3px; }
|
|
56
|
-
.hair { width: 1px; height: 18px; background: var(--border); margin: 0 3px; }
|
|
57
|
-
.transcript { flex: 1 1 auto; overflow: hidden; padding: 8px 0 0; display: flex; flex-direction: column; gap: 18px; }
|
|
58
|
-
.turn { padding: 0 26px; }
|
|
59
|
-
.turn.user { display: flex; justify-content: flex-end; }
|
|
60
|
-
.bubble { max-width: 78%; background: #f4f4f6; border-radius: 16px 16px 4px 16px; padding: 10px 14px; font-size: 13.5px; line-height: 1.62; color: #242428; }
|
|
61
|
-
.assistant { font-size: 13.5px; line-height: 1.72; color: #26262b; max-width: 92%; }
|
|
62
|
-
.assistant p { margin: 0 0 10px; }
|
|
63
|
-
.assistant .muted { color: var(--text-2); }
|
|
64
|
-
.inline-code { font: 11.5px ui-monospace, SFMono-Regular, Menlo, monospace; background: #f2f2f4; border-radius: 5px; padding: 1px 5px; color: #3b3b42; }
|
|
65
|
-
.composer-wrap { flex: none; padding: 8px 26px 18px; }
|
|
66
|
-
.composer { border: 1px solid var(--border-strong); border-radius: var(--radius); background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.03); padding: 12px 14px 10px; }
|
|
67
|
-
.composer .ph { font-size: 13.5px; color: var(--text-3); }
|
|
68
|
-
.composer .bar { display: flex; align-items: center; gap: 6px; margin-top: 18px; }
|
|
69
|
-
.chip { height: 26px; border-radius: 8px; border: 1px solid var(--border); background: #fff; display: flex; align-items: center; gap: 6px; padding: 0 8px; font-size: 12px; color: var(--text-2); }
|
|
70
|
-
.send { margin-left: auto; width: 30px; height: 30px; border-radius: 9px; background: #17171a; color: #fff; display: grid; place-items: center; }
|
|
71
|
-
|
|
72
|
-
/* ---------------- right bar ---------------- */
|
|
73
|
-
.rightbar { width: 540px; flex: 0 0 540px; border-left: 1px solid var(--border); display: flex; flex-direction: column; background: #fff; }
|
|
74
|
-
.tabstrip { height: 44px; flex: none; display: flex; align-items: center; gap: 4px; padding: 0 8px; border-bottom: 1px solid var(--border); }
|
|
75
|
-
.tab { height: 28px; border-radius: 8px; display: flex; align-items: center; gap: 6px; padding: 0 8px; font-size: 12.5px; color: var(--text-2); }
|
|
76
|
-
.tab .x { color: var(--text-3); margin-left: 2px; }
|
|
77
|
-
.tab.active { background: var(--bg-soft); color: var(--text); font-weight: 500; }
|
|
78
|
-
.strip-spacer { flex: 1 1 auto; }
|
|
79
|
-
.strip-right { display: flex; align-items: center; gap: 2px; }
|
|
80
|
-
|
|
81
|
-
/* panel toolbar — 参考 Codex Mobile Dev:设备下拉 + 一排图标,没有别的 */
|
|
82
|
-
.panelbar { height: 42px; flex: none; display: flex; align-items: center; gap: 2px; padding: 0 10px; border-bottom: 1px solid var(--border); }
|
|
83
|
-
.device-select { height: 28px; border-radius: 8px; display: flex; align-items: center; gap: 7px; padding: 0 8px; font-size: 12.5px; color: var(--text); background: var(--bg-soft); max-width: 240px; }
|
|
84
|
-
.device-select .nm { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
85
|
-
.device-select .chev { color: var(--text-3); }
|
|
86
|
-
.panelbar .grow { flex: 1 1 auto; }
|
|
87
|
-
.panelbar .iconbtn { width: 28px; height: 28px; color: var(--text-2); }
|
|
88
|
-
.panelbar .iconbtn:hover { background: var(--bg-soft); }
|
|
89
|
-
|
|
90
|
-
.pane { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
|
|
91
|
-
|
|
92
|
-
/* device list — 只有型号 / serial / 状态,没有分辨率网速 */
|
|
93
|
-
.devlist { padding: 8px 10px 14px; overflow: hidden; }
|
|
94
|
-
.group { padding: 14px 10px 6px; font-size: 11px; letter-spacing: .4px; color: var(--text-3); display: flex; align-items: center; gap: 6px; }
|
|
95
|
-
.group::after { content: ""; flex: 1; height: 1px; background: var(--border); }
|
|
96
|
-
.dev { display: flex; align-items: center; gap: 12px; padding: 10px; border-radius: 10px; }
|
|
97
|
-
.dev:hover { background: var(--bg-soft); }
|
|
98
|
-
.dev.sel { background: var(--accent-soft); }
|
|
99
|
-
.dev .ph-icon { width: 32px; height: 32px; border-radius: 9px; background: #f1f1f3; display: grid; place-items: center; color: #4b4b52; flex: none; position: relative; }
|
|
100
|
-
.dev .ph-icon .on { position: absolute; right: -1px; bottom: -1px; width: 9px; height: 9px; border-radius: 50%; background: var(--green); border: 2px solid #fff; }
|
|
101
|
-
.dev .meta { min-width: 0; flex: 1; }
|
|
102
|
-
.dev .model { font-size: 13px; font-weight: 500; display: flex; align-items: center; gap: 7px; }
|
|
103
|
-
.dev .model .tag { font-size: 10px; font-weight: 500; color: var(--text-2); background: #fff; border: 1px solid var(--border); border-radius: 5px; padding: 1px 5px; }
|
|
104
|
-
.dev .serial { font: 11px ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--text-3); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
105
|
-
.dev .open { font-size: 12px; color: var(--text-3); }
|
|
106
|
-
.dev:hover .open { color: var(--accent); }
|
|
107
|
-
.list-foot { margin-top: auto; padding: 12px 18px 16px; display: flex; align-items: center; gap: 10px; font-size: 11.5px; color: var(--text-3); }
|
|
108
|
-
.dot-live { width: 6px; height: 6px; border-radius: 50%; background: var(--green); display: inline-block; }
|
|
109
|
-
|
|
110
|
-
/* mirror canvas — 灰底 + 设备机身,画面之外什么都不放 */
|
|
111
|
-
.stage { flex: 1 1 auto; min-height: 0; background: #f2f2f4; display: flex; align-items: center; justify-content: center; position: relative; padding: 18px; }
|
|
112
|
-
.bezels { position: relative; }
|
|
113
|
-
.phone { position: relative; width: 252px; height: 548px; border-radius: 42px; background: #0b0b0d; padding: 9px; box-shadow: inset 0 0 0 1.5px #2c2c31, inset 0 0 0 4px #050506, 0 24px 48px rgba(0,0,0,.24); }
|
|
114
|
-
.phone::after { content: ""; position: absolute; right: -3px; top: 132px; width: 3px; height: 54px; border-radius: 2px; background: #232327; }
|
|
115
|
-
.phone::before { content: ""; position: absolute; left: -3px; top: 118px; width: 3px; height: 84px; border-radius: 2px; background: #232327; }
|
|
116
|
-
.screen { position: relative; width: 100%; height: 100%; border-radius: 34px; overflow: hidden; background: linear-gradient(165deg, #dbe8ff 0%, #e9dcff 42%, #ffe4d2 100%); }
|
|
117
|
-
.status { position: absolute; top: 0; left: 0; right: 0; height: 30px; display: flex; align-items: center; justify-content: space-between; padding: 0 20px; font-size: 10px; font-weight: 600; color: #1d1d22; z-index: 3; }
|
|
118
|
-
.punch { position: absolute; top: 12px; left: 50%; transform: translateX(-50%); width: 12px; height: 12px; border-radius: 50%; background: #0b0b0d; z-index: 4; }
|
|
119
|
-
.apps { position: absolute; top: 70px; left: 20px; right: 20px; display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px 10px; }
|
|
120
|
-
.app { display: flex; flex-direction: column; align-items: center; gap: 6px; }
|
|
121
|
-
.app .ic { width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center; color: #fff; font-size: 14px; font-weight: 700; }
|
|
122
|
-
.app .nm { font-size: 8px; color: #26262b; }
|
|
123
|
-
.dock { position: absolute; left: 16px; right: 16px; bottom: 16px; height: 62px; border-radius: 24px; background: rgba(255,255,255,.55); display: flex; align-items: center; justify-content: space-around; }
|
|
124
|
-
.dock .ic { width: 40px; height: 40px; border-radius: 12px; }
|
|
125
|
-
|
|
126
|
-
.hidden { display: none !important; }
|
|
127
|
-
</style>
|
|
128
|
-
</head>
|
|
129
|
-
<body>
|
|
130
|
-
<div class="frame">
|
|
131
|
-
<!-- LEFT -->
|
|
132
|
-
<aside class="sidebar">
|
|
133
|
-
<div class="brand"><div class="logo">D</div><div class="name">DeepSeek Harness</div></div>
|
|
134
|
-
<div class="newchat">
|
|
135
|
-
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>
|
|
136
|
-
新会话 <kbd>⌘N</kbd>
|
|
137
|
-
</div>
|
|
138
|
-
<div class="search">
|
|
139
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
|
|
140
|
-
搜索会话
|
|
141
|
-
</div>
|
|
142
|
-
<div class="sec">今天</div>
|
|
143
|
-
<div class="sess active"><span class="dot"></span><span class="t">手机插件:右侧 adb 预览</span></div>
|
|
144
|
-
<div class="sess"><span class="dot"></span><span class="t">dsh 有手机插件嘛?adb 的?</span></div>
|
|
145
|
-
<div class="sess"><span class="dot"></span><span class="t">云手机 pony 的 adb 转发</span></div>
|
|
146
|
-
<div class="sec" style="margin-top:8px">昨天</div>
|
|
147
|
-
<div class="sess"><span class="dot"></span><span class="t">DeBox 日志导出脚本</span></div>
|
|
148
|
-
<div class="sess"><span class="dot"></span><span class="t">RRWL release 流程梳理</span></div>
|
|
149
|
-
<div class="side-foot">
|
|
150
|
-
<div class="foot-row">
|
|
151
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="6" rx="2"/><rect x="3" y="14" width="18" height="6" rx="2"/></svg>
|
|
152
|
-
模型用量 <span class="badge">¥117.03</span><span class="pill"></span>
|
|
153
|
-
</div>
|
|
154
|
-
<div class="foot-row">
|
|
155
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.7 1.7 0 0 0-1.87-.34 1.7 1.7 0 0 0-1 1.55V21a2 2 0 1 1-4 0v-.09a1.7 1.7 0 0 0-1.11-1.55 1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.7 1.7 0 0 0 .34-1.87 1.7 1.7 0 0 0-1.55-1H3a2 2 0 1 1 0-4h.09A1.7 1.7 0 0 0 4.64 9a1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.7 1.7 0 0 0 9 4.64h.09A1.7 1.7 0 0 0 10.64 3V3a2 2 0 1 1 4 0v.09a1.7 1.7 0 0 0 1 1.55 1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.7 1.7 0 0 0 19.36 9v.09a1.7 1.7 0 0 0 1.55 1H21a2 2 0 1 1 0 4h-.09a1.7 1.7 0 0 0-1.51 1Z"/></svg>
|
|
156
|
-
设置
|
|
157
|
-
</div>
|
|
158
|
-
</div>
|
|
159
|
-
</aside>
|
|
160
|
-
|
|
161
|
-
<!-- CENTER -->
|
|
162
|
-
<main class="main">
|
|
163
|
-
<header class="conv-head">
|
|
164
|
-
<div class="conv-title">手机插件:右侧 adb 预览</div>
|
|
165
|
-
<div class="conv-sub">· 3 个会话</div>
|
|
166
|
-
<div class="head-right">
|
|
167
|
-
<button class="iconbtn active" title="手机">
|
|
168
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><rect x="6.5" y="2.5" width="11" height="19" rx="2.6"/><path d="M10.5 18.6h3"/></svg>
|
|
169
|
-
<span class="badge">5</span>
|
|
170
|
-
</button>
|
|
171
|
-
<button class="iconbtn" title="更多">
|
|
172
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg>
|
|
173
|
-
</button>
|
|
174
|
-
<div class="hair"></div>
|
|
175
|
-
<button class="iconbtn" title="收起右侧栏">
|
|
176
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2.5"/><path d="M15 4v16M9 10l-2 2 2 2"/></svg>
|
|
177
|
-
</button>
|
|
178
|
-
</div>
|
|
179
|
-
</header>
|
|
180
|
-
|
|
181
|
-
<div class="transcript">
|
|
182
|
-
<div class="turn user">
|
|
183
|
-
<div class="bubble">手机插件就叫 dsh-phone,右侧只预览画面,别加分辨率网速,也不要输入框。</div>
|
|
184
|
-
</div>
|
|
185
|
-
<div class="turn">
|
|
186
|
-
<div class="assistant">
|
|
187
|
-
<p>明白,按 Codex Mobile Dev 那个右侧面板的样子收敛:顶上只有一行「设备下拉 + 几个图标按钮」,下面整块留给设备画面。</p>
|
|
188
|
-
<p class="muted">本机现在有 5 台在线:Pixel 7a(USB)、100.107.25.26、100.90.248.53、云手机 pony / test。</p>
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
</div>
|
|
192
|
-
|
|
193
|
-
<div class="composer-wrap">
|
|
194
|
-
<div class="composer">
|
|
195
|
-
<div class="ph">继续输入…</div>
|
|
196
|
-
<div class="bar">
|
|
197
|
-
<div class="chip">
|
|
198
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v18M3 12h18"/></svg>
|
|
199
|
-
附件
|
|
200
|
-
</div>
|
|
201
|
-
<div class="chip">DeepSeek V4.1</div>
|
|
202
|
-
<div class="send">
|
|
203
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5M6 11l6-6 6 6"/></svg>
|
|
204
|
-
</div>
|
|
205
|
-
</div>
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
208
|
-
</main>
|
|
209
|
-
|
|
210
|
-
<!-- RIGHT -->
|
|
211
|
-
<section class="rightbar">
|
|
212
|
-
<div class="tabstrip">
|
|
213
|
-
<div class="tab active">
|
|
214
|
-
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><rect x="6.5" y="2.5" width="11" height="19" rx="2.6"/><path d="M10.5 18.6h3"/></svg>
|
|
215
|
-
手机
|
|
216
|
-
<span class="x">
|
|
217
|
-
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="m6 6 12 12M18 6 6 18"/></svg>
|
|
218
|
-
</span>
|
|
219
|
-
</div>
|
|
220
|
-
<button class="iconbtn" style="width:26px;height:26px" title="新建 tab">
|
|
221
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>
|
|
222
|
-
</button>
|
|
223
|
-
<button class="iconbtn" style="width:26px;height:26px" title="分屏">
|
|
224
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><rect x="3" y="4" width="18" height="16" rx="2.5"/><path d="M12 4v16"/></svg>
|
|
225
|
-
</button>
|
|
226
|
-
<div class="strip-spacer"></div>
|
|
227
|
-
<div class="strip-right">
|
|
228
|
-
<button class="iconbtn" style="width:26px;height:26px" title="全屏">
|
|
229
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M8 21H5a2 2 0 0 1-2-2v-3M16 21h3a2 2 0 0 0 2-2v-3"/></svg>
|
|
230
|
-
</button>
|
|
231
|
-
<button class="iconbtn" style="width:26px;height:26px" title="收起">
|
|
232
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
|
|
233
|
-
</button>
|
|
234
|
-
</div>
|
|
235
|
-
</div>
|
|
236
|
-
|
|
237
|
-
<div class="pane">
|
|
238
|
-
<!-- toolbar: 设备下拉 + 图标,参考 Codex 一行搞定 -->
|
|
239
|
-
<div class="panelbar" id="bar-list">
|
|
240
|
-
<div class="device-select">
|
|
241
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><rect x="6.5" y="2.5" width="11" height="19" rx="2.6"/><path d="M10.5 18.6h3"/></svg>
|
|
242
|
-
<span class="nm">选择设备</span>
|
|
243
|
-
<svg class="chev" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
|
|
244
|
-
</div>
|
|
245
|
-
<div class="grow"></div>
|
|
246
|
-
<button class="iconbtn" title="刷新设备">
|
|
247
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><path d="M21 12a9 9 0 1 1-2.64-6.36M21 3v6h-6"/></svg>
|
|
248
|
-
</button>
|
|
249
|
-
<button class="iconbtn" title="连接设备">
|
|
250
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>
|
|
251
|
-
</button>
|
|
252
|
-
</div>
|
|
253
|
-
|
|
254
|
-
<div class="panelbar hidden" id="bar-device">
|
|
255
|
-
<div class="device-select">
|
|
256
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><rect x="6.5" y="2.5" width="11" height="19" rx="2.6"/><path d="M10.5 18.6h3"/></svg>
|
|
257
|
-
<span class="nm">Pixel 7a</span>
|
|
258
|
-
<svg class="chev" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
|
|
259
|
-
</div>
|
|
260
|
-
<div class="grow"></div>
|
|
261
|
-
<button class="iconbtn" title="返回">
|
|
262
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
|
|
263
|
-
</button>
|
|
264
|
-
<button class="iconbtn" title="主页">
|
|
265
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><circle cx="12" cy="12" r="7.5"/></svg>
|
|
266
|
-
</button>
|
|
267
|
-
<button class="iconbtn" title="多任务">
|
|
268
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><rect x="5.5" y="5.5" width="13" height="13" rx="2"/></svg>
|
|
269
|
-
</button>
|
|
270
|
-
<button class="iconbtn" title="截图">
|
|
271
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="6" width="18" height="14" rx="2.5"/><circle cx="12" cy="13" r="3.4"/><path d="M8 6l1.4-2h5.2L16 6"/></svg>
|
|
272
|
-
</button>
|
|
273
|
-
<button class="iconbtn" title="更多">
|
|
274
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></svg>
|
|
275
|
-
</button>
|
|
276
|
-
</div>
|
|
277
|
-
|
|
278
|
-
<!-- LIST -->
|
|
279
|
-
<div id="view-list" style="display:flex;flex-direction:column;height:100%;min-height:0">
|
|
280
|
-
<div class="devlist">
|
|
281
|
-
<div class="group">已连接</div>
|
|
282
|
-
<div class="dev sel">
|
|
283
|
-
<div class="ph-icon">
|
|
284
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><rect x="6.5" y="2.5" width="11" height="19" rx="2.6"/><path d="M10.5 18.6h3"/></svg>
|
|
285
|
-
<span class="on"></span>
|
|
286
|
-
</div>
|
|
287
|
-
<div class="meta">
|
|
288
|
-
<div class="model">Pixel 7a <span class="tag">USB</span></div>
|
|
289
|
-
<div class="serial">34201JEHN07681</div>
|
|
290
|
-
</div>
|
|
291
|
-
<div class="open">›</div>
|
|
292
|
-
</div>
|
|
293
|
-
<div class="dev">
|
|
294
|
-
<div class="ph-icon">
|
|
295
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><rect x="6.5" y="2.5" width="11" height="19" rx="2.6"/><path d="M10.5 18.6h3"/></svg>
|
|
296
|
-
<span class="on"></span>
|
|
297
|
-
</div>
|
|
298
|
-
<div class="meta">
|
|
299
|
-
<div class="model">Pixel 7a <span class="tag">Wi-Fi</span></div>
|
|
300
|
-
<div class="serial">100.107.25.26:5555</div>
|
|
301
|
-
</div>
|
|
302
|
-
<div class="open">›</div>
|
|
303
|
-
</div>
|
|
304
|
-
<div class="dev">
|
|
305
|
-
<div class="ph-icon">
|
|
306
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><rect x="6.5" y="2.5" width="11" height="19" rx="2.6"/><path d="M10.5 18.6h3"/></svg>
|
|
307
|
-
<span class="on"></span>
|
|
308
|
-
</div>
|
|
309
|
-
<div class="meta">
|
|
310
|
-
<div class="model">小米 13 <span class="tag">thor</span></div>
|
|
311
|
-
<div class="serial">100.90.248.53:5555</div>
|
|
312
|
-
</div>
|
|
313
|
-
<div class="open">›</div>
|
|
314
|
-
</div>
|
|
315
|
-
<div class="group">云手机</div>
|
|
316
|
-
<div class="dev">
|
|
317
|
-
<div class="ph-icon">
|
|
318
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M7 18a4 4 0 0 1 .6-7.95A5.5 5.5 0 0 1 18.2 11 3.5 3.5 0 0 1 17.5 18Z"/></svg>
|
|
319
|
-
<span class="on"></span>
|
|
320
|
-
</div>
|
|
321
|
-
<div class="meta">
|
|
322
|
-
<div class="model">云手机 pony</div>
|
|
323
|
-
<div class="serial">unicom-openclaw-pony:5555</div>
|
|
324
|
-
</div>
|
|
325
|
-
<div class="open">›</div>
|
|
326
|
-
</div>
|
|
327
|
-
<div class="dev">
|
|
328
|
-
<div class="ph-icon">
|
|
329
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M7 18a4 4 0 0 1 .6-7.95A5.5 5.5 0 0 1 18.2 11 3.5 3.5 0 0 1 17.5 18Z"/></svg>
|
|
330
|
-
<span class="on"></span>
|
|
331
|
-
</div>
|
|
332
|
-
<div class="meta">
|
|
333
|
-
<div class="model">云手机 test</div>
|
|
334
|
-
<div class="serial">unicom-openclaw-test:5555</div>
|
|
335
|
-
</div>
|
|
336
|
-
<div class="open">›</div>
|
|
337
|
-
</div>
|
|
338
|
-
</div>
|
|
339
|
-
<div class="list-foot"><span class="dot-live"></span>5 台在线</div>
|
|
340
|
-
</div>
|
|
341
|
-
|
|
342
|
-
<!-- DEVICE -->
|
|
343
|
-
<div id="view-device" class="hidden" style="display:flex;flex-direction:column;height:100%;min-height:0">
|
|
344
|
-
<div class="stage">
|
|
345
|
-
<div class="bezels">
|
|
346
|
-
<div class="phone">
|
|
347
|
-
<div class="screen">
|
|
348
|
-
<div class="status"><span>9:41</span><span>5G ▮▮▮ 86%</span></div>
|
|
349
|
-
<div class="punch"></div>
|
|
350
|
-
<div class="apps">
|
|
351
|
-
<div class="app"><div class="ic" style="background:#34a853">拨</div><div class="nm">电话</div></div>
|
|
352
|
-
<div class="app"><div class="ic" style="background:#4285f4">消</div><div class="nm">信息</div></div>
|
|
353
|
-
<div class="app"><div class="ic" style="background:#ea4335">邮</div><div class="nm">Gmail</div></div>
|
|
354
|
-
<div class="app"><div class="ic" style="background:#1a73e8">地</div><div class="nm">地图</div></div>
|
|
355
|
-
<div class="app"><div class="ic" style="background:#0f9d58">相</div><div class="nm">相册</div></div>
|
|
356
|
-
<div class="app"><div class="ic" style="background:#fbbc04">设</div><div class="nm">设置</div></div>
|
|
357
|
-
<div class="app"><div class="ic" style="background:#673ab7">De</div><div class="nm">DeBox</div></div>
|
|
358
|
-
<div class="app"><div class="ic" style="background:#00bcd4">dsh</div><div class="nm">dsh</div></div>
|
|
359
|
-
</div>
|
|
360
|
-
<div class="dock">
|
|
361
|
-
<div class="ic" style="background:#34a853"></div>
|
|
362
|
-
<div class="ic" style="background:#4285f4"></div>
|
|
363
|
-
<div class="ic" style="background:#ea4335"></div>
|
|
364
|
-
<div class="ic" style="background:#fbbc04"></div>
|
|
365
|
-
</div>
|
|
366
|
-
</div>
|
|
367
|
-
</div>
|
|
368
|
-
</div>
|
|
369
|
-
</div>
|
|
370
|
-
</div>
|
|
371
|
-
</div>
|
|
372
|
-
</section>
|
|
373
|
-
</div>
|
|
374
|
-
|
|
375
|
-
<script>
|
|
376
|
-
if (location.hash === '#device') {
|
|
377
|
-
document.getElementById('view-list').classList.add('hidden');
|
|
378
|
-
document.getElementById('view-device').classList.remove('hidden');
|
|
379
|
-
document.getElementById('bar-list').classList.add('hidden');
|
|
380
|
-
document.getElementById('bar-device').classList.remove('hidden');
|
|
381
|
-
}
|
|
382
|
-
</script>
|
|
383
|
-
</body>
|
|
384
|
-
</html>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|