fix: stabilize native window layout transitions
This commit is contained in:
@@ -5,6 +5,10 @@ import MediaPlayer
|
||||
@_cdecl("terebi_app_main")
|
||||
@MainActor
|
||||
public func checkAppStartup() {
|
||||
if let index = CommandLine.arguments.firstIndex(of: "--check-window-layout") {
|
||||
WindowLayoutCheck.run(result: URL(fileURLWithPath: CommandLine.arguments[index + 1]))
|
||||
return
|
||||
}
|
||||
if let configuration = Bundle.main.url(forResource: "cli-check", withExtension: "json") {
|
||||
CLIInstanceCheck.run(configuration)
|
||||
return
|
||||
@@ -12,6 +16,80 @@ public func checkAppStartup() {
|
||||
AppStartupCheck.run()
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private enum WindowLayoutCheck {
|
||||
static var timer: Timer?
|
||||
static var resultURL: URL!
|
||||
static var step = 0
|
||||
static var windowIdentity: ObjectIdentifier?
|
||||
static var normalFrame = NSRect.zero
|
||||
// Infrastructure timeout only; assertions depend on native transition completion.
|
||||
static let deadline = ContinuousClock.now.advanced(by: .seconds(30))
|
||||
|
||||
static func run(result: URL) {
|
||||
resultURL = result
|
||||
_ = NSApplication.shared
|
||||
_ = NotificationCenter.default.addObserver(forName: NSApplication.willFinishLaunchingNotification,
|
||||
object: nil, queue: .main) { _ in
|
||||
MainActor.assumeIsolated { PlayerModel.shared.setVolume(0) }
|
||||
}
|
||||
_ = NotificationCenter.default.addObserver(forName: NSWindow.didUpdateNotification,
|
||||
object: nil, queue: .main) { _ in
|
||||
MainActor.assumeIsolated {
|
||||
for window in NSApp.windows { window.alphaValue = 0; window.ignoresMouseEvents = true }
|
||||
}
|
||||
}
|
||||
timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { _ in
|
||||
MainActor.assumeIsolated { advance() }
|
||||
}
|
||||
terebiAppMain()
|
||||
}
|
||||
|
||||
static func finish(_ error: String? = nil) {
|
||||
timer?.invalidate()
|
||||
let result: [String: Any] = ["passed": error == nil, "error": error ?? "", "steps": step]
|
||||
try! JSONSerialization.data(withJSONObject: result).write(to: resultURL, options: .atomic)
|
||||
PlayerModel.shared.shutdown()
|
||||
NSApp.terminate(nil)
|
||||
}
|
||||
|
||||
static func advance() {
|
||||
let model = PlayerModel.shared
|
||||
guard ContinuousClock.now < deadline else { finish("Window layout did not settle at step \(step)"); return }
|
||||
guard model.canTogglePlayback, model.state.has_video, !model.compactWindow.isTransitioning,
|
||||
let window = NSApp.windows.first(where: { $0.canBecomeMain && !($0 is NSPanel) }) else { return }
|
||||
if windowIdentity == nil { windowIdentity = ObjectIdentifier(window) }
|
||||
guard ObjectIdentifier(window) == windowIdentity else { finish("Transition replaced the playback window"); return }
|
||||
window.layoutIfNeeded()
|
||||
switch step % 8 {
|
||||
case 0, 4:
|
||||
if step > 0 {
|
||||
let actual = window.frame
|
||||
guard abs(actual.minX - normalFrame.minX) < 1, abs(actual.minY - normalFrame.minY) < 1,
|
||||
abs(actual.width - normalFrame.width) < 1, abs(actual.height - normalFrame.height) < 1,
|
||||
!model.isCompact else { finish("Normal geometry was not restored at step \(step): \(actual)"); return }
|
||||
}
|
||||
if step == 32 { finish(); return }
|
||||
model.showsQueue = step % 8 == 4
|
||||
case 1, 5:
|
||||
window.setFrame(NSRect(x: 100, y: 200, width: 763, height: 368), display: true)
|
||||
normalFrame = window.frame
|
||||
case 2, 6:
|
||||
// Repeated intent changes must keep the original restoration frame.
|
||||
model.toggleCompact()
|
||||
model.toggleCompact()
|
||||
model.toggleCompact()
|
||||
default:
|
||||
guard model.isCompact, window.frame.width >= 160, window.frame.height >= 90,
|
||||
abs(window.frame.width / window.frame.height - model.state.video_aspect_ratio) < 0.01 else {
|
||||
finish("Compact geometry did not settle at step \(step): \(window.frame)"); return
|
||||
}
|
||||
model.toggleCompact()
|
||||
}
|
||||
step += 1
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private enum AppStartupCheck {
|
||||
static var model: PlayerModel?
|
||||
|
||||
Reference in New Issue
Block a user