kitowall 2.6.0 → 3.0.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 +263 -5
- package/dist/core/live.js +1812 -0
- package/dist/core/workshop.js +11 -150
- package/dist/utils/exec.js +14 -1
- package/package.json +3 -2
package/dist/core/workshop.js
CHANGED
|
@@ -7,7 +7,6 @@ exports.setWorkshopApiKey = setWorkshopApiKey;
|
|
|
7
7
|
exports.workshopGetSteamRoots = workshopGetSteamRoots;
|
|
8
8
|
exports.workshopSetSteamRoots = workshopSetSteamRoots;
|
|
9
9
|
exports.workshopWallpaperEngineStatus = workshopWallpaperEngineStatus;
|
|
10
|
-
exports.workshopSceneEngineStatus = workshopSceneEngineStatus;
|
|
11
10
|
exports.workshopScanSteamDownloads = workshopScanSteamDownloads;
|
|
12
11
|
exports.workshopSyncSteamDownloads = workshopSyncSteamDownloads;
|
|
13
12
|
exports.workshopSearch = workshopSearch;
|
|
@@ -46,74 +45,6 @@ function clean(input) {
|
|
|
46
45
|
const v = input.trim();
|
|
47
46
|
return v.length > 0 ? v : undefined;
|
|
48
47
|
}
|
|
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
|
-
}
|
|
117
48
|
function getWePaths() {
|
|
118
49
|
const root = node_path_1.default.join(node_os_1.default.homedir(), '.local', 'share', 'kitsune', 'we');
|
|
119
50
|
return {
|
|
@@ -602,41 +533,13 @@ function workshopWallpaperEngineStatus() {
|
|
|
602
533
|
if (node_fs_1.default.existsSync(manifest))
|
|
603
534
|
manifests.push(manifest);
|
|
604
535
|
}
|
|
605
|
-
const engine = workshopSceneEngineStatus();
|
|
606
536
|
return {
|
|
607
537
|
ok: true,
|
|
608
538
|
installed: manifests.length > 0,
|
|
609
539
|
manifests,
|
|
610
|
-
steamapps
|
|
611
|
-
engine
|
|
540
|
+
steamapps
|
|
612
541
|
};
|
|
613
542
|
}
|
|
614
|
-
function workshopSceneEngineStatus() {
|
|
615
|
-
const configured = clean(process.env.KITOWALL_SCENE_ENGINE_CMD);
|
|
616
|
-
const candidates = configured
|
|
617
|
-
? [configured]
|
|
618
|
-
: ['linux-wallpaperengine', 'linux-wallpaperengine-cli', 'wallpaper-engine'];
|
|
619
|
-
for (const cmd of candidates) {
|
|
620
|
-
const resolved = resolveOnPath(cmd);
|
|
621
|
-
if (!resolved)
|
|
622
|
-
continue;
|
|
623
|
-
const verOut = (0, node_child_process_1.spawnSync)(resolved, ['--version'], {
|
|
624
|
-
encoding: 'utf8',
|
|
625
|
-
timeout: 2500,
|
|
626
|
-
env: buildSceneEngineEnv(resolved)
|
|
627
|
-
});
|
|
628
|
-
const stdout = clean(verOut.stdout) ?? '';
|
|
629
|
-
const stderr = clean(verOut.stderr) ?? '';
|
|
630
|
-
const version = (stdout.split('\n')[0] || stderr.split('\n')[0] || '').trim();
|
|
631
|
-
return {
|
|
632
|
-
installed: true,
|
|
633
|
-
path: resolved,
|
|
634
|
-
version: version || undefined,
|
|
635
|
-
command: cmd
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
|
-
return { installed: false, command: configured ?? 'linux-wallpaperengine' };
|
|
639
|
-
}
|
|
640
543
|
function findPreviewCandidate(dir) {
|
|
641
544
|
if (!node_fs_1.default.existsSync(dir))
|
|
642
545
|
return undefined;
|
|
@@ -1335,34 +1238,6 @@ function spawnMpvpaper(monitor, entry) {
|
|
|
1335
1238
|
}, 180);
|
|
1336
1239
|
});
|
|
1337
1240
|
}
|
|
1338
|
-
function spawnSceneEngine(monitor, wallpaperDir) {
|
|
1339
|
-
const engine = workshopSceneEngineStatus();
|
|
1340
|
-
if (!engine.installed || !engine.path) {
|
|
1341
|
-
throw new Error('Scene engine not installed. Install linux-wallpaperengine first.');
|
|
1342
|
-
}
|
|
1343
|
-
const enginePath = engine.path;
|
|
1344
|
-
return new Promise((resolve, reject) => {
|
|
1345
|
-
const child = (0, node_child_process_1.spawn)(enginePath, ['--screen-root', monitor, '--bg', wallpaperDir], {
|
|
1346
|
-
detached: true,
|
|
1347
|
-
stdio: 'ignore',
|
|
1348
|
-
env: buildSceneEngineEnv(enginePath)
|
|
1349
|
-
});
|
|
1350
|
-
let settled = false;
|
|
1351
|
-
const done = (fn) => {
|
|
1352
|
-
if (settled)
|
|
1353
|
-
return;
|
|
1354
|
-
settled = true;
|
|
1355
|
-
fn();
|
|
1356
|
-
};
|
|
1357
|
-
child.once('error', (err) => done(() => reject(err)));
|
|
1358
|
-
setTimeout(() => {
|
|
1359
|
-
done(() => {
|
|
1360
|
-
child.unref();
|
|
1361
|
-
resolve(child.pid ?? 0);
|
|
1362
|
-
});
|
|
1363
|
-
}, 220);
|
|
1364
|
-
});
|
|
1365
|
-
}
|
|
1366
1241
|
async function workshopApply(input) {
|
|
1367
1242
|
const id = clean(input.id);
|
|
1368
1243
|
const monitor = clean(input.monitor);
|
|
@@ -1382,8 +1257,8 @@ async function workshopApply(input) {
|
|
|
1382
1257
|
const type = project.type !== 'unknown'
|
|
1383
1258
|
? project.type
|
|
1384
1259
|
: detectTypeFromEntry(inferredEntry ?? node_path_1.default.join(dir, 'scene.json'));
|
|
1385
|
-
if (type
|
|
1386
|
-
throw new Error(`Unsupported wallpaper type for apply: ${type}. Supported: video
|
|
1260
|
+
if (type !== 'video') {
|
|
1261
|
+
throw new Error(`Unsupported wallpaper type for apply: ${type}. Supported: video (mpvpaper).`);
|
|
1387
1262
|
}
|
|
1388
1263
|
let state = readActiveState();
|
|
1389
1264
|
if (!state) {
|
|
@@ -1401,30 +1276,16 @@ async function workshopApply(input) {
|
|
|
1401
1276
|
}
|
|
1402
1277
|
let backend;
|
|
1403
1278
|
let pid = 0;
|
|
1404
|
-
if (
|
|
1405
|
-
|
|
1406
|
-
throw new Error(`Invalid backend for video wallpaper: ${requestedBackend}`);
|
|
1407
|
-
}
|
|
1408
|
-
if (!inferredEntry || !node_fs_1.default.existsSync(inferredEntry)) {
|
|
1409
|
-
throw new Error(`Video entry not found for wallpaper: ${id}`);
|
|
1410
|
-
}
|
|
1411
|
-
backend = 'mpvpaper';
|
|
1412
|
-
pid = await spawnMpvpaper(monitor, inferredEntry).catch((err) => {
|
|
1413
|
-
throw new Error(`Failed to launch mpvpaper: ${err instanceof Error ? err.message : String(err)}`);
|
|
1414
|
-
});
|
|
1279
|
+
if (!(requestedBackend === 'auto' || requestedBackend === 'mpvpaper')) {
|
|
1280
|
+
throw new Error(`Invalid backend for video wallpaper: ${requestedBackend}`);
|
|
1415
1281
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
throw new Error(`Invalid backend for scene wallpaper: ${requestedBackend}`);
|
|
1419
|
-
}
|
|
1420
|
-
backend = 'linux-wallpaperengine';
|
|
1421
|
-
pid = await spawnSceneEngine(monitor, dir).catch((err) => {
|
|
1422
|
-
throw new Error(`Failed to launch scene engine: ${err instanceof Error ? err.message : String(err)}`);
|
|
1423
|
-
});
|
|
1424
|
-
}
|
|
1425
|
-
else {
|
|
1426
|
-
throw new Error(`Unsupported wallpaper type for apply: ${type}`);
|
|
1282
|
+
if (!inferredEntry || !node_fs_1.default.existsSync(inferredEntry)) {
|
|
1283
|
+
throw new Error(`Video entry not found for wallpaper: ${id}`);
|
|
1427
1284
|
}
|
|
1285
|
+
backend = 'mpvpaper';
|
|
1286
|
+
pid = await spawnMpvpaper(monitor, inferredEntry).catch((err) => {
|
|
1287
|
+
throw new Error(`Failed to launch mpvpaper: ${err instanceof Error ? err.message : String(err)}`);
|
|
1288
|
+
});
|
|
1428
1289
|
if (!pid) {
|
|
1429
1290
|
throw new Error(`${backend} started without pid`);
|
|
1430
1291
|
}
|
package/dist/utils/exec.js
CHANGED
|
@@ -6,7 +6,20 @@ const child_process_1 = require("child_process");
|
|
|
6
6
|
function run(cmd, args = [], options = {}) {
|
|
7
7
|
return new Promise((resolve, reject) => {
|
|
8
8
|
const isFlatpak = Boolean(process.env.FLATPAK_ID);
|
|
9
|
-
const hostCommands = new Set([
|
|
9
|
+
const hostCommands = new Set([
|
|
10
|
+
'swww',
|
|
11
|
+
'swww-daemon',
|
|
12
|
+
'hyprctl',
|
|
13
|
+
'systemctl',
|
|
14
|
+
'which',
|
|
15
|
+
'xdg-open',
|
|
16
|
+
'steamcmd',
|
|
17
|
+
'ffmpeg',
|
|
18
|
+
'ffprobe',
|
|
19
|
+
'cargo',
|
|
20
|
+
'kitsune-livewallpaper',
|
|
21
|
+
'dd'
|
|
22
|
+
]);
|
|
10
23
|
const useHost = isFlatpak && hostCommands.has(cmd);
|
|
11
24
|
const finalCmd = useHost ? 'flatpak-spawn' : cmd;
|
|
12
25
|
const finalArgs = useHost ? ['--host', cmd, ...args] : args;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kitowall",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "CLI/daemon for Hyprland wallpapers using swww with pack-based rotation.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"package:all": "npm run release:check && npm run package:cli && npm run package:ui",
|
|
26
26
|
"test:smoke": "bash ./tests/smoke.e2e.sh",
|
|
27
27
|
"test:regression": "bash ./tests/regression.e2e.sh",
|
|
28
|
-
"test:e2e": "npm run test:smoke && npm run test:regression"
|
|
28
|
+
"test:e2e": "npm run test:smoke && npm run test:regression",
|
|
29
|
+
"test:live": "npm run build && node --test tests/live.test.js"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@types/node": "^22.10.0",
|