/** * 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[profile] — per-profile override, per device (API) * 3. $events_loc.launcher.launch_profiles[profile] — local persistent override * 4. DEFAULT_LAUNCH_PROFILES[profile/alias] — canonical built-ins + aliases * 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: * - speed_factor: number — delay multiplier for slower machines (1.0 = normal) * * Special pseudo-extension: * - url — web-based presentations. Handled by the launcher URL branch rather * than a cache-to-temp open flow. */ 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. Can be overridden per profile via launch_profiles[profile].post_delay_ms. */ post_delay_ms?: number; // --- Reserved for future use — not yet implemented --- // speed_factor?: number; // url?: string; } /** * macOS VLC profile — uses direct binary path for max reliability. * Bypasses `open -a` argument-handling quirks that could lose file path or re-use existing process. * * WHY nohup + &: * run_cmd uses exec() which blocks until the child process exits (or the 30s timeout fires). * The direct VLC binary forks a GUI process then exits — exec returns early and the code * proceeds to the post_script. The old post_script polled for VLC focus (up to 10s) then * sent Cmd+F, which was firing exactly 10–15 seconds into playback and stopping the video. * nohup + & detaches VLC immediately so exec returns in ~0ms, decoupling run_cmd from * VLC's lifecycle entirely. * * WHY --fullscreen: * Starting VLC fullscreen via flag avoids the need to send Cmd+F via AppleScript. The old * keystroke approach was the proximate cause of the video stopping — Cmd+F may have hit the * wrong VLC window, triggered a menu action, or paused playback during the fullscreen * transition. Using the flag is simpler and more reliable. * * WHY > /dev/null 2>&1: * VLC logs verbosely to stdout/stderr. exec() buffers output (1MB default). Without * redirection the buffer could overflow and kill VLC mid-playback. */ function make_vlc_mirror_mac_profile(): LaunchProfile { return { app: 'VLC (macOS)', display_mode: 'mirror', open_cmd: 'nohup /Applications/VLC.app/Contents/MacOS/VLC --no-play-and-exit --play-and-pause --fullscreen "{{path}}" > /dev/null 2>&1 &', post_delay_ms: 3000, // Activate VLC after it has had time to open. Fullscreen is already set by the flag // above — this just ensures VLC is the frontmost app and the presenter sees it. post_script: `tell application "VLC" activate end tell` }; } /** * Linux VLC profile — uses shell command for compatibility. */ function make_vlc_mirror_linux_profile(): LaunchProfile { return { app: 'VLC (Linux)', display_mode: 'mirror', // shell: prefix runs as bash command. Same flags as macOS: `--no-play-and-exit` keeps window open, `--play-and-pause` holds final frame. open_cmd: 'shell:vlc --no-play-and-exit --play-and-pause "{{path}}"', post_delay_ms: 1000 // No post_script on Linux — VLC opens fullscreen by default, no need to send F. }; } const POWERPOINT_MAC_EXTEND_PROFILE: LaunchProfile = { app: 'Microsoft PowerPoint', display_mode: 'extend', open_cmd: 'open -a "Microsoft PowerPoint" "{{path}}"', post_delay_ms: 1000, post_script: `repeat 20 times tell application "Microsoft PowerPoint" activate end tell delay 0.5 tell application "System Events" if frontmost of process "Microsoft PowerPoint" is true then exit repeat end tell end repeat delay 0.3 tell application "System Events" tell process "Microsoft PowerPoint" keystroke return using command down end tell end tell` }; const KEYNOTE_MAC_EXTEND_PROFILE: LaunchProfile = { app: 'Keynote', display_mode: 'extend', open_cmd: 'open -a "Keynote" "{{path}}"', post_delay_ms: 1000, // Keynote uses `start (front document)` which requires the document to actually be loaded — // polling frontmost is not enough here. Poll document count instead. post_script: `tell application "Keynote" activate end tell repeat 20 times delay 0.5 tell application "Keynote" if (count of documents) > 0 then exit repeat end tell end repeat delay 0.3 tell application "Keynote" start (front document) end tell` }; const LIBREOFFICE_MAC_EXTEND_PROFILE: LaunchProfile = { app: 'LibreOffice', display_mode: 'extend', open_cmd: 'open -a "LibreOffice" "{{path}}"', post_delay_ms: 1000, post_script: `repeat 20 times tell application "LibreOffice" activate end tell delay 0.5 tell application "System Events" if frontmost of process "soffice" is true then exit repeat end tell end repeat delay 0.3 tell application "System Events" tell process "soffice" key code 96 end tell end tell` }; const ACROBAT_MAC_MIRROR_PROFILE: LaunchProfile = { app: 'Adobe Acrobat Reader DC', display_mode: 'mirror', open_cmd: 'open -a "Adobe Acrobat Reader DC" "{{path}}"', post_delay_ms: 1000, post_script: `repeat 20 times tell application "Adobe Acrobat Reader DC" activate end tell delay 0.5 tell application "System Events" if frontmost of process "AdobeReader" is true then exit repeat end tell end repeat delay 0.3 tell application "System Events" tell process "AdobeReader" keystroke "l" using command down end tell end tell` }; const VLC_MIRROR_MAC_PROFILE: LaunchProfile = make_vlc_mirror_mac_profile(); const VLC_MIRROR_LINUX_PROFILE: LaunchProfile = make_vlc_mirror_linux_profile(); const POWERPOINT_WIN_EXTEND_PROFILE: LaunchProfile = { app: 'Microsoft Office PowerPoint (Windows)', display_mode: 'extend', open_cmd: 'open -a "Microsoft Office PowerPoint" "{{path}}"', post_delay_ms: 1500, post_script: `tell application "Microsoft Office PowerPoint" activate end tell repeat 15 times delay 0.5 tell application "System Events" if frontmost of process "Microsoft Office PowerPoint" is true then exit repeat end tell end repeat delay 0.3 tell application "System Events" key code 96 end tell` }; const LIBREOFFICE_WIN_EXTEND_PROFILE: LaunchProfile = { app: 'LibreOffice (Windows)', display_mode: 'extend', open_cmd: 'open -a "LibreOffice" "{{path}}"', post_delay_ms: 1500, post_script: `repeat 20 times tell application "LibreOffice" activate end tell delay 0.5 tell application "System Events" if frontmost of process "soffice" is true then exit repeat end tell end repeat delay 0.3 tell application "System Events" tell process "soffice" key code 96 end tell end tell` }; const ACROBAT_WIN_MIRROR_PROFILE: LaunchProfile = { app: 'Acrobat Reader (Windows)', display_mode: 'mirror', open_cmd: 'open -a "Acrobat Reader Windows" "{{path}}"', post_delay_ms: 1500, post_script: `repeat 20 times tell application "Acrobat Reader Windows" activate end tell delay 0.5 tell application "System Events" if frontmost of process "Acrobat Reader Windows" is true then exit repeat end tell end repeat delay 0.3 tell application "System Events" key code 108 using control down end tell` }; const URL_WEB_PROFILE: LaunchProfile = { app: 'Chrome', display_mode: 'extend', // No open_cmd or post_script — URL branch in handle_open_file() handles this }; const DEFAULT_OS_PROFILE: LaunchProfile = { app: 'OS Default', display_mode: 'none', // No open_cmd — execution falls through to open_local_file_v2(path) // No post_script }; type DefaultLaunchProfileDefinition = { name: string; aliases: string[]; profile: LaunchProfile; }; export const DEFAULT_LAUNCH_PROFILE_DEFS: DefaultLaunchProfileDefinition[] = [ { name: 'powerpoint_mac_extend', aliases: ['pptx', 'ppt'], profile: POWERPOINT_MAC_EXTEND_PROFILE }, { name: 'keynote_mac_extend', aliases: ['key'], profile: KEYNOTE_MAC_EXTEND_PROFILE }, { name: 'libreoffice_mac_extend', aliases: ['odp'], profile: LIBREOFFICE_MAC_EXTEND_PROFILE }, { name: 'acrobat_mac_mirror', aliases: ['pdf'], profile: ACROBAT_MAC_MIRROR_PROFILE }, { name: 'vlc_mirror_mac', aliases: [], profile: VLC_MIRROR_MAC_PROFILE }, { name: 'vlc_mirror_linux', aliases: [], profile: VLC_MIRROR_LINUX_PROFILE }, { name: 'vlc_mirror', aliases: ['mp4', 'mkv', 'mov', 'mpeg', 'avi', 'flv', 'ogg', 'ogv', 'mp3', 'm4v', 'm4a', 'webm', 'wmv', 'wav', 'aac', 'flac'], profile: VLC_MIRROR_MAC_PROFILE // Default to macOS (primary deployment platform) }, { name: 'powerpoint_win_extend', aliases: ['pptxwin', 'pptwin'], profile: POWERPOINT_WIN_EXTEND_PROFILE }, { name: 'libreoffice_win_extend', aliases: ['odpwin'], profile: LIBREOFFICE_WIN_EXTEND_PROFILE }, { name: 'acrobat_win_mirror', aliases: ['pdfwin'], profile: ACROBAT_WIN_MIRROR_PROFILE }, { name: 'url_web', aliases: ['url'], profile: URL_WEB_PROFILE }, { name: 'os_default', aliases: ['default'], profile: DEFAULT_OS_PROFILE } ]; export const DEFAULT_LAUNCH_PROFILE_LIBRARY: Record = Object.fromEntries( DEFAULT_LAUNCH_PROFILE_DEFS.map(({ name, profile }) => [name, profile]) ); export const DEFAULT_LAUNCH_PROFILE_ALIASES: Record = Object.fromEntries( DEFAULT_LAUNCH_PROFILE_DEFS.flatMap(({ name, aliases }) => aliases.map((alias) => [alias, name]) ) ); export const DEFAULT_LAUNCH_PROFILES: Record = Object.fromEntries( DEFAULT_LAUNCH_PROFILE_DEFS.flatMap(({ name, aliases, profile }) => [ [name, { ...profile }], ...aliases.map((alias) => [alias, { ...profile }]) ]) ); /** * 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> | null, local_profiles?: Record> | null ): LaunchProfile { const ext = (extension || '').toLowerCase().replace(/^\./, ''); const canonical_profile_name = DEFAULT_LAUNCH_PROFILE_ALIASES[ext] ?? ext; const built_in_profile = DEFAULT_LAUNCH_PROFILE_LIBRARY[canonical_profile_name] ?? DEFAULT_LAUNCH_PROFILE_LIBRARY.os_default; const local_profile = local_profiles?.[canonical_profile_name] ?? local_profiles?.[ext] ?? local_profiles?.['default'] ?? null; const device_profile = device_profiles?.[canonical_profile_name] ?? device_profiles?.[ext] ?? device_profiles?.['default'] ?? null; const profile = { ...built_in_profile, ...(local_profile ?? {}), ...(device_profile ?? {}) }; // Per-file display override wins over everything if (display_override) { profile.display_mode = display_override; } return profile; }