From 13978702ac61845927889986310085e8f90821da Mon Sep 17 00:00:00 2001 From: Renato Araujo Oliveira Filho Date: Mon, 18 Apr 2016 23:42:53 -0300 Subject: Post message on messaging menu if the notification get timeout. --- data/CMakeLists.txt | 7 +- data/ayatana-indicator-datetime.desktop.in | 1 + include/notifications/notifications.h | 11 ++- src/main.cpp | 5 +- src/notifications.cpp | 127 +++++++++++++++++++++++++++-- src/snap.cpp | 12 ++- tests/test-notification.cpp | 2 +- tests/test-sound.cpp | 2 +- 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); + /** Sets the time-out callback. This will be called exactly once. */ + void set_missed_click_callback (std::function); + + private: friend class Engine; class Impl; - std::unique_ptr impl; + std::shared_ptr 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 +#include +#include + + +#include + #include #include #include #include +#include + 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 m_string_hints; std::vector> m_actions; std::function m_closed_callback; + std::function m_missed_click_callback; }; Builder::Builder(): @@ -101,6 +111,18 @@ Builder::set_closed_callback (std::function cb) impl->m_closed_callback.swap (cb); } +void +Builder::set_missed_click_callback (std::function 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 nn; - std::function closed_callback; + Builder::Impl data; + }; + + struct messaging_menu_data + { + std::shared_ptr mm; + std::function 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 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 keys; + for (const auto& it : m_messaging_messages) + keys.insert (it.first); + + for (const std::string &key : keys) + remove (key); + } + private: const std::set& server_caps() const @@ -279,6 +370,22 @@ private: static_cast(gself)->remove_closed_notification(GPOINTER_TO_INT(gkey)); } + static void on_message_activated (MessagingMenuMessage *, + const char *actionId, + GVariant *, + gpointer gself) + { + auto self = static_cast(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(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 m_messaging_app; + std::map m_messaging_messages; + const std::string m_app_name; // key-to-data @@ -315,6 +430,8 @@ private: mutable std::set 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; -- cgit v1.2.3