/* 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 "platform/linux/integration_linux.h" #include "platform/platform_integration.h" #include "base/platform/base_platform_info.h" #include "base/platform/linux/base_linux_xdp_utilities.h" #include "core/sandbox.h" #include "core/application.h" #include "core/core_settings.h" #include "base/random.h" #include #include #include typedef GApplication TDesktopApplication; typedef GApplicationClass TDesktopApplicationClass; G_DEFINE_TYPE( TDesktopApplication, t_desktop_application, G_TYPE_APPLICATION) static void t_desktop_application_class_init( TDesktopApplicationClass *klass) { const auto application_class = G_APPLICATION_CLASS(klass); application_class->local_command_line = []( GApplication *application, char ***arguments, int *exit_status) -> gboolean { return false; }; application_class->command_line = []( GApplication *application, GApplicationCommandLine *cmdline) { return 0; }; application_class->before_emit = []( GApplication *application, GVariant *platformData) { if (Platform::IsWayland()) { static const auto keys = { "activation-token", "desktop-startup-id", }; for (const auto &key : keys) { const char *token = nullptr; g_variant_lookup(platformData, key, "&s", &token); if (token) { qputenv("XDG_ACTIVATION_TOKEN", token); break; } } } }; application_class->add_platform_data = []( GApplication *application, GVariantBuilder *builder) { if (Platform::IsWayland()) { const auto token = qgetenv("XDG_ACTIVATION_TOKEN"); if (!token.isEmpty()) { g_variant_builder_add( builder, "{sv}", "activation-token", g_variant_new_string(token.constData())); qunsetenv("XDG_ACTIVATION_TOKEN"); } } }; } static void t_desktop_application_init(TDesktopApplication *application) { } namespace Platform { namespace { using namespace gi::repository; namespace Gio = gi::repository::Gio; class LinuxIntegration final : public Integration { public: LinuxIntegration(); void init() override; private: [[nodiscard]] XdpInhibit::Inhibit inhibit() { return _inhibitProxy; } void initInhibit(); static void LaunchNativeApplication(); XdpInhibit::InhibitProxy _inhibitProxy; base::Platform::XDP::SettingWatcher _darkModeWatcher; }; LinuxIntegration::LinuxIntegration() : _inhibitProxy( XdpInhibit::InhibitProxy::new_for_bus_sync( Gio::BusType::SESSION_, Gio::DBusProxyFlags::DO_NOT_AUTO_START_AT_CONSTRUCTION_, base::Platform::XDP::kService, base::Platform::XDP::kObjectPath, nullptr)) , _darkModeWatcher([]( const Glib::ustring &group, const Glib::ustring &key, const Glib::VariantBase &value) { if (group == "org.freedesktop.appearance" && key == "color-scheme") { try { const auto ivalue = value.get_dynamic(); crl::on_main([=] { Core::App().settings().setSystemDarkMode(ivalue == 1); }); } catch (...) { } } }) { LOG(("Icon theme: %1").arg(QIcon::themeName())); LOG(("Fallback icon theme: %1").arg(QIcon::fallbackThemeName())); if (!QCoreApplication::eventDispatcher()->inherits( "QEventDispatcherGlib")) { g_warning("Qt is running without GLib event loop integration, " "except various functionality to not to work."); } } void LinuxIntegration::init() { initInhibit(); Glib::signal_idle().connect_once([] { LaunchNativeApplication(); }); } void LinuxIntegration::initInhibit() { if (!_inhibitProxy) { return; } auto uniqueName = _inhibitProxy.get_connection().get_unique_name(); uniqueName.erase(0, 1); uniqueName.replace(uniqueName.find('.'), 1, 1, '_'); const auto handleToken = "tdesktop" + std::to_string(base::RandomValue()); const auto sessionHandleToken = "tdesktop" + std::to_string(base::RandomValue()); const auto sessionHandle = "/org/freedesktop/portal/desktop/session/" + uniqueName + '/' + sessionHandleToken; inhibit().signal_state_changed().connect([ mySessionHandle = sessionHandle ]( XdpInhibit::Inhibit, const std::string &sessionHandle, GLib::Variant state) { if (sessionHandle != mySessionHandle) { return; } Core::App().setScreenIsLocked( GLib::VariantDict::new_( state ).lookup_value( "screensaver-active" ).get_boolean() ); }); const auto options = std::array{ GLib::Variant::new_dict_entry( GLib::Variant::new_string("handle_token"), GLib::Variant::new_variant( GLib::Variant::new_string(handleToken))), GLib::Variant::new_dict_entry( GLib::Variant::new_string("session_handle_token"), GLib::Variant::new_variant( GLib::Variant::new_string(sessionHandleToken))), }; inhibit().call_create_monitor( {}, GLib::Variant::new_array(options.data(), options.size()), nullptr); } void LinuxIntegration::LaunchNativeApplication() { const auto appId = QGuiApplication::desktopFileName().toStdString(); const auto app = Glib::wrap( G_APPLICATION( g_object_new( t_desktop_application_get_type(), "application-id", ::Gio::Application::id_is_valid(appId) ? appId.c_str() : nullptr, "flags", G_APPLICATION_HANDLES_OPEN, nullptr))); app->signal_startup().connect([weak = std::weak_ptr(app)] { const auto app = weak.lock(); if (!app) { return; } // GNotification InvokeQueued(qApp, [] { Core::App().notifications().createManager(); }); QEventLoop().exec(); app->quit(); }, true); app->signal_activate().connect([] { Core::Sandbox::Instance().customEnterFromEventLoop([] { Core::App().activate(); }); }, true); app->signal_open().connect([]( const ::Gio::Application::type_vec_files &files, const Glib::ustring &hint) { Core::Sandbox::Instance().customEnterFromEventLoop([&] { for (const auto &file : files) { QFileOpenEvent e( QUrl(QString::fromStdString(file->get_uri()))); QGuiApplication::sendEvent(qApp, &e); } }); }, true); app->add_action("quit", [] { Core::Sandbox::Instance().customEnterFromEventLoop([] { Core::Quit(); }); }); using Window::Notifications::Manager; using NotificationId = Manager::NotificationId; using NotificationIdTuple = std::invoke_result_t< decltype(&NotificationId::toTuple), NotificationId* >; const auto notificationIdVariantType = [] { try { return Glib::create_variant( NotificationId().toTuple() ).get_type(); } catch (...) { return Glib::VariantType(); } }(); app->add_action_with_parameter( "notification-activate", notificationIdVariantType, [](const Glib::VariantBase ¶meter) { Core::Sandbox::Instance().customEnterFromEventLoop([&] { try { const auto &app = Core::App(); app.notifications().manager().notificationActivated( NotificationId::FromTuple( parameter.get_dynamic() ) ); } catch (...) { } }); }); app->add_action_with_parameter( "notification-mark-as-read", notificationIdVariantType, [](const Glib::VariantBase ¶meter) { Core::Sandbox::Instance().customEnterFromEventLoop([&] { try { const auto &app = Core::App(); app.notifications().manager().notificationReplied( NotificationId::FromTuple( parameter.get_dynamic() ), {} ); } catch (...) { } }); }); app->run(0, nullptr); } } // namespace std::unique_ptr CreateIntegration() { return std::make_unique(); } } // namespace Platform