diff options
Diffstat (limited to 'osdep/mac')
-rw-r--r-- | osdep/mac/app_bridge.h | 78 | ||||
-rw-r--r-- | osdep/mac/app_bridge.m | 140 | ||||
-rw-r--r-- | osdep/mac/app_bridge_objc.h | 56 | ||||
-rw-r--r-- | osdep/mac/app_hub.swift | 130 | ||||
-rw-r--r-- | osdep/mac/application.swift | 123 | ||||
-rw-r--r-- | osdep/mac/event_helper.swift | 147 | ||||
-rw-r--r-- | osdep/mac/input_helper.swift | 294 | ||||
-rw-r--r-- | osdep/mac/libmpv_helper.swift | 177 | ||||
-rw-r--r-- | osdep/mac/log_helper.swift | 67 | ||||
-rw-r--r-- | osdep/mac/menu_bar.swift | 397 | ||||
-rw-r--r-- | osdep/mac/meson.build | 71 | ||||
-rw-r--r-- | osdep/mac/option_helper.swift | 78 | ||||
-rw-r--r-- | osdep/mac/precise_timer.swift | 152 | ||||
-rw-r--r-- | osdep/mac/presentation.swift | 56 | ||||
-rw-r--r-- | osdep/mac/remote_command_center.swift | 202 | ||||
-rw-r--r-- | osdep/mac/swift_compat.swift | 35 | ||||
-rw-r--r-- | osdep/mac/swift_extensions.swift | 110 | ||||
-rw-r--r-- | osdep/mac/touch_bar.swift | 290 | ||||
-rw-r--r-- | osdep/mac/type_helper.swift | 69 |
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)) + } + } + + |