diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style
index 3c03e95ce..a6cd8f0b2 100644
--- a/Telegram/Resources/basic.style
+++ b/Telegram/Resources/basic.style
@@ -72,8 +72,11 @@ linkCropLimit: 360px;
 linkFont: normalFont;
 linkOverFont: font(fsize underline);
 
-dateRadius: 6px;
-buttonRadius: 3px;
+roundRadiusLarge: 6px;
+roundRadiusSmall: 3px;
+
+dateRadius: roundRadiusLarge;
+buttonRadius: roundRadiusSmall;
 
 setLittleSkip: 9px;
 
diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp
index 64ca0da43..45476bfd5 100644
--- a/Telegram/SourceFiles/api/api_sending.cpp
+++ b/Telegram/SourceFiles/api/api_sending.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "api/api_sending.h"
 
+#include "api/api_text_entities.h"
 #include "base/unixtime.h"
 #include "data/data_document.h"
 #include "data/data_photo.h"
@@ -73,12 +74,12 @@ void SendExistingMedia(
 
 	auto caption = TextWithEntities{
 		message.textWithTags.text,
-		ConvertTextTagsToEntities(message.textWithTags.tags)
+		TextUtilities::ConvertTextTagsToEntities(message.textWithTags.tags)
 	};
 	TextUtilities::Trim(caption);
-	auto sentEntities = TextUtilities::EntitiesToMTP(
+	auto sentEntities = EntitiesToMTP(
 		caption.entities,
-		TextUtilities::ConvertOption::SkipLocal);
+		ConvertOption::SkipLocal);
 	if (!sentEntities.v.isEmpty()) {
 		sendFlags |= MTPmessages_SendMedia::Flag::f_entities;
 	}
diff --git a/Telegram/SourceFiles/api/api_text_entities.cpp b/Telegram/SourceFiles/api/api_text_entities.cpp
new file mode 100644
index 000000000..e67b86dcc
--- /dev/null
+++ b/Telegram/SourceFiles/api/api_text_entities.cpp
@@ -0,0 +1,129 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "api/api_text_entities.h"
+
+#include "main/main_session.h"
+#include "data/data_session.h"
+#include "data/data_user.h"
+
+namespace Api {
+namespace {
+
+using namespace TextUtilities;
+
+} // namespace
+
+EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities) {
+	auto result = EntitiesInText();
+	if (!entities.isEmpty()) {
+		result.reserve(entities.size());
+		for_const (auto &entity, entities) {
+			switch (entity.type()) {
+			case mtpc_messageEntityUrl: { auto &d = entity.c_messageEntityUrl(); result.push_back({ EntityType::Url, d.voffset().v, d.vlength().v }); } break;
+			case mtpc_messageEntityTextUrl: { auto &d = entity.c_messageEntityTextUrl(); result.push_back({ EntityType::CustomUrl, d.voffset().v, d.vlength().v, Clean(qs(d.vurl())) }); } break;
+			case mtpc_messageEntityEmail: { auto &d = entity.c_messageEntityEmail(); result.push_back({ EntityType::Email, d.voffset().v, d.vlength().v }); } break;
+			case mtpc_messageEntityHashtag: { auto &d = entity.c_messageEntityHashtag(); result.push_back({ EntityType::Hashtag, d.voffset().v, d.vlength().v }); } break;
+			case mtpc_messageEntityCashtag: { auto &d = entity.c_messageEntityCashtag(); result.push_back({ EntityType::Cashtag, d.voffset().v, d.vlength().v }); } break;
+			case mtpc_messageEntityPhone: break; // Skipping phones.
+			case mtpc_messageEntityMention: { auto &d = entity.c_messageEntityMention(); result.push_back({ EntityType::Mention, d.voffset().v, d.vlength().v }); } break;
+			case mtpc_messageEntityMentionName: {
+				auto &d = entity.c_messageEntityMentionName();
+				auto data = [&d] {
+					if (auto user = Auth().data().userLoaded(d.vuser_id().v)) {
+						return MentionNameDataFromFields({
+							d.vuser_id().v,
+							user->accessHash() });
+					}
+					return MentionNameDataFromFields(d.vuser_id().v);
+				};
+				result.push_back({ EntityType::MentionName, d.voffset().v, d.vlength().v, data() });
+			} break;
+			case mtpc_inputMessageEntityMentionName: {
+				auto &d = entity.c_inputMessageEntityMentionName();
+				auto data = ([&d]() -> QString {
+					if (d.vuser_id().type() == mtpc_inputUserSelf) {
+						return MentionNameDataFromFields(Auth().userId());
+					} else if (d.vuser_id().type() == mtpc_inputUser) {
+						auto &user = d.vuser_id().c_inputUser();
+						return MentionNameDataFromFields({ user.vuser_id().v, user.vaccess_hash().v });
+					}
+					return QString();
+				})();
+				if (!data.isEmpty()) {
+					result.push_back({ EntityType::MentionName, d.voffset().v, d.vlength().v, data });
+				}
+			} break;
+			case mtpc_messageEntityBotCommand: { auto &d = entity.c_messageEntityBotCommand(); result.push_back({ EntityType::BotCommand, d.voffset().v, d.vlength().v }); } break;
+			case mtpc_messageEntityBold: { auto &d = entity.c_messageEntityBold(); result.push_back({ EntityType::Bold, d.voffset().v, d.vlength().v }); } break;
+			case mtpc_messageEntityItalic: { auto &d = entity.c_messageEntityItalic(); result.push_back({ EntityType::Italic, d.voffset().v, d.vlength().v }); } break;
+			case mtpc_messageEntityUnderline: { auto &d = entity.c_messageEntityUnderline(); result.push_back({ EntityType::Underline, d.voffset().v, d.vlength().v }); } break;
+			case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset().v, d.vlength().v }); } break;
+			case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset().v, d.vlength().v }); } break;
+			case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, Clean(qs(d.vlanguage())) }); } break;
+				// #TODO entities
+			}
+		}
+	}
+	return result;
+}
+
+MTPVector<MTPMessageEntity> EntitiesToMTP(
+		const EntitiesInText &entities,
+		ConvertOption option) {
+	auto v = QVector<MTPMessageEntity>();
+	v.reserve(entities.size());
+	for_const (auto &entity, entities) {
+		if (entity.length() <= 0) continue;
+		if (option == ConvertOption::SkipLocal
+			&& entity.type() != EntityType::Bold
+			&& entity.type() != EntityType::Italic
+			&& entity.type() != EntityType::Underline
+			&& entity.type() != EntityType::StrikeOut
+			&& entity.type() != EntityType::Code // #TODO entities
+			&& entity.type() != EntityType::Pre
+			&& entity.type() != EntityType::MentionName
+			&& entity.type() != EntityType::CustomUrl) {
+			continue;
+		}
+
+		auto offset = MTP_int(entity.offset());
+		auto length = MTP_int(entity.length());
+		switch (entity.type()) {
+		case EntityType::Url: v.push_back(MTP_messageEntityUrl(offset, length)); break;
+		case EntityType::CustomUrl: v.push_back(MTP_messageEntityTextUrl(offset, length, MTP_string(entity.data()))); break;
+		case EntityType::Email: v.push_back(MTP_messageEntityEmail(offset, length)); break;
+		case EntityType::Hashtag: v.push_back(MTP_messageEntityHashtag(offset, length)); break;
+		case EntityType::Cashtag: v.push_back(MTP_messageEntityCashtag(offset, length)); break;
+		case EntityType::Mention: v.push_back(MTP_messageEntityMention(offset, length)); break;
+		case EntityType::MentionName: {
+			auto inputUser = ([](const QString &data) -> MTPInputUser {
+				auto fields = MentionNameDataToFields(data);
+				if (fields.userId == Auth().userId()) {
+					return MTP_inputUserSelf();
+				} else if (fields.userId) {
+					return MTP_inputUser(MTP_int(fields.userId), MTP_long(fields.accessHash));
+				}
+				return MTP_inputUserEmpty();
+			})(entity.data());
+			if (inputUser.type() != mtpc_inputUserEmpty) {
+				v.push_back(MTP_inputMessageEntityMentionName(offset, length, inputUser));
+			}
+		} break;
+		case EntityType::BotCommand: v.push_back(MTP_messageEntityBotCommand(offset, length)); break;
+		case EntityType::Bold: v.push_back(MTP_messageEntityBold(offset, length)); break;
+		case EntityType::Italic: v.push_back(MTP_messageEntityItalic(offset, length)); break;
+		case EntityType::Underline: v.push_back(MTP_messageEntityUnderline(offset, length)); break;
+		case EntityType::StrikeOut: v.push_back(MTP_messageEntityStrike(offset, length)); break;
+		case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break; // #TODO entities
+		case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
+		}
+	}
+	return MTP_vector<MTPMessageEntity>(std::move(v));
+}
+
+} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_text_entities.h b/Telegram/SourceFiles/api/api_text_entities.h
new file mode 100644
index 000000000..836a3d77a
--- /dev/null
+++ b/Telegram/SourceFiles/api/api_text_entities.h
@@ -0,0 +1,23 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "ui/text/text_entity.h"
+
+namespace Api {
+
+EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities);
+enum class ConvertOption {
+	WithLocal,
+	SkipLocal,
+};
+MTPVector<MTPMessageEntity> EntitiesToMTP(
+	const EntitiesInText &entities,
+	ConvertOption option = ConvertOption::WithLocal);
+
+} // namespace Api
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 9cf5ab3e3..e5ad1c9a6 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "apiwrap.h"
 
+#include "api/api_text_entities.h"
 #include "data/data_drafts.h"
 #include "data/data_photo.h"
 #include "data/data_web_page.h"
@@ -2562,9 +2563,9 @@ void ApiWrap::saveDraftsToCloud() {
 		if (!textWithTags.tags.isEmpty()) {
 			flags |= MTPmessages_SaveDraft::Flag::f_entities;
 		}
-		auto entities = TextUtilities::EntitiesToMTP(
-			ConvertTextTagsToEntities(textWithTags.tags),
-			TextUtilities::ConvertOption::SkipLocal);
+		auto entities = Api::EntitiesToMTP(
+			TextUtilities::ConvertTextTagsToEntities(textWithTags.tags),
+			Api::ConvertOption::SkipLocal);
 
 		const auto draftText = textWithTags.text;
 		history->setSentDraftText(draftText);
@@ -4830,9 +4831,9 @@ void ApiWrap::editUploadedFile(
 		return;
 	}
 
-	auto sentEntities = TextUtilities::EntitiesToMTP(
+	auto sentEntities = Api::EntitiesToMTP(
 		item->originalText().entities,
-		TextUtilities::ConvertOption::SkipLocal);
+		Api::ConvertOption::SkipLocal);
 
 	auto flagsEditMsg = MTPmessages_EditMessage::Flag::f_message | 0;
 	flagsEditMsg |= MTPmessages_EditMessage::Flag::f_no_webpage;
@@ -4934,7 +4935,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
 	auto sending = TextWithEntities();
 	auto left = TextWithEntities {
 		textWithTags.text,
-		ConvertTextTagsToEntities(textWithTags.tags)
+		TextUtilities::ConvertTextTagsToEntities(textWithTags.tags)
 	};
 	auto prepareFlags = Ui::ItemTextOptions(
 		history,
@@ -4988,8 +4989,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
 		if (silentPost) {
 			sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
 		}
-		auto localEntities = TextUtilities::EntitiesToMTP(sending.entities);
-		auto sentEntities = TextUtilities::EntitiesToMTP(sending.entities, TextUtilities::ConvertOption::SkipLocal);
+		auto localEntities = Api::EntitiesToMTP(sending.entities);
+		auto sentEntities = Api::EntitiesToMTP(
+			sending.entities,
+			Api::ConvertOption::SkipLocal);
 		if (!sentEntities.v.isEmpty()) {
 			sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
 		}
@@ -5274,9 +5277,9 @@ void ApiWrap::sendMediaWithRandomId(
 
 	auto caption = item->originalText();
 	TextUtilities::Trim(caption);
-	auto sentEntities = TextUtilities::EntitiesToMTP(
+	auto sentEntities = Api::EntitiesToMTP(
 		caption.entities,
-		TextUtilities::ConvertOption::SkipLocal);
+		Api::ConvertOption::SkipLocal);
 
 	const auto flags = MTPmessages_SendMedia::Flags(0)
 		| (replyTo
diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp
index 0dc9dbf19..916b0ed02 100644
--- a/Telegram/SourceFiles/app.cpp
+++ b/Telegram/SourceFiles/app.cpp
@@ -63,8 +63,6 @@ namespace {
 		*pressedLinkItem = nullptr,
 		*mousedItem = nullptr;
 
-	style::font monofont;
-
 	struct CornersPixmaps {
 		QPixmap p[4];
 	};
@@ -138,14 +136,6 @@ namespace App {
 		}
 	}
 
-	void tryFontFamily(QString &family, const QString &tryFamily) {
-		if (family.isEmpty()) {
-			if (!QFontInfo(QFont(tryFamily)).family().trimmed().compare(tryFamily, Qt::CaseInsensitive)) {
-				family = tryFamily;
-			}
-		}
-	}
-
 	void createMaskCorners() {
 		QImage mask[4];
 		prepareCorners(SmallMaskCorners, st::buttonRadius, QColor(255, 255, 255), nullptr, mask);
@@ -204,16 +194,6 @@ namespace App {
 	}
 
 	void initMedia() {
-		if (!::monofont) {
-			QString family;
-			tryFontFamily(family, qsl("Consolas"));
-			tryFontFamily(family, qsl("Liberation Mono"));
-			tryFontFamily(family, qsl("Menlo"));
-			tryFontFamily(family, qsl("Courier"));
-			if (family.isEmpty()) family = QFontDatabase::systemFont(QFontDatabase::FixedFont).family();
-			::monofont = style::font(st::normalFont->f.pixelSize(), 0, family);
-		}
-
 		createCorners();
 
 		using Update = Window::Theme::BackgroundUpdate;
@@ -293,10 +273,6 @@ namespace App {
 		mousedItem(nullptr);
 	}
 
-	const style::font &monofont() {
-		return ::monofont;
-	}
-
 	void quit() {
 		if (quitting()) {
 			return;
@@ -451,15 +427,6 @@ namespace App {
 		rectWithCorners(p, rect, st::msgInBg, MessageInCorners, corners);
 	}
 
-	QImage *cornersMask(ImageRoundRadius radius) {
-		switch (radius) {
-		case ImageRoundRadius::Large: return ::cornersMaskLarge;
-		case ImageRoundRadius::Small:
-		default: break;
-		}
-		return ::cornersMaskSmall;
-	}
-
 	void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, const CornersPixmaps &corner, const style::color *shadow, RectParts parts) {
 		auto cornerWidth = corner.p[0].width() / cIntRetinaFactor();
 		auto cornerHeight = corner.p[0].height() / cIntRetinaFactor();
diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h
index cb764c410..5f7173781 100644
--- a/Telegram/SourceFiles/app.h
+++ b/Telegram/SourceFiles/app.h
@@ -81,8 +81,6 @@ namespace App {
 	HistoryView::Element *mousedItem();
 	void clearMousedItems();
 
-	const style::font &monofont();
-
 	void initMedia();
 	void deinitMedia();
 
@@ -106,7 +104,6 @@ namespace App {
 	void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners);
 	void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners);
 
-	QImage *cornersMask(ImageRoundRadius radius);
 	void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, RoundCorners index, const style::color *shadow = nullptr, RectParts parts = RectPart::Full);
 	inline void roundRect(Painter &p, const QRect &rect, style::color bg, RoundCorners index, const style::color *shadow = nullptr, RectParts parts = RectPart::Full) {
 		return roundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, index, shadow, parts);
diff --git a/Telegram/SourceFiles/base/algorithm.h b/Telegram/SourceFiles/base/algorithm.h
index edcf7684f..fbf53e94c 100644
--- a/Telegram/SourceFiles/base/algorithm.h
+++ b/Telegram/SourceFiles/base/algorithm.h
@@ -81,6 +81,23 @@ struct pointer_comparator {
 
 };
 
+inline QString FromUtf8Safe(const char *string, int size = -1) {
+	if (!string || !size) {
+		return QString();
+	} else if (size < 0) {
+		size = strlen(string);
+	}
+	const auto result = QString::fromUtf8(string, size);
+	const auto back = result.toUtf8();
+	return (back.size() != size || memcmp(back.constData(), string, size))
+		? QString::fromLocal8Bit(string, size)
+		: result;
+}
+
+inline QString FromUtf8Safe(const QByteArray &string) {
+	return FromUtf8Safe(string.constData(), string.size());
+}
+
 } // namespace base
 
 template <typename T>
diff --git a/Telegram/SourceFiles/base/crc32hash.cpp b/Telegram/SourceFiles/base/crc32hash.cpp
new file mode 100644
index 000000000..91cbf9920
--- /dev/null
+++ b/Telegram/SourceFiles/base/crc32hash.cpp
@@ -0,0 +1,61 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "base/crc32hash.h"
+
+namespace base {
+namespace {
+
+class Crc32Table {
+public:
+	Crc32Table() {
+		auto poly = std::uint32_t(0x04c11db7);
+		for (auto i = 0; i != 256; ++i) {
+			_data[i] = reflect(i, 8) << 24;
+			for (auto j = 0; j != 8; ++j) {
+				_data[i] = (_data[i] << 1) ^ (_data[i] & (1 << 31) ? poly : 0);
+			}
+			_data[i] = reflect(_data[i], 32);
+		}
+	}
+
+	std::uint32_t operator[](int index) const {
+		return _data[index];
+	}
+
+private:
+	std::uint32_t reflect(std::uint32_t val, char ch) {
+		auto result = std::uint32_t(0);
+		for (int i = 1; i < (ch + 1); ++i) {
+			if (val & 1) {
+				result |= 1 << (ch - i);
+			}
+			val >>= 1;
+		}
+		return result;
+	}
+
+	std::uint32_t _data[256];
+
+};
+
+} // namespace
+
+std::int32_t crc32(const void *data, int len) {
+	static const auto kTable = Crc32Table();
+
+	const auto buffer = static_cast<const std::uint8_t*>(data);
+
+	auto crc = std::uint32_t(0xffffffff);
+	for (auto i = 0; i != len; ++i) {
+		crc = (crc >> 8) ^ kTable[(crc & 0xFF) ^ buffer[i]];
+	}
+
+	return static_cast<std::int32_t>(crc ^ 0xffffffff);
+}
+
+} // namespace base
diff --git a/Telegram/SourceFiles/base/crc32hash.h b/Telegram/SourceFiles/base/crc32hash.h
new file mode 100644
index 000000000..dac0b5886
--- /dev/null
+++ b/Telegram/SourceFiles/base/crc32hash.h
@@ -0,0 +1,16 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include <cstdint>
+
+namespace base {
+
+std::int32_t crc32(const void *data, int len);
+
+} // namespace base
diff --git a/Telegram/SourceFiles/base/object_ptr.h b/Telegram/SourceFiles/base/object_ptr.h
index 92479f4db..714ee600a 100644
--- a/Telegram/SourceFiles/base/object_ptr.h
+++ b/Telegram/SourceFiles/base/object_ptr.h
@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
+#include <QtCore/QPointer>
+
 // Smart pointer for QObject*, has move semantics, destroys object if it doesn't have a parent.
 template <typename Object>
 class object_ptr {
diff --git a/Telegram/SourceFiles/base/openssl_help.h b/Telegram/SourceFiles/base/openssl_help.h
index bb9aae7f4..f9ca3ccfb 100644
--- a/Telegram/SourceFiles/base/openssl_help.h
+++ b/Telegram/SourceFiles/base/openssl_help.h
@@ -524,6 +524,17 @@ inline void AddRandomSeed(bytes::const_span data) {
 	RAND_seed(data.data(), data.size());
 }
 
+template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
+[[nodiscard]] inline T RandomValue() {
+	unsigned char buffer[sizeof(T)];
+	if (!RAND_bytes(buffer, sizeof(T))) {
+		Unexpected("Could not generate random bytes!");
+	}
+	auto result = T();
+	memcpy(&result, buffer, sizeof(T));
+	return result;
+}
+
 inline bytes::vector Pbkdf2Sha512(
 		bytes::const_span password,
 		bytes::const_span salt,
diff --git a/Telegram/SourceFiles/base/qthelp_url.h b/Telegram/SourceFiles/base/qthelp_url.h
index c0e10bc6d..fd8e460be 100644
--- a/Telegram/SourceFiles/base/qthelp_url.h
+++ b/Telegram/SourceFiles/base/qthelp_url.h
@@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
+#include <QtCore/QUrl>
+#include <QtCore/QString>
+#include <QtCore/QRegularExpression>
+
 namespace qthelp {
 
 const QRegularExpression &RegExpDomain();
diff --git a/Telegram/SourceFiles/base/timer.cpp b/Telegram/SourceFiles/base/timer.cpp
index 07d8c2424..00204ad43 100644
--- a/Telegram/SourceFiles/base/timer.cpp
+++ b/Telegram/SourceFiles/base/timer.cpp
@@ -108,8 +108,8 @@ void Timer::timerEvent(QTimerEvent *e) {
 		cancel();
 	}
 
-	if (_callback) {
-		_callback();
+	if (const auto onstack = _callback) {
+		onstack();
 	}
 }
 
diff --git a/Telegram/SourceFiles/base/unique_qptr.h b/Telegram/SourceFiles/base/unique_qptr.h
index 7a1c1de3f..89bd2dc58 100644
--- a/Telegram/SourceFiles/base/unique_qptr.h
+++ b/Telegram/SourceFiles/base/unique_qptr.h
@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
+#include <QtCore/QPointer>
+
 namespace base {
 
 template <typename T>
diff --git a/Telegram/SourceFiles/boxes/abstract_box.cpp b/Telegram/SourceFiles/boxes/abstract_box.cpp
index 994c290d5..f4869e7d2 100644
--- a/Telegram/SourceFiles/boxes/abstract_box.cpp
+++ b/Telegram/SourceFiles/boxes/abstract_box.cpp
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/widgets/shadow.h"
 #include "ui/wrap/fade_wrap.h"
 #include "ui/text/text_utilities.h"
+#include "ui/painter.h"
 #include "base/timer.h"
 #include "mainwidget.h"
 #include "mainwindow.h"
@@ -135,7 +136,7 @@ void BoxContent::onDraggingScrollDelta(int delta) {
 }
 
 void BoxContent::onDraggingScrollTimer() {
-	auto delta = (_draggingScrollDelta > 0) ? qMin(_draggingScrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_draggingScrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed));
+	auto delta = (_draggingScrollDelta > 0) ? qMin(_draggingScrollDelta * 3 / 20 + 1, int32(Ui::kMaxScrollSpeed)) : qMax(_draggingScrollDelta * 3 / 20 - 1, -int32(Ui::kMaxScrollSpeed));
 	_scroll->scrollToY(_scroll->scrollTop() + delta);
 }
 
@@ -276,7 +277,6 @@ AbstractBox::AbstractBox(
 : LayerWidget(layer)
 , _layer(layer)
 , _content(std::move(content)) {
-	subscribe(Lang::Current().updated(), [=] { refreshLang(); });
 	_content->setParent(this);
 	_content->setDelegate(this);
 
@@ -398,10 +398,6 @@ bool AbstractBox::closeByOutsideClick() const {
 	return _closeByOutsideClick;
 }
 
-void AbstractBox::refreshLang() {
-	InvokeQueued(this, [this] { updateButtonsPositions(); });
-}
-
 bool AbstractBox::hasTitle() const {
 	return (_title != nullptr) || !_additionalTitle.current().isEmpty();
 }
@@ -464,7 +460,10 @@ QPointer<Ui::RoundButton> AbstractBox::addButton(
 	auto result = QPointer<Ui::RoundButton>(_buttons.back());
 	result->setClickedCallback(std::move(clickCallback));
 	result->show();
-	updateButtonsPositions();
+	result->widthValue(
+	) | rpl::start_with_next([=] {
+		updateButtonsPositions();
+	}, result->lifetime());
 	return result;
 }
 
@@ -476,7 +475,10 @@ QPointer<Ui::RoundButton> AbstractBox::addLeftButton(
 	auto result = QPointer<Ui::RoundButton>(_leftButton);
 	result->setClickedCallback(std::move(clickCallback));
 	result->show();
-	updateButtonsPositions();
+	result->widthValue(
+	) | rpl::start_with_next([=] {
+		updateButtonsPositions();
+	}, result->lifetime());
 	return result;
 }
 
diff --git a/Telegram/SourceFiles/boxes/abstract_box.h b/Telegram/SourceFiles/boxes/abstract_box.h
index 64930ccc7..0a38b109d 100644
--- a/Telegram/SourceFiles/boxes/abstract_box.h
+++ b/Telegram/SourceFiles/boxes/abstract_box.h
@@ -11,8 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/unique_qptr.h"
 #include "base/flags.h"
 #include "ui/effects/animation_value.h"
+#include "ui/text/text_entity.h"
 #include "ui/rp_widget.h"
 
+class Painter;
+
 namespace style {
 struct RoundButton;
 struct IconButton;
@@ -77,7 +80,7 @@ public:
 
 };
 
-class BoxContent : public Ui::RpWidget, protected base::Subscriber {
+class BoxContent : public Ui::RpWidget {
 	Q_OBJECT
 
 public:
@@ -270,10 +273,7 @@ private:
 
 };
 
-class AbstractBox
-	: public Window::LayerWidget
-	, public BoxContentDelegate
-	, protected base::Subscriber {
+class AbstractBox : public Window::LayerWidget, public BoxContentDelegate {
 public:
 	AbstractBox(
 		not_null<Window::LayerStackWidget*> layer,
@@ -345,7 +345,6 @@ private:
 
 	void paintAdditionalTitle(Painter &p);
 	void updateTitlePosition();
-	void refreshLang();
 
 	[[nodiscard]] bool hasTitle() const;
 	[[nodiscard]] int titleHeight() const;
diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp
index b7ddecd53..728afe6ed 100644
--- a/Telegram/SourceFiles/boxes/add_contact_box.cpp
+++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp
@@ -25,10 +25,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "window/window_session_controller.h"
 #include "ui/widgets/checkbox.h"
 #include "ui/widgets/buttons.h"
-#include "ui/widgets/input_fields.h"
 #include "ui/widgets/labels.h"
 #include "ui/toast/toast.h"
 #include "ui/special_buttons.h"
+#include "ui/special_fields.h"
 #include "ui/text_options.h"
 #include "ui/unread_badge.h"
 #include "ui/ui_utility.h"
diff --git a/Telegram/SourceFiles/boxes/add_contact_box.h b/Telegram/SourceFiles/boxes/add_contact_box.h
index ccdcbc3b6..76231b4be 100644
--- a/Telegram/SourceFiles/boxes/add_contact_box.h
+++ b/Telegram/SourceFiles/boxes/add_contact_box.h
@@ -139,7 +139,10 @@ private:
 
 };
 
-class SetupChannelBox : public BoxContent, public RPCSender {
+class SetupChannelBox
+	: public BoxContent
+	, public RPCSender
+	, private base::Subscriber {
 public:
 	SetupChannelBox(
 		QWidget*,
@@ -234,7 +237,10 @@ private:
 
 };
 
-class RevokePublicLinkBox : public BoxContent, public RPCSender {
+class RevokePublicLinkBox
+	: public BoxContent
+	, public RPCSender
+	, private base::Subscriber {
 public:
 	RevokePublicLinkBox(
 		QWidget*,
diff --git a/Telegram/SourceFiles/boxes/background_box.cpp b/Telegram/SourceFiles/boxes/background_box.cpp
index cec8274bb..7dd55753b 100644
--- a/Telegram/SourceFiles/boxes/background_box.cpp
+++ b/Telegram/SourceFiles/boxes/background_box.cpp
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "apiwrap.h"
 #include "mtproto/sender.h"
 #include "data/data_session.h"
+#include "data/data_file_origin.h"
 #include "boxes/background_preview_box.h"
 #include "boxes/confirm_box.h"
 #include "app.h"
diff --git a/Telegram/SourceFiles/boxes/background_preview_box.h b/Telegram/SourceFiles/boxes/background_preview_box.h
index 6d97e2f18..879cc0d02 100644
--- a/Telegram/SourceFiles/boxes/background_preview_box.h
+++ b/Telegram/SourceFiles/boxes/background_preview_box.h
@@ -25,7 +25,8 @@ class Checkbox;
 
 class BackgroundPreviewBox
 	: public BoxContent
-	, private HistoryView::SimpleElementDelegate {
+	, private HistoryView::SimpleElementDelegate
+	, private base::Subscriber {
 public:
 	BackgroundPreviewBox(
 		QWidget*,
diff --git a/Telegram/SourceFiles/boxes/calendar_box.h b/Telegram/SourceFiles/boxes/calendar_box.h
index 0f265617b..b718fcf0a 100644
--- a/Telegram/SourceFiles/boxes/calendar_box.h
+++ b/Telegram/SourceFiles/boxes/calendar_box.h
@@ -17,7 +17,7 @@ namespace Ui {
 class IconButton;
 } // namespace Ui
 
-class CalendarBox : public BoxContent {
+class CalendarBox : public BoxContent, private base::Subscriber {
 public:
 	CalendarBox(
 		QWidget*,
diff --git a/Telegram/SourceFiles/boxes/change_phone_box.cpp b/Telegram/SourceFiles/boxes/change_phone_box.cpp
index 0af50feed..6b50813b7 100644
--- a/Telegram/SourceFiles/boxes/change_phone_box.cpp
+++ b/Telegram/SourceFiles/boxes/change_phone_box.cpp
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/wrap/fade_wrap.h"
 #include "ui/toast/toast.h"
 #include "ui/text/text_utilities.h"
+#include "ui/special_fields.h"
 #include "boxes/confirm_phone_box.h"
 #include "boxes/confirm_box.h"
 #include "main/main_session.h"
diff --git a/Telegram/SourceFiles/boxes/confirm_box.cpp b/Telegram/SourceFiles/boxes/confirm_box.cpp
index dedb0c829..1e6532f69 100644
--- a/Telegram/SourceFiles/boxes/confirm_box.cpp
+++ b/Telegram/SourceFiles/boxes/confirm_box.cpp
@@ -254,8 +254,9 @@ void ConfirmBox::mouseReleaseEvent(QMouseEvent *e) {
 	_lastMousePos = e->globalPos();
 	updateHover();
 	if (const auto activated = ClickHandler::unpressed()) {
+		const auto guard = window();
 		Ui::hideLayer();
-		App::activateClickHandler(activated, e->button());
+		ActivateClickHandler(guard, activated, e->button());
 		return;
 	}
 	BoxContent::mouseReleaseEvent(e);
diff --git a/Telegram/SourceFiles/boxes/confirm_box.h b/Telegram/SourceFiles/boxes/confirm_box.h
index 3d21f5bc5..ce31a2078 100644
--- a/Telegram/SourceFiles/boxes/confirm_box.h
+++ b/Telegram/SourceFiles/boxes/confirm_box.h
@@ -95,7 +95,7 @@ public:
 
 };
 
-class MaxInviteBox : public BoxContent {
+class MaxInviteBox : public BoxContent, private base::Subscriber {
 public:
 	MaxInviteBox(QWidget*, not_null<ChannelData*> channel);
 
@@ -201,7 +201,10 @@ private:
 
 };
 
-class ConfirmInviteBox : public BoxContent, public RPCSender {
+class ConfirmInviteBox
+	: public BoxContent
+	, public RPCSender
+	, private base::Subscriber {
 public:
 	ConfirmInviteBox(
 		QWidget*,
diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
index 5d23f0351..36868c467 100644
--- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp
+++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "boxes/edit_caption_box.h"
 
 #include "apiwrap.h"
+#include "api/api_text_entities.h"
 #include "main/main_session.h"
 #include "chat_helpers/emoji_suggestions_widget.h"
 #include "chat_helpers/message_field.h"
@@ -21,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_photo.h"
 #include "data/data_user.h"
 #include "data/data_session.h"
+#include "data/data_file_origin.h"
 #include "history/history.h"
 #include "history/history_item.h"
 #include "lang/lang_keys.h"
@@ -31,11 +33,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "styles/style_chat_helpers.h"
 #include "styles/style_history.h"
 #include "ui/image/image.h"
+#include "ui/widgets/input_fields.h"
+#include "ui/widgets/checkbox.h"
+#include "ui/widgets/checkbox.h"
 #include "ui/special_buttons.h"
 #include "ui/text_options.h"
-#include "ui/widgets/input_fields.h"
 #include "window/window_session_controller.h"
-#include "ui/widgets/checkbox.h"
 #include "confirm_box.h"
 #include "facades.h"
 #include "app.h"
@@ -898,7 +901,7 @@ void EditCaptionBox::save() {
 	const auto textWithTags = _field->getTextWithAppliedMarkdown();
 	auto sending = TextWithEntities{
 		textWithTags.text,
-		ConvertTextTagsToEntities(textWithTags.tags)
+		TextUtilities::ConvertTextTagsToEntities(textWithTags.tags)
 	};
 	const auto prepareFlags = Ui::ItemTextOptions(
 		item->history(),
@@ -906,9 +909,9 @@ void EditCaptionBox::save() {
 	TextUtilities::PrepareForSending(sending, prepareFlags);
 	TextUtilities::Trim(sending);
 
-	const auto sentEntities = TextUtilities::EntitiesToMTP(
+	const auto sentEntities = Api::EntitiesToMTP(
 		sending.entities,
-		TextUtilities::ConvertOption::SkipLocal);
+		Api::ConvertOption::SkipLocal);
 	if (!sentEntities.v.isEmpty()) {
 		flags |= MTPmessages_EditMessage::Flag::f_entities;
 	}
@@ -917,7 +920,7 @@ void EditCaptionBox::save() {
 		const auto textWithTags = _field->getTextWithAppliedMarkdown();
 		auto sending = TextWithEntities{
 			textWithTags.text,
-			ConvertTextTagsToEntities(textWithTags.tags)
+			TextUtilities::ConvertTextTagsToEntities(textWithTags.tags)
 		};
 		item->setText(sending);
 
diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.h b/Telegram/SourceFiles/boxes/edit_caption_box.h
index 186fd8a59..5dde6f010 100644
--- a/Telegram/SourceFiles/boxes/edit_caption_box.h
+++ b/Telegram/SourceFiles/boxes/edit_caption_box.h
@@ -35,7 +35,10 @@ namespace Window {
 class SessionController;
 } // namespace Window
 
-class EditCaptionBox : public BoxContent, public RPCSender {
+class EditCaptionBox
+	: public BoxContent
+	, public RPCSender
+	, private base::Subscriber {
 public:
 	EditCaptionBox(
 		QWidget*,
diff --git a/Telegram/SourceFiles/boxes/edit_color_box.h b/Telegram/SourceFiles/boxes/edit_color_box.h
index 60ea019f8..46b8ccf20 100644
--- a/Telegram/SourceFiles/boxes/edit_color_box.h
+++ b/Telegram/SourceFiles/boxes/edit_color_box.h
@@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "boxes/abstract_box.h"
 
-class EditColorBox : public BoxContent {
+class EditColorBox : public BoxContent, private base::Subscriber {
 public:
 	enum class Mode {
 		RGBA,
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp
index a0b6b8eef..b00af3ac3 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp
@@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/wrap/padding_wrap.h"
 #include "ui/wrap/slide_wrap.h"
 #include "ui/wrap/vertical_layout.h"
+#include "ui/special_fields.h"
 #include "window/window_session_controller.h"
 #include <rpl/flatten_latest.h>
 
diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp
index fab66ef90..bd0ce7590 100644
--- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp
+++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp
@@ -441,7 +441,7 @@ void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
 		const auto index = stickerFromGlobalPos(e->globalPos());
 		if (index >= 0 && index < _pack.size() && !isMasksSet()) {
 			const auto sticker = _pack[index];
-			Core::App().postponeCall(crl::guard(App::main(), [=] {
+			Ui::PostponeCall(crl::guard(App::main(), [=] {
 				if (App::main()->onSendSticker(sticker)) {
 					Ui::hideSettingsAndLayer();
 				}
diff --git a/Telegram/SourceFiles/boxes/stickers_box.h b/Telegram/SourceFiles/boxes/stickers_box.h
index 6bbb4173a..1436e3b5a 100644
--- a/Telegram/SourceFiles/boxes/stickers_box.h
+++ b/Telegram/SourceFiles/boxes/stickers_box.h
@@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "mtproto/sender.h"
 #include "chat_helpers/stickers.h"
 #include "ui/effects/animations.h"
-#include "ui/widgets/input_fields.h"
+#include "ui/special_fields.h"
 
 class ConfirmBox;
 
@@ -32,7 +32,10 @@ namespace Main {
 class Session;
 } // namespace Main
 
-class StickersBox : public BoxContent, public RPCSender {
+class StickersBox final
+	: public BoxContent
+	, public RPCSender
+	, private base::Subscriber {
 public:
 	enum class Section {
 		Installed,
diff --git a/Telegram/SourceFiles/boxes/username_box.cpp b/Telegram/SourceFiles/boxes/username_box.cpp
index 3f17bb19c..55db8b294 100644
--- a/Telegram/SourceFiles/boxes/username_box.cpp
+++ b/Telegram/SourceFiles/boxes/username_box.cpp
@@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "mainwidget.h"
 #include "mainwindow.h"
 #include "ui/widgets/buttons.h"
-#include "ui/widgets/input_fields.h"
+#include "ui/special_fields.h"
 #include "ui/toast/toast.h"
 #include "core/application.h"
 #include "main/main_session.h"
diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp
index 053d602fb..ea0e5147a 100644
--- a/Telegram/SourceFiles/calls/calls_panel.cpp
+++ b/Telegram/SourceFiles/calls/calls_panel.cpp
@@ -10,15 +10,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_photo.h"
 #include "data/data_session.h"
 #include "data/data_user.h"
+#include "data/data_file_origin.h"
 #include "calls/calls_emoji_fingerprint.h"
-#include "styles/style_calls.h"
-#include "styles/style_history.h"
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/labels.h"
 #include "ui/widgets/shadow.h"
 #include "ui/effects/ripple_animation.h"
 #include "ui/image/image.h"
 #include "ui/wrap/fade_wrap.h"
+#include "ui/platform/ui_platform_utility.h"
 #include "ui/empty_userpic.h"
 #include "ui/emoji_config.h"
 #include "core/application.h"
@@ -31,6 +31,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "window/main_window.h"
 #include "layout.h"
 #include "app.h"
+#include "styles/style_calls.h"
+#include "styles/style_history.h"
 
 #include <QtWidgets/QDesktopWidget>
 #include <QtWidgets/QApplication>
@@ -440,7 +442,7 @@ void Panel::initLayout() {
 	});
 	createDefaultCacheImage();
 
-	Platform::InitOnTopPanel(this);
+	Ui::Platform::InitOnTopPanel(this);
 }
 
 void Panel::toggleOpacityAnimation(bool visible) {
@@ -592,7 +594,7 @@ bool Panel::isGoodUserPhoto(PhotoData *photo) {
 
 void Panel::initGeometry() {
 	auto center = Core::App().getPointForCallPanelCenter();
-	_useTransparency = Platform::TranslucentWindowsSupported(center);
+	_useTransparency = Ui::Platform::TranslucentWindowsSupported(center);
 	setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);
 	_padding = _useTransparency ? st::callShadow.extend : style::margins(st::lineWidth, st::lineWidth, st::lineWidth, st::lineWidth);
 	_contentTop = _padding.top() + st::callWidth;
@@ -704,7 +706,7 @@ void Panel::paintEvent(QPaintEvent *e) {
 			finishAnimating();
 			if (!_call || isHidden()) return;
 		} else {
-			Platform::StartTranslucentPaint(p, e);
+			Ui::Platform::StartTranslucentPaint(p, e);
 			p.setOpacity(opacity);
 
 			PainterHighQualityEnabler hq(p);
@@ -717,7 +719,7 @@ void Panel::paintEvent(QPaintEvent *e) {
 	}
 
 	if (_useTransparency) {
-		Platform::StartTranslucentPaint(p, e);
+		Ui::Platform::StartTranslucentPaint(p, e);
 		p.drawPixmapLeft(0, 0, width(), _cache);
 	} else {
 		p.drawPixmapLeft(_padding.left(), _padding.top(), width(), _userPhoto);
@@ -864,9 +866,9 @@ void Panel::stateChanged(State state) {
 	if (windowHandle()) {
 		// First stateChanged() is called before the first Platform::InitOnTopPanel(this).
 		if ((state == State::Starting) || (state == State::WaitingIncoming)) {
-			Platform::ReInitOnTopPanel(this);
+			Ui::Platform::ReInitOnTopPanel(this);
 		} else {
-			Platform::DeInitOnTopPanel(this);
+			Ui::Platform::DeInitOnTopPanel(this);
 		}
 	}
 	if (state == State::Established) {
diff --git a/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp b/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp
index 8eead3b4f..2668efcc6 100644
--- a/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp
+++ b/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp
@@ -135,7 +135,7 @@ void BotKeyboard::mouseReleaseEvent(QMouseEvent *e) {
 	updateSelected();
 
 	if (ClickHandlerPtr activated = ClickHandler::unpressed()) {
-		App::activateClickHandler(activated, e->button());
+		ActivateClickHandler(window(), activated, e->button());
 	}
 }
 
@@ -270,6 +270,10 @@ QPoint BotKeyboard::tooltipPos() const {
 	return _lastMousePos;
 }
 
+bool BotKeyboard::tooltipWindowActive() const {
+	return Ui::InFocusChain(window());
+}
+
 QString BotKeyboard::tooltipText() const {
 	if (ClickHandlerPtr lnk = ClickHandler::getActive()) {
 		return lnk->tooltip();
diff --git a/Telegram/SourceFiles/chat_helpers/bot_keyboard.h b/Telegram/SourceFiles/chat_helpers/bot_keyboard.h
index e8c0fa589..4b61a3b7f 100644
--- a/Telegram/SourceFiles/chat_helpers/bot_keyboard.h
+++ b/Telegram/SourceFiles/chat_helpers/bot_keyboard.h
@@ -46,6 +46,7 @@ public:
 	// AbstractTooltipShower interface
 	QString tooltipText() const override;
 	QPoint tooltipPos() const override;
+	bool tooltipWindowActive() const override;
 
 	// ClickHandlerHost interface
 	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp
index 03589d0b0..a9b4129f0 100644
--- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp
@@ -769,6 +769,10 @@ QPoint EmojiListWidget::tooltipPos() const {
 	return _lastMousePos;
 }
 
+bool EmojiListWidget::tooltipWindowActive() const {
+	return Ui::InFocusChain(window());
+}
+
 TabbedSelector::InnerFooter *EmojiListWidget::getFooter() const {
 	return _footer;
 }
diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h
index 564b53e16..68f3ee93a 100644
--- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h
+++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h
@@ -52,6 +52,7 @@ public:
 	// Ui::AbstractTooltipShower interface.
 	QString tooltipText() const override;
 	QPoint tooltipPos() const override;
+	bool tooltipWindowActive() const override;
 
 	rpl::producer<EmojiPtr> chosen() const;
 
diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp
index 33196bc3d..cd1dc1d38 100644
--- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp
@@ -356,7 +356,7 @@ void GifsListWidget::mouseReleaseEvent(QMouseEvent *e) {
 		int row = _selected / MatrixRowShift, column = _selected % MatrixRowShift;
 		selectInlineResult(row, column);
 	} else {
-		App::activateClickHandler(activated, e->button());
+		ActivateClickHandler(window(), activated, e->button());
 	}
 }
 
diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp
index 3eddbe557..93e4400e7 100644
--- a/Telegram/SourceFiles/chat_helpers/message_field.cpp
+++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp
@@ -39,21 +39,12 @@ using EditLinkAction = Ui::InputField::EditLinkAction;
 using EditLinkSelection = Ui::InputField::EditLinkSelection;
 
 constexpr auto kParseLinksTimeout = crl::time(1000);
-const auto kMentionTagStart = qstr("mention://user.");
-
-bool IsMentionLink(const QString &link) {
-	return link.startsWith(kMentionTagStart);
-}
 
 // For mention tags save and validate userId, ignore tags for different userId.
 class FieldTagMimeProcessor : public Ui::InputField::TagMimeProcessor {
 public:
-	QString mimeTagFromTag(const QString &tagId) override {
-		return ConvertTagToMimeTag(tagId);
-	}
-
 	QString tagFromMimeTag(const QString &mimeTag) override {
-		if (IsMentionLink(mimeTag)) {
+		if (TextUtilities::IsMentionLink(mimeTag)) {
 			auto match = QRegularExpression(":(\\d+)$").match(mimeTag);
 			if (!match.hasMatch()
 				|| match.capturedRef(1).toInt() != Auth().userId()) {
@@ -216,135 +207,20 @@ TextWithEntities StripSupportHashtag(TextWithEntities &&text) {
 
 } // namespace
 
-QString ConvertTagToMimeTag(const QString &tagId) {
-	if (IsMentionLink(tagId)) {
-		return tagId + ':' + QString::number(Auth().userId());
-	}
-	return tagId;
-}
-
 QString PrepareMentionTag(not_null<UserData*> user) {
-	return kMentionTagStart
+	return TextUtilities::kMentionTagStart
 		+ QString::number(user->bareId())
 		+ '.'
 		+ QString::number(user->accessHash());
 }
 
-EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
-	EntitiesInText result;
-	if (tags.isEmpty()) {
-		return result;
-	}
-
-	result.reserve(tags.size());
-	for (const auto &tag : tags) {
-		const auto push = [&](
-				EntityType type,
-				const QString &data = QString()) {
-			result.push_back(
-				EntityInText(type, tag.offset, tag.length, data));
-		};
-		if (IsMentionLink(tag.id)) {
-			if (auto match = qthelp::regex_match("^(\\d+\\.\\d+)(/|$)", tag.id.midRef(kMentionTagStart.size()))) {
-				push(EntityType::MentionName, match->captured(1));
-			}
-		} else if (tag.id == Ui::InputField::kTagBold) {
-			push(EntityType::Bold);
-		} else if (tag.id == Ui::InputField::kTagItalic) {
-			push(EntityType::Italic);
-		} else if (tag.id == Ui::InputField::kTagUnderline) {
-			push(EntityType::Underline);
-		} else if (tag.id == Ui::InputField::kTagStrikeOut) {
-			push(EntityType::StrikeOut);
-		} else if (tag.id == Ui::InputField::kTagCode) {
-			push(EntityType::Code);
-		} else if (tag.id == Ui::InputField::kTagPre) { // #TODO entities
-			push(EntityType::Pre);
-		} else /*if (ValidateUrl(tag.id)) */{ // We validate when we insert.
-			push(EntityType::CustomUrl, tag.id);
-		}
-	}
-	return result;
-}
-
-TextWithTags::Tags ConvertEntitiesToTextTags(const EntitiesInText &entities) {
-	TextWithTags::Tags result;
-	if (entities.isEmpty()) {
-		return result;
-	}
-
-	result.reserve(entities.size());
-	for (const auto &entity : entities) {
-		const auto push = [&](const QString &tag) {
-			result.push_back({ entity.offset(), entity.length(), tag });
-		};
-		switch (entity.type()) {
-		case EntityType::MentionName: {
-			auto match = QRegularExpression(R"(^(\d+\.\d+)$)").match(entity.data());
-			if (match.hasMatch()) {
-				push(kMentionTagStart + entity.data());
-			}
-		} break;
-		case EntityType::CustomUrl: {
-			const auto url = entity.data();
-			if (Ui::InputField::IsValidMarkdownLink(url)
-				&& !IsMentionLink(url)) {
-				push(url);
-			}
-		} break;
-		case EntityType::Bold: push(Ui::InputField::kTagBold); break;
-		case EntityType::Italic: push(Ui::InputField::kTagItalic); break;
-		case EntityType::Underline:
-			push(Ui::InputField::kTagUnderline);
-			break;
-		case EntityType::StrikeOut:
-			push(Ui::InputField::kTagStrikeOut);
-			break;
-		case EntityType::Code: push(Ui::InputField::kTagCode); break; // #TODO entities
-		case EntityType::Pre: push(Ui::InputField::kTagPre); break;
-		}
-	}
-	return result;
-}
-
-std::unique_ptr<QMimeData> MimeDataFromText(
-		const TextForMimeData &text) {
-	if (text.empty()) {
-		return nullptr;
-	}
-
-	auto result = std::make_unique<QMimeData>();
-	result->setText(text.expanded);
-	auto tags = ConvertEntitiesToTextTags(text.rich.entities);
-	if (!tags.isEmpty()) {
-		for (auto &tag : tags) {
-			tag.id = ConvertTagToMimeTag(tag.id);
-		}
-		result->setData(
-			TextUtilities::TagsTextMimeType(),
-			text.rich.text.toUtf8());
-		result->setData(
-			TextUtilities::TagsMimeType(),
-			TextUtilities::SerializeTags(tags));
-	}
-	return result;
-}
-
-void SetClipboardText(
-		const TextForMimeData &text,
-		QClipboard::Mode mode) {
-	if (auto data = MimeDataFromText(text)) {
-		QGuiApplication::clipboard()->setMimeData(data.release(), mode);
-	}
-}
-
 TextWithTags PrepareEditText(not_null<HistoryItem*> item) {
 	const auto original = item->history()->session().supportMode()
 		? StripSupportHashtag(item->originalText())
 		: item->originalText();
 	return TextWithTags{
 		original.text,
-		ConvertEntitiesToTextTags(original.entities)
+		TextUtilities::ConvertEntitiesToTextTags(original.entities)
 	};
 }
 
@@ -363,7 +239,7 @@ Fn<bool(
 			EditLinkAction action) {
 		if (action == EditLinkAction::Check) {
 			return Ui::InputField::IsValidMarkdownLink(link)
-				&& !IsMentionLink(link);
+				&& !TextUtilities::IsMentionLink(link);
 		}
 		Ui::show(Box<EditLinkBox>(session, text, link, [=](
 				const QString &text,
@@ -617,7 +493,7 @@ void MessageLinksParser::parse() {
 		Expects(tag != tagsEnd);
 
 		if (Ui::InputField::IsValidMarkdownLink(tag->id)
-			&& !IsMentionLink(tag->id)) {
+			&& !TextUtilities::IsMentionLink(tag->id)) {
 			ranges.push_back({ tag->offset, tag->length, tag->id });
 		}
 		++tag;
diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h
index a28632f00..8077c032b 100644
--- a/Telegram/SourceFiles/chat_helpers/message_field.h
+++ b/Telegram/SourceFiles/chat_helpers/message_field.h
@@ -21,16 +21,7 @@ namespace Window {
 class SessionController;
 } // namespace Window
 
-QString ConvertTagToMimeTag(const QString &tagId);
 QString PrepareMentionTag(not_null<UserData*> user);
-
-EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags);
-TextWithTags::Tags ConvertEntitiesToTextTags(
-	const EntitiesInText &entities);
-std::unique_ptr<QMimeData> MimeDataFromText(const TextForMimeData &text);
-void SetClipboardText(
-	const TextForMimeData &text,
-	QClipboard::Mode mode = QClipboard::Clipboard);
 TextWithTags PrepareEditText(not_null<HistoryItem*> item);
 
 Fn<bool(
diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp
index 6b6e5f7ca..fad55a7d9 100644
--- a/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp
+++ b/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp
@@ -323,8 +323,7 @@ void TabbedPanel::startShowAnimation() {
 		_showAnimation = std::make_unique<Ui::PanelAnimation>(st::emojiPanAnimation, Ui::PanelAnimation::Origin::BottomRight);
 		auto inner = rect().marginsRemoved(st::emojiPanMargins);
 		_showAnimation->setFinalImage(std::move(image), QRect(inner.topLeft() * cIntRetinaFactor(), inner.size() * cIntRetinaFactor()));
-		auto corners = App::cornersMask(ImageRoundRadius::Small);
-		_showAnimation->setCornerMasks(corners[0], corners[1], corners[2], corners[3]);
+		_showAnimation->setCornerMasks(Images::CornersMask(ImageRoundRadius::Small));
 		_showAnimation->start();
 	}
 	hideChildren();
diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp
index 9f01b36f9..4a6c50e58 100644
--- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp
+++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp
@@ -813,8 +813,7 @@ void TabbedSelector::switchTab() {
 	_slideAnimation = std::make_unique<SlideAnimation>();
 	auto slidingRect = QRect(0, _scroll->y() * cIntRetinaFactor(), width() * cIntRetinaFactor(), (height() - _scroll->y()) * cIntRetinaFactor());
 	_slideAnimation->setFinalImages(direction, std::move(wasCache), std::move(nowCache), slidingRect, wasSectionIcons);
-	auto corners = App::cornersMask(ImageRoundRadius::Small);
-	_slideAnimation->setCornerMasks(corners[0], corners[1], corners[2], corners[3]);
+	_slideAnimation->setCornerMasks(Images::CornersMask(ImageRoundRadius::Small));
 	_slideAnimation->start();
 
 	hideForSliding();
diff --git a/Telegram/SourceFiles/codegen/style/generator.cpp b/Telegram/SourceFiles/codegen/style/generator.cpp
index d18a64962..385cc1140 100644
--- a/Telegram/SourceFiles/codegen/style/generator.cpp
+++ b/Telegram/SourceFiles/codegen/style/generator.cpp
@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "codegen/style/generator.h"
 
+#include "base/crc32hash.h"
+
 #include <set>
 #include <memory>
 #include <functional>
@@ -33,54 +35,6 @@ const auto kMustBeContrast = std::map<QString, QString>{
 	{ "windowBoldFg", "windowBg" },
 };
 
-// crc32 hash, taken somewhere from the internet
-
-class Crc32Table {
-public:
-	Crc32Table() {
-		quint32 poly = 0x04c11db7;
-		for (auto i = 0; i != 256; ++i) {
-			_data[i] = reflect(i, 8) << 24;
-			for (auto j = 0; j != 8; ++j) {
-				_data[i] = (_data[i] << 1) ^ (_data[i] & (1 << 31) ? poly : 0);
-			}
-			_data[i] = reflect(_data[i], 32);
-		}
-	}
-
-	inline quint32 operator[](int index) const {
-		return _data[index];
-	}
-
-private:
-	quint32 reflect(quint32 val, char ch) {
-		quint32 result = 0;
-		for (int i = 1; i < (ch + 1); ++i) {
-			if (val & 1) {
-				result |= 1 << (ch - i);
-			}
-			val >>= 1;
-		}
-		return result;
-	}
-
-	quint32 _data[256];
-
-};
-
-qint32 hashCrc32(const void *data, int len) {
-	static Crc32Table table;
-
-	const uchar *buffer = static_cast<const uchar *>(data);
-
-	quint32 crc = 0xffffffff;
-	for (int i = 0; i != len; ++i) {
-		crc = (crc >> 8) ^ table[(crc & 0xFF) ^ buffer[i]];
-	}
-
-	return static_cast<qint32>(crc ^ 0xffffffff);
-}
-
 char hexChar(uchar ch) {
 	if (ch < 10) {
 		return '0' + ch;
@@ -844,7 +798,7 @@ void palette::finalize() {\n\
 		return false;
 	}
 	auto count = indexInPalette;
-	auto checksum = hashCrc32(checksumString.constData(), checksumString.size());
+	auto checksum = base::crc32(checksumString.constData(), checksumString.size());
 
 	source_->stream() << "\n\n";
 	for (const auto &[over, under] : kMustBeContrast) {
diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h
index 52d2662e3..d3460cc08 100644
--- a/Telegram/SourceFiles/config.h
+++ b/Telegram/SourceFiles/config.h
@@ -22,11 +22,6 @@ enum {
 	MaxPhoneCodeLength = 4, // max length of country phone code
 	MaxPhoneTailLength = 32, // rest of the phone number, without country code (seen 12 at least), need more for service numbers
 
-	MaxScrollSpeed = 37, // 37px per 15ms while select-by-drag
-	FingerAccuracyThreshold = 3, // touch flick ignore 3px
-	MaxScrollAccelerated = 4000, // 4000px per second
-	MaxScrollFlick = 2500, // 2500px per second
-
 	LocalEncryptIterCount = 4000, // key derivation iteration count
 	LocalEncryptNoPwdIterCount = 4, // key derivation iteration count without pwd (not secure anyway)
 	LocalEncryptSaltSize = 32, // 256 bit
diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp
index 7739c2be0..a2b99e6cd 100644
--- a/Telegram/SourceFiles/core/application.cpp
+++ b/Telegram/SourceFiles/core/application.cpp
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "core/sandbox.h"
 #include "core/local_url_handlers.h"
 #include "core/launcher.h"
+#include "core/core_ui_integration.h"
 #include "chat_helpers/emoji_keywords.h"
 #include "storage/localstorage.h"
 #include "platform/platform_specific.h"
@@ -80,6 +81,7 @@ Application *Application::Instance = nullptr;
 
 struct Application::Private {
 	base::Timer quitTimer;
+	UiIntegration uiIntegration;
 };
 
 Application::Application(not_null<Launcher*> launcher)
@@ -98,6 +100,8 @@ Application::Application(not_null<Launcher*> launcher)
 	Expects(!_logo.isNull());
 	Expects(!_logoNoMargin.isNull());
 
+	Ui::Integration::Set(&_private->uiIntegration);
+
 	activeAccount().sessionChanges(
 	) | rpl::start_with_next([=] {
 		if (_mediaView) {
@@ -778,25 +782,6 @@ void Application::refreshGlobalProxy() {
 	Sandbox::Instance().refreshGlobalProxy();
 }
 
-void Application::activateWindowDelayed(not_null<QWidget*> widget) {
-	Sandbox::Instance().activateWindowDelayed(widget);
-}
-
-void Application::pauseDelayedWindowActivations() {
-	Sandbox::Instance().pauseDelayedWindowActivations();
-}
-
-void Application::resumeDelayedWindowActivations() {
-	Sandbox::Instance().resumeDelayedWindowActivations();
-}
-
-void Application::preventWindowActivation() {
-	pauseDelayedWindowActivations();
-	postponeCall([=] {
-		resumeDelayedWindowActivations();
-	});
-}
-
 void Application::QuitAttempt() {
 	auto prevents = false;
 	if (IsAppLaunched()
@@ -871,19 +856,3 @@ Application &App() {
 }
 
 } // namespace Core
-
-namespace Ui {
-
-void PostponeCall(FnMut<void()> &&callable) {
-	Core::App().postponeCall(std::move(callable));
-}
-
-void RegisterLeaveSubscription(not_null<QWidget*> widget) {
-	Core::App().registerLeaveSubscription(widget);
-}
-
-void UnregisterLeaveSubscription(not_null<QWidget*> widget) {
-	Core::App().unregisterLeaveSubscription(widget);
-}
-
-} // namespace Ui
diff --git a/Telegram/SourceFiles/core/application.h b/Telegram/SourceFiles/core/application.h
index 0435af5aa..4b90819bf 100644
--- a/Telegram/SourceFiles/core/application.h
+++ b/Telegram/SourceFiles/core/application.h
@@ -205,10 +205,6 @@ public:
 	// Sandbox interface.
 	void postponeCall(FnMut<void()> &&callable);
 	void refreshGlobalProxy();
-	void activateWindowDelayed(not_null<QWidget*> widget);
-	void pauseDelayedWindowActivations();
-	void resumeDelayedWindowActivations();
-	void preventWindowActivation();
 
 	void quitPreventFinished();
 
diff --git a/Telegram/SourceFiles/core/click_handler.cpp b/Telegram/SourceFiles/core/click_handler.cpp
deleted file mode 100644
index 023f55a4c..000000000
--- a/Telegram/SourceFiles/core/click_handler.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
-This file is part of Telegram Desktop,
-the official desktop application for the Telegram messaging service.
-
-For license and copyright information please follow this link:
-https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
-*/
-#include "core/click_handler.h"
-
-ClickHandlerHost::~ClickHandlerHost() {
-	ClickHandler::hostDestroyed(this);
-}
-
-NeverFreedPointer<ClickHandlerPtr> ClickHandler::_active;
-NeverFreedPointer<ClickHandlerPtr> ClickHandler::_pressed;
-ClickHandlerHost *ClickHandler::_activeHost = nullptr;
-ClickHandlerHost *ClickHandler::_pressedHost = nullptr;
-
-bool ClickHandler::setActive(const ClickHandlerPtr &p, ClickHandlerHost *host) {
-	if ((_active && (*_active == p)) || (!_active && !p)) {
-		return false;
-	}
-
-	// emit clickHandlerActiveChanged only when there is no
-	// other pressed click handler currently, if there is
-	// this method will be called when it is unpressed
-	if (_active && *_active) {
-		const auto emitClickHandlerActiveChanged = false
-			|| !_pressed
-			|| !*_pressed
-			|| (*_pressed == *_active);
-		const auto wasactive = base::take(*_active);
-		if (_activeHost) {
-			if (emitClickHandlerActiveChanged) {
-				_activeHost->clickHandlerActiveChanged(wasactive, false);
-			}
-			_activeHost = nullptr;
-		}
-	}
-	if (p) {
-		_active.createIfNull();
-		*_active = p;
-		if ((_activeHost = host)) {
-			bool emitClickHandlerActiveChanged = (!_pressed || !*_pressed || *_pressed == *_active);
-			if (emitClickHandlerActiveChanged) {
-				_activeHost->clickHandlerActiveChanged(*_active, true);
-			}
-		}
-	}
-	return true;
-}
-
-auto ClickHandler::getTextEntity() const -> TextEntity {
-	return { EntityType::Invalid };
-}
diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp
index 92e9e8f2d..32828252a 100644
--- a/Telegram/SourceFiles/core/click_handler_types.cpp
+++ b/Telegram/SourceFiles/core/click_handler_types.cpp
@@ -10,17 +10,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "lang/lang_keys.h"
 #include "core/application.h"
 #include "core/local_url_handlers.h"
-#include "core/file_utilities.h"
 #include "mainwidget.h"
 #include "main/main_session.h"
-#include "platform/platform_specific.h"
-#include "history/view/history_view_element.h"
-#include "history/history_item.h"
 #include "boxes/confirm_box.h"
 #include "base/qthelp_regex.h"
-#include "base/qthelp_url.h"
 #include "storage/localstorage.h"
-#include "ui/widgets/tooltip.h"
+#include "history/view/history_view_element.h"
+#include "history/history_item.h"
 #include "data/data_user.h"
 #include "data/data_session.h"
 #include "facades.h"
@@ -38,63 +34,6 @@ bool UrlRequiresConfirmation(const QUrl &url) {
 
 } // namespace
 
-UrlClickHandler::UrlClickHandler(const QString &url, bool fullDisplayed)
-: TextClickHandler(fullDisplayed)
-, _originalUrl(url) {
-	if (isEmail()) {
-		_readable = _originalUrl;
-	} else {
-		const auto original = QUrl(_originalUrl);
-		const auto good = QUrl(original.isValid()
-			? original.toEncoded()
-			: QString());
-		_readable = good.isValid() ? good.toDisplayString() : _originalUrl;
-	}
-}
-
-QString UrlClickHandler::copyToClipboardContextItemText() const {
-	return isEmail()
-		? tr::lng_context_copy_email(tr::now)
-		: tr::lng_context_copy_link(tr::now);
-}
-
-QString UrlClickHandler::url() const {
-	if (isEmail()) {
-		return _originalUrl;
-	}
-
-	QUrl u(_originalUrl), good(u.isValid() ? u.toEncoded() : QString());
-	QString result(good.isValid() ? QString::fromUtf8(good.toEncoded()) : _originalUrl);
-
-	if (!result.isEmpty() && !QRegularExpression(qsl("^[a-zA-Z]+:")).match(result).hasMatch()) { // no protocol
-		return qsl("http://") + result;
-	}
-	return result;
-}
-
-void UrlClickHandler::Open(QString url, QVariant context) {
-	url = Core::TryConvertUrlToLocal(url);
-	if (Core::InternalPassportLink(url)) {
-		return;
-	}
-
-	Ui::Tooltip::Hide();
-	if (isEmail(url)) {
-		File::OpenEmailLink(url);
-	} else if (url.startsWith(qstr("tg://"), Qt::CaseInsensitive)) {
-		Core::App().openLocalUrl(url, context);
-	} else if (!url.isEmpty()) {
-		QDesktopServices::openUrl(url);
-	}
-}
-
-auto UrlClickHandler::getTextEntity() const -> TextEntity {
-	const auto type = isEmail(_originalUrl)
-		? EntityType::Email
-		: EntityType::Url;
-	return { type, _originalUrl };
-}
-
 void HiddenUrlClickHandler::Open(QString url, QVariant context) {
 	url = Core::TryConvertUrlToLocal(url);
 	if (Core::InternalPassportLink(url)) {
diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h
index a909b97d6..33ea64d0c 100644
--- a/Telegram/SourceFiles/core/click_handler_types.h
+++ b/Telegram/SourceFiles/core/click_handler_types.h
@@ -7,74 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
-#include "core/click_handler.h"
-
-class TextClickHandler : public ClickHandler {
-public:
-	TextClickHandler(bool fullDisplayed = true)
-	: _fullDisplayed(fullDisplayed) {
-	}
-
-	QString copyToClipboardText() const override {
-		return url();
-	}
-
-	QString tooltip() const override {
-		return _fullDisplayed ? QString() : readable();
-	}
-
-	void setFullDisplayed(bool full) {
-		_fullDisplayed = full;
-	}
-
-protected:
-	virtual QString url() const = 0;
-	virtual QString readable() const {
-		return url();
-	}
-
-	bool _fullDisplayed;
-
-};
-
-class UrlClickHandler : public TextClickHandler {
-public:
-	UrlClickHandler(const QString &url, bool fullDisplayed = true);
-
-	QString copyToClipboardContextItemText() const override;
-
-	QString dragText() const override {
-		return url();
-	}
-
-	TextEntity getTextEntity() const override;
-
-	static void Open(QString url, QVariant context = {});
-	void onClick(ClickContext context) const override {
-		const auto button = context.button;
-		if (button == Qt::LeftButton || button == Qt::MiddleButton) {
-			Open(url(), context.other);
-		}
-	}
-
-protected:
-	QString url() const override;
-	QString readable() const override {
-		return _readable;
-	}
-
-private:
-	static bool isEmail(const QString &url) {
-		int at = url.indexOf('@'), slash = url.indexOf('/');
-		return ((at > 0) && (slash < 0 || slash > at));
-	}
-	bool isEmail() const {
-		return isEmail(_originalUrl);
-	}
-
-	QString _originalUrl, _readable;
-
-};
+#include "ui/basic_click_handlers.h"
 
 class HiddenUrlClickHandler : public UrlClickHandler {
 public:
@@ -98,6 +31,7 @@ public:
 
 };
 
+class UserData;
 class BotGameUrlClickHandler : public UrlClickHandler {
 public:
 	BotGameUrlClickHandler(UserData *bot, QString url)
@@ -208,7 +142,6 @@ private:
 };
 
 class PeerData;
-class UserData;
 class BotCommandClickHandler : public TextClickHandler {
 public:
 	BotCommandClickHandler(const QString &cmd) : _cmd(cmd) {
diff --git a/Telegram/SourceFiles/core/core_ui_integration.cpp b/Telegram/SourceFiles/core/core_ui_integration.cpp
new file mode 100644
index 000000000..a05808707
--- /dev/null
+++ b/Telegram/SourceFiles/core/core_ui_integration.cpp
@@ -0,0 +1,217 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "core/core_ui_integration.h"
+
+#include "core/local_url_handlers.h"
+#include "core/file_utilities.h"
+#include "core/application.h"
+#include "core/sandbox.h"
+#include "core/click_handler_types.h"
+#include "ui/basic_click_handlers.h"
+#include "ui/emoji_config.h"
+#include "lang/lang_keys.h"
+#include "platform/platform_specific.h"
+#include "main/main_account.h"
+#include "main/main_session.h"
+#include "mainwindow.h"
+
+namespace Core {
+
+void UiIntegration::postponeCall(FnMut<void()> &&callable) {
+	Sandbox::Instance().postponeCall(std::move(callable));
+}
+
+void UiIntegration::registerLeaveSubscription(not_null<QWidget*> widget) {
+	Core::App().registerLeaveSubscription(widget);
+}
+
+void UiIntegration::unregisterLeaveSubscription(not_null<QWidget*> widget) {
+	Core::App().unregisterLeaveSubscription(widget);
+}
+
+void UiIntegration::writeLogEntry(const QString &entry) {
+	Logs::writeMain(entry);
+}
+
+QString UiIntegration::emojiCacheFolder() {
+	return cWorkingDir() + "tdata/emoji";
+}
+
+void UiIntegration::textActionsUpdated() {
+	if (const auto window = App::wnd()) {
+		window->updateGlobalMenu();
+	}
+}
+	
+void UiIntegration::activationFromTopPanel() {
+	Platform::IgnoreApplicationActivationRightNow();
+}
+
+std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
+		EntityType type,
+		const QString &text,
+		const QString &data,
+		const TextParseOptions &options) {
+	switch (type) {
+	case EntityType::CustomUrl:
+		return !data.isEmpty()
+			? std::make_shared<HiddenUrlClickHandler>(data)
+			: nullptr;
+
+	case EntityType::BotCommand:
+		return std::make_shared<BotCommandClickHandler>(data);
+
+	case EntityType::Hashtag:
+		if (options.flags & TextTwitterMentions) {
+			return std::make_shared<UrlClickHandler>(
+				(qsl("https://twitter.com/hashtag/")
+					+ data.mid(1)
+					+ qsl("?src=hash")),
+				true);
+		} else if (options.flags & TextInstagramMentions) {
+			return std::make_shared<UrlClickHandler>(
+				(qsl("https://instagram.com/explore/tags/")
+					+ data.mid(1)
+					+ '/'),
+				true);
+		}
+		return std::make_shared<HashtagClickHandler>(data);
+
+	case EntityType::Cashtag:
+		return std::make_shared<CashtagClickHandler>(data);
+
+	case EntityType::Mention:
+		if (options.flags & TextTwitterMentions) {
+			return std::make_shared<UrlClickHandler>(
+				qsl("https://twitter.com/") + data.mid(1),
+				true);
+		} else if (options.flags & TextInstagramMentions) {
+			return std::make_shared<UrlClickHandler>(
+				qsl("https://instagram.com/") + data.mid(1) + '/',
+				true);
+		}
+		return std::make_shared<MentionClickHandler>(data);
+
+	case EntityType::MentionName: {
+		auto fields = TextUtilities::MentionNameDataToFields(data);
+		if (fields.userId) {
+			return std::make_shared<MentionNameClickHandler>(
+				text,
+				fields.userId,
+				fields.accessHash);
+		} else {
+			LOG(("Bad mention name: %1").arg(data));
+		}
+	} break;
+	}
+	return nullptr;
+}
+
+bool UiIntegration::handleUrlClick(
+		const QString &url,
+		const QVariant &context) {
+	auto local = Core::TryConvertUrlToLocal(url);
+	if (Core::InternalPassportLink(local)) {
+		return true;
+	}
+
+	if (UrlClickHandler::IsEmail(url)) {
+		File::OpenEmailLink(url);
+		return true;
+	} else if (url.startsWith(qstr("tg://"), Qt::CaseInsensitive)) {
+		Core::App().openLocalUrl(url, context);
+		return true;
+	}
+	return false;
+
+}
+
+rpl::producer<> UiIntegration::forcePopupMenuHideRequests() {
+	return rpl::merge(
+		Core::App().passcodeLockChanges(),
+		Core::App().termsLockChanges()
+	) | rpl::map([] { return rpl::empty_value(); });
+}
+
+QString UiIntegration::convertTagToMimeTag(const QString &tagId) {
+	if (TextUtilities::IsMentionLink(tagId)) {
+		const auto &account = Core::App().activeAccount();
+		if (account.sessionExists()) {
+			return tagId + ':' + QString::number(account.session().userId());
+		}
+	}
+	return tagId;
+}
+
+const Ui::Emoji::One *UiIntegration::defaultEmojiVariant(
+		const Ui::Emoji::One *emoji) {
+	if (!emoji || !emoji->hasVariants()) {
+		return emoji;
+	}
+	const auto nonColored = emoji->nonColoredId();
+	const auto it = cEmojiVariants().constFind(nonColored);
+	const auto result = (it != cEmojiVariants().cend())
+		? emoji->variant(it.value())
+		: emoji;
+	AddRecentEmoji(result);
+	return result;
+}
+
+QString UiIntegration::phraseContextCopyText() {
+	return tr::lng_context_copy_text(tr::now);
+}
+
+QString UiIntegration::phraseContextCopyEmail() {
+	return tr::lng_context_copy_email(tr::now);
+}
+
+QString UiIntegration::phraseContextCopyLink() {
+	return tr::lng_context_copy_link(tr::now);
+}
+
+QString UiIntegration::phraseContextCopySelected() {
+	return tr::lng_context_copy_selected(tr::now);
+}
+
+QString UiIntegration::phraseFormattingTitle() {
+	return tr::lng_menu_formatting(tr::now);
+}
+
+QString UiIntegration::phraseFormattingLinkCreate() {
+	return tr::lng_menu_formatting_link_create(tr::now);
+}
+
+QString UiIntegration::phraseFormattingLinkEdit() {
+	return tr::lng_menu_formatting_link_edit(tr::now);
+}
+
+QString UiIntegration::phraseFormattingClear() {
+	return tr::lng_menu_formatting_clear(tr::now);
+}
+
+QString UiIntegration::phraseFormattingBold() {
+	return tr::lng_menu_formatting_bold(tr::now);
+}
+
+QString UiIntegration::phraseFormattingItalic() {
+	return tr::lng_menu_formatting_italic(tr::now);
+}
+
+QString UiIntegration::phraseFormattingUnderline() {
+	return tr::lng_menu_formatting_underline(tr::now);
+}
+
+QString UiIntegration::phraseFormattingStrikeOut() {
+	return tr::lng_menu_formatting_strike_out(tr::now);
+}
+
+QString UiIntegration::phraseFormattingMonospace() {
+	return tr::lng_menu_formatting_monospace(tr::now);
+}
+
+} // namespace Core
diff --git a/Telegram/SourceFiles/core/core_ui_integration.h b/Telegram/SourceFiles/core/core_ui_integration.h
new file mode 100644
index 000000000..e52bbb2ed
--- /dev/null
+++ b/Telegram/SourceFiles/core/core_ui_integration.h
@@ -0,0 +1,55 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "ui/ui_integration.h"
+
+namespace Core {
+
+class UiIntegration : public Ui::Integration {
+public:
+	void postponeCall(FnMut<void()> &&callable) override;
+	void registerLeaveSubscription(not_null<QWidget*> widget) override;
+	void unregisterLeaveSubscription(not_null<QWidget*> widget) override;
+
+	void writeLogEntry(const QString &entry) override;
+	QString emojiCacheFolder() override;
+
+	void textActionsUpdated() override;
+	void activationFromTopPanel() override;
+
+	std::shared_ptr<ClickHandler> createLinkHandler(
+		EntityType type,
+		const QString &text,
+		const QString &data,
+		const TextParseOptions &options) override;
+	bool handleUrlClick(
+		const QString &url,
+		const QVariant &context) override;
+	rpl::producer<> forcePopupMenuHideRequests() override;
+	QString convertTagToMimeTag(const QString &tagId) override;
+	const Ui::Emoji::One *defaultEmojiVariant(
+		const Ui::Emoji::One *emoji) override;
+
+	QString phraseContextCopyText() override;
+	QString phraseContextCopyEmail() override;
+	QString phraseContextCopyLink() override;
+	QString phraseContextCopySelected() override;
+	QString phraseFormattingTitle() override;
+	QString phraseFormattingLinkCreate() override;
+	QString phraseFormattingLinkEdit() override;
+	QString phraseFormattingClear() override;
+	QString phraseFormattingBold() override;
+	QString phraseFormattingItalic() override;
+	QString phraseFormattingUnderline() override;
+	QString phraseFormattingStrikeOut() override;
+	QString phraseFormattingMonospace() override;
+
+};
+
+} // namespace Core
diff --git a/Telegram/SourceFiles/core/file_utilities.cpp b/Telegram/SourceFiles/core/file_utilities.cpp
index dbd46ff07..0434d5599 100644
--- a/Telegram/SourceFiles/core/file_utilities.cpp
+++ b/Telegram/SourceFiles/core/file_utilities.cpp
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "platform/platform_file_utilities.h"
 #include "core/application.h"
 #include "base/unixtime.h"
+#include "ui/delayed_activation.h"
 #include "mainwindow.h"
 
 #include <QtWidgets/QFileDialog>
@@ -26,7 +27,7 @@ bool filedialogGetSaveFile(
 		const QString &initialPath) {
 	QStringList files;
 	QByteArray remoteContent;
-	Core::App().preventWindowActivation();
+	Ui::PreventDelayedActivation();
 	bool result = Platform::FileDialog::Get(
 		parent,
 		files,
@@ -119,7 +120,7 @@ namespace File {
 
 void OpenEmailLink(const QString &email) {
 	crl::on_main([=] {
-		Core::App().preventWindowActivation();
+		Ui::PreventDelayedActivation();
 		Platform::File::UnsafeOpenEmailLink(email);
 	});
 }
@@ -127,7 +128,7 @@ void OpenEmailLink(const QString &email) {
 void OpenWith(const QString &filepath, QPoint menuPosition) {
 	InvokeQueued(QCoreApplication::instance(), [=] {
 		if (!Platform::File::UnsafeShowOpenWithDropdown(filepath, menuPosition)) {
-			Core::App().preventWindowActivation();
+			Ui::PreventDelayedActivation();
 			if (!Platform::File::UnsafeShowOpenWith(filepath)) {
 				Platform::File::UnsafeLaunch(filepath);
 			}
@@ -137,14 +138,14 @@ void OpenWith(const QString &filepath, QPoint menuPosition) {
 
 void Launch(const QString &filepath) {
 	crl::on_main([=] {
-		Core::App().preventWindowActivation();
+		Ui::PreventDelayedActivation();
 		Platform::File::UnsafeLaunch(filepath);
 	});
 }
 
 void ShowInFolder(const QString &filepath) {
 	crl::on_main([=] {
-		Core::App().preventWindowActivation();
+		Ui::PreventDelayedActivation();
 		Platform::File::UnsafeShowInFolder(filepath);
 	});
 }
@@ -231,7 +232,7 @@ void GetOpenPath(
 	InvokeQueued(QCoreApplication::instance(), [=] {
 		auto files = QStringList();
 		auto remoteContent = QByteArray();
-		Core::App().preventWindowActivation();
+		Ui::PreventDelayedActivation();
 		const auto success = Platform::FileDialog::Get(
 			parent,
 			files,
@@ -265,7 +266,7 @@ void GetOpenPaths(
 	InvokeQueued(QCoreApplication::instance(), [=] {
 		auto files = QStringList();
 		auto remoteContent = QByteArray();
-		Core::App().preventWindowActivation();
+		Ui::PreventDelayedActivation();
 		const auto success = Platform::FileDialog::Get(
 			parent,
 			files,
@@ -314,7 +315,7 @@ void GetFolder(
 	InvokeQueued(QCoreApplication::instance(), [=] {
 		auto files = QStringList();
 		auto remoteContent = QByteArray();
-		Core::App().preventWindowActivation();
+		Ui::PreventDelayedActivation();
 		const auto success = Platform::FileDialog::Get(
 			parent,
 			files,
diff --git a/Telegram/SourceFiles/core/launcher.cpp b/Telegram/SourceFiles/core/launcher.cpp
index 702401a7c..a9190fc2b 100644
--- a/Telegram/SourceFiles/core/launcher.cpp
+++ b/Telegram/SourceFiles/core/launcher.cpp
@@ -323,7 +323,7 @@ QStringList Launcher::readArguments(int argc, char *argv[]) const {
 	auto result = QStringList();
 	result.reserve(argc);
 	for (auto i = 0; i != argc; ++i) {
-		result.push_back(fromUtf8Safe(argv[i]));
+		result.push_back(base::FromUtf8Safe(argv[i]));
 	}
 	return result;
 }
diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp
index b56fd8afe..0799098dc 100644
--- a/Telegram/SourceFiles/core/local_url_handlers.cpp
+++ b/Telegram/SourceFiles/core/local_url_handlers.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "core/local_url_handlers.h"
 
+#include "api/api_text_entities.h"
 #include "base/qthelp_regex.h"
 #include "base/qthelp_url.h"
 #include "lang/lang_cloud_manager.h"
@@ -337,8 +338,7 @@ bool HandleUnknown(
 	const auto callback = [=](const MTPDhelp_deepLinkInfo &result) {
 		const auto text = TextWithEntities{
 			qs(result.vmessage()),
-			TextUtilities::EntitiesFromMTP(
-				result.ventities().value_or_empty())
+			Api::EntitiesFromMTP(result.ventities().value_or_empty())
 		};
 		if (result.is_update_app()) {
 			const auto box = std::make_shared<QPointer<BoxContent>>();
diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp
index 5d9a4e303..638717db5 100644
--- a/Telegram/SourceFiles/core/sandbox.cpp
+++ b/Telegram/SourceFiles/core/sandbox.cpp
@@ -542,30 +542,6 @@ bool Sandbox::nativeEventFilter(
 	return false;
 }
 
-void Sandbox::activateWindowDelayed(not_null<QWidget*> widget) {
-	if (_delayedActivationsPaused) {
-		return;
-	} else if (std::exchange(_windowForDelayedActivation, widget.get())) {
-		return;
-	}
-	crl::on_main(this, [=] {
-		if (const auto widget = base::take(_windowForDelayedActivation)) {
-			if (!widget->isHidden()) {
-				widget->activateWindow();
-			}
-		}
-	});
-}
-
-void Sandbox::pauseDelayedWindowActivations() {
-	_windowForDelayedActivation = nullptr;
-	_delayedActivationsPaused = true;
-}
-
-void Sandbox::resumeDelayedWindowActivations() {
-	_delayedActivationsPaused = false;
-}
-
 rpl::producer<> Sandbox::widgetUpdateRequests() const {
 	return _widgetUpdateRequests.events();
 }
diff --git a/Telegram/SourceFiles/core/sandbox.h b/Telegram/SourceFiles/core/sandbox.h
index 3b31f878e..bf42c2267 100644
--- a/Telegram/SourceFiles/core/sandbox.h
+++ b/Telegram/SourceFiles/core/sandbox.h
@@ -48,10 +48,6 @@ public:
 		return callable();
 	}
 
-	void activateWindowDelayed(not_null<QWidget*> widget);
-	void pauseDelayedWindowActivations();
-	void resumeDelayedWindowActivations();
-
 	rpl::producer<> widgetUpdateRequests() const;
 
 	ProxyData sandboxProxy() const;
@@ -110,9 +106,6 @@ private:
 	std::vector<int> _previousLoopNestingLevels;
 	std::vector<PostponedCall> _postponedCalls;
 
-	QPointer<QWidget> _windowForDelayedActivation;
-	bool _delayedActivationsPaused = false;
-
 	not_null<Launcher*> _launcher;
 	std::unique_ptr<Application> _application;
 
diff --git a/Telegram/SourceFiles/core/utils.cpp b/Telegram/SourceFiles/core/utils.cpp
index e411864c5..0daea75a1 100644
--- a/Telegram/SourceFiles/core/utils.cpp
+++ b/Telegram/SourceFiles/core/utils.cpp
@@ -438,50 +438,6 @@ int GetNextRequestId() {
 	return result;
 }
 
-// crc32 hash, taken somewhere from the internet
-
-namespace {
-	uint32 _crc32Table[256];
-	class _Crc32Initializer {
-	public:
-		_Crc32Initializer() {
-			uint32 poly = 0x04c11db7;
-			for (uint32 i = 0; i < 256; ++i) {
-				_crc32Table[i] = reflect(i, 8) << 24;
-				for (uint32 j = 0; j < 8; ++j) {
-					_crc32Table[i] = (_crc32Table[i] << 1) ^ (_crc32Table[i] & (1 << 31) ? poly : 0);
-				}
-				_crc32Table[i] = reflect(_crc32Table[i], 32);
-			}
-		}
-
-	private:
-		uint32 reflect(uint32 val, char ch) {
-			uint32 result = 0;
-			for (int i = 1; i < (ch + 1); ++i) {
-				if (val & 1) {
-					result |= 1 << (ch - i);
-				}
-				val >>= 1;
-			}
-			return result;
-		}
-	};
-}
-
-int32 hashCrc32(const void *data, uint32 len) {
-	static _Crc32Initializer _crc32Initializer;
-
-	const uchar *buf = (const uchar *)data;
-
-	uint32 crc(0xffffffff);
-    for (uint32 i = 0; i < len; ++i) {
-		crc = (crc >> 8) ^ _crc32Table[(crc & 0xFF) ^ buf[i]];
-	}
-
-    return crc ^ 0xffffffff;
-}
-
 int32 *hashSha1(const void *data, uint32 len, void *dest) {
 	return (int32*)SHA1((const uchar*)data, (size_t)len, (uchar*)dest);
 }
diff --git a/Telegram/SourceFiles/core/utils.h b/Telegram/SourceFiles/core/utils.h
index 2484ceb91..c219f036e 100644
--- a/Telegram/SourceFiles/core/utils.h
+++ b/Telegram/SourceFiles/core/utils.h
@@ -127,8 +127,6 @@ private:
 
 };
 
-int32 hashCrc32(const void *data, uint32 len);
-
 int32 *hashSha1(const void *data, uint32 len, void *dest); // dest - ptr to 20 bytes, returns (int32*)dest
 inline std::array<char, 20> hashSha1(const void *data, int size) {
 	auto result = std::array<char, 20>();
@@ -198,19 +196,6 @@ private:
 
 };
 
-inline QString fromUtf8Safe(const char *str, int32 size = -1) {
-	if (!str || !size) return QString();
-	if (size < 0) size = int32(strlen(str));
-	QString result(QString::fromUtf8(str, size));
-	QByteArray back = result.toUtf8();
-	if (back.size() != size || memcmp(back.constData(), str, size)) return QString::fromLocal8Bit(str, size);
-	return result;
-}
-
-inline QString fromUtf8Safe(const QByteArray &str) {
-	return fromUtf8Safe(str.constData(), str.size());
-}
-
 static const QRegularExpression::PatternOptions reMultiline(QRegularExpression::DotMatchesEverythingOption | QRegularExpression::MultilineOption);
 
 template <typename T>
diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp
index f6139b859..3e11daf77 100644
--- a/Telegram/SourceFiles/data/data_drafts.cpp
+++ b/Telegram/SourceFiles/data/data_drafts.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "data/data_drafts.h"
 
+#include "api/api_text_entities.h"
 #include "ui/widgets/input_fields.h"
 #include "chat_helpers/message_field.h"
 #include "history/history.h"
@@ -48,9 +49,8 @@ void applyPeerCloudDraft(PeerId peerId, const MTPDdraftMessage &draft) {
 	const auto history = Auth().data().history(peerId);
 	const auto textWithTags = TextWithTags {
 		qs(draft.vmessage()),
-		ConvertEntitiesToTextTags(
-			TextUtilities::EntitiesFromMTP(
-				draft.ventities().value_or_empty()))
+		TextUtilities::ConvertEntitiesToTextTags(
+			Api::EntitiesFromMTP(draft.ventities().value_or_empty()))
 	};
 	auto replyTo = draft.vreply_to_msg_id().value_or_empty();
 	if (history->skipCloudDraft(textWithTags.text, replyTo, draft.vdate().v)) {
diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp
index 624b0d661..de36eab49 100644
--- a/Telegram/SourceFiles/data/data_peer.cpp
+++ b/Telegram/SourceFiles/data/data_peer.cpp
@@ -13,7 +13,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_photo.h"
 #include "data/data_folder.h"
 #include "data/data_session.h"
+#include "data/data_file_origin.h"
 #include "base/unixtime.h"
+#include "base/crc32hash.h"
 #include "lang/lang_keys.h"
 #include "observer_peer.h"
 #include "apiwrap.h"
@@ -69,7 +71,7 @@ style::color PeerUserpicColor(PeerId peerId) {
 PeerId FakePeerIdForJustName(const QString &name) {
 	return peerFromUser(name.isEmpty()
 		? 777
-		: hashCrc32(name.constData(), name.size() * sizeof(QChar)));
+		: base::crc32(name.constData(), name.size() * sizeof(QChar)));
 }
 
 } // namespace Data
diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.cpp b/Telegram/SourceFiles/data/data_scheduled_messages.cpp
index 429d840e6..288359fee 100644
--- a/Telegram/SourceFiles/data/data_scheduled_messages.cpp
+++ b/Telegram/SourceFiles/data/data_scheduled_messages.cpp
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_peer.h"
 #include "data/data_session.h"
 #include "api/api_hash.h"
+#include "api/api_text_entities.h"
 #include "main/main_session.h"
 #include "history/history.h"
 #include "history/history_item_components.h"
@@ -298,8 +299,7 @@ HistoryItem *ScheduledMessages::append(
 		message.match([&](const MTPDmessage &data) {
 			existing->updateSentContent({
 				qs(data.vmessage()),
-				TextUtilities::EntitiesFromMTP(
-					data.ventities().value_or_empty())
+				Api::EntitiesFromMTP(data.ventities().value_or_empty())
 			}, data.vmedia());
 			existing->updateReplyMarkup(data.vreply_markup());
 			existing->updateForwardedInfo(data.vfwd_from());
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index c4a783efa..17ffef451 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "main/main_session.h"
 #include "apiwrap.h"
 #include "mainwidget.h"
+#include "api/api_text_entities.h"
 #include "core/application.h"
 #include "core/crash_reports.h" // CrashReports::SetAnnotation
 #include "ui/image/image.h"
@@ -1650,7 +1651,7 @@ bool Session::checkEntitiesAndViewsUpdate(const MTPDmessage &data) {
 	if (const auto existing = message(peerToChannel(peer), data.vid().v)) {
 		existing->updateSentContent({
 			qs(data.vmessage()),
-			TextUtilities::EntitiesFromMTP(data.ventities().value_or_empty())
+			Api::EntitiesFromMTP(data.ventities().value_or_empty())
 		}, data.vmedia());
 		existing->updateReplyMarkup(data.vreply_markup());
 		existing->updateForwardedInfo(data.vfwd_from());
@@ -3676,7 +3677,7 @@ void Session::insertCheckedServiceNotification(
 				MTP_string(sending.text),
 				media,
 				MTPReplyMarkup(),
-				TextUtilities::EntitiesToMTP(sending.entities),
+				Api::EntitiesToMTP(sending.entities),
 				MTPint(),
 				MTPint(),
 				MTPstring(),
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
index e2e074e69..020dfed4c 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
@@ -727,7 +727,7 @@ void Widget::onDraggingScrollDelta(int delta) {
 }
 
 void Widget::onDraggingScrollTimer() {
-	auto delta = (_draggingScrollDelta > 0) ? qMin(_draggingScrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_draggingScrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed));
+	auto delta = (_draggingScrollDelta > 0) ? qMin(_draggingScrollDelta * 3 / 20 + 1, int32(Ui::kMaxScrollSpeed)) : qMax(_draggingScrollDelta * 3 / 20 - 1, -int32(Ui::kMaxScrollSpeed));
 	_scroll->scrollToY(_scroll->scrollTop() + delta);
 }
 
diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp
index e12eb114e..a4a8a501b 100644
--- a/Telegram/SourceFiles/facades.cpp
+++ b/Telegram/SourceFiles/facades.cpp
@@ -169,16 +169,6 @@ void showSettings() {
 	}
 }
 
-void activateClickHandler(ClickHandlerPtr handler, ClickContext context) {
-	crl::on_main(App::wnd(), [=] {
-		handler->onClick(context);
-	});
-}
-
-void activateClickHandler(ClickHandlerPtr handler, Qt::MouseButton button) {
-	activateClickHandler(handler, ClickContext{ button });
-}
-
 } // namespace App
 
 namespace Ui {
diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h
index 2817fd8bf..4e40fe6e3 100644
--- a/Telegram/SourceFiles/facades.h
+++ b/Telegram/SourceFiles/facades.h
@@ -74,9 +74,6 @@ void activateBotCommand(
 void searchByHashtag(const QString &tag, PeerData *inPeer);
 void showSettings();
 
-void activateClickHandler(ClickHandlerPtr handler, ClickContext context);
-void activateClickHandler(ClickHandlerPtr handler, Qt::MouseButton button);
-
 } // namespace App
 
 namespace Ui {
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
index 6d3b52db6..36f8d7a71 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/widgets/popup_menu.h"
 #include "ui/image/image.h"
 #include "ui/text/text_utilities.h"
+#include "ui/inactive_press.h"
 #include "core/file_utilities.h"
 #include "lang/lang_keys.h"
 #include "boxes/peers/edit_participant_box.h"
@@ -40,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_photo.h"
 #include "data/data_document.h"
 #include "data/data_media_types.h"
+#include "data/data_file_origin.h"
 #include "data/data_channel.h"
 #include "data/data_user.h"
 #include "facades.h"
@@ -515,6 +517,10 @@ QPoint InnerWidget::tooltipPos() const {
 	return _mousePosition;
 }
 
+bool InnerWidget::tooltipWindowActive() const {
+	return Ui::InFocusChain(window());
+}
+
 HistoryView::Context InnerWidget::elementContext() {
 	return HistoryView::Context::AdminLog;
 }
@@ -941,7 +947,7 @@ void InnerWidget::keyPressEvent(QKeyEvent *e) {
 		copySelectedText();
 #ifdef Q_OS_MAC
 	} else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) {
-		SetClipboardText(getSelectedText(), QClipboard::FindBuffer);
+		TextUtilities::SetClipboardText(getSelectedText(), QClipboard::FindBuffer);
 #endif // Q_OS_MAC
 	} else {
 		e->ignore();
@@ -1143,7 +1149,7 @@ void InnerWidget::copyContextImage(PhotoData *photo) {
 }
 
 void InnerWidget::copySelectedText() {
-	SetClipboardText(getSelectedText());
+	TextUtilities::SetClipboardText(getSelectedText());
 }
 
 void InnerWidget::showStickerPackInfo(not_null<DocumentData*> document) {
@@ -1174,7 +1180,7 @@ void InnerWidget::openContextGif(FullMsgId itemId) {
 
 void InnerWidget::copyContextText(FullMsgId itemId) {
 	if (const auto item = session().data().message(itemId)) {
-		SetClipboardText(HistoryItemText(item));
+		TextUtilities::SetClipboardText(HistoryItemText(item));
 	}
 }
 
@@ -1324,8 +1330,10 @@ void InnerWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton butt
 	_dragStartPosition = mapPointToItem(
 		mapFromGlobal(screenPos),
 		_mouseActionItem);
-	_pressWasInactive = _controller->widget()->wasInactivePress();
-	if (_pressWasInactive) _controller->widget()->setInactivePress(false);
+	_pressWasInactive = Ui::WasInactivePress(_controller->widget());
+	if (_pressWasInactive) {
+		Ui::MarkInactivePress(_controller->widget(), false);
+	}
 
 	if (ClickHandler::getPressed()) {
 		_mouseAction = MouseAction::PrepareDrag;
@@ -1412,7 +1420,7 @@ void InnerWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton but
 
 	if (activated) {
 		mouseActionCancel();
-		App::activateClickHandler(activated, button);
+		ActivateClickHandler(window(), activated, button);
 		return;
 	}
 	if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && button != Qt::RightButton) {
@@ -1432,7 +1440,7 @@ void InnerWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton but
 
 #if defined Q_OS_LINUX32 || defined Q_OS_LINUX64
 	if (_selectedItem && _selectedText.from != _selectedText.to) {
-		SetClipboardText(
+		TextUtilities::SetClipboardText(
 			_selectedItem->selectedText(_selectedText),
 			QClipboard::Selection);
 	}
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
index beb9b1672..f20c58382 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
@@ -81,6 +81,7 @@ public:
 	// Ui::AbstractTooltipShower interface.
 	QString tooltipText() const override;
 	QPoint tooltipPos() const override;
+	bool tooltipWindowActive() const override;
 
 	// HistoryView::ElementDelegate interface.
 	HistoryView::Context elementContext() override;
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
index 958603bd8..41615d44a 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history_service.h"
 #include "history/history_message.h"
 #include "history/history.h"
+#include "api/api_text_entities.h"
 #include "data/data_channel.h"
 #include "data/data_user.h"
 #include "data/data_session.h"
@@ -116,7 +117,7 @@ TextWithEntities ExtractEditedText(const MTPMessage &message) {
 	const auto &data = message.c_message();
 	return {
 		TextUtilities::Clean(qs(data.vmessage())),
-		TextUtilities::EntitiesFromMTP(data.ventities().value_or_empty())
+		Api::EntitiesFromMTP(data.ventities().value_or_empty())
 	};
 }
 
diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style
index 583f7725c..ec5b9c2ea 100644
--- a/Telegram/SourceFiles/history/history.style
+++ b/Telegram/SourceFiles/history/history.style
@@ -371,7 +371,7 @@ botKbScroll: defaultSolidScroll;
 historyDateFadeDuration: 200;
 
 historyPhotoLeft: 14px;
-historyMessageRadius: 6px;
+historyMessageRadius: roundRadiusLarge;
 historyBubbleTailInLeft: icon {{ "bubble_tail", msgInBg }};
 historyBubbleTailInLeftSelected: icon {{ "bubble_tail", msgInBgSelected }};
 historyBubbleTailOutLeft: icon {{ "bubble_tail", msgOutBg }};
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index 202e3eb71..abf6b33a7 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/toast/toast.h"
 #include "ui/text_options.h"
 #include "ui/ui_utility.h"
+#include "ui/inactive_press.h"
 #include "window/window_session_controller.h"
 #include "window/window_peer_menu.h"
 #include "boxes/confirm_box.h"
@@ -50,6 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_poll.h"
 #include "data/data_photo.h"
 #include "data/data_user.h"
+#include "data/data_file_origin.h"
 #include "facades.h"
 #include "app.h"
 
@@ -826,23 +828,23 @@ void HistoryInner::touchUpdateSpeed() {
 
 			// fingers are inacurates, we ignore small changes to avoid stopping the autoscroll because
 			// of a small horizontal offset when scrolling vertically
-			const int newSpeedY = (qAbs(pixelsPerSecond.y()) > FingerAccuracyThreshold) ? pixelsPerSecond.y() : 0;
-			const int newSpeedX = (qAbs(pixelsPerSecond.x()) > FingerAccuracyThreshold) ? pixelsPerSecond.x() : 0;
+			const int newSpeedY = (qAbs(pixelsPerSecond.y()) > Ui::kFingerAccuracyThreshold) ? pixelsPerSecond.y() : 0;
+			const int newSpeedX = (qAbs(pixelsPerSecond.x()) > Ui::kFingerAccuracyThreshold) ? pixelsPerSecond.x() : 0;
 			if (_touchScrollState == Ui::TouchScrollState::Auto) {
 				const int oldSpeedY = _touchSpeed.y();
 				const int oldSpeedX = _touchSpeed.x();
 				if ((oldSpeedY <= 0 && newSpeedY <= 0) || ((oldSpeedY >= 0 && newSpeedY >= 0)
 					&& (oldSpeedX <= 0 && newSpeedX <= 0)) || (oldSpeedX >= 0 && newSpeedX >= 0)) {
-					_touchSpeed.setY(snap((oldSpeedY + (newSpeedY / 4)), -MaxScrollAccelerated, +MaxScrollAccelerated));
-					_touchSpeed.setX(snap((oldSpeedX + (newSpeedX / 4)), -MaxScrollAccelerated, +MaxScrollAccelerated));
+					_touchSpeed.setY(snap((oldSpeedY + (newSpeedY / 4)), -Ui::kMaxScrollAccelerated, +Ui::kMaxScrollAccelerated));
+					_touchSpeed.setX(snap((oldSpeedX + (newSpeedX / 4)), -Ui::kMaxScrollAccelerated, +Ui::kMaxScrollAccelerated));
 				} else {
 					_touchSpeed = QPoint();
 				}
 			} else {
 				// we average the speed to avoid strange effects with the last delta
 				if (!_touchSpeed.isNull()) {
-					_touchSpeed.setX(snap((_touchSpeed.x() / 4) + (newSpeedX * 3 / 4), -MaxScrollFlick, +MaxScrollFlick));
-					_touchSpeed.setY(snap((_touchSpeed.y() / 4) + (newSpeedY * 3 / 4), -MaxScrollFlick, +MaxScrollFlick));
+					_touchSpeed.setX(snap((_touchSpeed.x() / 4) + (newSpeedX * 3 / 4), -Ui::kMaxScrollFlick, +Ui::kMaxScrollFlick));
+					_touchSpeed.setY(snap((_touchSpeed.y() / 4) + (newSpeedY * 3 / 4), -Ui::kMaxScrollFlick, +Ui::kMaxScrollFlick));
 				} else {
 					_touchSpeed = QPoint(newSpeedX, newSpeedY);
 				}
@@ -1033,8 +1035,10 @@ void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton but
 		? mouseActionView->data().get()
 		: nullptr;
 	_dragStartPosition = mapPointToItem(mapFromGlobal(screenPos), mouseActionView);
-	_pressWasInactive = _controller->widget()->wasInactivePress();
-	if (_pressWasInactive) _controller->widget()->setInactivePress(false);
+	_pressWasInactive = Ui::WasInactivePress(_controller->widget());
+	if (_pressWasInactive) {
+		Ui::MarkInactivePress(_controller->widget(), false);
+	}
 
 	if (ClickHandler::getPressed()) {
 		_mouseAction = MouseAction::PrepareDrag;
@@ -1187,7 +1191,7 @@ std::unique_ptr<QMimeData> HistoryInner::prepareDrag() {
 		}
 		return TextForMimeData();
 	}();
-	if (auto mimeData = MimeDataFromText(selectedText)) {
+	if (auto mimeData = TextUtilities::MimeDataFromText(selectedText)) {
 		updateDragSelection(nullptr, nullptr, false);
 		_widget->noSelectingScroll();
 
@@ -1343,7 +1347,7 @@ void HistoryInner::mouseActionFinish(
 		const auto pressedItemId = pressedItemView
 			? pressedItemView->data()->fullId()
 			: FullMsgId();
-		App::activateClickHandler(activated, {
+		ActivateClickHandler(window(), activated, {
 			button,
 			QVariant::fromValue(pressedItemId)
 		});
@@ -1401,7 +1405,7 @@ void HistoryInner::mouseActionFinish(
 	if (!_selected.empty() && _selected.cbegin()->second != FullSelection) {
 		const auto [item, selection] = *_selected.cbegin();
 		if (const auto view = item->mainView()) {
-			SetClipboardText(
+			TextUtilities::SetClipboardText(
 				view->selectedText(selection),
 				QClipboard::Selection);
 		}
@@ -1818,7 +1822,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
 }
 
 void HistoryInner::copySelectedText() {
-	SetClipboardText(getSelectedText());
+	TextUtilities::SetClipboardText(getSelectedText());
 }
 
 void HistoryInner::savePhotoToFile(not_null<PhotoData*> photo) {
@@ -1893,9 +1897,9 @@ void HistoryInner::saveContextGif(FullMsgId itemId) {
 void HistoryInner::copyContextText(FullMsgId itemId) {
 	if (const auto item = session().data().message(itemId)) {
 		if (const auto group = session().data().groups().find(item)) {
-			SetClipboardText(HistoryGroupText(group));
+			TextUtilities::SetClipboardText(HistoryGroupText(group));
 		} else {
-			SetClipboardText(HistoryItemText(item));
+			TextUtilities::SetClipboardText(HistoryItemText(item));
 		}
 	}
 }
@@ -1986,7 +1990,7 @@ void HistoryInner::keyPressEvent(QKeyEvent *e) {
 #ifdef Q_OS_MAC
 	} else if (e->key() == Qt::Key_E
 		&& e->modifiers().testFlag(Qt::ControlModifier)) {
-		SetClipboardText(getSelectedText(), QClipboard::FindBuffer);
+		TextUtilities::SetClipboardText(getSelectedText(), QClipboard::FindBuffer);
 #endif // Q_OS_MAC
 	} else if (e == QKeySequence::Delete) {
 		auto selectedState = getSelectionState();
@@ -3194,6 +3198,10 @@ QPoint HistoryInner::tooltipPos() const {
 	return _mousePosition;
 }
 
+bool HistoryInner::tooltipWindowActive() const {
+	return Ui::InFocusChain(window());
+}
+
 void HistoryInner::onParentGeometryChanged() {
 	auto mousePos = QCursor::pos();
 	auto mouseOver = _widget->rect().contains(_widget->mapFromGlobal(mousePos));
diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h
index 41a863c42..b26d5e7de 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.h
+++ b/Telegram/SourceFiles/history/history_inner_widget.h
@@ -110,6 +110,7 @@ public:
 	// Ui::AbstractTooltipShower interface.
 	QString tooltipText() const override;
 	QPoint tooltipPos() const override;
+	bool tooltipWindowActive() const override;
 
 	// HistoryView::ElementDelegate interface.
 	static not_null<HistoryView::ElementDelegate*> ElementDelegate();
diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp
index 071d7b8d4..e4f3633da 100644
--- a/Telegram/SourceFiles/history/history_message.cpp
+++ b/Telegram/SourceFiles/history/history_message.cpp
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "mainwidget.h"
 #include "mainwindow.h"
 #include "apiwrap.h"
+#include "api/api_text_entities.h"
 #include "history/history.h"
 #include "history/history_item_components.h"
 #include "history/history_location_manager.h"
@@ -442,7 +443,7 @@ HistoryMessage::HistoryMessage(
 	}
 	setText({
 		TextUtilities::Clean(qs(data.vmessage())),
-		TextUtilities::EntitiesFromMTP(data.ventities().value_or_empty())
+		Api::EntitiesFromMTP(data.ventities().value_or_empty())
 	});
 	if (const auto groupedId = data.vgrouped_id()) {
 		setGroupId(
@@ -1051,7 +1052,7 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) {
 
 	const auto textWithEntities = TextWithEntities{
 		qs(message.vmessage()),
-		TextUtilities::EntitiesFromMTP(message.ventities().value_or_empty())
+		Api::EntitiesFromMTP(message.ventities().value_or_empty())
 	};
 	setReplyMarkup(message.vreply_markup());
 	if (!isLocalUpdateMedia()) {
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index 254af33ea..b02b41f62 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history_widget.h"
 
 #include "api/api_sending.h"
+#include "api/api_text_entities.h"
 #include "boxes/confirm_box.h"
 #include "boxes/send_files_box.h"
 #include "boxes/share_box.h"
@@ -77,6 +78,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/widgets/popup_menu.h"
 #include "ui/text_options.h"
 #include "ui/unread_badge.h"
+#include "ui/delayed_activation.h"
 #include "main/main_session.h"
 #include "window/themes/window_theme.h"
 #include "window/notifications_manager.h"
@@ -133,7 +135,7 @@ ApiWrap::RequestMessageDataCallback replyEditMessageDataCallback() {
 void ActivateWindow(not_null<Window::SessionController*> controller) {
 	const auto window = controller->widget();
 	window->activateWindow();
-	Core::App().activateWindowDelayed(window);
+	Ui::ActivateWindowDelayed(window);
 }
 
 bool ShowHistoryEndInsteadOfUnread(
@@ -2833,7 +2835,9 @@ void HistoryWidget::saveEditMsg() {
 		_history,
 		session().user()).flags;
 	auto sending = TextWithEntities();
-	auto left = TextWithEntities { textWithTags.text, ConvertTextTagsToEntities(textWithTags.tags) };
+	auto left = TextWithEntities {
+		textWithTags.text,
+		TextUtilities::ConvertTextTagsToEntities(textWithTags.tags) };
 	TextUtilities::PrepareForSending(left, prepareFlags);
 
 	if (!TextUtilities::CutPart(sending, left, MaxMessageSize)) {
@@ -2854,8 +2858,10 @@ void HistoryWidget::saveEditMsg() {
 	if (webPageId == CancelledWebPageId) {
 		sendFlags |= MTPmessages_EditMessage::Flag::f_no_webpage;
 	}
-	auto localEntities = TextUtilities::EntitiesToMTP(sending.entities);
-	auto sentEntities = TextUtilities::EntitiesToMTP(sending.entities, TextUtilities::ConvertOption::SkipLocal);
+	auto localEntities = Api::EntitiesToMTP(sending.entities);
+	auto sentEntities = Api::EntitiesToMTP(
+		sending.entities,
+		Api::ConvertOption::SkipLocal);
 	if (!sentEntities.v.isEmpty()) {
 		sendFlags |= MTPmessages_EditMessage::Flag::f_entities;
 	}
@@ -4497,14 +4503,14 @@ void HistoryWidget::sendFileConfirmed(
 
 	auto caption = TextWithEntities{
 		file->caption.text,
-		ConvertTextTagsToEntities(file->caption.tags)
+		TextUtilities::ConvertTextTagsToEntities(file->caption.tags)
 	};
 	const auto prepareFlags = Ui::ItemTextOptions(
 		history,
 		session().user()).flags;
 	TextUtilities::PrepareForSending(caption, prepareFlags);
 	TextUtilities::Trim(caption);
-	auto localEntities = TextUtilities::EntitiesToMTP(caption.entities);
+	auto localEntities = Api::EntitiesToMTP(caption.entities);
 
 	if (itemToEdit) {
 		if (const auto id = itemToEdit->groupId()) {
@@ -6870,7 +6876,7 @@ QPoint HistoryWidget::clampMousePosition(QPoint point) {
 }
 
 void HistoryWidget::onScrollTimer() {
-	auto d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed));
+	auto d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(Ui::kMaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(Ui::kMaxScrollSpeed));
 	_scroll->scrollToY(_scroll->scrollTop() + d);
 }
 
diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
index 8d857d8bb..76726733f 100644
--- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
@@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_session.h"
 #include "data/data_groups.h"
 #include "data/data_channel.h"
+#include "data/data_file_origin.h"
 #include "core/file_utilities.h"
 #include "platform/platform_info.h"
 #include "window/window_peer_menu.h"
@@ -576,7 +577,7 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
 			? tr::lng_context_copy_selected(tr::now)
 			: tr::lng_context_copy_selected_items(tr::now);
 		result->addAction(text, [=] {
-			SetClipboardText(list->getSelectedText());
+			TextUtilities::SetClipboardText(list->getSelectedText());
 		});
 	}
 
@@ -609,11 +610,11 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
 				if (const auto item = owner->message(itemId)) {
 					if (asGroup) {
 						if (const auto group = owner->groups().find(item)) {
-							SetClipboardText(HistoryGroupText(group));
+							TextUtilities::SetClipboardText(HistoryGroupText(group));
 							return;
 						}
 					}
-					SetClipboardText(HistoryItemText(item));
+					TextUtilities::SetClipboardText(HistoryItemText(item));
 				}
 			});
 		}
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
index be5746de3..38745d248 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
@@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "main/main_session.h"
 #include "ui/widgets/popup_menu.h"
 #include "ui/toast/toast.h"
+#include "ui/inactive_press.h"
 #include "lang/lang_keys.h"
 #include "boxes/peers/edit_participant_box.h"
 #include "data/data_session.h"
@@ -1090,6 +1091,10 @@ QPoint ListWidget::tooltipPos() const {
 	return _mousePosition;
 }
 
+bool ListWidget::tooltipWindowActive() const {
+	return Ui::InFocusChain(window());
+}
+
 Context ListWidget::elementContext() {
 	return _delegate->listContext();
 }
@@ -1545,11 +1550,11 @@ void ListWidget::keyPressEvent(QKeyEvent *e) {
 		}
 	} else if (e == QKeySequence::Copy
 		&& (hasSelectedText() || hasSelectedItems())) {
-		SetClipboardText(getSelectedText());
+		TextUtilities::SetClipboardText(getSelectedText());
 #ifdef Q_OS_MAC
 	} else if (e->key() == Qt::Key_E
 		&& e->modifiers().testFlag(Qt::ControlModifier)) {
-		SetClipboardText(getSelectedText(), QClipboard::FindBuffer);
+		TextUtilities::SetClipboardText(getSelectedText(), QClipboard::FindBuffer);
 #endif // Q_OS_MAC
 	} else if (e == QKeySequence::Delete) {
 		_delegate->listDeleteRequest();
@@ -1893,8 +1898,10 @@ void ListWidget::mouseActionStart(
 	const auto pressElement = _overElement;
 
 	_mouseAction = MouseAction::None;
-	_pressWasInactive = _controller->widget()->wasInactivePress();
-	if (_pressWasInactive) _controller->widget()->setInactivePress(false);
+	_pressWasInactive = Ui::WasInactivePress(_controller->widget());
+	if (_pressWasInactive) {
+		Ui::MarkInactivePress(_controller->widget(), false);
+	}
 
 	if (ClickHandler::getPressed()) {
 		_mouseAction = MouseAction::PrepareDrag;
@@ -2016,7 +2023,7 @@ void ListWidget::mouseActionFinish(
 		activated = nullptr;
 	} else if (activated) {
 		mouseActionCancel();
-		App::activateClickHandler(activated, {
+		ActivateClickHandler(window(), activated, {
 			button,
 			QVariant::fromValue(pressState.itemId)
 		});
@@ -2058,7 +2065,7 @@ void ListWidget::mouseActionFinish(
 	if (_selectedTextItem
 		&& _selectedTextRange.from != _selectedTextRange.to) {
 		if (const auto view = viewForItem(_selectedTextItem)) {
-			SetClipboardText(
+			TextUtilities::SetClipboardText(
 				view->selectedText(_selectedTextRange),
 				QClipboard::Selection);
 }
@@ -2303,7 +2310,7 @@ std::unique_ptr<QMimeData> ListWidget::prepareDrag() {
 		}
 		return TextForMimeData();
 	}();
-	if (auto mimeData = MimeDataFromText(selectedText)) {
+	if (auto mimeData = TextUtilities::MimeDataFromText(selectedText)) {
 		clearDragSelection();
 //		_widget->noSelectingScroll(); #TODO scroll
 
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h
index d1687808d..d801a24f9 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.h
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h
@@ -178,6 +178,7 @@ public:
 	// AbstractTooltipShower interface
 	QString tooltipText() const override;
 	QPoint tooltipPos() const override;
+	bool tooltipWindowActive() const override;
 
 	// ElementDelegate interface.
 	Context elementContext() override;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
index f9913e836..f772eebfe 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/image/image.h"
 #include "data/data_session.h"
 #include "data/data_document.h"
+#include "data/data_file_origin.h"
 #include "app.h"
 #include "styles/style_history.h"
 
diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp
index e6ce77168..195b13915 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "window/window_session_controller.h" // isGifPausedAtLeastFor.
 #include "data/data_session.h"
 #include "data/data_document.h"
+#include "data/data_file_origin.h"
 #include "lottie/lottie_single_player.h"
 #include "styles/style_history.h"
 
diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp
index 8a42ab263..fdbd4f5fe 100644
--- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp
+++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "storage/file_download.h"
 #include "ui/widgets/popup_menu.h"
 #include "ui/ui_utility.h"
+#include "ui/inactive_press.h"
 #include "lang/lang_keys.h"
 #include "main/main_session.h"
 #include "mainwidget.h"
@@ -1823,8 +1824,13 @@ void ListWidget::mouseActionStart(
 	auto pressLayout = _overLayout;
 
 	_mouseAction = MouseAction::None;
-	_pressWasInactive = _controller->parentController()->widget()->wasInactivePress();
-	if (_pressWasInactive) _controller->parentController()->widget()->setInactivePress(false);
+	_pressWasInactive = Ui::WasInactivePress(
+		_controller->parentController()->widget());
+	if (_pressWasInactive) {
+		Ui::MarkInactivePress(
+			_controller->parentController()->widget(),
+			false);
+	}
 
 	if (ClickHandler::getPressed() && !hasSelected()) {
 		_mouseAction = MouseAction::PrepareDrag;
@@ -2018,7 +2024,7 @@ void ListWidget::mouseActionFinish(
 	_wasSelectedText = false;
 	if (activated) {
 		mouseActionCancel();
-		App::activateClickHandler(activated, button);
+		ActivateClickHandler(window(), activated, button);
 		return;
 	}
 
@@ -2045,7 +2051,7 @@ void ListWidget::mouseActionFinish(
 
 #if defined Q_OS_LINUX32 || defined Q_OS_LINUX64
 	//if (hasSelectedText()) { // #TODO linux clipboard
-	//	SetClipboardText(_selected.cbegin()->first->selectedText(_selected.cbegin()->second), QClipboard::Selection);
+	//	TextUtilities::SetClipboardText(_selected.cbegin()->first->selectedText(_selected.cbegin()->second), QClipboard::Selection);
 	//}
 #endif // Q_OS_LINUX32 || Q_OS_LINUX64
 }
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp
index c1b49724b..495ee9ed1 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "inline_bots/inline_bot_result.h"
 
+#include "api/api_text_entities.h"
 #include "data/data_photo.h"
 #include "data/data_document.h"
 #include "data/data_session.h"
@@ -136,7 +137,7 @@ std::unique_ptr<Result> Result::create(uint64 queryId, const MTPBotInlineResult
 	case mtpc_botInlineMessageMediaAuto: {
 		const auto &r = message->c_botInlineMessageMediaAuto();
 		const auto message = qs(r.vmessage());
-		const auto entities = TextUtilities::EntitiesFromMTP(
+		const auto entities = Api::EntitiesFromMTP(
 			r.ventities().value_or_empty());
 		if (result->_type == Type::Photo) {
 			if (!result->_photo) {
@@ -168,7 +169,7 @@ std::unique_ptr<Result> Result::create(uint64 queryId, const MTPBotInlineResult
 		const auto &r = message->c_botInlineMessageText();
 		result->sendData = std::make_unique<internal::SendText>(
 			qs(r.vmessage()),
-			TextUtilities::EntitiesFromMTP(r.ventities().value_or_empty()),
+			Api::EntitiesFromMTP(r.ventities().value_or_empty()),
 			r.is_no_webpage());
 		if (result->_type == Type::Photo) {
 			if (!result->_photo) {
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp
index 591c3a07f..62ef1c29c 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "inline_bots/inline_bot_send_data.h"
 
+#include "api/api_text_entities.h"
 #include "data/data_document.h"
 #include "inline_bots/inline_bot_result.h"
 #include "storage/localstorage.h"
@@ -78,7 +79,7 @@ QString SendDataCommon::getErrorOnSend(
 SendDataCommon::SentMTPMessageFields SendText::getSentMessageFields() const {
 	SentMTPMessageFields result;
 	result.text = MTP_string(_message);
-	result.entities = TextUtilities::EntitiesToMTP(_entities);
+	result.entities = Api::EntitiesToMTP(_entities);
 	return result;
 }
 
diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp
index 4c6d5c540..0c0e0d143 100644
--- a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp
+++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp
@@ -146,6 +146,10 @@ QPoint Inner::tooltipPos() const {
 	return _lastMousePos;
 }
 
+bool Inner::tooltipWindowActive() const {
+	return Ui::InFocusChain(window());
+}
+
 Inner::~Inner() = default;
 
 void Inner::paintEvent(QPaintEvent *e) {
@@ -239,7 +243,7 @@ void Inner::mouseReleaseEvent(QMouseEvent *e) {
 		int row = _selected / MatrixRowShift, column = _selected % MatrixRowShift;
 		selectInlineResult(row, column);
 	} else {
-		App::activateClickHandler(activated, e->button());
+		ActivateClickHandler(window(), activated, e->button());
 	}
 }
 
@@ -926,8 +930,7 @@ void Widget::startShowAnimation() {
 		_showAnimation = std::make_unique<Ui::PanelAnimation>(st::emojiPanAnimation, Ui::PanelAnimation::Origin::BottomLeft);
 		auto inner = rect().marginsRemoved(st::emojiPanMargins);
 		_showAnimation->setFinalImage(std::move(image), QRect(inner.topLeft() * cIntRetinaFactor(), inner.size() * cIntRetinaFactor()));
-		auto corners = App::cornersMask(ImageRoundRadius::Small);
-		_showAnimation->setCornerMasks(corners[0], corners[1], corners[2], corners[3]);
+		_showAnimation->setCornerMasks(Images::CornersMask(ImageRoundRadius::Small));
 		_showAnimation->start();
 	}
 	hideChildren();
diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.h b/Telegram/SourceFiles/inline_bots/inline_results_widget.h
index cddaeb12b..2c2281e89 100644
--- a/Telegram/SourceFiles/inline_bots/inline_results_widget.h
+++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.h
@@ -86,6 +86,7 @@ public:
 	// Ui::AbstractTooltipShower interface.
 	QString tooltipText() const override;
 	QPoint tooltipPos() const override;
+	bool tooltipWindowActive() const override;
 
 	~Inner();
 
diff --git a/Telegram/SourceFiles/intro/introphone.cpp b/Telegram/SourceFiles/intro/introphone.cpp
index 3962b5b58..d9a3e2d5f 100644
--- a/Telegram/SourceFiles/intro/introphone.cpp
+++ b/Telegram/SourceFiles/intro/introphone.cpp
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/labels.h"
 #include "ui/wrap/fade_wrap.h"
+#include "ui/special_fields.h"
 #include "main/main_account.h"
 #include "boxes/confirm_phone_box.h"
 #include "boxes/confirm_box.h"
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 2ec78ca6c..8853f7b29 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_chat.h"
 #include "data/data_user.h"
 #include "data/data_scheduled_messages.h"
+#include "api/api_text_entities.h"
 #include "ui/special_buttons.h"
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/shadow.h"
@@ -3818,7 +3819,7 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) {
 				}
 				item->updateSentContent({
 					sent.text,
-					TextUtilities::EntitiesFromMTP(list.value_or_empty())
+					Api::EntitiesFromMTP(list.value_or_empty())
 				}, d.vmedia());
 				item->contributeToSlowmode(d.vdate().v);
 				if (!wasAlready) {
@@ -4321,7 +4322,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
 		const auto &d = update.c_updateServiceNotification();
 		const auto text = TextWithEntities {
 			qs(d.vmessage()),
-			TextUtilities::EntitiesFromMTP(d.ventities().v)
+			Api::EntitiesFromMTP(d.ventities().v)
 		};
 		if (IsForceLogoutNotification(d)) {
 			Core::App().forceLogOut(text);
diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp
index 190637730..40b80f9fd 100644
--- a/Telegram/SourceFiles/mainwindow.cpp
+++ b/Telegram/SourceFiles/mainwindow.cpp
@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/widgets/popup_menu.h"
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/shadow.h"
+#include "ui/widgets/tooltip.h"
 #include "ui/emoji_config.h"
 #include "ui/ui_utility.h"
 #include "lang/lang_cloud_manager.h"
@@ -154,6 +155,11 @@ void MainWindow::firstShow() {
 
 	psFirstShow();
 	updateTrayMenu();
+
+	windowDeactivateEvents(
+	) | rpl::start_with_next([=] {
+		Ui::Tooltip::Hide();
+	}, lifetime());
 }
 
 void MainWindow::clearWidgetsHook() {
diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
index b94dd770a..99d773f64 100644
--- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
+++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/widgets/buttons.h"
 #include "ui/image/image.h"
 #include "ui/text/text_utilities.h"
+#include "ui/platform/ui_platform_utility.h"
 #include "ui/toast/toast.h"
 #include "ui/text_options.h"
 #include "ui/ui_utility.h"
@@ -1115,9 +1116,9 @@ void OverlayWidget::onSaveAs() {
 				filter = mimeType.filterString() + qsl(";;") + FileDialog::AllFilesFilter();
 			}
 
-			psBringToBack(this);
+			Ui::Platform::BringToBack(this);
 			file = FileNameForSave(tr::lng_save_file(tr::now), filter, qsl("doc"), name, true, alreadyDir);
-			psShowOverAll(this);
+			Ui::Platform::ShowOverAll(this);
 			if (!file.isEmpty() && file != location.name()) {
 				if (_doc->data().isEmpty()) {
 					QFile(file).remove();
@@ -1141,7 +1142,7 @@ void OverlayWidget::onSaveAs() {
 	} else {
 		if (!_photo || !_photo->loaded()) return;
 
-		psBringToBack(this);
+		Ui::Platform::BringToBack(this);
 		auto filter = qsl("JPEG Image (*.jpg);;") + FileDialog::AllFilesFilter();
 		FileDialog::GetWritePath(
 			this,
@@ -1153,13 +1154,13 @@ void OverlayWidget::onSaveAs() {
 				QString(),
 				false,
 				_photo->date),
-			crl::guard(this, [this, photo = _photo](const QString &result) {
+			crl::guard(this, [=, photo = _photo](const QString &result) {
 				if (!result.isEmpty() && _photo == photo && photo->loaded()) {
 					photo->large()->original().save(result, "JPG");
 				}
-				psShowOverAll(this);
-			}), crl::guard(this, [this] {
-				psShowOverAll(this);
+				Ui::Platform::ShowOverAll(this);
+			}), crl::guard(this, [=] {
+				Ui::Platform::ShowOverAll(this);
 			}));
 	}
 	activateWindow();
@@ -1977,13 +1978,13 @@ void OverlayWidget::updateThemePreviewGeometry() {
 void OverlayWidget::displayFinished() {
 	updateControls();
 	if (isHidden()) {
-		psUpdateOverlayed(this);
+		Ui::Platform::UpdateOverlayed(this);
 #ifdef Q_OS_LINUX
 		showFullScreen();
 #else // Q_OS_LINUX
 		show();
 #endif // Q_OS_LINUX
-		psShowOverAll(this);
+		Ui::Platform::ShowOverAll(this);
 		activateWindow();
 		QApplication::setActiveWindow(this);
 		setFocus();
@@ -3497,7 +3498,7 @@ void OverlayWidget::mouseReleaseEvent(QMouseEvent *e) {
 			showSaveMsgFile();
 			return;
 		}
-		App::activateClickHandler(activated, e->button());
+		ActivateClickHandler(this, activated, e->button());
 		return;
 	}
 
diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp
index 863a3b42f..d02e76c84 100644
--- a/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp
+++ b/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/wrap/slide_wrap.h"
 #include "ui/wrap/fade_wrap.h"
 #include "ui/text/text_utilities.h" // Ui::Text::ToUpper
+#include "ui/special_fields.h"
 #include "boxes/abstract_box.h"
 #include "boxes/confirm_phone_box.h"
 #include "data/data_user.h"
diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp
index 8e854fd0b..01afcd3fa 100644
--- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp
+++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp
@@ -23,7 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include <QtCore/QStandardPaths>
 #include <QtCore/QProcess>
 #include <QtCore/QVersionNumber>
-#include <qpa/qplatformnativeinterface.h>
 
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -92,10 +91,6 @@ void FallbackFontConfig() {
 
 namespace Platform {
 
-bool IsApplicationActive() {
-	return QApplication::activeWindow() != nullptr;
-}
-
 void SetApplicationIcon(const QIcon &icon) {
 	QApplication::setWindowIcon(icon);
 }
@@ -135,12 +130,6 @@ QRect psDesktopRect() {
 	return _monitorRect;
 }
 
-void psShowOverAll(QWidget *w, bool canFocus) {
-}
-
-void psBringToBack(QWidget *w) {
-}
-
 void psWriteDump() {
 }
 
@@ -243,29 +232,6 @@ void finish() {
 	Notifications::Finish();
 }
 
-bool TranslucentWindowsSupported(QPoint globalPosition) {
-	if (const auto native = QGuiApplication::platformNativeInterface()) {
-		if (const auto desktop = QApplication::desktop()) {
-			const auto index = desktop->screenNumber(globalPosition);
-			const auto screens = QGuiApplication::screens();
-			if (const auto screen = (index >= 0 && index < screens.size()) ? screens[index] : QGuiApplication::primaryScreen()) {
-				if (native->nativeResourceForScreen(QByteArray("compositingEnabled"), screen)) {
-					return true;
-				}
-
-				static auto WarnedAbout = base::flat_set<int>();
-				if (!WarnedAbout.contains(index)) {
-					WarnedAbout.insert(index);
-					LOG(("WARNING: Compositing is disabled for screen index %1 (for position %2,%3)").arg(index).arg(globalPosition.x()).arg(globalPosition.y()));
-				}
-			} else {
-				LOG(("WARNING: Could not get screen for index %1 (for position %2,%3)").arg(index).arg(globalPosition.x()).arg(globalPosition.y()));
-			}
-		}
-	}
-	return false;
-}
-
 void RegisterCustomScheme() {
 #ifndef TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME
 	auto home = getHomeDir();
@@ -432,9 +398,6 @@ void psAutoStart(bool start, bool silent) {
 void psSendToMenu(bool send, bool silent) {
 }
 
-void psUpdateOverlayed(QWidget *widget) {
-}
-
 bool linuxMoveFile(const char *from, const char *to) {
 	FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb");
 	if (!ffrom) {
diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.h b/Telegram/SourceFiles/platform/linux/specific_linux.h
index 9af2a64c0..2fef15047 100644
--- a/Telegram/SourceFiles/platform/linux/specific_linux.h
+++ b/Telegram/SourceFiles/platform/linux/specific_linux.h
@@ -20,26 +20,15 @@ namespace Platform {
 inline void SetWatchingMediaKeys(bool watching) {
 }
 
-bool IsApplicationActive();
-
-inline void StartTranslucentPaint(QPainter &p, QPaintEvent *e) {
-}
-
-inline void InitOnTopPanel(QWidget *panel) {
-}
-
-inline void DeInitOnTopPanel(QWidget *panel) {
-}
-
-inline void ReInitOnTopPanel(QWidget *panel) {
-}
-
 QString CurrentExecutablePath(int argc, char *argv[]);
 
 inline std::optional<crl::time> LastUserInputTime() {
 	return std::nullopt;
 }
 
+inline void IgnoreApplicationActivationRightNow() {
+}
+
 inline constexpr bool UseMainQueueGeneric() {
 	return true;
 }
@@ -70,15 +59,12 @@ void psAutoStart(bool start, bool silent = false);
 void psSendToMenu(bool send, bool silent = false);
 
 QRect psDesktopRect();
-void psShowOverAll(QWidget *w, bool canFocus = true);
-void psBringToBack(QWidget *w);
 
 int psCleanup();
 int psFixPrevious();
 
 void psNewVersion();
 
-void psUpdateOverlayed(QWidget *widget);
 inline QByteArray psDownloadPathBookmark(const QString &path) {
 	return QByteArray();
 }
diff --git a/Telegram/SourceFiles/platform/mac/mac_touchbar.mm b/Telegram/SourceFiles/platform/mac/mac_touchbar.mm
index 0569a15bf..23c818846 100644
--- a/Telegram/SourceFiles/platform/mac/mac_touchbar.mm
+++ b/Telegram/SourceFiles/platform/mac/mac_touchbar.mm
@@ -22,7 +22,7 @@
 #include "data/data_peer_values.h"
 #include "data/data_session.h"
 #include "dialogs/dialogs_layout.h"
-#include "emoji_config.h"
+#include "ui/emoji_config.h"
 #include "history/history.h"
 #include "lang/lang_keys.h"
 #include "mainwidget.h"
diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.h b/Telegram/SourceFiles/platform/mac/specific_mac.h
index a5bdf5209..50a8ccd1d 100644
--- a/Telegram/SourceFiles/platform/mac/specific_mac.h
+++ b/Telegram/SourceFiles/platform/mac/specific_mac.h
@@ -16,10 +16,6 @@ class LocationPoint;
 
 namespace Platform {
 
-inline bool TranslucentWindowsSupported(QPoint globalPosition) {
-	return true;
-}
-
 QString CurrentExecutablePath(int argc, char *argv[]);
 
 void RemoveQuarantine(const QString &path);
@@ -67,8 +63,6 @@ void psAutoStart(bool start, bool silent = false);
 void psSendToMenu(bool send, bool silent = false);
 
 QRect psDesktopRect();
-void psShowOverAll(QWidget *w, bool canFocus = true);
-void psBringToBack(QWidget *w);
 
 int psCleanup();
 int psFixPrevious();
@@ -77,8 +71,6 @@ bool psShowOpenWithMenu(int x, int y, const QString &file);
 
 void psNewVersion();
 
-void psUpdateOverlayed(QWidget *widget);
-
 void psDownloadPathEnableAccess();
 QByteArray psDownloadPathBookmark(const QString &path);
 QByteArray psPathBookmark(const QString &path);
diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.mm b/Telegram/SourceFiles/platform/mac/specific_mac.mm
index a3a9f5fbe..1b5018b4e 100644
--- a/Telegram/SourceFiles/platform/mac/specific_mac.mm
+++ b/Telegram/SourceFiles/platform/mac/specific_mac.mm
@@ -60,14 +60,6 @@ QRect psDesktopRect() {
 	return _monitorRect;
 }
 
-void psShowOverAll(QWidget *w, bool canFocus) {
-	objc_showOverAll(w->winId(), canFocus);
-}
-
-void psBringToBack(QWidget *w) {
-	objc_bringToBack(w->winId());
-}
-
 void psWriteDump() {
 #ifndef TDESKTOP_DISABLE_CRASH_REPORTS
 	double v = objc_appkitVersion();
@@ -128,14 +120,6 @@ void finish() {
 	objc_finish();
 }
 
-void StartTranslucentPaint(QPainter &p, QPaintEvent *e) {
-#ifdef OS_MAC_OLD
-	p.setCompositionMode(QPainter::CompositionMode_Source);
-	p.fillRect(e->rect(), Qt::transparent);
-	p.setCompositionMode(QPainter::CompositionMode_SourceOver);
-#endif // OS_MAC_OLD
-}
-
 QString CurrentExecutablePath(int argc, char *argv[]) {
 	return NS2QString([[NSBundle mainBundle] bundlePath]);
 }
@@ -274,6 +258,10 @@ std::optional<crl::time> LastUserInputTime() {
 	return (crl::now() - static_cast<crl::time>(idleTime));
 }
 
+void IgnoreApplicationActivationRightNow() {
+	objc_ignoreApplicationActivationRightNow();
+}
+	
 } // namespace Platform
 
 void psNewVersion() {
@@ -286,9 +274,6 @@ void psAutoStart(bool start, bool silent) {
 void psSendToMenu(bool send, bool silent) {
 }
 
-void psUpdateOverlayed(QWidget *widget) {
-}
-
 void psDownloadPathEnableAccess() {
 	objc_downloadPathEnableAccess(Global::DownloadPathBookmark());
 }
diff --git a/Telegram/SourceFiles/platform/mac/specific_mac_p.h b/Telegram/SourceFiles/platform/mac/specific_mac_p.h
index a149447b7..d02b5b33f 100644
--- a/Telegram/SourceFiles/platform/mac/specific_mac_p.h
+++ b/Telegram/SourceFiles/platform/mac/specific_mac_p.h
@@ -11,8 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 bool objc_handleMediaKeyEvent(void *e);
 
 bool objc_darkMode();
-void objc_showOverAll(WId winId, bool canFocus = true);
-void objc_bringToBack(WId winId);
 
 void objc_debugShowAlert(const QString &str);
 void objc_outputDebugString(const QString &str);
diff --git a/Telegram/SourceFiles/platform/mac/specific_mac_p.mm b/Telegram/SourceFiles/platform/mac/specific_mac_p.mm
index 9c13c498b..cbf4f5529 100644
--- a/Telegram/SourceFiles/platform/mac/specific_mac_p.mm
+++ b/Telegram/SourceFiles/platform/mac/specific_mac_p.mm
@@ -35,8 +35,6 @@ namespace {
 
 constexpr auto kIgnoreActivationTimeoutMs = 500;
 
-std::optional<bool> ApplicationIsActive;
-
 } // namespace
 
 NSImage *qt_mac_create_nsimage(const QPixmap &pm);
@@ -141,7 +139,6 @@ ApplicationDelegate *_sharedDelegate = nil;
 }
 
 - (void) applicationDidBecomeActive:(NSNotification *)aNotification {
-	ApplicationIsActive = true;
 	if (Core::IsAppLaunched() && !_ignoreActivation) {
 		Core::App().handleAppActivated();
 		if (auto window = App::wnd()) {
@@ -153,7 +150,6 @@ ApplicationDelegate *_sharedDelegate = nil;
 }
 
 - (void) applicationDidResignActive:(NSNotification *)aNotification {
-	ApplicationIsActive = false;
 }
 
 - (void) receiveWakeNote:(NSNotification*)aNotification {
@@ -207,12 +203,6 @@ void SetWatchingMediaKeys(bool watching) {
 	}
 }
 
-bool IsApplicationActive() {
-	return ApplicationIsActive
-		? *ApplicationIsActive
-		: (static_cast<QApplication*>(QCoreApplication::instance())->activeWindow() != nullptr);
-}
-
 void SetApplicationIcon(const QIcon &icon) {
 	NSImage *image = nil;
 	if (!icon.isNull()) {
@@ -224,46 +214,6 @@ void SetApplicationIcon(const QIcon &icon) {
 	[image release];
 }
 
-void InitOnTopPanel(QWidget *panel) {
-	Expects(!panel->windowHandle());
-
-	// Force creating windowHandle() without creating the platform window yet.
-	panel->setAttribute(Qt::WA_NativeWindow, true);
-	panel->windowHandle()->setProperty("_td_macNonactivatingPanelMask", QVariant(true));
-	panel->setAttribute(Qt::WA_NativeWindow, false);
-
-	panel->createWinId();
-
-	auto platformWindow = [reinterpret_cast<NSView*>(panel->winId()) window];
-	Assert([platformWindow isKindOfClass:[NSPanel class]]);
-
-	auto platformPanel = static_cast<NSPanel*>(platformWindow);
-	[platformPanel setLevel:NSPopUpMenuWindowLevel];
-	[platformPanel setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorStationary|NSWindowCollectionBehaviorFullScreenAuxiliary|NSWindowCollectionBehaviorIgnoresCycle];
-	[platformPanel setFloatingPanel:YES];
-	[platformPanel setHidesOnDeactivate:NO];
-
-	objc_ignoreApplicationActivationRightNow();
-}
-
-void DeInitOnTopPanel(QWidget *panel) {
-	auto platformWindow = [reinterpret_cast<NSView*>(panel->winId()) window];
-	Assert([platformWindow isKindOfClass:[NSPanel class]]);
-
-	auto platformPanel = static_cast<NSPanel*>(platformWindow);
-	auto newBehavior = ([platformPanel collectionBehavior] & (~NSWindowCollectionBehaviorCanJoinAllSpaces)) | NSWindowCollectionBehaviorMoveToActiveSpace;
-	[platformPanel setCollectionBehavior:newBehavior];
-}
-
-void ReInitOnTopPanel(QWidget *panel) {
-	auto platformWindow = [reinterpret_cast<NSView*>(panel->winId()) window];
-	Assert([platformWindow isKindOfClass:[NSPanel class]]);
-
-	auto platformPanel = static_cast<NSPanel*>(platformWindow);
-	auto newBehavior = ([platformPanel collectionBehavior] & (~NSWindowCollectionBehaviorMoveToActiveSpace)) | NSWindowCollectionBehaviorCanJoinAllSpaces;
-	[platformPanel setCollectionBehavior:newBehavior];
-}
-
 } // namespace Platform
 
 bool objc_darkMode() {
@@ -279,20 +229,6 @@ bool objc_darkMode() {
 	return result;
 }
 
-void objc_showOverAll(WId winId, bool canFocus) {
-	NSWindow *wnd = [reinterpret_cast<NSView *>(winId) window];
-	[wnd setLevel:NSPopUpMenuWindowLevel];
-	if (!canFocus) {
-		[wnd setStyleMask:NSUtilityWindowMask | NSNonactivatingPanelMask];
-		[wnd setCollectionBehavior:NSWindowCollectionBehaviorMoveToActiveSpace|NSWindowCollectionBehaviorStationary|NSWindowCollectionBehaviorFullScreenAuxiliary|NSWindowCollectionBehaviorIgnoresCycle];
-	}
-}
-
-void objc_bringToBack(WId winId) {
-	NSWindow *wnd = [reinterpret_cast<NSView *>(winId) window];
-	[wnd setLevel:NSModalPanelWindowLevel];
-}
-
 bool objc_handleMediaKeyEvent(void *ev) {
 	auto e = reinterpret_cast<NSEvent*>(ev);
 
diff --git a/Telegram/SourceFiles/platform/platform_specific.h b/Telegram/SourceFiles/platform/platform_specific.h
index 330ba48d3..c6162c95e 100644
--- a/Telegram/SourceFiles/platform/platform_specific.h
+++ b/Telegram/SourceFiles/platform/platform_specific.h
@@ -27,13 +27,7 @@ enum class SystemSettingsType {
 };
 
 void SetWatchingMediaKeys(bool watching);
-bool IsApplicationActive();
 void SetApplicationIcon(const QIcon &icon);
-bool TranslucentWindowsSupported(QPoint globalPosition);
-void StartTranslucentPaint(QPainter &p, QPaintEvent *e);
-void InitOnTopPanel(QWidget *panel);
-void DeInitOnTopPanel(QWidget *panel);
-void ReInitOnTopPanel(QWidget *panel);
 void RegisterCustomScheme();
 PermissionStatus GetPermissionStatus(PermissionType type);
 void RequestPermission(PermissionType type, Fn<void(PermissionStatus)> resultCallback);
@@ -45,6 +39,8 @@ bool OpenSystemSettings(SystemSettingsType type);
 	return LastUserInputTime().has_value();
 }
 
+void IgnoreApplicationActivationRightNow();
+	
 [[nodiscard]] constexpr bool UseMainQueueGeneric();
 void DrainMainQueue(); // Needed only if UseMainQueueGeneric() is false.
 
diff --git a/Telegram/SourceFiles/platform/win/main_window_win.cpp b/Telegram/SourceFiles/platform/win/main_window_win.cpp
index 0e8215efd..721eb3dd3 100644
--- a/Telegram/SourceFiles/platform/win/main_window_win.cpp
+++ b/Telegram/SourceFiles/platform/win/main_window_win.cpp
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "platform/win/windows_event_filter.h"
 #include "window/notifications_manager.h"
 #include "mainwindow.h"
+#include "base/crc32hash.h"
 #include "core/application.h"
 #include "lang/lang_keys.h"
 #include "storage/localstorage.h"
@@ -661,7 +662,7 @@ int32 MainWindow::screenNameChecksum(const QString &name) const {
 	} else {
 		memcpy(buffer, name.toStdWString().data(), sizeof(buffer));
 	}
-	return hashCrc32(buffer, sizeof(buffer));
+	return base::crc32(buffer, sizeof(buffer));
 }
 
 void MainWindow::psRefreshTaskbarIcon() {
diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp
index 83b31d693..973196c65 100644
--- a/Telegram/SourceFiles/platform/win/specific_win.cpp
+++ b/Telegram/SourceFiles/platform/win/specific_win.cpp
@@ -201,12 +201,6 @@ QRect psDesktopRect() {
 	return _monitorRect;
 }
 
-void psShowOverAll(QWidget *w, bool canFocus) {
-}
-
-void psBringToBack(QWidget *w) {
-}
-
 int psCleanup() {
 	__try
 	{
@@ -308,10 +302,6 @@ void start() {
 void finish() {
 }
 
-bool IsApplicationActive() {
-	return QApplication::activeWindow() != nullptr;
-}
-
 void SetApplicationIcon(const QIcon &icon) {
 	QApplication::setWindowIcon(icon);
 }
@@ -572,17 +562,6 @@ void psSendToMenu(bool send, bool silent) {
 	_manageAppLnk(send, silent, CSIDL_SENDTO, L"-sendpath", L"Telegram send to link.\nYou can disable send to menu item in Telegram settings.");
 }
 
-void psUpdateOverlayed(QWidget *widget) {
-	bool wm = widget->testAttribute(Qt::WA_Mapped), wv = widget->testAttribute(Qt::WA_WState_Visible);
-	if (!wm) widget->setAttribute(Qt::WA_Mapped, true);
-	if (!wv) widget->setAttribute(Qt::WA_WState_Visible, true);
-	widget->update();
-	QEvent e(QEvent::UpdateRequest);
-	QGuiApplication::sendEvent(widget, &e);
-	if (!wm) widget->setAttribute(Qt::WA_Mapped, false);
-	if (!wv) widget->setAttribute(Qt::WA_WState_Visible, false);
-}
-
 void psWriteDump() {
 #ifndef TDESKTOP_DISABLE_CRASH_REPORTS
 	PROCESS_MEMORY_COUNTERS data = { 0 };
diff --git a/Telegram/SourceFiles/platform/win/specific_win.h b/Telegram/SourceFiles/platform/win/specific_win.h
index cf2ab893f..c850e3639 100644
--- a/Telegram/SourceFiles/platform/win/specific_win.h
+++ b/Telegram/SourceFiles/platform/win/specific_win.h
@@ -19,26 +19,11 @@ namespace Platform {
 inline void SetWatchingMediaKeys(bool watching) {
 }
 
-bool IsApplicationActive();
-
-inline bool TranslucentWindowsSupported(QPoint globalPosition) {
-	return true;
-}
-
-inline void StartTranslucentPaint(QPainter &p, QPaintEvent *e) {
-}
-
-inline void InitOnTopPanel(QWidget *panel) {
-}
-
-inline void DeInitOnTopPanel(QWidget *panel) {
-}
-
-inline void ReInitOnTopPanel(QWidget *panel) {
-}
-
 QString CurrentExecutablePath(int argc, char *argv[]);
 
+inline void IgnoreApplicationActivationRightNow() {
+}
+
 inline constexpr bool UseMainQueueGeneric() {
 	return true;
 }
@@ -74,15 +59,12 @@ void psAutoStart(bool start, bool silent = false);
 void psSendToMenu(bool send, bool silent = false);
 
 QRect psDesktopRect();
-void psShowOverAll(QWidget *w, bool canFocus = true);
-void psBringToBack(QWidget *w);
 
 int psCleanup();
 int psFixPrevious();
 
 void psNewVersion();
 
-void psUpdateOverlayed(QWidget *widget);
 inline QByteArray psDownloadPathBookmark(const QString &path) {
 	return QByteArray();
 }
diff --git a/Telegram/SourceFiles/platform/win/windows_event_filter.cpp b/Telegram/SourceFiles/platform/win/windows_event_filter.cpp
index 395674a2f..b9302165c 100644
--- a/Telegram/SourceFiles/platform/win/windows_event_filter.cpp
+++ b/Telegram/SourceFiles/platform/win/windows_event_filter.cpp
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "platform/win/windows_dlls.h"
 #include "core/sandbox.h"
+#include "ui/inactive_press.h"
 #include "mainwindow.h"
 #include "main/main_session.h"
 #include "facades.h"
@@ -113,7 +114,7 @@ bool EventFilter::mainWindowEvent(
 
 	case WM_ACTIVATE: {
 		if (LOWORD(wParam) == WA_CLICKACTIVE) {
-			_window->setInactivePress(true);
+			Ui::MarkInactivePress(_window, true);
 		}
 		if (LOWORD(wParam) != WA_INACTIVE) {
 			_window->shadowsActivate();
diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp
index ab64ae5c1..67754d1fd 100644
--- a/Telegram/SourceFiles/storage/localimageloader.cpp
+++ b/Telegram/SourceFiles/storage/localimageloader.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "storage/localimageloader.h"
 
+#include "api/api_text_entities.h"
 #include "data/data_document.h"
 #include "data/data_session.h"
 #include "core/file_utilities.h"
@@ -126,9 +127,9 @@ MTPInputSingleMedia PrepareAlbumItemMedia(
 		uint64 randomId) {
 	auto caption = item->originalText();
 	TextUtilities::Trim(caption);
-	auto sentEntities = TextUtilities::EntitiesToMTP(
+	auto sentEntities = Api::EntitiesToMTP(
 		caption.entities,
-		TextUtilities::ConvertOption::SkipLocal);
+		Api::ConvertOption::SkipLocal);
 	const auto flags = !sentEntities.v.isEmpty()
 		? MTPDinputSingleMedia::Flag::f_entities
 		: MTPDinputSingleMedia::Flag(0);
diff --git a/Telegram/SourceFiles/support/support_helper.cpp b/Telegram/SourceFiles/support/support_helper.cpp
index e2ec406f5..4b2afed2d 100644
--- a/Telegram/SourceFiles/support/support_helper.cpp
+++ b/Telegram/SourceFiles/support/support_helper.cpp
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_drafts.h"
 #include "data/data_user.h"
 #include "data/data_session.h"
+#include "api/api_text_entities.h"
 #include "history/history.h"
 #include "boxes/abstract_box.h"
 #include "ui/toast/toast.h"
@@ -450,7 +451,7 @@ void Helper::applyInfo(
 		info.date = data.vdate().v;
 		info.text = TextWithEntities{
 			qs(data.vmessage()),
-			TextUtilities::EntitiesFromMTP(data.ventities().v) };
+			Api::EntitiesFromMTP(data.ventities().v) };
 		if (info.text.empty()) {
 			remove();
 		} else if (_userInformation[user] != info) {
@@ -505,13 +506,13 @@ void Helper::showEditInfoBox(not_null<UserData*> user) {
 	const auto info = infoCurrent(user);
 	const auto editData = TextWithTags{
 		info.text.text,
-		ConvertEntitiesToTextTags(info.text.entities)
+		TextUtilities::ConvertEntitiesToTextTags(info.text.entities)
 	};
 
 	const auto save = [=](TextWithTags result, Fn<void(bool)> done) {
 		saveInfo(user, TextWithEntities{
 			result.text,
-			ConvertTextTagsToEntities(result.tags)
+			TextUtilities::ConvertTextTagsToEntities(result.tags)
 		}, done);
 	};
 	Ui::show(
@@ -540,9 +541,9 @@ void Helper::saveInfo(
 		Ui::ItemTextDefaultOptions().flags);
 	TextUtilities::Trim(text);
 
-	const auto entities = TextUtilities::EntitiesToMTP(
+	const auto entities = Api::EntitiesToMTP(
 		text.entities,
-		TextUtilities::ConvertOption::SkipLocal);
+		Api::ConvertOption::SkipLocal);
 	_userInfoSaving[user].requestId = request(MTPhelp_EditUserInfo(
 		user->inputUser,
 		MTP_string(text.text),
diff --git a/Telegram/SourceFiles/ui/abstract_button.cpp b/Telegram/SourceFiles/ui/abstract_button.cpp
index a2505c103..799972b8f 100644
--- a/Telegram/SourceFiles/ui/abstract_button.cpp
+++ b/Telegram/SourceFiles/ui/abstract_button.cpp
@@ -111,12 +111,12 @@ void AbstractButton::setOver(bool over, StateChangeSource source) {
 	if (over && !(_state & StateFlag::Over)) {
 		auto was = _state;
 		_state |= StateFlag::Over;
-		RegisterLeaveSubscription(this);
+		Integration::Instance().registerLeaveSubscription(this);
 		onStateChanged(was, source);
 	} else if (!over && (_state & StateFlag::Over)) {
 		auto was = _state;
 		_state &= ~State(StateFlag::Over);
-		UnregisterLeaveSubscription(this);
+		Integration::Instance().unregisterLeaveSubscription(this);
 		onStateChanged(was, source);
 	}
 	updateCursor();
diff --git a/Telegram/SourceFiles/ui/basic_click_handlers.cpp b/Telegram/SourceFiles/ui/basic_click_handlers.cpp
new file mode 100644
index 000000000..b679ca58a
--- /dev/null
+++ b/Telegram/SourceFiles/ui/basic_click_handlers.cpp
@@ -0,0 +1,68 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "ui/basic_click_handlers.h"
+
+#include "ui/widgets/tooltip.h"
+#include "ui/text/text_entity.h"
+#include "ui/ui_integration.h"
+#include "base/qthelp_url.h"
+
+#include <QtCore/QUrl>
+#include <QtCore/QRegularExpression>
+#include <QtGui/QDesktopServices>
+#include <QtGui/QGuiApplication>
+
+UrlClickHandler::UrlClickHandler(const QString &url, bool fullDisplayed)
+: TextClickHandler(fullDisplayed)
+, _originalUrl(url) {
+	if (isEmail()) {
+		_readable = _originalUrl;
+	} else {
+		const auto original = QUrl(_originalUrl);
+		const auto good = QUrl(original.isValid()
+			? original.toEncoded()
+			: QString());
+		_readable = good.isValid() ? good.toDisplayString() : _originalUrl;
+	}
+}
+
+QString UrlClickHandler::copyToClipboardContextItemText() const {
+	return isEmail()
+		? Ui::Integration::Instance().phraseContextCopyEmail()
+		: Ui::Integration::Instance().phraseContextCopyLink();
+}
+
+QString UrlClickHandler::url() const {
+	if (isEmail()) {
+		return _originalUrl;
+	}
+
+	QUrl u(_originalUrl), good(u.isValid() ? u.toEncoded() : QString());
+	QString result(good.isValid() ? QString::fromUtf8(good.toEncoded()) : _originalUrl);
+
+	if (!result.isEmpty()
+		&& !QRegularExpression(
+			QStringLiteral("^[a-zA-Z]+:")).match(result).hasMatch()) {
+		// No protocol.
+		return QStringLiteral("http://") + result;
+	}
+	return result;
+}
+
+void UrlClickHandler::Open(QString url, QVariant context) {
+	Ui::Tooltip::Hide();
+	if (!Ui::Integration::Instance().handleUrlClick(url, context)
+		&& !url.isEmpty()) {
+		QDesktopServices::openUrl(url);
+	}
+}
+
+auto UrlClickHandler::getTextEntity() const -> TextEntity {
+	const auto type = isEmail() ? EntityType::Email : EntityType::Url;
+	return { type, _originalUrl };
+}
diff --git a/Telegram/SourceFiles/ui/basic_click_handlers.h b/Telegram/SourceFiles/ui/basic_click_handlers.h
new file mode 100644
index 000000000..a25a7ea78
--- /dev/null
+++ b/Telegram/SourceFiles/ui/basic_click_handlers.h
@@ -0,0 +1,79 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "base/algorithm.h"
+#include "ui/click_handler.h"
+
+class TextClickHandler : public ClickHandler {
+public:
+	TextClickHandler(bool fullDisplayed = true)
+	: _fullDisplayed(fullDisplayed) {
+	}
+
+	QString copyToClipboardText() const override {
+		return url();
+	}
+
+	QString tooltip() const override {
+		return _fullDisplayed ? QString() : readable();
+	}
+
+	void setFullDisplayed(bool full) {
+		_fullDisplayed = full;
+	}
+
+protected:
+	virtual QString url() const = 0;
+	virtual QString readable() const {
+		return url();
+	}
+
+	bool _fullDisplayed;
+
+};
+
+class UrlClickHandler : public TextClickHandler {
+public:
+	UrlClickHandler(const QString &url, bool fullDisplayed = true);
+
+	QString copyToClipboardContextItemText() const override;
+
+	QString dragText() const override {
+		return url();
+	}
+
+	TextEntity getTextEntity() const override;
+
+	static void Open(QString url, QVariant context = {});
+	void onClick(ClickContext context) const override {
+		const auto button = context.button;
+		if (button == Qt::LeftButton || button == Qt::MiddleButton) {
+			Open(url(), context.other);
+		}
+	}
+
+	[[nodiscard]] static bool IsEmail(const QString &url) {
+		const auto at = url.indexOf('@'), slash = url.indexOf('/');
+		return ((at > 0) && (slash < 0 || slash > at));
+	}
+
+protected:
+	QString url() const override;
+	QString readable() const override {
+		return _readable;
+	}
+
+private:
+	[[nodiscard]] bool isEmail() const {
+		return IsEmail(_originalUrl);
+	}
+
+	QString _originalUrl, _readable;
+
+};
diff --git a/Telegram/SourceFiles/ui/click_handler.cpp b/Telegram/SourceFiles/ui/click_handler.cpp
new file mode 100644
index 000000000..f201ae06b
--- /dev/null
+++ b/Telegram/SourceFiles/ui/click_handler.cpp
@@ -0,0 +1,172 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "ui/click_handler.h"
+
+#include "base/algorithm.h"
+#include "ui/text/text_entity.h"
+
+#include <QtCore/QPointer>
+
+namespace {
+
+ClickHandlerPtr &ClickHandlerActive() {
+	static auto result = ClickHandlerPtr();
+	return result;
+}
+
+ClickHandlerPtr &ClickHandlerPressed() {
+	static auto result = ClickHandlerPtr();
+	return result;
+}
+
+} // namespace
+
+ClickHandlerHost *ClickHandler::_activeHost = nullptr;
+ClickHandlerHost *ClickHandler::_pressedHost = nullptr;
+
+ClickHandlerHost::~ClickHandlerHost() {
+	ClickHandler::hostDestroyed(this);
+}
+
+bool ClickHandler::setActive(
+		const ClickHandlerPtr &p,
+		ClickHandlerHost *host) {
+	auto &active = ClickHandlerActive();
+	auto &pressed = ClickHandlerPressed();
+
+	if (active == p) {
+		return false;
+	}
+
+	// emit clickHandlerActiveChanged only when there is no
+	// other pressed click handler currently, if there is
+	// this method will be called when it is unpressed
+	if (active) {
+		const auto emitClickHandlerActiveChanged = false
+			|| !pressed
+			|| (pressed == active);
+		const auto wasactive = base::take(active);
+		if (_activeHost) {
+			if (emitClickHandlerActiveChanged) {
+				_activeHost->clickHandlerActiveChanged(wasactive, false);
+			}
+			_activeHost = nullptr;
+		}
+	}
+	if (p) {
+		active = p;
+		if ((_activeHost = host)) {
+			bool emitClickHandlerActiveChanged = (!pressed || pressed == active);
+			if (emitClickHandlerActiveChanged) {
+				_activeHost->clickHandlerActiveChanged(active, true);
+			}
+		}
+	}
+	return true;
+}
+
+bool ClickHandler::clearActive(ClickHandlerHost *host) {
+	if (host && _activeHost != host) {
+		return false;
+	}
+	return setActive(ClickHandlerPtr(), host);
+}
+
+void ClickHandler::pressed() {
+	auto &active = ClickHandlerActive();
+	auto &pressed = ClickHandlerPressed();
+
+	unpressed();
+	if (!active) {
+		return;
+	}
+	pressed = active;
+	if ((_pressedHost = _activeHost)) {
+		_pressedHost->clickHandlerPressedChanged(pressed, true);
+	}
+}
+
+ClickHandlerPtr ClickHandler::unpressed() {
+	auto &active = ClickHandlerActive();
+	auto &pressed = ClickHandlerPressed();
+
+	if (pressed) {
+		const auto activated = (active == pressed);
+		const auto waspressed = base::take(pressed);
+		if (_pressedHost) {
+			_pressedHost->clickHandlerPressedChanged(waspressed, false);
+			_pressedHost = nullptr;
+		}
+
+		if (activated) {
+			return active;
+		} else if (active && _activeHost) {
+			// emit clickHandlerActiveChanged for current active
+			// click handler, which we didn't emit while we has
+			// a pressed click handler
+			_activeHost->clickHandlerActiveChanged(active, true);
+		}
+	}
+	return ClickHandlerPtr();
+}
+
+ClickHandlerPtr ClickHandler::getActive() {
+	return ClickHandlerActive();
+}
+
+ClickHandlerPtr ClickHandler::getPressed() {
+	return ClickHandlerPressed();
+}
+
+bool ClickHandler::showAsActive(const ClickHandlerPtr &p) {
+	auto &active = ClickHandlerActive();
+	auto &pressed = ClickHandlerPressed();
+
+	return p && (p == active) && (!pressed || (p == pressed));
+}
+
+bool ClickHandler::showAsPressed(const ClickHandlerPtr &p) {
+	auto &active = ClickHandlerActive();
+	auto &pressed = ClickHandlerPressed();
+
+	return p && (p == active) && (p == pressed);
+}
+
+void ClickHandler::hostDestroyed(ClickHandlerHost *host) {
+	auto &active = ClickHandlerActive();
+	auto &pressed = ClickHandlerPressed();
+
+	if (_activeHost == host) {
+		active = nullptr;
+		_activeHost = nullptr;
+	}
+	if (_pressedHost == host) {
+		pressed = nullptr;
+		_pressedHost = nullptr;
+	}
+}
+
+auto ClickHandler::getTextEntity() const -> TextEntity {
+	return { EntityType::Invalid };
+}
+
+void ActivateClickHandler(
+		not_null<QWidget*> guard,
+		ClickHandlerPtr handler,
+		ClickContext context) {
+	crl::on_main(guard, [=] {
+		handler->onClick(context);
+	});
+}
+
+void ActivateClickHandler(
+		not_null<QWidget*> guard,
+		ClickHandlerPtr handler,
+		Qt::MouseButton button) {
+	ActivateClickHandler(guard, handler, ClickContext{ button });
+}
diff --git a/Telegram/SourceFiles/core/click_handler.h b/Telegram/SourceFiles/ui/click_handler.h
similarity index 57%
rename from Telegram/SourceFiles/core/click_handler.h
rename to Telegram/SourceFiles/ui/click_handler.h
index 22fa6f5f7..6c158dfb7 100644
--- a/Telegram/SourceFiles/core/click_handler.h
+++ b/Telegram/SourceFiles/ui/click_handler.h
@@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
+#include "base/basic_types.h"
+
+#include <QtCore/QVariant>
+
 class ClickHandler;
 using ClickHandlerPtr = std::shared_ptr<ClickHandler>;
 
@@ -65,86 +69,23 @@ public:
 
 	// This method should be called when mouse leaves the host.
 	// It returns true if the active handler was changed or false otherwise.
-	static bool clearActive(ClickHandlerHost *host = nullptr) {
-		if (host && _activeHost != host) {
-			return false;
-		}
-		return setActive(ClickHandlerPtr(), host);
-	}
+	static bool clearActive(ClickHandlerHost *host = nullptr);
 
 	// This method should be called on mouse press event.
-	static void pressed() {
-		unpressed();
-		if (!_active || !*_active) {
-			return;
-		}
-		_pressed.createIfNull();
-		*_pressed = *_active;
-		if ((_pressedHost = _activeHost)) {
-			_pressedHost->clickHandlerPressedChanged(*_pressed, true);
-		}
-	}
+	static void pressed();
 
 	// This method should be called on mouse release event.
 	// The activated click handler (if any) is returned.
-	static ClickHandlerPtr unpressed() {
-		if (_pressed && *_pressed) {
-			const auto activated = (_active && *_active == *_pressed);
-			const auto waspressed = base::take(*_pressed);
-			if (_pressedHost) {
-				_pressedHost->clickHandlerPressedChanged(waspressed, false);
-				_pressedHost = nullptr;
-			}
+	static ClickHandlerPtr unpressed();
 
-			if (activated) {
-				return *_active;
-			} else if (_active && *_active && _activeHost) {
-				// emit clickHandlerActiveChanged for current active
-				// click handler, which we didn't emit while we has
-				// a pressed click handler
-				_activeHost->clickHandlerActiveChanged(*_active, true);
-			}
-		}
-		return ClickHandlerPtr();
-	}
+	static ClickHandlerPtr getActive();
+	static ClickHandlerPtr getPressed();
 
-	static ClickHandlerPtr getActive() {
-		return _active ? *_active : ClickHandlerPtr();
-	}
-	static ClickHandlerPtr getPressed() {
-		return _pressed ? *_pressed : ClickHandlerPtr();
-	}
-
-	static bool showAsActive(const ClickHandlerPtr &p) {
-		if (!p || !_active || p != *_active) {
-			return false;
-		}
-		return !_pressed || !*_pressed || (p == *_pressed);
-	}
-	static bool showAsPressed(const ClickHandlerPtr &p) {
-		if (!p || !_active || p != *_active) {
-			return false;
-		}
-		return _pressed && (p == *_pressed);
-	}
-	static void hostDestroyed(ClickHandlerHost *host) {
-		if (_activeHost == host) {
-			if (_active) {
-				*_active = nullptr;
-			}
-			_activeHost = nullptr;
-		}
-		if (_pressedHost == host) {
-			if (_pressed) {
-				*_pressed = nullptr;
-			}
-			_pressedHost = nullptr;
-		}
-	}
+	static bool showAsActive(const ClickHandlerPtr &p);
+	static bool showAsPressed(const ClickHandlerPtr &p);
+	static void hostDestroyed(ClickHandlerHost *host);
 
 private:
-	static NeverFreedPointer<ClickHandlerPtr> _active;
-	static NeverFreedPointer<ClickHandlerPtr> _pressed;
 	static ClickHandlerHost *_activeHost;
 	static ClickHandlerHost *_pressedHost;
 
@@ -177,3 +118,12 @@ private:
 	Fn<void()> _handler;
 
 };
+
+void ActivateClickHandler(
+	not_null<QWidget*> guard,
+	ClickHandlerPtr handler,
+	ClickContext context);
+void ActivateClickHandler(
+	not_null<QWidget*> guard,
+	ClickHandlerPtr handler,
+	Qt::MouseButton button);
diff --git a/Telegram/SourceFiles/ui/delayed_activation.cpp b/Telegram/SourceFiles/ui/delayed_activation.cpp
new file mode 100644
index 000000000..d039223ba
--- /dev/null
+++ b/Telegram/SourceFiles/ui/delayed_activation.cpp
@@ -0,0 +1,45 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "ui/delayed_activation.h"
+
+#include "ui/ui_utility.h"
+
+#include <QtCore/QPointer>
+
+namespace Ui {
+namespace {
+
+auto Paused = false;
+auto Window = QPointer<QWidget>();
+
+} // namespace
+
+void ActivateWindowDelayed(not_null<QWidget*> widget) {
+	if (Paused) {
+		return;
+	} else if (std::exchange(Window, widget.get())) {
+		return;
+	}
+	crl::on_main(Window, [=] {
+		if (const auto widget = base::take(Window)) {
+			if (!widget->isHidden()) {
+				widget->activateWindow();
+			}
+		}
+	});
+}
+
+void PreventDelayedActivation() {
+	Window = nullptr;
+	Paused = true;
+	PostponeCall([] {
+		Paused = false;
+	});
+}
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/delayed_activation.h b/Telegram/SourceFiles/ui/delayed_activation.h
new file mode 100644
index 000000000..60eeaefb2
--- /dev/null
+++ b/Telegram/SourceFiles/ui/delayed_activation.h
@@ -0,0 +1,15 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+namespace Ui {
+
+void ActivateWindowDelayed(not_null<QWidget*> widget);
+void PreventDelayedActivation();
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/effects/animations.cpp b/Telegram/SourceFiles/ui/effects/animations.cpp
index 262c70ae2..7b575f1b6 100644
--- a/Telegram/SourceFiles/ui/effects/animations.cpp
+++ b/Telegram/SourceFiles/ui/effects/animations.cpp
@@ -167,7 +167,7 @@ void Manager::schedule() {
 	stopTimer();
 
 	_scheduled = true;
-	Ui::PostponeCall(delayedCallGuard(), [=] {
+	PostponeCall(delayedCallGuard(), [=] {
 		_scheduled = false;
 		if (_forceImmediateUpdate) {
 			_forceImmediateUpdate = false;
diff --git a/Telegram/SourceFiles/ui/effects/fade_animation.cpp b/Telegram/SourceFiles/ui/effects/fade_animation.cpp
index 5c7febd09..105ef111c 100644
--- a/Telegram/SourceFiles/ui/effects/fade_animation.cpp
+++ b/Telegram/SourceFiles/ui/effects/fade_animation.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/effects/fade_animation.h"
 
 #include "ui/ui_utility.h"
+#include "ui/painter.h"
 #include "app.h"
 
 namespace Ui {
@@ -22,7 +23,7 @@ FadeAnimation::FadeAnimation(TWidget *widget, float64 scale)
 , _scale(scale) {
 }
 
-bool FadeAnimation::paint(Painter &p) {
+bool FadeAnimation::paint(QPainter &p) {
 	if (_cache.isNull()) return false;
 
 	const auto cache = _cache;
diff --git a/Telegram/SourceFiles/ui/effects/fade_animation.h b/Telegram/SourceFiles/ui/effects/fade_animation.h
index bff9eb616..c110f56cf 100644
--- a/Telegram/SourceFiles/ui/effects/fade_animation.h
+++ b/Telegram/SourceFiles/ui/effects/fade_animation.h
@@ -17,7 +17,7 @@ class FadeAnimation {
 public:
 	FadeAnimation(TWidget *widget, float64 scale = 1.);
 
-	bool paint(Painter &p);
+	bool paint(QPainter &p);
 	void refreshCache();
 
 	using FinishedCallback = Fn<void()>;
diff --git a/Telegram/SourceFiles/ui/effects/panel_animation.cpp b/Telegram/SourceFiles/ui/effects/panel_animation.cpp
index 4d3109735..b7eba95df 100644
--- a/Telegram/SourceFiles/ui/effects/panel_animation.cpp
+++ b/Telegram/SourceFiles/ui/effects/panel_animation.cpp
@@ -55,11 +55,12 @@ void RoundShadowAnimation::setShadow(const style::Shadow &st) {
 	}
 }
 
-void RoundShadowAnimation::setCornerMasks(const QImage &topLeft, const QImage &topRight, const QImage &bottomLeft, const QImage &bottomRight) {
-	setCornerMask(_topLeft, topLeft);
-	setCornerMask(_topRight, topRight);
-	setCornerMask(_bottomLeft, bottomLeft);
-	setCornerMask(_bottomRight, bottomRight);
+void RoundShadowAnimation::setCornerMasks(
+		const std::array<QImage, 4> &corners) {
+	setCornerMask(_topLeft, corners[0]);
+	setCornerMask(_topRight, corners[1]);
+	setCornerMask(_bottomLeft, corners[2]);
+	setCornerMask(_bottomRight, corners[3]);
 }
 
 void RoundShadowAnimation::setCornerMask(Corner &corner, const QImage &image) {
diff --git a/Telegram/SourceFiles/ui/effects/panel_animation.h b/Telegram/SourceFiles/ui/effects/panel_animation.h
index d7379f7eb..73c91e096 100644
--- a/Telegram/SourceFiles/ui/effects/panel_animation.h
+++ b/Telegram/SourceFiles/ui/effects/panel_animation.h
@@ -13,7 +13,7 @@ namespace Ui {
 
 class RoundShadowAnimation {
 public:
-	void setCornerMasks(const QImage &topLeft, const QImage &topRight, const QImage &bottomLeft, const QImage &bottomRight);
+	void setCornerMasks(const std::array<QImage, 4> &corners);
 
 protected:
 	void start(int frameWidth, int frameHeight, float64 devicePixelRatio);
diff --git a/Telegram/SourceFiles/ui/emoji_config.cpp b/Telegram/SourceFiles/ui/emoji_config.cpp
index c365497c8..62cfad9a4 100644
--- a/Telegram/SourceFiles/ui/emoji_config.cpp
+++ b/Telegram/SourceFiles/ui/emoji_config.cpp
@@ -14,11 +14,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/bytes.h"
 #include "base/openssl_help.h"
 #include "base/parse_helper.h"
-#include "main/main_session.h"
-#include "app.h"
+#include "ui/style/style_core.h"
+#include "ui/painter.h"
+#include "ui/ui_utility.h"
+#include "ui/ui_log.h"
+#include "styles/style_basic.h"
 
 #include <QtCore/QJsonDocument>
 #include <QtCore/QJsonObject>
+#include <QtCore/QFile>
+#include <QtCore/QDir>
+
+#include <crl/crl_async.h>
 
 namespace Ui {
 namespace Emoji {
@@ -179,7 +186,7 @@ void SaveToFile(int id, const QImage &image, int size, int index) {
 	if (!f.open(QIODevice::WriteOnly)) {
 		if (!QDir::current().mkpath(internal::CacheFileFolder())
 			|| !f.open(QIODevice::WriteOnly)) {
-			LOG(("App Error: Could not open emoji cache '%1' for size %2_%3"
+			UI_LOG(("App Error: Could not open emoji cache '%1' for size %2_%3"
 				).arg(f.fileName()
 				).arg(size
 				).arg(index));
@@ -205,7 +212,7 @@ void SaveToFile(int id, const QImage &image, int size, int index) {
 		|| !write(data)
 		|| !write(openssl::Sha256(bytes::make_span(header), data))
 		|| false) {
-		LOG(("App Error: Could not write emoji cache '%1' for size %2"
+		UI_LOG(("App Error: Could not write emoji cache '%1' for size %2"
 			).arg(f.fileName()
 			).arg(size));
 	}
@@ -278,7 +285,7 @@ std::vector<QImage> LoadSprites(int id) {
 	auto result = std::vector<QImage>();
 	const auto folder = (id != 0)
 		? internal::SetDataPath(id) + '/'
-		: qsl(":/gui/emoji/");
+		: QStringLiteral(":/gui/emoji/");
 	const auto base = folder + "emoji_";
 	return ranges::view::ints(
 		0,
@@ -359,7 +366,7 @@ void ClearUniversalChecked() {
 namespace internal {
 
 QString CacheFileFolder() {
-	return cWorkingDir() + "tdata/emoji";
+	return Integration::Instance().emojiCacheFolder();
 }
 
 QString SetDataPath(int id) {
@@ -470,8 +477,8 @@ void Init() {
 	const auto persprite = kImagesPerRow * kImageRowsPerSprite;
 	SpritesCount = (count / persprite) + ((count % persprite) ? 1 : 0);
 
-	SizeNormal = style::ConvertScale(18, cScale() * cIntRetinaFactor());
-	SizeLarge = int(style::ConvertScale(18 * 4 / 3., cScale() * cIntRetinaFactor()));
+	SizeNormal = style::ConvertScale(18, style::Scale() * style::DevicePixelRatio());
+	SizeLarge = int(style::ConvertScale(18 * 4 / 3., style::Scale() * style::DevicePixelRatio()));
 	Universal = std::make_shared<UniversalImages>(ReadCurrentSetId());
 	CanClearUniversal = false;
 
@@ -479,9 +486,9 @@ void Init() {
 	InstanceLarge = std::make_unique<Instance>(SizeLarge);
 
 #if defined Q_OS_MAC && !defined OS_MAC_OLD
-	if (cScale() != kScaleForTouchBar) {
+	if (style::Scale() != kScaleForTouchBar) {
 		TouchbarSize = int(style::ConvertScale(18 * 4 / 3.,
-			kScaleForTouchBar * cIntRetinaFactor()));
+			kScaleForTouchBar * style::DevicePixelRatio()));
 		TouchbarInstance = std::make_unique<Instance>(TouchbarSize);
 		TouchbarEmoji = TouchbarInstance.get();
 	} else {
@@ -594,7 +601,7 @@ int GetSizeLarge() {
 
 #if defined Q_OS_MAC && !defined OS_MAC_OLD
 int GetSizeTouchbar() {
-	return (cScale() == kScaleForTouchBar)
+	return (style::Scale() == kScaleForTouchBar)
 		? GetSizeLarge()
 		: TouchbarSize;
 }
@@ -697,7 +704,7 @@ QVector<EmojiPtr> GetDefaultRecent() {
 	};
 	auto result = QVector<EmojiPtr>();
 	for (const auto oldKey : defaultRecent) {
-		if (const auto emoji = Ui::Emoji::FromOldKey(oldKey)) {
+		if (const auto emoji = FromOldKey(oldKey)) {
 			result.push_back(emoji);
 		}
 	}
@@ -705,7 +712,7 @@ QVector<EmojiPtr> GetDefaultRecent() {
 }
 
 const QPixmap &SinglePixmap(EmojiPtr emoji, int fontHeight) {
-	auto &map = (fontHeight == st::msgFont->height * cIntRetinaFactor())
+	auto &map = (fontHeight == st::normalFont->height * style::DevicePixelRatio())
 		? MainEmojiMap
 		: OtherEmojiMap[fontHeight];
 	auto i = map.find(emoji->index());
@@ -716,7 +723,7 @@ const QPixmap &SinglePixmap(EmojiPtr emoji, int fontHeight) {
 		SizeNormal + st::emojiPadding * 2,
 		fontHeight,
 		QImage::Format_ARGB32_Premultiplied);
-	image.setDevicePixelRatio(cRetinaFactor());
+	image.setDevicePixelRatio(style::DevicePixelRatio());
 	image.fill(Qt::transparent);
 	{
 		QPainter p(&image);
@@ -725,18 +732,18 @@ const QPixmap &SinglePixmap(EmojiPtr emoji, int fontHeight) {
 			p,
 			emoji,
 			SizeNormal,
-			st::emojiPadding * cIntRetinaFactor(),
+			st::emojiPadding * style::DevicePixelRatio(),
 			(fontHeight - SizeNormal) / 2);
 	}
 	return map.emplace(
 		emoji->index(),
-		App::pixmapFromImageInPlace(std::move(image))
+		PixmapFromImage(std::move(image))
 	).first->second;
 }
 
 void Draw(QPainter &p, EmojiPtr emoji, int size, int x, int y) {
 #if defined Q_OS_MAC && !defined OS_MAC_OLD
-	const auto s = (cScale() == kScaleForTouchBar)
+	const auto s = (style::Scale() == kScaleForTouchBar)
 		? SizeLarge
 		: TouchbarSize;
 	if (size == s) {
@@ -835,8 +842,8 @@ void Instance::generateCache() {
 }
 
 void Instance::pushSprite(QImage &&data) {
-	_sprites.push_back(App::pixmapFromImageInPlace(std::move(data)));
-	_sprites.back().setDevicePixelRatio(cRetinaFactor());
+	_sprites.push_back(PixmapFromImage(std::move(data)));
+	_sprites.back().setDevicePixelRatio(style::DevicePixelRatio());
 }
 
 const std::shared_ptr<UniversalImages> &SourceImages() {
diff --git a/Telegram/SourceFiles/ui/emoji_config.h b/Telegram/SourceFiles/ui/emoji_config.h
index 393cae0be..f6fe3bcbb 100644
--- a/Telegram/SourceFiles/ui/emoji_config.h
+++ b/Telegram/SourceFiles/ui/emoji_config.h
@@ -16,8 +16,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include <rpl/producer.h>
 
-using EmojiPtr = const Ui::Emoji::One*;
-
 namespace Ui {
 namespace Emoji {
 namespace internal {
diff --git a/Telegram/SourceFiles/ui/image/image_prepare.cpp b/Telegram/SourceFiles/ui/image/image_prepare.cpp
index a0b6f4b5c..ea011b0d0 100644
--- a/Telegram/SourceFiles/ui/image/image_prepare.cpp
+++ b/Telegram/SourceFiles/ui/image/image_prepare.cpp
@@ -8,8 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/image/image_prepare.h"
 
 #include "ui/effects/animation_value.h"
-#include "facades.h"
-#include "app.h"
+#include "ui/style/style_core.h"
+#include "ui/painter.h"
+#include "base/flat_map.h"
+#include "styles/palette.h"
+#include "styles/style_basic.h"
 
 namespace Images {
 namespace {
@@ -19,8 +22,6 @@ TG_FORCE_INLINE uint64 blurGetColors(const uchar *p) {
 }
 
 const QImage &circleMask(QSize size) {
-	Assert(Global::started());
-
 	uint64 key = (uint64(uint32(size.width())) << 32)
 		| uint64(uint32(size.height()));
 
@@ -34,7 +35,7 @@ const QImage &circleMask(QSize size) {
 		QImage::Format_ARGB32_Premultiplied);
 	mask.fill(Qt::transparent);
 	{
-		Painter p(&mask);
+		QPainter p(&mask);
 		PainterHighQualityEnabler hq(p);
 		p.setBrush(Qt::white);
 		p.setPen(Qt::NoPen);
@@ -43,6 +44,31 @@ const QImage &circleMask(QSize size) {
 	return masks.emplace(key, std::move(mask)).first->second;
 }
 
+std::array<QImage, 4> PrepareCornersMask(int radius) {
+	auto result = std::array<QImage, 4>();
+	const auto side = radius * style::DevicePixelRatio();
+	auto full = QImage(
+		QSize(side, side) * 3,
+		QImage::Format_ARGB32_Premultiplied);
+	full.fill(Qt::transparent);
+	{
+		QPainter p(&full);
+		PainterHighQualityEnabler hq(p);
+
+		p.setPen(Qt::NoPen);
+		p.setBrush(Qt::white);
+		p.drawRoundedRect(0, 0, side * 3, side * 3, side, side);
+	}
+	result[0] = full.copy(0, 0, side, side);
+	result[1] = full.copy(side * 2, 0, side, side);
+	result[2] = full.copy(0, side * 2, side, side);
+	result[3] = full.copy(side * 2, side * 2, side, side);
+	for (auto &image : result) {
+		image.setDevicePixelRatio(style::DevicePixelRatio());
+	}
+	return result;
+}
+
 } // namespace
 
 QPixmap PixmapFast(QImage &&image) {
@@ -52,6 +78,26 @@ QPixmap PixmapFast(QImage &&image) {
 	return QPixmap::fromImage(std::move(image), Qt::NoFormatConversion);
 }
 
+const std::array<QImage, 4> &CornersMask(ImageRoundRadius radius) {
+	if (radius == ImageRoundRadius::Large) {
+		static auto Mask = PrepareCornersMask(st::roundRadiusLarge);
+		return Mask;
+	} else {
+		static auto Mask = PrepareCornersMask(st::roundRadiusSmall);
+		return Mask;
+	}
+}
+
+std::array<QImage, 4> PrepareCorners(
+		ImageRoundRadius radius,
+		const style::color &color) {
+	auto result = CornersMask(radius);
+	for (auto &image : result) {
+		style::colorizeImage(image, color->c, &image);
+	}
+	return result;
+}
+
 QImage prepareBlur(QImage img) {
 	if (img.isNull()) {
 		return img;
@@ -75,7 +121,7 @@ QImage prepareBlur(QImage img) {
 			if (withalpha) {
 				QImage imgsmall(w, h, img.format());
 				{
-					Painter p(&imgsmall);
+					QPainter p(&imgsmall);
 					PainterHighQualityEnabler hq(p);
 
 					p.setCompositionMode(QPainter::CompositionMode_Source);
@@ -394,7 +440,7 @@ void prepareCircle(QImage &img) {
 	img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
 	Assert(!img.isNull());
 
-	Painter p(&img);
+	QPainter p(&img);
 	p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
 	p.drawImage(
 		QRect(QPoint(), img.size() / img.devicePixelRatio()),
@@ -471,12 +517,13 @@ void prepareRound(
 	}
 	Assert(!image.isNull());
 
-	image.setDevicePixelRatio(cRetinaFactor());
-	image = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied);
+	image.setDevicePixelRatio(style::DevicePixelRatio());
+	image = std::move(image).convertToFormat(
+		QImage::Format_ARGB32_Premultiplied);
 	Assert(!image.isNull());
 
-	auto masks = App::cornersMask(radius);
-	prepareRound(image, masks, corners, target);
+	auto masks = CornersMask(radius);
+	prepareRound(image, masks.data(), corners, target);
 }
 
 QImage prepareColored(style::color add, QImage image) {
@@ -542,12 +589,13 @@ QImage prepare(QImage img, int w, int h, Images::Options options, int outerw, in
 		Assert(!img.isNull());
 	}
 	if (outerw > 0 && outerh > 0) {
-		outerw *= cIntRetinaFactor();
-		outerh *= cIntRetinaFactor();
+		const auto pixelRatio = style::DevicePixelRatio();
+		outerw *= pixelRatio;
+		outerh *= pixelRatio;
 		if (outerw != w || outerh != h) {
-			img.setDevicePixelRatio(cRetinaFactor());
+			img.setDevicePixelRatio(pixelRatio);
 			auto result = QImage(outerw, outerh, QImage::Format_ARGB32_Premultiplied);
-			result.setDevicePixelRatio(cRetinaFactor());
+			result.setDevicePixelRatio(pixelRatio);
 			if (options & Images::Option::TransparentBackground) {
 				result.fill(Qt::transparent);
 			}
@@ -558,7 +606,7 @@ QImage prepare(QImage img, int w, int h, Images::Options options, int outerw, in
 						p.fillRect(0, 0, result.width(), result.height(), st::imageBg);
 					}
 				}
-				p.drawImage((result.width() - img.width()) / (2 * cIntRetinaFactor()), (result.height() - img.height()) / (2 * cIntRetinaFactor()), img);
+				p.drawImage((result.width() - img.width()) / (2 * pixelRatio), (result.height() - img.height()) / (2 * pixelRatio), img);
 			}
 			img = result;
 			Assert(!img.isNull());
@@ -584,7 +632,7 @@ QImage prepare(QImage img, int w, int h, Images::Options options, int outerw, in
 		Assert(colored != nullptr);
 		img = prepareColored(*colored, std::move(img));
 	}
-	img.setDevicePixelRatio(cRetinaFactor());
+	img.setDevicePixelRatio(style::DevicePixelRatio());
 	return img;
 }
 
diff --git a/Telegram/SourceFiles/ui/image/image_prepare.h b/Telegram/SourceFiles/ui/image/image_prepare.h
index b8af5715b..9838549d1 100644
--- a/Telegram/SourceFiles/ui/image/image_prepare.h
+++ b/Telegram/SourceFiles/ui/image/image_prepare.h
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "base/flags.h"
 #include "ui/rect_part.h"
+#include "ui/style/style_core.h"
 
 namespace Storage {
 namespace Cache {
@@ -25,9 +26,13 @@ enum class ImageRoundRadius {
 
 namespace Images {
 
-QPixmap PixmapFast(QImage &&image);
-
-QImage BlurLargeImage(QImage image, int radius);
+[[nodiscard]] QPixmap PixmapFast(QImage &&image);
+[[nodiscard]] QImage BlurLargeImage(QImage image, int radius);
+[[nodiscard]] const std::array<QImage, 4> &CornersMask(
+	ImageRoundRadius radius);
+[[nodiscard]] std::array<QImage, 4> PrepareCorners(
+	ImageRoundRadius radius,
+	const style::color &color);
 
 QImage prepareBlur(QImage image);
 void prepareRound(
diff --git a/Telegram/SourceFiles/ui/inactive_press.cpp b/Telegram/SourceFiles/ui/inactive_press.cpp
new file mode 100644
index 000000000..69ed27fe9
--- /dev/null
+++ b/Telegram/SourceFiles/ui/inactive_press.cpp
@@ -0,0 +1,54 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "ui/inactive_press.h"
+
+#include "base/timer.h"
+#include "base/qt_connection.h"
+
+#include <QtCore/QPointer>
+
+namespace Ui {
+namespace {
+
+constexpr auto kInactivePressTimeout = crl::time(200);
+
+struct InactivePressedWidget {
+	QWidget *widget = nullptr;
+	base::qt_connection connection;
+	base::Timer timer;
+};
+
+std::unique_ptr<InactivePressedWidget> Tracker;
+
+} // namespace
+
+void MarkInactivePress(not_null<QWidget*> widget, bool was) {
+	if (!was) {
+		if (WasInactivePress(widget)) {
+			Tracker = nullptr;
+		}
+		return;
+	}
+
+	Tracker = std::make_unique<InactivePressedWidget>();
+	Tracker->widget = widget;
+	Tracker->connection = QObject::connect(widget, &QWidget::destroyed, [=] {
+		Tracker->connection.release();
+		Tracker = nullptr;
+	});
+	Tracker->timer.setCallback([=] {
+		Tracker = nullptr;
+	});
+	Tracker->timer.callOnce(kInactivePressTimeout);
+}
+
+[[nodiscard]] bool WasInactivePress(not_null<QWidget*> widget) {
+	return Tracker && (Tracker->widget == widget);
+}
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/inactive_press.h b/Telegram/SourceFiles/ui/inactive_press.h
new file mode 100644
index 000000000..aace0c714
--- /dev/null
+++ b/Telegram/SourceFiles/ui/inactive_press.h
@@ -0,0 +1,15 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+namespace Ui {
+
+void MarkInactivePress(not_null<QWidget*> widget, bool was);
+[[nodiscard]] bool WasInactivePress(not_null<QWidget*> widget);
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/platform/linux/ui_platform_utility_linux.cpp b/Telegram/SourceFiles/ui/platform/linux/ui_platform_utility_linux.cpp
new file mode 100644
index 000000000..ebdc5a697
--- /dev/null
+++ b/Telegram/SourceFiles/ui/platform/linux/ui_platform_utility_linux.cpp
@@ -0,0 +1,48 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "ui/platform/linux/ui_platform_utility_linux.h"
+
+#include "base/flat_set.h"
+#include "ui/ui_log.h"
+
+#include <QtCore/QPoint>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QDesktopWidget>
+#include <qpa/qplatformnativeinterface.h>
+
+namespace Ui {
+namespace Platform {
+
+bool IsApplicationActive() {
+	return QApplication::activeWindow() != nullptr;
+}
+
+bool TranslucentWindowsSupported(QPoint globalPosition) {
+	if (const auto native = QGuiApplication::platformNativeInterface()) {
+		if (const auto desktop = QApplication::desktop()) {
+			const auto index = desktop->screenNumber(globalPosition);
+			const auto screens = QGuiApplication::screens();
+			if (const auto screen = (index >= 0 && index < screens.size()) ? screens[index] : QGuiApplication::primaryScreen()) {
+				if (native->nativeResourceForScreen(QByteArray("compositingEnabled"), screen)) {
+					return true;
+				}
+				static auto WarnedAbout = base::flat_set<int>();
+				if (!WarnedAbout.contains(index)) {
+					WarnedAbout.emplace(index);
+					UI_LOG(("WARNING: Compositing is disabled for screen index %1 (for position %2,%3)").arg(index).arg(globalPosition.x()).arg(globalPosition.y()));
+				}
+			} else {
+				UI_LOG(("WARNING: Could not get screen for index %1 (for position %2,%3)").arg(index).arg(globalPosition.x()).arg(globalPosition.y()));
+			}
+		}
+	}
+	return false;
+}
+
+} // namespace Platform
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/platform/linux/ui_platform_utility_linux.h b/Telegram/SourceFiles/ui/platform/linux/ui_platform_utility_linux.h
new file mode 100644
index 000000000..5e78e89e1
--- /dev/null
+++ b/Telegram/SourceFiles/ui/platform/linux/ui_platform_utility_linux.h
@@ -0,0 +1,38 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+class QPainter;
+class QPaintEvent;
+
+namespace Ui {
+namespace Platform {
+
+inline void StartTranslucentPaint(QPainter &p, QPaintEvent *e) {
+}
+
+inline void InitOnTopPanel(not_null<QWidget*> panel) {
+}
+
+inline void DeInitOnTopPanel(not_null<QWidget*> panel) {
+}
+
+inline void ReInitOnTopPanel(not_null<QWidget*> panel) {
+}
+
+inline void UpdateOverlayed(not_null<QWidget*> widget) {
+}
+
+inline void ShowOverAll(not_null<QWidget*> widget, bool canFocus) {
+}
+
+inline void BringToBack(not_null<QWidget*> widget) {
+}
+
+} // namespace Platform
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/platform/mac/ui_platform_utility_mac.h b/Telegram/SourceFiles/ui/platform/mac/ui_platform_utility_mac.h
new file mode 100644
index 000000000..b7fa2f6b9
--- /dev/null
+++ b/Telegram/SourceFiles/ui/platform/mac/ui_platform_utility_mac.h
@@ -0,0 +1,23 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include <QtCore/QPoint>
+
+namespace Ui {
+namespace Platform {
+
+inline bool TranslucentWindowsSupported(QPoint globalPosition) {
+	return true;
+}
+
+inline void UpdateOverlayed(not_null<QWidget*> widget) {
+}
+
+} // namespace Platform
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/platform/mac/ui_platform_utility_mac.mm b/Telegram/SourceFiles/ui/platform/mac/ui_platform_utility_mac.mm
new file mode 100644
index 000000000..993c1f725
--- /dev/null
+++ b/Telegram/SourceFiles/ui/platform/mac/ui_platform_utility_mac.mm
@@ -0,0 +1,88 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "ui/platform/mac/ui_platform_utility_mac.h"
+
+#include "ui/ui_integration.h"
+
+#include <QtGui/QPainter>
+#include <QtGui/QtEvents>
+#include <QtGui/QWindow>
+
+#include <Cocoa/Cocoa.h>
+
+namespace Ui {
+namespace Platform {
+
+bool IsApplicationActive() {
+	return [[NSApplication sharedApplication] isActive];
+}
+
+void InitOnTopPanel(not_null<QWidget*> panel) {
+	Expects(!panel->windowHandle());
+
+	// Force creating windowHandle() without creating the platform window yet.
+	panel->setAttribute(Qt::WA_NativeWindow, true);
+	panel->windowHandle()->setProperty("_td_macNonactivatingPanelMask", QVariant(true));
+	panel->setAttribute(Qt::WA_NativeWindow, false);
+
+	panel->createWinId();
+
+	auto platformWindow = [reinterpret_cast<NSView*>(panel->winId()) window];
+	Assert([platformWindow isKindOfClass:[NSPanel class]]);
+
+	auto platformPanel = static_cast<NSPanel*>(platformWindow);
+	[platformPanel setLevel:NSPopUpMenuWindowLevel];
+	[platformPanel setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorStationary|NSWindowCollectionBehaviorFullScreenAuxiliary|NSWindowCollectionBehaviorIgnoresCycle];
+	[platformPanel setFloatingPanel:YES];
+	[platformPanel setHidesOnDeactivate:NO];
+
+	Integration::Instance().activationFromTopPanel();
+}
+
+void DeInitOnTopPanel(not_null<QWidget*> panel) {
+	auto platformWindow = [reinterpret_cast<NSView*>(panel->winId()) window];
+	Assert([platformWindow isKindOfClass:[NSPanel class]]);
+
+	auto platformPanel = static_cast<NSPanel*>(platformWindow);
+	auto newBehavior = ([platformPanel collectionBehavior] & (~NSWindowCollectionBehaviorCanJoinAllSpaces)) | NSWindowCollectionBehaviorMoveToActiveSpace;
+	[platformPanel setCollectionBehavior:newBehavior];
+}
+
+void ReInitOnTopPanel(not_null<QWidget*> panel) {
+	auto platformWindow = [reinterpret_cast<NSView*>(panel->winId()) window];
+	Assert([platformWindow isKindOfClass:[NSPanel class]]);
+
+	auto platformPanel = static_cast<NSPanel*>(platformWindow);
+	auto newBehavior = ([platformPanel collectionBehavior] & (~NSWindowCollectionBehaviorMoveToActiveSpace)) | NSWindowCollectionBehaviorCanJoinAllSpaces;
+	[platformPanel setCollectionBehavior:newBehavior];
+}
+
+void StartTranslucentPaint(QPainter &p, QPaintEvent *e) {
+#ifdef OS_MAC_OLD
+	p.setCompositionMode(QPainter::CompositionMode_Source);
+	p.fillRect(e->rect(), Qt::transparent);
+	p.setCompositionMode(QPainter::CompositionMode_SourceOver);
+#endif // OS_MAC_OLD
+}
+
+void ShowOverAll(not_null<QWidget*> widget, bool canFocus) {
+	NSWindow *wnd = [reinterpret_cast<NSView*>(widget->winId()) window];
+	[wnd setLevel:NSPopUpMenuWindowLevel];
+	if (!canFocus) {
+		[wnd setStyleMask:NSUtilityWindowMask | NSNonactivatingPanelMask];
+		[wnd setCollectionBehavior:NSWindowCollectionBehaviorMoveToActiveSpace|NSWindowCollectionBehaviorStationary|NSWindowCollectionBehaviorFullScreenAuxiliary|NSWindowCollectionBehaviorIgnoresCycle];
+	}
+}
+
+void BringToBack(not_null<QWidget*> widget) {
+	NSWindow *wnd = [reinterpret_cast<NSView*>(widget->winId()) window];
+	[wnd setLevel:NSModalPanelWindowLevel];
+}
+
+} // namespace Platform
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/platform/ui_platform_utility.h b/Telegram/SourceFiles/ui/platform/ui_platform_utility.h
new file mode 100644
index 000000000..4cd3d97ef
--- /dev/null
+++ b/Telegram/SourceFiles/ui/platform/ui_platform_utility.h
@@ -0,0 +1,41 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+class QPoint;
+class QPainter;
+class QPaintEvent;
+
+namespace Ui {
+namespace Platform {
+
+[[nodiscard]] bool IsApplicationActive();
+
+[[nodiscard]] bool TranslucentWindowsSupported(QPoint globalPosition);
+void StartTranslucentPaint(QPainter &p, QPaintEvent *e);
+
+void InitOnTopPanel(not_null<QWidget*> panel);
+void DeInitOnTopPanel(not_null<QWidget*> panel);
+void ReInitOnTopPanel(not_null<QWidget*> panel);
+
+void UpdateOverlayed(not_null<QWidget*> widget);
+void ShowOverAll(not_null<QWidget*> widget, bool canFocus = true);
+void BringToBack(not_null<QWidget*> widget);
+
+} // namespace Platform
+} // namespace Ui
+
+// Platform dependent implementations.
+
+#ifdef Q_OS_MAC
+#include "ui/platform/mac/ui_platform_utility_mac.h"
+#elif defined Q_OS_LINUX // Q_OS_MAC
+#include "ui/platform/linux/ui_platform_utility_linux.h"
+#elif defined Q_OS_WINRT || defined Q_OS_WIN // Q_OS_MAC || Q_OS_LINUX
+#include "ui/platform/win/ui_platform_utility_win.h"
+#endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WINRT || Q_OS_WIN
diff --git a/Telegram/SourceFiles/ui/platform/win/ui_platform_utility_win.cpp b/Telegram/SourceFiles/ui/platform/win/ui_platform_utility_win.cpp
new file mode 100644
index 000000000..fe3dc0f1e
--- /dev/null
+++ b/Telegram/SourceFiles/ui/platform/win/ui_platform_utility_win.cpp
@@ -0,0 +1,32 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "ui/platform/win/ui_platform_utility_win.h"
+
+#include <QtWidgets/QApplication>
+
+namespace Ui {
+namespace Platform {
+
+bool IsApplicationActive() {
+	return QApplication::activeWindow() != nullptr;
+}
+
+void UpdateOverlayed(not_null<QWidget*> widget) {
+	const auto wm = widget->testAttribute(Qt::WA_Mapped);
+	const auto wv = widget->testAttribute(Qt::WA_WState_Visible);
+	if (!wm) widget->setAttribute(Qt::WA_Mapped, true);
+	if (!wv) widget->setAttribute(Qt::WA_WState_Visible, true);
+	widget->update();
+	QEvent e(QEvent::UpdateRequest);
+	QGuiApplication::sendEvent(widget, &e);
+	if (!wm) widget->setAttribute(Qt::WA_Mapped, false);
+	if (!wv) widget->setAttribute(Qt::WA_WState_Visible, false);
+}
+
+} // namespace Platform
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/platform/win/ui_platform_utility_win.h b/Telegram/SourceFiles/ui/platform/win/ui_platform_utility_win.h
new file mode 100644
index 000000000..ad83beb45
--- /dev/null
+++ b/Telegram/SourceFiles/ui/platform/win/ui_platform_utility_win.h
@@ -0,0 +1,41 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include <QtCore/QPoint>
+
+class QPainter;
+class QPaintEvent;
+
+namespace Ui {
+namespace Platform {
+
+inline bool TranslucentWindowsSupported(QPoint globalPosition) {
+	return true;
+}
+
+inline void InitOnTopPanel(not_null<QWidget*> panel) {
+}
+
+inline void DeInitOnTopPanel(not_null<QWidget*> panel) {
+}
+
+inline void ReInitOnTopPanel(not_null<QWidget*> panel) {
+}
+
+inline void StartTranslucentPaint(QPainter &p, QPaintEvent *e) {
+}
+
+inline void ShowOverAll(not_null<QWidget*> widget, bool canFocus) {
+}
+
+inline void BringToBack(not_null<QWidget*> widget) {
+}
+
+} // namespace Platform
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/round_rect.cpp b/Telegram/SourceFiles/ui/round_rect.cpp
new file mode 100644
index 000000000..5cd6a5c37
--- /dev/null
+++ b/Telegram/SourceFiles/ui/round_rect.cpp
@@ -0,0 +1,83 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "ui/round_rect.h"
+
+#include "ui/style/style_core.h"
+#include "ui/image/image_prepare.h"
+#include "ui/ui_utility.h"
+
+namespace Ui {
+
+
+void DrawRoundedRect(
+		QPainter &p,
+		const QRect &rect,
+		const QBrush &brush,
+		const std::array<QImage, 4> &corners,
+		RectParts parts) {
+	const auto pixelRatio = style::DevicePixelRatio();
+	const auto x = rect.x();
+	const auto y = rect.y();
+	const auto w = rect.width();
+	const auto h = rect.height();
+	auto cornerWidth = corners[0].width() / pixelRatio;
+	auto cornerHeight = corners[0].height() / pixelRatio;
+	if (w < 2 * cornerWidth || h < 2 * cornerHeight) return;
+	if (w > 2 * cornerWidth) {
+		if (parts & RectPart::Top) {
+			p.fillRect(x + cornerWidth, y, w - 2 * cornerWidth, cornerHeight, brush);
+		}
+		if (parts & RectPart::Bottom) {
+			p.fillRect(x + cornerWidth, y + h - cornerHeight, w - 2 * cornerWidth, cornerHeight, brush);
+		}
+	}
+	if (h > 2 * cornerHeight) {
+		if ((parts & RectPart::NoTopBottom) == RectPart::NoTopBottom) {
+			p.fillRect(x, y + cornerHeight, w, h - 2 * cornerHeight, brush);
+		} else {
+			if (parts & RectPart::Left) {
+				p.fillRect(x, y + cornerHeight, cornerWidth, h - 2 * cornerHeight, brush);
+			}
+			if ((parts & RectPart::Center) && w > 2 * cornerWidth) {
+				p.fillRect(x + cornerWidth, y + cornerHeight, w - 2 * cornerWidth, h - 2 * cornerHeight, brush);
+			}
+			if (parts & RectPart::Right) {
+				p.fillRect(x + w - cornerWidth, y + cornerHeight, cornerWidth, h - 2 * cornerHeight, brush);
+			}
+		}
+	}
+	if (parts & RectPart::TopLeft) {
+		p.drawImage(x, y, corners[0]);
+	}
+	if (parts & RectPart::TopRight) {
+		p.drawImage(x + w - cornerWidth, y, corners[1]);
+	}
+	if (parts & RectPart::BottomLeft) {
+		p.drawImage(x, y + h - cornerHeight, corners[2]);
+	}
+	if (parts & RectPart::BottomRight) {
+		p.drawImage(x + w - cornerWidth, y + h - cornerHeight, corners[3]);
+	}
+}
+
+RoundRect::RoundRect(
+	ImageRoundRadius radius,
+	const style::color &color)
+: _color(color)
+, _corners(Images::PrepareCorners(radius, color)) {
+	style::PaletteChanged(
+	) | rpl::start_with_next([=] {
+		_corners = Images::PrepareCorners(radius, _color);
+	}, _lifetime);
+}
+
+void RoundRect::paint(QPainter &p, const QRect &rect) const {
+	DrawRoundedRect(p, rect, _color, _corners);
+}
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/round_rect.h b/Telegram/SourceFiles/ui/round_rect.h
new file mode 100644
index 000000000..c7739ecdf
--- /dev/null
+++ b/Telegram/SourceFiles/ui/round_rect.h
@@ -0,0 +1,39 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "ui/rect_part.h"
+#include "ui/style/style_core.h"
+
+enum class ImageRoundRadius;
+class QPainter;
+
+namespace Ui {
+
+void DrawRoundedRect(
+	QPainter &p,
+	const QRect &rect,
+	const QBrush &brush,
+	const std::array<QImage, 4> & corners,
+	RectParts parts = RectPart::Full);
+
+class RoundRect final {
+public:
+	RoundRect(ImageRoundRadius radius, const style::color &color);
+
+	void paint(QPainter &p, const QRect &rect) const;
+
+private:
+	style::color _color;
+	std::array<QImage, 4> _corners;
+
+	rpl::lifetime _lifetime;
+
+};
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/special_buttons.cpp b/Telegram/SourceFiles/ui/special_buttons.cpp
index 17f0c7fb5..90da8b844 100644
--- a/Telegram/SourceFiles/ui/special_buttons.cpp
+++ b/Telegram/SourceFiles/ui/special_buttons.cpp
@@ -1117,4 +1117,8 @@ QPoint SilentToggle::tooltipPos() const {
 	return QCursor::pos();
 }
 
+bool SilentToggle::tooltipWindowActive() const {
+	return InFocusChain(window());
+}
+
 } // namespace Ui
diff --git a/Telegram/SourceFiles/ui/special_buttons.h b/Telegram/SourceFiles/ui/special_buttons.h
index b297703d3..47ac205fc 100644
--- a/Telegram/SourceFiles/ui/special_buttons.h
+++ b/Telegram/SourceFiles/ui/special_buttons.h
@@ -278,6 +278,7 @@ public:
 	// AbstractTooltipShower interface
 	QString tooltipText() const override;
 	QPoint tooltipPos() const override;
+	bool tooltipWindowActive() const override;
 
 protected:
 	void mouseMoveEvent(QMouseEvent *e) override;
diff --git a/Telegram/SourceFiles/ui/special_fields.cpp b/Telegram/SourceFiles/ui/special_fields.cpp
new file mode 100644
index 000000000..c7973ca59
--- /dev/null
+++ b/Telegram/SourceFiles/ui/special_fields.cpp
@@ -0,0 +1,413 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "ui/special_fields.h"
+
+#include "core/application.h"
+#include "lang/lang_keys.h"
+#include "data/data_countries.h" // Data::ValidPhoneCode
+#include "numbers.h"
+
+namespace Ui {
+namespace {
+
+constexpr auto kMaxUsernameLength = 32;
+
+} // namespace
+
+CountryCodeInput::CountryCodeInput(
+	QWidget *parent,
+	const style::InputField &st)
+: MaskedInputField(parent, st)
+, _nosignal(false) {
+}
+
+void CountryCodeInput::startErasing(QKeyEvent *e) {
+	setFocus();
+	keyPressEvent(e);
+}
+
+void CountryCodeInput::codeSelected(const QString &code) {
+	auto wasText = getLastText();
+	auto wasCursor = cursorPosition();
+	auto newText = '+' + code;
+	auto newCursor = newText.size();
+	setText(newText);
+	_nosignal = true;
+	correctValue(wasText, wasCursor, newText, newCursor);
+	_nosignal = false;
+	emit changed();
+}
+
+void CountryCodeInput::correctValue(
+		const QString &was,
+		int wasCursor,
+		QString &now,
+		int &nowCursor) {
+	QString newText, addToNumber;
+	int oldPos(nowCursor);
+	int newPos(-1);
+	int oldLen(now.length());
+	int start = 0;
+	int digits = 5;
+	newText.reserve(oldLen + 1);
+	if (oldLen && now[0] == '+') {
+		if (start == oldPos) {
+			newPos = newText.length();
+		}
+		++start;
+	}
+	newText += '+';
+	for (int i = start; i < oldLen; ++i) {
+		if (i == oldPos) {
+			newPos = newText.length();
+		}
+		auto ch = now[i];
+		if (ch.isDigit()) {
+			if (!digits || !--digits) {
+				addToNumber += ch;
+			} else {
+				newText += ch;
+			}
+		}
+	}
+	if (!addToNumber.isEmpty()) {
+		auto validCode = Data::ValidPhoneCode(newText.mid(1));
+		addToNumber = newText.mid(1 + validCode.length()) + addToNumber;
+		newText = '+' + validCode;
+	}
+	setCorrectedText(now, nowCursor, newText, newPos);
+
+	if (!_nosignal && was != newText) {
+		emit codeChanged(newText.mid(1));
+	}
+	if (!addToNumber.isEmpty()) {
+		emit addedToNumber(addToNumber);
+	}
+}
+
+PhonePartInput::PhonePartInput(QWidget *parent, const style::InputField &st) : MaskedInputField(parent, st/*, tr::lng_phone_ph(tr::now)*/) {
+}
+
+void PhonePartInput::paintAdditionalPlaceholder(Painter &p) {
+	if (!_pattern.isEmpty()) {
+		auto t = getDisplayedText();
+		auto ph = _additionalPlaceholder.mid(t.size());
+		if (!ph.isEmpty()) {
+			p.setClipRect(rect());
+			auto phRect = placeholderRect();
+			int tw = phFont()->width(t);
+			if (tw < phRect.width()) {
+				phRect.setLeft(phRect.left() + tw);
+				placeholderAdditionalPrepare(p);
+				p.drawText(phRect, ph, style::al_topleft);
+			}
+		}
+	}
+}
+
+void PhonePartInput::keyPressEvent(QKeyEvent *e) {
+	if (e->key() == Qt::Key_Backspace && getLastText().isEmpty()) {
+		emit voidBackspace(e);
+	} else {
+		MaskedInputField::keyPressEvent(e);
+	}
+}
+
+void PhonePartInput::correctValue(
+		const QString &was,
+		int wasCursor,
+		QString &now,
+		int &nowCursor) {
+	QString newText;
+	int oldPos(nowCursor), newPos(-1), oldLen(now.length()), digitCount = 0;
+	for (int i = 0; i < oldLen; ++i) {
+		if (now[i].isDigit()) {
+			++digitCount;
+		}
+	}
+	if (digitCount > MaxPhoneTailLength) digitCount = MaxPhoneTailLength;
+
+	bool inPart = !_pattern.isEmpty();
+	int curPart = -1, leftInPart = 0;
+	newText.reserve(oldLen);
+	for (int i = 0; i < oldLen; ++i) {
+		if (i == oldPos && newPos < 0) {
+			newPos = newText.length();
+		}
+
+		auto ch = now[i];
+		if (ch.isDigit()) {
+			if (!digitCount--) {
+				break;
+			}
+			if (inPart) {
+				if (leftInPart) {
+					--leftInPart;
+				} else {
+					newText += ' ';
+					++curPart;
+					inPart = curPart < _pattern.size();
+					leftInPart = inPart ? (_pattern.at(curPart) - 1) : 0;
+
+					++oldPos;
+				}
+			}
+			newText += ch;
+		} else if (ch == ' ' || ch == '-' || ch == '(' || ch == ')') {
+			if (inPart) {
+				if (leftInPart) {
+				} else {
+					newText += ch;
+					++curPart;
+					inPart = curPart < _pattern.size();
+					leftInPart = inPart ? _pattern.at(curPart) : 0;
+				}
+			} else {
+				newText += ch;
+			}
+		}
+	}
+	auto newlen = newText.size();
+	while (newlen > 0 && newText.at(newlen - 1).isSpace()) {
+		--newlen;
+	}
+	if (newlen < newText.size()) {
+		newText = newText.mid(0, newlen);
+	}
+	setCorrectedText(now, nowCursor, newText, newPos);
+}
+
+void PhonePartInput::addedToNumber(const QString &added) {
+	setFocus();
+	auto wasText = getLastText();
+	auto wasCursor = cursorPosition();
+	auto newText = added + wasText;
+	auto newCursor = newText.size();
+	setText(newText);
+	setCursorPosition(added.length());
+	correctValue(wasText, wasCursor, newText, newCursor);
+	startPlaceholderAnimation();
+}
+
+void PhonePartInput::onChooseCode(const QString &code) {
+	_pattern = phoneNumberParse(code);
+	if (!_pattern.isEmpty() && _pattern.at(0) == code.size()) {
+		_pattern.pop_front();
+	} else {
+		_pattern.clear();
+	}
+	_additionalPlaceholder = QString();
+	if (!_pattern.isEmpty()) {
+		_additionalPlaceholder.reserve(20);
+		for (const auto part : _pattern) {
+			_additionalPlaceholder.append(' ');
+			_additionalPlaceholder.append(QString(part, QChar(0x2212)));
+		}
+	}
+	setPlaceholderHidden(!_additionalPlaceholder.isEmpty());
+
+	auto wasText = getLastText();
+	auto wasCursor = cursorPosition();
+	auto newText = getLastText();
+	auto newCursor = newText.size();
+	correctValue(wasText, wasCursor, newText, newCursor);
+
+	startPlaceholderAnimation();
+}
+
+UsernameInput::UsernameInput(
+	QWidget *parent,
+	const style::InputField &st,
+	rpl::producer<QString> placeholder,
+	const QString &val,
+	bool isLink)
+: MaskedInputField(parent, st, std::move(placeholder), val) {
+	setLinkPlaceholder(
+		isLink ? Core::App().createInternalLink(QString()) : QString());
+}
+
+void UsernameInput::setLinkPlaceholder(const QString &placeholder) {
+	_linkPlaceholder = placeholder;
+	if (!_linkPlaceholder.isEmpty()) {
+		setTextMargins(style::margins(_st.textMargins.left() + _st.font->width(_linkPlaceholder), _st.textMargins.top(), _st.textMargins.right(), _st.textMargins.bottom()));
+		setPlaceholderHidden(true);
+	}
+}
+
+void UsernameInput::paintAdditionalPlaceholder(Painter &p) {
+	if (!_linkPlaceholder.isEmpty()) {
+		p.setFont(_st.font);
+		p.setPen(_st.placeholderFg);
+		p.drawText(QRect(_st.textMargins.left(), _st.textMargins.top(), width(), height() - _st.textMargins.top() - _st.textMargins.bottom()), _linkPlaceholder, style::al_topleft);
+	}
+}
+
+void UsernameInput::correctValue(
+		const QString &was,
+		int wasCursor,
+		QString &now,
+		int &nowCursor) {
+	auto newPos = nowCursor;
+	auto from = 0, len = now.size();
+	for (; from < len; ++from) {
+		if (!now.at(from).isSpace()) {
+			break;
+		}
+		if (newPos > 0) --newPos;
+	}
+	len -= from;
+	if (len > kMaxUsernameLength) {
+		len = kMaxUsernameLength + (now.at(from) == '@' ? 1 : 0);
+	}
+	for (int32 to = from + len; to > from;) {
+		--to;
+		if (!now.at(to).isSpace()) {
+			break;
+		}
+		--len;
+	}
+	setCorrectedText(now, nowCursor, now.mid(from, len), newPos);
+}
+
+PhoneInput::PhoneInput(
+	QWidget *parent,
+	const style::InputField &st,
+	rpl::producer<QString> placeholder,
+	const QString &defaultValue,
+	QString value)
+: MaskedInputField(parent, st, std::move(placeholder), value)
+, _defaultValue(defaultValue) {
+	if (value.isEmpty()) {
+		clearText();
+	} else {
+		auto pos = value.size();
+		correctValue(QString(), 0, value, pos);
+	}
+}
+
+void PhoneInput::focusInEvent(QFocusEvent *e) {
+	MaskedInputField::focusInEvent(e);
+	setSelection(cursorPosition(), cursorPosition());
+}
+
+void PhoneInput::clearText() {
+	auto value = _defaultValue;
+	setText(value);
+	auto pos = value.size();
+	correctValue(QString(), 0, value, pos);
+}
+
+void PhoneInput::paintAdditionalPlaceholder(Painter &p) {
+	if (!_pattern.isEmpty()) {
+		auto t = getDisplayedText();
+		auto ph = _additionalPlaceholder.mid(t.size());
+		if (!ph.isEmpty()) {
+			p.setClipRect(rect());
+			auto phRect = placeholderRect();
+			int tw = phFont()->width(t);
+			if (tw < phRect.width()) {
+				phRect.setLeft(phRect.left() + tw);
+				placeholderAdditionalPrepare(p);
+				p.drawText(phRect, ph, style::al_topleft);
+			}
+		}
+	}
+}
+
+void PhoneInput::correctValue(
+		const QString &was,
+		int wasCursor,
+		QString &now,
+		int &nowCursor) {
+	auto digits = now;
+	digits.replace(QRegularExpression(qsl("[^\\d]")), QString());
+	_pattern = phoneNumberParse(digits);
+
+	QString newPlaceholder;
+	if (_pattern.isEmpty()) {
+		newPlaceholder = QString();
+	} else if (_pattern.size() == 1 && _pattern.at(0) == digits.size()) {
+		newPlaceholder = QString(_pattern.at(0) + 2, ' ') + tr::lng_contact_phone(tr::now);
+	} else {
+		newPlaceholder.reserve(20);
+		for (int i = 0, l = _pattern.size(); i < l; ++i) {
+			if (i) {
+				newPlaceholder.append(' ');
+			} else {
+				newPlaceholder.append('+');
+			}
+			newPlaceholder.append(i ? QString(_pattern.at(i), QChar(0x2212)) : digits.mid(0, _pattern.at(i)));
+		}
+	}
+	if (_additionalPlaceholder != newPlaceholder) {
+		_additionalPlaceholder = newPlaceholder;
+		setPlaceholderHidden(!_additionalPlaceholder.isEmpty());
+		update();
+	}
+
+	QString newText;
+	int oldPos(nowCursor), newPos(-1), oldLen(now.length()), digitCount = qMin(digits.size(), MaxPhoneCodeLength + MaxPhoneTailLength);
+
+	bool inPart = !_pattern.isEmpty(), plusFound = false;
+	int curPart = 0, leftInPart = inPart ? _pattern.at(curPart) : 0;
+	newText.reserve(oldLen + 1);
+	newText.append('+');
+	for (int i = 0; i < oldLen; ++i) {
+		if (i == oldPos && newPos < 0) {
+			newPos = newText.length();
+		}
+
+		QChar ch(now[i]);
+		if (ch.isDigit()) {
+			if (!digitCount--) {
+				break;
+			}
+			if (inPart) {
+				if (leftInPart) {
+					--leftInPart;
+				} else {
+					newText += ' ';
+					++curPart;
+					inPart = curPart < _pattern.size();
+					leftInPart = inPart ? (_pattern.at(curPart) - 1) : 0;
+
+					++oldPos;
+				}
+			}
+			newText += ch;
+		} else if (ch == ' ' || ch == '-' || ch == '(' || ch == ')') {
+			if (inPart) {
+				if (leftInPart) {
+				} else {
+					newText += ch;
+					++curPart;
+					inPart = curPart < _pattern.size();
+					leftInPart = inPart ? _pattern.at(curPart) : 0;
+				}
+			} else {
+				newText += ch;
+			}
+		} else if (ch == '+') {
+			plusFound = true;
+		}
+	}
+	if (!plusFound && newText == qstr("+")) {
+		newText = QString();
+		newPos = 0;
+	}
+	int32 newlen = newText.size();
+	while (newlen > 0 && newText.at(newlen - 1).isSpace()) {
+		--newlen;
+	}
+	if (newlen < newText.size()) {
+		newText = newText.mid(0, newlen);
+	}
+	setCorrectedText(now, nowCursor, newText, newPos);
+}
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/special_fields.h b/Telegram/SourceFiles/ui/special_fields.h
new file mode 100644
index 000000000..2306c8606
--- /dev/null
+++ b/Telegram/SourceFiles/ui/special_fields.h
@@ -0,0 +1,121 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "ui/widgets/input_fields.h"
+
+namespace Ui {
+
+class CountryCodeInput : public MaskedInputField {
+	Q_OBJECT
+
+public:
+	CountryCodeInput(QWidget *parent, const style::InputField &st);
+
+public slots:
+	void startErasing(QKeyEvent *e);
+	void codeSelected(const QString &code);
+
+signals:
+	void codeChanged(const QString &code);
+	void addedToNumber(const QString &added);
+
+protected:
+	void correctValue(
+		const QString &was,
+		int wasCursor,
+		QString &now,
+		int &nowCursor) override;
+
+private:
+	bool _nosignal;
+
+};
+
+class PhonePartInput : public MaskedInputField {
+	Q_OBJECT
+
+public:
+	PhonePartInput(QWidget *parent, const style::InputField &st);
+
+public slots:
+	void addedToNumber(const QString &added);
+	void onChooseCode(const QString &code);
+
+signals:
+	void voidBackspace(QKeyEvent *e);
+
+protected:
+	void keyPressEvent(QKeyEvent *e) override;
+
+	void correctValue(
+		const QString &was,
+		int wasCursor,
+		QString &now,
+		int &nowCursor) override;
+	void paintAdditionalPlaceholder(Painter &p) override;
+
+private:
+	QVector<int> _pattern;
+	QString _additionalPlaceholder;
+
+};
+
+class UsernameInput : public MaskedInputField {
+public:
+	UsernameInput(
+		QWidget *parent,
+		const style::InputField &st,
+		rpl::producer<QString> placeholder,
+		const QString &val,
+		bool isLink);
+
+	void setLinkPlaceholder(const QString &placeholder);
+
+protected:
+	void correctValue(
+		const QString &was,
+		int wasCursor,
+		QString &now,
+		int &nowCursor) override;
+	void paintAdditionalPlaceholder(Painter &p) override;
+
+private:
+	QString _linkPlaceholder;
+
+};
+
+class PhoneInput : public MaskedInputField {
+public:
+	PhoneInput(
+		QWidget *parent,
+		const style::InputField &st,
+		rpl::producer<QString> placeholder,
+		const QString &defaultValue,
+		QString value);
+
+	void clearText();
+
+protected:
+	void focusInEvent(QFocusEvent *e) override;
+
+	void correctValue(
+		const QString &was,
+		int wasCursor,
+		QString &now,
+		int &nowCursor) override;
+	void paintAdditionalPlaceholder(Painter &p) override;
+
+private:
+	QString _defaultValue;
+	QVector<int> _pattern;
+	QString _additionalPlaceholder;
+
+};
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/style/style_core.cpp b/Telegram/SourceFiles/ui/style/style_core.cpp
index bb46cffb4..20dfdfdbd 100644
--- a/Telegram/SourceFiles/ui/style/style_core.cpp
+++ b/Telegram/SourceFiles/ui/style/style_core.cpp
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "styles/palette.h"
 
 #include <QtGui/QPainter>
+#include <QtGui/QFontDatabase>
 
 #include <rpl/event_stream.h>
 #include <rpl/variable.h>
@@ -28,6 +29,7 @@ constexpr auto kContrastDeltaL = 64;
 auto PaletteChanges = rpl::event_stream<>();
 auto ShortAnimationRunning = rpl::variable<bool>(false);
 auto RunningShortAnimations = 0;
+auto ResolvedMonospaceFont = style::font();
 
 std::vector<internal::ModuleBase*> &StyleModules() {
 	static auto result = std::vector<internal::ModuleBase*>();
@@ -40,6 +42,28 @@ void startModules(int scale) {
 	}
 }
 
+void ResolveMonospaceFont() {
+	auto family = QString();
+	const auto tryFont = [&](const QString &attempt) {
+		if (family.isEmpty()
+			&& !QFontInfo(QFont(attempt)).family().trimmed().compare(
+				attempt,
+				Qt::CaseInsensitive)) {
+			family = attempt;
+		}
+	};
+	tryFont("Consolas");
+	tryFont("Liberation Mono");
+	tryFont("Menlo");
+	tryFont("Courier");
+	if (family.isEmpty()) {
+		const auto type = QFontDatabase::FixedFont;
+		family = QFontDatabase::systemFont(type).family();
+	}
+	const auto size = st::normalFont->f.pixelSize();
+	ResolvedMonospaceFont = style::font(size, 0, family);
+}
+
 } // namespace
 
 void registerModule(ModuleBase *module) {
@@ -60,6 +84,17 @@ void StopShortAnimation() {
 
 } // namespace internal
 
+void startManager(int scale) {
+	internal::registerFontFamily("Open Sans");
+	internal::startModules(scale);
+	internal::ResolveMonospaceFont();
+}
+
+void stopManager() {
+	internal::destroyFonts();
+	internal::destroyIcons();
+}
+
 rpl::producer<> PaletteChanged() {
 	return internal::PaletteChanges.events();
 }
@@ -72,14 +107,8 @@ rpl::producer<bool> ShortAnimationPlaying() {
 	return internal::ShortAnimationRunning.value();
 }
 
-void startManager(int scale) {
-	internal::registerFontFamily("Open Sans");
-	internal::startModules(scale);
-}
-
-void stopManager() {
-	internal::destroyFonts();
-	internal::destroyIcons();
+const style::font &MonospaceFont() {
+	return internal::ResolvedMonospaceFont;
 }
 
 void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect, QPoint dstPoint) {
diff --git a/Telegram/SourceFiles/ui/style/style_core.h b/Telegram/SourceFiles/ui/style/style_core.h
index 6503fa72c..f62dace0a 100644
--- a/Telegram/SourceFiles/ui/style/style_core.h
+++ b/Telegram/SourceFiles/ui/style/style_core.h
@@ -44,6 +44,8 @@ void NotifyPaletteChanged();
 
 [[nodiscard]] rpl::producer<bool> ShortAnimationPlaying();
 
+const style::font &MonospaceFont();
+
 // *outResult must be r.width() x r.height(), ARGB32_Premultiplied.
 // QRect(0, 0, src.width(), src.height()) must contain r.
 void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect = QRect(), QPoint dstPoint = QPoint(0, 0));
diff --git a/Telegram/SourceFiles/ui/style/style_core_direction.h b/Telegram/SourceFiles/ui/style/style_core_direction.h
index 2cf6620ff..096087622 100644
--- a/Telegram/SourceFiles/ui/style/style_core_direction.h
+++ b/Telegram/SourceFiles/ui/style/style_core_direction.h
@@ -17,6 +17,10 @@ namespace style {
 [[nodiscard]] bool RightToLeft();
 void SetRightToLeft(bool rtl);
 
+[[nodiscard]] inline Qt::LayoutDirection LayoutDirection() {
+	return RightToLeft() ? Qt::RightToLeft : Qt::LeftToRight;
+}
+
 inline QRect centerrect(const QRect &inRect, const QRect &rect) {
 	return QRect(
 		inRect.x() + (inRect.width() - rect.width()) / 2,
diff --git a/Telegram/SourceFiles/ui/style/style_core_font.cpp b/Telegram/SourceFiles/ui/style/style_core_font.cpp
index e3c1c51f2..653dbfff0 100644
--- a/Telegram/SourceFiles/ui/style/style_core_font.cpp
+++ b/Telegram/SourceFiles/ui/style/style_core_font.cpp
@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/style/style_core_font.h"
 
 #include "base/algorithm.h"
-#include "logs.h"
+#include "ui/ui_log.h"
 
 #include <QtCore/QMap>
 #include <QtCore/QVector>
@@ -36,13 +36,13 @@ bool ValidateFont(const QString &familyName, int flags = 0) {
 	checkFont.setStyleStrategy(QFont::PreferQuality);
 	auto realFamily = QFontInfo(checkFont).family();
 	if (realFamily.trimmed().compare(familyName, Qt::CaseInsensitive)) {
-		LOG(("Font Error: could not resolve '%1' font, got '%2'.").arg(familyName).arg(realFamily));
+		UI_LOG(("Font Error: could not resolve '%1' font, got '%2'.").arg(familyName).arg(realFamily));
 		return false;
 	}
 
 	auto metrics = QFontMetrics(checkFont);
 	if (!metrics.height()) {
-		LOG(("Font Error: got a zero height in '%1'.").arg(familyName));
+		UI_LOG(("Font Error: got a zero height in '%1'.").arg(familyName));
 		return false;
 	}
 
@@ -52,7 +52,7 @@ bool ValidateFont(const QString &familyName, int flags = 0) {
 bool LoadCustomFont(const QString &filePath, const QString &familyName, int flags = 0) {
 	auto regularId = QFontDatabase::addApplicationFont(filePath);
 	if (regularId < 0) {
-		LOG(("Font Error: could not add '%1'.").arg(filePath));
+		UI_LOG(("Font Error: could not add '%1'.").arg(filePath));
 		return false;
 	}
 
@@ -65,7 +65,7 @@ bool LoadCustomFont(const QString &filePath, const QString &familyName, int flag
 		return false;
 	};
 	if (!found()) {
-		LOG(("Font Error: could not locate '%1' font in '%2'.").arg(familyName).arg(filePath));
+		UI_LOG(("Font Error: could not locate '%1' font in '%2'.").arg(familyName).arg(filePath));
 		return false;
 	}
 
@@ -96,13 +96,13 @@ void StartFonts() {
 	if (!regular || !bold) {
 		if (ValidateFont("Segoe UI") && ValidateFont("Segoe UI", style::internal::FontBold)) {
 			OpenSansOverride = "Segoe UI";
-			LOG(("Fonts Info: Using Segoe UI instead of Open Sans."));
+			UI_LOG(("Fonts Info: Using Segoe UI instead of Open Sans."));
 		}
 	}
 	if (!semibold) {
 		if (ValidateFont("Segoe UI Semibold")) {
 			OpenSansSemiboldOverride = "Segoe UI Semibold";
-			LOG(("Fonts Info: Using Segoe UI Semibold instead of Open Sans Semibold."));
+			UI_LOG(("Fonts Info: Using Segoe UI Semibold instead of Open Sans Semibold."));
 		}
 	}
 	// Disable default fallbacks to Segoe UI, see:
diff --git a/Telegram/SourceFiles/ui/text/text.cpp b/Telegram/SourceFiles/ui/text/text.cpp
index cc58fc4b2..7891995be 100644
--- a/Telegram/SourceFiles/ui/text/text.cpp
+++ b/Telegram/SourceFiles/ui/text/text.cpp
@@ -7,16 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "ui/text/text.h"
 
-#include "core/click_handler_types.h"
-#include "core/crash_reports.h"
+#include "ui/basic_click_handlers.h"
 #include "ui/text/text_block.h"
 #include "ui/text/text_isolated_emoji.h"
 #include "ui/emoji_config.h"
-#include "lang/lang_keys.h"
+#include "ui/ui_integration.h"
 #include "platform/platform_info.h"
-#include "boxes/confirm_box.h"
-#include "mainwindow.h"
-#include "app.h"
 
 #include <private/qfontengine_p.h>
 #include <private/qharfbuzz_p.h>
@@ -111,7 +107,7 @@ QFixed ComputeStopAfter(const TextParseOptions &options, const style::TextStyle
 // Open Sans tilde fix.
 bool ComputeCheckTilde(const style::TextStyle &st) {
 	const auto &font = st.font;
-	return (font->size() * cIntRetinaFactor() == 13)
+	return (font->size() * style::DevicePixelRatio() == 13)
 		&& (font->flags() == 0)
 		&& (font->f.family() == qstr("Open Sans"));
 }
@@ -126,10 +122,6 @@ bool chIsBad(QChar ch) {
         || (ch >= 65024 && ch < 65040 && ch != 65039)
         || (ch >= 127 && ch < 160 && ch != 156)
 
-		|| (Platform::IsMac()
-			&& !Platform::IsMac10_7OrGreater()
-			&& (ch == 8207 || ch == 8206 || ch == 8288))
-
         // qt harfbuzz crash see https://github.com/telegramdesktop/tdesktop/issues/4551
         || (Platform::IsMac() && ch == 6158)
 
@@ -811,7 +803,7 @@ void Parser::parseCurrentChar() {
 
 void Parser::parseEmojiFromCurrent() {
 	int len = 0;
-	auto e = Ui::Emoji::Find(_ptr - _emojiLookback, _end, &len);
+	auto e = Emoji::Find(_ptr - _emojiLookback, _end, &len);
 	if (!e) return;
 
 	for (int l = len - _emojiLookback - 1; l > 0; --l) {
@@ -820,8 +812,8 @@ void Parser::parseEmojiFromCurrent() {
 	if (e->hasPostfix()) {
 		Assert(!_t->_text.isEmpty());
 		const auto last = _t->_text[_t->_text.size() - 1];
-		if (last.unicode() != Ui::Emoji::kPostfix) {
-			_t->_text.push_back(QChar(Ui::Emoji::kPostfix));
+		if (last.unicode() != Emoji::kPostfix) {
+			_t->_text.push_back(QChar(Emoji::kPostfix));
 			++len;
 		}
 	}
@@ -940,63 +932,20 @@ void Parser::computeLinkText(const QString &linkData, QString *outLinkText, Link
 ClickHandlerPtr Parser::CreateHandlerForLink(
 		const TextLinkData &link,
 		const TextParseOptions &options) {
+	const auto result = Integration::Instance().createLinkHandler(
+		link.type,
+		link.text,
+		link.data,
+		options);
+	if (result) {
+		return result;
+	}
 	switch (link.type) {
-	case EntityType::CustomUrl:
-		return !link.data.isEmpty()
-			? std::make_shared<HiddenUrlClickHandler>(link.data)
-			: nullptr;
-
 	case EntityType::Email:
 	case EntityType::Url:
 		return std::make_shared<UrlClickHandler>(
 			link.data,
 			link.displayStatus == LinkDisplayedFull);
-
-	case EntityType::BotCommand:
-		return std::make_shared<BotCommandClickHandler>(link.data);
-
-	case EntityType::Hashtag:
-		if (options.flags & TextTwitterMentions) {
-			return std::make_shared<UrlClickHandler>(
-				(qsl("https://twitter.com/hashtag/")
-					+ link.data.mid(1)
-					+ qsl("?src=hash")),
-				true);
-		} else if (options.flags & TextInstagramMentions) {
-			return std::make_shared<UrlClickHandler>(
-				(qsl("https://instagram.com/explore/tags/")
-					+ link.data.mid(1)
-					+ '/'),
-				true);
-		}
-		return std::make_shared<HashtagClickHandler>(link.data);
-
-	case EntityType::Cashtag:
-		return std::make_shared<CashtagClickHandler>(link.data);
-
-	case EntityType::Mention:
-		if (options.flags & TextTwitterMentions) {
-			return std::make_shared<UrlClickHandler>(
-				qsl("https://twitter.com/") + link.data.mid(1),
-				true);
-		} else if (options.flags & TextInstagramMentions) {
-			return std::make_shared<UrlClickHandler>(
-				qsl("https://instagram.com/") + link.data.mid(1) + '/',
-				true);
-		}
-		return std::make_shared<MentionClickHandler>(link.data);
-
-	case EntityType::MentionName: {
-		auto fields = TextUtilities::MentionNameDataToFields(link.data);
-		if (fields.userId) {
-			return std::make_shared<MentionNameClickHandler>(
-				link.text,
-				fields.userId,
-				fields.accessHash);
-		} else {
-			LOG(("Bad mention name: %1").arg(link.data));
-		}
-	} break;
 	}
 	return nullptr;
 }
@@ -1155,7 +1104,7 @@ public:
 		_align = align;
 
 		_parDirection = _t->_startDir;
-		if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = cLangDir();
+		if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = style::LayoutDirection();
 		if ((*_t->_blocks.cbegin())->type() != TextBlockTNewline) {
 			initNextParagraph(_t->_blocks.cbegin());
 		}
@@ -1195,7 +1144,7 @@ public:
 				}
 
 				_parDirection = static_cast<NewlineBlock*>(b)->nextDirection();
-				if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = cLangDir();
+				if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = style::LayoutDirection();
 				initNextParagraph(i + 1);
 
 				longWordLine = true;
@@ -1598,7 +1547,7 @@ private:
 			}
 		}
 	    QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
-		if (rtl() && skipIndex == nItems - 1) {
+		if (style::RightToLeft() && skipIndex == nItems - 1) {
 			for (int32 i = nItems; i > 1;) {
 				--i;
 				visualOrder[i] = visualOrder[i - 1];
@@ -1705,10 +1654,10 @@ private:
 							}
 						}
 					}
-					Ui::Emoji::Draw(
+					Emoji::Draw(
 						*_p,
 						static_cast<EmojiBlock*>(currentBlock)->emoji,
-						Ui::Emoji::GetSizeNormal(),
+						Emoji::GetSizeNormal(),
 						(glyphX + st::emojiPadding).toInt(),
 						_y + _yDelta + emojiY);
 //				} else if (_p && currentBlock->type() == TextBlockSkip) { // debug
@@ -1900,7 +1849,7 @@ private:
 	}
 
 	void prepareElidedLine(QString &lineText, int32 lineStart, int32 &lineLength, AbstractBlock *&_endBlock, int repeat = 0) {
-		static const QString _Elide = qsl("...");
+		static const auto _Elide = QString::fromLatin1("...");
 
 		_f = _t->_st->font;
 		QStackTextEngine engine(lineText, _f->f);
@@ -2046,7 +1995,7 @@ private:
 		}
 		auto result = f;
 		if ((flags & TextBlockFPre) || (flags & TextBlockFCode)) {
-			result = App::monofont();
+			result = style::MonospaceFont();
 			if (result->size() != f->size() || result->flags() != f->flags()) {
 				result = style::font(f->size(), f->flags(), result->family());
 			}
@@ -3095,6 +3044,22 @@ void String::drawElided(Painter &painter, int32 left, int32 top, int32 w, int32
 	p.drawElided(left, top, w, align, lines, yFrom, yTo, removeFromEnd, breakEverywhere, selection);
 }
 
+void String::drawLeft(Painter &p, int32 left, int32 top, int32 width, int32 outerw, style::align align, int32 yFrom, int32 yTo, TextSelection selection) const {
+	draw(p, style::RightToLeft() ? (outerw - left - width) : left, top, width, align, yFrom, yTo, selection);
+}
+
+void String::drawLeftElided(Painter &p, int32 left, int32 top, int32 width, int32 outerw, int32 lines, style::align align, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere, TextSelection selection) const {
+	drawElided(p, style::RightToLeft() ? (outerw - left - width) : left, top, width, lines, align, yFrom, yTo, removeFromEnd, breakEverywhere, selection);
+}
+
+void String::drawRight(Painter &p, int32 right, int32 top, int32 width, int32 outerw, style::align align, int32 yFrom, int32 yTo, TextSelection selection) const {
+	draw(p, style::RightToLeft() ? right : (outerw - right - width), top, width, align, yFrom, yTo, selection);
+}
+
+void String::drawRightElided(Painter &p, int32 right, int32 top, int32 width, int32 outerw, int32 lines, style::align align, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere, TextSelection selection) const {
+	drawElided(p, style::RightToLeft() ? right : (outerw - right - width), top, width, lines, align, yFrom, yTo, removeFromEnd, breakEverywhere, selection);
+}
+
 StateResult String::getState(QPoint point, int width, StateRequest request) const {
 	return Renderer(nullptr, this).getState(point, width, request);
 }
diff --git a/Telegram/SourceFiles/ui/text/text.h b/Telegram/SourceFiles/ui/text/text.h
index 53e9dd0dd..5d7c2cd07 100644
--- a/Telegram/SourceFiles/ui/text/text.h
+++ b/Telegram/SourceFiles/ui/text/text.h
@@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "ui/text/text_entity.h"
 #include "ui/painter.h"
-#include "core/click_handler.h"
+#include "ui/click_handler.h"
 #include "base/flags.h"
 
 #include <private/qfixed_p.h>
@@ -138,18 +138,10 @@ public:
 
 	void draw(Painter &p, int32 left, int32 top, int32 width, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }, bool fullWidthSelection = true) const;
 	void drawElided(Painter &p, int32 left, int32 top, int32 width, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const;
-	void drawLeft(Painter &p, int32 left, int32 top, int32 width, int32 outerw, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }) const {
-		draw(p, rtl() ? (outerw - left - width) : left, top, width, align, yFrom, yTo, selection);
-	}
-	void drawLeftElided(Painter &p, int32 left, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const {
-		drawElided(p, rtl() ? (outerw - left - width) : left, top, width, lines, align, yFrom, yTo, removeFromEnd, breakEverywhere, selection);
-	}
-	void drawRight(Painter &p, int32 right, int32 top, int32 width, int32 outerw, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }) const {
-		draw(p, rtl() ? right : (outerw - right - width), top, width, align, yFrom, yTo, selection);
-	}
-	void drawRightElided(Painter &p, int32 right, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const {
-		drawElided(p, rtl() ? right : (outerw - right - width), top, width, lines, align, yFrom, yTo, removeFromEnd, breakEverywhere, selection);
-	}
+	void drawLeft(Painter &p, int32 left, int32 top, int32 width, int32 outerw, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }) const;
+	void drawLeftElided(Painter &p, int32 left, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const;
+	void drawRight(Painter &p, int32 right, int32 top, int32 width, int32 outerw, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }) const;
+	void drawRightElided(Painter &p, int32 right, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const;
 
 	StateResult getState(QPoint point, int width, StateRequest request = StateRequest()) const;
 	StateResult getStateLeft(QPoint point, int width, int outerw, StateRequest request = StateRequest()) const;
@@ -251,7 +243,7 @@ private:
 } // namespace Ui
 
 inline TextSelection snapSelection(int from, int to) {
-	return { static_cast<uint16>(snap(from, 0, 0xFFFF)), static_cast<uint16>(snap(to, 0, 0xFFFF)) };
+	return { static_cast<uint16>(std::clamp(from, 0, 0xFFFF)), static_cast<uint16>(std::clamp(to, 0, 0xFFFF)) };
 }
 inline TextSelection shiftSelection(TextSelection selection, uint16 byLength) {
 	return snapSelection(int(selection.from) + byLength, int(selection.to) + byLength);
diff --git a/Telegram/SourceFiles/ui/text/text_block.cpp b/Telegram/SourceFiles/ui/text/text_block.cpp
index b49a126b6..29173c5c2 100644
--- a/Telegram/SourceFiles/ui/text/text_block.cpp
+++ b/Telegram/SourceFiles/ui/text/text_block.cpp
@@ -7,8 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "ui/text/text_block.h"
 
-#include "core/crash_reports.h"
-#include "app.h"
+#include "styles/style_basic.h"
 
 #include <private/qfontengine_p.h>
 
@@ -310,7 +309,7 @@ TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResi
 		}
 
 		if ((flags & TextBlockFPre) || (flags & TextBlockFCode)) {
-			blockFont = App::monofont();
+			blockFont = style::MonospaceFont();
 			if (blockFont->size() != font->size() || blockFont->flags() != font->flags()) {
 				blockFont = style::font(font->size(), font->flags(), blockFont->family());
 			}
@@ -333,13 +332,8 @@ TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResi
 
 		const auto part = str.mid(_from, length);
 
-		// Attempt to catch a crash in text processing
-		CrashReports::SetAnnotationRef("CrashString", &part);
-
 		QStackTextEngine engine(part, blockFont->f);
 		BlockParser parser(&engine, this, minResizeWidth, _from, part);
-
-		CrashReports::ClearAnnotationRef("CrashString");
 	}
 }
 
diff --git a/Telegram/SourceFiles/ui/text/text_block.h b/Telegram/SourceFiles/ui/text/text_block.h
index a20b8123c..6fcf1cd51 100644
--- a/Telegram/SourceFiles/ui/text/text_block.h
+++ b/Telegram/SourceFiles/ui/text/text_block.h
@@ -7,6 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
+#include "ui/style/style_core.h"
+#include "ui/emoji_config.h"
+
 #include <private/qfixed_p.h>
 
 namespace Ui {
diff --git a/Telegram/SourceFiles/ui/text/text_entity.cpp b/Telegram/SourceFiles/ui/text/text_entity.cpp
index 91b5b1631..44be2c0ee 100644
--- a/Telegram/SourceFiles/ui/text/text_entity.cpp
+++ b/Telegram/SourceFiles/ui/text/text_entity.cpp
@@ -7,14 +7,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "ui/text/text_entity.h"
 
-#include "main/main_session.h"
 #include "lang/lang_tag.h"
 #include "base/qthelp_url.h"
+#include "base/qthelp_regex.h"
+#include "base/crc32hash.h"
+#include "ui/text/text.h"
+#include "ui/widgets/input_fields.h"
 #include "ui/emoji_config.h"
-#include "data/data_user.h"
-#include "data/data_session.h"
 
 #include <QtCore/QStack>
+#include <QtCore/QMimeData>
+#include <QtGui/QGuiApplication>
+#include <QtGui/QClipboard>
 
 namespace TextUtilities {
 namespace {
@@ -23,7 +27,7 @@ QString ExpressionMailNameAtEnd() {
 	// Matches email first part (before '@') at the end of the string.
 	// First we find a domain without protocol (like "gmail.com"), then
 	// we find '@' before it and then we look for the name before '@'.
-	return qsl("[a-zA-Z\\-_\\.0-9]{1,256}$");
+	return QString::fromUtf8("[a-zA-Z\\-_\\.0-9]{1,256}$");
 }
 
 QString Quotes() {
@@ -33,12 +37,12 @@ QString Quotes() {
 
 QString ExpressionSeparators(const QString &additional) {
 	static const auto quotes = Quotes();
-	return qsl("\\s\\.,:;<>|'\"\\[\\]\\{\\}\\~\\!\\?\\%\\^\\(\\)\\-\\+=\\x10") + quotes + additional;
+	return QString::fromUtf8("\\s\\.,:;<>|'\"\\[\\]\\{\\}\\~\\!\\?\\%\\^\\(\\)\\-\\+=\\x10") + quotes + additional;
 }
 
 QString Separators(const QString &additional) {
 	static const auto quotes = Quotes();
-	return qsl(" \x10\n\r\t.,:;<>|'\"[]{}!?%^()-+=")
+	return QString::fromUtf8(" \x10\n\r\t.,:;<>|'\"[]{}!?%^()-+=")
 		+ QChar(0xfdd0) // QTextBeginningOfFrame
 		+ QChar(0xfdd1) // QTextEndOfFrame
 		+ QChar(QChar::ParagraphSeparator)
@@ -48,35 +52,35 @@ QString Separators(const QString &additional) {
 }
 
 QString SeparatorsBold() {
-	return Separators(qsl("`~/"));
+	return Separators(QString::fromUtf8("`~/"));
 }
 
 QString SeparatorsItalic() {
-	return Separators(qsl("`*~/"));
+	return Separators(QString::fromUtf8("`*~/"));
 }
 
 QString SeparatorsStrikeOut() {
-	return Separators(qsl("`*~/"));
+	return Separators(QString::fromUtf8("`*~/"));
 }
 
 QString SeparatorsMono() {
-	return Separators(qsl("*~/"));
+	return Separators(QString::fromUtf8("*~/"));
 }
 
 QString ExpressionHashtag() {
-	return qsl("(^|[") + ExpressionSeparators(qsl("`\\*/")) + qsl("])#[\\w]{2,64}([\\W]|$)");
+	return QString::fromUtf8("(^|[") + ExpressionSeparators(QString::fromUtf8("`\\*/")) + QString::fromUtf8("])#[\\w]{2,64}([\\W]|$)");
 }
 
 QString ExpressionHashtagExclude() {
-	return qsl("^#?\\d+$");
+	return QString::fromUtf8("^#?\\d+$");
 }
 
 QString ExpressionMention() {
-	return qsl("(^|[") + ExpressionSeparators(qsl("`\\*/")) + qsl("])@[A-Za-z_0-9]{1,32}([\\W]|$)");
+	return QString::fromUtf8("(^|[") + ExpressionSeparators(QString::fromUtf8("`\\*/")) + QString::fromUtf8("])@[A-Za-z_0-9]{1,32}([\\W]|$)");
 }
 
 QString ExpressionBotCommand() {
-	return qsl("(^|[") + ExpressionSeparators(qsl("`\\*")) + qsl("])/[A-Za-z_0-9]{1,64}(@[A-Za-z_0-9]{5,32})?([\\W]|$)");
+	return QString::fromUtf8("(^|[") + ExpressionSeparators(QString::fromUtf8("`\\*")) + QString::fromUtf8("])/[A-Za-z_0-9]{1,64}(@[A-Za-z_0-9]{5,32})?([\\W]|$)");
 }
 
 QRegularExpression CreateRegExp(const QString &expression) {
@@ -89,340 +93,340 @@ QRegularExpression CreateRegExp(const QString &expression) {
 	return result;
 }
 
-QSet<int32> CreateValidProtocols() {
-	auto result = QSet<int32>();
-	auto addOne = [&result](const QString &string) {
-		result.insert(hashCrc32(string.constData(), string.size() * sizeof(QChar)));
+base::flat_set<int32> CreateValidProtocols() {
+	auto result = base::flat_set<int32>();
+	const auto addOne = [&](const QString &string) {
+		result.insert(base::crc32(string.constData(), string.size() * sizeof(QChar)));
 	};
-	addOne(qsl("itmss")); // itunes
-	addOne(qsl("http"));
-	addOne(qsl("https"));
-	addOne(qsl("ftp"));
-	addOne(qsl("tg")); // local urls
+	addOne(QString::fromLatin1("itmss")); // itunes
+	addOne(QString::fromLatin1("http"));
+	addOne(QString::fromLatin1("https"));
+	addOne(QString::fromLatin1("ftp"));
+	addOne(QString::fromLatin1("tg")); // local urls
 	return result;
 }
 
-QSet<int32> CreateValidTopDomains() {
-	auto result = QSet<int32>();
+base::flat_set<int32> CreateValidTopDomains() {
+	auto result = base::flat_set<int32>();
 	auto addOne = [&result](const QString &string) {
-		result.insert(hashCrc32(string.constData(), string.size() * sizeof(QChar)));
+		result.insert(base::crc32(string.constData(), string.size() * sizeof(QChar)));
 	};
-	addOne(qsl("ac"));
-	addOne(qsl("ad"));
-	addOne(qsl("ae"));
-	addOne(qsl("af"));
-	addOne(qsl("ag"));
-	addOne(qsl("ai"));
-	addOne(qsl("al"));
-	addOne(qsl("am"));
-	addOne(qsl("an"));
-	addOne(qsl("ao"));
-	addOne(qsl("aq"));
-	addOne(qsl("ar"));
-	addOne(qsl("as"));
-	addOne(qsl("at"));
-	addOne(qsl("au"));
-	addOne(qsl("aw"));
-	addOne(qsl("ax"));
-	addOne(qsl("az"));
-	addOne(qsl("ba"));
-	addOne(qsl("bb"));
-	addOne(qsl("bd"));
-	addOne(qsl("be"));
-	addOne(qsl("bf"));
-	addOne(qsl("bg"));
-	addOne(qsl("bh"));
-	addOne(qsl("bi"));
-	addOne(qsl("bj"));
-	addOne(qsl("bm"));
-	addOne(qsl("bn"));
-	addOne(qsl("bo"));
-	addOne(qsl("br"));
-	addOne(qsl("bs"));
-	addOne(qsl("bt"));
-	addOne(qsl("bv"));
-	addOne(qsl("bw"));
-	addOne(qsl("by"));
-	addOne(qsl("bz"));
-	addOne(qsl("ca"));
-	addOne(qsl("cc"));
-	addOne(qsl("cd"));
-	addOne(qsl("cf"));
-	addOne(qsl("cg"));
-	addOne(qsl("ch"));
-	addOne(qsl("ci"));
-	addOne(qsl("ck"));
-	addOne(qsl("cl"));
-	addOne(qsl("cm"));
-	addOne(qsl("cn"));
-	addOne(qsl("co"));
-	addOne(qsl("cr"));
-	addOne(qsl("cu"));
-	addOne(qsl("cv"));
-	addOne(qsl("cx"));
-	addOne(qsl("cy"));
-	addOne(qsl("cz"));
-	addOne(qsl("de"));
-	addOne(qsl("dj"));
-	addOne(qsl("dk"));
-	addOne(qsl("dm"));
-	addOne(qsl("do"));
-	addOne(qsl("dz"));
-	addOne(qsl("ec"));
-	addOne(qsl("ee"));
-	addOne(qsl("eg"));
-	addOne(qsl("eh"));
-	addOne(qsl("er"));
-	addOne(qsl("es"));
-	addOne(qsl("et"));
-	addOne(qsl("eu"));
-	addOne(qsl("fi"));
-	addOne(qsl("fj"));
-	addOne(qsl("fk"));
-	addOne(qsl("fm"));
-	addOne(qsl("fo"));
-	addOne(qsl("fr"));
-	addOne(qsl("ga"));
-	addOne(qsl("gd"));
-	addOne(qsl("ge"));
-	addOne(qsl("gf"));
-	addOne(qsl("gg"));
-	addOne(qsl("gh"));
-	addOne(qsl("gi"));
-	addOne(qsl("gl"));
-	addOne(qsl("gm"));
-	addOne(qsl("gn"));
-	addOne(qsl("gp"));
-	addOne(qsl("gq"));
-	addOne(qsl("gr"));
-	addOne(qsl("gs"));
-	addOne(qsl("gt"));
-	addOne(qsl("gu"));
-	addOne(qsl("gw"));
-	addOne(qsl("gy"));
-	addOne(qsl("hk"));
-	addOne(qsl("hm"));
-	addOne(qsl("hn"));
-	addOne(qsl("hr"));
-	addOne(qsl("ht"));
-	addOne(qsl("hu"));
-	addOne(qsl("id"));
-	addOne(qsl("ie"));
-	addOne(qsl("il"));
-	addOne(qsl("im"));
-	addOne(qsl("in"));
-	addOne(qsl("io"));
-	addOne(qsl("iq"));
-	addOne(qsl("ir"));
-	addOne(qsl("is"));
-	addOne(qsl("it"));
-	addOne(qsl("je"));
-	addOne(qsl("jm"));
-	addOne(qsl("jo"));
-	addOne(qsl("jp"));
-	addOne(qsl("ke"));
-	addOne(qsl("kg"));
-	addOne(qsl("kh"));
-	addOne(qsl("ki"));
-	addOne(qsl("km"));
-	addOne(qsl("kn"));
-	addOne(qsl("kp"));
-	addOne(qsl("kr"));
-	addOne(qsl("kw"));
-	addOne(qsl("ky"));
-	addOne(qsl("kz"));
-	addOne(qsl("la"));
-	addOne(qsl("lb"));
-	addOne(qsl("lc"));
-	addOne(qsl("li"));
-	addOne(qsl("lk"));
-	addOne(qsl("lr"));
-	addOne(qsl("ls"));
-	addOne(qsl("lt"));
-	addOne(qsl("lu"));
-	addOne(qsl("lv"));
-	addOne(qsl("ly"));
-	addOne(qsl("ma"));
-	addOne(qsl("mc"));
-	addOne(qsl("md"));
-	addOne(qsl("me"));
-	addOne(qsl("mg"));
-	addOne(qsl("mh"));
-	addOne(qsl("mk"));
-	addOne(qsl("ml"));
-	addOne(qsl("mm"));
-	addOne(qsl("mn"));
-	addOne(qsl("mo"));
-	addOne(qsl("mp"));
-	addOne(qsl("mq"));
-	addOne(qsl("mr"));
-	addOne(qsl("ms"));
-	addOne(qsl("mt"));
-	addOne(qsl("mu"));
-	addOne(qsl("mv"));
-	addOne(qsl("mw"));
-	addOne(qsl("mx"));
-	addOne(qsl("my"));
-	addOne(qsl("mz"));
-	addOne(qsl("na"));
-	addOne(qsl("nc"));
-	addOne(qsl("ne"));
-	addOne(qsl("nf"));
-	addOne(qsl("ng"));
-	addOne(qsl("ni"));
-	addOne(qsl("nl"));
-	addOne(qsl("no"));
-	addOne(qsl("np"));
-	addOne(qsl("nr"));
-	addOne(qsl("nu"));
-	addOne(qsl("nz"));
-	addOne(qsl("om"));
-	addOne(qsl("pa"));
-	addOne(qsl("pe"));
-	addOne(qsl("pf"));
-	addOne(qsl("pg"));
-	addOne(qsl("ph"));
-	addOne(qsl("pk"));
-	addOne(qsl("pl"));
-	addOne(qsl("pm"));
-	addOne(qsl("pn"));
-	addOne(qsl("pr"));
-	addOne(qsl("ps"));
-	addOne(qsl("pt"));
-	addOne(qsl("pw"));
-	addOne(qsl("py"));
-	addOne(qsl("qa"));
-	addOne(qsl("re"));
-	addOne(qsl("ro"));
-	addOne(qsl("ru"));
-	addOne(qsl("rs"));
-	addOne(qsl("rw"));
-	addOne(qsl("sa"));
-	addOne(qsl("sb"));
-	addOne(qsl("sc"));
-	addOne(qsl("sd"));
-	addOne(qsl("se"));
-	addOne(qsl("sg"));
-	addOne(qsl("sh"));
-	addOne(qsl("si"));
-	addOne(qsl("sj"));
-	addOne(qsl("sk"));
-	addOne(qsl("sl"));
-	addOne(qsl("sm"));
-	addOne(qsl("sn"));
-	addOne(qsl("so"));
-	addOne(qsl("sr"));
-	addOne(qsl("ss"));
-	addOne(qsl("st"));
-	addOne(qsl("su"));
-	addOne(qsl("sv"));
-	addOne(qsl("sx"));
-	addOne(qsl("sy"));
-	addOne(qsl("sz"));
-	addOne(qsl("tc"));
-	addOne(qsl("td"));
-	addOne(qsl("tf"));
-	addOne(qsl("tg"));
-	addOne(qsl("th"));
-	addOne(qsl("tj"));
-	addOne(qsl("tk"));
-	addOne(qsl("tl"));
-	addOne(qsl("tm"));
-	addOne(qsl("tn"));
-	addOne(qsl("to"));
-	addOne(qsl("tp"));
-	addOne(qsl("tr"));
-	addOne(qsl("tt"));
-	addOne(qsl("tv"));
-	addOne(qsl("tw"));
-	addOne(qsl("tz"));
-	addOne(qsl("ua"));
-	addOne(qsl("ug"));
-	addOne(qsl("uk"));
-	addOne(qsl("um"));
-	addOne(qsl("us"));
-	addOne(qsl("uy"));
-	addOne(qsl("uz"));
-	addOne(qsl("va"));
-	addOne(qsl("vc"));
-	addOne(qsl("ve"));
-	addOne(qsl("vg"));
-	addOne(qsl("vi"));
-	addOne(qsl("vn"));
-	addOne(qsl("vu"));
-	addOne(qsl("wf"));
-	addOne(qsl("ws"));
-	addOne(qsl("ye"));
-	addOne(qsl("yt"));
-	addOne(qsl("yu"));
-	addOne(qsl("za"));
-	addOne(qsl("zm"));
-	addOne(qsl("zw"));
-	addOne(qsl("arpa"));
-	addOne(qsl("aero"));
-	addOne(qsl("asia"));
-	addOne(qsl("biz"));
-	addOne(qsl("cat"));
-	addOne(qsl("com"));
-	addOne(qsl("coop"));
-	addOne(qsl("info"));
-	addOne(qsl("int"));
-	addOne(qsl("jobs"));
-	addOne(qsl("mobi"));
-	addOne(qsl("museum"));
-	addOne(qsl("name"));
-	addOne(qsl("net"));
-	addOne(qsl("org"));
-	addOne(qsl("post"));
-	addOne(qsl("pro"));
-	addOne(qsl("tel"));
-	addOne(qsl("travel"));
-	addOne(qsl("xxx"));
-	addOne(qsl("edu"));
-	addOne(qsl("gov"));
-	addOne(qsl("mil"));
-	addOne(qsl("local"));
-	addOne(qsl("xn--lgbbat1ad8j"));
-	addOne(qsl("xn--54b7fta0cc"));
-	addOne(qsl("xn--fiqs8s"));
-	addOne(qsl("xn--fiqz9s"));
-	addOne(qsl("xn--wgbh1c"));
-	addOne(qsl("xn--node"));
-	addOne(qsl("xn--j6w193g"));
-	addOne(qsl("xn--h2brj9c"));
-	addOne(qsl("xn--mgbbh1a71e"));
-	addOne(qsl("xn--fpcrj9c3d"));
-	addOne(qsl("xn--gecrj9c"));
-	addOne(qsl("xn--s9brj9c"));
-	addOne(qsl("xn--xkc2dl3a5ee0h"));
-	addOne(qsl("xn--45brj9c"));
-	addOne(qsl("xn--mgba3a4f16a"));
-	addOne(qsl("xn--mgbayh7gpa"));
-	addOne(qsl("xn--80ao21a"));
-	addOne(qsl("xn--mgbx4cd0ab"));
-	addOne(qsl("xn--l1acc"));
-	addOne(qsl("xn--mgbc0a9azcg"));
-	addOne(qsl("xn--mgb9awbf"));
-	addOne(qsl("xn--mgbai9azgqp6j"));
-	addOne(qsl("xn--ygbi2ammx"));
-	addOne(qsl("xn--wgbl6a"));
-	addOne(qsl("xn--p1ai"));
-	addOne(qsl("xn--mgberp4a5d4ar"));
-	addOne(qsl("xn--90a3ac"));
-	addOne(qsl("xn--yfro4i67o"));
-	addOne(qsl("xn--clchc0ea0b2g2a9gcd"));
-	addOne(qsl("xn--3e0b707e"));
-	addOne(qsl("xn--fzc2c9e2c"));
-	addOne(qsl("xn--xkc2al3hye2a"));
-	addOne(qsl("xn--mgbtf8fl"));
-	addOne(qsl("xn--kprw13d"));
-	addOne(qsl("xn--kpry57d"));
-	addOne(qsl("xn--o3cw4h"));
-	addOne(qsl("xn--pgbs0dh"));
-	addOne(qsl("xn--j1amh"));
-	addOne(qsl("xn--mgbaam7a8h"));
-	addOne(qsl("xn--mgb2ddes"));
-	addOne(qsl("xn--ogbpf8fl"));
+	addOne(QString::fromLatin1("ac"));
+	addOne(QString::fromLatin1("ad"));
+	addOne(QString::fromLatin1("ae"));
+	addOne(QString::fromLatin1("af"));
+	addOne(QString::fromLatin1("ag"));
+	addOne(QString::fromLatin1("ai"));
+	addOne(QString::fromLatin1("al"));
+	addOne(QString::fromLatin1("am"));
+	addOne(QString::fromLatin1("an"));
+	addOne(QString::fromLatin1("ao"));
+	addOne(QString::fromLatin1("aq"));
+	addOne(QString::fromLatin1("ar"));
+	addOne(QString::fromLatin1("as"));
+	addOne(QString::fromLatin1("at"));
+	addOne(QString::fromLatin1("au"));
+	addOne(QString::fromLatin1("aw"));
+	addOne(QString::fromLatin1("ax"));
+	addOne(QString::fromLatin1("az"));
+	addOne(QString::fromLatin1("ba"));
+	addOne(QString::fromLatin1("bb"));
+	addOne(QString::fromLatin1("bd"));
+	addOne(QString::fromLatin1("be"));
+	addOne(QString::fromLatin1("bf"));
+	addOne(QString::fromLatin1("bg"));
+	addOne(QString::fromLatin1("bh"));
+	addOne(QString::fromLatin1("bi"));
+	addOne(QString::fromLatin1("bj"));
+	addOne(QString::fromLatin1("bm"));
+	addOne(QString::fromLatin1("bn"));
+	addOne(QString::fromLatin1("bo"));
+	addOne(QString::fromLatin1("br"));
+	addOne(QString::fromLatin1("bs"));
+	addOne(QString::fromLatin1("bt"));
+	addOne(QString::fromLatin1("bv"));
+	addOne(QString::fromLatin1("bw"));
+	addOne(QString::fromLatin1("by"));
+	addOne(QString::fromLatin1("bz"));
+	addOne(QString::fromLatin1("ca"));
+	addOne(QString::fromLatin1("cc"));
+	addOne(QString::fromLatin1("cd"));
+	addOne(QString::fromLatin1("cf"));
+	addOne(QString::fromLatin1("cg"));
+	addOne(QString::fromLatin1("ch"));
+	addOne(QString::fromLatin1("ci"));
+	addOne(QString::fromLatin1("ck"));
+	addOne(QString::fromLatin1("cl"));
+	addOne(QString::fromLatin1("cm"));
+	addOne(QString::fromLatin1("cn"));
+	addOne(QString::fromLatin1("co"));
+	addOne(QString::fromLatin1("cr"));
+	addOne(QString::fromLatin1("cu"));
+	addOne(QString::fromLatin1("cv"));
+	addOne(QString::fromLatin1("cx"));
+	addOne(QString::fromLatin1("cy"));
+	addOne(QString::fromLatin1("cz"));
+	addOne(QString::fromLatin1("de"));
+	addOne(QString::fromLatin1("dj"));
+	addOne(QString::fromLatin1("dk"));
+	addOne(QString::fromLatin1("dm"));
+	addOne(QString::fromLatin1("do"));
+	addOne(QString::fromLatin1("dz"));
+	addOne(QString::fromLatin1("ec"));
+	addOne(QString::fromLatin1("ee"));
+	addOne(QString::fromLatin1("eg"));
+	addOne(QString::fromLatin1("eh"));
+	addOne(QString::fromLatin1("er"));
+	addOne(QString::fromLatin1("es"));
+	addOne(QString::fromLatin1("et"));
+	addOne(QString::fromLatin1("eu"));
+	addOne(QString::fromLatin1("fi"));
+	addOne(QString::fromLatin1("fj"));
+	addOne(QString::fromLatin1("fk"));
+	addOne(QString::fromLatin1("fm"));
+	addOne(QString::fromLatin1("fo"));
+	addOne(QString::fromLatin1("fr"));
+	addOne(QString::fromLatin1("ga"));
+	addOne(QString::fromLatin1("gd"));
+	addOne(QString::fromLatin1("ge"));
+	addOne(QString::fromLatin1("gf"));
+	addOne(QString::fromLatin1("gg"));
+	addOne(QString::fromLatin1("gh"));
+	addOne(QString::fromLatin1("gi"));
+	addOne(QString::fromLatin1("gl"));
+	addOne(QString::fromLatin1("gm"));
+	addOne(QString::fromLatin1("gn"));
+	addOne(QString::fromLatin1("gp"));
+	addOne(QString::fromLatin1("gq"));
+	addOne(QString::fromLatin1("gr"));
+	addOne(QString::fromLatin1("gs"));
+	addOne(QString::fromLatin1("gt"));
+	addOne(QString::fromLatin1("gu"));
+	addOne(QString::fromLatin1("gw"));
+	addOne(QString::fromLatin1("gy"));
+	addOne(QString::fromLatin1("hk"));
+	addOne(QString::fromLatin1("hm"));
+	addOne(QString::fromLatin1("hn"));
+	addOne(QString::fromLatin1("hr"));
+	addOne(QString::fromLatin1("ht"));
+	addOne(QString::fromLatin1("hu"));
+	addOne(QString::fromLatin1("id"));
+	addOne(QString::fromLatin1("ie"));
+	addOne(QString::fromLatin1("il"));
+	addOne(QString::fromLatin1("im"));
+	addOne(QString::fromLatin1("in"));
+	addOne(QString::fromLatin1("io"));
+	addOne(QString::fromLatin1("iq"));
+	addOne(QString::fromLatin1("ir"));
+	addOne(QString::fromLatin1("is"));
+	addOne(QString::fromLatin1("it"));
+	addOne(QString::fromLatin1("je"));
+	addOne(QString::fromLatin1("jm"));
+	addOne(QString::fromLatin1("jo"));
+	addOne(QString::fromLatin1("jp"));
+	addOne(QString::fromLatin1("ke"));
+	addOne(QString::fromLatin1("kg"));
+	addOne(QString::fromLatin1("kh"));
+	addOne(QString::fromLatin1("ki"));
+	addOne(QString::fromLatin1("km"));
+	addOne(QString::fromLatin1("kn"));
+	addOne(QString::fromLatin1("kp"));
+	addOne(QString::fromLatin1("kr"));
+	addOne(QString::fromLatin1("kw"));
+	addOne(QString::fromLatin1("ky"));
+	addOne(QString::fromLatin1("kz"));
+	addOne(QString::fromLatin1("la"));
+	addOne(QString::fromLatin1("lb"));
+	addOne(QString::fromLatin1("lc"));
+	addOne(QString::fromLatin1("li"));
+	addOne(QString::fromLatin1("lk"));
+	addOne(QString::fromLatin1("lr"));
+	addOne(QString::fromLatin1("ls"));
+	addOne(QString::fromLatin1("lt"));
+	addOne(QString::fromLatin1("lu"));
+	addOne(QString::fromLatin1("lv"));
+	addOne(QString::fromLatin1("ly"));
+	addOne(QString::fromLatin1("ma"));
+	addOne(QString::fromLatin1("mc"));
+	addOne(QString::fromLatin1("md"));
+	addOne(QString::fromLatin1("me"));
+	addOne(QString::fromLatin1("mg"));
+	addOne(QString::fromLatin1("mh"));
+	addOne(QString::fromLatin1("mk"));
+	addOne(QString::fromLatin1("ml"));
+	addOne(QString::fromLatin1("mm"));
+	addOne(QString::fromLatin1("mn"));
+	addOne(QString::fromLatin1("mo"));
+	addOne(QString::fromLatin1("mp"));
+	addOne(QString::fromLatin1("mq"));
+	addOne(QString::fromLatin1("mr"));
+	addOne(QString::fromLatin1("ms"));
+	addOne(QString::fromLatin1("mt"));
+	addOne(QString::fromLatin1("mu"));
+	addOne(QString::fromLatin1("mv"));
+	addOne(QString::fromLatin1("mw"));
+	addOne(QString::fromLatin1("mx"));
+	addOne(QString::fromLatin1("my"));
+	addOne(QString::fromLatin1("mz"));
+	addOne(QString::fromLatin1("na"));
+	addOne(QString::fromLatin1("nc"));
+	addOne(QString::fromLatin1("ne"));
+	addOne(QString::fromLatin1("nf"));
+	addOne(QString::fromLatin1("ng"));
+	addOne(QString::fromLatin1("ni"));
+	addOne(QString::fromLatin1("nl"));
+	addOne(QString::fromLatin1("no"));
+	addOne(QString::fromLatin1("np"));
+	addOne(QString::fromLatin1("nr"));
+	addOne(QString::fromLatin1("nu"));
+	addOne(QString::fromLatin1("nz"));
+	addOne(QString::fromLatin1("om"));
+	addOne(QString::fromLatin1("pa"));
+	addOne(QString::fromLatin1("pe"));
+	addOne(QString::fromLatin1("pf"));
+	addOne(QString::fromLatin1("pg"));
+	addOne(QString::fromLatin1("ph"));
+	addOne(QString::fromLatin1("pk"));
+	addOne(QString::fromLatin1("pl"));
+	addOne(QString::fromLatin1("pm"));
+	addOne(QString::fromLatin1("pn"));
+	addOne(QString::fromLatin1("pr"));
+	addOne(QString::fromLatin1("ps"));
+	addOne(QString::fromLatin1("pt"));
+	addOne(QString::fromLatin1("pw"));
+	addOne(QString::fromLatin1("py"));
+	addOne(QString::fromLatin1("qa"));
+	addOne(QString::fromLatin1("re"));
+	addOne(QString::fromLatin1("ro"));
+	addOne(QString::fromLatin1("ru"));
+	addOne(QString::fromLatin1("rs"));
+	addOne(QString::fromLatin1("rw"));
+	addOne(QString::fromLatin1("sa"));
+	addOne(QString::fromLatin1("sb"));
+	addOne(QString::fromLatin1("sc"));
+	addOne(QString::fromLatin1("sd"));
+	addOne(QString::fromLatin1("se"));
+	addOne(QString::fromLatin1("sg"));
+	addOne(QString::fromLatin1("sh"));
+	addOne(QString::fromLatin1("si"));
+	addOne(QString::fromLatin1("sj"));
+	addOne(QString::fromLatin1("sk"));
+	addOne(QString::fromLatin1("sl"));
+	addOne(QString::fromLatin1("sm"));
+	addOne(QString::fromLatin1("sn"));
+	addOne(QString::fromLatin1("so"));
+	addOne(QString::fromLatin1("sr"));
+	addOne(QString::fromLatin1("ss"));
+	addOne(QString::fromLatin1("st"));
+	addOne(QString::fromLatin1("su"));
+	addOne(QString::fromLatin1("sv"));
+	addOne(QString::fromLatin1("sx"));
+	addOne(QString::fromLatin1("sy"));
+	addOne(QString::fromLatin1("sz"));
+	addOne(QString::fromLatin1("tc"));
+	addOne(QString::fromLatin1("td"));
+	addOne(QString::fromLatin1("tf"));
+	addOne(QString::fromLatin1("tg"));
+	addOne(QString::fromLatin1("th"));
+	addOne(QString::fromLatin1("tj"));
+	addOne(QString::fromLatin1("tk"));
+	addOne(QString::fromLatin1("tl"));
+	addOne(QString::fromLatin1("tm"));
+	addOne(QString::fromLatin1("tn"));
+	addOne(QString::fromLatin1("to"));
+	addOne(QString::fromLatin1("tp"));
+	addOne(QString::fromLatin1("tr"));
+	addOne(QString::fromLatin1("tt"));
+	addOne(QString::fromLatin1("tv"));
+	addOne(QString::fromLatin1("tw"));
+	addOne(QString::fromLatin1("tz"));
+	addOne(QString::fromLatin1("ua"));
+	addOne(QString::fromLatin1("ug"));
+	addOne(QString::fromLatin1("uk"));
+	addOne(QString::fromLatin1("um"));
+	addOne(QString::fromLatin1("us"));
+	addOne(QString::fromLatin1("uy"));
+	addOne(QString::fromLatin1("uz"));
+	addOne(QString::fromLatin1("va"));
+	addOne(QString::fromLatin1("vc"));
+	addOne(QString::fromLatin1("ve"));
+	addOne(QString::fromLatin1("vg"));
+	addOne(QString::fromLatin1("vi"));
+	addOne(QString::fromLatin1("vn"));
+	addOne(QString::fromLatin1("vu"));
+	addOne(QString::fromLatin1("wf"));
+	addOne(QString::fromLatin1("ws"));
+	addOne(QString::fromLatin1("ye"));
+	addOne(QString::fromLatin1("yt"));
+	addOne(QString::fromLatin1("yu"));
+	addOne(QString::fromLatin1("za"));
+	addOne(QString::fromLatin1("zm"));
+	addOne(QString::fromLatin1("zw"));
+	addOne(QString::fromLatin1("arpa"));
+	addOne(QString::fromLatin1("aero"));
+	addOne(QString::fromLatin1("asia"));
+	addOne(QString::fromLatin1("biz"));
+	addOne(QString::fromLatin1("cat"));
+	addOne(QString::fromLatin1("com"));
+	addOne(QString::fromLatin1("coop"));
+	addOne(QString::fromLatin1("info"));
+	addOne(QString::fromLatin1("int"));
+	addOne(QString::fromLatin1("jobs"));
+	addOne(QString::fromLatin1("mobi"));
+	addOne(QString::fromLatin1("museum"));
+	addOne(QString::fromLatin1("name"));
+	addOne(QString::fromLatin1("net"));
+	addOne(QString::fromLatin1("org"));
+	addOne(QString::fromLatin1("post"));
+	addOne(QString::fromLatin1("pro"));
+	addOne(QString::fromLatin1("tel"));
+	addOne(QString::fromLatin1("travel"));
+	addOne(QString::fromLatin1("xxx"));
+	addOne(QString::fromLatin1("edu"));
+	addOne(QString::fromLatin1("gov"));
+	addOne(QString::fromLatin1("mil"));
+	addOne(QString::fromLatin1("local"));
+	addOne(QString::fromLatin1("xn--lgbbat1ad8j"));
+	addOne(QString::fromLatin1("xn--54b7fta0cc"));
+	addOne(QString::fromLatin1("xn--fiqs8s"));
+	addOne(QString::fromLatin1("xn--fiqz9s"));
+	addOne(QString::fromLatin1("xn--wgbh1c"));
+	addOne(QString::fromLatin1("xn--node"));
+	addOne(QString::fromLatin1("xn--j6w193g"));
+	addOne(QString::fromLatin1("xn--h2brj9c"));
+	addOne(QString::fromLatin1("xn--mgbbh1a71e"));
+	addOne(QString::fromLatin1("xn--fpcrj9c3d"));
+	addOne(QString::fromLatin1("xn--gecrj9c"));
+	addOne(QString::fromLatin1("xn--s9brj9c"));
+	addOne(QString::fromLatin1("xn--xkc2dl3a5ee0h"));
+	addOne(QString::fromLatin1("xn--45brj9c"));
+	addOne(QString::fromLatin1("xn--mgba3a4f16a"));
+	addOne(QString::fromLatin1("xn--mgbayh7gpa"));
+	addOne(QString::fromLatin1("xn--80ao21a"));
+	addOne(QString::fromLatin1("xn--mgbx4cd0ab"));
+	addOne(QString::fromLatin1("xn--l1acc"));
+	addOne(QString::fromLatin1("xn--mgbc0a9azcg"));
+	addOne(QString::fromLatin1("xn--mgb9awbf"));
+	addOne(QString::fromLatin1("xn--mgbai9azgqp6j"));
+	addOne(QString::fromLatin1("xn--ygbi2ammx"));
+	addOne(QString::fromLatin1("xn--wgbl6a"));
+	addOne(QString::fromLatin1("xn--p1ai"));
+	addOne(QString::fromLatin1("xn--mgberp4a5d4ar"));
+	addOne(QString::fromLatin1("xn--90a3ac"));
+	addOne(QString::fromLatin1("xn--yfro4i67o"));
+	addOne(QString::fromLatin1("xn--clchc0ea0b2g2a9gcd"));
+	addOne(QString::fromLatin1("xn--3e0b707e"));
+	addOne(QString::fromLatin1("xn--fzc2c9e2c"));
+	addOne(QString::fromLatin1("xn--xkc2al3hye2a"));
+	addOne(QString::fromLatin1("xn--mgbtf8fl"));
+	addOne(QString::fromLatin1("xn--kprw13d"));
+	addOne(QString::fromLatin1("xn--kpry57d"));
+	addOne(QString::fromLatin1("xn--o3cw4h"));
+	addOne(QString::fromLatin1("xn--pgbs0dh"));
+	addOne(QString::fromLatin1("xn--j1amh"));
+	addOne(QString::fromLatin1("xn--mgbaam7a8h"));
+	addOne(QString::fromLatin1("xn--mgb2ddes"));
+	addOne(QString::fromLatin1("xn--ogbpf8fl"));
 	addOne(QString::fromUtf8("\xd1\x80\xd1\x84"));
 	return result;
 }
@@ -1132,7 +1136,58 @@ inline QChar RemoveOneAccent(uint32 code) {
 }
 
 const QRegularExpression &RegExpWordSplit() {
-	static const auto result = QRegularExpression (qsl("[\\@\\s\\-\\+\\(\\)\\[\\]\\{\\}\\<\\>\\,\\.\\:\\!\\_\\;\\\"\\'\\x0]"));
+	static const auto result = QRegularExpression(QString::fromLatin1("[\\@\\s\\-\\+\\(\\)\\[\\]\\{\\}\\<\\>\\,\\.\\:\\!\\_\\;\\\"\\'\\x0]"));
+	return result;
+}
+
+[[nodiscard]] QString ExpandCustomLinks(const TextWithTags &text) {
+	const auto entities = ConvertTextTagsToEntities(text.tags);
+	auto &&urls = ranges::make_iterator_range(
+		entities.begin(),
+		entities.end()
+	) | ranges::view::filter([](const EntityInText &entity) {
+		return entity.type() == EntityType::CustomUrl;
+	});
+	const auto &original = text.text;
+	if (urls.begin() == urls.end()) {
+		return original;
+	}
+	auto result = QString();
+	auto offset = 0;
+	for (const auto &entity : urls) {
+		const auto till = entity.offset() + entity.length();
+		if (till > offset) {
+			result.append(original.midRef(offset, till - offset));
+		}
+		result.append(qstr(" (")).append(entity.data()).append(')');
+		offset = till;
+	}
+	if (original.size() > offset) {
+		result.append(original.midRef(offset));
+	}
+	return result;
+}
+
+std::unique_ptr<QMimeData> MimeDataFromText(
+		TextWithTags &&text,
+		const QString &expanded) {
+	if (expanded.isEmpty()) {
+		return nullptr;
+	}
+
+	auto result = std::make_unique<QMimeData>();
+	result->setText(expanded);
+	if (!text.tags.isEmpty()) {
+		for (auto &tag : text.tags) {
+			tag.id = Ui::Integration::Instance().convertTagToMimeTag(tag.id);
+		}
+		result->setData(
+			TextUtilities::TagsTextMimeType(),
+			text.text.toUtf8());
+		result->setData(
+			TextUtilities::TagsMimeType(),
+			TextUtilities::SerializeTags(text.tags));
+	}
 	return result;
 }
 
@@ -1168,7 +1223,7 @@ QString MarkdownBoldGoodBefore() {
 }
 
 QString MarkdownBoldBadAfter() {
-	return qsl("*");
+	return QString::fromLatin1("*");
 }
 
 QString MarkdownItalicGoodBefore() {
@@ -1176,7 +1231,7 @@ QString MarkdownItalicGoodBefore() {
 }
 
 QString MarkdownItalicBadAfter() {
-	return qsl("_");
+	return QString::fromLatin1("_");
 }
 
 QString MarkdownStrikeOutGoodBefore() {
@@ -1184,7 +1239,7 @@ QString MarkdownStrikeOutGoodBefore() {
 }
 
 QString MarkdownStrikeOutBadAfter() {
-	return qsl("~");
+	return QString::fromLatin1("~");
 }
 
 QString MarkdownCodeGoodBefore() {
@@ -1192,7 +1247,7 @@ QString MarkdownCodeGoodBefore() {
 }
 
 QString MarkdownCodeBadAfter() {
-	return qsl("`\n\r");
+	return QString::fromLatin1("`\n\r");
 }
 
 QString MarkdownPreGoodBefore() {
@@ -1200,17 +1255,17 @@ QString MarkdownPreGoodBefore() {
 }
 
 QString MarkdownPreBadAfter() {
-	return qsl("`");
+	return QString::fromLatin1("`");
 }
 
 bool IsValidProtocol(const QString &protocol) {
 	static const auto list = CreateValidProtocols();
-	return list.contains(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar)));
+	return list.contains(base::crc32(protocol.constData(), protocol.size() * sizeof(QChar)));
 }
 
 bool IsValidTopDomain(const QString &protocol) {
 	static const auto list = CreateValidTopDomains();
-	return list.contains(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar)));
+	return list.contains(base::crc32(protocol.constData(), protocol.size() * sizeof(QChar)));
 }
 
 QString Clean(const QString &text) {
@@ -1321,14 +1376,19 @@ QString RemoveEmoji(const QString &text) {
 	return result;
 }
 
-QStringList PrepareSearchWords(const QString &query, const QRegularExpression *SplitterOverride) {
+QStringList PrepareSearchWords(
+		const QString &query,
+		const QRegularExpression *SplitterOverride) {
 	auto clean = RemoveAccents(query.trimmed().toLower());
 	auto result = QStringList();
 	if (!clean.isEmpty()) {
-		auto list = clean.split(SplitterOverride ? *SplitterOverride : RegExpWordSplit(), QString::SkipEmptyParts);
+		auto list = clean.split(SplitterOverride
+			? *SplitterOverride
+			: RegExpWordSplit(),
+			QString::SkipEmptyParts);
 		auto size = list.size();
 		result.reserve(list.size());
-		for_const (auto &word, list) {
+		for (const auto &word : std::as_const(list)) {
 			auto trimmed = word.trimmed();
 			if (!trimmed.isEmpty()) {
 				result.push_back(trimmed);
@@ -1488,112 +1548,6 @@ bool checkTagStartInCommand(const QChar *start, int32 len, int32 tagStart, int32
 	return inCommand;
 }
 
-EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities) {
-	auto result = EntitiesInText();
-	if (!entities.isEmpty()) {
-		result.reserve(entities.size());
-		for_const (auto &entity, entities) {
-			switch (entity.type()) {
-			case mtpc_messageEntityUrl: { auto &d = entity.c_messageEntityUrl(); result.push_back({ EntityType::Url, d.voffset().v, d.vlength().v }); } break;
-			case mtpc_messageEntityTextUrl: { auto &d = entity.c_messageEntityTextUrl(); result.push_back({ EntityType::CustomUrl, d.voffset().v, d.vlength().v, Clean(qs(d.vurl())) }); } break;
-			case mtpc_messageEntityEmail: { auto &d = entity.c_messageEntityEmail(); result.push_back({ EntityType::Email, d.voffset().v, d.vlength().v }); } break;
-			case mtpc_messageEntityHashtag: { auto &d = entity.c_messageEntityHashtag(); result.push_back({ EntityType::Hashtag, d.voffset().v, d.vlength().v }); } break;
-			case mtpc_messageEntityCashtag: { auto &d = entity.c_messageEntityCashtag(); result.push_back({ EntityType::Cashtag, d.voffset().v, d.vlength().v }); } break;
-			case mtpc_messageEntityPhone: break; // Skipping phones.
-			case mtpc_messageEntityMention: { auto &d = entity.c_messageEntityMention(); result.push_back({ EntityType::Mention, d.voffset().v, d.vlength().v }); } break;
-			case mtpc_messageEntityMentionName: {
-				auto &d = entity.c_messageEntityMentionName();
-				auto data = [&d] {
-					if (auto user = Auth().data().userLoaded(d.vuser_id().v)) {
-						return MentionNameDataFromFields({
-							d.vuser_id().v,
-							user->accessHash() });
-					}
-					return MentionNameDataFromFields(d.vuser_id().v);
-				};
-				result.push_back({ EntityType::MentionName, d.voffset().v, d.vlength().v, data() });
-			} break;
-			case mtpc_inputMessageEntityMentionName: {
-				auto &d = entity.c_inputMessageEntityMentionName();
-				auto data = ([&d]() -> QString {
-					if (d.vuser_id().type() == mtpc_inputUserSelf) {
-						return MentionNameDataFromFields(Auth().userId());
-					} else if (d.vuser_id().type() == mtpc_inputUser) {
-						auto &user = d.vuser_id().c_inputUser();
-						return MentionNameDataFromFields({ user.vuser_id().v, user.vaccess_hash().v });
-					}
-					return QString();
-				})();
-				if (!data.isEmpty()) {
-					result.push_back({ EntityType::MentionName, d.voffset().v, d.vlength().v, data });
-				}
-			} break;
-			case mtpc_messageEntityBotCommand: { auto &d = entity.c_messageEntityBotCommand(); result.push_back({ EntityType::BotCommand, d.voffset().v, d.vlength().v }); } break;
-			case mtpc_messageEntityBold: { auto &d = entity.c_messageEntityBold(); result.push_back({ EntityType::Bold, d.voffset().v, d.vlength().v }); } break;
-			case mtpc_messageEntityItalic: { auto &d = entity.c_messageEntityItalic(); result.push_back({ EntityType::Italic, d.voffset().v, d.vlength().v }); } break;
-			case mtpc_messageEntityUnderline: { auto &d = entity.c_messageEntityUnderline(); result.push_back({ EntityType::Underline, d.voffset().v, d.vlength().v }); } break;
-			case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset().v, d.vlength().v }); } break;
-			case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset().v, d.vlength().v }); } break;
-			case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, Clean(qs(d.vlanguage())) }); } break;
-				// #TODO entities
-			}
-		}
-	}
-	return result;
-}
-
-MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &entities, ConvertOption option) {
-	auto v = QVector<MTPMessageEntity>();
-	v.reserve(entities.size());
-	for_const (auto &entity, entities) {
-		if (entity.length() <= 0) continue;
-		if (option == ConvertOption::SkipLocal
-			&& entity.type() != EntityType::Bold
-			&& entity.type() != EntityType::Italic
-			&& entity.type() != EntityType::Underline
-			&& entity.type() != EntityType::StrikeOut
-			&& entity.type() != EntityType::Code // #TODO entities
-			&& entity.type() != EntityType::Pre
-			&& entity.type() != EntityType::MentionName
-			&& entity.type() != EntityType::CustomUrl) {
-			continue;
-		}
-
-		auto offset = MTP_int(entity.offset());
-		auto length = MTP_int(entity.length());
-		switch (entity.type()) {
-		case EntityType::Url: v.push_back(MTP_messageEntityUrl(offset, length)); break;
-		case EntityType::CustomUrl: v.push_back(MTP_messageEntityTextUrl(offset, length, MTP_string(entity.data()))); break;
-		case EntityType::Email: v.push_back(MTP_messageEntityEmail(offset, length)); break;
-		case EntityType::Hashtag: v.push_back(MTP_messageEntityHashtag(offset, length)); break;
-		case EntityType::Cashtag: v.push_back(MTP_messageEntityCashtag(offset, length)); break;
-		case EntityType::Mention: v.push_back(MTP_messageEntityMention(offset, length)); break;
-		case EntityType::MentionName: {
-			auto inputUser = ([](const QString &data) -> MTPInputUser {
-				auto fields = MentionNameDataToFields(data);
-				if (fields.userId == Auth().userId()) {
-					return MTP_inputUserSelf();
-				} else if (fields.userId) {
-					return MTP_inputUser(MTP_int(fields.userId), MTP_long(fields.accessHash));
-				}
-				return MTP_inputUserEmpty();
-			})(entity.data());
-			if (inputUser.type() != mtpc_inputUserEmpty) {
-				v.push_back(MTP_inputMessageEntityMentionName(offset, length, inputUser));
-			}
-		} break;
-		case EntityType::BotCommand: v.push_back(MTP_messageEntityBotCommand(offset, length)); break;
-		case EntityType::Bold: v.push_back(MTP_messageEntityBold(offset, length)); break;
-		case EntityType::Italic: v.push_back(MTP_messageEntityItalic(offset, length)); break;
-		case EntityType::Underline: v.push_back(MTP_messageEntityUnderline(offset, length)); break;
-		case EntityType::StrikeOut: v.push_back(MTP_messageEntityStrike(offset, length)); break;
-		case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break; // #TODO entities
-		case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
-		}
-	}
-	return MTP_vector<MTPMessageEntity>(std::move(v));
-}
-
 TextWithEntities ParseEntities(const QString &text, int32 flags) {
 	const auto rich = ((flags & TextParseRichText) != 0);
 	auto result = TextWithEntities{ text, EntitiesInText() };
@@ -1897,7 +1851,7 @@ void ApplyServerCleaning(TextWithEntities &result) {
 
 	// Replace tabs with two spaces.
 	if (auto tabs = std::count(result.text.cbegin(), result.text.cend(), '\t')) {
-		auto replacement = qsl("  ");
+		auto replacement = QString::fromLatin1("  ");
 		auto replacementLength = replacement.size();
 		auto shift = (replacementLength - 1);
 		result.text.resize(len + shift * tabs);
@@ -2022,11 +1976,111 @@ TextWithTags::Tags DeserializeTags(QByteArray data, int textLength) {
 }
 
 QString TagsMimeType() {
-	return qsl("application/x-td-field-tags");
+	return QString::fromLatin1("application/x-td-field-tags");
 }
 
 QString TagsTextMimeType() {
-	return qsl("application/x-td-field-text");
+	return QString::fromLatin1("application/x-td-field-text");
+}
+
+bool IsMentionLink(const QString &link) {
+	return link.startsWith(kMentionTagStart);
+}
+
+EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
+	EntitiesInText result;
+	if (tags.isEmpty()) {
+		return result;
+	}
+
+	result.reserve(tags.size());
+	for (const auto &tag : tags) {
+		const auto push = [&](
+				EntityType type,
+				const QString &data = QString()) {
+			result.push_back(
+				EntityInText(type, tag.offset, tag.length, data));
+		};
+		if (IsMentionLink(tag.id)) {
+			if (auto match = qthelp::regex_match("^(\\d+\\.\\d+)(/|$)", tag.id.midRef(kMentionTagStart.size()))) {
+				push(EntityType::MentionName, match->captured(1));
+			}
+		} else if (tag.id == Ui::InputField::kTagBold) {
+			push(EntityType::Bold);
+		} else if (tag.id == Ui::InputField::kTagItalic) {
+			push(EntityType::Italic);
+		} else if (tag.id == Ui::InputField::kTagUnderline) {
+			push(EntityType::Underline);
+		} else if (tag.id == Ui::InputField::kTagStrikeOut) {
+			push(EntityType::StrikeOut);
+		} else if (tag.id == Ui::InputField::kTagCode) {
+			push(EntityType::Code);
+		} else if (tag.id == Ui::InputField::kTagPre) { // #TODO entities
+			push(EntityType::Pre);
+		} else /*if (ValidateUrl(tag.id)) */{ // We validate when we insert.
+			push(EntityType::CustomUrl, tag.id);
+		}
+	}
+	return result;
+}
+
+TextWithTags::Tags ConvertEntitiesToTextTags(const EntitiesInText &entities) {
+	TextWithTags::Tags result;
+	if (entities.isEmpty()) {
+		return result;
+	}
+
+	result.reserve(entities.size());
+	for (const auto &entity : entities) {
+		const auto push = [&](const QString &tag) {
+			result.push_back({ entity.offset(), entity.length(), tag });
+		};
+		switch (entity.type()) {
+		case EntityType::MentionName: {
+			auto match = QRegularExpression(R"(^(\d+\.\d+)$)").match(entity.data());
+			if (match.hasMatch()) {
+				push(kMentionTagStart + entity.data());
+			}
+		} break;
+		case EntityType::CustomUrl: {
+			const auto url = entity.data();
+			if (Ui::InputField::IsValidMarkdownLink(url)
+				&& !IsMentionLink(url)) {
+				push(url);
+			}
+		} break;
+		case EntityType::Bold: push(Ui::InputField::kTagBold); break;
+		case EntityType::Italic: push(Ui::InputField::kTagItalic); break;
+		case EntityType::Underline:
+			push(Ui::InputField::kTagUnderline);
+			break;
+		case EntityType::StrikeOut:
+			push(Ui::InputField::kTagStrikeOut);
+			break;
+		case EntityType::Code: push(Ui::InputField::kTagCode); break; // #TODO entities
+		case EntityType::Pre: push(Ui::InputField::kTagPre); break;
+		}
+	}
+	return result;
+}
+
+std::unique_ptr<QMimeData> MimeDataFromText(const TextForMimeData &text) {
+	return MimeDataFromText(
+		{ text.rich.text, ConvertEntitiesToTextTags(text.rich.entities) },
+		text.expanded);
+}
+
+std::unique_ptr<QMimeData> MimeDataFromText(TextWithTags &&text) {
+	const auto expanded = ExpandCustomLinks(text);
+	return MimeDataFromText(std::move(text), expanded);
+}
+
+void SetClipboardText(
+		const TextForMimeData &text,
+		QClipboard::Mode mode) {
+	if (auto data = MimeDataFromText(text)) {
+		QGuiApplication::clipboard()->setMimeData(data.release(), mode);
+	}
 }
 
 } // namespace TextUtilities
@@ -2083,8 +2137,8 @@ TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original,
 					return;
 				}
 				auto newEnd = newOffset + replacementEntity->length();
-				newOffset = snap(newOffset, replacementPosition, replacementEnd);
-				newEnd = snap(newEnd, replacementPosition, replacementEnd);
+				newOffset = std::clamp(newOffset, replacementPosition, replacementEnd);
+				newEnd = std::clamp(newEnd, replacementPosition, replacementEnd);
 				if (auto newLength = newEnd - newOffset) {
 					result.entities.push_back({ replacementEntity->type(), newOffset, newLength, replacementEntity->data() });
 				}
@@ -2092,7 +2146,7 @@ TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original,
 			}
 		};
 
-		for_const (auto &entity, original.entities) {
+		for (const auto &entity : std::as_const(original.entities)) {
 			// Transform the entity by the replacement.
 			auto offset = entity.offset();
 			auto end = offset + entity.length();
@@ -2102,8 +2156,8 @@ TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original,
 			if (end > replacementPosition) {
 				end = end + replacement.text.size() - kTagReplacementSize;
 			}
-			offset = snap(offset, 0, result.text.size());
-			end = snap(end, 0, result.text.size());
+			offset = std::clamp(offset, 0, result.text.size());
+			end = std::clamp(end, 0, result.text.size());
 
 			// Add all replacement entities that start before the current original entity.
 			addReplacementEntitiesUntil(offset);
diff --git a/Telegram/SourceFiles/ui/text/text_entity.h b/Telegram/SourceFiles/ui/text/text_entity.h
index 921111edf..34ad388e2 100644
--- a/Telegram/SourceFiles/ui/text/text_entity.h
+++ b/Telegram/SourceFiles/ui/text/text_entity.h
@@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/basic_types.h"
 
 #include <QtCore/QList>
+#include <QtCore/QVector>
+#include <QtGui/QClipboard>
 
 enum class EntityType {
 	Invalid = 0,
@@ -311,13 +313,6 @@ inline QString MentionNameDataFromFields(const MentionNameFields &fields) {
 	return result;
 }
 
-EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities);
-enum class ConvertOption {
-	WithLocal,
-	SkipLocal,
-};
-MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &entities, ConvertOption option = ConvertOption::WithLocal);
-
 // New entities are added to the ones that are already in result.
 // Changes text if (flags & TextParseMarkdown).
 TextWithEntities ParseEntities(const QString &text, int32 flags);
@@ -345,6 +340,18 @@ TextWithTags::Tags DeserializeTags(QByteArray data, int textLength);
 QString TagsMimeType();
 QString TagsTextMimeType();
 
+inline const auto kMentionTagStart = qstr("mention://user.");
+
+[[nodiscard]] bool IsMentionLink(const QString &link);
+EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags);
+TextWithTags::Tags ConvertEntitiesToTextTags(
+	const EntitiesInText &entities);
+std::unique_ptr<QMimeData> MimeDataFromText(const TextForMimeData &text);
+std::unique_ptr<QMimeData> MimeDataFromText(TextWithTags &&text);
+void SetClipboardText(
+	const TextForMimeData &text,
+	QClipboard::Mode mode = QClipboard::Clipboard);
+
 } // namespace TextUtilities
 
 namespace Lang {
diff --git a/Telegram/SourceFiles/ui/text/text_utilities.cpp b/Telegram/SourceFiles/ui/text/text_utilities.cpp
index da700a297..99638d377 100644
--- a/Telegram/SourceFiles/ui/text/text_utilities.cpp
+++ b/Telegram/SourceFiles/ui/text/text_utilities.cpp
@@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "ui/text/text_utilities.h"
 
+#include "base/algorithm.h"
+
+#include <QtCore/QRegularExpression>
+
 namespace Ui {
 namespace Text {
 namespace {
diff --git a/Telegram/SourceFiles/ui/text/text_utilities.h b/Telegram/SourceFiles/ui/text/text_utilities.h
index 8df726b21..516d36ae8 100644
--- a/Telegram/SourceFiles/ui/text/text_utilities.h
+++ b/Telegram/SourceFiles/ui/text/text_utilities.h
@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
+#include "ui/text/text_entity.h"
+
 namespace Ui {
 namespace Text {
 namespace details {
diff --git a/Telegram/SourceFiles/ui/text_options.h b/Telegram/SourceFiles/ui/text_options.h
index fe84377b6..d40d292a1 100644
--- a/Telegram/SourceFiles/ui/text_options.h
+++ b/Telegram/SourceFiles/ui/text_options.h
@@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 class History;
 class PeerData;
 
+struct TextParseOptions;
+
 namespace Ui {
 
 void InitTextOptions();
diff --git a/Telegram/SourceFiles/ui/ui_integration.cpp b/Telegram/SourceFiles/ui/ui_integration.cpp
new file mode 100644
index 000000000..c3609e23e
--- /dev/null
+++ b/Telegram/SourceFiles/ui/ui_integration.cpp
@@ -0,0 +1,120 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "ui/ui_integration.h"
+
+#include "ui/text/text_entity.h"
+#include "ui/basic_click_handlers.h"
+
+namespace Ui {
+namespace {
+
+Integration *IntegrationInstance = nullptr;
+
+} // namespace
+
+void Integration::Set(not_null<Integration*> instance) {
+	IntegrationInstance = instance;
+}
+
+Integration &Integration::Instance() {
+	Expects(IntegrationInstance != nullptr);
+
+	return *IntegrationInstance;
+}
+
+void Integration::textActionsUpdated() {
+}
+
+void Integration::activationFromTopPanel() {		
+}
+	
+std::shared_ptr<ClickHandler> Integration::createLinkHandler(
+		EntityType type,
+		const QString &text,
+		const QString &data,
+		const TextParseOptions &options) {
+	switch (type) {
+	case EntityType::CustomUrl:
+		return !data.isEmpty()
+			? std::make_shared<UrlClickHandler>(data, false)
+			: nullptr;
+	}
+	return nullptr;
+}
+
+bool Integration::handleUrlClick(
+		const QString &url,
+		const QVariant &context) {
+	return false;
+}
+
+QString Integration::convertTagToMimeTag(const QString &tagId) {
+	return tagId;
+}
+
+const Emoji::One *Integration::defaultEmojiVariant(const Emoji::One *emoji) {
+	return emoji;
+}
+
+rpl::producer<> Integration::forcePopupMenuHideRequests() {
+	return rpl::never<rpl::empty_value>();
+}
+
+QString Integration::phraseContextCopyText() {
+	return "Copy text";
+}
+
+QString Integration::phraseContextCopyEmail() {
+	return "Copy email";
+}
+
+QString Integration::phraseContextCopyLink() {
+	return "Copy link";
+}
+
+QString Integration::phraseContextCopySelected() {
+	return "Copy to clipboard";
+}
+
+QString Integration::phraseFormattingTitle() {
+	return "Formatting";
+}
+
+QString Integration::phraseFormattingLinkCreate() {
+	return "Create link";
+}
+
+QString Integration::phraseFormattingLinkEdit() {
+	return "Edit link";
+}
+
+QString Integration::phraseFormattingClear() {
+	return "Plain text";
+}
+
+QString Integration::phraseFormattingBold() {
+	return "Bold";
+}
+
+QString Integration::phraseFormattingItalic() {
+	return "Italic";
+}
+
+QString Integration::phraseFormattingUnderline() {
+	return "Underline";
+}
+
+QString Integration::phraseFormattingStrikeOut() {
+	return "Strike-through";
+}
+
+QString Integration::phraseFormattingMonospace() {
+	return "Monospace";
+}
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/ui_integration.h b/Telegram/SourceFiles/ui/ui_integration.h
index 8a3c33b74..01c1f8814 100644
--- a/Telegram/SourceFiles/ui/ui_integration.h
+++ b/Telegram/SourceFiles/ui/ui_integration.h
@@ -11,10 +11,62 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 // Methods that must be implemented outside lib_ui.
 
-namespace Ui {
+class QString;
+class QWidget;
+class QVariant;
 
-void PostponeCall(FnMut<void()> &&callable);
-void RegisterLeaveSubscription(not_null<QWidget*> widget);
-void UnregisterLeaveSubscription(not_null<QWidget*> widget);
+struct TextParseOptions;
+class ClickHandler;
+enum class EntityType;
+
+namespace Ui {
+namespace Emoji {
+class One;
+} // namespace Emoji
+
+class Integration {
+public:
+	static void Set(not_null<Integration*> instance);
+	static Integration &Instance();
+
+	virtual void postponeCall(FnMut<void()> &&callable) = 0;
+	virtual void registerLeaveSubscription(not_null<QWidget*> widget) = 0;
+	virtual void unregisterLeaveSubscription(not_null<QWidget*> widget) = 0;
+
+	virtual void writeLogEntry(const QString &entry) = 0;
+	[[nodiscard]] virtual QString emojiCacheFolder() = 0;
+
+	virtual void textActionsUpdated();
+	virtual void activationFromTopPanel();
+
+	[[nodiscard]] virtual std::shared_ptr<ClickHandler> createLinkHandler(
+		EntityType type,
+		const QString &text,
+		const QString &data,
+		const TextParseOptions &options);
+	[[nodiscard]] virtual bool handleUrlClick(
+		const QString &url,
+		const QVariant &context);
+	[[nodiscard]] virtual QString convertTagToMimeTag(const QString &tagId);
+	[[nodiscard]] virtual const Emoji::One *defaultEmojiVariant(
+		const Emoji::One *emoji);
+
+	[[nodiscard]] virtual rpl::producer<> forcePopupMenuHideRequests();
+
+	[[nodiscard]] virtual QString phraseContextCopyText();
+	[[nodiscard]] virtual QString phraseContextCopyEmail();
+	[[nodiscard]] virtual QString phraseContextCopyLink();
+	[[nodiscard]] virtual QString phraseContextCopySelected();
+	[[nodiscard]] virtual QString phraseFormattingTitle();
+	[[nodiscard]] virtual QString phraseFormattingLinkCreate();
+	[[nodiscard]] virtual QString phraseFormattingLinkEdit();
+	[[nodiscard]] virtual QString phraseFormattingClear();
+	[[nodiscard]] virtual QString phraseFormattingBold();
+	[[nodiscard]] virtual QString phraseFormattingItalic();
+	[[nodiscard]] virtual QString phraseFormattingUnderline();
+	[[nodiscard]] virtual QString phraseFormattingStrikeOut();
+	[[nodiscard]] virtual QString phraseFormattingMonospace();
+
+};
 
 } // namespace Ui
diff --git a/Telegram/SourceFiles/ui/ui_log.cpp b/Telegram/SourceFiles/ui/ui_log.cpp
new file mode 100644
index 000000000..578ce51a7
--- /dev/null
+++ b/Telegram/SourceFiles/ui/ui_log.cpp
@@ -0,0 +1,18 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "ui/ui_log.h"
+
+#include "ui/ui_integration.h"
+
+namespace Ui {
+
+void WriteLogEntry(const QString &message) {
+	Integration::Instance().writeLogEntry(message);
+}
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/ui_log.h b/Telegram/SourceFiles/ui/ui_log.h
new file mode 100644
index 000000000..f67e77e97
--- /dev/null
+++ b/Telegram/SourceFiles/ui/ui_log.h
@@ -0,0 +1,16 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+namespace Ui {
+
+void WriteLogEntry(const QString &message);
+
+} // namespace Ui
+
+#define UI_LOG(message) (::Ui::WriteLogEntry(QString message))
diff --git a/Telegram/SourceFiles/ui/ui_pch.h b/Telegram/SourceFiles/ui/ui_pch.h
index 5210fd835..4daffd028 100644
--- a/Telegram/SourceFiles/ui/ui_pch.h
+++ b/Telegram/SourceFiles/ui/ui_pch.h
@@ -17,8 +17,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include <QtGui/QCursor>
 #include <QtGui/QFont>
 #include <QtGui/QFontMetrics>
+#include <QtGui/QPainter>
+#include <QtGui/QImage>
+#include <QtGui/QPixmap>
 
 #include <QtWidgets/QWidget>
 
 #include <rpl/rpl.h>
 #include <range/v3/all.hpp>
+#include <crl/crl_time.h>
+#include <crl/crl_on_main.h>
+
+#include "base/algorithm.h"
+#include "base/basic_types.h"
+#include "base/flat_map.h"
+#include "base/flat_set.h"
diff --git a/Telegram/SourceFiles/ui/ui_utility.cpp b/Telegram/SourceFiles/ui/ui_utility.cpp
index 5947332da..878d59197 100644
--- a/Telegram/SourceFiles/ui/ui_utility.cpp
+++ b/Telegram/SourceFiles/ui/ui_utility.cpp
@@ -7,8 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "ui/ui_utility.h"
 
+#include "ui/style/style_core.h"
+
 #include <QtGui/QWindow>
 #include <QtGui/QGuiApplication>
+#include <QtGui/QtEvents>
+
+#include <array>
 
 namespace Ui {
 namespace {
@@ -97,8 +102,8 @@ QPixmap GrabWidget(not_null<QWidget*> target, QRect rect, QColor bg) {
 		rect = target->rect();
 	}
 
-	auto result = QPixmap(rect.size() * cIntRetinaFactor());
-	result.setDevicePixelRatio(cRetinaFactor());
+	auto result = QPixmap(rect.size() * style::DevicePixelRatio());
+	result.setDevicePixelRatio(style::DevicePixelRatio());
 	if (!target->testAttribute(Qt::WA_OpaquePaintEvent)) {
 		result.fill(bg);
 	}
@@ -116,9 +121,9 @@ QImage GrabWidgetToImage(not_null<QWidget*> target, QRect rect, QColor bg) {
 	}
 
 	auto result = QImage(
-		rect.size() * cIntRetinaFactor(),
+		rect.size() * style::DevicePixelRatio(),
 		QImage::Format_ARGB32_Premultiplied);
-	result.setDevicePixelRatio(cRetinaFactor());
+	result.setDevicePixelRatio(style::DevicePixelRatio());
 	if (!target->testAttribute(Qt::WA_OpaquePaintEvent)) {
 		result.fill(bg);
 	}
@@ -148,6 +153,10 @@ void ForceFullRepaint(not_null<QWidget*> widget) {
 	refresher->show();
 }
 
+void PostponeCall(FnMut<void()> &&callable) {
+	Integration::Instance().postponeCall(std::move(callable));
+}
+
 void SendSynteticMouseEvent(QWidget *widget, QEvent::Type type, Qt::MouseButton button, const QPoint &globalPoint) {
 	if (const auto windowHandle = widget->window()->windowHandle()) {
 		const auto localPoint = windowHandle->mapFromGlobal(globalPoint);
@@ -167,4 +176,8 @@ void SendSynteticMouseEvent(QWidget *widget, QEvent::Type type, Qt::MouseButton
 	}
 }
 
+QPixmap PixmapFromImage(QImage &&image) {
+	return QPixmap::fromImage(std::move(image), Qt::ColorOnly);
+}
+
 } // namespace Ui
diff --git a/Telegram/SourceFiles/ui/ui_utility.h b/Telegram/SourceFiles/ui/ui_utility.h
index 18f47e23b..fbd4d7309 100644
--- a/Telegram/SourceFiles/ui/ui_utility.h
+++ b/Telegram/SourceFiles/ui/ui_utility.h
@@ -8,10 +8,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #pragma once
 
 #include "base/unique_qptr.h"
+#include "ui/rect_part.h"
 #include "ui/ui_integration.h"
 
 #include <QtCore/QEvent>
 
+class QPixmap;
+class QImage;
+
+enum class RectPart;
+using RectParts = base::flags<RectPart>;
+
 template <typename Object>
 class object_ptr;
 
@@ -137,6 +144,8 @@ void RenderWidget(
 
 void ForceFullRepaint(not_null<QWidget*> widget);
 
+void PostponeCall(FnMut<void()> &&callable);
+
 template <
 	typename Guard,
 	typename Callable,
@@ -182,4 +191,6 @@ QPointer<const Widget> MakeWeak(not_null<const Widget*> object) {
 	return QPointer<const Widget>(object.get());
 }
 
+[[nodiscard]] QPixmap PixmapFromImage(QImage &&image);
+
 } // namespace Ui
diff --git a/Telegram/SourceFiles/ui/widgets/checkbox.h b/Telegram/SourceFiles/ui/widgets/checkbox.h
index d1b0416f1..a646e6ba9 100644
--- a/Telegram/SourceFiles/ui/widgets/checkbox.h
+++ b/Telegram/SourceFiles/ui/widgets/checkbox.h
@@ -9,8 +9,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "ui/widgets/buttons.h"
 #include "ui/effects/animations.h"
+#include "ui/text/text.h"
 #include "styles/style_widgets.h"
 
+class Painter;
+
 namespace Ui {
 
 class AbstractCheckView {
diff --git a/Telegram/SourceFiles/ui/widgets/dropdown_menu.cpp b/Telegram/SourceFiles/ui/widgets/dropdown_menu.cpp
index 28404ed0f..699331f46 100644
--- a/Telegram/SourceFiles/ui/widgets/dropdown_menu.cpp
+++ b/Telegram/SourceFiles/ui/widgets/dropdown_menu.cpp
@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "ui/widgets/dropdown_menu.h"
 
-#include "lang/lang_keys.h"
+#include <QtGui/QtEvents>
 
 namespace Ui {
 
@@ -20,7 +20,7 @@ DropdownMenu::DropdownMenu(QWidget *parent, const style::DropdownMenu &st) : Inn
 // Not ready with submenus yet.
 //DropdownMenu::DropdownMenu(QWidget *parent, QMenu *menu, const style::DropdownMenu &st) : InnerDropdown(parent, st.wrap)
 //, _st(st) {
-//	_menu = setOwnedWidget(object_ptr<Ui::Menu>(this, menu, _st.menu));
+//	_menu = setOwnedWidget(object_ptr<Menu>(this, menu, _st.menu));
 //	init();
 //
 //	for (auto action : actions()) {
@@ -140,7 +140,7 @@ bool DropdownMenu::handleKeyPress(int key) {
 	} else if (key == Qt::Key_Escape) {
 		hideMenu(_parent ? true : false);
 		return true;
-	} else if (key == (rtl() ? Qt::Key_Right : Qt::Key_Left)) {
+	} else if (key == (style::RightToLeft() ? Qt::Key_Right : Qt::Key_Left)) {
 		if (_parent) {
 			hideMenu(true);
 			return true;
@@ -185,6 +185,18 @@ void DropdownMenu::hideEvent(QHideEvent *e) {
 	}
 }
 
+void DropdownMenu::keyPressEvent(QKeyEvent *e) {
+	forwardKeyPress(e->key());
+}
+
+void DropdownMenu::mouseMoveEvent(QMouseEvent *e) {
+	forwardMouseMove(e->globalPos());
+}
+
+void DropdownMenu::mousePressEvent(QMouseEvent *e) {
+	forwardMousePress(e->globalPos());
+}
+
 void DropdownMenu::hideMenu(bool fast) {
 	if (isHidden()) return;
 	if (_parent && !isHiding()) {
diff --git a/Telegram/SourceFiles/ui/widgets/dropdown_menu.h b/Telegram/SourceFiles/ui/widgets/dropdown_menu.h
index 759f95e53..8caf9ef4d 100644
--- a/Telegram/SourceFiles/ui/widgets/dropdown_menu.h
+++ b/Telegram/SourceFiles/ui/widgets/dropdown_menu.h
@@ -35,16 +35,9 @@ public:
 protected:
 	void focusOutEvent(QFocusEvent *e) override;
 	void hideEvent(QHideEvent *e) override;
-
-	void keyPressEvent(QKeyEvent *e) override {
-		forwardKeyPress(e->key());
-	}
-	void mouseMoveEvent(QMouseEvent *e) override {
-		forwardMouseMove(e->globalPos());
-	}
-	void mousePressEvent(QMouseEvent *e) override {
-		forwardMousePress(e->globalPos());
-	}
+	void keyPressEvent(QKeyEvent *e) override;
+	void mouseMoveEvent(QMouseEvent *e) override;
+	void mousePressEvent(QMouseEvent *e) override;
 
 private slots:
 	void onHidden() {
@@ -63,7 +56,7 @@ private:
 	void init();
 	void hideFinish();
 
-	using TriggeredSource = Ui::Menu::TriggeredSource;
+	using TriggeredSource = Menu::TriggeredSource;
 	void handleActivated(QAction *action, int actionTop, TriggeredSource source);
 	void handleTriggered(QAction *action, int actionTop, TriggeredSource source);
 	void forwardKeyPress(int key);
@@ -89,7 +82,7 @@ private:
 	const style::DropdownMenu &_st;
 	Fn<void()> _hiddenCallback;
 
-	QPointer<Ui::Menu> _menu;
+	QPointer<Menu> _menu;
 
 	// Not ready with submenus yet.
 	//using Submenus = QMap<QAction*, SubmenuPointer>;
diff --git a/Telegram/SourceFiles/ui/widgets/inner_dropdown.cpp b/Telegram/SourceFiles/ui/widgets/inner_dropdown.cpp
index 0a49428a7..66b7a62fa 100644
--- a/Telegram/SourceFiles/ui/widgets/inner_dropdown.cpp
+++ b/Telegram/SourceFiles/ui/widgets/inner_dropdown.cpp
@@ -7,13 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "ui/widgets/inner_dropdown.h"
 
-#include "mainwindow.h"
 #include "ui/widgets/scroll_area.h"
 #include "ui/widgets/shadow.h"
 #include "ui/effects/panel_animation.h"
 #include "ui/image/image_prepare.h"
 #include "ui/ui_utility.h"
-#include "app.h"
 
 namespace {
 
@@ -29,6 +27,7 @@ InnerDropdown::InnerDropdown(
 	const style::InnerDropdown &st)
 : RpWidget(parent)
 , _st(st)
+, _roundRect(ImageRoundRadius::Small, _st.bg)
 , _scroll(this, _st.scroll) {
 	_hideTimer.setSingleShot(true);
 	connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(onHideAnimated()));
@@ -111,7 +110,7 @@ void InnerDropdown::onScroll() {
 }
 
 void InnerDropdown::paintEvent(QPaintEvent *e) {
-	Painter p(this);
+	QPainter p(this);
 
 	if (_a_show.animating()) {
 		if (auto opacity = _a_opacity.value(_hiding ? 0. : 1.)) {
@@ -133,7 +132,7 @@ void InnerDropdown::paintEvent(QPaintEvent *e) {
 		if (!_cache.isNull()) _cache = QPixmap();
 		const auto inner = rect().marginsRemoved(_st.padding);
 		Shadow::paint(p, inner, width(), _st.shadow);
-		App::roundRect(p, inner, _st.bg, ImageRoundRadius::Small);
+		_roundRect.paint(p, inner);
 	}
 }
 
@@ -295,11 +294,12 @@ void InnerDropdown::startShowAnimation() {
 		auto cache = grabForPanelAnimation();
 		_a_opacity = base::take(opacityAnimation);
 
+		const auto pixelRatio = style::DevicePixelRatio();
 		_showAnimation = std::make_unique<PanelAnimation>(_st.animation, _origin);
 		auto inner = rect().marginsRemoved(_st.padding);
-		_showAnimation->setFinalImage(std::move(cache), QRect(inner.topLeft() * cIntRetinaFactor(), inner.size() * cIntRetinaFactor()));
-		auto corners = App::cornersMask(ImageRoundRadius::Small);
-		_showAnimation->setCornerMasks(corners[0], corners[1], corners[2], corners[3]);
+		_showAnimation->setFinalImage(std::move(cache), QRect(inner.topLeft() * pixelRatio, inner.size() * pixelRatio));
+		_showAnimation->setCornerMasks(
+			Images::CornersMask(ImageRoundRadius::Small));
 		_showAnimation->start();
 	}
 	hideChildren();
@@ -308,12 +308,13 @@ void InnerDropdown::startShowAnimation() {
 
 QImage InnerDropdown::grabForPanelAnimation() {
 	SendPendingMoveResizeEvents(this);
-	auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
-	result.setDevicePixelRatio(cRetinaFactor());
+	const auto pixelRatio = style::DevicePixelRatio();
+	auto result = QImage(size() * pixelRatio, QImage::Format_ARGB32_Premultiplied);
+	result.setDevicePixelRatio(pixelRatio);
 	result.fill(Qt::transparent);
 	{
-		Painter p(&result);
-		App::roundRect(p, rect().marginsRemoved(_st.padding), _st.bg, ImageRoundRadius::Small);
+		QPainter p(&result);
+		_roundRect.paint(p, rect().marginsRemoved(_st.padding));
 		for (const auto child : children()) {
 			if (const auto widget = qobject_cast<QWidget*>(child)) {
 				RenderWidget(p, widget, widget->pos());
diff --git a/Telegram/SourceFiles/ui/widgets/inner_dropdown.h b/Telegram/SourceFiles/ui/widgets/inner_dropdown.h
index 9c6fffccc..5449dc1a1 100644
--- a/Telegram/SourceFiles/ui/widgets/inner_dropdown.h
+++ b/Telegram/SourceFiles/ui/widgets/inner_dropdown.h
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "styles/style_widgets.h"
 #include "ui/rp_widget.h"
+#include "ui/round_rect.h"
 #include "ui/effects/animations.h"
 #include "ui/effects/panel_animation.h"
 #include "base/object_ptr.h"
@@ -19,7 +20,7 @@ namespace Ui {
 
 class ScrollArea;
 
-class InnerDropdown : public Ui::RpWidget {
+class InnerDropdown : public RpWidget {
 	Q_OBJECT
 
 public:
@@ -105,14 +106,15 @@ private:
 
 	const style::InnerDropdown &_st;
 
+	RoundRect _roundRect;
 	PanelAnimation::Origin _origin = PanelAnimation::Origin::TopLeft;
 	std::unique_ptr<PanelAnimation> _showAnimation;
-	Ui::Animations::Simple _a_show;
+	Animations::Simple _a_show;
 
 	bool _autoHiding = true;
 	bool _hiding = false;
 	QPixmap _cache;
-	Ui::Animations::Simple _a_opacity;
+	Animations::Simple _a_opacity;
 
 	QTimer _hideTimer;
 	bool _ignoreShowEvents = false;
@@ -120,7 +122,7 @@ private:
 	Fn<void()> _hideStartCallback;
 	Fn<void()> _hiddenCallback;
 
-	object_ptr<Ui::ScrollArea> _scroll;
+	object_ptr<ScrollArea> _scroll;
 
 	int _maxHeight = 0;
 
diff --git a/Telegram/SourceFiles/ui/widgets/input_fields.cpp b/Telegram/SourceFiles/ui/widgets/input_fields.cpp
index f0d168535..1c0b13c0b 100644
--- a/Telegram/SourceFiles/ui/widgets/input_fields.cpp
+++ b/Telegram/SourceFiles/ui/widgets/input_fields.cpp
@@ -8,21 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/widgets/input_fields.h"
 
 #include "ui/widgets/popup_menu.h"
-#include "ui/countryinput.h"
+#include "ui/text/text.h"
 #include "ui/emoji_config.h"
 #include "ui/ui_utility.h"
+#include "base/openssl_help.h"
 #include "chat_helpers/emoji_suggestions_helper.h"
-#include "chat_helpers/message_field.h" // ConvertTextTagsToEntities
 #include "platform/platform_info.h"
-#include "window/themes/window_theme.h"
-#include "lang/lang_keys.h"
-#include "data/data_user.h"
-#include "data/data_countries.h" // Data::ValidPhoneCode
-#include "mainwindow.h"
-#include "numbers.h"
-#include "main/main_session.h"
-#include "core/application.h"
-#include "app.h"
 
 #include <QtWidgets/QCommonStyle>
 #include <QtWidgets/QScrollBar>
@@ -31,11 +22,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include <QtGui/QTextBlock>
 #include <QtGui/QTextDocumentFragment>
 #include <QtCore/QMimeData>
+#include <QtCore/QRegularExpression>
 
 namespace Ui {
 namespace {
 
-constexpr auto kMaxUsernameLength = 32;
 constexpr auto kInstantReplaceRandomId = QTextFormat::UserProperty;
 constexpr auto kInstantReplaceWhatId = QTextFormat::UserProperty + 1;
 constexpr auto kInstantReplaceWithId = QTextFormat::UserProperty + 2;
@@ -74,7 +65,7 @@ private:
 InputDocument::InputDocument(QObject *parent, const style::InputField &st)
 : QTextDocument(parent)
 , _st(st) {
-	Ui::Emoji::Updated(
+	Emoji::Updated(
 	) | rpl::start_with_next([=] {
 		_emojiCache.clear();
 	}, _lifetime);
@@ -90,11 +81,11 @@ QVariant InputDocument::loadResource(int type, const QUrl &name) {
 		return i->second;
 	}
 	auto result = [&] {
-		if (const auto emoji = Ui::Emoji::FromUrl(name.toDisplayString())) {
+		if (const auto emoji = Emoji::FromUrl(name.toDisplayString())) {
 			const auto height = std::max(
-				_st.font->height * cIntRetinaFactor(),
-				Ui::Emoji::GetSizeNormal());
-			return QVariant(Ui::Emoji::SinglePixmap(emoji, height));
+				_st.font->height * style::DevicePixelRatio(),
+				Emoji::GetSizeNormal());
+			return QVariant(Emoji::SinglePixmap(emoji, height));
 		}
 		return QVariant();
 	}();
@@ -624,8 +615,8 @@ QString AccumulateText(Iterator begin, Iterator end) {
 }
 
 QTextImageFormat PrepareEmojiFormat(EmojiPtr emoji, const QFont &font) {
-	const auto factor = cIntRetinaFactor();
-	const auto size = Ui::Emoji::GetSizeNormal();
+	const auto factor = style::DevicePixelRatio();
+	const auto size = Emoji::GetSizeNormal();
 	const auto width = size + st::emojiPadding * factor * 2;
 	const auto height = std::max(QFontMetrics(font).height() * factor, size);
 	auto result = QTextImageFormat();
@@ -702,7 +693,7 @@ QTextCharFormat PrepareTagFormat(
 		result.setFont(st.font->strikeout());
 	} else if (tag == kTagCode || tag == kTagPre) {
 		result.setForeground(st::defaultTextPalette.monoFg);
-		result.setFont(AdjustFont(App::monofont(), st.font));
+		result.setFont(AdjustFont(style::MonospaceFont(), st.font));
 	} else {
 		result.setForeground(st.textFg);
 		result.setFont(st.font);
@@ -816,42 +807,16 @@ struct FormattingAction {
 
 };
 
-QString ExpandCustomLinks(const TextWithTags &text) {
-	const auto entities = ConvertTextTagsToEntities(text.tags);
-	auto &&urls = ranges::make_iterator_range(
-		entities.begin(),
-		entities.end()
-	) | ranges::view::filter([](const EntityInText &entity) {
-		return entity.type() == EntityType::CustomUrl;
-	});
-	const auto &original = text.text;
-	if (urls.begin() == urls.end()) {
-		return original;
-	}
-	auto result = QString();
-	auto offset = 0;
-	for (const auto &entity : urls) {
-		const auto till = entity.offset() + entity.length();
-		if (till > offset) {
-			result.append(original.midRef(offset, till - offset));
-		}
-		result.append(qstr(" (")).append(entity.data()).append(')');
-		offset = till;
-	}
-	if (original.size() > offset) {
-		result.append(original.midRef(offset));
-	}
-	return result;
-}
-
 } // namespace
 
-const QString InputField::kTagBold = qsl("**");
-const QString InputField::kTagItalic = qsl("__");
-const QString InputField::kTagUnderline = qsl("^^"); // Not for Markdown.
-const QString InputField::kTagStrikeOut = qsl("~~");
-const QString InputField::kTagCode = qsl("`");
-const QString InputField::kTagPre = qsl("```");
+// kTagUnderline is not used for Markdown.
+
+const QString InputField::kTagBold = QStringLiteral("**");
+const QString InputField::kTagItalic = QStringLiteral("__");
+const QString InputField::kTagUnderline = QStringLiteral("^^");
+const QString InputField::kTagStrikeOut = QStringLiteral("~~");
+const QString InputField::kTagCode = QStringLiteral("`");
+const QString InputField::kTagPre = QStringLiteral("```");
 
 class InputField::Inner final : public QTextEdit {
 public:
@@ -979,16 +944,17 @@ FlatInput::FlatInput(
 		refreshPlaceholder(text);
 	}, lifetime());
 
-	subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) {
-		if (update.paletteChanged()) {
-			updatePalette();
-		}
-	});
+	style::PaletteChanged(
+	) | rpl::start_with_next([=] {
+		updatePalette();
+	}, lifetime());
 	updatePalette();
 
 	connect(this, SIGNAL(textChanged(const QString &)), this, SLOT(onTextChange(const QString &)));
 	connect(this, SIGNAL(textEdited(const QString &)), this, SLOT(onTextEdited()));
-	if (App::wnd()) connect(this, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu()));
+	connect(this, &FlatInput::selectionChanged, [] {
+		Integration::Instance().textActionsUpdated();
+	});
 
 	setStyle(InputStyle<FlatInput>::instance());
 	QLineEdit::setTextMargins(0, 0, 0, 0);
@@ -1168,7 +1134,7 @@ void FlatInput::refreshPlaceholder(const QString &text) {
 
 void FlatInput::contextMenuEvent(QContextMenuEvent *e) {
 	if (auto menu = createStandardContextMenu()) {
-		(new Ui::PopupMenu(this, menu))->popup(e->globalPos());
+		(new PopupMenu(this, menu))->popup(e->globalPos());
 	}
 }
 
@@ -1214,7 +1180,7 @@ QRect FlatInput::placeholderRect() const {
 void FlatInput::correctValue(const QString &was, QString &now) {
 }
 
-void FlatInput::phPrepare(Painter &p, float64 placeholderFocused) {
+void FlatInput::phPrepare(QPainter &p, float64 placeholderFocused) {
 	p.setFont(_st.font);
 	p.setPen(anim::pen(_st.phColor, _st.phFocusColor, placeholderFocused));
 }
@@ -1257,12 +1223,12 @@ void FlatInput::onTextEdited() {
 	if (wasText != _oldtext) emit changed();
 	updatePlaceholder();
 
-	if (App::wnd()) App::wnd()->updateGlobalMenu();
+	Integration::Instance().textActionsUpdated();
 }
 
 void FlatInput::onTextChange(const QString &text) {
 	_oldtext = text;
-	if (App::wnd()) App::wnd()->updateGlobalMenu();
+	Integration::Instance().textActionsUpdated();
 }
 
 InputField::InputField(
@@ -1306,7 +1272,7 @@ InputField::InputField(
 , _inner(std::make_unique<Inner>(this))
 , _lastTextWithTags(value)
 , _placeholderFull(std::move(placeholder)) {
-	_inner->setDocument(Ui::CreateChild<InputDocument>(_inner.get(), _st));
+	_inner->setDocument(CreateChild<InputDocument>(_inner.get(), _st));
 
 	_inner->setAcceptRichText(false);
 	resize(_st.width, _minHeight);
@@ -1326,12 +1292,10 @@ InputField::InputField(
 		refreshPlaceholder(text);
 	}, lifetime());
 
-	subscribe(Window::Theme::Background(), [=](
-			const Window::Theme::BackgroundUpdate &update) {
-		if (update.paletteChanged()) {
-			updatePalette();
-		}
-	});
+	style::PaletteChanged(
+	) | rpl::start_with_next([=] {
+		updatePalette();
+	}, lifetime());
 
 	_defaultCharFormat = _inner->textCursor().charFormat();
 	updatePalette();
@@ -1355,9 +1319,9 @@ InputField::InputField(
 	connect(_inner.get(), SIGNAL(undoAvailable(bool)), this, SLOT(onUndoAvailable(bool)));
 	connect(_inner.get(), SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool)));
 	connect(_inner.get(), SIGNAL(cursorPositionChanged()), this, SLOT(onCursorPositionChanged()));
-	if (App::wnd()) {
-		connect(_inner.get(), SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu()));
-	}
+	connect(_inner.get(), &Inner::selectionChanged, [] {
+		Integration::Instance().textActionsUpdated();
+	});
 
 	const auto bar = _inner->verticalScrollBar();
 	_scrollTop = bar->value();
@@ -1483,7 +1447,8 @@ void InputField::setTagMimeProcessor(
 }
 
 void InputField::setAdditionalMargin(int margin) {
-	_inner->setStyleSheet(qsl("QTextEdit { margin: %1px; }").arg(margin));
+	_inner->setStyleSheet(
+		QString::fromLatin1("QTextEdit { margin: %1px; }").arg(margin));
 	_additionalMargin = margin;
 	checkContentHeight();
 }
@@ -1613,7 +1578,7 @@ bool InputField::heightAutoupdated() {
 		+ _st.textMargins.top()
 		+ _st.textMargins.bottom()
 		+ 2 * _additionalMargin;
-	const auto newHeight = snap(contentHeight, _minHeight, _maxHeight);
+	const auto newHeight = std::clamp(contentHeight, _minHeight, _maxHeight);
 	if (height() != newHeight) {
 		resize(width(), newHeight);
 		return true;
@@ -1684,7 +1649,7 @@ void InputField::paintEvent(QPaintEvent *e) {
 	auto borderShownDegree = _a_borderShown.value(1.);
 	auto borderOpacity = _a_borderOpacity.value(_borderVisible ? 1. : 0.);
 	if (_st.borderActive && (borderOpacity > 0.)) {
-		auto borderStart = snap(_borderAnimationStart, 0, width());
+		auto borderStart = std::clamp(_borderAnimationStart, 0, width());
 		auto borderFrom = qRound(borderStart * (1. - borderShownDegree));
 		auto borderTo = borderStart + qRound((width() - borderStart) * borderShownDegree);
 		if (borderTo > borderFrom) {
@@ -1704,7 +1669,7 @@ void InputField::paintEvent(QPaintEvent *e) {
 
 		QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins));
 		r.moveTop(r.top() + placeholderTop);
-		if (rtl()) r.moveLeft(width() - r.left() - r.width());
+		if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
 
 		auto placeholderScale = 1. - (1. - _st.placeholderScale) * placeholderShiftDegree;
 		auto placeholderFg = anim::color(_st.placeholderFg, _st.placeholderFgActive, focusedDegree);
@@ -1739,7 +1704,7 @@ void InputField::paintEvent(QPaintEvent *e) {
 			} else {
 				auto r = rect().marginsRemoved(_st.textMargins + _st.placeholderMargins);
 				r.moveLeft(r.left() + placeholderLeft);
-				if (rtl()) r.moveLeft(width() - r.left() - r.width());
+				if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
 				p.drawText(r, _placeholder, _st.placeholderAlign);
 			}
 
@@ -1924,7 +1889,7 @@ QString InputField::getTextPart(
 			const auto emojiText = [&] {
 				if (format.isImageFormat()) {
 					const auto imageName = format.toImageFormat().name();
-					if (const auto emoji = Ui::Emoji::FromUrl(imageName)) {
+					if (const auto emoji = Emoji::FromUrl(imageName)) {
 						return emoji->text();
 					}
 				}
@@ -2003,7 +1968,7 @@ bool InputField::isRedoAvailable() const {
 
 void InputField::processFormatting(int insertPosition, int insertEnd) {
 	// Tilde formatting.
-	const auto tildeFormatting = (_st.font->f.pixelSize() * cIntRetinaFactor() == 13)
+	const auto tildeFormatting = (_st.font->f.pixelSize() * style::DevicePixelRatio() == 13)
 		&& (_st.font->f.family() == qstr("Open Sans"));
 	auto isTildeFragment = false;
 	const auto tildeFixedFont = AdjustFont(st::semiboldFont, _st.font);
@@ -2103,7 +2068,7 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
 					}
 
 					auto emojiLength = 0;
-					if (const auto emoji = Ui::Emoji::Find(ch, textEnd, &emojiLength)) {
+					if (const auto emoji = Emoji::Find(ch, textEnd, &emojiLength)) {
 						// Replace emoji if no current action is prepared.
 						if (action.type == ActionType::Invalid) {
 							action.type = ActionType::InsertEmoji;
@@ -2324,7 +2289,7 @@ void InputField::handleContentsChanged() {
 		checkContentHeight();
 	}
 	startPlaceholderAnimation();
-	if (App::wnd()) App::wnd()->updateGlobalMenu();
+	Integration::Instance().textActionsUpdated();
 }
 
 void InputField::highlightMarkdown() {
@@ -2362,12 +2327,12 @@ void InputField::highlightMarkdown() {
 
 void InputField::onUndoAvailable(bool avail) {
 	_undoAvailable = avail;
-	if (App::wnd()) App::wnd()->updateGlobalMenu();
+	Integration::Instance().textActionsUpdated();
 }
 
 void InputField::onRedoAvailable(bool avail) {
 	_redoAvailable = avail;
-	if (App::wnd()) App::wnd()->updateGlobalMenu();
+	Integration::Instance().textActionsUpdated();
 }
 
 void InputField::setDisplayFocused(bool focused) {
@@ -2414,28 +2379,13 @@ void InputField::startPlaceholderAnimation() {
 }
 
 QMimeData *InputField::createMimeDataFromSelectionInner() const {
-	auto result = std::make_unique<QMimeData>();
 	const auto cursor = _inner->textCursor();
 	const auto start = cursor.selectionStart();
 	const auto end = cursor.selectionEnd();
-	if (end > start) {
-		auto textWithTags = getTextWithTagsPart(start, end);
-		result->setText(ExpandCustomLinks(textWithTags));
-		if (!textWithTags.tags.isEmpty()) {
-			if (_tagMimeProcessor) {
-				for (auto &tag : textWithTags.tags) {
-					tag.id = _tagMimeProcessor->mimeTagFromTag(tag.id);
-				}
-			}
-			result->setData(
-				TextUtilities::TagsTextMimeType(),
-				textWithTags.text.toUtf8());
-			result->setData(
-				TextUtilities::TagsMimeType(),
-				TextUtilities::SerializeTags(textWithTags.tags));
-		}
-	}
-	return result.release();
+	return TextUtilities::MimeDataFromText((end > start)
+		? getTextWithTagsPart(start, end)
+		: TextWithTags()
+	).release();
 }
 
 void InputField::customUpDown(bool isCustom) {
@@ -3058,21 +3008,12 @@ void InputField::commitInstantReplacement(
 
 	auto format = [&]() -> QTextCharFormat {
 		auto emojiLength = 0;
-		const auto emoji = Ui::Emoji::Find(with, &emojiLength);
+		const auto emoji = Emoji::Find(with, &emojiLength);
 		if (!emoji || with.size() != emojiLength) {
 			return _defaultCharFormat;
 		}
-		const auto use = [&] {
-			if (!emoji->hasVariants()) {
-				return emoji;
-			}
-			const auto nonColored = emoji->nonColoredId();
-			const auto it = cEmojiVariants().constFind(nonColored);
-			return (it != cEmojiVariants().cend())
-				? emoji->variant(it.value())
-				: emoji;
-		}();
-		AddRecentEmoji(use);
+		const auto use = Integration::Instance().defaultEmojiVariant(
+			emoji);
 		return PrepareEmojiFormat(use, _st.font);
 	}();
 	const auto replacement = format.isImageFormat()
@@ -3080,7 +3021,9 @@ void InputField::commitInstantReplacement(
 		: with;
 	format.setProperty(kInstantReplaceWhatId, original);
 	format.setProperty(kInstantReplaceWithId, replacement);
-	format.setProperty(kInstantReplaceRandomId, rand_value<uint32>());
+	format.setProperty(
+		kInstantReplaceRandomId,
+		openssl::RandomValue<uint32>());
 	ApplyTagFormat(format, cursor.charFormat());
 	cursor.insertText(replacement, format);
 }
@@ -3362,7 +3305,7 @@ bool InputField::revertFormatReplace() {
 void InputField::contextMenuEventInner(QContextMenuEvent *e) {
 	if (const auto menu = _inner->createStandardContextMenu()) {
 		addMarkdownActions(menu, e);
-		_contextMenu = base::make_unique_q<Ui::PopupMenu>(this, menu);
+		_contextMenu = base::make_unique_q<PopupMenu>(this, menu);
 		_contextMenu->popup(e->globalPos());
 	}
 }
@@ -3373,7 +3316,11 @@ void InputField::addMarkdownActions(
 	if (!_markdownEnabled) {
 		return;
 	}
-	const auto formatting = new QAction(tr::lng_menu_formatting(tr::now), menu);
+	auto &integration = Integration::Instance();
+
+	const auto formatting = new QAction(
+		integration.phraseFormattingTitle(),
+		menu);
 	addMarkdownMenuAction(menu, formatting);
 
 	const auto submenu = new QMenu(menu);
@@ -3417,24 +3364,24 @@ void InputField::addMarkdownActions(
 		const auto selection = editLinkSelection(e);
 		const auto data = selectionEditLinkData(selection);
 		const auto base = data.link.isEmpty()
-			? tr::lng_menu_formatting_link_create(tr::now)
-			: tr::lng_menu_formatting_link_edit(tr::now);
+			? integration.phraseFormattingLinkCreate()
+			: integration.phraseFormattingLinkEdit();
 		add(base, kEditLinkSequence, false, [=] {
 			editMarkdownLink(selection);
 		});
 	};
 	const auto addclear = [&] {
 		const auto disabled = !hasText || !hasTags;
-		add(tr::lng_menu_formatting_clear(tr::now), kClearFormatSequence, disabled, [=] {
+		add(integration.phraseFormattingClear(), kClearFormatSequence, disabled, [=] {
 			clearSelectionMarkdown();
 		});
 	};
 
-	addtag(tr::lng_menu_formatting_bold(tr::now), QKeySequence::Bold, kTagBold);
-	addtag(tr::lng_menu_formatting_italic(tr::now), QKeySequence::Italic, kTagItalic);
-	addtag(tr::lng_menu_formatting_underline(tr::now), QKeySequence::Underline, kTagUnderline);
-	addtag(tr::lng_menu_formatting_strike_out(tr::now), kStrikeOutSequence, kTagStrikeOut);
-	addtag(tr::lng_menu_formatting_monospace(tr::now), kMonospaceSequence, kTagCode);
+	addtag(integration.phraseFormattingBold(), QKeySequence::Bold, kTagBold);
+	addtag(integration.phraseFormattingItalic(), QKeySequence::Italic, kTagItalic);
+	addtag(integration.phraseFormattingUnderline(), QKeySequence::Underline, kTagUnderline);
+	addtag(integration.phraseFormattingStrikeOut(), kStrikeOutSequence, kTagStrikeOut);
+	addtag(integration.phraseFormattingMonospace(), kMonospaceSequence, kTagCode);
 
 	if (_editLinkCallback) {
 		submenu->addSeparator();
@@ -3595,11 +3542,10 @@ MaskedInputField::MaskedInputField(
 		refreshPlaceholder(text);
 	}, lifetime());
 
-	subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) {
-		if (update.paletteChanged()) {
-			updatePalette();
-		}
-	});
+	style::PaletteChanged(
+	) | rpl::start_with_next([=] {
+		updatePalette();
+	}, lifetime());
 	updatePalette();
 
 	setAttribute(Qt::WA_OpaquePaintEvent);
@@ -3608,7 +3554,9 @@ MaskedInputField::MaskedInputField(
 	connect(this, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(onCursorPositionChanged(int,int)));
 
 	connect(this, SIGNAL(textEdited(const QString&)), this, SLOT(onTextEdited()));
-	if (App::wnd()) connect(this, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu()));
+	connect(this, &MaskedInputField::selectionChanged, [] {
+		Integration::Instance().textActionsUpdated();
+	});
 
 	setStyle(InputStyle<MaskedInputField>::instance());
 	QLineEdit::setTextMargins(0, 0, 0, 0);
@@ -3738,7 +3686,7 @@ void MaskedInputField::paintEvent(QPaintEvent *e) {
 	auto borderShownDegree = _a_borderShown.value(1.);
 	auto borderOpacity = _a_borderOpacity.value(_borderVisible ? 1. : 0.);
 	if (_st.borderActive && (borderOpacity > 0.)) {
-		auto borderStart = snap(_borderAnimationStart, 0, width());
+		auto borderStart = std::clamp(_borderAnimationStart, 0, width());
 		auto borderFrom = qRound(borderStart * (1. - borderShownDegree));
 		auto borderTo = borderStart + qRound((width() - borderStart) * borderShownDegree);
 		if (borderTo > borderFrom) {
@@ -3759,7 +3707,7 @@ void MaskedInputField::paintEvent(QPaintEvent *e) {
 
 		QRect r(rect().marginsRemoved(_textMargins + _st.placeholderMargins));
 		r.moveTop(r.top() + placeholderTop);
-		if (rtl()) r.moveLeft(width() - r.left() - r.width());
+		if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
 
 		auto placeholderScale = 1. - (1. - _st.placeholderScale) * placeholderShiftDegree;
 		auto placeholderFg = anim::color(_st.placeholderFg, _st.placeholderFgActive, focusedDegree);
@@ -3784,7 +3732,7 @@ void MaskedInputField::paintEvent(QPaintEvent *e) {
 
 			QRect r(rect().marginsRemoved(_textMargins + _st.placeholderMargins));
 			r.moveLeft(r.left() + placeholderLeft);
-			if (rtl()) r.moveLeft(width() - r.left() - r.width());
+			if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
 
 			p.setFont(_st.placeholderFont);
 			p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, focusedDegree));
@@ -3869,7 +3817,7 @@ void MaskedInputField::setPlaceholder(rpl::producer<QString> placeholder) {
 
 void MaskedInputField::contextMenuEvent(QContextMenuEvent *e) {
 	if (const auto menu = createStandardContextMenu()) {
-		(new Ui::PopupMenu(this, menu))->popup(e->globalPos());
+		(new PopupMenu(this, menu))->popup(e->globalPos());
 	}
 }
 
@@ -3982,213 +3930,19 @@ void MaskedInputField::onTextEdited() {
 	if (wasText != _oldtext) emit changed();
 	startPlaceholderAnimation();
 
-	if (App::wnd()) App::wnd()->updateGlobalMenu();
+	Integration::Instance().textActionsUpdated();
 }
 
 void MaskedInputField::onTextChange(const QString &text) {
 	_oldtext = QLineEdit::text();
 	setErrorShown(false);
-	if (App::wnd()) App::wnd()->updateGlobalMenu();
+	Integration::Instance().textActionsUpdated();
 }
 
 void MaskedInputField::onCursorPositionChanged(int oldPosition, int position) {
 	_oldcursor = position;
 }
 
-CountryCodeInput::CountryCodeInput(QWidget *parent, const style::InputField &st) : MaskedInputField(parent, st)
-, _nosignal(false) {
-}
-
-void CountryCodeInput::startErasing(QKeyEvent *e) {
-	setFocus();
-	keyPressEvent(e);
-}
-
-void CountryCodeInput::codeSelected(const QString &code) {
-	auto wasText = getLastText();
-	auto wasCursor = cursorPosition();
-	auto newText = '+' + code;
-	auto newCursor = newText.size();
-	setText(newText);
-	_nosignal = true;
-	correctValue(wasText, wasCursor, newText, newCursor);
-	_nosignal = false;
-	emit changed();
-}
-
-void CountryCodeInput::correctValue(
-		const QString &was,
-		int wasCursor,
-		QString &now,
-		int &nowCursor) {
-	QString newText, addToNumber;
-	int oldPos(nowCursor), newPos(-1), oldLen(now.length()), start = 0, digits = 5;
-	newText.reserve(oldLen + 1);
-	if (oldLen && now[0] == '+') {
-		if (start == oldPos) {
-			newPos = newText.length();
-		}
-		++start;
-	}
-	newText += '+';
-	for (int i = start; i < oldLen; ++i) {
-		if (i == oldPos) {
-			newPos = newText.length();
-		}
-		auto ch = now[i];
-		if (ch.isDigit()) {
-			if (!digits || !--digits) {
-				addToNumber += ch;
-			} else {
-				newText += ch;
-			}
-		}
-	}
-	if (!addToNumber.isEmpty()) {
-		auto validCode = Data::ValidPhoneCode(newText.mid(1));
-		addToNumber = newText.mid(1 + validCode.length()) + addToNumber;
-		newText = '+' + validCode;
-	}
-	setCorrectedText(now, nowCursor, newText, newPos);
-
-	if (!_nosignal && was != newText) {
-		emit codeChanged(newText.mid(1));
-	}
-	if (!addToNumber.isEmpty()) {
-		emit addedToNumber(addToNumber);
-	}
-}
-
-PhonePartInput::PhonePartInput(QWidget *parent, const style::InputField &st) : MaskedInputField(parent, st/*, tr::lng_phone_ph(tr::now)*/) {
-}
-
-void PhonePartInput::paintAdditionalPlaceholder(Painter &p) {
-	if (!_pattern.isEmpty()) {
-		auto t = getDisplayedText();
-		auto ph = _additionalPlaceholder.mid(t.size());
-		if (!ph.isEmpty()) {
-			p.setClipRect(rect());
-			auto phRect = placeholderRect();
-			int tw = phFont()->width(t);
-			if (tw < phRect.width()) {
-				phRect.setLeft(phRect.left() + tw);
-				placeholderAdditionalPrepare(p);
-				p.drawText(phRect, ph, style::al_topleft);
-			}
-		}
-	}
-}
-
-void PhonePartInput::keyPressEvent(QKeyEvent *e) {
-	if (e->key() == Qt::Key_Backspace && getLastText().isEmpty()) {
-		emit voidBackspace(e);
-	} else {
-		MaskedInputField::keyPressEvent(e);
-	}
-}
-
-void PhonePartInput::correctValue(
-		const QString &was,
-		int wasCursor,
-		QString &now,
-		int &nowCursor) {
-	QString newText;
-	int oldPos(nowCursor), newPos(-1), oldLen(now.length()), digitCount = 0;
-	for (int i = 0; i < oldLen; ++i) {
-		if (now[i].isDigit()) {
-			++digitCount;
-		}
-	}
-	if (digitCount > MaxPhoneTailLength) digitCount = MaxPhoneTailLength;
-
-	bool inPart = !_pattern.isEmpty();
-	int curPart = -1, leftInPart = 0;
-	newText.reserve(oldLen);
-	for (int i = 0; i < oldLen; ++i) {
-		if (i == oldPos && newPos < 0) {
-			newPos = newText.length();
-		}
-
-		auto ch = now[i];
-		if (ch.isDigit()) {
-			if (!digitCount--) {
-				break;
-			}
-			if (inPart) {
-				if (leftInPart) {
-					--leftInPart;
-				} else {
-					newText += ' ';
-					++curPart;
-					inPart = curPart < _pattern.size();
-					leftInPart = inPart ? (_pattern.at(curPart) - 1) : 0;
-
-					++oldPos;
-				}
-			}
-			newText += ch;
-		} else if (ch == ' ' || ch == '-' || ch == '(' || ch == ')') {
-			if (inPart) {
-				if (leftInPart) {
-				} else {
-					newText += ch;
-					++curPart;
-					inPart = curPart < _pattern.size();
-					leftInPart = inPart ? _pattern.at(curPart) : 0;
-				}
-			} else {
-				newText += ch;
-			}
-		}
-	}
-	auto newlen = newText.size();
-	while (newlen > 0 && newText.at(newlen - 1).isSpace()) {
-		--newlen;
-	}
-	if (newlen < newText.size()) {
-		newText = newText.mid(0, newlen);
-	}
-	setCorrectedText(now, nowCursor, newText, newPos);
-}
-
-void PhonePartInput::addedToNumber(const QString &added) {
-	setFocus();
-	auto wasText = getLastText();
-	auto wasCursor = cursorPosition();
-	auto newText = added + wasText;
-	auto newCursor = newText.size();
-	setText(newText);
-	setCursorPosition(added.length());
-	correctValue(wasText, wasCursor, newText, newCursor);
-	startPlaceholderAnimation();
-}
-
-void PhonePartInput::onChooseCode(const QString &code) {
-	_pattern = phoneNumberParse(code);
-	if (!_pattern.isEmpty() && _pattern.at(0) == code.size()) {
-		_pattern.pop_front();
-	} else {
-		_pattern.clear();
-	}
-	_additionalPlaceholder = QString();
-	if (!_pattern.isEmpty()) {
-		_additionalPlaceholder.reserve(20);
-		for (const auto part : _pattern) {
-			_additionalPlaceholder.append(' ');
-			_additionalPlaceholder.append(QString(part, QChar(0x2212)));
-		}
-	}
-	setPlaceholderHidden(!_additionalPlaceholder.isEmpty());
-
-	auto wasText = getLastText();
-	auto wasCursor = cursorPosition();
-	auto newText = getLastText();
-	auto newCursor = newText.size();
-	correctValue(wasText, wasCursor, newText, newCursor);
-
-	startPlaceholderAnimation();
-}
-
 PasswordInput::PasswordInput(
 	QWidget *parent,
 	const style::InputField &st,
@@ -4266,194 +4020,4 @@ void HexInput::correctValue(
 	setCorrectedText(now, nowCursor, newText, newPos);
 }
 
-UsernameInput::UsernameInput(
-	QWidget *parent,
-	const style::InputField &st,
-	rpl::producer<QString> placeholder,
-	const QString &val,
-	bool isLink)
-: MaskedInputField(parent, st, std::move(placeholder), val) {
-	setLinkPlaceholder(
-		isLink ? Core::App().createInternalLink(QString()) : QString());
-}
-
-void UsernameInput::setLinkPlaceholder(const QString &placeholder) {
-	_linkPlaceholder = placeholder;
-	if (!_linkPlaceholder.isEmpty()) {
-		setTextMargins(style::margins(_st.textMargins.left() + _st.font->width(_linkPlaceholder), _st.textMargins.top(), _st.textMargins.right(), _st.textMargins.bottom()));
-		setPlaceholderHidden(true);
-	}
-}
-
-void UsernameInput::paintAdditionalPlaceholder(Painter &p) {
-	if (!_linkPlaceholder.isEmpty()) {
-		p.setFont(_st.font);
-		p.setPen(_st.placeholderFg);
-		p.drawText(QRect(_st.textMargins.left(), _st.textMargins.top(), width(), height() - _st.textMargins.top() - _st.textMargins.bottom()), _linkPlaceholder, style::al_topleft);
-	}
-}
-
-void UsernameInput::correctValue(
-		const QString &was,
-		int wasCursor,
-		QString &now,
-		int &nowCursor) {
-	auto newPos = nowCursor;
-	auto from = 0, len = now.size();
-	for (; from < len; ++from) {
-		if (!now.at(from).isSpace()) {
-			break;
-		}
-		if (newPos > 0) --newPos;
-	}
-	len -= from;
-	if (len > kMaxUsernameLength) {
-		len = kMaxUsernameLength + (now.at(from) == '@' ? 1 : 0);
-	}
-	for (int32 to = from + len; to > from;) {
-		--to;
-		if (!now.at(to).isSpace()) {
-			break;
-		}
-		--len;
-	}
-	setCorrectedText(now, nowCursor, now.mid(from, len), newPos);
-}
-
-PhoneInput::PhoneInput(
-	QWidget *parent,
-	const style::InputField &st,
-	rpl::producer<QString> placeholder,
-	const QString &defaultValue,
-	QString value)
-: MaskedInputField(parent, st, std::move(placeholder), value)
-, _defaultValue(defaultValue) {
-	if (value.isEmpty()) {
-		clearText();
-	} else {
-		auto pos = value.size();
-		correctValue(QString(), 0, value, pos);
-	}
-}
-
-void PhoneInput::focusInEvent(QFocusEvent *e) {
-	MaskedInputField::focusInEvent(e);
-	setSelection(cursorPosition(), cursorPosition());
-}
-
-void PhoneInput::clearText() {
-	auto value = _defaultValue;
-	setText(value);
-	auto pos = value.size();
-	correctValue(QString(), 0, value, pos);
-}
-
-void PhoneInput::paintAdditionalPlaceholder(Painter &p) {
-	if (!_pattern.isEmpty()) {
-		auto t = getDisplayedText();
-		auto ph = _additionalPlaceholder.mid(t.size());
-		if (!ph.isEmpty()) {
-			p.setClipRect(rect());
-			auto phRect = placeholderRect();
-			int tw = phFont()->width(t);
-			if (tw < phRect.width()) {
-				phRect.setLeft(phRect.left() + tw);
-				placeholderAdditionalPrepare(p);
-				p.drawText(phRect, ph, style::al_topleft);
-			}
-		}
-	}
-}
-
-void PhoneInput::correctValue(
-		const QString &was,
-		int wasCursor,
-		QString &now,
-		int &nowCursor) {
-	auto digits = now;
-	digits.replace(QRegularExpression(qsl("[^\\d]")), QString());
-	_pattern = phoneNumberParse(digits);
-
-	QString newPlaceholder;
-	if (_pattern.isEmpty()) {
-		newPlaceholder = QString();
-	} else if (_pattern.size() == 1 && _pattern.at(0) == digits.size()) {
-		newPlaceholder = QString(_pattern.at(0) + 2, ' ') + tr::lng_contact_phone(tr::now);
-	} else {
-		newPlaceholder.reserve(20);
-		for (int i = 0, l = _pattern.size(); i < l; ++i) {
-			if (i) {
-				newPlaceholder.append(' ');
-			} else {
-				newPlaceholder.append('+');
-			}
-			newPlaceholder.append(i ? QString(_pattern.at(i), QChar(0x2212)) : digits.mid(0, _pattern.at(i)));
-		}
-	}
-	if (_additionalPlaceholder != newPlaceholder) {
-		_additionalPlaceholder = newPlaceholder;
-		setPlaceholderHidden(!_additionalPlaceholder.isEmpty());
-		update();
-	}
-
-	QString newText;
-	int oldPos(nowCursor), newPos(-1), oldLen(now.length()), digitCount = qMin(digits.size(), MaxPhoneCodeLength + MaxPhoneTailLength);
-
-	bool inPart = !_pattern.isEmpty(), plusFound = false;
-	int curPart = 0, leftInPart = inPart ? _pattern.at(curPart) : 0;
-	newText.reserve(oldLen + 1);
-	newText.append('+');
-	for (int i = 0; i < oldLen; ++i) {
-		if (i == oldPos && newPos < 0) {
-			newPos = newText.length();
-		}
-
-		QChar ch(now[i]);
-		if (ch.isDigit()) {
-			if (!digitCount--) {
-				break;
-			}
-			if (inPart) {
-				if (leftInPart) {
-					--leftInPart;
-				} else {
-					newText += ' ';
-					++curPart;
-					inPart = curPart < _pattern.size();
-					leftInPart = inPart ? (_pattern.at(curPart) - 1) : 0;
-
-					++oldPos;
-				}
-			}
-			newText += ch;
-		} else if (ch == ' ' || ch == '-' || ch == '(' || ch == ')') {
-			if (inPart) {
-				if (leftInPart) {
-				} else {
-					newText += ch;
-					++curPart;
-					inPart = curPart < _pattern.size();
-					leftInPart = inPart ? _pattern.at(curPart) : 0;
-				}
-			} else {
-				newText += ch;
-			}
-		} else if (ch == '+') {
-			plusFound = true;
-		}
-	}
-	if (!plusFound && newText == qstr("+")) {
-		newText = QString();
-		newPos = 0;
-	}
-	int32 newlen = newText.size();
-	while (newlen > 0 && newText.at(newlen - 1).isSpace()) {
-		--newlen;
-	}
-	if (newlen < newText.size()) {
-		newText = newText.mid(0, newlen);
-	}
-	setCorrectedText(now, nowCursor, newText, newPos);
-}
-
 } // namespace Ui
diff --git a/Telegram/SourceFiles/ui/widgets/input_fields.h b/Telegram/SourceFiles/ui/widgets/input_fields.h
index d15762d2d..ae480a05c 100644
--- a/Telegram/SourceFiles/ui/widgets/input_fields.h
+++ b/Telegram/SourceFiles/ui/widgets/input_fields.h
@@ -7,15 +7,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
+#include "ui/emoji_config.h"
 #include "ui/rp_widget.h"
 #include "ui/effects/animations.h"
+#include "ui/text/text_entity.h"
 #include "styles/style_widgets.h"
 
 #include <QtWidgets/QLineEdit>
 #include <QtWidgets/QTextEdit>
 #include <QtCore/QTimer>
 
-class UserData;
+class QTouchEvent;
+class Painter;
 
 namespace Ui {
 
@@ -51,7 +54,7 @@ enum class InputSubmitSettings {
 	None,
 };
 
-class FlatInput : public Ui::RpWidgetWrap<QLineEdit>, private base::Subscriber {
+class FlatInput : public RpWidgetWrap<QLineEdit> {
 	// The Q_OBJECT meta info is used for qobject_cast!
 	Q_OBJECT
 
@@ -110,7 +113,7 @@ protected:
 		return _st.font;
 	}
 
-	void phPrepare(Painter &p, float64 placeholderFocused);
+	void phPrepare(QPainter &p, float64 placeholderFocused);
 
 private:
 	void updatePalette();
@@ -136,7 +139,7 @@ private:
 	QPoint _touchStart;
 };
 
-class InputField : public RpWidget, private base::Subscriber {
+class InputField : public RpWidget {
 	Q_OBJECT
 
 public:
@@ -215,10 +218,8 @@ public:
 	// (and then to clipboard or to drag-n-drop object), here is a strategy for that.
 	class TagMimeProcessor {
 	public:
-		virtual QString mimeTagFromTag(const QString &tagId) = 0;
 		virtual QString tagFromMimeTag(const QString &mimeTag) = 0;
-		virtual ~TagMimeProcessor() {
-		}
+		virtual ~TagMimeProcessor() = default;
 	};
 	void setTagMimeProcessor(std::unique_ptr<TagMimeProcessor> &&processor);
 
@@ -481,17 +482,17 @@ private:
 	rpl::variable<QString> _placeholderFull;
 	QString _placeholder;
 	int _placeholderAfterSymbols = 0;
-	Ui::Animations::Simple _a_placeholderShifted;
+	Animations::Simple _a_placeholderShifted;
 	bool _placeholderShifted = false;
 	QPainterPath _placeholderPath;
 
-	Ui::Animations::Simple _a_borderShown;
+	Animations::Simple _a_borderShown;
 	int _borderAnimationStart = 0;
-	Ui::Animations::Simple _a_borderOpacity;
+	Animations::Simple _a_borderOpacity;
 	bool _borderVisible = false;
 
-	Ui::Animations::Simple _a_focused;
-	Ui::Animations::Simple _a_error;
+	Animations::Simple _a_focused;
+	Animations::Simple _a_error;
 
 	bool _focused = false;
 	bool _error = false;
@@ -504,7 +505,7 @@ private:
 
 	bool _correcting = false;
 	MimeDataHook _mimeDataHook;
-	base::unique_qptr<Ui::PopupMenu> _contextMenu;
+	base::unique_qptr<PopupMenu> _contextMenu;
 
 	QTextCharFormat _defaultCharFormat;
 
@@ -515,9 +516,7 @@ private:
 
 };
 
-class MaskedInputField
-	: public RpWidgetWrap<QLineEdit>
-	, private base::Subscriber {
+class MaskedInputField : public RpWidgetWrap<QLineEdit> {
 	// The Q_OBJECT meta info is used for qobject_cast!
 	Q_OBJECT
 
@@ -638,17 +637,17 @@ private:
 
 	rpl::variable<QString> _placeholderFull;
 	QString _placeholder;
-	Ui::Animations::Simple _a_placeholderShifted;
+	Animations::Simple _a_placeholderShifted;
 	bool _placeholderShifted = false;
 	QPainterPath _placeholderPath;
 
-	Ui::Animations::Simple _a_borderShown;
+	Animations::Simple _a_borderShown;
 	int _borderAnimationStart = 0;
-	Ui::Animations::Simple _a_borderOpacity;
+	Animations::Simple _a_borderOpacity;
 	bool _borderVisible = false;
 
-	Ui::Animations::Simple _a_focused;
-	Ui::Animations::Simple _a_error;
+	Animations::Simple _a_focused;
+	Animations::Simple _a_error;
 
 	bool _focused = false;
 	bool _error = false;
@@ -662,61 +661,6 @@ private:
 	QPoint _touchStart;
 };
 
-class CountryCodeInput : public MaskedInputField {
-	Q_OBJECT
-
-public:
-	CountryCodeInput(QWidget *parent, const style::InputField &st);
-
-public slots:
-	void startErasing(QKeyEvent *e);
-	void codeSelected(const QString &code);
-
-signals:
-	void codeChanged(const QString &code);
-	void addedToNumber(const QString &added);
-
-protected:
-	void correctValue(
-		const QString &was,
-		int wasCursor,
-		QString &now,
-		int &nowCursor) override;
-
-private:
-	bool _nosignal;
-
-};
-
-class PhonePartInput : public MaskedInputField {
-	Q_OBJECT
-
-public:
-	PhonePartInput(QWidget *parent, const style::InputField &st);
-
-public slots:
-	void addedToNumber(const QString &added);
-	void onChooseCode(const QString &code);
-
-signals:
-	void voidBackspace(QKeyEvent *e);
-
-protected:
-	void keyPressEvent(QKeyEvent *e) override;
-
-	void correctValue(
-		const QString &was,
-		int wasCursor,
-		QString &now,
-		int &nowCursor) override;
-	void paintAdditionalPlaceholder(Painter &p) override;
-
-private:
-	QVector<int> _pattern;
-	QString _additionalPlaceholder;
-
-};
-
 class PasswordInput : public MaskedInputField {
 public:
 	PasswordInput(QWidget *parent, const style::InputField &st, rpl::producer<QString> placeholder = nullptr, const QString &val = QString());
@@ -749,56 +693,4 @@ protected:
 
 };
 
-class UsernameInput : public MaskedInputField {
-public:
-	UsernameInput(
-		QWidget *parent,
-		const style::InputField &st,
-		rpl::producer<QString> placeholder,
-		const QString &val,
-		bool isLink);
-
-	void setLinkPlaceholder(const QString &placeholder);
-
-protected:
-	void correctValue(
-		const QString &was,
-		int wasCursor,
-		QString &now,
-		int &nowCursor) override;
-	void paintAdditionalPlaceholder(Painter &p) override;
-
-private:
-	QString _linkPlaceholder;
-
-};
-
-class PhoneInput : public MaskedInputField {
-public:
-	PhoneInput(
-		QWidget *parent,
-		const style::InputField &st,
-		rpl::producer<QString> placeholder,
-		const QString &defaultValue,
-		QString value);
-
-	void clearText();
-
-protected:
-	void focusInEvent(QFocusEvent *e) override;
-
-	void correctValue(
-		const QString &was,
-		int wasCursor,
-		QString &now,
-		int &nowCursor) override;
-	void paintAdditionalPlaceholder(Painter &p) override;
-
-private:
-	QString _defaultValue;
-	QVector<int> _pattern;
-	QString _additionalPlaceholder;
-
-};
-
 } // namespace Ui
diff --git a/Telegram/SourceFiles/ui/widgets/labels.cpp b/Telegram/SourceFiles/ui/widgets/labels.cpp
index 5b0dd8de8..75d6e0e7b 100644
--- a/Telegram/SourceFiles/ui/widgets/labels.cpp
+++ b/Telegram/SourceFiles/ui/widgets/labels.cpp
@@ -7,17 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "ui/widgets/labels.h"
 
+#include "ui/text/text_entity.h"
 #include "ui/widgets/popup_menu.h"
-#include "core/click_handler_types.h" // UrlClickHandler
-#include "chat_helpers/message_field.h" // SetClipboardText/MimeDataFromText
-#include "mainwindow.h"
-#include "lang/lang_keys.h"
-#include "facades.h"
-#include "app.h"
+#include "ui/basic_click_handlers.h" // UrlClickHandler
+#include "ui/inactive_press.h"
 
 #include <QtWidgets/QApplication>
 #include <QtGui/QClipboard>
 #include <QtGui/QDrag>
+#include <QtGui/QtEvents>
 #include <QtCore/QMimeData>
 
 namespace Ui {
@@ -49,7 +47,7 @@ void CrossFadeAnimation::addLine(Part was, Part now) {
 void CrossFadeAnimation::paintFrame(Painter &p, float64 positionReady, float64 alphaWas, float64 alphaNow) {
 	if (_lines.isEmpty()) return;
 
-	for_const (auto &line, _lines) {
+	for (const auto &line : std::as_const(_lines)) {
 		paintLine(p, line, positionReady, alphaWas, alphaNow);
 	}
 }
@@ -64,11 +62,12 @@ void CrossFadeAnimation::paintLine(Painter &p, const Line &line, float64 positio
 		return;
 	}
 
+	const auto pixelRatio = style::DevicePixelRatio();
 	auto positionWas = line.was.position;
 	auto positionNow = line.now.position;
 	auto left = anim::interpolate(positionWas.x(), positionNow.x(), positionReady);
-	auto topDelta = (snapshotNow.height() / cIntRetinaFactor()) - (snapshotWas.height() / cIntRetinaFactor());
-	auto widthDelta = (snapshotNow.width() / cIntRetinaFactor()) - (snapshotWas.width() / cIntRetinaFactor());
+	auto topDelta = (snapshotNow.height() / pixelRatio) - (snapshotWas.height() / pixelRatio);
+	auto widthDelta = (snapshotNow.width() / pixelRatio) - (snapshotWas.width() / pixelRatio);
 	auto topWas = anim::interpolate(positionWas.y(), positionNow.y() + topDelta, positionReady);
 	auto topNow = topWas - topDelta;
 
@@ -76,22 +75,22 @@ void CrossFadeAnimation::paintLine(Painter &p, const Line &line, float64 positio
 	if (!snapshotWas.isNull()) {
 		p.drawPixmap(left, topWas, snapshotWas);
 		if (topDelta > 0) {
-			p.fillRect(left, topWas - topDelta, snapshotWas.width() / cIntRetinaFactor(), topDelta, _bg);
+			p.fillRect(left, topWas - topDelta, snapshotWas.width() / pixelRatio, topDelta, _bg);
 		}
 	}
 	if (widthDelta > 0) {
-		p.fillRect(left + (snapshotWas.width() / cIntRetinaFactor()), topNow, widthDelta, snapshotNow.height() / cIntRetinaFactor(), _bg);
+		p.fillRect(left + (snapshotWas.width() / pixelRatio), topNow, widthDelta, snapshotNow.height() / pixelRatio, _bg);
 	}
 
 	p.setOpacity(alphaNow);
 	if (!snapshotNow.isNull()) {
 		p.drawPixmap(left, topNow, snapshotNow);
 		if (topDelta < 0) {
-			p.fillRect(left, topNow + topDelta, snapshotNow.width() / cIntRetinaFactor(), -topDelta, _bg);
+			p.fillRect(left, topNow + topDelta, snapshotNow.width() / pixelRatio, -topDelta, _bg);
 		}
 	}
 	if (widthDelta < 0) {
-		p.fillRect(left + (snapshotNow.width() / cIntRetinaFactor()), topWas, -widthDelta, snapshotWas.height() / cIntRetinaFactor(), _bg);
+		p.fillRect(left + (snapshotNow.width() / pixelRatio), topWas, -widthDelta, snapshotWas.height() / pixelRatio, _bg);
 	}
 }
 
@@ -140,8 +139,7 @@ void LabelSimple::paintEvent(QPaintEvent *e) {
 FlatLabel::FlatLabel(QWidget *parent, const style::FlatLabel &st)
 : RpWidget(parent)
 , _text(st.minWidth ? st.minWidth : QFIXED_MAX)
-, _st(st)
-, _contextCopyText(tr::lng_context_copy_text(tr::now)) {
+, _st(st) {
 	init();
 }
 
@@ -151,8 +149,7 @@ FlatLabel::FlatLabel(
 	const style::FlatLabel &st)
 : RpWidget(parent)
 , _text(st.minWidth ? st.minWidth : QFIXED_MAX)
-, _st(st)
-, _contextCopyText(tr::lng_context_copy_text(tr::now)) {
+, _st(st) {
 	setText(text);
 	init();
 }
@@ -163,14 +160,14 @@ FlatLabel::FlatLabel(
 	const style::FlatLabel &st)
 : RpWidget(parent)
 , _text(st.minWidth ? st.minWidth : QFIXED_MAX)
-, _st(st)
-, _contextCopyText(tr::lng_context_copy_text(tr::now)) {
+, _st(st) {
 	textUpdated();
 	std::move(
 		text
 	) | rpl::start_with_next([this](const QString &value) {
 		setText(value);
 	}, lifetime());
+	init();
 }
 
 FlatLabel::FlatLabel(
@@ -179,17 +176,19 @@ FlatLabel::FlatLabel(
 	const style::FlatLabel &st)
 : RpWidget(parent)
 , _text(st.minWidth ? st.minWidth : QFIXED_MAX)
-, _st(st)
-, _contextCopyText(tr::lng_context_copy_text(tr::now)) {
+, _st(st) {
 	textUpdated();
 	std::move(
 		text
 	) | rpl::start_with_next([this](const TextWithEntities &value) {
 		setMarkedText(value);
 	}, lifetime());
+	init();
 }
 
 void FlatLabel::init() {
+	_contextCopyText = Integration::Instance().phraseContextCopyText();
+
 	_trippleClickTimer.setSingleShot(true);
 
 	_touchSelectTimer.setSingleShot(true);
@@ -323,7 +322,7 @@ void FlatLabel::mousePressEvent(QMouseEvent *e) {
 	dragActionStart(e->globalPos(), e->button());
 }
 
-Ui::Text::StateResult FlatLabel::dragActionStart(const QPoint &p, Qt::MouseButton button) {
+Text::StateResult FlatLabel::dragActionStart(const QPoint &p, Qt::MouseButton button) {
 	_lastMousePos = p;
 	auto state = dragActionUpdate();
 
@@ -331,8 +330,10 @@ Ui::Text::StateResult FlatLabel::dragActionStart(const QPoint &p, Qt::MouseButto
 
 	ClickHandler::pressed();
 	_dragAction = NoDrag;
-	_dragWasInactive = App::wnd()->wasInactivePress();
-	if (_dragWasInactive) App::wnd()->setInactivePress(false);
+	_dragWasInactive = WasInactivePress(window());
+	if (_dragWasInactive) {
+		MarkInactivePress(window(), false);
+	}
 
 	if (ClickHandler::getPressed()) {
 		_dragStartPosition = mapFromGlobal(_lastMousePos);
@@ -376,7 +377,7 @@ Ui::Text::StateResult FlatLabel::dragActionStart(const QPoint &p, Qt::MouseButto
 	return state;
 }
 
-Ui::Text::StateResult FlatLabel::dragActionFinish(const QPoint &p, Qt::MouseButton button) {
+Text::StateResult FlatLabel::dragActionFinish(const QPoint &p, Qt::MouseButton button) {
 	_lastMousePos = p;
 	auto state = dragActionUpdate();
 
@@ -392,15 +393,18 @@ Ui::Text::StateResult FlatLabel::dragActionFinish(const QPoint &p, Qt::MouseButt
 	_selectionType = TextSelectType::Letters;
 
 	if (activated) {
+		const auto guard = window();
 		if (!_clickHandlerFilter
 			|| _clickHandlerFilter(activated, button)) {
-			App::activateClickHandler(activated, button);
+			ActivateClickHandler(guard, activated, button);
 		}
 	}
 
 #if defined Q_OS_LINUX32 || defined Q_OS_LINUX64
 	if (!_selection.empty()) {
-		SetClipboardText(_text.toTextForMimeData(_selection), QClipboard::Selection);
+		TextUtilities::SetClipboardText(
+			_text.toTextForMimeData(_selection),
+			QClipboard::Selection);
 	}
 #endif // Q_OS_LINUX32 || Q_OS_LINUX64
 
@@ -471,7 +475,7 @@ void FlatLabel::keyPressEvent(QKeyEvent *e) {
 	} else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) {
 		auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection;
 		if (!selection.empty()) {
-			SetClipboardText(_text.toTextForMimeData(selection), QClipboard::FindBuffer);
+			TextUtilities::SetClipboardText(_text.toTextForMimeData(selection), QClipboard::FindBuffer);
 		}
 #endif // Q_OS_MAC
 	}
@@ -573,12 +577,13 @@ void FlatLabel::showContextMenu(QContextMenuEvent *e, ContextMenuReason reason)
 		uponSelection = hasSelection;
 	}
 
-	_contextMenu = new Ui::PopupMenu(this);
+	_contextMenu = new PopupMenu(this);
 
 	if (fullSelection && !_contextCopyText.isEmpty()) {
 		_contextMenu->addAction(_contextCopyText, this, SLOT(onCopyContextText()));
 	} else if (uponSelection && !fullSelection) {
-		_contextMenu->addAction(tr::lng_context_copy_selected(tr::now), this, SLOT(onCopySelectedText()));
+		const auto text = Integration::Instance().phraseContextCopySelected();
+		_contextMenu->addAction(text, this, SLOT(onCopySelectedText()));
 	} else if (!hasSelection && !_contextCopyText.isEmpty()) {
 		_contextMenu->addAction(_contextCopyText, this, SLOT(onCopyContextText()));
 	}
@@ -607,12 +612,12 @@ void FlatLabel::showContextMenu(QContextMenuEvent *e, ContextMenuReason reason)
 void FlatLabel::onCopySelectedText() {
 	const auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection;
 	if (!selection.empty()) {
-		SetClipboardText(_text.toTextForMimeData(selection));
+		TextUtilities::SetClipboardText(_text.toTextForMimeData(selection));
 	}
 }
 
 void FlatLabel::onCopyContextText() {
-	SetClipboardText(_text.toTextForMimeData());
+	TextUtilities::SetClipboardText(_text.toTextForMimeData());
 }
 
 void FlatLabel::onTouchSelect() {
@@ -646,8 +651,8 @@ void FlatLabel::onExecuteDrag() {
 		}
 		return TextForMimeData();
 	}();
-	if (auto mimeData = MimeDataFromText(selectedText)) {
-		auto drag = new QDrag(App::wnd());
+	if (auto mimeData = TextUtilities::MimeDataFromText(selectedText)) {
+		auto drag = new QDrag(window());
 		drag->setMimeData(mimeData.release());
 		drag->exec(Qt::CopyAction);
 
@@ -708,7 +713,8 @@ std::unique_ptr<CrossFadeAnimation> FlatLabel::CrossFade(
 		if (lineWidth < 0) {
 			lineWidth = other.lineWidths[index];
 		}
-		auto fullWidth = data.full.width() / cIntRetinaFactor();
+		const auto pixelRatio = style::DevicePixelRatio();
+		auto fullWidth = data.full.width() / pixelRatio;
 		auto top = index * data.lineHeight + data.lineAddTop;
 		auto left = 0;
 		if (label->_st.align & Qt::AlignHCenter) {
@@ -716,10 +722,10 @@ std::unique_ptr<CrossFadeAnimation> FlatLabel::CrossFade(
 		} else if (label->_st.align & Qt::AlignRight) {
 			left += (fullWidth - lineWidth);
 		}
-		auto snapshotRect = data.full.rect().intersected(QRect(left * cIntRetinaFactor(), top * cIntRetinaFactor(), lineWidth * cIntRetinaFactor(), label->_st.style.font->height * cIntRetinaFactor()));
+		auto snapshotRect = data.full.rect().intersected(QRect(left * pixelRatio, top * pixelRatio, lineWidth * pixelRatio, label->_st.style.font->height * pixelRatio));
 		if (!snapshotRect.isEmpty()) {
-			result.snapshot = App::pixmapFromImageInPlace(data.full.copy(snapshotRect));
-			result.snapshot.setDevicePixelRatio(cRetinaFactor());
+			result.snapshot = PixmapFromImage(data.full.copy(snapshotRect));
+			result.snapshot.setDevicePixelRatio(pixelRatio);
 		}
 		auto positionBase = position + label->pos();
 		result.position = positionBase + QPoint(label->_st.margin.left() + left, label->_st.margin.top() + top);
@@ -732,7 +738,7 @@ std::unique_ptr<CrossFadeAnimation> FlatLabel::CrossFade(
 	return result;
 }
 
-Ui::Text::StateResult FlatLabel::dragActionUpdate() {
+Text::StateResult FlatLabel::dragActionUpdate() {
 	auto m = mapFromGlobal(_lastMousePos);
 	auto state = getTextState(m);
 	updateHover(state);
@@ -745,7 +751,7 @@ Ui::Text::StateResult FlatLabel::dragActionUpdate() {
 	return state;
 }
 
-void FlatLabel::updateHover(const Ui::Text::StateResult &state) {
+void FlatLabel::updateHover(const Text::StateResult &state) {
 	bool lnkChanged = ClickHandler::setActive(state.link, this);
 
 	if (!_selectable) {
@@ -808,15 +814,15 @@ void FlatLabel::refreshCursor(bool uponSymbol) {
 	}
 }
 
-Ui::Text::StateResult FlatLabel::getTextState(const QPoint &m) const {
-	Ui::Text::StateRequestElided request;
+Text::StateResult FlatLabel::getTextState(const QPoint &m) const {
+	Text::StateRequestElided request;
 	request.align = _st.align;
 	if (_selectable) {
-		request.flags |= Ui::Text::StateRequest::Flag::LookupSymbol;
+		request.flags |= Text::StateRequest::Flag::LookupSymbol;
 	}
 	int textWidth = width() - _st.margin.left() - _st.margin.right();
 
-	Ui::Text::StateResult state;
+	Text::StateResult state;
 	bool heightExceeded = _st.maxHeight && (_st.maxHeight < _fullTextHeight || textWidth < _text.maxWidth());
 	bool renderElided = _breakEverywhere || heightExceeded;
 	if (renderElided) {
@@ -824,7 +830,7 @@ Ui::Text::StateResult FlatLabel::getTextState(const QPoint &m) const {
 		auto lines = _st.maxHeight ? qMax(_st.maxHeight / lineHeight, 1) : ((height() / lineHeight) + 2);
 		request.lines = lines;
 		if (_breakEverywhere) {
-			request.flags |= Ui::Text::StateRequest::Flag::BreakEverywhere;
+			request.flags |= Text::StateRequest::Flag::BreakEverywhere;
 		}
 		state = _text.getStateElided(m - QPoint(_st.margin.left(), _st.margin.top()), textWidth, request);
 	} else {
diff --git a/Telegram/SourceFiles/ui/widgets/labels.h b/Telegram/SourceFiles/ui/widgets/labels.h
index bb6474d98..5ce3d89c2 100644
--- a/Telegram/SourceFiles/ui/widgets/labels.h
+++ b/Telegram/SourceFiles/ui/widgets/labels.h
@@ -9,11 +9,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "ui/rp_widget.h"
 #include "ui/wrap/padding_wrap.h"
+#include "ui/text/text.h"
+#include "ui/click_handler.h"
 #include "boxes/abstract_box.h"
 #include "styles/style_widgets.h"
 
 #include <QtCore/QTimer>
 
+class QTouchEvent;
+
 namespace Ui {
 
 class PopupMenu;
@@ -155,11 +159,11 @@ private:
 	void init();
 	void textUpdated();
 
-	Ui::Text::StateResult dragActionUpdate();
-	Ui::Text::StateResult dragActionStart(const QPoint &p, Qt::MouseButton button);
-	Ui::Text::StateResult dragActionFinish(const QPoint &p, Qt::MouseButton button);
-	void updateHover(const Ui::Text::StateResult &state);
-	Ui::Text::StateResult getTextState(const QPoint &m) const;
+	Text::StateResult dragActionUpdate();
+	Text::StateResult dragActionStart(const QPoint &p, Qt::MouseButton button);
+	Text::StateResult dragActionFinish(const QPoint &p, Qt::MouseButton button);
+	void updateHover(const Text::StateResult &state);
+	Text::StateResult getTextState(const QPoint &m) const;
 	void refreshCursor(bool uponSymbol);
 
 	int countTextWidth() const;
@@ -205,7 +209,7 @@ private:
 	QPoint _trippleClickPoint;
 	QTimer _trippleClickTimer;
 
-	Ui::PopupMenu *_contextMenu = nullptr;
+	PopupMenu *_contextMenu = nullptr;
 	QString _contextCopyText;
 
 	ClickHandlerFilter _clickHandlerFilter;
@@ -218,7 +222,7 @@ private:
 
 };
 
-class DividerLabel : public PaddingWrap<Ui::FlatLabel> {
+class DividerLabel : public PaddingWrap<FlatLabel> {
 public:
 	using PaddingWrap::PaddingWrap;
 
diff --git a/Telegram/SourceFiles/ui/widgets/menu.cpp b/Telegram/SourceFiles/ui/widgets/menu.cpp
index f658f039f..ba103cba8 100644
--- a/Telegram/SourceFiles/ui/widgets/menu.cpp
+++ b/Telegram/SourceFiles/ui/widgets/menu.cpp
@@ -11,10 +11,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/widgets/checkbox.h"
 #include "ui/text/text.h"
 
+#include <QtGui/QtEvents>
+
 namespace Ui {
 
 struct Menu::ActionData {
-	Ui::Text::String text;
+	Text::String text;
 	QString shortcut;
 	const style::icon *icon = nullptr;
 	const style::icon *iconOver = nullptr;
@@ -313,7 +315,7 @@ void Menu::handleKeyPress(int key) {
 		itemPressed(TriggeredSource::Keyboard);
 		return;
 	}
-	if (key == (rtl() ? Qt::Key_Left : Qt::Key_Right)) {
+	if (key == (style::RightToLeft() ? Qt::Key_Left : Qt::Key_Right)) {
 		if (_selected >= 0 && _actionsData[_selected].hasSubmenu) {
 			itemPressed(TriggeredSource::Keyboard);
 			return;
diff --git a/Telegram/SourceFiles/ui/widgets/popup_menu.cpp b/Telegram/SourceFiles/ui/widgets/popup_menu.cpp
index ea7d15175..3ff6f189a 100644
--- a/Telegram/SourceFiles/ui/widgets/popup_menu.cpp
+++ b/Telegram/SourceFiles/ui/widgets/popup_menu.cpp
@@ -9,14 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "ui/widgets/shadow.h"
 #include "ui/image/image_prepare.h"
+#include "ui/platform/ui_platform_utility.h"
 #include "ui/ui_utility.h"
+#include "ui/delayed_activation.h"
 #include "platform/platform_info.h"
-#include "platform/platform_specific.h"
-#include "mainwindow.h"
-#include "core/application.h"
-#include "lang/lang_keys.h"
-#include "app.h"
 
+#include <QtGui/QtEvents>
+#include <QtGui/QPainter>
 #include <QtWidgets/QApplication>
 #include <QtWidgets/QDesktopWidget>
 
@@ -25,6 +24,7 @@ namespace Ui {
 PopupMenu::PopupMenu(QWidget *parent, const style::PopupMenu &st)
 : RpWidget(parent)
 , _st(st)
+, _roundRect(ImageRoundRadius::Small, _st.menu.itemBg)
 , _menu(this, _st.menu) {
 	init();
 }
@@ -32,6 +32,7 @@ PopupMenu::PopupMenu(QWidget *parent, const style::PopupMenu &st)
 PopupMenu::PopupMenu(QWidget *parent, QMenu *menu, const style::PopupMenu &st)
 : RpWidget(parent)
 , _st(st)
+, _roundRect(ImageRoundRadius::Small, _st.menu.itemBg)
 , _menu(this, menu, _st.menu) {
 	init();
 
@@ -46,9 +47,7 @@ PopupMenu::PopupMenu(QWidget *parent, QMenu *menu, const style::PopupMenu &st)
 void PopupMenu::init() {
 	using namespace rpl::mappers;
 
-	rpl::merge(
-		Core::App().passcodeLockChanges(),
-		Core::App().termsLockChanges()
+	Integration::Instance().forcePopupMenuHideRequests(
 	) | rpl::start_with_next([=] {
 		hideMenu(true);
 	}, lifetime());
@@ -113,7 +112,7 @@ const std::vector<not_null<QAction*>> &PopupMenu::actions() const {
 }
 
 void PopupMenu::paintEvent(QPaintEvent *e) {
-	Painter p(this);
+	QPainter p(this);
 
 	if (_useTransparency) {
 		Platform::StartTranslucentPaint(p, e);
@@ -137,10 +136,10 @@ void PopupMenu::paintEvent(QPaintEvent *e) {
 	}
 }
 
-void PopupMenu::paintBg(Painter &p) {
+void PopupMenu::paintBg(QPainter &p) {
 	if (_useTransparency) {
 		Shadow::paint(p, _inner, width(), _st.shadow);
-		App::roundRect(p, _inner, _st.menu.itemBg, ImageRoundRadius::Small);
+		_roundRect.paint(p, _inner);
 	} else {
 		p.fillRect(0, 0, width() - _padding.right(), _padding.top(), _st.shadow.fallback);
 		p.fillRect(width() - _padding.right(), 0, _padding.right(), height() - _padding.bottom(), _st.shadow.fallback);
@@ -190,7 +189,7 @@ void PopupMenu::popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSou
 		currentSubmenu->hideMenu(true);
 	}
 	if (submenu) {
-		QPoint p(_inner.x() + (rtl() ? _padding.right() : _inner.width() - _padding.left()), _inner.y() + actionTop);
+		QPoint p(_inner.x() + (style::RightToLeft() ? _padding.right() : _inner.width() - _padding.left()), _inner.y() + actionTop);
 		_activeSubmenu = submenu;
 		_activeSubmenu->showMenu(geometry().topLeft() + p, this, source);
 
@@ -213,7 +212,7 @@ bool PopupMenu::handleKeyPress(int key) {
 	} else if (key == Qt::Key_Escape) {
 		hideMenu(_parent ? true : false);
 		return true;
-	} else if (key == (rtl() ? Qt::Key_Right : Qt::Key_Left)) {
+	} else if (key == (style::RightToLeft() ? Qt::Key_Right : Qt::Key_Left)) {
 		if (_parent) {
 			hideMenu(true);
 			return true;
@@ -258,6 +257,18 @@ void PopupMenu::hideEvent(QHideEvent *e) {
 	}
 }
 
+void PopupMenu::keyPressEvent(QKeyEvent *e) {
+	forwardKeyPress(e->key());
+}
+
+void PopupMenu::mouseMoveEvent(QMouseEvent *e) {
+	forwardMouseMove(e->globalPos());
+}
+
+void PopupMenu::mousePressEvent(QMouseEvent *e) {
+	forwardMousePress(e->globalPos());
+}
+
 void PopupMenu::hideMenu(bool fast) {
 	if (isHidden()) return;
 	if (_parent && !_a_opacity.animating()) {
@@ -368,11 +379,12 @@ void PopupMenu::startShowAnimation() {
 		auto cache = grabForPanelAnimation();
 		_a_opacity = base::take(opacityAnimation);
 
+		const auto pixelRatio = style::DevicePixelRatio();
 		_showAnimation = std::make_unique<PanelAnimation>(_st.animation, _origin);
-		_showAnimation->setFinalImage(std::move(cache), QRect(_inner.topLeft() * cIntRetinaFactor(), _inner.size() * cIntRetinaFactor()));
+		_showAnimation->setFinalImage(std::move(cache), QRect(_inner.topLeft() * pixelRatio, _inner.size() * pixelRatio));
 		if (_useTransparency) {
-			auto corners = App::cornersMask(ImageRoundRadius::Small);
-			_showAnimation->setCornerMasks(corners[0], corners[1], corners[2], corners[3]);
+			_showAnimation->setCornerMasks(
+				Images::CornersMask(ImageRoundRadius::Small));
 		} else {
 			_showAnimation->setSkipShadow(true);
 		}
@@ -400,13 +412,14 @@ void PopupMenu::showAnimationCallback() {
 
 QImage PopupMenu::grabForPanelAnimation() {
 	SendPendingMoveResizeEvents(this);
-	auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
-	result.setDevicePixelRatio(cRetinaFactor());
+	const auto pixelRatio = style::DevicePixelRatio();
+	auto result = QImage(size() * pixelRatio, QImage::Format_ARGB32_Premultiplied);
+	result.setDevicePixelRatio(pixelRatio);
 	result.fill(Qt::transparent);
 	{
-		Painter p(&result);
+		QPainter p(&result);
 		if (_useTransparency) {
-			App::roundRect(p, _inner, _st.menu.itemBg, ImageRoundRadius::Small);
+			_roundRect.paint(p, _inner);
 		} else {
 			p.fillRect(_inner, _st.menu.itemBg);
 		}
@@ -428,7 +441,7 @@ void PopupMenu::popup(const QPoint &p) {
 }
 
 void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source) {
-	if (!parent && Platform::IsMac() && !Platform::IsApplicationActive()) {
+	if (!parent && ::Platform::IsMac() && !Platform::IsApplicationActive()) {
 		_hiding = false;
 		_a_opacity.stop();
 		_a_show.stop();
@@ -447,7 +460,7 @@ void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource sou
 	_useTransparency = Platform::TranslucentWindowsSupported(p);
 	setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);
 	handleCompositingUpdate();
-	if (rtl()) {
+	if (style::RightToLeft()) {
 		if (w.x() - width() < r.x() - _padding.left()) {
 			if (_parent && w.x() + _parent->width() - _padding.left() - _padding.right() + width() - _padding.right() <= r.x() + r.width()) {
 				w.setX(w.x() + _parent->width() - _padding.left() - _padding.right());
@@ -488,9 +501,9 @@ void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource sou
 
 	startShowAnimation();
 
-	psUpdateOverlayed(this);
+	Platform::UpdateOverlayed(this);
 	show();
-	psShowOverAll(this);
+	Platform::ShowOverAll(this);
 	activateWindow();
 }
 
@@ -500,7 +513,7 @@ PopupMenu::~PopupMenu() {
 	}
 	if (const auto parent = parentWidget()) {
 		if (QApplication::focusWidget() != nullptr) {
-			Core::App().activateWindowDelayed(parent);
+			ActivateWindowDelayed(parent);
 		}
 	}
 	if (_destroyedCallback) {
diff --git a/Telegram/SourceFiles/ui/widgets/popup_menu.h b/Telegram/SourceFiles/ui/widgets/popup_menu.h
index a9f727a7d..05ca5ceaa 100644
--- a/Telegram/SourceFiles/ui/widgets/popup_menu.h
+++ b/Telegram/SourceFiles/ui/widgets/popup_menu.h
@@ -8,15 +8,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #pragma once
 
 #include "styles/style_widgets.h"
-#include "ui/rp_widget.h"
 #include "ui/widgets/menu.h"
 #include "ui/effects/animations.h"
 #include "ui/effects/panel_animation.h"
+#include "ui/round_rect.h"
+#include "ui/rp_widget.h"
 #include "base/object_ptr.h"
 
 namespace Ui {
 
-class PopupMenu : public Ui::RpWidget, private base::Subscriber {
+class PopupMenu : public RpWidget {
 public:
 	PopupMenu(QWidget *parent, const style::PopupMenu &st = st::defaultPopupMenu);
 	PopupMenu(QWidget *parent, QMenu *menu, const style::PopupMenu &st = st::defaultPopupMenu);
@@ -42,19 +43,12 @@ protected:
 	void paintEvent(QPaintEvent *e) override;
 	void focusOutEvent(QFocusEvent *e) override;
 	void hideEvent(QHideEvent *e) override;
-
-	void keyPressEvent(QKeyEvent *e) override {
-		forwardKeyPress(e->key());
-	}
-	void mouseMoveEvent(QMouseEvent *e) override {
-		forwardMouseMove(e->globalPos());
-	}
-	void mousePressEvent(QMouseEvent *e) override {
-		forwardMousePress(e->globalPos());
-	}
+	void keyPressEvent(QKeyEvent *e) override;
+	void mouseMoveEvent(QMouseEvent *e) override;
+	void mousePressEvent(QMouseEvent *e) override;
 
 private:
-	void paintBg(Painter &p);
+	void paintBg(QPainter &p);
 	void hideFast();
 	void setOrigin(PanelAnimation::Origin origin);
 	void showAnimated(PanelAnimation::Origin origin);
@@ -74,7 +68,7 @@ private:
 	void hideFinished();
 	void showStarted();
 
-	using TriggeredSource = Ui::Menu::TriggeredSource;
+	using TriggeredSource = Menu::TriggeredSource;
 	void handleCompositingUpdate();
 	void handleMenuResize();
 	void handleActivated(QAction *action, int actionTop, TriggeredSource source);
@@ -101,7 +95,8 @@ private:
 
 	const style::PopupMenu &_st;
 
-	object_ptr<Ui::Menu> _menu;
+	RoundRect _roundRect;
+	object_ptr<Menu> _menu;
 
 	using Submenus = QMap<QAction*, SubmenuPointer>;
 	Submenus _submenus;
@@ -115,12 +110,12 @@ private:
 
 	PanelAnimation::Origin _origin = PanelAnimation::Origin::TopLeft;
 	std::unique_ptr<PanelAnimation> _showAnimation;
-	Ui::Animations::Simple _a_show;
+	Animations::Simple _a_show;
 
 	bool _useTransparency = true;
 	bool _hiding = false;
 	QPixmap _cache;
-	Ui::Animations::Simple _a_opacity;
+	Animations::Simple _a_opacity;
 
 	bool _deleteOnHide = true;
 	bool _triggering = false;
diff --git a/Telegram/SourceFiles/ui/widgets/scroll_area.cpp b/Telegram/SourceFiles/ui/widgets/scroll_area.cpp
index 3dcbe270e..37187f476 100644
--- a/Telegram/SourceFiles/ui/widgets/scroll_area.cpp
+++ b/Telegram/SourceFiles/ui/widgets/scroll_area.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "ui/widgets/scroll_area.h"
 
+#include "ui/painter.h"
 #include "ui/ui_utility.h"
 
 #include <QtWidgets/QScrollBar>
@@ -25,7 +26,7 @@ ScrollShadow::ScrollShadow(ScrollArea *parent, const style::ScrollArea *st) : QW
 }
 
 void ScrollShadow::paintEvent(QPaintEvent *e) {
-	Painter p(this);
+	QPainter p(this);
 	p.fillRect(rect(), _st->shColor);
 }
 
@@ -51,7 +52,7 @@ ScrollBar::ScrollBar(ScrollArea *parent, bool vert, const style::ScrollArea *st)
 }
 
 void ScrollBar::recountSize() {
-	setGeometry(_vertical ? QRect(rtl() ? 0 : (area()->width() - _st->width), _st->deltat, _st->width, area()->height() - _st->deltat - _st->deltab) : QRect(_st->deltat, area()->height() - _st->width, area()->width() - _st->deltat - _st->deltab, _st->width));
+	setGeometry(_vertical ? QRect(style::RightToLeft() ? 0 : (area()->width() - _st->width), _st->deltat, _st->width, area()->height() - _st->deltat - _st->deltab) : QRect(_st->deltat, area()->height() - _st->width, area()->width() - _st->deltat - _st->deltab, _st->width));
 }
 
 void ScrollBar::onValueChanged() {
@@ -175,7 +176,7 @@ void ScrollBar::paintEvent(QPaintEvent *e) {
 	auto opacity = _a_opacity.value(_hiding ? 0. : 1.);
 	if (opacity == 0.) return;
 
-	Painter p(this);
+	QPainter p(this);
 	auto deltal = _vertical ? _st->deltax : 0, deltar = _vertical ? _st->deltax : 0;
 	auto deltat = _vertical ? 0 : _st->deltax, deltab = _vertical ? 0 : _st->deltax;
 	p.setPen(Qt::NoPen);
@@ -279,7 +280,7 @@ ScrollArea::ScrollArea(QWidget *parent, const style::ScrollArea &st, bool handle
 , _topShadow(this, &_st)
 , _bottomShadow(this, &_st)
 , _touchEnabled(handleTouch) {
-	setLayoutDirection(cLangDir());
+	setLayoutDirection(style::LayoutDirection());
 	setFocusPolicy(Qt::NoFocus);
 
 	connect(_verticalBar, SIGNAL(topShadowVisibility(bool)), _topShadow, SLOT(changeVisibility(bool)));
@@ -414,23 +415,23 @@ void ScrollArea::touchUpdateSpeed() {
 
 			// fingers are inacurates, we ignore small changes to avoid stopping the autoscroll because
 			// of a small horizontal offset when scrolling vertically
-			const int newSpeedY = (qAbs(pixelsPerSecond.y()) > FingerAccuracyThreshold) ? pixelsPerSecond.y() : 0;
-			const int newSpeedX = (qAbs(pixelsPerSecond.x()) > FingerAccuracyThreshold) ? pixelsPerSecond.x() : 0;
+			const int newSpeedY = (qAbs(pixelsPerSecond.y()) > kFingerAccuracyThreshold) ? pixelsPerSecond.y() : 0;
+			const int newSpeedX = (qAbs(pixelsPerSecond.x()) > kFingerAccuracyThreshold) ? pixelsPerSecond.x() : 0;
 			if (_touchScrollState == TouchScrollState::Auto) {
 				const int oldSpeedY = _touchSpeed.y();
 				const int oldSpeedX = _touchSpeed.x();
 				if ((oldSpeedY <= 0 && newSpeedY <= 0) || ((oldSpeedY >= 0 && newSpeedY >= 0)
 					&& (oldSpeedX <= 0 && newSpeedX <= 0)) || (oldSpeedX >= 0 && newSpeedX >= 0)) {
-					_touchSpeed.setY(snap((oldSpeedY + (newSpeedY / 4)), -MaxScrollAccelerated, +MaxScrollAccelerated));
-					_touchSpeed.setX(snap((oldSpeedX + (newSpeedX / 4)), -MaxScrollAccelerated, +MaxScrollAccelerated));
+					_touchSpeed.setY(std::clamp((oldSpeedY + (newSpeedY / 4)), -kMaxScrollAccelerated, +kMaxScrollAccelerated));
+					_touchSpeed.setX(std::clamp((oldSpeedX + (newSpeedX / 4)), -kMaxScrollAccelerated, +kMaxScrollAccelerated));
 				} else {
 					_touchSpeed = QPoint();
 				}
 			} else {
 				// we average the speed to avoid strange effects with the last delta
 				if (!_touchSpeed.isNull()) {
-					_touchSpeed.setX(snap((_touchSpeed.x() / 4) + (newSpeedX * 3 / 4), -MaxScrollFlick, +MaxScrollFlick));
-					_touchSpeed.setY(snap((_touchSpeed.y() / 4) + (newSpeedY * 3 / 4), -MaxScrollFlick, +MaxScrollFlick));
+					_touchSpeed.setX(std::clamp((_touchSpeed.x() / 4) + (newSpeedX * 3 / 4), -kMaxScrollFlick, +kMaxScrollFlick));
+					_touchSpeed.setY(std::clamp((_touchSpeed.y() / 4) + (newSpeedY * 3 / 4), -kMaxScrollFlick, +kMaxScrollFlick));
 				} else {
 					_touchSpeed = QPoint(newSpeedX, newSpeedY);
 				}
@@ -588,7 +589,7 @@ void ScrollArea::scrollContentsBy(int dx, int dy) {
 }
 
 bool ScrollArea::touchScroll(const QPoint &delta) {
-	int32 scTop = scrollTop(), scMax = scrollTopMax(), scNew = snap(scTop - delta.y(), 0, scMax);
+	int32 scTop = scrollTop(), scMax = scrollTopMax(), scNew = std::clamp(scTop - delta.y(), 0, scMax);
 	if (scNew == scTop) return false;
 
 	scrollToY(scNew);
diff --git a/Telegram/SourceFiles/ui/widgets/scroll_area.h b/Telegram/SourceFiles/ui/widgets/scroll_area.h
index e82875d40..ee9b3221d 100644
--- a/Telegram/SourceFiles/ui/widgets/scroll_area.h
+++ b/Telegram/SourceFiles/ui/widgets/scroll_area.h
@@ -14,9 +14,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include <QtWidgets/QScrollArea>
 #include <QtCore/QTimer>
+#include <QtGui/QtEvents>
 
 namespace Ui {
 
+// 37px per 15ms while select-by-drag.
+inline constexpr auto kMaxScrollSpeed = 37;
+
+// Touch flick ignore 3px.
+inline constexpr auto kFingerAccuracyThreshold = 3;
+
+// 4000px per second.
+inline constexpr auto kMaxScrollAccelerated = 4000;
+
+// 2500px per second.
+inline constexpr auto kMaxScrollFlick = 2500;
+
 enum class TouchScrollState {
 	Manual, // Scrolling manually with the finger on the screen
 	Auto, // Scrolling automatically
@@ -106,14 +119,14 @@ private:
 	crl::time _hideIn = 0;
 	QTimer _hideTimer;
 
-	Ui::Animations::Simple _a_over;
-	Ui::Animations::Simple _a_barOver;
-	Ui::Animations::Simple _a_opacity;
+	Animations::Simple _a_over;
+	Animations::Simple _a_barOver;
+	Animations::Simple _a_opacity;
 
 	QRect _bar;
 };
 
-class ScrollArea : public Ui::RpWidgetWrap<QScrollArea> {
+class ScrollArea : public RpWidgetWrap<QScrollArea> {
 	Q_OBJECT
 
 public:
diff --git a/Telegram/SourceFiles/ui/widgets/separate_panel.cpp b/Telegram/SourceFiles/ui/widgets/separate_panel.cpp
index 3c8d30789..fab73dc97 100644
--- a/Telegram/SourceFiles/ui/widgets/separate_panel.cpp
+++ b/Telegram/SourceFiles/ui/widgets/separate_panel.cpp
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/wrap/fade_wrap.h"
 #include "ui/toast/toast.h"
 #include "ui/widgets/tooltip.h"
+#include "ui/platform/ui_platform_utility.h"
 #include "window/layer_widget.h"
 #include "window/themes/window_theme.h"
 #include "core/application.h"
@@ -164,7 +165,7 @@ void SeparatePanel::initLayout() {
 		}
 	});
 
-	Platform::InitOnTopPanel(this);
+	Ui::Platform::InitOnTopPanel(this);
 }
 
 void SeparatePanel::createBorderImage() {
@@ -350,7 +351,7 @@ void SeparatePanel::setInnerSize(QSize size) {
 
 void SeparatePanel::initGeometry(QSize size) {
 	const auto center = Core::App().getPointForCallPanelCenter();
-	_useTransparency = Platform::TranslucentWindowsSupported(center);
+	_useTransparency = Ui::Platform::TranslucentWindowsSupported(center);
 	_padding = _useTransparency
 		? st::callShadow.extend
 		: style::margins(
@@ -397,7 +398,7 @@ void SeparatePanel::paintEvent(QPaintEvent *e) {
 			finishAnimating();
 			if (isHidden()) return;
 		} else {
-			Platform::StartTranslucentPaint(p, e);
+			Ui::Platform::StartTranslucentPaint(p, e);
 			p.setOpacity(opacity);
 
 			PainterHighQualityEnabler hq(p);
@@ -418,7 +419,7 @@ void SeparatePanel::paintEvent(QPaintEvent *e) {
 	}
 
 	if (_useTransparency) {
-		Platform::StartTranslucentPaint(p, e);
+		Ui::Platform::StartTranslucentPaint(p, e);
 		paintShadowBorder(p);
 	} else {
 		paintOpaqueBorder(p);
diff --git a/Telegram/SourceFiles/ui/widgets/shadow.cpp b/Telegram/SourceFiles/ui/widgets/shadow.cpp
index 2f1ed2682..a4bd8c929 100644
--- a/Telegram/SourceFiles/ui/widgets/shadow.cpp
+++ b/Telegram/SourceFiles/ui/widgets/shadow.cpp
@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "ui/ui_utility.h"
 #include "styles/style_widgets.h"
+#include "styles/palette.h"
+
+#include <QtGui/QPainter>
+#include <QtGui/QtEvents>
 
 namespace Ui {
 
@@ -22,7 +26,11 @@ PlainShadow::PlainShadow(QWidget *parent, style::color color)
 	resize(st::lineWidth, st::lineWidth);
 }
 
-void Shadow::paint(Painter &p, const QRect &box, int outerWidth, const style::Shadow &st, RectParts sides) {
+void PlainShadow::paintEvent(QPaintEvent *e) {
+	QPainter(this).fillRect(e->rect(), _color);
+}
+
+void Shadow::paint(QPainter &p, const QRect &box, int outerWidth, const style::Shadow &st, RectParts sides) {
 	auto left = (sides & RectPart::Left);
 	auto top = (sides & RectPart::Top);
 	auto right = (sides & RectPart::Right);
@@ -90,19 +98,19 @@ QPixmap Shadow::grab(
 		(sides & RectPart::Bottom) ? shadow.extend.bottom() : 0
 	);
 	auto full = QRect(0, 0, extend.left() + rect.width() + extend.right(), extend.top() + rect.height() + extend.bottom());
-	auto result = QPixmap(full.size() * cIntRetinaFactor());
-	result.setDevicePixelRatio(cRetinaFactor());
+	auto result = QPixmap(full.size() * style::DevicePixelRatio());
+	result.setDevicePixelRatio(style::DevicePixelRatio());
 	result.fill(Qt::transparent);
 	{
-		Painter p(&result);
-		Ui::Shadow::paint(p, full.marginsRemoved(extend), full.width(), shadow);
+		QPainter p(&result);
+		Shadow::paint(p, full.marginsRemoved(extend), full.width(), shadow);
 		RenderWidget(p, target, QPoint(extend.left(), extend.top()));
 	}
 	return result;
 }
 
 void Shadow::paintEvent(QPaintEvent *e) {
-	Painter p(this);
+	QPainter p(this);
 	paint(p, rect().marginsRemoved(_st.extend), width(), _st, _sides);
 }
 
diff --git a/Telegram/SourceFiles/ui/widgets/shadow.h b/Telegram/SourceFiles/ui/widgets/shadow.h
index a33c97075..9bbd11e67 100644
--- a/Telegram/SourceFiles/ui/widgets/shadow.h
+++ b/Telegram/SourceFiles/ui/widgets/shadow.h
@@ -22,9 +22,7 @@ public:
 	PlainShadow(QWidget *parent, style::color color);
 
 protected:
-	void paintEvent(QPaintEvent *e) override {
-		Painter(this).fillRect(e->rect(), _color);
-	}
+	void paintEvent(QPaintEvent *e) override;
 
 private:
 	style::color _color;
@@ -43,7 +41,7 @@ public:
 	}
 
 	static void paint(
-		Painter &p,
+		QPainter &p,
 		const QRect &box,
 		int outerWidth,
 		const style::Shadow &st,
diff --git a/Telegram/SourceFiles/ui/widgets/tooltip.cpp b/Telegram/SourceFiles/ui/widgets/tooltip.cpp
index df65d32b6..982d4b24a 100644
--- a/Telegram/SourceFiles/ui/widgets/tooltip.cpp
+++ b/Telegram/SourceFiles/ui/widgets/tooltip.cpp
@@ -7,10 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "ui/widgets/tooltip.h"
 
-#include "mainwindow.h"
-#include "platform/platform_specific.h"
 #include "ui/ui_utility.h"
-#include "app.h"
+#include "ui/platform/ui_platform_utility.h"
+#include "base/invoke_queued.h"
 #include "styles/style_widgets.h"
 
 #include <QtWidgets/QApplication>
@@ -20,14 +19,6 @@ namespace Ui {
 
 Tooltip *TooltipInstance = nullptr;
 
-bool AbstractTooltipShower::tooltipWindowActive() const {
-	if (auto window = App::wnd()) {
-		window->updateIsActive(0);
-		return window->isActive();
-	}
-	return false;
-}
-
 const style::Tooltip *AbstractTooltipShower::tooltipSt() const {
 	return &st::defaultTooltip;
 }
@@ -47,16 +38,13 @@ Tooltip::Tooltip() : RpWidget(nullptr) {
 
 	_showTimer.setCallback([=] { performShow(); });
 	_hideByLeaveTimer.setCallback([=] { Hide(); });
-
-	App::wnd()->windowDeactivateEvents(
-	) | rpl::start_with_next([=] {
-		Hide();
-	}, lifetime());
 }
 
 void Tooltip::performShow() {
 	if (_shower) {
-		auto text = _shower->tooltipWindowActive() ? _shower->tooltipText() : QString();
+		auto text = _shower->tooltipWindowActive()
+			? _shower->tooltipText()
+			: QString();
 		if (text.isEmpty()) {
 			Hide();
 		} else {
@@ -113,7 +101,7 @@ void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *
 
 	// count tooltip position
 	QPoint p(m + _st->shift);
-	if (rtl()) {
+	if (style::RightToLeft()) {
 		p.setX(m.x() - s.width() - _st->shift.x());
 	}
 	if (s.width() < 2 * _st->shift.x()) {
@@ -189,7 +177,7 @@ void Tooltip::Hide() {
 		instance->_showTimer.cancel();
 		instance->_hideByLeaveTimer.cancel();
 		instance->hide();
-		InvokeQueued(instance, [instance] { instance->deleteLater(); });
+		InvokeQueued(instance, [=] { instance->deleteLater(); });
 	}
 }
 
@@ -246,12 +234,15 @@ void ImportantTooltip::countApproachSide(RectParts preferSide) {
 	if ((allowedAbove && allowedBelow) || (!allowedAbove && !allowedBelow)) {
 		_side = preferSide;
 	} else {
-		_side = (allowedAbove ? RectPart::Top : RectPart::Bottom) | (preferSide & (RectPart::Left | RectPart::Center | RectPart::Right));
+		_side = (allowedAbove ? RectPart::Top : RectPart::Bottom)
+			| (preferSide & (RectPart::Left | RectPart::Center | RectPart::Right));
 	}
 	if (_useTransparency) {
-		auto arrow = QImage(QSize(_st.arrow * 2, _st.arrow) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
+		auto arrow = QImage(
+			QSize(_st.arrow * 2, _st.arrow) * style::DevicePixelRatio(),
+			QImage::Format_ARGB32_Premultiplied);
 		arrow.fill(Qt::transparent);
-		arrow.setDevicePixelRatio(cRetinaFactor());
+		arrow.setDevicePixelRatio(style::DevicePixelRatio());
 		{
 			Painter p(&arrow);
 			PainterHighQualityEnabler hq(p);
@@ -266,7 +257,7 @@ void ImportantTooltip::countApproachSide(RectParts preferSide) {
 		if (_side & RectPart::Bottom) {
 			arrow = std::move(arrow).transformed(QTransform(1, 0, 0, -1, 0, 0));
 		}
-		_arrow = App::pixmapFromImageInPlace(std::move(arrow));
+		_arrow = PixmapFromImage(std::move(arrow));
 	}
 }
 
diff --git a/Telegram/SourceFiles/ui/widgets/tooltip.h b/Telegram/SourceFiles/ui/widgets/tooltip.h
index 6bc0e02e2..d2bc51f4f 100644
--- a/Telegram/SourceFiles/ui/widgets/tooltip.h
+++ b/Telegram/SourceFiles/ui/widgets/tooltip.h
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/timer.h"
 #include "base/object_ptr.h"
 #include "ui/effects/animations.h"
+#include "ui/text/text.h"
 #include "ui/rp_widget.h"
 #include "ui/rect_part.h"
 
@@ -24,13 +25,13 @@ class AbstractTooltipShower {
 public:
 	virtual QString tooltipText() const = 0;
 	virtual QPoint tooltipPos() const = 0;
-	virtual bool tooltipWindowActive() const;
+	virtual bool tooltipWindowActive() const = 0;
 	virtual const style::Tooltip *tooltipSt() const;
 	virtual ~AbstractTooltipShower();
 
 };
 
-class Tooltip : public Ui::RpWidget {
+class Tooltip : public RpWidget {
 public:
 	static void Show(int32 delay, const AbstractTooltipShower *shower);
 	static void Hide();
diff --git a/Telegram/SourceFiles/ui/wrap/fade_wrap.cpp b/Telegram/SourceFiles/ui/wrap/fade_wrap.cpp
index 803f411e0..e9e6f094a 100644
--- a/Telegram/SourceFiles/ui/wrap/fade_wrap.cpp
+++ b/Telegram/SourceFiles/ui/wrap/fade_wrap.cpp
@@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/wrap/fade_wrap.h"
 
 #include "ui/widgets/shadow.h"
+#include "ui/painter.h"
+#include "styles/palette.h"
 
 namespace Ui {
 
diff --git a/Telegram/SourceFiles/ui/wrap/slide_wrap.cpp b/Telegram/SourceFiles/ui/wrap/slide_wrap.cpp
index bfc979e55..fe99f0f75 100644
--- a/Telegram/SourceFiles/ui/wrap/slide_wrap.cpp
+++ b/Telegram/SourceFiles/ui/wrap/slide_wrap.cpp
@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "ui/wrap/slide_wrap.h"
 
+#include "styles/style_basic.h"
+
 #include <rpl/combine.h>
 #include <range/v3/algorithm/find.hpp>
 
diff --git a/Telegram/SourceFiles/ui/wrap/slide_wrap.h b/Telegram/SourceFiles/ui/wrap/slide_wrap.h
index d53aefb77..f95bb0f51 100644
--- a/Telegram/SourceFiles/ui/wrap/slide_wrap.h
+++ b/Telegram/SourceFiles/ui/wrap/slide_wrap.h
@@ -63,7 +63,7 @@ private:
 
 	bool _toggled = true;
 	rpl::event_stream<bool> _toggledChanged;
-	Ui::Animations::Simple _animation;
+	Animations::Simple _animation;
 	int _duration = 0;
 
 };
@@ -127,14 +127,14 @@ inline object_ptr<SlideWrap<>> CreateSlideSkipWidget(
 class MultiSlideTracker {
 public:
 	template <typename Widget>
-	void track(const Ui::SlideWrap<Widget> *wrap) {
+	void track(const SlideWrap<Widget> *wrap) {
 		_widgets.push_back(wrap);
 	}
 
 	rpl::producer<bool> atLeastOneShownValue() const;
 
 private:
-	std::vector<const Ui::SlideWrap<Ui::RpWidget>*> _widgets;
+	std::vector<const SlideWrap<Ui::RpWidget>*> _widgets;
 
 };
 
diff --git a/Telegram/SourceFiles/window/layer_widget.cpp b/Telegram/SourceFiles/window/layer_widget.cpp
index 4e68a2634..ff0d601d5 100644
--- a/Telegram/SourceFiles/window/layer_widget.cpp
+++ b/Telegram/SourceFiles/window/layer_widget.cpp
@@ -344,6 +344,16 @@ bool LayerWidget::overlaps(const QRect &globalRect) {
 	return false;
 }
 
+void LayerWidget::mousePressEvent(QMouseEvent *e) {
+	e->accept();
+}
+
+void LayerWidget::resizeEvent(QResizeEvent *e) {
+	if (_resizedCallback) {
+		_resizedCallback();
+	}
+}
+
 void LayerStackWidget::setHideByBackgroundClick(bool hide) {
 	_hideByBackgroundClick = hide;
 }
diff --git a/Telegram/SourceFiles/window/layer_widget.h b/Telegram/SourceFiles/window/layer_widget.h
index 5ffa1fcc2..3d41806f2 100644
--- a/Telegram/SourceFiles/window/layer_widget.h
+++ b/Telegram/SourceFiles/window/layer_widget.h
@@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/rp_widget.h"
 #include "ui/effects/animations.h"
 #include "base/object_ptr.h"
-#include "data/data_file_origin.h"
+#include "base/flags.h"
 
 namespace Lottie {
 class SinglePlayer;
@@ -76,14 +76,8 @@ protected:
 			callback();
 		}
 	}
-	void mousePressEvent(QMouseEvent *e) override {
-		e->accept();
-	}
-	void resizeEvent(QResizeEvent *e) override {
-		if (_resizedCallback) {
-			_resizedCallback();
-		}
-	}
+	void mousePressEvent(QMouseEvent *e) override;
+	void resizeEvent(QResizeEvent *e) override;
 	virtual void doSetInnerFocus() {
 		setFocus();
 	}
diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp
index 0a4ef73d3..0a2562d6b 100644
--- a/Telegram/SourceFiles/window/main_window.cpp
+++ b/Telegram/SourceFiles/window/main_window.cpp
@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "lang/lang_keys.h"
 #include "data/data_session.h"
 #include "main/main_session.h"
+#include "base/crc32hash.h"
 #include "ui/ui_utility.h"
 #include "apiwrap.h"
 #include "mainwindow.h"
@@ -42,7 +43,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 namespace Window {
 namespace {
 
-constexpr auto kInactivePressTimeout = crl::time(200);
 constexpr auto kSaveWindowPositionTimeout = crl::time(1000);
 
 } // namespace
@@ -158,7 +158,6 @@ MainWindow::MainWindow(not_null<Controller*> controller)
 	}
 
 	_isActiveTimer.setCallback([this] { updateIsActive(0); });
-	_inactivePressTimer.setCallback([this] { setInactivePress(false); });
 }
 
 Main::Account &MainWindow::account() const {
@@ -446,8 +445,8 @@ void MainWindow::setTitleVisible(bool visible) {
 }
 
 int32 MainWindow::screenNameChecksum(const QString &name) const {
-	auto bytes = name.toUtf8();
-	return hashCrc32(bytes.constData(), bytes.size());
+	const auto bytes = name.toUtf8();
+	return base::crc32(bytes.constData(), bytes.size());
 }
 
 void MainWindow::setPositionInited() {
@@ -665,15 +664,6 @@ void MainWindow::launchDrag(std::unique_ptr<QMimeData> data) {
 	}
 }
 
-void MainWindow::setInactivePress(bool inactive) {
-	_wasInactivePress = inactive;
-	if (_wasInactivePress) {
-		_inactivePressTimer.callOnce(kInactivePressTimeout);
-	} else {
-		_inactivePressTimer.cancel();
-	}
-}
-
 MainWindow::~MainWindow() = default;
 
 } // namespace Window
diff --git a/Telegram/SourceFiles/window/main_window.h b/Telegram/SourceFiles/window/main_window.h
index e908d0c10..739520329 100644
--- a/Telegram/SourceFiles/window/main_window.h
+++ b/Telegram/SourceFiles/window/main_window.h
@@ -43,10 +43,6 @@ public:
 	}
 	Main::Account &account() const;
 	Window::SessionController *sessionController() const;
-	void setInactivePress(bool inactive);
-	bool wasInactivePress() const {
-		return _wasInactivePress;
-	}
 
 	bool hideNoQuit();
 
@@ -187,8 +183,6 @@ private:
 
 	bool _isActive = false;
 	base::Timer _isActiveTimer;
-	bool _wasInactivePress = false;
-	base::Timer _inactivePressTimer;
 
 	base::Observable<void> _dragFinished;
 	rpl::event_stream<> _leaveEvents;
diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp
index f5f63c00b..54ddca477 100644
--- a/Telegram/SourceFiles/window/notifications_manager_default.cpp
+++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "lang/lang_keys.h"
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/input_fields.h"
+#include "ui/platform/ui_platform_utility.h"
 #include "ui/text_options.h"
 #include "ui/emoji_config.h"
 #include "ui/empty_userpic.h"
@@ -390,7 +391,7 @@ Widget::Widget(
 	setAttribute(Qt::WA_MacAlwaysShowToolWindow);
 	setAttribute(Qt::WA_OpaquePaintEvent);
 
-	Platform::InitOnTopPanel(this);
+	Ui::Platform::InitOnTopPanel(this);
 
 	_a_opacity.start([this] { opacityAnimationCallback(); }, 0., 1., st::notifyFastAnim);
 }
@@ -481,7 +482,7 @@ void Widget::addToHeight(int add) {
 	auto newHeight = height() + add;
 	auto newPosition = computePosition(newHeight);
 	updateGeometry(newPosition.x(), newPosition.y(), width(), newHeight);
-	psUpdateOverlayed(this);
+	Ui::Platform::UpdateOverlayed(this);
 }
 
 void Widget::updateGeometry(int x, int y, int width, int height) {
diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp
index dd7942825..11409f74e 100644
--- a/Telegram/SourceFiles/window/themes/window_theme.cpp
+++ b/Telegram/SourceFiles/window/themes/window_theme.cpp
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/parse_helper.h"
 #include "base/zlib_help.h"
 #include "base/unixtime.h"
+#include "base/crc32hash.h"
 #include "data/data_session.h"
 #include "main/main_account.h" // Account::sessionValue.
 #include "ui/image/image.h"
@@ -353,7 +354,7 @@ bool LoadTheme(
 			cache->colors = style::main_palette::save();
 		}
 		cache->paletteChecksum = style::palette::Checksum();
-		cache->contentChecksum = hashCrc32(content.constData(), content.size());
+		cache->contentChecksum = base::crc32(content.constData(), content.size());
 	}
 	return true;
 }
@@ -364,7 +365,7 @@ bool InitializeFromCache(
 	if (cache.paletteChecksum != style::palette::Checksum()) {
 		return false;
 	}
-	if (cache.contentChecksum != hashCrc32(content.constData(), content.size())) {
+	if (cache.contentChecksum != base::crc32(content.constData(), content.size())) {
 		return false;
 	}
 
@@ -1220,7 +1221,7 @@ void KeepFromEditor(
 	auto &object = saved.object;
 	cache.colors = style::main_palette::save();
 	cache.paletteChecksum = style::palette::Checksum();
-	cache.contentChecksum = hashCrc32(content.constData(), content.size());
+	cache.contentChecksum = base::crc32(content.constData(), content.size());
 	cache.background = themeParsed.background;
 	cache.tiled = themeParsed.tiled;
 	object.cloud = cloud;
diff --git a/Telegram/SourceFiles/window/themes/window_theme_editor_box.cpp b/Telegram/SourceFiles/window/themes/window_theme_editor_box.cpp
index 18c169f84..2b7c2b55c 100644
--- a/Telegram/SourceFiles/window/themes/window_theme_editor_box.cpp
+++ b/Telegram/SourceFiles/window/themes/window_theme_editor_box.cpp
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/widgets/labels.h"
 #include "ui/image/image_prepare.h"
 #include "ui/toast/toast.h"
+#include "ui/special_fields.h"
 #include "info/profile/info_profile_button.h"
 #include "main/main_account.h"
 #include "main/main_session.h"
diff --git a/Telegram/SourceFiles/window/window_lock_widgets.cpp b/Telegram/SourceFiles/window/window_lock_widgets.cpp
index f063542a9..96109def0 100644
--- a/Telegram/SourceFiles/window/window_lock_widgets.cpp
+++ b/Telegram/SourceFiles/window/window_lock_widgets.cpp
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "storage/localstorage.h"
 #include "mainwindow.h"
 #include "core/application.h"
+#include "api/api_text_entities.h"
 #include "ui/text/text.h"
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/checkbox.h"
@@ -187,7 +188,7 @@ TermsLock TermsLock::FromMTP(const MTPDhelp_termsOfService &data) {
 		bytes::make_vector(data.vid().c_dataJSON().vdata().v),
 		TextWithEntities {
 			TextUtilities::Clean(qs(data.vtext())),
-			TextUtilities::EntitiesFromMTP(data.ventities().v) },
+			Api::EntitiesFromMTP(data.ventities().v) },
 		(minAge ? std::make_optional(minAge->v) : std::nullopt),
 		data.is_popup()
 	};
diff --git a/Telegram/ThirdParty/emoji_suggestions/emoji_suggestions.cpp b/Telegram/ThirdParty/emoji_suggestions/emoji_suggestions.cpp
index d20d8ae7e..4c05b8b77 100644
--- a/Telegram/ThirdParty/emoji_suggestions/emoji_suggestions.cpp
+++ b/Telegram/ThirdParty/emoji_suggestions/emoji_suggestions.cpp
@@ -226,7 +226,7 @@ std::vector<Suggestion> Completer::resolve() {
 	if (!_querySize) {
 		return std::vector<Suggestion>();
 	}
-	_initialList = Ui::Emoji::internal::GetReplacements(*_queryBegin);
+	_initialList = internal::GetReplacements(*_queryBegin);
 	if (!_initialList) {
 		return std::vector<Suggestion>();
 	}
diff --git a/Telegram/gyp/codegen.gyp b/Telegram/gyp/codegen.gyp
index b2702a681..fa19de33e 100644
--- a/Telegram/gyp/codegen.gyp
+++ b/Telegram/gyp/codegen.gyp
@@ -55,7 +55,9 @@
       'common_executable.gypi',
       'qt.gypi',
     ],
-
+    'dependencies': [
+      'lib_base.gyp:lib_base',
+    ],
     'include_dirs': [
       '<(src_loc)',
     ],
diff --git a/Telegram/gyp/lib_base.gyp b/Telegram/gyp/lib_base.gyp
index 94c09b93a..04da8e59d 100644
--- a/Telegram/gyp/lib_base.gyp
+++ b/Telegram/gyp/lib_base.gyp
@@ -50,6 +50,8 @@
       '<(src_loc)/base/binary_guard.h',
       '<(src_loc)/base/build_config.h',
       '<(src_loc)/base/bytes.h',
+      '<(src_loc)/base/crc32hash.cpp',
+      '<(src_loc)/base/crc32hash.h',
       '<(src_loc)/base/concurrent_timer.cpp',
       '<(src_loc)/base/concurrent_timer.h',
       '<(src_loc)/base/flags.h',
diff --git a/Telegram/gyp/lib_ui.gyp b/Telegram/gyp/lib_ui.gyp
index baea31c78..9cd552281 100644
--- a/Telegram/gyp/lib_ui.gyp
+++ b/Telegram/gyp/lib_ui.gyp
@@ -15,9 +15,11 @@
     'includes': [
       'common.gypi',
       'qt.gypi',
+      'qt_moc.gypi',
       'codegen_styles_rule.gypi',
       'codegen_rules_ui.gypi',
       'pch.gypi',
+      'openssl.gypi',
     ],
     'dependencies': [
       'codegen.gyp:codegen_emoji',
@@ -42,6 +44,7 @@
       'dependent_style_files': [
       ],
       'style_timestamp': '<(SHARED_INTERMEDIATE_DIR)/update_dependent_styles_ui.timestamp',
+      'list_sources_command': 'python <(DEPTH)/list_sources.py --input <(DEPTH)/lib_ui_sources.txt --replace src_loc=<(src_loc)',
       'pch_source': '<(src_loc)/ui/ui_pch.cpp',
       'pch_header': '<(src_loc)/ui/ui_pch.h',
     },
@@ -58,33 +61,11 @@
     ],
     'sources': [
       '<@(style_files)',
-      '<(src_loc)/ui/effects/animation_value.cpp',
-      '<(src_loc)/ui/effects/animation_value.h',
-      '<(src_loc)/ui/effects/animations.cpp',
-      '<(src_loc)/ui/effects/animations.h',
-      '<(src_loc)/ui/style/style_core.cpp',
-      '<(src_loc)/ui/style/style_core.h',
-      '<(src_loc)/ui/style/style_core_color.cpp',
-      '<(src_loc)/ui/style/style_core_color.h',
-      '<(src_loc)/ui/style/style_core_direction.cpp',
-      '<(src_loc)/ui/style/style_core_direction.h',
-      '<(src_loc)/ui/style/style_core_font.cpp',
-      '<(src_loc)/ui/style/style_core_font.h',
-      '<(src_loc)/ui/style/style_core_icon.cpp',
-      '<(src_loc)/ui/style/style_core_icon.h',
-      '<(src_loc)/ui/style/style_core_scale.cpp',
-      '<(src_loc)/ui/style/style_core_scale.h',
-      '<(src_loc)/ui/style/style_core_types.cpp',
-      '<(src_loc)/ui/style/style_core_types.h',
-      '<(src_loc)/ui/widgets/buttons.cpp',
-      '<(src_loc)/ui/widgets/buttons.h',
-      '<(src_loc)/ui/abstract_button.cpp',
-      '<(src_loc)/ui/abstract_button.h',
-      '<(src_loc)/ui/painter.h',
-      '<(src_loc)/ui/ui_integration.h',
-      '<(src_loc)/ui/ui_utility.h',
-      '<(emoji_suggestions_loc)/emoji_suggestions.cpp',
-      '<(emoji_suggestions_loc)/emoji_suggestions.h',
+      '<!@(<(list_sources_command) <(qt_moc_list_sources_arg))',
+      'lib_ui_sources.txt',
+    ],
+    'sources!': [
+      '<!@(<(list_sources_command) <(qt_moc_list_sources_arg) --exclude_for <(build_os))',
     ],
   }],
 }
diff --git a/Telegram/gyp/lib_ui_sources.txt b/Telegram/gyp/lib_ui_sources.txt
new file mode 100644
index 000000000..d847fd39c
--- /dev/null
+++ b/Telegram/gyp/lib_ui_sources.txt
@@ -0,0 +1,91 @@
+<(src_loc)/ui/effects/animation_value.cpp
+<(src_loc)/ui/effects/animation_value.h
+<(src_loc)/ui/effects/animations.cpp
+<(src_loc)/ui/effects/animations.h
+<(src_loc)/ui/image/image_prepare.cpp
+<(src_loc)/ui/image/image_prepare.h
+<(src_loc)/ui/platform/ui_platform_utility.h
+<(src_loc)/ui/platform/linux/ui_platform_utility_linux.cpp
+<(src_loc)/ui/platform/linux/ui_platform_utility_linux.h
+<(src_loc)/ui/platform/mac/ui_platform_utility_mac.h
+<(src_loc)/ui/platform/mac/ui_platform_utility_mac.mm
+<(src_loc)/ui/platform/win/ui_platform_utility_win.cpp
+<(src_loc)/ui/platform/win/ui_platform_utility_win.h
+<(src_loc)/ui/style/style_core.cpp
+<(src_loc)/ui/style/style_core.h
+<(src_loc)/ui/style/style_core_color.cpp
+<(src_loc)/ui/style/style_core_color.h
+<(src_loc)/ui/style/style_core_direction.cpp
+<(src_loc)/ui/style/style_core_direction.h
+<(src_loc)/ui/style/style_core_font.cpp
+<(src_loc)/ui/style/style_core_font.h
+<(src_loc)/ui/style/style_core_icon.cpp
+<(src_loc)/ui/style/style_core_icon.h
+<(src_loc)/ui/style/style_core_scale.cpp
+<(src_loc)/ui/style/style_core_scale.h
+<(src_loc)/ui/style/style_core_types.cpp
+<(src_loc)/ui/style/style_core_types.h
+<(src_loc)/ui/text/text.cpp
+<(src_loc)/ui/text/text.h
+<(src_loc)/ui/text/text_block.cpp
+<(src_loc)/ui/text/text_block.h
+<(src_loc)/ui/text/text_entity.cpp
+<(src_loc)/ui/text/text_entity.h
+<(src_loc)/ui/text/text_isolated_emoji.h
+<(src_loc)/ui/text/text_utilities.cpp
+<(src_loc)/ui/text/text_utilities.h
+<(src_loc)/ui/widgets/buttons.cpp
+<(src_loc)/ui/widgets/buttons.h
+<(src_loc)/ui/widgets/dropdown_menu.cpp
+<(src_loc)/ui/widgets/dropdown_menu.h
+<(src_loc)/ui/widgets/inner_dropdown.cpp
+<(src_loc)/ui/widgets/inner_dropdown.h
+<(src_loc)/ui/widgets/input_fields.cpp
+<(src_loc)/ui/widgets/input_fields.h
+<(src_loc)/ui/widgets/labels.cpp
+<(src_loc)/ui/widgets/labels.h
+<(src_loc)/ui/widgets/menu.cpp
+<(src_loc)/ui/widgets/menu.h
+<(src_loc)/ui/widgets/popup_menu.cpp
+<(src_loc)/ui/widgets/popup_menu.h
+<(src_loc)/ui/widgets/shadow.cpp
+<(src_loc)/ui/widgets/shadow.h
+<(src_loc)/ui/widgets/scroll_area.cpp
+<(src_loc)/ui/widgets/scroll_area.h
+<(src_loc)/ui/widgets/tooltip.cpp
+<(src_loc)/ui/widgets/tooltip.h
+<(src_loc)/ui/wrap/fade_wrap.cpp
+<(src_loc)/ui/wrap/fade_wrap.h
+<(src_loc)/ui/wrap/padding_wrap.cpp
+<(src_loc)/ui/wrap/padding_wrap.h
+<(src_loc)/ui/wrap/slide_wrap.cpp
+<(src_loc)/ui/wrap/slide_wrap.h
+<(src_loc)/ui/wrap/vertical_layout.cpp
+<(src_loc)/ui/wrap/vertical_layout.h
+<(src_loc)/ui/wrap/wrap.h
+<(src_loc)/ui/abstract_button.cpp
+<(src_loc)/ui/abstract_button.h
+<(src_loc)/ui/click_handler.cpp
+<(src_loc)/ui/click_handler.h
+<(src_loc)/ui/basic_click_handlers.cpp
+<(src_loc)/ui/basic_click_handlers.h
+<(src_loc)/ui/delayed_activation.cpp
+<(src_loc)/ui/delayed_activation.h
+<(src_loc)/ui/emoji_config.cpp
+<(src_loc)/ui/emoji_config.h
+<(src_loc)/ui/focus_persister.h
+<(src_loc)/ui/inactive_press.cpp
+<(src_loc)/ui/inactive_press.h
+<(src_loc)/ui/painter.h
+<(src_loc)/ui/round_rect.cpp
+<(src_loc)/ui/round_rect.h
+<(src_loc)/ui/rp_widget.cpp
+<(src_loc)/ui/rp_widget.h
+<(src_loc)/ui/ui_integration.cpp
+<(src_loc)/ui/ui_integration.h
+<(src_loc)/ui/ui_log.cpp
+<(src_loc)/ui/ui_log.h
+<(src_loc)/ui/ui_utility.cpp
+<(src_loc)/ui/ui_utility.h
+<(emoji_suggestions_loc)/emoji_suggestions.cpp
+<(emoji_suggestions_loc)/emoji_suggestions.h
diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt
index 071ce4ead..8fa8433a9 100644
--- a/Telegram/gyp/telegram_sources.txt
+++ b/Telegram/gyp/telegram_sources.txt
@@ -4,6 +4,8 @@
 <(src_loc)/api/api_sending.h
 <(src_loc)/api/api_single_message_search.cpp
 <(src_loc)/api/api_single_message_search.h
+<(src_loc)/api/api_text_entities.cpp
+<(src_loc)/api/api_text_entities.h
 <(src_loc)/boxes/peers/add_participants_box.cpp
 <(src_loc)/boxes/peers/add_participants_box.h
 <(src_loc)/boxes/peers/edit_contact_box.cpp
@@ -139,14 +141,14 @@
 <(src_loc)/core/application.h
 <(src_loc)/core/changelogs.cpp
 <(src_loc)/core/changelogs.h
-<(src_loc)/core/click_handler.cpp
-<(src_loc)/core/click_handler.h
 <(src_loc)/core/click_handler_types.cpp
 <(src_loc)/core/click_handler_types.h
 <(src_loc)/core/core_cloud_password.cpp
 <(src_loc)/core/core_cloud_password.h
 <(src_loc)/core/core_settings.cpp
 <(src_loc)/core/core_settings.h
+<(src_loc)/core/core_ui_integration.cpp
+<(src_loc)/core/core_ui_integration.h
 <(src_loc)/core/crash_report_window.cpp
 <(src_loc)/core/crash_report_window.h
 <(src_loc)/core/crash_reports.cpp
@@ -757,19 +759,8 @@
 <(src_loc)/ui/image/image.h
 <(src_loc)/ui/image/image_location.cpp
 <(src_loc)/ui/image/image_location.h
-<(src_loc)/ui/image/image_prepare.cpp
-<(src_loc)/ui/image/image_prepare.h
 <(src_loc)/ui/image/image_source.cpp
 <(src_loc)/ui/image/image_source.h
-<(src_loc)/ui/text/text.cpp
-<(src_loc)/ui/text/text.h
-<(src_loc)/ui/text/text_block.cpp
-<(src_loc)/ui/text/text_block.h
-<(src_loc)/ui/text/text_entity.cpp
-<(src_loc)/ui/text/text_entity.h
-<(src_loc)/ui/text/text_isolated_emoji.h
-<(src_loc)/ui/text/text_utilities.cpp
-<(src_loc)/ui/text/text_utilities.h
 <(src_loc)/ui/toast/toast.cpp
 <(src_loc)/ui/toast/toast.h
 <(src_loc)/ui/toast/toast_manager.cpp
@@ -782,59 +773,27 @@
 <(src_loc)/ui/widgets/continuous_sliders.h
 <(src_loc)/ui/widgets/discrete_sliders.cpp
 <(src_loc)/ui/widgets/discrete_sliders.h
-<(src_loc)/ui/widgets/dropdown_menu.cpp
-<(src_loc)/ui/widgets/dropdown_menu.h
-<(src_loc)/ui/widgets/inner_dropdown.cpp
-<(src_loc)/ui/widgets/inner_dropdown.h
-<(src_loc)/ui/widgets/input_fields.cpp
-<(src_loc)/ui/widgets/input_fields.h
-<(src_loc)/ui/widgets/labels.cpp
-<(src_loc)/ui/widgets/labels.h
 <(src_loc)/ui/widgets/level_meter.cpp
 <(src_loc)/ui/widgets/level_meter.h
-<(src_loc)/ui/widgets/menu.cpp
-<(src_loc)/ui/widgets/menu.h
 <(src_loc)/ui/widgets/multi_select.cpp
 <(src_loc)/ui/widgets/multi_select.h
-<(src_loc)/ui/widgets/popup_menu.cpp
-<(src_loc)/ui/widgets/popup_menu.h
 <(src_loc)/ui/widgets/separate_panel.cpp
 <(src_loc)/ui/widgets/separate_panel.h
-<(src_loc)/ui/widgets/scroll_area.cpp
-<(src_loc)/ui/widgets/scroll_area.h
-<(src_loc)/ui/widgets/shadow.cpp
-<(src_loc)/ui/widgets/shadow.h
-<(src_loc)/ui/widgets/tooltip.cpp
-<(src_loc)/ui/widgets/tooltip.h
-<(src_loc)/ui/wrap/fade_wrap.cpp
-<(src_loc)/ui/wrap/fade_wrap.h
-<(src_loc)/ui/wrap/padding_wrap.cpp
-<(src_loc)/ui/wrap/padding_wrap.h
-<(src_loc)/ui/wrap/slide_wrap.cpp
-<(src_loc)/ui/wrap/slide_wrap.h
-<(src_loc)/ui/wrap/vertical_layout.cpp
-<(src_loc)/ui/wrap/vertical_layout.h
-<(src_loc)/ui/wrap/wrap.h
 <(src_loc)/ui/countryinput.cpp
 <(src_loc)/ui/countryinput.h
-<(src_loc)/ui/emoji_config.cpp
-<(src_loc)/ui/emoji_config.h
 <(src_loc)/ui/empty_userpic.cpp
 <(src_loc)/ui/empty_userpic.h
-<(src_loc)/ui/focus_persister.h
 <(src_loc)/ui/grouped_layout.cpp
 <(src_loc)/ui/grouped_layout.h
 <(src_loc)/ui/resize_area.h
-<(src_loc)/ui/rp_widget.cpp
-<(src_loc)/ui/rp_widget.h
 <(src_loc)/ui/search_field_controller.cpp
 <(src_loc)/ui/search_field_controller.h
 <(src_loc)/ui/special_buttons.cpp
 <(src_loc)/ui/special_buttons.h
+<(src_loc)/ui/special_fields.cpp
+<(src_loc)/ui/special_fields.h
 <(src_loc)/ui/text_options.cpp
 <(src_loc)/ui/text_options.h
-<(src_loc)/ui/ui_utility.cpp
-<(src_loc)/ui/ui_utility.h
 <(src_loc)/ui/unread_badge.cpp
 <(src_loc)/ui/unread_badge.h
 <(src_loc)/window/layer_widget.cpp
@@ -911,8 +870,6 @@
 <(src_loc)/qt_static_plugins.cpp
 <(src_loc)/settings.cpp
 <(src_loc)/settings.h
-<(emoji_suggestions_loc)/emoji_suggestions.cpp
-<(emoji_suggestions_loc)/emoji_suggestions.h
 
 platforms: !win
 <(minizip_loc)/crypt.h