From 3d36b4f86693a49c281fda7af82823b185674142 Mon Sep 17 00:00:00 2001 From: ilya-fedin Date: Sun, 29 Dec 2019 15:41:45 +0000 Subject: [PATCH] Replace libnotify with QtDBus notification implementation (#6825) --- .../platform/linux/linux_libnotify.cpp | 110 --- .../platform/linux/linux_libnotify.h | 120 --- .../SourceFiles/platform/linux/linux_libs.cpp | 5 - .../platform/linux/main_window_linux.cpp | 7 - .../linux/notifications_manager_linux.cpp | 715 +++++++----------- .../linux/notifications_manager_linux.h | 95 ++- .../platform/linux/specific_linux.cpp | 1 - Telegram/gyp/telegram/sources.txt | 2 - 8 files changed, 371 insertions(+), 684 deletions(-) delete mode 100644 Telegram/SourceFiles/platform/linux/linux_libnotify.cpp delete mode 100644 Telegram/SourceFiles/platform/linux/linux_libnotify.h diff --git a/Telegram/SourceFiles/platform/linux/linux_libnotify.cpp b/Telegram/SourceFiles/platform/linux/linux_libnotify.cpp deleted file mode 100644 index 657c460753..0000000000 --- a/Telegram/SourceFiles/platform/linux/linux_libnotify.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* -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/linux_libnotify.h" - -#include "platform/linux/linux_libs.h" - -namespace Platform { -namespace Libs { -namespace { - -bool loadLibrary(QLibrary &lib, const char *name, int version) { - DEBUG_LOG(("Loading '%1' with version %2...").arg(QLatin1String(name)).arg(version)); - lib.setFileNameAndVersion(QLatin1String(name), version); - if (lib.load()) { - DEBUG_LOG(("Loaded '%1' with version %2!").arg(QLatin1String(name)).arg(version)); - return true; - } - lib.setFileNameAndVersion(QLatin1String(name), QString()); - if (lib.load()) { - DEBUG_LOG(("Loaded '%1' without version!").arg(QLatin1String(name))); - return true; - } - LOG(("Could not load '%1' with version %2 :(").arg(QLatin1String(name)).arg(version)); - return false; -} - -} // namespace - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -f_notify_init notify_init = nullptr; -f_notify_uninit notify_uninit = nullptr; -f_notify_is_initted notify_is_initted = nullptr; -//f_notify_get_app_name notify_get_app_name = nullptr; -//f_notify_set_app_name notify_set_app_name = nullptr; -f_notify_get_server_caps notify_get_server_caps = nullptr; -f_notify_get_server_info notify_get_server_info = nullptr; - -f_notify_notification_new notify_notification_new = nullptr; -//f_notify_notification_update notify_notification_update = nullptr; -f_notify_notification_show notify_notification_show = nullptr; -//f_notify_notification_set_app_name notify_notification_set_app_name = nullptr; -f_notify_notification_set_timeout notify_notification_set_timeout = nullptr; -//f_notify_notification_set_category notify_notification_set_category = nullptr; -//f_notify_notification_set_urgency notify_notification_set_urgency = nullptr; -//f_notify_notification_set_icon_from_pixbuf notify_notification_set_icon_from_pixbuf = nullptr; -f_notify_notification_set_image_from_pixbuf notify_notification_set_image_from_pixbuf = nullptr; -//f_notify_notification_set_hint notify_notification_set_hint = nullptr; -//f_notify_notification_set_hint_int32 notify_notification_set_hint_int32 = nullptr; -//f_notify_notification_set_hint_uint32 notify_notification_set_hint_uint32 = nullptr; -//f_notify_notification_set_hint_double notify_notification_set_hint_double = nullptr; -f_notify_notification_set_hint_string notify_notification_set_hint_string = nullptr; -//f_notify_notification_set_hint_byte notify_notification_set_hint_byte = nullptr; -//f_notify_notification_set_hint_byte_array notify_notification_set_hint_byte_array = nullptr; -//f_notify_notification_clear_hints notify_notification_clear_hints = nullptr; -f_notify_notification_add_action notify_notification_add_action = nullptr; -f_notify_notification_clear_actions notify_notification_clear_actions = nullptr; -f_notify_notification_close notify_notification_close = nullptr; -f_notify_notification_get_closed_reason notify_notification_get_closed_reason = nullptr; - -void startLibNotify() { - DEBUG_LOG(("Loading libnotify")); - - QLibrary lib_notify; - if (!loadLibrary(lib_notify, "notify", 4)) { - if (!loadLibrary(lib_notify, "notify", 5)) { - if (!loadLibrary(lib_notify, "notify", 1)) { - return; - } - } - } - - load(lib_notify, "notify_init", notify_init); - load(lib_notify, "notify_uninit", notify_uninit); - load(lib_notify, "notify_is_initted", notify_is_initted); -// load(lib_notify, "notify_get_app_name", notify_get_app_name); -// load(lib_notify, "notify_set_app_name", notify_set_app_name); - load(lib_notify, "notify_get_server_caps", notify_get_server_caps); - load(lib_notify, "notify_get_server_info", notify_get_server_info); - - load(lib_notify, "notify_notification_new", notify_notification_new); -// load(lib_notify, "notify_notification_update", notify_notification_update); - load(lib_notify, "notify_notification_show", notify_notification_show); -// load(lib_notify, "notify_notification_set_app_name", notify_notification_set_app_name); - load(lib_notify, "notify_notification_set_timeout", notify_notification_set_timeout); -// load(lib_notify, "notify_notification_set_category", notify_notification_set_category); -// load(lib_notify, "notify_notification_set_urgency", notify_notification_set_urgency); -// load(lib_notify, "notify_notification_set_icon_from_pixbuf", notify_notification_set_icon_from_pixbuf); - load(lib_notify, "notify_notification_set_image_from_pixbuf", notify_notification_set_image_from_pixbuf); -// load(lib_notify, "notify_notification_set_hint", notify_notification_set_hint); -// load(lib_notify, "notify_notification_set_hint_int32", notify_notification_set_hint_int32); -// load(lib_notify, "notify_notification_set_hint_uint32", notify_notification_set_hint_uint32); -// load(lib_notify, "notify_notification_set_hint_double", notify_notification_set_hint_double); - load(lib_notify, "notify_notification_set_hint_string", notify_notification_set_hint_string); -// load(lib_notify, "notify_notification_set_hint_byte", notify_notification_set_hint_byte); -// load(lib_notify, "notify_notification_set_hint_byte_array", notify_notification_set_hint_byte_array); -// load(lib_notify, "notify_notification_clear_hints", notify_notification_clear_hints); - load(lib_notify, "notify_notification_add_action", notify_notification_add_action); - load(lib_notify, "notify_notification_clear_actions", notify_notification_clear_actions); - load(lib_notify, "notify_notification_close", notify_notification_close); - load(lib_notify, "notify_notification_get_closed_reason", notify_notification_get_closed_reason); -} -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - -} // namespace Libs -} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_libnotify.h b/Telegram/SourceFiles/platform/linux/linux_libnotify.h deleted file mode 100644 index 6dcfd676a1..0000000000 --- a/Telegram/SourceFiles/platform/linux/linux_libnotify.h +++ /dev/null @@ -1,120 +0,0 @@ -/* -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 -*/ -#pragma once - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -extern "C" { -#undef signals -#include -#define signals public -} // extern "C" - -namespace Platform { -namespace Libs { - -void startLibNotify(); - -constexpr gint NOTIFY_EXPIRES_DEFAULT = -1; -constexpr gint NOTIFY_EXPIRES_NEVER = 0; - -struct NotifyNotification; -typedef enum { - NOTIFY_URGENCY_LOW, - NOTIFY_URGENCY_NORMAL, - NOTIFY_URGENCY_CRITICAL, -} NotifyUrgency; - -using NotifyActionCallback = void (*)(NotifyNotification *notification, char *action, gpointer user_data); - -using f_notify_init = gboolean (*)(const char *app_name); -extern f_notify_init notify_init; - -using f_notify_uninit = void (*)(void); -extern f_notify_uninit notify_uninit; - -using f_notify_is_initted = gboolean (*)(void); -extern f_notify_is_initted notify_is_initted; - -//using f_notify_get_app_name = const char* (*)(void); -//extern f_notify_get_app_name notify_get_app_name; - -//using f_notify_set_app_name = void (*)(const char *app_name); -//extern f_notify_set_app_name notify_set_app_name; - -using f_notify_get_server_caps = GList* (*)(void); -extern f_notify_get_server_caps notify_get_server_caps; - -using f_notify_get_server_info = gboolean (*)(char **ret_name, char **ret_vendor, char **ret_version, char **ret_spec_version); -extern f_notify_get_server_info notify_get_server_info; - -using f_notify_notification_new = NotifyNotification* (*)(const char *summary, const char *body, const char *icon); -extern f_notify_notification_new notify_notification_new; - -//using f_notify_notification_update = gboolean (*)(NotifyNotification *notification, const char *summary, const char *body, const char *icon); -//extern f_notify_notification_update notify_notification_update; - -using f_notify_notification_show = gboolean (*)(NotifyNotification *notification, GError **error); -extern f_notify_notification_show notify_notification_show; - -//using f_notify_notification_set_app_name = void (*)(NotifyNotification *notification, const char *app_name); -//extern f_notify_notification_set_app_name notify_notification_set_app_name; - -using f_notify_notification_set_timeout = void (*)(NotifyNotification *notification, gint timeout); -extern f_notify_notification_set_timeout notify_notification_set_timeout; - -//using f_notify_notification_set_category = void (*)(NotifyNotification *notification, const char *category); -//extern f_notify_notification_set_category notify_notification_set_category; - -//using f_notify_notification_set_urgency = void (*)(NotifyNotification *notification, NotifyUrgency urgency); -//extern f_notify_notification_set_urgency notify_notification_set_urgency; - -//using f_notify_notification_set_icon_from_pixbuf = void (*)(NotifyNotification *notification, GdkPixbuf *icon); -//extern f_notify_notification_set_icon_from_pixbuf notify_notification_set_icon_from_pixbuf; - -using f_notify_notification_set_image_from_pixbuf = void (*)(NotifyNotification *notification, GdkPixbuf *pixbuf); -extern f_notify_notification_set_image_from_pixbuf notify_notification_set_image_from_pixbuf; - -//using f_notify_notification_set_hint = void (*)(NotifyNotification *notification, const char *key, GVariant *value); -//extern f_notify_notification_set_hint notify_notification_set_hint; - -//using f_notify_notification_set_hint_int32 = void (*)(NotifyNotification *notification, const char *key, gint value); -//extern f_notify_notification_set_hint_int32 notify_notification_set_hint_int32; - -//using f_notify_notification_set_hint_uint32 = void (*)(NotifyNotification *notification, const char *key, guint value); -//extern f_notify_notification_set_hint_uint32 notify_notification_set_hint_uint32; - -//using f_notify_notification_set_hint_double = void (*)(NotifyNotification *notification, const char *key, gdouble value); -//extern f_notify_notification_set_hint_double notify_notification_set_hint_double; - -using f_notify_notification_set_hint_string = void (*)(NotifyNotification *notification, const char *key, const char *value); -extern f_notify_notification_set_hint_string notify_notification_set_hint_string; - -//using f_notify_notification_set_hint_byte = void (*)(NotifyNotification *notification, const char *key, guchar value); -//extern f_notify_notification_set_hint_byte notify_notification_set_hint_byte; - -//using f_notify_notification_set_hint_byte_array = void (*)(NotifyNotification *notification, const char *key, const guchar *value, gsize len); -//extern f_notify_notification_set_hint_byte_array notify_notification_set_hint_byte_array; - -//using f_notify_notification_clear_hints = void (*)(NotifyNotification *notification); -//extern f_notify_notification_clear_hints notify_notification_clear_hints; - -using f_notify_notification_add_action = void (*)(NotifyNotification *notification, const char *action, const char *label, NotifyActionCallback callback, gpointer user_data, GFreeFunc free_func); -extern f_notify_notification_add_action notify_notification_add_action; - -using f_notify_notification_clear_actions = void (*)(NotifyNotification *notification); -extern f_notify_notification_clear_actions notify_notification_clear_actions; - -using f_notify_notification_close = gboolean (*)(NotifyNotification *notification, GError **error); -extern f_notify_notification_close notify_notification_close; - -using f_notify_notification_get_closed_reason = gint (*)(const NotifyNotification *notification); -extern f_notify_notification_get_closed_reason notify_notification_get_closed_reason; - -} // namespace Libs -} // namespace Platform -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.cpp b/Telegram/SourceFiles/platform/linux/linux_libs.cpp index 5071d63d18..d60734e7ca 100644 --- a/Telegram/SourceFiles/platform/linux/linux_libs.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_libs.cpp @@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/linux/linux_libs.h" #include "platform/linux/linux_gdk_helper.h" -#include "platform/linux/linux_libnotify.h" #include "platform/linux/linux_desktop_environment.h" #include @@ -290,10 +289,6 @@ void start() { } else { LOG(("Could not load gtk-x11-2.0!")); } - - if (gtkLoaded) { - startLibNotify(); - } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index e3a60a92f7..42de1f9473 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -274,13 +274,6 @@ void MainWindow::psSetupTrayIcon() { } trayIcon->setIcon(icon); - // This is very important for native notifications via libnotify! - // Some notification servers compose several notifications with a "Reply" - // action into one and after that a click on "Reply" button does not call - // the specified callback from any of the sent notification - libnotify - // just ignores ibus messages, but Qt tray icon at least emits this signal. - connect(trayIcon, SIGNAL(messageClicked()), this, SLOT(showFromTray())); - attachToTrayIcon(trayIcon); } updateIconCounters(); diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index b2dd4e2f64..e858946064 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -7,390 +7,309 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/notifications_manager_linux.h" -#include "window/notifications_utilities.h" -#include "platform/linux/linux_libnotify.h" -#include "platform/linux/linux_libs.h" #include "history/history.h" #include "lang/lang_keys.h" #include "facades.h" +#include +#include +#include +#include + namespace Platform { namespace Notifications { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION namespace { -bool LibNotifyLoaded() { - return (Libs::notify_init != nullptr) - && (Libs::notify_uninit != nullptr) - && (Libs::notify_is_initted != nullptr) -// && (Libs::notify_get_app_name != nullptr) -// && (Libs::notify_set_app_name != nullptr) - && (Libs::notify_get_server_caps != nullptr) - && (Libs::notify_get_server_info != nullptr) - && (Libs::notify_notification_new != nullptr) -// && (Libs::notify_notification_update != nullptr) - && (Libs::notify_notification_show != nullptr) -// && (Libs::notify_notification_set_app_name != nullptr) - && (Libs::notify_notification_set_timeout != nullptr) -// && (Libs::notify_notification_set_category != nullptr) -// && (Libs::notify_notification_set_urgency != nullptr) -// && (Libs::notify_notification_set_icon_from_pixbuf != nullptr) - && (Libs::notify_notification_set_image_from_pixbuf != nullptr) -// && (Libs::notify_notification_set_hint != nullptr) -// && (Libs::notify_notification_set_hint_int32 != nullptr) -// && (Libs::notify_notification_set_hint_uint32 != nullptr) -// && (Libs::notify_notification_set_hint_double != nullptr) - && (Libs::notify_notification_set_hint_string != nullptr) -// && (Libs::notify_notification_set_hint_byte != nullptr) -// && (Libs::notify_notification_set_hint_byte_array != nullptr) -// && (Libs::notify_notification_clear_hints != nullptr) - && (Libs::notify_notification_add_action != nullptr) - && (Libs::notify_notification_clear_actions != nullptr) - && (Libs::notify_notification_close != nullptr) - && (Libs::notify_notification_get_closed_reason != nullptr) - && (Libs::g_object_ref_sink != nullptr) - && (Libs::g_object_unref != nullptr) - && (Libs::g_list_free_full != nullptr) - && (Libs::g_error_free != nullptr) - && (Libs::g_signal_connect_data != nullptr) - && (Libs::g_signal_handler_disconnect != nullptr) -// && (Libs::gdk_pixbuf_new_from_data != nullptr) - && (Libs::gdk_pixbuf_new_from_file != nullptr); +constexpr auto kService = str_const("org.freedesktop.Notifications"); +constexpr auto kObjectPath = str_const("/org/freedesktop/Notifications"); +constexpr auto kInterface = kService; + +std::vector GetServerInformation( + const std::shared_ptr ¬ificationInterface) { + std::vector serverInformation; + auto serverInformationReply = notificationInterface + ->call("GetServerInformation"); + + if (serverInformationReply.type() == QDBusMessage::ReplyMessage) { + for (const auto &arg : serverInformationReply.arguments()) { + if (static_cast(arg.type()) + == QMetaType::QString) { + serverInformation.push_back(arg.toString()); + } else { + LOG(("Native notification error: " + "all elements in GetServerInformation " + "should be strings")); + } + } + } else if (serverInformationReply.type() == QDBusMessage::ErrorMessage) { + LOG(("Native notification error: %1") + .arg(QDBusError(serverInformationReply).message())); + } else { + LOG(("Native notification error: " + "error while getting information about notification daemon")); + } + + return serverInformation; } -QString escapeHtml(const QString &text) { - auto result = QString(); - auto copyFrom = 0, textSize = text.size(); - auto data = text.constData(); - for (auto i = 0; i != textSize; ++i) { - auto ch = data[i]; - if (ch == '<' || ch == '>' || ch == '&') { - if (!copyFrom) { - result.reserve(textSize * 5); - } - if (i > copyFrom) { - result.append(data + copyFrom, i - copyFrom); - } - switch (ch.unicode()) { - case '<': result.append(qstr("<")); break; - case '>': result.append(qstr(">")); break; - case '&': result.append(qstr("&")); break; - } - copyFrom = i + 1; - } +std::vector GetCapabilities( + const std::shared_ptr ¬ificationInterface) { + QDBusReply capabilitiesReply = notificationInterface + ->call("GetCapabilities"); + + if (capabilitiesReply.isValid()) { + return capabilitiesReply.value().toVector().toStdVector(); + } else { + LOG(("Native notification error: %1") + .arg(capabilitiesReply.error().message())); } - if (copyFrom > 0) { - result.append(data + copyFrom, textSize - copyFrom); - return result; - } - return text; + + return std::vector(); } -class NotificationData { -public: - NotificationData(const std::shared_ptr &guarded, const QString &title, const QString &body, const QStringList &capabilities, PeerId peerId, MsgId msgId) - : _data(Libs::notify_notification_new(title.toUtf8().constData(), body.toUtf8().constData(), nullptr)) { - if (valid()) { - init(guarded, capabilities, peerId, msgId); - } - } - bool valid() const { - return (_data != nullptr); - } - NotificationData(const NotificationData &other) = delete; - NotificationData &operator=(const NotificationData &other) = delete; - NotificationData(NotificationData &&other) = delete; - NotificationData &operator=(NotificationData &&other) = delete; - - void setImage(const QString &imagePath) { - auto imagePathNative = QFile::encodeName(imagePath); - if (auto pixbuf = Libs::gdk_pixbuf_new_from_file(imagePathNative.constData(), nullptr)) { - Libs::notify_notification_set_image_from_pixbuf(_data, pixbuf); - Libs::g_object_unref(Libs::g_object_cast(pixbuf)); - } - } - bool show() { - if (valid()) { - GError *error = nullptr; - - Libs::notify_notification_show(_data, &error); - if (!error) { - return true; - } - - logError(error); - } - return false; +QVersionNumber ParseSpecificationVersion( + const std::vector &serverInformation) { + if (serverInformation.size() >= 4) { + return QVersionNumber::fromString(serverInformation[3]); + } else { + LOG(("Native notification error: " + "server information should have 4 elements")); } - bool close() { - if (valid()) { - GError *error = nullptr; - Libs::notify_notification_close(_data, &error); - if (!error) { - return true; - } - - logError(error); - } - return false; - } - - ~NotificationData() { - if (valid()) { -// if (_handlerId > 0) { -// Libs::g_signal_handler_disconnect(Libs::g_object_cast(_data), _handlerId); -// } -// Libs::notify_notification_clear_actions(_data); - Libs::g_object_unref(Libs::g_object_cast(_data)); - } - } - -private: - void init(const std::shared_ptr &guarded, const QStringList &capabilities, PeerId peerId, MsgId msgId) { - if (capabilities.contains(qsl("append"))) { - Libs::notify_notification_set_hint_string(_data, "append", "true"); - } else if (capabilities.contains(qsl("x-canonical-append"))) { - Libs::notify_notification_set_hint_string(_data, "x-canonical-append", "true"); - } - - Libs::notify_notification_set_hint_string(_data, "desktop-entry", "telegramdesktop"); - - auto signalReceiver = Libs::g_object_cast(_data); - auto signalHandler = G_CALLBACK(NotificationData::notificationClosed); - auto signalName = "closed"; - auto signalDataFreeMethod = &NotificationData::notificationDataFreeClosure; - auto signalData = new NotificationDataStruct(guarded, peerId, msgId); - _handlerId = Libs::g_signal_connect_helper(signalReceiver, signalName, signalHandler, signalData, signalDataFreeMethod); - - Libs::notify_notification_set_timeout(_data, Libs::NOTIFY_EXPIRES_DEFAULT); - - if ((*guarded)->hasActionsSupport()) { - auto label = tr::lng_notification_reply(tr::now).toUtf8(); - auto actionReceiver = _data; - auto actionHandler = &NotificationData::notificationClicked; - auto actionLabel = label.constData(); - auto actionName = "default"; - auto actionDataFreeMethod = &NotificationData::notificationDataFree; - auto actionData = new NotificationDataStruct(guarded, peerId, msgId); - Libs::notify_notification_add_action(actionReceiver, actionName, actionLabel, actionHandler, actionData, actionDataFreeMethod); - } - } - - void logError(GError *error) { - LOG(("LibNotify Error: domain %1, code %2, message '%3'").arg(error->domain).arg(error->code).arg(QString::fromUtf8(error->message))); - Libs::g_error_free(error); - } - - struct NotificationDataStruct { - NotificationDataStruct(const std::shared_ptr &guarded, PeerId peerId, MsgId msgId) - : weak(guarded) - , peerId(peerId) - , msgId(msgId) { - } - - std::weak_ptr weak; - PeerId peerId = 0; - MsgId msgId = 0; - }; - static void performOnMainQueue(NotificationDataStruct *data, FnMut task) { - const auto weak = data->weak; - crl::on_main(weak, [=, task = std::move(task)]() mutable { - task(*weak.lock()); - }); - } - static void notificationDataFree(gpointer data) { - auto notificationData = static_cast(data); - delete notificationData; - } - static void notificationDataFreeClosure(gpointer data, GClosure *closure) { - auto notificationData = static_cast(data); - delete notificationData; - } - static void notificationClosed(Libs::NotifyNotification *notification, gpointer data) { - auto closedReason = Libs::notify_notification_get_closed_reason(notification); - auto notificationData = static_cast(data); - performOnMainQueue(notificationData, [peerId = notificationData->peerId, msgId = notificationData->msgId](Manager *manager) { - manager->clearNotification(peerId, msgId); - }); - } - static void notificationClicked(Libs::NotifyNotification *notification, char *action, gpointer data) { - auto notificationData = static_cast(data); - performOnMainQueue(notificationData, [peerId = notificationData->peerId, msgId = notificationData->msgId](Manager *manager) { - manager->notificationActivated(peerId, msgId); - }); - } - - Libs::NotifyNotification *_data = nullptr; - gulong _handlerId = 0; - -}; - -using Notification = std::shared_ptr; - -QString GetServerName() { - if (!LibNotifyLoaded()) { - return QString(); - } - if (!Libs::notify_is_initted() && !Libs::notify_init("Telegram Desktop")) { - LOG(("LibNotify Error: failed to init!")); - return QString(); - } - - gchar *name = nullptr; - auto guard = gsl::finally([&name] { - if (name) Libs::g_free(name); - }); - - if (!Libs::notify_get_server_info(&name, nullptr, nullptr, nullptr)) { - LOG(("LibNotify Error: could not get server name!")); - return QString(); - } - if (!name) { - LOG(("LibNotify Error: successfully got empty server name!")); - return QString(); - } - - auto result = QString::fromUtf8(static_cast(name)); - LOG(("Notifications Server: %1").arg(result)); - - return result; + return QVersionNumber(); } -auto LibNotifyServerName = QString(); +} -} // namespace -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION +NotificationData::NotificationData( + const std::shared_ptr ¬ificationInterface, + const base::weak_ptr &manager, + const QString &title, const QString &subtitle, + const QString &msg, PeerId peerId, MsgId msgId) +: _notificationInterface(notificationInterface) +, _manager(manager) +, _title(title) +, _peerId(peerId) +, _msgId(msgId) { + auto capabilities = GetCapabilities(_notificationInterface); + auto capabilitiesEnd = capabilities.end(); + + if (ranges::find(capabilities, qsl("body-markup")) != capabilitiesEnd) { + _body = subtitle.isEmpty() + ? msg.toHtmlEscaped() + : qsl("%1\n%2").arg(subtitle.toHtmlEscaped()) + .arg(msg.toHtmlEscaped()); + } else { + _body = subtitle.isEmpty() + ? msg + : qsl("%1\n%2").arg(subtitle).arg(msg); + } + + if (ranges::find(capabilities, qsl("actions")) != capabilitiesEnd) { + // icon name according to https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html + _actions << "mail-reply-sender" + << tr::lng_notification_reply(tr::now); + + connect(_notificationInterface.get(), + SIGNAL(ActionInvoked(uint, QString)), + this, SLOT(notificationClicked(uint))); + } + + if (ranges::find(capabilities, qsl("action-icons")) != capabilitiesEnd) { + _hints["action-icons"] = true; + } + + // suppress system sound if telegram sound activated, otherwise use system sound + if (ranges::find(capabilities, qsl("sound")) != capabilitiesEnd) { + if (Global::SoundNotify()) { + _hints["suppress-sound"] = true; + } else { + // sound name according to http://0pointer.de/public/sound-naming-spec.html + _hints["sound-name"] = "message-new-instant"; + } + } + + if (ranges::find(capabilities, qsl("x-canonical-append")) + != capabilitiesEnd) { + _hints["x-canonical-append"] = "true"; + } + + _hints["category"] = "im.received"; + _hints["desktop-entry"] = "telegramdesktop"; + + connect(_notificationInterface.get(), + SIGNAL(NotificationClosed(uint, uint)), + this, SLOT(notificationClosed(uint))); +} + +bool NotificationData::show() { + QDBusReply notifyReply = _notificationInterface->call("Notify", + str_const_toString(AppName), uint(0), "telegram", _title, _body, + _actions, _hints, -1); + + if (notifyReply.isValid()) { + _notificationId = notifyReply.value(); + } else { + LOG(("Native notification error: %1") + .arg(notifyReply.error().message())); + } + + return notifyReply.isValid(); +} + +bool NotificationData::close() { + QDBusReply closeReply = _notificationInterface + ->call("CloseNotification", _notificationId); + + if (!closeReply.isValid()) { + LOG(("Native notification error: %1") + .arg(closeReply.error().message())); + } + + return closeReply.isValid(); +} + +void NotificationData::setImage(const QString &imagePath) { + auto specificationVersion = ParseSpecificationVersion( + GetServerInformation(_notificationInterface)); + + QString imageKey; + + if (!specificationVersion.isNull()) { + const auto majorVersion = specificationVersion.majorVersion(); + const auto minorVersion = specificationVersion.minorVersion(); + + if ((majorVersion == 1 && minorVersion >= 2) || majorVersion > 1) { + imageKey = "image-data"; + } else if (majorVersion == 1 && minorVersion) { + imageKey = "image_data"; + } else if ((majorVersion == 1 && minorVersion < 1) + || majorVersion < 1) { + imageKey = "icon_data"; + } else { + LOG(("Native notification error: unknown specification version")); + return; + } + } else { + LOG(("Native notification error: specification version is null")); + return; + } + + auto image = QImage(imagePath).convertToFormat(QImage::Format_RGBA8888); + QByteArray imageBytes((const char*)image.constBits(), + image.sizeInBytes()); + + ImageData imageData; + imageData.width = image.width(); + imageData.height = image.height(); + imageData.rowStride = image.bytesPerLine(); + imageData.hasAlpha = true; + imageData.bitsPerSample = 8; + imageData.channels = 4; + imageData.data = imageBytes; + + _hints[imageKey] = QVariant::fromValue(imageData); +} + +void NotificationData::notificationClosed(uint id) { + if (id == _notificationId) { + const auto manager = _manager; + crl::on_main(manager, [=] { + manager->clearNotification(_peerId, _msgId); + }); + } +} + +void NotificationData::notificationClicked(uint id) { + if (id == _notificationId) { + const auto manager = _manager; + crl::on_main(manager, [=] { + manager->notificationActivated(_peerId, _msgId); + }); + } +} + +QDBusArgument &operator<<(QDBusArgument &argument, + const NotificationData::ImageData &imageData) { + argument.beginStructure(); + argument << imageData.width + << imageData.height + << imageData.rowStride + << imageData.hasAlpha + << imageData.bitsPerSample + << imageData.channels + << imageData.data; + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, + NotificationData::ImageData &imageData) { + argument.beginStructure(); + argument >> imageData.width + >> imageData.height + >> imageData.rowStride + >> imageData.hasAlpha + >> imageData.bitsPerSample + >> imageData.channels + >> imageData.data; + argument.endStructure(); + return argument; +} bool Supported() { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION static auto Checked = false; + static auto NotificationDaemonRunning = false; + if (!Checked) { Checked = true; - LibNotifyServerName = GetServerName(); + NotificationDaemonRunning = QDBusInterface( + str_const_toString(kService), + str_const_toString(kObjectPath), + str_const_toString(kInterface)).isValid(); } - return !LibNotifyServerName.isEmpty(); -#else - return false; -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION + return NotificationDaemonRunning; } -std::unique_ptr Create(Window::Notifications::System *system) { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION +std::unique_ptr Create( + Window::Notifications::System *system) { if (Global::NativeNotifications() && Supported()) { return std::make_unique(system); } -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION return nullptr; } -void Finish() { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - if (Libs::notify_is_initted && Libs::notify_uninit) { - if (Libs::notify_is_initted()) { - Libs::notify_uninit(); - } - } -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION -} +Manager::Private::Private(Manager *manager, Type type) +: _cachedUserpics(type) +, _manager(manager) +, _notificationInterface(std::make_shared( + str_const_toString(kService), + str_const_toString(kObjectPath), + str_const_toString(kInterface))) { + qDBusRegisterMetaType(); -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -class Manager::Private { -public: - using Type = Window::Notifications::CachedUserpics::Type; - explicit Private(Type type) - : _cachedUserpics(type) { + auto specificationVersion = ParseSpecificationVersion( + GetServerInformation(_notificationInterface)); + + auto capabilities = GetCapabilities(_notificationInterface); + + if (!specificationVersion.isNull()) { + LOG(("Notification daemon specification version: %1") + .arg(specificationVersion.toString())); } - void init(Manager *manager); + if (!capabilities.empty()) { + const auto capabilitiesString = std::accumulate( + capabilities.begin(), + capabilities.end(), + QString{}, + [](auto &s, auto &p) { + return s + (p + qstr(", ")); + }).chopped(2); - void showNotification( - not_null peer, - MsgId msgId, - const QString &title, - const QString &subtitle, - const QString &msg, - bool hideNameAndPhoto, - bool hideReplyButton); - void clearAll(); - void clearFromHistory(not_null history); - void clearNotification(PeerId peerId, MsgId msgId); - - bool hasPoorSupport() const { - return _poorSupported; + LOG(("Notification daemon capabilities: %1").arg(capabilitiesString)); } - bool hasActionsSupport() const { - return _actionsSupported; - } - - ~Private(); - -private: - QString escapeNotificationText(const QString &text) const; - void showNextNotification(); - - struct QueuedNotification { - PeerData *peer = nullptr; - MsgId msgId = 0; - QString title; - QString body; - bool hideNameAndPhoto = false; - }; - - QString _serverName; - QStringList _capabilities; - - using QueuedNotifications = QList; - QueuedNotifications _queuedNotifications; - - using Notifications = QMap>; - Notifications _notifications; - - Window::Notifications::CachedUserpics _cachedUserpics; - bool _actionsSupported = false; - bool _markupSupported = false; - bool _poorSupported = false; - - std::shared_ptr _guarded; - -}; -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -void Manager::Private::init(Manager *manager) { - _guarded = std::make_shared(manager); - - if (auto capabilities = Libs::notify_get_server_caps()) { - for (auto capability = capabilities; capability; capability = capability->next) { - auto capabilityText = QString::fromUtf8(static_cast(capability->data)); - _capabilities.push_back(capabilityText); - } - Libs::g_list_free_full(capabilities, Libs::g_free); - - LOG(("LibNotify capabilities: %1").arg(_capabilities.join(qstr(", ")))); - if (_capabilities.contains(qsl("actions"))) { - _actionsSupported = true; - } - if (_capabilities.contains(qsl("body-markup"))) { - _markupSupported = true; - } - } else { - LOG(("LibNotify Error: could not get capabilities!")); - } - - // Unity and other Notify OSD users handle desktop notifications - // extremely poor, even without the ability to close() them. - _serverName = LibNotifyServerName; - Assert(!_serverName.isEmpty()); - if (_serverName == qstr("notify-osd")) { -// _poorSupported = true; - _actionsSupported = false; - } -} - -QString Manager::Private::escapeNotificationText(const QString &text) const { - return _markupSupported ? escapeHtml(text) : text; } void Manager::Private::showNotification( @@ -401,93 +320,44 @@ void Manager::Private::showNotification( const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) { - auto titleText = escapeNotificationText(title); - auto subtitleText = escapeNotificationText(subtitle); - auto msgText = escapeNotificationText(msg); - if (_markupSupported && !subtitleText.isEmpty()) { - subtitleText = qstr("") + subtitleText + qstr(""); - } - auto bodyText = subtitleText.isEmpty() ? msgText : (subtitleText + '\n' + msgText); - - QueuedNotification notification; - notification.peer = peer; - notification.msgId = msgId; - notification.title = titleText; - notification.body = bodyText; - notification.hideNameAndPhoto = hideNameAndPhoto; - _queuedNotifications.push_back(notification); - - showNextNotification(); -} - -void Manager::Private::showNextNotification() { - // Show only one notification at a time in Unity / Notify OSD. - if (_poorSupported) { - for (auto b = _notifications.begin(); !_notifications.isEmpty() && b->isEmpty();) { - _notifications.erase(b); - } - if (!_notifications.isEmpty()) { - return; - } - } - - QueuedNotification data; - while (!_queuedNotifications.isEmpty()) { - data = _queuedNotifications.front(); - _queuedNotifications.pop_front(); - if (data.peer) { - break; - } - } - if (!data.peer) { - return; - } - - auto peerId = data.peer->id; - auto msgId = data.msgId; auto notification = std::make_shared( - _guarded, - data.title, - data.body, - _capabilities, - peerId, + _notificationInterface, + _manager, + title, + subtitle, + msg, + peer->id, msgId); - if (!notification->valid()) { - return; - } - const auto key = data.hideNameAndPhoto + const auto key = hideNameAndPhoto ? InMemoryKey() - : data.peer->userpicUniqueKey(); - notification->setImage(_cachedUserpics.get(key, data.peer)); + :peer->userpicUniqueKey(); + notification->setImage(_cachedUserpics.get(key, peer)); - auto i = _notifications.find(peerId); + auto i = _notifications.find(peer->id); if (i != _notifications.cend()) { auto j = i->find(msgId); if (j != i->cend()) { auto oldNotification = j.value(); i->erase(j); oldNotification->close(); - i = _notifications.find(peerId); + i = _notifications.find(peer->id); } } if (i == _notifications.cend()) { - i = _notifications.insert(peerId, QMap()); + i = _notifications.insert(peer->id, QMap()); } - _notifications[peerId].insert(msgId, notification); + _notifications[peer->id].insert(msgId, notification); if (!notification->show()) { - i = _notifications.find(peerId); + i = _notifications.find(peer->id); if (i != _notifications.cend()) { i->remove(msgId); if (i->isEmpty()) _notifications.erase(i); } - showNextNotification(); } } void Manager::Private::clearAll() { - _queuedNotifications.clear(); - auto temp = base::take(_notifications); for_const (auto ¬ifications, temp) { for_const (auto notification, notifications) { @@ -497,14 +367,6 @@ void Manager::Private::clearAll() { } void Manager::Private::clearFromHistory(not_null history) { - for (auto i = _queuedNotifications.begin(); i != _queuedNotifications.end();) { - if (i->peer == history->peer) { - i = _queuedNotifications.erase(i); - } else { - ++i; - } - } - auto i = _notifications.find(history->peer->id); if (i != _notifications.cend()) { auto temp = base::take(i.value()); @@ -514,8 +376,6 @@ void Manager::Private::clearFromHistory(not_null history) { notification->close(); } } - - showNextNotification(); } void Manager::Private::clearNotification(PeerId peerId, MsgId msgId) { @@ -526,31 +386,21 @@ void Manager::Private::clearNotification(PeerId peerId, MsgId msgId) { _notifications.erase(i); } } - - showNextNotification(); } Manager::Private::~Private() { clearAll(); } -Manager::Manager(Window::Notifications::System *system) : NativeManager(system) -, _private(std::make_unique(Private::Type::Rounded)) { - _private->init(this); +Manager::Manager(Window::Notifications::System *system) +: NativeManager(system) +, _private(std::make_unique(this, Private::Type::Rounded)) { } void Manager::clearNotification(PeerId peerId, MsgId msgId) { _private->clearNotification(peerId, msgId); } -bool Manager::hasPoorSupport() const { - return _private->hasPoorSupport(); -} - -bool Manager::hasActionsSupport() const { - return _private->hasActionsSupport(); -} - Manager::~Manager() = default; void Manager::doShowNativeNotification( @@ -578,7 +428,6 @@ void Manager::doClearAllFast() { void Manager::doClearFromHistory(not_null history) { _private->clearFromHistory(history); } -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } // namespace Notifications } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h index f40204a56c..b5bcc726d3 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h @@ -8,6 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "platform/platform_notifications_manager.h" +#include "window/notifications_utilities.h" +#include "base/weak_ptr.h" + +#include +#include namespace Platform { namespace Notifications { @@ -23,16 +28,64 @@ inline bool SkipToast() { inline void FlashBounce() { } -void Finish(); +class NotificationData : public QObject { + Q_OBJECT -class Manager : public Window::Notifications::NativeManager { +public: + NotificationData( + const std::shared_ptr ¬ificationInterface, + const base::weak_ptr &manager, + const QString &title, const QString &subtitle, + const QString &msg, PeerId peerId, MsgId msgId); + + NotificationData(const NotificationData &other) = delete; + NotificationData &operator=(const NotificationData &other) = delete; + NotificationData(NotificationData &&other) = delete; + NotificationData &operator=(NotificationData &&other) = delete; + + bool show(); + bool close(); + void setImage(const QString &imagePath); + + struct ImageData { + int width, height, rowStride; + bool hasAlpha; + int bitsPerSample, channels; + QByteArray data; + }; + +private: + std::shared_ptr _notificationInterface; + base::weak_ptr _manager; + + QString _title; + QString _body; + QStringList _actions; + QVariantMap _hints; + + uint _notificationId; + PeerId _peerId; + MsgId _msgId; + +private slots: + void notificationClosed(uint id); + void notificationClicked(uint id); +}; + +using Notification = std::shared_ptr; + +QDBusArgument &operator<<(QDBusArgument &argument, + const NotificationData::ImageData &imageData); + +const QDBusArgument &operator>>(const QDBusArgument &argument, + NotificationData::ImageData &imageData); + +class Manager + : public Window::Notifications::NativeManager + , public base::has_weak_ptr { public: Manager(Window::Notifications::System *system); - void clearNotification(PeerId peerId, MsgId msgId); - bool hasPoorSupport() const; - bool hasActionsSupport() const; - ~Manager(); protected: @@ -53,5 +106,35 @@ private: }; +class Manager::Private { +public: + using Type = Window::Notifications::CachedUserpics::Type; + explicit Private(Manager *manager, Type type); + + void showNotification( + not_null peer, + MsgId msgId, + const QString &title, + const QString &subtitle, + const QString &msg, + bool hideNameAndPhoto, + bool hideReplyButton); + void clearAll(); + void clearFromHistory(not_null history); + void clearNotification(PeerId peerId, MsgId msgId); + + ~Private(); + +private: + using Notifications = QMap>; + Notifications _notifications; + + Window::Notifications::CachedUserpics _cachedUserpics; + base::weak_ptr _manager; + std::shared_ptr _notificationInterface; +}; + } // namespace Notifications } // namespace Platform + +Q_DECLARE_METATYPE(Platform::Notifications::NotificationData::ImageData) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 01afcd3fa4..e4689994bd 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -229,7 +229,6 @@ void start() { } void finish() { - Notifications::Finish(); } void RegisterCustomScheme() { diff --git a/Telegram/gyp/telegram/sources.txt b/Telegram/gyp/telegram/sources.txt index 70df322f10..6893fc7c96 100644 --- a/Telegram/gyp/telegram/sources.txt +++ b/Telegram/gyp/telegram/sources.txt @@ -594,8 +594,6 @@ <(src_loc)/platform/linux/linux_desktop_environment.h <(src_loc)/platform/linux/linux_gdk_helper.cpp <(src_loc)/platform/linux/linux_gdk_helper.h -<(src_loc)/platform/linux/linux_libnotify.cpp -<(src_loc)/platform/linux/linux_libnotify.h <(src_loc)/platform/linux/linux_libs.cpp <(src_loc)/platform/linux/linux_libs.h <(src_loc)/platform/linux/file_utilities_linux.cpp