108 lines
3.6 KiB
Python
108 lines
3.6 KiB
Python
#!/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.')
|