updates to launcer
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import shutil
|
||||
|
||||
root = Path.cwd()
|
||||
source_file = root / "OrionToolbar/OrionMenuView.swift"
|
||||
controller_file = root / "OrionToolbar/OrionSettingsModal.swift"
|
||||
|
||||
if not source_file.exists():
|
||||
raise SystemExit("ERROR: OrionMenuView.swift not found")
|
||||
|
||||
source = source_file.read_text()
|
||||
|
||||
old = """ private func showSettings() {
|
||||
openSettings()
|
||||
NSApplication.shared.activate(ignoringOtherApps: true)
|
||||
}"""
|
||||
|
||||
new = """ private func showSettings() {
|
||||
OrionSettingsModal.shared.present(vm: vm)
|
||||
}"""
|
||||
|
||||
if old not in source:
|
||||
raise SystemExit(
|
||||
"ERROR: Settings implementation differs from expected source. "
|
||||
"No files changed. Inspect showSettings() before patching."
|
||||
)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S-%f")
|
||||
backup_dir = root / "backups" / timestamp
|
||||
|
||||
for path in [source_file, controller_file]:
|
||||
if path.exists():
|
||||
destination = backup_dir / path.relative_to(root)
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(path, destination)
|
||||
|
||||
controller = r'''import AppKit
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
final class OrionSettingsModal: NSObject, NSWindowDelegate {
|
||||
static let shared = OrionSettingsModal()
|
||||
|
||||
private var window: NSWindow?
|
||||
private var modalRunning = false
|
||||
|
||||
func present(vm: OrionVMController) {
|
||||
if let window {
|
||||
NSApplication.shared.activate(ignoringOtherApps: true)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
window.orderFrontRegardless()
|
||||
return
|
||||
}
|
||||
|
||||
let content = SettingsView()
|
||||
.environmentObject(vm)
|
||||
|
||||
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.contentView = NSHostingView(rootView: content)
|
||||
window.center()
|
||||
window.level = .floating
|
||||
window.isReleasedWhenClosed = false
|
||||
window.delegate = self
|
||||
|
||||
self.window = window
|
||||
|
||||
NSApplication.shared.activate(ignoringOtherApps: true)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
window.orderFrontRegardless()
|
||||
|
||||
DispatchQueue.main.async { [weak self, weak window] in
|
||||
guard let self,
|
||||
let window,
|
||||
self.window === window,
|
||||
!self.modalRunning else {
|
||||
return
|
||||
}
|
||||
|
||||
self.modalRunning = true
|
||||
NSApplication.shared.runModal(for: window)
|
||||
self.modalRunning = false
|
||||
}
|
||||
}
|
||||
|
||||
func windowWillClose(_ notification: Notification) {
|
||||
if modalRunning {
|
||||
NSApplication.shared.stopModal()
|
||||
}
|
||||
|
||||
window = nil
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
source_file.write_text(source.replace(old, new, 1))
|
||||
controller_file.write_text(controller)
|
||||
|
||||
print("UPDATED:", source_file.relative_to(root))
|
||||
print("CREATED:", controller_file.relative_to(root))
|
||||
print("BACKUP:", backup_dir)
|
||||
print()
|
||||
print("IMPORTANT: Ensure OrionSettingsModal.swift is included")
|
||||
print("in the OrionToolbar Xcode application target.")
|
||||
@@ -0,0 +1,130 @@
|
||||
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.")
|
||||
Reference in New Issue
Block a user