summaryrefslogtreecommitdiffstats
path: root/osdep
diff options
context:
space:
mode:
authorAkemi <der.richter@gmx.de>2017-02-25 21:56:59 +0100
committerAkemi <der.richter@gmx.de>2017-03-26 20:26:18 +0200
commitf8a223b7aca08491e22438df0f1ea233d602907c (patch)
tree4547edcf3b4f0ed7b37bdd1cdf42221328964dff /osdep
parentafbd657bb8e955accf8022c96e867973c1fb0a41 (diff)
downloadmpv-f8a223b7aca08491e22438df0f1ea233d602907c.tar.bz2
mpv-f8a223b7aca08491e22438df0f1ea233d602907c.tar.xz
osx: initial Touch Bar support
Diffstat (limited to 'osdep')
-rw-r--r--osdep/macosx_application.m46
-rw-r--r--osdep/macosx_application_objc.h5
-rw-r--r--osdep/macosx_events.h2
-rw-r--r--osdep/macosx_events.m61
-rw-r--r--osdep/macosx_events_objc.h2
-rw-r--r--osdep/macosx_touchbar.h45
-rw-r--r--osdep/macosx_touchbar.m272
7 files changed, 433 insertions, 0 deletions
diff --git a/osdep/macosx_application.m b/osdep/macosx_application.m
index d7cdf4b832..cccd60b4e2 100644
--- a/osdep/macosx_application.m
+++ b/osdep/macosx_application.m
@@ -17,10 +17,12 @@
#include <stdio.h>
#include <pthread.h>
+#include "config.h"
#include "mpv_talloc.h"
#include "common/msg.h"
#include "input/input.h"
+#include "player/client.h"
#import "osdep/macosx_application_objc.h"
#include "osdep/macosx_compat.h"
@@ -28,6 +30,10 @@
#include "osdep/threads.h"
#include "osdep/main-fn.h"
+#if HAVE_MACOS_TOUCHBAR
+#import "osdep/macosx_touchbar.h"
+#endif
+
#define MPV_PROTOCOL @"mpv://"
// Whether the NSApplication singleton was created. If this is false, we are
@@ -106,6 +112,38 @@ static void terminate_cocoa_application(void)
[super dealloc];
}
+#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;
+}
+
+- (void)toggleTouchBarMenu
+{
+ [NSApp toggleTouchBarCustomizationPalette:self];
+}
+#endif
+
+- (void)processEvent:(struct mpv_event *)event
+{
+#if HAVE_MACOS_TOUCHBAR
+ [(TouchBar *)self.touchBar processEvent:event];
+#endif
+}
+
+- (void)queueCommand:(char *)cmd
+{
+ [_eventsResponder queueCommand:cmd];
+}
+
#define _R(P, T, E, K) \
{ \
NSMenuItem *tmp = [self menuItemWithParent:(P) title:(T) \
@@ -139,6 +177,13 @@ static void terminate_cocoa_application(void)
NSMenu *menu = [[NSMenu alloc] initWithTitle:@"Window"];
_R(menu, @"Minimize", @"m", MPM_MINIMIZE)
_R(menu, @"Zoom", @"z", MPM_ZOOM)
+
+#if HAVE_MACOS_TOUCHBAR
+ [menu addItem:[NSMenuItem separatorItem]];
+ [self menuItemWithParent:menu title:@"Customize Touch Bar…"
+ action:@selector(toggleTouchBarMenu) keyEquivalent: @""];
+#endif
+
return [menu autorelease];
}
@@ -322,6 +367,7 @@ int cocoa_main(int argc, char *argv[])
{
@autoreleasepool {
application_instantiated = true;
+ [[EventsResponder sharedInstance] setIsApplication:YES];
struct playback_thread_ctx ctx = {0};
ctx.argc = &argc;
diff --git a/osdep/macosx_application_objc.h b/osdep/macosx_application_objc.h
index 4741a14a64..8bbe26d359 100644
--- a/osdep/macosx_application_objc.h
+++ b/osdep/macosx_application_objc.h
@@ -18,10 +18,15 @@
#import <Cocoa/Cocoa.h>
#include "osdep/macosx_application.h"
+struct mpv_event;
+
@interface Application : NSApplication
+
- (void)initialize_menu;
- (void)registerSelector:(SEL)selector forKey:(MPMenuKey)key;
- (void)stopPlayback;
+- (void)processEvent:(struct mpv_event *)event;
+- (void)queueCommand:(char *)cmd;
@property(nonatomic, retain) NSMutableDictionary *menuItems;
@property(nonatomic, retain) NSArray *files;
diff --git a/osdep/macosx_events.h b/osdep/macosx_events.h
index a6bfbfef95..019f24feef 100644
--- a/osdep/macosx_events.h
+++ b/osdep/macosx_events.h
@@ -22,6 +22,7 @@
#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);
@@ -36,5 +37,6 @@ 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
index cd1d9df9d8..47448fd965 100644
--- a/osdep/macosx_events.m
+++ b/osdep/macosx_events.m
@@ -28,18 +28,22 @@
#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"
#include "osdep/macosx_compat.h"
#import "osdep/macosx_events_objc.h"
+#import "osdep/macosx_application_objc.h"
#include "config.h"
@interface EventsResponder ()
{
struct input_ctx *_inputContext;
+ struct mpv_handle *_ctx;
+ BOOL _is_application;
NSCondition *_input_lock;
CFMachPortRef _mk_tap_port;
#if HAVE_APPLE_REMOTE
@@ -49,6 +53,8 @@
- (BOOL)handleMediaKey:(NSEvent *)event;
- (NSEvent *)handleKey:(NSEvent *)event;
+- (void)setMpvHandle:(struct mpv_handle *)ctx;
+- (void)readEvents;
- (void)startEventMonitor;
- (void)startAppleRemote;
- (void)stopAppleRemote;
@@ -210,6 +216,20 @@ 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)
+{
+ [[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
+ (EventsResponder *)sharedInstance
@@ -286,6 +306,47 @@ void cocoa_set_input_context(struct input_ctx *input_context)
return r;
}
+- (void)setIsApplication:(BOOL)isApplication
+{
+ _is_application = isApplication;
+}
+
+- (void)setMpvHandle:(struct mpv_handle *)ctx
+{
+ if (_is_application) {
+ dispatch_sync(dispatch_get_main_queue(), ^{ _ctx = ctx; });
+ } else {
+ _ctx = ctx;
+ }
+}
+
+- (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
+{
+ switch (event->event_id) {
+ case MPV_EVENT_SHUTDOWN: {
+ mpv_detach_destroy(_ctx);
+ _ctx = nil;
+ break;
+ }
+ }
+
+ if(_is_application) {
+ [NSApp processEvent:event];
+ }
+}
+
- (void)startEventMonitor
{
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown|NSEventMaskKeyUp
diff --git a/osdep/macosx_events_objc.h b/osdep/macosx_events_objc.h
index 70a058e651..99f00dcc1e 100644
--- a/osdep/macosx_events_objc.h
+++ b/osdep/macosx_events_objc.h
@@ -29,6 +29,8 @@ struct input_ctx;
- (void)setInputContext:(struct input_ctx *)ctx;
+- (void)setIsApplication:(BOOL)isApplication;
+
/// Blocks until inputContext is present.
- (void)waitForInputContext;
diff --git a/osdep/macosx_touchbar.h b/osdep/macosx_touchbar.h
new file mode 100644
index 0000000000..9a5611859b
--- /dev/null
+++ b/osdep/macosx_touchbar.h
@@ -0,0 +1,45 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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;
+
+@end
diff --git a/osdep/macosx_touchbar.m b/osdep/macosx_touchbar.m
new file mode 100644
index 0000000000..846ce6a933
--- /dev/null
+++ b/osdep/macosx_touchbar.m
@@ -0,0 +1,272 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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;
+
+- (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"
+ }]
+ };
+ }
+ return self;
+}
+
+-(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) {
+ self.position = *(double *)property->data;
+ self.position = self.position < 0 ? 0 : self.position;
+ [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) {
+ NSButton *playButton = self.touchbarItems[play][@"view"];
+ if (*(int *)property->data) {
+ playButton.image = self.touchbarItems[play][@"imageAlt"];
+ } else {
+ playButton.image = self.touchbarItems[play][@"image"];
+ }
+ }
+}
+
+- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar
+ makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
+{
+ if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"slider"]) {
+ NSSliderTouchBarItem *tbItem = [[NSSliderTouchBarItem alloc] initWithIdentifier:identifier];
+ tbItem.slider.minValue = 0.0f;
+ tbItem.slider.maxValue = 100.0f;
+ tbItem.target = self;
+ tbItem.action = @selector(seekbarChanged:);
+ tbItem.customizationLabel = self.touchbarItems[identifier][@"name"];
+ [self.touchbarItems[identifier] setObject:tbItem.slider forKey:@"view"];
+ 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"];
+ 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"];
+ return tbItem;
+ }
+
+ return nil;
+}
+
+- (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"];
+ }
+}
+
+- (void)updateTouchBarTimeItemConstrains
+{
+ [self removeConstraintForIdentifier:currentPosition];
+ [self removeConstraintForIdentifier:timeLeft];
+
+ if (self.duration <= 0) {
+ [self applyConstraintFromString:[self formatTime:self.position]
+ forIdentifier:currentPosition];
+ } else {
+ NSString *durFormat = [self formatTime:self.duration];
+
+ [self applyConstraintFromString:durFormat forIdentifier:currentPosition];
+ [self applyConstraintFromString:[NSString stringWithFormat:@"-%@", durFormat]
+ forIdentifier:timeLeft];
+ }
+}
+
+- (void)updateTouchBarTimeItems
+{
+ NSSlider *seekSlider = self.touchbarItems[seekBar][@"view"];
+ NSTextField *curPosItem = self.touchbarItems[currentPosition][@"view"];
+ NSTextField *timeLeftItem = self.touchbarItems[timeLeft][@"view"];
+
+ if (self.duration <= 0) {
+ seekSlider.enabled = NO;
+ seekSlider.doubleValue = 0;
+ timeLeftItem.stringValue = @"";
+ }
+ else {
+ seekSlider.enabled = YES;
+ if (!seekSlider.highlighted)
+ seekSlider.doubleValue = (self.position/self.duration)*100;
+ int left = (int)(floor(self.duration)-floor(self.position));
+ NSString *leftFormat = [self formatTime:left];
+ timeLeftItem.stringValue = [NSString stringWithFormat:@"-%@", leftFormat];
+ }
+ NSString *posFormat = [self formatTime:(int)floor(self.position)];
+ curPosItem.stringValue = posFormat;
+
+ [self updateTouchBarTimeItemConstrains];
+}
+
+- (NSString *)getIdentifierFromView:(id)view
+{
+ NSString *identifier;
+ for (identifier in self.touchbarItems)
+ if([self.touchbarItems[identifier][@"view"] isEqual:view])
+ break;
+ return identifier;
+}
+
+- (void)buttonAction:(NSButton *)sender
+{
+ NSString *identifier = [self getIdentifierFromView:sender];
+ [self.app queueCommand:(char *)[self.touchbarItems[identifier][@"cmd"] UTF8String]];
+}
+
+- (void)seekbarChanged:(NSSliderTouchBarItem *)sender
+{
+ NSString *identifier = [self getIdentifierFromView:sender.slider];
+ NSString *seek = [NSString stringWithFormat:
+ self.touchbarItems[identifier][@"cmd"], sender.slider.doubleValue];
+ [self.app queueCommand:(char *)[seek UTF8String]];
+}
+
+@end