feat(launcher): add display mode toggle; fix silent display layout failures

- Add Extend/Mirror toggle to Native OS config section (always visible,
  no Technical Mode required). Default: Extend. State updates on success.
- Replace .catch(() => {}) swallowing with console.warn logging on both
  set_display_layout call sites so failures appear in the Electron console
- Remove old edit-mode-only Extend button (replaced by new toggle)
- Update PROJECT doc: displayplacer install note, binary lookup order, GitHub link
- Clean up TODO__Agents.md: resolve stale items, add new low-priority Electron items

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-20 16:33:33 -04:00
parent d9726d062e
commit 76569a872f
4 changed files with 66 additions and 91 deletions

View File

@@ -6,6 +6,7 @@ import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
import {
Code,
Columns2,
Copy,
FlaskConical,
FolderOpen,
Image,
@@ -27,6 +28,7 @@ let test_cmd_result = $state('');
let remote_app: 'powerpoint' | 'keynote' = $state('powerpoint');
let remote_status = $state('');
let system_status = $state('');
let display_mode = $state<'extend' | 'mirror'>('extend');
async function handle_remote_control(
action: 'next' | 'prev' | 'start' | 'stop'
@@ -55,6 +57,18 @@ async function handle_system_action(promise: Promise<any>, label: string) {
setTimeout(() => (system_status = ''), 3000);
}
async function handle_display_mode(mode: 'extend' | 'mirror') {
system_status = `Setting display: ${mode}...`;
const res = await native.set_display_layout({ mode });
if (res?.success) {
display_mode = mode;
system_status = `Display: ${mode}`;
} else {
system_status = `Display error: ${res?.error || 'No external display?'}`;
}
setTimeout(() => (system_status = ''), 4000);
}
// Modal state for dangerous actions
let show_power_confirm = $state<{ action: string; label: string } | null>(null);
</script>
@@ -124,6 +138,33 @@ let show_power_confirm = $state<{ action: string; label: string } | null>(null);
</div>
</div>
<!-- Display mode toggle: Extend = independent screens (default); Mirror = same content on both -->
<div class="flex flex-col gap-2">
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">Display Mode</p>
<div class="grid grid-cols-2 gap-1">
<button
type="button"
onclick={() => handle_display_mode('extend')}
class="btn btn-xs justify-start border-2"
class:border-primary-500={display_mode === 'extend'}
class:preset-tonal-primary={display_mode === 'extend'}
class:border-surface-400={display_mode !== 'extend'}
class:preset-tonal-surface={display_mode !== 'extend'}>
<Columns2 size="0.85em" class="mr-1 shrink-0" /> Extend
</button>
<button
type="button"
onclick={() => handle_display_mode('mirror')}
class="btn btn-xs justify-start border-2"
class:border-warning-500={display_mode === 'mirror'}
class:preset-tonal-warning={display_mode === 'mirror'}
class:border-surface-400={display_mode !== 'mirror'}
class:preset-tonal-surface={display_mode !== 'mirror'}>
<Copy size="0.85em" class="mr-1 shrink-0" /> Mirror
</button>
</div>
</div>
<!-- 2. Presentation Remote Control (Common) -->
<div class="flex flex-col gap-2">
<div class="flex flex-row items-center justify-between px-1">
@@ -186,19 +227,6 @@ let show_power_confirm = $state<{ action: string; label: string } | null>(null);
System Actions
</p>
<div class="grid grid-cols-1 gap-1">
<button
type="button"
onclick={() =>
handle_system_action(
native.set_display_layout({
mode: 'extend'
}),
'Extend Display'
)}
class="btn btn-xs preset-tonal-surface hover:preset-filled-primary-500 justify-start">
<Columns2 size="0.85em" class="mr-1 shrink-0" /> Extend
Mode
</button>
<button
type="button"
onclick={() =>

View File

@@ -219,9 +219,8 @@ async function handle_open_file() {
// URL presentations may still want to set display mode (e.g. Google Slides → extend)
if (profile.display_mode !== 'none') {
open_file_status_message = `Setting display (${profile.display_mode})...`;
await native.set_display_layout({ mode: profile.display_mode }).catch(() => {
/* No external display or displayplacer unconfigured — continue */
});
const disp_res = await native.set_display_layout({ mode: profile.display_mode }).catch((e: any) => ({ success: false, error: String(e) }));
if (!disp_res?.success) console.warn('[Launcher] set_display_layout:', disp_res?.error ?? 'no external display');
}
open_file_status_message = `Opening ${event_file_obj.title || 'URL'}...`;
@@ -357,9 +356,8 @@ async function handle_open_file() {
// --- Step 3: Set display layout (skip silently on failure / no external display) ---
if (profile.display_mode !== 'none') {
open_file_status_message = `Setting display (${profile.display_mode})...`;
await native.set_display_layout({ mode: profile.display_mode }).catch(() => {
/* No external display or displayplacer unavailable — continue */
});
const disp_res = await native.set_display_layout({ mode: profile.display_mode }).catch((e: any) => ({ success: false, error: String(e) }));
if (!disp_res?.success) console.warn('[Launcher] set_display_layout:', disp_res?.error ?? 'no external display');
}
// --- Step 4: Open the file ---