Files
OrionToolbar/patches/oriontoolbar_settings_modal_fix.py
2026-10-23 09:56:46 +01:00

131 lines
3.7 KiB
Python

from pathlib import Path
from datetime import datetime
import shutil
root = Path.cwd()
path = root / "OrionToolbar/OrionMenuView.swift"
if not path.is_file():
raise SystemExit("ERROR: OrionMenuView.swift not found.")
source = path.read_text()
marker = "// A MenuBarExtra window is transient."
if marker not in source:
raise SystemExit(
"ERROR: Existing Settings presenter not found. "
"No changes made."
)
# The presenter is the final declaration in the file.
prefix, existing = source.split(marker, 1)
if "private final class OrionSettingsWindowPresenter" not in existing:
raise SystemExit(
"ERROR: Unexpected Settings presenter. No changes made."
)
replacement = r'''// Settings must be independent of the transient MenuBarExtra panel.
// An AppKit modal session keeps Settings open and blocks other app windows.
@MainActor
private final class OrionSettingsWindowPresenter: NSObject, NSWindowDelegate {
private static var current: OrionSettingsWindowPresenter?
private var window: NSWindow?
private var modalSession: NSApplication.ModalSession?
private var modalTimer: Timer?
static func present(vm: OrionVMController) {
if let current, let window = current.window {
NSApp.activate(ignoringOtherApps: true)
window.makeKeyAndOrderFront(nil)
window.orderFrontRegardless()
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],
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)
window.orderFrontRegardless()
// Begin a non-blocking AppKit modal session.
// Unlike runModal(for:), this does not block SwiftUI's event loop.
modalSession = NSApp.beginModalSession(for: window)
modalTimer = Timer.scheduledTimer(
withTimeInterval: 0.05,
repeats: true
) { [weak self] _ in
MainActor.assumeIsolated {
guard let self, let session = self.modalSession else {
return
}
let response = NSApp.runModalSession(session)
if response != .continue {
self.closeModalSession()
}
}
}
}
private func closeModalSession() {
modalTimer?.invalidate()
modalTimer = nil
if let session = modalSession {
NSApp.endModalSession(session)
modalSession = nil
}
}
func windowWillClose(_ notification: Notification) {
closeModalSession()
window = nil
Self.current = nil
}
}
'''
backup_dir = (
root / "backups"
/ datetime.now().strftime("%Y%m%d-%H%M%S-%f")
)
backup = backup_dir / path.relative_to(root)
backup.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(path, backup)
path.write_text(prefix + replacement)
print("UPDATED:", path.relative_to(root))
print("BACKUP:", backup.relative_to(root))
print("Settings now uses an AppKit modal session.")