@windypro-rourou/dsh-logcat 0.7.0 → 0.8.0
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/README.en.md +1 -0
- package/README.md +3 -0
- package/lib/client.js +169 -30
- package/package.json +2 -4
package/README.en.md
CHANGED
|
@@ -37,6 +37,7 @@ An Android real-device debugging workbench for the DSH Web GUI (an Android Studi
|
|
|
37
37
|
- Reverse engineering / memory: `proc_list` (process list), `proc_maps` (memory maps + module bases), `proc_status` (process state / memory summary), `proc_smaps` (top Pss regions), `mem_dump` (read memory at an address as hex), `mem_search` (search memory for hex/string patterns), `frida_server` (deploy/start/stop frida-server), `frida_script` (generate hook/trace/scan/bypass/dump script templates)
|
|
38
38
|
- **Real-device debugging workflow**: while building an Android app, the agent's announcement dynamically lists connected devices (serial + model) and the current test package. After confirming with the user: `adb_install` to deploy → `adb_exec`/`app_launch` to start → `logcat_set_package` to lock → `logcat_recent` / `logcat_crash` for crashes → `ui_dump` + `input_*` for UI automation → `adb_pull` for artifacts. A closed loop on real hardware.
|
|
39
39
|
- **Reverse-engineering workflow**: `proc_list` to find the process → `proc_maps` for module bases → `mem_dump` at a target address / `mem_search` for signatures → `proc_smaps` for memory detail → `frida_script` + `frida_server` for dynamic instrumentation. Reading another app's memory/maps needs root or a debuggable app (run-as); tools return clear errors otherwise.
|
|
40
|
+
- **Native integration (DSH 0.2)**: mounts through the official slots as a central `main` panel with its own `sidebar.panellist` row — the same docking, theming and layout behaviour as built-in panels; `dsh.client.inject` is aligned with the 0.2 manifest. Older clients (0.1.x) fall back to the right-hand drawer automatically, so one install serves both. Icons are a bundled SVG set (no icon dependency) and colours prefer the host's `--dsw-*` design tokens.
|
|
40
41
|
- **Extra routes**: `POST /api/dsh-logcat/exec` (shell), `POST /api/dsh-logcat/package` (set package), `GET /api/dsh-logcat/screenshot`, `POST /api/dsh-logcat/install-adb`, `GET /api/dsh-logcat/history`, `GET /api/dsh-logcat/crashes` + `GET /api/dsh-logcat/crash-file`, `POST /api/dsh-logcat/install-apk`, `POST /api/dsh-logcat/keyevent`, `POST /api/dsh-logcat/wifi-connect` / `wifi-disconnect`, `GET /api/dsh-logcat/processes`, `POST /api/dsh-logcat/mem-search`, `GET /api/dsh-logcat/mem-dump`.
|
|
41
42
|
- **On-disk data**: logs are written per device per day to `~/.dsh/logcat/logs/<serial>/logcat-MM-DD.log` (raw threadtime lines); crash snapshots live in `~/.dsh/logcat/crashes/<serial>/<timestamp>-<FATAL|ANR>/` (`screenshot.png` + `crash.log` + `meta.json`).
|
|
42
43
|
|
package/README.md
CHANGED
|
@@ -59,6 +59,9 @@ DSH Web GUI 的安卓实机调试面板(类似 Android Studio 的 Logcat 视
|
|
|
59
59
|
- **逆向工作流**:`proc_list` 定位进程 → `proc_maps` 拿模块基址 → `mem_dump` 读目标地址 / `mem_search` 搜特征模式 →
|
|
60
60
|
`proc_smaps` 看内存占用明细 → `frida_script` 生成脚本 + `frida_server` 起 frida 做动态插桩。
|
|
61
61
|
读其他应用内存/maps 需要 root 或 debuggable 应用(run-as),工具会给出明确提示。
|
|
62
|
+
- **原生集成(DSH 0.2)**:通过官方槽位挂载为「中央面板 + 左侧栏 Logcat 图标」(`main` + `sidebar.panellist`),
|
|
63
|
+
与内置面板同样的停靠/主题/布局体验;`dsh.client.inject` 已按 0.2 清单修正。旧版客户端(0.1.x)自动退回右侧抽屉,
|
|
64
|
+
一份安装通用。图标为内置 SVG 图标集(不依赖图标包),配色优先取宿主 `--dsw-*` 设计 token。
|
|
62
65
|
- **附加能力**:`POST /api/dsh-logcat/exec` 执行 shell、`POST /api/dsh-logcat/package` 设置包名、
|
|
63
66
|
`GET /api/dsh-logcat/screenshot` 截屏、`POST /api/dsh-logcat/install-adb` 一键装 adb、
|
|
64
67
|
`GET /api/dsh-logcat/history` 历史回溯、`GET /api/dsh-logcat/crashes` + `GET /api/dsh-logcat/crash-file` 崩溃快照读取、
|
package/lib/client.js
CHANGED
|
@@ -238,6 +238,53 @@ window.__ModuleLoader__.load({
|
|
|
238
238
|
.lc-pausedbar .lc-btn { padding: 2px 10px; }
|
|
239
239
|
.lc-fab { position: absolute; right: 14px; bottom: 14px; z-index: 4; border: 1px solid rgba(66,133,244,.45); background: light-dark(rgba(66,133,244,.14), rgba(66,133,244,.24)); color: #4285f4; font: inherit; font-size: 12px; font-weight: 700; padding: 8px 14px; border-radius: 999px; cursor: pointer; box-shadow: 0 4px 14px rgba(0,0,0,.22); }
|
|
240
240
|
.lc-fab:hover { background: light-dark(rgba(66,133,244,.22), rgba(66,133,244,.34)); }
|
|
241
|
+
/* ---- design pass: token layer + refinements ------------------------------
|
|
242
|
+
Colours resolve from the DSH design tokens when the host exposes them and
|
|
243
|
+
fall back to a hand-tuned pair otherwise, so the panel reads native on 0.2
|
|
244
|
+
and still looks right on older clients. One accent (blue); severity hues are
|
|
245
|
+
data encoding, not decoration. */
|
|
246
|
+
.lc-panel {
|
|
247
|
+
--lc-accent: var(--dsw-alias-brand-primary, #3b82f6);
|
|
248
|
+
--lc-accent-soft: light-dark(rgba(59,130,246,.10), rgba(59,130,246,.20));
|
|
249
|
+
--lc-raised: var(--dsw-alias-bg-layer-2, light-dark(#f7f8fa, #1c1f26));
|
|
250
|
+
--lc-fg: var(--dsw-alias-label-primary, light-dark(#16181d, #e6e9ef));
|
|
251
|
+
--lc-fg-2: var(--dsw-alias-label-secondary, light-dark(#4a4f57, #a8afba));
|
|
252
|
+
--lc-dim: var(--dsw-alias-label-caption, light-dark(#6b7078, #8b93a0));
|
|
253
|
+
--lc-border: var(--dsw-alias-border-l2, light-dark(rgba(0,0,0,.10), rgba(255,255,255,.12)));
|
|
254
|
+
--lc-border-strong: var(--dsw-alias-border-l4, light-dark(rgba(0,0,0,.20), rgba(255,255,255,.22)));
|
|
255
|
+
--lc-hover: var(--dsw-alias-interactive-bg-hover, light-dark(rgba(0,0,0,.045), rgba(255,255,255,.07)));
|
|
256
|
+
background: var(--dsw-alias-bg-layer-1, light-dark(#ffffff, #16181d));
|
|
257
|
+
color: var(--lc-fg);
|
|
258
|
+
font-family: system-ui, -apple-system, "Segoe UI Variable Text", "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
259
|
+
}
|
|
260
|
+
.lc-header { padding: 11px 14px; gap: 9px; border-bottom-color: var(--lc-border); }
|
|
261
|
+
.lc-title { font-size: 14px; font-weight: 650; letter-spacing: -.01em; }
|
|
262
|
+
.lc-tabs { gap: 2px; padding: 0 10px; border-bottom-color: var(--lc-border); }
|
|
263
|
+
.lc-tab { font-size: 13px; font-weight: 500; padding: 8px 12px; border-radius: 0; box-shadow: none; opacity: .62; transition: opacity .12s ease, color .12s ease; }
|
|
264
|
+
.lc-tab:hover { opacity: .95; background: transparent; }
|
|
265
|
+
.lc-tab[data-on] { opacity: 1; background: transparent; color: var(--lc-accent); box-shadow: inset 0 -2px 0 var(--lc-accent); }
|
|
266
|
+
.lc-chip { background: light-dark(rgba(0,0,0,.035), rgba(255,255,255,.05)); font-variant-numeric: tabular-nums; }
|
|
267
|
+
.lc-icobtn { width: 28px; height: 26px; border-radius: 8px; border-color: transparent; color: var(--lc-fg-2); transition: background .12s ease, color .12s ease, border-color .12s ease; }
|
|
268
|
+
.lc-icobtn:hover { background: var(--lc-hover); color: var(--lc-fg); }
|
|
269
|
+
.lc-icobtn[data-on] { background: var(--lc-accent-soft); border-color: var(--lc-accent); color: var(--lc-accent); }
|
|
270
|
+
.lc-btn { border-color: transparent; border-radius: 8px; color: var(--lc-fg-2); transition: background .12s ease, color .12s ease; }
|
|
271
|
+
.lc-btn:hover { background: var(--lc-hover); color: var(--lc-fg); }
|
|
272
|
+
.lc-btn[data-on] { background: var(--lc-accent-soft); color: var(--lc-accent); }
|
|
273
|
+
.lc-icobtn:focus-visible, .lc-btn:focus-visible, .lc-key:focus-visible, .lc-tab:focus-visible { outline: 2px solid var(--lc-accent); outline-offset: 1px; }
|
|
274
|
+
.lc-key { display: inline-flex; align-items: center; gap: 4px; border-radius: 7px; border-color: transparent; background: light-dark(rgba(0,0,0,.035), rgba(255,255,255,.05)); }
|
|
275
|
+
.lc-key:hover { background: var(--lc-hover); }
|
|
276
|
+
.lc-search, .lc-select { border-radius: 8px; border-color: var(--lc-border); background: var(--lc-raised); transition: border-color .12s ease, background .12s ease; }
|
|
277
|
+
.lc-search:focus, .lc-select:focus { border-color: var(--lc-accent); background: light-dark(#ffffff, #16181d); outline: none; }
|
|
278
|
+
.lc-line { font-family: ui-monospace, "Cascadia Mono", "JetBrains Mono", Consolas, monospace; font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
279
|
+
.lc-line:hover { background: var(--lc-hover); }
|
|
280
|
+
.lc-log.compact .lc-line { font-size: 11px; }
|
|
281
|
+
.lc-status { background: light-dark(rgba(0,0,0,.015), rgba(255,255,255,.022)); font-variant-numeric: tabular-nums; border-top-color: var(--lc-border); }
|
|
282
|
+
.lc-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; color: var(--lc-dim); font-size: 12.5px; text-align: center; }
|
|
283
|
+
.lc-empty-icon { color: light-dark(rgba(0,0,0,.20), rgba(255,255,255,.22)); }
|
|
284
|
+
.lc-overlay { background: light-dark(rgba(15,23,42,.28), rgba(0,0,0,.48)); backdrop-filter: blur(2px); }
|
|
285
|
+
.lc-modal { border-radius: 14px; border-color: var(--lc-border-strong); box-shadow: 0 24px 64px light-dark(rgba(15,23,42,.20), rgba(0,0,0,.55)); }
|
|
286
|
+
.lc-hit { background: var(--lc-accent-soft); }
|
|
287
|
+
.lc-re-head { text-transform: none; letter-spacing: 0; }
|
|
241
288
|
`;
|
|
242
289
|
//#endregion
|
|
243
290
|
|
|
@@ -275,7 +322,7 @@ window.__ModuleLoader__.load({
|
|
|
275
322
|
const VIDEO_TIERS = { "360x800": 1200000, "540x1200": 2500000, "720x1600": 4000000, "1080x2400": 6000000 };
|
|
276
323
|
|
|
277
324
|
/** The Logcat panel view. */
|
|
278
|
-
function LogcatPanel({ controller }) {
|
|
325
|
+
function LogcatPanel({ controller, embedded }) {
|
|
279
326
|
const [adbPath, setAdbPath] = useState("");
|
|
280
327
|
const [adbReady, setAdbReady] = useState(false);
|
|
281
328
|
const [devices, setDevices] = useState([]);
|
|
@@ -1341,7 +1388,7 @@ window.__ModuleLoader__.load({
|
|
|
1341
1388
|
: devices.map((d) =>
|
|
1342
1389
|
h("option", { key: d.serial, value: d.serial },
|
|
1343
1390
|
(d.model !== "" ? d.model + " · " : "") + d.serial + " [" + d.state + "]"))),
|
|
1344
|
-
h("button", { type: "button", className: "lc-close", onClick: () => controller.close(), title: "关闭面板" }, "
|
|
1391
|
+
embedded === true ? null : h("button", { type: "button", className: "lc-close", onClick: () => controller.close(), title: "关闭面板" }, h(Icon, { name: "close", size: 14 })),
|
|
1345
1392
|
),
|
|
1346
1393
|
h("div", { className: "lc-stats" },
|
|
1347
1394
|
stats?.model
|
|
@@ -1420,11 +1467,11 @@ window.__ModuleLoader__.load({
|
|
|
1420
1467
|
style: { maxWidth: 180 },
|
|
1421
1468
|
}),
|
|
1422
1469
|
h("button", { type: "button", className: "lc-btn", onClick: () => setPackageFilter(pkgInput), title: "按包名过滤日志(agent 侧 logcat_recent 同步生效)" }, "包名"),
|
|
1423
|
-
h("button", { type: "button", className: "lc-icobtn", onClick: takeScreenshot, title: "截取真机屏幕并下载 PNG" }, "
|
|
1424
|
-
h("button", { type: "button", className: "lc-icobtn", onClick: loadHistory, title: "从磁盘加载历史日志(重启不丢)" }, "
|
|
1425
|
-
h("button", { type: "button", className: "lc-icobtn", "data-on": eventsOn ? "" : undefined, onClick: toggleEvents, title: "logcat -b events 事件缓冲(应用启动/崩溃/生命周期)" }, "
|
|
1426
|
-
h("button", { type: "button", className: "lc-icobtn", onClick: openCrashModal, title: "崩溃自动快照历史(截图+上下文)" }, "
|
|
1427
|
-
h("button", { type: "button", className: "lc-icobtn", onClick: () => setWifiOpen(true), title: "WiFi 无线调试(adb pair + connect)" }, "
|
|
1470
|
+
h("button", { type: "button", className: "lc-icobtn", onClick: takeScreenshot, title: "截取真机屏幕并下载 PNG" }, h(Icon, { name: "camera" })),
|
|
1471
|
+
h("button", { type: "button", className: "lc-icobtn", onClick: loadHistory, title: "从磁盘加载历史日志(重启不丢)" }, h(Icon, { name: "history" })),
|
|
1472
|
+
h("button", { type: "button", className: "lc-icobtn", "data-on": eventsOn ? "" : undefined, onClick: toggleEvents, title: "logcat -b events 事件缓冲(应用启动/崩溃/生命周期)" }, h(Icon, { name: "events" })),
|
|
1473
|
+
h("button", { type: "button", className: "lc-icobtn", onClick: openCrashModal, title: "崩溃自动快照历史(截图+上下文)" }, h(Icon, { name: "crash" })),
|
|
1474
|
+
h("button", { type: "button", className: "lc-icobtn", onClick: () => setWifiOpen(true), title: "WiFi 无线调试(adb pair + connect)" }, h(Icon, { name: "wifi" })),
|
|
1428
1475
|
h("input", {
|
|
1429
1476
|
ref: fileInputRef,
|
|
1430
1477
|
type: "file",
|
|
@@ -1432,25 +1479,25 @@ window.__ModuleLoader__.load({
|
|
|
1432
1479
|
style: { display: "none" },
|
|
1433
1480
|
onChange: (e) => { installApkFile(e.target.files?.[0] ?? null); e.target.value = ""; },
|
|
1434
1481
|
}),
|
|
1435
|
-
h("button", { type: "button", className: "lc-icobtn", onClick: () => fileInputRef.current?.click(), title: "选择本地 APK 安装到当前设备", disabled: installingApk ? true : undefined }, "
|
|
1482
|
+
h("button", { type: "button", className: "lc-icobtn", onClick: () => fileInputRef.current?.click(), title: "选择本地 APK 安装到当前设备", disabled: installingApk ? true : undefined }, h(Icon, { name: "package" })),
|
|
1436
1483
|
h("button", { type: "button", className: "lc-icobtn", "data-on": paused ? "" : undefined, onClick: togglePause, title: "暂停冻结视图(新日志缓冲) / 继续回放" },
|
|
1437
|
-
paused ? "
|
|
1438
|
-
h("button", { type: "button", className: "lc-icobtn", onClick: clearLog, title: "清空当前视图" }, "
|
|
1439
|
-
h("button", { type: "button", className: "lc-icobtn", onClick: copyLog, title: "复制过滤后日志" }, "
|
|
1440
|
-
h("button", { type: "button", className: "lc-icobtn", onClick: exportLog, title: "导出过滤后日志 .txt" }, "
|
|
1484
|
+
paused ? h(Icon, { name: "play" }) : h(Icon, { name: "pause" })),
|
|
1485
|
+
h("button", { type: "button", className: "lc-icobtn", onClick: clearLog, title: "清空当前视图" }, h(Icon, { name: "trash" })),
|
|
1486
|
+
h("button", { type: "button", className: "lc-icobtn", onClick: copyLog, title: "复制过滤后日志" }, h(Icon, { name: "copy" })),
|
|
1487
|
+
h("button", { type: "button", className: "lc-icobtn", onClick: exportLog, title: "导出过滤后日志 .txt" }, h(Icon, { name: "download" })),
|
|
1441
1488
|
h("button", {
|
|
1442
1489
|
type: "button",
|
|
1443
1490
|
className: "lc-icobtn",
|
|
1444
1491
|
"data-on": autoScroll ? "" : undefined,
|
|
1445
1492
|
title: "新日志自动滚到底部",
|
|
1446
1493
|
onClick: () => setAutoScroll(!autoScroll),
|
|
1447
|
-
}, "
|
|
1494
|
+
}, h(Icon, { name: "pin" })),
|
|
1448
1495
|
)
|
|
1449
1496
|
: null,
|
|
1450
1497
|
tab === "log" || tab === "screen"
|
|
1451
1498
|
? h("div", { className: "lc-keys" },
|
|
1452
|
-
[["home", "
|
|
1453
|
-
h("button", { key, type: "button", className: "lc-key", title: "发送按键 " + key, onClick: () => sendKey(key) }, label)))
|
|
1499
|
+
[["home", "home", "Home"], ["back", "back", "返回"], ["recents", "grid", "最近"], ["wakeup", "sun", "唤醒"], ["power", "power", "电源"], ["volume_up", "volumeUp", ""], ["volume_down", "volumeDown", ""]].map(([key, icon, label]) =>
|
|
1500
|
+
h("button", { key, type: "button", className: "lc-key", title: "发送按键 " + key, onClick: () => sendKey(key) }, h(Icon, { name: icon, size: 14 }), label)))
|
|
1454
1501
|
: null,
|
|
1455
1502
|
lastCrash !== null
|
|
1456
1503
|
? h("div", { className: "lc-crashbar" },
|
|
@@ -1469,7 +1516,7 @@ window.__ModuleLoader__.load({
|
|
|
1469
1516
|
: null,
|
|
1470
1517
|
paused && tab === "log" && !eventsOn
|
|
1471
1518
|
? h("div", { className: "lc-pausedbar" },
|
|
1472
|
-
h("span", { style: { flex: 1 } }, "
|
|
1519
|
+
h("span", { style: { flex: 1 } }, h(Icon, { name: "pause", size: 14 }), "已暂停 — 新日志缓冲中(" + pendingCount + " 条),列表已冻结"),
|
|
1473
1520
|
h("button", { type: "button", className: "lc-btn", onClick: togglePause, title: "恢复实时,回放缓冲日志" }, "继续"),
|
|
1474
1521
|
)
|
|
1475
1522
|
: null,
|
|
@@ -1551,11 +1598,11 @@ window.__ModuleLoader__.load({
|
|
|
1551
1598
|
h("input", { placeholder: "输入文字后回车发送…", value: screenText, onChange: (e) => setScreenText(e.target.value), onKeyDown: (e) => { if (e.key === "Enter") sendScreenText(); }, style: { flex: 1, minWidth: 120 } }),
|
|
1552
1599
|
h("button", { type: "button", className: "lc-btn", onClick: () => sendKey("wakeup"), title: "唤醒设备屏幕(等效按电源键点亮)" }, "唤醒"),
|
|
1553
1600
|
h("button", { type: "button", className: "lc-btn", onClick: downloadScreen, title: "下载当前帧 PNG" }, "下载"),
|
|
1554
|
-
h("button", { type: "button", className: "lc-btn", onClick: toggleScreenFullscreen, title: "全屏查看(Esc 退出)" }, "
|
|
1601
|
+
h("button", { type: "button", className: "lc-btn", onClick: toggleScreenFullscreen, title: "全屏查看(Esc 退出)" }, h(Icon, { name: "expand", size: 14 })),
|
|
1555
1602
|
),
|
|
1556
1603
|
hostVideoCapable === false
|
|
1557
1604
|
? h("div", { className: "lc-empty" },
|
|
1558
|
-
h("span", { className: "lc-empty-icon" }, "
|
|
1605
|
+
h("span", { className: "lc-empty-icon" }, h(Icon, { name: "crash", size: 26 })),
|
|
1559
1606
|
h("span", null, "运行中的 GUI 宿主版本过旧 —— 重启 GUI(dsh web)后刷新,投屏才会生效"))
|
|
1560
1607
|
: h("div", {
|
|
1561
1608
|
className: "lc-screen-img",
|
|
@@ -1575,7 +1622,7 @@ window.__ModuleLoader__.load({
|
|
|
1575
1622
|
ripples.map((r) => h("span", { key: r.id, className: "lc-tap-ripple", style: { left: r.x, top: r.y } })),
|
|
1576
1623
|
!screenHasFrame
|
|
1577
1624
|
? h("div", { className: "lc-empty" },
|
|
1578
|
-
h("span", { className: "lc-empty-icon" }, "
|
|
1625
|
+
h("span", { className: "lc-empty-icon" }, h(Icon, { name: "plug", size: 26 })),
|
|
1579
1626
|
h("span", null, devices.length === 0 ? "连接设备并开启 USB 调试后自动开始投屏" : "正在建立投屏流…"))
|
|
1580
1627
|
: null,
|
|
1581
1628
|
),
|
|
@@ -1586,11 +1633,11 @@ window.__ModuleLoader__.load({
|
|
|
1586
1633
|
: h(VirtualLog, { entries: filteredEvents, scrollTop, onScrollTop: setScrollTop, bodyRef, keyword, onTagClick: addKeyword, onLineDblClick: copyLine, rowDensity }))
|
|
1587
1634
|
: (filtered.length === 0
|
|
1588
1635
|
? h("div", { className: "lc-empty" },
|
|
1589
|
-
h("span", { className: "lc-empty-icon" }, paused ? "
|
|
1636
|
+
h("span", { className: "lc-empty-icon" }, h(Icon, { name: paused ? "pause" : devices.length === 0 ? "plug" : "file", size: 26 })),
|
|
1590
1637
|
h("span", null, paused ? "已暂停(" + entries.length + " 条已缓冲)" : (devices.length === 0 ? "连接设备并开启 USB 调试后自动开始…" : "暂无日志")))
|
|
1591
1638
|
: h(VirtualLog, { entries: filtered, scrollTop, onScrollTop: setScrollTop, bodyRef, keyword, onTagClick: addKeyword, onLineDblClick: copyLine, rowDensity })),
|
|
1592
1639
|
tab === "log" && !eventsOn && live && !paused
|
|
1593
|
-
? h("button", { type: "button", className: "lc-fab", onClick: togglePause, title: "暂停:冻结当前视图(新日志进入缓冲)" }, "
|
|
1640
|
+
? h("button", { type: "button", className: "lc-fab", onClick: togglePause, title: "暂停:冻结当前视图(新日志进入缓冲)" }, h(Icon, { name: "pause", size: 14 }), "暂停")
|
|
1594
1641
|
: null,
|
|
1595
1642
|
),
|
|
1596
1643
|
crashModal
|
|
@@ -1606,7 +1653,7 @@ window.__ModuleLoader__.load({
|
|
|
1606
1653
|
? h("span", null, "暂无崩溃快照")
|
|
1607
1654
|
: crashList.map((c) =>
|
|
1608
1655
|
h("div", { key: c.id, className: "lc-crash-item", onClick: () => toggleCrashDetail(c.id) },
|
|
1609
|
-
h("div", { className: "t" }, c.ts + " [" + c.kind + "]"
|
|
1656
|
+
h("div", { className: "t" }, c.ts + " [" + c.kind + "]", c.hasScreenshot ? h(Icon, { name: "camera", size: 12 }) : null),
|
|
1610
1657
|
h("div", { className: "s" }, c.summary),
|
|
1611
1658
|
crashOpenId === c.id && crashDetail !== null
|
|
1612
1659
|
? h("div", { className: "lc-crash-detail" },
|
|
@@ -1983,6 +2030,91 @@ window.__ModuleLoader__.load({
|
|
|
1983
2030
|
//#endregion
|
|
1984
2031
|
|
|
1985
2032
|
//#region entry
|
|
2033
|
+
//#region icons
|
|
2034
|
+
/**
|
|
2035
|
+
* Minimal geometric glyphs on one 24px grid, stroked with currentColor —
|
|
2036
|
+
* deliberately dependency-free (the client bundle may only require shell
|
|
2037
|
+
* seeds), so the panel ships its own small, consistent icon set instead of
|
|
2038
|
+
* emoji or an external icon package.
|
|
2039
|
+
*/
|
|
2040
|
+
const ICONS = {
|
|
2041
|
+
camera: '<path d="M4 8.8h3.2L8.7 6.4h6.6l1.5 2.4H20v9.2H4z"/><circle cx="12" cy="13" r="3.1"/>',
|
|
2042
|
+
history: '<path d="M4.4 12a7.6 7.6 0 1 1 2.2 5.4"/><path d="M4 4.6v3.2h3.2"/><path d="M12 8.4V12l2.6 1.8"/>',
|
|
2043
|
+
events: '<path d="M4 6.5h16M4 12h16M4 17.5h10"/>',
|
|
2044
|
+
crash: '<path d="M12 4 20.5 19H3.5z"/><path d="M12 9.5v4.2"/><path d="M12 16.6h.01"/>',
|
|
2045
|
+
wifi: '<path d="M4 9.2a12 12 0 0 1 16 0"/><path d="M7 12.6a8 8 0 0 1 10 0"/><path d="M10 16a4 4 0 0 1 4 0"/><path d="M12 19h.01"/>',
|
|
2046
|
+
package: '<path d="M12 3.4 20 7.8v8.4L12 20.6 4 16.2V7.8z"/><path d="M4 7.8 12 12l8-4.2"/><path d="M12 12v8.6"/>',
|
|
2047
|
+
pause: '<path d="M9.2 6v12M14.8 6v12"/>',
|
|
2048
|
+
play: '<path d="M8.4 5.4 18.6 12 8.4 18.6z"/>',
|
|
2049
|
+
trash: '<path d="M5 7h14"/><path d="M10 7V5h4v2"/><path d="M6.6 7l1 13h8.8l1-13"/><path d="M10.5 10.5v6M13.5 10.5v6"/>',
|
|
2050
|
+
copy: '<rect x="9" y="9" width="11" height="11" rx="2"/><path d="M15 5.5H5.5V15"/>',
|
|
2051
|
+
download: '<path d="M12 4v11"/><path d="M7.6 10.8 12 15.2l4.4-4.4"/><path d="M5 19h14"/>',
|
|
2052
|
+
pin: '<path d="M12 3.6c2.9 0 4.8 2 4.8 4.4 0 2.9-2.4 4.8-3.4 7.6h-2.8C9.6 12.8 7.2 10.9 7.2 8 7.2 5.6 9.1 3.6 12 3.6z"/><path d="M12 15.6V20.4"/>',
|
|
2053
|
+
expand: '<path d="M9.5 4.5H4.5v5"/><path d="M14.5 4.5h5v5"/><path d="M14.5 19.5h5v-5"/><path d="M9.5 19.5h-5v-5"/>',
|
|
2054
|
+
home: '<path d="M4.5 11 12 4.6 19.5 11v8.4h-15z"/><path d="M10 19.4v-4.6h4v4.6"/>',
|
|
2055
|
+
back: '<path d="M14.6 5.6 8.2 12l6.4 6.4"/>',
|
|
2056
|
+
grid: '<rect x="4.5" y="4.5" width="6" height="6" rx="1.4"/><rect x="13.5" y="4.5" width="6" height="6" rx="1.4"/><rect x="4.5" y="13.5" width="6" height="6" rx="1.4"/><rect x="13.5" y="13.5" width="6" height="6" rx="1.4"/>',
|
|
2057
|
+
sun: '<circle cx="12" cy="12" r="4"/><path d="M12 3.4v2.2M12 18.4v2.2M4.6 12H6.8M17.2 12h2.2M6.8 6.8l1.6 1.6M15.6 15.6l1.6 1.6M17.2 6.8l-1.6 1.6M8.4 15.6l-1.6 1.6"/>',
|
|
2058
|
+
power: '<path d="M12 4v8"/><path d="M7.6 7.2a7 7 0 1 0 8.8 0"/>',
|
|
2059
|
+
volumeUp: '<path d="M4.5 9.8h3l4-3v10.4l-4-3h-3z"/><path d="M15 9.6a3.6 3.6 0 0 1 0 4.8"/><path d="M17.6 7.6a6.8 6.8 0 0 1 0 8.8"/>',
|
|
2060
|
+
volumeDown: '<path d="M4.5 9.8h3l4-3v10.4l-4-3h-3z"/><path d="M15 10.4a3 3 0 0 1 0 3.2"/>',
|
|
2061
|
+
plug: '<path d="M9.5 3.6v4.8M14.5 3.6v4.8"/><path d="M6.4 8.4h11.2v2.6a5.6 5.6 0 0 1-11.2 0z"/><path d="M12 16.6v3.8"/>',
|
|
2062
|
+
file: '<path d="M6.6 3.6h7.2l4 4v12.8H6.6z"/><path d="M13.8 3.6v4h4"/><path d="M9.4 12.4h5.2M9.4 15.6h5.2"/>',
|
|
2063
|
+
close: '<path d="M6.4 6.4 17.6 17.6M17.6 6.4 6.4 17.6"/>',
|
|
2064
|
+
search: '<circle cx="11" cy="11" r="5.6"/><path d="M15.2 15.2 19.4 19.4"/>',
|
|
2065
|
+
};
|
|
2066
|
+
|
|
2067
|
+
/** One glyph from the set above, at the requested square edge. */
|
|
2068
|
+
function Icon({ name, size = 16 }) {
|
|
2069
|
+
const body = ICONS[name];
|
|
2070
|
+
if (body === undefined) return null;
|
|
2071
|
+
return h("svg", {
|
|
2072
|
+
width: size,
|
|
2073
|
+
height: size,
|
|
2074
|
+
viewBox: "0 0 24 24",
|
|
2075
|
+
fill: "none",
|
|
2076
|
+
stroke: "currentColor",
|
|
2077
|
+
strokeWidth: 1.7,
|
|
2078
|
+
strokeLinecap: "round",
|
|
2079
|
+
strokeLinejoin: "round",
|
|
2080
|
+
"aria-hidden": true,
|
|
2081
|
+
dangerouslySetInnerHTML: { __html: body },
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
//#endregion
|
|
2085
|
+
|
|
2086
|
+
//#region dsh 0.2 native integration
|
|
2087
|
+
/** The glyph the left sidebar shows for this panel. */
|
|
2088
|
+
function SidebarLogcatIcon({ size = 16 }) {
|
|
2089
|
+
return h(Icon, { name: "file", size: size ?? 16 });
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
/**
|
|
2093
|
+
* Register the panel as a first-class 0.2 surface: the central `main` panel
|
|
2094
|
+
* (selected by the sidebar entry id) plus its `sidebar.panellist` row.
|
|
2095
|
+
* @returns whether the host accepted the native seats synchronously — when
|
|
2096
|
+
* it did, the legacy drawer is not mounted at all.
|
|
2097
|
+
*/
|
|
2098
|
+
function registerNative(ctx, makePanel) {
|
|
2099
|
+
if (ctx?.slots === undefined || typeof ctx.slots.inject !== "function") return false;
|
|
2100
|
+
let active = false;
|
|
2101
|
+
try {
|
|
2102
|
+
ctx.slots.inject("main", () => {
|
|
2103
|
+
active = true;
|
|
2104
|
+
return ctx.slots.register({ name: "main", key: "logcat" }, makePanel);
|
|
2105
|
+
});
|
|
2106
|
+
ctx.slots.inject("sidebar.panellist", () => ctx.slots.register(
|
|
2107
|
+
{ name: "sidebar.panellist", id: "logcat", order: 100, label: "Logcat" },
|
|
2108
|
+
SidebarLogcatIcon,
|
|
2109
|
+
));
|
|
2110
|
+
} catch (error) {
|
|
2111
|
+
console.warn("[dsh-logcat] native slot registration unavailable:", error);
|
|
2112
|
+
return false;
|
|
2113
|
+
}
|
|
2114
|
+
return active;
|
|
2115
|
+
}
|
|
2116
|
+
//#endregion
|
|
2117
|
+
|
|
1986
2118
|
/** Required services (fiber inject waiting — the runtime must be up first). */
|
|
1987
2119
|
const inject = ["slots"];
|
|
1988
2120
|
|
|
@@ -2003,16 +2135,23 @@ window.__ModuleLoader__.load({
|
|
|
2003
2135
|
styleGuard.observe(document.head, { childList: true });
|
|
2004
2136
|
|
|
2005
2137
|
const controller = new PanelController();
|
|
2006
|
-
const
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2138
|
+
const legacy = [];
|
|
2139
|
+
const mountLegacy = () => {
|
|
2140
|
+
try {
|
|
2141
|
+
legacy.push(mountSidebarEntry(controller));
|
|
2142
|
+
legacy.push(mountPanel(controller));
|
|
2143
|
+
} catch (error) {
|
|
2144
|
+
console.warn("[dsh-logcat] legacy mount failed:", error);
|
|
2145
|
+
}
|
|
2146
|
+
};
|
|
2147
|
+
// dsh 0.2 exposes `main` + `sidebar.panellist`: the panel becomes a native
|
|
2148
|
+
// central surface with its own sidebar row. Clients that do not offer those
|
|
2149
|
+
// seats keep the self-healing sidebar entry and the right-hand drawer.
|
|
2150
|
+
const native = registerNative(ctx, () => h(LogcatPanel, { controller, embedded: true }));
|
|
2151
|
+
if (!native) mountLegacy();
|
|
2013
2152
|
ctx.effect(() => () => {
|
|
2014
2153
|
styleGuard.disconnect();
|
|
2015
|
-
for (const dispose of
|
|
2154
|
+
for (const dispose of legacy.splice(0)) dispose();
|
|
2016
2155
|
style.remove();
|
|
2017
2156
|
}, "dsh-logcat: ui mounts");
|
|
2018
2157
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windypro-rourou/dsh-logcat",
|
|
3
3
|
"description": "Android Logcat viewer for the dsh web GUI: auto-connects to any adb device in debug mode, live logcat stream with level/keyword filters, pause/clear/export, plus agent tools (logcat_recent). Hot-pluggable — mounted via ~/.dsh/cordis.patch.yml + a profile node_modules copy, no dsh source changes.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@11.22.0",
|
|
7
7
|
"engines": {
|
|
@@ -22,9 +22,7 @@
|
|
|
22
22
|
"patch": "./cordis.patch.yml"
|
|
23
23
|
},
|
|
24
24
|
"client": {
|
|
25
|
-
"inject": [
|
|
26
|
-
"@deepseek-ai/dsh-client-runtime"
|
|
27
|
-
],
|
|
25
|
+
"inject": [],
|
|
28
26
|
"platform": "web"
|
|
29
27
|
}
|
|
30
28
|
},
|