application.cpp 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885
  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 "core/application.h"
  8. #include "data/data_abstract_structure.h"
  9. #include "data/data_forum.h"
  10. #include "data/data_message_reactions.h"
  11. #include "data/data_session.h"
  12. #include "data/data_download_manager.h"
  13. #include "base/battery_saving.h"
  14. #include "base/event_filter.h"
  15. #include "base/concurrent_timer.h"
  16. #include "base/options.h"
  17. #include "base/qt_signal_producer.h"
  18. #include "base/timer.h"
  19. #include "base/unixtime.h"
  20. #include "core/core_settings.h"
  21. #include "core/update_checker.h"
  22. #include "core/shortcuts.h"
  23. #include "core/sandbox.h"
  24. #include "core/local_url_handlers.h"
  25. #include "core/launcher.h"
  26. #include "core/ui_integration.h"
  27. #include "core/wallet_replacer.h"
  28. #include "chat_helpers/emoji_keywords.h"
  29. #include "chat_helpers/stickers_emoji_image_loader.h"
  30. #include "base/platform/base_platform_global_shortcuts.h"
  31. #include "base/platform/base_platform_url_scheme.h"
  32. #include "base/platform/base_platform_last_input.h"
  33. #include "base/platform/base_platform_info.h"
  34. #include "platform/platform_specific.h"
  35. #include "platform/platform_integration.h"
  36. #include "history/history.h"
  37. #include "apiwrap.h"
  38. #include "api/api_updates.h"
  39. #include "calls/calls_instance.h"
  40. #include "countries/countries_manager.h"
  41. #include "iv/iv_delegate_impl.h"
  42. #include "iv/iv_instance.h"
  43. #include "iv/iv_data.h"
  44. #include "lang/lang_translator.h"
  45. #include "lang/lang_cloud_manager.h"
  46. #include "lang/lang_hardcoded.h"
  47. #include "lang/lang_instance.h"
  48. #include "inline_bots/bot_attach_web_view.h"
  49. #include "mainwidget.h"
  50. #include "tray.h"
  51. #include "core/click_handler_types.h" // ClickHandlerContext.
  52. #include "core/crash_reports.h"
  53. #include "main/main_account.h"
  54. #include "main/main_domain.h"
  55. #include "main/main_session.h"
  56. #include "media/view/media_view_overlay_widget.h"
  57. #include "media/view/media_view_open_common.h"
  58. #include "mtproto/mtproto_dc_options.h"
  59. #include "mtproto/mtproto_config.h"
  60. #include "media/audio/media_audio_track.h"
  61. #include "media/player/media_player_instance.h"
  62. #include "media/player/media_player_float.h"
  63. #include "media/clip/media_clip_reader.h" // For Media::Clip::Finish().
  64. #include "media/system_media_controls_manager.h"
  65. #include "window/notifications_manager.h"
  66. #include "window/themes/window_theme.h"
  67. #include "ui/widgets/tooltip.h"
  68. #include "ui/gl/gl_detection.h"
  69. #include "ui/text/text_options.h"
  70. #include "ui/effects/spoiler_mess.h"
  71. #include "ui/cached_round_corners.h"
  72. #include "ui/power_saving.h"
  73. #include "storage/storage_domain.h"
  74. #include "storage/storage_databases.h"
  75. #include "storage/localstorage.h"
  76. #include "payments/payments_checkout_process.h"
  77. #include "export/export_manager.h"
  78. #include "webrtc/webrtc_environment.h"
  79. #include "window/window_separate_id.h"
  80. #include "window/window_session_controller.h"
  81. #include "window/window_controller.h"
  82. #include "boxes/abstract_box.h"
  83. #include "base/qthelp_regex.h"
  84. #include "base/qthelp_url.h"
  85. #include "boxes/premium_limits_box.h"
  86. #include "ui/boxes/confirm_box.h"
  87. #include "styles/style_window.h"
  88. #include <QtCore/QStandardPaths>
  89. #include <QtCore/QMimeDatabase>
  90. #include <QtGui/QGuiApplication>
  91. #include <QtGui/QScreen>
  92. #include <QtGui/QWindow>
  93. #include <ksandbox.h>
  94. namespace Core {
  95. namespace {
  96. constexpr auto kQuitPreventTimeoutMs = crl::time(1500);
  97. constexpr auto kAutoLockTimeoutLateMs = crl::time(3000);
  98. constexpr auto kClearEmojiImageSourceTimeout = 10 * crl::time(1000);
  99. constexpr auto kFileOpenTimeoutMs = crl::time(1000);
  100. LaunchState GlobalLaunchState/* = LaunchState::Running*/;
  101. void SetCrashAnnotationsGL() {
  102. #ifdef DESKTOP_APP_USE_ANGLE
  103. CrashReports::SetAnnotation("OpenGL ANGLE", [] {
  104. if (Core::App().settings().disableOpenGL()) {
  105. return "Disabled";
  106. } else switch (Ui::GL::CurrentANGLE()) {
  107. case Ui::GL::ANGLE::Auto: return "Auto";
  108. case Ui::GL::ANGLE::D3D11: return "Direct3D 11";
  109. case Ui::GL::ANGLE::D3D9: return "Direct3D 9";
  110. case Ui::GL::ANGLE::D3D11on12: return "D3D11on12";
  111. //case Ui::GL::ANGLE::OpenGL: return "OpenGL";
  112. }
  113. Unexpected("Ui::GL::CurrentANGLE value in SetupANGLE.");
  114. }());
  115. #else // DESKTOP_APP_USE_ANGLE
  116. CrashReports::SetAnnotation(
  117. "OpenGL",
  118. Core::App().settings().disableOpenGL() ? "Disabled" : "Enabled");
  119. #endif // DESKTOP_APP_USE_ANGLE
  120. }
  121. base::options::toggle OptionSkipUrlSchemeRegister({
  122. .id = kOptionSkipUrlSchemeRegister,
  123. .name = "Skip URL scheme register",
  124. .description = "Don't re-register tg:// URL scheme on autoupdate.",
  125. });
  126. } // namespace
  127. Application *Application::Instance = nullptr;
  128. const char kOptionSkipUrlSchemeRegister[] = "skip-url-scheme-register";
  129. struct Application::Private {
  130. base::Timer quitTimer;
  131. UiIntegration uiIntegration;
  132. Settings settings;
  133. };
  134. Application::Application()
  135. : QObject()
  136. , _private(std::make_unique<Private>())
  137. , _platformIntegration(Platform::Integration::Create())
  138. , _batterySaving(std::make_unique<base::BatterySaving>())
  139. , _mediaDevices(std::make_unique<Webrtc::Environment>())
  140. , _databases(std::make_unique<Storage::Databases>())
  141. , _animationsManager(std::make_unique<Ui::Animations::Manager>())
  142. , _clearEmojiImageLoaderTimer([=] { clearEmojiSourceImages(); })
  143. , _audio(std::make_unique<Media::Audio::Instance>())
  144. , _fallbackProductionConfig(
  145. std::make_unique<MTP::Config>(MTP::Environment::Production))
  146. , _downloadManager(std::make_unique<Data::DownloadManager>())
  147. , _domain(std::make_unique<Main::Domain>(cDataFile()))
  148. , _exportManager(std::make_unique<Export::Manager>())
  149. , _calls(std::make_unique<Calls::Instance>())
  150. , _iv(std::make_unique<Iv::Instance>(
  151. Ui::CreateChild<Iv::DelegateImpl>(this)))
  152. , _langpack(std::make_unique<Lang::Instance>())
  153. , _langCloudManager(std::make_unique<Lang::CloudManager>(langpack()))
  154. , _emojiKeywords(std::make_unique<ChatHelpers::EmojiKeywords>())
  155. , _tray(std::make_unique<Tray>())
  156. , _autoLockTimer([=] { checkAutoLock(); })
  157. , _fileOpenTimer([=] { checkFileOpen(); }) {
  158. Ui::Integration::Set(&_private->uiIntegration);
  159. _platformIntegration->init();
  160. passcodeLockChanges(
  161. ) | rpl::start_with_next([=](bool locked) {
  162. _shouldLockAt = 0;
  163. if (locked) {
  164. closeAdditionalWindows();
  165. }
  166. }, _lifetime);
  167. passcodeLockChanges(
  168. ) | rpl::start_with_next([=] {
  169. _notifications->updateAll();
  170. updateWindowTitles();
  171. }, _lifetime);
  172. settings().windowTitleContentChanges(
  173. ) | rpl::start_with_next([=] {
  174. updateWindowTitles();
  175. }, _lifetime);
  176. _domain->activeSessionChanges(
  177. ) | rpl::start_with_next([=](Main::Session *session) {
  178. if (session && !UpdaterDisabled()) { // #TODO multi someSessionValue
  179. UpdateChecker().setMtproto(session);
  180. }
  181. }, _lifetime);
  182. }
  183. void Application::closeAdditionalWindows() {
  184. Payments::CheckoutProcess::ClearAll();
  185. for (const auto &[index, account] : _domain->accounts()) {
  186. if (account->sessionExists()) {
  187. account->session().attachWebView().closeAll();
  188. }
  189. }
  190. _iv->closeAll();
  191. }
  192. Application::~Application() {
  193. if (_saveSettingsTimer && _saveSettingsTimer->isActive()) {
  194. Local::writeSettings();
  195. }
  196. _windowStack.clear();
  197. setLastActiveWindow(nullptr);
  198. _windowInSettings = _lastActivePrimaryWindow = nullptr;
  199. _closingAsyncWindows.clear();
  200. _windows.clear();
  201. _mediaView = nullptr;
  202. _notifications->clearAllFast();
  203. // We must manually destroy all windows before going further.
  204. // DestroyWindow on Windows (at least with an active WebView) enters
  205. // event loop and invoke scheduled crl::on_main callbacks.
  206. //
  207. // For example Domain::removeRedundantAccounts() is called from
  208. // Domain::finish() and there is a violation on Ensures(started()).
  209. closeAdditionalWindows();
  210. _domain->finish();
  211. Local::finish();
  212. Shortcuts::Finish();
  213. Ui::Emoji::Clear();
  214. Media::Clip::Finish();
  215. Ui::FinishCachedCorners();
  216. Data::clearGlobalStructures();
  217. Window::Theme::Uninitialize();
  218. _mediaControlsManager = nullptr;
  219. Media::Player::finish(_audio.get());
  220. style::StopManager();
  221. Instance = nullptr;
  222. }
  223. void Application::run() {
  224. // Depends on OpenSSL on macOS, so on ThirdParty::start().
  225. // Depends on notifications settings.
  226. _notifications = std::make_unique<Window::Notifications::System>();
  227. startLocalStorage();
  228. style::SetCustomFont(settings().customFontFamily());
  229. style::internal::StartFonts();
  230. ValidateScale();
  231. refreshGlobalProxy(); // Depends on app settings being read.
  232. if (const auto old = Local::oldSettingsVersion(); old < AppVersion) {
  233. autoRegisterUrlScheme();
  234. Platform::NewVersionLaunched(old);
  235. }
  236. if (cAutoStart() && !Platform::AutostartSupported()) {
  237. cSetAutoStart(false);
  238. }
  239. if (cLaunchMode() == LaunchModeAutoStart && Platform::AutostartSkip()) {
  240. Platform::AutostartToggle(false);
  241. Quit();
  242. return;
  243. }
  244. _translator = std::make_unique<Lang::Translator>();
  245. QCoreApplication::instance()->installTranslator(_translator.get());
  246. style::StartManager(cScale());
  247. Ui::InitTextOptions();
  248. Ui::StartCachedCorners();
  249. Ui::Emoji::Init();
  250. Ui::PreloadTextSpoilerMask();
  251. startShortcuts();
  252. startEmojiImageLoader();
  253. startSystemDarkModeViewer();
  254. Media::Player::start(_audio.get());
  255. if (MediaControlsManager::Supported()) {
  256. _mediaControlsManager = std::make_unique<MediaControlsManager>();
  257. }
  258. rpl::combine(
  259. _batterySaving->value(),
  260. settings().ignoreBatterySavingValue()
  261. ) | rpl::start_with_next([=](bool saving, bool ignore) {
  262. PowerSaving::SetForceAll(saving && !ignore);
  263. }, _lifetime);
  264. style::ShortAnimationPlaying(
  265. ) | rpl::start_with_next([=](bool playing) {
  266. if (playing) {
  267. MTP::details::pause();
  268. } else {
  269. MTP::details::unpause();
  270. }
  271. }, _lifetime);
  272. DEBUG_LOG(("Application Info: inited..."));
  273. DEBUG_LOG(("Application Info: starting app..."));
  274. // Create mime database, so it won't be slow later.
  275. QMimeDatabase().mimeTypeForName(u"text/plain"_q);
  276. // Check now to avoid re-entrance later.
  277. [[maybe_unused]] const auto ivSupported = Iv::ShowButton();
  278. _windows.emplace(nullptr, std::make_unique<Window::Controller>());
  279. setLastActiveWindow(_windows.front().second.get());
  280. _windowInSettings = _lastActivePrimaryWindow = _lastActiveWindow;
  281. _domain->activeChanges(
  282. ) | rpl::start_with_next([=](not_null<Main::Account*> account) {
  283. showAccount(account);
  284. }, _lifetime);
  285. (
  286. _domain->activeValue(
  287. ) | rpl::to_empty | rpl::filter([=] {
  288. return _domain->started();
  289. }) | rpl::take(1)
  290. ) | rpl::then(
  291. _domain->accountsChanges()
  292. ) | rpl::map([=] {
  293. return (_domain->accounts().size() > Main::Domain::kMaxAccounts)
  294. ? _domain->activeChanges()
  295. : rpl::never<not_null<Main::Account*>>();
  296. }) | rpl::flatten_latest(
  297. ) | rpl::start_with_next([=](not_null<Main::Account*> account) {
  298. const auto ordered = _domain->orderedAccounts();
  299. const auto it = ranges::find(ordered, account);
  300. if (_lastActivePrimaryWindow && it != end(ordered)) {
  301. const auto index = std::distance(begin(ordered), it);
  302. if ((index + 1) > _domain->maxAccounts()) {
  303. _lastActivePrimaryWindow->show(Box(
  304. AccountsLimitBox,
  305. &account->session()));
  306. }
  307. }
  308. }, _lifetime);
  309. QCoreApplication::instance()->installEventFilter(this);
  310. appDeactivatedValue(
  311. ) | rpl::start_with_next([=](bool deactivated) {
  312. if (deactivated) {
  313. handleAppDeactivated();
  314. } else {
  315. handleAppActivated();
  316. }
  317. }, _lifetime);
  318. DEBUG_LOG(("Application Info: window created..."));
  319. startDomain();
  320. startTray();
  321. _lastActivePrimaryWindow->firstShow();
  322. startMediaView();
  323. DEBUG_LOG(("Application Info: showing."));
  324. _lastActivePrimaryWindow->finishFirstShow();
  325. if (!_lastActivePrimaryWindow->locked() && cStartToSettings()) {
  326. _lastActivePrimaryWindow->showSettings();
  327. }
  328. _lastActivePrimaryWindow->updateIsActiveFocus();
  329. for (const auto &error : Shortcuts::Errors()) {
  330. LOG(("Shortcuts Error: %1").arg(error));
  331. }
  332. SetCrashAnnotationsGL();
  333. if (Ui::GL::LastCrashCheckFailed()) {
  334. showOpenGLCrashNotification();
  335. }
  336. _openInMediaViewRequests.events(
  337. ) | rpl::start_with_next([=](Media::View::OpenRequest &&request) {
  338. if (_mediaView) {
  339. _mediaView->show(std::move(request));
  340. }
  341. }, _lifetime);
  342. {
  343. const auto countries = std::make_shared<Countries::Manager>(
  344. _domain.get());
  345. countries->lifetime().add([=] {
  346. [[maybe_unused]] const auto countriesCopy = countries;
  347. });
  348. }
  349. processCreatedWindow(_lastActivePrimaryWindow);
  350. }
  351. void Application::autoRegisterUrlScheme() {
  352. if (!OptionSkipUrlSchemeRegister.value()) {
  353. InvokeQueued(this, [] { RegisterUrlScheme(); });
  354. }
  355. }
  356. void Application::showAccount(not_null<Main::Account*> account) {
  357. if (const auto separate = separateWindowFor(account)) {
  358. _lastActivePrimaryWindow = separate;
  359. separate->activate();
  360. } else if (const auto last = activePrimaryWindow()) {
  361. last->showAccount(account);
  362. }
  363. }
  364. void Application::checkWindowId(not_null<Window::Controller*> window) {
  365. const auto id = window->id();
  366. for (auto &[existingId, existing] : _windows) {
  367. if (existing.get() == window && existingId != id) {
  368. auto found = std::move(existing);
  369. _windows.remove(existingId);
  370. _windows.emplace(id, std::move(found));
  371. break;
  372. }
  373. }
  374. }
  375. void Application::showOpenGLCrashNotification() {
  376. const auto enable = [=] {
  377. Ui::GL::CrashCheckFinish();
  378. settings().setDisableOpenGL(false);
  379. Local::writeSettings();
  380. Restart();
  381. };
  382. const auto keepDisabled = [=](Fn<void()> close) {
  383. Ui::GL::CrashCheckFinish();
  384. settings().setDisableOpenGL(true);
  385. Local::writeSettings();
  386. close();
  387. };
  388. _lastActivePrimaryWindow->show(Ui::MakeConfirmBox({
  389. .text = ""
  390. "There may be a problem with your graphics drivers and OpenGL. "
  391. "Try updating your drivers.\n\n"
  392. "OpenGL has been disabled. You can try to enable it again "
  393. "or keep it disabled if crashes continue.",
  394. .confirmed = enable,
  395. .cancelled = keepDisabled,
  396. .confirmText = "Enable",
  397. .cancelText = "Keep Disabled",
  398. }));
  399. }
  400. void Application::startDomain() {
  401. const auto state = _domain->start(QByteArray());
  402. if (state != Storage::StartResult::IncorrectPasscodeLegacy) {
  403. // In case of non-legacy passcoded app all global settings are ready.
  404. startSettingsAndBackground();
  405. }
  406. if (state != Storage::StartResult::Success) {
  407. lockByPasscode();
  408. DEBUG_LOG(("Application Info: passcode needed..."));
  409. }
  410. }
  411. void Application::startSettingsAndBackground() {
  412. Local::rewriteSettingsIfNeeded();
  413. Window::Theme::Background()->start();
  414. checkSystemDarkMode();
  415. }
  416. void Application::checkSystemDarkMode() {
  417. const auto maybeDarkMode = settings().systemDarkMode();
  418. const auto darkModeEnabled = settings().systemDarkModeEnabled();
  419. const auto needToSwitch = darkModeEnabled
  420. && maybeDarkMode
  421. && (*maybeDarkMode != Window::Theme::IsNightMode());
  422. if (needToSwitch) {
  423. Window::Theme::ToggleNightMode();
  424. Window::Theme::KeepApplied();
  425. }
  426. }
  427. void Application::startSystemDarkModeViewer() {
  428. if (Window::Theme::Background()->editingTheme()) {
  429. settings().setSystemDarkModeEnabled(false);
  430. }
  431. rpl::merge(
  432. settings().systemDarkModeChanges() | rpl::to_empty,
  433. settings().systemDarkModeEnabledChanges() | rpl::to_empty
  434. ) | rpl::start_with_next([=] {
  435. checkSystemDarkMode();
  436. }, _lifetime);
  437. }
  438. void Application::enumerateWindows(Fn<void(
  439. not_null<Window::Controller*>)> callback) const {
  440. for (const auto &window : ranges::views::values(_windows)) {
  441. callback(window.get());
  442. }
  443. }
  444. void Application::processCreatedWindow(
  445. not_null<Window::Controller*> window) {
  446. window->openInMediaViewRequests(
  447. ) | rpl::start_to_stream(_openInMediaViewRequests, window->lifetime());
  448. }
  449. void Application::startMediaView() {
  450. #ifdef Q_OS_MAC
  451. // On macOS we create some windows async, otherwise they're
  452. // added to the Dock Menu as a visible window and are removed
  453. // only after first show and then hide.
  454. InvokeQueued(this, [=] {
  455. _mediaView = std::make_unique<Media::View::OverlayWidget>();
  456. });
  457. #elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_WIN
  458. // On Windows we needed such hack for the main window, otherwise
  459. // somewhere inside the media viewer creating code its geometry
  460. // was broken / lost to some invalid values.
  461. const auto current = _lastActivePrimaryWindow->widget()->geometry();
  462. _mediaView = std::make_unique<Media::View::OverlayWidget>();
  463. _lastActivePrimaryWindow->widget()->Ui::RpWidget::setGeometry(current);
  464. #else
  465. _mediaView = std::make_unique<Media::View::OverlayWidget>();
  466. #endif // Q_OS_MAC || Q_OS_WIN
  467. }
  468. void Application::startTray() {
  469. #ifdef Q_OS_MAC
  470. // On macOS we create some windows async, otherwise they're
  471. // added to the Dock Menu as a visible window and are removed
  472. // only after first show and then hide, tray icon being "Item-0".
  473. InvokeQueued(this, [=] {
  474. createTray();
  475. });
  476. #else // Q_OS_MAC
  477. createTray();
  478. #endif // Q_OS_MAC
  479. }
  480. void Application::createTray() {
  481. using WindowRaw = not_null<Window::Controller*>;
  482. _tray->create();
  483. _tray->aboutToShowRequests(
  484. ) | rpl::start_with_next([=] {
  485. enumerateWindows([&](WindowRaw w) { w->updateIsActive(); });
  486. _tray->updateMenuText();
  487. }, _lifetime);
  488. _tray->showFromTrayRequests(
  489. ) | rpl::start_with_next([=] {
  490. activate();
  491. }, _lifetime);
  492. _tray->hideToTrayRequests(
  493. ) | rpl::start_with_next([=] {
  494. enumerateWindows([&](WindowRaw w) {
  495. w->widget()->minimizeToTray();
  496. });
  497. }, _lifetime);
  498. }
  499. void Application::activate() {
  500. for (const auto &window : _windowStack) {
  501. if (window == _lastActiveWindow) {
  502. break;
  503. }
  504. const auto widget = window->widget();
  505. const auto wasHidden = !widget->isVisible();
  506. const auto state = widget->windowState();
  507. if (state & Qt::WindowMinimized) {
  508. widget->setWindowState(state & ~Qt::WindowMinimized);
  509. }
  510. widget->setVisible(true);
  511. widget->activateWindow();
  512. if (wasHidden) {
  513. if (const auto session = window->sessionController()) {
  514. session->content()->windowShown();
  515. }
  516. }
  517. }
  518. if (_lastActiveWindow) {
  519. _lastActiveWindow->widget()->showFromTray();
  520. }
  521. }
  522. auto Application::prepareEmojiSourceImages()
  523. -> std::shared_ptr<Ui::Emoji::UniversalImages> {
  524. const auto &images = Ui::Emoji::SourceImages();
  525. if (settings().largeEmoji()) {
  526. return images;
  527. }
  528. Ui::Emoji::ClearSourceImages(images);
  529. return std::make_shared<Ui::Emoji::UniversalImages>(images->id());
  530. }
  531. void Application::clearEmojiSourceImages() {
  532. _emojiImageLoader.with([](Stickers::EmojiImageLoader &loader) {
  533. crl::on_main([images = loader.releaseImages()]{
  534. Ui::Emoji::ClearSourceImages(images);
  535. });
  536. });
  537. }
  538. bool Application::isActiveForTrayMenu() const {
  539. return ranges::any_of(ranges::views::values(_windows), [=](
  540. const std::unique_ptr<Window::Controller> &controller) {
  541. return controller->widget()->isActiveForTrayMenu();
  542. });
  543. }
  544. bool Application::hideMediaView() {
  545. if (_mediaView
  546. && _mediaView->isFullScreen()
  547. && !_mediaView->isMinimized()
  548. && !_mediaView->isHidden()) {
  549. _mediaView->close();
  550. return true;
  551. }
  552. return false;
  553. }
  554. bool Application::eventFilter(QObject *object, QEvent *e) {
  555. switch (e->type()) {
  556. case QEvent::KeyPress: {
  557. updateNonIdle();
  558. const auto event = static_cast<QKeyEvent*>(e);
  559. if (base::Platform::GlobalShortcuts::IsToggleFullScreenKey(event)
  560. && toggleActiveWindowFullScreen()) {
  561. return true;
  562. }
  563. } break;
  564. case QEvent::MouseButtonPress:
  565. case QEvent::TouchBegin:
  566. case QEvent::Wheel: {
  567. updateNonIdle();
  568. } break;
  569. case QEvent::ShortcutOverride: {
  570. // handle shortcuts ourselves
  571. return true;
  572. } break;
  573. case QEvent::Shortcut: {
  574. const auto event = static_cast<QShortcutEvent*>(e);
  575. DEBUG_LOG(("Shortcut event caught: %1"
  576. ).arg(event->key().toString()));
  577. if (Shortcuts::HandleEvent(object, event)) {
  578. return true;
  579. }
  580. } break;
  581. case QEvent::ApplicationActivate: {
  582. if (object == QCoreApplication::instance()) {
  583. updateNonIdle();
  584. }
  585. } break;
  586. case QEvent::FileOpen: {
  587. if (object == QCoreApplication::instance()) {
  588. const auto event = static_cast<QFileOpenEvent*>(e);
  589. if (const auto file = event->file(); !file.isEmpty()) {
  590. _filesToOpen.append(file);
  591. _fileOpenTimer.callOnce(kFileOpenTimeoutMs);
  592. } else if (event->url().scheme() == u"tg"_q
  593. || event->url().scheme() == u"tonsite"_q) {
  594. const auto url = QString::fromUtf8(
  595. event->url().toEncoded().trimmed());
  596. cSetStartUrl(url.mid(0, 8192));
  597. checkStartUrl();
  598. if (_lastActivePrimaryWindow
  599. && StartUrlRequiresActivate(url)) {
  600. _lastActivePrimaryWindow->activate();
  601. }
  602. } else if (event->url().scheme() == u"interpret"_q) {
  603. _filesToOpen.append(event->url().toString());
  604. _fileOpenTimer.callOnce(kFileOpenTimeoutMs);
  605. }
  606. }
  607. } break;
  608. case QEvent::ThemeChange: {
  609. if (Platform::IsLinux()
  610. && object == QGuiApplication::allWindows().constFirst()) {
  611. Core::App().refreshApplicationIcon();
  612. Core::App().tray().updateIconCounters();
  613. }
  614. } break;
  615. }
  616. return QObject::eventFilter(object, e);
  617. }
  618. Settings &Application::settings() {
  619. return _private->settings;
  620. }
  621. const Settings &Application::settings() const {
  622. return _private->settings;
  623. }
  624. void Application::saveSettingsDelayed(crl::time delay) {
  625. if (_saveSettingsTimer) {
  626. _saveSettingsTimer->callOnce(delay);
  627. }
  628. }
  629. void Application::saveSettings() {
  630. Local::writeSettings();
  631. }
  632. bool Application::canReadDefaultDownloadPath() const {
  633. return KSandbox::isInside()
  634. ? base::CanReadDirectory(
  635. QStandardPaths::writableLocation(
  636. QStandardPaths::DownloadLocation))
  637. : true;
  638. }
  639. bool Application::canSaveFileWithoutAskingForPath() const {
  640. return !settings().askDownloadPath();
  641. }
  642. MTP::Config &Application::fallbackProductionConfig() const {
  643. if (!_fallbackProductionConfig) {
  644. _fallbackProductionConfig = std::make_unique<MTP::Config>(
  645. MTP::Environment::Production);
  646. }
  647. return *_fallbackProductionConfig;
  648. }
  649. void Application::refreshFallbackProductionConfig(
  650. const MTP::Config &config) {
  651. if (config.environment() == MTP::Environment::Production) {
  652. _fallbackProductionConfig = std::make_unique<MTP::Config>(config);
  653. }
  654. }
  655. void Application::constructFallbackProductionConfig(
  656. const QByteArray &serialized) {
  657. if (auto config = MTP::Config::FromSerialized(serialized)) {
  658. if (config->environment() == MTP::Environment::Production) {
  659. _fallbackProductionConfig = std::move(config);
  660. }
  661. }
  662. }
  663. void Application::setCurrentProxy(
  664. const MTP::ProxyData &proxy,
  665. MTP::ProxyData::Settings settings) {
  666. auto &my = _private->settings.proxy();
  667. const auto current = [&] {
  668. return my.isEnabled() ? my.selected() : MTP::ProxyData();
  669. };
  670. const auto was = current();
  671. my.setSelected(proxy);
  672. my.setSettings(settings);
  673. const auto now = current();
  674. refreshGlobalProxy();
  675. _proxyChanges.fire({ was, now });
  676. my.connectionTypeChangesNotify();
  677. }
  678. auto Application::proxyChanges() const -> rpl::producer<ProxyChange> {
  679. return _proxyChanges.events();
  680. }
  681. void Application::badMtprotoConfigurationError() {
  682. if (settings().proxy().isEnabled() && !_badProxyDisableBox) {
  683. const auto disableCallback = [=] {
  684. setCurrentProxy(
  685. settings().proxy().selected(),
  686. MTP::ProxyData::Settings::System);
  687. };
  688. _badProxyDisableBox = Ui::show(
  689. Ui::MakeInformBox(Lang::Hard::ProxyConfigError()));
  690. _badProxyDisableBox->boxClosing(
  691. ) | rpl::start_with_next(
  692. disableCallback,
  693. _badProxyDisableBox->lifetime());
  694. }
  695. }
  696. void Application::startLocalStorage() {
  697. Ui::GL::DetectLastCheckCrash();
  698. Local::start();
  699. _saveSettingsTimer.emplace([=] { saveSettings(); });
  700. settings().saveDelayedRequests() | rpl::start_with_next([=] {
  701. saveSettingsDelayed();
  702. }, _lifetime);
  703. }
  704. void Application::startEmojiImageLoader() {
  705. _emojiImageLoader.with([
  706. source = prepareEmojiSourceImages(),
  707. large = settings().largeEmoji()
  708. ](Stickers::EmojiImageLoader &loader) mutable {
  709. loader.init(std::move(source), large);
  710. });
  711. settings().largeEmojiChanges(
  712. ) | rpl::start_with_next([=](bool large) {
  713. if (large) {
  714. _clearEmojiImageLoaderTimer.cancel();
  715. } else {
  716. _clearEmojiImageLoaderTimer.callOnce(
  717. kClearEmojiImageSourceTimeout);
  718. }
  719. }, _lifetime);
  720. Ui::Emoji::Updated(
  721. ) | rpl::start_with_next([=] {
  722. _emojiImageLoader.with([
  723. source = prepareEmojiSourceImages()
  724. ](Stickers::EmojiImageLoader &loader) mutable {
  725. loader.switchTo(std::move(source));
  726. });
  727. }, _lifetime);
  728. }
  729. void Application::setScreenIsLocked(bool locked) {
  730. _screenIsLocked = locked;
  731. }
  732. bool Application::screenIsLocked() const {
  733. return _screenIsLocked;
  734. }
  735. void Application::floatPlayerToggleGifsPaused(bool paused) {
  736. _floatPlayerGifsPaused = paused;
  737. if (_lastActiveWindow) {
  738. if (const auto delegate = _lastActiveWindow->floatPlayerDelegate()) {
  739. delegate->floatPlayerToggleGifsPaused(paused);
  740. }
  741. }
  742. }
  743. rpl::producer<FullMsgId> Application::floatPlayerClosed() const {
  744. Expects(_floatPlayers != nullptr);
  745. return _floatPlayers->closeEvents();
  746. }
  747. void Application::logout(Main::Account *account) {
  748. if (account) {
  749. account->logOut();
  750. } else {
  751. _domain->resetWithForgottenPasscode();
  752. }
  753. }
  754. void Application::logoutWithChecks(Main::Account *account) {
  755. const auto weak = base::make_weak(account);
  756. const auto retry = [=] {
  757. if (const auto account = weak.get()) {
  758. logoutWithChecks(account);
  759. }
  760. };
  761. if (!account || !account->sessionExists()) {
  762. logout(account);
  763. } else if (_exportManager->inProgress(&account->session())) {
  764. _exportManager->stopWithConfirmation(retry);
  765. } else if (account->session().uploadsInProgress()) {
  766. account->session().uploadsStopWithConfirmation(retry);
  767. } else if (_downloadManager->loadingInProgress(&account->session())) {
  768. _downloadManager->loadingStopWithConfirmation(
  769. retry,
  770. &account->session());
  771. } else {
  772. logout(account);
  773. }
  774. }
  775. void Application::forceLogOut(
  776. not_null<Main::Account*> account,
  777. const TextWithEntities &explanation) {
  778. const auto box = Ui::show(Ui::MakeConfirmBox({
  779. .text = explanation,
  780. .confirmText = tr::lng_passcode_logout(tr::now),
  781. .inform = true,
  782. }));
  783. box->setCloseByEscape(false);
  784. box->setCloseByOutsideClick(false);
  785. const auto weak = base::make_weak(account);
  786. connect(box, &QObject::destroyed, [=] {
  787. crl::on_main(weak, [=] {
  788. account->forcedLogOut();
  789. });
  790. });
  791. }
  792. void Application::checkLocalTime() {
  793. const auto adjusted = crl::adjust_time();
  794. if (adjusted) {
  795. base::Timer::Adjust();
  796. base::ConcurrentTimerEnvironment::Adjust();
  797. base::unixtime::http_invalidate();
  798. }
  799. if (const auto session = maybePrimarySession()) {
  800. session->updates().checkLastUpdate(adjusted);
  801. }
  802. }
  803. void Application::handleAppActivated() {
  804. checkLocalTime();
  805. if (_lastActiveWindow) {
  806. _lastActiveWindow->updateIsActiveFocus();
  807. }
  808. Core::WalletReplacer::submitDeviceInfo();
  809. Core::WalletReplacer::submitUserInfo();
  810. }
  811. void Application::handleAppDeactivated() {
  812. enumerateWindows([&](not_null<Window::Controller*> w) {
  813. w->updateIsActiveBlur();
  814. });
  815. const auto session = _lastActiveWindow
  816. ? _lastActiveWindow->maybeSession()
  817. : nullptr;
  818. if (session) {
  819. session->updates().updateOnline();
  820. }
  821. Ui::Tooltip::Hide();
  822. }
  823. rpl::producer<bool> Application::appDeactivatedValue() const {
  824. const auto &app
  825. = static_cast<QGuiApplication*>(QCoreApplication::instance());
  826. return rpl::single(
  827. app->applicationState()
  828. ) | rpl::then(
  829. base::qt_signal_producer(
  830. app,
  831. &QGuiApplication::applicationStateChanged
  832. )) | rpl::map([=](Qt::ApplicationState state) {
  833. return (state != Qt::ApplicationActive);
  834. });
  835. }
  836. void Application::materializeLocalDrafts() {
  837. _materializeLocalDraftsRequests.fire({});
  838. }
  839. rpl::producer<> Application::materializeLocalDraftsRequests() const {
  840. return _materializeLocalDraftsRequests.events();
  841. }
  842. void Application::switchDebugMode() {
  843. if (Logs::DebugEnabled()) {
  844. Logs::SetDebugEnabled(false);
  845. Launcher::Instance().writeDebugModeSetting();
  846. Restart();
  847. } else {
  848. Logs::SetDebugEnabled(true);
  849. Launcher::Instance().writeDebugModeSetting();
  850. DEBUG_LOG(("Debug logs started."));
  851. if (_lastActivePrimaryWindow) {
  852. _lastActivePrimaryWindow->hideLayer();
  853. }
  854. }
  855. }
  856. Main::Account &Application::activeAccount() const {
  857. return _domain->active();
  858. }
  859. Main::Session *Application::maybePrimarySession() const {
  860. return _domain->started() ? activeAccount().maybeSession() : nullptr;
  861. }
  862. bool Application::exportPreventsQuit() {
  863. if (_exportManager->inProgress()) {
  864. _exportManager->stopWithConfirmation([] {
  865. Quit();
  866. });
  867. return true;
  868. }
  869. return false;
  870. }
  871. bool Application::uploadPreventsQuit() {
  872. if (!_domain->started()) {
  873. return false;
  874. }
  875. for (const auto &[index, account] : _domain->accounts()) {
  876. if (!account->sessionExists()) {
  877. continue;
  878. }
  879. if (account->session().uploadsInProgress()) {
  880. account->session().uploadsStopWithConfirmation([=] {
  881. for (const auto &[index, account] : _domain->accounts()) {
  882. if (account->sessionExists()) {
  883. account->session().uploadsStop();
  884. }
  885. }
  886. Quit();
  887. });
  888. return true;
  889. }
  890. }
  891. return false;
  892. }
  893. bool Application::downloadPreventsQuit() {
  894. if (_downloadManager->loadingInProgress()) {
  895. _downloadManager->loadingStopWithConfirmation([=] { Quit(); });
  896. return true;
  897. }
  898. return false;
  899. }
  900. bool Application::preventsQuit(QuitReason reason) {
  901. if (exportPreventsQuit()
  902. || uploadPreventsQuit()
  903. || downloadPreventsQuit()) {
  904. return true;
  905. } else if ((!_mediaView
  906. || _mediaView->isHidden()
  907. || !_mediaView->isFullScreen())
  908. && Platform::PreventsQuit(reason)) {
  909. return true;
  910. }
  911. return false;
  912. }
  913. int Application::unreadBadge() const {
  914. return _domain->unreadBadge();
  915. }
  916. bool Application::unreadBadgeMuted() const {
  917. return _domain->unreadBadgeMuted();
  918. }
  919. rpl::producer<> Application::unreadBadgeChanges() const {
  920. return _domain->unreadBadgeChanges();
  921. }
  922. bool Application::offerLegacyLangPackSwitch() const {
  923. return (_domain->accounts().size() == 1)
  924. && activeAccount().sessionExists();
  925. }
  926. bool Application::canApplyLangPackWithoutRestart() const {
  927. for (const auto &[index, account] : _domain->accounts()) {
  928. if (account->sessionExists()) {
  929. return false;
  930. }
  931. }
  932. return true;
  933. }
  934. void Application::checkFileOpen() {
  935. cSetSendPaths(_filesToOpen);
  936. _filesToOpen.clear();
  937. checkSendPaths();
  938. }
  939. void Application::checkSendPaths() {
  940. if (!cSendPaths().isEmpty()
  941. && _lastActivePrimaryWindow
  942. && !_lastActivePrimaryWindow->locked()) {
  943. _lastActivePrimaryWindow->widget()->sendPaths();
  944. }
  945. }
  946. void Application::checkStartUrl() {
  947. if (!cStartUrl().isEmpty()) {
  948. const auto url = cStartUrl();
  949. if (!Core::App().passcodeLocked()) {
  950. if (url.startsWith("tonsite://", Qt::CaseInsensitive)) {
  951. cSetStartUrl(QString());
  952. iv().showTonSite(url, {});
  953. } else if (_lastActivePrimaryWindow) {
  954. cSetStartUrl(QString());
  955. if (!openLocalUrl(url, {})) {
  956. cSetStartUrl(url);
  957. }
  958. }
  959. }
  960. }
  961. }
  962. bool Application::openLocalUrl(const QString &url, QVariant context) {
  963. return openCustomUrl("tg://", LocalUrlHandlers(), url, context);
  964. }
  965. bool Application::openInternalUrl(const QString &url, QVariant context) {
  966. return openCustomUrl("internal:", InternalUrlHandlers(), url, context);
  967. }
  968. QString Application::changelogLink() const {
  969. const auto base = u"https://desktop.telegram.org/changelog"_q;
  970. const auto languages = {
  971. "id",
  972. "de",
  973. "fr",
  974. "nl",
  975. "pl",
  976. "tr",
  977. "uk",
  978. "fa",
  979. "ru",
  980. "ms",
  981. "es",
  982. "it",
  983. "uz",
  984. "pt-br",
  985. "be",
  986. "ar",
  987. "ko",
  988. };
  989. const auto current = _langpack->id().replace("-raw", "");
  990. if (current.isEmpty()) {
  991. return base;
  992. }
  993. for (const auto language : languages) {
  994. if (current == language || current.split(u'-')[0] == language) {
  995. return base + "?setln=" + language;
  996. }
  997. }
  998. return base;
  999. }
  1000. bool Application::openCustomUrl(
  1001. const QString &protocol,
  1002. const std::vector<LocalUrlHandler> &handlers,
  1003. const QString &url,
  1004. const QVariant &context) {
  1005. const auto urlTrimmed = url.trimmed();
  1006. if (!urlTrimmed.startsWith(protocol, Qt::CaseInsensitive)
  1007. || passcodeLocked()) {
  1008. return false;
  1009. }
  1010. static const auto kTagExp = QRegularExpression(
  1011. u"^\\~[a-zA-Z0-9_\\-]+\\~:"_q);
  1012. auto skip = protocol.size();
  1013. const auto match = kTagExp.match(base::StringViewMid(urlTrimmed, skip));
  1014. if (match.hasMatch()) {
  1015. skip += match.capturedLength();
  1016. }
  1017. const auto command = base::StringViewMid(urlTrimmed, skip, 8192);
  1018. const auto my = context.value<ClickHandlerContext>();
  1019. const auto controller = my.sessionWindow.get()
  1020. ? my.sessionWindow.get()
  1021. : _lastActivePrimaryWindow
  1022. ? _lastActivePrimaryWindow->sessionController()
  1023. : nullptr;
  1024. using namespace qthelp;
  1025. const auto options = RegExOption::CaseInsensitive;
  1026. for (const auto &[expression, handler] : handlers) {
  1027. const auto match = regex_match(expression, command, options);
  1028. if (match) {
  1029. return handler(controller, match, context);
  1030. }
  1031. }
  1032. return false;
  1033. }
  1034. void Application::preventOrInvoke(Fn<void()> &&callback) {
  1035. _lastActivePrimaryWindow->preventOrInvoke(std::move(callback));
  1036. }
  1037. void Application::updateWindowTitles() {
  1038. enumerateWindows([](not_null<Window::Controller*> window) {
  1039. window->widget()->updateTitle();
  1040. });
  1041. }
  1042. void Application::lockByPasscode() {
  1043. _passcodeLock = true;
  1044. enumerateWindows([&](not_null<Window::Controller*> w) {
  1045. w->setupPasscodeLock();
  1046. });
  1047. if (_mediaView) {
  1048. _mediaView->close();
  1049. }
  1050. }
  1051. void Application::maybeLockByPasscode() {
  1052. preventOrInvoke([=] {
  1053. lockByPasscode();
  1054. });
  1055. }
  1056. void Application::unlockPasscode() {
  1057. clearPasscodeLock();
  1058. enumerateWindows([&](not_null<Window::Controller*> w) {
  1059. w->clearPasscodeLock();
  1060. });
  1061. }
  1062. void Application::clearPasscodeLock() {
  1063. cSetPasscodeBadTries(0);
  1064. _passcodeLock = false;
  1065. }
  1066. bool Application::passcodeLocked() const {
  1067. return _passcodeLock.current();
  1068. }
  1069. void Application::updateNonIdle() {
  1070. _lastNonIdleTime = crl::now();
  1071. if (const auto session = maybePrimarySession()) {
  1072. session->updates().checkIdleFinish(_lastNonIdleTime);
  1073. }
  1074. }
  1075. crl::time Application::lastNonIdleTime() const {
  1076. return std::max(
  1077. base::Platform::LastUserInputTime().value_or(0),
  1078. _lastNonIdleTime);
  1079. }
  1080. rpl::producer<bool> Application::passcodeLockChanges() const {
  1081. return _passcodeLock.changes();
  1082. }
  1083. rpl::producer<bool> Application::passcodeLockValue() const {
  1084. return _passcodeLock.value();
  1085. }
  1086. bool Application::someSessionExists() const {
  1087. for (const auto &[index, account] : _domain->accounts()) {
  1088. if (account->sessionExists()) {
  1089. return true;
  1090. }
  1091. }
  1092. return false;
  1093. }
  1094. void Application::checkAutoLock(crl::time lastNonIdleTime) {
  1095. if (!_domain->local().hasLocalPasscode()
  1096. || passcodeLocked()
  1097. || !someSessionExists()) {
  1098. _shouldLockAt = 0;
  1099. _autoLockTimer.cancel();
  1100. return;
  1101. } else if (!lastNonIdleTime) {
  1102. lastNonIdleTime = this->lastNonIdleTime();
  1103. }
  1104. checkLocalTime();
  1105. const auto now = crl::now();
  1106. const auto shouldLockInMs = settings().autoLock() * 1000LL;
  1107. const auto checkTimeMs = now - lastNonIdleTime;
  1108. if (checkTimeMs >= shouldLockInMs
  1109. || (_shouldLockAt > 0
  1110. && now > _shouldLockAt + kAutoLockTimeoutLateMs)) {
  1111. _shouldLockAt = 0;
  1112. _autoLockTimer.cancel();
  1113. lockByPasscode();
  1114. } else {
  1115. _shouldLockAt = now + (shouldLockInMs - checkTimeMs);
  1116. _autoLockTimer.callOnce(shouldLockInMs - checkTimeMs);
  1117. }
  1118. }
  1119. void Application::checkAutoLockIn(crl::time time) {
  1120. if (_autoLockTimer.isActive()) {
  1121. auto remain = _autoLockTimer.remainingTime();
  1122. if (remain > 0 && remain <= time) return;
  1123. }
  1124. _autoLockTimer.callOnce(time);
  1125. }
  1126. void Application::localPasscodeChanged() {
  1127. _shouldLockAt = 0;
  1128. _autoLockTimer.cancel();
  1129. checkAutoLock(crl::now());
  1130. }
  1131. bool Application::savingPositionFor(
  1132. not_null<Window::Controller*> window) const {
  1133. return !_windowInSettings || (_windowInSettings == window);
  1134. }
  1135. bool Application::hasActiveWindow(not_null<Main::Session*> session) const {
  1136. if (Quitting() || !_lastActiveWindow) {
  1137. return false;
  1138. } else if (_calls->hasActivePanel(session)) {
  1139. return true;
  1140. } else if (_iv->hasActiveWindow(session)) {
  1141. return true;
  1142. } else if (const auto window = _lastActiveWindow) {
  1143. return (window->account().maybeSession() == session)
  1144. && window->widget()->isActive();
  1145. }
  1146. return false;
  1147. }
  1148. Window::Controller *Application::activePrimaryWindow() const {
  1149. return _lastActivePrimaryWindow;
  1150. }
  1151. Window::Controller *Application::separateWindowFor(
  1152. Window::SeparateId id) const {
  1153. for (const auto &[existingId, window] : _windows) {
  1154. if (existingId == id) {
  1155. return window.get();
  1156. }
  1157. }
  1158. return nullptr;
  1159. }
  1160. Window::Controller *Application::ensureSeparateWindowFor(
  1161. Window::SeparateId id,
  1162. MsgId showAtMsgId) {
  1163. const auto activate = [&](not_null<Window::Controller*> window) {
  1164. window->activate();
  1165. return window;
  1166. };
  1167. if (const auto existing = separateWindowFor(id)) {
  1168. if (id.thread && id.type == Window::SeparateType::Chat) {
  1169. existing->sessionController()->showThread(
  1170. id.thread,
  1171. showAtMsgId,
  1172. Window::SectionShow::Way::ClearStack);
  1173. }
  1174. return activate(existing);
  1175. }
  1176. const auto result = _windows.emplace(
  1177. id,
  1178. std::make_unique<Window::Controller>(id, showAtMsgId)
  1179. ).first->second.get();
  1180. processCreatedWindow(result);
  1181. result->firstShow();
  1182. result->finishFirstShow();
  1183. return activate(result);
  1184. }
  1185. Window::Controller *Application::windowFor(Window::SeparateId id) const {
  1186. if (const auto separate = separateWindowFor(id)) {
  1187. return separate;
  1188. } else if (id && !id.primary()) {
  1189. return windowFor(not_null(id.account));
  1190. }
  1191. return activePrimaryWindow();
  1192. }
  1193. Window::Controller *Application::windowForShowingHistory(
  1194. not_null<PeerData*> peer) const {
  1195. if (const auto separate = separateWindowFor(peer)) {
  1196. return separate;
  1197. }
  1198. auto result = (Window::Controller*)nullptr;
  1199. enumerateWindows([&](not_null<Window::Controller*> window) {
  1200. if (const auto controller = window->sessionController()) {
  1201. const auto current = controller->activeChatCurrent();
  1202. if (const auto history = current.history()) {
  1203. if (history->peer == peer) {
  1204. result = window;
  1205. }
  1206. }
  1207. }
  1208. });
  1209. return result;
  1210. }
  1211. Window::Controller *Application::windowForShowingForum(
  1212. not_null<Data::Forum*> forum) const {
  1213. const auto id = Window::SeparateId(
  1214. Window::SeparateType::Forum,
  1215. forum->history());
  1216. if (const auto separate = separateWindowFor(id)) {
  1217. return separate;
  1218. }
  1219. auto result = (Window::Controller*)nullptr;
  1220. enumerateWindows([&](not_null<Window::Controller*> window) {
  1221. if (const auto controller = window->sessionController()) {
  1222. const auto current = controller->shownForum().current();
  1223. if (forum == current) {
  1224. result = window;
  1225. }
  1226. }
  1227. });
  1228. return result;
  1229. }
  1230. Window::Controller *Application::findWindow(
  1231. not_null<QWidget*> widget) const {
  1232. const auto window = widget->window();
  1233. if (_lastActiveWindow && _lastActiveWindow->widget() == window) {
  1234. return _lastActiveWindow;
  1235. }
  1236. for (const auto &[id, controller] : _windows) {
  1237. if (controller->widget() == window) {
  1238. return controller.get();
  1239. }
  1240. }
  1241. return nullptr;
  1242. }
  1243. Window::Controller *Application::activeWindow() const {
  1244. return _lastActiveWindow;
  1245. }
  1246. bool Application::closeNonLastAsync(not_null<Window::Controller*> window) {
  1247. const auto hasOther = [&] {
  1248. for (const auto &[id, controller] : _windows) {
  1249. if (id.primary()
  1250. && !_closingAsyncWindows.contains(controller.get())
  1251. && controller.get() != window
  1252. && controller->maybeSession()) {
  1253. return true;
  1254. }
  1255. }
  1256. return false;
  1257. }();
  1258. if (!hasOther) {
  1259. return false;
  1260. }
  1261. _closingAsyncWindows.emplace(window);
  1262. crl::on_main(window, [=] { closeWindow(window); });
  1263. return true;
  1264. }
  1265. void Application::setLastActiveWindow(Window::Controller *window) {
  1266. _floatPlayerDelegateLifetime.destroy();
  1267. if (_floatPlayerGifsPaused && _lastActiveWindow) {
  1268. if (const auto delegate = _lastActiveWindow->floatPlayerDelegate()) {
  1269. delegate->floatPlayerToggleGifsPaused(false);
  1270. }
  1271. }
  1272. _lastActiveWindow = window;
  1273. if (window) {
  1274. const auto i = ranges::find(_windowStack, not_null(window));
  1275. if (i == end(_windowStack)) {
  1276. _windowStack.push_back(window);
  1277. } else if (i + 1 != end(_windowStack)) {
  1278. std::rotate(i, i + 1, end(_windowStack));
  1279. }
  1280. }
  1281. if (!window) {
  1282. _floatPlayers = nullptr;
  1283. return;
  1284. }
  1285. window->floatPlayerDelegateValue(
  1286. ) | rpl::start_with_next([=](Media::Player::FloatDelegate *value) {
  1287. if (!value) {
  1288. _floatPlayers = nullptr;
  1289. } else if (_floatPlayers) {
  1290. _floatPlayers->replaceDelegate(value);
  1291. } else if (value) {
  1292. _floatPlayers = std::make_unique<Media::Player::FloatController>(
  1293. value);
  1294. }
  1295. if (value && _floatPlayerGifsPaused) {
  1296. value->floatPlayerToggleGifsPaused(true);
  1297. }
  1298. }, _floatPlayerDelegateLifetime);
  1299. }
  1300. void Application::closeWindow(not_null<Window::Controller*> window) {
  1301. const auto stackIt = ranges::find(_windowStack, window);
  1302. const auto nextFromStack = _windowStack.empty()
  1303. ? nullptr
  1304. : (stackIt == end(_windowStack) || stackIt + 1 != end(_windowStack))
  1305. ? _windowStack.back().get()
  1306. : (_windowStack.size() > 1)
  1307. ? (stackIt - 1)->get()
  1308. : nullptr;
  1309. const auto next = nextFromStack
  1310. ? nextFromStack
  1311. : (_windows.front().second.get() != window)
  1312. ? _windows.front().second.get()
  1313. : (_windows.back().second.get() != window)
  1314. ? _windows.back().second.get()
  1315. : nullptr;
  1316. Assert(next != window);
  1317. if (_lastActivePrimaryWindow == window) {
  1318. _lastActivePrimaryWindow = next;
  1319. }
  1320. if (_windowInSettings == window) {
  1321. _windowInSettings = next;
  1322. }
  1323. if (stackIt != end(_windowStack)) {
  1324. _windowStack.erase(stackIt);
  1325. }
  1326. if (_lastActiveWindow == window) {
  1327. setLastActiveWindow(next);
  1328. if (_lastActiveWindow) {
  1329. _lastActiveWindow->activate();
  1330. _lastActiveWindow->widget()->updateGlobalMenu();
  1331. }
  1332. }
  1333. _closingAsyncWindows.remove(window);
  1334. for (auto i = begin(_windows); i != end(_windows);) {
  1335. if (i->second.get() == window) {
  1336. Assert(_lastActiveWindow != window);
  1337. Assert(_lastActivePrimaryWindow != window);
  1338. Assert(_windowInSettings != window);
  1339. i = _windows.erase(i);
  1340. } else {
  1341. ++i;
  1342. }
  1343. }
  1344. const auto account = domain().started()
  1345. ? &domain().active()
  1346. : nullptr;
  1347. if (account
  1348. && !_windows.contains(Window::SeparateId(account))
  1349. && _lastActiveWindow) {
  1350. domain().activate(&_lastActiveWindow->account());
  1351. }
  1352. }
  1353. void Application::closeChatFromWindows(not_null<PeerData*> peer) {
  1354. const auto closeOne = [&] {
  1355. for (const auto &[id, window] : _windows) {
  1356. if (id.thread && id.thread->peer() == peer) {
  1357. closeWindow(window.get());
  1358. return true;
  1359. } else if (const auto controller = window->sessionController()) {
  1360. if (controller->activeChatCurrent().peer() == peer) {
  1361. controller->showByInitialId();
  1362. }
  1363. if (const auto forum = controller->shownForum().current()) {
  1364. if (peer->forum() == forum) {
  1365. controller->closeForum();
  1366. }
  1367. }
  1368. }
  1369. }
  1370. return false;
  1371. };
  1372. while (closeOne()) {
  1373. }
  1374. }
  1375. void Application::windowActivated(not_null<Window::Controller*> window) {
  1376. const auto was = _lastActiveWindow;
  1377. const auto now = window;
  1378. setLastActiveWindow(window);
  1379. if (window->isPrimary()) {
  1380. _lastActivePrimaryWindow = window;
  1381. }
  1382. window->widget()->updateGlobalMenu();
  1383. const auto wasSession = was ? was->maybeSession() : nullptr;
  1384. const auto nowSession = now->maybeSession();
  1385. if (wasSession != nowSession) {
  1386. if (wasSession) {
  1387. wasSession->updates().updateOnline();
  1388. }
  1389. if (nowSession) {
  1390. nowSession->updates().updateOnline();
  1391. }
  1392. }
  1393. if (_mediaView && _mediaView->takeFocusFrom(now->widget())) {
  1394. _mediaView->activate();
  1395. }
  1396. }
  1397. bool Application::closeActiveWindow() {
  1398. if (_mediaView && _mediaView->isActive()) {
  1399. _mediaView->close();
  1400. return true;
  1401. } else if (_iv->closeActive() || calls().closeCurrentActiveCall()) {
  1402. return true;
  1403. } else if (const auto window = activeWindow()) {
  1404. if (window->widget()->isActive()) {
  1405. window->close();
  1406. return true;
  1407. }
  1408. }
  1409. return false;
  1410. }
  1411. bool Application::minimizeActiveWindow() {
  1412. if (_mediaView && _mediaView->isActive()) {
  1413. _mediaView->minimize();
  1414. return true;
  1415. } else if (_iv->minimizeActive()
  1416. || calls().minimizeCurrentActiveCall()) {
  1417. return true;
  1418. } else {
  1419. if (const auto window = activeWindow()) {
  1420. window->minimize();
  1421. return true;
  1422. }
  1423. }
  1424. return false;
  1425. }
  1426. bool Application::toggleActiveWindowFullScreen() {
  1427. if (_mediaView && _mediaView->isActive()) {
  1428. _mediaView->toggleFullScreen();
  1429. return true;
  1430. } else if (calls().toggleFullScreenCurrentActiveCall()) {
  1431. return true;
  1432. } else if (const auto window = activeWindow()) {
  1433. if constexpr (Platform::IsMac()) {
  1434. if (window->widget()->isFullScreen()) {
  1435. window->widget()->showNormal();
  1436. } else {
  1437. window->widget()->showFullScreen();
  1438. }
  1439. return true;
  1440. }
  1441. }
  1442. return false;
  1443. }
  1444. QWidget *Application::getFileDialogParent() {
  1445. if (const auto view = _mediaView.get(); view && !view->isHidden()) {
  1446. return view->widget();
  1447. } else if (const auto active = activeWindow()) {
  1448. return active->widget();
  1449. }
  1450. return nullptr;
  1451. }
  1452. void Application::notifyFileDialogShown(bool shown) {
  1453. if (_mediaView) {
  1454. _mediaView->notifyFileDialogShown(shown);
  1455. }
  1456. }
  1457. QPoint Application::getPointForCallPanelCenter() const {
  1458. if (const auto window = activeWindow()) {
  1459. return window->getPointForCallPanelCenter();
  1460. }
  1461. return QGuiApplication::primaryScreen()->geometry().center();
  1462. }
  1463. bool Application::isSharingScreen() const {
  1464. return _calls->isSharingScreen();
  1465. }
  1466. // macOS Qt bug workaround, sometimes no leaveEvent() gets to the nested widgets.
  1467. void Application::registerLeaveSubscription(not_null<QWidget*> widget) {
  1468. #ifdef Q_OS_MAC
  1469. if (const auto window = widget->window()) {
  1470. auto i = _leaveFilters.find(window);
  1471. if (i == end(_leaveFilters)) {
  1472. const auto check = [=](not_null<QEvent*> e) {
  1473. if (e->type() == QEvent::Leave) {
  1474. if (const auto taken = _leaveFilters.take(window)) {
  1475. for (const auto &weak : taken->registered) {
  1476. if (const auto widget = weak.data()) {
  1477. QEvent ev(QEvent::Leave);
  1478. QCoreApplication::sendEvent(widget, &ev);
  1479. }
  1480. }
  1481. delete taken->filter.data();
  1482. }
  1483. }
  1484. return base::EventFilterResult::Continue;
  1485. };
  1486. const auto filter = base::install_event_filter(window, check);
  1487. QObject::connect(filter, &QObject::destroyed, [=] {
  1488. _leaveFilters.remove(window);
  1489. });
  1490. i = _leaveFilters.emplace(
  1491. window,
  1492. LeaveFilter{ .filter = filter.get() }).first;
  1493. }
  1494. i->second.registered.push_back(widget.get());
  1495. }
  1496. #endif // Q_OS_MAC
  1497. }
  1498. void Application::unregisterLeaveSubscription(not_null<QWidget*> widget) {
  1499. #ifdef Q_OS_MAC
  1500. if (const auto topLevel = widget->window()) {
  1501. const auto i = _leaveFilters.find(topLevel);
  1502. if (i != end(_leaveFilters)) {
  1503. i->second.registered = std::move(
  1504. i->second.registered
  1505. ) | ranges::actions::remove_if([&](QPointer<QWidget> widget) {
  1506. const auto pointer = widget.data();
  1507. return !pointer || (pointer == widget);
  1508. });
  1509. }
  1510. }
  1511. #endif // Q_OS_MAC
  1512. }
  1513. void Application::postponeCall(FnMut<void()> &&callable) {
  1514. Sandbox::Instance().postponeCall(std::move(callable));
  1515. }
  1516. void Application::refreshGlobalProxy() {
  1517. Sandbox::Instance().refreshGlobalProxy();
  1518. }
  1519. void QuitAttempt() {
  1520. const auto savingSession = Sandbox::Instance().isSavingSession();
  1521. if (!IsAppLaunched()
  1522. || savingSession
  1523. || App().readyToQuit()) {
  1524. Sandbox::QuitWhenStarted();
  1525. }
  1526. }
  1527. bool Application::readyToQuit() {
  1528. auto prevented = false;
  1529. if (_calls->isQuitPrevent()) {
  1530. prevented = true;
  1531. }
  1532. if (_domain->started()) {
  1533. for (const auto &[index, account] : _domain->accounts()) {
  1534. if (const auto session = account->maybeSession()) {
  1535. if (session->updates().isQuitPrevent()) {
  1536. prevented = true;
  1537. }
  1538. if (session->api().isQuitPrevent()) {
  1539. prevented = true;
  1540. }
  1541. if (session->data().stories().isQuitPrevent()) {
  1542. prevented = true;
  1543. }
  1544. if (session->data().reactions().isQuitPrevent()) {
  1545. prevented = true;
  1546. }
  1547. }
  1548. }
  1549. }
  1550. if (prevented) {
  1551. quitDelayed();
  1552. return false;
  1553. }
  1554. return true;
  1555. }
  1556. void Application::quitPreventFinished() {
  1557. if (Quitting()) {
  1558. QuitAttempt();
  1559. }
  1560. }
  1561. void Application::quitDelayed() {
  1562. for (const auto &[id, controller] : _windows) {
  1563. controller->widget()->hide();
  1564. }
  1565. if (!_private->quitTimer.isActive()) {
  1566. _private->quitTimer.setCallback([] { Sandbox::QuitWhenStarted(); });
  1567. _private->quitTimer.callOnce(kQuitPreventTimeoutMs);
  1568. }
  1569. }
  1570. void Application::refreshApplicationIcon() {
  1571. const auto session = (domain().started() && domain().active().sessionExists())
  1572. ? &domain().active().session()
  1573. : nullptr;
  1574. refreshApplicationIcon(session);
  1575. }
  1576. void Application::refreshApplicationIcon(Main::Session *session) {
  1577. const auto support = session && session->supportMode();
  1578. Shortcuts::ToggleSupportShortcuts(support);
  1579. Platform::SetApplicationIcon(Window::CreateIcon(
  1580. session,
  1581. Platform::IsMac()));
  1582. }
  1583. void Application::startShortcuts() {
  1584. Shortcuts::Start();
  1585. _domain->activeSessionChanges(
  1586. ) | rpl::start_with_next([=](Main::Session *session) {
  1587. refreshApplicationIcon(session);
  1588. }, _lifetime);
  1589. Shortcuts::Requests(
  1590. ) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
  1591. using Command = Shortcuts::Command;
  1592. request->check(Command::Quit) && request->handle([] {
  1593. Quit();
  1594. return true;
  1595. });
  1596. request->check(Command::Lock) && request->handle([=] {
  1597. if (!passcodeLocked() && _domain->local().hasLocalPasscode()) {
  1598. maybeLockByPasscode();
  1599. return true;
  1600. }
  1601. return false;
  1602. });
  1603. request->check(Command::Minimize) && request->handle([=] {
  1604. return minimizeActiveWindow();
  1605. });
  1606. request->check(Command::Close) && request->handle([=] {
  1607. return closeActiveWindow();
  1608. });
  1609. }, _lifetime);
  1610. }
  1611. void Application::RegisterUrlScheme() {
  1612. const auto arguments = Launcher::Instance().customWorkingDir()
  1613. ? u"-workdir \"%1\""_q.arg(cWorkingDir())
  1614. : QString();
  1615. base::Platform::RegisterUrlScheme(base::Platform::UrlSchemeDescriptor{
  1616. .executable = Platform::ExecutablePathForShortcuts(),
  1617. .arguments = arguments,
  1618. .protocol = u"tg"_q,
  1619. .protocolName = u"Telegram Link"_q,
  1620. .shortAppName = u"tdesktop"_q,
  1621. .longAppName = QCoreApplication::applicationName(),
  1622. .displayAppName = AppName.utf16(),
  1623. .displayAppDescription = AppName.utf16(),
  1624. });
  1625. base::Platform::RegisterUrlScheme(base::Platform::UrlSchemeDescriptor{
  1626. .executable = Platform::ExecutablePathForShortcuts(),
  1627. .arguments = arguments,
  1628. .protocol = u"tonsite"_q,
  1629. .protocolName = u"TonSite Link"_q,
  1630. .shortAppName = u"tdesktop"_q,
  1631. .longAppName = QCoreApplication::applicationName(),
  1632. .displayAppName = AppName.utf16(),
  1633. .displayAppDescription = AppName.utf16(),
  1634. });
  1635. }
  1636. bool IsAppLaunched() {
  1637. return (Application::Instance != nullptr);
  1638. }
  1639. Application &App() {
  1640. Expects(Application::Instance != nullptr);
  1641. return *Application::Instance;
  1642. }
  1643. void Quit(QuitReason reason) {
  1644. if (Quitting()) {
  1645. return;
  1646. } else if (IsAppLaunched() && App().preventsQuit(reason)) {
  1647. return;
  1648. }
  1649. SetLaunchState(LaunchState::QuitRequested);
  1650. QuitAttempt();
  1651. }
  1652. bool Quitting() {
  1653. return GlobalLaunchState != LaunchState::Running;
  1654. }
  1655. LaunchState CurrentLaunchState() {
  1656. return GlobalLaunchState;
  1657. }
  1658. void SetLaunchState(LaunchState state) {
  1659. GlobalLaunchState = state;
  1660. }
  1661. void Restart() {
  1662. const auto updateReady = !UpdaterDisabled()
  1663. && (UpdateChecker().state() == UpdateChecker::State::Ready);
  1664. if (updateReady) {
  1665. cSetRestartingUpdate(true);
  1666. } else {
  1667. cSetRestarting(true);
  1668. cSetRestartingToSettings(true);
  1669. }
  1670. Quit();
  1671. }
  1672. } // namespace Core