diff options
Diffstat (limited to 'src/notifications.cpp')
-rw-r--r-- | src/notifications.cpp | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/src/notifications.cpp b/src/notifications.cpp new file mode 100644 index 0000000..18f15d9 --- /dev/null +++ b/src/notifications.cpp @@ -0,0 +1,370 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <notifications/notifications.h> + +#include <libnotify/notify.h> + +#include <map> +#include <set> +#include <string> +#include <vector> + +namespace unity { +namespace indicator { +namespace notifications { + +static G_DEFINE_QUARK(NotificationKey, notification_key) + +static G_DEFINE_QUARK(NotificationAction, notification_action) + +/*** +**** +***/ + +class Builder::Impl +{ +public: + std::string m_title; + std::string m_body; + std::string m_icon_name; + std::chrono::seconds m_duration; + std::set<std::string> m_string_hints; + std::vector<std::pair<std::string,std::string>> m_actions; + std::function<void(const std::string&)> m_closed_callback; +}; + +Builder::Builder(): + impl(new Impl()) +{ +} + +Builder::~Builder() +{ +} + +void +Builder::set_title (const std::string& title) +{ + impl->m_title = title; +} + +void +Builder::set_body (const std::string& body) +{ + impl->m_body = body; +} + +void +Builder::set_icon_name (const std::string& icon_name) +{ + impl->m_icon_name = icon_name; +} + +void +Builder::set_timeout (const std::chrono::seconds& duration) +{ + impl->m_duration = duration; +} + +void +Builder::add_hint (const std::string& name) +{ + impl->m_string_hints.insert (name); +} + +void +Builder::add_action (const std::string& action, const std::string& label) +{ + impl->m_actions.push_back(std::pair<std::string,std::string>(action,label)); +} + +void +Builder::set_closed_callback (std::function<void (const std::string&)> cb) +{ + impl->m_closed_callback.swap (cb); +} + +/*** +**** +***/ + +class Engine::Impl +{ + struct notification_data + { + std::shared_ptr<NotifyNotification> nn; + std::function<void(const std::string&)> closed_callback; + }; + +public: + + Impl(const std::string& app_name): + m_app_name(app_name) + { + if (!notify_init(app_name.c_str())) + g_critical("Unable to initialize libnotify!"); + } + + ~Impl() + { + close_all (); + + notify_uninit (); + } + + const std::string& app_name() const + { + return m_app_name; + } + + bool supports_actions() const + { + return server_caps().count("actions") != 0; + } + + void close_all () + { + // call close() on all our keys + + std::set<int> keys; + for (const auto& it : m_notifications) + keys.insert (it.first); + + for (const int key : keys) + close (key); + } + + void close (int key) + { + auto it = m_notifications.find(key); + if (it != m_notifications.end()) + { + // tell the server to close the notification + GError * error = nullptr; + if (!notify_notification_close (it->second.nn.get(), &error)) + { + g_warning ("Unable to close notification %d: %s", key, error->message); + g_error_free (error); + } + + // call the user callback and remove it from our bookkeeping + remove_closed_notification (key); + } + } + + int show (const Builder& builder) + { + int ret = -1; + const auto& info = *builder.impl; + + std::shared_ptr<NotifyNotification> nn ( + notify_notification_new(info.m_title.c_str(), + info.m_body.c_str(), + info.m_icon_name.c_str()), + [this](NotifyNotification * n) { + g_signal_handlers_disconnect_by_data(n, this); + g_object_unref (G_OBJECT(n)); + } + ); + + if (info.m_duration.count() != 0) + { + const auto& d= info.m_duration; + auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(d); + + notify_notification_set_hint (nn.get(), + HINT_TIMEOUT, + g_variant_new_int32(ms.count())); + } + + for (const auto& hint : info.m_string_hints) + { + notify_notification_set_hint (nn.get(), + hint.c_str(), + g_variant_new_boolean(true)); + } + + for (const auto& action : info.m_actions) + { + notify_notification_add_action (nn.get(), + action.first.c_str(), + action.second.c_str(), + on_notification_clicked, + nullptr, + nullptr); + } + + static int next_key = 1; + const int key = next_key++; + g_object_set_qdata (G_OBJECT(nn.get()), + notification_key_quark(), + GINT_TO_POINTER(key)); + + m_notifications[key] = { nn, info.m_closed_callback }; + g_signal_connect (nn.get(), "closed", + G_CALLBACK(on_notification_closed), this); + + GError * error = nullptr; + if (notify_notification_show(nn.get(), &error)) + { + ret = key; + } + else + { + g_critical ("Unable to show notification for '%s': %s", + info.m_title.c_str(), + error->message); + g_error_free (error); + m_notifications.erase(key); + } + + return ret; + } + +private: + + const std::set<std::string>& server_caps() const + { + if (G_UNLIKELY(m_lazy_caps.empty())) + { + auto caps_gl = notify_get_server_caps(); + std::string caps_str; + for(auto l=caps_gl; l!=nullptr; l=l->next) + { + m_lazy_caps.insert((const char*)l->data); + + caps_str += (const char*) l->data;; + if (l->next != nullptr) + caps_str += ", "; + } + + g_debug("%s notify_get_server() returned [%s]", G_STRFUNC, caps_str.c_str()); + g_list_free_full(caps_gl, g_free); + } + + return m_lazy_caps; + } + + static void on_notification_clicked (NotifyNotification * nn, + char * action, + gpointer) + { + g_object_set_qdata_full (G_OBJECT(nn), + notification_action_quark(), + g_strdup(action), + g_free); + } + + static void on_notification_closed (NotifyNotification * nn, gpointer gself) + { + const GQuark q = notification_key_quark(); + const gpointer gkey = g_object_get_qdata(G_OBJECT(nn), q); + static_cast<Impl*>(gself)->remove_closed_notification(GPOINTER_TO_INT(gkey)); + } + + void remove_closed_notification (int key) + { + auto it = m_notifications.find(key); + g_return_if_fail (it != m_notifications.end()); + + const auto& ndata = it->second; + auto nn = ndata.nn.get(); + if (ndata.closed_callback) + { + std::string action; + + const GQuark q = notification_action_quark(); + const gpointer p = g_object_get_qdata(G_OBJECT(nn), q); + if (p != nullptr) + action = static_cast<const char*>(p); + + ndata.closed_callback (action); + } + + m_notifications.erase(it); + } + + /*** + **** + ***/ + + const std::string m_app_name; + + // key-to-data + std::map<int,notification_data> m_notifications; + + // server capabilities. + // as the name indicates, don't use this directly: use server_caps() instead + mutable std::set<std::string> m_lazy_caps; + + static constexpr char const * HINT_TIMEOUT {"x-canonical-snap-decisions-timeout"}; +}; + +/*** +**** +***/ + +Engine::Engine(const std::string& app_name): + impl(new Impl(app_name)) +{ +} + +Engine::~Engine() +{ +} + +bool +Engine::supports_actions() const +{ + return impl->supports_actions(); +} + +int +Engine::show(const Builder& builder) +{ + return impl->show(builder); +} + +void +Engine::close_all() +{ + impl->close_all(); +} + +void +Engine::close(int key) +{ + impl->close(key); +} + +const std::string& +Engine::app_name() const +{ + return impl->app_name(); +} + +/*** +**** +***/ + +} // namespace notifications +} // namespace indicator +} // namespace unity + |