summaryrefslogtreecommitdiffstats
path: root/video/out/mac/common.swift
diff options
context:
space:
mode:
Diffstat (limited to 'video/out/mac/common.swift')
-rw-r--r--video/out/mac/common.swift676
1 files changed, 676 insertions, 0 deletions
diff --git a/video/out/mac/common.swift b/video/out/mac/common.swift
new file mode 100644
index 0000000000..50a508e872
--- /dev/null
+++ b/video/out/mac/common.swift
@@ -0,0 +1,676 @@
+/*
+ * 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 option: OptionHelper
+ var input: InputHelper?
+ var log: LogHelper
+ var vo: UnsafeMutablePointer<vo>?
+ let queue: DispatchQueue = DispatchQueue(label: "io.mpv.queue")
+
+ @objc 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 appNotificationObservers: [NSObjectProtocol] = []
+
+ var cursorVisibilityWanted: Bool = true
+
+ var title: String = "mpv" {
+ didSet { if let window = window { window.title = title } }
+ }
+
+ init(_ option: OptionHelper, _ log: LogHelper) {
+ self.option = option
+ self.log = log
+ }
+
+ func initMisc(_ vo: UnsafeMutablePointer<vo>) {
+ startDisplayLink(vo)
+ initLightSensor()
+ addDisplayReconfigureObserver()
+ addAppNotifications()
+ option.setMacOptionCallback(macOptsWakeupCallback, context: self)
+ }
+
+ func initApp() {
+ var policy: NSApplication.ActivationPolicy = .regular
+ switch option.mac.macos_app_activation_policy {
+ case 0: policy = .regular
+ case 1: policy = .accessory
+ case 2: policy = .prohibited
+ default: break
+ }
+
+ NSApp.setActivationPolicy(policy)
+ setAppIcon()
+ }
+
+ func initWindow(_ vo: UnsafeMutablePointer<vo>, _ previousActiveApp: NSRunningApplication?) {
+ let (targetScreen, wr) = getInitProperties(vo)
+
+ guard let view = self.view else {
+ log.error("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.error("Something went wrong, no Window was initialized")
+ exit(1)
+ }
+
+ window.setOnTop(Bool(option.vo.ontop), Int(option.vo.ontop_level))
+ window.setOnAllWorkspaces(Bool(option.vo.all_workspaces))
+ window.keepAspect = Bool(option.vo.keepaspect_window)
+ window.title = title
+ window.border = Bool(option.vo.border)
+
+ titleBar = TitleBar(frame: wr, window: window, common: self)
+
+ let maximized = Bool(option.vo.window_maximized)
+ let minimized = Bool(option.vo.window_minimized)
+ window.isRestorable = false
+ window.isReleasedWhenClosed = false
+ window.setMaximized((minimized || !maximized) ? window.isZoomed : maximized)
+ window.setMinimized(minimized)
+ window.makeMain()
+ window.makeKey()
+
+ view.layer?.contentsScale = window.backingScaleFactor
+
+ if !minimized {
+ window.orderFront(nil)
+ }
+
+ NSApp.activate(ignoringOtherApps: option.vo.focus_on >= 1)
+
+ // workaround for macOS 10.15 to refocus the previous App
+ if option.vo.focus_on == 0 {
+ previousActiveApp?.activate()
+ }
+ }
+
+ func initView(_ vo: UnsafeMutablePointer<vo>, _ layer: CALayer) {
+ let (_, wr) = getInitProperties(vo)
+
+ view = View(frame: wr, common: self)
+ guard let view = self.view else {
+ log.error("Something went wrong, no View was initialized")
+ exit(1)
+ }
+
+ view.layer = layer
+ view.wantsLayer = true
+ view.layerContentsPlacement = .scaleProportionallyToFit
+ layer.delegate = view
+ }
+
+ func initWindowState() {
+ if option.vo.fullscreen {
+ DispatchQueue.main.async {
+ self.window?.toggleFullScreen(nil)
+ }
+ } else {
+ window?.isMovableByWindowBackground = true
+ }
+ }
+
+ func uninitCommon() {
+ setCursorVisibility(true)
+ stopDisplaylink()
+ uninitLightSensor()
+ removeDisplayReconfigureObserver()
+ removeAppNotifications()
+ enableDisplaySleep()
+ window?.orderOut(nil)
+
+ titleBar?.removeFromSuperview()
+ view?.removeFromSuperview()
+ }
+
+ 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 screen = getTargetScreen(forFullscreen: false) ?? NSScreen.main,
+ let link = self.link else {
+ log.warning("Couldn't start DisplayLink, no Screen or DisplayLink available")
+ return
+ }
+
+ CVDisplayLinkSetCurrentCGDisplay(link, screen.displayID)
+ CVDisplayLinkSetOutputHandler(link) { link, now, out, inFlags, outFlags -> CVReturn in
+ return self.displayLinkCallback(link, now, out, inFlags, outFlags)
+ }
+ 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.warning("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.verbose("Falling back to nominal display refresh rate: \(nominalFps)")
+ return nominalFps
+ } else {
+ return actualFps
+ }
+ }
+ } else {
+ log.warning("No DisplayLink available")
+ }
+
+ log.warning("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) 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.warning("lightSensorUpdate not implemented")
+ }
+
+ func initLightSensor() {
+ let srv = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleLMUController"))
+ if srv == IO_OBJECT_NULL {
+ log.verbose("Can't find an ambient light sensor")
+ return
+ }
+
+ lightSensorIOPort = IONotificationPortCreate(kIOMasterPortDefault)
+ IONotificationPortSetDispatchQueue(lightSensorIOPort, queue)
+ var n = io_object_t()
+ IOServiceAddInterestNotification(lightSensorIOPort, srv, kIOGeneralInterest, lightSensorCallback,
+ TypeHelper.bridge(obj: self), &n)
+ let kr = IOServiceOpen(srv, mach_task_self_, 0, &lightSensor)
+ IOObjectRelease(srv)
+
+ if kr != KERN_SUCCESS {
+ log.verbose("Can't start ambient light sensor connection")
+ return
+ }
+ lightSensorCallback(TypeHelper.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.verbose("Detected display mode change, updating screen refresh rate")
+ com.flagEvents(VO_EVENT_WIN_STATE)
+ }
+ }
+ }
+
+ func addDisplayReconfigureObserver() {
+ CGDisplayRegisterReconfigurationCallback(reconfigureCallback, TypeHelper.bridge(obj: self))
+ }
+
+ func removeDisplayReconfigureObserver() {
+ CGDisplayRemoveReconfigurationCallback(reconfigureCallback, TypeHelper.bridge(obj: self))
+ }
+
+ func addAppNotifications() {
+ appNotificationObservers.append(NotificationCenter.default.addObserver(
+ forName: NSApplication.didBecomeActiveNotification,
+ object: nil,
+ queue: .main,
+ using: { [weak self] (_) in self?.appDidBecomeActive() }
+ ))
+ appNotificationObservers.append(NotificationCenter.default.addObserver(
+ forName: NSApplication.didResignActiveNotification,
+ object: nil,
+ queue: .main,
+ using: { [weak self] (_) in self?.appDidResignActive() }
+ ))
+ }
+
+ func removeAppNotifications() {
+ appNotificationObservers.forEach { NotificationCenter.default.removeObserver($0) }
+ appNotificationObservers.removeAll()
+ }
+
+ func appDidBecomeActive() {
+ flagEvents(VO_EVENT_FOCUS)
+ }
+
+ func appDidResignActive() {
+ flagEvents(VO_EVENT_FOCUS)
+ }
+
+ func setAppIcon() {
+ if ProcessInfo.processInfo.environment["MPVBUNDLE"] != "true" {
+ NSApp.applicationIconImage = AppHub.shared.getIcon()
+ }
+ }
+
+ func updateCursorVisibility() {
+ setCursorVisibility(cursorVisibilityWanted)
+ }
+
+ func setCursorVisibility(_ visible: Bool) {
+ NSCursor.setHiddenUntilMouseMoves(!visible && (view?.canHideCursor() ?? false))
+ }
+
+ func updateICCProfile() {
+ log.warning("updateICCProfile not implemented")
+ }
+
+ func getScreenBy(id screenID: Int) -> NSScreen? {
+ if screenID >= NSScreen.screens.count {
+ log.info("Screen ID \(screenID) does not exist, falling back to current device")
+ return nil
+ } else if screenID < 0 {
+ return nil
+ }
+ return NSScreen.screens[screenID]
+ }
+
+ func getScreenBy(name screenName: String?) -> NSScreen? {
+ for screen in NSScreen.screens
+ where [screen.localizedName, screen.name, screen.uniqueName, screen.serialNumber].contains(screenName) {
+ return screen
+ }
+ return nil
+ }
+
+ func getTargetScreen(forFullscreen fs: Bool) -> NSScreen? {
+ let screenID = fs ? option.vo.fsscreen_id : option.vo.screen_id
+ var name: String?
+ if let screenName = fs ? option.vo.fsscreen_name : option.vo.screen_name {
+ name = String(cString: screenName)
+ }
+ return getScreenBy(id: Int(screenID)) ?? getScreenBy(name: name)
+ }
+
+ func getCurrentScreen() -> NSScreen? {
+ return window != nil ? window?.screen :
+ getTargetScreen(forFullscreen: false) ??
+ NSScreen.main
+ }
+
+ func getWindowGeometry(forScreen screen: NSScreen,
+ videoOut vo: UnsafeMutablePointer<vo>) -> NSRect {
+ let r = screen.convertRectToBacking(screen.frame)
+ let targetFrame = option.mac.macos_geometry_calculation == FRAME_VISIBLE
+ ? screen.visibleFrame : screen.frame
+ let rv = screen.convertRectToBacking(targetFrame)
+
+ // convert origin to be relative to target screen
+ var originY = rv.origin.y - r.origin.y
+ let originX = rv.origin.x - r.origin.x
+ // flip the y origin, mp_rect expects the origin at the top-left
+ // macOS' windowing system operates from the bottom-left
+ originY = -(originY + rv.size.height)
+ var screenRC: mp_rect = mp_rect(x0: Int32(originX),
+ y0: Int32(originY),
+ x1: Int32(originX + rv.size.width),
+ y1: Int32(originY + rv.size.height))
+
+ var geo: vo_win_geometry = vo_win_geometry()
+ vo_calc_window_geometry2(vo, &screenRC, Double(screen.backingScaleFactor), &geo)
+ vo_apply_window_geometry(vo, &geo)
+
+ let height = CGFloat(geo.win.y1 - geo.win.y0)
+ let width = CGFloat(geo.win.x1 - geo.win.x0)
+ // flip the y origin again
+ let y = CGFloat(-geo.win.y1)
+ let x = CGFloat(geo.win.x0)
+ return screen.convertRectFromBacking(NSRect(x: x, y: y, width: width, height: height))
+ }
+
+ func getInitProperties(_ vo: UnsafeMutablePointer<vo>) -> (NSScreen, NSRect) {
+ guard let targetScreen = getTargetScreen(forFullscreen: false) ?? NSScreen.main else {
+ log.error("Something went wrong, no Screen was found")
+ exit(1)
+ }
+
+ let wr = getWindowGeometry(forScreen: targetScreen, videoOut: vo)
+
+ return (targetScreen, wr)
+ }
+
+ // call before initApp, because on macOS +10.15 it changes the active App
+ func getActiveApp() -> NSRunningApplication? {
+ return NSWorkspace.shared.runningApplications.first(where: {$0.isActive})
+ }
+
+ func flagEvents(_ ev: Int) {
+ eventsLock.lock()
+ events |= ev
+ eventsLock.unlock()
+
+ guard let vo = vo else {
+ log.warning("vo nil in flagEvents")
+ return
+ }
+ vo_wakeup(vo)
+ }
+
+ 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 {
+ switch mp_voctrl(request) {
+ case VOCTRL_CHECK_EVENTS:
+ events.pointee |= Int32(checkEvents())
+ return VO_TRUE
+ case VOCTRL_VO_OPTS_CHANGED:
+ var opt: UnsafeMutableRawPointer?
+ while option.nextChangedOption(property: &opt) {
+ switch opt {
+ case TypeHelper.toPointer(&option.voPtr.pointee.border):
+ DispatchQueue.main.async {
+ self.window?.border = Bool(self.option.vo.border)
+ }
+ case TypeHelper.toPointer(&option.voPtr.pointee.fullscreen):
+ DispatchQueue.main.async {
+ self.window?.toggleFullScreen(nil)
+ }
+ case TypeHelper.toPointer(&option.voPtr.pointee.ontop),
+ TypeHelper.toPointer(&option.voPtr.pointee.ontop_level):
+ DispatchQueue.main.async {
+ self.window?.setOnTop(Bool(self.option.vo.ontop), Int(self.option.vo.ontop_level))
+ }
+ case TypeHelper.toPointer(&option.voPtr.pointee.all_workspaces):
+ DispatchQueue.main.async {
+ self.window?.setOnAllWorkspaces(Bool(self.option.vo.all_workspaces))
+ }
+ case TypeHelper.toPointer(&option.voPtr.pointee.keepaspect_window):
+ DispatchQueue.main.async {
+ self.window?.keepAspect = Bool(self.option.vo.keepaspect_window)
+ }
+ case TypeHelper.toPointer(&option.voPtr.pointee.window_minimized):
+ DispatchQueue.main.async {
+ self.window?.setMinimized(Bool(self.option.vo.window_minimized))
+ }
+ case TypeHelper.toPointer(&option.voPtr.pointee.window_maximized):
+ DispatchQueue.main.async {
+ self.window?.setMaximized(Bool(self.option.vo.window_maximized))
+ }
+ case TypeHelper.toPointer(&option.voPtr.pointee.cursor_passthrough):
+ DispatchQueue.main.async {
+ self.window?.ignoresMouseEvents = self.option.vo.cursor_passthrough
+ }
+ case TypeHelper.toPointer(&option.voPtr.pointee.geometry),
+ TypeHelper.toPointer(&option.voPtr.pointee.autofit),
+ TypeHelper.toPointer(&option.voPtr.pointee.autofit_smaller),
+ TypeHelper.toPointer(&option.voPtr.pointee.autofit_larger):
+ DispatchQueue.main.async {
+ let (_, wr) = self.getInitProperties(vo)
+ self.window?.updateFrame(wr)
+ }
+ default:
+ break
+ }
+ }
+ return VO_TRUE
+ case VOCTRL_GET_DISPLAY_FPS:
+ let fps = data!.assumingMemoryBound(to: CDouble.self)
+ fps.pointee = currentFps()
+ return VO_TRUE
+ case VOCTRL_GET_WINDOW_ID:
+ guard let window = window else {
+ return VO_NOTAVAIL
+ }
+ let wid = data!.assumingMemoryBound(to: Int64.self)
+ wid.pointee = unsafeBitCast(window, to: Int64.self)
+ return VO_TRUE
+ case VOCTRL_GET_HIDPI_SCALE:
+ let scaleFactor = data!.assumingMemoryBound(to: CDouble.self)
+ let screen = getCurrentScreen()
+ let factor = window?.backingScaleFactor ??
+ screen?.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.setCursorVisibility(self.cursorVisibilityWanted)
+ }
+ return VO_TRUE
+ case VOCTRL_GET_ICC_PROFILE:
+ let screen = getCurrentScreen()
+ guard var iccData = screen?.colorSpace?.iccProfileData else {
+ log.warning("No Screen available to retrieve ICC profile")
+ return VO_TRUE
+ }
+
+ let icc = data!.assumingMemoryBound(to: bstr.self)
+ iccData.withUnsafeMutableBytes { (ptr: UnsafeMutableRawBufferPointer) in
+ guard let baseAddress = ptr.baseAddress, ptr.count > 0 else { return }
+ let u8Ptr = baseAddress.assumingMemoryBound(to: UInt8.self)
+ icc.pointee = bstrdup(nil, bstr(start: u8Ptr, len: ptr.count))
+ }
+ return VO_TRUE
+ case VOCTRL_GET_AMBIENT_LUX:
+ if lightSensor != 0 {
+ let lux = data!.assumingMemoryBound(to: Int32.self)
+ lux.pointee = Int32(lmuToLux(lastLmu))
+ return VO_TRUE
+ }
+ return VO_NOTIMPL
+ case VOCTRL_GET_UNFS_WINDOW_SIZE:
+ let sizeData = data!.assumingMemoryBound(to: Int32.self)
+ let size = UnsafeMutableBufferPointer(start: sizeData, count: 2)
+ let rect = (Bool(option.vo.hidpi_window_scale) ? window?.unfsContentFrame
+ : window?.unfsContentFramePixel) ?? NSRect(x: 0, y: 0, width: 1280, height: 720)
+
+ 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 = NSRect(x: 0, y: 0, width: CGFloat(size[0]), height: CGFloat(size[1]))
+ DispatchQueue.main.async {
+ if let screen = self.window?.currentScreen, !Bool(self.option.vo.hidpi_window_scale) {
+ 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>?>?
+ var count: Int32 = 0
+ let displayName = getCurrentScreen()?.uniqueName ?? "Unknown"
+
+ app_bridge_tarray_append(nil, &array, &count, ta_xstrdup(nil, displayName))
+ app_bridge_tarray_append(nil, &array, &count, nil)
+ dnames.pointee = array
+ return VO_TRUE
+ case VOCTRL_GET_DISPLAY_RES:
+ guard let screen = getCurrentScreen() else {
+ log.warning("No Screen available to retrieve frame")
+ return VO_NOTAVAIL
+ }
+ let sizeData = data!.assumingMemoryBound(to: Int32.self)
+ let size = UnsafeMutableBufferPointer(start: sizeData, count: 2)
+ let frame = screen.convertRectToBacking(screen.frame)
+ size[0] = Int32(frame.size.width)
+ size[1] = Int32(frame.size.height)
+ return VO_TRUE
+ case VOCTRL_GET_FOCUSED:
+ let focus = data!.assumingMemoryBound(to: CBool.self)
+ focus.pointee = NSApp.isActive
+ return VO_TRUE
+ case VOCTRL_UPDATE_WINDOW_TITLE:
+ let title = String(cString: data!.assumingMemoryBound(to: CChar.self))
+ DispatchQueue.main.async {
+ self.title = title
+ }
+ return VO_TRUE
+ default:
+ return VO_NOTIMPL
+ }
+ }
+
+ let macOptsWakeupCallback: OptionHelper.WakeupCallback = { ( ctx ) in
+ let com = unsafeBitCast(ctx, to: Common.self)
+ DispatchQueue.main.async {
+ com.macOptsUpdate()
+ }
+ }
+
+ func macOptsUpdate() {
+ var opt: UnsafeMutableRawPointer?
+ while option.nextChangedMacOption(property: &opt) {
+ switch opt {
+ case TypeHelper.toPointer(&option.macPtr.pointee.macos_title_bar_appearance):
+ titleBar?.set(appearance: Int(option.mac.macos_title_bar_appearance))
+ case TypeHelper.toPointer(&option.macPtr.pointee.macos_title_bar_material):
+ titleBar?.set(material: Int(option.mac.macos_title_bar_material))
+ case TypeHelper.toPointer(&option.macPtr.pointee.macos_title_bar_color):
+ titleBar?.set(color: option.mac.macos_title_bar_color)
+ default:
+ break
+ }
+ }
+ }
+}