kitowall 3.5.0 → 3.5.11

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jhojan Esteban Molina Valencia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -66,6 +66,9 @@ npm run package:all
66
66
 
67
67
  ## Flatpak (Linux)
68
68
  ```bash
69
+ # 0) Install host deps (Arch Linux)
70
+ ./BOOTSTRAP_FLATPAK_BUILD_DEPS.sh
71
+
69
72
  # 1) Build desktop binary
70
73
  cd ui
71
74
  npm run tauri:build
@@ -78,6 +81,20 @@ cd ..
78
81
  flatpak-builder flatpak/build-dir flatpak/io.kitotsu.KitoWall.yml --user --install --force-clean
79
82
  ```
80
83
 
84
+ For Flathub source pipeline:
85
+ ```bash
86
+ ./BOOTSTRAP_FLATPAK_BUILD_DEPS.sh
87
+ ./GENERATE_FLATHUB_SOURCES.sh 2.1.0 && ./BUILD_FLATPAK_FROMSOURCE.sh
88
+ ```
89
+
90
+ Integrated local Flatpak (Kitowall + Kitsune + Kitsune-RenderCore in one app):
91
+ ```bash
92
+ ./BOOTSTRAP_FLATPAK_BUILD_DEPS.sh
93
+ ./GENERATE_FLATHUB_SOURCES.sh 2.1.0
94
+ ./BUILD_FLATPAK_INTEGRATED_LOCAL.sh
95
+ flatpak run io.kitotsu.KitoWall
96
+ ```
97
+
81
98
  ## User Docs
82
99
  - Current status: `STATUS.md`
83
100
  - Config examples: `CONFIG_EXAMPLES.md`
package/dist/cli.js CHANGED
File without changes
package/dist/core/init.js CHANGED
@@ -150,7 +150,10 @@ WantedBy=graphical-session.target
150
150
  await (0, exec_1.run)('systemctl', ['--user', 'daemon-reload']);
151
151
  await (0, exec_1.run)('systemctl', ['--user', 'enable', '--now', `swww-daemon@${ns}.service`]);
152
152
  await (0, exec_1.run)('systemctl', ['--user', 'enable', '--now', 'kitowall-watch.service']);
153
- await (0, exec_1.run)('systemctl', ['--user', 'enable', '--now', 'kitowall-login-apply.service']);
153
+ // login-apply is oneshot and can fail on first run if library is still empty.
154
+ // Enable it for next graphical session, but do not make init fail here.
155
+ await (0, exec_1.run)('systemctl', ['--user', 'enable', 'kitowall-login-apply.service']);
156
+ await (0, exec_1.run)('systemctl', ['--user', 'start', 'kitowall-login-apply.service']).catch(() => { });
154
157
  // Si quieres: aplicar una vez ahora mismo
155
158
  if (opts.apply) {
156
159
  const controller = new controller_1.Controller(config, state);
package/dist/core/live.js CHANGED
@@ -33,6 +33,7 @@ exports.liveViewData = liveViewData;
33
33
  const node_fs_1 = __importDefault(require("node:fs"));
34
34
  const node_os_1 = __importDefault(require("node:os"));
35
35
  const node_path_1 = __importDefault(require("node:path"));
36
+ const node_child_process_1 = require("node:child_process");
36
37
  const node_stream_1 = require("node:stream");
37
38
  const promises_1 = require("node:stream/promises");
38
39
  const exec_1 = require("../utils/exec");
@@ -46,6 +47,8 @@ const LIVE_BROWSER_UA = process.env.KITOWALL_LIVE_UA ||
46
47
  'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
47
48
  const LIVE_BROWSER_UA_FALLBACK = process.env.KITOWALL_LIVE_UA_FALLBACK ||
48
49
  'insomnia/12.3.1';
50
+ const INTEGRATED_RENDERCORE_ENV = 'KITOWALL_FLATPAK_INTEGRATED_RENDERCORE';
51
+ const INTEGRATED_RENDERCORE_BIN_FALLBACK = '/app/bin/kitsune-rendercore';
49
52
  function nowUnix() {
50
53
  return Math.floor(Date.now() / 1000);
51
54
  }
@@ -102,10 +105,13 @@ function getLiveLockPath() {
102
105
  return node_path_1.default.join(getLiveInternalRoot(), 'index.lock');
103
106
  }
104
107
  function defaultRunnerConfig() {
108
+ const integratedBin = clean(process.env.KITOWALL_LIVE_RUNNER_BIN) || INTEGRATED_RENDERCORE_BIN_FALLBACK;
105
109
  return {
106
110
  mode: 'bin',
107
111
  cargo_project_dir: process.env.KITOWALL_LIVE_RUNNER_PROJECT?.trim() || '',
108
- bin_name: process.env.KITOWALL_LIVE_RUNNER_BIN?.trim() || 'kitsune-rendercore'
112
+ bin_name: integratedRendercoreMode()
113
+ ? integratedBin
114
+ : (process.env.KITOWALL_LIVE_RUNNER_BIN?.trim() || 'kitsune-rendercore')
109
115
  };
110
116
  }
111
117
  function defaultIndex() {
@@ -864,6 +870,127 @@ function normalizeApplyDefaults(raw, fallback) {
864
870
  function rendercoreEnvPath() {
865
871
  return node_path_1.default.join(node_os_1.default.homedir(), '.config', 'kitsune-rendercore', 'env');
866
872
  }
873
+ function integratedRendercoreMode() {
874
+ if (!process.env.FLATPAK_ID)
875
+ return false;
876
+ const v = clean(process.env[INTEGRATED_RENDERCORE_ENV]).toLowerCase();
877
+ return v === '1' || v === 'true' || v === 'yes' || v === 'on';
878
+ }
879
+ function resolveRendercoreBin(index) {
880
+ if (integratedRendercoreMode()) {
881
+ return clean(process.env.KITOWALL_LIVE_RUNNER_BIN) || INTEGRATED_RENDERCORE_BIN_FALLBACK;
882
+ }
883
+ return clean(index.runner.bin_name) || 'kitsune-rendercore';
884
+ }
885
+ function integratedRendercorePidPath() {
886
+ return node_path_1.default.join(getLiveInternalRoot(), 'rendercore.pid');
887
+ }
888
+ function integratedRendercoreLogPath() {
889
+ return node_path_1.default.join(getLiveInternalRoot(), 'rendercore.log');
890
+ }
891
+ function readIntegratedRendercorePid() {
892
+ const p = integratedRendercorePidPath();
893
+ try {
894
+ const raw = clean(node_fs_1.default.readFileSync(p, 'utf8'));
895
+ const pid = Number(raw);
896
+ if (!Number.isInteger(pid) || pid <= 1)
897
+ return null;
898
+ return pid;
899
+ }
900
+ catch {
901
+ return null;
902
+ }
903
+ }
904
+ function processRunning(pid) {
905
+ try {
906
+ process.kill(pid, 0);
907
+ return true;
908
+ }
909
+ catch {
910
+ return false;
911
+ }
912
+ }
913
+ function integratedRendercoreStatus(bin) {
914
+ const pid = readIntegratedRendercorePid();
915
+ if (!pid) {
916
+ return {
917
+ stdout: `Integrated rendercore inactive (bin=${bin})`,
918
+ stderr: '',
919
+ code: 3
920
+ };
921
+ }
922
+ if (!processRunning(pid)) {
923
+ try {
924
+ node_fs_1.default.rmSync(integratedRendercorePidPath(), { force: true });
925
+ }
926
+ catch { }
927
+ return {
928
+ stdout: `Integrated rendercore stale pid file removed (pid=${pid})`,
929
+ stderr: '',
930
+ code: 3
931
+ };
932
+ }
933
+ return {
934
+ stdout: `Integrated rendercore active (pid=${pid}, bin=${bin})`,
935
+ stderr: '',
936
+ code: 0
937
+ };
938
+ }
939
+ function integratedRendercoreStart(bin) {
940
+ ensureLiveDirs();
941
+ const current = integratedRendercoreStatus(bin);
942
+ if (current.code === 0)
943
+ return current;
944
+ const outFd = node_fs_1.default.openSync(integratedRendercoreLogPath(), 'a');
945
+ const child = (0, node_child_process_1.spawn)(bin, [], {
946
+ cwd: node_os_1.default.homedir(),
947
+ env: process.env,
948
+ detached: true,
949
+ stdio: ['ignore', outFd, outFd]
950
+ });
951
+ child.unref();
952
+ node_fs_1.default.closeSync(outFd);
953
+ node_fs_1.default.writeFileSync(integratedRendercorePidPath(), `${child.pid}\n`, 'utf8');
954
+ return {
955
+ stdout: `Integrated rendercore started (pid=${child.pid}, bin=${bin})`,
956
+ stderr: '',
957
+ code: 0
958
+ };
959
+ }
960
+ function integratedRendercoreStop(bin) {
961
+ const pid = readIntegratedRendercorePid();
962
+ if (!pid) {
963
+ return {
964
+ stdout: `Integrated rendercore already stopped (bin=${bin})`,
965
+ stderr: '',
966
+ code: 0
967
+ };
968
+ }
969
+ try {
970
+ process.kill(pid, 'SIGTERM');
971
+ }
972
+ catch { }
973
+ const until = Date.now() + 2000;
974
+ while (Date.now() < until) {
975
+ if (!processRunning(pid))
976
+ break;
977
+ }
978
+ if (processRunning(pid)) {
979
+ try {
980
+ process.kill(pid, 'SIGKILL');
981
+ }
982
+ catch { }
983
+ }
984
+ try {
985
+ node_fs_1.default.rmSync(integratedRendercorePidPath(), { force: true });
986
+ }
987
+ catch { }
988
+ return {
989
+ stdout: `Integrated rendercore stopped (pid=${pid}, bin=${bin})`,
990
+ stderr: '',
991
+ code: 0
992
+ };
993
+ }
867
994
  function syncRendercoreEnv(defaults) {
868
995
  const p = rendercoreEnvPath();
869
996
  (0, fs_1.ensureDir)(node_path_1.default.dirname(p));
@@ -1516,7 +1643,7 @@ async function liveApply(opts) {
1516
1643
  if (!item || !node_fs_1.default.existsSync(item.file_path)) {
1517
1644
  throw new Error(`Live file not found for ${idRaw}. Re-download it with live fetch.`);
1518
1645
  }
1519
- const bin = index.runner.bin_name;
1646
+ const bin = resolveRendercoreBin(index);
1520
1647
  const setVideoArgs = [
1521
1648
  'set-video',
1522
1649
  '--monitor', monitor,
@@ -1526,8 +1653,13 @@ async function liveApply(opts) {
1526
1653
  // Live mode owns wallpaper state: stop static rotation services while live is active.
1527
1654
  await (0, workshop_1.workshopCoexistenceEnter)();
1528
1655
  // Keep live authority persistent across session restarts.
1529
- await (0, exec_1.run)(bin, ['service', 'install'], { timeoutMs: 20000 });
1530
- await (0, exec_1.run)(bin, ['service', 'enable'], { timeoutMs: 20000 });
1656
+ if (integratedRendercoreMode()) {
1657
+ integratedRendercoreStart(bin);
1658
+ }
1659
+ else {
1660
+ await (0, exec_1.run)(bin, ['service', 'install'], { timeoutMs: 20000 });
1661
+ await (0, exec_1.run)(bin, ['service', 'enable'], { timeoutMs: 20000 });
1662
+ }
1531
1663
  withLiveLock(() => {
1532
1664
  const current = readIndex();
1533
1665
  const updatedItems = current.items.map(v => (v.id === item.id ? { ...v, last_applied_at: nowUnix() } : v));
@@ -1670,18 +1802,26 @@ async function liveDoctor(opts) {
1670
1802
  catch {
1671
1803
  deps.hyprctl = false;
1672
1804
  }
1673
- try {
1674
- await (0, exec_1.run)('which', [index.runner.bin_name], { timeoutMs: 4000 });
1675
- deps.runner_bin = true;
1805
+ const runnerBin = resolveRendercoreBin(index);
1806
+ if (runnerBin.startsWith('/')) {
1807
+ deps.runner_bin = node_fs_1.default.existsSync(runnerBin);
1676
1808
  }
1677
- catch {
1678
- deps.runner_bin = false;
1679
- fix.push(`Install ${index.runner.bin_name} and ensure it is available in PATH`);
1809
+ else {
1810
+ try {
1811
+ await (0, exec_1.run)('which', [runnerBin], { timeoutMs: 4000 });
1812
+ deps.runner_bin = true;
1813
+ }
1814
+ catch {
1815
+ deps.runner_bin = false;
1816
+ }
1817
+ }
1818
+ if (!deps.runner_bin) {
1819
+ fix.push(`Install ${runnerBin} and ensure it is available in PATH`);
1680
1820
  }
1681
1821
  if (opts?.fix && deps.runner_bin) {
1682
1822
  try {
1683
- await (0, exec_1.run)(index.runner.bin_name, ['install-deps'], { timeoutMs: 180000 });
1684
- fix.push(`Executed: ${index.runner.bin_name} install-deps`);
1823
+ await (0, exec_1.run)(runnerBin, ['install-deps'], { timeoutMs: 180000 });
1824
+ fix.push(`Executed: ${runnerBin} install-deps`);
1685
1825
  try {
1686
1826
  await (0, exec_1.run)('ffmpeg', ['-version'], { timeoutMs: 4000 });
1687
1827
  deps.ffmpeg = true;
@@ -1702,7 +1842,7 @@ async function liveDoctor(opts) {
1702
1842
  }
1703
1843
  }
1704
1844
  else if (!deps.ffmpeg || !deps.hyprctl) {
1705
- fix.push(`Run '${index.runner.bin_name} install-deps' to install missing runtime dependencies`);
1845
+ fix.push(`Run '${runnerBin} install-deps' to install missing runtime dependencies`);
1706
1846
  }
1707
1847
  return {
1708
1848
  ok: Object.values(deps).every(Boolean) || (deps.hyprctl === false && Object.keys(deps).filter(k => k !== 'hyprctl').every(k => deps[k])),
@@ -1714,7 +1854,31 @@ async function liveDoctor(opts) {
1714
1854
  }
1715
1855
  async function liveServiceAutostart(action) {
1716
1856
  const index = readIndex();
1717
- let bin = index.runner.bin_name;
1857
+ let bin = resolveRendercoreBin(index);
1858
+ if (integratedRendercoreMode()) {
1859
+ let out;
1860
+ if (action === 'status') {
1861
+ out = integratedRendercoreStatus(bin);
1862
+ }
1863
+ else if (action === 'start' || action === 'enable' || action === 'install') {
1864
+ out = integratedRendercoreStart(bin);
1865
+ }
1866
+ else if (action === 'stop' || action === 'disable') {
1867
+ out = integratedRendercoreStop(bin);
1868
+ }
1869
+ else {
1870
+ integratedRendercoreStop(bin);
1871
+ out = integratedRendercoreStart(bin);
1872
+ }
1873
+ return {
1874
+ ok: true,
1875
+ action,
1876
+ runner: `${bin} (integrated)`,
1877
+ stdout: out.stdout.trim(),
1878
+ stderr: out.stderr.trim(),
1879
+ code: out.code
1880
+ };
1881
+ }
1718
1882
  let out;
1719
1883
  const extractResult = (err) => {
1720
1884
  const result = err?.result;
@@ -36,6 +36,37 @@ async function installSystemd(opts) {
36
36
  const userDir = (0, node_path_1.join)((0, node_os_1.homedir)(), '.config', 'systemd', 'user');
37
37
  ensureDir(userDir);
38
38
  const every = systemdInterval(opts.every);
39
+ const ns = (opts.namespace && opts.namespace.trim()) ? opts.namespace.trim() : 'kitowall';
40
+ const isFlatpak = Boolean(process.env.FLATPAK_ID);
41
+ const flatpakAppId = (process.env.FLATPAK_ID || 'io.kitotsu.KitoWall').trim();
42
+ const nodePath = process.execPath;
43
+ const cliPath = (0, node_path_1.resolve)(process.argv[1] || '');
44
+ const xdgRuntimeDir = (process.env.XDG_RUNTIME_DIR && process.env.XDG_RUNTIME_DIR.trim())
45
+ ? process.env.XDG_RUNTIME_DIR.trim()
46
+ : `/run/user/${process.getuid?.() ?? 1000}`;
47
+ const pathEnv = [
48
+ `${(0, node_os_1.homedir)()}/.local/bin`,
49
+ '/usr/local/bin',
50
+ '/usr/bin',
51
+ '/bin'
52
+ ].join(':');
53
+ const waylandBootstrap = 'WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-$(ls \\"$XDG_RUNTIME_DIR\\"/wayland-* 2>/dev/null | xargs -r -n1 basename | sort | tail -n1)}"; ' +
54
+ 'if [ -z "$WAYLAND_DISPLAY" ]; then WAYLAND_DISPLAY=wayland-1; fi; ' +
55
+ 'export WAYLAND_DISPLAY;';
56
+ const nextCmd = isFlatpak
57
+ ? `${waylandBootstrap} exec /usr/bin/flatpak run --command=kitowall ${flatpakAppId} rotate-now --namespace ${ns} --force`
58
+ : `${waylandBootstrap} exec ${JSON.stringify(nodePath)} ${JSON.stringify(cliPath)} rotate-now --namespace ${JSON.stringify(ns)} --force`;
59
+ const kitowallNextService = `
60
+ [Unit]
61
+ Description=Kitowall apply next wallpapers
62
+
63
+ [Service]
64
+ Type=oneshot
65
+ Environment=PATH=${pathEnv}
66
+ Environment=XDG_RUNTIME_DIR=${xdgRuntimeDir}
67
+ ExecStart=/bin/sh -lc ${JSON.stringify(nextCmd)}
68
+ `.trimStart();
69
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(userDir, 'kitowall-next.service'), kitowallNextService, 'utf8');
39
70
  const kitowallTimer = `
40
71
  [Unit]
41
72
  Description=Run kitowall-next periodically
@@ -54,6 +85,8 @@ async function installSystemd(opts) {
54
85
  `.trimStart();
55
86
  (0, node_fs_1.writeFileSync)((0, node_path_1.join)(userDir, 'kitowall-next.timer'), kitowallTimer, 'utf8');
56
87
  await (0, exec_1.run)('systemctl', ['--user', 'daemon-reload']);
88
+ await (0, exec_1.run)('systemctl', ['--user', 'reset-failed', 'kitowall-next.service']).catch(() => { });
89
+ await (0, exec_1.run)('systemctl', ['--user', 'reset-failed', 'kitowall-next.timer']).catch(() => { });
57
90
  await (0, exec_1.run)('systemctl', ['--user', 'enable', '--now', 'kitowall-next.timer']);
58
91
  }
59
92
  async function uninstallSystemd() {
@@ -6,21 +6,25 @@ 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 integratedRendercore = isFlatpak && (process.env.KITOWALL_FLATPAK_INTEGRATED_RENDERCORE === '1'
10
+ || process.env.KITOWALL_FLATPAK_INTEGRATED_RENDERCORE === 'true');
9
11
  const hostCommands = new Set([
10
12
  'swww',
11
13
  'swww-daemon',
12
14
  'hyprctl',
13
15
  'systemctl',
14
- 'which',
15
16
  'xdg-open',
16
17
  'steamcmd',
17
18
  'ffmpeg',
18
19
  'ffprobe',
19
20
  'cargo',
20
21
  'kitsune-livewallpaper',
21
- 'kitsune-rendercore',
22
22
  'dd'
23
23
  ]);
24
+ if (!integratedRendercore) {
25
+ hostCommands.add('which');
26
+ hostCommands.add('kitsune-rendercore');
27
+ }
24
28
  const useHost = isFlatpak && hostCommands.has(cmd);
25
29
  const finalCmd = useHost ? 'flatpak-spawn' : cmd;
26
30
  const finalArgs = useHost ? ['--host', cmd, ...args] : args;
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "kitowall",
3
- "version": "3.5.0",
3
+ "version": "3.5.11",
4
4
  "description": "CLI/daemon for Hyprland wallpapers using swww with pack-based rotation.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/KitotsuMolina/Kitowall"
8
+ },
5
9
  "license": "SEE LICENSE IN LICENSE.md",
6
10
  "type": "commonjs",
7
11
  "files": [