linkshell-cli 0.10.45 → 0.10.47
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 +2 -0
- package/dist/cli/src/auth.d.ts +6 -2
- package/dist/cli/src/auth.js +12 -4
- package/dist/cli/src/auth.js.map +1 -1
- package/dist/cli/src/commands/desktop-workspace-state.d.ts +19 -0
- package/dist/cli/src/commands/desktop-workspace-state.js +92 -0
- package/dist/cli/src/commands/desktop-workspace-state.js.map +1 -0
- package/dist/cli/src/commands/desktop-workspace.d.ts +12 -0
- package/dist/cli/src/commands/desktop-workspace.js +415 -0
- package/dist/cli/src/commands/desktop-workspace.js.map +1 -0
- package/dist/cli/src/commands/desktop.d.ts +5 -5
- package/dist/cli/src/commands/desktop.js +55 -5
- package/dist/cli/src/commands/desktop.js.map +1 -1
- package/dist/cli/src/commands/login.d.ts +3 -1
- package/dist/cli/src/commands/login.js +44 -26
- package/dist/cli/src/commands/login.js.map +1 -1
- package/dist/cli/src/index.js +15 -0
- package/dist/cli/src/index.js.map +1 -1
- package/dist/cli/src/pro-membership.d.ts +16 -0
- package/dist/cli/src/pro-membership.js +91 -0
- package/dist/cli/src/pro-membership.js.map +1 -0
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/dist/client-core/src/pro-membership.d.ts +18 -0
- package/dist/client-core/src/pro-membership.js +109 -0
- package/dist/client-core/src/pro-membership.js.map +1 -0
- package/dist/host/src/input.d.ts +29 -2
- package/dist/host/src/input.js +99 -4
- package/dist/host/src/input.js.map +1 -1
- package/dist/host/src/screen-session.d.ts +35 -0
- package/dist/host/src/screen-session.js +26 -0
- package/dist/host/src/screen-session.js.map +1 -0
- package/dist/host/src/screen-viewer.js +293 -30
- package/dist/host/src/screen-viewer.js.map +1 -1
- package/dist/host/src/screen.d.ts +3 -1
- package/dist/host/src/screen.js +454 -160
- package/dist/host/src/screen.js.map +1 -1
- package/package.json +6 -4
- package/src/auth.ts +9 -4
- package/src/commands/desktop-workspace-state.ts +87 -0
- package/src/commands/desktop-workspace.ts +353 -0
- package/src/commands/desktop.ts +49 -5
- package/src/commands/login.ts +33 -34
- package/src/index.ts +16 -0
- package/src/pro-membership.ts +82 -0
|
@@ -421,6 +421,132 @@ function cleanShortcuts(list) {
|
|
|
421
421
|
}
|
|
422
422
|
return out;
|
|
423
423
|
}
|
|
424
|
+
|
|
425
|
+
/** A configuration belongs to this socket; only its latest acknowledgement may restore input. */
|
|
426
|
+
function createScreenSession(initial, effects) {
|
|
427
|
+
let desired = { active: true, display: 0, width: "1920" }, hidden = false;
|
|
428
|
+
let capabilities = null, opened = false, closed = false, scheduled = false;
|
|
429
|
+
let requestId = 0, pending = null, sentKey = "", error = "", inputReady = true, generation;
|
|
430
|
+
const clean = (next) => {
|
|
431
|
+
const out = {};
|
|
432
|
+
if (!next || typeof next !== "object") return out;
|
|
433
|
+
if (typeof next.active === "boolean") out.active = next.active;
|
|
434
|
+
if (Number.isSafeInteger(next.display) && next.display >= 0) out.display = next.display;
|
|
435
|
+
if (next.width === "native") out.width = "native";
|
|
436
|
+
else if ((typeof next.width === "string" || typeof next.width === "number") && Number.isInteger(Number(next.width)) && Number(next.width) >= 640 && Number(next.width) <= 3840) out.width = String(Number(next.width));
|
|
437
|
+
if ([30, 60, 120].includes(next.maxFps)) out.maxFps = next.maxFps;
|
|
438
|
+
return out;
|
|
439
|
+
};
|
|
440
|
+
desired = Object.assign(desired, clean(initial));
|
|
441
|
+
const original = { ...desired };
|
|
442
|
+
const state = () => ({ display: desired.display, width: desired.width, ...(desired.maxFps ? { maxFps: desired.maxFps } : {}), paused: !desired.active || hidden });
|
|
443
|
+
const key = () => JSON.stringify(state());
|
|
444
|
+
const targetChanged = () => ["display", "width", "maxFps"].some((name) => desired[name] !== original[name]);
|
|
445
|
+
const changed = () => effects.changed?.({ ...desired, paused: state().paused, inputReady, error });
|
|
446
|
+
const suspend = () => { effects.release(); inputReady = false; };
|
|
447
|
+
const pump = () => {
|
|
448
|
+
scheduled = false;
|
|
449
|
+
if (closed || !opened || !capabilities) return;
|
|
450
|
+
const next = state(), nextKey = key();
|
|
451
|
+
if (capabilities.configure) {
|
|
452
|
+
if (nextKey === sentKey) return;
|
|
453
|
+
suspend();
|
|
454
|
+
sentKey = nextKey;
|
|
455
|
+
pending = { requestId: ++requestId, key: nextKey, ...next };
|
|
456
|
+
effects.send({ t: "screen.configure", requestId, ...next });
|
|
457
|
+
} else {
|
|
458
|
+
if (targetChanged() && !next.paused) {
|
|
459
|
+
if (nextKey !== sentKey) effects.reload({ display: desired.display, width: desired.width, maxFps: desired.maxFps });
|
|
460
|
+
inputReady = false;
|
|
461
|
+
} else inputReady = !next.paused;
|
|
462
|
+
sentKey = nextKey;
|
|
463
|
+
}
|
|
464
|
+
changed();
|
|
465
|
+
};
|
|
466
|
+
const schedule = () => {
|
|
467
|
+
if (!scheduled && !closed) { scheduled = true; effects.defer(pump); }
|
|
468
|
+
};
|
|
469
|
+
return {
|
|
470
|
+
desired: () => ({ ...desired }),
|
|
471
|
+
active: () => !closed && !state().paused,
|
|
472
|
+
canInput: () => !closed && !state().paused && inputReady,
|
|
473
|
+
generation: () => generation,
|
|
474
|
+
supported: () => capabilities?.configure === true,
|
|
475
|
+
known: () => capabilities !== null,
|
|
476
|
+
open() {
|
|
477
|
+
if (closed) return;
|
|
478
|
+
opened = true;
|
|
479
|
+
if (state().paused) { suspend(); effects.send({ t: "hidden" }); }
|
|
480
|
+
schedule();
|
|
481
|
+
},
|
|
482
|
+
update(next) {
|
|
483
|
+
if (closed) return;
|
|
484
|
+
const before = key();
|
|
485
|
+
desired = Object.assign({}, desired, clean(next));
|
|
486
|
+
if (key() === before && !error) return;
|
|
487
|
+
suspend();
|
|
488
|
+
// A pause can reach the native receiver before this microtask runs. Even a
|
|
489
|
+
// quick return to the same target needs a fresh apply and input generation.
|
|
490
|
+
pending = null;
|
|
491
|
+
sentKey = "";
|
|
492
|
+
error = "";
|
|
493
|
+
if (!capabilities?.configure && opened && state().paused !== JSON.parse(before).paused) effects.send({ t: state().paused ? "hidden" : "shown" });
|
|
494
|
+
changed();
|
|
495
|
+
schedule();
|
|
496
|
+
},
|
|
497
|
+
visibility(nextHidden) {
|
|
498
|
+
if (closed || hidden === nextHidden) return;
|
|
499
|
+
hidden = nextHidden;
|
|
500
|
+
suspend();
|
|
501
|
+
pending = null;
|
|
502
|
+
sentKey = "";
|
|
503
|
+
error = "";
|
|
504
|
+
if (!capabilities?.configure && opened) effects.send({ t: state().paused ? "hidden" : "shown" });
|
|
505
|
+
changed();
|
|
506
|
+
schedule();
|
|
507
|
+
},
|
|
508
|
+
receive(message) {
|
|
509
|
+
if (closed || !message || typeof message !== "object") return;
|
|
510
|
+
if (message.t === "capabilities") {
|
|
511
|
+
capabilities = { ...message, configure: message.version === 1 && message.configure === true, pause: message.version === 1 && message.pause === true };
|
|
512
|
+
sentKey = "";
|
|
513
|
+
pending = null;
|
|
514
|
+
if (capabilities.configure || state().paused || targetChanged()) suspend();
|
|
515
|
+
else inputReady = true;
|
|
516
|
+
effects.report(capabilities);
|
|
517
|
+
changed();
|
|
518
|
+
schedule();
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (!pending || message.requestId !== pending.requestId || pending.key !== key()) return;
|
|
522
|
+
if (message.t !== "configured" && message.t !== "error") return;
|
|
523
|
+
if (message.t === "configured" && (message.display !== pending.display || message.paused !== pending.paused || !Number.isSafeInteger(message.generation) || message.generation < 0)) return;
|
|
524
|
+
inputReady = message.t === "configured" && !message.paused;
|
|
525
|
+
if (message.t === "configured") generation = message.generation;
|
|
526
|
+
error = message.t === "error" ? message.message || "屏幕配置失败,请重试" : "";
|
|
527
|
+
pending = null;
|
|
528
|
+
if (error) sentKey = "";
|
|
529
|
+
effects.report(message);
|
|
530
|
+
changed();
|
|
531
|
+
},
|
|
532
|
+
close() {
|
|
533
|
+
if (closed) return;
|
|
534
|
+
suspend();
|
|
535
|
+
closed = true;
|
|
536
|
+
pending = null;
|
|
537
|
+
changed();
|
|
538
|
+
},
|
|
539
|
+
reconnect() {
|
|
540
|
+
if (closed) return;
|
|
541
|
+
suspend();
|
|
542
|
+
capabilities = pending = null;
|
|
543
|
+
generation = undefined;
|
|
544
|
+
sentKey = "";
|
|
545
|
+
error = "";
|
|
546
|
+
changed();
|
|
547
|
+
},
|
|
548
|
+
};
|
|
549
|
+
}
|
|
424
550
|
`;
|
|
425
551
|
const BODY = String.raw `
|
|
426
552
|
const $ = (id) => document.getElementById(id);
|
|
@@ -430,6 +556,11 @@ const sheet = $("sheet"), actions = $("actions"), maker = $("maker"), naming = $
|
|
|
430
556
|
const query = new URLSearchParams(location.search);
|
|
431
557
|
const app = window.ReactNativeWebView;
|
|
432
558
|
const tellApp = (message) => { if (app) app.postMessage(JSON.stringify(message)); };
|
|
559
|
+
const tellSession = (session) => {
|
|
560
|
+
const message = { type: "screenSession", session };
|
|
561
|
+
tellApp(message);
|
|
562
|
+
if (typeof window.__linkshellSession === "function") window.__linkshellSession(message);
|
|
563
|
+
};
|
|
433
564
|
const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
|
|
434
565
|
const coarse = matchMedia("(pointer: coarse)").matches;
|
|
435
566
|
const android = /Android/i.test(navigator.userAgent);
|
|
@@ -442,7 +573,7 @@ const HINTS = {
|
|
|
442
573
|
};
|
|
443
574
|
let stored = null;
|
|
444
575
|
try { stored = localStorage.getItem("linkshell.screen.mode"); } catch {}
|
|
445
|
-
let mode = MODES.includes(query.get("mode")) ? query.get("mode") : MODES.includes(stored) ? stored : "view";
|
|
576
|
+
let mode = MODES.includes(query.get("mode")) ? query.get("mode") : MODES.includes(window.__linkshellChrome?.mode) ? window.__linkshellChrome.mode : MODES.includes(stored) ? stored : "view";
|
|
446
577
|
|
|
447
578
|
// What the app around the page has done to the phone's screen. A page in a plain browser has none of it.
|
|
448
579
|
// "clear" is the side of a phone lying down that has no camera in it.
|
|
@@ -464,7 +595,8 @@ const mods = new Set();
|
|
|
464
595
|
|
|
465
596
|
// ---- The picture ----------------------------------------------------------
|
|
466
597
|
|
|
467
|
-
|
|
598
|
+
let sessionError = "";
|
|
599
|
+
const say = (text) => { text = sessionError || text; note.textContent = text; note.classList.toggle("gone", !text); };
|
|
468
600
|
const round = (value, digits) => (value == null || !isFinite(value) ? null : +value.toFixed(digits === undefined ? 1 : digits));
|
|
469
601
|
const hex = (n) => n.toString(16).padStart(2, "0");
|
|
470
602
|
function sps(unit) {
|
|
@@ -499,12 +631,35 @@ const asked = new URLSearchParams(location.search);
|
|
|
499
631
|
asked.delete("video");
|
|
500
632
|
if (wantVideo) asked.set("video", "1");
|
|
501
633
|
// How wide the video track may be (the socket's picture has its own ladder). The app says it in the address;
|
|
502
|
-
// a plain browser remembers its own. A
|
|
634
|
+
// a plain browser remembers its own. A capable host applies a new choice to this same track.
|
|
503
635
|
const WIDTHS = ["1280", "1920", "2560", "native"];
|
|
504
636
|
let keptWidth = null;
|
|
505
637
|
try { keptWidth = localStorage.getItem("linkshell.screen.width"); } catch {}
|
|
506
|
-
|
|
638
|
+
let width = WIDTHS.includes(query.get("width")) ? query.get("width") : WIDTHS.includes(keptWidth) ? keptWidth : "1920";
|
|
639
|
+
const initialSession = typeof window.__linkshellSession === "object" && window.__linkshellSession ? window.__linkshellSession : {};
|
|
640
|
+
const session = createScreenSession({ active: true, display: Number(query.get("display")) || 0, width, maxFps: Number(query.get("maxFps")) || undefined, ...initialSession }, {
|
|
641
|
+
send: (message) => send(message),
|
|
642
|
+
defer: (action) => Promise.resolve().then(action),
|
|
643
|
+
release: () => releaseInput(),
|
|
644
|
+
report: tellSession,
|
|
645
|
+
reload: (state) => reloadSession(state),
|
|
646
|
+
changed: (state) => {
|
|
647
|
+
width = state.width;
|
|
648
|
+
const previousError = sessionError;
|
|
649
|
+
sessionError = state.error;
|
|
650
|
+
if (sessionError) say(sessionError);
|
|
651
|
+
else if (state.paused) say("屏幕已暂停");
|
|
652
|
+
else if (!state.inputReady) say("正在调整屏幕…");
|
|
653
|
+
else if (previousError || note.textContent === "屏幕已暂停" || note.textContent === "正在调整屏幕…") say("");
|
|
654
|
+
if (state.inputReady && mode !== "view") askControl();
|
|
655
|
+
refreshWidths();
|
|
656
|
+
refresh();
|
|
657
|
+
},
|
|
658
|
+
});
|
|
659
|
+
width = session.desired().width;
|
|
507
660
|
asked.set("width", width);
|
|
661
|
+
asked.set("display", String(session.desired().display));
|
|
662
|
+
if (session.desired().maxFps) asked.set("maxFps", String(session.desired().maxFps));
|
|
508
663
|
// A page that is a srcdoc (the gateway's web client puts it in one) has no address of its own to load again:
|
|
509
664
|
// it asks the page around it to, where that page says it can, and offers no choice where it can't.
|
|
510
665
|
const framed = document.URL === "about:srcdoc";
|
|
@@ -515,10 +670,34 @@ const ws = nativePicture
|
|
|
515
670
|
: new WebSocket((location.protocol === "https:" ? "wss://" : "ws://") + location.host + "/stream?" + asked);
|
|
516
671
|
ws.binaryType = "arraybuffer";
|
|
517
672
|
const send = (message) => { if (ws.readyState === 1) ws.send(JSON.stringify(message)); };
|
|
518
|
-
|
|
673
|
+
let capabilityTimer = 0, socketClosed = false;
|
|
674
|
+
function waitForCapabilities() {
|
|
675
|
+
clearTimeout(capabilityTimer);
|
|
676
|
+
capabilityTimer = setTimeout(() => {
|
|
677
|
+
if (!session.known()) session.receive({ t: "capabilities", version: 1, configure: false, pause: false, retainMs: 0 });
|
|
678
|
+
}, 10000);
|
|
679
|
+
}
|
|
680
|
+
ws.onopen = () => { session.open(); waitForCapabilities(); if (mode !== "view") askControl(); };
|
|
519
681
|
// Out of sight, a page answers late (its timers slow to one a second) and shows nothing: the host sends no
|
|
520
682
|
// picture down the socket meanwhile, and starts again at a keyframe.
|
|
521
|
-
document.addEventListener("visibilitychange", () =>
|
|
683
|
+
document.addEventListener("visibilitychange", () => session.visibility(document.hidden));
|
|
684
|
+
window.linkshellSession = (state) => session.update(state);
|
|
685
|
+
|
|
686
|
+
function reloadSession(state) {
|
|
687
|
+
if (nativePicture) return;
|
|
688
|
+
if (reloadVia) return reloadVia({ ...state, mode });
|
|
689
|
+
const next = new URLSearchParams(location.search);
|
|
690
|
+
for (const name of ["display", "width", "maxFps"]) if (state[name] !== undefined) next.set(name, String(state[name]));
|
|
691
|
+
next.set("mode", mode);
|
|
692
|
+
location.replace(location.pathname + "?" + next);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function refreshWidths() {
|
|
696
|
+
for (const tile of $("widths").querySelectorAll(".tile")) {
|
|
697
|
+
tile.classList.toggle("on", tile.dataset.width === width);
|
|
698
|
+
tile.setAttribute("aria-pressed", String(tile.dataset.width === width));
|
|
699
|
+
}
|
|
700
|
+
}
|
|
522
701
|
ws.onmessage = (event) => {
|
|
523
702
|
if (typeof event.data === "string") return heard(JSON.parse(event.data));
|
|
524
703
|
if (!("VideoDecoder" in window)) return;
|
|
@@ -559,14 +738,23 @@ ws.onmessage = (event) => {
|
|
|
559
738
|
decoder.decode(new EncodedVideoChunk({ type: key ? "key" : "delta", timestamp: seq, data: unit }));
|
|
560
739
|
};
|
|
561
740
|
ws.onclose = () => {
|
|
741
|
+
if (socketClosed) return;
|
|
742
|
+
socketClosed = true;
|
|
743
|
+
clearTimeout(capabilityTimer);
|
|
744
|
+
session.close();
|
|
562
745
|
// A video track is this socket's: without the one there is no finding the other again, and no hands.
|
|
563
746
|
drop();
|
|
564
747
|
if (requestRelay()) return;
|
|
748
|
+
tellApp({ type: "screenClosed" });
|
|
565
749
|
if (blocked || note.classList.contains("gone") || note.textContent.startsWith("正在")) say("屏幕连接已断开");
|
|
566
750
|
blocked = false;
|
|
567
751
|
};
|
|
568
752
|
|
|
569
753
|
function heard(message) {
|
|
754
|
+
if (message.session) {
|
|
755
|
+
if (message.session.t === "capabilities") clearTimeout(capabilityTimer);
|
|
756
|
+
return session.receive(message.session);
|
|
757
|
+
}
|
|
570
758
|
if (message.error) return say(message.error);
|
|
571
759
|
if (message.rtc) return signal(message.rtc);
|
|
572
760
|
if (message.pong) return meter && meter.pong(message.pong);
|
|
@@ -635,7 +823,7 @@ function signal(message) {
|
|
|
635
823
|
if (!mine) return;
|
|
636
824
|
if (message.t === "offer") {
|
|
637
825
|
tried = true;
|
|
638
|
-
patience = setTimeout(() =>
|
|
826
|
+
patience = setTimeout(() => connectionPatience(mine), patienceSeconds * 1000);
|
|
639
827
|
if (failAfter) setTimeout(() => { if (rtc === mine) giveUp("asked to (failAfter)"); }, failAfter * 1000);
|
|
640
828
|
// The answer is the browser's own, as it made it: the sender's playout-delay header has to survive in it.
|
|
641
829
|
mine.described = mine.pc.setRemoteDescription({ type: "offer", sdp: message.sdp })
|
|
@@ -702,16 +890,23 @@ function watch(mine) {
|
|
|
702
890
|
}
|
|
703
891
|
|
|
704
892
|
function away(mine, since) {
|
|
893
|
+
if (rtc !== mine) return;
|
|
705
894
|
// A page that was put aside (another app, another tab) could not hear the path come back: once it is
|
|
706
895
|
// looked at again it is given the time again.
|
|
707
896
|
const now = performance.now();
|
|
708
|
-
if (
|
|
897
|
+
if (!session.active() || now - since > 6000) mine.away = setTimeout(() => away(mine, now), 4000);
|
|
709
898
|
else giveUp("disconnected for more than 4 s");
|
|
710
899
|
}
|
|
711
900
|
|
|
901
|
+
function connectionPatience(mine) {
|
|
902
|
+
if (rtc !== mine) return;
|
|
903
|
+
if (!session.active()) patience = setTimeout(() => connectionPatience(mine), patienceSeconds * 1000);
|
|
904
|
+
else giveUp("not connected within " + patienceSeconds + " s of the offer");
|
|
905
|
+
}
|
|
906
|
+
|
|
712
907
|
function frameless() {
|
|
713
908
|
// A page nobody is looking at is shown nothing, and one waiting for a tap has not begun: neither is the path's doing.
|
|
714
|
-
if (
|
|
909
|
+
if (!session.active() || blocked) patience = setTimeout(frameless, 4000);
|
|
715
910
|
else giveUp("connected, but no frame within 4 s");
|
|
716
911
|
}
|
|
717
912
|
|
|
@@ -859,6 +1054,7 @@ async function presentTrack(track) {
|
|
|
859
1054
|
if (mine.stopped || !mine.latest) return;
|
|
860
1055
|
const frame = mine.latest;
|
|
861
1056
|
mine.latest = null;
|
|
1057
|
+
if (!session.active()) { frame.close(); return; }
|
|
862
1058
|
try {
|
|
863
1059
|
// The canvas carries the frame's own pixels; the layout gives it its place on the page.
|
|
864
1060
|
if (gpu.width !== frame.displayWidth || gpu.height !== frame.displayHeight) {
|
|
@@ -914,6 +1110,7 @@ async function presentTrack(track) {
|
|
|
914
1110
|
if (value) value.close();
|
|
915
1111
|
break;
|
|
916
1112
|
}
|
|
1113
|
+
if (!session.active()) { value.close(); continue; }
|
|
917
1114
|
if (mine.latest) {
|
|
918
1115
|
mine.latest.close();
|
|
919
1116
|
mine.dropped += 1;
|
|
@@ -1270,7 +1467,7 @@ function expandControls(open) {
|
|
|
1270
1467
|
}
|
|
1271
1468
|
function armControlsIdle() {
|
|
1272
1469
|
clearTimeout(controls.timer);
|
|
1273
|
-
if (!controlsMayCollapse() || controls.target === 0) return;
|
|
1470
|
+
if (!session.active() || !controlsMayCollapse() || controls.target === 0) return;
|
|
1274
1471
|
controls.timer = setTimeout(() => { if (controlsMayCollapse()) expandControls(false); }, 3000);
|
|
1275
1472
|
}
|
|
1276
1473
|
function layoutControls(safe, anchor) {
|
|
@@ -1310,7 +1507,7 @@ reducedMotion.addEventListener("change", () => {
|
|
|
1310
1507
|
});
|
|
1311
1508
|
orb.addEventListener("pointerdown", (event) => {
|
|
1312
1509
|
event.preventDefault(); event.stopPropagation();
|
|
1313
|
-
if (!controls.orb) return;
|
|
1510
|
+
if (!session.active() || !controls.orb) return;
|
|
1314
1511
|
try { orb.setPointerCapture(event.pointerId); } catch {}
|
|
1315
1512
|
clearTimeout(controls.timer);
|
|
1316
1513
|
controls.drag = { id: event.pointerId, x: event.clientX, y: event.clientY, origin: { ...controls.orb }, moved: false };
|
|
@@ -1542,12 +1739,12 @@ function panBy(dx, dy) {
|
|
|
1542
1739
|
|
|
1543
1740
|
// ---- Sending --------------------------------------------------------------
|
|
1544
1741
|
|
|
1545
|
-
const controlling = () => mode !== "view" && control.available && control.trusted;
|
|
1742
|
+
const controlling = () => session.canInput() && mode !== "view" && control.available && control.trusted;
|
|
1546
1743
|
|
|
1547
1744
|
function askControl() {
|
|
1548
|
-
if (control.asked) return;
|
|
1745
|
+
if (control.asked || !session.canInput()) return;
|
|
1549
1746
|
control.asked = true;
|
|
1550
|
-
send({ t: "control" });
|
|
1747
|
+
send({ t: "control", ...(session.generation() !== undefined ? { screenGeneration: session.generation() } : {}) });
|
|
1551
1748
|
}
|
|
1552
1749
|
|
|
1553
1750
|
// Where an event travels: down the socket, in order, until a video's two channels to the app are open; then
|
|
@@ -1562,6 +1759,8 @@ function askControl() {
|
|
|
1562
1759
|
// after something sent later.
|
|
1563
1760
|
let wired = false, held = 0, sure = true, sent = 0, rest = 0, movedAt = -Infinity;
|
|
1564
1761
|
const trail = [];
|
|
1762
|
+
const heldButtons = new Map();
|
|
1763
|
+
let inputEpoch = 0;
|
|
1565
1764
|
|
|
1566
1765
|
function lanes() {
|
|
1567
1766
|
if (nativePicture) { wired = true; return; }
|
|
@@ -1572,15 +1771,23 @@ function lanes() {
|
|
|
1572
1771
|
}
|
|
1573
1772
|
|
|
1574
1773
|
function post(message, lane) {
|
|
1774
|
+
if (!session.canInput()) return;
|
|
1775
|
+
if (session.generation() !== undefined) message.screenGeneration = session.generation();
|
|
1776
|
+
transmitInput(message, lane);
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
function transmitInput(message, lane) {
|
|
1575
1780
|
if (nativePicture) return nativePost({ kind: "input", message, replaceable: lane === "pointer" });
|
|
1576
1781
|
if (!wired) return send(message);
|
|
1577
1782
|
message.i = ++sent;
|
|
1578
|
-
rtc.channels[lane]
|
|
1783
|
+
const channel = rtc && rtc.channels[lane];
|
|
1784
|
+
if (channel && channel.readyState === "open") channel.send(JSON.stringify(message));
|
|
1579
1785
|
}
|
|
1580
1786
|
|
|
1581
1787
|
let moveWaiting = false;
|
|
1582
1788
|
/** Says where the pointer is: in order, when something is about to happen there. */
|
|
1583
1789
|
function tell(placed) {
|
|
1790
|
+
if (!session.canInput()) { moveWaiting = false; return; }
|
|
1584
1791
|
lanes();
|
|
1585
1792
|
moveWaiting = false;
|
|
1586
1793
|
const now = performance.now(), lane = laneOf("move", placed, held > 0, false);
|
|
@@ -1597,6 +1804,7 @@ function tell(placed) {
|
|
|
1597
1804
|
const flush = () => { if (moveWaiting) tell(false); };
|
|
1598
1805
|
// A pointer position a frame: as often as the picture could show it.
|
|
1599
1806
|
function moved() {
|
|
1807
|
+
if (!session.canInput()) return;
|
|
1600
1808
|
if (!moveWaiting) requestAnimationFrame(flush);
|
|
1601
1809
|
moveWaiting = true;
|
|
1602
1810
|
movedAt = performance.now();
|
|
@@ -1605,13 +1813,64 @@ function moved() {
|
|
|
1605
1813
|
}
|
|
1606
1814
|
// Everything else goes at once, after the position it happens at.
|
|
1607
1815
|
function act(message) {
|
|
1816
|
+
if (!session.canInput()) return;
|
|
1608
1817
|
lanes();
|
|
1609
1818
|
const placed = moveWaiting || !sure;
|
|
1610
1819
|
if (placed) tell(true);
|
|
1611
|
-
if (message.t === "down") held
|
|
1820
|
+
if (message.t === "down") { heldButtons.set(message.b, { ...message }); held = heldButtons.size; }
|
|
1612
1821
|
if (message.t === "scroll" && placed && gesture) gesture.tied = true;
|
|
1613
1822
|
post(message, laneOf(message.t, placed, held > 0, !!gesture && !!gesture.tied));
|
|
1614
|
-
if (message.t === "up")
|
|
1823
|
+
if (message.t === "up") { heldButtons.delete(message.b); held = heldButtons.size; }
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
/** Input queued for the old display must never be replayed when the retained page is shown again. */
|
|
1827
|
+
function releaseInput() {
|
|
1828
|
+
inputEpoch += 1;
|
|
1829
|
+
control.asked = false;
|
|
1830
|
+
lanes();
|
|
1831
|
+
for (const button of heldButtons.values()) {
|
|
1832
|
+
const released = { ...button, t: "up" };
|
|
1833
|
+
if (session.generation() !== undefined) released.screenGeneration = session.generation();
|
|
1834
|
+
transmitInput(released, "input");
|
|
1835
|
+
}
|
|
1836
|
+
heldButtons.clear();
|
|
1837
|
+
held = 0;
|
|
1838
|
+
sure = true;
|
|
1839
|
+
moveWaiting = false;
|
|
1840
|
+
clearTimeout(rest);
|
|
1841
|
+
clearTimeout(fade);
|
|
1842
|
+
clearTimeout(settling);
|
|
1843
|
+
clearTimeout(toastTimer);
|
|
1844
|
+
clearTimeout(disarming);
|
|
1845
|
+
clearInterval(statusTimer);
|
|
1846
|
+
clearTimeout(controls.timer);
|
|
1847
|
+
if (gesture) clearTimeout(gesture.hold);
|
|
1848
|
+
for (const id of touches.keys()) { try { stage.releasePointerCapture(id); } catch {} }
|
|
1849
|
+
touches.clear();
|
|
1850
|
+
gesture = lastTap = mouseHeld = null;
|
|
1851
|
+
mouseClicks = { t: 0, n: 0, x: 0, y: 0, b: 0 };
|
|
1852
|
+
movedAt = -Infinity;
|
|
1853
|
+
trail.length = 0;
|
|
1854
|
+
mods.clear();
|
|
1855
|
+
composing = writing = false;
|
|
1856
|
+
composed = "";
|
|
1857
|
+
leftover = wanted = null;
|
|
1858
|
+
askedAt = -Infinity;
|
|
1859
|
+
keysOpen = false;
|
|
1860
|
+
if (document.activeElement && document.activeElement !== document.body) document.activeElement.blur();
|
|
1861
|
+
resetTyping();
|
|
1862
|
+
shortcut.value = "";
|
|
1863
|
+
disarm();
|
|
1864
|
+
pointer.classList.remove("pressed", "on");
|
|
1865
|
+
toast.classList.remove("on");
|
|
1866
|
+
if (controls.drag) { try { orb.releasePointerCapture(controls.drag.id); } catch {} }
|
|
1867
|
+
controls.drag = null;
|
|
1868
|
+
for (const element of pressedControls) element.style.scale = "";
|
|
1869
|
+
pressedControls.clear();
|
|
1870
|
+
finishPresentation();
|
|
1871
|
+
drawKeys();
|
|
1872
|
+
clearTimeout(controls.timer);
|
|
1873
|
+
clearTimeout(settling);
|
|
1615
1874
|
}
|
|
1616
1875
|
|
|
1617
1876
|
let fade = 0;
|
|
@@ -1687,7 +1946,7 @@ function endOne() {
|
|
|
1687
1946
|
}
|
|
1688
1947
|
|
|
1689
1948
|
stage.addEventListener("pointerdown", (event) => {
|
|
1690
|
-
if (presentation.active) { event.preventDefault(); return; }
|
|
1949
|
+
if (!session.active() || presentation.active) { event.preventDefault(); return; }
|
|
1691
1950
|
if (!immersiveControls() || controls.target > 0) wake();
|
|
1692
1951
|
// A touch that closes the menu, or the shortcuts, does nothing else.
|
|
1693
1952
|
const closing = [closeMenu(), closeSheet()].some(Boolean);
|
|
@@ -1720,7 +1979,7 @@ stage.addEventListener("pointerdown", (event) => {
|
|
|
1720
1979
|
});
|
|
1721
1980
|
|
|
1722
1981
|
stage.addEventListener("pointermove", (event) => {
|
|
1723
|
-
if (presentation.active) return;
|
|
1982
|
+
if (!session.active() || presentation.active) return;
|
|
1724
1983
|
if (event.pointerType === "mouse") return mouseMove(event);
|
|
1725
1984
|
const touch = touches.get(event.pointerId);
|
|
1726
1985
|
if (!touch || !gesture) return;
|
|
@@ -1852,7 +2111,7 @@ function mouseUp(event) {
|
|
|
1852
2111
|
}
|
|
1853
2112
|
stage.addEventListener("wheel", (event) => {
|
|
1854
2113
|
event.preventDefault();
|
|
1855
|
-
if (presentation.active) return;
|
|
2114
|
+
if (!session.active() || presentation.active) return;
|
|
1856
2115
|
if (controlling() && !event.ctrlKey) return act({ t: "scroll", dx: -event.deltaX, dy: -event.deltaY });
|
|
1857
2116
|
zoomTo(zoom * Math.exp(-event.deltaY / 300), { x: event.clientX, y: event.clientY });
|
|
1858
2117
|
}, { passive: false });
|
|
@@ -1990,6 +2249,7 @@ const keyboardShown = () => keyboardIsUp({
|
|
|
1990
2249
|
|
|
1991
2250
|
/** Brings the phone's keyboard up for a field. Within a touch: a browser gives a keyboard to nothing else. */
|
|
1992
2251
|
function ask(field, again) {
|
|
2252
|
+
if (!session.canInput()) return;
|
|
1993
2253
|
// A keyboard goes to the view the keys go to, and inside an app that is not always the page: the app
|
|
1994
2254
|
// sees to it, and has the page ask once more (linkshellKeyboard) if no keyboard has come.
|
|
1995
2255
|
if (!again) tellApp({ type: "keyboard" });
|
|
@@ -2055,7 +2315,8 @@ document.addEventListener("visibilitychange", () => { if (!document.hidden) sett
|
|
|
2055
2315
|
|
|
2056
2316
|
/** A control that works on touch-up and never takes the focus (the keyboard stays as it is). */
|
|
2057
2317
|
function tappable(element, action) {
|
|
2058
|
-
|
|
2318
|
+
let epoch = -1;
|
|
2319
|
+
element.addEventListener("pointerdown", (event) => { event.preventDefault(); event.stopPropagation(); if (!session.active()) return; epoch = inputEpoch; wake(); pressFeedback(element, true); });
|
|
2059
2320
|
element.addEventListener("pointercancel", () => pressFeedback(element, false));
|
|
2060
2321
|
element.addEventListener("pointerleave", () => pressFeedback(element, false));
|
|
2061
2322
|
element.addEventListener("touchstart", (event) => event.preventDefault(), { passive: false });
|
|
@@ -2064,6 +2325,8 @@ function tappable(element, action) {
|
|
|
2064
2325
|
event.preventDefault();
|
|
2065
2326
|
event.stopPropagation();
|
|
2066
2327
|
pressFeedback(element, false);
|
|
2328
|
+
if (!session.active() || epoch !== inputEpoch) return;
|
|
2329
|
+
epoch = -1;
|
|
2067
2330
|
const box = element.getBoundingClientRect(), cx = box.left + box.width / 2, cy = box.top + box.height / 2;
|
|
2068
2331
|
if (Math.abs(event.clientX - cx) <= element.offsetWidth / 2 && Math.abs(event.clientY - cy) <= element.offsetHeight / 2) action();
|
|
2069
2332
|
});
|
|
@@ -2072,7 +2335,7 @@ function tappable(element, action) {
|
|
|
2072
2335
|
/** The same inside something that scrolls: the touch is left to the browser, and counts if it stayed where it landed. */
|
|
2073
2336
|
function pressable(element, action) {
|
|
2074
2337
|
let from = null;
|
|
2075
|
-
element.addEventListener("pointerdown", (event) => { event.stopPropagation(); wake(); pressFeedback(element, true); from = { id: event.pointerId, x: event.clientX, y: event.clientY }; });
|
|
2338
|
+
element.addEventListener("pointerdown", (event) => { event.stopPropagation(); if (!session.active()) return; wake(); pressFeedback(element, true); from = { id: event.pointerId, x: event.clientX, y: event.clientY, epoch: inputEpoch }; });
|
|
2076
2339
|
element.addEventListener("pointercancel", () => { from = null; pressFeedback(element, false); });
|
|
2077
2340
|
element.addEventListener("pointerleave", () => pressFeedback(element, false));
|
|
2078
2341
|
// The mouse's events a browser makes up after a touch would land on whatever the action has left under the
|
|
@@ -2084,7 +2347,7 @@ function pressable(element, action) {
|
|
|
2084
2347
|
from = null;
|
|
2085
2348
|
pressFeedback(element, false);
|
|
2086
2349
|
event.stopPropagation();
|
|
2087
|
-
if (was && was.id === event.pointerId && Math.hypot(event.clientX - was.x, event.clientY - was.y) < SLOP) action();
|
|
2350
|
+
if (session.active() && was && was.epoch === inputEpoch && was.id === event.pointerId && Math.hypot(event.clientX - was.x, event.clientY - was.y) < SLOP) action();
|
|
2088
2351
|
});
|
|
2089
2352
|
}
|
|
2090
2353
|
|
|
@@ -2103,6 +2366,8 @@ function hint(text, ms) {
|
|
|
2103
2366
|
|
|
2104
2367
|
/** Says why a controlling mode is not controlling, or how it works once it is. */
|
|
2105
2368
|
function explain(was) {
|
|
2369
|
+
if (!session.active()) return;
|
|
2370
|
+
if (!session.canInput()) return hint("正在调整屏幕…", 0);
|
|
2106
2371
|
if (!control.known) return hint("正在准备控制电脑…", 0);
|
|
2107
2372
|
if (!control.available) return hint(control.reason || "这台电脑暂时不能被控制");
|
|
2108
2373
|
if (!control.trusted) {
|
|
@@ -2237,7 +2502,7 @@ async function status() {
|
|
|
2237
2502
|
layout();
|
|
2238
2503
|
};
|
|
2239
2504
|
// The choice is the track's: the socket's picture follows its own ladder.
|
|
2240
|
-
const choosing = live && canChooseWidth;
|
|
2505
|
+
const choosing = live && (canChooseWidth || session.supported());
|
|
2241
2506
|
if ($("widths").classList.contains("gone") === choosing) {
|
|
2242
2507
|
$("widths").classList.toggle("gone", !choosing);
|
|
2243
2508
|
layout();
|
|
@@ -2294,12 +2559,7 @@ for (const tile of $("widths").querySelectorAll(".tile")) {
|
|
|
2294
2559
|
if (chosen === width) return;
|
|
2295
2560
|
try { localStorage.setItem("linkshell.screen.width", chosen); } catch {}
|
|
2296
2561
|
tellApp({ type: "width", width: chosen });
|
|
2297
|
-
|
|
2298
|
-
if (reloadVia) return reloadVia({ width: chosen, mode });
|
|
2299
|
-
const next = new URLSearchParams(location.search);
|
|
2300
|
-
next.set("width", chosen);
|
|
2301
|
-
next.set("mode", mode);
|
|
2302
|
-
location.replace(location.pathname + "?" + next);
|
|
2562
|
+
window.linkshellSession({ width: chosen });
|
|
2303
2563
|
});
|
|
2304
2564
|
}
|
|
2305
2565
|
pressable($("full"), () => {
|
|
@@ -2761,6 +3021,7 @@ if (!nativePicture && query.get("measure")) meter = (() => {
|
|
|
2761
3021
|
resetTyping();
|
|
2762
3022
|
refresh();
|
|
2763
3023
|
wake();
|
|
3024
|
+
session.visibility(document.hidden);
|
|
2764
3025
|
if (nativePicture) {
|
|
2765
3026
|
window.linkshellNative = {
|
|
2766
3027
|
picture: (size) => {
|
|
@@ -2776,6 +3037,8 @@ if (nativePicture) {
|
|
|
2776
3037
|
stats: (stats) => { nativeStats = stats; },
|
|
2777
3038
|
connecting: () => {
|
|
2778
3039
|
nativeStats = null;
|
|
3040
|
+
session.reconnect();
|
|
3041
|
+
waitForCapabilities();
|
|
2779
3042
|
Object.assign(control, { asked: false, known: false, trusted: false });
|
|
2780
3043
|
say("正在连接电脑屏幕…");
|
|
2781
3044
|
if (mode !== "view") askControl();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screen-viewer.js","sourceRoot":"","sources":["../../../../host/src/screen-viewer.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,2EAA2E;AAC3E,6EAA6E;AAC7E,mEAAmE;AACnE,EAAE;AACF,4DAA4D;AAC5D,yEAAyE;AACzE,2CAA2C;AAC3C,6EAA6E;AAC7E,+EAA+E;AAC/E,2CAA2C;AAC3C,qGAAqG;AACrG,+EAA+E;AAC/E,6FAA6F;AAC7F,EAAE;AACF,oGAAoG;AACpG,mGAAmG;AACnG,+FAA+F;AAC/F,8FAA8F;AAE9F,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE,CAC5B,uJAAuJ,IAAI,QAAQ,CAAC;AAEtK,MAAM,KAAK,GAAG;IACZ,IAAI,EAAE,IAAI,CAAC,qHAAqH,CAAC;IACjI,QAAQ,EAAE,IAAI,CAAC,6DAA6D,CAAC;IAC7E,KAAK,EAAE,IAAI,CAAC,oGAAoG,CAAC;IACjH,QAAQ,EAAE,IAAI,CAAC,iJAAiJ,CAAC;IACjK,GAAG,EAAE,IAAI,CAAC,0EAA0E,CAAC;IACrF,MAAM,EAAE,IAAI,CAAC,oDAAoD,CAAC;IAClE,MAAM,EAAE,IAAI,CAAC,oDAAoD,CAAC;IAClE,IAAI,EAAE,IAAI,CAAC,4BAA4B,CAAC;IACxC,KAAK,EAAE,IAAI,CAAC,qDAAqD,CAAC;IAClE,OAAO,EAAE,IAAI,CAAC,sEAAsE,CAAC;IACrF,IAAI,EAAE,IAAI,CAAC,iEAAiE,CAAC;IAC7E,KAAK,EAAE,IAAI,CAAC,4CAA4C,CAAC;CAC1D,CAAC;AAEF,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoHvB,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"screen-viewer.js","sourceRoot":"","sources":["../../../../host/src/screen-viewer.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,2EAA2E;AAC3E,6EAA6E;AAC7E,mEAAmE;AACnE,EAAE;AACF,4DAA4D;AAC5D,yEAAyE;AACzE,2CAA2C;AAC3C,6EAA6E;AAC7E,+EAA+E;AAC/E,2CAA2C;AAC3C,qGAAqG;AACrG,+EAA+E;AAC/E,6FAA6F;AAC7F,EAAE;AACF,oGAAoG;AACpG,mGAAmG;AACnG,+FAA+F;AAC/F,8FAA8F;AAE9F,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE,CAC5B,uJAAuJ,IAAI,QAAQ,CAAC;AAEtK,MAAM,KAAK,GAAG;IACZ,IAAI,EAAE,IAAI,CAAC,qHAAqH,CAAC;IACjI,QAAQ,EAAE,IAAI,CAAC,6DAA6D,CAAC;IAC7E,KAAK,EAAE,IAAI,CAAC,oGAAoG,CAAC;IACjH,QAAQ,EAAE,IAAI,CAAC,iJAAiJ,CAAC;IACjK,GAAG,EAAE,IAAI,CAAC,0EAA0E,CAAC;IACrF,MAAM,EAAE,IAAI,CAAC,oDAAoD,CAAC;IAClE,MAAM,EAAE,IAAI,CAAC,oDAAoD,CAAC;IAClE,IAAI,EAAE,IAAI,CAAC,4BAA4B,CAAC;IACxC,KAAK,EAAE,IAAI,CAAC,qDAAqD,CAAC;IAClE,OAAO,EAAE,IAAI,CAAC,sEAAsE,CAAC;IACrF,IAAI,EAAE,IAAI,CAAC,iEAAiE,CAAC;IAC7E,KAAK,EAAE,IAAI,CAAC,4CAA4C,CAAC;CAC1D,CAAC;AAEF,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoHvB,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0YrC,CAAC;AAEF,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA68EtB,CAAC;AAEF,MAAM,MAAM,GAAG,WAAW,YAAY,GAAG,IAAI,OAAO,CAAC;AAErD,MAAM,UAAU,UAAU;IACxB,OAAO;;;;SAIA,KAAK;;;;;;;;;;;iHAWmG,KAAK,CAAC,KAAK;;;;;;;;;;;;;;;kEAe1D,KAAK,CAAC,QAAQ;iEACf,KAAK,CAAC,KAAK;+DACb,KAAK,CAAC,GAAG;gEACR,KAAK,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6CAmD7B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;UACxD,MAAM;eACD,CAAC;AAChB,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ChildProcess } from "node:child_process";
|
|
2
|
+
import { type StreamProfile } from "./input.js";
|
|
2
3
|
export interface Display {
|
|
3
4
|
index: number;
|
|
4
5
|
name: string;
|
|
@@ -12,7 +13,7 @@ export interface ScreenAccess {
|
|
|
12
13
|
problem?: string;
|
|
13
14
|
}
|
|
14
15
|
/** ffmpeg's capture of an X display (Linux), for one rung of the ladder (see `screen-pacer.ts`): 0 is the best picture. */
|
|
15
|
-
export declare function captureArgs(level?: number): string[];
|
|
16
|
+
export declare function captureArgs(level?: number, profile?: StreamProfile): string[];
|
|
16
17
|
/** Splits an Annex-B stream at access-unit delimiters (NAL type 9) into whole frames. */
|
|
17
18
|
export declare class AccessUnitSplitter {
|
|
18
19
|
private buffer;
|
|
@@ -68,6 +69,7 @@ export declare class ScreenShare {
|
|
|
68
69
|
/** The viewer the app is sending the screen to as a video track. One at a time, like captures. */
|
|
69
70
|
private video?;
|
|
70
71
|
private readonly controls;
|
|
72
|
+
private readonly viewers;
|
|
71
73
|
constructor(log: (message: string) => void,
|
|
72
74
|
/** The servers both ends ask for their public address, to reach each other directly. */
|
|
73
75
|
iceServers?: () => string[]);
|