kitowall 3.2.0 → 3.4.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 CHANGED
@@ -1503,6 +1503,7 @@ async function main() {
1503
1503
  const pack = cleanOpt(getOptionValue(args, '--pack'));
1504
1504
  const namespace = cleanOpt(getOptionValue(args, '--namespace')) ?? 'kitowall';
1505
1505
  const force = cmd === 'rotate-now' || args.includes('--force');
1506
+ const forceStatic = args.includes('--force-static');
1506
1507
  if (state.mode === 'manual' && !force) {
1507
1508
  console.log(JSON.stringify({
1508
1509
  ok: true,
@@ -1513,6 +1514,20 @@ async function main() {
1513
1514
  }, null, 2));
1514
1515
  return;
1515
1516
  }
1517
+ // If live mode currently owns wallpaper state, skip static rotation unless explicitly forced.
1518
+ if (!forceStatic) {
1519
+ const coexist = await (0, workshop_1.workshopCoexistenceStatus)().catch(() => ({ ok: true, snapshot: [], current: {} }));
1520
+ if (Array.isArray(coexist.snapshot) && coexist.snapshot.length > 0) {
1521
+ console.log(JSON.stringify({
1522
+ ok: true,
1523
+ skipped: true,
1524
+ reason: 'livewallpaper_active',
1525
+ hint: 'Use: kitowall next --force-static (or rotate-now --force-static) to switch back to static mode',
1526
+ namespace
1527
+ }, null, 2));
1528
+ return;
1529
+ }
1530
+ }
1516
1531
  await switchToStaticWallpaperMode();
1517
1532
  const result = await controller.applyNext(pack, namespace);
1518
1533
  console.log(JSON.stringify({
package/dist/core/live.js CHANGED
@@ -1524,19 +1524,10 @@ async function liveApply(opts) {
1524
1524
  ];
1525
1525
  await (0, exec_1.run)(bin, setVideoArgs, { timeoutMs: 120000 });
1526
1526
  // Live mode owns wallpaper state: stop static rotation services while live is active.
1527
- try {
1528
- await (0, workshop_1.workshopCoexistenceEnter)();
1529
- }
1530
- catch { }
1527
+ await (0, workshop_1.workshopCoexistenceEnter)();
1531
1528
  // Keep live authority persistent across session restarts.
1532
- try {
1533
- await (0, exec_1.run)(bin, ['service', 'install'], { timeoutMs: 20000 });
1534
- }
1535
- catch { }
1536
- try {
1537
- await (0, exec_1.run)(bin, ['service', 'enable'], { timeoutMs: 20000 });
1538
- }
1539
- catch { }
1529
+ await (0, exec_1.run)(bin, ['service', 'install'], { timeoutMs: 20000 });
1530
+ await (0, exec_1.run)(bin, ['service', 'enable'], { timeoutMs: 20000 });
1540
1531
  withLiveLock(() => {
1541
1532
  const current = readIndex();
1542
1533
  const updatedItems = current.items.map(v => (v.id === item.id ? { ...v, last_applied_at: nowUnix() } : v));
@@ -1725,14 +1716,53 @@ async function liveServiceAutostart(action) {
1725
1716
  const index = readIndex();
1726
1717
  let bin = index.runner.bin_name;
1727
1718
  let out;
1719
+ const extractResult = (err) => {
1720
+ const result = err?.result;
1721
+ if (!result)
1722
+ return null;
1723
+ return {
1724
+ stdout: String(result.stdout ?? ''),
1725
+ stderr: String(result.stderr ?? ''),
1726
+ code: Number(result.code ?? 1)
1727
+ };
1728
+ };
1728
1729
  try {
1729
1730
  out = await (0, exec_1.run)(bin, ['service', action], { timeoutMs: 30000 });
1730
1731
  }
1731
1732
  catch (err) {
1733
+ const partial = extractResult(err);
1734
+ if (action === 'status' && partial) {
1735
+ out = partial;
1736
+ return {
1737
+ ok: true,
1738
+ action,
1739
+ runner: bin,
1740
+ stdout: out.stdout.trim(),
1741
+ stderr: out.stderr.trim(),
1742
+ code: out.code
1743
+ };
1744
+ }
1732
1745
  // Legacy compatibility: if user still points to kitsune-livewallpaper,
1733
1746
  // map to its older subcommand to avoid hard failure.
1734
1747
  if (clean(bin) === 'kitsune-livewallpaper') {
1735
- out = await (0, exec_1.run)(bin, ['service-autostart', action], { timeoutMs: 30000 });
1748
+ try {
1749
+ out = await (0, exec_1.run)(bin, ['service-autostart', action], { timeoutMs: 30000 });
1750
+ }
1751
+ catch (legacyErr) {
1752
+ const legacyPartial = extractResult(legacyErr);
1753
+ if (action === 'status' && legacyPartial) {
1754
+ out = legacyPartial;
1755
+ return {
1756
+ ok: true,
1757
+ action,
1758
+ runner: bin,
1759
+ stdout: out.stdout.trim(),
1760
+ stderr: out.stderr.trim(),
1761
+ code: out.code
1762
+ };
1763
+ }
1764
+ throw legacyErr;
1765
+ }
1736
1766
  }
1737
1767
  else {
1738
1768
  throw err;
@@ -131,9 +131,12 @@ function getCoexistServices() {
131
131
  const defaults = [
132
132
  'swww-daemon.service',
133
133
  'swww-daemon@kitowall.service',
134
+ 'kitowall-watch.service',
135
+ 'kitowall-next.service',
136
+ 'kitowall-next.timer',
134
137
  'hyprwall-watch.service',
135
- 'hyprwall-next.timer',
136
- 'kitowall-next.timer'
138
+ 'hyprwall-next.service',
139
+ 'hyprwall-next.timer'
137
140
  ];
138
141
  const configured = Array.isArray(cfg.coexistServices) ? cfg.coexistServices.map(v => String(v).trim()).filter(Boolean) : [];
139
142
  return configured.length > 0 ? configured : defaults;
@@ -1005,14 +1008,26 @@ async function isUnitActive(unit) {
1005
1008
  return false;
1006
1009
  }
1007
1010
  }
1011
+ async function isUnitEnabled(unit) {
1012
+ try {
1013
+ const out = await (0, exec_1.run)('systemctl', ['--user', 'is-enabled', unit], { timeoutMs: 4000 });
1014
+ return out.stdout.trim() === 'enabled';
1015
+ }
1016
+ catch {
1017
+ return false;
1018
+ }
1019
+ }
1008
1020
  async function workshopCoexistenceEnter() {
1009
1021
  const paths = getWePaths();
1010
1022
  ensureWePaths(paths);
1011
1023
  const units = getCoexistServices();
1012
1024
  const active = [];
1025
+ const enabled = [];
1013
1026
  for (const unit of units) {
1014
1027
  if (await isUnitActive(unit))
1015
1028
  active.push(unit);
1029
+ if (await isUnitEnabled(unit))
1030
+ enabled.push(unit);
1016
1031
  }
1017
1032
  for (const unit of active) {
1018
1033
  try {
@@ -1022,8 +1037,16 @@ async function workshopCoexistenceEnter() {
1022
1037
  // best effort
1023
1038
  }
1024
1039
  }
1040
+ for (const unit of enabled) {
1041
+ try {
1042
+ await (0, exec_1.run)('systemctl', ['--user', 'disable', unit]);
1043
+ }
1044
+ catch {
1045
+ // best effort
1046
+ }
1047
+ }
1025
1048
  const snapshotId = String(now());
1026
- const snap = { id: snapshotId, ts: now(), active };
1049
+ const snap = { id: snapshotId, ts: now(), active, enabled };
1027
1050
  (0, fs_1.writeJson)(node_path_1.default.join(snapshotDir(paths), `${snapshotId}.json`), snap);
1028
1051
  (0, fs_1.writeJson)(snapshotFile(paths), { id: snapshotId, ts: snap.ts });
1029
1052
  return { ok: true, stopped: active, snapshot: active, snapshot_id: snapshotId };
@@ -1036,18 +1059,29 @@ async function workshopCoexistenceExit() {
1036
1059
  return { ok: true, restored: [] };
1037
1060
  const raw = JSON.parse(node_fs_1.default.readFileSync(snapPath, 'utf8'));
1038
1061
  let active = Array.isArray(raw.active) ? raw.active.map(v => String(v)) : [];
1062
+ let enabled = Array.isArray(raw.enabled) ? raw.enabled.map(v => String(v)) : [];
1039
1063
  if ((!active || active.length === 0) && raw.id) {
1040
1064
  const historical = node_path_1.default.join(snapshotDir(paths), `${raw.id}.json`);
1041
1065
  if (node_fs_1.default.existsSync(historical)) {
1042
1066
  try {
1043
1067
  const snap = JSON.parse(node_fs_1.default.readFileSync(historical, 'utf8'));
1044
1068
  active = Array.isArray(snap.active) ? snap.active.map(v => String(v)) : [];
1069
+ enabled = Array.isArray(snap.enabled) ? snap.enabled.map(v => String(v)) : [];
1045
1070
  }
1046
1071
  catch {
1047
1072
  active = [];
1073
+ enabled = [];
1048
1074
  }
1049
1075
  }
1050
1076
  }
1077
+ for (const unit of enabled) {
1078
+ try {
1079
+ await (0, exec_1.run)('systemctl', ['--user', 'enable', unit]);
1080
+ }
1081
+ catch {
1082
+ // best effort
1083
+ }
1084
+ }
1051
1085
  const restored = [];
1052
1086
  for (const unit of active) {
1053
1087
  try {
@@ -18,6 +18,7 @@ function run(cmd, args = [], options = {}) {
18
18
  'ffprobe',
19
19
  'cargo',
20
20
  'kitsune-livewallpaper',
21
+ 'kitsune-rendercore',
21
22
  'dd'
22
23
  ]);
23
24
  const useHost = isFlatpak && hostCommands.has(cmd);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kitowall",
3
- "version": "3.2.0",
3
+ "version": "3.4.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",