diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index cf7cb60ed..f0cb97ca7 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1125,6 +1125,7 @@ PRIVATE platform/platform_integration.h platform/platform_main_window.h platform/platform_notifications_manager.h + platform/platform_specific.cpp platform/platform_specific.h platform/platform_tray.h platform/platform_window_title.h diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 1d96b1105..6dce4c719 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -9,10 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/linux/notifications_manager_linux.h" #include "window/notifications_utilities.h" +#include "base/options.h" #include "base/platform/base_platform_info.h" #include "base/platform/linux/base_linux_glibmm_helper.h" #include "base/platform/linux/base_linux_dbus_utilities.h" +#include "platform/platform_specific.h" #include "core/application.h" +#include "core/sandbox.h" #include "core/core_settings.h" #include "data/data_forum_topic.h" #include "history/history.h" @@ -27,6 +30,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include + namespace Platform { namespace Notifications { namespace { @@ -341,6 +346,9 @@ private: const not_null _manager; NotificationId _id; + Glib::RefPtr _application; + Glib::RefPtr _notification; + Glib::RefPtr _dbusConnection; Glib::ustring _title; Glib::ustring _body; @@ -367,7 +375,8 @@ NotificationData::NotificationData( not_null manager, NotificationId id) : _manager(manager) -, _id(id) { +, _id(id) +, _application(Gio::Application::get_default()) { } bool NotificationData::init( @@ -375,6 +384,64 @@ bool NotificationData::init( const QString &subtitle, const QString &msg, Window::Notifications::Manager::DisplayOptions options) { + if (_application) { + _notification = Gio::Notification::create(title.toStdString()); + + _notification->set_body( + subtitle.isEmpty() + ? msg.toStdString() + : qsl("%1\n%2").arg(subtitle, msg).toStdString()); + + _notification->set_icon( + Gio::ThemedIcon::create(base::IconName().toStdString())); + + // glib 2.42+, we keep glib 2.40+ compatibility + static const auto set_priority = [] { + // reset dlerror after dlsym call + const auto guard = gsl::finally([] { dlerror(); }); + return reinterpret_cast( + dlsym(RTLD_DEFAULT, "g_notification_set_priority")); + }(); + + if (set_priority) { + // for chat messages, according to + // https://docs.gtk.org/gio/enum.NotificationPriority.html + set_priority(_notification->gobj(), G_NOTIFICATION_PRIORITY_HIGH); + } + + // glib 2.70+, we keep glib 2.40+ compatibility + static const auto set_category = [] { + // reset dlerror after dlsym call + const auto guard = gsl::finally([] { dlerror(); }); + return reinterpret_cast( + dlsym(RTLD_DEFAULT, "g_notification_set_category")); + }(); + + if (set_category) { + set_category(_notification->gobj(), "im.received"); + } + + const auto idTuple = _id.toTuple(); + + _notification->set_default_action( + "app.notification-reply", + idTuple); + + if (!options.hideMarkAsRead) { + _notification->add_button( + tr::lng_context_mark_read(tr::now).toStdString(), + "app.notification-mark-as-read", + idTuple); + } + + _notification->add_button( + tr::lng_notification_reply(tr::now).toStdString(), + "app.notification-reply", + idTuple); + + return true; + } + Noexcept([&] { _dbusConnection = Gio::DBus::Connection::get_sync( Gio::DBus::BusType::SESSION); @@ -545,6 +612,17 @@ NotificationData::~NotificationData() { } void NotificationData::show() { + if (_application && _notification) { + _application->send_notification( + std::to_string(_id.contextId.sessionId) + + '-' + + std::to_string(_id.contextId.peerId.value) + + '-' + + std::to_string(_id.msgId.bare), + _notification); + return; + } + // a hack for snap's activation restriction const auto weak = base::make_weak(this); StartServiceAsync(crl::guard(weak, [=] { @@ -587,6 +665,17 @@ void NotificationData::show() { } void NotificationData::close() { + if (_application) { + _application->withdraw_notification( + std::to_string(_id.contextId.sessionId) + + '-' + + std::to_string(_id.contextId.peerId.value) + + '-' + + std::to_string(_id.msgId.bare)); + _manager->clearNotification(_id); + return; + } + _dbusConnection->call( std::string(kObjectPath), std::string(kInterface), @@ -602,7 +691,16 @@ void NotificationData::close() { } void NotificationData::setImage(const QString &imagePath) { - if (imagePath.isEmpty() || _imageKey.empty()) { + if (imagePath.isEmpty()) { + return; + } + + if (_notification) { + _notification->set_icon(Gio::Icon::create(imagePath.toStdString())); + return; + } + + if (_imageKey.empty()) { return; } @@ -696,13 +794,13 @@ bool SkipFlashBounceForCustom() { } bool Supported() { - return ServiceRegistered; + return ServiceRegistered || Gio::Application::get_default(); } bool Enforced() { // Wayland doesn't support positioning // and custom notifications don't work here - return IsWayland(); + return IsWayland() || OptionGApplication.value(); } bool ByDefault() { @@ -728,25 +826,30 @@ bool ByDefault() { } void Create(Window::Notifications::System *system) { - static const auto ServiceWatcher = CreateServiceWatcher(); - const auto managerSetter = [=] { using ManagerType = Window::Notifications::ManagerType; if ((Core::App().settings().nativeNotifications() || Enforced()) && Supported()) { - if (system->managerType() != ManagerType::Native) { + if (system->manager().type() != ManagerType::Native) { system->setManager(std::make_unique(system)); } } else if (Enforced()) { - if (system->managerType() != ManagerType::Dummy) { + if (system->manager().type() != ManagerType::Dummy) { using DummyManager = Window::Notifications::DummyManager; system->setManager(std::make_unique(system)); } - } else if (system->managerType() != ManagerType::Default) { + } else if (system->manager().type() != ManagerType::Default) { system->setManager(nullptr); } }; + if (Gio::Application::get_default()) { + managerSetter(); + return; + } + + static const auto ServiceWatcher = CreateServiceWatcher(); + const auto counter = std::make_shared(2); const auto oneReady = [=] { if (!--*counter) { @@ -1098,7 +1201,7 @@ void Manager::doClearFromSession(not_null session) { } bool Manager::doSkipAudio() const { - return _private->inhibited(); + return _private->inhibited() || Gio::Application::get_default(); } bool Manager::doSkipToast() const { @@ -1106,7 +1209,7 @@ bool Manager::doSkipToast() const { } bool Manager::doSkipFlashBounce() const { - return _private->inhibited(); + return _private->inhibited() || Gio::Application::get_default(); } } // namespace Notifications diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 15498ed09..f6b6276da 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/linux/specific_linux.h" #include "base/random.h" +#include "base/options.h" #include "base/platform/base_platform_info.h" #include "platform/linux/linux_desktop_environment.h" #include "platform/linux/linux_wayland_integration.h" @@ -16,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/localstorage.h" #include "core/sandbox.h" #include "core/application.h" +#include "core/local_url_handlers.h" #include "core/core_settings.h" #include "core/update_checker.h" #include "window/window_controller.h" @@ -57,6 +59,58 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL using namespace Platform; using Platform::internal::WaylandIntegration; +#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION +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->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())); + } + } + }; +} + +static void t_desktop_application_init(TDesktopApplication *application) { +} +#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION + namespace Platform { namespace { @@ -172,6 +226,197 @@ void PortalAutostart(bool start, bool silent) { } } } + +void LaunchGApplication() { + const auto connection = [] { + try { + return Gio::DBus::Connection::get_sync( + Gio::DBus::BusType::SESSION); + } catch (...) { + return Glib::RefPtr(); + } + }(); + + using namespace base::Platform::DBus; + const auto activatableNames = [&] { + try { + if (connection) { + return ListActivatableNames(connection); + } + } catch (...) { + } + + return std::vector(); + }(); + + const auto freedesktopNotifications = [&] { + try { + if (connection && NameHasOwner( + connection, + "org.freedesktop.Notifications")) { + return true; + } + } catch (...) { + } + + if (ranges::contains( + activatableNames, + "org.freedesktop.Notifications")) { + return true; + } + + return false; + }; + + const auto gtkNotifications = [&] { + try { + if (connection && NameHasOwner( + connection, + "org.gtk.Notifications")) { + return true; + } + } catch (...) { + } + + if (ranges::contains(activatableNames, "org.gtk.Notifications")) { + return true; + } + + return false; + }; + + if (OptionGApplication.value() + || gtkNotifications() + || (KSandbox::isFlatpak() && !freedesktopNotifications())) { + Glib::signal_idle().connect_once([] { + const auto appId = QGuiApplication::desktopFileName() + .chopped(8) + .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([=] { + QEventLoop loop; + loop.exec(QEventLoop::ApplicationExec); + app->quit(); + }, true); + + app->signal_activate().connect([] { + Core::Sandbox::Instance().customEnterFromEventLoop([] { + if (const auto w = App::wnd()) { + w->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) { + if (file->get_uri_scheme() == "file") { + gSendPaths.append( + QString::fromStdString(file->get_path())); + continue; + } + const auto url = QString::fromStdString( + file->get_uri()); + if (url.isEmpty()) { + continue; + } + if (url.startsWith(qstr("interpret://"))) { + gSendPaths.append(url); + continue; + } + if (Core::StartUrlRequiresActivate(url)) { + if (const auto w = App::wnd()) { + w->activate(); + } + } + cSetStartUrl(url); + Core::App().checkStartUrl(); + } + if (!cSendPaths().isEmpty()) { + if (const auto w = App::wnd()) { + w->sendPaths(); + } + } + }); + }, true); + + app->add_action("Quit", [] { + Core::Sandbox::Instance().customEnterFromEventLoop([] { + Core::Quit(); + }); + }); + + using Window::Notifications::Manager; + using NotificationId = Manager::NotificationId; + using NotificationIdTuple = std::result_of< + decltype(&NotificationId::toTuple)(NotificationId*) + >::type; + + const auto notificationIdVariantType = [] { + try { + return base::Platform::MakeGlibVariant( + NotificationId().toTuple()).get_type(); + } catch (...) { + return Glib::VariantType(); + } + }(); + + app->add_action_with_parameter( + "notification-reply", + notificationIdVariantType, + [](const Glib::VariantBase ¶meter) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + try { + const auto &app = Core::App(); + const auto ¬ifications = app.notifications(); + notifications.manager().notificationActivated( + NotificationId::FromTuple( + base::Platform::GlibVariantCast< + NotificationIdTuple + >(parameter))); + } 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(); + const auto ¬ifications = app.notifications(); + notifications.manager().notificationReplied( + NotificationId::FromTuple( + base::Platform::GlibVariantCast< + NotificationIdTuple + >(parameter)), + {}); + } catch (...) { + } + }); + }); + + app->hold(); + app->run(0, nullptr); + }); + } +} #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION bool GenerateDesktopFile( @@ -587,6 +832,10 @@ namespace ThirdParty { void start() { LOG(("Icon theme: %1").arg(QIcon::themeName())); LOG(("Fallback icon theme: %1").arg(QIcon::fallbackThemeName())); + +#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION + LaunchGApplication(); +#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION } void finish() { diff --git a/Telegram/SourceFiles/platform/platform_specific.cpp b/Telegram/SourceFiles/platform/platform_specific.cpp new file mode 100644 index 000000000..15dc33bf1 --- /dev/null +++ b/Telegram/SourceFiles/platform/platform_specific.cpp @@ -0,0 +1,26 @@ +/* +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/platform_specific.h" + +#include "base/options.h" + +namespace Platform { + +const char kOptionGApplication[] = "gapplication"; + +base::options::toggle OptionGApplication({ + .id = kOptionGApplication, + .name = "GApplication", + .description = "Force enable GNOME's GApplication and GNotification." + " This changes notification behavior to be native to GNOME." + " When disabled, autodetect is used.", + .scope = base::options::linux, + .restartRequired = true, +}); + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/platform_specific.h b/Telegram/SourceFiles/platform/platform_specific.h index dc863b179..725ecdb7b 100644 --- a/Telegram/SourceFiles/platform/platform_specific.h +++ b/Telegram/SourceFiles/platform/platform_specific.h @@ -7,8 +7,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +namespace base::options { + +template +class option; + +using toggle = option; + +} // namespace base::options + namespace Platform { +extern const char kOptionGApplication[]; +extern base::options::toggle OptionGApplication; + void start(); void finish(); diff --git a/Telegram/SourceFiles/settings/settings_experimental.cpp b/Telegram/SourceFiles/settings/settings_experimental.cpp index b0313c6b8..8b9b82f1f 100644 --- a/Telegram/SourceFiles/settings/settings_experimental.cpp +++ b/Telegram/SourceFiles/settings/settings_experimental.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_style_radius.h" #include "base/options.h" #include "core/application.h" +#include "platform/platform_specific.h" #include "chat_helpers/tabbed_panel.h" #include "dialogs/dialogs_inner_widget.h" #include "history/history_widget.h" @@ -143,6 +144,7 @@ void SetupExperimental( addToggle(Settings::kOptionMonoSettingsIcons); addToggle(Webview::kOptionWebviewDebugEnabled); addToggle(kOptionAutoScrollInactiveChat); + addToggle(Platform::kOptionGApplication); } } // namespace diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index c349686ba..c9896d7b7 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -125,9 +125,9 @@ void System::setManager(std::unique_ptr manager) { } } -ManagerType System::managerType() const { +Manager &System::manager() const { Expects(_manager != nullptr); - return _manager->type(); + return *_manager; } Main::Session *System::findSession(uint64 sessionId) const { diff --git a/Telegram/SourceFiles/window/notifications_manager.h b/Telegram/SourceFiles/window/notifications_manager.h index e1bc0ac56..57401671c 100644 --- a/Telegram/SourceFiles/window/notifications_manager.h +++ b/Telegram/SourceFiles/window/notifications_manager.h @@ -84,7 +84,7 @@ public: void createManager(); void setManager(std::unique_ptr manager); - [[nodiscard]] ManagerType managerType() const; + [[nodiscard]] Manager &manager() const; void checkDelayed(); void schedule(Data::ItemNotification notification); @@ -217,6 +217,22 @@ public: friend inline auto operator<=>( const ContextId&, const ContextId&) = default; + + [[nodiscard]] auto toTuple() const { + return std::make_tuple( + sessionId, + peerId.value, + topicRootId.bare); + } + + template + [[nodiscard]] static auto FromTuple(const T &tuple) { + return ContextId{ + std::get<0>(tuple), + PeerIdHelper(std::get<1>(tuple)), + std::get<2>(tuple), + }; + } }; struct NotificationId { ContextId contextId; @@ -225,6 +241,20 @@ public: friend inline auto operator<=>( const NotificationId&, const NotificationId&) = default; + + [[nodiscard]] auto toTuple() const { + return std::make_tuple( + contextId.toTuple(), + msgId.bare); + } + + template + [[nodiscard]] static auto FromTuple(const T &tuple) { + return NotificationId{ + ContextId::FromTuple(std::get<0>(tuple)), + std::get<1>(tuple), + }; + } }; struct NotificationFields { not_null item; diff --git a/Telegram/lib_base b/Telegram/lib_base index 1cc6aba58..d3f9e8261 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 1cc6aba584869df04341467cf7c5f72c8354086f +Subproject commit d3f9e826197c6ed42f8ef2d021d8eb74b2d390cc diff --git a/Telegram/lib_webview b/Telegram/lib_webview index b9d81771a..9623bb460 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit b9d81771a0d7533dd07805f0618193277715da80 +Subproject commit 9623bb460b9be6b0481d775f9e01e5084170024e