feat(desktop): add pure auto-launch helpers with tests

This commit is contained in:
Jannis Braun
2026-04-27 12:33:52 +02:00
parent 4574a99f2a
commit 191ba5f248
2 changed files with 120 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
import { describe, it, expect } from 'vitest';
import {
deriveStartMinimizedFromArgs,
parseExecPathFromDesktopFile,
shouldReapplyAppImage,
} from './autoLaunch';
describe('deriveStartMinimizedFromArgs', () => {
it('returns true when --hidden is present', () => {
expect(deriveStartMinimizedFromArgs(['--hidden'])).toBe(true);
expect(deriveStartMinimizedFromArgs(['--foo', '--hidden', '--bar'])).toBe(true);
});
it('returns false when --hidden is absent', () => {
expect(deriveStartMinimizedFromArgs([])).toBe(false);
expect(deriveStartMinimizedFromArgs(['--other'])).toBe(false);
});
it('handles undefined safely', () => {
expect(deriveStartMinimizedFromArgs(undefined)).toBe(false);
});
});
describe('parseExecPathFromDesktopFile', () => {
it('extracts an unquoted path', () => {
const content = [
'[Desktop Entry]',
'Type=Application',
'Name=Backspace',
'Exec=/opt/Backspace/backspace --hidden',
'X-GNOME-Autostart-enabled=true',
].join('\n');
expect(parseExecPathFromDesktopFile(content)).toBe('/opt/Backspace/backspace');
});
it('extracts a double-quoted path (handles spaces in path)', () => {
const content = 'Exec="/home/user/Apps/Backspace 1.0.AppImage" --hidden\n';
expect(parseExecPathFromDesktopFile(content)).toBe('/home/user/Apps/Backspace 1.0.AppImage');
});
it('returns null when no Exec= line is present', () => {
expect(parseExecPathFromDesktopFile('[Desktop Entry]\nName=X\n')).toBeNull();
});
it('ignores commented Exec lines', () => {
expect(parseExecPathFromDesktopFile('#Exec=/wrong\nExec=/right\n')).toBe('/right');
});
it('returns null on empty input', () => {
expect(parseExecPathFromDesktopFile('')).toBeNull();
});
});
describe('shouldReapplyAppImage', () => {
it('returns true when AppImage path differs from recorded path', () => {
expect(shouldReapplyAppImage('/home/u/Backspace-2.0.AppImage', '/home/u/Backspace-1.0.AppImage')).toBe(true);
});
it('returns false when paths match', () => {
expect(shouldReapplyAppImage('/home/u/Backspace-2.0.AppImage', '/home/u/Backspace-2.0.AppImage')).toBe(false);
});
it('returns false when no recorded path exists — treat missing autostart entry as user-disabled', () => {
// A missing .desktop file is a user signal (they disabled via their DE's startup manager
// or removed it manually), not a stale-path-needs-refresh signal. The OS-authoritative
// architecture must treat this as "OS state changed, do not override".
expect(shouldReapplyAppImage('/home/u/Backspace.AppImage', null)).toBe(false);
});
it('returns false when there is no current AppImage env (not in AppImage runtime)', () => {
expect(shouldReapplyAppImage(null, '/anything')).toBe(false);
expect(shouldReapplyAppImage(null, null)).toBe(false);
});
});
+51
View File
@@ -0,0 +1,51 @@
export function deriveStartMinimizedFromArgs(args: string[] | undefined): boolean {
if (!args) return false;
return args.includes('--hidden');
}
/**
* Extracts the executable path from a freedesktop .desktop file's Exec= line.
* Handles double-quoted paths (which may contain spaces). Ignores commented
* lines (leading #). Returns null if no Exec= line is present or parseable.
*
* The .desktop spec also defines field codes (%f, %u, etc.) and backslash
* escapes; we don't need to honour them here because we only ever compare the
* leading executable token, never re-execute it.
*/
export function parseExecPathFromDesktopFile(content: string): string | null {
const lines = content.split(/\r?\n/);
for (const raw of lines) {
const line = raw.trim();
if (!line || line.startsWith('#')) continue;
if (!line.startsWith('Exec=')) continue;
const value = line.slice('Exec='.length).trimStart();
if (!value) continue;
if (value.startsWith('"')) {
const end = value.indexOf('"', 1);
if (end === -1) return null;
return value.slice(1, end);
}
const sp = value.indexOf(' ');
return sp === -1 ? value : value.slice(0, sp);
}
return null;
}
/**
* Returns true iff the existing autostart entry's recorded executable path differs
* from the current AppImage path (i.e., the AppImage moved and the entry is stale).
*
* A missing recorded path (recordedExecPath === null, i.e. the .desktop file does
* not exist) is intentionally treated as "do nothing" — it represents the user
* disabling autostart via their desktop environment's startup manager or removing
* the file manually. Re-creating it would override OS-level user intent, which is
* exactly the bug class this whole refactor exists to fix.
*/
export function shouldReapplyAppImage(
currentAppImagePath: string | null,
recordedExecPath: string | null,
): boolean {
if (!currentAppImagePath) return false;
if (!recordedExecPath) return false;
return currentAppImagePath !== recordedExecPath;
}