fix: use a nonactivating compact panel across fullscreen spaces

This commit is contained in:
2026-10-25 22:29:11 +01:00
parent 599e2f548a
commit d8fb648c90
8 changed files with 393 additions and 125 deletions
+48 -15
View File
@@ -275,6 +275,10 @@ private enum WindowDragCheck {
}
static func interceptDrags(in window: NSWindow) {
for (method, original) in implementations.reversed() {
imp_removeBlock(method_setImplementation(method, original))
}
implementations.removeAll()
target = window
let method = class_getInstanceMethod(type(of: window), #selector(NSWindow.performDrag(with:)))!
let original = unsafeBitCast(method_getImplementation(method),
@@ -309,8 +313,9 @@ private enum WindowDragCheck {
guard ContinuousClock.now < deadline else {
finish("Window drag timed out at \(names[probe]), phase \(phase)"); return
}
guard let window = NSApp.windows.first(where: { $0.canBecomeMain && !($0 is NSPanel) }) else { return }
let model = PlayerModel.shared
guard let window = model.compactWindow.presentationWindow
?? NSApp.windows.first(where: { $0.canBecomeMain && !($0 is NSPanel) }) else { return }
let fixtures = URL(fileURLWithPath: CommandLine.arguments.last!, isDirectory: true)
if target == nil {
guard model.canTogglePlayback else { return }
@@ -322,10 +327,11 @@ private enum WindowDragCheck {
window.makeMain()
NSApp.activate(ignoringOtherApps: true)
}
if target !== window { interceptDrags(in: window) }
if phase == 0 {
guard NSApp.isActive, window.isKeyWindow, window.isMainWindow else {
guard NSApp.isActive, window.isKeyWindow, !window.canBecomeMain || window.isMainWindow else {
window.makeKeyAndOrderFront(nil)
window.makeMain()
if window.canBecomeMain { window.makeMain() }
NSApp.activate(ignoringOtherApps: true)
return
}
@@ -588,7 +594,10 @@ private enum WindowLayoutCheck {
static var timer: Timer?
static var resultURL: URL!
static var step = 0
static var windowIdentity: ObjectIdentifier?
static var normalWindow: NSWindow?
static var renderer: NSView?
static var content: NSView?
static var activationPolicy: NSApplication.ActivationPolicy?
static var normalFrame = NSRect.zero
static var compactFrame = NSRect.zero
static var animationStart: NSRect?
@@ -676,7 +685,28 @@ private enum WindowLayoutCheck {
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,
let window = NSApp.windows.first(where: { $0.canBecomeMain && !($0 is NSPanel) }) else { return }
let normal = normalWindow ?? NSApp.windows.first(where: { $0.canBecomeMain && !($0 is NSPanel) }),
let video = model.videoView else { return }
let window = model.compactWindow.presentationWindow ?? normal
let hostedContent = model.hostedPlayerView ?? window.contentView
if normalWindow == nil {
normalWindow = normal
renderer = video
content = hostedContent
activationPolicy = NSApp.activationPolicy()
}
guard video === renderer, hostedContent === content, hostedContent?.window === window,
video.window === window, NSApp.activationPolicy() == activationPolicy else {
finish("Transition replaced the content/renderer or changed app activation policy"); return
}
if window !== normal {
guard let panel = window as? NSPanel, panel.styleMask.contains(.nonactivatingPanel),
!normal.isVisible, !normal.isKeyWindow else {
finish("Compact presentation is not an independent nonactivating panel hiding the normal window"); return
}
} else if model.isCompact {
finish("Compact playback retained the normal window"); return
}
if model.compactWindow.isTransitioning || model.isQueueTransitioning {
window.layoutIfNeeded()
if model.isQueueTransitioning && !model.isCompact && !sidebarHeaderIsVisible(in: window) {
@@ -718,8 +748,9 @@ private enum WindowLayoutCheck {
queueVideoSize = nil
observingQueue = false
}
if windowIdentity == nil { windowIdentity = ObjectIdentifier(window) }
guard ObjectIdentifier(window) == windowIdentity else { finish("Transition replaced the playback window"); return }
guard model.isCompact ? window !== normal : window === normal else {
finish("Settled presentation does not match the requested mode"); return
}
if model.fullScreenIntent != nil { return }
window.layoutIfNeeded()
if compactQueueOnly && step == 0 {
@@ -1079,20 +1110,21 @@ private enum CLIInstanceCheck {
let model = PlayerModel.shared
let pid = ProcessInfo.processInfo.processIdentifier
// Text input can create non-main auxiliary windows; count actual player windows.
let windows = NSApp.windows.filter { $0.canBecomeMain && !($0 is NSPanel) && $0.contentView != nil }
let windows = NSApp.windows.filter { $0.canBecomeMain && !($0 is NSPanel) }
let presentation = model.compactWindow.presentationWindow ?? windows.first
for window in windows { window.alphaValue = 0 }
let request = directory.appendingPathComponent("keys-\(pid).json")
var pendingRequestID = ""
if let data = try? Data(contentsOf: request),
let command = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let id = command["id"] as? String, id != requestID, let window = windows.first {
let id = command["id"] as? String, id != requestID, let window = presentation {
pendingRequestID = id
if !NSApp.isActive || !window.isKeyWindow || !window.isMainWindow {
if !NSApp.isActive || !window.isKeyWindow || window.canBecomeMain && !window.isMainWindow {
// Synthetic native key requests need foreground focus; cooperative activation
// can be refused by the unrelated app currently active on the workstation.
NSApp.activate(ignoringOtherApps: true)
window.makeKeyAndOrderFront(nil)
window.makeMain()
if window.canBecomeMain { window.makeMain() }
} else {
requestID = id
if let media = command["media"] as? String { model.open(URL(fileURLWithPath: media)) }
@@ -1140,8 +1172,9 @@ private enum CLIInstanceCheck {
}
}
if let events = command["events"] as? [[String: Any]] {
window.makeKey()
for input in events {
let window = model.compactWindow.presentationWindow ?? window
window.makeKey()
if let title = input["select"] as? String {
guard let menu = NSApp.mainMenu else { fatalError("Native application menu is missing") }
prepareMenu(menu)
@@ -1206,14 +1239,14 @@ private enum CLIInstanceCheck {
"requestID": requestID,
"pendingRequestID": pendingRequestID,
"active": NSApp.isActive,
"keyWindow": windows.first?.isKeyWindow ?? false,
"mainWindow": windows.first?.isMainWindow ?? false,
"keyWindow": presentation?.isKeyWindow ?? false,
"mainWindow": presentation?.isMainWindow ?? false,
"editorText": editorText,
"compact": model.isCompact,
"showsQueue": model.showsQueue,
"queueURLs": model.queue.items.map(\.url.absoluteString),
"queueIndex": model.queue.items.firstIndex { $0.id == model.queue.currentID } ?? -1,
"window": windowSnapshot(windows.first),
"window": windowSnapshot(presentation),
"onTop": model.isOnTop,
]
do {