kitowall 3.5.14 → 3.5.27

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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  `Kitowall` is a wallpaper manager for Hyprland/Wayland using `swww`.
6
6
 
7
- Current version: `1.0.7`.
7
+ Current version: `3.5.15`.
8
8
 
9
9
  ## What You Can Do
10
10
  - Rotate wallpapers with transitions.
@@ -43,11 +43,35 @@ If your system needs it on Wayland:
43
43
  WEBKIT_DISABLE_DMABUF_RENDERER=1 npm run tauri:dev
44
44
  ```
45
45
 
46
+ ## AppImage (Recommended)
47
+ AppImage is now the primary desktop distribution for `kitowall-ui`.
48
+
49
+ 1) Download the latest `Kitowall-<version>-x86_64.AppImage` from GitHub Releases.
50
+ 2) Make it executable:
51
+ ```bash
52
+ chmod +x ./Kitowall-*.AppImage
53
+ ```
54
+ 3) First-time host bootstrap (installs CLI/runtime dependencies on host):
55
+ ```bash
56
+ ./scripts/bootstrap-host.sh
57
+ ```
58
+ If `bootstrap-host.sh` fails on Arch with errors like `zstd: undefined symbol: POOL_free` or `bsdtar: Write error`, repair core compression/archive libs and retry:
59
+ ```bash
60
+ sudo pacman -Syu
61
+ sudo pacman -S --overwrite '*' zstd libarchive
62
+ ```
63
+ Reason: this fixes host library mismatches (partial/out-of-sync updates) that break AUR package builds and package compression.
64
+
65
+ 4) Initialize services:
66
+ ```bash
67
+ kitowall init --namespace kitowall --apply --force
68
+ ```
69
+
46
70
  ## Package / Release
47
71
  - Release checklist: `RELEASE_CHECKLIST.md`
48
72
  - Release notes: `RELEASE_NOTES_1.0.0.md`
49
73
  - Dependencies: `DEPENDENCIES.md`
50
- - Flatpak packaging: `flatpak/`
74
+ - AppImage CI: `.github/workflows/build-appimage.yml`
51
75
 
52
76
  Main commands:
53
77
  ```bash
@@ -64,46 +88,11 @@ npm run package:ui
64
88
  npm run package:all
65
89
  ```
66
90
 
67
- ## Flatpak (Linux)
68
- ```bash
69
- # 0) Install host deps (Arch Linux)
70
- ./BOOTSTRAP_FLATPAK_BUILD_DEPS.sh
71
-
72
- # 1) Build desktop binary
73
- cd ui
74
- npm run tauri:build
75
- cd ..
76
-
77
- # 2) Prepare flatpak sources (binary + icon)
78
- ./flatpak/prepare.sh
79
-
80
- # 3) Build and install flatpak
81
- flatpak-builder flatpak/build-dir flatpak/io.kitotsu.KitoWall.yml --user --install --force-clean
82
- ```
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
-
98
91
  ## User Docs
99
92
  - Current status: `STATUS.md`
100
93
  - Config examples: `CONFIG_EXAMPLES.md`
101
94
  - UI details: `ui/README.md`
102
95
 
103
- ## Known Issues
104
- - Flatpak watch unit failing with `/app/bin/node` in user systemd:
105
- - `issues/flatpak-watch-service-failed.md`
106
-
107
96
  ## Legal
108
97
  - License: `LICENSE.md`
109
98
  - Attribution notice: `NOTICE.md`
package/dist/core/init.js CHANGED
@@ -17,9 +17,6 @@ function esc(a) {
17
17
  return JSON.stringify(a);
18
18
  }
19
19
  async function runHostShell(cmd) {
20
- if (process.env.FLATPAK_ID) {
21
- return (0, exec_1.run)('flatpak-spawn', ['--host', 'sh', '-lc', cmd]);
22
- }
23
20
  return (0, exec_1.run)('sh', ['-lc', cmd]);
24
21
  }
25
22
  async function hostCmdExists(cmd) {
@@ -63,44 +60,6 @@ async function ensureHostDeps() {
63
60
  `Install on host (Arch): sudo pacman -S --needed swww hyprland`);
64
61
  }
65
62
  }
66
- async function ensureHostFlatpakBridges(appId) {
67
- const home = (0, node_os_1.homedir)();
68
- const localBinDir = (0, node_path_1.join)(home, '.local', 'bin');
69
- ensureDir(localBinDir);
70
- const writeBridge = (name, command) => {
71
- const bridgePath = (0, node_path_1.join)(localBinDir, name);
72
- const bridgeScript = `#!/usr/bin/env bash
73
- set -euo pipefail
74
- exec /usr/bin/flatpak run --command=${command} ${appId} "$@"
75
- `;
76
- (0, node_fs_1.writeFileSync)(bridgePath, bridgeScript, { encoding: 'utf8', mode: 0o755 });
77
- return bridgePath;
78
- };
79
- writeBridge('kitowall', 'kitowall');
80
- writeBridge('kitsune', 'kitsune');
81
- const rendercoreBridgePath = writeBridge('kitsune-rendercore', 'kitsune-rendercore');
82
- const userDir = (0, node_path_1.join)(home, '.config', 'systemd', 'user');
83
- ensureDir(userDir);
84
- const unitPath = (0, node_path_1.join)(userDir, 'kitsune-rendercore.service');
85
- const unit = `
86
- [Unit]
87
- Description=Kitsune RenderCore Live Wallpaper (Flatpak bridge)
88
- After=graphical-session.target
89
- PartOf=graphical-session.target
90
-
91
- [Service]
92
- Type=simple
93
- ExecStart=${rendercoreBridgePath}
94
- Restart=on-failure
95
- RestartSec=1
96
-
97
- [Install]
98
- WantedBy=default.target
99
- `.trimStart();
100
- (0, node_fs_1.writeFileSync)(unitPath, unit, 'utf8');
101
- await (0, exec_1.run)('systemctl', ['--user', 'daemon-reload']);
102
- await (0, exec_1.run)('systemctl', ['--user', 'enable', '--now', 'kitsune-rendercore.service']).catch(() => { });
103
- }
104
63
  async function disableIfExists(unit) {
105
64
  await (0, exec_1.run)('systemctl', ['--user', 'disable', '--now', unit]).catch(() => { });
106
65
  await (0, exec_1.run)('systemctl', ['--user', 'reset-failed', unit]).catch(() => { });
@@ -128,22 +87,15 @@ async function initKitowall(opts) {
128
87
  const state = (0, state_1.loadState)(); // crea/migra state si hace falta
129
88
  const ns = (opts.namespace && opts.namespace.trim()) ? opts.namespace.trim() : 'kitowall';
130
89
  const force = !!opts.force;
131
- const isFlatpak = Boolean(process.env.FLATPAK_ID);
132
- const flatpakAppId = (process.env.FLATPAK_ID || 'io.kitotsu.KitoWall').trim();
133
90
  // Validaciones mínimas
134
91
  await ensureHostDeps();
135
- if (isFlatpak) {
136
- await ensureHostFlatpakBridges(flatpakAppId);
137
- }
138
92
  // Apagar servicios que pisan el wallpaper
139
93
  await detectAndHandleConflicts(force);
140
94
  const userDir = (0, node_path_1.join)((0, node_os_1.homedir)(), '.config', 'systemd', 'user');
141
95
  ensureDir(userDir);
142
96
  const nodePath = process.execPath;
143
97
  const cliPath = (0, node_path_1.resolve)(process.argv[1]); // dist/cli.js absoluto
144
- const cliInvoke = isFlatpak
145
- ? `/usr/bin/flatpak run --command=kitowall ${flatpakAppId}`
146
- : `${JSON.stringify(nodePath)} ${JSON.stringify(cliPath)}`;
98
+ const cliInvoke = `${JSON.stringify(nodePath)} ${JSON.stringify(cliPath)}`;
147
99
  const xdgRuntimeDir = (process.env.XDG_RUNTIME_DIR && process.env.XDG_RUNTIME_DIR.trim())
148
100
  ? process.env.XDG_RUNTIME_DIR.trim()
149
101
  : `/run/user/${process.getuid?.() ?? 1000}`;
package/dist/core/live.js CHANGED
@@ -47,8 +47,8 @@ const LIVE_BROWSER_UA = process.env.KITOWALL_LIVE_UA ||
47
47
  'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
48
48
  const LIVE_BROWSER_UA_FALLBACK = process.env.KITOWALL_LIVE_UA_FALLBACK ||
49
49
  'insomnia/12.3.1';
50
- const INTEGRATED_RENDERCORE_ENV = 'KITOWALL_FLATPAK_INTEGRATED_RENDERCORE';
51
- const INTEGRATED_RENDERCORE_BIN_FALLBACK = '/app/bin/kitsune-rendercore';
50
+ const INTEGRATED_RENDERCORE_ENV = 'KITOWALL_INTEGRATED_RENDERCORE';
51
+ const INTEGRATED_RENDERCORE_BIN_FALLBACK = 'kitsune-rendercore';
52
52
  function nowUnix() {
53
53
  return Math.floor(Date.now() / 1000);
54
54
  }
@@ -871,8 +871,6 @@ function rendercoreEnvPath() {
871
871
  return node_path_1.default.join(node_os_1.default.homedir(), '.config', 'kitsune-rendercore', 'env');
872
872
  }
873
873
  function integratedRendercoreMode() {
874
- if (!process.env.FLATPAK_ID)
875
- return false;
876
874
  const v = clean(process.env[INTEGRATED_RENDERCORE_ENV]).toLowerCase();
877
875
  return v === '1' || v === 'true' || v === 'yes' || v === 'on';
878
876
  }
@@ -1044,6 +1042,158 @@ function syncRendercoreEnv(defaults) {
1044
1042
  }
1045
1043
  node_fs_1.default.writeFileSync(p, `${lines.join('\n')}\n`, 'utf8');
1046
1044
  }
1045
+ function shSingleQuote(v) {
1046
+ return `'${String(v).replace(/'/g, `'\\''`)}'`;
1047
+ }
1048
+ async function hostCommandExists(cmd) {
1049
+ try {
1050
+ await (0, exec_1.run)('sh', ['-lc', `command -v ${cmd} >/dev/null 2>&1`], { timeoutMs: 5000 });
1051
+ return true;
1052
+ }
1053
+ catch {
1054
+ return false;
1055
+ }
1056
+ }
1057
+ async function resolveHostExecutablePath(bin) {
1058
+ const raw = clean(bin);
1059
+ if (!raw)
1060
+ throw new Error('empty binary name');
1061
+ if (raw.includes('/')) {
1062
+ if (!node_fs_1.default.existsSync(raw))
1063
+ throw new Error(`binary not found: ${raw}`);
1064
+ return raw;
1065
+ }
1066
+ const out = await (0, exec_1.run)('sh', ['-lc', `command -v ${raw}`], { timeoutMs: 5000 });
1067
+ const resolved = clean(out.stdout.split('\n')[0]);
1068
+ if (!resolved)
1069
+ throw new Error(`binary not found in PATH: ${raw}`);
1070
+ return resolved;
1071
+ }
1072
+ async function installHostPackagesArch(packages) {
1073
+ if (packages.length === 0)
1074
+ return;
1075
+ const quoted = packages.map(shSingleQuote).join(' ');
1076
+ const cmd = `if command -v sudo >/dev/null 2>&1; then ` +
1077
+ `(sudo -n pacman -S --needed --noconfirm ${quoted} || sudo pacman -S --needed ${quoted}); ` +
1078
+ `else pacman -S --needed ${quoted}; fi`;
1079
+ await (0, exec_1.run)('sh', ['-lc', cmd], { timeoutMs: 240000 });
1080
+ }
1081
+ async function ensureRendercoreHostRuntimeDeps() {
1082
+ const missing = [];
1083
+ if (!(await hostCommandExists('ffmpeg')))
1084
+ missing.push('ffmpeg');
1085
+ if (!(await hostCommandExists('hyprctl')))
1086
+ missing.push('hyprctl');
1087
+ if (!(await hostCommandExists('systemctl')))
1088
+ missing.push('systemctl');
1089
+ if (missing.length === 0)
1090
+ return { installed: [], missing: [] };
1091
+ const canPacman = await hostCommandExists('pacman');
1092
+ const toInstall = new Set();
1093
+ if (missing.includes('ffmpeg'))
1094
+ toInstall.add('ffmpeg');
1095
+ if (missing.includes('hyprctl'))
1096
+ toInstall.add('hyprland');
1097
+ if (canPacman && toInstall.size > 0) {
1098
+ await installHostPackagesArch(Array.from(toInstall));
1099
+ }
1100
+ const afterMissing = [];
1101
+ if (!(await hostCommandExists('ffmpeg')))
1102
+ afterMissing.push('ffmpeg');
1103
+ if (!(await hostCommandExists('hyprctl')))
1104
+ afterMissing.push('hyprctl');
1105
+ if (!(await hostCommandExists('systemctl')))
1106
+ afterMissing.push('systemctl');
1107
+ return { installed: Array.from(toInstall), missing: afterMissing };
1108
+ }
1109
+ function rendercoreVideoMapPath() {
1110
+ return node_path_1.default.join(node_os_1.default.homedir(), '.config', 'kitsune-rendercore', 'video-map.conf');
1111
+ }
1112
+ function rendercoreUserServicePath() {
1113
+ return node_path_1.default.join(node_os_1.default.homedir(), '.config', 'systemd', 'user', 'kitsune-rendercore.service');
1114
+ }
1115
+ function readEnvPairs(p) {
1116
+ const out = {};
1117
+ if (!node_fs_1.default.existsSync(p))
1118
+ return out;
1119
+ const raw = node_fs_1.default.readFileSync(p, 'utf8');
1120
+ for (const line of raw.split('\n')) {
1121
+ const t = line.trim();
1122
+ if (!t || t.startsWith('#'))
1123
+ continue;
1124
+ const eq = t.indexOf('=');
1125
+ if (eq <= 0)
1126
+ continue;
1127
+ out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim();
1128
+ }
1129
+ return out;
1130
+ }
1131
+ function writeEnvPairs(p, pairs) {
1132
+ const keys = Object.keys(pairs).sort((a, b) => a.localeCompare(b));
1133
+ const lines = keys.map((k) => `${k}=${pairs[k]}`);
1134
+ node_fs_1.default.writeFileSync(p, `${lines.join('\n')}\n`, 'utf8');
1135
+ }
1136
+ function ensureRendercoreServiceFiles(bin, defaults) {
1137
+ const envPath = rendercoreEnvPath();
1138
+ const mapPath = rendercoreVideoMapPath();
1139
+ const servicePath = rendercoreUserServicePath();
1140
+ (0, fs_1.ensureDir)(node_path_1.default.dirname(envPath));
1141
+ (0, fs_1.ensureDir)(node_path_1.default.dirname(servicePath));
1142
+ if (!node_fs_1.default.existsSync(mapPath))
1143
+ node_fs_1.default.writeFileSync(mapPath, '', 'utf8');
1144
+ const envPairs = readEnvPairs(envPath);
1145
+ envPairs.KRC_VIDEO_MAP_FILE = mapPath;
1146
+ envPairs.KRC_VIDEO_FPS = String(defaults.video_fps);
1147
+ envPairs.KRC_VIDEO_SPEED = String(defaults.video_speed);
1148
+ envPairs.KRC_HWACCEL = defaults.hwaccel;
1149
+ envPairs.KRC_QUALITY = defaults.quality;
1150
+ envPairs.KRC_PAUSE_ON_STEAM_GAME = defaults.pause_on_steam_game ? 'true' : 'false';
1151
+ envPairs.KRC_STEAM_POLL_MS = String(defaults.steam_poll_ms);
1152
+ writeEnvPairs(envPath, envPairs);
1153
+ const home = node_os_1.default.homedir();
1154
+ const pathEnv = `${home}/.local/bin:${home}/.cargo/bin:/usr/local/bin:/usr/bin:/bin`;
1155
+ const serviceBody = [
1156
+ '[Unit]',
1157
+ 'Description=Kitsune RenderCore Live Wallpaper',
1158
+ 'After=graphical-session.target',
1159
+ 'PartOf=graphical-session.target',
1160
+ '',
1161
+ '[Service]',
1162
+ 'Type=simple',
1163
+ `Environment=PATH=${pathEnv}`,
1164
+ `EnvironmentFile=-${envPath}`,
1165
+ `ExecStart=${bin}`,
1166
+ 'Restart=on-failure',
1167
+ 'RestartSec=1',
1168
+ '',
1169
+ '[Install]',
1170
+ 'WantedBy=graphical-session.target',
1171
+ ''
1172
+ ].join('\n');
1173
+ node_fs_1.default.writeFileSync(servicePath, serviceBody, 'utf8');
1174
+ return { service_path: servicePath, env_path: envPath, map_path: mapPath };
1175
+ }
1176
+ function extractExecResult(err) {
1177
+ const result = err?.result;
1178
+ if (!result)
1179
+ return null;
1180
+ return {
1181
+ stdout: String(result.stdout ?? ''),
1182
+ stderr: String(result.stderr ?? ''),
1183
+ code: Number(result.code ?? 1)
1184
+ };
1185
+ }
1186
+ async function runSystemctlUser(args, acceptNonZero = false) {
1187
+ try {
1188
+ return await (0, exec_1.run)('systemctl', ['--user', ...args], { timeoutMs: 30000 });
1189
+ }
1190
+ catch (err) {
1191
+ const partial = extractExecResult(err);
1192
+ if (acceptNonZero && partial)
1193
+ return partial;
1194
+ throw err;
1195
+ }
1196
+ }
1047
1197
  async function resolvePost(provider, pageUrl) {
1048
1198
  const html = await fetchHtml(pageUrl, pageUrl);
1049
1199
  const parsed = liveParsePostFromHtml(provider, pageUrl, html);
@@ -1657,8 +1807,16 @@ async function liveApply(opts) {
1657
1807
  integratedRendercoreStart(bin);
1658
1808
  }
1659
1809
  else {
1660
- await (0, exec_1.run)(bin, ['service', 'install'], { timeoutMs: 20000 });
1661
- await (0, exec_1.run)(bin, ['service', 'enable'], { timeoutMs: 20000 });
1810
+ const deps = await ensureRendercoreHostRuntimeDeps();
1811
+ if (deps.missing.length > 0) {
1812
+ throw new Error(`Missing host dependencies: ${deps.missing.join(', ')}. ` +
1813
+ 'Install on host (Arch): sudo pacman -S --needed ffmpeg hyprland');
1814
+ }
1815
+ const binPath = await resolveHostExecutablePath(bin);
1816
+ ensureRendercoreServiceFiles(binPath, index.apply_defaults);
1817
+ await runSystemctlUser(['daemon-reload']);
1818
+ await runSystemctlUser(['enable', 'kitsune-rendercore.service']);
1819
+ await runSystemctlUser(['start', 'kitsune-rendercore.service']);
1662
1820
  }
1663
1821
  withLiveLock(() => {
1664
1822
  const current = readIndex();
@@ -1818,10 +1976,15 @@ async function liveDoctor(opts) {
1818
1976
  if (!deps.runner_bin) {
1819
1977
  fix.push(`Install ${runnerBin} and ensure it is available in PATH`);
1820
1978
  }
1821
- if (opts?.fix && deps.runner_bin) {
1979
+ if (opts?.fix) {
1822
1980
  try {
1823
- await (0, exec_1.run)(runnerBin, ['install-deps'], { timeoutMs: 180000 });
1824
- fix.push(`Executed: ${runnerBin} install-deps`);
1981
+ const ensured = await ensureRendercoreHostRuntimeDeps();
1982
+ if (ensured.installed.length > 0) {
1983
+ fix.push(`Installed host packages: ${ensured.installed.join(', ')}`);
1984
+ }
1985
+ if (ensured.missing.length > 0) {
1986
+ fix.push(`Still missing host dependencies: ${ensured.missing.join(', ')}`);
1987
+ }
1825
1988
  try {
1826
1989
  await (0, exec_1.run)('ffmpeg', ['-version'], { timeoutMs: 4000 });
1827
1990
  deps.ffmpeg = true;
@@ -1838,11 +2001,11 @@ async function liveDoctor(opts) {
1838
2001
  }
1839
2002
  }
1840
2003
  catch (e) {
1841
- fix.push(`Failed to execute '${index.runner.bin_name} install-deps': ${String(e)}`);
2004
+ fix.push(`Failed to install host dependencies: ${String(e)}`);
1842
2005
  }
1843
2006
  }
1844
2007
  else if (!deps.ffmpeg || !deps.hyprctl) {
1845
- fix.push(`Run '${runnerBin} install-deps' to install missing runtime dependencies`);
2008
+ fix.push('Run `kitowall live doctor --fix` to install missing runtime dependencies');
1846
2009
  }
1847
2010
  return {
1848
2011
  ok: Object.values(deps).every(Boolean) || (deps.hyprctl === false && Object.keys(deps).filter(k => k !== 'hyprctl').every(k => deps[k])),
@@ -1880,62 +2043,61 @@ async function liveServiceAutostart(action) {
1880
2043
  };
1881
2044
  }
1882
2045
  let out;
1883
- const extractResult = (err) => {
1884
- const result = err?.result;
1885
- if (!result)
1886
- return null;
1887
- return {
1888
- stdout: String(result.stdout ?? ''),
1889
- stderr: String(result.stderr ?? ''),
1890
- code: Number(result.code ?? 1)
1891
- };
1892
- };
1893
- try {
1894
- out = await (0, exec_1.run)(bin, ['service', action], { timeoutMs: 30000 });
1895
- }
1896
- catch (err) {
1897
- const partial = extractResult(err);
1898
- if (action === 'status' && partial) {
1899
- out = partial;
1900
- return {
1901
- ok: true,
1902
- action,
1903
- runner: bin,
1904
- stdout: out.stdout.trim(),
1905
- stderr: out.stderr.trim(),
1906
- code: out.code
2046
+ if (action === 'install') {
2047
+ const deps = await ensureRendercoreHostRuntimeDeps();
2048
+ if (deps.missing.length > 0) {
2049
+ const message = `Missing host dependencies: ${deps.missing.join(', ')}. ` +
2050
+ 'Install on host (Arch): sudo pacman -S --needed ffmpeg hyprland';
2051
+ out = { stdout: '', stderr: message, code: 1 };
2052
+ }
2053
+ else {
2054
+ const binPath = await resolveHostExecutablePath(bin);
2055
+ const installed = ensureRendercoreServiceFiles(binPath, index.apply_defaults);
2056
+ await runSystemctlUser(['daemon-reload']);
2057
+ out = {
2058
+ stdout: `Installed user service at ${installed.service_path}`,
2059
+ stderr: '',
2060
+ code: 0
1907
2061
  };
1908
2062
  }
1909
- // Legacy compatibility: if user still points to kitsune-livewallpaper,
1910
- // map to its older subcommand to avoid hard failure.
1911
- if (clean(bin) === 'kitsune-livewallpaper') {
1912
- try {
1913
- out = await (0, exec_1.run)(bin, ['service-autostart', action], { timeoutMs: 30000 });
1914
- }
1915
- catch (legacyErr) {
1916
- const legacyPartial = extractResult(legacyErr);
1917
- if (action === 'status' && legacyPartial) {
1918
- out = legacyPartial;
1919
- return {
1920
- ok: true,
1921
- action,
1922
- runner: bin,
1923
- stdout: out.stdout.trim(),
1924
- stderr: out.stderr.trim(),
1925
- code: out.code
1926
- };
1927
- }
1928
- throw legacyErr;
1929
- }
2063
+ }
2064
+ else if (action === 'status') {
2065
+ out = await runSystemctlUser(['status', '--no-pager', 'kitsune-rendercore.service'], true);
2066
+ }
2067
+ else if (action === 'enable') {
2068
+ if (!node_fs_1.default.existsSync(rendercoreUserServicePath())) {
2069
+ const binPath = await resolveHostExecutablePath(bin);
2070
+ ensureRendercoreServiceFiles(binPath, index.apply_defaults);
2071
+ await runSystemctlUser(['daemon-reload']);
1930
2072
  }
1931
- else {
1932
- throw err;
2073
+ out = await runSystemctlUser(['enable', 'kitsune-rendercore.service']);
2074
+ }
2075
+ else if (action === 'disable') {
2076
+ out = await runSystemctlUser(['disable', '--now', 'kitsune-rendercore.service'], true);
2077
+ }
2078
+ else if (action === 'start') {
2079
+ if (!node_fs_1.default.existsSync(rendercoreUserServicePath())) {
2080
+ const binPath = await resolveHostExecutablePath(bin);
2081
+ ensureRendercoreServiceFiles(binPath, index.apply_defaults);
2082
+ await runSystemctlUser(['daemon-reload']);
2083
+ }
2084
+ out = await runSystemctlUser(['start', 'kitsune-rendercore.service']);
2085
+ }
2086
+ else if (action === 'stop') {
2087
+ out = await runSystemctlUser(['stop', 'kitsune-rendercore.service'], true);
2088
+ }
2089
+ else {
2090
+ if (!node_fs_1.default.existsSync(rendercoreUserServicePath())) {
2091
+ const binPath = await resolveHostExecutablePath(bin);
2092
+ ensureRendercoreServiceFiles(binPath, index.apply_defaults);
2093
+ await runSystemctlUser(['daemon-reload']);
1933
2094
  }
2095
+ out = await runSystemctlUser(['restart', 'kitsune-rendercore.service']);
1934
2096
  }
1935
2097
  return {
1936
2098
  ok: true,
1937
2099
  action,
1938
- runner: bin,
2100
+ runner: `${bin} (systemd-user)`,
1939
2101
  stdout: out.stdout.trim(),
1940
2102
  stderr: out.stderr.trim(),
1941
2103
  code: out.code
@@ -37,8 +37,6 @@ async function installSystemd(opts) {
37
37
  ensureDir(userDir);
38
38
  const every = systemdInterval(opts.every);
39
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
40
  const nodePath = process.execPath;
43
41
  const cliPath = (0, node_path_1.resolve)(process.argv[1] || '');
44
42
  const xdgRuntimeDir = (process.env.XDG_RUNTIME_DIR && process.env.XDG_RUNTIME_DIR.trim())
@@ -53,9 +51,7 @@ async function installSystemd(opts) {
53
51
  const waylandBootstrap = 'WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-$(ls \\"$XDG_RUNTIME_DIR\\"/wayland-* 2>/dev/null | xargs -r -n1 basename | sort | tail -n1)}"; ' +
54
52
  'if [ -z "$WAYLAND_DISPLAY" ]; then WAYLAND_DISPLAY=wayland-1; fi; ' +
55
53
  '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`;
54
+ const nextCmd = `${waylandBootstrap} exec ${JSON.stringify(nodePath)} ${JSON.stringify(cliPath)} rotate-now --namespace ${JSON.stringify(ns)} --force`;
59
55
  const kitowallNextService = `
60
56
  [Unit]
61
57
  Description=Kitowall apply next wallpapers
@@ -8,10 +8,7 @@ function startSwwwDaemon(namespace) {
8
8
  const args = [];
9
9
  if (namespace)
10
10
  args.push('--namespace', namespace);
11
- const inFlatpak = Boolean(process.env.FLATPAK_ID);
12
- const cmd = inFlatpak ? 'flatpak-spawn' : 'swww-daemon';
13
- const spawnArgs = inFlatpak ? ['--host', 'swww-daemon', ...args] : args;
14
- const child = (0, node_child_process_1.spawn)(cmd, spawnArgs, {
11
+ const child = (0, node_child_process_1.spawn)('swww-daemon', args, {
15
12
  stdio: 'ignore',
16
13
  detached: true
17
14
  });
@@ -5,29 +5,8 @@ exports.run = run;
5
5
  const child_process_1 = require("child_process");
6
6
  function run(cmd, args = [], options = {}) {
7
7
  return new Promise((resolve, reject) => {
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');
11
- const hostCommands = new Set([
12
- 'swww',
13
- 'swww-daemon',
14
- 'hyprctl',
15
- 'systemctl',
16
- 'xdg-open',
17
- 'steamcmd',
18
- 'ffmpeg',
19
- 'ffprobe',
20
- 'cargo',
21
- 'kitsune-livewallpaper',
22
- 'dd'
23
- ]);
24
- if (!integratedRendercore) {
25
- hostCommands.add('which');
26
- hostCommands.add('kitsune-rendercore');
27
- }
28
- const useHost = isFlatpak && hostCommands.has(cmd);
29
- const finalCmd = useHost ? 'flatpak-spawn' : cmd;
30
- const finalArgs = useHost ? ['--host', cmd, ...args] : args;
8
+ const finalCmd = cmd;
9
+ const finalArgs = args;
31
10
  const child = (0, child_process_1.spawn)(finalCmd, finalArgs, {
32
11
  cwd: options.cwd,
33
12
  env: options.env,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kitowall",
3
- "version": "3.5.14",
3
+ "version": "3.5.27",
4
4
  "description": "CLI/daemon for Hyprland wallpapers using swww with pack-based rotation.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,6 +26,7 @@
26
26
  "release:check": "npm run build && npm run test:e2e",
27
27
  "package:cli": "npm pack",
28
28
  "package:ui": "npm --prefix ui run tauri:build",
29
+ "package:appimage": "npm --prefix ui run tauri:build",
29
30
  "package:all": "npm run release:check && npm run package:cli && npm run package:ui",
30
31
  "test:smoke": "bash ./tests/smoke.e2e.sh",
31
32
  "test:regression": "bash ./tests/regression.e2e.sh",