aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/CMakeLists.txt7
-rw-r--r--data/ayatana-indicator-datetime.desktop.in1
-rw-r--r--include/notifications/notifications.h11
-rw-r--r--src/main.cpp5
-rw-r--r--src/notifications.cpp127
-rw-r--r--src/snap.cpp12
-rw-r--r--tests/test-notification.cpp2
-rw-r--r--tests/test-sound.cpp2
8 files changed, 154 insertions, 13 deletions
diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt
index c460586..14d36b8 100644
--- a/data/CMakeLists.txt
+++ b/data/CMakeLists.txt
@@ -32,6 +32,7 @@ if (${SYSTEMD_FOUND})
# build it
set (pkglibexecdir "${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}")
+ set (messaging_menu_icon "${CMAKE_INSTALL_FULL_DATAROOTDIR}/icons/suru/actions/scalable/appointment.svg")
configure_file ("${SYSTEMD_USER_FILE_IN}" "${SYSTEMD_USER_FILE}")
# install it
@@ -55,7 +56,11 @@ set (XDG_AUTOSTART_FILE_IN "${CMAKE_CURRENT_SOURCE_DIR}/${XDG_AUTOSTART_NAME}.in
set (pkglibexecdir "${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}")
configure_file ("${XDG_AUTOSTART_FILE_IN}" "${XDG_AUTOSTART_FILE}")
-# install it
+# install desktop file used by messaging-menu
+install (FILES "${XDG_AUTOSTART_FILE}"
+ DESTINATION "${CMAKE_INSTALL_FULL_DATAROOTDIR}/applications/")
+
+# install XDG autostart
install (FILES "${XDG_AUTOSTART_FILE}"
DESTINATION "${XDG_AUTOSTART_DIR}")
diff --git a/data/ayatana-indicator-datetime.desktop.in b/data/ayatana-indicator-datetime.desktop.in
index 70df0d7..6d27166 100644
--- a/data/ayatana-indicator-datetime.desktop.in
+++ b/data/ayatana-indicator-datetime.desktop.in
@@ -6,3 +6,4 @@ OnlyShowIn=MATE;Unity;XFCE;Pantheon;
NoDisplay=true
StartupNotify=false
Terminal=false
+Icon=@messaging_menu_icon@
diff --git a/include/notifications/notifications.h b/include/notifications/notifications.h
index 0de1e23..2bb6694 100644
--- a/include/notifications/notifications.h
+++ b/include/notifications/notifications.h
@@ -50,6 +50,8 @@ public:
void set_icon_name (const std::string& icon_name);
+ void set_start_time(uint64_t time);
+
/* Set an interval, after which the notification will automatically
be closed. If not set, the notification server's default timeout
is used. */
@@ -62,19 +64,24 @@ public:
static constexpr char const * HINT_NONSHAPED_ICON {"x-canonical-non-shaped-icon"};
static constexpr char const * HINT_AFFIRMATIVE_HINT {"x-canonical-private-affirmative-tint"};
static constexpr char const * HINT_REJECTION_TINT {"x-canonical-private-rejection-tint"};
+ static constexpr char const * HINT_INTERACTIVE {"x-canonical-switch-to-application"};
/* Add an action button.
This may fail if the Engine doesn't support actions.
@see Engine::supports_actions() */
void add_action (const std::string& action, const std::string& label);
- /** Sets the closed callback. This will be called exactly once. */
+ /** Sets the closed callback. This will be called exactly once. After notification dissapear */
void set_closed_callback (std::function<void(const std::string& action)>);
+ /** Sets the time-out callback. This will be called exactly once. */
+ void set_missed_click_callback (std::function<void()>);
+
+
private:
friend class Engine;
class Impl;
- std::unique_ptr<Impl> impl;
+ std::shared_ptr<Impl> impl;
};
/**
diff --git a/src/main.cpp b/src/main.cpp
index bb77c0e..f9a934a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -156,7 +156,10 @@ main(int /*argc*/, char** /*argv*/)
auto on_snooze = [snooze_planner](const Appointment& appointment, const Alarm& alarm) {
snooze_planner->add(appointment, alarm);
};
- auto on_ok = [](const Appointment&, const Alarm&){};
+ auto on_ok = [actions](const Appointment& app, const Alarm&){
+ //TODO: add support for desktop
+ actions->phone_open_appointment(app, app.begin);
+ };
auto on_alarm_reached = [&engine, &snap, &on_snooze, &on_ok](const Appointment& appointment, const Alarm& alarm) {
(*snap)(appointment, alarm, on_snooze, on_ok);
engine->disable_ubuntu_alarm(appointment);
diff --git a/src/notifications.cpp b/src/notifications.cpp
index 051653d..2d22087 100644
--- a/src/notifications.cpp
+++ b/src/notifications.cpp
@@ -21,10 +21,18 @@
#include <libnotify/notify.h>
+#include <messaging-menu/messaging-menu-app.h>
+#include <messaging-menu/messaging-menu-message.h>
+
+
+#include <uuid/uuid.h>
+
#include <map>
#include <set>
#include <string>
#include <vector>
+#include <memory>
+
namespace ayatana {
namespace indicator {
@@ -45,9 +53,11 @@ 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_missed_click_callback;
};
Builder::Builder():
@@ -101,6 +111,18 @@ Builder::set_closed_callback (std::function<void (const std::string&)> cb)
impl->m_closed_callback.swap (cb);
}
+void
+Builder::set_missed_click_callback (std::function<void()> cb)
+{
+ impl->m_missed_click_callback.swap (cb);
+}
+
+void
+Builder::set_start_time (uint64_t time)
+{
+ impl->m_start_time = time;
+}
+
/***
****
***/
@@ -110,23 +132,39 @@ 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::shared_ptr<MessagingMenuMessage> mm;
+ std::function<void()> callback;
};
public:
Impl(const std::string& app_name):
+ m_messaging_app(messaging_menu_app_new(DATETIME_INDICATOR_DESKTOP_FILE), g_object_unref),
m_app_name(app_name)
{
if (!notify_init(app_name.c_str()))
g_critical("Unable to initialize libnotify!");
+
+ // messaging menu
+ GIcon *icon = g_themed_icon_new("calendar-app");
+
+ messaging_menu_app_register(m_messaging_app.get());
+ messaging_menu_app_append_source(m_messaging_app.get(), m_app_name.c_str(), icon, "Calendar");
+ g_object_unref(icon);
}
~Impl()
{
close_all ();
+ remove_all ();
notify_uninit ();
+ messaging_menu_app_unregister (m_messaging_app.get());
}
const std::string& app_name() const
@@ -217,7 +255,7 @@ 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);
@@ -238,6 +276,59 @@ public:
return ret;
}
+ std::string post(const Builder::Impl& data)
+ {
+ uuid_t message_uuid;
+ uuid_generate(message_uuid);
+
+ char message_id[37];
+ uuid_unparse(message_uuid, message_id);
+
+ GIcon *icon = g_themed_icon_new(data.m_icon_name.c_str());
+ std::shared_ptr<MessagingMenuMessage> msg (messaging_menu_message_new(message_id,
+ icon,
+ data.m_title.c_str(),
+ nullptr,
+ data.m_body.c_str(),
+ data.m_start_time * 1000000), // secs -> microsecs
+ g_object_ref);
+ g_object_unref(icon);
+ if (msg)
+ {
+ m_messaging_messages[std::string(message_id)] = { msg, data.m_missed_click_callback };
+ g_signal_connect(msg.get(), "activate",
+ G_CALLBACK(on_message_activated), this);
+ messaging_menu_app_append_message(m_messaging_app.get(), msg.get(), m_app_name.c_str(), false);
+ 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(m_messaging_app.get(), it->second.mm.get());
+ m_messaging_messages.erase(it);
+ }
+ }
+
+ void remove_all ()
+ {
+ // call remove() on all our keys
+
+ std::set<std::string> keys;
+ for (const auto& it : m_messaging_messages)
+ keys.insert (it.first);
+
+ for (const std::string &key : keys)
+ remove (key);
+ }
+
private:
const std::set<std::string>& server_caps() const
@@ -279,6 +370,22 @@ private:
static_cast<Impl*>(gself)->remove_closed_notification(GPOINTER_TO_INT(gkey));
}
+ static void on_message_activated (MessagingMenuMessage *,
+ const char *actionId,
+ GVariant *,
+ gpointer gself)
+ {
+ auto self = static_cast<Impl*>(gself);
+ auto it = self->m_messaging_messages.find(actionId);
+ g_return_if_fail (it != self->m_messaging_messages.end());
+ const auto& ndata = it->second;
+
+ if (ndata.callback)
+ ndata.callback();
+
+ self->m_messaging_messages.erase(it);
+ }
+
void remove_closed_notification (int key)
{
auto it = m_notifications.find(key);
@@ -286,16 +393,20 @@ 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);
@@ -305,6 +416,10 @@ private:
****
***/
+ // messaging menu
+ std::shared_ptr<MessagingMenuApp> m_messaging_app;
+ std::map<std::string, messaging_menu_data> m_messaging_messages;
+
const std::string m_app_name;
// key-to-data
@@ -315,6 +430,8 @@ private:
mutable std::set<std::string> m_lazy_caps;
static constexpr char const * HINT_TIMEOUT {"x-canonical-snap-decisions-timeout"};
+ static constexpr char const * DATETIME_INDICATOR_DESKTOP_FILE {"indicator-datetime.desktop"};
+ static constexpr char const * DATETIME_INDICATOR_SOURCE_ID {"indicator-datetime"};
};
/***
diff --git a/src/snap.cpp b/src/snap.cpp
index 259592e..5c530be 100644
--- a/src/snap.cpp
+++ b/src/snap.cpp
@@ -122,8 +122,9 @@ public:
const auto minutes = std::chrono::minutes(m_settings->alarm_duration.get());
ain::Builder b;
b.set_body (appointment.summary);
- b.set_icon_name (appointment.is_ubuntu_alarm() ? "alarm-clock" : "reminder");
+ b.set_icon_name (appointment.is_ubuntu_alarm() ? "alarm-clock" : "appointment");
b.add_hint (ain::Builder::HINT_NONSHAPED_ICON);
+ b.set_start_time (appointment.begin.to_unix());
const char * timefmt;
if (is_locale_12h()) {
@@ -152,6 +153,9 @@ public:
b.add_hint (ain::Builder::HINT_AFFIRMATIVE_HINT);
b.add_action ("ok", _("OK"));
b.add_action ("snooze", _("Snooze"));
+ } else {
+ b.add_hint (ain::Builder::HINT_INTERACTIVE);
+ b.add_action ("ok", _("OK"));
}
// add 'sound', 'haptic', and 'awake' objects to the capture so
@@ -161,10 +165,14 @@ public:
(const std::string& action){
if (action == "snooze")
snooze(appointment, alarm);
- else
+ else if (action == "ok")
ok(appointment, alarm);
});
+ b.set_missed_click_callback([appointment, alarm, ok](){
+ ok(appointment, alarm);
+ });
+
const auto key = m_engine->show(b);
if (key)
m_notifications.insert (key);
diff --git a/tests/test-notification.cpp b/tests/test-notification.cpp
index 4c11dca..b951cee 100644
--- a/tests/test-notification.cpp
+++ b/tests/test-notification.cpp
@@ -63,7 +63,7 @@ TEST_F(NotificationFixture,Notification)
bool expected_notify_called;
bool expected_vibrate_called;
} test_appts[] = {
- { appt, "reminder", "Event", true, true },
+ { appt, "appointment", "Event", true, true },
{ ualarm, "alarm-clock", "Alarm", true, true }
};
diff --git a/tests/test-sound.cpp b/tests/test-sound.cpp
index a222f39..59c1a28 100644
--- a/tests/test-sound.cpp
+++ b/tests/test-sound.cpp
@@ -85,7 +85,7 @@ TEST_F(NotificationFixture, InteractiveDuration)
// confirm that the icon passed to Notify was "alarm-clock"
g_variant_get_child (params, 2, "&s", &str);
- ASSERT_STREQ("reminder", str);
+ ASSERT_STREQ("appointment", str);
// confirm that the hints passed to Notify included a timeout matching duration_minutes
int32_t i32;