specific_mac_p.mm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "platform/mac/specific_mac_p.h"
  8. #include "mainwindow.h"
  9. #include "mainwidget.h"
  10. #include "calls/calls_instance.h"
  11. #include "core/sandbox.h"
  12. #include "core/application.h"
  13. #include "core/core_settings.h"
  14. #include "core/crash_reports.h"
  15. #include "storage/localstorage.h"
  16. #include "media/audio/media_audio.h"
  17. #include "media/player/media_player_instance.h"
  18. #include "window/window_controller.h"
  19. #include "base/platform/mac/base_utilities_mac.h"
  20. #include "base/platform/base_platform_info.h"
  21. #include "lang/lang_keys.h"
  22. #include "base/timer.h"
  23. #include "styles/style_window.h"
  24. #include "platform/platform_specific.h"
  25. #include <QtGui/QWindow>
  26. #include <QtWidgets/QApplication>
  27. #if __has_include(<QtCore/QOperatingSystemVersion>)
  28. #include <QtCore/QOperatingSystemVersion>
  29. #endif // __has_include(<QtCore/QOperatingSystemVersion>)
  30. #if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
  31. #include <qpa/qwindowsysteminterface.h>
  32. #endif // Qt < 6.6.0
  33. #include <Cocoa/Cocoa.h>
  34. #include <CoreFoundation/CFURL.h>
  35. #include <IOKit/IOKitLib.h>
  36. #include <IOKit/hidsystem/ev_keymap.h>
  37. using Platform::Q2NSString;
  38. using Platform::NS2QString;
  39. namespace {
  40. constexpr auto kIgnoreActivationTimeoutMs = 500;
  41. NSMenuItem *CreateMenuItem(
  42. QString title,
  43. rpl::lifetime &lifetime,
  44. Fn<void()> callback,
  45. bool enabled = true) {
  46. id block = [^{
  47. Core::Sandbox::Instance().customEnterFromEventLoop(callback);
  48. } copy];
  49. NSMenuItem *item = [[NSMenuItem alloc]
  50. initWithTitle:Q2NSString(title)
  51. action:@selector(invoke)
  52. keyEquivalent:@""];
  53. [item setTarget:block];
  54. [item setEnabled:enabled];
  55. lifetime.add([=] {
  56. [block release];
  57. });
  58. return [item autorelease];
  59. }
  60. } // namespace
  61. @interface RpMenu : NSMenu {
  62. }
  63. - (rpl::lifetime &) lifetime;
  64. @end // @interface Menu
  65. @implementation RpMenu {
  66. rpl::lifetime _lifetime;
  67. }
  68. - (rpl::lifetime &) lifetime {
  69. return _lifetime;
  70. }
  71. @end // @implementation Menu
  72. @interface qVisualize : NSObject {
  73. }
  74. + (id)str:(const QString &)str;
  75. - (id)initWithString:(const QString &)str;
  76. + (id)bytearr:(const QByteArray &)arr;
  77. - (id)initWithByteArray:(const QByteArray &)arr;
  78. - (id)debugQuickLookObject;
  79. @end // @interface qVisualize
  80. @implementation qVisualize {
  81. NSString *value;
  82. }
  83. + (id)bytearr:(const QByteArray &)arr {
  84. return [[qVisualize alloc] initWithByteArray:arr];
  85. }
  86. - (id)initWithByteArray:(const QByteArray &)arr {
  87. if (self = [super init]) {
  88. value = [NSString stringWithUTF8String:arr.constData()];
  89. }
  90. return self;
  91. }
  92. + (id)str:(const QString &)str {
  93. return [[qVisualize alloc] initWithString:str];
  94. }
  95. - (id)initWithString:(const QString &)str {
  96. if (self = [super init]) {
  97. value = [NSString stringWithUTF8String:str.toUtf8().constData()];
  98. }
  99. return self;
  100. }
  101. - (id)debugQuickLookObject {
  102. return value;
  103. }
  104. @end // @implementation qVisualize
  105. @interface ApplicationDelegate : NSObject<NSApplicationDelegate> {
  106. }
  107. - (BOOL) applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag;
  108. - (void) applicationDidBecomeActive:(NSNotification *)aNotification;
  109. - (void) applicationDidResignActive:(NSNotification *)aNotification;
  110. - (void) receiveWakeNote:(NSNotification*)note;
  111. - (void) ignoreApplicationActivationRightNow;
  112. - (NSMenu *) applicationDockMenu:(NSApplication *)sender;
  113. @end // @interface ApplicationDelegate
  114. ApplicationDelegate *_sharedDelegate = nil;
  115. @implementation ApplicationDelegate {
  116. bool _ignoreActivation;
  117. base::Timer _ignoreActivationStop;
  118. }
  119. - (instancetype) init {
  120. _ignoreActivation = false;
  121. _ignoreActivationStop.setCallback([self] {
  122. _ignoreActivation = false;
  123. });
  124. return [super init];
  125. }
  126. - (BOOL) applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag {
  127. Core::Sandbox::Instance().customEnterFromEventLoop([&] {
  128. if (Core::IsAppLaunched()) {
  129. if (const auto window = Core::App().activeWindow()) {
  130. if (window->widget()->isHidden()) {
  131. window->widget()->showFromTray();
  132. }
  133. }
  134. }
  135. });
  136. return YES;
  137. }
  138. - (void) applicationDidBecomeActive:(NSNotification *)aNotification {
  139. Core::Sandbox::Instance().customEnterFromEventLoop([&] {
  140. if (Core::IsAppLaunched() && !_ignoreActivation) {
  141. Core::App().handleAppActivated();
  142. if (const auto window = Core::App().activeWindow()) {
  143. if (window->widget()->isHidden()) {
  144. if (Core::App().calls().hasVisiblePanel()) {
  145. Core::App().calls().activateCurrentCall();
  146. } else {
  147. window->widget()->showFromTray();
  148. }
  149. }
  150. }
  151. }
  152. });
  153. }
  154. - (void) applicationDidResignActive:(NSNotification *)aNotification {
  155. }
  156. - (void) receiveWakeNote:(NSNotification*)aNotification {
  157. if (!Core::IsAppLaunched()) {
  158. return;
  159. }
  160. Core::Sandbox::Instance().customEnterFromEventLoop([&] {
  161. Core::App().checkLocalTime();
  162. LOG(("Audio Info: "
  163. "-receiveWakeNote: received, scheduling detach from audio device"));
  164. Media::Audio::ScheduleDetachFromDeviceSafe();
  165. #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
  166. Core::App().settings().setSystemDarkMode(Platform::IsDarkMode());
  167. #elif QT_VERSION < QT_VERSION_CHECK(6, 6, 0) // Qt < 6.5.0
  168. QWindowSystemInterface::handleThemeChange();
  169. #endif // Qt < 6.6.0
  170. });
  171. }
  172. - (void) ignoreApplicationActivationRightNow {
  173. _ignoreActivation = true;
  174. _ignoreActivationStop.callOnce(kIgnoreActivationTimeoutMs);
  175. }
  176. - (NSMenu *) applicationDockMenu:(NSApplication *)sender {
  177. if (!Core::IsAppLaunched()) {
  178. return nil;
  179. }
  180. RpMenu* dockMenu = [[[RpMenu alloc] initWithTitle: @""] autorelease];
  181. [dockMenu setAutoenablesItems:false];
  182. auto notifyCallback = [] {
  183. auto &settings = Core::App().settings();
  184. settings.setDesktopNotify(!settings.desktopNotify());
  185. };
  186. [dockMenu addItem:CreateMenuItem(
  187. Core::App().settings().desktopNotify()
  188. ? tr::lng_disable_notifications_from_tray(tr::now)
  189. : tr::lng_enable_notifications_from_tray(tr::now),
  190. [dockMenu lifetime],
  191. std::move(notifyCallback))];
  192. using namespace Media::Player;
  193. const auto state = instance()->getState(instance()->getActiveType());
  194. if (!IsStoppedOrStopping(state.state)) {
  195. [dockMenu addItem:[NSMenuItem separatorItem]];
  196. [dockMenu addItem:CreateMenuItem(
  197. tr::lng_mac_menu_player_previous(tr::now),
  198. [dockMenu lifetime],
  199. [] { instance()->previous(); },
  200. instance()->previousAvailable(instance()->getActiveType()))];
  201. [dockMenu addItem:CreateMenuItem(
  202. IsPausedOrPausing(state.state)
  203. ? tr::lng_mac_menu_player_resume(tr::now)
  204. : tr::lng_mac_menu_player_pause(tr::now),
  205. [dockMenu lifetime],
  206. [] { instance()->playPause(); })];
  207. [dockMenu addItem:CreateMenuItem(
  208. tr::lng_mac_menu_player_next(tr::now),
  209. [dockMenu lifetime],
  210. [] { instance()->next(); },
  211. instance()->nextAvailable(instance()->getActiveType()))];
  212. }
  213. return dockMenu;
  214. }
  215. @end // @implementation ApplicationDelegate
  216. namespace Platform {
  217. void SetApplicationIcon(const QIcon &icon) {
  218. NSImage *image = nil;
  219. if (!icon.isNull()) {
  220. auto pixmap = icon.pixmap(1024, 1024);
  221. pixmap.setDevicePixelRatio(style::DevicePixelRatio());
  222. image = Q2NSImage(pixmap.toImage());
  223. }
  224. [[NSApplication sharedApplication] setApplicationIconImage:image];
  225. }
  226. } // namespace Platform
  227. void objc_debugShowAlert(const QString &str) {
  228. @autoreleasepool {
  229. NSAlert *alert = [[NSAlert alloc] init];
  230. alert.messageText = @"Debug Message";
  231. alert.informativeText = Q2NSString(str);
  232. [alert runModal];
  233. }
  234. }
  235. void objc_outputDebugString(const QString &str) {
  236. @autoreleasepool {
  237. NSLog(@"%@", Q2NSString(str));
  238. }
  239. }
  240. void objc_start() {
  241. // Patch: Fix macOS regression. On 10.14.4, it crashes on GPU switches.
  242. // See https://bugreports.qt.io/browse/QTCREATORBUG-22215
  243. const auto version = QOperatingSystemVersion::current();
  244. if (version.majorVersion() == 10
  245. && version.minorVersion() == 14
  246. && version.microVersion() == 4) {
  247. qputenv("QT_MAC_PRO_WEBENGINE_WORKAROUND", "1");
  248. }
  249. _sharedDelegate = [[ApplicationDelegate alloc] init];
  250. [[NSApplication sharedApplication] setDelegate:_sharedDelegate];
  251. [[[NSWorkspace sharedWorkspace] notificationCenter]
  252. addObserver: _sharedDelegate
  253. selector: @selector(receiveWakeNote:)
  254. name: NSWorkspaceDidWakeNotification object: NULL];
  255. }
  256. void objc_ignoreApplicationActivationRightNow() {
  257. if (_sharedDelegate) {
  258. [_sharedDelegate ignoreApplicationActivationRightNow];
  259. }
  260. }
  261. namespace {
  262. NSURL *_downloadPathUrl = nil;
  263. }
  264. void objc_finish() {
  265. [_sharedDelegate release];
  266. _sharedDelegate = nil;
  267. if (_downloadPathUrl) {
  268. [_downloadPathUrl stopAccessingSecurityScopedResource];
  269. _downloadPathUrl = nil;
  270. }
  271. }
  272. void objc_activateProgram(WId winId) {
  273. [NSApp activateIgnoringOtherApps:YES];
  274. if (winId) {
  275. NSWindow *w = [reinterpret_cast<NSView*>(winId) window];
  276. [w makeKeyAndOrderFront:NSApp];
  277. }
  278. }
  279. bool objc_moveFile(const QString &from, const QString &to) {
  280. @autoreleasepool {
  281. NSString *f = Q2NSString(from), *t = Q2NSString(to);
  282. if ([[NSFileManager defaultManager] fileExistsAtPath:t]) {
  283. NSData *data = [NSData dataWithContentsOfFile:f];
  284. if (data) {
  285. if ([data writeToFile:t atomically:YES]) {
  286. if ([[NSFileManager defaultManager] removeItemAtPath:f error:nil]) {
  287. return true;
  288. }
  289. }
  290. }
  291. } else {
  292. if ([[NSFileManager defaultManager] moveItemAtPath:f toPath:t error:nil]) {
  293. return true;
  294. }
  295. }
  296. }
  297. return false;
  298. }
  299. double objc_appkitVersion() {
  300. return NSAppKitVersionNumber;
  301. }
  302. QString objc_documentsPath() {
  303. NSURL *url = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil];
  304. if (url) {
  305. return QString::fromUtf8([[url path] fileSystemRepresentation]) + '/';
  306. }
  307. return QString();
  308. }
  309. QString objc_appDataPath() {
  310. NSURL *url = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil];
  311. if (url) {
  312. return QString::fromUtf8([[url path] fileSystemRepresentation]) + '/' + AppName.utf16() + '/';
  313. }
  314. return QString();
  315. }
  316. QByteArray objc_downloadPathBookmark(const QString &path) {
  317. #ifndef OS_MAC_STORE
  318. return QByteArray();
  319. #else // OS_MAC_STORE
  320. NSURL *url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:path.toUtf8().constData()] isDirectory:YES];
  321. if (!url) return QByteArray();
  322. NSError *error = nil;
  323. NSData *data = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
  324. return data ? QByteArray::fromNSData(data) : QByteArray();
  325. #endif // OS_MAC_STORE
  326. }
  327. void objc_downloadPathEnableAccess(const QByteArray &bookmark) {
  328. #ifdef OS_MAC_STORE
  329. if (bookmark.isEmpty()) return;
  330. BOOL isStale = NO;
  331. NSError *error = nil;
  332. NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark.toNSData() options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
  333. if (!url) return;
  334. if ([url startAccessingSecurityScopedResource]) {
  335. if (_downloadPathUrl) {
  336. [_downloadPathUrl stopAccessingSecurityScopedResource];
  337. }
  338. _downloadPathUrl = [url retain];
  339. Core::App().settings().setDownloadPath(NS2QString([_downloadPathUrl path]) + '/');
  340. if (isStale) {
  341. NSData *data = [_downloadPathUrl bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
  342. if (data) {
  343. Core::App().settings().setDownloadPathBookmark(QByteArray::fromNSData(data));
  344. Local::writeSettings();
  345. }
  346. }
  347. }
  348. #endif // OS_MAC_STORE
  349. }