diff options
Diffstat (limited to 'src/notifications.cpp')
-rw-r--r-- | src/notifications.cpp | 207 |
1 files changed, 202 insertions, 5 deletions
diff --git a/src/notifications.cpp b/src/notifications.cpp index 051653d..b36227b 100644 --- a/src/notifications.cpp +++ b/src/notifications.cpp @@ -21,10 +21,23 @@ #include <libnotify/notify.h> +#include <messaging-menu/messaging-menu-app.h> +#include <messaging-menu/messaging-menu-message.h> + +#ifdef HAS_URLDISPATCHER +#include <lomiri-url-dispatcher.h> +#endif + +#include <uuid/uuid.h> + +#include <gio/gdesktopappinfo.h> + #include <map> #include <set> #include <string> #include <vector> +#include <memory> + namespace ayatana { namespace indicator { @@ -45,9 +58,13 @@ public: std::string m_body; std::string m_icon_name; std::chrono::seconds m_duration; + gint64 m_start_time {}; 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; + std::function<void()> m_timeout_callback; + bool m_show_notification_bubble; + bool m_post_to_messaging_menu; }; Builder::Builder(): @@ -101,6 +118,30 @@ Builder::set_closed_callback (std::function<void (const std::string&)> cb) impl->m_closed_callback.swap (cb); } +void +Builder::set_timeout_callback (std::function<void()> cb) +{ + impl->m_timeout_callback.swap (cb); +} + +void +Builder::set_start_time (uint64_t time) +{ + impl->m_start_time = time; +} + +void +Builder::set_show_notification_bubble (bool show) +{ + impl->m_show_notification_bubble = show; +} + +void +Builder::set_post_to_messaging_menu (bool post) +{ + impl->m_post_to_messaging_menu = post; +} + /*** **** ***/ @@ -110,7 +151,14 @@ class Engine::Impl struct notification_data { std::shared_ptr<NotifyNotification> nn; - std::function<void(const std::string&)> closed_callback; + Builder::Impl data; + }; + + struct messaging_menu_data + { + std::string msg_id; + std::function<void()> callback; + Engine::Impl *self; }; public: @@ -120,13 +168,23 @@ public: { if (!notify_init(app_name.c_str())) g_critical("Unable to initialize libnotify!"); + + // messaging menu + auto app_id = calendar_app_id(); + if (!app_id.empty()) { + m_messaging_app.reset(messaging_menu_app_new(app_id.c_str()), g_object_unref); + messaging_menu_app_register(m_messaging_app.get()); + } } ~Impl() { close_all (); + remove_all (); notify_uninit (); + if (m_messaging_app) + messaging_menu_app_unregister (m_messaging_app.get()); } const std::string& app_name() const @@ -217,10 +275,15 @@ public: notification_key_quark(), GINT_TO_POINTER(key)); - m_notifications[key] = { nn, info.m_closed_callback }; + m_notifications[key] = { nn, info }; g_signal_connect (nn.get(), "closed", G_CALLBACK(on_notification_closed), this); + if (!info.m_show_notification_bubble) { + post(info); + return ret; + } + GError * error = nullptr; if (notify_notification_show(nn.get(), &error)) { @@ -238,6 +301,76 @@ public: return ret; } + std::string post(const Builder::Impl& data) + { + if (!data.m_post_to_messaging_menu) { + return ""; + } + + if (!m_messaging_app) { + return std::string(); + } + uuid_t message_uuid; + uuid_generate(message_uuid); + + char uuid_buf[37]; + uuid_unparse(message_uuid, uuid_buf); + const std::string message_id(uuid_buf); + + // use full icon path name, "calendar-app" does not work with themed icons + auto icon_file = g_file_new_for_path(calendar_app_icon().c_str()); + // messaging_menu_message_new: will take control of icon object + GIcon *icon = g_file_icon_new(icon_file); + g_object_unref(icon_file); + + // check if source exists + if (!messaging_menu_app_has_source(m_messaging_app.get(), m_app_name.c_str())) + messaging_menu_app_append_source(m_messaging_app.get(), m_app_name.c_str(), nullptr, "Calendar"); + + auto msg = messaging_menu_message_new(message_id.c_str(), + icon, + data.m_title.c_str(), + nullptr, + data.m_body.c_str(), + data.m_start_time * G_USEC_PER_SEC); // secs -> microsecs + if (msg) + { + std::shared_ptr<messaging_menu_data> msg_data(new messaging_menu_data{message_id, data.m_timeout_callback, this}); + m_messaging_messages[message_id] = msg_data; + g_signal_connect(G_OBJECT(msg), "activate", + G_CALLBACK(on_message_activated), msg_data.get()); + messaging_menu_app_append_message(m_messaging_app.get(), msg, m_app_name.c_str(), false); + + // we use that to keep track of messaging, in case of message get cleared from menu + g_object_set_data_full(G_OBJECT(msg), "destroy-notify", msg_data.get(), on_message_destroyed); + // keep the message control with message_menu + g_object_unref(msg); + + return message_id; + } else { + g_warning("Fail to create messaging menu message"); + } + return ""; + } + + void remove (const std::string &key) + { + auto it = m_messaging_messages.find(key); + if (it != m_messaging_messages.end()) + { + // tell the server to remove message + messaging_menu_app_remove_message_by_id(m_messaging_app.get(), it->second->msg_id.c_str()); + // message will be remove by on_message_destroyed cb. + } + } + + void remove_all () + { + // call remove() on all our keys + while (!m_messaging_messages.empty()) + remove(m_messaging_messages.begin()->first); + } + private: const std::set<std::string>& server_caps() const @@ -279,6 +412,28 @@ private: static_cast<Impl*>(gself)->remove_closed_notification(GPOINTER_TO_INT(gkey)); } + static void on_message_activated (MessagingMenuMessage *, + const char *, + GVariant *, + gpointer data) + { + auto msg_data = static_cast<messaging_menu_data*>(data); + auto it = msg_data->self->m_messaging_messages.find(msg_data->msg_id); + g_return_if_fail (it != msg_data->self->m_messaging_messages.end()); + const auto& ndata = it->second; + + if (ndata->callback) + ndata->callback(); + } + + static void on_message_destroyed(gpointer data) + { + auto msg_data = static_cast<messaging_menu_data*>(data); + auto it = msg_data->self->m_messaging_messages.find(msg_data->msg_id); + if (it != msg_data->self->m_messaging_messages.end()) + msg_data->self->m_messaging_messages.erase(it); + } + void remove_closed_notification (int key) { auto it = m_notifications.find(key); @@ -286,25 +441,67 @@ private: const auto& ndata = it->second; auto nn = ndata.nn.get(); - if (ndata.closed_callback) + + if (ndata.data.m_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); + ndata.data.m_closed_callback (action); + // empty action means that the notification got timeout + // post a message on messaging menu + if (action.empty()) + post(ndata.data); } m_notifications.erase(it); } + static std::string calendar_app_id() + { +#ifdef HAS_URLDISPATCHER + auto urls = g_strsplit("calendar://", ",", 0); + auto appids = lomiri_url_dispatch_url_appid(const_cast<const gchar**>(urls)); + g_strfreev(urls); + std::string result; + if (appids != nullptr) { + // Due the use of old API by messaging_menu we need append a extra ".desktop" to the app_id. + result = std::string(appids[0]) + ".desktop"; + g_strfreev(appids); + } + return result; +#else + return std::string(); +#endif + } + + static std::string calendar_app_icon() + { + auto app_desktop = g_desktop_app_info_new(calendar_app_id().c_str()); + if (app_desktop != nullptr) { + auto icon_name = g_desktop_app_info_get_string(app_desktop, "Icon"); + g_object_unref(app_desktop); + if (icon_name) { + std::string result(icon_name); + g_free(icon_name); + return result; + } + } + g_warning("Fail to get calendar icon"); + return std::string(); + } + /*** **** ***/ + // messaging menu + std::shared_ptr<MessagingMenuApp> m_messaging_app; + std::map<std::string, std::shared_ptr<messaging_menu_data> > m_messaging_messages; + const std::string m_app_name; // key-to-data |