kitowall 2.3.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +23 -2
- package/dist/core/workshop.js +293 -9
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -131,8 +131,12 @@ Commands:
|
|
|
131
131
|
we library List downloaded workshop items
|
|
132
132
|
we scan-steam Detect Steam workshop folders and list downloaded ids
|
|
133
133
|
we sync-steam Sync local Steam Workshop 431960 items into Kitsune downloads
|
|
134
|
-
we app-status Detect if Wallpaper Engine
|
|
134
|
+
we app-status Detect if Wallpaper Engine and scene engine are installed
|
|
135
135
|
we active Show current livewallpaper authority/lock state
|
|
136
|
+
we apply <id> --monitor <name> [--backend auto|mpvpaper|linux-wallpaperengine]
|
|
137
|
+
Apply live wallpaper on one monitor (auto resolves by wallpaper type)
|
|
138
|
+
we apply --map DP-1:<id1>,HDMI-A-1:<id2> [--backend auto|mpvpaper|linux-wallpaperengine]
|
|
139
|
+
Apply wallpapers in batch by monitor map
|
|
136
140
|
we stop [--monitor <name> | --all] Stop livewallpaper instances and restore previous services
|
|
137
141
|
we coexist enter|exit|status Temporarily stop/restore wallpaper rotation services
|
|
138
142
|
check [--namespace <ns>] [--json] Quick system check (no changes)
|
|
@@ -425,6 +429,23 @@ async function main() {
|
|
|
425
429
|
console.log(JSON.stringify(out, null, 2));
|
|
426
430
|
return;
|
|
427
431
|
}
|
|
432
|
+
if (action === 'apply') {
|
|
433
|
+
const mapValue = cleanOpt(getOptionValue(args, '--map'));
|
|
434
|
+
const backend = cleanOpt(getOptionValue(args, '--backend')) ?? 'auto';
|
|
435
|
+
if (mapValue) {
|
|
436
|
+
const out = await (0, workshop_1.workshopApplyMap)({ map: mapValue, backend });
|
|
437
|
+
console.log(JSON.stringify(out, null, 2));
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const id = cleanOpt(args[2] ?? null);
|
|
441
|
+
const monitor = cleanOpt(getOptionValue(args, '--monitor'));
|
|
442
|
+
if (!id || !monitor) {
|
|
443
|
+
throw new Error('Usage: we apply <id> --monitor <name> [--backend auto|mpvpaper|linux-wallpaperengine] OR we apply --map DP-1:<id1>,HDMI-A-1:<id2>');
|
|
444
|
+
}
|
|
445
|
+
const out = await (0, workshop_1.workshopApply)({ id, monitor, backend });
|
|
446
|
+
console.log(JSON.stringify(out, null, 2));
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
428
449
|
if (action === 'stop') {
|
|
429
450
|
const monitor = cleanOpt(getOptionValue(args, '--monitor'));
|
|
430
451
|
const out = await (0, workshop_1.workshopStop)({
|
|
@@ -453,7 +474,7 @@ async function main() {
|
|
|
453
474
|
}
|
|
454
475
|
throw new Error('Usage: we coexist <enter|exit|status>');
|
|
455
476
|
}
|
|
456
|
-
throw new Error('Usage: we <config|search|details|download|job|jobs|library|scan-steam|sync-steam|app-status|active|stop|run-job|coexist> ...');
|
|
477
|
+
throw new Error('Usage: we <config|search|details|download|job|jobs|library|scan-steam|sync-steam|app-status|active|apply|stop|run-job|coexist> ...');
|
|
457
478
|
}
|
|
458
479
|
// Regular commands (need config/state)
|
|
459
480
|
const config = (0, config_1.loadConfig)();
|
package/dist/core/workshop.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.setWorkshopApiKey = setWorkshopApiKey;
|
|
|
7
7
|
exports.workshopGetSteamRoots = workshopGetSteamRoots;
|
|
8
8
|
exports.workshopSetSteamRoots = workshopSetSteamRoots;
|
|
9
9
|
exports.workshopWallpaperEngineStatus = workshopWallpaperEngineStatus;
|
|
10
|
+
exports.workshopSceneEngineStatus = workshopSceneEngineStatus;
|
|
10
11
|
exports.workshopScanSteamDownloads = workshopScanSteamDownloads;
|
|
11
12
|
exports.workshopSyncSteamDownloads = workshopSyncSteamDownloads;
|
|
12
13
|
exports.workshopSearch = workshopSearch;
|
|
@@ -22,6 +23,8 @@ exports.workshopRunJob = workshopRunJob;
|
|
|
22
23
|
exports.workshopGetJob = workshopGetJob;
|
|
23
24
|
exports.workshopListJobs = workshopListJobs;
|
|
24
25
|
exports.workshopLibrary = workshopLibrary;
|
|
26
|
+
exports.workshopApply = workshopApply;
|
|
27
|
+
exports.workshopApplyMap = workshopApplyMap;
|
|
25
28
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
26
29
|
const node_os_1 = __importDefault(require("node:os"));
|
|
27
30
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -43,6 +46,74 @@ function clean(input) {
|
|
|
43
46
|
const v = input.trim();
|
|
44
47
|
return v.length > 0 ? v : undefined;
|
|
45
48
|
}
|
|
49
|
+
function isExecutable(filePath) {
|
|
50
|
+
try {
|
|
51
|
+
node_fs_1.default.accessSync(filePath, node_fs_1.default.constants.X_OK);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function resolveOnPath(binary) {
|
|
59
|
+
const bin = clean(binary);
|
|
60
|
+
if (!bin)
|
|
61
|
+
return undefined;
|
|
62
|
+
if (bin.includes('/')) {
|
|
63
|
+
const resolved = node_path_1.default.resolve(bin);
|
|
64
|
+
return node_fs_1.default.existsSync(resolved) && isExecutable(resolved) ? resolved : undefined;
|
|
65
|
+
}
|
|
66
|
+
const pathEnv = clean(process.env.PATH) ?? '';
|
|
67
|
+
const parts = pathEnv.split(':').filter(Boolean);
|
|
68
|
+
for (const p of parts) {
|
|
69
|
+
const candidate = node_path_1.default.join(p, bin);
|
|
70
|
+
if (node_fs_1.default.existsSync(candidate) && isExecutable(candidate))
|
|
71
|
+
return candidate;
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
function buildSceneEngineEnv(enginePath) {
|
|
76
|
+
const env = { ...process.env };
|
|
77
|
+
const libPaths = [];
|
|
78
|
+
const seen = new Set();
|
|
79
|
+
const pushLibPath = (candidate) => {
|
|
80
|
+
const value = clean(candidate);
|
|
81
|
+
if (!value || seen.has(value))
|
|
82
|
+
return;
|
|
83
|
+
seen.add(value);
|
|
84
|
+
libPaths.push(value);
|
|
85
|
+
};
|
|
86
|
+
const current = clean(process.env.LD_LIBRARY_PATH);
|
|
87
|
+
if (current) {
|
|
88
|
+
for (const segment of current.split(':')) {
|
|
89
|
+
const v = clean(segment);
|
|
90
|
+
if (v)
|
|
91
|
+
pushLibPath(v);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const resolvedEngine = clean(enginePath);
|
|
95
|
+
if (resolvedEngine) {
|
|
96
|
+
const engineDir = node_path_1.default.dirname(resolvedEngine);
|
|
97
|
+
if (node_fs_1.default.existsSync(node_path_1.default.join(engineDir, 'libcef.so'))) {
|
|
98
|
+
pushLibPath(engineDir);
|
|
99
|
+
}
|
|
100
|
+
const siblingLib = node_path_1.default.join(engineDir, 'lib');
|
|
101
|
+
if (node_fs_1.default.existsSync(siblingLib) && node_fs_1.default.statSync(siblingLib).isDirectory()) {
|
|
102
|
+
pushLibPath(siblingLib);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Arch linux-wallpaperengine installs runtime libs here when bypassing the wrapper.
|
|
106
|
+
if (node_fs_1.default.existsSync('/opt/linux-wallpaperengine/libcef.so')) {
|
|
107
|
+
pushLibPath('/opt/linux-wallpaperengine');
|
|
108
|
+
}
|
|
109
|
+
if (node_fs_1.default.existsSync('/opt/linux-wallpaperengine/lib') && node_fs_1.default.statSync('/opt/linux-wallpaperengine/lib').isDirectory()) {
|
|
110
|
+
pushLibPath('/opt/linux-wallpaperengine/lib');
|
|
111
|
+
}
|
|
112
|
+
if (libPaths.length > 0) {
|
|
113
|
+
env.LD_LIBRARY_PATH = libPaths.join(':');
|
|
114
|
+
}
|
|
115
|
+
return env;
|
|
116
|
+
}
|
|
46
117
|
function getWePaths() {
|
|
47
118
|
const root = node_path_1.default.join(node_os_1.default.homedir(), '.local', 'share', 'kitsune', 'we');
|
|
48
119
|
return {
|
|
@@ -375,19 +446,46 @@ function detectAudioReactiveFromText(text) {
|
|
|
375
446
|
function readProjectInfo(dir) {
|
|
376
447
|
const projectPath = node_path_1.default.join(dir, 'project.json');
|
|
377
448
|
if (!node_fs_1.default.existsSync(projectPath)) {
|
|
378
|
-
return { type: 'unknown', audioReactive: false };
|
|
449
|
+
return { type: 'unknown', audioReactive: false, entry: undefined };
|
|
379
450
|
}
|
|
380
451
|
try {
|
|
381
452
|
const raw = node_fs_1.default.readFileSync(projectPath, 'utf8');
|
|
382
453
|
const json = JSON.parse(raw);
|
|
383
454
|
const type = normalizeWallpaperType(json.type);
|
|
384
455
|
const title = clean(json.title) ?? '';
|
|
456
|
+
const fileEntry = clean(json.file);
|
|
457
|
+
const entry = fileEntry ? node_path_1.default.join(dir, fileEntry) : undefined;
|
|
385
458
|
const audioReactive = detectAudioReactiveFromText(`${title}\n${raw}`);
|
|
386
|
-
return { type, audioReactive };
|
|
459
|
+
return { type, audioReactive, entry };
|
|
387
460
|
}
|
|
388
461
|
catch {
|
|
389
|
-
return { type: 'unknown', audioReactive: false };
|
|
462
|
+
return { type: 'unknown', audioReactive: false, entry: undefined };
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function inferVideoEntry(dir) {
|
|
466
|
+
if (!node_fs_1.default.existsSync(dir))
|
|
467
|
+
return undefined;
|
|
468
|
+
const files = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
469
|
+
const preferred = ['.mp4', '.webm', '.gif', '.mkv', '.avi', '.mov'];
|
|
470
|
+
for (const ext of preferred) {
|
|
471
|
+
const match = files.find((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(ext));
|
|
472
|
+
if (match)
|
|
473
|
+
return node_path_1.default.join(dir, match.name);
|
|
474
|
+
}
|
|
475
|
+
return undefined;
|
|
476
|
+
}
|
|
477
|
+
function detectTypeFromEntry(entry) {
|
|
478
|
+
const e = clean(entry)?.toLowerCase();
|
|
479
|
+
if (!e)
|
|
480
|
+
return 'unknown';
|
|
481
|
+
if (e.endsWith('.mp4') || e.endsWith('.webm') || e.endsWith('.gif') || e.endsWith('.mkv') || e.endsWith('.avi') || e.endsWith('.mov')) {
|
|
482
|
+
return 'video';
|
|
390
483
|
}
|
|
484
|
+
if (e.endsWith('index.html') || e.endsWith('.html'))
|
|
485
|
+
return 'web';
|
|
486
|
+
if (e.endsWith('scene.json') || e.endsWith('gifscene.json'))
|
|
487
|
+
return 'scene';
|
|
488
|
+
return 'unknown';
|
|
391
489
|
}
|
|
392
490
|
function readLibraryFoldersVdf(vdfPath) {
|
|
393
491
|
if (!node_fs_1.default.existsSync(vdfPath))
|
|
@@ -503,13 +601,41 @@ function workshopWallpaperEngineStatus() {
|
|
|
503
601
|
if (node_fs_1.default.existsSync(manifest))
|
|
504
602
|
manifests.push(manifest);
|
|
505
603
|
}
|
|
604
|
+
const engine = workshopSceneEngineStatus();
|
|
506
605
|
return {
|
|
507
606
|
ok: true,
|
|
508
607
|
installed: manifests.length > 0,
|
|
509
608
|
manifests,
|
|
510
|
-
steamapps
|
|
609
|
+
steamapps,
|
|
610
|
+
engine
|
|
511
611
|
};
|
|
512
612
|
}
|
|
613
|
+
function workshopSceneEngineStatus() {
|
|
614
|
+
const configured = clean(process.env.KITOWALL_SCENE_ENGINE_CMD);
|
|
615
|
+
const candidates = configured
|
|
616
|
+
? [configured]
|
|
617
|
+
: ['linux-wallpaperengine', 'linux-wallpaperengine-cli', 'wallpaper-engine'];
|
|
618
|
+
for (const cmd of candidates) {
|
|
619
|
+
const resolved = resolveOnPath(cmd);
|
|
620
|
+
if (!resolved)
|
|
621
|
+
continue;
|
|
622
|
+
const verOut = (0, node_child_process_1.spawnSync)(resolved, ['--version'], {
|
|
623
|
+
encoding: 'utf8',
|
|
624
|
+
timeout: 2500,
|
|
625
|
+
env: buildSceneEngineEnv(resolved)
|
|
626
|
+
});
|
|
627
|
+
const stdout = clean(verOut.stdout) ?? '';
|
|
628
|
+
const stderr = clean(verOut.stderr) ?? '';
|
|
629
|
+
const version = (stdout.split('\n')[0] || stderr.split('\n')[0] || '').trim();
|
|
630
|
+
return {
|
|
631
|
+
installed: true,
|
|
632
|
+
path: resolved,
|
|
633
|
+
version: version || undefined,
|
|
634
|
+
command: cmd
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
return { installed: false, command: configured ?? 'linux-wallpaperengine' };
|
|
638
|
+
}
|
|
513
639
|
function findPreviewCandidate(dir) {
|
|
514
640
|
if (!node_fs_1.default.existsSync(dir))
|
|
515
641
|
return undefined;
|
|
@@ -602,17 +728,19 @@ function workshopSyncSteamDownloads() {
|
|
|
602
728
|
author_name: 'Steam Workshop',
|
|
603
729
|
time_updated: Math.floor(Date.now() / 1000),
|
|
604
730
|
wallpaper_type: info.type,
|
|
605
|
-
audio_reactive: info.audioReactive
|
|
731
|
+
audio_reactive: info.audioReactive,
|
|
732
|
+
entry: info.entry
|
|
606
733
|
});
|
|
607
734
|
}
|
|
608
735
|
else {
|
|
609
736
|
try {
|
|
610
737
|
const current = JSON.parse(node_fs_1.default.readFileSync(metadataFile, 'utf8'));
|
|
611
|
-
if (!current.wallpaper_type || current.wallpaper_type === 'unknown' || current.audio_reactive === undefined) {
|
|
738
|
+
if (!current.wallpaper_type || current.wallpaper_type === 'unknown' || current.audio_reactive === undefined || !current.entry) {
|
|
612
739
|
writeMetaFile({
|
|
613
740
|
...current,
|
|
614
741
|
wallpaper_type: current.wallpaper_type ?? info.type,
|
|
615
|
-
audio_reactive: current.audio_reactive ?? info.audioReactive
|
|
742
|
+
audio_reactive: current.audio_reactive ?? info.audioReactive,
|
|
743
|
+
entry: current.entry ?? info.entry
|
|
616
744
|
});
|
|
617
745
|
}
|
|
618
746
|
}
|
|
@@ -1026,9 +1154,10 @@ async function workshopCoexistenceStatus() {
|
|
|
1026
1154
|
return { ok: true, snapshot, current };
|
|
1027
1155
|
}
|
|
1028
1156
|
async function workshopStop(options) {
|
|
1029
|
-
|
|
1157
|
+
let state = readActiveState();
|
|
1030
1158
|
const hadActive = !!state;
|
|
1031
1159
|
const stopped = [];
|
|
1160
|
+
let shouldRestore = false;
|
|
1032
1161
|
if (state?.instances) {
|
|
1033
1162
|
if (options?.monitor) {
|
|
1034
1163
|
const inst = state.instances[options.monitor];
|
|
@@ -1040,6 +1169,7 @@ async function workshopStop(options) {
|
|
|
1040
1169
|
}
|
|
1041
1170
|
else {
|
|
1042
1171
|
clearActiveState();
|
|
1172
|
+
shouldRestore = true;
|
|
1043
1173
|
}
|
|
1044
1174
|
}
|
|
1045
1175
|
else {
|
|
@@ -1048,12 +1178,14 @@ async function workshopStop(options) {
|
|
|
1048
1178
|
stopped.push(monitor);
|
|
1049
1179
|
}
|
|
1050
1180
|
clearActiveState();
|
|
1181
|
+
shouldRestore = true;
|
|
1051
1182
|
}
|
|
1052
1183
|
}
|
|
1053
1184
|
else if (options?.all) {
|
|
1054
1185
|
clearActiveState();
|
|
1186
|
+
shouldRestore = true;
|
|
1055
1187
|
}
|
|
1056
|
-
const coexist = await workshopCoexistenceExit();
|
|
1188
|
+
const coexist = shouldRestore ? await workshopCoexistenceExit() : { ok: true, restored: [] };
|
|
1057
1189
|
return {
|
|
1058
1190
|
ok: true,
|
|
1059
1191
|
stopped_instances: stopped,
|
|
@@ -1165,6 +1297,9 @@ function workshopLibrary() {
|
|
|
1165
1297
|
if (meta.audio_reactive === undefined) {
|
|
1166
1298
|
meta.audio_reactive = info.audioReactive;
|
|
1167
1299
|
}
|
|
1300
|
+
if (!meta.entry) {
|
|
1301
|
+
meta.entry = info.entry;
|
|
1302
|
+
}
|
|
1168
1303
|
}
|
|
1169
1304
|
items.push({
|
|
1170
1305
|
id,
|
|
@@ -1176,3 +1311,152 @@ function workshopLibrary() {
|
|
|
1176
1311
|
items.sort((a, b) => a.id.localeCompare(b.id));
|
|
1177
1312
|
return { root: paths.downloads, items };
|
|
1178
1313
|
}
|
|
1314
|
+
function spawnMpvpaper(monitor, entry) {
|
|
1315
|
+
return new Promise((resolve, reject) => {
|
|
1316
|
+
const child = (0, node_child_process_1.spawn)('mpvpaper', ['-o', 'no-audio --loop-file=inf', monitor, entry], {
|
|
1317
|
+
detached: true,
|
|
1318
|
+
stdio: 'ignore',
|
|
1319
|
+
env: process.env
|
|
1320
|
+
});
|
|
1321
|
+
let settled = false;
|
|
1322
|
+
const done = (fn) => {
|
|
1323
|
+
if (settled)
|
|
1324
|
+
return;
|
|
1325
|
+
settled = true;
|
|
1326
|
+
fn();
|
|
1327
|
+
};
|
|
1328
|
+
child.once('error', (err) => done(() => reject(err)));
|
|
1329
|
+
setTimeout(() => {
|
|
1330
|
+
done(() => {
|
|
1331
|
+
child.unref();
|
|
1332
|
+
resolve(child.pid ?? 0);
|
|
1333
|
+
});
|
|
1334
|
+
}, 180);
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
function spawnSceneEngine(monitor, wallpaperDir) {
|
|
1338
|
+
const engine = workshopSceneEngineStatus();
|
|
1339
|
+
if (!engine.installed || !engine.path) {
|
|
1340
|
+
throw new Error('Scene engine not installed. Install linux-wallpaperengine first.');
|
|
1341
|
+
}
|
|
1342
|
+
const enginePath = engine.path;
|
|
1343
|
+
return new Promise((resolve, reject) => {
|
|
1344
|
+
const child = (0, node_child_process_1.spawn)(enginePath, ['--screen-root', monitor, '--bg', wallpaperDir], {
|
|
1345
|
+
detached: true,
|
|
1346
|
+
stdio: 'ignore',
|
|
1347
|
+
env: buildSceneEngineEnv(enginePath)
|
|
1348
|
+
});
|
|
1349
|
+
let settled = false;
|
|
1350
|
+
const done = (fn) => {
|
|
1351
|
+
if (settled)
|
|
1352
|
+
return;
|
|
1353
|
+
settled = true;
|
|
1354
|
+
fn();
|
|
1355
|
+
};
|
|
1356
|
+
child.once('error', (err) => done(() => reject(err)));
|
|
1357
|
+
setTimeout(() => {
|
|
1358
|
+
done(() => {
|
|
1359
|
+
child.unref();
|
|
1360
|
+
resolve(child.pid ?? 0);
|
|
1361
|
+
});
|
|
1362
|
+
}, 220);
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
async function workshopApply(input) {
|
|
1366
|
+
const id = clean(input.id);
|
|
1367
|
+
const monitor = clean(input.monitor);
|
|
1368
|
+
const requestedBackend = clean(input.backend) ?? 'auto';
|
|
1369
|
+
if (!id)
|
|
1370
|
+
throw new Error('id is required');
|
|
1371
|
+
if (!monitor)
|
|
1372
|
+
throw new Error('monitor is required');
|
|
1373
|
+
const paths = getWePaths();
|
|
1374
|
+
ensureWePaths(paths);
|
|
1375
|
+
const dir = node_path_1.default.join(paths.downloads, id);
|
|
1376
|
+
if (!node_fs_1.default.existsSync(dir)) {
|
|
1377
|
+
throw new Error(`Wallpaper not found in downloads: ${id}`);
|
|
1378
|
+
}
|
|
1379
|
+
const project = readProjectInfo(dir);
|
|
1380
|
+
const inferredEntry = project.entry && node_fs_1.default.existsSync(project.entry) ? project.entry : inferVideoEntry(dir);
|
|
1381
|
+
const type = project.type !== 'unknown'
|
|
1382
|
+
? project.type
|
|
1383
|
+
: detectTypeFromEntry(inferredEntry ?? node_path_1.default.join(dir, 'scene.json'));
|
|
1384
|
+
if (type === 'web' || type === 'application' || type === 'unknown') {
|
|
1385
|
+
throw new Error(`Unsupported wallpaper type for apply: ${type}. Supported: video, scene.`);
|
|
1386
|
+
}
|
|
1387
|
+
let state = readActiveState();
|
|
1388
|
+
if (!state) {
|
|
1389
|
+
const coexist = await workshopCoexistenceEnter();
|
|
1390
|
+
state = {
|
|
1391
|
+
mode: 'livewallpaper',
|
|
1392
|
+
started_at: now(),
|
|
1393
|
+
snapshot_id: coexist.snapshot_id,
|
|
1394
|
+
instances: {}
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
const current = state.instances[monitor];
|
|
1398
|
+
if (current?.pid) {
|
|
1399
|
+
killPid(current.pid);
|
|
1400
|
+
}
|
|
1401
|
+
let backend;
|
|
1402
|
+
let pid = 0;
|
|
1403
|
+
if (type === 'video') {
|
|
1404
|
+
if (!(requestedBackend === 'auto' || requestedBackend === 'mpvpaper')) {
|
|
1405
|
+
throw new Error(`Invalid backend for video wallpaper: ${requestedBackend}`);
|
|
1406
|
+
}
|
|
1407
|
+
if (!inferredEntry || !node_fs_1.default.existsSync(inferredEntry)) {
|
|
1408
|
+
throw new Error(`Video entry not found for wallpaper: ${id}`);
|
|
1409
|
+
}
|
|
1410
|
+
backend = 'mpvpaper';
|
|
1411
|
+
pid = await spawnMpvpaper(monitor, inferredEntry).catch((err) => {
|
|
1412
|
+
throw new Error(`Failed to launch mpvpaper: ${err instanceof Error ? err.message : String(err)}`);
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
else if (type === 'scene') {
|
|
1416
|
+
if (!(requestedBackend === 'auto' || requestedBackend === 'linux-wallpaperengine')) {
|
|
1417
|
+
throw new Error(`Invalid backend for scene wallpaper: ${requestedBackend}`);
|
|
1418
|
+
}
|
|
1419
|
+
backend = 'linux-wallpaperengine';
|
|
1420
|
+
pid = await spawnSceneEngine(monitor, dir).catch((err) => {
|
|
1421
|
+
throw new Error(`Failed to launch scene engine: ${err instanceof Error ? err.message : String(err)}`);
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
else {
|
|
1425
|
+
throw new Error(`Unsupported wallpaper type for apply: ${type}`);
|
|
1426
|
+
}
|
|
1427
|
+
if (!pid) {
|
|
1428
|
+
throw new Error(`${backend} started without pid`);
|
|
1429
|
+
}
|
|
1430
|
+
state.instances[monitor] = {
|
|
1431
|
+
id,
|
|
1432
|
+
pid,
|
|
1433
|
+
backend,
|
|
1434
|
+
type
|
|
1435
|
+
};
|
|
1436
|
+
writeActiveState(state);
|
|
1437
|
+
return { ok: true, applied: true, monitor, id, backend, pid, state };
|
|
1438
|
+
}
|
|
1439
|
+
async function workshopApplyMap(input) {
|
|
1440
|
+
const raw = clean(input.map);
|
|
1441
|
+
if (!raw)
|
|
1442
|
+
throw new Error('map is required');
|
|
1443
|
+
const entries = raw.split(',').map(v => v.trim()).filter(Boolean);
|
|
1444
|
+
if (entries.length === 0)
|
|
1445
|
+
throw new Error('map has no entries');
|
|
1446
|
+
const applied = [];
|
|
1447
|
+
for (const pair of entries) {
|
|
1448
|
+
const idx = pair.indexOf(':');
|
|
1449
|
+
if (idx <= 0 || idx === pair.length - 1) {
|
|
1450
|
+
throw new Error(`Invalid map entry: ${pair}. Expected <monitor>:<id>`);
|
|
1451
|
+
}
|
|
1452
|
+
const monitor = pair.slice(0, idx).trim();
|
|
1453
|
+
const id = pair.slice(idx + 1).trim();
|
|
1454
|
+
if (!monitor || !id) {
|
|
1455
|
+
throw new Error(`Invalid map entry: ${pair}. Expected <monitor>:<id>`);
|
|
1456
|
+
}
|
|
1457
|
+
const out = await workshopApply({ id, monitor, backend: input.backend });
|
|
1458
|
+
applied.push({ monitor: out.monitor, id: out.id, backend: out.backend, pid: out.pid });
|
|
1459
|
+
}
|
|
1460
|
+
const state = readActiveState() ?? undefined;
|
|
1461
|
+
return { ok: true, applied, state };
|
|
1462
|
+
}
|