diff --git a/osdep/macOS_mpv_helper.swift b/osdep/macOS_mpv_helper.swift index fe747db08c..35435835de 100644 --- a/osdep/macOS_mpv_helper.swift +++ b/osdep/macOS_mpv_helper.swift @@ -44,15 +44,23 @@ class MPVHelper: NSObject { mpvHandle = mpv mpvLog = mp_log_new(UnsafeMutablePointer(mpvHandle), mp_client_get_log(mpvHandle), "cocoacb") - mpctx = UnsafeMutablePointer(mp_client_get_core(mpvHandle)) - inputContext = mpctx!.pointee.input - - if let app = NSApp as? Application { - let ptr = mp_get_config_group(mpctx!, mp_client_get_global(mpvHandle), - app.getMacOSConf()) - macOpts = UnsafeMutablePointer(OpaquePointer(ptr))!.pointee + guard let mpctx = UnsafeMutablePointer(mp_client_get_core(mpvHandle)) else { + sendError("No MPContext available") + exit(1) } + self.mpctx = mpctx + inputContext = mpctx.pointee.input + guard let app = NSApp as? Application, + let ptr = mp_get_config_group(mpctx, + mp_client_get_global(mpvHandle), + app.getMacOSConf()) else + { + sendError("macOS config group couldn't be retrieved'") + exit(1) + } + macOpts = UnsafeMutablePointer(OpaquePointer(ptr)).pointee + mpv_observe_property(mpvHandle, 0, "ontop", MPV_FORMAT_FLAG) mpv_observe_property(mpvHandle, 0, "border", MPV_FORMAT_FLAG) mpv_observe_property(mpvHandle, 0, "keepaspect-window", MPV_FORMAT_FLAG) @@ -83,7 +91,7 @@ class MPVHelper: NSObject { } let getProcAddress: (@convention(c) (UnsafeMutableRawPointer?, UnsafePointer?) - -> UnsafeMutableRawPointer?)! = + -> UnsafeMutableRawPointer?) = { (ctx: UnsafeMutableRawPointer?, name: UnsafePointer?) -> UnsafeMutableRawPointer? in @@ -214,27 +222,31 @@ class MPVHelper: NSObject { } func getStringProperty(_ name: String) -> String? { - if mpvHandle == nil { return nil } - let value = mpv_get_property_string(mpvHandle, name) - let str = value == nil ? nil : String(cString: value!) + guard let mpv = mpvHandle, + let value = mpv_get_property_string(mpv, name) else + { + return nil + } + + let str = String(cString: value) mpv_free(value) return str } func canBeDraggedAt(_ pos: NSPoint) -> Bool { - if inputContext == nil { return false } - let canDrag = !mp_input_test_dragging(inputContext!, Int32(pos.x), Int32(pos.y)) + guard let input = inputContext else { return false } + let canDrag = !mp_input_test_dragging(input, Int32(pos.x), Int32(pos.y)) return canDrag } func setMousePosition(_ pos: NSPoint) { - if inputContext == nil { return } - mp_input_set_mouse_pos(inputContext!, Int32(pos.x), Int32(pos.y)) + guard let input = inputContext else { return } + mp_input_set_mouse_pos(input, Int32(pos.x), Int32(pos.y)) } func putAxis(_ mpkey: Int32, delta: Double) { - if inputContext == nil { return } - mp_input_put_wheel(inputContext!, mpkey, delta) + guard let input = inputContext else { return } + mp_input_put_wheel(input, mpkey, delta) } func sendVerbose(_ msg: String) { @@ -257,7 +269,7 @@ class MPVHelper: NSObject { if mpvLog == nil { sendFallback(message: msg, type: t) } else { - let args: [CVarArg] = [ (msg as NSString).utf8String! ] + let args: [CVarArg] = [ (msg as NSString).utf8String ?? "NO MESSAGE"] mp_msg_va(mpvLog, Int32(t), "%s\n", getVaList(args)) } } diff --git a/osdep/macOS_swift_extensions.swift b/osdep/macOS_swift_extensions.swift index cc7438fd8c..7929d48f9a 100644 --- a/osdep/macOS_swift_extensions.swift +++ b/osdep/macOS_swift_extensions.swift @@ -21,7 +21,7 @@ extension NSScreen { public var displayID: CGDirectDisplayID { get { - return deviceDescription["NSScreenNumber"] as! CGDirectDisplayID + return deviceDescription["NSScreenNumber"] as? CGDirectDisplayID ?? 0 } } @@ -37,8 +37,8 @@ extension NSScreen { repeat { object = IOIteratorNext(iter) - let info = IODisplayCreateInfoDictionary(object, IOOptionBits(kIODisplayOnlyPreferredName)).takeRetainedValue() as! [String:AnyObject] - if (info[kDisplayVendorID] as? UInt32 == CGDisplayVendorNumber(displayID) && + if let info = IODisplayCreateInfoDictionary(object, IOOptionBits(kIODisplayOnlyPreferredName)).takeRetainedValue() as? [String:AnyObject], + (info[kDisplayVendorID] as? UInt32 == CGDisplayVendorNumber(displayID) && info[kDisplayProductID] as? UInt32 == CGDisplayModelNumber(displayID) && info[kDisplaySerialNumber] as? UInt32 ?? 0 == CGDisplaySerialNumber(displayID)) { @@ -60,11 +60,11 @@ extension NSScreen { extension NSColor { convenience init(hex: String) { - let int = Int(hex.dropFirst(), radix: 16) - let alpha = CGFloat((int! >> 24) & 0x000000FF)/255 - let red = CGFloat((int! >> 16) & 0x000000FF)/255 - let green = CGFloat((int! >> 8) & 0x000000FF)/255 - let blue = CGFloat((int!) & 0x000000FF)/255 + let int = Int(hex.dropFirst(), radix: 16) ?? 0 + let alpha = CGFloat((int >> 24) & 0x000000FF)/255 + let red = CGFloat((int >> 16) & 0x000000FF)/255 + let green = CGFloat((int >> 8) & 0x000000FF)/255 + let blue = CGFloat((int) & 0x000000FF)/255 self.init(calibratedRed: red, green: green, blue: blue, alpha: alpha) } diff --git a/video/out/cocoa-cb/events_view.swift b/video/out/cocoa-cb/events_view.swift index b9de12ca26..9c30e32ca0 100644 --- a/video/out/cocoa-cb/events_view.swift +++ b/video/out/cocoa-cb/events_view.swift @@ -20,9 +20,7 @@ import Cocoa class EventsView: NSView { weak var cocoaCB: CocoaCB! - var mpv: MPVHelper! { - get { return cocoaCB == nil ? nil : cocoaCB.mpv } - } + var mpv: MPVHelper { get { return cocoaCB.mpv } } var tracker: NSTrackingArea? var hasMouseDown: Bool = false @@ -46,13 +44,14 @@ class EventsView: NSView { } override func updateTrackingAreas() { - if tracker != nil { - removeTrackingArea(tracker!) + if let tracker = self.tracker { + removeTrackingArea(tracker) } tracker = NSTrackingArea(rect: bounds, options: [.activeAlways, .mouseEnteredAndExited, .mouseMoved, .enabledDuringMouseDrag], owner: self, userInfo: nil) + // here tracker is guaranteed to be none-nil addTrackingArea(tracker!) if containsMouseLocation() { @@ -72,6 +71,7 @@ class EventsView: NSView { } func isURL(_ str: String) -> Bool { + // force unwrapping is fine here, regex is guarnteed to be valid let regex = try! NSRegularExpression(pattern: "^(https?|ftp)://[^\\s/$.?#].[^\\s]*$", options: .caseInsensitive) let isURL = regex.numberOfMatches(in: str, @@ -135,14 +135,14 @@ class EventsView: NSView { if mpv.getBoolProperty("input-cursor") { cocoa_put_key_with_modifiers(SWIFT_KEY_MOUSE_LEAVE, 0) } - cocoaCB.titleBar.hide() + cocoaCB.titleBar?.hide() } override func mouseMoved(with event: NSEvent) { - if mpv != nil && mpv.getBoolProperty("input-cursor") { + if mpv.getBoolProperty("input-cursor") { signalMouseMovement(event) } - cocoaCB.titleBar.show() + cocoaCB.titleBar?.show() } override func mouseDragged(with event: NSEvent) { @@ -161,7 +161,7 @@ class EventsView: NSView { if mpv.getBoolProperty("input-cursor") { signalMouseUp(event) } - cocoaCB.window.isMoving = false + cocoaCB.window?.isMoving = false } override func rightMouseDown(with event: NSEvent) { @@ -210,8 +210,8 @@ class EventsView: NSView { point = convertToBacking(point) point.y = -point.y - cocoaCB.window.updateMovableBackground(point) - if !cocoaCB.window.isMoving { + cocoaCB.window?.updateMovableBackground(point) + if !(cocoaCB.window?.isMoving ?? false) { mpv.setMousePosition(point) } } @@ -257,21 +257,23 @@ class EventsView: NSView { func containsMouseLocation() -> Bool { if cocoaCB == nil { return false } var topMargin: CGFloat = 0.0 - let menuBarHeight = NSApp.mainMenu!.menuBarHeight + let menuBarHeight = NSApp.mainMenu?.menuBarHeight ?? 23.0 - if cocoaCB.window.isInFullscreen && (menuBarHeight > 0) { + guard let window = cocoaCB.window else { return false } + guard var vF = window.screen?.frame else { return false } + + if window.isInFullscreen && (menuBarHeight > 0) { topMargin = TitleBar.height + 1 + menuBarHeight } - guard var vF = window?.screen?.frame else { return false } vF.size.height -= topMargin - let vFW = window!.convertFromScreen(vF) + let vFW = window.convertFromScreen(vF) let vFV = convert(vFW, from: nil) - let pt = convert(window!.mouseLocationOutsideOfEventStream, from: nil) + let pt = convert(window.mouseLocationOutsideOfEventStream, from: nil) var clippedBounds = bounds.intersection(vFV) - if !cocoaCB.window.isInFullscreen { + if !window.isInFullscreen { clippedBounds.origin.y += TitleBar.height clippedBounds.size.height -= TitleBar.height } @@ -279,8 +281,8 @@ class EventsView: NSView { } func canHideCursor() -> Bool { - if cocoaCB.window == nil { return false } - return !hasMouseDown && containsMouseLocation() && window!.isKeyWindow + guard let window = cocoaCB.window else { return false } + return !hasMouseDown && containsMouseLocation() && window.isKeyWindow } func getMpvButton(_ event: NSEvent) -> Int32 { diff --git a/video/out/cocoa-cb/title_bar.swift b/video/out/cocoa-cb/title_bar.swift index 8a7bf30f02..596a13ba7b 100644 --- a/video/out/cocoa-cb/title_bar.swift +++ b/video/out/cocoa-cb/title_bar.swift @@ -19,19 +19,17 @@ import Cocoa class TitleBar: NSVisualEffectView { - weak var cocoaCB: CocoaCB! = nil - var mpv: MPVHelper! { - get { return cocoaCB == nil ? nil : cocoaCB.mpv } - } + weak var cocoaCB: CocoaCB! + var mpv: MPVHelper { get { return cocoaCB.mpv } } - var systemBar: NSView { - get { return (cocoaCB.window.standardWindowButton(.closeButton)?.superview)! } + var systemBar: NSView? { + get { return cocoaCB.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 [NSWindowButton]).flatMap { cocoaCB.window.standardWindowButton($0) } } + get { return ([.closeButton, .miniaturizeButton, .zoomButton] as [NSWindowButton]).flatMap { cocoaCB.window?.standardWindowButton($0) } } } override var material: NSVisualEffectView.Material { @@ -52,24 +50,28 @@ class TitleBar: NSVisualEffectView { } } - convenience init(frame: NSRect, window: NSWindow, cocoaCB ccb: CocoaCB) { + init(frame: NSRect, window: NSWindow, cocoaCB ccb: CocoaCB) { let f = NSMakeRect(0, frame.size.height - TitleBar.height, frame.size.width, TitleBar.height) - self.init(frame: f) cocoaCB = ccb + super.init(frame: f) alphaValue = 0 blendingMode = .withinWindow autoresizingMask = [.viewWidthSizable, .viewMinYMargin] - systemBar.alphaValue = 0 + systemBar?.alphaValue = 0 state = .followsWindowActiveState wantsLayer = true - window.contentView!.addSubview(self, positioned: .above, relativeTo: nil) + window.contentView?.addSubview(self, positioned: .above, relativeTo: nil) window.titlebarAppearsTransparent = true window.styleMask.insert(.fullSizeContentView) - set(appearance: Int(mpv.macOpts!.macos_title_bar_appearance)) - set(material: Int(mpv.macOpts!.macos_title_bar_material)) - set(color: mpv.macOpts!.macos_title_bar_color) + 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") } // catch these events so they are not propagated to the underlying view @@ -87,34 +89,34 @@ class TitleBar: NSVisualEffectView { } if action == "Minimize" { - cocoaCB.window.miniaturize(self) + window?.miniaturize(self) } else if action == "Maximize" { - cocoaCB.window.zoom(self) + window?.zoom(self) } } } func set(appearance: Any) { if appearance is Int { - window!.appearance = appearanceFrom(string: String(appearance as! Int)) + window?.appearance = appearanceFrom(string: String(appearance as? Int ?? 0)) } else { - window!.appearance = appearanceFrom(string: appearance as! String) + window?.appearance = appearanceFrom(string: appearance as? String ?? "auto") } } func set(material: Any) { if material is Int { - self.material = materialFrom(string: String(material as! Int)) + self.material = materialFrom(string: String(material as? Int ?? 0)) } else { - self.material = materialFrom(string: material as! String) + self.material = materialFrom(string: material as? String ?? "titlebar") } } func set(color: Any) { if color is String { - layer?.backgroundColor = NSColor(hex: color as! String).cgColor + layer?.backgroundColor = NSColor(hex: color as? String ?? "#00000000").cgColor } else { - let col = color as! m_color + let col = color as? m_color ?? m_color(r: 0, g: 0, b: 0, a: 0) let red = CGFloat(col.r)/255 let green = CGFloat(col.g)/255 let blue = CGFloat(col.b)/255 @@ -125,20 +127,21 @@ class TitleBar: NSVisualEffectView { } func show() { - if (!cocoaCB.window.border && !cocoaCB.window.isInFullscreen) { return } - let loc = cocoaCB.view.convert(cocoaCB.window.mouseLocationOutsideOfEventStream, from: nil) + guard let window = cocoaCB.window else { return } + if !window.border && !window.isInFullscreen { return } + let loc = cocoaCB.view?.convert(window.mouseLocationOutsideOfEventStream, from: nil) buttons.forEach { $0.isHidden = false } NSAnimationContext.runAnimationGroup({ (context) -> Void in context.duration = 0.20 - systemBar.animator().alphaValue = 1 - if !cocoaCB.window.isInFullscreen && !cocoaCB.window.isAnimating { + systemBar?.animator().alphaValue = 1 + if !window.isInFullscreen && !window.isAnimating { animator().alphaValue = 1 isHidden = false } }, completionHandler: nil ) - if loc.y > TitleBar.height { + if loc?.y ?? 0 > TitleBar.height { hideDelayed() } else { NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(hide), object: nil) @@ -146,14 +149,15 @@ class TitleBar: NSVisualEffectView { } func hide() { - if cocoaCB.window.isInFullscreen && !cocoaCB.window.isAnimating { + guard let window = cocoaCB.window else { return } + if window.isInFullscreen && !window.isAnimating { alphaValue = 0 isHidden = true return } NSAnimationContext.runAnimationGroup({ (context) -> Void in context.duration = 0.20 - systemBar.animator().alphaValue = 0 + systemBar?.animator().alphaValue = 0 animator().alphaValue = 0 }, completionHandler: { self.buttons.forEach { $0.isHidden = true } diff --git a/video/out/cocoa-cb/video_layer.swift b/video/out/cocoa-cb/video_layer.swift index 5de57b9e00..895d9faf29 100644 --- a/video/out/cocoa-cb/video_layer.swift +++ b/video/out/cocoa-cb/video_layer.swift @@ -19,63 +19,61 @@ 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, + 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", + kCGLPFAAllowOfflineRenderers.rawValue: "kCGLPFAAllowOfflineRenderers", + kCGLPFASupportsAutomaticGraphicsSwitching.rawValue: "kCGLPFASupportsAutomaticGraphicsSwitching", +] + class VideoLayer: CAOpenGLLayer { weak var cocoaCB: CocoaCB! - var mpv: MPVHelper! { - get { return cocoaCB == nil ? nil : cocoaCB.mpv } - } + var mpv: MPVHelper { get { return cocoaCB.mpv } } let videoLock = NSLock() let displayLock = NSLock() + let cglContext: CGLContextObj + let cglPixelFormat: CGLPixelFormatObj var needsFlip: Bool = false var forceDraw: Bool = false - var cglContext: CGLContextObj? = nil - var cglPixelFormat: CGLPixelFormatObj? = nil - var surfaceSize: NSSize? + var surfaceSize: NSSize = NSSize(width: 0, height: 0) enum Draw: Int { case normal = 1, atomic, atomicEnd } var draw: Draw = .normal let queue: DispatchQueue = DispatchQueue(label: "io.mpv.queue.draw") - 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, - 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", - kCGLPFAAllowOfflineRenderers.rawValue: "kCGLPFAAllowOfflineRenderers", - kCGLPFASupportsAutomaticGraphicsSwitching.rawValue: "kCGLPFASupportsAutomaticGraphicsSwitching", - ] - var needsICCUpdate: Bool = false { didSet { if needsICCUpdate == true { @@ -95,24 +93,30 @@ class VideoLayer: CAOpenGLLayer { init(cocoaCB ccb: CocoaCB) { cocoaCB = ccb + cglPixelFormat = VideoLayer.createPixelFormat(ccb.mpv) + cglContext = VideoLayer.createContext(ccb.mpv, cglPixelFormat) super.init() autoresizingMask = [.layerWidthSizable, .layerHeightSizable] backgroundColor = NSColor.black.cgColor - cglPixelFormat = copyCGLPixelFormat(forDisplayMask: 0) - CGLCreateContext(cglPixelFormat!, nil, &cglContext) var i: GLint = 1 - CGLSetParameter(cglContext!, kCGLCPSwapInterval, &i) - CGLSetCurrentContext(cglContext!) + CGLSetParameter(cglContext, kCGLCPSwapInterval, &i) + CGLSetCurrentContext(cglContext) mpv.initRender() mpv.setRenderUpdateCallback(updateCallback, context: self) mpv.setRenderControlCallback(cocoaCB.controlCallback, context: cocoaCB) } + //necessary for when the layer containing window changes the screen override init(layer: Any) { - let oldLayer = layer as! VideoLayer + guard let oldLayer = layer as? VideoLayer else { + fatalError("init(layer: Any) passed an invalid layer") + } cocoaCB = oldLayer.cocoaCB + surfaceSize = oldLayer.surfaceSize + cglPixelFormat = oldLayer.cglPixelFormat + cglContext = oldLayer.cglContext super.init() } @@ -127,7 +131,7 @@ class VideoLayer: CAOpenGLLayer { if inLiveResize == false { isAsynchronous = false } - return mpv != nil && cocoaCB.backendState == .initialized && + return cocoaCB.backendState == .initialized && (forceDraw || mpv.isRenderUpdateFrame()) } @@ -147,7 +151,7 @@ class VideoLayer: CAOpenGLLayer { } updateSurfaceSize() - mpv.drawRender(surfaceSize!, ctx) + mpv.drawRender(surfaceSize, ctx) if needsICCUpdate { needsICCUpdate = false @@ -158,12 +162,12 @@ class VideoLayer: CAOpenGLLayer { func updateSurfaceSize() { var dims: [GLint] = [0, 0, 0, 0] glGetIntegerv(GLenum(GL_VIEWPORT), &dims) - surfaceSize = NSMakeSize(CGFloat(dims[2]), CGFloat(dims[3])) + surfaceSize = NSSize(width: CGFloat(dims[2]), height: CGFloat(dims[3])) - if NSEqualSizes(surfaceSize!, NSZeroSize) { + if NSEqualSizes(surfaceSize, NSZeroSize) { surfaceSize = bounds.size - surfaceSize!.width *= contentsScale - surfaceSize!.height *= contentsScale + surfaceSize.width *= contentsScale + surfaceSize.height *= contentsScale } } @@ -182,28 +186,65 @@ class VideoLayer: CAOpenGLLayer { } override func copyCGLPixelFormat(forDisplayMask mask: UInt32) -> CGLPixelFormatObj { - if cglPixelFormat != nil { return cglPixelFormat! } + 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: VideoLayer = unsafeBitCast(ctx, to: VideoLayer.self) + layer.update() + } + + override func display() { + displayLock.lock() + let isUpdate = needsFlip + super.display() + CATransaction.flush() + if isUpdate && needsFlip { + CGLSetCurrentContext(cglContext) + if mpv.isRenderUpdateFrame() { + mpv.drawRender(NSZeroSize, 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(_ mpv: MPVHelper) -> CGLPixelFormatObj { var pix: CGLPixelFormatObj? var err: CGLError = CGLError(rawValue: 0) + let swRender = mpv.macOpts?.cocoa_cb_sw_renderer ?? -1 - if mpv.macOpts!.cocoa_cb_sw_renderer != 1 { - (pix, err) = createPixelFormat() + if swRender != 1 { + (pix, err) = VideoLayer.findPixelFormat(mpv) } - if (err != kCGLNoError || pix == nil) && mpv.macOpts!.cocoa_cb_sw_renderer != 0 { - (pix, err) = createPixelFormat(software: true) + if (err != kCGLNoError || pix == nil) && swRender != 0 { + (pix, err) = VideoLayer.findPixelFormat(mpv, software: true) } - if err != kCGLNoError || pix == nil { + guard let pixelFormat = pix, err == kCGLNoError else { mpv.sendError("Couldn't create any CGL pixel format") exit(1) } - return pix! + return pixelFormat } - func createPixelFormat(software: Bool = false) -> (CGLPixelFormatObj?, CGLError) { + class func findPixelFormat(_ mpv: MPVHelper, software: Bool = false) -> (CGLPixelFormatObj?, CGLError) { var pix: CGLPixelFormatObj? var err: CGLError = CGLError(rawValue: 0) var npix: GLint = 0 @@ -221,7 +262,7 @@ class VideoLayer: CAOpenGLLayer { glFormat.remove(at: index) } else { let attArray = glFormat.map({ (value: _CGLPixelFormatAttribute) -> String in - return attributeLookUp[value.rawValue]! + return attributeLookUp[value.rawValue] ?? "unknown attribute" }) mpv.sendVerbose("Created CGL pixel format with attributes: " + @@ -236,45 +277,23 @@ class VideoLayer: CAOpenGLLayer { "\(software ? "software" : "hardware accelerated") " + "CGL pixel format: \(errS) (\(err.rawValue))") - if software == false && mpv.macOpts!.cocoa_cb_sw_renderer == -1 { + if software == false && (mpv.macOpts?.cocoa_cb_sw_renderer ?? -1) == -1 { mpv.sendWarning("Falling back to software renderer") } return (pix, err) } - override func copyCGLContext(forPixelFormat pf: CGLPixelFormatObj) -> CGLContextObj { - contentsScale = cocoaCB.window.backingScaleFactor - return cglContext! - } + class func createContext(_ mpv: MPVHelper, _ pixelFormat: CGLPixelFormatObj) -> CGLContextObj { + var context: CGLContextObj? + let error = CGLCreateContext(pixelFormat, nil, &context) - let updateCallback: mpv_render_update_fn = { (ctx) in - let layer: VideoLayer = MPVHelper.bridge(ptr: ctx!) - layer.update() - } - - override func display() { - displayLock.lock() - let isUpdate = needsFlip - super.display() - CATransaction.flush() - if isUpdate && needsFlip { - CGLSetCurrentContext(cglContext!) - if mpv.isRenderUpdateFrame() { - mpv.drawRender(NSZeroSize, cglContext!, skip: true) - } + guard let cglContext = context, error == kCGLNoError else { + let errS = String(cString: CGLErrorString(error)) + mpv.sendError("Couldn't create a CGLContext: " + errS) + exit(1) } - displayLock.unlock() - } - func update(force: Bool = false) { - if force { forceDraw = true } - queue.async { - if self.forceDraw || !self.inLiveResize { - self.needsFlip = true - self.display() - } - } + return cglContext } - } diff --git a/video/out/cocoa-cb/window.swift b/video/out/cocoa-cb/window.swift index b3ccd9d94b..35d9b1bd91 100644 --- a/video/out/cocoa-cb/window.swift +++ b/video/out/cocoa-cb/window.swift @@ -20,9 +20,7 @@ import Cocoa class Window: NSWindow, NSWindowDelegate { weak var cocoaCB: CocoaCB! = nil - var mpv: MPVHelper! { - get { return cocoaCB == nil ? nil : cocoaCB.mpv } - } + var mpv: MPVHelper { get { return cocoaCB.mpv } } var targetScreen: NSScreen? var previousScreen: NSScreen? @@ -37,12 +35,12 @@ class Window: NSWindow, NSWindowDelegate { var keepAspect: Bool = true { didSet { - if !isInFullscreen { - unfsContentFrame = convertToScreen(contentView!.frame) + if let contentViewFrame = contentView?.frame, !isInFullscreen { + unfsContentFrame = convertToScreen(contentViewFrame) } if keepAspect { - contentAspectRatio = unfsContentFrame!.size + contentAspectRatio = unfsContentFrame?.size ?? contentAspectRatio } else { resizeIncrements = NSSize(width: 1.0, height: 1.0) } @@ -50,7 +48,7 @@ class Window: NSWindow, NSWindowDelegate { } var border: Bool = true { - didSet { if !border { cocoaCB.titleBar.hide() } } + didSet { if !border { cocoaCB.titleBar?.hide() } } } override var canBecomeKey: Bool { return true } @@ -74,10 +72,10 @@ class Window: NSWindow, NSWindowDelegate { // workaround for an AppKit bug where the NSWindow can't be placed on a // none Main screen NSScreen outside the Main screen's frame bounds - if screen != NSScreen.main() { + if let wantedScreen = screen, screen != NSScreen.main() { var absoluteWantedOrigin = contentRect.origin - absoluteWantedOrigin.x += screen!.frame.origin.x - absoluteWantedOrigin.y += screen!.frame.origin.y + absoluteWantedOrigin.x += wantedScreen.frame.origin.x + absoluteWantedOrigin.y += wantedScreen.frame.origin.y if !NSEqualPoints(absoluteWantedOrigin, self.frame.origin) { self.setFrameOrigin(absoluteWantedOrigin) @@ -89,13 +87,16 @@ class Window: NSWindow, NSWindowDelegate { minSize = NSMakeSize(160, 90) collectionBehavior = .fullScreenPrimary delegate = self - contentView!.addSubview(view) - view.frame = contentView!.frame - unfsContentFrame = convertToScreen(contentView!.frame) - targetScreen = screen! - currentScreen = screen! - unfScreen = screen! + if let cView = contentView { + cView.addSubview(view) + view.frame = cView.frame + unfsContentFrame = convertToScreen(cView.frame) + } + + targetScreen = screen + currentScreen = screen + unfScreen = screen if let app = NSApp as? Application { app.menuBar.register(#selector(setHalfWindowSize), for: MPM_H_SIZE) @@ -123,13 +124,13 @@ class Window: NSWindow, NSWindowDelegate { previousScreen = screen } - if !isInFullscreen { - unfsContentFrame = convertToScreen(contentView!.frame) + if let contentViewFrame = contentView?.frame, !isInFullscreen { + unfsContentFrame = convertToScreen(contentViewFrame) unfScreen = screen } // move window to target screen when going to fullscreen - if !isInFullscreen && (targetScreen != screen) { - let frame = calculateWindowPosition(for: targetScreen!, withoutBounds: false) + if let tScreen = targetScreen, !isInFullscreen && (tScreen != screen) { + let frame = calculateWindowPosition(for: tScreen, withoutBounds: false) setFrame(frame, display: true) } @@ -154,19 +155,21 @@ class Window: NSWindow, NSWindowDelegate { } func window(_ window: NSWindow, startCustomAnimationToEnterFullScreenWithDuration duration: TimeInterval) { - cocoaCB.view.layerContentsPlacement = .scaleProportionallyToFit - cocoaCB.titleBar.hide() + guard let tScreen = targetScreen else { return } + cocoaCB.view?.layerContentsPlacement = .scaleProportionallyToFit + cocoaCB.titleBar?.hide() NSAnimationContext.runAnimationGroup({ (context) -> Void in context.duration = getFsAnimationDuration(duration - 0.05) - window.animator().setFrame(targetScreen!.frame, display: true) + window.animator().setFrame(tScreen.frame, 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) - cocoaCB.view.layerContentsPlacement = .scaleProportionallyToFill - cocoaCB.titleBar.hide() + guard let tScreen = targetScreen, let currentScreen = screen else { return } + let newFrame = calculateWindowPosition(for: tScreen, withoutBounds: tScreen == screen) + let intermediateFrame = aspectFit(rect: newFrame, in: currentScreen.frame) + cocoaCB.view?.layerContentsPlacement = .scaleProportionallyToFill + cocoaCB.titleBar?.hide() styleMask.remove(.fullScreen) setFrame(intermediateFrame, display: true) @@ -181,27 +184,29 @@ class Window: NSWindow, NSWindowDelegate { cocoaCB.flagEvents(VO_EVENT_FULLSCREEN_STATE) cocoaCB.updateCusorVisibility() endAnimation(frame) - cocoaCB.titleBar.show() + cocoaCB.titleBar?.show() } func windowDidExitFullScreen(_ notification: Notification) { + guard let tScreen = targetScreen else { return } isInFullscreen = false cocoaCB.flagEvents(VO_EVENT_FULLSCREEN_STATE) - endAnimation(calculateWindowPosition(for: targetScreen!, withoutBounds: targetScreen == screen)) - cocoaCB.view.layerContentsPlacement = .scaleProportionallyToFit + endAnimation(calculateWindowPosition(for: tScreen, withoutBounds: targetScreen == screen)) + cocoaCB.view?.layerContentsPlacement = .scaleProportionallyToFit } func windowDidFailToEnterFullScreen(_ window: NSWindow) { - let newFrame = calculateWindowPosition(for: targetScreen!, withoutBounds: targetScreen == screen) + guard let tScreen = targetScreen else { return } + let newFrame = calculateWindowPosition(for: tScreen, withoutBounds: targetScreen == screen) setFrame(newFrame, display: true) endAnimation() } func windowDidFailToExitFullScreen(_ window: NSWindow) { - let newFrame = targetScreen!.frame - setFrame(newFrame, display: true) + guard let targetFrame = targetScreen?.frame else { return } + setFrame(targetFrame, display: true) endAnimation() - cocoaCB.view.layerContentsPlacement = .scaleProportionallyToFit + cocoaCB.view?.layerContentsPlacement = .scaleProportionallyToFit } func endAnimation(_ newFrame: NSRect = NSZeroRect) { @@ -213,29 +218,31 @@ class Window: NSWindow, NSWindowDelegate { } isAnimating = false - cocoaCB.layer.update() + cocoaCB.layer?.update() cocoaCB.checkShutdown() } func setToFullScreen() { + guard let targetFrame = targetScreen?.frame else { return } styleMask.insert(.fullScreen) NSApp.presentationOptions = [.autoHideMenuBar, .autoHideDock] - setFrame(targetScreen!.frame, display: true) + setFrame(targetFrame, display: true) endAnimation() isInFullscreen = true cocoaCB.flagEvents(VO_EVENT_FULLSCREEN_STATE) - cocoaCB.layer.update() + cocoaCB.layer?.update() } func setToWindow() { - let newFrame = calculateWindowPosition(for: targetScreen!, withoutBounds: targetScreen == screen) + guard let tScreen = targetScreen else { return } + let newFrame = calculateWindowPosition(for: tScreen, withoutBounds: targetScreen == screen) NSApp.presentationOptions = [] setFrame(newFrame, display: true) styleMask.remove(.fullScreen) endAnimation() isInFullscreen = false cocoaCB.flagEvents(VO_EVENT_FULLSCREEN_STATE) - cocoaCB.layer.update() + cocoaCB.layer?.update() } func getFsAnimationDuration(_ def: Double) -> Double{ @@ -243,35 +250,37 @@ class Window: NSWindow, NSWindowDelegate { if duration == "default" { return def } else { - return Double(duration)!/1000 + return (Double(duration) ?? 0.2)/1000 } } func setOnTop(_ state: Bool, _ ontopLevel: Any) { + let stdLevel = Int(CGWindowLevelForKey(.normalWindow)) + if state { if ontopLevel is Int { - switch ontopLevel as! Int { + switch ontopLevel as? Int { case -1: level = Int(CGWindowLevelForKey(.floatingWindow)) case -2: level = Int(CGWindowLevelForKey(.statusWindow))+1 default: - level = ontopLevel as! Int + level = ontopLevel as? Int ?? stdLevel } } else { - switch ontopLevel as! String { + switch ontopLevel as? String { case "window": level = Int(CGWindowLevelForKey(.floatingWindow)) case "system": level = Int(CGWindowLevelForKey(.statusWindow))+1 default: - level = Int(ontopLevel as! String)! + level = Int(ontopLevel as? String ?? "") ?? stdLevel } } collectionBehavior.remove(.transient) collectionBehavior.insert(.managed) } else { - level = Int(CGWindowLevelForKey(.normalWindow)) + level = stdLevel } } @@ -292,7 +301,7 @@ class Window: NSWindow, NSWindowDelegate { } func updateSize(_ size: NSSize) { - if size != contentView!.frame.size { + if let currentSize = contentView?.frame.size, size != currentSize { let newContentFrame = centeredContentSize(for: frame, size: size) if !isInFullscreen { updateFrame(newContentFrame) @@ -305,8 +314,8 @@ class Window: NSWindow, NSWindowDelegate { override func setFrame(_ frameRect: NSRect, display flag: Bool) { super.setFrame(frameRect, display: flag) - if keepAspect { - contentAspectRatio = unfsContentFrame!.size + if let size = unfsContentFrame?.size, keepAspect { + contentAspectRatio = size } } @@ -328,10 +337,13 @@ class Window: NSWindow, NSWindowDelegate { } func calculateWindowPosition(for tScreen: NSScreen, withoutBounds: Bool) -> NSRect { - var newFrame = frameRect(forContentRect: unfsContentFrame!) + guard let contentFrame = unfsContentFrame, let screen = unfScreen else { + return frame + } + var newFrame = frameRect(forContentRect: contentFrame) let targetFrame = tScreen.frame let targetVisibleFrame = tScreen.visibleFrame - let unfsScreenFrame = unfScreen!.frame + let unfsScreenFrame = screen.frame let visibleWindow = NSIntersectionRect(unfsScreenFrame, newFrame) // calculate visible area of every side @@ -398,10 +410,12 @@ class Window: NSWindow, NSWindowDelegate { return frameRect } + guard let ts: NSScreen = tScreen ?? screen ?? NSScreen.main() else { + 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 vf: NSRect = (isAnimating ? (targetScreen ?? ts) : ts).visibleFrame let ncf: NSRect = contentRect(forFrameRect: nf) // screen bounds top and bottom @@ -451,19 +465,19 @@ class Window: NSWindow, NSWindowDelegate { } func windowDidChangeScreenProfile(_ notification: Notification) { - cocoaCB.layer.needsICCUpdate = true + cocoaCB.layer?.needsICCUpdate = true } func windowDidChangeBackingProperties(_ notification: Notification) { - cocoaCB.layer.contentsScale = backingScaleFactor + cocoaCB.layer?.contentsScale = backingScaleFactor } func windowWillStartLiveResize(_ notification: Notification) { - cocoaCB.layer.inLiveResize = true + cocoaCB.layer?.inLiveResize = true } func windowDidEndLiveResize(_ notification: Notification) { - cocoaCB.layer.inLiveResize = false + cocoaCB.layer?.inLiveResize = false } func windowShouldClose(_ sender: Any) -> Bool { @@ -489,7 +503,7 @@ class Window: NSWindow, NSWindowDelegate { func windowDidChangeOcclusionState(_ notification: Notification) { if occlusionState.contains(.visible) { - cocoaCB.layer.update(force: true) + cocoaCB.layer?.update(force: true) } } diff --git a/video/out/cocoa_cb_common.swift b/video/out/cocoa_cb_common.swift index f545e72a3d..14c752a8f1 100644 --- a/video/out/cocoa_cb_common.swift +++ b/video/out/cocoa_cb_common.swift @@ -20,11 +20,11 @@ import IOKit.pwr_mgt class CocoaCB: NSObject { - var mpv: MPVHelper! - var window: Window! - var titleBar: TitleBar! - var view: EventsView! - var layer: VideoLayer! + var mpv: MPVHelper + var window: Window? + var titleBar: TitleBar? + var view: EventsView? + var layer: VideoLayer? var link: CVDisplayLink? var cursorHidden: Bool = false @@ -32,7 +32,7 @@ class CocoaCB: NSObject { var isShuttingDown: Bool = false var title: String = "mpv" { - didSet { if window != nil { window.title = title } } + didSet { if let window = window { window.title = title } } } enum State { @@ -52,9 +52,9 @@ class CocoaCB: NSObject { let queue: DispatchQueue = DispatchQueue(label: "io.mpv.queue") - convenience init(_ mpvHandle: OpaquePointer) { - self.init() + init(_ mpvHandle: OpaquePointer) { mpv = MPVHelper(mpvHandle) + super.init() layer = VideoLayer(cocoaCB: self) } @@ -63,9 +63,9 @@ class CocoaCB: NSObject { backendState = .needsInit view = EventsView(cocoaCB: self) - view.layer = layer - view.wantsLayer = true - view.layerContentsPlacement = .scaleProportionallyToFit + view?.layer = layer + view?.wantsLayer = true + view?.layerContentsPlacement = .scaleProportionallyToFit startDisplayLink(vo) initLightSensor() addDisplayReconfigureObserver() @@ -73,29 +73,42 @@ class CocoaCB: NSObject { } func uninit() { - window.orderOut(nil) + window?.orderOut(nil) } func reconfig(_ vo: UnsafeMutablePointer) { + mpv.vo = vo if backendState == .needsInit { DispatchQueue.main.sync { self.initBackend(vo) } } else { DispatchQueue.main.async { self.updateWindowSize(vo) - self.layer.update() + self.layer?.update() } } } func initBackend(_ vo: UnsafeMutablePointer) { let opts: mp_vo_opts = vo.pointee.opts.pointee - mpv.vo = vo NSApp.setActivationPolicy(.regular) setAppIcon() - let targetScreen = getScreenBy(id: Int(opts.screen_id)) ?? NSScreen.main() - let wr = getWindowGeometry(forScreen: targetScreen!, videoOut: vo) + guard let view = self.view else { + mpv.sendError("Something went wrong, no View was initialized") + exit(1) + } + guard let targetScreen = getScreenBy(id: Int(opts.screen_id)) ?? NSScreen.main() else { + mpv.sendError("Something went wrong, no Screen was found") + exit(1) + } + + let wr = getWindowGeometry(forScreen: targetScreen, videoOut: vo) window = Window(contentRect: wr, screen: targetScreen, view: view, cocoaCB: self) + guard let window = self.window else { + mpv.sendError("Something went wrong, no Window was initialized") + exit(1) + } + updateICCProfile() window.setOnTop(Bool(opts.ontop), Int(opts.ontop_level)) window.keepAspect = Bool(opts.keepaspect_window) @@ -111,7 +124,7 @@ class CocoaCB: NSObject { if Bool(opts.fullscreen) { DispatchQueue.main.async { - self.window.toggleFullScreen(nil) + self.window?.toggleFullScreen(nil) } } else { window.isMovableByWindowBackground = true @@ -122,13 +135,17 @@ class CocoaCB: NSObject { func updateWindowSize(_ vo: UnsafeMutablePointer) { let opts: mp_vo_opts = vo.pointee.opts.pointee - let targetScreen = getScreenBy(id: Int(opts.screen_id)) ?? NSScreen.main() - let wr = getWindowGeometry(forScreen: targetScreen!, videoOut: vo) - if !window.isVisible { - window.makeKeyAndOrderFront(nil) + guard let targetScreen = getScreenBy(id: Int(opts.screen_id)) ?? NSScreen.main() else { + mpv.sendWarning("Couldn't update Window size, no Screen available") + return } - layer.atomicDrawingStart() - window.updateSize(wr.size) + + let wr = getWindowGeometry(forScreen: targetScreen, videoOut: vo) + if !(window?.isVisible ?? false) { + window?.makeKeyAndOrderFront(nil) + } + layer?.atomicDrawingStart() + window?.updateSize(wr.size) } func setAppIcon() { @@ -144,60 +161,75 @@ class CocoaCB: NSObject { flagsIn: CVOptionFlags, flagsOut: UnsafeMutablePointer, displayLinkContext: UnsafeMutableRawPointer?) -> CVReturn in - let ccb: CocoaCB = MPVHelper.bridge(ptr: displayLinkContext!) + let ccb = unsafeBitCast(displayLinkContext, to: CocoaCB.self) ccb.mpv.reportRenderFlip() return kCVReturnSuccess } func startDisplayLink(_ vo: UnsafeMutablePointer) { let opts: mp_vo_opts = vo.pointee.opts.pointee - let screen = getScreenBy(id: Int(opts.screen_id)) ?? NSScreen.main() - CVDisplayLinkCreateWithActiveCGDisplays(&link) - CVDisplayLinkSetCurrentCGDisplay(link!, screen!.displayID) + + guard let screen = getScreenBy(id: Int(opts.screen_id)) ?? NSScreen.main(), + let link = self.link else + { + mpv.sendWarning("Couldn't start DisplayLink, no Screen or DisplayLink available") + return + } + + CVDisplayLinkSetCurrentCGDisplay(link, screen.displayID) if #available(macOS 10.12, *) { - CVDisplayLinkSetOutputHandler(link!) { link, now, out, inFlags, outFlags -> CVReturn in + CVDisplayLinkSetOutputHandler(link) { link, now, out, inFlags, outFlags -> CVReturn in self.mpv.reportRenderFlip() return kCVReturnSuccess } } else { - CVDisplayLinkSetOutputCallback(link!, linkCallback, MPVHelper.bridge(obj: self)) + CVDisplayLinkSetOutputCallback(link, linkCallback, MPVHelper.bridge(obj: self)) } - CVDisplayLinkStart(link!) + CVDisplayLinkStart(link) } func stopDisplaylink() { - if link != nil && CVDisplayLinkIsRunning(link!) { - CVDisplayLinkStop(link!) + if let link = self.link, CVDisplayLinkIsRunning(link) { + CVDisplayLinkStop(link) } } func updateDisplaylink() { - CVDisplayLinkSetCurrentCGDisplay(link!, window.screen!.displayID) + guard let screen = window?.screen, let link = self.link else { + mpv.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 { - var actualFps = CVDisplayLinkGetActualOutputVideoRefreshPeriod(link!) - let nominalData = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link!) + 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 (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 { - mpv.sendVerbose("Falling back to nominal display refresh rate: \(nominalFps)") - return nominalFps - } else { - return actualFps + if actualFps > 0 { + actualFps = 1/actualFps + } + + if fabs(actualFps - nominalFps) > 0.1 { + mpv.sendVerbose("Falling back to nominal display refresh rate: \(nominalFps)") + return nominalFps + } else { + return actualFps + } } + } else { + mpv.sendWarning("No DisplayLink available") } + mpv.sendWarning("Falling back to standard display refresh rate: 60Hz") return 60.0 } @@ -221,8 +253,7 @@ class CocoaCB: NSObject { } func setCursorVisiblility(_ visible: Bool) { - let visibility = visible ? true : !view.canHideCursor() - + let visibility = visible ? true : !(view?.canHideCursor() ?? false) if visibility && cursorHidden { NSCursor.unhide() cursorHidden = false; @@ -233,9 +264,14 @@ class CocoaCB: NSObject { } func updateICCProfile() { - mpv.setRenderICCProfile(window.screen!.colorSpace!) + guard let colorSpace = window?.screen?.colorSpace else { + mpv.sendWarning("Couldn't update ICC Profile, no color space available") + return + } + + mpv.setRenderICCProfile(colorSpace) if #available(macOS 10.11, *) { - layer.colorspace = window.screen!.colorSpace!.cgColorSpace! + layer?.colorspace = colorSpace.cgColorSpace } } @@ -258,7 +294,7 @@ class CocoaCB: NSObject { } var lightSensorCallback: IOServiceInterestCallback = { (ctx, service, messageType, messageArgument) -> Void in - let ccb: CocoaCB = MPVHelper.bridge(ptr: ctx!) + let ccb = unsafeBitCast(ctx, to: CocoaCB.self) var outputs: UInt32 = 2 var values: [UInt64] = [0, 0] @@ -303,8 +339,10 @@ class CocoaCB: NSObject { var reconfigureCallback: CGDisplayReconfigurationCallBack = { (display, flags, userInfo) in if flags.contains(.setModeFlag) { - let ccb: CocoaCB = MPVHelper.bridge(ptr: userInfo!) - if ccb.window.screen!.displayID == display { + let ccb = unsafeBitCast(userInfo, to: CocoaCB.self) + let displayID = ccb.window?.screen?.displayID ?? display + + if displayID == display { ccb.mpv.sendVerbose("Detected display mode change, updating screen refresh rate"); ccb.flagEvents(VO_EVENT_WIN_STATE) } @@ -327,19 +365,19 @@ class CocoaCB: NSObject { case "current", "default", "all": return getScreenBy(id: -1) default: - return getScreenBy(id: Int(screenID)!) + return getScreenBy(id: Int(screenID) ?? 0) } } func getScreenBy(id screenID: Int) -> NSScreen? { - let screens = NSScreen.screens() - if screenID >= screens!.count { + guard let screens = NSScreen.screens() else { return nil} + if screenID >= screens.count { mpv.sendInfo("Screen ID \(screenID) does not exist, falling back to current device") return nil } else if screenID < 0 { return nil } - return screens![screenID] + return screens[screenID] } func getWindowGeometry(forScreen targetScreen: NSScreen, @@ -379,25 +417,32 @@ class CocoaCB: NSObject { } var controlCallback: mp_render_cb_control_fn = { ( vo, ctx, events, request, data ) -> Int32 in - let ccb: CocoaCB = MPVHelper.bridge(ptr: ctx!) + let ccb = unsafeBitCast(ctx, to: CocoaCB.self) switch mp_voctrl(request) { case VOCTRL_CHECK_EVENTS: - events!.pointee = Int32(ccb.checkEvents()) - return VO_TRUE + if let ev = events { + ev.pointee = Int32(ccb.checkEvents()) + return VO_TRUE + } + return VO_FALSE case VOCTRL_FULLSCREEN: DispatchQueue.main.async { - ccb.window.toggleFullScreen(nil) + ccb.window?.toggleFullScreen(nil) } return VO_TRUE case VOCTRL_GET_FULLSCREEN: - let fsData = data!.assumingMemoryBound(to: Int32.self) - fsData.pointee = ccb.window.isInFullscreen ? 1 : 0 - return VO_TRUE + if let fsData = data?.assumingMemoryBound(to: Int32.self) { + fsData.pointee = (ccb.window?.isInFullscreen ?? false) ? 1 : 0 + return VO_TRUE + } + return VO_FALSE case VOCTRL_GET_DISPLAY_FPS: - let fps = data!.assumingMemoryBound(to: CDouble.self) - fps.pointee = ccb.currentFps() - return VO_TRUE + if let fps = data?.assumingMemoryBound(to: CDouble.self) { + fps.pointee = ccb.currentFps() + return VO_TRUE + } + return VO_FALSE case VOCTRL_RESTORE_SCREENSAVER: ccb.enableDisplaySleep() return VO_TRUE @@ -405,56 +450,74 @@ class CocoaCB: NSObject { ccb.disableDisplaySleep() return VO_TRUE case VOCTRL_SET_CURSOR_VISIBILITY: - ccb.cursorVisibilityWanted = data!.assumingMemoryBound(to: CBool.self).pointee - DispatchQueue.main.async { - ccb.setCursorVisiblility(ccb.cursorVisibilityWanted) - } - 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 !ccb.mpv.getBoolProperty("hidpi-window-scale") { - rect = ccb.window.currentScreen!.convertRectFromBacking(rect) + if let cursorVisibility = data?.assumingMemoryBound(to: CBool.self) { + ccb.cursorVisibilityWanted = cursorVisibility.pointee + DispatchQueue.main.async { + ccb.setCursorVisiblility(ccb.cursorVisibilityWanted) } - ccb.window.updateSize(rect.size) + return VO_TRUE } - return VO_TRUE + return VO_FALSE + case VOCTRL_SET_UNFS_WINDOW_SIZE: + if 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 = ccb.window?.currentScreen, !ccb.mpv.getBoolProperty("hidpi-window-scale") { + rect = screen.convertRectFromBacking(rect) + } + ccb.window?.updateSize(rect.size) + } + return VO_TRUE + } + return VO_FALSE case VOCTRL_GET_WIN_STATE: - let minimized = data!.assumingMemoryBound(to: Int32.self) - minimized.pointee = ccb.window?.isMiniaturized ?? false ? - VO_WIN_STATE_MINIMIZED : Int32(0) - return VO_TRUE - case VOCTRL_GET_DISPLAY_NAMES: - let opts: mp_vo_opts = vo!.pointee.opts!.pointee - let dnames = data!.assumingMemoryBound(to: UnsafeMutablePointer?>?.self) - var array: UnsafeMutablePointer?>? = nil - var count: Int32 = 0 - let screen = ccb.window != nil ? ccb.window.screen : - ccb.getScreenBy(id: Int(opts.screen_id)) ?? - 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 { - ccb.title = String(cString: titleData) + if let minimized = data?.assumingMemoryBound(to: Int32.self) { + minimized.pointee = ccb.window?.isMiniaturized ?? false ? + VO_WIN_STATE_MINIMIZED : Int32(0) + return VO_TRUE } - return VO_TRUE + return VO_FALSE + case VOCTRL_GET_DISPLAY_NAMES: + if let opts: mp_vo_opts = vo?.pointee.opts?.pointee, + let dnames = data?.assumingMemoryBound(to: UnsafeMutablePointer?>?.self) + { + var array: UnsafeMutablePointer?>? = nil + var count: Int32 = 0 + let screen = ccb.window != nil ? ccb.window?.screen : + ccb.getScreenBy(id: Int(opts.screen_id)) ?? + 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 + } + return VO_FALSE + case VOCTRL_UPDATE_WINDOW_TITLE: + if let titleData = data?.assumingMemoryBound(to: Int8.self) { + DispatchQueue.main.async { + ccb.title = String(cString: titleData) + } + return VO_TRUE + } + return VO_FALSE case VOCTRL_PREINIT: - DispatchQueue.main.sync { ccb.preinit(vo!) } - return VO_TRUE + if let vout = vo { + DispatchQueue.main.sync { ccb.preinit(vout) } + return VO_TRUE + } + return VO_FALSE case VOCTRL_UNINIT: DispatchQueue.main.async { ccb.uninit() } return VO_TRUE case VOCTRL_RECONFIG: - ccb.reconfig(vo!) - return VO_TRUE + if let vout = vo { + ccb.reconfig(vout) + return VO_TRUE + } + return VO_FALSE default: return VO_NOTIMPL } @@ -463,7 +526,7 @@ class CocoaCB: NSObject { func shutdown(_ destroy: Bool = false) { isShuttingDown = window?.isAnimating ?? false || window?.isInFullscreen ?? false if window?.isInFullscreen ?? false && !(window?.isAnimating ?? false) { - window.close() + window?.close() } if isShuttingDown { return } @@ -503,27 +566,27 @@ class CocoaCB: NSObject { switch String(cString: property.name) { case "border": if let data = MPVHelper.mpvFlagToBool(property.data) { - window.border = data + window?.border = data } case "ontop": if let data = MPVHelper.mpvFlagToBool(property.data) { - window.setOnTop(data, mpv.getStringProperty("ontop-level") ?? "window") + window?.setOnTop(data, mpv.getStringProperty("ontop-level") ?? "window") } case "keepaspect-window": if let data = MPVHelper.mpvFlagToBool(property.data) { - window.keepAspect = data + window?.keepAspect = data } case "macos-title-bar-appearance": if let data = MPVHelper.mpvStringArrayToString(property.data) { - titleBar.set(appearance: data) + titleBar?.set(appearance: data) } case "macos-title-bar-material": if let data = MPVHelper.mpvStringArrayToString(property.data) { - titleBar.set(material: data) + titleBar?.set(material: data) } case "macos-title-bar-color": if let data = MPVHelper.mpvStringArrayToString(property.data) { - titleBar.set(color: data) + titleBar?.set(color: data) } default: break