summaryrefslogtreecommitdiffstats
path: root/osdep/mac
diff options
context:
space:
mode:
Diffstat (limited to 'osdep/mac')
-rw-r--r--osdep/mac/app_bridge.h78
-rw-r--r--osdep/mac/app_bridge.m140
-rw-r--r--osdep/mac/app_bridge_objc.h56
-rw-r--r--osdep/mac/app_hub.swift130
-rw-r--r--osdep/mac/application.swift123
-rw-r--r--osdep/mac/event_helper.swift147
-rw-r--r--osdep/mac/input_helper.swift294
-rw-r--r--osdep/mac/libmpv_helper.swift177
-rw-r--r--osdep/mac/log_helper.swift67
-rw-r--r--osdep/mac/menu_bar.swift397
-rw-r--r--osdep/mac/meson.build71
-rw-r--r--osdep/mac/option_helper.swift78
-rw-r--r--osdep/mac/precise_timer.swift152
-rw-r--r--osdep/mac/presentation.swift56
-rw-r--r--osdep/mac/remote_command_center.swift202
-rw-r--r--osdep/mac/swift_compat.swift35
-rw-r--r--osdep/mac/swift_extensions.swift110
-rw-r--r--osdep/mac/touch_bar.swift290
-rw-r--r--osdep/mac/type_helper.swift69
19 files changed, 2672 insertions, 0 deletions
diff --git a/osdep/mac/app_bridge.h b/osdep/mac/app_bridge.h
new file mode 100644
index 0000000000..cc77c2f4dd
--- /dev/null
+++ b/osdep/mac/app_bridge.h
@@ -0,0 +1,78 @@
+/*
+ * 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/>.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include "options/m_option.h"
+
+struct input_ctx;
+struct mpv_handle;
+
+enum {
+ FRAME_VISIBLE = 0,
+ FRAME_WHOLE,
+};
+
+enum {
+ RENDER_TIMER_PRESENTATION_FEEDBACK = -1,
+ RENDER_TIMER_SYSTEM,
+ RENDER_TIMER_CALLBACK,
+ RENDER_TIMER_PRECISE,
+};
+
+enum {
+ MAC_CSP_AUTO = -1,
+ MAC_CSP_DISPLAY_P3,
+ MAC_CSP_DISPLAY_P3_HLG,
+ MAC_CSP_DISPLAY_P3_PQ,
+ MAC_CSP_DISPLAY_P3_LINEAR,
+ MAC_CSP_DCI_P3,
+ MAC_CSP_BT_2020,
+ MAC_CSP_BT_2020_LINEAR,
+ MAC_CSP_BT_2100_HLG,
+ MAC_CSP_BT_2100_PQ,
+ MAC_CSP_BT_709,
+ MAC_CSP_SRGB,
+ MAC_CSP_SRGB_LINEAR,
+ MAC_CSP_RGB_LINEAR,
+ MAC_CSP_ADOBE,
+};
+
+struct macos_opts {
+ 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;
+ int cocoa_cb_output_csp;
+};
+
+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);
+void cocoa_init_cocoa_cb(void);
+// multithreaded wrapper for mpv_main
+int cocoa_main(int argc, char *argv[]);
+
+extern const struct m_sub_options macos_conf;
diff --git a/osdep/mac/app_bridge.m b/osdep/mac/app_bridge.m
new file mode 100644
index 0000000000..c61ba3b6cb
--- /dev/null
+++ b/osdep/mac/app_bridge.m
@@ -0,0 +1,140 @@
+/*
+ * 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"
+
+#import "osdep/mac/app_bridge_objc.h"
+
+#if HAVE_SWIFT
+#include "osdep/mac/swift.h"
+#endif
+
+#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}, {"feedback", RENDER_TIMER_PRESENTATION_FEEDBACK})},
+ {"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)},
+ {"cocoa-cb-output-csp", OPT_CHOICE(cocoa_cb_output_csp,
+ {"auto", MAC_CSP_AUTO},
+ {"display-p3", MAC_CSP_DISPLAY_P3},
+ {"display-p3-hlg", MAC_CSP_DISPLAY_P3_HLG},
+ {"display-p3-pq", MAC_CSP_DISPLAY_P3_PQ},
+ {"display-p3-linear", MAC_CSP_DISPLAY_P3_LINEAR},
+ {"dci-p3", MAC_CSP_DCI_P3},
+ {"bt.2020", MAC_CSP_BT_2020},
+ {"bt.2020-linear", MAC_CSP_BT_2020_LINEAR},
+ {"bt.2100-hlg", MAC_CSP_BT_2100_HLG},
+ {"bt.2100-pq", MAC_CSP_BT_2100_PQ},
+ {"bt.709", MAC_CSP_BT_709},
+ {"srgb", MAC_CSP_SRGB},
+ {"srgb-linear", MAC_CSP_SRGB_LINEAR},
+ {"rgb-linear", MAC_CSP_RGB_LINEAR},
+ {"adobe", MAC_CSP_ADOBE})},
+ {0}
+ },
+ .size = sizeof(struct macos_opts),
+ .defaults = &(const struct macos_opts){
+ .macos_title_bar_color = {0, 0, 0, 0},
+ .macos_fs_animation_duration = -1,
+ .macos_render_timer = RENDER_TIMER_CALLBACK,
+ .cocoa_cb_sw_renderer = -1,
+ .cocoa_cb_10bit_context = true,
+ .cocoa_cb_output_csp = MAC_CSP_AUTO,
+ },
+};
+
+static const char app_icon[] =
+#include "TOOLS/osxbundle/icon.icns.inc"
+;
+
+NSData *app_bridge_icon(void)
+{
+ return [NSData dataWithBytesNoCopy:(void *)app_icon length:sizeof(app_icon) - 1 freeWhenDone:NO];
+}
+
+void app_bridge_tarray_append(void *t, char ***a, int *i, char *s)
+{
+ MP_TARRAY_APPEND(t, *a, *i, s);
+}
+
+const struct m_sub_options *app_bridge_mac_conf(void)
+{
+ return &macos_conf;
+}
+
+const struct m_sub_options *app_bridge_vo_conf(void)
+{
+ return &vo_sub_opts;
+}
+
+void cocoa_init_media_keys(void)
+{
+ [[AppHub shared] startRemote];
+}
+
+void cocoa_uninit_media_keys(void)
+{
+ [[AppHub shared] stopRemote];
+}
+
+void cocoa_set_input_context(struct input_ctx *input_context)
+{
+ [[AppHub shared] initInput:input_context];
+}
+
+void cocoa_set_mpv_handle(struct mpv_handle *ctx)
+{
+ [[AppHub shared] initMpv:ctx];
+}
+
+void cocoa_init_cocoa_cb(void)
+{
+ [[AppHub shared] initCocoaCb];
+}
+
+int cocoa_main(int argc, char *argv[])
+{
+ return [(Application *)[Application sharedApplication] main:argc :argv];
+}
+
diff --git a/osdep/mac/app_bridge_objc.h b/osdep/mac/app_bridge_objc.h
new file mode 100644
index 0000000000..60201982cf
--- /dev/null
+++ b/osdep/mac/app_bridge_objc.h
@@ -0,0 +1,56 @@
+/*
+ * 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 <QuartzCore/QuartzCore.h>
+
+#include "player/client.h"
+#include "video/out/libmpv.h"
+#include "libmpv/render_gl.h"
+
+#include "options/m_config.h"
+#include "player/core.h"
+#include "input/input.h"
+#include "input/event.h"
+#include "input/keycodes.h"
+#include "video/out/win_state.h"
+
+#include "osdep/main-fn.h"
+#include "osdep/mac/app_bridge.h"
+
+// complex macros won't get imported to swift so we have to reassign them
+static int SWIFT_MBTN_LEFT = MP_MBTN_LEFT;
+static int SWIFT_MBTN_MID = MP_MBTN_MID;
+static int SWIFT_MBTN_RIGHT = MP_MBTN_RIGHT;
+static int SWIFT_WHEEL_UP = MP_WHEEL_UP;
+static int SWIFT_WHEEL_DOWN = MP_WHEEL_DOWN;
+static int SWIFT_WHEEL_LEFT = MP_WHEEL_LEFT;
+static int SWIFT_WHEEL_RIGHT = MP_WHEEL_RIGHT;
+static int SWIFT_MBTN_BACK = MP_MBTN_BACK;
+static int SWIFT_MBTN_FORWARD = MP_MBTN_FORWARD;
+static int SWIFT_MBTN9 = MP_MBTN9;
+
+static int SWIFT_KEY_MOUSE_LEAVE = MP_KEY_MOUSE_LEAVE;
+static int SWIFT_KEY_MOUSE_ENTER = MP_KEY_MOUSE_ENTER;
+
+static const char *swift_mpv_version = mpv_version;
+static const char *swift_mpv_copyright = mpv_copyright;
+
+NSData *app_bridge_icon(void);
+void app_bridge_tarray_append(void *t, char ***a, int *i, char *s);
+const struct m_sub_options *app_bridge_mac_conf(void);
+const struct m_sub_options *app_bridge_vo_conf(void);
diff --git a/osdep/mac/app_hub.swift b/osdep/mac/app_hub.swift
new file mode 100644
index 0000000000..81390744dc
--- /dev/null
+++ b/osdep/mac/app_hub.swift
@@ -0,0 +1,130 @@
+/*
+ * 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 AppHub: NSObject {
+ @objc static let shared = AppHub()
+
+ var mpv: OpaquePointer?
+ var input: InputHelper
+ var log: LogHelper
+ var option: OptionHelper?
+ var event: EventHelper?
+ var menu: MenuBar?
+#if HAVE_MACOS_MEDIA_PLAYER
+ var remote: RemoteCommandCenter?
+#endif
+#if HAVE_MACOS_TOUCHBAR
+ var touchBar: TouchBar?
+#endif
+#if HAVE_MACOS_COCOA_CB
+ var cocoaCb: CocoaCB?
+#endif
+
+ let MPV_PROTOCOL: String = "mpv://"
+ var isApplication: Bool { return NSApp is Application }
+ var openEvents: Int = 0
+
+ private override init() {
+ input = InputHelper()
+ log = LogHelper()
+ super.init()
+ if isApplication { menu = MenuBar(self) }
+#if HAVE_MACOS_MEDIA_PLAYER
+ remote = RemoteCommandCenter(self)
+#endif
+ log.verbose("AppHub initialised")
+ }
+
+ @objc func initMpv(_ mpv: OpaquePointer) {
+ event = EventHelper(self, mpv)
+ if let mpv = event?.mpv {
+ self.mpv = mpv
+ log.log = mp_log_new(nil, mp_client_get_log(mpv), "app")
+ option = OptionHelper(UnsafeMutablePointer(mpv), mp_client_get_global(mpv))
+ input.option = option
+ }
+
+#if HAVE_MACOS_MEDIA_PLAYER
+ remote?.registerEvents()
+#endif
+#if HAVE_MACOS_TOUCHBAR
+ touchBar = TouchBar(self)
+#endif
+ log.verbose("AppHub functionality initialised")
+ }
+
+ @objc func initInput(_ input: OpaquePointer?) {
+ log.verbose("Initialising Input")
+ self.input.signal(input: input)
+ }
+
+ @objc func initCocoaCb() {
+#if HAVE_MACOS_COCOA_CB
+ if !isApplication { return }
+ log.verbose("Initialising CocoaCB")
+ DispatchQueue.main.sync {
+ self.cocoaCb = self.cocoaCb ?? CocoaCB(mpv_create_client(mpv, "cocoacb"))
+ }
+#endif
+ }
+
+ @objc func startRemote() {
+#if HAVE_MACOS_MEDIA_PLAYER
+ log.verbose("Starting RemoteCommandCenter")
+ remote?.start()
+#endif
+ }
+
+ @objc func stopRemote() {
+#if HAVE_MACOS_MEDIA_PLAYER
+ log.verbose("Stoping RemoteCommandCenter")
+ remote?.stop()
+#endif
+ }
+
+ func open(urls: [URL]) {
+ let files = urls.map {
+ if $0.isFileURL { return $0.path }
+ var path = $0.absoluteString
+ if path.hasPrefix(MPV_PROTOCOL) { path.removeFirst(MPV_PROTOCOL.count) }
+ return path.removingPercentEncoding ?? path
+ }.sorted { (strL: String, strR: String) -> Bool in
+ return strL.localizedStandardCompare(strR) == .orderedAscending
+ }
+ log.verbose("\(openEvents > 0 ? "Appending" : "Opening") dropped files: \(files)")
+ input.open(files: files, append: openEvents > 0)
+ openEvents += 1
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.openEvents -= 1 }
+ }
+
+ func getIcon() -> NSImage {
+ guard let iconData = app_bridge_icon(), let icon = NSImage(data: iconData) else {
+ return NSImage(size: NSSize(width: 1, height: 1))
+ }
+ return icon
+ }
+
+ func getMacConf() -> UnsafePointer<m_sub_options>? {
+ return app_bridge_mac_conf()
+ }
+
+ func getVoConf() -> UnsafePointer<m_sub_options>? {
+ return app_bridge_vo_conf()
+ }
+}
diff --git a/osdep/mac/application.swift b/osdep/mac/application.swift
new file mode 100644
index 0000000000..23a15023e8
--- /dev/null
+++ b/osdep/mac/application.swift
@@ -0,0 +1,123 @@
+/*
+ * 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 Application: NSApplication, NSApplicationDelegate {
+ var appHub: AppHub { return AppHub.shared }
+ var eventManager: NSAppleEventManager { return NSAppleEventManager.shared() }
+ var isBundle: Bool { return ProcessInfo.processInfo.environment["MPVBUNDLE"] == "true" }
+ var playbackThreadId: mp_thread!
+ var argc: Int32?
+ var argv: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
+
+ override init() {
+ super.init()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func sendEvent(_ event: NSEvent) {
+ if modalWindow != nil || !appHub.input.processKey(event: event) {
+ super.sendEvent(event)
+ }
+ appHub.input.wakeup()
+ }
+
+#if HAVE_MACOS_TOUCHBAR
+ override func makeTouchBar() -> NSTouchBar? {
+ return appHub.touchBar
+ }
+#endif
+
+ func application(_ application: NSApplication, open urls: [URL]) {
+ appHub.open(urls: urls)
+ }
+
+ func applicationWillFinishLaunching(_ notification: Notification) {
+ // register quit and exit events
+ eventManager.setEventHandler(
+ self,
+ andSelector: #selector(handleQuit(event:replyEvent:)),
+ forEventClass: AEEventClass(kCoreEventClass),
+ andEventID: kAEQuitApplication
+ )
+ atexit_b({
+ // clean up after exit() was called
+ DispatchQueue.main.async {
+ NSApp.hide(NSApp)
+ NSApp.setActivationPolicy(.prohibited)
+ self.eventManager.removeEventHandler(forEventClass: AEEventClass(kCoreEventClass), andEventID: kAEQuitApplication)
+ }
+ })
+ }
+
+ // quit from App icon, external quit from NSWorkspace
+ @objc func handleQuit(event: NSAppleEventDescriptor?, replyEvent: NSAppleEventDescriptor?) {
+ // send quit to core, terminates mpv_main called in playbackThread,
+ if !appHub.input.command("quit") {
+ appHub.log.warning("Could not properly shut down mpv")
+ exit(1)
+ }
+ }
+
+ func setupBundle() {
+ if !isBundle { return }
+
+ // started from finder the first argument after the binary may start with -psn_
+ if CommandLine.argc > 1 && CommandLine.arguments[1].hasPrefix("-psn_") {
+ argc? = 1
+ argv?[1] = nil
+ }
+
+ let path = (ProcessInfo.processInfo.environment["PATH"] ?? "") +
+ ":/usr/local/bin:/usr/local/sbin:/opt/local/bin:/opt/local/sbin"
+ appHub.log.verbose("Setting Bundle $PATH to: \(path)")
+ _ = path.withCString { setenv("PATH", $0, 1) }
+ }
+
+ let playbackThread: @convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? = { (ptr: UnsafeMutableRawPointer) in
+ let application: Application = TypeHelper.bridge(ptr: ptr)
+ mp_thread_set_name("core/playback")
+ let exitCode: Int32 = mpv_main(application.argc ?? 1, application.argv)
+ // exit of any proper shut down
+ exit(exitCode)
+ }
+
+ @objc func main(_ argc: Int32, _ argv: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>) -> Int {
+ self.argc = argc
+ self.argv = argv
+
+ NSApp = self
+ NSApp.delegate = self
+ NSApp.setActivationPolicy(isBundle ? .regular : .accessory)
+ setupBundle()
+ pthread_create(&playbackThreadId, nil, playbackThread, TypeHelper.bridge(obj: self))
+ appHub.input.wait()
+ NSApp.run()
+
+ // should never be reached
+ print("""
+ There was either a problem initializing Cocoa or the Runloop was stopped unexpectedly. \
+ Please report this issues to a developer.\n
+ """)
+ pthread_join(playbackThreadId, nil)
+ return 1
+ }
+}
diff --git a/osdep/mac/event_helper.swift b/osdep/mac/event_helper.swift
new file mode 100644
index 0000000000..0f194ce8aa
--- /dev/null
+++ b/osdep/mac/event_helper.swift
@@ -0,0 +1,147 @@
+/*
+ * 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
+
+protocol EventSubscriber: AnyObject {
+ var uid: Int { get }
+ func handle(event: EventHelper.Event)
+}
+
+extension EventSubscriber {
+ var uid: Int { return Int(bitPattern: ObjectIdentifier(self)) }
+}
+
+extension EventHelper {
+ typealias WakeupCallback = (@convention(c) (UnsafeMutableRawPointer?) -> Void)?
+
+ struct Event {
+ var id: String {
+ return name + (name.starts(with: "MPV_EVENT_") ? "" : String(format.rawValue))
+ }
+ var idReset: String {
+ return name + (name.starts(with: "MPV_EVENT_") ? "" : String(MPV_FORMAT_NONE.rawValue))
+ }
+ let name: String
+ let format: mpv_format
+ let string: String?
+ let bool: Bool?
+ let double: Double?
+
+ init(
+ name: String = "",
+ format: mpv_format = MPV_FORMAT_NONE,
+ string: String? = nil,
+ bool: Bool? = nil,
+ double: Double? = nil
+
+ ) {
+ self.name = name
+ self.format = format
+ self.string = string
+ self.bool = bool
+ self.double = double
+ }
+ }
+}
+
+class EventHelper {
+ unowned let appHub: AppHub
+ var mpv: OpaquePointer?
+ var events: [String: [Int: EventSubscriber]] = [:]
+
+ init?(_ appHub: AppHub, _ mpv: OpaquePointer) {
+ if !appHub.isApplication {
+ mpv_destroy(mpv)
+ return nil
+ }
+
+ self.appHub = appHub
+ self.mpv = mpv
+ mpv_set_wakeup_callback(mpv, wakeup, TypeHelper.bridge(obj: self))
+ }
+
+ func subscribe(_ subscriber: any EventSubscriber, event: Event) {
+ guard let mpv = mpv else { return }
+
+ if !event.name.isEmpty {
+ if !events.keys.contains(event.idReset) {
+ events[event.idReset] = [:]
+ }
+ if !events.keys.contains(event.id) {
+ mpv_observe_property(mpv, 0, event.name, event.format)
+ events[event.id] = [:]
+ }
+ events[event.idReset]?[subscriber.uid] = subscriber
+ events[event.id]?[subscriber.uid] = subscriber
+ }
+ }
+
+ let wakeup: WakeupCallback = { ( ctx ) in
+ let event = unsafeBitCast(ctx, to: EventHelper.self)
+ DispatchQueue.main.async { event.eventLoop() }
+ }
+
+ func eventLoop() {
+ while let mpv = mpv, let event = mpv_wait_event(mpv, 0) {
+ if event.pointee.event_id == MPV_EVENT_NONE { break }
+ handle(event: event)
+ }
+ }
+
+ func handle(event: UnsafeMutablePointer<mpv_event>) {
+ switch event.pointee.event_id {
+ case MPV_EVENT_PROPERTY_CHANGE:
+ handle(property: event)
+ default:
+ for (_, subscriber) in events[String(describing: event.pointee.event_id)] ?? [:] {
+ subscriber.handle(event: .init(name: String(describing: event.pointee.event_id)))
+ }
+ }
+
+ if event.pointee.event_id == MPV_EVENT_SHUTDOWN {
+ mpv_destroy(mpv)
+ mpv = nil
+ }
+ }
+
+ func handle(property mpvEvent: UnsafeMutablePointer<mpv_event>) {
+ let pData = OpaquePointer(mpvEvent.pointee.data)
+ guard let property = UnsafePointer<mpv_event_property>(pData)?.pointee else {
+ return
+ }
+
+ let name = String(cString: property.name)
+ let format = property.format
+ for (_, subscriber) in events[name + String(format.rawValue)] ?? [:] {
+ var event: Event?
+ switch format {
+ case MPV_FORMAT_STRING:
+ event = .init(name: name, format: format, string: TypeHelper.toString(property.data))
+ case MPV_FORMAT_FLAG:
+ event = .init(name: name, format: format, bool: TypeHelper.toBool(property.data))
+ case MPV_FORMAT_DOUBLE:
+ event = .init(name: name, format: format, double: TypeHelper.toDouble(property.data))
+ case MPV_FORMAT_NONE:
+ event = .init(name: name, format: format)
+ default: break
+ }
+
+ if let e = event { subscriber.handle(event: e) }
+ }
+ }
+}
diff --git a/osdep/mac/input_helper.swift b/osdep/mac/input_helper.swift
new file mode 100644
index 0000000000..0fa0119ad4
--- /dev/null
+++ b/osdep/mac/input_helper.swift
@@ -0,0 +1,294 @@
+/*
+ * 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 Carbon.HIToolbox
+
+class InputHelper: NSObject {
+ var option: OptionHelper?
+ var lock = NSCondition()
+ private var input: OpaquePointer?
+
+ let keymap: [mp_keymap] = [
+ // special keys
+ .init(kVK_Return, MP_KEY_ENTER), .init(kVK_Escape, MP_KEY_ESC),
+ .init(kVK_Delete, MP_KEY_BACKSPACE), .init(kVK_Tab, MP_KEY_TAB),
+ .init(kVK_VolumeUp, MP_KEY_VOLUME_UP), .init(kVK_VolumeDown, MP_KEY_VOLUME_DOWN),
+ .init(kVK_Mute, MP_KEY_MUTE),
+
+ // cursor keys
+ .init(kVK_UpArrow, MP_KEY_UP), .init(kVK_DownArrow, MP_KEY_DOWN),
+ .init(kVK_LeftArrow, MP_KEY_LEFT), .init(kVK_RightArrow, MP_KEY_RIGHT),
+
+ // navigation block
+ .init(kVK_Help, MP_KEY_INSERT), .init(kVK_ForwardDelete, MP_KEY_DELETE),
+ .init(kVK_Home, MP_KEY_HOME), .init(kVK_End, MP_KEY_END),
+ .init(kVK_PageUp, MP_KEY_PAGE_UP), .init(kVK_PageDown, MP_KEY_PAGE_DOWN),
+
+ // F-keys
+ .init(kVK_F1, MP_KEY_F + 1), .init(kVK_F2, MP_KEY_F + 2), .init(kVK_F3, MP_KEY_F + 3),
+ .init(kVK_F4, MP_KEY_F + 4), .init(kVK_F5, MP_KEY_F + 5), .init(kVK_F6, MP_KEY_F + 6),
+ .init(kVK_F7, MP_KEY_F + 7), .init(kVK_F8, MP_KEY_F + 8), .init(kVK_F9, MP_KEY_F + 9),
+ .init(kVK_F10, MP_KEY_F + 10), .init(kVK_F11, MP_KEY_F + 11), .init(kVK_F12, MP_KEY_F + 12),
+ .init(kVK_F13, MP_KEY_F + 13), .init(kVK_F14, MP_KEY_F + 14), .init(kVK_F15, MP_KEY_F + 15),
+ .init(kVK_F16, MP_KEY_F + 16), .init(kVK_F17, MP_KEY_F + 17), .init(kVK_F18, MP_KEY_F + 18),
+ .init(kVK_F19, MP_KEY_F + 19), .init(kVK_F20, MP_KEY_F + 20),
+
+ // numpad
+ .init(kVK_ANSI_KeypadPlus, Int32(Character("+").asciiValue ?? 0)),
+ .init(kVK_ANSI_KeypadMinus, Int32(Character("-").asciiValue ?? 0)),
+ .init(kVK_ANSI_KeypadMultiply, Int32(Character("*").asciiValue ?? 0)),
+ .init(kVK_ANSI_KeypadDivide, Int32(Character("/").asciiValue ?? 0)),
+ .init(kVK_ANSI_KeypadEnter, MP_KEY_KPENTER), .init(kVK_ANSI_KeypadDecimal, MP_KEY_KPDEC),
+ .init(kVK_ANSI_Keypad0, MP_KEY_KP0), .init(kVK_ANSI_Keypad1, MP_KEY_KP1),
+ .init(kVK_ANSI_Keypad2, MP_KEY_KP2), .init(kVK_ANSI_Keypad3, MP_KEY_KP3),
+ .init(kVK_ANSI_Keypad4, MP_KEY_KP4), .init(kVK_ANSI_Keypad5, MP_KEY_KP5),
+ .init(kVK_ANSI_Keypad6, MP_KEY_KP6), .init(kVK_ANSI_Keypad7, MP_KEY_KP7),
+ .init(kVK_ANSI_Keypad8, MP_KEY_KP8), .init(kVK_ANSI_Keypad9, MP_KEY_KP9),
+
+ .init(0, 0)
+ ]
+
+ init(_ input: OpaquePointer? = nil, _ option: OptionHelper? = nil) {
+ super.init()
+ self.input = input
+ self.option = option
+ }
+
+ func put(
+ key: Int32,
+ modifiers: NSEvent.ModifierFlags = .init(rawValue: 0),
+ type: NSEvent.EventType = .applicationDefined
+ ) {
+ lock.withLock {
+ putKey(key, modifiers: modifiers, type: type)
+ }
+ }
+
+ private func putKey(
+ _ key: Int32,
+ modifiers: NSEvent.ModifierFlags = .init(rawValue: 0),
+ type: NSEvent.EventType = .applicationDefined
+ ) {
+ if key < 1 { return }
+
+ guard let input = input else { return }
+ let code = key | mapModifier(modifiers) | mapType(type)
+ mp_input_put_key(input, code)
+
+ if type == .keyUp {
+ mp_input_put_key(input, MP_INPUT_RELEASE_ALL)
+ }
+ }
+
+ @objc func processKey(event: NSEvent) -> Bool {
+ if event.type != .keyDown && event.type != .keyUp { return false }
+ if NSApp.mainMenu?.performKeyEquivalent(with: event) ?? false || event.isARepeat { return true }
+
+ return lock.withLock {
+ let mpkey = lookup_keymap_table(keymap, Int32(event.keyCode))
+ if mpkey > 0 {
+ putKey(mpkey, modifiers: event.modifierFlags, type: event.type)
+ return true
+ }
+
+ guard let chars = event.characters, let charsNoMod = event.charactersIgnoringModifiers else { return false }
+ var key = (useAltGr() && event.modifierFlags.contains(.optionRight)) ? chars : charsNoMod
+ if key.isEmpty { key = mapDeadKey(event) }
+ if key.isEmpty { return false }
+ key.withCString {
+ var bstr = bstr0($0)
+ putKey(bstr_decode_utf8(bstr, &bstr), modifiers: event.modifierFlags, type: event.type)
+ }
+ return true
+ }
+ }
+
+ func processMouse(event: NSEvent) {
+ if !mouseEnabled() { return }
+ lock.withLock {
+ putKey(map(button: event.buttonNumber), modifiers: event.modifierFlags, type: event.type)
+ if event.clickCount > 1 {
+ putKey(map(button: event.buttonNumber), modifiers: event.modifierFlags, type: .keyUp)
+ }
+ }
+ }
+
+ func processWheel(event: NSEvent) {
+ if !mouseEnabled() { return }
+ lock.withLock {
+ guard let input = input else { return }
+ let modifiers = event.modifierFlags
+ let precise = event.hasPreciseScrollingDeltas
+ var deltaX = event.deltaX * 0.1
+ var deltaY = event.deltaY * 0.1
+
+ if !precise {
+ deltaX = modifiers.contains(.shift) ? event.scrollingDeltaY : event.scrollingDeltaX
+ deltaY = modifiers.contains(.shift) ? event.scrollingDeltaX : event.scrollingDeltaY
+ }
+
+ var key = deltaY > 0 ? SWIFT_WHEEL_UP : SWIFT_WHEEL_DOWN
+ var delta = Double(deltaY)
+ if abs(deltaX) > abs(deltaY) {
+ key = deltaX > 0 ? SWIFT_WHEEL_LEFT : SWIFT_WHEEL_RIGHT
+ delta = Double(deltaX)
+ }
+
+ mp_input_put_wheel(input, key | mapModifier(modifiers), precise ? abs(delta) : 1)
+ }
+ }
+
+ func draggable(at pos: NSPoint) -> Bool {
+ lock.withLock {
+ guard let input = input else { return false }
+ return !mp_input_test_dragging(input, Int32(pos.x), Int32(pos.y))
+ }
+ }
+
+ func mouseEnabled() -> Bool {
+ lock.withLock {
+ guard let input = input else { return true }
+ return mp_input_mouse_enabled(input)
+ }
+ }
+
+ func setMouse(position: NSPoint) {
+ if !mouseEnabled() { return }
+ lock.withLock {
+ guard let input = input else { return }
+ mp_input_set_mouse_pos(input, Int32(position.x), Int32(position.y))
+ }
+ }
+
+