summaryrefslogtreecommitdiffstats
path: root/osdep/macos
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:36:56 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:36:56 +0000
commit51de1d8436100f725f3576aefa24a2bd2057bc28 (patch)
treec6d1d5264b6d40a8d7ca34129f36b7d61e188af3 /osdep/macos
parentInitial commit. (diff)
downloadmpv-upstream/0.37.0.tar.xz
mpv-upstream/0.37.0.zip
Adding upstream version 0.37.0.upstream/0.37.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--osdep/macos/libmpv_helper.swift250
-rw-r--r--osdep/macos/log_helper.swift47
-rw-r--r--osdep/macos/mpv_helper.swift156
-rw-r--r--osdep/macos/precise_timer.swift153
-rw-r--r--osdep/macos/remote_command_center.swift191
-rw-r--r--osdep/macos/swift_compat.swift36
-rw-r--r--osdep/macos/swift_extensions.swift58
-rw-r--r--osdep/macosx_application.h55
-rw-r--r--osdep/macosx_application.m375
-rw-r--r--osdep/macosx_application_objc.h40
-rw-r--r--osdep/macosx_events.h36
-rw-r--r--osdep/macosx_events.m408
-rw-r--r--osdep/macosx_events_objc.h45
-rw-r--r--osdep/macosx_menubar.h30
-rw-r--r--osdep/macosx_menubar.m853
-rw-r--r--osdep/macosx_menubar_objc.h25
-rw-r--r--osdep/macosx_touchbar.h46
-rw-r--r--osdep/macosx_touchbar.m334
18 files changed, 3138 insertions, 0 deletions
diff --git a/osdep/macos/libmpv_helper.swift b/osdep/macos/libmpv_helper.swift
new file mode 100644
index 0000000..8b1c697
--- /dev/null
+++ b/osdep/macos/libmpv_helper.swift
@@ -0,0 +1,250 @@
+/*
+ * 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 glDummy: @convention(c) () -> Void = {}
+
+class LibmpvHelper {
+ var log: LogHelper
+ var mpvHandle: OpaquePointer?
+ var mpvRenderContext: OpaquePointer?
+ var macOptsPtr: UnsafeMutableRawPointer?
+ var macOpts: macos_opts = macos_opts()
+ var fbo: GLint = 1
+ let deinitLock = NSLock()
+
+ init(_ mpv: OpaquePointer, _ mpLog: OpaquePointer?) {
+ mpvHandle = mpv
+ log = LogHelper(mpLog)
+
+ guard let app = NSApp as? Application,
+ let ptr = mp_get_config_group(nil,
+ mp_client_get_global(mpvHandle),
+ app.getMacOSConf()) else
+ {
+ log.sendError("macOS config group couldn't be retrieved'")
+ exit(1)
+ }
+ macOptsPtr = ptr
+ macOpts = UnsafeMutablePointer<macos_opts>(OpaquePointer(ptr)).pointee
+ }
+
+ func initRender() {
+ let advanced: CInt = 1
+ let api = UnsafeMutableRawPointer(mutating: (MPV_RENDER_API_TYPE_OPENGL as NSString).utf8String)
+ let pAddress = mpv_opengl_init_params(get_proc_address: getProcAddress,
+ get_proc_address_ctx: nil)
+
+ MPVHelper.withUnsafeMutableRawPointers([pAddress, advanced]) { (pointers: [UnsafeMutableRawPointer?]) in
+ var params: [mpv_render_param] = [
+ mpv_render_param(type: MPV_RENDER_PARAM_API_TYPE, data: api),
+ mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, data: pointers[0]),
+ mpv_render_param(type: MPV_RENDER_PARAM_ADVANCED_CONTROL, data: pointers[1]),
+ mpv_render_param()
+ ]
+
+ if (mpv_render_context_create(&mpvRenderContext, mpvHandle, &params) < 0) {
+ log.sendError("Render context init has failed.")
+ exit(1)
+ }
+ }
+
+ }
+
+ let getProcAddress: (@convention(c) (UnsafeMutableRawPointer?, UnsafePointer<Int8>?)
+ -> UnsafeMutableRawPointer?) =
+ {
+ (ctx: UnsafeMutableRawPointer?, name: UnsafePointer<Int8>?)
+ -> UnsafeMutableRawPointer? in
+ let symbol: CFString = CFStringCreateWithCString(
+ kCFAllocatorDefault, name, kCFStringEncodingASCII)
+ let identifier = CFBundleGetBundleWithIdentifier("com.apple.opengl" as CFString)
+ let addr = CFBundleGetFunctionPointerForName(identifier, symbol)
+
+ if symbol as String == "glFlush" {
+ return unsafeBitCast(glDummy, to: UnsafeMutableRawPointer.self)
+ }
+
+ return addr
+ }
+
+ func setRenderUpdateCallback(_ callback: @escaping mpv_render_update_fn, context object: AnyObject) {
+ if mpvRenderContext == nil {
+ log.sendWarning("Init mpv render context first.")
+ } else {
+ mpv_render_context_set_update_callback(mpvRenderContext, callback, MPVHelper.bridge(obj: object))
+ }
+ }
+
+ func setRenderControlCallback(_ callback: @escaping mp_render_cb_control_fn, context object: AnyObject) {
+ if mpvRenderContext == nil {
+ log.sendWarning("Init mpv render context first.")
+ } else {
+ mp_render_context_set_control_callback(mpvRenderContext, callback, MPVHelper.bridge(obj: object))
+ }
+ }
+
+ func reportRenderFlip() {
+ if mpvRenderContext == nil { return }
+ mpv_render_context_report_swap(mpvRenderContext)
+ }
+
+ func isRenderUpdateFrame() -> Bool {
+ deinitLock.lock()
+ if mpvRenderContext == nil {
+ deinitLock.unlock()
+ return false
+ }
+ let flags: UInt64 = mpv_render_context_update(mpvRenderContext)
+ deinitLock.unlock()
+ return flags & UInt64(MPV_RENDER_UPDATE_FRAME.rawValue) > 0
+ }
+
+ func drawRender(_ surface: NSSize, _ depth: GLint, _ ctx: CGLContextObj, skip: Bool = false) {
+ deinitLock.lock()
+ if mpvRenderContext != nil {
+ var i: GLint = 0
+ let flip: CInt = 1
+ let skip: CInt = skip ? 1 : 0
+ let ditherDepth = depth
+ glGetIntegerv(GLenum(GL_DRAW_FRAMEBUFFER_BINDING), &i)
+ // CAOpenGLLayer has ownership of FBO zero yet can return it to us,
+ // so only utilize a newly received FBO ID if it is nonzero.
+ fbo = i != 0 ? i : fbo
+
+ let data = mpv_opengl_fbo(fbo: Int32(fbo),
+ w: Int32(surface.width),
+ h: Int32(surface.height),
+ internal_format: 0)
+
+ MPVHelper.withUnsafeMutableRawPointers([data, flip, ditherDepth, skip]) { (pointers: [UnsafeMutableRawPointer?]) in
+ var params: [mpv_render_param] = [
+ mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_FBO, data: pointers[0]),
+ mpv_render_param(type: MPV_RENDER_PARAM_FLIP_Y, data: pointers[1]),
+ mpv_render_param(type: MPV_RENDER_PARAM_DEPTH, data: pointers[2]),
+ mpv_render_param(type: MPV_RENDER_PARAM_SKIP_RENDERING, data: pointers[3]),
+ mpv_render_param()
+ ]
+ mpv_render_context_render(mpvRenderContext, &params);
+ }
+ } else {
+ glClearColor(0, 0, 0, 1)
+ glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
+ }
+
+ if !skip { CGLFlushDrawable(ctx) }
+
+ deinitLock.unlock()
+ }
+
+ func setRenderICCProfile(_ profile: NSColorSpace) {
+ if mpvRenderContext == nil { return }
+ guard var iccData = profile.iccProfileData else {
+ log.sendWarning("Invalid ICC profile data.")
+ return
+ }
+ iccData.withUnsafeMutableBytes { (ptr: UnsafeMutableRawBufferPointer) in
+ guard let baseAddress = ptr.baseAddress, ptr.count > 0 else { return }
+
+ let u8Ptr = baseAddress.assumingMemoryBound(to: UInt8.self)
+ let iccBstr = bstrdup(nil, bstr(start: u8Ptr, len: ptr.count))
+ var icc = mpv_byte_array(data: iccBstr.start, size: iccBstr.len)
+ withUnsafeMutableBytes(of: &icc) { (ptr: UnsafeMutableRawBufferPointer) in
+ let params = mpv_render_param(type: MPV_RENDER_PARAM_ICC_PROFILE, data: ptr.baseAddress)
+ mpv_render_context_set_parameter(mpvRenderContext, params)
+ }
+ }
+ }
+
+ func setRenderLux(_ lux: Int) {
+ if mpvRenderContext == nil { return }
+ var light = lux
+ withUnsafeMutableBytes(of: &light) { (ptr: UnsafeMutableRawBufferPointer) in
+ let params = mpv_render_param(type: MPV_RENDER_PARAM_AMBIENT_LIGHT, data: ptr.baseAddress)
+ mpv_render_context_set_parameter(mpvRenderContext, params)
+ }
+ }
+
+ func commandAsync(_ cmd: [String?], id: UInt64 = 1) {
+ if mpvHandle == nil { return }
+ var mCmd = cmd
+ mCmd.append(nil)
+ var cargs = mCmd.map { $0.flatMap { UnsafePointer<Int8>(strdup($0)) } }
+ mpv_command_async(mpvHandle, id, &cargs)
+ for ptr in cargs { free(UnsafeMutablePointer(mutating: ptr)) }
+ }
+
+ // Unsafe function when called while using the render API
+ func command(_ cmd: String) {
+ if mpvHandle == nil { return }
+ mpv_command_string(mpvHandle, cmd)
+ }
+
+ func getBoolProperty(_ name: String) -> Bool {
+ if mpvHandle == nil { return false }
+ var value = Int32()
+ mpv_get_property(mpvHandle, name, MPV_FORMAT_FLAG, &value)
+ return value > 0
+ }
+
+ func getIntProperty(_ name: String) -> Int {
+ if mpvHandle == nil { return 0 }
+ var value = Int64()
+ mpv_get_property(mpvHandle, name, MPV_FORMAT_INT64, &value)
+ return Int(value)
+ }
+
+ func getStringProperty(_ name: String) -> String? {
+ guard let mpv = mpvHandle else { return nil }
+ guard let value = mpv_get_property_string(mpv, name) else { return nil }
+ let str = String(cString: value)
+ mpv_free(value)
+ return str
+ }
+
+ func deinitRender() {
+ mpv_render_context_set_update_callback(mpvRenderContext, nil, nil)
+ mp_render_context_set_control_callback(mpvRenderContext, nil, nil)
+ deinitLock.lock()
+ mpv_render_context_free(mpvRenderContext)
+ mpvRenderContext = nil
+ deinitLock.unlock()
+ }
+
+ func deinitMPV(_ destroy: Bool = false) {
+ if destroy {
+ mpv_destroy(mpvHandle)
+ }
+ ta_free(macOptsPtr)
+ macOptsPtr = nil
+ mpvHandle = nil
+ }
+
+ // *(char **) MPV_FORMAT_STRING on mpv_event_property
+ class func mpvStringArrayToString(_ obj: UnsafeMutableRawPointer) -> String? {
+ let cstr = UnsafeMutablePointer<UnsafeMutablePointer<Int8>>(OpaquePointer(obj))
+ return String(cString: cstr[0])
+ }
+
+ // MPV_FORMAT_FLAG
+ class func mpvFlagToBool(_ obj: UnsafeMutableRawPointer) -> Bool? {
+ return UnsafePointer<Bool>(OpaquePointer(obj))?.pointee
+ }
+}
diff --git a/osdep/macos/log_helper.swift b/osdep/macos/log_helper.swift
new file mode 100644
index 0000000..9464075
--- /dev/null
+++ b/osdep/macos/log_helper.swift
@@ -0,0 +1,47 @@
+/*
+ * 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 LogHelper: NSObject {
+ var log: OpaquePointer?
+
+ init(_ log: OpaquePointer?) {
+ self.log = log
+ }
+
+ func sendVerbose(_ msg: String) {
+ send(message: msg, type: MSGL_V)
+ }
+
+ func sendInfo(_ msg: String) {
+ send(message: msg, type: MSGL_INFO)
+ }
+
+ func sendWarning(_ msg: String) {
+ send(message: msg, type: MSGL_WARN)
+ }
+
+ func sendError(_ msg: String) {
+ send(message: msg, type: MSGL_ERR)
+ }
+
+ func send(message msg: String, type t: Int) {
+ let args: [CVarArg] = [ (msg as NSString).utf8String ?? "NO MESSAGE"]
+ mp_msg_va(log, Int32(t), "%s\n", getVaList(args))
+ }
+}
diff --git a/osdep/macos/mpv_helper.swift b/osdep/macos/mpv_helper.swift
new file mode 100644
index 0000000..3b2a716
--- /dev/null
+++ b/osdep/macos/mpv_helper.swift
@@ -0,0 +1,156 @@
+/*
+ * 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
+
+typealias swift_wakeup_cb_fn = (@convention(c) (UnsafeMutableRawPointer?) -> Void)?
+
+class MPVHelper {
+ var log: LogHelper
+ var vo: UnsafeMutablePointer<vo>
+ var optsCachePtr: UnsafeMutablePointer<m_config_cache>
+ var optsPtr: UnsafeMutablePointer<mp_vo_opts>
+ var macOptsCachePtr: UnsafeMutablePointer<m_config_cache>
+ var macOptsPtr: UnsafeMutablePointer<macos_opts>
+
+ // these computed properties return a local copy of the struct accessed:
+ // - don't use if you rely on the pointers
+ // - only for reading
+ var vout: vo { get { return vo.pointee } }
+ var optsCache: m_config_cache { get { return optsCachePtr.pointee } }
+ var opts: mp_vo_opts { get { return optsPtr.pointee } }
+ var macOptsCache: m_config_cache { get { return macOptsCachePtr.pointee } }
+ var macOpts: macos_opts { get { return macOptsPtr.pointee } }
+
+ var input: OpaquePointer { get { return vout.input_ctx } }
+
+ init(_ vo: UnsafeMutablePointer<vo>, _ log: LogHelper) {
+ self.vo = vo
+ self.log = log
+
+ guard let app = NSApp as? Application,
+ let cache = m_config_cache_alloc(vo, vo.pointee.global, app.getVoSubConf()) else
+ {
+ log.sendError("NSApp couldn't be retrieved")
+ exit(1)
+ }
+
+ optsCachePtr = cache
+ optsPtr = UnsafeMutablePointer<mp_vo_opts>(OpaquePointer(cache.pointee.opts))
+
+ guard let macCache = m_config_cache_alloc(vo,
+ vo.pointee.global,
+ app.getMacOSConf()) else
+ {
+ // will never be hit, mp_get_config_group asserts for invalid groups
+ exit(1)
+ }
+ macOptsCachePtr = macCache
+ macOptsPtr = UnsafeMutablePointer<macos_opts>(OpaquePointer(macCache.pointee.opts))
+ }
+
+ func canBeDraggedAt(_ pos: NSPoint) -> Bool {
+ let canDrag = !mp_input_test_dragging(input, Int32(pos.x), Int32(pos.y))
+ return canDrag
+ }
+
+ func mouseEnabled() -> Bool {
+ return mp_input_mouse_enabled(input)
+ }
+
+ func setMousePosition(_ pos: NSPoint) {
+ mp_input_set_mouse_pos(input, Int32(pos.x), Int32(pos.y))
+ }
+
+ func putAxis(_ mpkey: Int32, delta: Double) {
+ mp_input_put_wheel(input, mpkey, delta)
+ }
+
+ func nextChangedOption(property: inout UnsafeMutableRawPointer?) -> Bool {
+ return m_config_cache_get_next_changed(optsCachePtr, &property)
+ }
+
+ func setOption(fullscreen: Bool) {
+ optsPtr.pointee.fullscreen = fullscreen
+ _ = withUnsafeMutableBytes(of: &optsPtr.pointee.fullscreen) { (ptr: UnsafeMutableRawBufferPointer) in
+ m_config_cache_write_opt(optsCachePtr, ptr.baseAddress)
+ }
+ }
+
+ func setOption(minimized: Bool) {
+ optsPtr.pointee.window_minimized = minimized
+ _ = withUnsafeMutableBytes(of: &optsPtr.pointee.window_minimized) { (ptr: UnsafeMutableRawBufferPointer) in
+ m_config_cache_write_opt(optsCachePtr, ptr.baseAddress)
+ }
+ }
+
+ func setOption(maximized: Bool) {
+ optsPtr.pointee.window_maximized = maximized
+ _ = withUnsafeMutableBytes(of: &optsPtr.pointee.window_maximized) { (ptr: UnsafeMutableRawBufferPointer) in
+ m_config_cache_write_opt(optsCachePtr, ptr.baseAddress)
+ }
+ }
+
+ func setMacOptionCallback(_ callback: swift_wakeup_cb_fn, context object: AnyObject) {
+ m_config_cache_set_wakeup_cb(macOptsCachePtr, callback, MPVHelper.bridge(obj: object))
+ }
+
+ func nextChangedMacOption(property: inout UnsafeMutableRawPointer?) -> Bool {
+ return m_config_cache_get_next_changed(macOptsCachePtr, &property)
+ }
+
+ func command(_ cmd: String) {
+ let cCmd = UnsafePointer<Int8>(strdup(cmd))
+ let mpvCmd = mp_input_parse_cmd(input, bstr0(cCmd), "")
+ mp_input_queue_cmd(input, mpvCmd)
+ free(UnsafeMutablePointer(mutating: cCmd))
+ }
+
+ // (__bridge void*)
+ class func bridge<T: AnyObject>(obj: T) -> UnsafeMutableRawPointer {
+ return UnsafeMutableRawPointer(Unmanaged.passUnretained(obj).toOpaque())
+ }
+
+ // (__bridge T*)
+ class func bridge<T: AnyObject>(ptr: UnsafeRawPointer) -> T {
+ return Unmanaged<T>.fromOpaque(ptr).takeUnretainedValue()
+ }
+
+ class func withUnsafeMutableRawPointers(_ arguments: [Any],
+ pointers: [UnsafeMutableRawPointer?] = [],
+ closure: (_ pointers: [UnsafeMutableRawPointer?]) -> Void) {
+ if arguments.count > 0 {
+ let args = Array(arguments.dropFirst(1))
+ var newPtrs = pointers
+ var firstArg = arguments.first
+ withUnsafeMutableBytes(of: &firstArg) { (ptr: UnsafeMutableRawBufferPointer) in
+ newPtrs.append(ptr.baseAddress)
+ withUnsafeMutableRawPointers(args, pointers: newPtrs, closure: closure)
+ }
+
+ return
+ }
+
+ closure(pointers)
+ }
+
+ class func getPointer<T>(_ value: inout T) -> UnsafeMutableRawPointer? {
+ return withUnsafeMutableBytes(of: &value) { (ptr: UnsafeMutableRawBufferPointer) in
+ ptr.baseAddress
+ }
+ }
+}
diff --git a/osdep/macos/precise_timer.swift b/osdep/macos/precise_timer.swift
new file mode 100644
index 0000000..f4ad3bb
--- /dev/null
+++ b/osdep/macos/precise_timer.swift
@@ -0,0 +1,153 @@
+/*
+ * 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
+
+struct Timing {
+ let time: UInt64
+ let closure: () -> ()
+}
+
+class PreciseTimer {
+ unowned var common: Common
+ var mpv: MPVHelper? { get { return common.mpv } }
+
+ let nanoPerSecond: Double = 1e+9
+ let machToNano: Double = {
+ var timebase: mach_timebase_info = mach_timebase_info()
+ mach_timebase_info(&timebase)
+ return Double(timebase.numer) / Double(timebase.denom)
+ }()
+
+ let condition = NSCondition()
+ var events: [Timing] = []
+ var isRunning: Bool = true
+ var isHighPrecision: Bool = false
+
+ var thread: pthread_t!
+ var threadPort: thread_port_t = thread_port_t()
+ let policyFlavor = thread_policy_flavor_t(THREAD_TIME_CONSTRAINT_POLICY)
+ let policyCount = MemoryLayout<thread_time_constraint_policy>.size /
+ MemoryLayout<integer_t>.size
+ var typeNumber: mach_msg_type_number_t {
+ return mach_msg_type_number_t(policyCount)
+ }
+ var threadAttr: pthread_attr_t = {
+ var attr = pthread_attr_t()
+ var param = sched_param()
+ pthread_attr_init(&attr)
+ param.sched_priority = sched_get_priority_max(SCHED_FIFO)
+ pthread_attr_setschedparam(&attr, &param)
+ pthread_attr_setschedpolicy(&attr, SCHED_FIFO)
+ return attr
+ }()
+
+ init?(common com: Common) {
+ common = com
+
+ pthread_create(&thread, &threadAttr, entryC, MPVHelper.bridge(obj: self))
+ if thread == nil {
+ common.log.sendWarning("Couldn't create pthread for high precision timer")
+ return nil
+ }
+
+ threadPort = pthread_mach_thread_np(thread)
+ }
+
+ func updatePolicy(periodSeconds: Double = 1 / 60.0) {
+ let period = periodSeconds * nanoPerSecond / machToNano
+ var policy = thread_time_constraint_policy(
+ period: UInt32(period),
+ computation: UInt32(0.75 * period),
+ constraint: UInt32(0.85 * period),
+ preemptible: 1
+ )
+
+ let success = withUnsafeMutablePointer(to: &policy) {
+ $0.withMemoryRebound(to: integer_t.self, capacity: policyCount) {
+ thread_policy_set(threadPort, policyFlavor, $0, typeNumber)
+ }
+ }
+
+ isHighPrecision = success == KERN_SUCCESS
+ if !isHighPrecision {
+ common.log.sendWarning("Couldn't create a high precision timer")
+ }
+ }
+
+ func terminate() {
+ condition.lock()
+ isRunning = false
+ condition.signal()
+ condition.unlock()
+ pthread_kill(thread, SIGALRM)
+ pthread_join(thread, nil)
+ }
+
+ func scheduleAt(time: UInt64, closure: @escaping () -> ()) {
+ condition.lock()
+ let firstEventTime = events.first?.time ?? 0
+ let lastEventTime = events.last?.time ?? 0
+ events.append(Timing(time: time, closure: closure))
+
+ if lastEventTime > time {
+ events.sort{ $0.time < $1.time }
+ }
+
+ condition.signal()
+ condition.unlock()
+
+ if firstEventTime > time {
+ pthread_kill(thread, SIGALRM)
+ }
+ }
+
+ let threadSignal: @convention(c) (Int32) -> () = { (sig: Int32) in }
+
+ let entryC: @convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? = { (ptr: UnsafeMutableRawPointer) in
+ let ptimer: PreciseTimer = MPVHelper.bridge(ptr: ptr)
+ ptimer.entry()
+ return nil
+ }
+
+ func entry() {
+ signal(SIGALRM, threadSignal)
+
+ while isRunning {
+ condition.lock()
+ while events.count == 0 && isRunning {
+ condition.wait()
+ }
+
+ if !isRunning { break }
+
+ guard let event = events.first else {
+ continue
+ }
+ condition.unlock()
+
+ mach_wait_until(event.time)
+
+ condition.lock()
+ if events.first?.time == event.time && isRunning {
+ event.closure()
+ events.removeFirst()
+ }
+ condition.unlock()
+ }
+ }
+}
diff --git a/osdep/macos/remote_command_center.swift b/osdep/macos/remote_command_center.swift
new file mode 100644
index 0000000..6fb2229
--- /dev/null
+++ b/osdep/macos/remote_command_center.swift
@@ -0,0 +1,191 @@
+/*
+ * 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 MediaPlayer
+
+class RemoteCommandCenter: NSObject {
+ enum KeyType {
+ case normal
+ case repeatable
+ }
+
+ var config: [MPRemoteCommand:[String:Any]] = [
+ MPRemoteCommandCenter.shared().pauseCommand: [
+ "mpKey": MP_KEY_PAUSEONLY,
+ "keyType": KeyType.normal
+ ],
+ MPRemoteCommandCenter.shared().playCommand: [
+ "mpKey": MP_KEY_PLAYONLY,
+ "keyType": KeyType.normal
+ ],
+ MPRemoteCommandCenter.shared().stopCommand: [
+ "mpKey": MP_KEY_STOP,
+ "keyType": KeyType.normal
+ ],
+ MPRemoteCommandCenter.shared().nextTrackCommand: [
+ "mpKey": MP_KEY_NEXT,
+ "keyType": KeyType.normal
+ ],
+ MPRemoteCommandCenter.shared().previousTrackCommand: [
+ "mpKey": MP_KEY_PREV,
+ "keyType": KeyType.normal
+ ],
+ MPRemoteCommandCenter.shared().togglePlayPauseCommand: [
+ "mpKey": MP_KEY_PLAY,
+ "keyType": KeyType.normal
+ ],
+ MPRemoteCommandCenter.shared().seekForwardCommand: [
+ "mpKey": MP_KEY_FORWARD,
+ "keyType": KeyType.repeatable,
+ "state": MP_KEY_STATE_UP
+ ],
+ MPRemoteCommandCenter.shared().seekBackwardCommand: [
+ "mpKey": MP_KEY_REWIND,
+ "keyType": KeyType.repeatable,
+ "state": MP_KEY_STATE_UP
+ ],
+ ]
+
+ var nowPlayingInfo: [String: Any] = [
+ MPNowPlayingInfoPropertyMediaType: NSNumber(value: MPNowPlayingInfoMediaType.video.rawValue),
+ MPNowPlayingInfoPropertyDefaultPlaybackRate: NSNumber(value: 1),
+ MPNowPlayingInfoPropertyPlaybackProgress: NSNumber(value: 0.0),
+ MPMediaItemPropertyPlaybackDuration: NSNumber(value: 0),
+ MPMediaItemPropertyTitle: "mpv",
+ MPMediaItemPropertyAlbumTitle: "mpv",
+ MPMediaItemPropertyArtist: "mpv",
+ ]
+
+ let disabledCommands: [MPRemoteCommand] = [
+ MPRemoteCommandCenter.shared().changePlaybackRateCommand,
+ MPRemoteCommandCenter.shared().changeRepeatModeCommand,
+ MPRemoteCommandCenter.shared().changeShuffleModeCommand,
+ MPRemoteCommandCenter.shared().skipForwardCommand,
+ MPRemoteCommandCenter.shared().skipBackwardCommand,
+ MPRemoteCommandCenter.shared().changePlaybackPositionCommand,
+ MPRemoteCommandCenter.shared().enableLanguageOptionCommand,
+ MPRemoteCommandCenter.shared().disableLanguageOptionCommand,
+ MPRemoteCommandCenter.shared().ratingCommand,
+ MPRemoteCommandCenter.shared().likeCommand,
+ MPRemoteCommandCenter.shared().dislikeCommand,
+ MPRemoteCommandCenter.shared().bookmarkCommand,
+ ]
+
+ var mpInfoCenter: MPNowPlayingInfoCenter { get { return MPNowPlayingInfoCenter.default() } }
+ var isPaused: Bool = false { didSet { updatePlaybackState() } }
+
+ @objc override init() {
+ super.init()
+
+ for cmd in disabledCommands {
+ cmd.isEnabled = false
+ }
+ }
+
+ @objc func start() {
+ for (cmd, _) in config {
+ cmd.isEnabled = true
+ cmd.addTarget { [unowned self] event in
+ return self.cmdHandler(event)
+ }
+ }
+
+ if let app = NSApp as? Application, let icon = app.getMPVIcon() {
+ let albumArt = MPMediaItemArtwork(boundsSize: icon.size) { _ in
+ return icon
+ }
+ nowPlayingInfo[MPMediaItemPropertyArtwork] = albumArt
+ }
+
+ mpInfoCenter.nowPlayingInfo = nowPlayingInfo
+ mpInfoCenter.playbackState = .playing
+
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(self.makeCurrent),
+ name: NSApplication.willBecomeActiveNotification,
+ object: nil
+ )
+ }
+
+ @objc func stop() {
+ for (cmd, _) in config {
+ cmd.isEnabled = false
+ cmd.removeTarget(nil)
+ }
+
+ mpInfoCenter.nowPlayingInfo = nil
+ mpInfoCenter.playbackState = .unknown
+ }
+
+ @objc func makeCurrent(notification: NSNotification) {
+ mpInfoCenter.playbackState = .paused
+ mpInfoCenter.playbackState = .playing
+ updatePlaybackState()
+ }
+
+ func updatePlaybackState() {
+ mpInfoCenter.playbackState = isPaused ? .paused : .playing
+ }
+
+ func cmdHandler(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
+ guard let cmdConfig = config[event.command],
+ let mpKey = cmdConfig["mpKey"] as? Int32,
+ let keyType = cmdConfig["keyType"] as? KeyType else
+ {
+ return .commandFailed
+ }
+
+ var state = cmdConfig["state"] as? UInt32 ?? 0
+
+ if let currentState = cmdConfig["state"] as? UInt32, keyType == .repeatable {
+ state = MP_KEY_STATE_DOWN
+ config[event.command]?["state"] = MP_KEY_STATE_DOWN
+ if currentState == MP_KEY_STATE_DOWN {
+ state = MP_KEY_STATE_UP
+ config[event.command]?["state"] = MP_KEY_STATE_UP
+ }
+ }
+
+ EventsResponder.sharedInstance().handleMPKey(mpKey, withMask: Int32(state))
+
+ return .success
+ }
+
+ @objc func processEvent(_ event: UnsafeMutablePointer<mpv_event>) {
+ switch event.pointee.event_id {
+ case MPV_EVENT_PROPERTY_CHANGE:
+ handlePropertyChange(event)
+ default:
+ break
+ }
+ }
+
+ func handlePropertyChange(_ event: UnsafeMutablePointer<mpv_event>) {
+ let pData = OpaquePointer(event.pointee.data)
+ guard let property = UnsafePointer<mpv_event_property>(pData)?.pointee else {
+ return
+ }
+
+ switch String(cString: property.name) {
+ case "pause" where property.format == MPV_FORMAT_FLAG:
+ isPaused = LibmpvHelper.mpvFlagToBool(property.data) ?? false
+ default:
+ break
+ }
+ }
+}
diff --git a/osdep/macos/swift_compat.swift b/osdep/macos/swift_compat.swift
new file mode 100644
index 0000000..83059da
--- /dev/null
+++ b/osdep/macos/swift_compat.swift
@@ -0,0 +1,36 @@
+/*
+ * 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/>.
+ */
+
+
+#if !swift(>=5.0)
+extension Data {
+ mutating func withUnsafeMutableBytes<Type>(_ body: (UnsafeMutableRawBufferPointer) throws -> Type) rethrows -> Type {
+ let dataCount = count
+ return try withUnsafeMutableBytes { (ptr: UnsafeMutablePointer<UInt8>) throws -> Type in
+ try body(UnsafeMutableRawBufferPointer(start: ptr, count: dataCount))
+ }
+ }
+}
+#endif
+
+#if !swift(>=4.2)
+extension NSDraggingInfo {
+ var draggingPasteboard: NSPasteboard {
+ get { return draggingPasteboard() }
+ }
+}
+#endif
diff --git a/osdep/macos/swift_extensions.swift b/osdep/macos/swift_extensions.swift
new file mode 100644
index 0000000..127c568
--- /dev/null
+++ b/osdep/macos/swift_extensions.swift
@@ -0,0 +1,58 @@
+/*
+ * 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
+
+extension NSDeviceDescriptionKey {
+ static let screenNumber = NSDeviceDescriptionKey("NSScreenNumber")
+}
+
+extension NSScreen {
+
+ public var displayID: CGDirectDisplayID {
+ get {
+ return deviceDescription[.screenNumber] as? CGDirectDisplayID ?? 0
+ }
+ }
+}
+
+extension NSColor {
+
+ convenience init(hex: String) {
+ 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)
+ }
+}
+
+extension Bool {
+
+ init(_ int32: Int32) {
+ self.init(int32 != 0)
+ }
+}
+
+extension Int32 {
+
+ init(_ bool: Bool) {
+ self.init(bool ? 1 : 0)
+ }
+}
diff --git a/osdep/macosx_application.h b/osdep/macosx_application.h
new file mode 100644
index 0000000..753b9f0
--- /dev/null
+++ b/osdep/macosx_application.h
@@ -0,0 +1,55 @@
+/*
+ * 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/>.
+ */
+
+#ifndef MPV_MACOSX_APPLICATION
+#define MPV_MACOSX_APPLICATION
+
+#include "osdep/macosx_menubar.h"
+#include "options/m_option.h"
+
+enum {
+ FRAME_VISIBLE = 0,
+ FRAME_WHOLE,
+};
+
+enum {
+ RENDER_TIMER_CALLBACK = 0,
+ RENDER_TIMER_PRECISE,
+ RENDER_TIMER_SYSTEM,
+};
+
+struct macos_opts {
+ int macos_title_bar_style;
+ int macos_title_bar_appearance;
+ int macos_title_bar_material;
+ struct m_color macos_title_bar_color;
+ int macos_fs_animation_duration;
+ bool macos_force_dedicated_gpu;
+ int macos_app_activation_policy;
+ int macos_geometry_calculation;
+ int macos_render_timer;
+ int cocoa_cb_sw_renderer;
+ bool cocoa_cb_10bit_context;
+};
+
+// multithreaded wrapper for mpv_main
+int cocoa_main(int argc, char *argv[]);
+void cocoa_register_menu_item_action(MPMenuKey key, void* action);
+
+extern const struct m_sub_options macos_conf;
+
+#endif /* MPV_MACOSX_APPLICATION */
diff --git a/osdep/macosx_application.m b/osdep/macosx_application.m
new file mode 100644
index 0000000..73503ad
--- /dev/null
+++ b/osdep/macosx_application.m
@@ -0,0 +1,375 @@
+/*
+ * 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/>.
+ */
+
+#include <stdio.h>
+#include "config.h"
+#include "mpv_talloc.h"
+
+#include "common/msg.h"
+#include "input/input.h"
+#include "player/client.h"
+#include "options/m_config.h"
+#include "options/options.h"
+
+#import "osdep/macosx_application_objc.h"
+#import "osdep/macosx_events_objc.h"
+#include "osdep/threads.h"
+#include "osdep/main-fn.h"
+
+#if HAVE_MACOS_TOUCHBAR
+#import "osdep/macosx_touchbar.h"
+#endif
+#if HAVE_MACOS_COCOA_CB
+#include "osdep/macOS_swift.h"
+#endif
+
+#define MPV_PROTOCOL @"mpv://"
+
+#define OPT_BASE_STRUCT struct macos_opts
+const struct m_sub_options macos_conf = {
+ .opts = (const struct m_option[]) {
+ {"macos-title-bar-appearance", OPT_CHOICE(macos_title_bar_appearance,
+ {"auto", 0}, {"aqua", 1}, {"darkAqua", 2},
+ {"vibrantLight", 3}, {"vibrantDark", 4},
+ {"aquaHighContrast", 5}, {"darkAquaHighContrast", 6},
+ {"vibrantLightHighContrast", 7},
+ {"vibrantDarkHighContrast", 8})},
+ {"macos-title-bar-material", OPT_CHOICE(macos_title_bar_material,
+ {"titlebar", 0}, {"selection", 1}, {"menu", 2},
+ {"popover", 3}, {"sidebar", 4}, {"headerView", 5},
+ {"sheet", 6}, {"windowBackground", 7}, {"hudWindow", 8},
+ {"fullScreen", 9}, {"toolTip", 10}, {"contentBackground", 11},
+ {"underWindowBackground", 12}, {"underPageBackground", 13},
+ {"dark", 14}, {"light", 15}, {"mediumLight", 16},
+ {"ultraDark", 17})},
+ {"macos-title-bar-color", OPT_COLOR(macos_title_bar_color)},
+ {"macos-fs-animation-duration",
+ OPT_CHOICE(macos_fs_animation_duration, {"default", -1}),
+ M_RANGE(0, 1000)},
+ {"macos-force-dedicated-gpu", OPT_BOOL(macos_force_dedicated_gpu)},
+ {"macos-app-activation-policy", OPT_CHOICE(macos_app_activation_policy,
+ {"regular", 0}, {"accessory", 1}, {"prohibited", 2})},
+ {"macos-geometry-calculation", OPT_CHOICE(macos_geometry_calculation,
+ {"visible", FRAME_VISIBLE}, {"whole", FRAME_WHOLE})},
+ {"macos-render-timer", OPT_CHOICE(macos_render_timer,
+ {"callback", RENDER_TIMER_CALLBACK}, {"precise", RENDER_TIMER_PRECISE},
+ {"system", RENDER_TIMER_SYSTEM})},
+ {"cocoa-cb-sw-renderer", OPT_CHOICE(cocoa_cb_sw_renderer,
+ {"auto", -1}, {"no", 0}, {"yes", 1})},
+ {"cocoa-cb-10bit-context", OPT_BOOL(cocoa_cb_10bit_context)},
+ {0}
+ },
+ .size = sizeof(struct macos_opts),
+ .defaults = &(const struct macos_opts){
+ .macos_title_bar_color = {0, 0, 0, 0},
+ .macos_fs_animation_duration = -1,
+ .cocoa_cb_sw_renderer = -1,
+ .cocoa_cb_10bit_context = true
+ },
+};
+
+// Whether the NSApplication singleton was created. If this is false, we are
+// running in libmpv mode, and cocoa_main() was never called.
+static bool application_instantiated;
+
+static mp_thread playback_thread_id;
+
+@interface Application ()
+{
+ EventsResponder *_eventsResponder;
+}
+
+@end
+
+static Application *mpv_shared_app(void)
+{
+ return (Application *)[Application sharedApplication];
+}
+
+static void terminate_cocoa_application(void)
+{
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [NSApp hide:NSApp];
+ [NSApp terminate:NSApp];
+ });
+}
+
+@implementation Application
+@synthesize menuBar = _menu_bar;
+@synthesize openCount = _open_count;
+@synthesize cocoaCB = _cocoa_cb;
+
+- (void)sendEvent:(NSEvent *)event
+{
+ if ([self modalWindow] || ![_eventsResponder processKeyEvent:event])
+ [super sendEvent:event];
+ [_eventsResponder wakeup];
+}
+
+- (id)init
+{
+ if (self = [super init]) {
+ _eventsResponder = [EventsResponder sharedInstance];
+
+ NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager];
+ [em setEventHandler:self
+ andSelector:@selector(getUrl:withReplyEvent:)
+ forEventClass:kInternetEventClass
+ andEventID:kAEGetURL];
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager];
+ [em removeEventHandlerForEventClass:kInternetEventClass
+ andEventID:kAEGetURL];
+ [em removeEventHandlerForEventClass:kCoreEventClass
+ andEventID:kAEQuitApplication];
+ [super dealloc];
+}
+
+static const char macosx_icon[] =
+#include "TOOLS/osxbundle/icon.icns.inc"
+;
+
+- (NSImage *)getMPVIcon
+{
+ // The C string contains a trailing null, so we strip it away
+ NSData *icon_data = [NSData dataWithBytesNoCopy:(void *)macosx_icon
+ length:sizeof(macosx_icon) - 1
+ freeWhenDone:NO];
+ return [[NSImage alloc] initWithData:icon_data];
+}
+
+#if HAVE_MACOS_TOUCHBAR
+- (NSTouchBar *)makeTouchBar
+{
+ TouchBar *tBar = [[TouchBar alloc] init];
+ [tBar setApp:self];
+ tBar.delegate = tBar;
+ tBar.customizationIdentifier = customID;
+ tBar.defaultItemIdentifiers = @[play, previousItem, nextItem, seekBar];
+ tBar.customizationAllowedItemIdentifiers = @[play, seekBar, previousItem,
+ nextItem, previousChapter, nextChapter, cycleAudio, cycleSubtitle,
+ currentPosition, timeLeft];
+ return tBar;
+}
+#endif
+
+- (void)processEvent:(struct mpv_event *)event
+{
+#if HAVE_MACOS_TOUCHBAR
+ [(TouchBar *)self.touchBar processEvent:event];
+#endif
+ if (_cocoa_cb) {
+ [_cocoa_cb processEvent:event];
+ }
+}
+
+- (void)setMpvHandle:(struct mpv_handle *)ctx
+{
+#if HAVE_MACOS_COCOA_CB
+ [NSApp setCocoaCB:[[CocoaCB alloc] init:ctx]];
+#endif
+}
+
+- (const struct m_sub_options *)getMacOSConf
+{
+ return &macos_conf;
+}
+
+- (const struct m_sub_options *)getVoSubConf
+{
+ return &vo_sub_opts;
+}
+
+- (void)queueCommand:(char *)cmd
+{
+ [_eventsResponder queueCommand:cmd];
+}
+
+- (void)stopMPV:(char *)cmd
+{
+ if (![_eventsResponder queueCommand:cmd])
+ terminate_cocoa_application();
+}
+
+- (void)applicationWillFinishLaunching:(NSNotification *)notification
+{
+ NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager];
+ [em setEventHandler:self
+ andSelector:@selector(handleQuitEvent:withReplyEvent:)
+ forEventClass:kCoreEventClass
+ andEventID:kAEQuitApplication];
+}
+
+- (void)handleQuitEvent:(NSAppleEventDescriptor *)event
+ withReplyEvent:(NSAppleEventDescriptor *)replyEvent
+{
+ [self stopMPV:"quit"];
+}
+
+- (void)getUrl:(NSAppleEventDescriptor *)event
+ withReplyEvent:(NSAppleEventDescriptor *)replyEvent
+{
+ NSString *url =
+ [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
+
+ url = [url stringByReplacingOccurrencesOfString:MPV_PROTOCOL
+ withString:@""
+ options:NSAnchoredSearch
+ range:NSMakeRange(0, [MPV_PROTOCOL length])];
+
+ url = [url stringByRemovingPercentEncoding];
+ [_eventsResponder handleFilesArray:@[url]];
+}
+
+- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
+{
+ if (mpv_shared_app().openCount > 0) {
+ mpv_shared_app().openCount--;
+ return;
+ }
+ [self openFiles:filenames];
+}
+
+- (void)openFiles:(NSArray *)filenames
+{
+ SEL cmpsel = @selector(localizedStandardCompare:);
+ NSArray *files = [filenames sortedArrayUsingSelector:cmpsel];
+ [_eventsResponder handleFilesArray:files];
+}
+@end
+
+struct playback_thread_ctx {
+ int *argc;
+ char ***argv;
+};
+
+static void cocoa_run_runloop(void)
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [NSApp run];
+ [pool drain];
+}
+
+static MP_THREAD_VOID playback_thread(void *ctx_obj)
+{
+ mp_thread_set_name("core/playback");
+ @autoreleasepool {
+ struct playback_thread_ctx *ctx = (struct playback_thread_ctx*) ctx_obj;
+ int r = mpv_main(*ctx->argc, *ctx->argv);
+ terminate_cocoa_application();
+ // normally never reached - unless the cocoa mainloop hasn't started yet
+ exit(r);
+ }
+}
+
+void cocoa_register_menu_item_action(MPMenuKey key, void* action)
+{
+ if (application_instantiated)
+ [[NSApp menuBar] registerSelector:(SEL)action forKey:key];
+}
+
+static void init_cocoa_application(bool regular)
+{
+ NSApp = mpv_shared_app();
+ [NSApp setDelegate:NSApp];
+ [NSApp setMenuBar:[[MenuBar alloc] init]];
+
+ // Will be set to Regular from cocoa_common during UI creation so that we
+ // don't create an icon when playing audio only files.
+ [NSApp setActivationPolicy: regular ?
+ NSApplicationActivationPolicyRegular :
+ NSApplicationActivationPolicyAccessory];
+
+ atexit_b(^{
+ // Because activation policy has just been set to behave like a real
+ // application, that policy must be reset on exit to prevent, among
+ // other things, the menubar created here from remaining on screen.
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
+ });
+ });
+}
+
+static bool bundle_started_from_finder()
+{
+ NSString* bundle = [[[NSProcessInfo processInfo] environment] objectForKey:@"MPVBUNDLE"];
+ return [bundle isEqual:@"true"];
+}
+
+static bool is_psn_argument(char *arg_to_check)
+{
+ NSString *arg = [NSString stringWithUTF8String:arg_to_check];
+ return [arg hasPrefix:@"-psn_"];
+}
+
+static void setup_bundle(int *argc, char *argv[])
+{
+ if (*argc > 1 && is_psn_argument(argv[1])) {
+ *argc = 1;
+ argv[1] = NULL;
+ }
+
+ NSDictionary *env = [[NSProcessInfo processInfo] environment];
+ NSString *path_bundle = [env objectForKey:@"PATH"];
+ NSString *path_new = [NSString stringWithFormat:@"%@:%@:%@:%@:%@",
+ path_bundle,
+ @"/usr/local/bin",
+ @"/usr/local/sbin",
+ @"/opt/local/bin",
+ @"/opt/local/sbin"];
+ setenv("PATH", [path_new UTF8String], 1);
+}
+
+int cocoa_main(int argc, char *argv[])
+{
+ @autoreleasepool {
+ application_instantiated = true;
+ [[EventsResponder sharedInstance] setIsApplication:YES];
+
+ struct playback_thread_ctx ctx = {0};
+ ctx.argc = &argc;
+ ctx.argv = &argv;
+
+ if (bundle_started_from_finder()) {
+ setup_bundle(&argc, argv);
+ init_cocoa_application(true);
+ } else {
+ for (int i = 1; i < argc; i++)
+ if (argv[i][0] != '-')
+ mpv_shared_app().openCount++;
+ init_cocoa_application(false);
+ }
+
+ mp_thread_create(&playback_thread_id, playback_thread, &ctx);
+ [[EventsResponder sharedInstance] waitForInputContext];
+ cocoa_run_runloop();
+
+ // This should never be reached: cocoa_run_runloop blocks until the
+ // process is quit
+ fprintf(stderr, "There was either a problem "
+ "initializing Cocoa or the Runloop was stopped unexpectedly. "
+ "Please report this issues to a developer.\n");
+ mp_thread_join(playback_thread_id);
+ return 1;
+ }
+}
diff --git a/osdep/macosx_application_objc.h b/osdep/macosx_application_objc.h
new file mode 100644
index 0000000..11959a8
--- /dev/null
+++ b/osdep/macosx_application_objc.h
@@ -0,0 +1,40 @@
+/*
+ * 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/Cocoa.h>
+#include "osdep/macosx_application.h"
+#import "osdep/macosx_menubar_objc.h"
+
+@class CocoaCB;
+struct mpv_event;
+struct mpv_handle;
+
+@interface Application : NSApplication
+
+- (NSImage *)getMPVIcon;
+- (void)processEvent:(struct mpv_event *)event;
+- (void)queueCommand:(char *)cmd;
+- (void)stopMPV:(char *)cmd;
+- (void)openFiles:(NSArray *)filenames;
+- (void)setMpvHandle:(struct mpv_handle *)ctx;
+- (const struct m_sub_options *)getMacOSConf;
+- (const struct m_sub_options *)getVoSubConf;
+
+@property(nonatomic, retain) MenuBar *menuBar;
+@property(nonatomic, assign) size_t openCount;
+@property(nonatomic, retain) CocoaCB *cocoaCB;
+@end
diff --git a/osdep/macosx_events.h b/osdep/macosx_events.h
new file mode 100644
index 0000000..9188c8b
--- /dev/null
+++ b/osdep/macosx_events.h
@@ -0,0 +1,36 @@
+/*
+ * Cocoa Application Event Handling
+ *
+ * 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/>.
+ */
+
+#ifndef MACOSX_EVENTS_H
+#define MACOSX_EVENTS_H
+#include "input/keycodes.h"
+
+struct input_ctx;
+struct mpv_handle;
+
+void cocoa_put_key(int keycode);
+void cocoa_put_key_with_modifiers(int keycode, int modifiers);
+
+void cocoa_init_media_keys(void);
+void cocoa_uninit_media_keys(void);
+
+void cocoa_set_input_context(struct input_ctx *input_context);
+void cocoa_set_mpv_handle(struct mpv_handle *ctx);
+
+#endif
diff --git a/osdep/macosx_events.m b/osdep/macosx_events.m
new file mode 100644
index 0000000..627077a
--- /dev/null
+++ b/osdep/macosx_events.m
@@ -0,0 +1,408 @@
+/*
+ * Cocoa Application Event Handling
+ *
+ * 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/>.
+ */
+
+// Carbon header is included but Carbon is NOT linked to mpv's binary. This
+// file only needs this include to use the keycode definitions in keymap.
+#import <Carbon/Carbon.h>
+
+// Media keys definitions
+#import <IOKit/hidsystem/ev_keymap.h>
+#import <Cocoa/Cocoa.h>
+
+#include "mpv_talloc.h"
+#include "input/event.h"
+#include "input/input.h"
+#include "player/client.h"
+#include "input/keycodes.h"
+// doesn't make much sense, but needed to access keymap functionality
+#include "video/out/vo.h"
+
+#import "osdep/macosx_events_objc.h"
+#import "osdep/macosx_application_objc.h"
+
+#include "config.h"
+
+#if HAVE_MACOS_COCOA_CB
+#include "osdep/macOS_swift.h"
+#endif
+
+@interface EventsResponder ()
+{
+ struct input_ctx *_inputContext;
+ struct mpv_handle *_ctx;
+ BOOL _is_application;
+ NSCondition *_input_lock;
+}
+
+- (NSEvent *)handleKey:(NSEvent *)event;
+- (BOOL)setMpvHandle:(struct mpv_handle *)ctx;
+- (void)readEvents;
+- (void)startMediaKeys;
+- (void)stopMediaKeys;
+- (int)mapKeyModifiers:(int)cocoaModifiers;
+- (int)keyModifierMask:(NSEvent *)event;
+@end
+
+
+#define NSLeftAlternateKeyMask (0x000020 | NSEventModifierFlagOption)
+#define NSRightAlternateKeyMask (0x000040 | NSEventModifierFlagOption)
+
+static bool LeftAltPressed(int mask)
+{
+ return (mask & NSLeftAlternateKeyMask) == NSLeftAlternateKeyMask;
+}
+
+static bool RightAltPressed(int mask)
+{
+ return (mask & NSRightAlternateKeyMask) == NSRightAlternateKeyMask;
+}
+
+static const struct mp_keymap keymap[] = {
+ // special keys
+ {kVK_Return, MP_KEY_ENTER}, {kVK_Escape, MP_KEY_ESC},
+ {kVK_Delete, MP_KEY_BACKSPACE}, {kVK_Option, MP_KEY_BACKSPACE},
+ {kVK_Control, MP_KEY_BACKSPACE}, {kVK_Shift, MP_KEY_BACKSPACE},
+ {kVK_Tab, MP_KEY_TAB},
+
+ // cursor keys
+ {kVK_UpArrow, MP_KEY_UP}, {kVK_DownArrow, MP_KEY_DOWN},
+ {kVK_LeftArrow, MP_KEY_LEFT}, {kVK_RightArrow, MP_KEY_RIGHT},
+
+ // navigation block
+ {kVK_Help, MP_KEY_INSERT}, {kVK_ForwardDelete, MP_KEY_DELETE},
+ {kVK_Home, MP_KEY_HOME}, {kVK_End, MP_KEY_END},
+ {kVK_PageUp, MP_KEY_PAGE_UP}, {kVK_PageDown, MP_KEY_PAGE_DOWN},
+
+ // F-keys
+ {kVK_F1, MP_KEY_F + 1}, {kVK_F2, MP_KEY_F + 2}, {kVK_F3, MP_KEY_F + 3},
+ {kVK_F4, MP_KEY_F + 4}, {kVK_F5, MP_KEY_F + 5}, {kVK_F6, MP_KEY_F + 6},
+ {kVK_F7, MP_KEY_F + 7}, {kVK_F8, MP_KEY_F + 8}, {kVK_F9, MP_KEY_F + 9},
+ {kVK_F10, MP_KEY_F + 10}, {kVK_F11, MP_KEY_F + 11}, {kVK_F12, MP_KEY_F + 12},
+ {kVK_F13, MP_KEY_F + 13}, {kVK_F14, MP_KEY_F + 14}, {kVK_F15, MP_KEY_F + 15},
+ {kVK_F16, MP_KEY_F + 16}, {kVK_F17, MP_KEY_F + 17}, {kVK_F18, MP_KEY_F + 18},
+ {kVK_F19, MP_KEY_F + 19}, {kVK_F20, MP_KEY_F + 20},
+
+ // numpad
+ {kVK_ANSI_KeypadPlus, '+'}, {kVK_ANSI_KeypadMinus, '-'},
+ {kVK_ANSI_KeypadMultiply, '*'}, {kVK_ANSI_KeypadDivide, '/'},
+ {kVK_ANSI_KeypadEnter, MP_KEY_KPENTER},
+ {kVK_ANSI_KeypadDecimal, MP_KEY_KPDEC},
+ {kVK_ANSI_Keypad0, MP_KEY_KP0}, {kVK_ANSI_Keypad1, MP_KEY_KP1},
+ {kVK_ANSI_Keypad2, MP_KEY_KP2}, {kVK_ANSI_Keypad3, MP_KEY_KP3},
+ {kVK_ANSI_Keypad4, MP_KEY_KP4}, {kVK_ANSI_Keypad5, MP_KEY_KP5},
+ {kVK_ANSI_Keypad6, MP_KEY_KP6}, {kVK_ANSI_Keypad7, MP_KEY_KP7},
+ {kVK_ANSI_Keypad8, MP_KEY_KP8}, {kVK_ANSI_Keypad9, MP_KEY_KP9},
+
+ {0, 0}
+};
+
+static int convert_key(unsigned key, unsigned charcode)
+{
+ int mpkey = lookup_keymap_table(keymap, key);
+ if (mpkey)
+ return mpkey;
+ return charcode;
+}
+
+void cocoa_init_media_keys(void)
+{
+ [[EventsResponder sharedInstance] startMediaKeys];
+}
+
+void cocoa_uninit_media_keys(void)
+{
+ [[EventsResponder sharedInstance] stopMediaKeys];
+}
+
+void cocoa_put_key(int keycode)
+{
+ [[EventsResponder sharedInstance] putKey:keycode];
+}
+
+void cocoa_put_key_with_modifiers(int keycode, int modifiers)
+{
+ keycode |= [[EventsResponder sharedInstance] mapKeyModifiers:modifiers];
+ cocoa_put_key(keycode);
+}
+
+void cocoa_set_input_context(struct input_ctx *input_context)
+{
+ [[EventsResponder sharedInstance] setInputContext:input_context];
+}
+
+static void wakeup(void *context)
+{
+ [[EventsResponder sharedInstance] readEvents];
+}
+
+void cocoa_set_mpv_handle(struct mpv_handle *ctx)
+{
+ if ([[EventsResponder sharedInstance] setMpvHandle:ctx]) {
+ mpv_observe_property(ctx, 0, "duration", MPV_FORMAT_DOUBLE);
+ mpv_observe_property(ctx, 0, "time-pos", MPV_FORMAT_DOUBLE);
+ mpv_observe_property(ctx, 0, "pause", MPV_FORMAT_FLAG);
+ mpv_set_wakeup_callback(ctx, wakeup, NULL);
+ }
+}
+
+@implementation EventsResponder
+
+@synthesize remoteCommandCenter = _remoteCommandCenter;
+
++ (EventsResponder *)sharedInstance
+{
+ static EventsResponder *responder = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ responder = [EventsResponder new];
+ });
+ return responder;
+}
+
+- (id)init
+{
+ self = [super init];
+ if (self) {
+ _input_lock = [NSCondition new];
+ }
+ return self;
+}
+
+- (void)waitForInputContext
+{
+ [_input_lock lock];
+ while (!_inputContext)
+ [_input_lock wait];
+ [_input_lock unlock];
+}
+
+- (void)setInputContext:(struct input_ctx *)ctx
+{
+ [_input_lock lock];
+ _inputContext = ctx;
+ [_input_lock signal];
+ [_input_lock unlock];
+}
+
+- (void)wakeup
+{
+ [_input_lock lock];
+ if (_inputContext)
+ mp_input_wakeup(_inputContext);
+ [_input_lock unlock];
+}
+
+- (bool)queueCommand:(char *)cmd
+{
+ bool r = false;
+ [_input_lock lock];
+ if (_inputContext) {
+ mp_cmd_t *cmdt = mp_input_parse_cmd(_inputContext, bstr0(cmd), "");
+ mp_input_queue_cmd(_inputContext, cmdt);
+ r = true;
+ }
+ [_input_lock unlock];
+ return r;
+}
+
+- (void)putKey:(int)keycode
+{
+ [_input_lock lock];
+ if (_inputContext)
+ mp_input_put_key(_inputContext, keycode);
+ [_input_lock unlock];
+}
+
+- (BOOL)useAltGr
+{
+ BOOL r = YES;
+ [_input_lock lock];
+ if (_inputContext)
+ r = mp_input_use_alt_gr(_inputContext);
+ [_input_lock unlock];
+ return r;
+}
+
+- (void)setIsApplication:(BOOL)isApplication
+{
+ _is_application = isApplication;
+}
+
+- (BOOL)setMpvHandle:(struct mpv_handle *)ctx
+{
+ if (_is_application) {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ _ctx = ctx;
+ [NSApp setMpvHandle:ctx];
+ });
+ return YES;
+ } else {
+ mpv_destroy(ctx);
+ return NO;
+ }
+}
+
+- (void)readEvents
+{
+ dispatch_async(dispatch_get_main_queue(), ^{
+ while (_ctx) {
+ mpv_event *event = mpv_wait_event(_ctx, 0);
+ if (event->event_id == MPV_EVENT_NONE)
+ break;
+ [self processEvent:event];
+ }
+ });
+}
+
+-(void)processEvent:(struct mpv_event *)event
+{
+ if(_is_application) {
+ [NSApp processEvent:event];
+ }
+
+ if (_remoteCommandCenter) {
+ [_remoteCommandCenter processEvent:event];
+ }
+
+ switch (event->event_id) {
+ case MPV_EVENT_SHUTDOWN: {
+#if HAVE_MACOS_COCOA_CB
+ if ([(Application *)NSApp cocoaCB].isShuttingDown) {
+ _ctx = nil;
+ return;
+ }
+#endif
+ mpv_destroy(_ctx);
+ _ctx = nil;
+ break;
+ }
+ }
+}
+
+- (void)startMediaKeys
+{
+#if HAVE_MACOS_MEDIA_PLAYER
+ if (_remoteCommandCenter == nil) {
+ _remoteCommandCenter = [[RemoteCommandCenter alloc] init];
+ }
+#endif
+
+ [_remoteCommandCenter start];
+}
+
+- (void)stopMediaKeys
+{
+ [_remoteCommandCenter stop];
+}
+
+- (int)mapKeyModifiers:(int)cocoaModifiers
+{
+ int mask = 0;
+ if (cocoaModifiers & NSEventModifierFlagShift)
+ mask |= MP_KEY_MODIFIER_SHIFT;
+ if (cocoaModifiers & NSEventModifierFlagControl)
+ mask |= MP_KEY_MODIFIER_CTRL;
+ if (LeftAltPressed(cocoaModifiers) ||
+ (RightAltPressed(cocoaModifiers) && ![self useAltGr]))
+ mask |= MP_KEY_MODIFIER_ALT;
+ if (cocoaModifiers & NSEventModifierFlagCommand)
+ mask |= MP_KEY_MODIFIER_META;
+ return mask;
+}
+
+- (int)mapTypeModifiers:(NSEventType)type
+{
+ NSDictionary *map = @{
+ @(NSEventTypeKeyDown) : @(MP_KEY_STATE_DOWN),
+ @(NSEventTypeKeyUp) : @(MP_KEY_STATE_UP),
+ };
+ return [map[@(type)] intValue];
+}
+
+- (int)keyModifierMask:(NSEvent *)event
+{
+ return [self mapKeyModifiers:[event modifierFlags]] |
+ [self mapTypeModifiers:[event type]];
+}
+
+-(BOOL)handleMPKey:(int)key withMask:(int)mask
+{
+ if (key > 0) {
+ cocoa_put_key(key | mask);
+ if (mask & MP_KEY_STATE_UP)
+ cocoa_put_key(MP_INPUT_RELEASE_ALL);
+ return YES;
+ } else {
+ return NO;
+ }
+}
+
+- (NSEvent*)handleKey:(NSEvent *)event
+{
+ if ([event isARepeat]) return nil;
+
+ NSString *chars;
+
+ if ([self useAltGr] && RightAltPressed([event modifierFlags])) {
+ chars = [event characters];
+ } else {
+ chars = [event charactersIgnoringModifiers];
+ }
+
+ struct bstr t = bstr0([chars UTF8String]);
+ int key = convert_key([event keyCode], bstr_decode_utf8(t, &t));
+
+ if (key > -1)
+ [self handleMPKey:key withMask:[self keyModifierMask:event]];
+
+ return nil;
+}
+
+- (bool)processKeyEvent:(NSEvent *)event
+{
+ if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp){
+ if (![[NSApp mainMenu] performKeyEquivalent:event])
+ [self handleKey:event];
+ return true;
+ }
+ return false;
+}
+
+- (void)handleFilesArray:(NSArray *)files
+{
+ enum mp_dnd_action action = [NSEvent modifierFlags] &
+ NSEventModifierFlagShift ? DND_APPEND : DND_REPLACE;
+
+ size_t num_files = [files count];
+ char **files_utf8 = talloc_array(NULL, char*, num_files);
+ [files enumerateObjectsUsingBlock:^(NSString *p, NSUInteger i, BOOL *_){
+ if ([p hasPrefix:@"file:///.file/id="])
+ p = [[NSURL URLWithString:p] path];
+ char *filename = (char *)[p UTF8String];
+ size_t bytes = [p lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+ files_utf8[i] = talloc_memdup(files_utf8, filename, bytes + 1);
+ }];
+ [_input_lock lock];
+ if (_inputContext)
+ mp_event_drop_files(_inputContext, num_files, files_utf8, action);
+ [_input_lock unlock];
+ talloc_free(files_utf8);
+}
+
+@end
diff --git a/osdep/macosx_events_objc.h b/osdep/macosx_events_objc.h
new file mode 100644
index 0000000..9394fe7
--- /dev/null
+++ b/osdep/macosx_events_objc.h
@@ -0,0 +1,45 @@
+/*
+ * Cocoa Application Event Handling
+ *
+ * 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/Cocoa.h>
+#include "osdep/macosx_events.h"
+
+@class RemoteCommandCenter;
+struct input_ctx;
+
+@interface EventsResponder : NSObject
+
++ (EventsResponder *)sharedInstance;
+- (void)setInputContext:(struct input_ctx *)ctx;
+- (void)setIsApplication:(BOOL)isApplication;
+
+/// Blocks until inputContext is present.
+- (void)waitForInputContext;
+- (void)wakeup;
+- (void)putKey:(int)keycode;
+- (void)handleFilesArray:(NSArray *)files;
+
+- (bool)queueCommand:(char *)cmd;
+- (bool)processKeyEvent:(NSEvent *)event;
+
+- (BOOL)handleMPKey:(int)key withMask:(int)mask;
+
+@property(nonatomic, retain) RemoteCommandCenter *remoteCommandCenter;
+
+@end
diff --git a/osdep/macosx_menubar.h b/osdep/macosx_menubar.h
new file mode 100644
index 0000000..509083d
--- /dev/null
+++ b/osdep/macosx_menubar.h
@@ -0,0 +1,30 @@
+/*
+ * 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/>.
+ */
+
+#ifndef MPV_MACOSX_MENU
+#define MPV_MACOSX_MENU
+
+// Menu Keys identifying menu items
+typedef enum {
+ MPM_H_SIZE,
+ MPM_N_SIZE,
+ MPM_D_SIZE,
+ MPM_MINIMIZE,
+ MPM_ZOOM,
+} MPMenuKey;
+
+#endif /* MPV_MACOSX_MENU */
diff --git a/osdep/macosx_menubar.m b/osdep/macosx_menubar.m
new file mode 100644
index 0000000..5c6cd47
--- /dev/null
+++ b/osdep/macosx_menubar.m
@@ -0,0 +1,853 @@
+/*
+ * 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/>.
+ */
+
+#include "config.h"
+#include "common/common.h"
+
+#import "macosx_menubar_objc.h"
+#import "osdep/macosx_application_objc.h"
+
+@implementation MenuBar
+{
+ NSArray *menuTree;
+}
+
+- (id)init
+{
+ if (self = [super init]) {
+ NSUserDefaults *userDefaults =[NSUserDefaults standardUserDefaults];
+ [userDefaults setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"];
+ [userDefaults setBool:YES forKey:@"NSDisabledDictationMenuItem"];
+ [userDefaults setBool:YES forKey:@"NSDisabledCharacterPaletteMenuItem"];
+ [NSWindow setAllowsAutomaticWindowTabbing: NO];
+
+ menuTree = @[
+ @{
+ @"name": @"Apple",
+ @"menu": @[
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"About mpv",
+ @"action" : @"about",
+ @"key" : @"",
+ @"target" : self
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Preferences…",
+ @"action" : @"preferences:",
+ @"key" : @",",
+ @"target" : self,
+ @"file" : @"mpv.conf",
+ @"alertTitle1": @"No Application found to open your config file.",
+ @"alertText1" : @"Please open the mpv.conf file with "
+ "your preferred text editor in the now "
+ "open folder to edit your config.",
+ @"alertTitle2": @"No config file found.",
+ @"alertText2" : @"Please create a mpv.conf file with your "
+ "preferred text editor in the now open folder.",
+ @"alertTitle3": @"No config path or file found.",
+ @"alertText3" : @"Please create the following path ~/.config/mpv/ "
+ "and a mpv.conf file within with your preferred "
+ "text editor."
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Keyboard Shortcuts Config…",
+ @"action" : @"preferences:",
+ @"key" : @"",
+ @"target" : self,
+ @"file" : @"input.conf",
+ @"alertTitle1": @"No Application found to open your config file.",
+ @"alertText1" : @"Please open the input.conf file with "
+ "your preferred text editor in the now "
+ "open folder to edit your config.",
+ @"alertTitle2": @"No config file found.",
+ @"alertText2" : @"Please create a input.conf file with your "
+ "preferred text editor in the now open folder.",
+ @"alertTitle3": @"No config path or file found.",
+ @"alertText3" : @"Please create the following path ~/.config/mpv/ "
+ "and a input.conf file within with your preferred "
+ "text editor."
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Services",
+ @"key" : @"",
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Hide mpv",
+ @"action" : @"hide:",
+ @"key" : @"h",
+ @"target" : NSApp
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Hide Others",
+ @"action" : @"hideOtherApplications:",
+ @"key" : @"h",
+ @"modifiers" : [NSNumber numberWithUnsignedInteger:
+ NSEventModifierFlagCommand |
+ NSEventModifierFlagOption],
+ @"target" : NSApp
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Show All",
+ @"action" : @"unhideAllApplications:",
+ @"key" : @"",
+ @"target" : NSApp
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Quit and Remember Position",
+ @"action" : @"quit:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"quit-watch-later"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Quit mpv",
+ @"action" : @"quit:",
+ @"key" : @"q",
+ @"target" : self,
+ @"cmd" : @"quit"
+ }]
+ ]
+ },
+ @{
+ @"name": @"File",
+ @"menu": @[
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Open File…",
+ @"action" : @"openFile",
+ @"key" : @"o",
+ @"target" : self
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Open URL…",
+ @"action" : @"openURL",
+ @"key" : @"O",
+ @"target" : self
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Open Playlist…",
+ @"action" : @"openPlaylist",
+ @"key" : @"",
+ @"target" : self
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Close",
+ @"action" : @"performClose:",
+ @"key" : @"w"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Save Screenshot",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"async screenshot"
+ }]
+ ]
+ },
+ @{
+ @"name": @"Edit",
+ @"menu": @[
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Undo",
+ @"action" : @"undo:",
+ @"key" : @"z"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Redo",
+ @"action" : @"redo:",
+ @"key" : @"Z"
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Cut",
+ @"action" : @"cut:",
+ @"key" : @"x"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Copy",
+ @"action" : @"copy:",
+ @"key" : @"c"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Paste",
+ @"action" : @"paste:",
+ @"key" : @"v"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Select All",
+ @"action" : @"selectAll:",
+ @"key" : @"a"
+ }]
+ ]
+ },
+ @{
+ @"name": @"View",
+ @"menu": @[
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Toggle Fullscreen",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"cycle fullscreen"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Toggle Float on Top",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"cycle ontop"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Toggle Visibility on All Workspaces",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"cycle on-all-workspaces"
+ }],
+#if HAVE_MACOS_TOUCHBAR
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Customize Touch Bar…",
+ @"action" : @"toggleTouchBarCustomizationPalette:",
+ @"key" : @"",
+ @"target" : NSApp
+ }]
+#endif
+ ]
+ },
+ @{
+ @"name": @"Video",
+ @"menu": @[
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Zoom Out",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"add panscan -0.1"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Zoom In",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"add panscan 0.1"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Reset Zoom",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"set panscan 0"
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Aspect Ratio 4:3",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"set video-aspect-override \"4:3\""
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Aspect Ratio 16:9",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"set video-aspect-override \"16:9\""
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Aspect Ratio 1.85:1",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"set video-aspect-override \"1.85:1\""
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Aspect Ratio 2.35:1",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"set video-aspect-override \"2.35:1\""
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Reset Aspect Ratio",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"set video-aspect-override \"-1\""
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Rotate Left",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"cycle-values video-rotate 0 270 180 90"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Rotate Right",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"cycle-values video-rotate 90 180 270 0"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Reset Rotation",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"set video-rotate 0"
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Half Size",
+ @"key" : @"0",
+ @"cmdSpecial" : [NSNumber numberWithInt:MPM_H_SIZE]
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Normal Size",
+ @"key" : @"1",
+ @"cmdSpecial" : [NSNumber numberWithInt:MPM_N_SIZE]
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Double Size",
+ @"key" : @"2",
+ @"cmdSpecial" : [NSNumber numberWithInt:MPM_D_SIZE]
+ }]
+ ]
+ },
+ @{
+ @"name": @"Audio",
+ @"menu": @[
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Next Audio Track",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"cycle audio"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Previous Audio Track",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"cycle audio down"
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Toggle Mute",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"cycle mute"
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Play Audio Later",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"add audio-delay 0.1"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Play Audio Earlier",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"add audio-delay -0.1"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Reset Audio Delay",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"set audio-delay 0.0 "
+ }]
+ ]
+ },
+ @{
+ @"name": @"Subtitle",
+ @"menu": @[
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Next Subtitle Track",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"cycle sub"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Previous Subtitle Track",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"cycle sub down"
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Toggle Force Style",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"cycle-values sub-ass-override \"force\" \"no\""
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Display Subtitles Later",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"add sub-delay 0.1"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Display Subtitles Earlier",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"add sub-delay -0.1"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Reset Subtitle Delay",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"set sub-delay 0.0"
+ }]
+ ]
+ },
+ @{
+ @"name": @"Playback",
+ @"menu": @[
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Toggle Pause",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"cycle pause"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Increase Speed",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"add speed 0.1"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Decrease Speed",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"add speed -0.1"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Reset Speed",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"set speed 1.0"
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Show Playlist",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"script-message osc-playlist"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Show Chapters",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"script-message osc-chapterlist"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Show Tracks",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"script-message osc-tracklist"
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Next File",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"playlist-next"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Previous File",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"playlist-prev"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Toggle Loop File",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"cycle-values loop-file \"inf\" \"no\""
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Toggle Loop Playlist",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"cycle-values loop-playlist \"inf\" \"no\""
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Shuffle",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"playlist-shuffle"
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Next Chapter",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"add chapter 1"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Previous Chapter",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"add chapter -1"
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Step Forward",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"frame-step"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Step Backward",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"frame-back-step"
+ }]
+ ]
+ },
+ @{
+ @"name": @"Window",
+ @"menu": @[
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Minimize",
+ @"key" : @"m",
+ @"cmdSpecial" : [NSNumber numberWithInt:MPM_MINIMIZE]
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Zoom",
+ @"key" : @"z",
+ @"cmdSpecial" : [NSNumber numberWithInt:MPM_ZOOM]
+ }]
+ ]
+ },
+ @{
+ @"name": @"Help",
+ @"menu": @[
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"mpv Website…",
+ @"action" : @"url:",
+ @"key" : @"",
+ @"target" : self,
+ @"url" : @"https://mpv.io"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"mpv on github…",
+ @"action" : @"url:",
+ @"key" : @"",
+ @"target" : self,
+ @"url" : @"https://github.com/mpv-player/mpv"
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Online Manual…",
+ @"action" : @"url:",
+ @"key" : @"",
+ @"target" : self,
+ @"url" : @"https://mpv.io/manual/master/"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Online Wiki…",
+ @"action" : @"url:",
+ @"key" : @"",
+ @"target" : self,
+ @"url" : @"https://github.com/mpv-player/mpv/wiki"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Release Notes…",
+ @"action" : @"url:",
+ @"key" : @"",
+ @"target" : self,
+ @"url" : @"https://github.com/mpv-player/mpv/blob/master/RELEASE_NOTES"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Keyboard Shortcuts…",
+ @"action" : @"url:",
+ @"key" : @"",
+ @"target" : self,
+ @"url" : @"https://github.com/mpv-player/mpv/blob/master/etc/input.conf"
+ }],
+ @{ @"name": @"separator" },
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Report Issue…",
+ @"action" : @"url:",
+ @"key" : @"",
+ @"target" : self,
+ @"url" : @"https://github.com/mpv-player/mpv/issues/new/choose"
+ }],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Show log File…",
+ @"action" : @"showFile:",
+ @"key" : @"",
+ @"target" : self,
+ @"file" : @"~/Library/Logs/mpv.log",
+ @"alertTitle" : @"No log File found.",
+ @"alertText" : @"You deactivated logging for the Bundle."
+ }]
+ ]
+ }
+ ];
+
+ [NSApp setMainMenu:[self mainMenu]];
+ }
+
+ return self;
+}
+
+- (NSMenu *)mainMenu
+{
+ NSMenu *mainMenu = [[NSMenu alloc] initWithTitle:@"MainMenu"];
+ [NSApp setServicesMenu:[[NSMenu alloc] init]];
+ NSString* bundle = [[[NSProcessInfo processInfo] environment] objectForKey:@"MPVBUNDLE"];
+
+ for(id mMenu in menuTree) {
+ NSMenu *menu = [[NSMenu alloc] initWithTitle:mMenu[@"name"]];
+ NSMenuItem *mItem = [mainMenu addItemWithTitle:mMenu[@"name"]
+ action:nil
+ keyEquivalent:@""];
+ [mainMenu setSubmenu:menu forItem:mItem];
+
+ for(id subMenu in mMenu[@"menu"]) {
+ NSString *name = subMenu[@"name"];
+ NSString *action = subMenu[@"action"];
+
+#if HAVE_MACOS_TOUCHBAR
+ if ([action isEqual:@"toggleTouchBarCustomizationPalette:"]) {
+ continue;
+ }
+#endif
+
+ if ([name isEqual:@"Show log File…"] && ![bundle isEqual:@"true"]) {
+ continue;
+ }
+
+ if ([name isEqual:@"separator"]) {
+ [menu addItem:[NSMenuItem separatorItem]];
+ } else {
+ NSMenuItem *iItem = [menu addItemWithTitle:name
+ action:NSSelectorFromString(action)
+ keyEquivalent:subMenu[@"key"]];
+ [iItem setTarget:subMenu[@"target"]];
+ [subMenu setObject:iItem forKey:@"menuItem"];
+
+ NSNumber *m = subMenu[@"modifiers"];
+ if (m) {
+ [iItem setKeyEquivalentModifierMask:m.unsignedIntegerValue];
+ }
+
+ if ([subMenu[@"name"] isEqual:@"Services"]) {
+ iItem.submenu = [NSApp servicesMenu];
+ }
+ }
+ }
+ }
+
+ return mainMenu;
+}
+
+- (void)about
+{
+ NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
+ @"mpv", @"ApplicationName",
+ [(Application *)NSApp getMPVIcon], @"ApplicationIcon",
+ [NSString stringWithUTF8String:mpv_copyright], @"Copyright",
+ [NSString stringWithUTF8String:mpv_version], @"ApplicationVersion",
+ nil];
+ [NSApp orderFrontStandardAboutPanelWithOptions:options];
+}
+
+- (void)preferences:(NSMenuItem *)menuItem
+{
+ NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSMutableDictionary *mItemDict = [self getDictFromMenuItem:menuItem];
+ NSArray *configPaths = @[
+ [NSString stringWithFormat:@"%@/.mpv/", NSHomeDirectory()],
+ [NSString stringWithFormat:@"%@/.config/mpv/", NSHomeDirectory()]];
+
+ for (id path in configPaths) {
+ NSString *fileP = [path stringByAppendingString:mItemDict[@"file"]];
+ if ([fileManager fileExistsAtPath:fileP]){
+ if ([workspace openFile:fileP])
+ return;
+ [workspace openFile:path];
+ [self alertWithTitle:mItemDict[@"alertTitle1"]
+ andText:mItemDict[@"alertText1"]];
+ return;
+ }
+ if ([workspace openFile:path]) {
+ [self alertWithTitle:mItemDict[@"alertTitle2"]
+ andText:mItemDict[@"alertText2"]];
+ return;
+ }
+ }
+
+ [self alertWithTitle:mItemDict[@"alertTitle3"]
+ andText:mItemDict[@"alertText3"]];
+}
+
+- (void)quit:(NSMenuItem *)menuItem
+{
+ NSString *cmd = [self getDictFromMenuItem:menuItem][@"cmd"];
+ [(Application *)NSApp stopMPV:(char *)[cmd UTF8String]];
+}
+
+- (void)openFile
+{
+ NSOpenPanel *panel = [[NSOpenPanel alloc] init];
+ [panel setCanChooseDirectories:YES];
+ [panel setAllowsMultipleSelection:YES];
+
+ if ([panel runModal] == NSModalResponseOK){
+ NSMutableArray *fileArray = [[NSMutableArray alloc] init];
+ for (id url in [panel URLs])
+ [fileArray addObject:[url path]];
+ [(Application *)NSApp openFiles:fileArray];
+ }
+}
+
+- (void)openPlaylist
+{
+ NSOpenPanel *panel = [[NSOpenPanel alloc] init];
+
+ if ([panel runModal] == NSModalResponseOK){
+ NSString *pl = [NSString stringWithFormat:@"loadlist \"%@\"",
+ [panel URLs][0].path];
+ [(Application *)NSApp queueCommand:(char *)[pl UTF8String]];
+ }
+}
+
+- (void)openURL
+{
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setMessageText:@"Open URL"];
+ [alert addButtonWithTitle:@"Ok"];
+ [alert addButtonWithTitle:@"Cancel"];
+ [alert setIcon:[(Application *)NSApp getMPVIcon]];
+
+ NSTextField *input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 300, 24)];
+ [input setPlaceholderString:@"URL"];
+ [alert setAccessoryView:input];
+
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+ [input becomeFirstResponder];
+ });
+
+ if ([alert runModal] == NSAlertFirstButtonReturn && [input stringValue].length > 0) {
+ NSArray *url = [NSArray arrayWithObjects:[input stringValue], nil];
+ [(Application *)NSApp openFiles:url];
+ }
+}
+
+- (void)cmd:(NSMenuItem *)menuItem
+{
+ NSString *cmd = [self getDictFromMenuItem:menuItem][@"cmd"];
+ [(Application *)NSApp queueCommand:(char *)[cmd UTF8String]];
+}
+
+- (void)url:(NSMenuItem *)menuItem
+{
+ NSString *url = [self getDictFromMenuItem:menuItem][@"url"];
+ [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:url]];
+}
+
+- (void)showFile:(NSMenuItem *)menuItem
+{
+ NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSMutableDictionary *mItemDict = [self getDictFromMenuItem:menuItem];
+ NSString *file = [mItemDict[@"file"] stringByExpandingTildeInPath];
+
+ if ([fileManager fileExistsAtPath:file]){
+ NSURL *url = [NSURL fileURLWithPath:file];
+ NSArray *urlArray = [NSArray arrayWithObjects:url, nil];
+
+ [workspace activateFileViewerSelectingURLs:urlArray];
+ return;
+ }
+
+ [self alertWithTitle:mItemDict[@"alertTitle"]
+ andText:mItemDict[@"alertText"]];
+}
+
+- (void)alertWithTitle:(NSString *)title andText:(NSString *)text
+{
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setMessageText:title];
+ [alert setInformativeText:text];
+ [alert addButtonWithTitle:@"Ok"];
+ [alert setIcon:[(Application *)NSApp getMPVIcon]];
+ [alert runModal];
+}
+
+- (NSMutableDictionary *)getDictFromMenuItem:(NSMenuItem *)menuItem
+{
+ for(id mMenu in menuTree) {
+ for(id subMenu in mMenu[@"menu"]) {
+ if([subMenu[@"menuItem"] isEqual:menuItem])
+ return subMenu;
+ }
+ }
+
+ return nil;
+}
+
+- (void)registerSelector:(SEL)action forKey:(MPMenuKey)key
+{
+ for(id mMenu in menuTree) {
+ for(id subMenu in mMenu[@"menu"]) {
+ if([subMenu[@"cmdSpecial"] isEqual:[NSNumber numberWithInt:key]]) {
+ [subMenu[@"menuItem"] setAction:action];
+ return;
+ }
+ }
+ }
+}
+
+@end
diff --git a/osdep/macosx_menubar_objc.h b/osdep/macosx_menubar_objc.h
new file mode 100644
index 0000000..072fef8
--- /dev/null
+++ b/osdep/macosx_menubar_objc.h
@@ -0,0 +1,25 @@
+/*
+ * 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/Cocoa.h>
+#include "osdep/macosx_menubar.h"
+
+@interface MenuBar : NSObject
+
+- (void)registerSelector:(SEL)action forKey:(MPMenuKey)key;
+
+@end
diff --git a/osdep/macosx_touchbar.h b/osdep/macosx_touchbar.h
new file mode 100644
index 0000000..a03b68c
--- /dev/null
+++ b/osdep/macosx_touchbar.h
@@ -0,0 +1,46 @@
+/*
+ * 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/Cocoa.h>
+#import "osdep/macosx_application_objc.h"
+
+#define BASE_ID @"io.mpv.touchbar"
+static NSTouchBarCustomizationIdentifier customID = BASE_ID;
+static NSTouchBarItemIdentifier seekBar = BASE_ID ".seekbar";
+static NSTouchBarItemIdentifier play = BASE_ID ".play";
+static NSTouchBarItemIdentifier nextItem = BASE_ID ".nextItem";
+static NSTouchBarItemIdentifier previousItem = BASE_ID ".previousItem";
+static NSTouchBarItemIdentifier nextChapter = BASE_ID ".nextChapter";
+static NSTouchBarItemIdentifier previousChapter = BASE_ID ".previousChapter";
+static NSTouchBarItemIdentifier cycleAudio = BASE_ID ".cycleAudio";
+static NSTouchBarItemIdentifier cycleSubtitle = BASE_ID ".cycleSubtitle";
+static NSTouchBarItemIdentifier currentPosition = BASE_ID ".currentPosition";
+static NSTouchBarItemIdentifier timeLeft = BASE_ID ".timeLeft";
+
+struct mpv_event;
+
+@interface TouchBar : NSTouchBar <NSTouchBarDelegate>
+
+-(void)processEvent:(struct mpv_event *)event;
+
+@property(nonatomic, retain) Application *app;
+@property(nonatomic, retain) NSDictionary *touchbarItems;
+@property(nonatomic, assign) double duration;
+@property(nonatomic, assign) double position;
+@property(nonatomic, assign) int pause;
+
+@end
diff --git a/osdep/macosx_touchbar.m b/osdep/macosx_touchbar.m
new file mode 100644
index 0000000..ccce8f7
--- /dev/null
+++ b/osdep/macosx_touchbar.m
@@ -0,0 +1,334 @@
+/*
+ * 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/>.
+ */
+
+#include "player/client.h"
+#import "macosx_touchbar.h"
+
+@implementation TouchBar
+
+@synthesize app = _app;
+@synthesize touchbarItems = _touchbar_items;
+@synthesize duration = _duration;
+@synthesize position = _position;
+@synthesize pause = _pause;
+
+- (id)init
+{
+ if (self = [super init]) {
+ self.touchbarItems = @{
+ seekBar: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"slider",
+ @"name": @"Seek Bar",
+ @"cmd": @"seek %f absolute-percent"
+ }],
+ play: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"button",
+ @"name": @"Play Button",
+ @"cmd": @"cycle pause",
+ @"image": [NSImage imageNamed:NSImageNameTouchBarPauseTemplate],
+ @"imageAlt": [NSImage imageNamed:NSImageNameTouchBarPlayTemplate]
+ }],
+ previousItem: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"button",
+ @"name": @"Previous Playlist Item",
+ @"cmd": @"playlist-prev",
+ @"image": [NSImage imageNamed:NSImageNameTouchBarGoBackTemplate]
+ }],
+ nextItem: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"button",
+ @"name": @"Next Playlist Item",
+ @"cmd": @"playlist-next",
+ @"image": [NSImage imageNamed:NSImageNameTouchBarGoForwardTemplate]
+ }],
+ previousChapter: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"button",
+ @"name": @"Previous Chapter",
+ @"cmd": @"add chapter -1",
+ @"image": [NSImage imageNamed:NSImageNameTouchBarSkipBackTemplate]
+ }],
+ nextChapter: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"button",
+ @"name": @"Next Chapter",
+ @"cmd": @"add chapter 1",
+ @"image": [NSImage imageNamed:NSImageNameTouchBarSkipAheadTemplate]
+ }],
+ cycleAudio: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"button",
+ @"name": @"Cycle Audio",
+ @"cmd": @"cycle audio",
+ @"image": [NSImage imageNamed:NSImageNameTouchBarAudioInputTemplate]
+ }],
+ cycleSubtitle: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"button",
+ @"name": @"Cycle Subtitle",
+ @"cmd": @"cycle sub",
+ @"image": [NSImage imageNamed:NSImageNameTouchBarComposeTemplate]
+ }],
+ currentPosition: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"text",
+ @"name": @"Current Position"
+ }],
+ timeLeft: [NSMutableDictionary dictionaryWithDictionary:@{
+ @"type": @"text",
+ @"name": @"Time Left"
+ }]
+ };
+
+ [self addObserver:self forKeyPath:@"visible" options:0 context:nil];
+ }
+ return self;
+}
+
+- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar
+ makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
+{
+ if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"slider"]) {
+ NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
+ NSSlider *slider = [NSSlider sliderWithTarget:self action:@selector(seekbarChanged:)];
+ slider.minValue = 0.0f;
+ slider.maxValue = 100.0f;
+ tbItem.view = slider;
+ tbItem.customizationLabel = self.touchbarItems[identifier][@"name"];
+ [self.touchbarItems[identifier] setObject:slider forKey:@"view"];
+ [self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"];
+ [tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil];
+ return tbItem;
+ } else if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"button"]) {
+ NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
+ NSImage *tbImage = self.touchbarItems[identifier][@"image"];
+ NSButton *tbButton = [NSButton buttonWithImage:tbImage target:self action:@selector(buttonAction:)];
+ tbItem.view = tbButton;
+ tbItem.customizationLabel = self.touchbarItems[identifier][@"name"];
+ [self.touchbarItems[identifier] setObject:tbButton forKey:@"view"];
+ [self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"];
+ [tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil];
+ return tbItem;
+ } else if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"text"]) {
+ NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
+ NSTextField *tbText = [NSTextField labelWithString:@"0:00"];
+ tbText.alignment = NSTextAlignmentCenter;
+ tbItem.view = tbText;
+ tbItem.customizationLabel = self.touchbarItems[identifier][@"name"];
+ [self.touchbarItems[identifier] setObject:tbText forKey:@"view"];
+ [self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"];
+ [tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil];
+ return tbItem;
+ }
+
+ return nil;
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary<NSKeyValueChangeKey,id> *)change
+ context:(void *)context {
+ if ([keyPath isEqualToString:@"visible"]) {
+ NSNumber *visible = [object valueForKey:@"visible"];
+ if (visible.boolValue) {
+ [self updateTouchBarTimeItems];
+ [self updatePlayButton];
+ }
+ }
+}
+
+- (void)updateTouchBarTimeItems
+{
+ if (!self.isVisible)
+ return;
+
+ [self updateSlider];
+ [self updateTimeLeft];
+ [self updateCurrentPosition];
+}
+
+- (void)updateSlider
+{
+ NSCustomTouchBarItem *tbItem = self.touchbarItems[seekBar][@"tbItem"];
+ if (!tbItem.visible)
+ return;
+
+ NSSlider *seekSlider = self.touchbarItems[seekBar][@"view"];
+
+ if (self.duration <= 0) {
+ seekSlider.enabled = NO;
+ seekSlider.doubleValue = 0;
+ } else {
+ seekSlider.enabled = YES;
+ if (!seekSlider.highlighted)
+ seekSlider.doubleValue = (self.position/self.duration)*100;
+ }
+}
+
+- (void)updateTimeLeft
+{
+ NSCustomTouchBarItem *tbItem = self.touchbarItems[timeLeft][@"tbItem"];
+ if (!tbItem.visible)
+ return;
+
+ NSTextField *timeLeftItem = self.touchbarItems[timeLeft][@"view"];
+
+ [self removeConstraintForIdentifier:timeLeft];
+ if (self.duration <= 0) {
+ timeLeftItem.stringValue = @"";
+ } else {
+ int left = (int)(floor(self.duration)-floor(self.position));
+ NSString *leftFormat = [self formatTime:left];
+ NSString *durFormat = [self formatTime:self.duration];
+ timeLeftItem.stringValue = [NSString stringWithFormat:@"-%@", leftFormat];
+ [self applyConstraintFromString:[NSString stringWithFormat:@"-%@", durFormat]
+ forIdentifier:timeLeft];
+ }
+}
+
+- (void)updateCurrentPosition
+{
+ NSCustomTouchBarItem *tbItem = self.touchbarItems[currentPosition][@"tbItem"];
+ if (!tbItem.visible)
+ return;
+
+ NSTextField *curPosItem = self.touchbarItems[currentPosition][@"view"];
+ NSString *posFormat = [self formatTime:(int)floor(self.position)];
+ curPosItem.stringValue = posFormat;
+
+ [self removeConstraintForIdentifier:currentPosition];
+ if (self.duration <= 0) {
+ [self applyConstraintFromString:[self formatTime:self.position]
+ forIdentifier:currentPosition];
+ } else {
+ NSString *durFormat = [self formatTime:self.duration];
+ [self applyConstraintFromString:durFormat forIdentifier:currentPosition];
+ }
+}
+
+- (void)updatePlayButton
+{
+ NSCustomTouchBarItem *tbItem = self.touchbarItems[play][@"tbItem"];
+ if (!self.isVisible || !tbItem.visible)
+ return;
+
+ NSButton *playButton = self.touchbarItems[play][@"view"];
+ if (self.pause) {
+ playButton.image = self.touchbarItems[play][@"imageAlt"];
+ } else {
+ playButton.image = self.touchbarItems[play][@"image"];
+ }
+}
+
+- (void)buttonAction:(NSButton *)sender
+{
+ NSString *identifier = [self getIdentifierFromView:sender];
+ [self.app queueCommand:(char *)[self.touchbarItems[identifier][@"cmd"] UTF8String]];
+}
+
+- (void)seekbarChanged:(NSSlider *)slider
+{
+ NSString *identifier = [self getIdentifierFromView:slider];
+ NSString *seek = [NSString stringWithFormat:
+ self.touchbarItems[identifier][@"cmd"], slider.doubleValue];
+ [self.app queueCommand:(char *)[seek UTF8String]];
+}
+
+- (NSString *)formatTime:(int)time
+{
+ int seconds = time % 60;
+ int minutes = (time / 60) % 60;
+ int hours = time / (60 * 60);
+
+ NSString *stime = hours > 0 ? [NSString stringWithFormat:@"%d:", hours] : @"";
+ stime = (stime.length > 0 || minutes > 9) ?
+ [NSString stringWithFormat:@"%@%02d:", stime, minutes] :
+ [NSString stringWithFormat:@"%d:", minutes];
+ stime = [NSString stringWithFormat:@"%@%02d", stime, seconds];
+
+ return stime;
+}
+
+- (void)removeConstraintForIdentifier:(NSTouchBarItemIdentifier)identifier
+{
+ NSTextField *field = self.touchbarItems[identifier][@"view"];
+ [field removeConstraint:self.touchbarItems[identifier][@"constrain"]];
+}
+
+- (void)applyConstraintFromString:(NSString *)string
+ forIdentifier:(NSTouchBarItemIdentifier)identifier
+{
+ NSTextField *field = self.touchbarItems[identifier][@"view"];
+ if (field) {
+ NSString *fString = [[string componentsSeparatedByCharactersInSet:
+ [NSCharacterSet decimalDigitCharacterSet]] componentsJoinedByString:@"0"];
+ NSTextField *textField = [NSTextField labelWithString:fString];
+ NSSize size = [textField frame].size;
+
+ NSLayoutConstraint *con =
+ [NSLayoutConstraint constraintWithItem:field
+ attribute:NSLayoutAttributeWidth
+ relatedBy:NSLayoutRelationEqual
+ toItem:nil
+ attribute:NSLayoutAttributeNotAnAttribute
+ multiplier:1.0
+ constant:(int)ceil(size.width*1.1)];
+ [field addConstraint:con];
+ [self.touchbarItems[identifier] setObject:con forKey:@"constrain"];
+ }
+}
+
+- (NSString *)getIdentifierFromView:(id)view
+{
+ NSString *identifier;
+ for (identifier in self.touchbarItems)
+ if([self.touchbarItems[identifier][@"view"] isEqual:view])
+ break;
+ return identifier;
+}
+
+- (void)processEvent:(struct mpv_event *)event
+{
+ switch (event->event_id) {
+ case MPV_EVENT_END_FILE: {
+ self.position = 0;
+ self.duration = 0;
+ break;
+ }
+ case MPV_EVENT_PROPERTY_CHANGE: {
+ [self handlePropertyChange:(mpv_event_property *)event->data];
+ break;
+ }
+ }
+}
+
+- (void)handlePropertyChange:(struct mpv_event_property *)property
+{
+ NSString *name = [NSString stringWithUTF8String:property->name];
+ mpv_format format = property->format;
+
+ if ([name isEqualToString:@"time-pos"] && format == MPV_FORMAT_DOUBLE) {
+ double newPosition = *(double *)property->data;
+ newPosition = newPosition < 0 ? 0 : newPosition;
+ if ((int)(floor(newPosition) - floor(self.position)) != 0) {
+ self.position = newPosition;
+ [self updateTouchBarTimeItems];
+ }
+ } else if ([name isEqualToString:@"duration"] && format == MPV_FORMAT_DOUBLE) {
+ self.duration = *(double *)property->data;
+ [self updateTouchBarTimeItems];
+ } else if ([name isEqualToString:@"pause"] && format == MPV_FORMAT_FLAG) {
+ self.pause = *(int *)property->data;
+ [self updatePlayButton];
+ }
+}
+
+@end