dsh-apk-vm 0.6.0 → 0.6.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/CHANGELOG.md +26 -0
- package/README.md +21 -1
- package/client.js +72 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.2
|
|
4
|
+
|
|
5
|
+
Sidebar usability.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Expand / restore button** in the VM panel header. It turns the panel into a
|
|
10
|
+
full-window overlay so a narrow `dsh-better-sidebar` tab can show a large phone screen;
|
|
11
|
+
press Escape or click the button again to restore the sidebar layout.
|
|
12
|
+
- The overlay hides the APK/debug aside, keeps the H.264 stream and input controls, and
|
|
13
|
+
reuses the existing canvas/WebSocket session (no second stream).
|
|
14
|
+
|
|
15
|
+
## 0.6.1
|
|
16
|
+
|
|
17
|
+
Network panel completeness.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Network card pcap path input and apply action (capture starts on the next emulator start).
|
|
22
|
+
- `setTcpdumpPath` API/tool wiring for per-instance pcap configuration.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Clearing the hosts list now unmounts the `/etc/hosts` bind mount instead of leaving the last
|
|
27
|
+
entries active until reboot.
|
|
28
|
+
|
|
3
29
|
## 0.6.0
|
|
4
30
|
|
|
5
31
|
Network controls.
|
package/README.md
CHANGED
|
@@ -8,6 +8,26 @@
|
|
|
8
8
|
|
|
9
9
|
**Run a real Android APK inside a local, rooted, open-source Android VM and preview it in the DeepSeek Harness sidebar — streamed as H.264/WebCodecs video at up to 60 fps.**
|
|
10
10
|
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="docs/screenshot-panel.png">
|
|
13
|
+
<img src="docs/screenshot-panel.png" alt="APK Virtual Machine panel with a live H.264 Android screen in DeepSeek Harness" width="820">
|
|
14
|
+
</a>
|
|
15
|
+
</p>
|
|
16
|
+
<p align="center"><sub>Real panel capture: rooted Android 14 guest, live H.264 stream, input controls and installed-app tools — no mockup.</sub></p>
|
|
17
|
+
<p align="center">
|
|
18
|
+
<code>dsh plugin --profile web add dsh-apk-vm</code>
|
|
19
|
+
·
|
|
20
|
+
<a href="examples/README.md">reproducible demo</a>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
**Why it is useful**
|
|
24
|
+
|
|
25
|
+
- **Sidebar-first Android** — install and use an APK without opening a separate emulator window.
|
|
26
|
+
- **Real, low-latency stream** — H.264/WebCodecs up to 60 fps with tap, swipe, key and text input.
|
|
27
|
+
- **Agent-native** — `apk_vm` tools, a live UI Inspector, copy-ready selectors and record/replay flows.
|
|
28
|
+
- **Local and inspectable** — open-source AOSP emulator, rooted debug API, network/storage controls, no telemetry.
|
|
29
|
+
- **Expandable viewer** — turn the narrow sidebar panel into a full-window VM with one click; Escape restores it.
|
|
30
|
+
|
|
11
31
|
This repository contains the static DSH plugin `dsh-apk-vm`. It adds a first-class sidebar
|
|
12
32
|
entry ("APK Virtual Machine" / "APK 虚拟机") whose main panel boots an AOSP Android Emulator,
|
|
13
33
|
installs an APK into the guest, streams the guest display into the panel, and forwards
|
|
@@ -24,7 +44,7 @@ same-origin, loopback-only debug HTTP API and an `apk_vm` Agent tool.
|
|
|
24
44
|
> 当前 **Windows x64 与 Linux x86_64(headless)为端到端已验证平台**;macOS 仅做探测/CI
|
|
25
45
|
> 覆盖,详见 `docs/LINUX-E2E.md`。
|
|
26
46
|
|
|
27
|
-
**Status:** `0.6.
|
|
47
|
+
**Status:** `0.6.2` · Apache-2.0 · no npm runtime dependencies · no build step.
|
|
28
48
|
|
|
29
49
|
This is an open-source DSH plugin published on npm as **`dsh-apk-vm`**. Repository:
|
|
30
50
|
[github.com/yuxiaole-bili/dsh-apk-vm](https://github.com/yuxiaole-bili/dsh-apk-vm).
|
package/client.js
CHANGED
|
@@ -103,6 +103,8 @@ window.__ModuleLoader__.load({
|
|
|
103
103
|
'action.installLaunch': 'Install & launch',
|
|
104
104
|
'action.dismiss': 'Dismiss',
|
|
105
105
|
'action.openSidebar': 'Open in side bar',
|
|
106
|
+
'action.expand': 'Expand',
|
|
107
|
+
'action.restore': 'Exit expanded',
|
|
106
108
|
'a11y.refresh': 'Refresh VM state',
|
|
107
109
|
'a11y.retry': 'Retry the request',
|
|
108
110
|
'a11y.openSidebar': 'Open the APK VM in dsh-better-sidebar',
|
|
@@ -205,6 +207,7 @@ window.__ModuleLoader__.load({
|
|
|
205
207
|
'network.testOk': 'Connectivity OK',
|
|
206
208
|
'network.testFail': 'Connectivity check failed',
|
|
207
209
|
'network.pcap': 'Download pcap',
|
|
210
|
+
'network.pcapPath': 'Host pcap path (.pcap, next start)',
|
|
208
211
|
'network.restartRequired': 'Restart the VM to apply custom DNS',
|
|
209
212
|
'network.sharedHint': 'Shared forwards are reachable from other VMs through the emulator host alias.',
|
|
210
213
|
'network.guestUrl': 'Guest URL: {url}',
|
|
@@ -335,6 +338,8 @@ window.__ModuleLoader__.load({
|
|
|
335
338
|
'action.installLaunch': '安装并启动',
|
|
336
339
|
'action.dismiss': '关闭',
|
|
337
340
|
'action.openSidebar': '在侧栏打开',
|
|
341
|
+
'action.expand': '放大',
|
|
342
|
+
'action.restore': '退出放大',
|
|
338
343
|
'a11y.refresh': '刷新虚拟机状态',
|
|
339
344
|
'a11y.retry': '重试请求',
|
|
340
345
|
'a11y.openSidebar': '在 dsh-better-sidebar 中打开 APK 虚拟机',
|
|
@@ -437,6 +442,7 @@ window.__ModuleLoader__.load({
|
|
|
437
442
|
'network.testOk': '连通性正常',
|
|
438
443
|
'network.testFail': '连通性检查失败',
|
|
439
444
|
'network.pcap': '下载 pcap',
|
|
445
|
+
'network.pcapPath': '宿主机 pcap 路径(.pcap,下次启动)',
|
|
440
446
|
'network.restartRequired': '重启虚拟机后生效自定义 DNS',
|
|
441
447
|
'network.sharedHint': '共享转发可被其他 VM 通过模拟器宿主别名访问。',
|
|
442
448
|
'network.guestUrl': 'Guest URL: {url}',
|
|
@@ -1062,7 +1068,17 @@ window.__ModuleLoader__.load({
|
|
|
1062
1068
|
'.apkvm-embedded .apkvm-phonewrap{flex:0 0 auto;min-height:320px}' +
|
|
1063
1069
|
'.apkvm-embedded .apkvm-controls{max-width:none}' +
|
|
1064
1070
|
'@media (max-width:900px){.apkvm-header{flex-wrap:wrap}}' +
|
|
1065
|
-
'.apkvm-embedded .apkvm-header{flex-wrap:wrap}'
|
|
1071
|
+
'.apkvm-embedded .apkvm-header{flex-wrap:wrap}' +
|
|
1072
|
+
'.apkvm-page.apkvm-maximized{position:fixed;inset:0;z-index:2147483000;width:100vw;height:100vh;background:var(--dsw-alias-bg-base);box-shadow:0 0 0 1px var(--dsw-alias-border-l2)}' +
|
|
1073
|
+
'.apkvm-page.apkvm-maximized .apkvm-header{flex:0 0 auto;background:var(--dsw-alias-bg-base)}' +
|
|
1074
|
+
'.apkvm-page.apkvm-maximized .apkvm-body{flex:1 1 auto;min-height:0;padding:10px 16px 14px;gap:10px;overflow:hidden;flex-direction:row}' +
|
|
1075
|
+
'.apkvm-page.apkvm-maximized .apkvm-aside{display:none}' +
|
|
1076
|
+
'.apkvm-page.apkvm-maximized .apkvm-stage{flex:1 1 auto;min-height:0;max-height:none}' +
|
|
1077
|
+
'.apkvm-page.apkvm-maximized .apkvm-phonewrap{flex:1 1 auto;min-height:0;padding:0}' +
|
|
1078
|
+
'.apkvm-page.apkvm-maximized .apkvm-phone{height:100%;max-height:calc(100vh - 150px);padding:6px;border-radius:22px}' +
|
|
1079
|
+
'.apkvm-page.apkvm-maximized .apkvm-screen{height:100%;max-height:calc(100vh - 170px);border-radius:16px}' +
|
|
1080
|
+
'.apkvm-page.apkvm-maximized .apkvm-controls{max-width:760px}' +
|
|
1081
|
+
'.apkvm-page.apkvm-maximized .apkvm-stagehint{max-width:760px}';
|
|
1066
1082
|
|
|
1067
1083
|
/* ------------------------------------------------------------------ *
|
|
1068
1084
|
* Render-error boundary: an unexpected exception inside the panel keeps
|
|
@@ -1318,6 +1334,9 @@ window.__ModuleLoader__.load({
|
|
|
1318
1334
|
var hostsTextState = React.useState('');
|
|
1319
1335
|
var hostsText = hostsTextState[0];
|
|
1320
1336
|
var setHostsText = hostsTextState[1];
|
|
1337
|
+
var pcapPathState = React.useState('');
|
|
1338
|
+
var pcapPath = pcapPathState[0];
|
|
1339
|
+
var setPcapPath = pcapPathState[1];
|
|
1321
1340
|
|
|
1322
1341
|
var actualFpsState = React.useState(0);
|
|
1323
1342
|
var actualFps = actualFpsState[0];
|
|
@@ -1368,6 +1387,10 @@ window.__ModuleLoader__.load({
|
|
|
1368
1387
|
var canvasRef = React.useRef(null);
|
|
1369
1388
|
var decoderRef = React.useRef(null);
|
|
1370
1389
|
var wsRef = React.useRef(null);
|
|
1390
|
+
var pageRef = React.useRef(null);
|
|
1391
|
+
var maximizedState = React.useState(false);
|
|
1392
|
+
var isMaximized = maximizedState[0];
|
|
1393
|
+
var setIsMaximized = maximizedState[1];
|
|
1371
1394
|
|
|
1372
1395
|
/* ---- derived snapshot values --------------------------------- */
|
|
1373
1396
|
var vmInfo = snapshot ? snapshot.vm : null;
|
|
@@ -1523,6 +1546,21 @@ window.__ModuleLoader__.load({
|
|
|
1523
1546
|
if (ready) loadNetwork();
|
|
1524
1547
|
}, [ready, activeInstance]);
|
|
1525
1548
|
|
|
1549
|
+
// Escape leaves the expanded VM view; the expand button is inside the
|
|
1550
|
+
// full-screen overlay too, so mouse-only users are never trapped.
|
|
1551
|
+
React.useEffect(function () {
|
|
1552
|
+
if (!isMaximized || typeof document === 'undefined' || typeof document.addEventListener !== 'function') {
|
|
1553
|
+
return undefined;
|
|
1554
|
+
}
|
|
1555
|
+
function onKeyDown(event) {
|
|
1556
|
+
if (event && event.key === 'Escape') setIsMaximized(false);
|
|
1557
|
+
}
|
|
1558
|
+
document.addEventListener('keydown', onKeyDown);
|
|
1559
|
+
return function () {
|
|
1560
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
1561
|
+
};
|
|
1562
|
+
}, [isMaximized]);
|
|
1563
|
+
|
|
1526
1564
|
// Track document visibility so the frame stream pauses when hidden.
|
|
1527
1565
|
React.useEffect(function () {
|
|
1528
1566
|
if (typeof document === 'undefined' || typeof document.addEventListener !== 'function') {
|
|
@@ -4216,6 +4254,18 @@ window.__ModuleLoader__.load({
|
|
|
4216
4254
|
},
|
|
4217
4255
|
'aria-label': T('network.hosts'),
|
|
4218
4256
|
}),
|
|
4257
|
+
h('input', {
|
|
4258
|
+
className: 'apkvm-input',
|
|
4259
|
+
type: 'text',
|
|
4260
|
+
value: pcapPath,
|
|
4261
|
+
placeholder: T('network.pcapPath'),
|
|
4262
|
+
spellCheck: false,
|
|
4263
|
+
disabled: Boolean(networkBusy),
|
|
4264
|
+
onChange: function (event) {
|
|
4265
|
+
setPcapPath(event && event.target ? String(event.target.value) : '');
|
|
4266
|
+
},
|
|
4267
|
+
'aria-label': T('network.pcapPath'),
|
|
4268
|
+
}),
|
|
4219
4269
|
h(
|
|
4220
4270
|
'div',
|
|
4221
4271
|
{ style: { display: 'flex', alignItems: 'center', gap: '5px', flexWrap: 'wrap' } },
|
|
@@ -4225,6 +4275,14 @@ window.__ModuleLoader__.load({
|
|
|
4225
4275
|
disabled: !ready || Boolean(networkBusy),
|
|
4226
4276
|
onClick: applyHosts,
|
|
4227
4277
|
}),
|
|
4278
|
+
actionBtn('pcap', {
|
|
4279
|
+
key: 'pcap-apply',
|
|
4280
|
+
small: true,
|
|
4281
|
+
disabled: !ready || Boolean(networkBusy) || !pcapPath.trim(),
|
|
4282
|
+
onClick: function () {
|
|
4283
|
+
networkAction('pcap', { path: pcapPath.trim() });
|
|
4284
|
+
},
|
|
4285
|
+
}),
|
|
4228
4286
|
network && network.pcap && network.pcap.enabled
|
|
4229
4287
|
? h(
|
|
4230
4288
|
'a',
|
|
@@ -4491,6 +4549,17 @@ window.__ModuleLoader__.load({
|
|
|
4491
4549
|
(videoMode === 'h264' ? T('fps.mode.h264') : T('fps.mode.png'))
|
|
4492
4550
|
),
|
|
4493
4551
|
primaryAction,
|
|
4552
|
+
actionBtn(T(isMaximized ? 'action.restore' : 'action.expand'), {
|
|
4553
|
+
key: 'maximize',
|
|
4554
|
+
small: true,
|
|
4555
|
+
icon: h('span', { 'aria-hidden': 'true', style: { fontSize: '14px', lineHeight: '14px' } }, isMaximized ? '⛜' : '⛶'),
|
|
4556
|
+
ariaLabel: T(isMaximized ? 'action.restore' : 'action.expand'),
|
|
4557
|
+
onClick: function () {
|
|
4558
|
+
setIsMaximized(function (value) {
|
|
4559
|
+
return !value;
|
|
4560
|
+
});
|
|
4561
|
+
},
|
|
4562
|
+
}),
|
|
4494
4563
|
!embedded && sidebarService
|
|
4495
4564
|
? actionBtn(T('action.openSidebar'), {
|
|
4496
4565
|
key: 'open-sidebar',
|
|
@@ -4672,7 +4741,8 @@ window.__ModuleLoader__.load({
|
|
|
4672
4741
|
return h(
|
|
4673
4742
|
'div',
|
|
4674
4743
|
{
|
|
4675
|
-
className: 'apkvm-page' + (embedded ? ' apkvm-embedded' : ''),
|
|
4744
|
+
className: 'apkvm-page' + (embedded ? ' apkvm-embedded' : '') + (isMaximized ? ' apkvm-maximized' : ''),
|
|
4745
|
+
ref: pageRef,
|
|
4676
4746
|
onDragEnter: onPageDragEnter,
|
|
4677
4747
|
onDragOver: onPageDragOver,
|
|
4678
4748
|
onDragLeave: onPageDragLeave,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-apk-vm",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local AOSP Android VM for DeepSeek Harness: preview rooted APKs at up to 60 fps, automate UIAutomator flows, and give each Agent an isolated VM.",
|
|
6
6
|
"license": "Apache-2.0",
|