integration_linux.cpp 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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/integration_linux.h"
  8. #include "platform/platform_integration.h"
  9. #include "base/platform/base_platform_info.h"
  10. #include "base/platform/linux/base_linux_xdp_utilities.h"
  11. #include "core/sandbox.h"
  12. #include "core/application.h"
  13. #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
  14. #include "core/core_settings.h"
  15. #endif
  16. #include "base/random.h"
  17. #include <QtCore/QAbstractEventDispatcher>
  18. #include <gio/gio.hpp>
  19. #include <xdpinhibit/xdpinhibit.hpp>
  20. namespace Platform {
  21. namespace {
  22. using namespace gi::repository;
  23. namespace GObject = gi::repository::GObject;
  24. class Application : public Gio::impl::ApplicationImpl {
  25. public:
  26. Application();
  27. void before_emit_(GLib::Variant platformData) noexcept override {
  28. if (Platform::IsWayland()) {
  29. static const auto keys = {
  30. "activation-token",
  31. "desktop-startup-id",
  32. };
  33. for (const auto &key : keys) {
  34. if (auto token = platformData.lookup_value(key)) {
  35. qputenv(
  36. "XDG_ACTIVATION_TOKEN",
  37. token.get_string(nullptr).c_str());
  38. break;
  39. }
  40. }
  41. }
  42. }
  43. void activate_() noexcept override {
  44. Core::Sandbox::Instance().customEnterFromEventLoop([] {
  45. Core::App().activate();
  46. });
  47. }
  48. void open_(
  49. gi::Collection<gi::DSpan, ::GFile*, gi::transfer_none_t> files,
  50. const gi::cstring_v hint) noexcept override {
  51. for (auto file : files) {
  52. QFileOpenEvent e(QUrl(QString::fromStdString(file.get_uri())));
  53. QGuiApplication::sendEvent(qApp, &e);
  54. }
  55. }
  56. void add_platform_data_(
  57. GLib::VariantBuilder_Ref builder) noexcept override {
  58. if (Platform::IsWayland()) {
  59. const auto token = qgetenv("XDG_ACTIVATION_TOKEN");
  60. if (!token.isEmpty()) {
  61. builder.add_value(
  62. GLib::Variant::new_dict_entry(
  63. GLib::Variant::new_string("activation-token"),
  64. GLib::Variant::new_variant(
  65. GLib::Variant::new_string(token.toStdString()))));
  66. qunsetenv("XDG_ACTIVATION_TOKEN");
  67. }
  68. }
  69. }
  70. };
  71. Application::Application()
  72. : Gio::impl::ApplicationImpl(this) {
  73. const auto appId = QGuiApplication::desktopFileName().toStdString();
  74. if (Gio::Application::id_is_valid(appId)) {
  75. set_application_id(appId);
  76. }
  77. set_flags(Gio::ApplicationFlags::HANDLES_OPEN_);
  78. auto actionMap = Gio::ActionMap(*this);
  79. auto quitAction = Gio::SimpleAction::new_("quit");
  80. quitAction.signal_activate().connect([](
  81. Gio::SimpleAction,
  82. GLib::Variant parameter) {
  83. Core::Sandbox::Instance().customEnterFromEventLoop([] {
  84. Core::Quit();
  85. });
  86. });
  87. actionMap.add_action(quitAction);
  88. const auto notificationIdVariantType = GLib::VariantType::new_("a{sv}");
  89. auto notificationActivateAction = Gio::SimpleAction::new_(
  90. "notification-activate",
  91. notificationIdVariantType);
  92. actionMap.add_action(notificationActivateAction);
  93. auto notificationMarkAsReadAction = Gio::SimpleAction::new_(
  94. "notification-mark-as-read",
  95. notificationIdVariantType);
  96. actionMap.add_action(notificationMarkAsReadAction);
  97. }
  98. gi::ref_ptr<Application> MakeApplication() {
  99. const auto result = gi::make_ref<Application>();
  100. if (const auto registered = result->register_(); !registered) {
  101. LOG(("App Error: Failed to register: %1").arg(
  102. registered.error().message_().c_str()));
  103. return nullptr;
  104. }
  105. return result;
  106. }
  107. class LinuxIntegration final : public Integration, public base::has_weak_ptr {
  108. public:
  109. LinuxIntegration();
  110. void init() override;
  111. private:
  112. [[nodiscard]] XdpInhibit::Inhibit inhibit() {
  113. return _inhibitProxy;
  114. }
  115. void initInhibit();
  116. const gi::ref_ptr<Application> _application;
  117. XdpInhibit::InhibitProxy _inhibitProxy;
  118. #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
  119. base::Platform::XDP::SettingWatcher _darkModeWatcher;
  120. #endif // Qt < 6.5.0
  121. };
  122. LinuxIntegration::LinuxIntegration()
  123. : _application(MakeApplication())
  124. #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
  125. , _darkModeWatcher(
  126. "org.freedesktop.appearance",
  127. "color-scheme",
  128. [](GLib::Variant value) {
  129. Core::Sandbox::Instance().customEnterFromEventLoop([&] {
  130. Core::App().settings().setSystemDarkMode(value.get_uint32() == 1);
  131. });
  132. })
  133. #endif // Qt < 6.5.0
  134. {
  135. LOG(("Icon theme: %1").arg(QIcon::themeName()));
  136. LOG(("Fallback icon theme: %1").arg(QIcon::fallbackThemeName()));
  137. if (!QCoreApplication::eventDispatcher()->inherits(
  138. "QEventDispatcherGlib")) {
  139. g_warning("Qt is running without GLib event loop integration, "
  140. "expect various functionality to not to work.");
  141. }
  142. }
  143. void LinuxIntegration::init() {
  144. XdpInhibit::InhibitProxy::new_for_bus(
  145. Gio::BusType::SESSION_,
  146. Gio::DBusProxyFlags::NONE_,
  147. base::Platform::XDP::kService,
  148. base::Platform::XDP::kObjectPath,
  149. crl::guard(this, [=](GObject::Object, Gio::AsyncResult res) {
  150. _inhibitProxy = XdpInhibit::InhibitProxy::new_for_bus_finish(
  151. res,
  152. nullptr);
  153. initInhibit();
  154. }));
  155. }
  156. void LinuxIntegration::initInhibit() {
  157. if (!_inhibitProxy) {
  158. return;
  159. }
  160. std::string uniqueName = _inhibitProxy.get_connection().get_unique_name();
  161. uniqueName.erase(0, 1);
  162. uniqueName.replace(uniqueName.find('.'), 1, 1, '_');
  163. const auto handleToken = "tdesktop"
  164. + std::to_string(base::RandomValue<uint>());
  165. const auto sessionHandleToken = "tdesktop"
  166. + std::to_string(base::RandomValue<uint>());
  167. const auto sessionHandle = base::Platform::XDP::kObjectPath
  168. + std::string("/session/")
  169. + uniqueName
  170. + '/'
  171. + sessionHandleToken;
  172. inhibit().signal_state_changed().connect([
  173. mySessionHandle = sessionHandle
  174. ](
  175. XdpInhibit::Inhibit,
  176. const std::string &sessionHandle,
  177. GLib::Variant state) {
  178. if (sessionHandle != mySessionHandle) {
  179. return;
  180. }
  181. Core::App().setScreenIsLocked(
  182. GLib::VariantDict::new_(
  183. state
  184. ).lookup_value(
  185. "screensaver-active"
  186. ).get_boolean()
  187. );
  188. });
  189. inhibit().call_create_monitor(
  190. "",
  191. GLib::Variant::new_array({
  192. GLib::Variant::new_dict_entry(
  193. GLib::Variant::new_string("handle_token"),
  194. GLib::Variant::new_variant(
  195. GLib::Variant::new_string(handleToken))),
  196. GLib::Variant::new_dict_entry(
  197. GLib::Variant::new_string("session_handle_token"),
  198. GLib::Variant::new_variant(
  199. GLib::Variant::new_string(sessionHandleToken))),
  200. }),
  201. nullptr);
  202. }
  203. } // namespace
  204. std::unique_ptr<Integration> CreateIntegration() {
  205. return std::make_unique<LinuxIntegration>();
  206. }
  207. } // namespace Platform