From 58604406f839d70cd821e466c9592dbe5c96d4e5 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 29 Apr 2019 16:41:51 +0300 Subject: [PATCH] Added initial implementation touchbar. --- .../media/player/media_player_widget.cpp | 3 + .../platform/linux/specific_linux.cpp | 4 + .../platform/mac/main_window_mac.mm | 18 ++ .../platform/mac/specific_mac_p.mm | 19 +- Telegram/SourceFiles/platform/mac/touchbar.h | 37 +++ Telegram/SourceFiles/platform/mac/touchbar.mm | 275 ++++++++++++++++++ .../SourceFiles/platform/platform_specific.h | 7 + .../SourceFiles/platform/win/specific_win.cpp | 4 + Telegram/gyp/telegram_sources.txt | 2 + 9 files changed, 362 insertions(+), 7 deletions(-) create mode 100644 Telegram/SourceFiles/platform/mac/touchbar.h create mode 100644 Telegram/SourceFiles/platform/mac/touchbar.mm diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp index 8ad4c1fd11..a9cb654030 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.cpp +++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/player/media_player_widget.h" +#include "platform/platform_specific.h" #include "data/data_document.h" #include "data/data_session.h" #include "ui/widgets/labels.h" @@ -166,6 +167,7 @@ Widget::Widget(QWidget *parent) : RpWidget(parent) }); setType(AudioMsgId::Type::Song); _playPause->finishTransform(); + Platform::SetTouchBar(Platform::TouchBarType::AudioPlayer); } void Widget::updateVolumeToggleIcon() { @@ -190,6 +192,7 @@ void Widget::setCloseCallback(Fn callback) { } void Widget::stopAndClose() { + Platform::SetTouchBar(Platform::TouchBarType::None); _voiceIsActive = false; if (_type == AudioMsgId::Type::Voice) { const auto songData = instance()->current(AudioMsgId::Type::Song); diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 31f32cc664..ccfafddcc2 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -406,6 +406,10 @@ void RequestPermission(PermissionType type, Fn resultCal void OpenSystemSettingsForPermission(PermissionType type) { } +void SetTouchBar(TouchBarType type) { + // TouchBar is Mac only feature. +} + bool OpenSystemSettings(SystemSettingsType type) { if (type == SystemSettingsType::Audio) { auto options = std::vector(); diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index 47a5f1d291..3c4d13786a 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -30,6 +30,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include "media/player/media_player_instance.h" +#include "media/audio/media_audio.h" +#include "platform/mac/touchbar.h" + @interface MainWindowObserver : NSObject { } @@ -107,6 +111,8 @@ public: void willExitFullScreen(); bool clipboardHasText(); + + TouchBar *_touchBar; ~Private(); @@ -183,6 +189,9 @@ MainWindow::Private::Private(MainWindow *window) , _observer([[MainWindowObserver alloc] init:this]) { _generalPasteboard = [NSPasteboard generalPasteboard]; + _touchBar = [[TouchBar alloc] init]; + [_touchBar setTouchBarType:Platform::TouchBarType::AudioPlayer]; + @autoreleasepool { [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:_observer selector:@selector(activeSpaceDidChange:) name:NSWorkspaceActiveSpaceDidChangeNotification object:nil]; @@ -208,6 +217,10 @@ void MainWindow::Private::setWindowBadge(const QString &str) { void MainWindow::Private::setWindowTitle(const QString &str) { _public->setWindowTitle(str); + if ([[NSApplication sharedApplication] respondsToSelector:@selector(isAutomaticCustomizeTouchBarMenuItemEnabled)]) { + [NSApplication sharedApplication].automaticCustomizeTouchBarMenuItemEnabled = YES; + } + [NSApplication sharedApplication].mainWindow.touchBar = [_touchBar makeTouchBar]; updateNativeTitle(); } @@ -388,6 +401,11 @@ MainWindow::MainWindow() _private->updateNativeTitle(); } }); + + subscribe(Media::Player::instance()->updatedNotifier(), + [=](const Media::Player::TrackState &state) { + [_private->_touchBar handlePropertyChange:state]; + }); } void MainWindow::closeWithoutDestroy() { diff --git a/Telegram/SourceFiles/platform/mac/specific_mac_p.mm b/Telegram/SourceFiles/platform/mac/specific_mac_p.mm index aa98977235..75f5805ea1 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac_p.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac_p.mm @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/audio/media_audio.h" #include "media/player/media_player_instance.h" #include "platform/mac/mac_utilities.h" +#include "platform/mac/touchbar.h" #include "lang/lang_keys.h" #include "base/timer.h" #include "styles/style_window.h" @@ -214,14 +215,18 @@ bool IsApplicationActive() { } void SetApplicationIcon(const QIcon &icon) { - NSImage *image = nil; - if (!icon.isNull()) { - auto pixmap = icon.pixmap(1024, 1024); + NSImage *image = nil; + if (!icon.isNull()) { + auto pixmap = icon.pixmap(1024, 1024); pixmap.setDevicePixelRatio(cRetinaFactor()); - image = static_cast(qt_mac_create_nsimage(pixmap)); - } - [[NSApplication sharedApplication] setApplicationIconImage:image]; - [image release]; + image = static_cast(qt_mac_create_nsimage(pixmap)); + } + [[NSApplication sharedApplication] setApplicationIconImage:image]; + [image release]; +} + +void SetTouchBar(TouchBarType type) { + } void InitOnTopPanel(QWidget *panel) { diff --git a/Telegram/SourceFiles/platform/mac/touchbar.h b/Telegram/SourceFiles/platform/mac/touchbar.h new file mode 100644 index 0000000000..db683566e4 --- /dev/null +++ b/Telegram/SourceFiles/platform/mac/touchbar.h @@ -0,0 +1,37 @@ +/* + This file is part of Telegram Desktop, + the official desktop application for the Telegram messaging service. + + For license and copyright information please follow this link: + https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL + */ + +#include "platform/platform_specific.h" +#include "media/audio/media_audio.h" +#include "media/player/media_player_instance.h" +#import + +static NSString * _Nullable BASE_ID = @"telegram.touchbar"; +static NSTouchBarCustomizationIdentifier _Nullable customID = @"telegram.touchbar"; +static NSTouchBarItemIdentifier _Nullable seekBar = [NSString stringWithFormat:@"%@.seekbar", BASE_ID]; +static NSTouchBarItemIdentifier _Nullable play = [NSString stringWithFormat:@"%@.play", BASE_ID]; +static NSTouchBarItemIdentifier _Nullable nextItem = [NSString stringWithFormat:@"%@.nextItem", BASE_ID]; +static NSTouchBarItemIdentifier _Nullable previousItem = [NSString stringWithFormat:@"%@.previousItem", BASE_ID]; +static NSTouchBarItemIdentifier _Nullable nextChapter = [NSString stringWithFormat:@"%@.nextChapter", BASE_ID]; +static NSTouchBarItemIdentifier _Nullable previousChapter = [NSString stringWithFormat:@"%@.previousChapter", BASE_ID]; +static NSTouchBarItemIdentifier _Nullable cycleAudio = [NSString stringWithFormat:@"%@.cycleAudio", BASE_ID]; +static NSTouchBarItemIdentifier _Nullable cycleSubtitle = [NSString stringWithFormat:@"%@.cycleSubtitle", BASE_ID]; +static NSTouchBarItemIdentifier _Nullable currentPosition = [NSString stringWithFormat:@"%@.currentPosition", BASE_ID]; +static NSTouchBarItemIdentifier _Nullable timeLeft = [NSString stringWithFormat:@"%@.timeLeft", BASE_ID]; + +@interface TouchBar : NSTouchBar +@property Platform::TouchBarType touchBarType; + +@property(retain) NSDictionary * _Nullable touchbarItems; +@property(nonatomic, assign) double duration; +@property(nonatomic, assign) double position; + +- (nullable NSTouchBar *) makeTouchBar; +- (void)handlePropertyChange:(Media::Player::TrackState)property; + +@end diff --git a/Telegram/SourceFiles/platform/mac/touchbar.mm b/Telegram/SourceFiles/platform/mac/touchbar.mm new file mode 100644 index 0000000000..ca13b493a7 --- /dev/null +++ b/Telegram/SourceFiles/platform/mac/touchbar.mm @@ -0,0 +1,275 @@ +/* + This file is part of Telegram Desktop, + the official desktop application for the Telegram messaging service. + + For license and copyright information please follow this link: + https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL + */ + +#import "touchbar.h" +#import + +#include "mainwindow.h" +#include "mainwidget.h" +#include "core/sandbox.h" +#include "core/application.h" +#include "core/crash_reports.h" +#include "storage/localstorage.h" +#include "media/audio/media_audio.h" +#include "media/player/media_player_instance.h" +#include "media/view/media_view_playback_progress.h" +#include "media/audio/media_audio.h" +#include "platform/mac/mac_utilities.h" +#include "platform/platform_specific.h" +#include "lang/lang_keys.h" +#include "base/timer.h" +#include "styles/style_window.h" + +namespace { +constexpr auto kPlayPause = 0x000; +constexpr auto kPlaylistPrevious = 0x001; +constexpr auto kPlaylistNext = 0x002; +constexpr auto kSavedMessages = 0x002; + +constexpr auto kMs = 1000; + +constexpr auto kSongType = AudioMsgId::Type::Song; +} + +@interface TouchBar() +@end + +@implementation TouchBar + +- (instancetype)init { + self = [super init]; + if (self) { + self.touchBarType = Platform::TouchBarType::None; + + self.touchbarItems = @{ + seekBar: [NSMutableDictionary dictionaryWithDictionary:@{ + @"type": @"slider", + @"name": @"Seek Bar", + @"cmd": [NSNumber numberWithInt:kPlayPause] + }], + play: [NSMutableDictionary dictionaryWithDictionary:@{ + @"type": @"button", + @"name": @"Play Button", + @"cmd": [NSNumber numberWithInt:kPlayPause], + @"image": [NSImage imageNamed:NSImageNameTouchBarPauseTemplate], + @"imageAlt": [NSImage imageNamed:NSImageNameTouchBarPlayTemplate] + }], + previousItem: [NSMutableDictionary dictionaryWithDictionary:@{ + @"type": @"button", + @"name": @"Previous Playlist Item", + @"cmd": [NSNumber numberWithInt:kPlaylistPrevious], + @"image": [NSImage imageNamed:NSImageNameTouchBarGoBackTemplate] + }], + nextItem: [NSMutableDictionary dictionaryWithDictionary:@{ + @"type": @"button", + @"name": @"Next Playlist Item", + @"cmd": [NSNumber numberWithInt:kPlaylistNext], + @"image": [NSImage imageNamed:NSImageNameTouchBarGoForwardTemplate] + }], + previousChapter: [NSMutableDictionary dictionaryWithDictionary:@{ + @"type": @"button", + @"name": @"Previous Chapter", + @"cmd": [NSNumber numberWithInt:kPlayPause], + @"image": [NSImage imageNamed:NSImageNameTouchBarSkipBackTemplate] + }], + nextChapter: [NSMutableDictionary dictionaryWithDictionary:@{ + @"type": @"button", + @"name": @"Next Chapter", + @"cmd": [NSNumber numberWithInt:kPlayPause], + @"image": [NSImage imageNamed:NSImageNameTouchBarSkipAheadTemplate] + }], + cycleAudio: [NSMutableDictionary dictionaryWithDictionary:@{ + @"type": @"button", + @"name": @"Cycle Audio", + @"cmd": [NSNumber numberWithInt:kPlayPause], + @"image": [NSImage imageNamed:NSImageNameTouchBarAudioInputTemplate] + }], + cycleSubtitle: [NSMutableDictionary dictionaryWithDictionary:@{ + @"type": @"button", + @"name": @"Cycle Subtitle", + @"cmd": [NSNumber numberWithInt:kPlayPause], + @"image": [NSImage imageNamed:NSImageNameTouchBarComposeTemplate] + }], + currentPosition: [NSMutableDictionary dictionaryWithDictionary:@{ + @"type": @"text", + @"name": @"Current Position" + }] + }; + } + return self; +} + +- (nullable NSTouchBar *) makeTouchBar{ + NSTouchBar *touchBar = [[NSTouchBar alloc] init]; + touchBar.delegate = self; + touchBar.customizationIdentifier = @"TOUCH_BAR"; + + touchBar.customizationIdentifier = customID; + touchBar.defaultItemIdentifiers = @[play, previousItem, nextItem, seekBar]; + touchBar.customizationAllowedItemIdentifiers = @[play, seekBar, previousItem, + nextItem, previousChapter, nextChapter, cycleAudio, cycleSubtitle, + currentPosition]; + + return touchBar; +} + +- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar + makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"slider"]) { + NSSliderTouchBarItem *item = [[NSSliderTouchBarItem alloc] initWithIdentifier:identifier]; + item.slider.minValue = 0.0f; + item.slider.maxValue = 1.0f; + item.target = self; + item.action = @selector(seekbarChanged:); + item.customizationLabel = self.touchbarItems[identifier][@"name"]; + [self.touchbarItems[identifier] setObject:item.slider forKey:@"view"]; + return item; + } else if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"button"]) { + NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; + NSImage *image = self.touchbarItems[identifier][@"image"]; + NSButton *button = [NSButton buttonWithImage:image target:self action:@selector(buttonAction:)]; + item.view = button; + item.customizationLabel = self.touchbarItems[identifier][@"name"]; + [self.touchbarItems[identifier] setObject:button forKey:@"view"]; + return item; + } else if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"text"]) { + NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; + NSTextField *text = [NSTextField labelWithString:@"0:00"]; + text.alignment = NSTextAlignmentCenter; + item.view = text; + item.customizationLabel = self.touchbarItems[identifier][@"name"]; + [self.touchbarItems[identifier] setObject:text forKey:@"view"]; + return item; + } + + return nil; +} + +- (void)handlePropertyChange:(Media::Player::TrackState)property { + self.position = property.position < 0 ? 0 : property.position; + self.duration = property.length; + [self updateTouchBarTimeItems]; + NSButton *playButton = self.touchbarItems[play][@"view"]; + if (property.state == Media::Player::State::Playing) { + playButton.image = self.touchbarItems[play][@"image"]; + } else { + playButton.image = self.touchbarItems[play][@"imageAlt"]; + } + + [self.touchbarItems[nextItem][@"view"] + setEnabled:Media::Player::instance()->nextAvailable(kSongType)]; + [self.touchbarItems[previousItem][@"view"] + setEnabled:Media::Player::instance()->previousAvailable(kSongType)]; +} + +- (NSString *)formatTime:(int)time { + const int seconds = time % 60; + const int minutes = (time / 60) % 60; + const 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.5)]; + [field addConstraint:con]; + [self.touchbarItems[identifier] setObject:con forKey:@"constrain"]; + } +} + +- (void)updateTouchBarTimeItemConstrains { + [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)updateTouchBarTimeItems { + NSSlider *seekSlider = self.touchbarItems[seekBar][@"view"]; + NSTextField *curPosItem = self.touchbarItems[currentPosition][@"view"]; + + if (self.duration <= 0) { + seekSlider.enabled = NO; + seekSlider.doubleValue = 0; + } else { + seekSlider.enabled = YES; + if (!seekSlider.highlighted) { + seekSlider.doubleValue = (self.position / self.duration) * seekSlider.maxValue; + } + } + const auto timeToString = [&](int t) { + return [self formatTime:(int)floor(t / kMs)]; + }; + curPosItem.stringValue = [NSString stringWithFormat:@"%@ / %@", + timeToString(self.position), + timeToString(self.duration)]; + + [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]; + const auto command = [self.touchbarItems[identifier][@"cmd"] intValue]; + LOG(("BUTTON %1").arg(command)); + Core::Sandbox::Instance().customEnterFromEventLoop([=] { + if (command == kPlayPause) { + Media::Player::instance()->playPause(); + } else if (command == kPlaylistPrevious) { + Media::Player::instance()->previous(); + } else if (command == kPlaylistNext) { + Media::Player::instance()->next(); + } + }); +} + +- (void)seekbarChanged:(NSSliderTouchBarItem *)sender { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + Media::Player::instance()->finishSeeking(kSongType, sender.slider.doubleValue); + }); +} + +@end diff --git a/Telegram/SourceFiles/platform/platform_specific.h b/Telegram/SourceFiles/platform/platform_specific.h index 919065a02d..3c8a782db6 100644 --- a/Telegram/SourceFiles/platform/platform_specific.h +++ b/Telegram/SourceFiles/platform/platform_specific.h @@ -26,6 +26,11 @@ enum class SystemSettingsType { Audio, }; +enum class TouchBarType { + AudioPlayer, + None, +}; + void SetWatchingMediaKeys(bool watching); bool IsApplicationActive(); void SetApplicationIcon(const QIcon &icon); @@ -40,6 +45,8 @@ void RequestPermission(PermissionType type, Fn resultCal void OpenSystemSettingsForPermission(PermissionType type); bool OpenSystemSettings(SystemSettingsType type); +void SetTouchBar(TouchBarType type); + [[nodiscard]] QString SystemLanguage(); [[nodiscard]] QString SystemCountry(); diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index ea8071405a..ff1045e58e 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -646,6 +646,10 @@ void OpenSystemSettingsForPermission(PermissionType type) { } } +void SetTouchBar(TouchBarType type) { + // TouchBar is Mac only feature. +} + bool OpenSystemSettings(SystemSettingsType type) { if (type == SystemSettingsType::Audio) { crl::on_main([] { diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index df4d7e910e..43e043a552 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -589,6 +589,8 @@ <(src_loc)/platform/mac/specific_mac_p.h <(src_loc)/platform/mac/window_title_mac.mm <(src_loc)/platform/mac/window_title_mac.h +<(src_loc)/platform/mac/touchbar.h +<(src_loc)/platform/mac/touchbar.mm <(src_loc)/platform/win/audio_win.cpp <(src_loc)/platform/win/audio_win.h <(src_loc)/platform/win/file_utilities_win.cpp