#!/usr/bin/env python3 """Replace fragile MenuBarExtra Settings sheet with a standalone modal NSWindow.""" from pathlib import Path from datetime import datetime import shutil root = Path(__file__).resolve().parent.parent if not (root / 'OrionToolbar/OrionMenuView.swift').exists(): root = Path.cwd() path = root / 'OrionToolbar/OrionMenuView.swift' if not path.is_file(): raise SystemExit(f'Missing {path}') s = path.read_text() if 'OrionSettingsWindowPresenter.present(vm: vm)' in s: print('Already applied') raise SystemExit(0) start = s.find(' .sheet(isPresented: $settingsPresented) {') if start < 0: raise SystemExit('Expected Settings sheet not found. No changes made.') # Match Swift brace depth for the .sheet closure. brace = s.find('{', start) depth = 0 end = None for i in range(brace, len(s)): if s[i] == '{': depth += 1 elif s[i] == '}': depth -= 1 if depth == 0: end = i + 1 break if end is None: raise SystemExit('Unbalanced Settings sheet. No changes made.') s = s[:start] + s[end:] s = s.replace(' @State private var settingsPresented = false\n', '', 1) start = s.find(' private func showSettings() {') if start < 0: raise SystemExit('showSettings() not found. No changes made.') brace = s.find('{', start) depth = 0 end = None for i in range(brace, len(s)): if s[i] == '{': depth += 1 elif s[i] == '}': depth -= 1 if depth == 0: end = i + 1 break if end is None: raise SystemExit('Unbalanced showSettings(). No changes made.') s = s[:start] + ''' private func showSettings() { OrionSettingsWindowPresenter.present(vm: vm) }''' + s[end:] # Put window presentation in the existing file so no Xcode project membership changes are required. s += ''' // A MenuBarExtra window is transient. Sheets attached to it can disappear // when the user interacts with controls inside Settings. Use a real window. @MainActor private final class OrionSettingsWindowPresenter: NSObject, NSWindowDelegate { private static var current: OrionSettingsWindowPresenter? private var window: NSWindow? static func present(vm: OrionVMController) { if let current, let window = current.window { NSApp.activate(ignoringOtherApps: true) window.makeKeyAndOrderFront(nil) return } let presenter = OrionSettingsWindowPresenter() current = presenter presenter.open(vm: vm) } private func open(vm: OrionVMController) { let content = SettingsView() .environmentObject(vm) .preferredColorScheme(.dark) let window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 650, height: 380), styleMask: [.titled, .closable, .miniaturizable], backing: .buffered, defer: false ) window.title = "OrionToolbar Settings" window.contentViewController = NSHostingController(rootView: content) window.delegate = self window.isReleasedWhenClosed = false window.level = .floating window.center() self.window = window NSApp.activate(ignoringOtherApps: true) window.makeKeyAndOrderFront(nil) } func windowWillClose(_ notification: Notification) { window = nil Self.current = nil } } ''' backup = root / 'backups' / datetime.now().strftime('%Y%m%d-%H%M%S-%f') / 'OrionToolbar' / path.name backup.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(path, backup) path.write_text(s) print('Updated:', path) print('Backup:', backup) print('Settings now uses a persistent foreground window.')