ui_integration.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "core/ui_integration.h"
  8. #include "api/api_text_entities.h"
  9. #include "core/local_url_handlers.h"
  10. #include "core/file_utilities.h"
  11. #include "core/application.h"
  12. #include "core/sandbox.h"
  13. #include "core/click_handler_types.h"
  14. #include "data/stickers/data_custom_emoji.h"
  15. #include "data/data_session.h"
  16. #include "iv/iv_instance.h"
  17. #include "ui/text/text_custom_emoji.h"
  18. #include "ui/text/text_utilities.h"
  19. #include "ui/basic_click_handlers.h"
  20. #include "ui/emoji_config.h"
  21. #include "lang/lang_keys.h"
  22. #include "platform/platform_specific.h"
  23. #include "boxes/url_auth_box.h"
  24. #include "core/phone_click_handler.h"
  25. #include "main/main_account.h"
  26. #include "main/main_session.h"
  27. #include "main/main_app_config.h"
  28. #include "mtproto/mtproto_config.h"
  29. #include "window/window_controller.h"
  30. #include "window/window_session_controller.h"
  31. #include "mainwindow.h"
  32. namespace Core {
  33. namespace {
  34. const auto kGoodPrefix = u"https://"_q;
  35. const auto kBadPrefix = u"http://"_q;
  36. [[nodiscard]] QUrl UrlForAutoLogin(const QString &url) {
  37. return (url.startsWith(kGoodPrefix, Qt::CaseInsensitive)
  38. || url.startsWith(kBadPrefix, Qt::CaseInsensitive))
  39. ? QUrl(url)
  40. : QUrl();
  41. }
  42. [[nodiscard]] QString DomainForAutoLogin(const QUrl &url) {
  43. return url.isValid() ? url.host().toLower() : QString();
  44. }
  45. [[nodiscard]] QString UrlWithAutoLoginToken(
  46. const QString &url,
  47. QUrl parsed,
  48. const QString &domain,
  49. QVariant context) {
  50. const auto my = context.value<ClickHandlerContext>();
  51. const auto window = my.sessionWindow.get();
  52. const auto &active = window
  53. ? window->session().account()
  54. : Core::App().activeAccount();
  55. const auto token = active.mtp().configValues().autologinToken;
  56. const auto domains = active.appConfig().get<std::vector<QString>>(
  57. "autologin_domains",
  58. {});
  59. if (token.isEmpty()
  60. || domain.isEmpty()
  61. || !ranges::contains(domains, domain)) {
  62. return url;
  63. }
  64. const auto added = "autologin_token=" + token;
  65. parsed.setQuery(parsed.hasQuery()
  66. ? (parsed.query() + '&' + added)
  67. : added);
  68. if (url.startsWith(kBadPrefix, Qt::CaseInsensitive)) {
  69. parsed.setScheme("https");
  70. }
  71. return QString::fromUtf8(parsed.toEncoded());
  72. }
  73. [[nodiscard]] bool BotAutoLogin(
  74. const QString &url,
  75. const QString &domain,
  76. QVariant context) {
  77. auto &account = Core::App().activeAccount();
  78. const auto &config = account.appConfig();
  79. const auto domains = config.get<std::vector<QString>>(
  80. "url_auth_domains",
  81. {});
  82. if (!account.sessionExists()
  83. || domain.isEmpty()
  84. || !ranges::contains(domains, domain)) {
  85. return false;
  86. }
  87. const auto good = url.startsWith(kBadPrefix, Qt::CaseInsensitive)
  88. ? (kGoodPrefix + url.mid(kBadPrefix.size()))
  89. : url;
  90. UrlAuthBox::Activate(&account.session(), good, context);
  91. return true;
  92. }
  93. [[nodiscard]] QString OpenGLCheckFilePath() {
  94. return cWorkingDir() + "tdata/opengl_crash_check";
  95. }
  96. [[nodiscard]] QString ANGLEBackendFilePath() {
  97. return cWorkingDir() + "tdata/angle_backend";
  98. }
  99. } // namespace
  100. Ui::Text::MarkedContext TextContext(TextContextArgs &&args) {
  101. using Context = Ui::Text::MarkedContext;
  102. using Factory = Ui::Text::CustomEmojiFactory;
  103. const auto session = args.session;
  104. auto simple = [session](QStringView data, const Context &context) {
  105. return session->data().customEmojiManager().create(
  106. data,
  107. context.repaint);
  108. };
  109. auto factory = !args.customEmojiLoopLimit
  110. ? Factory(simple)
  111. : (args.customEmojiLoopLimit > 0)
  112. ? Factory([simple, loop = args.customEmojiLoopLimit](
  113. QStringView data,
  114. const Context &context) {
  115. return std::make_unique<Ui::Text::LimitedLoopsEmoji>(
  116. simple(data, context),
  117. loop);
  118. })
  119. : Factory([simple](
  120. QStringView data,
  121. const Context &context) {
  122. return std::make_unique<Ui::Text::FirstFrameEmoji>(
  123. simple(data, context));
  124. });
  125. args.details.session = session;
  126. return {
  127. .repaint = std::move(args.repaint),
  128. .customEmojiFactory = std::move(factory),
  129. .other = std::move(args.details),
  130. };
  131. }
  132. void UiIntegration::postponeCall(FnMut<void()> &&callable) {
  133. Sandbox::Instance().postponeCall(std::move(callable));
  134. }
  135. void UiIntegration::registerLeaveSubscription(not_null<QWidget*> widget) {
  136. Core::App().registerLeaveSubscription(widget);
  137. }
  138. void UiIntegration::unregisterLeaveSubscription(not_null<QWidget*> widget) {
  139. Core::App().unregisterLeaveSubscription(widget);
  140. }
  141. QString UiIntegration::emojiCacheFolder() {
  142. return cWorkingDir() + "tdata/emoji";
  143. }
  144. QString UiIntegration::openglCheckFilePath() {
  145. return OpenGLCheckFilePath();
  146. }
  147. QString UiIntegration::angleBackendFilePath() {
  148. return ANGLEBackendFilePath();
  149. }
  150. void UiIntegration::textActionsUpdated() {
  151. if (const auto window = Core::App().activeWindow()) {
  152. window->widget()->updateGlobalMenu();
  153. }
  154. }
  155. void UiIntegration::activationFromTopPanel() {
  156. Platform::IgnoreApplicationActivationRightNow();
  157. }
  158. bool UiIntegration::screenIsLocked() {
  159. return Core::App().screenIsLocked();
  160. }
  161. std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
  162. const EntityLinkData &data,
  163. const Ui::Text::MarkedContext &context) {
  164. const auto my = std::any_cast<Core::TextContextDetails>(&context.other);
  165. switch (data.type) {
  166. case EntityType::Url:
  167. return (!data.data.isEmpty()
  168. && UrlClickHandler::IsSuspicious(data.data))
  169. ? std::make_shared<HiddenUrlClickHandler>(data.data)
  170. : Integration::createLinkHandler(data, context);
  171. case EntityType::CustomUrl:
  172. return !data.data.isEmpty()
  173. ? std::make_shared<HiddenUrlClickHandler>(data.data)
  174. : Integration::createLinkHandler(data, context);
  175. case EntityType::BotCommand:
  176. return std::make_shared<BotCommandClickHandler>(data.data);
  177. case EntityType::Hashtag:
  178. using HashtagMentionType = TextContextDetails::HashtagMentionType;
  179. if (my && my->type == HashtagMentionType::Twitter) {
  180. return std::make_shared<UrlClickHandler>(
  181. (u"https://twitter.com/hashtag/"_q
  182. + data.data.mid(1)
  183. + u"?src=hash"_q),
  184. true);
  185. } else if (my && my->type == HashtagMentionType::Instagram) {
  186. return std::make_shared<UrlClickHandler>(
  187. (u"https://instagram.com/explore/tags/"_q
  188. + data.data.mid(1)
  189. + '/'),
  190. true);
  191. }
  192. return std::make_shared<HashtagClickHandler>(data.data);
  193. case EntityType::Cashtag:
  194. return std::make_shared<CashtagClickHandler>(data.data);
  195. case EntityType::Mention:
  196. using HashtagMentionType = TextContextDetails::HashtagMentionType;
  197. if (my && my->type == HashtagMentionType::Twitter) {
  198. return std::make_shared<UrlClickHandler>(
  199. u"https://twitter.com/"_q + data.data.mid(1),
  200. true);
  201. } else if (my && my->type == HashtagMentionType::Instagram) {
  202. return std::make_shared<UrlClickHandler>(
  203. u"https://instagram.com/"_q + data.data.mid(1) + '/',
  204. true);
  205. }
  206. return std::make_shared<MentionClickHandler>(data.data);
  207. case EntityType::MentionName: {
  208. auto fields = TextUtilities::MentionNameDataToFields(data.data);
  209. if (!my || !my->session) {
  210. LOG(("Mention name without a session: %1").arg(data.data));
  211. } else if (fields.userId) {
  212. return std::make_shared<MentionNameClickHandler>(
  213. my->session,
  214. data.text,
  215. fields.userId,
  216. fields.accessHash);
  217. } else {
  218. LOG(("Bad mention name: %1").arg(data.data));
  219. }
  220. } break;
  221. case EntityType::Code:
  222. return std::make_shared<MonospaceClickHandler>(data.text, data.type);
  223. case EntityType::Pre:
  224. return std::make_shared<MonospaceClickHandler>(data.text, data.type);
  225. case EntityType::Phone:
  226. return my->session
  227. ? std::make_shared<PhoneClickHandler>(my->session, data.text)
  228. : nullptr;
  229. }
  230. return Integration::createLinkHandler(data, context);
  231. }
  232. bool UiIntegration::handleUrlClick(
  233. const QString &url,
  234. const QVariant &context) {
  235. const auto local = Core::TryConvertUrlToLocal(url);
  236. if (Core::InternalPassportLink(local)) {
  237. return true;
  238. }
  239. if (UrlClickHandler::IsEmail(url)) {
  240. File::OpenEmailLink(url);
  241. return true;
  242. } else if (local.startsWith(u"tg://"_q, Qt::CaseInsensitive)) {
  243. Core::App().openLocalUrl(local, context);
  244. return true;
  245. } else if (local.startsWith(u"tonsite://"_q, Qt::CaseInsensitive)) {
  246. Core::App().iv().showTonSite(local, context);
  247. return true;
  248. } else if (local.startsWith(u"internal:"_q, Qt::CaseInsensitive)) {
  249. Core::App().openInternalUrl(local, context);
  250. return true;
  251. } else if (Iv::PreferForUri(url)
  252. && !context.value<ClickHandlerContext>().ignoreIv) {
  253. const auto my = context.value<ClickHandlerContext>();
  254. if (const auto controller = my.sessionWindow.get()) {
  255. Core::App().iv().openWithIvPreferred(controller, url, context);
  256. return true;
  257. }
  258. }
  259. auto parsed = UrlForAutoLogin(url);
  260. const auto domain = DomainForAutoLogin(parsed);
  261. const auto skip = context.value<ClickHandlerContext>().skipBotAutoLogin;
  262. if (skip || !BotAutoLogin(url, domain, context)) {
  263. File::OpenUrl(
  264. UrlWithAutoLoginToken(url, std::move(parsed), domain, context));
  265. }
  266. return true;
  267. }
  268. bool UiIntegration::copyPreOnClick(const QVariant &context) {
  269. const auto my = context.value<ClickHandlerContext>();
  270. if (const auto window = my.sessionWindow.get()) {
  271. window->showToast(tr::lng_code_copied(tr::now));
  272. } else if (my.show) {
  273. my.show->showToast(tr::lng_code_copied(tr::now));
  274. }
  275. return true;
  276. }
  277. rpl::producer<> UiIntegration::forcePopupMenuHideRequests() {
  278. return Core::App().passcodeLockChanges() | rpl::to_empty;
  279. }
  280. const Ui::Emoji::One *UiIntegration::defaultEmojiVariant(
  281. const Ui::Emoji::One *emoji) {
  282. if (!emoji) {
  283. return emoji;
  284. }
  285. const auto result = Core::App().settings().lookupEmojiVariant(emoji);
  286. Core::App().settings().incrementRecentEmoji({ result });
  287. return result;
  288. }
  289. QString UiIntegration::phraseContextCopyText() {
  290. return tr::lng_context_copy_text(tr::now);
  291. }
  292. QString UiIntegration::phraseContextCopyEmail() {
  293. return tr::lng_context_copy_email(tr::now);
  294. }
  295. QString UiIntegration::phraseContextCopyLink() {
  296. return tr::lng_context_copy_link(tr::now);
  297. }
  298. QString UiIntegration::phraseContextCopySelected() {
  299. return tr::lng_context_copy_selected(tr::now);
  300. }
  301. QString UiIntegration::phraseFormattingTitle() {
  302. return tr::lng_menu_formatting(tr::now);
  303. }
  304. QString UiIntegration::phraseFormattingLinkCreate() {
  305. return tr::lng_menu_formatting_link_create(tr::now);
  306. }
  307. QString UiIntegration::phraseFormattingLinkEdit() {
  308. return tr::lng_menu_formatting_link_edit(tr::now);
  309. }
  310. QString UiIntegration::phraseFormattingClear() {
  311. return tr::lng_menu_formatting_clear(tr::now);
  312. }
  313. QString UiIntegration::phraseFormattingBold() {
  314. return tr::lng_menu_formatting_bold(tr::now);
  315. }
  316. QString UiIntegration::phraseFormattingItalic() {
  317. return tr::lng_menu_formatting_italic(tr::now);
  318. }
  319. QString UiIntegration::phraseFormattingUnderline() {
  320. return tr::lng_menu_formatting_underline(tr::now);
  321. }
  322. QString UiIntegration::phraseFormattingStrikeOut() {
  323. return tr::lng_menu_formatting_strike_out(tr::now);
  324. }
  325. QString UiIntegration::phraseFormattingBlockquote() {
  326. return tr::lng_menu_formatting_blockquote(tr::now);
  327. }
  328. QString UiIntegration::phraseFormattingMonospace() {
  329. return tr::lng_menu_formatting_monospace(tr::now);
  330. }
  331. QString UiIntegration::phraseFormattingSpoiler() {
  332. return tr::lng_menu_formatting_spoiler(tr::now);
  333. }
  334. QString UiIntegration::phraseButtonOk() {
  335. return tr::lng_box_ok(tr::now);
  336. }
  337. QString UiIntegration::phraseButtonClose() {
  338. return tr::lng_close(tr::now);
  339. }
  340. QString UiIntegration::phraseButtonCancel() {
  341. return tr::lng_cancel(tr::now);
  342. }
  343. QString UiIntegration::phrasePanelCloseWarning() {
  344. return tr::lng_bot_close_warning_title(tr::now);
  345. }
  346. QString UiIntegration::phrasePanelCloseUnsaved() {
  347. return tr::lng_bot_close_warning(tr::now);
  348. }
  349. QString UiIntegration::phrasePanelCloseAnyway() {
  350. return tr::lng_bot_close_warning_sure(tr::now);
  351. }
  352. QString UiIntegration::phraseBotSharePhone() {
  353. return tr::lng_bot_share_phone(tr::now);
  354. }
  355. QString UiIntegration::phraseBotSharePhoneTitle() {
  356. return tr::lng_settings_phone_label(tr::now);
  357. }
  358. QString UiIntegration::phraseBotSharePhoneConfirm() {
  359. return tr::lng_bot_share_phone_confirm(tr::now);
  360. }
  361. QString UiIntegration::phraseBotAllowWrite() {
  362. return tr::lng_bot_allow_write(tr::now);
  363. }
  364. QString UiIntegration::phraseBotAllowWriteTitle() {
  365. return tr::lng_bot_allow_write_title(tr::now);
  366. }
  367. QString UiIntegration::phraseBotAllowWriteConfirm() {
  368. return tr::lng_bot_allow_write_confirm(tr::now);
  369. }
  370. QString UiIntegration::phraseQuoteHeaderCopy() {
  371. return tr::lng_code_block_header_copy(tr::now);
  372. }
  373. bool OpenGLLastCheckFailed() {
  374. return QFile::exists(OpenGLCheckFilePath());
  375. }
  376. } // namespace Core