kitowall 2.2.0 → 2.3.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 +17 -1
- package/dist/core/workshop.js +190 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -132,6 +132,8 @@ Commands:
|
|
|
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
134
|
we app-status Detect if Wallpaper Engine (AppID 431960) is installed
|
|
135
|
+
we active Show current livewallpaper authority/lock state
|
|
136
|
+
we stop [--monitor <name> | --all] Stop livewallpaper instances and restore previous services
|
|
135
137
|
we coexist enter|exit|status Temporarily stop/restore wallpaper rotation services
|
|
136
138
|
check [--namespace <ns>] [--json] Quick system check (no changes)
|
|
137
139
|
|
|
@@ -418,6 +420,20 @@ async function main() {
|
|
|
418
420
|
console.log(JSON.stringify(out, null, 2));
|
|
419
421
|
return;
|
|
420
422
|
}
|
|
423
|
+
if (action === 'active') {
|
|
424
|
+
const out = (0, workshop_1.workshopActiveStatus)();
|
|
425
|
+
console.log(JSON.stringify(out, null, 2));
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (action === 'stop') {
|
|
429
|
+
const monitor = cleanOpt(getOptionValue(args, '--monitor'));
|
|
430
|
+
const out = await (0, workshop_1.workshopStop)({
|
|
431
|
+
monitor,
|
|
432
|
+
all: args.includes('--all') || !monitor
|
|
433
|
+
});
|
|
434
|
+
console.log(JSON.stringify(out, null, 2));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
421
437
|
if (action === 'coexist') {
|
|
422
438
|
const sub = cleanOpt(args[2] ?? null);
|
|
423
439
|
if (sub === 'enter') {
|
|
@@ -437,7 +453,7 @@ async function main() {
|
|
|
437
453
|
}
|
|
438
454
|
throw new Error('Usage: we coexist <enter|exit|status>');
|
|
439
455
|
}
|
|
440
|
-
throw new Error('Usage: we <config|search|details|download|job|jobs|library|scan-steam|sync-steam|app-status|run-job|coexist> ...');
|
|
456
|
+
throw new Error('Usage: we <config|search|details|download|job|jobs|library|scan-steam|sync-steam|app-status|active|stop|run-job|coexist> ...');
|
|
441
457
|
}
|
|
442
458
|
// Regular commands (need config/state)
|
|
443
459
|
const config = (0, config_1.loadConfig)();
|
package/dist/core/workshop.js
CHANGED
|
@@ -12,9 +12,12 @@ exports.workshopSyncSteamDownloads = workshopSyncSteamDownloads;
|
|
|
12
12
|
exports.workshopSearch = workshopSearch;
|
|
13
13
|
exports.workshopDetails = workshopDetails;
|
|
14
14
|
exports.workshopQueueDownload = workshopQueueDownload;
|
|
15
|
+
exports.workshopActiveStatus = workshopActiveStatus;
|
|
16
|
+
exports.workshopSetActive = workshopSetActive;
|
|
15
17
|
exports.workshopCoexistenceEnter = workshopCoexistenceEnter;
|
|
16
18
|
exports.workshopCoexistenceExit = workshopCoexistenceExit;
|
|
17
19
|
exports.workshopCoexistenceStatus = workshopCoexistenceStatus;
|
|
20
|
+
exports.workshopStop = workshopStop;
|
|
18
21
|
exports.workshopRunJob = workshopRunJob;
|
|
19
22
|
exports.workshopGetJob = workshopGetJob;
|
|
20
23
|
exports.workshopListJobs = workshopListJobs;
|
|
@@ -64,6 +67,7 @@ function ensureWePaths(paths) {
|
|
|
64
67
|
(0, fs_1.ensureDir)(paths.runtime);
|
|
65
68
|
(0, fs_1.ensureDir)(node_path_1.default.join(paths.cache, 'search'));
|
|
66
69
|
(0, fs_1.ensureDir)(node_path_1.default.join(paths.steamcmd, 'logs'));
|
|
70
|
+
(0, fs_1.ensureDir)(node_path_1.default.join(paths.root, 'coexistence', 'snapshots'));
|
|
67
71
|
}
|
|
68
72
|
function getWeConfigPath() {
|
|
69
73
|
return node_path_1.default.join(node_os_1.default.homedir(), '.config', 'kitowall', 'we.json');
|
|
@@ -351,6 +355,40 @@ function writeMetaFile(meta) {
|
|
|
351
355
|
cached_at: now()
|
|
352
356
|
});
|
|
353
357
|
}
|
|
358
|
+
function normalizeWallpaperType(raw) {
|
|
359
|
+
const t = clean(raw)?.toLowerCase();
|
|
360
|
+
if (!t)
|
|
361
|
+
return 'unknown';
|
|
362
|
+
if (t === 'video')
|
|
363
|
+
return 'video';
|
|
364
|
+
if (t === 'scene')
|
|
365
|
+
return 'scene';
|
|
366
|
+
if (t === 'web')
|
|
367
|
+
return 'web';
|
|
368
|
+
if (t === 'application')
|
|
369
|
+
return 'application';
|
|
370
|
+
return 'unknown';
|
|
371
|
+
}
|
|
372
|
+
function detectAudioReactiveFromText(text) {
|
|
373
|
+
return /(audio|spectrum|visualizer|now[\s_-]?playing|media[\s_-]?integration|fft|bass|reactive)/i.test(text);
|
|
374
|
+
}
|
|
375
|
+
function readProjectInfo(dir) {
|
|
376
|
+
const projectPath = node_path_1.default.join(dir, 'project.json');
|
|
377
|
+
if (!node_fs_1.default.existsSync(projectPath)) {
|
|
378
|
+
return { type: 'unknown', audioReactive: false };
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
const raw = node_fs_1.default.readFileSync(projectPath, 'utf8');
|
|
382
|
+
const json = JSON.parse(raw);
|
|
383
|
+
const type = normalizeWallpaperType(json.type);
|
|
384
|
+
const title = clean(json.title) ?? '';
|
|
385
|
+
const audioReactive = detectAudioReactiveFromText(`${title}\n${raw}`);
|
|
386
|
+
return { type, audioReactive };
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
return { type: 'unknown', audioReactive: false };
|
|
390
|
+
}
|
|
391
|
+
}
|
|
354
392
|
function readLibraryFoldersVdf(vdfPath) {
|
|
355
393
|
if (!node_fs_1.default.existsSync(vdfPath))
|
|
356
394
|
return [];
|
|
@@ -543,6 +581,7 @@ function workshopSyncSteamDownloads() {
|
|
|
543
581
|
imported += 1;
|
|
544
582
|
}
|
|
545
583
|
const metadataFile = node_path_1.default.join(paths.metadata, `${id}.json`);
|
|
584
|
+
const info = readProjectInfo(sourceDir);
|
|
546
585
|
if (!node_fs_1.default.existsSync(metadataFile)) {
|
|
547
586
|
const previewCandidate = findPreviewCandidate(sourceDir);
|
|
548
587
|
let thumbLocal;
|
|
@@ -561,9 +600,26 @@ function workshopSyncSteamDownloads() {
|
|
|
561
600
|
tags: [],
|
|
562
601
|
preview_thumb_local: thumbLocal,
|
|
563
602
|
author_name: 'Steam Workshop',
|
|
564
|
-
time_updated: Math.floor(Date.now() / 1000)
|
|
603
|
+
time_updated: Math.floor(Date.now() / 1000),
|
|
604
|
+
wallpaper_type: info.type,
|
|
605
|
+
audio_reactive: info.audioReactive
|
|
565
606
|
});
|
|
566
607
|
}
|
|
608
|
+
else {
|
|
609
|
+
try {
|
|
610
|
+
const current = JSON.parse(node_fs_1.default.readFileSync(metadataFile, 'utf8'));
|
|
611
|
+
if (!current.wallpaper_type || current.wallpaper_type === 'unknown' || current.audio_reactive === undefined) {
|
|
612
|
+
writeMetaFile({
|
|
613
|
+
...current,
|
|
614
|
+
wallpaper_type: current.wallpaper_type ?? info.type,
|
|
615
|
+
audio_reactive: current.audio_reactive ?? info.audioReactive
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
catch {
|
|
620
|
+
// ignore malformed metadata
|
|
621
|
+
}
|
|
622
|
+
}
|
|
567
623
|
}
|
|
568
624
|
}
|
|
569
625
|
return {
|
|
@@ -813,6 +869,78 @@ async function copyDownloadedContent(job) {
|
|
|
813
869
|
function snapshotFile(paths) {
|
|
814
870
|
return node_path_1.default.join(paths.runtime, 'coexistence.snapshot.json');
|
|
815
871
|
}
|
|
872
|
+
function snapshotDir(paths) {
|
|
873
|
+
return node_path_1.default.join(paths.root, 'coexistence', 'snapshots');
|
|
874
|
+
}
|
|
875
|
+
function activeLockFile(paths) {
|
|
876
|
+
return node_path_1.default.join(paths.runtime, 'active.lock');
|
|
877
|
+
}
|
|
878
|
+
function activeStateFile(paths) {
|
|
879
|
+
return node_path_1.default.join(paths.runtime, 'active.json');
|
|
880
|
+
}
|
|
881
|
+
function readActiveState() {
|
|
882
|
+
const paths = getWePaths();
|
|
883
|
+
ensureWePaths(paths);
|
|
884
|
+
const p = activeStateFile(paths);
|
|
885
|
+
if (!node_fs_1.default.existsSync(p))
|
|
886
|
+
return null;
|
|
887
|
+
try {
|
|
888
|
+
return JSON.parse(node_fs_1.default.readFileSync(p, 'utf8'));
|
|
889
|
+
}
|
|
890
|
+
catch {
|
|
891
|
+
return null;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
function writeActiveState(state) {
|
|
895
|
+
const paths = getWePaths();
|
|
896
|
+
ensureWePaths(paths);
|
|
897
|
+
(0, fs_1.writeJson)(activeStateFile(paths), state);
|
|
898
|
+
node_fs_1.default.writeFileSync(activeLockFile(paths), `${state.mode}:${state.started_at}\n`, 'utf8');
|
|
899
|
+
}
|
|
900
|
+
function clearActiveState() {
|
|
901
|
+
const paths = getWePaths();
|
|
902
|
+
ensureWePaths(paths);
|
|
903
|
+
try {
|
|
904
|
+
node_fs_1.default.unlinkSync(activeStateFile(paths));
|
|
905
|
+
}
|
|
906
|
+
catch {
|
|
907
|
+
// ignore
|
|
908
|
+
}
|
|
909
|
+
try {
|
|
910
|
+
node_fs_1.default.unlinkSync(activeLockFile(paths));
|
|
911
|
+
}
|
|
912
|
+
catch {
|
|
913
|
+
// ignore
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
function killPid(pid) {
|
|
917
|
+
if (!pid || !Number.isFinite(pid) || pid <= 1)
|
|
918
|
+
return false;
|
|
919
|
+
try {
|
|
920
|
+
process.kill(pid, 'SIGTERM');
|
|
921
|
+
return true;
|
|
922
|
+
}
|
|
923
|
+
catch {
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
function workshopActiveStatus() {
|
|
928
|
+
const paths = getWePaths();
|
|
929
|
+
ensureWePaths(paths);
|
|
930
|
+
const state = readActiveState();
|
|
931
|
+
const lock = node_fs_1.default.existsSync(activeLockFile(paths));
|
|
932
|
+
return {
|
|
933
|
+
ok: true,
|
|
934
|
+
active: lock && !!state,
|
|
935
|
+
lock_path: activeLockFile(paths),
|
|
936
|
+
state_path: activeStateFile(paths),
|
|
937
|
+
state: state ?? undefined
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function workshopSetActive(state) {
|
|
941
|
+
writeActiveState(state);
|
|
942
|
+
return { ok: true, active: true, state };
|
|
943
|
+
}
|
|
816
944
|
async function isUnitActive(unit) {
|
|
817
945
|
try {
|
|
818
946
|
const out = await (0, exec_1.run)('systemctl', ['--user', 'show', unit, '--property', 'ActiveState', '--value']);
|
|
@@ -839,8 +967,11 @@ async function workshopCoexistenceEnter() {
|
|
|
839
967
|
// best effort
|
|
840
968
|
}
|
|
841
969
|
}
|
|
842
|
-
|
|
843
|
-
|
|
970
|
+
const snapshotId = String(now());
|
|
971
|
+
const snap = { id: snapshotId, ts: now(), active };
|
|
972
|
+
(0, fs_1.writeJson)(node_path_1.default.join(snapshotDir(paths), `${snapshotId}.json`), snap);
|
|
973
|
+
(0, fs_1.writeJson)(snapshotFile(paths), { id: snapshotId, ts: snap.ts });
|
|
974
|
+
return { ok: true, stopped: active, snapshot: active, snapshot_id: snapshotId };
|
|
844
975
|
}
|
|
845
976
|
async function workshopCoexistenceExit() {
|
|
846
977
|
const paths = getWePaths();
|
|
@@ -849,7 +980,19 @@ async function workshopCoexistenceExit() {
|
|
|
849
980
|
if (!node_fs_1.default.existsSync(snapPath))
|
|
850
981
|
return { ok: true, restored: [] };
|
|
851
982
|
const raw = JSON.parse(node_fs_1.default.readFileSync(snapPath, 'utf8'));
|
|
852
|
-
|
|
983
|
+
let active = Array.isArray(raw.active) ? raw.active.map(v => String(v)) : [];
|
|
984
|
+
if ((!active || active.length === 0) && raw.id) {
|
|
985
|
+
const historical = node_path_1.default.join(snapshotDir(paths), `${raw.id}.json`);
|
|
986
|
+
if (node_fs_1.default.existsSync(historical)) {
|
|
987
|
+
try {
|
|
988
|
+
const snap = JSON.parse(node_fs_1.default.readFileSync(historical, 'utf8'));
|
|
989
|
+
active = Array.isArray(snap.active) ? snap.active.map(v => String(v)) : [];
|
|
990
|
+
}
|
|
991
|
+
catch {
|
|
992
|
+
active = [];
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
853
996
|
const restored = [];
|
|
854
997
|
for (const unit of active) {
|
|
855
998
|
try {
|
|
@@ -882,6 +1025,42 @@ async function workshopCoexistenceStatus() {
|
|
|
882
1025
|
}
|
|
883
1026
|
return { ok: true, snapshot, current };
|
|
884
1027
|
}
|
|
1028
|
+
async function workshopStop(options) {
|
|
1029
|
+
const state = readActiveState();
|
|
1030
|
+
const hadActive = !!state;
|
|
1031
|
+
const stopped = [];
|
|
1032
|
+
if (state?.instances) {
|
|
1033
|
+
if (options?.monitor) {
|
|
1034
|
+
const inst = state.instances[options.monitor];
|
|
1035
|
+
if (killPid(inst?.pid))
|
|
1036
|
+
stopped.push(options.monitor);
|
|
1037
|
+
delete state.instances[options.monitor];
|
|
1038
|
+
if (Object.keys(state.instances).length > 0 && !options?.all) {
|
|
1039
|
+
writeActiveState(state);
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
clearActiveState();
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
else {
|
|
1046
|
+
for (const [monitor, inst] of Object.entries(state.instances)) {
|
|
1047
|
+
if (killPid(inst?.pid))
|
|
1048
|
+
stopped.push(monitor);
|
|
1049
|
+
}
|
|
1050
|
+
clearActiveState();
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
else if (options?.all) {
|
|
1054
|
+
clearActiveState();
|
|
1055
|
+
}
|
|
1056
|
+
const coexist = await workshopCoexistenceExit();
|
|
1057
|
+
return {
|
|
1058
|
+
ok: true,
|
|
1059
|
+
stopped_instances: stopped,
|
|
1060
|
+
restored_units: coexist.restored,
|
|
1061
|
+
had_active: hadActive
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
885
1064
|
async function workshopRunJob(jobId) {
|
|
886
1065
|
const job = readJob(jobId);
|
|
887
1066
|
if (!job)
|
|
@@ -980,6 +1159,13 @@ function workshopLibrary() {
|
|
|
980
1159
|
meta = undefined;
|
|
981
1160
|
}
|
|
982
1161
|
}
|
|
1162
|
+
const info = readProjectInfo(p);
|
|
1163
|
+
if (meta) {
|
|
1164
|
+
meta.wallpaper_type = meta.wallpaper_type ?? info.type;
|
|
1165
|
+
if (meta.audio_reactive === undefined) {
|
|
1166
|
+
meta.audio_reactive = info.audioReactive;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
983
1169
|
items.push({
|
|
984
1170
|
id,
|
|
985
1171
|
path: p,
|