feat(launcher): implement LaunchProfile system — MasterKey replacement

- Add ae_launcher__default_launch_profiles.ts with LaunchProfile interface,
  DEFAULT_LAUNCH_PROFILES constant, and resolve_launch_profile() helper.
  Covers pptx/ppt/key/odp/pdf, all VLC media formats, Windows/Parallels
  variants (pptxwin/pptwin/odpwin/pdfwin), and a catch-all 'default'.

- Replace get_launch_script_template() with get_launch_profile() in
  launcher_file_cont.svelte. Override priority: device API config >
  local persistent config > built-in defaults > 'default' catch-all.

- Rewrite handle_open_file() native branch with 9-step profile-driven flow:
  copy_from_cache_to_temp → resolve profile → set_display_layout (silent fail)
  → open (run_cmd or OS default) → sleep(post_delay_ms) → run post_script
  → fallback to OS default on open failure → surface status/error detail.

- Add open_file_error_detail state var; show error pre block in status
  alert for native error state, show fallback note for fallback state.

- Add display override toggle button in event_file_meta (visible when
  trusted_access + is_native): cycles null → extend → mirror → null,
  PATCHes event_file.cfg_json.display_override via V3 CRUD.
This commit is contained in:
Scott Idem
2026-05-12 12:17:43 -04:00
parent a3d229c803
commit 422c9c341c
2 changed files with 557 additions and 49 deletions

View File

@@ -0,0 +1,382 @@
/**
* ae_launcher__default_launch_profiles.ts
*
* Built-in launch profiles for the Aether Events Launcher — the Svelte-side
* replacement for the legacy OSIT MasterKey Swift app.
*
* These are the last-resort defaults. Override priority (high → low):
* 1. event_file.cfg_json.display_override — per-file, display_mode only
* 2. event_device.data_json.launch_profiles[ext] — full profile, per device (API)
* 3. $events_loc.launcher.launch_profiles[ext] — local persistent override
* 4. DEFAULT_LAUNCH_PROFILES[ext] — these built-ins
* 5. DEFAULT_LAUNCH_PROFILES['default'] — catch-all
*
* Keys are lowercase file extensions without the dot: "pptx", "key", "pdf", etc.
* The special key "default" catches any unrecognised extension.
*
* post_script formats:
* - Plain string → run as AppleScript via run_osascript() (macOS only)
* - "shell:..." prefix → run as shell command via run_cmd()
*
* Reserved for future use (not yet read anywhere):
* - speed_factor: number — delay multiplier for slower machines (1.0 = normal)
* - url: string — for URL-type presentations (e.g. Google Slides)
*/
export interface LaunchProfile {
/** Human-readable label for status messages */
app: string;
/** Display layout to set before opening. 'extend' only applied if external display found. */
display_mode: 'extend' | 'mirror' | 'none';
/**
* Shell command to open the file. {{path}} is replaced with the resolved temp path.
* If omitted, falls back to open_local_file_v2(path) — OS default handler.
*/
open_cmd?: string;
/**
* Script to run after the file opens and post_delay_ms has elapsed.
* Plain string → AppleScript (macOS). "shell:" prefix → shell command.
*/
post_script?: string;
/** Milliseconds to wait after open_cmd before running post_script. Default: 2000 */
post_delay_ms?: number;
// --- Reserved for future use — not yet implemented ---
// speed_factor?: number;
// url?: string;
}
export const DEFAULT_LAUNCH_PROFILES: Record<string, LaunchProfile> = {
// -------------------------------------------------------------------------
// macOS presentation formats
// -------------------------------------------------------------------------
pptx: {
app: 'Microsoft PowerPoint',
display_mode: 'extend',
open_cmd: 'open -a "Microsoft PowerPoint" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "Microsoft PowerPoint"
activate
end tell
tell application "System Events"
tell process "Microsoft PowerPoint"
keystroke return using command down
end tell
end tell`
},
ppt: {
app: 'Microsoft PowerPoint',
display_mode: 'extend',
open_cmd: 'open -a "Microsoft PowerPoint" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "Microsoft PowerPoint"
activate
end tell
tell application "System Events"
tell process "Microsoft PowerPoint"
keystroke return using command down
end tell
end tell`
},
key: {
app: 'Keynote',
display_mode: 'extend',
open_cmd: 'open -a "Keynote" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "Keynote"
activate
start (front document)
end tell`
},
odp: {
app: 'LibreOffice',
display_mode: 'extend',
open_cmd: 'open -a "LibreOffice" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "LibreOffice"
activate
end tell
tell application "System Events"
tell process "soffice"
key code 96
end tell
end tell`
},
pdf: {
app: 'Adobe Acrobat Reader DC',
display_mode: 'mirror',
open_cmd: 'open -a "Adobe Acrobat Reader DC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "Adobe Acrobat Reader DC"
activate
end tell
tell application "System Events"
tell process "AdobeReader"
keystroke "l" using command down
end tell
end tell`
},
// -------------------------------------------------------------------------
// Media (VLC) — mirror display
// -------------------------------------------------------------------------
mp4: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
mkv: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
mov: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
mpeg: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
avi: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
flv: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
ogg: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
ogv: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
mp3: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
wmv: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
// -------------------------------------------------------------------------
// Windows / Parallels variants — longer post_delay_ms (Parallels needs more time)
// -------------------------------------------------------------------------
pptxwin: {
app: 'Microsoft Office PowerPoint (Windows)',
display_mode: 'extend',
open_cmd: 'open -a "Microsoft Office PowerPoint" "{{path}}"',
post_delay_ms: 3000,
post_script: `tell application "Microsoft Office PowerPoint"
activate
end tell
tell application "System Events"
key code 96
end tell`
},
pptwin: {
app: 'Microsoft Office PowerPoint (Windows)',
display_mode: 'extend',
open_cmd: 'open -a "Microsoft Office PowerPoint" "{{path}}"',
post_delay_ms: 3000,
post_script: `tell application "Microsoft Office PowerPoint"
activate
end tell
tell application "System Events"
key code 96
end tell`
},
odpwin: {
app: 'LibreOffice (Windows)',
display_mode: 'extend',
open_cmd: 'open -a "LibreOffice" "{{path}}"',
post_delay_ms: 3000,
post_script: `tell application "LibreOffice"
activate
end tell
tell application "System Events"
tell process "soffice"
key code 96
end tell
end tell`
},
pdfwin: {
app: 'Acrobat Reader (Windows)',
display_mode: 'mirror',
open_cmd: 'open -a "Acrobat Reader Windows" "{{path}}"',
post_delay_ms: 3000,
post_script: `tell application "Acrobat Reader Windows"
activate
end tell
tell application "System Events"
key code 108 using control down
end tell`
},
// -------------------------------------------------------------------------
// Catch-all — OS default handler, no display change
// Works on macOS (open) and Linux (xdg-open via open_local_file_v2)
// -------------------------------------------------------------------------
default: {
app: 'OS Default',
display_mode: 'none'
// No open_cmd — execution falls through to open_local_file_v2(path)
// No post_script
}
};
/**
* Returns a shallow copy of the built-in profile for the given extension,
* with a display_override applied if provided.
*
* Falls back to 'default' if no specific profile exists.
*/
export function resolve_launch_profile(
extension: string,
display_override?: 'extend' | 'mirror' | 'none' | null,
device_profiles?: Record<string, LaunchProfile> | null,
local_profiles?: Record<string, LaunchProfile> | null
): LaunchProfile {
const ext = (extension || '').toLowerCase().replace(/^\./, '');
// Priority: device config → local config → built-ins → default
const source =
device_profiles?.[ext] ??
device_profiles?.['default'] ??
local_profiles?.[ext] ??
local_profiles?.['default'] ??
DEFAULT_LAUNCH_PROFILES[ext] ??
DEFAULT_LAUNCH_PROFILES['default'];
const profile = { ...source };
// Per-file display override wins over everything
if (display_override) {
profile.display_mode = display_override;
}
return profile;
}