diff options
Diffstat (limited to 'video')
-rw-r--r-- | video/out/cocoa-cb/events_view.swift | 266 | ||||
-rw-r--r-- | video/out/cocoa-cb/video_layer.swift | 210 | ||||
-rw-r--r-- | video/out/cocoa-cb/window.swift | 456 | ||||
-rw-r--r-- | video/out/cocoa_cb_common.swift | 485 | ||||
-rw-r--r-- | video/out/opengl/context_cocoa.c | 2 | ||||
-rw-r--r-- | video/out/vo.h | 5 | ||||
-rw-r--r-- | video/out/vo_opengl_cb.c | 88 |
7 files changed, 1492 insertions, 20 deletions
diff --git a/video/out/cocoa-cb/events_view.swift b/video/out/cocoa-cb/events_view.swift new file mode 100644 index 0000000000..4cb154c64a --- /dev/null +++ b/video/out/cocoa-cb/events_view.swift @@ -0,0 +1,266 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +import Cocoa + +class EventsView: NSView { + + weak var cocoaCB: CocoaCB! + var mpv: MPVHelper! { + get { return cocoaCB == nil ? nil : cocoaCB.mpv } + } + + var tracker: NSTrackingArea? + var hasMouseDown: Bool = false + + override var isFlipped: Bool { return true } + override var acceptsFirstResponder: Bool { return true } + + + init(frame frameRect: NSRect, cocoaCB ccb: CocoaCB) { + cocoaCB = ccb + super.init(frame: frameRect) + autoresizingMask = [.viewWidthSizable, .viewHeightSizable] + wantsBestResolutionOpenGLSurface = true + register(forDraggedTypes: [NSFilenamesPboardType, NSURLPboardType]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func updateTrackingAreas() { + if tracker != nil { + removeTrackingArea(tracker!) + } + + if mpv != nil && !mpv.getBoolProperty("input-cursor") { + return + } + + tracker = NSTrackingArea(rect: self.bounds, + options: [.activeAlways, .mouseEnteredAndExited, .mouseMoved, .enabledDuringMouseDrag], + owner: self, userInfo: nil) + addTrackingArea(tracker!) + + if containsMouseLocation() { + cocoa_put_key_with_modifiers(SWIFT_KEY_MOUSE_LEAVE, 0) + } + } + + override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { + guard let types = sender.draggingPasteboard().types else { return [] } + if types.contains(NSFilenamesPboardType) || types.contains(NSURLPboardType) { + return .copy + } + return [] + } + + override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { + let pb = sender.draggingPasteboard() + guard let types = sender.draggingPasteboard().types else { return false } + if types.contains(NSFilenamesPboardType) { + if let files = pb.propertyList(forType: NSFilenamesPboardType) as? [Any] { + EventsResponder.sharedInstance().handleFilesArray(files) + return true + } + } else if types.contains(NSURLPboardType) { + if let url = pb.propertyList(forType: NSURLPboardType) as? [Any] { + EventsResponder.sharedInstance().handleFilesArray(url) + return true + } + } + return false + } + + override func acceptsFirstMouse(for event: NSEvent?) -> Bool { + return mpv.getBoolProperty("input-cursor") + } + + override func becomeFirstResponder() -> Bool { + return mpv.getBoolProperty("input-cursor") || + mpv.getBoolProperty("input-vo-keyboard") + } + + override func resignFirstResponder() -> Bool { + return true + } + + override func mouseEntered(with event: NSEvent) { + if mpv.getBoolProperty("input-cursor") { + cocoa_put_key_with_modifiers(SWIFT_KEY_MOUSE_ENTER, 0) + } + } + + override func mouseExited(with event: NSEvent) { + if mpv.getBoolProperty("input-cursor") { + cocoa_put_key_with_modifiers(SWIFT_KEY_MOUSE_LEAVE, 0) + } + } + + override func mouseMoved(with event: NSEvent) { + if mpv != nil && mpv.getBoolProperty("input-cursor") { + signalMouseMovement(event) + } + } + + override func mouseDragged(with event: NSEvent) { + if mpv.getBoolProperty("input-cursor") { + signalMouseMovement(event) + } + } + + override func mouseDown(with event: NSEvent) { + if mpv.getBoolProperty("input-cursor") { + signalMouseDown(event) + } + } + + override func mouseUp(with event: NSEvent) { + if mpv.getBoolProperty("input-cursor") { + signalMouseUp(event) + } + cocoaCB.window.isMoving = false + } + + override func rightMouseDown(with event: NSEvent) { + if mpv.getBoolProperty("input-cursor") { + signalMouseDown(event) + } + } + + override func rightMouseUp(with event: NSEvent) { + if mpv.getBoolProperty("input-cursor") { + signalMouseUp(event) + } + } + + override func otherMouseDown(with event: NSEvent) { + if mpv.getBoolProperty("input-cursor") { + signalMouseDown(event) + } + } + + override func otherMouseUp(with event: NSEvent) { + if mpv.getBoolProperty("input-cursor") { + signalMouseUp(event) + } + } + + func signalMouseDown(_ event: NSEvent) { + signalMouseEvent(event, SWIFT_KEY_STATE_DOWN) + if event.clickCount > 1 { + signalMouseEvent(event, SWIFT_KEY_STATE_UP) + } + } + + func signalMouseUp(_ event: NSEvent) { + signalMouseEvent(event, SWIFT_KEY_STATE_UP) + } + + func signalMouseEvent(_ event: NSEvent, _ state: Int32) { + hasMouseDown = state == SWIFT_KEY_STATE_DOWN + let mpkey = getMpvButton(event) + cocoa_put_key_with_modifiers((mpkey | state), Int32(event.modifierFlags.rawValue)); + } + + func signalMouseMovement(_ event: NSEvent) { + var point = convert(event.locationInWindow, from: nil) + point = convertToBacking(point) + point.y = -point.y + + cocoaCB.window.updateMovableBackground(point) + if !cocoaCB.window.isMoving { + mpv.setMousePosition(point) + } + } + + func preciseScroll(_ event: NSEvent) { + var delta: Double + var cmd: Int32 + + if fabs(event.deltaY) >= fabs(event.deltaX) { + delta = Double(event.deltaY) * 0.1; + cmd = delta > 0 ? SWIFT_WHEEL_UP : SWIFT_WHEEL_DOWN; + } else { + delta = Double(event.deltaX) * 0.1; + cmd = delta > 0 ? SWIFT_WHEEL_RIGHT : SWIFT_WHEEL_LEFT; + } + + mpv.putAxis(cmd, delta: fabs(delta)) + } + + override func scrollWheel(with event: NSEvent) { + if !mpv.getBoolProperty("input-cursor") { + return + } + + if event.hasPreciseScrollingDeltas { + preciseScroll(event) + } else { + let modifiers = event.modifierFlags + let deltaX = modifiers.contains(.shift) ? event.scrollingDeltaY : event.scrollingDeltaX + let deltaY = modifiers.contains(.shift) ? event.scrollingDeltaX : event.scrollingDeltaY + var mpkey: Int32 + + if fabs(deltaY) >= fabs(deltaX) { + mpkey = deltaY > 0 ? SWIFT_WHEEL_UP : SWIFT_WHEEL_DOWN; + } else { + mpkey = deltaX > 0 ? SWIFT_WHEEL_RIGHT : SWIFT_WHEEL_LEFT; + } + + cocoa_put_key_with_modifiers(mpkey, Int32(modifiers.rawValue)) + } + } + + func containsMouseLocation() -> Bool { + if cocoaCB == nil { return false } + var topMargin: CGFloat = 0.0 + let menuBarHeight = NSApp.mainMenu!.menuBarHeight + + if cocoaCB.window.isInFullscreen && (menuBarHeight > 0) { + let titleBar = NSWindow.frameRect(forContentRect: CGRect.zero, styleMask: .titled) + topMargin = titleBar.size.height + 1 + menuBarHeight + } + + var vF = window!.screen!.frame + vF.size.height -= topMargin + + let vFW = window!.convertFromScreen(vF) + let vFV = convert(vFW, from: nil) + let pt = convert(window!.mouseLocationOutsideOfEventStream, from: nil) + + let clippedBounds = bounds.intersection(vFV) + return clippedBounds.contains(pt) + } + + func canHideCursor() -> Bool { + return !hasMouseDown && containsMouseLocation() && window!.isKeyWindow + } + + func getMpvButton(_ event: NSEvent) -> Int32 { + let buttonNumber = event.buttonNumber + switch (buttonNumber) { + case 0: return SWIFT_MBTN_LEFT; + case 1: return SWIFT_MBTN_RIGHT; + case 2: return SWIFT_MBTN_MID; + case 3: return SWIFT_MBTN_BACK; + case 4: return SWIFT_MBTN_FORWARD; + default: return SWIFT_MBTN9 + Int32(buttonNumber - 5); + } + } +} diff --git a/video/out/cocoa-cb/video_layer.swift b/video/out/cocoa-cb/video_layer.swift new file mode 100644 index 0000000000..05f0894159 --- /dev/null +++ b/video/out/cocoa-cb/video_layer.swift @@ -0,0 +1,210 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +import Cocoa +import OpenGL.GL +import OpenGL.GL3 + +class VideoLayer: CAOpenGLLayer { + + weak var cocoaCB: CocoaCB! + var mpv: MPVHelper! { + get { return cocoaCB == nil ? nil : cocoaCB.mpv } + } + + let videoLock = NSLock() + var hasVideo: Bool = false + var neededFlips: Int = 0 + var cglContext: CGLContextObj? = nil + + var canDrawOffScreen: Bool = false + var lastThread: Thread? = nil + + var needsICCUpdate: Bool = false { + didSet { + if needsICCUpdate == true { + neededFlips += 1 + } + } + } + + let surfaceLock = NSLock() + var surfaceSize: NSSize? + + var inLiveResize: Bool = false { + didSet { + if inLiveResize == false { + isAsynchronous = false + display() + } else { + surfaceLock.lock() + updateSurfaceSize() + surfaceLock.unlock() + isAsynchronous = true + } + } + } + + init(cocoaCB ccb: CocoaCB) { + cocoaCB = ccb + super.init() + autoresizingMask = [.layerWidthSizable, .layerHeightSizable] + backgroundColor = NSColor.black.cgColor + contentsScale = cocoaCB.window.backingScaleFactor + } + + override init(layer: Any) { + let oldLayer = layer as! VideoLayer + cocoaCB = oldLayer.cocoaCB + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setUpGLCB() { + self.mpv.initGLCB() + self.mpv.setGLCBUpdateCallback(self.updateCallback, context: self) + self.mpv.setGLCBControlCallback(self.cocoaCB.controlCallback, context: self.cocoaCB) + } + + override func canDraw(inCGLContext ctx: CGLContextObj, + pixelFormat pf: CGLPixelFormatObj, + forLayerTime t: CFTimeInterval, + displayTime ts: UnsafePointer<CVTimeStamp>?) -> Bool { + return mpv != nil && cocoaCB.backendState == .init + } + + override func draw(inCGLContext ctx: CGLContextObj, + pixelFormat pf: CGLPixelFormatObj, + forLayerTime t: CFTimeInterval, + displayTime ts: UnsafePointer<CVTimeStamp>?) { + neededFlips = 0 + canDrawOffScreen = Thread.current == lastThread + lastThread = Thread.current + draw(ctx) + } + + func draw(_ ctx: CGLContextObj) { + surfaceLock.lock() + if inLiveResize == false { + updateSurfaceSize() + } + + mpv.drawGLCB(surfaceSize!) + surfaceLock.unlock() + CGLFlushDrawable(ctx) + + if needsICCUpdate { + needsICCUpdate = false + cocoaCB.updateICCProfile() + } + } + + func updateSurfaceSize() { + surfaceSize = bounds.size + surfaceSize!.width *= contentsScale + surfaceSize!.height *= contentsScale + } + + override func copyCGLPixelFormat(forDisplayMask mask: UInt32) -> CGLPixelFormatObj { + let glVersions: [CGLOpenGLProfile] = [ + kCGLOGLPVersion_3_2_Core, + kCGLOGLPVersion_Legacy + ] + + var pix: CGLPixelFormatObj? + var err: CGLError = CGLError(rawValue: 0) + var npix: GLint = 0 + + verLoop : for ver in glVersions { + var glAttributes: [CGLPixelFormatAttribute] = [ + kCGLPFAOpenGLProfile, CGLPixelFormatAttribute(ver.rawValue), + kCGLPFAAccelerated, + kCGLPFADoubleBuffer, + kCGLPFABackingStore, + kCGLPFAAllowOfflineRenderers, + kCGLPFASupportsAutomaticGraphicsSwitching, + _CGLPixelFormatAttribute(rawValue: 0) + ] + + for index in stride(from: glAttributes.count-2, through: 4, by: -1) { + err = CGLChoosePixelFormat(glAttributes, &pix, &npix) + if err == kCGLBadAttribute { + glAttributes.remove(at: index) + } else { + break verLoop + } + } + } + + if err != kCGLNoError { + fatalError("Couldn't create CGL pixel format: \(CGLErrorString(err)) (\(err))") + } + return pix! + } + + override func copyCGLContext(forPixelFormat pf: CGLPixelFormatObj) -> CGLContextObj { + let ctx = super.copyCGLContext(forPixelFormat: pf) + var i: GLint = 1 + CGLSetParameter(ctx, kCGLCPSwapInterval, &i) + CGLSetCurrentContext(ctx) + cglContext = ctx + + if let app = NSApp as? Application { + app.initMPVCore() + } + return ctx + } + + let updateCallback: mpv_opengl_cb_update_fn = { (ctx) in + let layer: VideoLayer = MPVHelper.bridge(ptr: ctx!) + layer.neededFlips += 1 + } + + override func display() { + super.display() + if !isAsynchronous { + CATransaction.flush() + } + } + + func setVideo(_ state: Bool) { + videoLock.lock() + hasVideo = state + neededFlips = 0 + videoLock.unlock() + } + + func reportFlip() { + mpv.reportGLCBFlip() + videoLock.lock() + if !isAsynchronous && neededFlips > 0 && hasVideo { + if !cocoaCB.window.occlusionState.contains(.visible) && + neededFlips > 1 && canDrawOffScreen + { + draw(cglContext!) + display() + } else { + display() + } + } + videoLock.unlock() + } + +} diff --git a/video/out/cocoa-cb/window.swift b/video/out/cocoa-cb/window.swift new file mode 100644 index 0000000000..c93537c03a --- /dev/null +++ b/video/out/cocoa-cb/window.swift @@ -0,0 +1,456 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +import Cocoa + +class Window: NSWindow, NSWindowDelegate { + + weak var cocoaCB: CocoaCB! = nil + var mpv: MPVHelper! { + get { return cocoaCB == nil ? nil : cocoaCB.mpv } + } + + var targetScreen: NSScreen? + var previousScreen: NSScreen? + var currentScreen: NSScreen? + var unfScreen: NSScreen? + + var unfsContentFrame: NSRect? + var isInFullscreen: Bool = false + var isAnimating: Bool = false + var isMoving: Bool = false + var forceTargetScreen: Bool = false + + var keepAspect: Bool = true { + didSet { + if !isInFullscreen { + unfsContentFrame = convertToScreen(contentView!.frame) + } + + if keepAspect { + contentAspectRatio = unfsContentFrame!.size + } else { + resizeIncrements = NSSize(width: 1.0, height: 1.0) + } + } + } + + override var canBecomeKey: Bool { return true } + override var canBecomeMain: Bool { return true } + + override var styleMask: NSWindowStyleMask { + get { return super.styleMask } + set { + let responder = firstResponder + let windowTitle = title + super.styleMask = newValue + makeFirstResponder(responder) + title = windowTitle + } + } + + convenience init(cocoaCB ccb: CocoaCB) { + self.init(contentRect: NSMakeRect(0, 0, 960, 480), + styleMask: [.titled, .closable, .miniaturizable, .resizable], + backing: .buffered, defer: false, screen: NSScreen.main()) + cocoaCB = ccb + title = "mpv" + } + + convenience init(contentRect: NSRect, styleMask style: NSWindowStyleMask, + screen: NSScreen?, cocoaCB ccb: CocoaCB) + { + self.init(contentRect: contentRect, styleMask: style, + backing: .buffered, defer: false, screen: screen) + cocoaCB = ccb + minSize = NSMakeSize(160, 90) + collectionBehavior = .fullScreenPrimary + delegate = self + + unfsContentFrame = convertToScreen(contentView!.frame) + targetScreen = screen! + currentScreen = screen! + unfScreen = screen! + + if let app = NSApp as? Application { + app.menuBar.register(#selector(setHalfWindowSize), for: MPM_H_SIZE) + app.menuBar.register(#selector(setNormalWindowSize), for: MPM_N_SIZE) + app.menuBar.register(#selector(setDoubleWindowSize), for: MPM_D_SIZE) + app.menuBar.register(#selector(performMiniaturize(_:)), for: MPM_MINIMIZE) + app.menuBar.register(#selector(performZoom(_:)), for: MPM_ZOOM) + } + } + + override func toggleFullScreen(_ sender: Any?) { + if isAnimating { + return + } + + isAnimating = true + + targetScreen = cocoaCB.getTargetScreen(forFullscreen: !isInFullscreen) + if targetScreen == nil && previousScreen == nil { + targetScreen = screen + } else if targetScreen == nil { + targetScreen = previousScreen + previousScreen = nil + } else { + previousScreen = screen + } + + if !isInFullscreen { + unfsContentFrame = convertToScreen(contentView!.frame) + unfScreen = screen + } + // move window to target screen when going to fullscreen + if !isInFullscreen && (targetScreen != screen) { + let frame = calculateWindowPosition(for: targetScreen!, withoutBounds: false) + setFrame(frame, display: true) + } + + if mpv.getBoolProperty("native-fs") { + super.toggleFullScreen(sender) + } else { + if !isInFullscreen { + setToFullScreen() + } + else { + setToWindow() + } + } + } + + func customWindowsToEnterFullScreen(for window: NSWindow) -> [NSWindow]? { + return [window] + } + + func customWindowsToExitFullScreen(for window: NSWindow) -> [NSWindow]? { + return [window] + } + + func window(_ window: NSWindow, startCustomAnimationToEnterFullScreenWithDuration duration: TimeInterval) { + var newFrame = targetScreen!.frame + let cRect = contentRect(forFrameRect: frame) + newFrame.size.height = newFrame.size.height - (frame.size.height - cRect.size.height) + let intermediateFrame = aspectFit(rect: cRect, in: newFrame) + + NSAnimationContext.runAnimationGroup({ (context) -> Void in + context.duration = duration-0.1 + window.animator().setFrame(intermediateFrame, display: true) + }, completionHandler: { }) + } + + func window(_ window: NSWindow, startCustomAnimationToExitFullScreenWithDuration duration: TimeInterval) { + let newFrame = calculateWindowPosition(for: targetScreen!, withoutBounds: targetScreen == screen) + let intermediateFrame = aspectFit(rect: newFrame, in: screen!.frame) + setFrame(intermediateFrame, display: true) + cocoaCB.layer.display() + + NSAnimationContext.runAnimationGroup({ (context) -> Void in + context.duration = duration-0.1 + window.animator().setFrame(newFrame, display: true) + }, completionHandler: { }) + } + + func windowDidEnterFullScreen(_ notification: Notification) { + isInFullscreen = true + cocoaCB.flagEvents(VO_EVENT_FULLSCREEN_STATE) + cocoaCB.updateCusorVisibility() + endAnimation() + } + + func windowDidExitFullScreen(_ notification: Notification) { + isInFullscreen = false + cocoaCB.flagEvents(VO_EVENT_FULLSCREEN_STATE) + endAnimation() + } + + func windowDidFailToEnterFullScreen(_ window: NSWindow) { + let newFrame = calculateWindowPosition(for: targetScreen!, withoutBounds: targetScreen == screen) + setFrame(newFrame, display: true) + endAnimation() + } + + func windowDidFailToExitFullScreen(_ window: NSWindow) { + let newFrame = targetScreen!.frame + setFrame(newFrame, display: true) + endAnimation() + } + + func endAnimation() { + isAnimating = false + cocoaCB.isShuttingDown = false + } + + func setToFullScreen() { + styleMask.insert(.fullScreen) + NSApp.presentationOptions = [.autoHideMenuBar, .autoHideDock] + setFrame(targetScreen!.frame, display: true) + endAnimation() + isInFullscreen = true + cocoaCB.flagEvents(VO_EVENT_FULLSCREEN_STATE) + cocoaCB.layer.neededFlips += 1 + } + + func setToWindow() { + let newFrame = calculateWindowPosition(for: targetScreen!, withoutBounds: targetScreen == screen) + NSApp.presentationOptions = [] + setFrame(newFrame, display: true) + styleMask.remove(.fullScreen) + endAnimation() + isInFullscreen = false + cocoaCB.flagEvents(VO_EVENT_FULLSCREEN_STATE) + cocoaCB.layer.neededFlips += 1 + } + + func setBorder(_ state: Bool) { + if styleMask.contains(.titled) != state { + if state { + styleMask.remove(.borderless) + styleMask.insert(.titled) + } else { + styleMask.remove(.titled) + styleMask.insert(.borderless) + } + } + } + + func setOnTop(_ state: Bool) { + if state { + let ontopLevel = mpv.getStringProperty("ontop-level") ?? "window" + switch ontopLevel { + case "window": + level = Int(CGWindowLevelForKey(.floatingWindow)) + case "system": + level = Int(CGWindowLevelForKey(.statusWindow))+1 + default: + level = Int(ontopLevel)! + } + collectionBehavior.remove(.transient) + collectionBehavior.insert(.managed) + } else { + level = Int(CGWindowLevelForKey(.normalWindow)) + } + } + + func updateMovableBackground(_ pos: NSPoint) { + if !isInFullscreen { + isMovableByWindowBackground = mpv.canBeDraggedAt(pos) + } else { + isMovableByWindowBackground = false + } + } + + func updateFrame(_ rect: NSRect) { + if rect != frame { + let cRect = frameRect(forContentRect: rect) + setFrame(cRect, display: true) + unfsContentFrame = rect + } + } + + func updateSize(_ size: NSSize) { + if size != contentView!.frame.size { + let newContentFrame = centeredContentSize(for: frame, size: size) + if !isInFullscreen { + updateFrame(newContentFrame) + } else { + unfsContentFrame = newContentFrame + } + } + } + + override func setFrame(_ frameRect: NSRect, display flag: Bool) { + super.setFrame(frameRect, display: flag) + if keepAspect { + contentAspectRatio = unfsContentFrame!.size + } + } + + func centeredContentSize(for rect: NSRect, size sz: NSSize) -> NSRect { + let cRect = contentRect(forFrameRect: rect) + let dx = (cRect.size.width - sz.width) / 2 + let dy = (cRect.size.height - sz.height) / 2 + return NSInsetRect(cRect, dx, dy) + } + + func aspectFit(rect r: NSRect, in rTarget: NSRect) -> NSRect { + var s = rTarget.width / r.width; + if r.height*s > rTarget.height { + s = rTarget.height / r.height + } + let w = r.width * s + let h = r.height * s + return NSRect(x: rTarget.midX - w/2, y: rTarget.midY - h/2, width: w, height: h) + } + + func calculateWindowPosition(for tScreen: NSScreen, withoutBounds: Bool) -> NSRect { + var newFrame = frameRect(forContentRect: unfsContentFrame!) + let targetFrame = tScreen.frame + let targetVisibleFrame = tScreen.visibleFrame + let unfsScreenFrame = unfScreen!.frame + let visibleWindow = NSIntersectionRect(unfsScreenFrame, newFrame) + + // calculate visible area of every side + let left = newFrame.origin.x - unfsScreenFrame.origin.x + let right = unfsScreenFrame.size.width - + (newFrame.origin.x - unfsScreenFrame.origin.x + newFrame.size.width) + let bottom = newFrame.origin.y - unfsScreenFrame.origin.y + let top = unfsScreenFrame.size.height - + (newFrame.origin.y - unfsScreenFrame.origin.y + newFrame.size.height) + + // normalize visible areas, decide which one to take horizontal/vertical + var xPer = (unfsScreenFrame.size.width - visibleWindow.size.width) + var yPer = (unfsScreenFrame.size.height - visibleWindow.size.height) + if xPer != 0 { xPer = (left >= 0 || right < 0 ? left : right) / xPer } + if yPer != 0 { yPer = (bottom >= 0 || top < 0 ? bottom : top) / yPer } + + // calculate visible area for every side for target screen + let xNewLeft = targetFrame.origin.x + + (targetFrame.size.width - visibleWindow.size.width) * xPer + let xNewRight = targetFrame.origin.x + targetFrame.size.width - + (targetFrame.size.width - visibleWindow.size.width) * xPer - newFrame.size.width + let yNewBottom = targetFrame.origin.y + + (targetFrame.size.height - visibleWindow.size.height) * yPer + let yNewTop = targetFrame.origin.y + targetFrame.size.height - + (targetFrame.size.height - visibleWindow.size.height) * yPer - newFrame.size.height + + // calculate new coordinates, decide which one to take horizontal/vertical + newFrame.origin.x = left >= 0 || right < 0 ? xNewLeft : xNewRight + newFrame.origin.y = bottom >= 0 || top < 0 ? yNewBottom : yNewTop + + // don't place new window on top of a visible menubar + let topMar = targetFrame.size.height - + (newFrame.origin.y - targetFrame.origin.y + newFrame.size.height) + let menuBarHeight = targetFrame.size.height - + (targetVisibleFrame.size.height + targetVisibleFrame.origin.y) + if topMar < menuBarHeight { + newFrame.origin.y -= top - menuBarHeight + } + + if withoutBounds { + return newFrame + } + + // screen bounds right and left + if newFrame.origin.x + newFrame.size.width > targetFrame.origin.x + targetFrame.size.width { + newFrame.origin.x = targetFrame.origin.x + targetFrame.size.width - newFrame.size.width + } + if newFrame.origin.x < targetFrame.origin.x { + newFrame.origin.x = targetFrame.origin.x + } + + // screen bounds top and bottom + if newFrame.origin.y + newFrame.size.height > targetFrame.origin.y + targetFrame.size.height { + newFrame.origin.y = targetFrame.origin.y + targetFrame.size.height - newFrame.size.height + } + if newFrame.origin.y < targetFrame.origin.y { + newFrame.origin.y = targetFrame.origin.y + } + return newFrame + } + + override func constrainFrameRect(_ frameRect: NSRect, to tScreen: NSScreen?) -> NSRect { + if (isAnimating && !isInFullscreen) || (!isAnimating && isInFullscreen) { + return frameRect + } + + var nf: NSRect = frameRect + let ts: NSScreen = tScreen ?? screen ?? NSScreen.main()! + let of: NSRect = frame + let vf: NSRect = (isAnimating ? targetScreen! : ts).visibleFrame + let ncf: NSRect = contentRect(forFrameRect: nf) + + // screen bounds top and bottom + if NSMaxY(nf) > NSMaxY(vf) { + nf.origin.y = NSMaxY(vf) - NSHeight(nf) + } + if NSMaxY(ncf) < NSMinY(vf) { + nf.origin.y = NSMinY(vf) + NSMinY(ncf) - NSMaxY(ncf) + } + + // screen bounds right and left + if NSMinX(nf) > NSMaxX(vf) { + nf.origin.x = NSMaxX(vf) - NSWidth(nf) + } + if NSMaxX(nf) < NSMinX(vf) { + nf.origin.x = NSMinX(vf) + } + + if NSHeight(nf) < NSHeight(vf) && NSHeight(of) > NSHeight(vf) && !isInFullscreen { + // If the window height is smaller than the visible frame, but it was + // bigger previously recenter the smaller window vertically. This is + // needed to counter the 'snap to top' behaviour. + nf.origin.y = (NSHeight(vf) - NSHeight(nf)) / 2 + } + return nf + } + + func setNormalWindowSize() { setWindowScale(1.0) } + func setHalfWindowSize() { setWindowScale(0.5) } + func setDoubleWindowSize() { setWindowScale(2.0) } + + func setWindowScale(_ scale: Double) { + mpv.commandAsync(["osd-auto", "set", "window-scale", "\(scale)"]) + } + + func windowDidChangeScreen(_ notification: Notification) { + if screen == nil { + return + } + if !isAnimating && (currentScreen != screen) { + previousScreen = screen + } + if currentScreen != screen { + cocoaCB.updateDisplaylink() + } + currentScreen = screen + } + + func windowDidChangeScreenProfile(_ notification: Notification) { + cocoaCB.layer.needsICCUpdate = true + } + + func windowDidChangeBackingProperties(_ notification: Notification) { + cocoaCB.layer.contentsScale = backingScaleFactor + } + + func windowWillStartLiveResize(_ notification: Notification) { + cocoaCB.layer.inLiveResize = true + } + + func windowDidEndLiveResize(_ notification: Notification) { + cocoaCB.layer.inLiveResize = false + } + + func windowShouldClose(_ sender: Any) -> Bool { + cocoa_put_key(SWIFT_KEY_CLOSE_WIN) + return false + } + + func windowDidResignKey(_ notification: Notification) { + cocoaCB.setCursorVisiblility(true) + } + + func windowDidBecomeKey(_ notification: Notification) { + cocoaCB.updateCusorVisibility() + } + + func windowWillMove(_ notification: Notification) { + isMoving = true + } +} diff --git a/video/out/cocoa_cb_common.swift b/video/out/cocoa_cb_common.swift new file mode 100644 index 0000000000..9a6da82ecf --- /dev/null +++ b/video/out/cocoa_cb_common.swift @@ -0,0 +1,485 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +import Cocoa +import IOKit.pwr_mgt + +class CocoaCB: NSObject { + + var mpv: MPVHelper! + var window: Window! |