application.cpp 49 KB

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