kitowall 1.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.
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getLogsPath = getLogsPath;
7
+ exports.redactUrl = redactUrl;
8
+ exports.appendSystemLog = appendSystemLog;
9
+ exports.listSystemLogs = listSystemLogs;
10
+ exports.clearSystemLogs = clearSystemLogs;
11
+ // Persistent system logs for requests/downloads and diagnostics.
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const os_1 = __importDefault(require("os"));
15
+ const fs_2 = require("../utils/fs");
16
+ const LOG_DIR = path_1.default.join(os_1.default.homedir(), '.local', 'state', 'kitowall');
17
+ const LOG_FILE = path_1.default.join(LOG_DIR, 'logs.jsonl');
18
+ const MAX_LINES = 5000;
19
+ function getLogsPath() {
20
+ return LOG_FILE;
21
+ }
22
+ function redactUrl(rawUrl) {
23
+ if (!rawUrl)
24
+ return rawUrl;
25
+ try {
26
+ const u = new URL(rawUrl);
27
+ const sensitive = new Set([
28
+ 'api_key',
29
+ 'apikey',
30
+ 'key',
31
+ 'token',
32
+ 'access_token',
33
+ 'client_id',
34
+ 'client_secret',
35
+ 'authorization'
36
+ ]);
37
+ for (const key of Array.from(u.searchParams.keys())) {
38
+ if (sensitive.has(key.toLowerCase())) {
39
+ u.searchParams.set(key, '***');
40
+ }
41
+ }
42
+ return u.toString();
43
+ }
44
+ catch {
45
+ return rawUrl;
46
+ }
47
+ }
48
+ function appendSystemLog(entry) {
49
+ try {
50
+ (0, fs_2.ensureDir)(LOG_DIR);
51
+ const row = {
52
+ ts: entry.ts ?? Date.now(),
53
+ level: entry.level,
54
+ source: entry.source,
55
+ pack: entry.pack,
56
+ action: entry.action,
57
+ message: entry.message,
58
+ url: redactUrl(entry.url),
59
+ status: entry.status,
60
+ meta: entry.meta
61
+ };
62
+ fs_1.default.appendFileSync(LOG_FILE, `${JSON.stringify(row)}\n`, 'utf8');
63
+ trimLogsIfNeeded();
64
+ }
65
+ catch {
66
+ // Logging must never break core features.
67
+ }
68
+ }
69
+ function listSystemLogs(opts) {
70
+ if (!fs_1.default.existsSync(LOG_FILE))
71
+ return [];
72
+ const raw = fs_1.default.readFileSync(LOG_FILE, 'utf8');
73
+ const rows = raw.split('\n').filter(Boolean);
74
+ const parsed = [];
75
+ for (const line of rows) {
76
+ try {
77
+ const item = JSON.parse(line);
78
+ parsed.push(item);
79
+ }
80
+ catch {
81
+ // ignore invalid lines
82
+ }
83
+ }
84
+ let out = parsed.sort((a, b) => b.ts - a.ts);
85
+ if (opts?.source)
86
+ out = out.filter(e => (e.source ?? '') === opts.source);
87
+ if (opts?.pack)
88
+ out = out.filter(e => (e.pack ?? '') === opts.pack);
89
+ if (opts?.level)
90
+ out = out.filter(e => e.level === opts.level);
91
+ if (opts?.q) {
92
+ const q = opts.q.toLowerCase();
93
+ out = out.filter(e => {
94
+ const blob = [
95
+ e.source ?? '',
96
+ e.pack ?? '',
97
+ e.action ?? '',
98
+ e.message ?? '',
99
+ e.url ?? '',
100
+ JSON.stringify(e.meta ?? {})
101
+ ].join(' ').toLowerCase();
102
+ return blob.includes(q);
103
+ });
104
+ }
105
+ const limit = Math.max(1, Math.floor(opts?.limit ?? 200));
106
+ return out.slice(0, limit);
107
+ }
108
+ function clearSystemLogs() {
109
+ try {
110
+ if (!fs_1.default.existsSync(LOG_FILE))
111
+ return { ok: true, removed: 0 };
112
+ const raw = fs_1.default.readFileSync(LOG_FILE, 'utf8');
113
+ const removed = raw.split('\n').filter(Boolean).length;
114
+ fs_1.default.unlinkSync(LOG_FILE);
115
+ return { ok: true, removed };
116
+ }
117
+ catch {
118
+ return { ok: true, removed: 0 };
119
+ }
120
+ }
121
+ function trimLogsIfNeeded() {
122
+ try {
123
+ if (!fs_1.default.existsSync(LOG_FILE))
124
+ return;
125
+ const raw = fs_1.default.readFileSync(LOG_FILE, 'utf8');
126
+ const rows = raw.split('\n').filter(Boolean);
127
+ if (rows.length <= MAX_LINES)
128
+ return;
129
+ const keep = rows.slice(rows.length - MAX_LINES);
130
+ fs_1.default.writeFileSync(LOG_FILE, `${keep.join('\n')}\n`, 'utf8');
131
+ }
132
+ catch {
133
+ // best effort
134
+ }
135
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectOutputs = detectOutputs;
4
+ // Output detection via hyprctl or swww.
5
+ const exec_1 = require("../utils/exec");
6
+ async function outputsFromHyprctl() {
7
+ const result = await (0, exec_1.run)('hyprctl', ['monitors', '-j']);
8
+ const parsed = JSON.parse(result.stdout);
9
+ return parsed.map(m => ({ name: m.name }));
10
+ }
11
+ async function outputsFromSwwwQuery() {
12
+ const result = await (0, exec_1.run)('swww', ['query']);
13
+ const lines = result.stdout.split('\n').map(l => l.trim()).filter(Boolean);
14
+ const outputs = [];
15
+ for (const line of lines) {
16
+ const idx = line.indexOf(':');
17
+ if (idx > 0)
18
+ outputs.push({ name: line.slice(0, idx).trim() });
19
+ }
20
+ return outputs;
21
+ }
22
+ async function detectOutputs() {
23
+ try {
24
+ const outputs = await outputsFromHyprctl();
25
+ if (outputs.length > 0)
26
+ return outputs;
27
+ }
28
+ catch {
29
+ // ignore and fallback
30
+ }
31
+ try {
32
+ const outputs = await outputsFromSwwwQuery();
33
+ if (outputs.length > 0)
34
+ return outputs;
35
+ }
36
+ catch {
37
+ // ignore
38
+ }
39
+ return [];
40
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Scheduler = void 0;
4
+ class Scheduler {
5
+ constructor(options) {
6
+ this.timer = null;
7
+ this.intervalMs = Math.max(options.intervalSeconds, 1) * 1000;
8
+ this.onTick = options.onTick;
9
+ }
10
+ start() {
11
+ if (this.timer)
12
+ return;
13
+ this.timer = setInterval(() => {
14
+ void this.onTick();
15
+ }, this.intervalMs);
16
+ }
17
+ stop() {
18
+ if (!this.timer)
19
+ return;
20
+ clearInterval(this.timer);
21
+ this.timer = null;
22
+ }
23
+ }
24
+ exports.Scheduler = Scheduler;
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.STATE_SCHEMA_VERSION = void 0;
7
+ exports.setMode = setMode;
8
+ exports.cleanupDisconnectedOutputs = cleanupDisconnectedOutputs;
9
+ exports.commitSelection = commitSelection;
10
+ exports.getStatePath = getStatePath;
11
+ exports.defaultState = defaultState;
12
+ exports.loadState = loadState;
13
+ exports.saveState = saveState;
14
+ // Runtime state persistence.
15
+ const os_1 = __importDefault(require("os"));
16
+ const path_1 = __importDefault(require("path"));
17
+ const fs_1 = __importDefault(require("fs"));
18
+ const fs_2 = require("../utils/fs");
19
+ exports.STATE_SCHEMA_VERSION = 1;
20
+ function setMode(state, mode) {
21
+ state.mode = mode;
22
+ state.last_updated = Date.now();
23
+ }
24
+ function deepEqual(a, b) {
25
+ return JSON.stringify(a) === JSON.stringify(b);
26
+ }
27
+ const RECENT_LIMIT = 10;
28
+ // Cola LRU simple: dedupe + push + cap
29
+ function pushRecent(queue, value, limit = RECENT_LIMIT) {
30
+ const next = (queue ?? []).filter(v => v !== value);
31
+ next.push(value);
32
+ return next.slice(Math.max(0, next.length - limit));
33
+ }
34
+ // Anti-growth hard: recorta TODO a RECENT_LIMIT siempre
35
+ function trimRecentState(state, limit = RECENT_LIMIT) {
36
+ // Global
37
+ if (!Array.isArray(state.recent_global))
38
+ state.recent_global = [];
39
+ state.recent_global = state.recent_global.slice(-limit);
40
+ // Por output
41
+ if (typeof state.recent_by_output !== 'object' || state.recent_by_output === null) {
42
+ state.recent_by_output = {};
43
+ }
44
+ for (const out of Object.keys(state.recent_by_output)) {
45
+ const q = state.recent_by_output[out];
46
+ if (!Array.isArray(q)) {
47
+ state.recent_by_output[out] = [];
48
+ }
49
+ else {
50
+ state.recent_by_output[out] = q.slice(-limit);
51
+ }
52
+ }
53
+ }
54
+ /**
55
+ * Borra state de outputs que ya no existen (hotplug cleanup).
56
+ * Llamar en cada tick con la lista de outputs actuales.
57
+ */
58
+ function cleanupDisconnectedOutputs(state, currentOutputs) {
59
+ const alive = new Set(currentOutputs);
60
+ // last_set
61
+ if (!state.last_set)
62
+ state.last_set = {};
63
+ for (const out of Object.keys(state.last_set)) {
64
+ if (!alive.has(out))
65
+ delete state.last_set[out];
66
+ }
67
+ // recent_by_output
68
+ if (!state.recent_by_output)
69
+ state.recent_by_output = {};
70
+ for (const out of Object.keys(state.recent_by_output)) {
71
+ if (!alive.has(out))
72
+ delete state.recent_by_output[out];
73
+ }
74
+ state.last_outputs = [...currentOutputs];
75
+ // seguridad extra
76
+ trimRecentState(state);
77
+ }
78
+ /**
79
+ * Registra lo que se aplicó para un output y actualiza colas recientes.
80
+ * Llamar DESPUÉS de aplicar con swww.
81
+ */
82
+ function commitSelection(state, output, pathStr, now = Date.now()) {
83
+ if (!state.last_set)
84
+ state.last_set = {};
85
+ if (!state.recent_by_output)
86
+ state.recent_by_output = {};
87
+ if (!state.recent_global)
88
+ state.recent_global = [];
89
+ state.last_set[output] = pathStr;
90
+ state.recent_by_output[output] = pushRecent(state.recent_by_output[output] ?? [], pathStr);
91
+ state.recent_global = pushRecent(state.recent_global ?? [], pathStr);
92
+ state.last_updated = now;
93
+ // anti-growth hard
94
+ trimRecentState(state);
95
+ }
96
+ function getStatePath() {
97
+ return path_1.default.join(os_1.default.homedir(), '.local', 'state', 'kitowall', 'state.json');
98
+ }
99
+ function ensureStateDir() {
100
+ const dir = path_1.default.dirname(getStatePath());
101
+ if (!fs_1.default.existsSync(dir))
102
+ fs_1.default.mkdirSync(dir, { recursive: true });
103
+ }
104
+ function defaultState() {
105
+ return {
106
+ schemaVersion: exports.STATE_SCHEMA_VERSION,
107
+ mode: 'manual',
108
+ current_pack: null,
109
+ last_outputs: [],
110
+ last_set: {},
111
+ last_updated: 0,
112
+ recent_by_output: {},
113
+ recent_global: []
114
+ };
115
+ }
116
+ function loadState() {
117
+ const statePath = getStatePath();
118
+ const fallback = defaultState();
119
+ const stateFileExists = fs_1.default.existsSync(statePath);
120
+ const state = (0, fs_2.readJson)(statePath, fallback);
121
+ if (!stateFileExists || !state || Object.keys(state).length === 0) {
122
+ ensureStateDir();
123
+ (0, fs_2.writeJson)(statePath, fallback);
124
+ return fallback;
125
+ }
126
+ // Snapshot antes de migraciones para decidir si guardar o no
127
+ const originalState = JSON.parse(JSON.stringify(state));
128
+ // Freeze schema contract: only migrate missing/invalid to current version.
129
+ if (typeof state.schemaVersion !== 'number' || !Number.isFinite(state.schemaVersion)) {
130
+ state.schemaVersion = exports.STATE_SCHEMA_VERSION;
131
+ }
132
+ else if (state.schemaVersion > exports.STATE_SCHEMA_VERSION) {
133
+ throw new Error(`Unsupported state schemaVersion ${state.schemaVersion}. This build supports up to ${exports.STATE_SCHEMA_VERSION}.`);
134
+ }
135
+ else if (state.schemaVersion < exports.STATE_SCHEMA_VERSION) {
136
+ state.schemaVersion = exports.STATE_SCHEMA_VERSION;
137
+ }
138
+ // Migraciones / defaults
139
+ if (state.mode !== 'manual' && state.mode !== 'rotate')
140
+ state.mode = 'manual';
141
+ if (typeof state.current_pack === 'undefined')
142
+ state.current_pack = null;
143
+ if (!state.last_outputs)
144
+ state.last_outputs = [];
145
+ if (!state.last_set)
146
+ state.last_set = {};
147
+ if (typeof state.last_updated !== 'number')
148
+ state.last_updated = 0;
149
+ if (!state.recent_by_output)
150
+ state.recent_by_output = {};
151
+ if (!state.recent_global)
152
+ state.recent_global = [];
153
+ // Normalización básica (por si vienen corruptos)
154
+ if (!Array.isArray(state.recent_global))
155
+ state.recent_global = [];
156
+ if (typeof state.recent_by_output !== 'object' || state.recent_by_output === null) {
157
+ state.recent_by_output = {};
158
+ }
159
+ // Anti-growth hard (Fase 2B): recorta colas a 10 siempre
160
+ trimRecentState(state);
161
+ // Persistir solo si hubo cambios por migración
162
+ if (!deepEqual(state, originalState)) {
163
+ ensureStateDir();
164
+ (0, fs_2.writeJson)(statePath, state);
165
+ }
166
+ return state;
167
+ }
168
+ function saveState(state) {
169
+ if (typeof state.schemaVersion !== 'number' || !Number.isFinite(state.schemaVersion)) {
170
+ state.schemaVersion = exports.STATE_SCHEMA_VERSION;
171
+ }
172
+ if (state.schemaVersion !== exports.STATE_SCHEMA_VERSION) {
173
+ throw new Error(`Invalid state schemaVersion ${state.schemaVersion}. Expected ${exports.STATE_SCHEMA_VERSION}.`);
174
+ }
175
+ ensureStateDir();
176
+ const statePath = getStatePath();
177
+ const existing = fs_1.default.existsSync(statePath) ? (0, fs_2.readJson)(statePath, defaultState()) : null;
178
+ if (existing && deepEqual(existing, state))
179
+ return;
180
+ (0, fs_2.writeJson)(statePath, state);
181
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.installSystemd = installSystemd;
4
+ exports.uninstallSystemd = uninstallSystemd;
5
+ exports.systemdStatus = systemdStatus;
6
+ // src/core/systemd.ts
7
+ const node_fs_1 = require("node:fs");
8
+ const node_os_1 = require("node:os");
9
+ const node_path_1 = require("node:path");
10
+ const exec_1 = require("../utils/exec");
11
+ function ensureDir(path) {
12
+ (0, node_fs_1.mkdirSync)(path, { recursive: true });
13
+ }
14
+ function systemdInterval(every) {
15
+ // Acepta: 30s / 10m / 1h / 1d -> systemd: 30s / 10min / 1h / 1day
16
+ const m = every.trim().match(/^(\d+)\s*([smhd])$/i);
17
+ if (!m)
18
+ throw new Error(`Invalid --every value: ${every} (use like 30s, 10m, 1h)`);
19
+ const n = Number(m[1]);
20
+ const u = m[2].toLowerCase();
21
+ if (!Number.isFinite(n) || n <= 0)
22
+ throw new Error(`Invalid --every value: ${every}`);
23
+ if (u === 's')
24
+ return `${n}s`;
25
+ if (u === 'm')
26
+ return `${n}min`;
27
+ if (u === 'h')
28
+ return `${n}h`;
29
+ return `${n}day`;
30
+ }
31
+ /**
32
+ * installSystemd: instala/actualiza SOLO el TIMER.
33
+ * Para compatibilidad, acepta pack/namespace pero no los usa.
34
+ */
35
+ async function installSystemd(opts) {
36
+ const userDir = (0, node_path_1.join)((0, node_os_1.homedir)(), '.config', 'systemd', 'user');
37
+ ensureDir(userDir);
38
+ const every = systemdInterval(opts.every);
39
+ const kitowallTimer = `
40
+ [Unit]
41
+ Description=Run kitowall-next periodically
42
+
43
+ [Timer]
44
+ OnBootSec=2s
45
+ OnStartupSec=2s
46
+ AccuracySec=1s
47
+ RandomizedDelaySec=0
48
+ OnUnitActiveSec=${every}
49
+ Unit=kitowall-next.service
50
+ Persistent=true
51
+
52
+ [Install]
53
+ WantedBy=timers.target
54
+ `.trimStart();
55
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(userDir, 'kitowall-next.timer'), kitowallTimer, 'utf8');
56
+ await (0, exec_1.run)('systemctl', ['--user', 'daemon-reload']);
57
+ await (0, exec_1.run)('systemctl', ['--user', 'enable', '--now', 'kitowall-next.timer']);
58
+ }
59
+ async function uninstallSystemd() {
60
+ await (0, exec_1.run)('systemctl', ['--user', 'disable', '--now', 'kitowall-next.timer']).catch(() => { });
61
+ await (0, exec_1.run)('systemctl', ['--user', 'daemon-reload']).catch(() => { });
62
+ }
63
+ async function systemdStatus() {
64
+ await (0, exec_1.run)('systemctl', ['--user', 'status', 'kitowall-next.timer']);
65
+ }
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.watchMonitors = watchMonitors;
7
+ // src/core/watch.ts
8
+ const node_net_1 = __importDefault(require("node:net"));
9
+ const node_fs_1 = require("node:fs");
10
+ const node_path_1 = require("node:path");
11
+ const config_1 = require("./config");
12
+ const state_1 = require("./state");
13
+ const controller_1 = require("./controller");
14
+ function sleep(ms) {
15
+ return new Promise(r => setTimeout(r, ms));
16
+ }
17
+ function getSocket2Path() {
18
+ const xdg = process.env.XDG_RUNTIME_DIR || `/run/user/${process.getuid?.() ?? 1000}`;
19
+ const sig = process.env.HYPRLAND_INSTANCE_SIGNATURE;
20
+ if (sig) {
21
+ const p = (0, node_path_1.join)(xdg, 'hypr', sig, '.socket2.sock');
22
+ if ((0, node_fs_1.existsSync)(p))
23
+ return p;
24
+ }
25
+ // Fallback: buscar cualquier socket2 (por si el env no está)
26
+ const base = (0, node_path_1.join)(xdg, 'hypr');
27
+ if (!(0, node_fs_1.existsSync)(base))
28
+ throw new Error('Hyprland runtime dir not found');
29
+ const dirs = (0, node_fs_1.readdirSync)(base, { withFileTypes: true })
30
+ .filter(d => d.isDirectory())
31
+ .map(d => (0, node_path_1.join)(base, d.name, '.socket2.sock'))
32
+ .filter(p => (0, node_fs_1.existsSync)(p));
33
+ if (dirs.length === 0)
34
+ throw new Error('Hyprland socket2 not found');
35
+ return dirs[0];
36
+ }
37
+ function isMonitorEvent(line) {
38
+ const s = line.toLowerCase();
39
+ // Hyprland events: monitoradded/monitorremoved, etc.
40
+ return s.includes('monitoradded') || s.includes('monitorremoved') || s.includes('monitor') && s.includes('added') || s.includes('removed');
41
+ }
42
+ async function watchMonitors(opts) {
43
+ const config = (0, config_1.loadConfig)();
44
+ const state = (0, state_1.loadState)();
45
+ const controller = new controller_1.Controller(config, state);
46
+ const sockPath = getSocket2Path();
47
+ const debounceMs = Math.max(100, opts.debounceMs ?? 800);
48
+ let buf = '';
49
+ let timer = null;
50
+ async function trigger() {
51
+ // pequeño delay para que Hyprland termine de registrar el monitor
52
+ await sleep(200);
53
+ await controller.applyNext(undefined);
54
+ }
55
+ const client = node_net_1.default.createConnection(sockPath);
56
+ client.on('data', (data) => {
57
+ buf += data.toString('utf8');
58
+ let idx;
59
+ while ((idx = buf.indexOf('\n')) >= 0) {
60
+ const line = buf.slice(0, idx).trim();
61
+ buf = buf.slice(idx + 1);
62
+ if (!line)
63
+ continue;
64
+ if (!isMonitorEvent(line))
65
+ continue;
66
+ if (timer)
67
+ clearTimeout(timer);
68
+ timer = setTimeout(() => {
69
+ trigger().catch(() => { });
70
+ }, debounceMs);
71
+ }
72
+ });
73
+ client.on('error', (err) => {
74
+ throw err;
75
+ });
76
+ // Mantener vivo
77
+ await new Promise(() => { });
78
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applySwww = applySwww;
4
+ // swww backend for per-output wallpaper application.
5
+ const exec_1 = require("../utils/exec");
6
+ const node_child_process_1 = require("node:child_process");
7
+ function startSwwwDaemon(namespace) {
8
+ const args = [];
9
+ if (namespace)
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, {
15
+ stdio: 'ignore',
16
+ detached: true
17
+ });
18
+ // Avoid crashing caller on missing binary or spawn failures.
19
+ child.on('error', () => { });
20
+ child.unref();
21
+ }
22
+ async function ensureSwwwRunning(namespace) {
23
+ const q = namespace ? ['query', '--namespace', namespace] : ['query'];
24
+ try {
25
+ await (0, exec_1.run)('swww', q);
26
+ return;
27
+ }
28
+ catch { }
29
+ startSwwwDaemon(namespace);
30
+ // reintenta unas veces
31
+ for (let i = 0; i < 10; i++) {
32
+ try {
33
+ await (0, exec_1.run)('swww', q);
34
+ return;
35
+ }
36
+ catch { }
37
+ await new Promise(r => setTimeout(r, 150));
38
+ }
39
+ throw new Error('No se pudo iniciar swww-daemon.');
40
+ }
41
+ async function applySwww(images, transition, namespace = 'kitowall') {
42
+ await ensureSwwwRunning(namespace);
43
+ for (const item of images) {
44
+ const args = [
45
+ 'img',
46
+ '--namespace', namespace,
47
+ '-o',
48
+ item.output,
49
+ item.path,
50
+ '--transition-type',
51
+ transition.type,
52
+ '--transition-fps',
53
+ String(transition.fps),
54
+ '--transition-duration',
55
+ String(transition.duration)
56
+ ];
57
+ if (transition.angle !== undefined) {
58
+ args.push('--transition-angle', String(transition.angle));
59
+ }
60
+ if (transition.pos) {
61
+ args.push('--transition-pos', transition.pos);
62
+ }
63
+ await (0, exec_1.run)('swww', args);
64
+ }
65
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.run = run;
4
+ // Shell execution wrapper.
5
+ const child_process_1 = require("child_process");
6
+ function run(cmd, args = [], options = {}) {
7
+ return new Promise((resolve, reject) => {
8
+ const isFlatpak = Boolean(process.env.FLATPAK_ID);
9
+ const hostCommands = new Set(['swww', 'swww-daemon', 'hyprctl', 'systemctl', 'which', 'xdg-open']);
10
+ const useHost = isFlatpak && hostCommands.has(cmd);
11
+ const finalCmd = useHost ? 'flatpak-spawn' : cmd;
12
+ const finalArgs = useHost ? ['--host', cmd, ...args] : args;
13
+ const child = (0, child_process_1.spawn)(finalCmd, finalArgs, {
14
+ cwd: options.cwd,
15
+ env: options.env,
16
+ stdio: ['ignore', 'pipe', 'pipe']
17
+ });
18
+ let stdout = '';
19
+ let stderr = '';
20
+ const timeout = options.timeoutMs
21
+ ? setTimeout(() => {
22
+ child.kill('SIGKILL');
23
+ reject(new Error(`Command timed out: ${finalCmd}`));
24
+ }, options.timeoutMs)
25
+ : null;
26
+ child.stdout.on('data', (d) => (stdout += String(d)));
27
+ child.stderr.on('data', (d) => (stderr += String(d)));
28
+ child.on('error', (err) => {
29
+ if (timeout)
30
+ clearTimeout(timeout);
31
+ reject(err);
32
+ });
33
+ child.on('close', (code) => {
34
+ if (timeout)
35
+ clearTimeout(timeout);
36
+ const result = { stdout, stderr, code: code ?? 0 };
37
+ if (code && code !== 0) {
38
+ const err = new Error(`Command failed: ${finalCmd} ${finalArgs.join(' ')}\n` +
39
+ `exit=${code}\nstdout=${stdout.trim()}\nstderr=${stderr.trim()}`);
40
+ err.result = result;
41
+ reject(err);
42
+ return;
43
+ }
44
+ resolve(result);
45
+ });
46
+ });
47
+ }