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