fix: track hover across the entire player to prevent overlay feedback flicker
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import AppKit
|
||||
import Combine
|
||||
import Darwin
|
||||
import MediaPlayer
|
||||
import ObjectiveC
|
||||
@@ -6,6 +7,10 @@ import ObjectiveC
|
||||
@_cdecl("terebi_app_main")
|
||||
@MainActor
|
||||
public func checkAppStartup() {
|
||||
if let index = CommandLine.arguments.firstIndex(of: "--check-overlay-hover") {
|
||||
OverlayHoverCheck.run(result: URL(fileURLWithPath: CommandLine.arguments[index + 1]))
|
||||
return
|
||||
}
|
||||
if let index = CommandLine.arguments.firstIndex(of: "--check-window-drag") {
|
||||
WindowDragCheck.run(result: URL(fileURLWithPath: CommandLine.arguments[index + 1]))
|
||||
return
|
||||
@@ -25,6 +30,88 @@ public func checkAppStartup() {
|
||||
AppStartupCheck.run()
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private enum OverlayHoverCheck {
|
||||
static var timer: Timer?
|
||||
static var observation: AnyCancellable?
|
||||
static var resultURL: URL!
|
||||
static var step = 0
|
||||
static var sample = 0
|
||||
static var hidden = 0
|
||||
static var savedPointer = NSPoint.zero
|
||||
static let names = ["video", "title", "playback controls", "top-right controls",
|
||||
"queue controls", "window exit", "window reentry"]
|
||||
// Infrastructure watchdog only; verdicts use native hover events and visibility changes.
|
||||
static let deadline = ContinuousClock.now.advanced(by: .seconds(20))
|
||||
|
||||
static func run(result: URL) {
|
||||
resultURL = result
|
||||
_ = NSApplication.shared
|
||||
savedPointer = NSEvent.mouseLocation
|
||||
timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { _ in
|
||||
MainActor.assumeIsolated { advance() }
|
||||
}
|
||||
terebiAppMain()
|
||||
}
|
||||
|
||||
static func finish(_ error: String = "") {
|
||||
timer?.invalidate()
|
||||
observation = nil
|
||||
CGWarpMouseCursorPosition(CGPoint(x: savedPointer.x, y: NSScreen.screens[0].frame.maxY - savedPointer.y))
|
||||
let report: [String: Any] = ["passed": error.isEmpty, "error": error, "steps": step]
|
||||
try! JSONSerialization.data(withJSONObject: report).write(to: resultURL, options: .atomic)
|
||||
PlayerModel.shared.shutdown()
|
||||
NSApp.terminate(nil)
|
||||
}
|
||||
|
||||
static func advance() {
|
||||
guard ContinuousClock.now < deadline else { finish("Overlay hover timed out"); return }
|
||||
let model = PlayerModel.shared
|
||||
guard model.canTogglePlayback, model.state.has_video,
|
||||
let window = NSApp.windows.first(where: { $0.canBecomeMain && !($0 is NSPanel) }),
|
||||
let content = window.contentView else { return }
|
||||
if observation == nil {
|
||||
model.setVolume(0)
|
||||
model.setPlayback(playing: false)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
window.makeMain()
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
observation = model.$controlsVisible.removeDuplicates().dropFirst().sink { visible in
|
||||
if !visible { hidden += 1 }
|
||||
}
|
||||
}
|
||||
if step == names.count { finish(); return }
|
||||
if step == 4 && !model.showsQueue { model.showsQueue = true; return }
|
||||
guard !model.isQueueTransitioning else { return }
|
||||
if sample == 0 {
|
||||
let w = content.bounds.width
|
||||
let h = content.bounds.height
|
||||
let points = [NSPoint(x: w / 2, y: h / 2), NSPoint(x: 150, y: h - 16),
|
||||
NSPoint(x: w / 2, y: 64), NSPoint(x: w - 46, y: h - 16),
|
||||
NSPoint(x: w - 46, y: h - 16), NSPoint(x: -20, y: h / 2),
|
||||
NSPoint(x: w - 46, y: h - 16)]
|
||||
hidden = 0
|
||||
let screen = window.convertPoint(toScreen: points[step])
|
||||
CGWarpMouseCursorPosition(CGPoint(x: screen.x, y: NSScreen.screens[0].frame.maxY - screen.y))
|
||||
let event = NSEvent.mouseEvent(with: .mouseMoved, location: points[step], modifierFlags: [],
|
||||
timestamp: 0, windowNumber: window.windowNumber, context: nil,
|
||||
eventNumber: step, clickCount: 0, pressure: 0)!
|
||||
NSApp.postEvent(NSEvent(cgEvent: event.cgEvent!)!, atStart: false)
|
||||
}
|
||||
// Let native tracking and SwiftUI updates settle; no auto-hide timer is under test.
|
||||
sample += 1
|
||||
if sample == 20 {
|
||||
let outside = step == 5
|
||||
guard model.controlsVisible != outside, outside || hidden == 0 else {
|
||||
finish("\(names[step]): visible=\(model.controlsVisible), hidden transitions=\(hidden)")
|
||||
return
|
||||
}
|
||||
sample = 0
|
||||
step += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private enum WindowDragCheck {
|
||||
static var timer: Timer?
|
||||
|
||||
Reference in New Issue
Block a user