fix: keep playlist reordering active while dragging
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-queue-drag") {
|
||||
QueueDragCheck.run(result: URL(fileURLWithPath: CommandLine.arguments[index + 1]))
|
||||
return
|
||||
}
|
||||
if let index = CommandLine.arguments.firstIndex(of: "--check-window-layout") {
|
||||
WindowLayoutCheck.run(result: URL(fileURLWithPath: CommandLine.arguments[index + 1]))
|
||||
return
|
||||
@@ -16,6 +20,117 @@ public func checkAppStartup() {
|
||||
AppStartupCheck.run()
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private enum QueueDragCheck {
|
||||
static var timer: Timer?
|
||||
static var step = 0
|
||||
static var resultURL: URL!
|
||||
static var original: [UUID] = []
|
||||
static var current: UUID?
|
||||
static var start = NSPoint.zero
|
||||
static var end = NSPoint.zero
|
||||
static var previousPoint = NSPoint.zero
|
||||
// Deadline only; ordering assertions do not depend on the wall clock.
|
||||
static let deadline = ContinuousClock.now.advanced(by: .seconds(20))
|
||||
|
||||
static func run(result: URL) {
|
||||
resultURL = result
|
||||
_ = NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown, .leftMouseDragged, .leftMouseUp]) {
|
||||
NSLog("[DEBUG-queue-drag] received %@", String(describing: $0))
|
||||
return $0
|
||||
}
|
||||
timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { _ in
|
||||
MainActor.assumeIsolated { advance() }
|
||||
}
|
||||
terebiAppMain()
|
||||
}
|
||||
|
||||
static func finish(_ error: String? = nil) {
|
||||
timer?.invalidate()
|
||||
try! JSONSerialization.data(withJSONObject: ["passed": error == nil, "error": error ?? ""])
|
||||
.write(to: resultURL, options: .atomic)
|
||||
PlayerModel.shared.shutdown()
|
||||
NSApp.terminate(nil)
|
||||
}
|
||||
|
||||
static func mouse(_ type: NSEvent.EventType, at point: NSPoint, in window: NSWindow) {
|
||||
let event = NSEvent.mouseEvent(with: type, location: point, modifierFlags: [],
|
||||
timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil,
|
||||
eventNumber: step, clickCount: 1, pressure: type == .leftMouseUp ? 0 : 1)!
|
||||
let native = event.cgEvent!
|
||||
if type == .leftMouseDragged {
|
||||
native.setDoubleValueField(.mouseEventDeltaX, value: point.x - previousPoint.x)
|
||||
native.setDoubleValueField(.mouseEventDeltaY, value: previousPoint.y - point.y)
|
||||
}
|
||||
previousPoint = point
|
||||
NSApp.postEvent(NSEvent(cgEvent: native)!, atStart: false)
|
||||
}
|
||||
|
||||
static func advance() {
|
||||
let model = PlayerModel.shared
|
||||
guard ContinuousClock.now < deadline else { finish("Queue drag did not complete at step \(step)"); return }
|
||||
guard model.canTogglePlayback,
|
||||
let window = NSApp.windows.first(where: { $0.canBecomeMain && !($0 is NSPanel) }),
|
||||
let content = window.contentView else { return }
|
||||
if step == 0 {
|
||||
model.setVolume(0)
|
||||
model.addToQueue(["B", "C"].map { URL(fileURLWithPath: "/queue-drag-check/\($0).mp4") })
|
||||
current = model.queue.currentID
|
||||
original = model.queue.items.map(\.id)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
} else if step == 1 {
|
||||
guard !model.isQueueTransitioning else { return }
|
||||
guard NSApp.isActive && window.isKeyWindow else {
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
return
|
||||
}
|
||||
window.layoutIfNeeded()
|
||||
if let bitmap = content.bitmapImageRepForCachingDisplay(in: content.bounds) {
|
||||
content.cacheDisplay(in: content.bounds, to: bitmap)
|
||||
try! bitmap.representation(using: .png, properties: [:])!.write(to: URL(fileURLWithPath: "/tmp/terebi-queue-debug.png"))
|
||||
}
|
||||
let top = content.isFlipped ? content.bounds.minY : content.bounds.maxY
|
||||
let sign: CGFloat = content.isFlipped ? 1 : -1
|
||||
start = content.convert(NSPoint(x: content.bounds.maxX - 100, y: top + sign * 82), to: nil)
|
||||
end = NSPoint(x: start.x, y: start.y - 120)
|
||||
NSLog("[DEBUG-queue-drag] start %@ end %@ hit %@", String(describing: start), String(describing: end),
|
||||
String(describing: content.hitTest(content.convert(start, from: nil))))
|
||||
mouse(.leftMouseDown, at: start, in: window)
|
||||
} else if step == 2 {
|
||||
mouse(.leftMouseDragged, at: NSPoint(x: start.x, y: start.y - 10), in: window)
|
||||
} else if step == 3 {
|
||||
mouse(.leftMouseDragged, at: end, in: window)
|
||||
} else if step == 12 {
|
||||
guard model.queue.items.map(\.id) == original else {
|
||||
finish("Queue committed before release"); return
|
||||
}
|
||||
// Several event-loop turns without a mouseDragged event, then release in place.
|
||||
mouse(.leftMouseUp, at: end, in: window)
|
||||
} else if step == 14 {
|
||||
guard model.queue.items.map(\.id) == [original[1], original[2], original[0]],
|
||||
model.queue.currentID == current else {
|
||||
finish("Stationary release order: \(model.queue.items.map(\.url.lastPathComponent))"); return
|
||||
}
|
||||
mouse(.leftMouseDown, at: end, in: window)
|
||||
} else if step == 15 {
|
||||
mouse(.leftMouseDragged, at: NSPoint(x: end.x, y: end.y + 10), in: window)
|
||||
} else if step == 16 {
|
||||
mouse(.leftMouseDragged, at: start, in: window)
|
||||
} else if step == 25 {
|
||||
mouse(.leftMouseUp, at: start, in: window)
|
||||
} else if step == 27 {
|
||||
guard model.queue.items.map(\.id) == original, model.queue.currentID == current else {
|
||||
finish("Stationary upward release did not restore the original order"); return
|
||||
}
|
||||
finish()
|
||||
return
|
||||
}
|
||||
step += 1
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private enum WindowLayoutCheck {
|
||||
static var timer: Timer?
|
||||
|
||||
Reference in New Issue
Block a user