dsh-mobilecode 0.11.1 → 0.11.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/README.md +14 -10
- package/lib/client.js +55 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -133,22 +133,26 @@ card is **Device 1**; co-op docks a second, **Device 2**. It is produced
|
|
|
133
133
|
stage, and a ☰ **device menu** adds notifications / quick settings / collapse /
|
|
134
134
|
lock / wake / assistant.
|
|
135
135
|
- The header **device picker** groups online devices (🖥 emulator / 📱 physical,
|
|
136
|
-
streaming badge)
|
|
136
|
+
streaming badge) — each online emulator row carries a **⏻ off** power button
|
|
137
|
+
(`POST /off` → `adb emu kill`; physical devices are refused) — and lists
|
|
138
|
+
*stopped* configured AVDs with a one-click **⏻ boot** button
|
|
137
139
|
(`POST /boot`, the merged device-start action from the retired Start-server
|
|
138
140
|
flow: validate → spawn detached → adopt if already running → wait boot
|
|
139
|
-
complete → the card streams it immediately); a
|
|
140
|
-
|
|
141
|
-
sizes (Fit / 100% / S·240 / M·320 presets + a select)
|
|
142
|
-
(none / bezel / device) reshape the stage
|
|
141
|
+
complete → the card streams it immediately); a running AVD appears only as
|
|
142
|
+
its online row, never a duplicate boot entry. A **Live** badge shows the
|
|
143
|
+
streamed serial. Quick sizes (Fit / 100% / S·240 / M·320 presets + a select)
|
|
144
|
+
and frame styles (none / bezel / device) reshape the stage — each stream pane
|
|
145
|
+
carries its own copy of these controls (0.11.2).
|
|
143
146
|
- **Screenshot** captures a still via `POST /stream/still` (a real `screencap`,
|
|
144
147
|
embedded as a data URL up to 4 MB) and flips the stage to a still view with a
|
|
145
148
|
"back to Live" link.
|
|
146
149
|
- **Co-op split view (⧉, v0.9.0)**: with two or more online devices the card
|
|
147
|
-
header grows a ⧉ toggle that docks a
|
|
148
|
-
its own device select,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
150
|
+
header grows a ⧉ toggle that docks a second **Device 2** pane beside it —
|
|
151
|
+
its own device select, live `<img>`, tap/drag control, and (since 0.11.2) its
|
|
152
|
+
own Size/Frame row mirroring Device 1's; both panes default to Fit and share
|
|
153
|
+
one CSS height cap, so the pair starts identical and can be styled
|
|
154
|
+
independently. Each pane grants its own HMAC capability; closing one pane
|
|
155
|
+
never stalls or disturbs the other stream.
|
|
152
156
|
- **Security**: every stream route sits behind a loopback + trusted-browser
|
|
153
157
|
transport fence (peer address, loopback `Host`, `Sec-Fetch-Site` / `Origin` —
|
|
154
158
|
so a LAN client cannot spoof localhost and a DNS-rebinding `Host` is rejected),
|
package/lib/client.js
CHANGED
|
@@ -118,14 +118,14 @@ window.__ModuleLoader__.load({
|
|
|
118
118
|
.mc-live-bar select { background: light-dark(#fff, #20242b); color: inherit; border: 1px solid light-dark(rgba(0,0,0,.18), rgba(255,255,255,.18)); border-radius: 6px; padding: 3px 6px; font: inherit; font-size: 12px; max-width: 200px; }
|
|
119
119
|
.mc-live-stage { position: relative; align-self: center; background: #000; border-radius: 10px; overflow: hidden; line-height: 0; box-shadow: inset 0 0 0 1px light-dark(rgba(0,0,0,.12), rgba(255,255,255,.14)); }
|
|
120
120
|
.mc-live-stage img { display: block; max-width: 100%; max-height: 60vh; width: auto; height: auto; cursor: crosshair; touch-action: none; user-select: none; }
|
|
121
|
-
.mc-live-stage .mc-live-off { display: flex; align-items: center; justify-content: center; width: 260px; height: 460px; color: #9aa0a8; font-size: 13px; line-height: 1.5; text-align: center; }
|
|
121
|
+
.mc-live-stage .mc-live-off { display: flex; align-items: center; justify-content: center; width: 260px; max-width: 100%; height: 460px; color: #9aa0a8; font-size: 13px; line-height: 1.5; text-align: center; }
|
|
122
122
|
/* 0.9.1 co-op: two forced columns — side-by-side MUST survive a narrow drawer. */
|
|
123
123
|
.mc-live-section { display: flex; flex-direction: column; gap: 10px; }
|
|
124
124
|
.mc-live-section.coop { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; align-items: start; }
|
|
125
125
|
.mc-live-section.coop > .mc-card { min-width: 0; }
|
|
126
126
|
.mc-live-section.coop .mc-card-head { flex-wrap: wrap; }
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
/* No coop-specific height cap: both panes must share Device 1's 60vh rule so
|
|
128
|
+
they match at every size, Fit included (0.11.2). */
|
|
129
129
|
.mc-coop-select { width: auto; padding: 3px 6px; font-size: 12px; }
|
|
130
130
|
.mc-live-nav { display: flex; align-items: center; justify-content: center; gap: 8px; }
|
|
131
131
|
.mc-live-nav button { border: 1px solid light-dark(rgba(0,0,0,.16), rgba(255,255,255,.18)); background: light-dark(#fff, #20242b); color: inherit; border-radius: 999px; width: 40px; height: 32px; cursor: pointer; font-size: 14px; }
|
|
@@ -196,6 +196,42 @@ window.__ModuleLoader__.load({
|
|
|
196
196
|
? { width: m.value + "px", maxWidth: "100%", maxHeight: "60vh" }
|
|
197
197
|
: {});
|
|
198
198
|
const stageClassFor = (f) => "mc-live-stage" + (f !== "none" ? " " + f : "");
|
|
199
|
+
const SIZE_PRESETS = [
|
|
200
|
+
{ label: "Fit", mode: { mode: "fit" } },
|
|
201
|
+
{ label: "100%", mode: { mode: "pct", value: 100 } },
|
|
202
|
+
{ label: "S · 240", mode: { mode: "px", value: 240 } },
|
|
203
|
+
{ label: "M · 320", mode: { mode: "px", value: 320 } },
|
|
204
|
+
];
|
|
205
|
+
/** Size presets + px/% select + frame seg — shared by both stream panes
|
|
206
|
+
* (0.11.2) so Device 2 gets the same controls as Device 1. */
|
|
207
|
+
function StageControls({ sizeMode, setSizeMode, frame, setFrame }) {
|
|
208
|
+
const isCurrent = (m) => m.mode === sizeMode.mode && (m.value === undefined || m.value === sizeMode.value);
|
|
209
|
+
return h("div", { className: "mc-live-size" },
|
|
210
|
+
h("span", { className: "mc-hint" }, "Size"),
|
|
211
|
+
h("div", { className: "mc-seg" }, SIZE_PRESETS.map((p) => h("button", { key: p.label, "data-on": isCurrent(p.mode) || undefined, onClick: () => setSizeMode(p.mode) }, p.label))),
|
|
212
|
+
h("select", { className: "mc-input mc-live-sizesel", value: sizeMode.mode === "pct" ? sizeMode.value + "%" : sizeMode.mode === "px" ? String(sizeMode.value) : "fit", onChange: (e) => {
|
|
213
|
+
const v = e.target.value;
|
|
214
|
+
if (v === "fit") setSizeMode({ mode: "fit" });
|
|
215
|
+
else if (v.endsWith("%")) setSizeMode({ mode: "pct", value: Number(v.slice(0, -1)) });
|
|
216
|
+
else setSizeMode({ mode: "px", value: Number(v) });
|
|
217
|
+
} },
|
|
218
|
+
h("option", { value: "fit" }, "fit"),
|
|
219
|
+
h("option", { value: "50%" }, "50%"),
|
|
220
|
+
h("option", { value: "75%" }, "75%"),
|
|
221
|
+
h("option", { value: "100%" }, "100%"),
|
|
222
|
+
h("option", { value: "125%" }, "125%"),
|
|
223
|
+
h("option", { value: "240" }, "S · 240px"),
|
|
224
|
+
h("option", { value: "320" }, "M · 320px"),
|
|
225
|
+
h("option", { value: "420" }, "L · 420px"),
|
|
226
|
+
),
|
|
227
|
+
h("span", { className: "mc-hint" }, "Frame"),
|
|
228
|
+
h("div", { className: "mc-seg" },
|
|
229
|
+
h("button", { "data-on": frame === "none" || undefined, onClick: () => setFrame("none") }, "none"),
|
|
230
|
+
h("button", { "data-on": frame === "bezel" || undefined, onClick: () => setFrame("bezel") }, "bezel"),
|
|
231
|
+
h("button", { "data-on": frame === "device" || undefined, onClick: () => setFrame("device") }, "device"),
|
|
232
|
+
),
|
|
233
|
+
);
|
|
234
|
+
}
|
|
199
235
|
|
|
200
236
|
function PanelController() {
|
|
201
237
|
let panelOpen = false;
|
|
@@ -585,7 +621,9 @@ window.__ModuleLoader__.load({
|
|
|
585
621
|
|
|
586
622
|
/** Device 1 stream: picker-popover header, SVG toolbar pill, device
|
|
587
623
|
* menu, quick sizes + frame styles, and tap/drag with duration. */
|
|
588
|
-
function LiveDeviceCard({ coopOn, onToggleCoop, onSerial
|
|
624
|
+
function LiveDeviceCard({ coopOn, onToggleCoop, onSerial }) {
|
|
625
|
+
const [sizeMode, setSizeMode] = useState({ mode: "fit" }); // {mode:'fit'|'pct'|'px', value?}
|
|
626
|
+
const [frame, setFrame] = useState("none"); // 'none' | 'bezel' | 'device'
|
|
589
627
|
const [devices, setDevices] = useState([]);
|
|
590
628
|
const [avds, setAvds] = useState([]);
|
|
591
629
|
const [serial, setSerial] = useState("");
|
|
@@ -699,13 +737,6 @@ window.__ModuleLoader__.load({
|
|
|
699
737
|
{ action: "wake", label: "Wake" },
|
|
700
738
|
{ action: "assistant", label: "Assistant" },
|
|
701
739
|
];
|
|
702
|
-
const SIZE_PRESETS = [
|
|
703
|
-
{ label: "Fit", mode: { mode: "fit" } },
|
|
704
|
-
{ label: "100%", mode: { mode: "pct", value: 100 } },
|
|
705
|
-
{ label: "S · 240", mode: { mode: "px", value: 240 } },
|
|
706
|
-
{ label: "M · 320", mode: { mode: "px", value: 320 } },
|
|
707
|
-
];
|
|
708
|
-
const isCurrent = (m) => m.mode === sizeMode.mode && (m.value === undefined || m.value === sizeMode.value);
|
|
709
740
|
|
|
710
741
|
return h("div", { className: "mc-card mc-live" },
|
|
711
742
|
h("div", { className: "mc-card-head" },
|
|
@@ -781,39 +812,19 @@ window.__ModuleLoader__.load({
|
|
|
781
812
|
? h("div", { className: "mc-live-cap" }, "Still captured" + (still?.width ? " (" + still.width + "x" + still.height + ")" : "") + " · ",
|
|
782
813
|
h("a", { className: "mc-log-toggle", onClick: showLive }, "back to Live"))
|
|
783
814
|
: h("div", { className: "mc-live-cap" }, "tap or drag on the screen to drive the device"),
|
|
784
|
-
h(
|
|
785
|
-
h("span", { className: "mc-hint" }, "Size"),
|
|
786
|
-
h("div", { className: "mc-seg" }, SIZE_PRESETS.map((p) => h("button", { key: p.label, "data-on": isCurrent(p.mode) || undefined, onClick: () => setSizeMode(p.mode) }, p.label))),
|
|
787
|
-
h("select", { className: "mc-input mc-live-sizesel", value: sizeMode.mode === "pct" ? sizeMode.value + "%" : sizeMode.mode === "px" ? String(sizeMode.value) : "fit", onChange: (e) => {
|
|
788
|
-
const v = e.target.value;
|
|
789
|
-
if (v === "fit") setSizeMode({ mode: "fit" });
|
|
790
|
-
else if (v.endsWith("%")) setSizeMode({ mode: "pct", value: Number(v.slice(0, -1)) });
|
|
791
|
-
else setSizeMode({ mode: "px", value: Number(v) });
|
|
792
|
-
} },
|
|
793
|
-
h("option", { value: "fit" }, "fit"),
|
|
794
|
-
h("option", { value: "50%" }, "50%"),
|
|
795
|
-
h("option", { value: "75%" }, "75%"),
|
|
796
|
-
h("option", { value: "100%" }, "100%"),
|
|
797
|
-
h("option", { value: "125%" }, "125%"),
|
|
798
|
-
h("option", { value: "240" }, "S · 240px"),
|
|
799
|
-
h("option", { value: "320" }, "M · 320px"),
|
|
800
|
-
h("option", { value: "420" }, "L · 420px"),
|
|
801
|
-
),
|
|
802
|
-
h("span", { className: "mc-hint" }, "Frame"),
|
|
803
|
-
h("div", { className: "mc-seg" },
|
|
804
|
-
h("button", { "data-on": frame === "none" || undefined, onClick: () => setFrame("none") }, "none"),
|
|
805
|
-
h("button", { "data-on": frame === "bezel" || undefined, onClick: () => setFrame("bezel") }, "bezel"),
|
|
806
|
-
h("button", { "data-on": frame === "device" || undefined, onClick: () => setFrame("device") }, "device"),
|
|
807
|
-
),
|
|
808
|
-
),
|
|
815
|
+
h(StageControls, { sizeMode, setSizeMode, frame, setFrame }),
|
|
809
816
|
);
|
|
810
817
|
}
|
|
811
818
|
/** Co-op second pane (0.9.0): a compact stream view for a second device —
|
|
812
|
-
* device select, live image, tap/drag.
|
|
813
|
-
*
|
|
814
|
-
|
|
819
|
+
* device select, live image, tap/drag — and (0.11.2) its own Size/Frame
|
|
820
|
+
* row, mirroring Device 1's controls. No toolbar/menu: those belong to
|
|
821
|
+
* the full card. Defaults are identical, so the panes match out of the
|
|
822
|
+
* box and can be adjusted independently. */
|
|
823
|
+
function CoopPane({ excludeSerial }) {
|
|
815
824
|
const [devices, setDevices] = useState([]);
|
|
816
825
|
const [serial, setSerial] = useState("");
|
|
826
|
+
const [sizeMode, setSizeMode] = useState({ mode: "fit" });
|
|
827
|
+
const [frame, setFrame] = useState("none");
|
|
817
828
|
const [error, setError] = useState("");
|
|
818
829
|
const live = useLiveStream({ serial, onError: setError });
|
|
819
830
|
const grant = (device) => live.grant(device).then((ok) => { if (ok) setError(""); });
|
|
@@ -858,21 +869,19 @@ window.__ModuleLoader__.load({
|
|
|
858
869
|
: h("div", { className: "mc-live-off" }, devices.length === 0 ? "No second device — boot another emulator." : "Connecting to " + serial + "..."),
|
|
859
870
|
),
|
|
860
871
|
h("div", { className: "mc-live-cap" }, "tap or drag on the screen to drive this device"),
|
|
872
|
+
h(StageControls, { sizeMode, setSizeMode, frame, setFrame }),
|
|
861
873
|
);
|
|
862
874
|
}
|
|
863
875
|
|
|
864
876
|
/** Stream section: the classic single card, plus a co-op mode that docks
|
|
865
|
-
* a second pane next to it for two-player testing.
|
|
877
|
+
* a second pane next to it for two-player testing. Each pane owns its
|
|
878
|
+
* Size/Frame knobs (0.11.2); the shared CSS base makes them match. */
|
|
866
879
|
function LiveStreamSection() {
|
|
867
880
|
const [coop, setCoop] = useState(false);
|
|
868
881
|
const [serialA, setSerialA] = useState("");
|
|
869
|
-
// Shared knobs (0.11.0): Device 1's Size/Frame controls drive both
|
|
870
|
-
// panes, so the pair always renders at the same height.
|
|
871
|
-
const [sizeMode, setSizeMode] = useState({ mode: "fit" }); // {mode:'fit'|'pct'|'px', value?}
|
|
872
|
-
const [frame, setFrame] = useState("none"); // 'none' | 'bezel' | 'device'
|
|
873
882
|
return h("div", { className: "mc-live-section" + (coop ? " coop" : "") },
|
|
874
|
-
h(LiveDeviceCard, { coopOn: coop, onToggleCoop: () => setCoop((v) => !v), onSerial: setSerialA
|
|
875
|
-
coop && h(CoopPane, { excludeSerial: serialA
|
|
883
|
+
h(LiveDeviceCard, { coopOn: coop, onToggleCoop: () => setCoop((v) => !v), onSerial: setSerialA }),
|
|
884
|
+
coop && h(CoopPane, { excludeSerial: serialA }),
|
|
876
885
|
);
|
|
877
886
|
}
|
|
878
887
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mobilecode",
|
|
3
3
|
"description": "MobileCode for the dsh web GUI: detect iOS/Android projects, run preview servers, and drive the simulator/emulator from the session — 37 agent tools (device_run, device_screen, device_ui_tree, device_ui_rows, device_tap_row, device_tap_element, device_wait_for, device_scroll_to, device_input, device_batch, device_intent, device_connect, device_pair_qr, device_perf, device_meminfo, device_backtrace, device_display, device_avd_create, device_pair_capture, device_app_info, device_install, device_uninstall, device_reboot, device_log, live screen stream, multimodal screenshots) plus a host-mediated co-op mesh hub (random callsigns, JSON pub/sub, tunable latency/jitter/drop/dup/throttle) so two virtual devices — and the AI watching them — can talk in real time. One classified adb boundary and a Wi-Fi connect/pair QR flow in the Connection settings tab. Hot-pluggable — mounted via the profile bundle list + cordis.patch.yml, no dsh source changes.",
|
|
4
|
-
"version": "0.11.
|
|
4
|
+
"version": "0.11.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@11.22.0",
|
|
7
7
|
"engines": {
|