aboutsummaryrefslogtreecommitdiff
path: root/src/notifications.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/notifications.cpp')
-rw-r--r--src/notifications.cpp207
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