feat(launcher): restore macOS default wallpaper + external-only apply fix

- electron_relay: add restore_macos_default_wallpaper() — uses run_osascript
  to find first .heic in /System/Library/Desktop Pictures/ (version-agnostic)
- wallpaper cfg: Restore macOS Default button (native or edit_mode); clears
  applied-URL tracking so next configured URL re-applies correctly
- wallpaper cfg: fix Apply Now / Save & Apply enabled when only external URL
  is filled; display target becomes 'external' to leave built-in unchanged

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-13 18:37:15 -04:00
parent 17e522f826
commit af28fba263
2 changed files with 67 additions and 1 deletions

View File

@@ -413,6 +413,38 @@ export async function set_wallpaper({
return await native.set_wallpaper({ path, url, url_external, display, api_key, account_id });
}
/**
* Restores the macOS default wallpaper on all displays.
* Scans /System/Library/Desktop Pictures/ for the first .heic file — works across
* all recent macOS versions without needing to know the version name.
* No-op on non-macOS (Linux/Windows return success:false from run_osascript).
*/
export async function restore_macos_default_wallpaper(
display: 'all' | 'primary' | 'external' = 'all'
): Promise<{ success: boolean; error?: string }> {
const display_target =
display === 'primary'
? 'tell desktop 1'
: display === 'external'
? 'tell desktop 2'
: 'tell every desktop';
const script = `
set pic_path to do shell script "ls '/System/Library/Desktop Pictures/'*.heic 2>/dev/null | head -1"
if pic_path is "" then
error "No default macOS wallpaper (.heic) found in /System/Library/Desktop Pictures/"
end if
tell application "System Events"
${display_target}
set picture to pic_path
end tell
end tell
`.trim();
const result = await run_osascript(script);
return result ?? { success: false, error: 'Native bridge not available' };
}
export async function update_app(args: {
source: 'url' | 'file';
url?: string;

View File

@@ -4,7 +4,7 @@ import { events_func } from '$lib/ae_events/ae_events_functions';
import { events_loc } from '$lib/stores/ae_events_stores';
import * as native from '$lib/electron/electron_relay';
import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
import { FlaskConical, Image, Monitor, Save, Zap } from '@lucide/svelte';
import { FlaskConical, Image, Monitor, RotateCcw, Save, Zap } from '@lucide/svelte';
interface Props {
on_expand?: () => void;
@@ -23,6 +23,7 @@ let url_input = $state('');
let url_external_input = $state('');
let save_status = $state('');
let apply_status = $state('');
let restore_status = $state('');
let last_device_id: string | null = null;
function get_native_device(): NativeDeviceLike | null {
@@ -148,6 +149,20 @@ async function handle_apply() {
}
}
async function handle_restore_default() {
restore_status = 'Restoring...';
const result = await native.restore_macos_default_wallpaper('all');
if (result?.success) {
// Clear tracked applied URL so the next config URL re-applies correctly.
$events_loc.launcher.wallpaper_applied_url = null;
$events_loc.launcher.wallpaper_applied_url_external = null;
restore_status = 'Restored ✓';
} else {
restore_status = `Error: ${(result as any)?.error ?? 'Unknown error'}`;
}
setTimeout(() => { if (restore_status.startsWith('Restored') || restore_status.startsWith('Error')) restore_status = ''; }, 4000);
}
async function handle_save_and_apply() {
await handle_save();
if (save_status !== 'Save failed' && save_status !== 'No device loaded') {
@@ -276,6 +291,16 @@ const section_description = $derived(
Save & Apply
</button>
<!-- Restore macOS default — safety valve for end-of-show cleanup -->
{#if $ae_loc.is_native || $ae_loc.edit_mode}
<button
type="button"
onclick={handle_restore_default}
class="btn btn-sm preset-tonal-surface h-8 w-full text-[10px] opacity-60 hover:opacity-100">
<RotateCcw size="0.8em" class="mr-1" /> Restore macOS Default
</button>
{/if}
{#if save_status}
<div
class="text-primary-500 text-center text-[9px] italic"
@@ -292,5 +317,14 @@ const section_description = $derived(
{apply_status}
</div>
{/if}
{#if restore_status}
<div
class="text-[9px] italic text-center"
class:text-success-500={restore_status.includes('✓')}
class:text-primary-500={restore_status === 'Restoring...'}
class:text-error-500={restore_status.includes('Error')}>
{restore_status}
</div>
{/if}
</div>
</Launcher_Cfg_Section>