From 6db52f7fa93902aff5d99564f17f0a2d8942cac2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 3 Oct 2016 11:56:03 +0300 Subject: [PATCH] LibNotify Linux notifications support added, testing. --- Telegram/SourceFiles/config.h | 4 + .../platform/linux/linux_libnotify.cpp | 118 +++++++++++ .../platform/linux/linux_libnotify.h | 131 ++++++++++++ .../SourceFiles/platform/linux/linux_libs.cpp | 7 + .../SourceFiles/platform/linux/linux_libs.h | 3 + .../platform/linux/main_window_linux.cpp | 11 - .../linux/notifications_manager_linux.cpp | 199 +++++++++++++++++- .../linux/notifications_manager_linux.h | 21 +- .../win/notifications_manager_win.cpp | 103 +-------- .../platform/win/notifications_manager_win.h | 8 +- .../window/notifications_utilities.cpp | 116 ++++++++++ .../window/notifications_utilities.h | 57 +++++ Telegram/gyp/Telegram.gyp | 4 + 13 files changed, 664 insertions(+), 118 deletions(-) create mode 100644 Telegram/SourceFiles/platform/linux/linux_libnotify.cpp create mode 100644 Telegram/SourceFiles/platform/linux/linux_libnotify.h create mode 100644 Telegram/SourceFiles/window/notifications_utilities.cpp create mode 100644 Telegram/SourceFiles/window/notifications_utilities.h diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index a9b18db740..6a0d52540b 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -292,6 +292,10 @@ static const int32 ApiId = 17349; static const char *ApiHash = "344583e45741c457fe1862106095a5eb"; #endif +#if Q_BYTE_ORDER == Q_BIG_ENDIAN +#error "Only little endian is supported!" +#endif // Q_BYTE_ORDER == Q_BIG_ENDIAN + #ifndef BETA_VERSION_MACRO #error "Beta version macro is not defined." #endif diff --git a/Telegram/SourceFiles/platform/linux/linux_libnotify.cpp b/Telegram/SourceFiles/platform/linux/linux_libnotify.cpp new file mode 100644 index 0000000000..ddae19ce14 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/linux_libnotify.cpp @@ -0,0 +1,118 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#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 + +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)) { + 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); +} + +} // namespace Libs +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_libnotify.h b/Telegram/SourceFiles/platform/linux/linux_libnotify.h new file mode 100644 index 0000000000..e59431ffe9 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/linux_libnotify.h @@ -0,0 +1,131 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +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 diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.cpp b/Telegram/SourceFiles/platform/linux/linux_libs.cpp index 484b6c534d..92185611e0 100644 --- a/Telegram/SourceFiles/platform/linux/linux_libs.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_libs.cpp @@ -22,6 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "platform/linux/linux_libs.h" #include "platform/linux/linux_gdk_helper.h" +#include "platform/linux/linux_libnotify.h" namespace Platform { namespace Libs { @@ -175,6 +176,7 @@ f_app_indicator_set_menu app_indicator_set_menu = nullptr; f_app_indicator_set_icon_full app_indicator_set_icon_full = nullptr; f_gdk_init_check gdk_init_check = nullptr; f_gdk_pixbuf_new_from_data gdk_pixbuf_new_from_data = nullptr; +f_gdk_pixbuf_new_from_file gdk_pixbuf_new_from_file = nullptr; f_gtk_status_icon_new_from_pixbuf gtk_status_icon_new_from_pixbuf = nullptr; f_gtk_status_icon_set_from_pixbuf gtk_status_icon_set_from_pixbuf = nullptr; f_gtk_status_icon_new_from_file gtk_status_icon_new_from_file = nullptr; @@ -233,6 +235,7 @@ void start() { if (gtkLoaded) { load(lib_gtk, "gdk_init_check", gdk_init_check); load(lib_gtk, "gdk_pixbuf_new_from_data", gdk_pixbuf_new_from_data); + load(lib_gtk, "gdk_pixbuf_new_from_file", gdk_pixbuf_new_from_file); load(lib_gtk, "gtk_status_icon_new_from_pixbuf", gtk_status_icon_new_from_pixbuf); load(lib_gtk, "gtk_status_icon_set_from_pixbuf", gtk_status_icon_set_from_pixbuf); load(lib_gtk, "gtk_status_icon_new_from_file", gtk_status_icon_new_from_file); @@ -266,6 +269,10 @@ void start() { load(lib_unity, "unity_launcher_entry_set_count_visible", unity_launcher_entry_set_count_visible); } #endif // TDESKTOP_DISABLE_UNITY_INTEGRATION + + if (gtkLoaded) { + startLibNotify(); + } } } // namespace Libs diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.h b/Telegram/SourceFiles/platform/linux/linux_libs.h index 6fd9a802f3..88f1e659c8 100644 --- a/Telegram/SourceFiles/platform/linux/linux_libs.h +++ b/Telegram/SourceFiles/platform/linux/linux_libs.h @@ -273,6 +273,9 @@ extern f_gdk_init_check gdk_init_check; typedef GdkPixbuf* (*f_gdk_pixbuf_new_from_data)(const guchar *data, GdkColorspace colorspace, gboolean has_alpha, int bits_per_sample, int width, int height, int rowstride, GdkPixbufDestroyNotify destroy_fn, gpointer destroy_fn_data); extern f_gdk_pixbuf_new_from_data gdk_pixbuf_new_from_data; +typedef GdkPixbuf* (*f_gdk_pixbuf_new_from_file)(const gchar *filename, GError **error); +extern f_gdk_pixbuf_new_from_file gdk_pixbuf_new_from_file; + typedef GtkStatusIcon* (*f_gtk_status_icon_new_from_pixbuf)(GdkPixbuf *pixbuf); extern f_gtk_status_icon_new_from_pixbuf gtk_status_icon_new_from_pixbuf; diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index e35e753e87..9559058f53 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -63,22 +63,11 @@ gboolean _trayIconResized(GtkStatusIcon *status_icon, gint size, gpointer popup_ return FALSE; } -#if Q_BYTE_ORDER == Q_BIG_ENDIAN - -#define QT_RED 3 -#define QT_GREEN 2 -#define QT_BLUE 1 -#define QT_ALPHA 0 - -#else - #define QT_RED 0 #define QT_GREEN 1 #define QT_BLUE 2 #define QT_ALPHA 3 -#endif - #define GTK_RED 2 #define GTK_GREEN 1 #define GTK_BLUE 0 diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index ca680140b2..5ef3467cfb 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -21,17 +21,210 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "platform/linux/notifications_manager_linux.h" +#include "window/notifications_utilities.h" +#include "platform/linux/linux_libnotify.h" +#include "platform/linux/linux_libs.h" + namespace Platform { namespace Notifications { +namespace { -void start() { +NeverFreedPointer ManagerInstance; + +bool supported() { + 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_unref != nullptr) +// && (Libs::gdk_pixbuf_new_from_data != nullptr) + && (Libs::gdk_pixbuf_new_from_file != nullptr); } -Window::Notifications::Manager *manager() { - return nullptr; +} // namespace + +void start() { + if (supported()) { + if (Libs::notify_is_initted() || Libs::notify_init("Telegram Desktop")) { + ManagerInstance.makeIfNull(); + } + } +} + +Manager *manager() { + return ManagerInstance.data(); } void finish() { + if (manager()) { + ManagerInstance.reset(); + Libs::notify_uninit(); + } +} + +class Manager::Impl { +public: + void showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, bool showUserpic, const QString &msg, bool showReplyButton); + void clearAll(); + void clearFromHistory(History *history); + +private: + static void notificationClosed(Libs::NotifyNotification *notification, gpointer data); + void clearNotification(Libs::NotifyNotification *notification); + + using Notifications = QMap>; + Notifications _notifications; + + using NotificationsData = QMap>; + NotificationsData _notificationsData; + + Window::Notifications::CachedUserpics _cachedUserpics; + +}; + +void Manager::Impl::notificationClosed(Libs::NotifyNotification *notification, gpointer data) { + if (auto manager = ManagerInstance.data()) { + manager->_impl->clearNotification(notification); + } +} + +void Manager::Impl::clearNotification(Libs::NotifyNotification *notification) { + auto dataIt = _notificationsData.find(notification); + if (dataIt == _notificationsData.cend()) { + return; + } + + auto peerId = dataIt->first; + auto msgId = dataIt->second; + _notificationsData.erase(dataIt); + + auto i = _notifications.find(peerId); + if (i != _notifications.cend()) { + auto it = i.value().find(msgId); + if (it != i.value().cend()) { + Libs::g_object_unref(Libs::g_object_cast(it.value())); + i.value().erase(it); + if (i.value().isEmpty()) { + _notifications.erase(i); + } + } + } +} + +void Manager::Impl::showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, bool showUserpic, const QString &msg, bool showReplyButton) { + auto titleUtf8 = title.toUtf8(); + auto bodyUtf8 = (subtitle.isEmpty() ? msg : ("" + subtitle + "\n" + msg)).toUtf8(); + auto notification = Libs::notify_notification_new(titleUtf8.constData(), bodyUtf8.constData(), nullptr); + if (!notification) { + return; + } + + Libs::g_signal_connect_helper(Libs::g_object_cast(notification), "closed", G_CALLBACK(Manager::Impl::notificationClosed), peer); + + StorageKey key; + if (showUserpic) { + key = peer->userpicUniqueKey(); + } else { + key = StorageKey(0, 0); + } + auto userpicPath = _cachedUserpics.get(key, peer); + auto userpicPathNative = QFile::encodeName(userpicPath); + if (auto pixbuf = Libs::gdk_pixbuf_new_from_file(userpicPathNative.constData(), nullptr)) { + Libs::notify_notification_set_image_from_pixbuf(notification, pixbuf); + Libs::g_object_unref(Libs::g_object_cast(pixbuf)); + } + + 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); + _notificationsData.remove(oldNotification); + Libs::notify_notification_close(oldNotification, nullptr); + Libs::g_object_unref(Libs::g_object_cast(oldNotification)); + i = _notifications.find(peer->id); + } + } + if (i == _notifications.cend()) { + i = _notifications.insert(peer->id, QMap()); + } + auto result = Libs::notify_notification_show(notification, nullptr); + if (!result) { + Libs::g_object_unref(Libs::g_object_cast(notification)); + i = _notifications.find(peer->id); + if (i != _notifications.cend() && i->isEmpty()) _notifications.erase(i); + return; + } + _notifications[peer->id].insert(msgId, notification); + _notificationsData.insert(notification, qMakePair(peer->id, msgId)); +} + +void Manager::Impl::clearAll() { + _notificationsData.clear(); + + auto temp = createAndSwap(_notifications); + for_const (auto ¬ifications, temp) { + for_const (auto notification, notifications) { + Libs::notify_notification_close(notification, nullptr); + Libs::g_object_unref(Libs::g_object_cast(notification)); + } + } +} + +void Manager::Impl::clearFromHistory(History *history) { + auto i = _notifications.find(history->peer->id); + if (i != _notifications.cend()) { + auto temp = createAndSwap(i.value()); + _notifications.erase(i); + + for_const (auto notification, temp) { + _notificationsData.remove(notification); + Libs::notify_notification_close(notification, nullptr); + Libs::g_object_unref(Libs::g_object_cast(notification)); + } + } +} + +Manager::Manager() : _impl(std_::make_unique()) { +} + +Manager::~Manager() = default; + +void Manager::doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, bool showUserpic, const QString &msg, bool showReplyButton) { + _impl->showNotification(peer, msgId, title, subtitle, showUserpic, msg, showReplyButton); +} + +void Manager::doClearAllFast() { + _impl->clearAll(); +} + +void Manager::doClearFromHistory(History *history) { + _impl->clearFromHistory(history); } } // namespace Notifications diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h index 4478c9bad6..e7b7e06834 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h @@ -25,12 +25,31 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Platform { namespace Notifications { +class Manager; + void start(); -Window::Notifications::Manager *manager(); +Manager *manager(); void finish(); inline void defaultNotificationShown(QWidget *widget) { } +class Manager : public Window::Notifications::NativeManager { +public: + Manager(); + ~Manager(); + +protected: + void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, bool showUserpic, const QString &msg, bool showReplyButton) override; + void doClearAllFast() override; + void doClearFromHistory(History *history) override; + +private: + class Impl; + friend class Impl; + std_::unique_ptr _impl; + +}; + } // namespace Notifications } // namespace Platform diff --git a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp index 2cf32473bd..417f18604c 100644 --- a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp +++ b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "platform/win/notifications_manager_win.h" +#include "window/notifications_utilities.h" #include "platform/win/windows_app_user_model_id.h" #include "platform/win/windows_dlls.h" #include "mainwindow.h" @@ -47,9 +48,6 @@ namespace Platform { namespace Notifications { namespace { -// Delete notify photo file after 1 minute of not using. -constexpr int kNotifyDeletePhotoAfterMs = 60000; - NeverFreedPointer ManagerInstance; ComPtr _notificationManager; @@ -65,13 +63,6 @@ struct NotificationPtr { }; using Notifications = QMap>; Notifications _notifications; -struct Image { - uint64 until; - QString path; -}; -using Images = QMap; -Images _images; -bool _imageSavedFlag = false; class StringReferenceWrapper { public: @@ -155,7 +146,6 @@ bool init() { if (!SUCCEEDED(wrap_GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), &_notificationFactory))) { return false; } - QDir().mkpath(cWorkingDir() + qsl("tdata/temp")); return true; } @@ -322,38 +312,6 @@ private: }; -QString getImage(const StorageKey &key, PeerData *peer) { - uint64 ms = getms(true); - auto i = _images.find(key); - if (i != _images.cend()) { - if (i->until) { - i->until = ms + kNotifyDeletePhotoAfterMs; - if (auto manager = ManagerInstance.data()) { - manager->clearPhotosInMs(-kNotifyDeletePhotoAfterMs); - } - } - } else { - Image v; - if (key.first) { - v.until = ms + kNotifyDeletePhotoAfterMs; - if (auto manager = ManagerInstance.data()) { - manager->clearPhotosInMs(-kNotifyDeletePhotoAfterMs); - } - } else { - v.until = 0; - } - v.path = cWorkingDir() + qsl("tdata/temp/") + QString::number(rand_value(), 16) + qsl(".png"); - if (key.first || key.second) { - peer->saveUserpic(v.path, st::notifyMacPhotoSize); - } else { - App::wnd()->iconLarge().save(v.path, "PNG"); - } - i = _images.insert(key, v); - _imageSavedFlag = true; - } - return i->path; -} - } // namespace void start() { @@ -377,28 +335,6 @@ void finish() { ManagerInstance.clear(); } -uint64 clearImages(uint64 ms) { - uint64 result = 0; - for (auto i = _images.begin(); i != _images.end();) { - if (!i->until) { - ++i; - continue; - } - if (i->until <= ms) { - QFile(i->path).remove(); - i = _images.erase(i); - } else { - if (!result) { - result = i->until; - } else { - accumulate_min(result, i->until); - } - ++i; - } - } - return result; -} - class Manager::Impl { public: bool showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, bool showUserpic, const QString &msg, bool showReplyButton); @@ -411,8 +347,7 @@ public: ~Impl(); private: - QTimer _clearPhotosTimer; - friend class Manager; + Window::Notifications::CachedUserpics _cachedUserpics; }; @@ -421,10 +356,6 @@ Manager::Impl::~Impl() { if (_notificationManager) _notificationManager.Reset(); if (_notifier) _notifier.Reset(); if (_notificationFactory) _notificationFactory.Reset(); - - if (_imageSavedFlag) { - psDeleteDir(cWorkingDir() + qsl("tdata/temp")); - } } void Manager::Impl::clearAll() { @@ -446,8 +377,8 @@ void Manager::Impl::clearFromHistory(History *history) { auto temp = createAndSwap(i.value()); _notifications.erase(i); - for (auto j = temp.cbegin(), e = temp.cend(); j != e; ++j) { - _notifier->Hide(j->p.Get()); + for_const (auto ¬ification, temp) { + _notifier->Hide(notification.p.Get()); } } } @@ -485,16 +416,15 @@ bool Manager::Impl::showNotification(PeerData *peer, MsgId msgId, const QString if (!SUCCEEDED(hr)) return false; StorageKey key; - QString imagePath; if (showUserpic) { key = peer->userpicUniqueKey(); } else { key = StorageKey(0, 0); } - QString image = getImage(key, peer); - std::wstring wimage = QDir::toNativeSeparators(image).toStdWString(); + auto userpicPath = _cachedUserpics.get(key, peer); + auto userpicPathWide = QDir::toNativeSeparators(userpicPath).toStdWString(); - hr = SetImageSrc(wimage.c_str(), toastXml.Get()); + hr = SetImageSrc(userpicPathWide.c_str(), toastXml.Get()); if (!SUCCEEDED(hr)) return false; ComPtr nodeList; @@ -576,31 +506,12 @@ bool Manager::Impl::showNotification(PeerData *peer, MsgId msgId, const QString } Manager::Manager() : _impl(std_::make_unique()) { - connect(&_impl->_clearPhotosTimer, SIGNAL(timeout()), this, SLOT(onClearPhotos())); -} - -void Manager::clearPhotosInMs(int ms) { - if (ms < 0) { - ms = -ms; - if (_impl->_clearPhotosTimer.isActive() && _impl->_clearPhotosTimer.remainingTime() <= ms) { - return; - } - } - _impl->_clearPhotosTimer.start(ms); } void Manager::clearNotification(PeerId peerId, MsgId msgId) { _impl->clearNotification(peerId, msgId); } -void Manager::onClearPhotos() { - auto ms = getms(true); - auto minuntil = clearImages(ms); - if (minuntil) { - clearPhotosInMs(int32(minuntil - ms)); - } -} - Manager::~Manager() = default; void Manager::doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, bool showUserpic, const QString &msg, bool showReplyButton) { diff --git a/Telegram/SourceFiles/platform/win/notifications_manager_win.h b/Telegram/SourceFiles/platform/win/notifications_manager_win.h index f1e24d0574..37597a1757 100644 --- a/Telegram/SourceFiles/platform/win/notifications_manager_win.h +++ b/Telegram/SourceFiles/platform/win/notifications_manager_win.h @@ -35,20 +35,14 @@ void finish(); inline void defaultNotificationShown(QWidget *widget) { } -class Manager : public QObject, public Window::Notifications::NativeManager { - Q_OBJECT - +class Manager : public Window::Notifications::NativeManager { public: Manager(); - void clearPhotosInMs(int ms); void clearNotification(PeerId peerId, MsgId msgId); ~Manager(); -private slots: - void onClearPhotos(); - protected: void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, bool showUserpic, const QString &msg, bool showReplyButton) override; void doClearAllFast() override; diff --git a/Telegram/SourceFiles/window/notifications_utilities.cpp b/Telegram/SourceFiles/window/notifications_utilities.cpp new file mode 100644 index 0000000000..db2aae815e --- /dev/null +++ b/Telegram/SourceFiles/window/notifications_utilities.cpp @@ -0,0 +1,116 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "window/notifications_utilities.h" + +#include "pspecific.h" +#include "mainwindow.h" + +namespace Window { +namespace Notifications { +namespace { + +// Delete notify photo file after 1 minute of not using. +constexpr int kNotifyDeletePhotoAfterMs = 60000; + +} // namespace + +CachedUserpics::CachedUserpics() { + connect(&_clearTimer, SIGNAL(timeout()), this, SLOT(onClear())); + QDir().mkpath(cWorkingDir() + qsl("tdata/temp")); +} + +QString CachedUserpics::get(const StorageKey &key, PeerData *peer) { + uint64 ms = getms(true); + auto i = _images.find(key); + if (i != _images.cend()) { + if (i->until) { + i->until = ms + kNotifyDeletePhotoAfterMs; + clearInMs(-kNotifyDeletePhotoAfterMs); + } + } else { + Image v; + if (key.first) { + v.until = ms + kNotifyDeletePhotoAfterMs; + clearInMs(-kNotifyDeletePhotoAfterMs); + } else { + v.until = 0; + } + v.path = cWorkingDir() + qsl("tdata/temp/") + QString::number(rand_value(), 16) + qsl(".png"); + if (key.first || key.second) { + peer->saveUserpic(v.path, st::notifyMacPhotoSize); + } else { + App::wnd()->iconLarge().save(v.path, "PNG"); + } + i = _images.insert(key, v); + _someSavedFlag = true; + } + return i->path; +} + +uint64 CachedUserpics::clear(uint64 ms) { + uint64 result = 0; + for (auto i = _images.begin(); i != _images.end();) { + if (!i->until) { + ++i; + continue; + } + if (i->until <= ms) { + QFile(i->path).remove(); + i = _images.erase(i); + } else { + if (!result) { + result = i->until; + } else { + accumulate_min(result, i->until); + } + ++i; + } + } + return result; +} + +void CachedUserpics::clearInMs(int ms) { + if (ms < 0) { + ms = -ms; + if (_clearTimer.isActive() && _clearTimer.remainingTime() <= ms) { + return; + } + } + _clearTimer.start(ms); +} + +void CachedUserpics::onClear() { + auto ms = getms(true); + auto minuntil = clear(ms); + if (minuntil) { + clearInMs(int(minuntil - ms)); + } +} + +CachedUserpics::~CachedUserpics() { + if (_someSavedFlag) { + psDeleteDir(cWorkingDir() + qsl("tdata/temp")); + } +} + +} // namespace Notifications +} // namespace Window diff --git a/Telegram/SourceFiles/window/notifications_utilities.h b/Telegram/SourceFiles/window/notifications_utilities.h new file mode 100644 index 0000000000..384690d300 --- /dev/null +++ b/Telegram/SourceFiles/window/notifications_utilities.h @@ -0,0 +1,57 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "window/notifications_manager.h" + +namespace Window { +namespace Notifications { + +class CachedUserpics : public QObject { + Q_OBJECT + +public: + CachedUserpics(); + + QString get(const StorageKey &key, PeerData *peer); + + ~CachedUserpics(); + +private slots: + void onClear(); + +private: + void clearInMs(int ms); + uint64 clear(uint64 ms); + + struct Image { + uint64 until; + QString path; + }; + using Images = QMap; + Images _images; + bool _someSavedFlag = false; + SingleTimer _clearTimer; + +}; + +} // namesapce Notifications +} // namespace Window diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index 41df541892..cb4b63355f 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -318,6 +318,8 @@ '<(src_loc)/pspecific_linux.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_dialog_linux.cpp', @@ -486,6 +488,8 @@ '<(src_loc)/window/notifications_manager.h', '<(src_loc)/window/notifications_manager_default.cpp', '<(src_loc)/window/notifications_manager_default.h', + '<(src_loc)/window/notifications_utilities.cpp', + '<(src_loc)/window/notifications_utilities.h', '<(src_loc)/window/section_widget.cpp', '<(src_loc)/window/section_widget.h', '<(src_loc)/window/slide_animation.cpp',