whipdesk 0.1.1 → 0.1.3
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 +21 -0
- package/dist/agent.cjs +462 -91
- package/dist/mobile-web/assets/index-CaqF5am7.css +1 -0
- package/dist/mobile-web/assets/index-D-eJUnpy.js +2 -0
- package/dist/mobile-web/assets/index.esm-DZJXVPeZ.js +33 -0
- package/dist/mobile-web/assets/loading-whip-anim-DmlnYIJ-.gif +0 -0
- package/dist/mobile-web/firebase-messaging-sw.js +34 -26
- package/dist/mobile-web/index.html +2 -2
- package/package.json +2 -2
- package/dist/mobile-web/assets/index-C1gDHBib.css +0 -1
- package/dist/mobile-web/assets/index-YSgNjLfG.js +0 -2
- package/dist/mobile-web/assets/index.esm-wogdVWd2.js +0 -30
package/dist/agent.cjs
CHANGED
|
@@ -18909,7 +18909,7 @@ var require_view = __commonJS({
|
|
|
18909
18909
|
var dirname3 = path.dirname;
|
|
18910
18910
|
var basename2 = path.basename;
|
|
18911
18911
|
var extname = path.extname;
|
|
18912
|
-
var
|
|
18912
|
+
var join11 = path.join;
|
|
18913
18913
|
var resolve = path.resolve;
|
|
18914
18914
|
module2.exports = View;
|
|
18915
18915
|
function View(name, options) {
|
|
@@ -18971,12 +18971,12 @@ var require_view = __commonJS({
|
|
|
18971
18971
|
};
|
|
18972
18972
|
View.prototype.resolve = function resolve2(dir, file) {
|
|
18973
18973
|
var ext = this.ext;
|
|
18974
|
-
var path2 =
|
|
18974
|
+
var path2 = join11(dir, file);
|
|
18975
18975
|
var stat2 = tryStat(path2);
|
|
18976
18976
|
if (stat2 && stat2.isFile()) {
|
|
18977
18977
|
return path2;
|
|
18978
18978
|
}
|
|
18979
|
-
path2 =
|
|
18979
|
+
path2 = join11(dir, basename2(file, ext), "index" + ext);
|
|
18980
18980
|
stat2 = tryStat(path2);
|
|
18981
18981
|
if (stat2 && stat2.isFile()) {
|
|
18982
18982
|
return path2;
|
|
@@ -22802,7 +22802,7 @@ var require_send = __commonJS({
|
|
|
22802
22802
|
var Stream = require("stream");
|
|
22803
22803
|
var util = require("util");
|
|
22804
22804
|
var extname = path.extname;
|
|
22805
|
-
var
|
|
22805
|
+
var join11 = path.join;
|
|
22806
22806
|
var normalize = path.normalize;
|
|
22807
22807
|
var resolve = path.resolve;
|
|
22808
22808
|
var sep2 = path.sep;
|
|
@@ -22974,7 +22974,7 @@ var require_send = __commonJS({
|
|
|
22974
22974
|
return res;
|
|
22975
22975
|
}
|
|
22976
22976
|
parts = path2.split(sep2);
|
|
22977
|
-
path2 = normalize(
|
|
22977
|
+
path2 = normalize(join11(root, path2));
|
|
22978
22978
|
} else {
|
|
22979
22979
|
if (UP_PATH_REGEXP.test(path2)) {
|
|
22980
22980
|
debug('malicious path "%s"', path2);
|
|
@@ -23107,7 +23107,7 @@ var require_send = __commonJS({
|
|
|
23107
23107
|
if (err) return self.onStatError(err);
|
|
23108
23108
|
return self.error(404);
|
|
23109
23109
|
}
|
|
23110
|
-
var p =
|
|
23110
|
+
var p = join11(path2, self._index[i]);
|
|
23111
23111
|
debug('stat "%s"', p);
|
|
23112
23112
|
fs.stat(p, function(err2, stat2) {
|
|
23113
23113
|
if (err2) return next(err2);
|
|
@@ -27641,7 +27641,7 @@ var require_websocket_server = __commonJS({
|
|
|
27641
27641
|
|
|
27642
27642
|
// src/index.ts
|
|
27643
27643
|
var import_node_readline2 = require("node:readline");
|
|
27644
|
-
var
|
|
27644
|
+
var import_node_os9 = require("node:os");
|
|
27645
27645
|
|
|
27646
27646
|
// src/config.ts
|
|
27647
27647
|
var import_node_crypto = require("node:crypto");
|
|
@@ -27693,7 +27693,7 @@ function isPackaged() {
|
|
|
27693
27693
|
}
|
|
27694
27694
|
|
|
27695
27695
|
// src/version.ts
|
|
27696
|
-
var AGENT_VERSION = "0.1.
|
|
27696
|
+
var AGENT_VERSION = "0.1.3";
|
|
27697
27697
|
|
|
27698
27698
|
// src/config.ts
|
|
27699
27699
|
var here = (0, import_node_path2.dirname)((0, import_node_url2.fileURLToPath)(__whipdesk_meta_url));
|
|
@@ -28274,12 +28274,20 @@ async function readNsScreens() {
|
|
|
28274
28274
|
return null;
|
|
28275
28275
|
}
|
|
28276
28276
|
}
|
|
28277
|
+
function friendlyDisplayName(raw, index) {
|
|
28278
|
+
const s = String(raw ?? "").trim();
|
|
28279
|
+
if (!s) return `Display ${index + 1}`;
|
|
28280
|
+
const m = /DISPLAY(\d+)/i.exec(s);
|
|
28281
|
+
if (m) return `Display ${m[1]}`;
|
|
28282
|
+
if (/^\\\\/.test(s)) return `Display ${index + 1}`;
|
|
28283
|
+
return s;
|
|
28284
|
+
}
|
|
28277
28285
|
async function listBaseDisplays() {
|
|
28278
28286
|
try {
|
|
28279
28287
|
const displays = await import_screenshot_desktop.default.listDisplays();
|
|
28280
28288
|
return displays.map((d, index) => ({
|
|
28281
28289
|
id: typeof d.id === "number" ? d.id : index,
|
|
28282
|
-
name:
|
|
28290
|
+
name: friendlyDisplayName(d.name, index),
|
|
28283
28291
|
primary: Boolean(d.primary) || index === 0
|
|
28284
28292
|
}));
|
|
28285
28293
|
} catch (error) {
|
|
@@ -28446,7 +28454,7 @@ function buildWelcome(ctx) {
|
|
|
28446
28454
|
return {
|
|
28447
28455
|
type: "welcome",
|
|
28448
28456
|
protocol: PROTOCOL_VERSION,
|
|
28449
|
-
agent: { version: AGENT_VERSION, platform: (0, import_node_os2.platform)(), hostname: (0, import_node_os2.hostname)() },
|
|
28457
|
+
agent: { version: AGENT_VERSION, platform: (0, import_node_os2.platform)(), hostname: (0, import_node_os2.hostname)(), hdr: ctx.hdrActive || void 0 },
|
|
28450
28458
|
screen: ctx.screen,
|
|
28451
28459
|
capture: { fps: ctx.config.fps, quality: ctx.config.quality, maxWidth: ctx.config.maxWidth },
|
|
28452
28460
|
capabilities: {
|
|
@@ -28541,11 +28549,96 @@ async function dispatch(ctx, msg, controller) {
|
|
|
28541
28549
|
}
|
|
28542
28550
|
|
|
28543
28551
|
// src/capture/encoder.ts
|
|
28544
|
-
var
|
|
28552
|
+
var import_node_child_process3 = require("node:child_process");
|
|
28545
28553
|
var import_node_dgram = require("node:dgram");
|
|
28546
28554
|
var import_node_util2 = require("node:util");
|
|
28547
28555
|
var import_ffmpeg_static = __toESM(require("ffmpeg-static"), 1);
|
|
28548
|
-
|
|
28556
|
+
|
|
28557
|
+
// src/capture/hdr-win.ts
|
|
28558
|
+
var import_node_child_process2 = require("node:child_process");
|
|
28559
|
+
var cached = null;
|
|
28560
|
+
var inflight = null;
|
|
28561
|
+
function invalidateHdrCache() {
|
|
28562
|
+
cached = null;
|
|
28563
|
+
}
|
|
28564
|
+
async function windowsHdrState(maxAgeMs = 15e3) {
|
|
28565
|
+
if (process.platform !== "win32") return null;
|
|
28566
|
+
if (cached && Date.now() - cached.at < maxAgeMs) return cached.state;
|
|
28567
|
+
if (inflight) return inflight;
|
|
28568
|
+
inflight = probe().then((state) => {
|
|
28569
|
+
if (state) cached = { state, at: Date.now() };
|
|
28570
|
+
return state;
|
|
28571
|
+
}).finally(() => {
|
|
28572
|
+
inflight = null;
|
|
28573
|
+
});
|
|
28574
|
+
return inflight;
|
|
28575
|
+
}
|
|
28576
|
+
async function probe() {
|
|
28577
|
+
return new Promise((resolve) => {
|
|
28578
|
+
(0, import_node_child_process2.execFile)(
|
|
28579
|
+
"powershell",
|
|
28580
|
+
["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", PROBE_PS],
|
|
28581
|
+
{ timeout: 8e3, windowsHide: true },
|
|
28582
|
+
(err, stdout) => {
|
|
28583
|
+
if (err) {
|
|
28584
|
+
log.debug("hdr probe failed:", err.message);
|
|
28585
|
+
return resolve(null);
|
|
28586
|
+
}
|
|
28587
|
+
let supported = false;
|
|
28588
|
+
let active = false;
|
|
28589
|
+
let sawAny = false;
|
|
28590
|
+
for (const line of String(stdout).split(/\r?\n/)) {
|
|
28591
|
+
const m = /^([01]) ([01])$/.exec(line.trim());
|
|
28592
|
+
if (!m) continue;
|
|
28593
|
+
sawAny = true;
|
|
28594
|
+
if (m[1] === "1") supported = true;
|
|
28595
|
+
if (m[2] === "1") active = true;
|
|
28596
|
+
}
|
|
28597
|
+
resolve(sawAny ? { supported, active } : null);
|
|
28598
|
+
}
|
|
28599
|
+
);
|
|
28600
|
+
});
|
|
28601
|
+
}
|
|
28602
|
+
var PROBE_PS = `
|
|
28603
|
+
$src = @'
|
|
28604
|
+
using System;
|
|
28605
|
+
using System.Runtime.InteropServices;
|
|
28606
|
+
public static class WdHdr {
|
|
28607
|
+
[StructLayout(LayoutKind.Sequential)] public struct LUID { public uint Low; public int High; }
|
|
28608
|
+
[StructLayout(LayoutKind.Sequential)] public struct PATH_SOURCE { public LUID adapterId; public uint id; public uint modeInfoIdx; public uint statusFlags; }
|
|
28609
|
+
[StructLayout(LayoutKind.Sequential)] public struct RATIONAL { public uint num; public uint den; }
|
|
28610
|
+
[StructLayout(LayoutKind.Sequential)] public struct PATH_TARGET { public LUID adapterId; public uint id; public uint modeInfoIdx; public uint outputTechnology; public uint rotation; public uint scaling; public RATIONAL refreshRate; public uint scanLineOrdering; public int targetAvailable; public uint statusFlags; }
|
|
28611
|
+
[StructLayout(LayoutKind.Sequential)] public struct PATH_INFO { public PATH_SOURCE sourceInfo; public PATH_TARGET targetInfo; public uint flags; }
|
|
28612
|
+
[StructLayout(LayoutKind.Sequential, Size = 64)] public struct MODE_INFO { public uint infoType; public uint id; public LUID adapterId; }
|
|
28613
|
+
[StructLayout(LayoutKind.Sequential)] public struct DEVICE_INFO_HEADER { public uint type; public uint size; public LUID adapterId; public uint id; }
|
|
28614
|
+
[StructLayout(LayoutKind.Sequential)] public struct GET_ADVANCED_COLOR_INFO { public DEVICE_INFO_HEADER header; public uint value; public uint colorEncoding; public uint bitsPerColorChannel; }
|
|
28615
|
+
[DllImport("user32.dll")] public static extern int GetDisplayConfigBufferSizes(uint flags, out uint numPaths, out uint numModes);
|
|
28616
|
+
[DllImport("user32.dll")] public static extern int QueryDisplayConfig(uint flags, ref uint numPaths, [Out] PATH_INFO[] paths, ref uint numModes, [Out] MODE_INFO[] modes, IntPtr topologyId);
|
|
28617
|
+
[DllImport("user32.dll")] public static extern int DisplayConfigGetDeviceInfo(ref GET_ADVANCED_COLOR_INFO info);
|
|
28618
|
+
public static void Probe() {
|
|
28619
|
+
uint np, nm;
|
|
28620
|
+
if (GetDisplayConfigBufferSizes(2, out np, out nm) != 0) return;
|
|
28621
|
+
var paths = new PATH_INFO[np];
|
|
28622
|
+
var modes = new MODE_INFO[nm];
|
|
28623
|
+
if (QueryDisplayConfig(2, ref np, paths, ref nm, modes, IntPtr.Zero) != 0) return;
|
|
28624
|
+
for (int i = 0; i < np; i++) {
|
|
28625
|
+
var q = new GET_ADVANCED_COLOR_INFO();
|
|
28626
|
+
q.header.type = 9;
|
|
28627
|
+
q.header.size = (uint)Marshal.SizeOf(typeof(GET_ADVANCED_COLOR_INFO));
|
|
28628
|
+
q.header.adapterId = paths[i].targetInfo.adapterId;
|
|
28629
|
+
q.header.id = paths[i].targetInfo.id;
|
|
28630
|
+
if (DisplayConfigGetDeviceInfo(ref q) == 0)
|
|
28631
|
+
Console.WriteLine(((q.value & 1) != 0 ? "1" : "0") + " " + ((q.value & 2) != 0 ? "1" : "0"));
|
|
28632
|
+
}
|
|
28633
|
+
}
|
|
28634
|
+
}
|
|
28635
|
+
'@
|
|
28636
|
+
Add-Type -TypeDefinition $src -Language CSharp
|
|
28637
|
+
[WdHdr]::Probe()
|
|
28638
|
+
`.trim();
|
|
28639
|
+
|
|
28640
|
+
// src/capture/encoder.ts
|
|
28641
|
+
var exec2 = (0, import_node_util2.promisify)(import_node_child_process3.execFile);
|
|
28549
28642
|
var VIDEO_PAYLOAD_TYPE = 96;
|
|
28550
28643
|
var VIDEO_CLOCK_RATE = 9e4;
|
|
28551
28644
|
var encoderName;
|
|
@@ -28597,29 +28690,55 @@ async function avScreenIndex(displayId) {
|
|
|
28597
28690
|
const map = await avScreenMapPromise;
|
|
28598
28691
|
return map.get(displayId) ?? map.get(0) ?? null;
|
|
28599
28692
|
}
|
|
28600
|
-
|
|
28693
|
+
var HDR_TONEMAP = ",tonemap=tonemap=hable:desat=0:peak=10,zscale=tin=linear:pin=bt709:t=bt709:p=bt709:m=bt709:r=tv,format=yuv420p";
|
|
28694
|
+
async function captureDeviceFor(displayIndex, fps, winFallback) {
|
|
28601
28695
|
const r = String(Math.max(1, Math.min(60, Math.round(fps))));
|
|
28696
|
+
const ts2 = ["-fflags", "+genpts", "-use_wallclock_as_timestamps", "1"];
|
|
28602
28697
|
if (process.platform === "darwin") {
|
|
28603
28698
|
const idx = await avScreenIndex(displayIndex);
|
|
28604
28699
|
if (idx == null) return null;
|
|
28605
|
-
return
|
|
28700
|
+
return {
|
|
28701
|
+
inputArgs: [...ts2, "-f", "avfoundation", "-capture_cursor", "1", "-pixel_format", "nv12", "-framerate", r, "-i", `${idx}:none`],
|
|
28702
|
+
hwPrefix: "",
|
|
28703
|
+
postFilter: ""
|
|
28704
|
+
};
|
|
28606
28705
|
}
|
|
28607
28706
|
if (process.platform === "win32") {
|
|
28608
|
-
|
|
28707
|
+
if (winFallback) {
|
|
28708
|
+
return { inputArgs: [...ts2, "-f", "gdigrab", "-framerate", r, "-i", "desktop"], hwPrefix: "", postFilter: "" };
|
|
28709
|
+
}
|
|
28710
|
+
const idx = Math.max(0, Math.round(displayIndex));
|
|
28711
|
+
const hdr = (await windowsHdrState())?.active === true;
|
|
28712
|
+
if (hdr) {
|
|
28713
|
+
return {
|
|
28714
|
+
inputArgs: ["-f", "lavfi", "-i", `ddagrab=output_idx=${idx}:framerate=${r}:draw_mouse=1:output_fmt=rgbaf16`],
|
|
28715
|
+
// gbrpf32le BEFORE the split/crop/scale: swscale handles planar-float scaling everywhere,
|
|
28716
|
+
// while direct rgbaf16 scaling is spottier across builds. Tone-mapping itself stays
|
|
28717
|
+
// per-branch AFTER the scale (postFilter) so the heavy math runs at ≤maxWidth, not 4K.
|
|
28718
|
+
hwPrefix: "hwdownload,format=rgbaf16,format=gbrpf32le,",
|
|
28719
|
+
postFilter: HDR_TONEMAP,
|
|
28720
|
+
winHdr: true
|
|
28721
|
+
};
|
|
28722
|
+
}
|
|
28723
|
+
return {
|
|
28724
|
+
inputArgs: ["-f", "lavfi", "-i", `ddagrab=output_idx=${idx}:framerate=${r}:draw_mouse=1`],
|
|
28725
|
+
hwPrefix: "hwdownload,format=bgra,",
|
|
28726
|
+
postFilter: ""
|
|
28727
|
+
};
|
|
28609
28728
|
}
|
|
28610
28729
|
if (process.platform === "linux") {
|
|
28611
|
-
return ["-f", "x11grab", "-framerate", r, "-i", process.env.DISPLAY || ":0.0"];
|
|
28730
|
+
return { inputArgs: [...ts2, "-f", "x11grab", "-framerate", r, "-i", process.env.DISPLAY || ":0.0"], hwPrefix: "", postFilter: "" };
|
|
28612
28731
|
}
|
|
28613
28732
|
return null;
|
|
28614
28733
|
}
|
|
28615
28734
|
function videoFilter(crop, maxWidth) {
|
|
28616
|
-
const scale = `scale=min(${Math.round(maxWidth)}\\,iw):-2`;
|
|
28735
|
+
const scale = `scale=trunc(min(${Math.round(maxWidth)}\\,iw)/2)*2:-2`;
|
|
28617
28736
|
if (isFull(crop)) return scale;
|
|
28618
28737
|
const c = crop;
|
|
28619
28738
|
return `crop=iw*${c.w.toFixed(4)}:ih*${c.h.toFixed(4)}:iw*${c.x.toFixed(4)}:ih*${c.y.toFixed(4)},${scale}`;
|
|
28620
28739
|
}
|
|
28621
28740
|
function overviewFilter(ov) {
|
|
28622
|
-
return `scale=min(${Math.round(ov.width)}\\,iw):-2,fps=${Math.max(1, Math.round(ov.fps))}`;
|
|
28741
|
+
return `scale=trunc(min(${Math.round(ov.width)}\\,iw)/2)*2:-2,fps=${Math.max(1, Math.round(ov.fps))}`;
|
|
28623
28742
|
}
|
|
28624
28743
|
function sameCrop(a, b) {
|
|
28625
28744
|
if (isFull(a) && isFull(b)) return true;
|
|
@@ -28650,12 +28769,21 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28650
28769
|
spawnAt = 0;
|
|
28651
28770
|
lastPacketAt = 0;
|
|
28652
28771
|
everGotPacket = false;
|
|
28772
|
+
/** Whether the CURRENT spawn has delivered any packet — distinguishes a capture that "went
|
|
28773
|
+
* silent" from one that never started (the avfoundation zero-frame deadlock) in stall logs. */
|
|
28774
|
+
spawnGotPacket = false;
|
|
28775
|
+
/** Consecutive stall-restarts whose spawn never produced a frame (resets on any packet). */
|
|
28776
|
+
framelessRestarts = 0;
|
|
28777
|
+
/** Resolves the restart loop's bounded wait as soon as the current spawn's first packet lands. */
|
|
28778
|
+
firstPacketWaiter = null;
|
|
28653
28779
|
deadRestarts = 0;
|
|
28654
28780
|
erroredOut = false;
|
|
28655
28781
|
stopped = false;
|
|
28656
28782
|
restarting = false;
|
|
28657
28783
|
/** Config the running ffmpeg was built from; lets a restart detect that `cfg` moved during it. */
|
|
28658
28784
|
appliedCfg = null;
|
|
28785
|
+
/** Windows only: set once ddagrab fails to produce a frame, switching capture to gdigrab. */
|
|
28786
|
+
winCaptureFallback = false;
|
|
28659
28787
|
/** Don't declare a not-yet-producing capture dead until it's had this long to start up. */
|
|
28660
28788
|
static FIRST_FRAME_GRACE_MS = 6e3;
|
|
28661
28789
|
/** Once it HAS produced frames, restart if it goes silent this long (display sleep, etc.). */
|
|
@@ -28674,7 +28802,7 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28674
28802
|
this.activeListener = cb;
|
|
28675
28803
|
}
|
|
28676
28804
|
async start() {
|
|
28677
|
-
await this.
|
|
28805
|
+
await this.restart();
|
|
28678
28806
|
}
|
|
28679
28807
|
/** Change the zoom crop or target display. A no-op when nothing changed, so a redundant viewport
|
|
28680
28808
|
* echo never restarts ffmpeg. If a restart is already running (the controller settled a new zoom
|
|
@@ -28695,23 +28823,35 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28695
28823
|
if (this.stopped) return false;
|
|
28696
28824
|
const cfg = this.cfg;
|
|
28697
28825
|
const enc = await pickH264Encoder();
|
|
28698
|
-
const capture = await captureDeviceFor(cfg.displayIndex, cfg.fps);
|
|
28826
|
+
const capture = await captureDeviceFor(cfg.displayIndex, cfg.fps, this.winCaptureFallback);
|
|
28699
28827
|
if (!enc || !import_ffmpeg_static.default || !capture) {
|
|
28700
28828
|
if (!capture) log.warn("no direct screen-capture device on this platform \u2014 screen sharing unavailable");
|
|
28701
28829
|
this.fail();
|
|
28702
28830
|
return false;
|
|
28703
28831
|
}
|
|
28704
28832
|
const ov = !isFull(cfg.crop) && cfg.overview ? cfg.overview : null;
|
|
28705
|
-
let sock;
|
|
28833
|
+
let sock = null;
|
|
28706
28834
|
let sockOv = null;
|
|
28707
28835
|
try {
|
|
28708
28836
|
sock = await bindUdp();
|
|
28709
|
-
if (ov)
|
|
28837
|
+
if (ov) {
|
|
28838
|
+
sockOv = await bindUdp();
|
|
28839
|
+
for (let tries = 0; tries < 4; tries++) {
|
|
28840
|
+
const p = sock.address().port;
|
|
28841
|
+
const q = sockOv.address().port;
|
|
28842
|
+
if (Math.abs(p - q) > 1) break;
|
|
28843
|
+
const again = await bindUdp();
|
|
28844
|
+
closeQuietly(sockOv);
|
|
28845
|
+
sockOv = again;
|
|
28846
|
+
}
|
|
28847
|
+
}
|
|
28710
28848
|
} catch {
|
|
28849
|
+
if (sock) closeQuietly(sock);
|
|
28711
28850
|
if (sockOv) closeQuietly(sockOv);
|
|
28712
28851
|
this.fail();
|
|
28713
28852
|
return false;
|
|
28714
28853
|
}
|
|
28854
|
+
if (!sock) return false;
|
|
28715
28855
|
if (this.stopped) {
|
|
28716
28856
|
closeQuietly(sock);
|
|
28717
28857
|
if (sockOv) closeQuietly(sockOv);
|
|
@@ -28720,7 +28860,7 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28720
28860
|
const port = sock.address().port;
|
|
28721
28861
|
const portOv = sockOv ? sockOv.address().port : 0;
|
|
28722
28862
|
const preset = enc === "libx264" ? ["-preset", "ultrafast", "-tune", "zerolatency"] : ["-realtime", "1"];
|
|
28723
|
-
const input = ["-hide_banner", "-loglevel", "error",
|
|
28863
|
+
const input = ["-hide_banner", "-loglevel", "error", ...capture.inputArgs, "-an"];
|
|
28724
28864
|
const h264Out = (kbps, gop, ssrc, outPort) => [
|
|
28725
28865
|
"-fps_mode",
|
|
28726
28866
|
"vfr",
|
|
@@ -28731,6 +28871,12 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28731
28871
|
"yuv420p",
|
|
28732
28872
|
"-g",
|
|
28733
28873
|
String(gop),
|
|
28874
|
+
// Also key on WALL-CLOCK time, not just frame count: with VFR a static screen yields few output
|
|
28875
|
+
// frames, so a frames-based GOP can leave a lost IDR unrepaired "until something moves". Forcing
|
|
28876
|
+
// a keyframe ~1s (by PTS, which advances via -use_wallclock_as_timestamps) guarantees the client
|
|
28877
|
+
// can always re-sync within ~1s even with no PLI and a frozen desktop.
|
|
28878
|
+
"-force_key_frames",
|
|
28879
|
+
"expr:gte(t,n_forced*1)",
|
|
28734
28880
|
"-b:v",
|
|
28735
28881
|
kbpsArg(kbps),
|
|
28736
28882
|
"-maxrate",
|
|
@@ -28741,26 +28887,30 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28741
28887
|
String(VIDEO_PAYLOAD_TYPE),
|
|
28742
28888
|
"-ssrc",
|
|
28743
28889
|
ssrc,
|
|
28890
|
+
// buffer_size = SO_SNDBUF: keyframe bursts must not overflow the send side either (the
|
|
28891
|
+
// receive side is widened in bindUdp — see the black-bottom-of-keyframe note there).
|
|
28744
28892
|
"-f",
|
|
28745
28893
|
"rtp",
|
|
28746
|
-
`rtp://127.0.0.1:${outPort}?pkt_size=1200`
|
|
28894
|
+
`rtp://127.0.0.1:${outPort}?pkt_size=1200&buffer_size=1048576`
|
|
28747
28895
|
];
|
|
28748
28896
|
const mainGop = Math.max(10, Math.round(cfg.fps));
|
|
28749
28897
|
const args = ov ? [
|
|
28750
28898
|
...input,
|
|
28751
28899
|
// The full captured frame fans into two encodes: [m] the sharp crop, [o] the small overview.
|
|
28900
|
+
// hwPrefix (ddagrab) downloads once, before the split, so both branches get RAM frames.
|
|
28901
|
+
// postFilter (HDR tone map) runs per-branch AFTER the scale — cheap at output size.
|
|
28752
28902
|
"-filter_complex",
|
|
28753
|
-
`[0:v]split=2[m][o];[m]${videoFilter(cfg.crop, cfg.maxWidth)}[mainout];[o]${overviewFilter(ov)}[ovout]`,
|
|
28903
|
+
`[0:v]${capture.hwPrefix}split=2[m][o];[m]${videoFilter(cfg.crop, cfg.maxWidth)}${capture.postFilter}[mainout];[o]${overviewFilter(ov)}${capture.postFilter}[ovout]`,
|
|
28754
28904
|
"-map",
|
|
28755
28905
|
"[mainout]",
|
|
28756
28906
|
...h264Out(cfg.kbps, mainGop, "1", port),
|
|
28757
28907
|
"-map",
|
|
28758
28908
|
"[ovout]",
|
|
28759
28909
|
...h264Out(ov.kbps, Math.max(1, Math.round(ov.fps)), "2", portOv)
|
|
28760
|
-
] : [...input, "-vf", videoFilter(cfg.crop, cfg.maxWidth)
|
|
28910
|
+
] : [...input, "-vf", `${capture.hwPrefix}${videoFilter(cfg.crop, cfg.maxWidth)}${capture.postFilter}`, ...h264Out(cfg.kbps, mainGop, "1", port)];
|
|
28761
28911
|
let proc;
|
|
28762
28912
|
try {
|
|
28763
|
-
proc = (0,
|
|
28913
|
+
proc = (0, import_node_child_process3.spawn)(import_ffmpeg_static.default, args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
28764
28914
|
} catch (error) {
|
|
28765
28915
|
log.warn("video encoder failed to start:", error.message);
|
|
28766
28916
|
closeQuietly(sock);
|
|
@@ -28774,14 +28924,18 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28774
28924
|
this.appliedCfg = cfg;
|
|
28775
28925
|
this.spawnAt = Date.now();
|
|
28776
28926
|
this.lastPacketAt = Date.now();
|
|
28927
|
+
this.spawnGotPacket = false;
|
|
28777
28928
|
const spawnCrop = cfg.crop;
|
|
28778
28929
|
let firstPacket = true;
|
|
28779
28930
|
sock.on("message", (msg) => {
|
|
28780
28931
|
this.lastPacketAt = Date.now();
|
|
28781
28932
|
this.everGotPacket = true;
|
|
28933
|
+
this.spawnGotPacket = true;
|
|
28782
28934
|
this.deadRestarts = 0;
|
|
28783
28935
|
if (firstPacket) {
|
|
28784
28936
|
firstPacket = false;
|
|
28937
|
+
this.framelessRestarts = 0;
|
|
28938
|
+
this.firstPacketWaiter?.();
|
|
28785
28939
|
this.activeListener?.(spawnCrop);
|
|
28786
28940
|
}
|
|
28787
28941
|
this.listener?.(msg);
|
|
@@ -28797,13 +28951,26 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28797
28951
|
for (const line of d.toString().split("\n")) {
|
|
28798
28952
|
const t = line.trim();
|
|
28799
28953
|
if (!t || /^objc\[|NSKVONotifying|not linked into application/.test(t)) continue;
|
|
28954
|
+
if (process.platform === "win32" && /AcquireNextFrame failed|Output parameters changed|Requested output format unavailable/i.test(t)) {
|
|
28955
|
+
invalidateHdrCache();
|
|
28956
|
+
}
|
|
28800
28957
|
log.debug("ffmpeg:", t.slice(0, 200));
|
|
28801
28958
|
}
|
|
28802
28959
|
});
|
|
28803
28960
|
proc.on("error", (e) => log.warn("video encoder process error:", e.message));
|
|
28961
|
+
proc.on("exit", (code, sig) => {
|
|
28962
|
+
if (this.proc !== proc || this.stopped) return;
|
|
28963
|
+
log.debug(`ffmpeg exited unexpectedly (${code ?? sig ?? "?"})`);
|
|
28964
|
+
const delay2 = Date.now() - this.spawnAt < 2e3 ? 2e3 : 400;
|
|
28965
|
+
const t = setTimeout(() => {
|
|
28966
|
+
if (this.proc === proc && !this.stopped) void this.restart();
|
|
28967
|
+
}, delay2);
|
|
28968
|
+
t.unref?.();
|
|
28969
|
+
});
|
|
28804
28970
|
this.startHealthMonitor();
|
|
28971
|
+
const cropDesc = isFull(cfg.crop) ? "full desktop" : `zoomed ${cfg.crop.x.toFixed(2)},${cfg.crop.y.toFixed(2)} ${cfg.crop.w.toFixed(2)}x${cfg.crop.h.toFixed(2)}`;
|
|
28805
28972
|
log.debug(
|
|
28806
|
-
`video: H.264 capture started (${enc}, ${kbpsArg(cfg.kbps)}@${cfg.fps}fps, ${
|
|
28973
|
+
`video: H.264 capture started (${enc}, ${kbpsArg(cfg.kbps)}@${cfg.fps}fps, ${cropDesc}${ov ? " + overview" : ""}${capture.winHdr ? ", HDR desktop tone-mapped to SDR" : ""})`
|
|
28807
28974
|
);
|
|
28808
28975
|
return true;
|
|
28809
28976
|
}
|
|
@@ -28817,13 +28984,23 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28817
28984
|
if (this.stopped || this.restarting || !this.proc) return;
|
|
28818
28985
|
const now = Date.now();
|
|
28819
28986
|
if (this.everGotPacket) {
|
|
28820
|
-
|
|
28821
|
-
|
|
28987
|
+
const stallMs = this.spawnGotPacket ? _ScreenCaptureSession.STALL_MS : Math.min(4e3 + this.framelessRestarts * 1500, 1e4);
|
|
28988
|
+
if (now - this.lastPacketAt > stallMs) {
|
|
28989
|
+
if (!this.spawnGotPacket) this.framelessRestarts += 1;
|
|
28990
|
+
log.debug(
|
|
28991
|
+
`screen capture stalled \u2014 restarting (${this.spawnGotPacket ? "went silent" : `spawn never produced a frame after ${Math.round(stallMs / 1e3)}s`})`
|
|
28992
|
+
);
|
|
28822
28993
|
void this.restart();
|
|
28823
28994
|
}
|
|
28824
28995
|
return;
|
|
28825
28996
|
}
|
|
28826
28997
|
if (now - this.spawnAt < _ScreenCaptureSession.FIRST_FRAME_GRACE_MS) return;
|
|
28998
|
+
if (process.platform === "win32" && !this.winCaptureFallback) {
|
|
28999
|
+
this.winCaptureFallback = true;
|
|
29000
|
+
log.debug("screen capture: ddagrab produced no frame \u2014 falling back to gdigrab");
|
|
29001
|
+
void this.restart();
|
|
29002
|
+
return;
|
|
29003
|
+
}
|
|
28827
29004
|
if (++this.deadRestarts > _ScreenCaptureSession.MAX_DEAD_RESTARTS) {
|
|
28828
29005
|
this.kill();
|
|
28829
29006
|
this.fail();
|
|
@@ -28838,13 +29015,58 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28838
29015
|
this.restarting = true;
|
|
28839
29016
|
try {
|
|
28840
29017
|
do {
|
|
28841
|
-
this.
|
|
29018
|
+
await this.killAndWait();
|
|
28842
29019
|
if (!await this.spawn()) break;
|
|
29020
|
+
await this.waitForFirstPacket(3e3);
|
|
28843
29021
|
} while (!this.stopped && this.appliedCfg !== null && !sameConfig(this.cfg, this.appliedCfg));
|
|
28844
29022
|
} finally {
|
|
28845
29023
|
this.restarting = false;
|
|
28846
29024
|
}
|
|
28847
29025
|
}
|
|
29026
|
+
/**
|
|
29027
|
+
* Kill the current ffmpeg and WAIT until it has actually EXITED — plus a short macOS beat for
|
|
29028
|
+
* WindowServer to release its screen-capture session — before the caller spawns the next one.
|
|
29029
|
+
*
|
|
29030
|
+
* kill() alone is fire-and-forget: signal delivery and the OS-side capture teardown are both
|
|
29031
|
+
* asynchronous, so under re-crop churn (pan swipe after swipe) the next ffmpeg starts while the
|
|
29032
|
+
* old capture session is still registered — the documented avfoundation deadlock where BOTH
|
|
29033
|
+
* captures yield zero frames. Worse, the health monitor's recovery restart repeats the same
|
|
29034
|
+
* unserialized kill→spawn every 5s, re-triggering the overlap each time: the observed
|
|
29035
|
+
* "stalled — restarting" loop with no "crop live" ever following, dead for tens of seconds.
|
|
29036
|
+
* Serializing exit→settle→spawn removes the overlap entirely, at ~150ms per re-crop.
|
|
29037
|
+
*/
|
|
29038
|
+
async killAndWait() {
|
|
29039
|
+
const proc = this.proc;
|
|
29040
|
+
const exited = proc && proc.exitCode === null && proc.signalCode === null ? new Promise((resolve) => {
|
|
29041
|
+
const cap = setTimeout(resolve, 2500);
|
|
29042
|
+
cap.unref?.();
|
|
29043
|
+
proc.once("exit", () => {
|
|
29044
|
+
clearTimeout(cap);
|
|
29045
|
+
resolve();
|
|
29046
|
+
});
|
|
29047
|
+
}) : null;
|
|
29048
|
+
this.kill();
|
|
29049
|
+
if (exited) {
|
|
29050
|
+
await exited;
|
|
29051
|
+
if (process.platform === "darwin") await new Promise((r) => setTimeout(r, 150));
|
|
29052
|
+
}
|
|
29053
|
+
}
|
|
29054
|
+
/** Bounded wait for the current spawn's first RTP packet (resolves immediately if it already
|
|
29055
|
+
* produced, on stop, or at the cap). See the restart loop for why replacing a capture that
|
|
29056
|
+
* hasn't produced yet is dangerous on macOS. */
|
|
29057
|
+
waitForFirstPacket(capMs) {
|
|
29058
|
+
if (this.spawnGotPacket || this.stopped) return Promise.resolve();
|
|
29059
|
+
return new Promise((resolve) => {
|
|
29060
|
+
const done = () => {
|
|
29061
|
+
clearTimeout(cap);
|
|
29062
|
+
if (this.firstPacketWaiter === done) this.firstPacketWaiter = null;
|
|
29063
|
+
resolve();
|
|
29064
|
+
};
|
|
29065
|
+
const cap = setTimeout(done, capMs);
|
|
29066
|
+
cap.unref?.();
|
|
29067
|
+
this.firstPacketWaiter = done;
|
|
29068
|
+
});
|
|
29069
|
+
}
|
|
28848
29070
|
fail() {
|
|
28849
29071
|
if (this.erroredOut) return;
|
|
28850
29072
|
this.erroredOut = true;
|
|
@@ -28858,11 +29080,22 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28858
29080
|
}
|
|
28859
29081
|
const proc = this.proc;
|
|
28860
29082
|
this.proc = null;
|
|
28861
|
-
if (proc) {
|
|
29083
|
+
if (proc && proc.exitCode === null && proc.signalCode === null) {
|
|
28862
29084
|
try {
|
|
28863
|
-
proc.kill("
|
|
29085
|
+
proc.kill("SIGTERM");
|
|
28864
29086
|
} catch {
|
|
28865
29087
|
}
|
|
29088
|
+
const hardKill = setTimeout(
|
|
29089
|
+
() => {
|
|
29090
|
+
try {
|
|
29091
|
+
proc.kill("SIGKILL");
|
|
29092
|
+
} catch {
|
|
29093
|
+
}
|
|
29094
|
+
},
|
|
29095
|
+
this.spawnGotPacket ? 1500 : 700
|
|
29096
|
+
);
|
|
29097
|
+
hardKill.unref?.();
|
|
29098
|
+
proc.once("exit", () => clearTimeout(hardKill));
|
|
28866
29099
|
}
|
|
28867
29100
|
if (this.sock) {
|
|
28868
29101
|
closeQuietly(this.sock);
|
|
@@ -28880,6 +29113,10 @@ function bindUdp() {
|
|
|
28880
29113
|
sock.once("error", reject);
|
|
28881
29114
|
sock.bind(0, "127.0.0.1", () => {
|
|
28882
29115
|
sock.removeListener("error", reject);
|
|
29116
|
+
try {
|
|
29117
|
+
sock.setRecvBufferSize(4 * 1024 * 1024);
|
|
29118
|
+
} catch {
|
|
29119
|
+
}
|
|
28883
29120
|
resolve(sock);
|
|
28884
29121
|
});
|
|
28885
29122
|
});
|
|
@@ -28989,7 +29226,15 @@ var VideoHub = class {
|
|
|
28989
29226
|
if (this.opts.onCropActive) session.onActive((crop) => this.opts.onCropActive(crop));
|
|
28990
29227
|
await session.start();
|
|
28991
29228
|
if (this.paused || this.sinks.size === 0 && this.overviewSinks.size === 0) session.stop();
|
|
28992
|
-
else
|
|
29229
|
+
else {
|
|
29230
|
+
this.session = session;
|
|
29231
|
+
void session.reconfigure({
|
|
29232
|
+
displayIndex: this.opts.displayIndex(),
|
|
29233
|
+
crop: this.crop,
|
|
29234
|
+
kbps: this.kbps,
|
|
29235
|
+
fps: this.fps
|
|
29236
|
+
});
|
|
29237
|
+
}
|
|
28993
29238
|
})().finally(() => this.starting = null);
|
|
28994
29239
|
return this.starting;
|
|
28995
29240
|
}
|
|
@@ -29125,6 +29370,9 @@ async function answerWebRtcOffer(ctx, offerSdp, onClosed, opts = {}) {
|
|
|
29125
29370
|
pc.connectionStateChange.subscribe((s) => {
|
|
29126
29371
|
if (s === "failed" || s === "closed" || s === "disconnected") teardown();
|
|
29127
29372
|
});
|
|
29373
|
+
pc.iceConnectionStateChange.subscribe((s) => {
|
|
29374
|
+
if (s === "failed" || s === "closed" || s === "disconnected") teardown();
|
|
29375
|
+
});
|
|
29128
29376
|
await pc.setRemoteDescription({ type: "offer", sdp: offerSdp });
|
|
29129
29377
|
if (offerVideoCount > 0 && canSendVideo && ctx.video) {
|
|
29130
29378
|
for (let i = 0; i < offerVideoCount; i++) {
|
|
@@ -29147,9 +29395,9 @@ async function answerWebRtcOffer(ctx, offerSdp, onClosed, opts = {}) {
|
|
|
29147
29395
|
}
|
|
29148
29396
|
};
|
|
29149
29397
|
(isOverview ? overviewSinks : sinks).push(sink);
|
|
29150
|
-
const
|
|
29151
|
-
if (authenticated)
|
|
29152
|
-
else pendingAttach.push(
|
|
29398
|
+
const run3 = () => void (isOverview ? ctx.video.attachOverview(sink) : ctx.video.attach(sink));
|
|
29399
|
+
if (authenticated) run3();
|
|
29400
|
+
else pendingAttach.push(run3);
|
|
29153
29401
|
} catch (error) {
|
|
29154
29402
|
log.warn(`video track ${i} setup failed:`, error.message);
|
|
29155
29403
|
}
|
|
@@ -29341,12 +29589,15 @@ function printSetupReminder() {
|
|
|
29341
29589
|
console.log(" Setup / permissions:");
|
|
29342
29590
|
for (const line of lines) console.log(line);
|
|
29343
29591
|
console.log("");
|
|
29592
|
+
console.log(" Troubleshooting: relaunch with --verbose (e.g. `whipdesk --verbose`) for detailed");
|
|
29593
|
+
console.log(" capture/network logs \u2014 attach them when reporting an issue.");
|
|
29594
|
+
console.log("");
|
|
29344
29595
|
}
|
|
29345
29596
|
|
|
29346
29597
|
// src/server.ts
|
|
29347
29598
|
var import_node_http = require("node:http");
|
|
29348
|
-
var
|
|
29349
|
-
var
|
|
29599
|
+
var import_node_fs10 = require("node:fs");
|
|
29600
|
+
var import_node_path12 = require("node:path");
|
|
29350
29601
|
var import_node_url3 = require("node:url");
|
|
29351
29602
|
var import_express = __toESM(require_express2(), 1);
|
|
29352
29603
|
|
|
@@ -29405,12 +29656,12 @@ var ScreenCapturer = class {
|
|
|
29405
29656
|
};
|
|
29406
29657
|
|
|
29407
29658
|
// src/input/applescript.ts
|
|
29408
|
-
var
|
|
29659
|
+
var import_node_child_process4 = require("node:child_process");
|
|
29409
29660
|
var import_node_util3 = require("node:util");
|
|
29410
|
-
var exec3 = (0, import_node_util3.promisify)(
|
|
29661
|
+
var exec3 = (0, import_node_util3.promisify)(import_node_child_process4.execFile);
|
|
29411
29662
|
async function createAppleScriptBackend() {
|
|
29412
29663
|
if (process.platform !== "darwin") return null;
|
|
29413
|
-
const
|
|
29664
|
+
const run3 = (script) => exec3("osascript", ["-e", script]).then(() => void 0);
|
|
29414
29665
|
return {
|
|
29415
29666
|
name: "applescript (keyboard-only)",
|
|
29416
29667
|
canMouse: false,
|
|
@@ -29431,17 +29682,17 @@ async function createAppleScriptBackend() {
|
|
|
29431
29682
|
async scroll() {
|
|
29432
29683
|
},
|
|
29433
29684
|
async typeText(text, submit) {
|
|
29434
|
-
if (text) await
|
|
29435
|
-
if (submit) await
|
|
29685
|
+
if (text) await run3(`tell application "System Events" to keystroke ${asAppleString(text)}`);
|
|
29686
|
+
if (submit) await run3(`tell application "System Events" to key code 36`);
|
|
29436
29687
|
},
|
|
29437
29688
|
async keyTap(name, modifiers = []) {
|
|
29438
29689
|
const using = modifiers.map(modifierClause).filter((m) => m !== null).join(", ");
|
|
29439
29690
|
const usingClause = using ? ` using {${using}}` : "";
|
|
29440
29691
|
const code = KEY_CODES[name.toLowerCase()];
|
|
29441
29692
|
if (code !== void 0) {
|
|
29442
|
-
await
|
|
29693
|
+
await run3(`tell application "System Events" to key code ${code}${usingClause}`);
|
|
29443
29694
|
} else if (name.length === 1) {
|
|
29444
|
-
await
|
|
29695
|
+
await run3(`tell application "System Events" to keystroke ${asAppleString(name)}${usingClause}`);
|
|
29445
29696
|
}
|
|
29446
29697
|
}
|
|
29447
29698
|
};
|
|
@@ -29805,7 +30056,7 @@ function meanDiff(a, b) {
|
|
|
29805
30056
|
}
|
|
29806
30057
|
|
|
29807
30058
|
// src/presence.ts
|
|
29808
|
-
var
|
|
30059
|
+
var import_node_child_process5 = require("node:child_process");
|
|
29809
30060
|
var Presence = class {
|
|
29810
30061
|
watchers = 0;
|
|
29811
30062
|
start() {
|
|
@@ -29815,7 +30066,7 @@ var Presence = class {
|
|
|
29815
30066
|
if (process.platform !== "darwin") return;
|
|
29816
30067
|
const escape2 = (s) => s.replace(/[\\"]/g, "\\$&");
|
|
29817
30068
|
const script = `display notification "${escape2(body)}" with title "${escape2(title)}"`;
|
|
29818
|
-
(0,
|
|
30069
|
+
(0, import_node_child_process5.execFile)("osascript", ["-e", script], () => void 0);
|
|
29819
30070
|
}
|
|
29820
30071
|
setTitle(text) {
|
|
29821
30072
|
if (process.stdout.isTTY) process.stdout.write(`\x1B]2;${text}\x07`);
|
|
@@ -29847,7 +30098,7 @@ var Presence = class {
|
|
|
29847
30098
|
};
|
|
29848
30099
|
|
|
29849
30100
|
// src/power/keep-awake.ts
|
|
29850
|
-
var
|
|
30101
|
+
var import_node_child_process6 = require("node:child_process");
|
|
29851
30102
|
var import_node_os4 = require("node:os");
|
|
29852
30103
|
var KeepAwake = class {
|
|
29853
30104
|
child = null;
|
|
@@ -29890,15 +30141,15 @@ var KeepAwake = class {
|
|
|
29890
30141
|
spawnBlocker() {
|
|
29891
30142
|
switch ((0, import_node_os4.platform)()) {
|
|
29892
30143
|
case "darwin":
|
|
29893
|
-
return (0,
|
|
30144
|
+
return (0, import_node_child_process6.spawn)("caffeinate", ["-i", "-w", String(process.pid)], { stdio: "ignore" });
|
|
29894
30145
|
case "win32":
|
|
29895
|
-
return (0,
|
|
30146
|
+
return (0, import_node_child_process6.spawn)(
|
|
29896
30147
|
"powershell",
|
|
29897
30148
|
["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", WINDOWS_KEEP_AWAKE],
|
|
29898
30149
|
{ stdio: "ignore", windowsHide: true }
|
|
29899
30150
|
);
|
|
29900
30151
|
case "linux":
|
|
29901
|
-
return (0,
|
|
30152
|
+
return (0, import_node_child_process6.spawn)(
|
|
29902
30153
|
"systemd-inhibit",
|
|
29903
30154
|
[
|
|
29904
30155
|
"--what=sleep:idle",
|
|
@@ -29923,7 +30174,7 @@ var WINDOWS_KEEP_AWAKE = [
|
|
|
29923
30174
|
].join(" ");
|
|
29924
30175
|
|
|
29925
30176
|
// src/power/wake-display.ts
|
|
29926
|
-
var
|
|
30177
|
+
var import_node_child_process7 = require("node:child_process");
|
|
29927
30178
|
var import_node_os5 = require("node:os");
|
|
29928
30179
|
var DisplayWake = class {
|
|
29929
30180
|
hold = null;
|
|
@@ -29944,21 +30195,22 @@ var DisplayWake = class {
|
|
|
29944
30195
|
this.stopHold();
|
|
29945
30196
|
}
|
|
29946
30197
|
}
|
|
29947
|
-
/** Turn the display on right now (best-effort, never throws).
|
|
29948
|
-
|
|
30198
|
+
/** Turn the display on right now (best-effort, never throws). `holdSeconds` keeps it on that
|
|
30199
|
+
* long on macOS (scheduled actions need the panel up for the whole wake→click→type sequence). */
|
|
30200
|
+
wake(holdSeconds = 5) {
|
|
29949
30201
|
try {
|
|
29950
30202
|
switch ((0, import_node_os5.platform)()) {
|
|
29951
30203
|
case "darwin":
|
|
29952
|
-
(0,
|
|
30204
|
+
(0, import_node_child_process7.spawn)("caffeinate", ["-u", "-t", String(holdSeconds)], { stdio: "ignore" }).unref();
|
|
29953
30205
|
break;
|
|
29954
30206
|
case "win32":
|
|
29955
|
-
(0,
|
|
30207
|
+
(0, import_node_child_process7.spawn)("powershell", ["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", WINDOWS_WAKE], {
|
|
29956
30208
|
stdio: "ignore",
|
|
29957
30209
|
windowsHide: true
|
|
29958
30210
|
}).unref();
|
|
29959
30211
|
break;
|
|
29960
30212
|
case "linux":
|
|
29961
|
-
(0,
|
|
30213
|
+
(0, import_node_child_process7.spawn)("sh", ["-c", "xset s reset; xset dpms force on"], { stdio: "ignore" }).unref();
|
|
29962
30214
|
break;
|
|
29963
30215
|
}
|
|
29964
30216
|
} catch (e) {
|
|
@@ -29969,10 +30221,10 @@ var DisplayWake = class {
|
|
|
29969
30221
|
try {
|
|
29970
30222
|
switch ((0, import_node_os5.platform)()) {
|
|
29971
30223
|
case "darwin":
|
|
29972
|
-
this.hold = (0,
|
|
30224
|
+
this.hold = (0, import_node_child_process7.spawn)("caffeinate", ["-d", "-w", String(process.pid)], { stdio: "ignore" });
|
|
29973
30225
|
break;
|
|
29974
30226
|
case "win32":
|
|
29975
|
-
this.hold = (0,
|
|
30227
|
+
this.hold = (0, import_node_child_process7.spawn)(
|
|
29976
30228
|
"powershell",
|
|
29977
30229
|
["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", WINDOWS_HOLD],
|
|
29978
30230
|
{ stdio: "ignore", windowsHide: true }
|
|
@@ -30024,6 +30276,37 @@ var WINDOWS_HOLD = [
|
|
|
30024
30276
|
"while ($true) { Start-Sleep -Seconds 3600 }"
|
|
30025
30277
|
].join(" ");
|
|
30026
30278
|
|
|
30279
|
+
// src/power/session-lock.ts
|
|
30280
|
+
var import_node_child_process8 = require("node:child_process");
|
|
30281
|
+
var import_node_os6 = require("node:os");
|
|
30282
|
+
async function isSessionLocked() {
|
|
30283
|
+
try {
|
|
30284
|
+
switch ((0, import_node_os6.platform)()) {
|
|
30285
|
+
case "darwin": {
|
|
30286
|
+
const out = await run("ioreg", ["-n", "Root", "-d1"]);
|
|
30287
|
+
const m = /"IOConsoleLocked"\s*=\s*(Yes|No)/.exec(out);
|
|
30288
|
+
return m ? m[1] === "Yes" : null;
|
|
30289
|
+
}
|
|
30290
|
+
case "win32": {
|
|
30291
|
+
const out = await run("tasklist", ["/FI", "IMAGENAME eq LogonUI.exe", "/NH"]);
|
|
30292
|
+
return /LogonUI\.exe/i.test(out);
|
|
30293
|
+
}
|
|
30294
|
+
default:
|
|
30295
|
+
return null;
|
|
30296
|
+
}
|
|
30297
|
+
} catch {
|
|
30298
|
+
return null;
|
|
30299
|
+
}
|
|
30300
|
+
}
|
|
30301
|
+
function run(cmd, args) {
|
|
30302
|
+
return new Promise((resolve, reject) => {
|
|
30303
|
+
(0, import_node_child_process8.execFile)(cmd, args, { timeout: 4e3, windowsHide: true }, (err, stdout) => {
|
|
30304
|
+
if (err) reject(err);
|
|
30305
|
+
else resolve(String(stdout));
|
|
30306
|
+
});
|
|
30307
|
+
});
|
|
30308
|
+
}
|
|
30309
|
+
|
|
30027
30310
|
// src/security/setup.ts
|
|
30028
30311
|
var import_node_readline = require("node:readline");
|
|
30029
30312
|
|
|
@@ -30073,9 +30356,9 @@ var PinGuard = class _PinGuard {
|
|
|
30073
30356
|
get salt() {
|
|
30074
30357
|
return this.record?.salt ?? "";
|
|
30075
30358
|
}
|
|
30076
|
-
/** Persist a new PIN (>=
|
|
30359
|
+
/** Persist a new PIN (>= 6 chars). Stores only the stretched key. */
|
|
30077
30360
|
setPin(pin) {
|
|
30078
|
-
if (pin.length <
|
|
30361
|
+
if (pin.length < 6) throw new Error("PIN must be at least 6 characters");
|
|
30079
30362
|
const salt = (0, import_node_crypto5.randomBytes)(16).toString("hex");
|
|
30080
30363
|
const key = stretch(pin, salt, ITERATIONS);
|
|
30081
30364
|
this.record = { salt, iterations: ITERATIONS, key };
|
|
@@ -30382,9 +30665,9 @@ var import_node_path9 = require("node:path");
|
|
|
30382
30665
|
|
|
30383
30666
|
// src/monitor/agents.ts
|
|
30384
30667
|
var import_node_fs7 = require("node:fs");
|
|
30385
|
-
var
|
|
30668
|
+
var import_node_os7 = require("node:os");
|
|
30386
30669
|
var import_node_path7 = require("node:path");
|
|
30387
|
-
var home = (0,
|
|
30670
|
+
var home = (0, import_node_os7.homedir)();
|
|
30388
30671
|
var has = (tokens, name) => tokens.includes(name);
|
|
30389
30672
|
function claudeProjectDir(cwd) {
|
|
30390
30673
|
return (0, import_node_path7.join)(home, ".claude", "projects", cwd.replace(/[/\\.]/g, "-"));
|
|
@@ -30445,8 +30728,8 @@ function readLastJsonlLine(path) {
|
|
|
30445
30728
|
}
|
|
30446
30729
|
function vsCodeUserDirs() {
|
|
30447
30730
|
const variants = ["Code", "Code - Insiders", "VSCodium"];
|
|
30448
|
-
if ((0,
|
|
30449
|
-
if ((0,
|
|
30731
|
+
if ((0, import_node_os7.platform)() === "darwin") return variants.map((v) => (0, import_node_path7.join)(home, "Library", "Application Support", v, "User"));
|
|
30732
|
+
if ((0, import_node_os7.platform)() === "win32") {
|
|
30450
30733
|
const appData = process.env.APPDATA || (0, import_node_path7.join)(home, "AppData", "Roaming");
|
|
30451
30734
|
return variants.map((v) => (0, import_node_path7.join)(appData, v, "User"));
|
|
30452
30735
|
}
|
|
@@ -30488,11 +30771,12 @@ function vsCodeCopilotInstalled() {
|
|
|
30488
30771
|
} catch {
|
|
30489
30772
|
}
|
|
30490
30773
|
}
|
|
30774
|
+
if (!value) value = vsCodeChatSessionDirs().length > 0;
|
|
30491
30775
|
copilotInstalledCache = { value, at: now };
|
|
30492
30776
|
return value;
|
|
30493
30777
|
}
|
|
30494
30778
|
function isVsCodeExtensionHost(cmd) {
|
|
30495
|
-
return cmd.includes("--type=extensionhost") || cmd.includes("extensionhostprocess");
|
|
30779
|
+
return cmd.includes("--type=extensionhost") || cmd.includes("extensionhostprocess") || cmd.includes("node.mojom.nodeservice") && cmd.includes("--inspect-port");
|
|
30496
30780
|
}
|
|
30497
30781
|
var AGENTS = [
|
|
30498
30782
|
{
|
|
@@ -30575,11 +30859,11 @@ function agentLabel(kind) {
|
|
|
30575
30859
|
}
|
|
30576
30860
|
|
|
30577
30861
|
// src/monitor/processes.ts
|
|
30578
|
-
var
|
|
30579
|
-
var
|
|
30580
|
-
function
|
|
30862
|
+
var import_node_child_process9 = require("node:child_process");
|
|
30863
|
+
var import_node_os8 = require("node:os");
|
|
30864
|
+
function run2(cmd, args, timeoutMs = 5e3) {
|
|
30581
30865
|
return new Promise((resolve) => {
|
|
30582
|
-
(0,
|
|
30866
|
+
(0, import_node_child_process9.execFile)(cmd, args, { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024, windowsHide: true }, (err, stdout) => {
|
|
30583
30867
|
resolve(err && !stdout ? "" : stdout.toString());
|
|
30584
30868
|
});
|
|
30585
30869
|
});
|
|
@@ -30594,7 +30878,7 @@ function tokenize(command) {
|
|
|
30594
30878
|
return out;
|
|
30595
30879
|
}
|
|
30596
30880
|
async function listUnix() {
|
|
30597
|
-
const out = await
|
|
30881
|
+
const out = await run2("ps", ["-axww", "-o", "pid=,ppid=,pcpu=,tty=,command="]);
|
|
30598
30882
|
const procs = [];
|
|
30599
30883
|
for (const line of out.split("\n")) {
|
|
30600
30884
|
const m = line.match(/^\s*(\d+)\s+(\d+)\s+([\d.]+)\s+(\S+)\s+(.*)$/);
|
|
@@ -30614,7 +30898,7 @@ async function listUnix() {
|
|
|
30614
30898
|
}
|
|
30615
30899
|
async function listWindows() {
|
|
30616
30900
|
const script = "Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CommandLine,Name | ConvertTo-Csv -NoTypeInformation";
|
|
30617
|
-
const out = await
|
|
30901
|
+
const out = await run2("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], 9e3);
|
|
30618
30902
|
const procs = [];
|
|
30619
30903
|
for (const line of out.split(/\r?\n/)) {
|
|
30620
30904
|
const m = line.match(/^"(\d+)","(\d*)",(.*)$/);
|
|
@@ -30638,16 +30922,16 @@ async function listWindows() {
|
|
|
30638
30922
|
return procs;
|
|
30639
30923
|
}
|
|
30640
30924
|
function listProcesses() {
|
|
30641
|
-
return (0,
|
|
30925
|
+
return (0, import_node_os8.platform)() === "win32" ? listWindows() : listUnix();
|
|
30642
30926
|
}
|
|
30643
30927
|
async function processCwd(pid) {
|
|
30644
|
-
const os = (0,
|
|
30928
|
+
const os = (0, import_node_os8.platform)();
|
|
30645
30929
|
if (os === "win32") return "";
|
|
30646
30930
|
if (os === "linux") {
|
|
30647
|
-
const out2 = await
|
|
30931
|
+
const out2 = await run2("readlink", [`/proc/${pid}/cwd`], 2e3);
|
|
30648
30932
|
return out2.trim();
|
|
30649
30933
|
}
|
|
30650
|
-
const out = await
|
|
30934
|
+
const out = await run2("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], 3e3);
|
|
30651
30935
|
for (const line of out.split("\n")) if (line.startsWith("n")) return line.slice(1).trim();
|
|
30652
30936
|
return "";
|
|
30653
30937
|
}
|
|
@@ -30824,8 +31108,8 @@ var SessionMonitor = class {
|
|
|
30824
31108
|
this.timer.unref?.();
|
|
30825
31109
|
}
|
|
30826
31110
|
async cwdFor(pid) {
|
|
30827
|
-
const
|
|
30828
|
-
if (
|
|
31111
|
+
const cached2 = this.cwdCache.get(pid);
|
|
31112
|
+
if (cached2 !== void 0) return cached2;
|
|
30829
31113
|
const cwd = await processCwd(pid).catch(() => "");
|
|
30830
31114
|
this.cwdCache.set(pid, cwd);
|
|
30831
31115
|
return cwd;
|
|
@@ -31036,6 +31320,30 @@ function saveAlwaysAgents(stateDir2, agents) {
|
|
|
31036
31320
|
}
|
|
31037
31321
|
}
|
|
31038
31322
|
|
|
31323
|
+
// src/timer-store.ts
|
|
31324
|
+
var import_node_fs9 = require("node:fs");
|
|
31325
|
+
var import_node_path11 = require("node:path");
|
|
31326
|
+
var FILE2 = "timers.json";
|
|
31327
|
+
function loadTimers(stateDir2) {
|
|
31328
|
+
try {
|
|
31329
|
+
const raw = (0, import_node_fs9.readFileSync)((0, import_node_path11.join)(stateDir2, FILE2), "utf8");
|
|
31330
|
+
const parsed = JSON.parse(raw);
|
|
31331
|
+
if (!Array.isArray(parsed.timers)) return [];
|
|
31332
|
+
return parsed.timers.filter(
|
|
31333
|
+
(t) => !!t && typeof t.id === "string" && typeof t.fireAtMs === "number"
|
|
31334
|
+
);
|
|
31335
|
+
} catch {
|
|
31336
|
+
return [];
|
|
31337
|
+
}
|
|
31338
|
+
}
|
|
31339
|
+
function saveTimers(stateDir2, timers) {
|
|
31340
|
+
try {
|
|
31341
|
+
(0, import_node_fs9.mkdirSync)(stateDir2, { recursive: true });
|
|
31342
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path11.join)(stateDir2, FILE2), JSON.stringify({ timers }), { mode: 384 });
|
|
31343
|
+
} catch {
|
|
31344
|
+
}
|
|
31345
|
+
}
|
|
31346
|
+
|
|
31039
31347
|
// src/util/update-check.ts
|
|
31040
31348
|
async function checkForUpdate(current) {
|
|
31041
31349
|
try {
|
|
@@ -31078,8 +31386,8 @@ function announceUpdate(latest, current, notify) {
|
|
|
31078
31386
|
}
|
|
31079
31387
|
|
|
31080
31388
|
// src/server.ts
|
|
31081
|
-
var here2 = (0,
|
|
31082
|
-
var webDist = isPackaged() ? (0,
|
|
31389
|
+
var here2 = (0, import_node_path12.dirname)((0, import_node_url3.fileURLToPath)(__whipdesk_meta_url));
|
|
31390
|
+
var webDist = isPackaged() ? (0, import_node_path12.join)(here2, "mobile-web") : (0, import_node_path12.join)(here2, "..", "..", "mobile-web", "dist");
|
|
31083
31391
|
async function startAgent() {
|
|
31084
31392
|
const config = loadConfig();
|
|
31085
31393
|
const capturer = new ScreenCapturer({ quality: config.quality, maxWidth: config.maxWidth });
|
|
@@ -31169,6 +31477,7 @@ async function startAgent() {
|
|
|
31169
31477
|
let captureFailing = false;
|
|
31170
31478
|
let viewport = { x: 0, y: 0, w: 1, h: 1 };
|
|
31171
31479
|
const timers = /* @__PURE__ */ new Map();
|
|
31480
|
+
const persistTimers = () => saveTimers(config.stateDir, [...timers.values()]);
|
|
31172
31481
|
const listTimers = () => [...timers.values()].map((t) => ({ id: t.id, label: t.label, fireAtMs: t.fireAtMs, hasAction: !!t.action }));
|
|
31173
31482
|
const broadcastTimers = () => {
|
|
31174
31483
|
const msg = { type: "timers", timers: listTimers() };
|
|
@@ -31190,13 +31499,46 @@ async function startAgent() {
|
|
|
31190
31499
|
const t = timers.get(id);
|
|
31191
31500
|
if (!t) return;
|
|
31192
31501
|
timers.delete(id);
|
|
31502
|
+
persistTimers();
|
|
31193
31503
|
broadcastTimers();
|
|
31194
31504
|
const a = t.action;
|
|
31195
31505
|
if (a) {
|
|
31196
31506
|
try {
|
|
31197
31507
|
log.info(`timer "${t.label || id}" firing${a.kind ? ` (${a.kind})` : ""}`);
|
|
31508
|
+
displayWake.wake(30);
|
|
31509
|
+
await delay(3e3);
|
|
31510
|
+
const locked = await isSessionLocked();
|
|
31511
|
+
if (locked === true) {
|
|
31512
|
+
throw new Error(
|
|
31513
|
+
"the screen is locked, so the input would go to the lock screen instead of your app. For unattended scheduled work, set the machine to not require a password immediately after display sleep."
|
|
31514
|
+
);
|
|
31515
|
+
}
|
|
31198
31516
|
if (typeof a.x === "number" && typeof a.y === "number") {
|
|
31199
|
-
|
|
31517
|
+
const pinnedId = t.displayId;
|
|
31518
|
+
let restoreInput = null;
|
|
31519
|
+
if (pinnedId !== void 0 && pinnedId !== activeDisplay) {
|
|
31520
|
+
const pinned = displays.find((d) => d.id === pinnedId);
|
|
31521
|
+
if (!pinned || pinned.width <= 0 || pinned.height <= 0) {
|
|
31522
|
+
throw new Error(`the display it was scheduled on ([${pinnedId}]) is no longer connected`);
|
|
31523
|
+
}
|
|
31524
|
+
input.setActiveDisplay({
|
|
31525
|
+
originX: pinned.originX,
|
|
31526
|
+
originY: pinned.originY,
|
|
31527
|
+
width: pinned.width,
|
|
31528
|
+
height: pinned.height
|
|
31529
|
+
});
|
|
31530
|
+
restoreInput = () => {
|
|
31531
|
+
const cur = displays.find((d) => d.id === activeDisplay);
|
|
31532
|
+
input.setActiveDisplay(
|
|
31533
|
+
cur && cur.width > 0 && cur.height > 0 ? { originX: cur.originX, originY: cur.originY, width: cur.width, height: cur.height } : null
|
|
31534
|
+
);
|
|
31535
|
+
};
|
|
31536
|
+
}
|
|
31537
|
+
try {
|
|
31538
|
+
await input.click(a.button ?? "left", false, a.x, a.y);
|
|
31539
|
+
} finally {
|
|
31540
|
+
restoreInput?.();
|
|
31541
|
+
}
|
|
31200
31542
|
if (a.kind === "key" || a.kind === "text") await delay(250);
|
|
31201
31543
|
}
|
|
31202
31544
|
if (a.kind === "key" && a.key) await input.keyTap(a.key);
|
|
@@ -31205,8 +31547,8 @@ async function startAgent() {
|
|
|
31205
31547
|
const message = error.message ?? String(error);
|
|
31206
31548
|
log.warn("timer action failed:", message);
|
|
31207
31549
|
hub.emit({
|
|
31208
|
-
title: t.label || "
|
|
31209
|
-
body: `
|
|
31550
|
+
title: t.label || "Scheduled work",
|
|
31551
|
+
body: `Time's up, but the scheduled action failed: ${message.slice(0, 120)}`,
|
|
31210
31552
|
level: "error",
|
|
31211
31553
|
source: "timer"
|
|
31212
31554
|
});
|
|
@@ -31214,12 +31556,29 @@ async function startAgent() {
|
|
|
31214
31556
|
}
|
|
31215
31557
|
}
|
|
31216
31558
|
hub.emit({
|
|
31217
|
-
title: t.label || "
|
|
31218
|
-
body: a ? "
|
|
31559
|
+
title: t.label || "Scheduled work",
|
|
31560
|
+
body: a ? "Time's up \u2014 scheduled action done." : "Time's up.",
|
|
31219
31561
|
level: "success",
|
|
31220
31562
|
source: "timer"
|
|
31221
31563
|
});
|
|
31222
31564
|
};
|
|
31565
|
+
for (const t of loadTimers(config.stateDir)) {
|
|
31566
|
+
if (t.fireAtMs <= Date.now()) {
|
|
31567
|
+
hub.emit({
|
|
31568
|
+
title: t.label || "Scheduled work",
|
|
31569
|
+
body: "This came due while WhipDesk was not running \u2014 its action was NOT performed.",
|
|
31570
|
+
level: "error",
|
|
31571
|
+
source: "timer"
|
|
31572
|
+
});
|
|
31573
|
+
} else {
|
|
31574
|
+
timers.set(t.id, t);
|
|
31575
|
+
}
|
|
31576
|
+
}
|
|
31577
|
+
persistTimers();
|
|
31578
|
+
if (timers.size > 0) {
|
|
31579
|
+
ensureTimerTicker();
|
|
31580
|
+
log.info(`restored ${timers.size} pending timer${timers.size === 1 ? "" : "s"} from disk`);
|
|
31581
|
+
}
|
|
31223
31582
|
const captureOnce = async () => {
|
|
31224
31583
|
if (regionWatcher.count === 0) return "idle";
|
|
31225
31584
|
try {
|
|
@@ -31279,6 +31638,7 @@ async function startAgent() {
|
|
|
31279
31638
|
displays,
|
|
31280
31639
|
videoAvailable,
|
|
31281
31640
|
video,
|
|
31641
|
+
hdrActive: false,
|
|
31282
31642
|
get activeDisplay() {
|
|
31283
31643
|
return activeDisplay;
|
|
31284
31644
|
},
|
|
@@ -31286,6 +31646,7 @@ async function startAgent() {
|
|
|
31286
31646
|
addController(controller) {
|
|
31287
31647
|
controllers.add(controller);
|
|
31288
31648
|
log.info(`controller connected (${controllers.size} active)`);
|
|
31649
|
+
void windowsHdrState().then((s) => ctx.hdrActive = s?.active === true);
|
|
31289
31650
|
displayWake.setActive(true);
|
|
31290
31651
|
for (const c of controllers) c.send({ type: "presence", watchers: controllers.size });
|
|
31291
31652
|
controller.send({ type: "watchers", regions: regionWatcher.list() });
|
|
@@ -31384,13 +31745,15 @@ async function startAgent() {
|
|
|
31384
31745
|
addTimer(msg) {
|
|
31385
31746
|
const fireInMs = Math.max(1e3, Math.min(msg.fireInMs, 7 * 24 * 36e5));
|
|
31386
31747
|
const fireAtMs = Date.now() + fireInMs;
|
|
31387
|
-
timers.set(msg.id, { id: msg.id, label: msg.label, fireAtMs, action: msg.action });
|
|
31748
|
+
timers.set(msg.id, { id: msg.id, label: msg.label, fireAtMs, action: msg.action, displayId: activeDisplay });
|
|
31749
|
+
persistTimers();
|
|
31388
31750
|
ensureTimerTicker();
|
|
31389
31751
|
broadcastTimers();
|
|
31390
31752
|
log.info(`timer "${msg.label}" in ${Math.round(fireInMs / 1e3)}s${msg.action ? ` (+${msg.action.kind})` : ""}`);
|
|
31391
31753
|
},
|
|
31392
31754
|
removeTimer(id) {
|
|
31393
31755
|
if (!timers.delete(id)) return;
|
|
31756
|
+
persistTimers();
|
|
31394
31757
|
broadcastTimers();
|
|
31395
31758
|
},
|
|
31396
31759
|
listTimers() {
|
|
@@ -31474,9 +31837,9 @@ async function startAgent() {
|
|
|
31474
31837
|
}
|
|
31475
31838
|
res.json({ ok: true });
|
|
31476
31839
|
});
|
|
31477
|
-
if ((0,
|
|
31840
|
+
if ((0, import_node_fs10.existsSync)(webDist)) {
|
|
31478
31841
|
app.use(import_express.default.static(webDist));
|
|
31479
|
-
app.get("/*splat", (_req, res) => res.sendFile((0,
|
|
31842
|
+
app.get("/*splat", (_req, res) => res.sendFile((0, import_node_path12.join)(webDist, "index.html")));
|
|
31480
31843
|
} else {
|
|
31481
31844
|
log.warn(`mobile-web build not found at ${webDist} \u2014 run "npm run build:web"`);
|
|
31482
31845
|
app.get(
|
|
@@ -31499,6 +31862,14 @@ async function startAgent() {
|
|
|
31499
31862
|
}
|
|
31500
31863
|
log.info(`listening on :${config.port} (capture: ${capturer.backend}, input: ${input.name})`);
|
|
31501
31864
|
log.info(pin.isSet ? "connection PIN: required" : "connection PIN: NONE (set one in a terminal)");
|
|
31865
|
+
void windowsHdrState().then((s) => {
|
|
31866
|
+
ctx.hdrActive = s?.active === true;
|
|
31867
|
+
if (s?.active) {
|
|
31868
|
+
log.warn(
|
|
31869
|
+
"HDR is ON on this desktop \u2014 the remote stream is tone-mapped to SDR and colors may look washed out. Turn HDR off if it bothers you."
|
|
31870
|
+
);
|
|
31871
|
+
}
|
|
31872
|
+
});
|
|
31502
31873
|
return { server, config, presence, keepAwake, ctx };
|
|
31503
31874
|
}
|
|
31504
31875
|
function isLevel(value) {
|
|
@@ -31562,8 +31933,8 @@ async function main() {
|
|
|
31562
31933
|
registry = await startDeviceRegistry({
|
|
31563
31934
|
rtdb,
|
|
31564
31935
|
identity,
|
|
31565
|
-
name: (0,
|
|
31566
|
-
platform: (0,
|
|
31936
|
+
name: (0, import_node_os9.hostname)(),
|
|
31937
|
+
platform: (0, import_node_os9.platform)(),
|
|
31567
31938
|
version: AGENT_VERSION,
|
|
31568
31939
|
getLan: () => ({ ip: getLanIp(), port: config.port, token: config.token })
|
|
31569
31940
|
});
|