| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423 |
- /*
- 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/mac/specific_mac_p.h"
- #include "mainwindow.h"
- #include "mainwidget.h"
- #include "calls/calls_instance.h"
- #include "core/sandbox.h"
- #include "core/application.h"
- #include "core/core_settings.h"
- #include "core/crash_reports.h"
- #include "storage/localstorage.h"
- #include "media/audio/media_audio.h"
- #include "media/player/media_player_instance.h"
- #include "window/window_controller.h"
- #include "base/platform/mac/base_utilities_mac.h"
- #include "base/platform/base_platform_info.h"
- #include "lang/lang_keys.h"
- #include "base/timer.h"
- #include "styles/style_window.h"
- #include "platform/platform_specific.h"
- #include <QtGui/QWindow>
- #include <QtWidgets/QApplication>
- #if __has_include(<QtCore/QOperatingSystemVersion>)
- #include <QtCore/QOperatingSystemVersion>
- #endif // __has_include(<QtCore/QOperatingSystemVersion>)
- #if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
- #include <qpa/qwindowsysteminterface.h>
- #endif // Qt < 6.6.0
- #include <Cocoa/Cocoa.h>
- #include <CoreFoundation/CFURL.h>
- #include <IOKit/IOKitLib.h>
- #include <IOKit/hidsystem/ev_keymap.h>
- using Platform::Q2NSString;
- using Platform::NS2QString;
- namespace {
- constexpr auto kIgnoreActivationTimeoutMs = 500;
- NSMenuItem *CreateMenuItem(
- QString title,
- rpl::lifetime &lifetime,
- Fn<void()> callback,
- bool enabled = true) {
- id block = [^{
- Core::Sandbox::Instance().customEnterFromEventLoop(callback);
- } copy];
- NSMenuItem *item = [[NSMenuItem alloc]
- initWithTitle:Q2NSString(title)
- action:@selector(invoke)
- keyEquivalent:@""];
- [item setTarget:block];
- [item setEnabled:enabled];
- lifetime.add([=] {
- [block release];
- });
- return [item autorelease];
- }
- } // namespace
- @interface RpMenu : NSMenu {
- }
- - (rpl::lifetime &) lifetime;
- @end // @interface Menu
- @implementation RpMenu {
- rpl::lifetime _lifetime;
- }
- - (rpl::lifetime &) lifetime {
- return _lifetime;
- }
- @end // @implementation Menu
- @interface qVisualize : NSObject {
- }
- + (id)str:(const QString &)str;
- - (id)initWithString:(const QString &)str;
- + (id)bytearr:(const QByteArray &)arr;
- - (id)initWithByteArray:(const QByteArray &)arr;
- - (id)debugQuickLookObject;
- @end // @interface qVisualize
- @implementation qVisualize {
- NSString *value;
- }
- + (id)bytearr:(const QByteArray &)arr {
- return [[qVisualize alloc] initWithByteArray:arr];
- }
- - (id)initWithByteArray:(const QByteArray &)arr {
- if (self = [super init]) {
- value = [NSString stringWithUTF8String:arr.constData()];
- }
- return self;
- }
- + (id)str:(const QString &)str {
- return [[qVisualize alloc] initWithString:str];
- }
- - (id)initWithString:(const QString &)str {
- if (self = [super init]) {
- value = [NSString stringWithUTF8String:str.toUtf8().constData()];
- }
- return self;
- }
- - (id)debugQuickLookObject {
- return value;
- }
- @end // @implementation qVisualize
- @interface ApplicationDelegate : NSObject<NSApplicationDelegate> {
- }
- - (BOOL) applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag;
- - (void) applicationDidBecomeActive:(NSNotification *)aNotification;
- - (void) applicationDidResignActive:(NSNotification *)aNotification;
- - (void) receiveWakeNote:(NSNotification*)note;
- - (void) ignoreApplicationActivationRightNow;
- - (NSMenu *) applicationDockMenu:(NSApplication *)sender;
- @end // @interface ApplicationDelegate
- ApplicationDelegate *_sharedDelegate = nil;
- @implementation ApplicationDelegate {
- bool _ignoreActivation;
- base::Timer _ignoreActivationStop;
- }
- - (instancetype) init {
- _ignoreActivation = false;
- _ignoreActivationStop.setCallback([self] {
- _ignoreActivation = false;
- });
- return [super init];
- }
- - (BOOL) applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag {
- Core::Sandbox::Instance().customEnterFromEventLoop([&] {
- if (Core::IsAppLaunched()) {
- if (const auto window = Core::App().activeWindow()) {
- if (window->widget()->isHidden()) {
- window->widget()->showFromTray();
- }
- }
- }
- });
- return YES;
- }
- - (void) applicationDidBecomeActive:(NSNotification *)aNotification {
- Core::Sandbox::Instance().customEnterFromEventLoop([&] {
- if (Core::IsAppLaunched() && !_ignoreActivation) {
- Core::App().handleAppActivated();
- if (const auto window = Core::App().activeWindow()) {
- if (window->widget()->isHidden()) {
- if (Core::App().calls().hasVisiblePanel()) {
- Core::App().calls().activateCurrentCall();
- } else {
- window->widget()->showFromTray();
- }
- }
- }
- }
- });
- }
- - (void) applicationDidResignActive:(NSNotification *)aNotification {
- }
- - (void) receiveWakeNote:(NSNotification*)aNotification {
- if (!Core::IsAppLaunched()) {
- return;
- }
- Core::Sandbox::Instance().customEnterFromEventLoop([&] {
- Core::App().checkLocalTime();
- LOG(("Audio Info: "
- "-receiveWakeNote: received, scheduling detach from audio device"));
- Media::Audio::ScheduleDetachFromDeviceSafe();
- #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
- Core::App().settings().setSystemDarkMode(Platform::IsDarkMode());
- #elif QT_VERSION < QT_VERSION_CHECK(6, 6, 0) // Qt < 6.5.0
- QWindowSystemInterface::handleThemeChange();
- #endif // Qt < 6.6.0
- });
- }
- - (void) ignoreApplicationActivationRightNow {
- _ignoreActivation = true;
- _ignoreActivationStop.callOnce(kIgnoreActivationTimeoutMs);
- }
- - (NSMenu *) applicationDockMenu:(NSApplication *)sender {
- if (!Core::IsAppLaunched()) {
- return nil;
- }
- RpMenu* dockMenu = [[[RpMenu alloc] initWithTitle: @""] autorelease];
- [dockMenu setAutoenablesItems:false];
- auto notifyCallback = [] {
- auto &settings = Core::App().settings();
- settings.setDesktopNotify(!settings.desktopNotify());
- };
- [dockMenu addItem:CreateMenuItem(
- Core::App().settings().desktopNotify()
- ? tr::lng_disable_notifications_from_tray(tr::now)
- : tr::lng_enable_notifications_from_tray(tr::now),
- [dockMenu lifetime],
- std::move(notifyCallback))];
- using namespace Media::Player;
- const auto state = instance()->getState(instance()->getActiveType());
- if (!IsStoppedOrStopping(state.state)) {
- [dockMenu addItem:[NSMenuItem separatorItem]];
- [dockMenu addItem:CreateMenuItem(
- tr::lng_mac_menu_player_previous(tr::now),
- [dockMenu lifetime],
- [] { instance()->previous(); },
- instance()->previousAvailable(instance()->getActiveType()))];
- [dockMenu addItem:CreateMenuItem(
- IsPausedOrPausing(state.state)
- ? tr::lng_mac_menu_player_resume(tr::now)
- : tr::lng_mac_menu_player_pause(tr::now),
- [dockMenu lifetime],
- [] { instance()->playPause(); })];
- [dockMenu addItem:CreateMenuItem(
- tr::lng_mac_menu_player_next(tr::now),
- [dockMenu lifetime],
- [] { instance()->next(); },
- instance()->nextAvailable(instance()->getActiveType()))];
- }
- return dockMenu;
- }
- @end // @implementation ApplicationDelegate
- namespace Platform {
- void SetApplicationIcon(const QIcon &icon) {
- NSImage *image = nil;
- if (!icon.isNull()) {
- auto pixmap = icon.pixmap(1024, 1024);
- pixmap.setDevicePixelRatio(style::DevicePixelRatio());
- image = Q2NSImage(pixmap.toImage());
- }
- [[NSApplication sharedApplication] setApplicationIconImage:image];
- }
- } // namespace Platform
- void objc_debugShowAlert(const QString &str) {
- @autoreleasepool {
- NSAlert *alert = [[NSAlert alloc] init];
- alert.messageText = @"Debug Message";
- alert.informativeText = Q2NSString(str);
- [alert runModal];
- }
- }
- void objc_outputDebugString(const QString &str) {
- @autoreleasepool {
- NSLog(@"%@", Q2NSString(str));
- }
- }
- void objc_start() {
- // Patch: Fix macOS regression. On 10.14.4, it crashes on GPU switches.
- // See https://bugreports.qt.io/browse/QTCREATORBUG-22215
- const auto version = QOperatingSystemVersion::current();
- if (version.majorVersion() == 10
- && version.minorVersion() == 14
- && version.microVersion() == 4) {
- qputenv("QT_MAC_PRO_WEBENGINE_WORKAROUND", "1");
- }
- _sharedDelegate = [[ApplicationDelegate alloc] init];
- [[NSApplication sharedApplication] setDelegate:_sharedDelegate];
- [[[NSWorkspace sharedWorkspace] notificationCenter]
- addObserver: _sharedDelegate
- selector: @selector(receiveWakeNote:)
- name: NSWorkspaceDidWakeNotification object: NULL];
- }
- void objc_ignoreApplicationActivationRightNow() {
- if (_sharedDelegate) {
- [_sharedDelegate ignoreApplicationActivationRightNow];
- }
- }
- namespace {
- NSURL *_downloadPathUrl = nil;
- }
- void objc_finish() {
- [_sharedDelegate release];
- _sharedDelegate = nil;
- if (_downloadPathUrl) {
- [_downloadPathUrl stopAccessingSecurityScopedResource];
- _downloadPathUrl = nil;
- }
- }
- void objc_activateProgram(WId winId) {
- [NSApp activateIgnoringOtherApps:YES];
- if (winId) {
- NSWindow *w = [reinterpret_cast<NSView*>(winId) window];
- [w makeKeyAndOrderFront:NSApp];
- }
- }
- bool objc_moveFile(const QString &from, const QString &to) {
- @autoreleasepool {
- NSString *f = Q2NSString(from), *t = Q2NSString(to);
- if ([[NSFileManager defaultManager] fileExistsAtPath:t]) {
- NSData *data = [NSData dataWithContentsOfFile:f];
- if (data) {
- if ([data writeToFile:t atomically:YES]) {
- if ([[NSFileManager defaultManager] removeItemAtPath:f error:nil]) {
- return true;
- }
- }
- }
- } else {
- if ([[NSFileManager defaultManager] moveItemAtPath:f toPath:t error:nil]) {
- return true;
- }
- }
- }
- return false;
- }
- double objc_appkitVersion() {
- return NSAppKitVersionNumber;
- }
- QString objc_documentsPath() {
- NSURL *url = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil];
- if (url) {
- return QString::fromUtf8([[url path] fileSystemRepresentation]) + '/';
- }
- return QString();
- }
- QString objc_appDataPath() {
- NSURL *url = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil];
- if (url) {
- return QString::fromUtf8([[url path] fileSystemRepresentation]) + '/' + AppName.utf16() + '/';
- }
- return QString();
- }
- QByteArray objc_downloadPathBookmark(const QString &path) {
- #ifndef OS_MAC_STORE
- return QByteArray();
- #else // OS_MAC_STORE
- NSURL *url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:path.toUtf8().constData()] isDirectory:YES];
- if (!url) return QByteArray();
- NSError *error = nil;
- NSData *data = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
- return data ? QByteArray::fromNSData(data) : QByteArray();
- #endif // OS_MAC_STORE
- }
- void objc_downloadPathEnableAccess(const QByteArray &bookmark) {
- #ifdef OS_MAC_STORE
- if (bookmark.isEmpty()) return;
- BOOL isStale = NO;
- NSError *error = nil;
- NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark.toNSData() options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
- if (!url) return;
- if ([url startAccessingSecurityScopedResource]) {
- if (_downloadPathUrl) {
- [_downloadPathUrl stopAccessingSecurityScopedResource];
- }
- _downloadPathUrl = [url retain];
- Core::App().settings().setDownloadPath(NS2QString([_downloadPathUrl path]) + '/');
- if (isStale) {
- NSData *data = [_downloadPathUrl bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
- if (data) {
- Core::App().settings().setDownloadPathBookmark(QByteArray::fromNSData(data));
- Local::writeSettings();
- }
- }
- }
- #endif // OS_MAC_STORE
- }
|