diff options
Diffstat (limited to 'video/out/mac')
-rw-r--r-- | video/out/mac/common.swift | 544 | ||||
-rw-r--r-- | video/out/mac/gl_layer.swift | 322 | ||||
-rw-r--r-- | video/out/mac/title_bar.swift | 255 | ||||
-rw-r--r-- | video/out/mac/view.swift | 297 | ||||
-rw-r--r-- | video/out/mac/window.swift | 547 |
5 files changed, 1965 insertions, 0 deletions
diff --git a/video/out/mac/common.swift b/video/out/mac/common.swift new file mode 100644 index 0000000000..b4aa105b23 --- /dev/null +++ b/video/out/mac/common.swift @@ -0,0 +1,544 @@ +/* + * 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 Common: NSObject { + var mpv: MPVHelper? + var log: LogHelper + let queue: DispatchQueue = DispatchQueue(label: "io.mpv.queue") + + var window: Window? + var view: View? + var titleBar: TitleBar? + + var link: CVDisplayLink? + + let eventsLock = NSLock() + var events: Int = 0 + + var lightSensor: io_connect_t = 0 + var lastLmu: UInt64 = 0 + var lightSensorIOPort: IONotificationPortRef? + + var displaySleepAssertion: IOPMAssertionID = IOPMAssertionID(0) + + var cursorVisibilityWanted: Bool = true + + var title: String = "mpv" { + didSet { if let window = window { window.title = title } } + } + + init(_ mpLog: OpaquePointer?) { + log = LogHelper(mpLog) + } + + func initMisc(_ vo: UnsafeMutablePointer<vo>) { + startDisplayLink(vo) + initLightSensor() + addDisplayReconfigureObserver() + } + + func initApp() { + NSApp.setActivationPolicy(.regular) + setAppIcon() + } + + func initWindow(_ vo: UnsafeMutablePointer<vo>) { + let (mpv, targetScreen, wr) = getInitProperties(vo) + + guard let view = self.view else { + log.sendError("Something went wrong, no View was initialized") + exit(1) + } + + window = Window(contentRect: wr, screen: targetScreen, view: view, common: self) + guard let window = self.window else { + log.sendError("Something went wrong, no Window was initialized") + exit(1) + } + + window.setOnTop(Bool(mpv.opts.ontop), Int(mpv.opts.ontop_level)) + window.keepAspect = Bool(mpv.opts.keepaspect_window) + window.title = title + window.border = Bool(mpv.opts.border) + + titleBar = TitleBar(frame: wr, window: window, common: self) + + let minimized = Bool(mpv.opts.window_minimized) + window.isRestorable = false + window.isReleasedWhenClosed = false + window.setMaximized(minimized ? false : Bool(mpv.opts.window_maximized)) + window.setMinimized(minimized) + window.makeMain() + window.makeKey() + + if !minimized { + window.orderFront(nil) + } + + NSApp.activate(ignoringOtherApps: true) + } + + func initView(_ vo: UnsafeMutablePointer<vo>, _ layer: CALayer) { + let (_, _, wr) = getInitProperties(vo) + + view = View(frame: wr, common: self) + guard let view = self.view else { + log.sendError("Something went wrong, no View was initialized") + exit(1) + } + + view.layer = layer + view.wantsLayer = true + view.layerContentsPlacement = .scaleProportionallyToFit + } + + func initWindowState() { + if mpv?.opts.fullscreen ?? false { + DispatchQueue.main.async { + self.window?.toggleFullScreen(nil) + } + } else { + window?.isMovableByWindowBackground = true + } + } + + func uninitCommon() { + setCursorVisiblility(true) + stopDisplaylink() + uninitLightSensor() + removeDisplayReconfigureObserver() + enableDisplaySleep() + window?.orderOut(nil) + + titleBar?.removeFromSuperview() + view?.removeFromSuperview() + } + + let linkCallback: CVDisplayLinkOutputCallback = { + (displayLink: CVDisplayLink, + inNow: UnsafePointer<CVTimeStamp>, + inOutputTime: UnsafePointer<CVTimeStamp>, + flagsIn: CVOptionFlags, + flagsOut: UnsafeMutablePointer<CVOptionFlags>, + displayLinkContext: UnsafeMutableRawPointer?) -> CVReturn in + let com = unsafeBitCast(displayLinkContext, to: Common.self) + return com.displayLinkCallback(displayLink, inNow, inOutputTime, flagsIn, flagsOut) + } + + func displayLinkCallback(_ displayLink: CVDisplayLink, + _ inNow: UnsafePointer<CVTimeStamp>, + _ inOutputTime: UnsafePointer<CVTimeStamp>, + _ flagsIn: CVOptionFlags, + _ flagsOut: UnsafeMutablePointer<CVOptionFlags>) -> CVReturn + { + return kCVReturnSuccess + } + + func startDisplayLink(_ vo: UnsafeMutablePointer<vo>) { + CVDisplayLinkCreateWithActiveCGDisplays(&link) + + guard let opts: mp_vo_opts = mpv?.opts, + let screen = getScreenBy(id: Int(opts.screen_id)) ?? NSScreen.main, + let link = self.link else + { + log.sendWarning("Couldn't start DisplayLink, no MPVHelper, Screen or DisplayLink available") + return + } + + CVDisplayLinkSetCurrentCGDisplay(link, screen.displayID) + if #available(macOS 10.12, *) { + CVDisplayLinkSetOutputHandler(link) { link, now, out, inFlags, outFlags -> CVReturn in + return self.displayLinkCallback(link, now, out, inFlags, outFlags) + } + } else { + CVDisplayLinkSetOutputCallback(link, linkCallback, MPVHelper.bridge(obj: self)) + } + CVDisplayLinkStart(link) + } + + func stopDisplaylink() { + if let link = self.link, CVDisplayLinkIsRunning(link) { + CVDisplayLinkStop(link) + } + } + + func updateDisplaylink() { + guard let screen = window?.screen, let link = self.link else { + log.sendWarning("Couldn't update DisplayLink, no Screen or DisplayLink available") + return + } + + CVDisplayLinkSetCurrentCGDisplay(link, screen.displayID) + queue.asyncAfter(deadline: DispatchTime.now() + 0.1) { + self.flagEvents(VO_EVENT_WIN_STATE) + } + } + + func currentFps() -> Double { + if let link = self.link { + var actualFps = CVDisplayLinkGetActualOutputVideoRefreshPeriod(link) + let nominalData = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link) + + if (nominalData.flags & Int32(CVTimeFlags.isIndefinite.rawValue)) < 1 { + let nominalFps = Double(nominalData.timeScale) / Double(nominalData.timeValue) + + if actualFps > 0 { + actualFps = 1/actualFps + } + + if fabs(actualFps - nominalFps) > 0.1 { + log.sendVerbose("Falling back to nominal display refresh rate: \(nominalFps)") + return nominalFps + } else { + return actualFps + } + } + } else { + log.sendWarning("No DisplayLink available") + } + + log.sendWarning("Falling back to standard display refresh rate: 60Hz") + return 60.0 + } + + func enableDisplaySleep() { + IOPMAssertionRelease(displaySleepAssertion) + displaySleepAssertion = IOPMAssertionID(0) + } + + func disableDisplaySleep() { + if displaySleepAssertion != IOPMAssertionID(0) { return } + IOPMAssertionCreateWithName( + kIOPMAssertionTypePreventUserIdleDisplaySleep as CFString, + IOPMAssertionLevel(kIOPMAssertionLevelOn), + "io.mpv.video_playing_back" as CFString, + &displaySleepAssertion) + } + + func lmuToLux(_ v: UInt64) -> Int { + // the polinomial approximation for apple lmu value -> lux was empirically + // derived by firefox developers (Apple provides no documentation). + // https://bugzilla.mozilla.org/show_bug.cgi?id=793728 + let power_c4: Double = 1 / pow(10, 27) + let power_c3: Double = 1 / pow(10, 19) + let power_c2: Double = 1 / pow(10, 12) + let power_c1: Double = 1 / pow(10, 5) + + let lum = Double(v) + let term4: Double = -3.0 * power_c4 * pow(lum, 4.0) + let term3: Double = 2.6 * power_c3 * pow(lum, 3.0) + let term2: Double = -3.4 * power_c2 * pow(lum, 2.0) + let term1: Double = 3.9 * power_c1 * lum + + let lux = Int(ceil(term4 + term3 + term2 + term1 - 0.19)) + return lux > 0 ? lux : 0 + } + + var lightSensorCallback: IOServiceInterestCallback = { (ctx, service, messageType, messageArgument) -> Void in + let com = unsafeBitCast(ctx, to: Common.self) + + var outputs: UInt32 = 2 + var values: [UInt64] = [0, 0] + + var kr = IOConnectCallMethod(com.lightSensor, 0, nil, 0, nil, 0, &values, &outputs, nil, nil) + if kr == KERN_SUCCESS { + var mean = (values[0] + values[1]) / 2 + if com.lastLmu != mean { + com.lastLmu = mean + com.lightSensorUpdate() + } + } + } + + func lightSensorUpdate() { + log.sendWarning("lightSensorUpdate not implemented") + } + + func initLightSensor() { + let srv = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleLMUController")) + if srv == IO_OBJECT_NULL { + log.sendVerbose("Can't find an ambient light sensor") + return + } + + lightSensorIOPort = IONotificationPortCreate(kIOMasterPortDefault) + IONotificationPortSetDispatchQueue(lightSensorIOPort, queue) + var n = io_object_t() + IOServiceAddInterestNotification(lightSensorIOPort, srv, kIOGeneralInterest, lightSensorCallback, MPVHelper.bridge(obj: self), &n) + let kr = IOServiceOpen(srv, mach_task_self_, 0, &lightSensor) + IOObjectRelease(srv) + + if kr != KERN_SUCCESS { + log.sendVerbose("Can't start ambient light sensor connection") + return + } + lightSensorCallback(MPVHelper.bridge(obj: self), 0, 0, nil) + } + + func uninitLightSensor() { + if lightSensorIOPort != nil { + IONotificationPortDestroy(lightSensorIOPort) + IOObjectRelease(lightSensor) + } + } + + var reconfigureCallback: CGDisplayReconfigurationCallBack = { (display, flags, userInfo) in + if flags.contains(.setModeFlag) { + let com = unsafeBitCast(userInfo, to: Common.self) + let displayID = com.window?.screen?.displayID ?? display + + if displayID == display { + com.log.sendVerbose("Detected display mode change, updating screen refresh rate") + com.flagEvents(VO_EVENT_WIN_STATE) + } + } + } + + func addDisplayReconfigureObserver() { + CGDisplayRegisterReconfigurationCallback(reconfigureCallback, MPVHelper.bridge(obj: self)) + } + + func removeDisplayReconfigureObserver() { + CGDisplayRemoveReconfigurationCallback(reconfigureCallback, MPVHelper.bridge(obj: self)) + } + + func setAppIcon() { + if let app = NSApp as? Application, + ProcessInfo.processInfo.environment["MPVBUNDLE"] != "true" + { + NSApp.applicationIconImage = app.getMPVIcon() + } + } + + func updateCursorVisibility() { + setCursorVisiblility(cursorVisibilityWanted) + } + + func setCursorVisiblility(_ visible: Bool) { + NSCursor.setHiddenUntilMouseMoves(!visible && (view?.canHideCursor() ?? false)) + } + + func updateICCProfile() { + log.sendWarning("updateICCProfile not implemented") + } + + func getScreenBy(id screenID: Int) -> NSScreen? { + if screenID >= NSScreen.screens.count { + log.sendInfo("Screen ID \(screenID) does not exist, falling back to current device") + return nil + } else if screenID < 0 { + return nil + } + return NSScreen.screens[screenID] + } + + func getTargetScreen(forFullscreen fs: Bool) -> NSScreen? { + let screenID = fs ? (mpv?.opts.fsscreen_id ?? 0) : (mpv?.opts.screen_id ?? 0) + return getScreenBy(id: Int(screenID)) + } + + func getWindowGeometry(forScreen targetScreen: NSScreen, + videoOut vo: UnsafeMutablePointer<vo>) -> NSRect { + let r = targetScreen.convertRectToBacking(targetScreen.frame) + var screenRC: mp_rect = mp_rect(x0: Int32(0), + y0: Int32(0), + x1: Int32(r.size.width), + y1: Int32(r.size.height)) + + var geo: vo_win_geometry = vo_win_geometry() + vo_calc_window_geometry2(vo, &screenRC, Double(targetScreen.backingScaleFactor), &geo) + + // flip y coordinates + geo.win.y1 = Int32(r.size.height) - geo.win.y1 + geo.win.y0 = Int32(r.size.height) - geo.win.y0 + + let wr = NSMakeRect(CGFloat(geo.win.x0), CGFloat(geo.win.y1), + CGFloat(geo.win.x1 - geo.win.x0), + CGFloat(geo.win.y0 - geo.win.y1)) + return targetScreen.convertRectFromBacking(wr) + } + + func getInitProperties(_ vo: UnsafeMutablePointer<vo>) -> (MPVHelper, NSScreen, NSRect) { + guard let mpv = mpv else { + log.sendError("Something went wrong, no MPVHelper was initialized") + exit(1) + } + guard let targetScreen = getScreenBy(id: Int(mpv.opts.screen_id)) ?? NSScreen.main else { + log.sendError("Something went wrong, no Screen was found") + exit(1) + } + + let wr = getWindowGeometry(forScreen: targetScreen, videoOut: vo) + + return (mpv, targetScreen, wr) + } + + func flagEvents(_ ev: Int) { + eventsLock.lock() + events |= ev + eventsLock.unlock() + + guard let vout = mpv?.vo else { + log.sendWarning("vo nil in flagEvents") + return + } + vo_wakeup(vout) + } + + func checkEvents() -> Int { + eventsLock.lock() + let ev = events + events = 0 + eventsLock.unlock() + return ev + } + + func windowDidEndAnimation() {} + func windowSetToFullScreen() {} + func windowSetToWindow() {} + func windowDidUpdateFrame() {} + func windowDidChangeScreen() {} + func windowDidChangeScreenProfile() {} + func windowDidChangeBackingProperties() {} + func windowWillStartLiveResize() {} + func windowDidEndLiveResize() {} + func windowDidResize() {} + func windowDidChangeOcclusionState() {} + + @objc func control(_ vo: UnsafeMutablePointer<vo>, + events: UnsafeMutablePointer<Int32>, + request: UInt32, + data: UnsafeMutableRawPointer) -> Int32 + { + guard let mpv = mpv else { + log.sendWarning("Unexpected nil value in Control Callback") + return VO_FALSE + } + + switch mp_voctrl(request) { + case VOCTRL_CHECK_EVENTS: + events.pointee |= Int32(checkEvents()) + return VO_TRUE + case VOCTRL_VO_OPTS_CHANGED: + var opt: UnsafeMutableRawPointer? + while mpv.nextChangedConfig(property: &opt) { + if opt! == UnsafeMutableRawPointer(&mpv.optsPtr.pointee.border) { + DispatchQueue.main.async { + self.window?.border = Bool(mpv.opts.border) + } + } + if opt! == UnsafeMutableRawPointer(&mpv.optsPtr.pointee.fullscreen) { + DispatchQueue.main.async { + self.window?.toggleFullScreen(nil) + } + } + if opt! == UnsafeMutableRawPointer(&mpv.optsPtr.pointee.ontop) { + DispatchQueue.main.async { + self.window?.setOnTop(Bool(mpv.opts.ontop), Int(mpv.opts.ontop_level)) + } + } + if opt! == UnsafeMutableRawPointer(&mpv.optsPtr.pointee.keepaspect_window) { + DispatchQueue.main.async { + self.window?.keepAspect = Bool(mpv.opts.keepaspect_window) + } + } + if opt! == UnsafeMutableRawPointer(&mpv.optsPtr.pointee.window_minimized) { + DispatchQueue.main.async { + self.window?.setMinimized(Bool(mpv.opts.window_minimized)) + } + } + if opt! == UnsafeMutableRawPointer(&mpv.optsPtr.pointee.window_maximized) { + DispatchQueue.main.async { + self.window?.setMaximized(Bool(mpv.opts.window_maximized)) + } + } + } + return VO_TRUE + case VOCTRL_GET_DISPLAY_FPS: + let fps = data.assumingMemoryBound(to: CDouble.self) + fps.pointee = currentFps() + return VO_TRUE + case VOCTRL_GET_HIDPI_SCALE: + let scaleFactor = data.assumingMemoryBound(to: CDouble.self) + let factor = window?.backingScaleFactor ?? + getTargetScreen(forFullscreen: false)?.backingScaleFactor ?? + NSScreen.main?.backingScaleFactor ?? 1.0 + scaleFactor.pointee = Double(factor) + return VO_TRUE + case VOCTRL_RESTORE_SCREENSAVER: + enableDisplaySleep() + return VO_TRUE + case VOCTRL_KILL_SCREENSAVER: + disableDisplaySleep() + return VO_TRUE + case VOCTRL_SET_CURSOR_VISIBILITY: + let cursorVisibility = data.assumingMemoryBound(to: CBool.self) + cursorVisibilityWanted = cursorVisibility.pointee + DispatchQueue.main.async { + self.setCursorVisiblility(self.cursorVisibilityWanted) + } + return VO_TRUE + case VOCTRL_GET_UNFS_WINDOW_SIZE: + let sizeData = data.assumingMemoryBound(to: Int32.self) + let size = UnsafeMutableBufferPointer(start: sizeData, count: 2) + var rect = window?.unfsContentFrame ?? NSRect(x: 0, y: 0, width: 1280, height: 720) + if let screen = window?.currentScreen, !Bool(mpv.opts.hidpi_window_scale) { + rect = screen.convertRectToBacking(rect) + } + + size[0] = Int32(rect.size.width) + size[1] = Int32(rect.size.height) + return VO_TRUE + case VOCTRL_SET_UNFS_WINDOW_SIZE: + let sizeData = data.assumingMemoryBound(to: Int32.self) + let size = UnsafeBufferPointer(start: sizeData, count: 2) + var rect = NSMakeRect(0, 0, CGFloat(size[0]), CGFloat(size[1])) + DispatchQueue.main.async { + if let screen = self.window?.currentScreen, !Bool(self.mpv?.opts.hidpi_window_scale ?? 1) { + rect = screen.convertRectFromBacking(rect) + } + self.window?.updateSize(rect.size) + } + return VO_TRUE + case VOCTRL_GET_DISPLAY_NAMES: + let dnames = data.assumingMemoryBound(to: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?.self) + var array: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>? = nil + var count: Int32 = 0 + let screen = window != nil ? window?.screen : + getTargetScreen(forFullscreen: false) ?? + NSScreen.main + let displayName = screen?.displayName ?? "Unknown" + + SWIFT_TARRAY_STRING_APPEND(nil, &array, &count, ta_xstrdup(nil, displayName)) + SWIFT_TARRAY_STRING_APPEND(nil, &array, &count, nil) + dnames.pointee = array + return VO_TRUE + case VOCTRL_UPDATE_WINDOW_TITLE: + let titleData = data.assumingMemoryBound(to: Int8.self) + DispatchQueue.main.async { + let title = NSString(utf8String: titleData) as String? + self.title = title ?? "Unknown Title" + } + return VO_TRUE + default: + return VO_NOTIMPL + } + } +} diff --git a/video/out/mac/gl_layer.swift b/video/out/mac/gl_layer.swift new file mode 100644 index 0000000000..6c48004f4f --- /dev/null +++ b/video/out/mac/gl_layer.swift @@ -0,0 +1,322 @@ +/* + * 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 + +let glVersions: [CGLOpenGLProfile] = [ + kCGLOGLPVersion_3_2_Core, + kCGLOGLPVersion_Legacy +] + +let glFormatBase: [CGLPixelFormatAttribute] = [ + kCGLPFAOpenGLProfile, + kCGLPFAAccelerated, + kCGLPFADoubleBuffer +] + +let glFormatSoftwareBase: [CGLPixelFormatAttribute] = [ + kCGLPFAOpenGLProfile, + kCGLPFARendererID, + CGLPixelFormatAttribute(UInt32(kCGLRendererGenericFloatID)), + kCGLPFADoubleBuffer +] + +let glFormatOptional: [[CGLPixelFormatAttribute]] = [ + [kCGLPFABackingStore], + [kCGLPFAAllowOfflineRenderers] +] + +let glFormat10Bit: [CGLPixelFormatAttribute] = [ + kCGLPFAColorSize, + _CGLPixelFormatAttribute(rawValue: 64), + kCGLPFAColorFloat +] + +let glFormatAutoGPU: [CGLPixelFormatAttribute] = [ + kCGLPFASupportsAutomaticGraphicsSwitching +] + +let attributeLookUp: [UInt32:String] = [ + kCGLOGLPVersion_3_2_Core.rawValue: "kCGLOGLPVersion_3_2_Core", + kCGLOGLPVersion_Legacy.rawValue: "kCGLOGLPVersion_Legacy", + kCGLPFAOpenGLProfile.rawValue: "kCGLPFAOpenGLProfile", + UInt32(kCGLRendererGenericFloatID): "kCGLRendererGenericFloatID", + kCGLPFARendererID.rawValue: "kCGLPFARendererID", + kCGLPFAAccelerated.rawValue: "kCGLPFAAccelerated", + kCGLPFADoubleBuffer.rawValue: "kCGLPFADoubleBuffer", + kCGLPFABackingStore.rawValue: "kCGLPFABackingStore", + kCGLPFAColorSize.rawValue: "kCGLPFAColorSize", + kCGLPFAColorFloat.rawValue: "kCGLPFAColorFloat", + kCGLPFAAllowOfflineRenderers.rawValue: "kCGLPFAAllowOfflineRenderers", + kCGLPFASupportsAutomaticGraphicsSwitching.rawValue: "kCGLPFASupportsAutomaticGraphicsSwitching", +] + +class GLLayer: CAOpenGLLayer { + unowned var cocoaCB: CocoaCB + var libmpv: LibmpvHelper { get { return cocoaCB.libmpv } } + + let displayLock = NSLock() + let cglContext: CGLContextObj + let cglPixelFormat: CGLPixelFormatObj + var needsFlip: Bool = false + var forceDraw: Bool = false + var surfaceSize: NSSize = NSSize(width: 0, height: 0) + var bufferDepth: GLint = 8 + + enum Draw: Int { case normal = 1, atomic, atomicEnd } + var draw: Draw = .normal + + let queue: DispatchQueue = DispatchQueue(label: "io.mpv.queue.draw") + + var needsICCUpdate: Bool = false { + didSet { + if needsICCUpdate == true { + update() + } + } + } + + var inLiveResize: Bool = false { + didSet { + if inLiveResize { + isAsynchronous = true + } + update(force: true) + } + } + + init(cocoaCB ccb: CocoaCB) { + cocoaCB = ccb + (cglPixelFormat, bufferDepth) = GLLayer.createPixelFormat(ccb) + cglContext = GLLayer.createContext(ccb, cglPixelFormat) + super.init() + autoresizingMask = [.layerWidthSizable, .layerHeightSizable] + backgroundColor = NSColor.black.cgColor + + if #available(macOS 10.12, *), bufferDepth > 8 { + contentsFormat = .RGBA16Float + } + + var i: GLint = 1 + CGLSetParameter(cglContext, kCGLCPSwapInterval, &i) + CGLSetCurrentContext(cglContext) + + libmpv.initRender() + libmpv.setRenderUpdateCallback(updateCallback, context: self) + libmpv.setRenderControlCallback(cocoaCB.controlCallback, context: cocoaCB) + } + + // necessary for when the layer containing window changes the screen + override init(layer: Any) { + guard let oldLayer = layer as? GLLayer else { + fatalError("init(layer: Any) passed an invalid layer") + } + cocoaCB = oldLayer.cocoaCB + surfaceSize = oldLayer.surfaceSize + cglPixelFormat = oldLayer.cglPixelFormat + cglContext = oldLayer.cglContext + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func canDraw(inCGLContext ctx: CGLContextObj, + pixelFormat pf: CGLPixelFormatObj, + forLayerTime t: CFTimeInterval, + displayTime ts: UnsafePointer<CVTimeStamp>?) -> Bool { + if inLiveResize == false { + isAsynchronous = false + } + return cocoaCB.backendState == .initialized && + (forceDraw || libmpv.isRenderUpdateFrame()) + } + + override func draw(inCGLContext ctx: CGLContextObj, + pixelFormat pf: CGLPixelFormatObj, + forLayerTime t: CFTimeInterval, + displayTime ts: UnsafePointer<CVTimeStamp>?) { + needsFlip = false + forceDraw = false + + if draw.rawValue >= Draw.atomic.rawValue { + if draw == .atomic { + draw = .atomicEnd + } else { + atomicDrawingEnd() + } + } + + updateSurfaceSize() + libmpv.drawRender(surfaceSize, bufferDepth, ctx) + + if needsICCUpdate { + needsICCUpdate = false + cocoaCB.updateICCProfile() + } + } + + func updateSurfaceSize() { + var dims: [GLint] = [0, 0, 0, 0] + glGetIntegerv(GLenum(GL_VIEWPORT), &dims) + surfaceSize = NSSize(width: CGFloat(dims[2]), height: CGFloat(dims[3])) + + if NSEqualSizes(surfaceSize, NSZeroSize) { + surfaceSize = bounds.size + surfaceSize.width *= contentsScale + surfaceSize.height *= contentsScale + } + } + + func atomicDrawingStart() { + if draw == .normal { + NSDisableScreenUpdates() + draw = .atomic + } + } + + func atomicDrawingEnd() { + if draw.rawValue >= Draw.atomic.rawValue { + NSEnableScreenUpdates() + draw = .normal + } + } + + override func copyCGLPixelFormat(forDisplayMask mask: UInt32) -> CGLPixelFormatObj { + return cglPixelFormat + } + + override func copyCGLContext(forPixelFormat pf: CGLPixelFormatObj) -> CGLContextObj { + contentsScale = cocoaCB.window?.backingScaleFactor ?? 1.0 + return cglContext + } + + let updateCallback: mpv_render_update_fn = { (ctx) in + let layer: GLLayer = unsafeBitCast(ctx, to: GLLayer.self) + layer.update() + } + + override func display() { + displayLock.lock() + let isUpdate = needsFlip + super.display() + CATransaction.flush() + if isUpdate && needsFlip { + CGLSetCurrentContext(cglContext) + if libmpv.isRenderUpdateFrame() { + libmpv.drawRender(NSZeroSize, bufferDepth, cglContext, skip: true) + } + } + displayLock.unlock() + } + + func update(force: Bool = false) { + if force { forceDraw = true } + queue.async { + if self.forceDraw || !self.inLiveResize { + self.needsFlip = true + self.display() + } + } + } + + class func createPixelFormat(_ ccb: CocoaCB) -> (CGLPixelFormatObj, GLint) { + var pix: CGLPixelFormatObj? + var depth: GLint = 8 + var err: CGLError = CGLError(rawValue: 0) + let swRender = ccb.libmpv.macOpts.cocoa_cb_sw_renderer + + if swRender != 1 { + (pix, depth, err) = GLLayer.findPixelFormat(ccb) + } + + if (err != kCGLNoError || pix == nil) && swRender != 0 { + (pix, depth, err) = GLLayer.findPixelFormat(ccb, software: true) + } + + guard let pixelFormat = pix, err == kCGLNoError else { + ccb.log.sendError("Couldn't create any CGL pixel format") + exit(1) + } + + return (pixelFormat, depth) + } + + class func findPixelFormat(_ ccb: CocoaCB, software: Bool = false) -> (CGLPixelFormatObj?, GLint, CGLError) { + var pix: CGLPixelFormatObj? + var err: CGLError = CGLError(rawValue: 0) + var npix: GLint = 0 + + for ver in glVersions { + var glBase = software ? glFormatSoftwareBase : glFormatBase + glBase.insert(CGLPixelFormatAttribute(ver.rawValue), at: 1) + + var glFormat = [glBase] + if (ccb.libmpv.macOpts.cocoa_cb_10bit_context == 1) { + glFormat += [glFormat10Bit] + } + glFormat += glFormatOptional + + if (ccb.libmpv.macOpts.macos_force_dedicated_gpu == 0) { + glFormat += [glFormatAutoGPU] + } + + for index in stride(from: glFormat.count-1, through: 0, by: -1) { + let format = glFormat.flatMap { $0 } + [_CGLPixelFormatAttribute(rawValue: 0)] + err = CGLChoosePixelFormat(format, &pix, &npix) + + if err == kCGLBadAttribute || err == kCGLBadPixelFormat || pix == nil { + glFormat.remove(at: index) + } else { + let attArray = format.map({ (value: _CGLPixelFormatAttribute) -> String in + return attributeLookUp[value.rawValue] ?? String(value.rawValue) + }) + + ccb.log.sendVerbose("Created CGL pixel format with attributes: " + + "\(attArray.joined(separator: ", "))") + return (pix, glFormat.contains(glFormat10Bit) ? 16 : 8, err) + } + } + } + + let errS = String(cString: CGLErrorString(err)) + ccb.log.sendWarning("Couldn't create a " + + "\(software ? "software" : "hardware accelerated") " + + "CGL pixel format: \(errS) (\(err.rawValue))") + if software == false && ccb.libmpv.macOpts.cocoa_cb_sw_renderer == -1 { + ccb.log.sendWarning("Falling back to software renderer") + } + + return (pix, 8, err) + } + + class func createContext(_ ccb: CocoaCB, _ pixelFormat: CGLPixelFormatObj) -> CGLContextObj { + var context: CGLContextObj? + let error = CGLCreateContext(pixelFormat, nil, &context) + + guard let cglContext = context, error == kCGLNoError else { + let errS = String(cString: CGLErrorString(error)) + ccb.log.sendError("Couldn't create a CGLContext: " + errS) + exit(1) + } + + return cglContext + } +} diff --git a/video/out/mac/title_bar.swift b/video/out/mac/title_bar.swift new file mode 100644 index 0000000000..623fe8f57d --- /dev/null +++ b/video/out/mac/title_bar.swift @@ -0,0 +1,255 @@ +/* + * 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 TitleBar: NSVisualEffectView { + unowned var common: Common + var mpv: MPVHelper? { get { return common.mpv } } + + var systemBar: NSView? { + get { return common.window?.standardWindowButton(.closeButton)?.superview } + } + static var height: CGFloat { + get { return NSWindow.frameRect(forContentRect: CGRect.zero, styleMask: .titled).size.height } + } + var buttons: [NSButton] { + get { return ([.closeButton, .miniaturizeButton, .zoomButton] as [NSWindow.ButtonType]).compactMap { common.window?.standardWindowButton($0) } } + } + + override var material: NSVisualEffectView.Material { + get { return super.material } + set { + super.material = newValue + // fix for broken deprecated materials + if material == .light || material == .dark { + state = .active + } else if #available(macOS 10.11, *), + material == .mediumLight || material == .ultraDark + { + state = .active + } else { + state = .followsWindowActiveState + } + + } + } + + init(frame: NSRect, window: NSWindow, common com: Common) { + let f = NSMakeRect(0, frame.size.height - TitleBar.height, + frame.size.width, TitleBar.height) + common = com + super.init(frame: f) + buttons.forEach { $0.isHidden = true } + isHidden = true + alphaValue = 0 + blendingMode = .withinWindow + autoresizingMask = [.width, .minYMargin] + systemBar?.alphaValue = 0 + state = .followsWindowActiveState + wantsLayer = true + + window.contentView?.addSubview(self, positioned: .above, relativeTo: nil) + window.titlebarAppearsTransparent = true + window.styleMask.insert(.fullSizeContentView) + set(appearance: Int(mpv?.macOpts.macos_title_bar_appearance ?? 0)) + set(material: Int(mpv?.macOpts.macos_title_bar_material ?? 0)) + set(color: mpv?.macOpts.macos_title_bar_color ?? "#00000000") + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } |