1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "core/application.h"
9
10 #include "data/data_abstract_structure.h"
11 #include "data/data_photo.h"
12 #include "data/data_document.h"
13 #include "data/data_session.h"
14 #include "data/data_user.h"
15 #include "base/timer.h"
16 #include "base/event_filter.h"
17 #include "base/concurrent_timer.h"
18 #include "base/qt_signal_producer.h"
19 #include "base/unixtime.h"
20 #include "core/update_checker.h"
21 #include "core/shortcuts.h"
22 #include "core/sandbox.h"
23 #include "core/local_url_handlers.h"
24 #include "core/launcher.h"
25 #include "core/ui_integration.h"
26 #include "chat_helpers/emoji_keywords.h"
27 #include "chat_helpers/stickers_emoji_image_loader.h"
28 #include "base/qt_adapters.h"
29 #include "base/platform/base_platform_url_scheme.h"
30 #include "base/platform/base_platform_last_input.h"
31 #include "base/platform/base_platform_info.h"
32 #include "platform/platform_specific.h"
33 #include "mainwindow.h"
34 #include "dialogs/dialogs_entry.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 "lang/lang_file_parser.h"
41 #include "lang/lang_translator.h"
42 #include "lang/lang_cloud_manager.h"
43 #include "lang/lang_hardcoded.h"
44 #include "lang/lang_instance.h"
45 #include "mainwidget.h"
46 #include "core/file_utilities.h"
47 #include "core/crash_reports.h"
48 #include "main/main_account.h"
49 #include "main/main_domain.h"
50 #include "main/main_session.h"
51 #include "media/view/media_view_overlay_widget.h"
52 #include "media/view/media_view_open_common.h"
53 #include "mtproto/mtproto_dc_options.h"
54 #include "mtproto/mtproto_config.h"
55 #include "mtproto/mtp_instance.h"
56 #include "media/audio/media_audio.h"
57 #include "media/audio/media_audio_track.h"
58 #include "media/player/media_player_instance.h"
59 #include "media/player/media_player_float.h"
60 #include "media/clip/media_clip_reader.h" // For Media::Clip::Finish().
61 #include "window/notifications_manager.h"
62 #include "window/themes/window_theme.h"
63 #include "window/window_lock_widgets.h"
64 #include "history/history_location_manager.h"
65 #include "ui/widgets/tooltip.h"
66 #include "ui/gl/gl_detection.h"
67 #include "ui/image/image.h"
68 #include "ui/text/text_options.h"
69 #include "ui/emoji_config.h"
70 #include "ui/effects/animations.h"
71 #include "ui/cached_round_corners.h"
72 #include "storage/serialize_common.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 "window/window_session_controller.h"
79 #include "window/window_controller.h"
80 #include "base/qthelp_regex.h"
81 #include "base/qthelp_url.h"
82 #include "boxes/connection_box.h"
83 #include "ui/boxes/confirm_box.h"
84 #include "boxes/share_box.h"
85 #include "app.h"
86
87 #include <QtCore/QMimeDatabase>
88 #include <QtGui/QGuiApplication>
89 #include <QtGui/QScreen>
90
91 namespace Core {
92 namespace {
93
94 constexpr auto kQuitPreventTimeoutMs = crl::time(1500);
95 constexpr auto kAutoLockTimeoutLateMs = crl::time(3000);
96 constexpr auto kClearEmojiImageSourceTimeout = 10 * crl::time(1000);
97
SetCrashAnnotationsGL()98 void SetCrashAnnotationsGL() {
99 #ifdef Q_OS_WIN
100 CrashReports::SetAnnotation("OpenGL ANGLE", [] {
101 if (Core::App().settings().disableOpenGL()) {
102 return "Disabled";
103 } else switch (Ui::GL::CurrentANGLE()) {
104 case Ui::GL::ANGLE::Auto: return "Auto";
105 case Ui::GL::ANGLE::D3D11: return "Direct3D 11";
106 case Ui::GL::ANGLE::D3D9: return "Direct3D 9";
107 case Ui::GL::ANGLE::D3D11on12: return "D3D11on12";
108 case Ui::GL::ANGLE::OpenGL: return "OpenGL";
109 }
110 Unexpected("Ui::GL::CurrentANGLE value in SetupANGLE.");
111 }());
112 #else // Q_OS_WIN
113 CrashReports::SetAnnotation(
114 "OpenGL",
115 Core::App().settings().disableOpenGL() ? "Disabled" : "Enabled");
116 #endif // Q_OS_WIN
117 }
118
119 } // namespace
120
121 Application *Application::Instance = nullptr;
122
123 struct Application::Private {
124 base::Timer quitTimer;
125 UiIntegration uiIntegration;
126 };
127
Application(not_null<Launcher * > launcher)128 Application::Application(not_null<Launcher*> launcher)
129 : QObject()
130 , _launcher(launcher)
131 , _private(std::make_unique<Private>())
132 , _databases(std::make_unique<Storage::Databases>())
133 , _animationsManager(std::make_unique<Ui::Animations::Manager>())
134 , _clearEmojiImageLoaderTimer([=] { clearEmojiSourceImages(); })
135 , _audio(std::make_unique<Media::Audio::Instance>())
136 , _fallbackProductionConfig(
137 std::make_unique<MTP::Config>(MTP::Environment::Production))
138 , _domain(std::make_unique<Main::Domain>(cDataFile()))
139 , _exportManager(std::make_unique<Export::Manager>())
140 , _calls(std::make_unique<Calls::Instance>())
141 , _langpack(std::make_unique<Lang::Instance>())
142 , _langCloudManager(std::make_unique<Lang::CloudManager>(langpack()))
143 , _emojiKeywords(std::make_unique<ChatHelpers::EmojiKeywords>())
__anon3ff10dba0402null144 , _autoLockTimer([=] { checkAutoLock(); }) {
145 Ui::Integration::Set(&_private->uiIntegration);
146
147 passcodeLockChanges(
__anon3ff10dba0502null148 ) | rpl::start_with_next([=] {
149 _shouldLockAt = 0;
150 }, _lifetime);
151
152 passcodeLockChanges(
__anon3ff10dba0602null153 ) | rpl::start_with_next([=] {
154 _notifications->updateAll();
155 }, _lifetime);
156
157 _domain->activeSessionChanges(
__anon3ff10dba0702(Main::Session *session) 158 ) | rpl::start_with_next([=](Main::Session *session) {
159 if (session && !UpdaterDisabled()) { // #TODO multi someSessionValue
160 UpdateChecker().setMtproto(session);
161 }
162 }, _lifetime);
163 }
164
~Application()165 Application::~Application() {
166 if (_saveSettingsTimer && _saveSettingsTimer->isActive()) {
167 Local::writeSettings();
168 }
169
170 // Depend on activeWindow() for now :(
171 Shortcuts::Finish();
172
173 _window = nullptr;
174 _mediaView = nullptr;
175 _notifications->clearAllFast();
176
177 // We must manually destroy all windows before going further.
178 // DestroyWindow on Windows (at least with an active WebView) enters
179 // event loop and invoke scheduled crl::on_main callbacks.
180 //
181 // For example Domain::removeRedundantAccounts() is called from
182 // Domain::finish() and there is a violation on Ensures(started()).
183 Payments::CheckoutProcess::ClearAll();
184
185 _domain->finish();
186
187 Local::finish();
188
189 Shortcuts::Finish();
190
191 Ui::Emoji::Clear();
192 Media::Clip::Finish();
193
194 Ui::FinishCachedCorners();
195 Data::clearGlobalStructures();
196
197 Window::Theme::Uninitialize();
198
199 Media::Player::finish(_audio.get());
200 style::stopManager();
201
202 ThirdParty::finish();
203
204 Instance = nullptr;
205 }
206
run()207 void Application::run() {
208 style::internal::StartFonts();
209
210 ThirdParty::start();
211
212 // Depends on OpenSSL on macOS, so on ThirdParty::start().
213 // Depends on notifications settings.
214 _notifications = std::make_unique<Window::Notifications::System>();
215
216 startLocalStorage();
217 ValidateScale();
218
219 refreshGlobalProxy(); // Depends on app settings being read.
220
221 if (Local::oldSettingsVersion() < AppVersion) {
222 RegisterUrlScheme();
223 psNewVersion();
224 }
225
226 if (cAutoStart() && !Platform::AutostartSupported()) {
227 cSetAutoStart(false);
228 }
229
230 if (cLaunchMode() == LaunchModeAutoStart && Platform::AutostartSkip()) {
231 Platform::AutostartToggle(false);
232 App::quit();
233 return;
234 }
235
236 _translator = std::make_unique<Lang::Translator>();
237 QCoreApplication::instance()->installTranslator(_translator.get());
238
239 style::startManager(cScale());
240 Ui::InitTextOptions();
241 Ui::StartCachedCorners();
242 Ui::Emoji::Init();
243 startEmojiImageLoader();
244 startSystemDarkModeViewer();
245 Media::Player::start(_audio.get());
246
247 style::ShortAnimationPlaying(
248 ) | rpl::start_with_next([=](bool playing) {
249 if (playing) {
250 MTP::details::pause();
251 } else {
252 MTP::details::unpause();
253 }
254 }, _lifetime);
255
256 DEBUG_LOG(("Application Info: inited..."));
257
258 cChangeDateFormat(QLocale::system().dateFormat(QLocale::ShortFormat));
259 cChangeTimeFormat(QLocale::system().timeFormat(QLocale::ShortFormat));
260
261 DEBUG_LOG(("Application Info: starting app..."));
262
263 // Create mime database, so it won't be slow later.
264 QMimeDatabase().mimeTypeForName(qsl("text/plain"));
265
266 _window = std::make_unique<Window::Controller>();
267
268 _domain->activeChanges(
269 ) | rpl::start_with_next([=](not_null<Main::Account*> account) {
270 _window->showAccount(account);
271 }, _window->widget()->lifetime());
272
273 QCoreApplication::instance()->installEventFilter(this);
274
275 appDeactivatedValue(
276 ) | rpl::start_with_next([=](bool deactivated) {
277 if (deactivated) {
278 handleAppDeactivated();
279 } else {
280 handleAppActivated();
281 }
282 }, _lifetime);
283
284 DEBUG_LOG(("Application Info: window created..."));
285
286 // Depend on activeWindow() for now :(
287 startShortcuts();
288 startDomain();
289
290 _window->widget()->show();
291
292 const auto currentGeometry = _window->widget()->geometry();
293 _mediaView = std::make_unique<Media::View::OverlayWidget>();
294 _window->widget()->Ui::RpWidget::setGeometry(currentGeometry);
295
296 DEBUG_LOG(("Application Info: showing."));
297 _window->finishFirstShow();
298
299 if (!_window->locked() && cStartToSettings()) {
300 _window->showSettings();
301 }
302
303 _window->updateIsActiveFocus();
304
305 for (const auto &error : Shortcuts::Errors()) {
306 LOG(("Shortcuts Error: %1").arg(error));
307 }
308
309 SetCrashAnnotationsGL();
310 if (!Platform::IsMac() && Ui::GL::LastCrashCheckFailed()) {
311 showOpenGLCrashNotification();
312 }
313
314 _window->openInMediaViewRequests(
315 ) | rpl::start_with_next([=](Media::View::OpenRequest &&request) {
316 if (_mediaView) {
317 _mediaView->show(std::move(request));
318 }
319 }, _window->lifetime());
320
321 {
322 const auto countries = std::make_shared<Countries::Manager>(
323 _domain.get());
324 countries->lifetime().add([=] {
325 [[maybe_unused]] const auto countriesCopy = countries;
326 });
327 }
328 }
329
showOpenGLCrashNotification()330 void Application::showOpenGLCrashNotification() {
331 const auto enable = [=] {
332 Ui::GL::ForceDisable(false);
333 Ui::GL::CrashCheckFinish();
334 Core::App().settings().setDisableOpenGL(false);
335 Local::writeSettings();
336 App::restart();
337 };
338 const auto keepDisabled = [=] {
339 Ui::GL::ForceDisable(true);
340 Ui::GL::CrashCheckFinish();
341 Core::App().settings().setDisableOpenGL(true);
342 Local::writeSettings();
343 };
344 _window->show(Box<Ui::ConfirmBox>(
345 "There may be a problem with your graphics drivers and OpenGL. "
346 "Try updating your drivers.\n\n"
347 "OpenGL has been disabled. You can try to enable it again "
348 "or keep it disabled if crashes continue.",
349 "Enable",
350 "Keep Disabled",
351 enable,
352 keepDisabled));
353 }
354
startDomain()355 void Application::startDomain() {
356 const auto state = _domain->start(QByteArray());
357 if (state != Storage::StartResult::IncorrectPasscodeLegacy) {
358 // In case of non-legacy passcoded app all global settings are ready.
359 startSettingsAndBackground();
360 }
361 if (state != Storage::StartResult::Success) {
362 lockByPasscode();
363 DEBUG_LOG(("Application Info: passcode needed..."));
364 }
365 }
366
startSettingsAndBackground()367 void Application::startSettingsAndBackground() {
368 Local::rewriteSettingsIfNeeded();
369 Window::Theme::Background()->start();
370 checkSystemDarkMode();
371 }
372
checkSystemDarkMode()373 void Application::checkSystemDarkMode() {
374 const auto maybeDarkMode = _settings.systemDarkMode();
375 const auto darkModeEnabled = _settings.systemDarkModeEnabled();
376 const auto needToSwitch = darkModeEnabled
377 && maybeDarkMode
378 && (*maybeDarkMode != Window::Theme::IsNightMode());
379 if (needToSwitch) {
380 Window::Theme::ToggleNightMode();
381 Window::Theme::KeepApplied();
382 }
383 }
384
startSystemDarkModeViewer()385 void Application::startSystemDarkModeViewer() {
386 if (Window::Theme::Background()->editingTheme()) {
387 _settings.setSystemDarkModeEnabled(false);
388 }
389 rpl::merge(
390 _settings.systemDarkModeChanges() | rpl::to_empty,
391 _settings.systemDarkModeEnabledChanges() | rpl::to_empty
392 ) | rpl::start_with_next([=] {
393 checkSystemDarkMode();
394 }, _lifetime);
395 }
396
prepareEmojiSourceImages()397 auto Application::prepareEmojiSourceImages()
398 -> std::shared_ptr<Ui::Emoji::UniversalImages> {
399 const auto &images = Ui::Emoji::SourceImages();
400 if (_settings.largeEmoji()) {
401 return images;
402 }
403 Ui::Emoji::ClearSourceImages(images);
404 return std::make_shared<Ui::Emoji::UniversalImages>(images->id());
405 }
406
clearEmojiSourceImages()407 void Application::clearEmojiSourceImages() {
408 _emojiImageLoader.with([](Stickers::EmojiImageLoader &loader) {
409 crl::on_main([images = loader.releaseImages()]{
410 Ui::Emoji::ClearSourceImages(images);
411 });
412 });
413 }
414
hideMediaView()415 bool Application::hideMediaView() {
416 if (_mediaView && !_mediaView->isHidden()) {
417 _mediaView->hide();
418 if (const auto window = activeWindow()) {
419 window->reActivate();
420 }
421 return true;
422 }
423 return false;
424 }
425
eventFilter(QObject * object,QEvent * e)426 bool Application::eventFilter(QObject *object, QEvent *e) {
427 switch (e->type()) {
428 case QEvent::KeyPress:
429 case QEvent::MouseButtonPress:
430 case QEvent::TouchBegin:
431 case QEvent::Wheel: {
432 updateNonIdle();
433 } break;
434
435 case QEvent::ShortcutOverride: {
436 // handle shortcuts ourselves
437 return true;
438 } break;
439
440 case QEvent::Shortcut: {
441 const auto event = static_cast<QShortcutEvent*>(e);
442 DEBUG_LOG(("Shortcut event caught: %1"
443 ).arg(event->key().toString()));
444 if (Shortcuts::HandleEvent(event)) {
445 return true;
446 }
447 } break;
448
449 case QEvent::ApplicationActivate: {
450 if (object == QCoreApplication::instance()) {
451 updateNonIdle();
452 }
453 } break;
454
455 case QEvent::FileOpen: {
456 if (object == QCoreApplication::instance()) {
457 const auto event = static_cast<QFileOpenEvent*>(e);
458 const auto url = QString::fromUtf8(
459 event->url().toEncoded().trimmed());
460 if (url.startsWith(qstr("tg://"), Qt::CaseInsensitive)) {
461 cSetStartUrl(url.mid(0, 8192));
462 checkStartUrl();
463 }
464 if (StartUrlRequiresActivate(url)) {
465 _window->activate();
466 }
467 }
468 } break;
469 }
470
471 return QObject::eventFilter(object, e);
472 }
473
saveSettingsDelayed(crl::time delay)474 void Application::saveSettingsDelayed(crl::time delay) {
475 if (_saveSettingsTimer) {
476 _saveSettingsTimer->callOnce(delay);
477 }
478 }
479
saveSettings()480 void Application::saveSettings() {
481 Local::writeSettings();
482 }
483
fallbackProductionConfig() const484 MTP::Config &Application::fallbackProductionConfig() const {
485 if (!_fallbackProductionConfig) {
486 _fallbackProductionConfig = std::make_unique<MTP::Config>(
487 MTP::Environment::Production);
488 }
489 return *_fallbackProductionConfig;
490 }
491
refreshFallbackProductionConfig(const MTP::Config & config)492 void Application::refreshFallbackProductionConfig(
493 const MTP::Config &config) {
494 if (config.environment() == MTP::Environment::Production) {
495 _fallbackProductionConfig = std::make_unique<MTP::Config>(config);
496 }
497 }
498
constructFallbackProductionConfig(const QByteArray & serialized)499 void Application::constructFallbackProductionConfig(
500 const QByteArray &serialized) {
501 if (auto config = MTP::Config::FromSerialized(serialized)) {
502 if (config->environment() == MTP::Environment::Production) {
503 _fallbackProductionConfig = std::move(config);
504 }
505 }
506 }
507
setCurrentProxy(const MTP::ProxyData & proxy,MTP::ProxyData::Settings settings)508 void Application::setCurrentProxy(
509 const MTP::ProxyData &proxy,
510 MTP::ProxyData::Settings settings) {
511 const auto current = [&] {
512 return _settings.proxy().isEnabled()
513 ? _settings.proxy().selected()
514 : MTP::ProxyData();
515 };
516 const auto was = current();
517 _settings.proxy().setSelected(proxy);
518 _settings.proxy().setSettings(settings);
519 const auto now = current();
520 refreshGlobalProxy();
521 _proxyChanges.fire({ was, now });
522 _settings.proxy().connectionTypeChangesNotify();
523 }
524
proxyChanges() const525 auto Application::proxyChanges() const -> rpl::producer<ProxyChange> {
526 return _proxyChanges.events();
527 }
528
badMtprotoConfigurationError()529 void Application::badMtprotoConfigurationError() {
530 if (_settings.proxy().isEnabled() && !_badProxyDisableBox) {
531 const auto disableCallback = [=] {
532 setCurrentProxy(
533 _settings.proxy().selected(),
534 MTP::ProxyData::Settings::System);
535 };
536 _badProxyDisableBox = Ui::show(Box<Ui::InformBox>(
537 Lang::Hard::ProxyConfigError(),
538 disableCallback));
539 }
540 }
541
startLocalStorage()542 void Application::startLocalStorage() {
543 Local::start();
544 _saveSettingsTimer.emplace([=] { saveSettings(); });
545 _settings.saveDelayedRequests() | rpl::start_with_next([=] {
546 saveSettingsDelayed();
547 }, _lifetime);
548 }
549
startEmojiImageLoader()550 void Application::startEmojiImageLoader() {
551 _emojiImageLoader.with([
552 source = prepareEmojiSourceImages(),
553 large = _settings.largeEmoji()
554 ](Stickers::EmojiImageLoader &loader) mutable {
555 loader.init(std::move(source), large);
556 });
557
558 _settings.largeEmojiChanges(
559 ) | rpl::start_with_next([=](bool large) {
560 if (large) {
561 _clearEmojiImageLoaderTimer.cancel();
562 } else {
563 _clearEmojiImageLoaderTimer.callOnce(
564 kClearEmojiImageSourceTimeout);
565 }
566 }, _lifetime);
567
568 Ui::Emoji::Updated(
569 ) | rpl::start_with_next([=] {
570 _emojiImageLoader.with([
571 source = prepareEmojiSourceImages()
572 ](Stickers::EmojiImageLoader &loader) mutable {
573 loader.switchTo(std::move(source));
574 });
575 }, _lifetime);
576 }
577
setScreenIsLocked(bool locked)578 void Application::setScreenIsLocked(bool locked) {
579 _screenIsLocked = locked;
580 }
581
screenIsLocked() const582 bool Application::screenIsLocked() const {
583 return _screenIsLocked;
584 }
585
setDefaultFloatPlayerDelegate(not_null<Media::Player::FloatDelegate * > delegate)586 void Application::setDefaultFloatPlayerDelegate(
587 not_null<Media::Player::FloatDelegate*> delegate) {
588 Expects(!_defaultFloatPlayerDelegate == !_floatPlayers);
589
590 _defaultFloatPlayerDelegate = delegate;
591 _replacementFloatPlayerDelegate = nullptr;
592 if (_floatPlayers) {
593 _floatPlayers->replaceDelegate(delegate);
594 } else {
595 _floatPlayers = std::make_unique<Media::Player::FloatController>(
596 delegate);
597 }
598 }
599
replaceFloatPlayerDelegate(not_null<Media::Player::FloatDelegate * > replacement)600 void Application::replaceFloatPlayerDelegate(
601 not_null<Media::Player::FloatDelegate*> replacement) {
602 Expects(_floatPlayers != nullptr);
603
604 _replacementFloatPlayerDelegate = replacement;
605 _floatPlayers->replaceDelegate(replacement);
606 }
607
restoreFloatPlayerDelegate(not_null<Media::Player::FloatDelegate * > replacement)608 void Application::restoreFloatPlayerDelegate(
609 not_null<Media::Player::FloatDelegate*> replacement) {
610 Expects(_floatPlayers != nullptr);
611
612 if (_replacementFloatPlayerDelegate == replacement) {
613 _replacementFloatPlayerDelegate = nullptr;
614 _floatPlayers->replaceDelegate(_defaultFloatPlayerDelegate);
615 }
616 }
617
floatPlayerClosed() const618 rpl::producer<FullMsgId> Application::floatPlayerClosed() const {
619 Expects(_floatPlayers != nullptr);
620
621 return _floatPlayers->closeEvents();
622 }
623
logout(Main::Account * account)624 void Application::logout(Main::Account *account) {
625 if (account) {
626 account->logOut();
627 } else {
628 _domain->resetWithForgottenPasscode();
629 }
630 }
631
forceLogOut(not_null<Main::Account * > account,const TextWithEntities & explanation)632 void Application::forceLogOut(
633 not_null<Main::Account*> account,
634 const TextWithEntities &explanation) {
635 const auto box = Ui::show(Box<Ui::InformBox>(
636 explanation,
637 tr::lng_passcode_logout(tr::now)));
638 box->setCloseByEscape(false);
639 box->setCloseByOutsideClick(false);
640 const auto weak = base::make_weak(account.get());
641 connect(box, &QObject::destroyed, [=] {
642 crl::on_main(weak, [=] {
643 account->forcedLogOut();
644 });
645 });
646 }
647
checkLocalTime()648 void Application::checkLocalTime() {
649 const auto adjusted = crl::adjust_time();
650 if (adjusted) {
651 base::Timer::Adjust();
652 base::ConcurrentTimerEnvironment::Adjust();
653 base::unixtime::http_invalidate();
654 }
655 if (const auto session = maybeActiveSession()) {
656 session->updates().checkLastUpdate(adjusted);
657 }
658 }
659
handleAppActivated()660 void Application::handleAppActivated() {
661 checkLocalTime();
662 if (_window) {
663 _window->updateIsActiveFocus();
664 }
665 }
666
handleAppDeactivated()667 void Application::handleAppDeactivated() {
668 if (_window) {
669 _window->updateIsActiveBlur();
670 }
671 Ui::Tooltip::Hide();
672 }
673
appDeactivatedValue() const674 rpl::producer<bool> Application::appDeactivatedValue() const {
675 const auto &app =
676 static_cast<QGuiApplication*>(QCoreApplication::instance());
677 return rpl::single(
678 app->applicationState()
679 ) | rpl::then(
680 base::qt_signal_producer(
681 app,
682 &QGuiApplication::applicationStateChanged
683 )) | rpl::map([=](Qt::ApplicationState state) {
684 return (state != Qt::ApplicationActive);
685 });
686 }
687
call_handleObservables()688 void Application::call_handleObservables() {
689 base::HandleObservables();
690 }
691
switchDebugMode()692 void Application::switchDebugMode() {
693 if (Logs::DebugEnabled()) {
694 Logs::SetDebugEnabled(false);
695 _launcher->writeDebugModeSetting();
696 App::restart();
697 } else {
698 Logs::SetDebugEnabled(true);
699 _launcher->writeDebugModeSetting();
700 DEBUG_LOG(("Debug logs started."));
701 Ui::hideLayer();
702 }
703 }
704
switchFreeType()705 void Application::switchFreeType() {
706 if (cUseFreeType()) {
707 QFile(cWorkingDir() + qsl("tdata/withfreetype")).remove();
708 cSetUseFreeType(false);
709 } else {
710 QFile f(cWorkingDir() + qsl("tdata/withfreetype"));
711 if (f.open(QIODevice::WriteOnly)) {
712 f.write("1");
713 f.close();
714 }
715 cSetUseFreeType(true);
716 }
717 App::restart();
718 }
719
writeInstallBetaVersionsSetting()720 void Application::writeInstallBetaVersionsSetting() {
721 _launcher->writeInstallBetaVersionsSetting();
722 }
723
activeAccount() const724 Main::Account &Application::activeAccount() const {
725 return _domain->active();
726 }
727
maybeActiveSession() const728 Main::Session *Application::maybeActiveSession() const {
729 return _domain->started() ? activeAccount().maybeSession() : nullptr;
730 }
731
exportPreventsQuit()732 bool Application::exportPreventsQuit() {
733 if (_exportManager->inProgress()) {
734 _exportManager->stopWithConfirmation([] {
735 App::quit();
736 });
737 return true;
738 }
739 return false;
740 }
741
unreadBadge() const742 int Application::unreadBadge() const {
743 return _domain->unreadBadge();
744 }
745
unreadBadgeMuted() const746 bool Application::unreadBadgeMuted() const {
747 return _domain->unreadBadgeMuted();
748 }
749
unreadBadgeChanges() const750 rpl::producer<> Application::unreadBadgeChanges() const {
751 return _domain->unreadBadgeChanges();
752 }
753
offerLegacyLangPackSwitch() const754 bool Application::offerLegacyLangPackSwitch() const {
755 return (_domain->accounts().size() == 1)
756 && activeAccount().sessionExists();
757 }
758
canApplyLangPackWithoutRestart() const759 bool Application::canApplyLangPackWithoutRestart() const {
760 for (const auto &[index, account] : _domain->accounts()) {
761 if (account->sessionExists()) {
762 return false;
763 }
764 }
765 return true;
766 }
767
checkStartUrl()768 void Application::checkStartUrl() {
769 if (!cStartUrl().isEmpty() && _window && !_window->locked()) {
770 const auto url = cStartUrl();
771 cSetStartUrl(QString());
772 if (!openLocalUrl(url, {})) {
773 cSetStartUrl(url);
774 }
775 }
776 }
777
openLocalUrl(const QString & url,QVariant context)778 bool Application::openLocalUrl(const QString &url, QVariant context) {
779 return openCustomUrl("tg://", LocalUrlHandlers(), url, context);
780 }
781
openInternalUrl(const QString & url,QVariant context)782 bool Application::openInternalUrl(const QString &url, QVariant context) {
783 return openCustomUrl("internal:", InternalUrlHandlers(), url, context);
784 }
785
changelogLink() const786 QString Application::changelogLink() const {
787 const auto base = u"https://desktop.telegram.org/changelog"_q;
788 const auto languages = {
789 "id",
790 "de",
791 "fr",
792 "nl",
793 "pl",
794 "tr",
795 "uk",
796 "fa",
797 "ru",
798 "ms",
799 "es",
800 "it",
801 "uz",
802 "pt-br",
803 "be",
804 "ar",
805 "ko",
806 };
807 const auto current = _langpack->id().replace("-raw", "");
808 if (current.isEmpty()) {
809 return base;
810 }
811 for (const auto language : languages) {
812 if (current == language || current.split(u'-')[0] == language) {
813 return base + "?setln=" + language;
814 }
815 }
816 return base;
817 }
818
openCustomUrl(const QString & protocol,const std::vector<LocalUrlHandler> & handlers,const QString & url,const QVariant & context)819 bool Application::openCustomUrl(
820 const QString &protocol,
821 const std::vector<LocalUrlHandler> &handlers,
822 const QString &url,
823 const QVariant &context) {
824 const auto urlTrimmed = url.trimmed();
825 if (!urlTrimmed.startsWith(protocol, Qt::CaseInsensitive)
826 || passcodeLocked()) {
827 return false;
828 }
829 const auto command = base::StringViewMid(urlTrimmed, protocol.size(), 8192);
830 const auto controller = _window ? _window->sessionController() : nullptr;
831
832 using namespace qthelp;
833 const auto options = RegExOption::CaseInsensitive;
834 for (const auto &[expression, handler] : handlers) {
835 const auto match = regex_match(expression, command, options);
836 if (match) {
837 return handler(controller, match, context);
838 }
839 }
840 return false;
841
842 }
843
preventOrInvoke(Fn<void ()> && callback)844 void Application::preventOrInvoke(Fn<void()> &&callback) {
845 _window->preventOrInvoke(std::move(callback));
846 }
847
lockByPasscode()848 void Application::lockByPasscode() {
849 preventOrInvoke([=] {
850 if (_window) {
851 _passcodeLock = true;
852 _window->setupPasscodeLock();
853 }
854 });
855 }
856
unlockPasscode()857 void Application::unlockPasscode() {
858 clearPasscodeLock();
859 if (_window) {
860 _window->clearPasscodeLock();
861 }
862 }
863
clearPasscodeLock()864 void Application::clearPasscodeLock() {
865 cSetPasscodeBadTries(0);
866 _passcodeLock = false;
867 }
868
passcodeLocked() const869 bool Application::passcodeLocked() const {
870 return _passcodeLock.current();
871 }
872
updateNonIdle()873 void Application::updateNonIdle() {
874 _lastNonIdleTime = crl::now();
875 if (const auto session = maybeActiveSession()) {
876 session->updates().checkIdleFinish(_lastNonIdleTime);
877 }
878 }
879
lastNonIdleTime() const880 crl::time Application::lastNonIdleTime() const {
881 return std::max(
882 base::Platform::LastUserInputTime().value_or(0),
883 _lastNonIdleTime);
884 }
885
passcodeLockChanges() const886 rpl::producer<bool> Application::passcodeLockChanges() const {
887 return _passcodeLock.changes();
888 }
889
passcodeLockValue() const890 rpl::producer<bool> Application::passcodeLockValue() const {
891 return _passcodeLock.value();
892 }
893
someSessionExists() const894 bool Application::someSessionExists() const {
895 for (const auto &[index, account] : _domain->accounts()) {
896 if (account->sessionExists()) {
897 return true;
898 }
899 }
900 return false;
901 }
902
checkAutoLock(crl::time lastNonIdleTime)903 void Application::checkAutoLock(crl::time lastNonIdleTime) {
904 if (!_domain->local().hasLocalPasscode()
905 || passcodeLocked()
906 || !someSessionExists()) {
907 _shouldLockAt = 0;
908 _autoLockTimer.cancel();
909 return;
910 } else if (!lastNonIdleTime) {
911 lastNonIdleTime = this->lastNonIdleTime();
912 }
913
914 checkLocalTime();
915 const auto now = crl::now();
916 const auto shouldLockInMs = _settings.autoLock() * 1000LL;
917 const auto checkTimeMs = now - lastNonIdleTime;
918 if (checkTimeMs >= shouldLockInMs || (_shouldLockAt > 0 && now > _shouldLockAt + kAutoLockTimeoutLateMs)) {
919 _shouldLockAt = 0;
920 _autoLockTimer.cancel();
921 lockByPasscode();
922 } else {
923 _shouldLockAt = now + (shouldLockInMs - checkTimeMs);
924 _autoLockTimer.callOnce(shouldLockInMs - checkTimeMs);
925 }
926 }
927
checkAutoLockIn(crl::time time)928 void Application::checkAutoLockIn(crl::time time) {
929 if (_autoLockTimer.isActive()) {
930 auto remain = _autoLockTimer.remainingTime();
931 if (remain > 0 && remain <= time) return;
932 }
933 _autoLockTimer.callOnce(time);
934 }
935
localPasscodeChanged()936 void Application::localPasscodeChanged() {
937 _shouldLockAt = 0;
938 _autoLockTimer.cancel();
939 checkAutoLock(crl::now());
940 }
941
hasActiveWindow(not_null<Main::Session * > session) const942 bool Application::hasActiveWindow(not_null<Main::Session*> session) const {
943 if (App::quitting() || !_window) {
944 return false;
945 } else if (_calls->hasActivePanel(session)) {
946 return true;
947 } else if (const auto controller = _window->sessionController()) {
948 if (&controller->session() == session
949 && _window->widget()->isActive()) {
950 return true;
951 }
952 }
953 return false;
954 }
955
saveCurrentDraftsToHistories()956 void Application::saveCurrentDraftsToHistories() {
957 if (!_window) {
958 return;
959 } else if (const auto controller = _window->sessionController()) {
960 controller->content()->saveFieldToHistoryLocalDraft();
961 }
962 }
963
activeWindow() const964 Window::Controller *Application::activeWindow() const {
965 return _window.get();
966 }
967
closeActiveWindow()968 bool Application::closeActiveWindow() {
969 if (hideMediaView()) {
970 return true;
971 }
972 if (!calls().closeCurrentActiveCall()) {
973 if (const auto window = activeWindow()) {
974 if (window->widget()->isVisible()
975 && window->widget()->isActive()) {
976 window->close();
977 return true;
978 }
979 }
980 }
981 return false;
982 }
983
minimizeActiveWindow()984 bool Application::minimizeActiveWindow() {
985 hideMediaView();
986 if (!calls().minimizeCurrentActiveCall()) {
987 if (const auto window = activeWindow()) {
988 window->minimize();
989 return true;
990 }
991 }
992 return false;
993 }
994
getFileDialogParent()995 QWidget *Application::getFileDialogParent() {
996 return (_mediaView && !_mediaView->isHidden())
997 ? static_cast<QWidget*>(_mediaView->widget())
998 : activeWindow()
999 ? static_cast<QWidget*>(activeWindow()->widget())
1000 : nullptr;
1001 }
1002
notifyFileDialogShown(bool shown)1003 void Application::notifyFileDialogShown(bool shown) {
1004 if (_mediaView) {
1005 _mediaView->notifyFileDialogShown(shown);
1006 }
1007 }
1008
checkMediaViewActivation()1009 void Application::checkMediaViewActivation() {
1010 if (_mediaView && !_mediaView->isHidden()) {
1011 _mediaView->activate();
1012 }
1013 }
1014
getPointForCallPanelCenter() const1015 QPoint Application::getPointForCallPanelCenter() const {
1016 if (const auto window = activeWindow()) {
1017 return window->getPointForCallPanelCenter();
1018 }
1019 return QGuiApplication::primaryScreen()->geometry().center();
1020 }
1021
1022 // macOS Qt bug workaround, sometimes no leaveEvent() gets to the nested widgets.
registerLeaveSubscription(not_null<QWidget * > widget)1023 void Application::registerLeaveSubscription(not_null<QWidget*> widget) {
1024 #ifdef Q_OS_MAC
1025 if (const auto window = widget->window()) {
1026 auto i = _leaveFilters.find(window);
1027 if (i == end(_leaveFilters)) {
1028 const auto check = [=](not_null<QEvent*> e) {
1029 if (e->type() == QEvent::Leave) {
1030 if (const auto taken = _leaveFilters.take(window)) {
1031 for (const auto &weak : taken->registered) {
1032 if (const auto widget = weak.data()) {
1033 QEvent ev(QEvent::Leave);
1034 QCoreApplication::sendEvent(widget, &ev);
1035 }
1036 }
1037 delete taken->filter.data();
1038 }
1039 }
1040 return base::EventFilterResult::Continue;
1041 };
1042 const auto filter = base::install_event_filter(window, check);
1043 QObject::connect(filter, &QObject::destroyed, [=] {
1044 _leaveFilters.remove(window);
1045 });
1046 i = _leaveFilters.emplace(
1047 window,
1048 LeaveFilter{ .filter = filter.get() }).first;
1049 }
1050 i->second.registered.push_back(widget.get());
1051 }
1052 #endif // Q_OS_MAC
1053 }
1054
unregisterLeaveSubscription(not_null<QWidget * > widget)1055 void Application::unregisterLeaveSubscription(not_null<QWidget*> widget) {
1056 #ifdef Q_OS_MAC
1057 if (const auto topLevel = widget->window()) {
1058 const auto i = _leaveFilters.find(topLevel);
1059 if (i != end(_leaveFilters)) {
1060 i->second.registered = std::move(
1061 i->second.registered
1062 ) | ranges::actions::remove_if([&](QPointer<QWidget> widget) {
1063 const auto pointer = widget.data();
1064 return !pointer || (pointer == widget);
1065 });
1066 }
1067 }
1068 #endif // Q_OS_MAC
1069 }
1070
postponeCall(FnMut<void ()> && callable)1071 void Application::postponeCall(FnMut<void()> &&callable) {
1072 Sandbox::Instance().postponeCall(std::move(callable));
1073 }
1074
refreshGlobalProxy()1075 void Application::refreshGlobalProxy() {
1076 Sandbox::Instance().refreshGlobalProxy();
1077 }
1078
QuitAttempt()1079 void Application::QuitAttempt() {
1080 if (!IsAppLaunched()
1081 || Sandbox::Instance().isSavingSession()
1082 || App().readyToQuit()) {
1083 Sandbox::QuitWhenStarted();
1084 }
1085 }
1086
readyToQuit()1087 bool Application::readyToQuit() {
1088 auto prevented = false;
1089 if (_calls->isQuitPrevent()) {
1090 prevented = true;
1091 }
1092 if (_domain->started()) {
1093 for (const auto &[index, account] : _domain->accounts()) {
1094 if (const auto session = account->maybeSession()) {
1095 if (session->updates().isQuitPrevent()) {
1096 prevented = true;
1097 }
1098 if (session->api().isQuitPrevent()) {
1099 prevented = true;
1100 }
1101 }
1102 }
1103 }
1104 if (prevented) {
1105 quitDelayed();
1106 return false;
1107 }
1108 return true;
1109 }
1110
quitPreventFinished()1111 void Application::quitPreventFinished() {
1112 if (App::quitting()) {
1113 QuitAttempt();
1114 }
1115 }
1116
quitDelayed()1117 void Application::quitDelayed() {
1118 if (!_private->quitTimer.isActive()) {
1119 _private->quitTimer.setCallback([] { Sandbox::QuitWhenStarted(); });
1120 _private->quitTimer.callOnce(kQuitPreventTimeoutMs);
1121 }
1122 }
1123
startShortcuts()1124 void Application::startShortcuts() {
1125 Shortcuts::Start();
1126
1127 _domain->activeSessionChanges(
1128 ) | rpl::start_with_next([=](Main::Session *session) {
1129 const auto support = session && session->supportMode();
1130 Shortcuts::ToggleSupportShortcuts(support);
1131 Platform::SetApplicationIcon(Window::CreateIcon(session));
1132 }, _lifetime);
1133
1134 Shortcuts::Requests(
1135 ) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
1136 using Command = Shortcuts::Command;
1137 request->check(Command::Quit) && request->handle([] {
1138 App::quit();
1139 return true;
1140 });
1141 request->check(Command::Lock) && request->handle([=] {
1142 if (!passcodeLocked() && _domain->local().hasLocalPasscode()) {
1143 lockByPasscode();
1144 return true;
1145 }
1146 return false;
1147 });
1148 request->check(Command::Minimize) && request->handle([=] {
1149 return minimizeActiveWindow();
1150 });
1151 request->check(Command::Close) && request->handle([=] {
1152 return closeActiveWindow();
1153 });
1154 }, _lifetime);
1155 }
1156
RegisterUrlScheme()1157 void Application::RegisterUrlScheme() {
1158 base::Platform::RegisterUrlScheme(base::Platform::UrlSchemeDescriptor{
1159 .executable = cExeDir() + cExeName(),
1160 .arguments = qsl("-workdir \"%1\"").arg(cWorkingDir()),
1161 .protocol = qsl("tg"),
1162 .protocolName = qsl("Telegram Link"),
1163 .shortAppName = qsl("tdesktop"),
1164 .longAppName = QCoreApplication::applicationName(),
1165 .displayAppName = AppName.utf16(),
1166 .displayAppDescription = AppName.utf16(),
1167 });
1168 }
1169
IsAppLaunched()1170 bool IsAppLaunched() {
1171 return (Application::Instance != nullptr);
1172 }
1173
App()1174 Application &App() {
1175 Expects(Application::Instance != nullptr);
1176
1177 return *Application::Instance;
1178 }
1179
1180 } // namespace Core
1181