main_window_linux.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  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 "platform/linux/main_window_linux.h"
  8. #include "styles/style_window.h"
  9. #include "platform/linux/specific_linux.h"
  10. #include "history/history.h"
  11. #include "history/history_widget.h"
  12. #include "history/history_inner_widget.h"
  13. #include "main/main_account.h" // Account::sessionChanges.
  14. #include "main/main_session.h"
  15. #include "mainwindow.h"
  16. #include "core/application.h"
  17. #include "core/core_settings.h"
  18. #include "core/sandbox.h"
  19. #include "boxes/peer_list_controllers.h"
  20. #include "boxes/about_box.h"
  21. #include "lang/lang_keys.h"
  22. #include "storage/localstorage.h"
  23. #include "window/window_controller.h"
  24. #include "window/window_session_controller.h"
  25. #include "base/platform/base_platform_info.h"
  26. #include "base/event_filter.h"
  27. #include "ui/platform/ui_platform_window_title.h"
  28. #include "ui/widgets/popup_menu.h"
  29. #include "ui/widgets/fields/input_field.h"
  30. #include "ui/ui_utility.h"
  31. #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
  32. #include "base/platform/linux/base_linux_xcb_utilities.h"
  33. #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
  34. #include <QtCore/QSize>
  35. #include <QtCore/QMimeData>
  36. #include <QtGui/QWindow>
  37. #include <QtWidgets/QMenuBar>
  38. #include <QtWidgets/QLineEdit>
  39. #include <QtWidgets/QTextEdit>
  40. #include <gio/gio.hpp>
  41. namespace Platform {
  42. namespace {
  43. using WorkMode = Core::Settings::WorkMode;
  44. #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
  45. void XCBSkipTaskbar(QWindow *window, bool skip) {
  46. const base::Platform::XCB::Connection connection;
  47. if (!connection || xcb_connection_has_error(connection)) {
  48. return;
  49. }
  50. const auto root = base::Platform::XCB::GetRootWindow(connection);
  51. if (!root) {
  52. return;
  53. }
  54. const auto stateAtom = base::Platform::XCB::GetAtom(
  55. connection,
  56. "_NET_WM_STATE");
  57. if (!stateAtom) {
  58. return;
  59. }
  60. const auto skipTaskbarAtom = base::Platform::XCB::GetAtom(
  61. connection,
  62. "_NET_WM_STATE_SKIP_TASKBAR");
  63. if (!skipTaskbarAtom) {
  64. return;
  65. }
  66. xcb_client_message_event_t xev;
  67. xev.response_type = XCB_CLIENT_MESSAGE;
  68. xev.type = stateAtom;
  69. xev.sequence = 0;
  70. xev.window = window->winId();
  71. xev.format = 32;
  72. xev.data.data32[0] = skip ? 1 : 0;
  73. xev.data.data32[1] = skipTaskbarAtom;
  74. xev.data.data32[2] = 0;
  75. xev.data.data32[3] = 0;
  76. xev.data.data32[4] = 0;
  77. free(
  78. xcb_request_check(
  79. connection,
  80. xcb_send_event_checked(
  81. connection,
  82. false,
  83. root,
  84. XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
  85. | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY,
  86. reinterpret_cast<const char*>(&xev))));
  87. }
  88. #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
  89. void SkipTaskbar(QWindow *window, bool skip) {
  90. #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
  91. if (IsX11()) {
  92. XCBSkipTaskbar(window, skip);
  93. return;
  94. }
  95. #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
  96. }
  97. void SendKeySequence(
  98. Qt::Key key,
  99. Qt::KeyboardModifiers modifiers = Qt::NoModifier) {
  100. const auto focused = QApplication::focusWidget();
  101. if (qobject_cast<QLineEdit*>(focused)
  102. || qobject_cast<QTextEdit*>(focused)
  103. || dynamic_cast<HistoryInner*>(focused)) {
  104. QApplication::postEvent(
  105. focused,
  106. new QKeyEvent(QEvent::KeyPress, key, modifiers));
  107. QApplication::postEvent(
  108. focused,
  109. new QKeyEvent(QEvent::KeyRelease, key, modifiers));
  110. }
  111. }
  112. void ForceDisabled(QAction *action, bool disabled) {
  113. if (action->isEnabled()) {
  114. if (disabled) action->setDisabled(true);
  115. } else if (!disabled) {
  116. action->setDisabled(false);
  117. }
  118. }
  119. } // namespace
  120. MainWindow::MainWindow(not_null<Window::Controller*> controller)
  121. : Window::MainWindow(controller) {
  122. }
  123. void MainWindow::workmodeUpdated(Core::Settings::WorkMode mode) {
  124. if (!TrayIconSupported()) {
  125. return;
  126. }
  127. SkipTaskbar(windowHandle(), mode == WorkMode::TrayOnly);
  128. }
  129. void MainWindow::unreadCounterChangedHook() {
  130. updateUnityCounter();
  131. }
  132. void MainWindow::updateWindowIcon() {
  133. const auto session = sessionController()
  134. ? &sessionController()->session()
  135. : nullptr;
  136. setWindowIcon(Window::CreateIcon(session));
  137. }
  138. void MainWindow::updateUnityCounter() {
  139. #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
  140. qApp->setBadgeNumber(Core::App().unreadBadge());
  141. #else // Qt >= 6.6.0
  142. using namespace gi::repository;
  143. static const auto djbStringHash = [](const std::string &string) {
  144. uint hash = 5381;
  145. for (const auto &curChar : string) {
  146. hash = (hash << 5) + hash + curChar;
  147. }
  148. return hash;
  149. };
  150. const auto launcherUrl = "application://"
  151. + QGuiApplication::desktopFileName().toStdString()
  152. + ".desktop";
  153. const auto counterSlice = std::min(Core::App().unreadBadge(), 9999);
  154. auto connection = Gio::bus_get_sync(Gio::BusType::SESSION_, nullptr);
  155. if (!connection) {
  156. return;
  157. }
  158. connection.emit_signal(
  159. {},
  160. "/com/canonical/unity/launcherentry/"
  161. + std::to_string(djbStringHash(launcherUrl)),
  162. "com.canonical.Unity.LauncherEntry",
  163. "Update",
  164. GLib::Variant::new_tuple({
  165. GLib::Variant::new_string(launcherUrl),
  166. GLib::Variant::new_array({
  167. GLib::Variant::new_dict_entry(
  168. GLib::Variant::new_string("count"),
  169. GLib::Variant::new_variant(
  170. GLib::Variant::new_int64(counterSlice))),
  171. GLib::Variant::new_dict_entry(
  172. GLib::Variant::new_string("count-visible"),
  173. GLib::Variant::new_variant(
  174. GLib::Variant::new_boolean(counterSlice))),
  175. }),
  176. }));
  177. #endif // Qt < 6.6.0
  178. }
  179. void MainWindow::createGlobalMenu() {
  180. const auto ensureWindowShown = [=] {
  181. if (isHidden()) {
  182. showFromTray();
  183. }
  184. };
  185. psMainMenu = new QMenuBar(this);
  186. psMainMenu->hide();
  187. auto file = psMainMenu->addMenu(tr::lng_mac_menu_file(tr::now));
  188. psLogout = file->addAction(
  189. tr::lng_mac_menu_logout(tr::now),
  190. this,
  191. [=] {
  192. ensureWindowShown();
  193. controller().showLogoutConfirmation();
  194. });
  195. auto quit = file->addAction(
  196. tr::lng_mac_menu_quit_telegram(tr::now, lt_telegram, u"Telegram"_q),
  197. this,
  198. [=] { quitFromTray(); },
  199. QKeySequence::Quit);
  200. quit->setMenuRole(QAction::QuitRole);
  201. quit->setShortcutContext(Qt::WidgetShortcut);
  202. auto edit = psMainMenu->addMenu(tr::lng_mac_menu_edit(tr::now));
  203. psUndo = edit->addAction(
  204. tr::lng_linux_menu_undo(tr::now),
  205. [] { SendKeySequence(Qt::Key_Z, Qt::ControlModifier); },
  206. QKeySequence::Undo);
  207. psUndo->setShortcutContext(Qt::WidgetShortcut);
  208. psRedo = edit->addAction(
  209. tr::lng_linux_menu_redo(tr::now),
  210. [] {
  211. SendKeySequence(
  212. Qt::Key_Z,
  213. Qt::ControlModifier | Qt::ShiftModifier);
  214. },
  215. QKeySequence::Redo);
  216. psRedo->setShortcutContext(Qt::WidgetShortcut);
  217. edit->addSeparator();
  218. psCut = edit->addAction(
  219. tr::lng_mac_menu_cut(tr::now),
  220. [] { SendKeySequence(Qt::Key_X, Qt::ControlModifier); },
  221. QKeySequence::Cut);
  222. psCut->setShortcutContext(Qt::WidgetShortcut);
  223. psCopy = edit->addAction(
  224. tr::lng_mac_menu_copy(tr::now),
  225. [] { SendKeySequence(Qt::Key_C, Qt::ControlModifier); },
  226. QKeySequence::Copy);
  227. psCopy->setShortcutContext(Qt::WidgetShortcut);
  228. psPaste = edit->addAction(
  229. tr::lng_mac_menu_paste(tr::now),
  230. [] { SendKeySequence(Qt::Key_V, Qt::ControlModifier); },
  231. QKeySequence::Paste);
  232. psPaste->setShortcutContext(Qt::WidgetShortcut);
  233. psDelete = edit->addAction(
  234. tr::lng_mac_menu_delete(tr::now),
  235. [] { SendKeySequence(Qt::Key_Delete); },
  236. QKeySequence(Qt::ControlModifier | Qt::Key_Backspace));
  237. psDelete->setShortcutContext(Qt::WidgetShortcut);
  238. edit->addSeparator();
  239. psBold = edit->addAction(
  240. tr::lng_menu_formatting_bold(tr::now),
  241. [] { SendKeySequence(Qt::Key_B, Qt::ControlModifier); },
  242. QKeySequence::Bold);
  243. psBold->setShortcutContext(Qt::WidgetShortcut);
  244. psItalic = edit->addAction(
  245. tr::lng_menu_formatting_italic(tr::now),
  246. [] { SendKeySequence(Qt::Key_I, Qt::ControlModifier); },
  247. QKeySequence::Italic);
  248. psItalic->setShortcutContext(Qt::WidgetShortcut);
  249. psUnderline = edit->addAction(
  250. tr::lng_menu_formatting_underline(tr::now),
  251. [] { SendKeySequence(Qt::Key_U, Qt::ControlModifier); },
  252. QKeySequence::Underline);
  253. psUnderline->setShortcutContext(Qt::WidgetShortcut);
  254. psStrikeOut = edit->addAction(
  255. tr::lng_menu_formatting_strike_out(tr::now),
  256. [] {
  257. SendKeySequence(
  258. Qt::Key_X,
  259. Qt::ControlModifier | Qt::ShiftModifier);
  260. },
  261. Ui::kStrikeOutSequence);
  262. psStrikeOut->setShortcutContext(Qt::WidgetShortcut);
  263. psBlockquote = edit->addAction(
  264. tr::lng_menu_formatting_blockquote(tr::now),
  265. [] {
  266. SendKeySequence(
  267. Qt::Key_Period,
  268. Qt::ControlModifier | Qt::ShiftModifier);
  269. },
  270. Ui::kBlockquoteSequence);
  271. psBlockquote->setShortcutContext(Qt::WidgetShortcut);
  272. psMonospace = edit->addAction(
  273. tr::lng_menu_formatting_monospace(tr::now),
  274. [] {
  275. SendKeySequence(
  276. Qt::Key_M,
  277. Qt::ControlModifier | Qt::ShiftModifier);
  278. },
  279. Ui::kMonospaceSequence);
  280. psMonospace->setShortcutContext(Qt::WidgetShortcut);
  281. psClearFormat = edit->addAction(
  282. tr::lng_menu_formatting_clear(tr::now),
  283. [] {
  284. SendKeySequence(
  285. Qt::Key_N,
  286. Qt::ControlModifier | Qt::ShiftModifier);
  287. },
  288. Ui::kClearFormatSequence);
  289. psClearFormat->setShortcutContext(Qt::WidgetShortcut);
  290. edit->addSeparator();
  291. psSelectAll = edit->addAction(
  292. tr::lng_mac_menu_select_all(tr::now),
  293. [] { SendKeySequence(Qt::Key_A, Qt::ControlModifier); },
  294. QKeySequence::SelectAll);
  295. psSelectAll->setShortcutContext(Qt::WidgetShortcut);
  296. edit->addSeparator();
  297. auto prefs = edit->addAction(
  298. tr::lng_mac_menu_preferences(tr::now),
  299. this,
  300. [=] {
  301. ensureWindowShown();
  302. controller().showSettings();
  303. },
  304. QKeySequence(Qt::ControlModifier | Qt::Key_Comma));
  305. prefs->setMenuRole(QAction::PreferencesRole);
  306. prefs->setShortcutContext(Qt::WidgetShortcut);
  307. auto tools = psMainMenu->addMenu(tr::lng_linux_menu_tools(tr::now));
  308. psContacts = tools->addAction(
  309. tr::lng_mac_menu_contacts(tr::now),
  310. crl::guard(this, [=] {
  311. if (isHidden()) {
  312. showFromTray();
  313. }
  314. if (!sessionController()) {
  315. return;
  316. }
  317. sessionController()->show(
  318. PrepareContactsBox(sessionController()));
  319. }));
  320. psAddContact = tools->addAction(
  321. tr::lng_mac_menu_add_contact(tr::now),
  322. this,
  323. [=] {
  324. Expects(sessionController() != nullptr);
  325. ensureWindowShown();
  326. sessionController()->showAddContact();
  327. });
  328. tools->addSeparator();
  329. psNewGroup = tools->addAction(
  330. tr::lng_mac_menu_new_group(tr::now),
  331. this,
  332. [=] {
  333. Expects(sessionController() != nullptr);
  334. ensureWindowShown();
  335. sessionController()->showNewGroup();
  336. });
  337. psNewChannel = tools->addAction(
  338. tr::lng_mac_menu_new_channel(tr::now),
  339. this,
  340. [=] {
  341. Expects(sessionController() != nullptr);
  342. ensureWindowShown();
  343. sessionController()->showNewChannel();
  344. });
  345. auto help = psMainMenu->addMenu(tr::lng_linux_menu_help(tr::now));
  346. auto about = help->addAction(
  347. tr::lng_mac_menu_about_telegram(
  348. tr::now,
  349. lt_telegram,
  350. u"Telegram"_q),
  351. [=] {
  352. ensureWindowShown();
  353. controller().show(Box<AboutBox>());
  354. });
  355. about->setMenuRole(QAction::AboutQtRole);
  356. updateGlobalMenu();
  357. }
  358. void MainWindow::updateGlobalMenuHook() {
  359. if (!positionInited()) {
  360. return;
  361. }
  362. const auto focused = QApplication::focusWidget();
  363. auto canUndo = false;
  364. auto canRedo = false;
  365. auto canCut = false;
  366. auto canCopy = false;
  367. auto canPaste = false;
  368. auto canDelete = false;
  369. auto canSelectAll = false;
  370. const auto mimeData = QGuiApplication::clipboard()->mimeData();
  371. const auto clipboardHasText = mimeData ? mimeData->hasText() : false;
  372. auto markdownState = Ui::MarkdownEnabledState();
  373. if (const auto edit = qobject_cast<QLineEdit*>(focused)) {
  374. canCut = canCopy = canDelete = edit->hasSelectedText();
  375. canSelectAll = !edit->text().isEmpty();
  376. canUndo = edit->isUndoAvailable();
  377. canRedo = edit->isRedoAvailable();
  378. canPaste = clipboardHasText;
  379. } else if (const auto edit = qobject_cast<QTextEdit*>(focused)) {
  380. canCut = canCopy = canDelete = edit->textCursor().hasSelection();
  381. canSelectAll = !edit->document()->isEmpty();
  382. canUndo = edit->document()->isUndoAvailable();
  383. canRedo = edit->document()->isRedoAvailable();
  384. canPaste = clipboardHasText;
  385. if (canCopy) {
  386. if (const auto inputField = dynamic_cast<Ui::InputField*>(
  387. focused->parentWidget())) {
  388. markdownState = inputField->markdownEnabledState();
  389. }
  390. }
  391. } else if (const auto list = dynamic_cast<HistoryInner*>(focused)) {
  392. canCopy = list->canCopySelected();
  393. canDelete = list->canDeleteSelected();
  394. }
  395. updateIsActive();
  396. const auto logged = (sessionController() != nullptr);
  397. const auto inactive = !logged || controller().locked();
  398. const auto support = logged
  399. && sessionController()->session().supportMode();
  400. ForceDisabled(psLogout, !logged && !Core::App().passcodeLocked());
  401. ForceDisabled(psUndo, !canUndo);
  402. ForceDisabled(psRedo, !canRedo);
  403. ForceDisabled(psCut, !canCut);
  404. ForceDisabled(psCopy, !canCopy);
  405. ForceDisabled(psPaste, !canPaste);
  406. ForceDisabled(psDelete, !canDelete);
  407. ForceDisabled(psSelectAll, !canSelectAll);
  408. ForceDisabled(psContacts, inactive || support);
  409. ForceDisabled(psAddContact, inactive);
  410. ForceDisabled(psNewGroup, inactive || support);
  411. ForceDisabled(psNewChannel, inactive || support);
  412. const auto diabled = [=](const QString &tag) {
  413. return !markdownState.enabledForTag(tag);
  414. };
  415. using Field = Ui::InputField;
  416. ForceDisabled(psBold, diabled(Field::kTagBold));
  417. ForceDisabled(psItalic, diabled(Field::kTagItalic));
  418. ForceDisabled(psUnderline, diabled(Field::kTagUnderline));
  419. ForceDisabled(psStrikeOut, diabled(Field::kTagStrikeOut));
  420. ForceDisabled(psBlockquote, diabled(Field::kTagBlockquote));
  421. ForceDisabled(
  422. psMonospace,
  423. diabled(Field::kTagPre) || diabled(Field::kTagCode));
  424. ForceDisabled(psClearFormat, markdownState.disabled());
  425. }
  426. bool MainWindow::eventFilter(QObject *obj, QEvent *evt) {
  427. const auto t = evt->type();
  428. if (t == QEvent::FocusIn || t == QEvent::FocusOut) {
  429. if (qobject_cast<QLineEdit*>(obj)
  430. || qobject_cast<QTextEdit*>(obj)
  431. || dynamic_cast<HistoryInner*>(obj)) {
  432. if (QApplication::focusWidget()) {
  433. updateGlobalMenu();
  434. }
  435. }
  436. } else if (obj == this && t == QEvent::Paint) {
  437. if (!_exposed) {
  438. _exposed = true;
  439. SkipTaskbar(
  440. windowHandle(),
  441. (Core::App().settings().workMode() == WorkMode::TrayOnly)
  442. && TrayIconSupported());
  443. }
  444. } else if (obj == this && t == QEvent::Hide) {
  445. _exposed = false;
  446. } else if (obj == this && t == QEvent::ThemeChange) {
  447. updateWindowIcon();
  448. }
  449. return Window::MainWindow::eventFilter(obj, evt);
  450. }
  451. MainWindow::~MainWindow() {
  452. }
  453. } // namespace Platform