From fcd77b806a8826d5f694f78c63943d0f768ef6ec Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sat, 26 Jul 2014 23:35:38 -0500 Subject: refactor the Notifications / sound / awake code --- src/snap.cpp | 627 +++++------------------------------------------------------ 1 file changed, 53 insertions(+), 574 deletions(-) (limited to 'src/snap.cpp') diff --git a/src/snap.cpp b/src/snap.cpp index c21a398..4da4b45 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -17,25 +17,20 @@ * Charles Kerr */ -#include -#include -#include #include -#include +#include +#include #include -#include #include -#include #include -#include -#include // std::call_once() -#include #include +namespace uin = unity::indicator::notifications; + namespace unity { namespace indicator { namespace datetime { @@ -44,175 +39,8 @@ namespace datetime { **** ***/ -namespace -{ - -static constexpr char const * APP_NAME = {"indicator-datetime-service"}; - -/** - * Plays a sound, possibly looping. - */ -class Sound -{ - typedef Sound Self; - -public: - - Sound(const std::shared_ptr& clock, - const std::string& uri, - unsigned int volume, - unsigned int duration_minutes, - bool loop): - m_clock(clock), - m_uri(uri), - m_volume(volume), - m_loop(loop), - m_loop_end_time(clock->localtime().add_full(0, 0, 0, 0, (int)duration_minutes, 0.0)) - { - // init GST once - static std::once_flag once; - std::call_once(once, [](){ - GError* error = nullptr; - gst_init_check (nullptr, nullptr, &error); - if (error) - { - g_critical("Unable to play alarm sound: %s", error->message); - g_error_free(error); - } - }); - - if (m_loop) - { - g_debug("Looping '%s' until cutoff time %s", - m_uri.c_str(), - m_loop_end_time.format("%F %T").c_str()); - } - else - { - g_debug("Playing '%s' once", m_uri.c_str()); - } - - m_play = gst_element_factory_make("playbin", "play"); - - auto bus = gst_pipeline_get_bus(GST_PIPELINE(m_play)); - m_watch_source = gst_bus_add_watch(bus, bus_callback, this); - gst_object_unref(bus); - - play(); - } - - ~Sound() - { - stop(); - - g_source_remove(m_watch_source); - - if (m_play != nullptr) - { - gst_element_set_state (m_play, GST_STATE_NULL); - g_clear_pointer (&m_play, gst_object_unref); - } - } - -private: - - void stop() - { - if (m_play != nullptr) - { - gst_element_set_state (m_play, GST_STATE_PAUSED); - } - } - - void play() - { - g_return_if_fail(m_play != nullptr); - - g_object_set(G_OBJECT (m_play), "uri", m_uri.c_str(), - "volume", get_volume(), - nullptr); - gst_element_set_state (m_play, GST_STATE_PLAYING); - } - - // convert settings range [1..100] to gst playbin's range is [0...1.0] - gdouble get_volume() const - { - constexpr int in_range_lo = 1; - constexpr int in_range_hi = 100; - const double in = CLAMP(m_volume, in_range_lo, in_range_hi); - const double pct = (in - in_range_lo) / (in_range_hi - in_range_lo); - - constexpr double out_range_lo = 0.0; - constexpr double out_range_hi = 1.0; - return out_range_lo + (pct * (out_range_hi - out_range_lo)); - } - - static gboolean bus_callback(GstBus*, GstMessage* msg, gpointer gself) - { - auto self = static_cast(gself); - - if ((GST_MESSAGE_TYPE(msg) == GST_MESSAGE_EOS) && - (self->m_loop) && - (self->m_clock->localtime() < self->m_loop_end_time)) - { - gst_element_seek(self->m_play, - 1.0, - GST_FORMAT_TIME, - GST_SEEK_FLAG_FLUSH, - GST_SEEK_TYPE_SET, - 0, - GST_SEEK_TYPE_NONE, - (gint64)GST_CLOCK_TIME_NONE); - } - - return G_SOURCE_CONTINUE; // keep listening - } - - /*** - **** - ***/ - - const std::shared_ptr m_clock; - const std::string m_uri; - const unsigned int m_volume; - const bool m_loop; - const DateTime m_loop_end_time; - guint m_watch_source = 0; - GstElement* m_play = nullptr; -}; - -class SoundBuilder -{ -public: - void set_clock(const std::shared_ptr& c) {m_clock = c;} - void set_uri(const std::string& uri) {m_uri = uri;} - void set_volume(const unsigned int v) {m_volume = v;} - void set_duration_minutes(unsigned int i) {m_duration_minutes=i;} - unsigned int duration_minutes() const {return m_duration_minutes;} - void set_looping(bool b) {m_looping=b;} - - Sound* operator()() { - return new Sound (m_clock, - m_uri, - m_volume, - m_duration_minutes, - m_looping); - } - -private: - std::shared_ptr m_clock; - std::string m_uri; - unsigned int m_volume = 50; - unsigned int m_duration_minutes = 30; - bool m_looping = true; -}; - -/** -*** libnotify -- snap decisions -**/ - -std::string get_alarm_uri(const Appointment& appointment, - const std::shared_ptr& settings) +static std::string get_alarm_uri(const Appointment& appointment, + const std::shared_ptr& settings) { const char* FALLBACK {"/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg"}; @@ -244,384 +72,21 @@ std::string get_alarm_uri(const Appointment& appointment, return uri; } -int32_t n_existing_snaps = 0; - -} // unnamed namespace - -/** - * A popup notification (with optional sound) - * that emits a Response signal when done. - */ -class Snap::Popup -{ -public: - - Popup(const Appointment& appointment, const SoundBuilder& sound_builder): - m_appointment(appointment), - m_interactive(get_interactive()), - m_sound_builder(sound_builder), - m_cancellable(g_cancellable_new()) - { - g_bus_get (G_BUS_TYPE_SYSTEM, m_cancellable, on_system_bus_ready, this); - - show(); - } - - ~Popup() - { - if (m_cancellable != nullptr) - { - g_cancellable_cancel (m_cancellable); - g_clear_object (&m_cancellable); - } - - if (m_system_bus != nullptr) - { - unforce_awake (); - unforce_screen (); - g_clear_object (&m_system_bus); - } - - if (m_nn != nullptr) - { - notify_notification_clear_actions(m_nn); - g_signal_handlers_disconnect_by_data(m_nn, this); - g_clear_object(&m_nn); - } - } - - typedef enum - { - RESPONSE_SHOW, - RESPONSE_DISMISS, - RESPONSE_CLOSE - } - Response; - - core::Signal& response() { return m_response; } - -private: - - void show() - { - const Appointment& appointment = m_appointment; - - /// strftime(3) format string for an alarm's snap decision - const auto timestr = appointment.begin.format(_("%a, %X")); - auto title = g_strdup_printf(_("Alarm %s"), timestr.c_str()); - const auto body = appointment.summary; - const gchar* icon_name = "alarm-clock"; - - m_nn = notify_notification_new(title, body.c_str(), icon_name); - if (m_interactive) - { - const auto duration = std::chrono::minutes(m_sound_builder.duration_minutes()); - - notify_notification_set_hint(m_nn, HINT_SNAP, - g_variant_new_boolean(true)); - notify_notification_set_hint(m_nn, HINT_TINT, - g_variant_new_boolean(true)); - notify_notification_set_hint(m_nn, HINT_TIMEOUT, - g_variant_new_int32(std::chrono::duration_cast(duration).count())); - - /// alarm popup dialog's button to show the active alarm - notify_notification_add_action(m_nn, "show", _("Show"), - on_snap_show, this, nullptr); - /// alarm popup dialog's button to shut up the alarm - notify_notification_add_action(m_nn, "dismiss", _("Dismiss"), - on_snap_dismiss, this, nullptr); - g_signal_connect(m_nn, "closed", G_CALLBACK(on_snap_closed), this); - } - - bool shown = true; - GError* error = nullptr; - notify_notification_show(m_nn, &error); - if (error != NULL) - { - g_critical("Unable to show snap decision for '%s': %s", - body.c_str(), error->message); - g_error_free(error); - shown = false; - } - - // Loop the sound *only* if we're prompting the user for a response. - // Otherwise, just play the sound once. - m_sound_builder.set_looping (shown && m_interactive); - m_sound.reset (m_sound_builder()); - - // if showing the notification didn't work, - // treat it as if the user clicked the 'show' button - if (!shown) - { - on_snap_show(nullptr, nullptr, this); - on_snap_dismiss(nullptr, nullptr, this); - } - - g_free(title); - } - - // user clicked 'show' - static void on_snap_show(NotifyNotification*, gchar*, gpointer gself) - { - auto self = static_cast(gself); - self->m_response_value = RESPONSE_SHOW; - self->m_sound.reset(); - } - - // user clicked 'dismiss' - static void on_snap_dismiss(NotifyNotification*, gchar*, gpointer gself) - { - auto self = static_cast(gself); - self->m_response_value = RESPONSE_DISMISS; - self->m_sound.reset(); - } - - // the popup was closed - static void on_snap_closed(NotifyNotification*, gpointer gself) - { - auto self = static_cast(gself); - self->m_sound.reset(); - self->m_response(self->m_response_value); - } - - /*** - **** Interactive - ***/ - - static std::set get_server_caps() - { - std::set caps_set; - auto caps_gl = notify_get_server_caps(); - std::string caps_str; - for(auto l=caps_gl; l!=nullptr; l=l->next) - { - caps_set.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 caps_set; - } - - static bool get_interactive() - { - static bool interactive; - - static std::once_flag once; - std::call_once(once, [](){ - interactive = get_server_caps().count("actions") != 0; - }); - - return interactive; - } - - /*** - **** - ***/ - - static void on_system_bus_ready (GObject *, GAsyncResult *res, gpointer gself) - { - GError * error; - GDBusConnection * system_bus; - - error = nullptr; - system_bus = g_bus_get_finish (res, &error); - if (error != nullptr) - { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning ("Unable to get bus: %s", error->message); - - g_error_free (error); - } - else if (system_bus != nullptr) - { - auto self = static_cast(gself); - - self->m_system_bus = G_DBUS_CONNECTION (g_object_ref (system_bus)); - - // ask powerd to keep the system awake - static constexpr int32_t POWERD_SYS_STATE_ACTIVE = 1; - g_dbus_connection_call (system_bus, - BUS_POWERD_NAME, - BUS_POWERD_PATH, - BUS_POWERD_INTERFACE, - "requestSysState", - g_variant_new("(si)", APP_NAME, POWERD_SYS_STATE_ACTIVE), - G_VARIANT_TYPE("(s)"), - G_DBUS_CALL_FLAGS_NONE, - -1, - self->m_cancellable, - on_force_awake_response, - self); - - // ask unity-system-compositor to turn on the screen - g_dbus_connection_call (system_bus, - BUS_SCREEN_NAME, - BUS_SCREEN_PATH, - BUS_SCREEN_INTERFACE, - "keepDisplayOn", - nullptr, - G_VARIANT_TYPE("(i)"), - G_DBUS_CALL_FLAGS_NONE, - -1, - self->m_cancellable, - on_force_screen_response, - self); - - g_object_unref (system_bus); - } - } - - static void on_force_awake_response (GObject * connection, - GAsyncResult * res, - gpointer gself) - { - GError * error; - GVariant * args; - - error = nullptr; - args = g_dbus_connection_call_finish (G_DBUS_CONNECTION(connection), res, &error); - if (error != nullptr) - { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning ("Unable to inhibit sleep: %s", error->message); - - g_error_free (error); - } - else - { - auto self = static_cast(gself); - - g_clear_pointer (&self->m_awake_cookie, g_free); - g_variant_get (args, "(s)", &self->m_awake_cookie); - g_debug ("m_awake_cookie is now '%s'", self->m_awake_cookie); - - g_variant_unref (args); - } - } - - static void on_force_screen_response (GObject * connection, - GAsyncResult * res, - gpointer gself) - { - GError * error; - GVariant * args; - - error = nullptr; - args = g_dbus_connection_call_finish (G_DBUS_CONNECTION(connection), res, &error); - if (error != nullptr) - { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning ("Unable to turn on the screen: %s", error->message); - - g_error_free (error); - } - else - { - auto self = static_cast(gself); - - self->m_screen_cookie = NO_SCREEN_COOKIE; - g_variant_get (args, "(i)", &self->m_screen_cookie); - g_debug ("m_screen_cookie is now '%d'", self->m_screen_cookie); - - g_variant_unref (args); - } - } - - void unforce_awake () - { - g_return_if_fail (G_IS_DBUS_CONNECTION(m_system_bus)); - - if (m_awake_cookie != nullptr) - { - g_dbus_connection_call (m_system_bus, - BUS_POWERD_NAME, - BUS_POWERD_PATH, - BUS_POWERD_INTERFACE, - "clearSysState", - g_variant_new("(s)", m_awake_cookie), - nullptr, - G_DBUS_CALL_FLAGS_NONE, - -1, - nullptr, - nullptr, - nullptr); - - g_clear_pointer (&m_awake_cookie, g_free); - } - } - - void unforce_screen () - { - g_return_if_fail (G_IS_DBUS_CONNECTION(m_system_bus)); - - if (m_screen_cookie != NO_SCREEN_COOKIE) - { - g_dbus_connection_call (m_system_bus, - BUS_SCREEN_NAME, - BUS_SCREEN_PATH, - BUS_SCREEN_INTERFACE, - "removeDisplayOnRequest", - g_variant_new("(i)", m_screen_cookie), - nullptr, - G_DBUS_CALL_FLAGS_NONE, - -1, - nullptr, - nullptr, - nullptr); - - m_screen_cookie = NO_SCREEN_COOKIE; - } - } - - /*** - **** - ***/ - - typedef Popup Self; - - const Appointment m_appointment; - const bool m_interactive; - SoundBuilder m_sound_builder; - std::unique_ptr m_sound; - core::Signal m_response; - Response m_response_value = RESPONSE_CLOSE; - NotifyNotification* m_nn = nullptr; - GCancellable * m_cancellable = nullptr; - GDBusConnection * m_system_bus = nullptr; - char * m_awake_cookie = nullptr; - int32_t m_screen_cookie = NO_SCREEN_COOKIE; - - static constexpr int32_t NO_SCREEN_COOKIE { std::numeric_limits::min() }; - - static constexpr char const * HINT_SNAP {"x-canonical-snap-decisions"}; - static constexpr char const * HINT_TINT {"x-canonical-private-button-tint"}; - static constexpr char const * HINT_TIMEOUT {"x-canonical-snap-decisions-timeout"}; -}; - /*** **** ***/ -Snap::Snap(const std::shared_ptr& clock, +Snap::Snap(const std::shared_ptr& engine, const std::shared_ptr& settings): - m_clock(clock), + m_engine(engine), m_settings(settings) { - if (!n_existing_snaps++ && !notify_init(APP_NAME)) - g_critical("libnotify initialization failed"); } Snap::~Snap() { - for (auto popup : m_pending) - delete popup; - - if (!--n_existing_snaps) - notify_uninit(); + for (const auto& key : m_notifications) + m_engine->close (key); } void Snap::operator()(const Appointment& appointment, @@ -634,38 +99,52 @@ void Snap::operator()(const Appointment& appointment, return; } - // create a popup... - SoundBuilder sound_builder; - sound_builder.set_uri(get_alarm_uri(appointment, m_settings)); - sound_builder.set_volume(m_settings->alarm_volume.get()); - sound_builder.set_clock(m_clock); - sound_builder.set_duration_minutes(m_settings->alarm_duration.get()); - auto popup = new Popup(appointment, sound_builder); - - m_pending.insert(popup); - - // listen for it to finish... - popup->response().connect([this, - appointment, - show, - dismiss, - popup](Popup::Response response){ - - m_pending.erase(popup); - - // we can't delete the Popup inside its response() signal handler - // because core::signal deadlocks, so push that to an idle func - g_idle_add([](gpointer gdata){ - delete static_cast(gdata); - return G_SOURCE_REMOVE; - }, popup); - - // maybe notify the client code that the popup's done - if (response == Popup::RESPONSE_SHOW) + // force the system to stay awake + auto awake = std::make_shared(m_engine->app_name()); + + // create the sound..., + const auto uri = get_alarm_uri(appointment, m_settings); + const auto volume = m_settings->alarm_volume.get(); + const bool loop = m_engine->supports_actions(); + auto sound = std::make_shared(uri, volume, loop); + + // show a notification... + const auto minutes = m_settings->alarm_duration.get(); + const bool interactive = m_engine->supports_actions(); + uin::Builder notification_builder; + notification_builder.set_body (appointment.summary); + notification_builder.set_icon_name ("alarm-clock"); + notification_builder.add_hint (uin::Builder::HINT_SNAP); + notification_builder.add_hint (uin::Builder::HINT_TINT); + const auto timestr = appointment.begin.format (_("%a, %X")); + auto title = g_strdup_printf (_("Alarm %s"), timestr.c_str()); + notification_builder.set_title (title); + g_free (title); + notification_builder.set_timeout (std::chrono::duration_cast(std::chrono::minutes(minutes))); + if (interactive) { + notification_builder.add_action ("show", _("Show")); + notification_builder.add_action ("dismiss", _("Dismiss")); + } + + // add the 'sound' and 'awake' objects to the capture so that + // they stay alive until the closed callback is called; i.e., + // for the lifespan of the notficiation + notification_builder.set_closed_callback([appointment, + show, + dismiss, + sound, + awake](const std::string& action){ + if (action == "show") show(appointment); - else if (response == Popup::RESPONSE_DISMISS) + else dismiss(appointment); }); + + const auto key = m_engine->show (notification_builder); + if (key) + m_notifications.insert (key); + else + show(appointment); } /*** -- cgit v1.2.3 From a1e8addae51d3f8a026630b47b0c42c11cd52507 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 27 Jul 2014 00:08:29 -0500 Subject: copyediting --- src/notifications.cpp | 2 +- src/snap.cpp | 29 +++++++++++++---------------- 2 files changed, 14 insertions(+), 17 deletions(-) (limited to 'src/snap.cpp') diff --git a/src/notifications.cpp b/src/notifications.cpp index 41ced42..486a910 100644 --- a/src/notifications.cpp +++ b/src/notifications.cpp @@ -160,7 +160,7 @@ public: // close() removes the item from m_notifications, // so increment the iterator before it gets invalidated - for (auto it=m_notifications.begin(), end=m_notifications.end(); it!=end; ) + for (auto it=m_notifications.begin(), e=m_notifications.end(); it!=e; ) { const int key = it->first; ++it; diff --git a/src/snap.cpp b/src/snap.cpp index 4da4b45..d99e5ef 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -109,38 +109,35 @@ void Snap::operator()(const Appointment& appointment, auto sound = std::make_shared(uri, volume, loop); // show a notification... - const auto minutes = m_settings->alarm_duration.get(); + const auto minutes = std::chrono::minutes(m_settings->alarm_duration.get()); const bool interactive = m_engine->supports_actions(); - uin::Builder notification_builder; - notification_builder.set_body (appointment.summary); - notification_builder.set_icon_name ("alarm-clock"); - notification_builder.add_hint (uin::Builder::HINT_SNAP); - notification_builder.add_hint (uin::Builder::HINT_TINT); + uin::Builder b; + b.set_body (appointment.summary); + b.set_icon_name ("alarm-clock"); + b.add_hint (uin::Builder::HINT_SNAP); + b.add_hint (uin::Builder::HINT_TINT); const auto timestr = appointment.begin.format (_("%a, %X")); auto title = g_strdup_printf (_("Alarm %s"), timestr.c_str()); - notification_builder.set_title (title); + b.set_title (title); g_free (title); - notification_builder.set_timeout (std::chrono::duration_cast(std::chrono::minutes(minutes))); + b.set_timeout (std::chrono::duration_cast(minutes)); if (interactive) { - notification_builder.add_action ("show", _("Show")); - notification_builder.add_action ("dismiss", _("Dismiss")); + b.add_action ("show", _("Show")); + b.add_action ("dismiss", _("Dismiss")); } // add the 'sound' and 'awake' objects to the capture so that // they stay alive until the closed callback is called; i.e., // for the lifespan of the notficiation - notification_builder.set_closed_callback([appointment, - show, - dismiss, - sound, - awake](const std::string& action){ + b.set_closed_callback([appointment, show, dismiss, sound, awake] + (const std::string& action){ if (action == "show") show(appointment); else dismiss(appointment); }); - const auto key = m_engine->show (notification_builder); + const auto key = m_engine->show(b); if (key) m_notifications.insert (key); else -- cgit v1.2.3 From b0936139bfef6fe169b5c17be4b2dafa3c2e2c3a Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 27 Jul 2014 11:01:30 -0500 Subject: copyediting: comments, use anonymous namespace --- include/notifications/notifications.h | 5 +++-- src/awake.cpp | 14 ++++++++++---- src/notifications.cpp | 27 +++++++++++++++++---------- src/snap.cpp | 11 ++++++++--- src/sound.cpp | 25 ++++--------------------- 5 files changed, 42 insertions(+), 40 deletions(-) (limited to 'src/snap.cpp') diff --git a/include/notifications/notifications.h b/include/notifications/notifications.h index 43442d3..c2e2d85 100644 --- a/include/notifications/notifications.h +++ b/include/notifications/notifications.h @@ -34,8 +34,7 @@ class Engine; /** * Helper class for showing notifications. * - * Populate the builder, with the relevant properites, - * then pass it to Engine::show(). + * Populate the builder, then pass it to Engine::show(). * * @see Engine::show(Builder) */ @@ -46,7 +45,9 @@ public: ~Builder(); void set_title (const std::string& title); + void set_body (const std::string& body); + void set_icon_name (const std::string& icon_name); /* Set an interval, after which the notification will automatically diff --git a/src/awake.cpp b/src/awake.cpp index 19826ae..57358ab 100644 --- a/src/awake.cpp +++ b/src/awake.cpp @@ -58,7 +58,9 @@ public: private: - static void on_system_bus_ready (GObject *, GAsyncResult *res, gpointer gself) + static void on_system_bus_ready (GObject *, + GAsyncResult *res, + gpointer gself) { GError * error; GDBusConnection * system_bus; @@ -119,7 +121,9 @@ private: GVariant * args; error = nullptr; - args = g_dbus_connection_call_finish (G_DBUS_CONNECTION(connection), res, &error); + args = g_dbus_connection_call_finish (G_DBUS_CONNECTION(connection), + res, + &error); if (error != nullptr) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && @@ -150,7 +154,9 @@ private: GVariant * args; error = nullptr; - args = g_dbus_connection_call_finish (G_DBUS_CONNECTION(connection), res, &error); + args = g_dbus_connection_call_finish (G_DBUS_CONNECTION(connection), + res, + &error); if (error != nullptr) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && @@ -233,7 +239,7 @@ private: ***/ Awake::Awake(const std::string& app_name): - impl(new Impl (app_name)) + impl(new Impl (app_name)) { } diff --git a/src/notifications.cpp b/src/notifications.cpp index 6d993df..bad4b1a 100644 --- a/src/notifications.cpp +++ b/src/notifications.cpp @@ -168,10 +168,11 @@ public: void close (int key) { - // if we've got this one... auto it = m_notifications.find(key); if (it != m_notifications.end()) { + // tell the server to close, call the close() callback, + // and immediately forget about the nn. GError * error = nullptr; if (!notify_notification_close (it->second.nn.get(), &error)) { @@ -187,9 +188,9 @@ public: int ret = -1; const auto& info = *builder.impl; - auto * nn = notify_notification_new (info.m_title.c_str(), - info.m_body.c_str(), - info.m_icon_name.c_str()); + auto nn = notify_notification_new (info.m_title.c_str(), + info.m_body.c_str(), + info.m_icon_name.c_str()); if (info.m_duration.count() != 0) { @@ -243,7 +244,9 @@ public: } else { - g_critical ("Unable to show notification for '%s': %s", info.m_title.c_str(), error->message); + g_critical ("Unable to show notification for '%s': %s", + info.m_title.c_str(), + error->message); g_error_free (error); g_object_unref (nn); } @@ -266,8 +269,8 @@ private: static void on_notification_closed (NotifyNotification * nn, gpointer gself) { const GQuark q = notification_key_quark(); - const int key = GPOINTER_TO_INT(g_object_get_qdata(G_OBJECT(nn), q)); - static_cast(gself)->on_closed (key); + const gpointer gkey = g_object_get_qdata(G_OBJECT(nn), q); + static_cast(gself)->on_closed(GPOINTER_TO_INT(gkey)); } void on_closed (int key) @@ -281,8 +284,8 @@ private: { std::string action; - const auto q = notification_action_quark(); - const auto p = g_object_get_qdata (G_OBJECT(nn), q); + const GQuark q = notification_action_quark(); + const gpointer p = g_object_get_qdata(G_OBJECT(nn), q); if (p != nullptr) action = static_cast(p); @@ -298,7 +301,11 @@ private: ***/ const std::string m_app_name; + + // key-to-data std::map m_notifications; + + // server capabilities std::set m_caps; static constexpr char const * HINT_TIMEOUT {"x-canonical-snap-decisions-timeout"}; @@ -320,7 +327,7 @@ Engine::~Engine() bool Engine::supports_actions() const { - return impl->supports_actions(); + return true;//impl->supports_actions(); } int diff --git a/src/snap.cpp b/src/snap.cpp index d99e5ef..e9df256 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -39,8 +39,11 @@ namespace datetime { **** ***/ -static std::string get_alarm_uri(const Appointment& appointment, - const std::shared_ptr& settings) +namespace // unnamed namespace +{ + +std::string get_alarm_uri(const Appointment& appointment, + const std::shared_ptr& settings) { const char* FALLBACK {"/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg"}; @@ -72,6 +75,8 @@ static std::string get_alarm_uri(const Appointment& appointment, return uri; } +} // unnamed namespace + /*** **** ***/ @@ -102,7 +107,7 @@ void Snap::operator()(const Appointment& appointment, // force the system to stay awake auto awake = std::make_shared(m_engine->app_name()); - // create the sound..., + // create the sound... const auto uri = get_alarm_uri(appointment, m_settings); const auto volume = m_settings->alarm_volume.get(); const bool loop = m_engine->supports_actions(); diff --git a/src/sound.cpp b/src/sound.cpp index 052b168..7658658 100644 --- a/src/sound.cpp +++ b/src/sound.cpp @@ -63,13 +63,14 @@ public: gst_object_unref(bus); g_debug("Playing '%s'", m_uri.c_str()); - play(); + g_object_set(G_OBJECT (m_play), "uri", m_uri.c_str(), + "volume", get_volume(), + nullptr); + gst_element_set_state (m_play, GST_STATE_PLAYING); } ~Impl() { - stop(); - g_source_remove(m_watch_source); if (m_play != nullptr) @@ -81,24 +82,6 @@ public: private: - void stop() - { - if (m_play != nullptr) - { - gst_element_set_state (m_play, GST_STATE_PAUSED); - } - } - - void play() - { - g_return_if_fail(m_play != nullptr); - - g_object_set(G_OBJECT (m_play), "uri", m_uri.c_str(), - "volume", get_volume(), - nullptr); - gst_element_set_state (m_play, GST_STATE_PLAYING); - } - // convert settings range [1..100] to gst playbin's range is [0...1.0] gdouble get_volume() const { -- cgit v1.2.3 From d6b290fda978379fb07285aaddfeb31686735667 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 30 Jul 2014 14:54:37 -0500 Subject: move Snap's guts into an Impl class --- include/datetime/snap.h | 6 +- src/snap.cpp | 185 +++++++++++++++++++++++++++--------------------- 2 files changed, 107 insertions(+), 84 deletions(-) (limited to 'src/snap.cpp') diff --git a/include/datetime/snap.h b/include/datetime/snap.h index 78d9f65..ef5c868 100644 --- a/include/datetime/snap.h +++ b/include/datetime/snap.h @@ -27,7 +27,6 @@ #include #include -#include namespace unity { namespace indicator { @@ -49,9 +48,8 @@ public: appointment_func dismiss); private: - const std::shared_ptr m_engine; - const std::shared_ptr m_settings; - std::set m_notifications; + class Impl; + std::unique_ptr impl; }; } // namespace datetime diff --git a/src/snap.cpp b/src/snap.cpp index e9df256..0eb176c 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -27,6 +27,7 @@ #include #include +#include #include namespace uin = unity::indicator::notifications; @@ -39,114 +40,138 @@ namespace datetime { **** ***/ -namespace // unnamed namespace +class Snap::Impl { +public: -std::string get_alarm_uri(const Appointment& appointment, - const std::shared_ptr& settings) -{ - const char* FALLBACK {"/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg"}; - - const std::string candidates[] = { appointment.audio_url, - settings->alarm_sound.get(), - FALLBACK }; + Impl(const std::shared_ptr& engine, + const std::shared_ptr& settings): + m_engine(engine), + m_settings(settings) + { + } - std::string uri; + ~Impl() + { + for (const auto& key : m_notifications) + m_engine->close (key); + } - for(const auto& candidate : candidates) + void operator()(const Appointment& appointment, + appointment_func show, + appointment_func dismiss) { - if (gst_uri_is_valid (candidate.c_str())) + if (!appointment.has_alarms) { - uri = candidate; - break; + dismiss(appointment); + return; + } + + // force the system to stay awake + auto awake = std::make_shared(m_engine->app_name()); + + // create the sound... + const auto uri = get_alarm_uri(appointment, m_settings); + const auto volume = m_settings->alarm_volume.get(); + const bool loop = m_engine->supports_actions(); + auto sound = std::make_shared(uri, volume, loop); + + // show a notification... + const auto minutes = std::chrono::minutes(m_settings->alarm_duration.get()); + const bool interactive = m_engine->supports_actions(); + uin::Builder b; + b.set_body (appointment.summary); + b.set_icon_name ("alarm-clock"); + b.add_hint (uin::Builder::HINT_SNAP); + b.add_hint (uin::Builder::HINT_TINT); + const auto timestr = appointment.begin.format (_("%a, %X")); + auto title = g_strdup_printf (_("Alarm %s"), timestr.c_str()); + b.set_title (title); + g_free (title); + b.set_timeout (std::chrono::duration_cast(minutes)); + if (interactive) { + b.add_action ("show", _("Show")); + b.add_action ("dismiss", _("Dismiss")); } - else if (g_file_test(candidate.c_str(), G_FILE_TEST_EXISTS)) + + // add the 'sound' and 'awake' objects to the capture so that + // they stay alive until the closed callback is called; i.e., + // for the lifespan of the notficiation + b.set_closed_callback([appointment, show, dismiss, sound, awake] + (const std::string& action){ + if (action == "show") + show(appointment); + else + dismiss(appointment); + }); + + const auto key = m_engine->show(b); + if (key) + m_notifications.insert (key); + else + show(appointment); + } + +private: + + std::string get_alarm_uri(const Appointment& appointment, + const std::shared_ptr& settings) const + { + const char* FALLBACK {"/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg"}; + + const std::string candidates[] = { appointment.audio_url, + settings->alarm_sound.get(), + FALLBACK }; + + std::string uri; + + for(const auto& candidate : candidates) { - gchar* tmp = gst_filename_to_uri(candidate.c_str(), nullptr); - if (tmp != nullptr) + if (gst_uri_is_valid (candidate.c_str())) { - uri = tmp; - g_free (tmp); + uri = candidate; break; } + else if (g_file_test(candidate.c_str(), G_FILE_TEST_EXISTS)) + { + gchar* tmp = gst_filename_to_uri(candidate.c_str(), nullptr); + if (tmp != nullptr) + { + uri = tmp; + g_free (tmp); + break; + } + } } - } - return uri; -} + return uri; + } -} // unnamed namespace + const std::shared_ptr m_engine; + const std::shared_ptr m_settings; + std::set m_notifications; +}; /*** **** ***/ -Snap::Snap(const std::shared_ptr& engine, +Snap::Snap(const std::shared_ptr& engine, const std::shared_ptr& settings): - m_engine(engine), - m_settings(settings) + impl(new Impl(engine, settings)) { } Snap::~Snap() { - for (const auto& key : m_notifications) - m_engine->close (key); } -void Snap::operator()(const Appointment& appointment, - appointment_func show, - appointment_func dismiss) +void +Snap::operator()(const Appointment& appointment, + appointment_func show, + appointment_func dismiss) { - if (!appointment.has_alarms) - { - dismiss(appointment); - return; - } - - // force the system to stay awake - auto awake = std::make_shared(m_engine->app_name()); - - // create the sound... - const auto uri = get_alarm_uri(appointment, m_settings); - const auto volume = m_settings->alarm_volume.get(); - const bool loop = m_engine->supports_actions(); - auto sound = std::make_shared(uri, volume, loop); - - // show a notification... - const auto minutes = std::chrono::minutes(m_settings->alarm_duration.get()); - const bool interactive = m_engine->supports_actions(); - uin::Builder b; - b.set_body (appointment.summary); - b.set_icon_name ("alarm-clock"); - b.add_hint (uin::Builder::HINT_SNAP); - b.add_hint (uin::Builder::HINT_TINT); - const auto timestr = appointment.begin.format (_("%a, %X")); - auto title = g_strdup_printf (_("Alarm %s"), timestr.c_str()); - b.set_title (title); - g_free (title); - b.set_timeout (std::chrono::duration_cast(minutes)); - if (interactive) { - b.add_action ("show", _("Show")); - b.add_action ("dismiss", _("Dismiss")); - } - - // add the 'sound' and 'awake' objects to the capture so that - // they stay alive until the closed callback is called; i.e., - // for the lifespan of the notficiation - b.set_closed_callback([appointment, show, dismiss, sound, awake] - (const std::string& action){ - if (action == "show") - show(appointment); - else - dismiss(appointment); - }); - - const auto key = m_engine->show(b); - if (key) - m_notifications.insert (key); - else - show(appointment); + (*impl)(appointment, show, dismiss); } /*** -- cgit v1.2.3 From 559d185dd7d51e56fbd8246970ef520d3edd18ae Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 30 Jul 2014 16:01:03 -0500 Subject: initial draft of haptic feedback when alarms play --- CMakeLists.txt | 3 +- debian/control | 1 + include/notifications/haptic.h | 57 ++++++++++++++++++++++++++ src/CMakeLists.txt | 1 + src/haptic.cpp | 92 ++++++++++++++++++++++++++++++++++++++++++ src/notifications.cpp | 2 +- src/snap.cpp | 8 +++- 7 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 include/notifications/haptic.h create mode 100644 src/haptic.cpp (limited to 'src/snap.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b4987e..3bc7e81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,8 @@ pkg_check_modules (SERVICE_DEPS REQUIRED gstreamer-1.0>=1.2 libnotify>=0.7.6 url-dispatcher-1>=1 - properties-cpp>=0.0.1) + properties-cpp>=0.0.1 + ubuntu-platform-api>=2.2.0) include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS}) CHECK_INCLUDE_FILE(ubuntu/hardware/alarm.h HAVE_UBUNTU_HW_ALARM_H) diff --git a/debian/control b/debian/control index 77e9241..dab9096 100644 --- a/debian/control +++ b/debian/control @@ -21,6 +21,7 @@ Build-Depends: cmake, libproperties-cpp-dev, libubuntu-platform-hardware-api-headers [armhf i386 amd64], libubuntu-platform-hardware-api-dev [armhf i386 amd64], + libubuntu-application-api-dev [armhf i386 amd64], libdbustest1-dev, locales, Standards-Version: 3.9.3 diff --git a/include/notifications/haptic.h b/include/notifications/haptic.h new file mode 100644 index 0000000..3036485 --- /dev/null +++ b/include/notifications/haptic.h @@ -0,0 +1,57 @@ +/* + * 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 . + * + * Authors: + * Charles Kerr + */ + +#ifndef UNITY_INDICATOR_NOTIFICATIONS_HAPTIC_H +#define UNITY_INDICATOR_NOTIFICATIONS_HAPTIC_H + +#include + +namespace unity { +namespace indicator { +namespace notifications { + +/*** +**** +***/ + +/** + * A class that forces the screen display on and inhibits sleep + */ +class Haptic +{ +public: + enum Mode { VIBRATE_DEFAULT }; + + Haptic(const Mode& mode = VIBRATE_DEFAULT); + ~Haptic(); + +private: + class Impl; + std::unique_ptr impl; +}; + +/*** +**** +***/ + +} // namespace notifications +} // namespace indicator +} // namespace unity + +#endif // UNITY_INDICATOR_NOTIFICATIONS_HAPTIC_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 754d537..a466a48 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,6 +20,7 @@ set (SERVICE_CXX_SOURCES exporter.cpp formatter.cpp formatter-desktop.cpp + haptic.cpp locations.cpp locations-settings.cpp menu.cpp diff --git a/src/haptic.cpp b/src/haptic.cpp new file mode 100644 index 0000000..c5a20df --- /dev/null +++ b/src/haptic.cpp @@ -0,0 +1,92 @@ +/* + * 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 . + * + * Authors: + * Charles Kerr + */ + +#include + +#include + +#include + +namespace unity { +namespace indicator { +namespace notifications { + +/*** +**** +***/ + +class Haptic::Impl +{ +public: + + Impl(const Mode& mode): + m_mode(mode), + m_sensor(ua_sensors_haptic_new()) + { + if (m_sensor == nullptr) + g_warning ("Haptic device unavailable"); + else + m_tag = g_timeout_add_seconds (1, on_timeout, this); + } + + ~Impl() + { + if (m_tag) + g_source_remove(m_tag); + } + +private: + + static gboolean on_timeout (gpointer gself) + { + static_cast(gself)->vibrate_now(); + return G_SOURCE_CONTINUE; + } + + void vibrate_now() + { + const uint32_t msec = 1500; + ua_sensors_haptic_vibrate_once (m_sensor, msec); + } + + const Mode m_mode; + UASensorsHaptic * m_sensor = nullptr; + guint m_tag = 0; +}; + +/*** +**** +***/ + +Haptic::Haptic(const Mode& mode): + impl(new Impl (mode)) +{ +} + +Haptic::~Haptic() +{ +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/notifications.cpp b/src/notifications.cpp index da7351b..c66f634 100644 --- a/src/notifications.cpp +++ b/src/notifications.cpp @@ -327,7 +327,7 @@ Engine::~Engine() bool Engine::supports_actions() const { - return impl->supports_actions(); + return true; //impl->supports_actions(); } int diff --git a/src/snap.cpp b/src/snap.cpp index 0eb176c..f3e0f20 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -76,6 +77,9 @@ public: const bool loop = m_engine->supports_actions(); auto sound = std::make_shared(uri, volume, loop); + // create the haptic feedback... + auto haptic = std::make_shared(); + // show a notification... const auto minutes = std::chrono::minutes(m_settings->alarm_duration.get()); const bool interactive = m_engine->supports_actions(); @@ -94,10 +98,10 @@ public: b.add_action ("dismiss", _("Dismiss")); } - // add the 'sound' and 'awake' objects to the capture so that + // add 'sound', 'haptic', and 'awake' objects to the capture so // they stay alive until the closed callback is called; i.e., // for the lifespan of the notficiation - b.set_closed_callback([appointment, show, dismiss, sound, awake] + b.set_closed_callback([appointment, show, dismiss, sound, awake, haptic] (const std::string& action){ if (action == "show") show(appointment); -- cgit v1.2.3 From 8afa4ba3102138f245fba704eba03b907edefa2b Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Thu, 31 Jul 2014 17:06:50 -0500 Subject: configurable haptic mode, part 2 of 3: use the new haptic mode setting when popping up notifications; sync notification tests --- src/snap.cpp | 5 ++++- tests/test-snap.cpp | 49 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 8 deletions(-) (limited to 'src/snap.cpp') diff --git a/src/snap.cpp b/src/snap.cpp index beefa94..0b2322a 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -78,7 +78,10 @@ public: auto sound = std::make_shared(uri, volume, loop); // create the haptic feedback... - auto haptic = std::make_shared(); + const auto haptic_mode = m_settings->alarm_haptic.get(); + std::shared_ptr haptic; + if (haptic_mode == "pulse") + haptic = std::make_shared(uin::Haptic::MODE_PULSE); // show a notification... const auto minutes = std::chrono::minutes(m_settings->alarm_duration.get()); diff --git a/tests/test-snap.cpp b/tests/test-snap.cpp index 06e0a80..5afaab1 100644 --- a/tests/test-snap.cpp +++ b/tests/test-snap.cpp @@ -416,13 +416,6 @@ TEST_F(SnapFixture, InhibitSleep) nullptr, &error)); - // confirm that haptic feedback got called - EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (haptic_mock, - haptic_obj, - HAPTIC_METHOD_VIBRATE_PATTERN, - nullptr, - &error)); - // force-close the snap wait_msec(100); delete snap; @@ -485,3 +478,45 @@ TEST_F(SnapFixture, ForceScreen) &error)); g_assert_no_error(error); } + +/*** +**** +***/ + +TEST_F(SnapFixture, HapticModes) +{ + auto settings = std::make_shared(); + auto ne = std::make_shared(APP_NAME); + auto func = [this](const Appointment&){g_idle_add(quit_idle, loop);}; + GError * error = nullptr; + + // invoke a snap decision while haptic feedback is set to "pulse", + // confirm that VibratePattern got called + settings->alarm_haptic.set("pulse"); + auto snap = new Snap (ne, settings); + (*snap)(appt, func, func); + wait_msec(100); + EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (haptic_mock, + haptic_obj, + HAPTIC_METHOD_VIBRATE_PATTERN, + nullptr, + &error)); + delete snap; + + // invoke a snap decision while haptic feedback is set to "none", + // confirm that VibratePattern =didn't= get called + wait_msec(100); + dbus_test_dbus_mock_object_clear_method_calls (haptic_mock, haptic_obj, &error); + settings->alarm_haptic.set("none"); + snap = new Snap (ne, settings); + (*snap)(appt, func, func); + wait_msec(100); + EXPECT_FALSE (dbus_test_dbus_mock_object_check_method_call (haptic_mock, + haptic_obj, + HAPTIC_METHOD_VIBRATE_PATTERN, + nullptr, + &error)); + delete snap; + + g_assert_no_error (error); +} -- cgit v1.2.3