aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/com.canonical.indicator.datetime.AlarmProperties.xml9
-rw-r--r--data/com.canonical.indicator.datetime.gschema.xml.in8
-rw-r--r--include/CMakeLists.txt1
-rw-r--r--include/datetime/dbus-shared.h14
-rw-r--r--include/datetime/settings-live.h1
-rw-r--r--include/datetime/settings-shared.h1
-rw-r--r--include/datetime/settings.h1
-rw-r--r--include/datetime/snap.h14
-rw-r--r--include/notifications/CMakeLists.txt0
-rw-r--r--include/notifications/awake.h55
-rw-r--r--include/notifications/dbus-shared.h36
-rw-r--r--include/notifications/haptic.h60
-rw-r--r--include/notifications/notifications.h116
-rw-r--r--include/notifications/sound.h60
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/awake.cpp256
-rw-r--r--src/exporter.cpp1
-rw-r--r--src/haptic.cpp170
-rw-r--r--src/main.cpp9
-rw-r--r--src/notifications.cpp370
-rw-r--r--src/settings-live.cpp14
-rw-r--r--src/snap.cpp693
-rw-r--r--src/sound.cpp143
-rw-r--r--tests/manual2
-rw-r--r--tests/manual-test-snap.cpp11
-rw-r--r--tests/test-exporter.cpp12
-rw-r--r--tests/test-settings.cpp1
-rw-r--r--tests/test-snap.cpp142
28 files changed, 1558 insertions, 646 deletions
diff --git a/data/com.canonical.indicator.datetime.AlarmProperties.xml b/data/com.canonical.indicator.datetime.AlarmProperties.xml
index d25fa82..9b38af9 100644
--- a/data/com.canonical.indicator.datetime.AlarmProperties.xml
+++ b/data/com.canonical.indicator.datetime.AlarmProperties.xml
@@ -11,6 +11,15 @@
</doc:doc>
</property>
+ <property name="HapticFeedback" type="s" access="readwrite">
+ <doc:doc>
+ <doc:description>
+ <doc:para>What kind of haptic feedback, if any, to trigger with an alarm.</doc:para>
+ <doc:para>Two modes are currently supported: 'pulse', 'none'.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </property>
+
<property name="DefaultVolume" type="i" access="readwrite">
<doc:doc>
<doc:description>
diff --git a/data/com.canonical.indicator.datetime.gschema.xml.in b/data/com.canonical.indicator.datetime.gschema.xml.in
index 62b42c1..4e4acd8 100644
--- a/data/com.canonical.indicator.datetime.gschema.xml.in
+++ b/data/com.canonical.indicator.datetime.gschema.xml.in
@@ -123,6 +123,14 @@
Some timezones can be known by many different cities or names. This setting describes how the current zone prefers to be named. Format is "TIMEZONE NAME" (e.g. "America/New_York Boston" to name the New_York zone Boston).
</_description>
</key>
+ <key name="alarm-haptic-feedback" type="s">
+ <default>'pulse'</default>
+ <_summary>What kind of haptic feedback, if any, to trigger with an alarm.</_summary>
+ <_description>
+ What kind of haptic feedback, if any, to trigger with an alarm.
+ Two modes are currently supported: 'pulse', 'none'.
+ </_description>
+ </key>
<key name="alarm-default-sound" type="s">
<default>'/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg'</default>
<_summary>The alarm's default sound file.</_summary>
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
index 486e9c7..15a7c33 100644
--- a/include/CMakeLists.txt
+++ b/include/CMakeLists.txt
@@ -1 +1,2 @@
add_subdirectory(datetime)
+add_subdirectory(notifications)
diff --git a/include/datetime/dbus-shared.h b/include/datetime/dbus-shared.h
index 4b71ce5..db10c1d 100644
--- a/include/datetime/dbus-shared.h
+++ b/include/datetime/dbus-shared.h
@@ -18,18 +18,10 @@
* Charles Kerr <charles.kerr@canonical.com>
*/
-#ifndef _DBUS_SHARED_H_
-#define _DBUS_SHARED_H_
+#ifndef _INDICATOR_DATETIME_DBUS_SHARED_H_
+#define _INDICATOR_DATETIME_DBUS_SHARED_H_
#define BUS_DATETIME_NAME "com.canonical.indicator.datetime"
#define BUS_DATETIME_PATH "/com/canonical/indicator/datetime"
-#define BUS_SCREEN_NAME "com.canonical.Unity.Screen"
-#define BUS_SCREEN_PATH "/com/canonical/Unity/Screen"
-#define BUS_SCREEN_INTERFACE "com.canonical.Unity.Screen"
-
-#define BUS_POWERD_NAME "com.canonical.powerd"
-#define BUS_POWERD_PATH "/com/canonical/powerd"
-#define BUS_POWERD_INTERFACE "com.canonical.powerd"
-
-#endif /* _DBUS_SHARED_H_ */
+#endif /* _INDICATOR_DATETIME_DBUS_SHARED_H_ */
diff --git a/include/datetime/settings-live.h b/include/datetime/settings-live.h
index 4db2d40..4850e69 100644
--- a/include/datetime/settings-live.h
+++ b/include/datetime/settings-live.h
@@ -58,6 +58,7 @@ private:
void update_alarm_sound();
void update_alarm_volume();
void update_alarm_duration();
+ void update_alarm_haptic();
GSettings* m_settings;
diff --git a/include/datetime/settings-shared.h b/include/datetime/settings-shared.h
index 23d2e1c..a211821 100644
--- a/include/datetime/settings-shared.h
+++ b/include/datetime/settings-shared.h
@@ -48,5 +48,6 @@ TimeFormatMode;
#define SETTINGS_ALARM_SOUND_S "alarm-default-sound"
#define SETTINGS_ALARM_VOLUME_S "alarm-default-volume"
#define SETTINGS_ALARM_DURATION_S "alarm-duration-minutes"
+#define SETTINGS_ALARM_HAPTIC_S "alarm-haptic-feedback"
#endif // INDICATOR_DATETIME_SETTINGS_SHARED
diff --git a/include/datetime/settings.h b/include/datetime/settings.h
index e5f885e..c6fe13b 100644
--- a/include/datetime/settings.h
+++ b/include/datetime/settings.h
@@ -57,6 +57,7 @@ public:
core::Property<TimeFormatMode> time_format_mode;
core::Property<std::string> timezone_name;
core::Property<std::string> alarm_sound;
+ core::Property<std::string> alarm_haptic;
core::Property<unsigned int> alarm_volume;
core::Property<unsigned int> alarm_duration;
};
diff --git a/include/datetime/snap.h b/include/datetime/snap.h
index 1c90496..ef5c868 100644
--- a/include/datetime/snap.h
+++ b/include/datetime/snap.h
@@ -21,12 +21,12 @@
#define INDICATOR_DATETIME_SNAP_H
#include <datetime/appointment.h>
-#include <datetime/clock.h>
#include <datetime/settings.h>
+#include <notifications/notifications.h>
+
#include <functional>
#include <memory>
-#include <set>
namespace unity {
namespace indicator {
@@ -38,7 +38,7 @@ namespace datetime {
class Snap
{
public:
- Snap(const std::shared_ptr<Clock>& clock,
+ Snap(const std::shared_ptr<unity::indicator::notifications::Engine>& engine,
const std::shared_ptr<const Settings>& settings);
virtual ~Snap();
@@ -48,12 +48,8 @@ public:
appointment_func dismiss);
private:
- const std::shared_ptr<Clock> m_clock;
- const std::shared_ptr<const Settings> m_settings;
-
- class Popup;
- friend class Popup;
- std::set<Popup*> m_pending;
+ class Impl;
+ std::unique_ptr<Impl> impl;
};
} // namespace datetime
diff --git a/include/notifications/CMakeLists.txt b/include/notifications/CMakeLists.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/include/notifications/CMakeLists.txt
diff --git a/include/notifications/awake.h b/include/notifications/awake.h
new file mode 100644
index 0000000..fd812c1
--- /dev/null
+++ b/include/notifications/awake.h
@@ -0,0 +1,55 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef UNITY_INDICATOR_NOTIFICATIONS_AWAKE_H
+#define UNITY_INDICATOR_NOTIFICATIONS_AWAKE_H
+
+#include <memory>
+
+namespace unity {
+namespace indicator {
+namespace notifications {
+
+/***
+****
+***/
+
+/**
+ * A class that forces the screen display on and inhibits sleep
+ */
+class Awake
+{
+public:
+ Awake(const std::string& app_name);
+ ~Awake();
+
+private:
+ class Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+/***
+****
+***/
+
+} // namespace notifications
+} // namespace indicator
+} // namespace unity
+
+#endif // UNITY_INDICATOR_NOTIFICATIONS_AWAKE_H
diff --git a/include/notifications/dbus-shared.h b/include/notifications/dbus-shared.h
new file mode 100644
index 0000000..af714e7
--- /dev/null
+++ b/include/notifications/dbus-shared.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Ted Gould <ted@canonical.com>
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef UNITY_INDICATOR_NOTIFICATIONS_DBUS_SHARED_H
+#define UNITY_INDICATOR_NOTIFICATIONS_DBUS_SHARED_H
+
+#define BUS_SCREEN_NAME "com.canonical.Unity.Screen"
+#define BUS_SCREEN_PATH "/com/canonical/Unity/Screen"
+#define BUS_SCREEN_INTERFACE "com.canonical.Unity.Screen"
+
+#define BUS_POWERD_NAME "com.canonical.powerd"
+#define BUS_POWERD_PATH "/com/canonical/powerd"
+#define BUS_POWERD_INTERFACE "com.canonical.powerd"
+
+#define BUS_HAPTIC_NAME "com.canonical.usensord"
+#define BUS_HAPTIC_PATH "/com/canonical/usensord/haptic"
+#define BUS_HAPTIC_INTERFACE "com.canonical.usensord.haptic"
+
+#endif /* INDICATOR_NOTIFICATIONS_DBUS_SHARED_H */
diff --git a/include/notifications/haptic.h b/include/notifications/haptic.h
new file mode 100644
index 0000000..bfb5679
--- /dev/null
+++ b/include/notifications/haptic.h
@@ -0,0 +1,60 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef UNITY_INDICATOR_NOTIFICATIONS_HAPTIC_H
+#define UNITY_INDICATOR_NOTIFICATIONS_HAPTIC_H
+
+#include <memory>
+
+namespace unity {
+namespace indicator {
+namespace notifications {
+
+/***
+****
+***/
+
+/**
+ * Tries to emit haptic feedback to match the user-specified mode.
+ */
+class Haptic
+{
+public:
+ enum Mode
+ {
+ MODE_PULSE
+ };
+
+ Haptic(const Mode& mode = MODE_PULSE);
+ ~Haptic();
+
+private:
+ class Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+/***
+****
+***/
+
+} // namespace notifications
+} // namespace indicator
+} // namespace unity
+
+#endif // UNITY_INDICATOR_NOTIFICATIONS_HAPTIC_H
diff --git a/include/notifications/notifications.h b/include/notifications/notifications.h
new file mode 100644
index 0000000..260b466
--- /dev/null
+++ b/include/notifications/notifications.h
@@ -0,0 +1,116 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef UNITY_INDICATOR_NOTIFICATIONS_NOTIFICATIONS_H
+#define UNITY_INDICATOR_NOTIFICATIONS_NOTIFICATIONS_H
+
+#include <chrono>
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace unity {
+namespace indicator {
+namespace notifications {
+
+class Engine;
+
+/**
+ * Helper class for showing notifications.
+ *
+ * Populate the builder, then pass it to Engine::show().
+ *
+ * @see Engine::show(Builder)
+ */
+class Builder
+{
+public:
+ Builder();
+ ~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
+ be closed. If not set, the notification server's default timeout
+ is used. */
+ void set_timeout (const std::chrono::seconds& duration);
+
+ /* Add a notification hint.
+ These keys may be dependent on the notification server. */
+ void add_hint (const std::string& name);
+ 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_NONSHAPEDICON {"x-canonical-non-shaped-icon"};
+
+ /* 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. */
+ void set_closed_callback (std::function<void(const std::string& action)>);
+
+private:
+ friend class Engine;
+ class Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+/**
+ * Manages Notifications and the connection to the notification server.
+ *
+ * When this class is destroyed, any remaining notifications it created
+ * will be closed and their closed() callbacks will be invoked.
+ */
+class Engine
+{
+public:
+ Engine(const std::string& app_name);
+ ~Engine();
+
+ /** @see Builder::set_action() */
+ bool supports_actions() const;
+
+ /** Show a notification.
+ @return zero on failure, or a key that can be passed to close() */
+ int show(const Builder& builder);
+
+ /** Close a notification.
+ @param key the int returned by show() */
+ void close(int key);
+
+ /** Close all remaining notifications. */
+ void close_all();
+
+ const std::string& app_name() const;
+
+private:
+ class Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+} // namespace notifications
+} // namespace indicator
+} // namespace unity
+
+#endif // UNITY_INDICATOR_NOTIFICATIONS_NOTIFICATIONS_H
diff --git a/include/notifications/sound.h b/include/notifications/sound.h
new file mode 100644
index 0000000..f5f549c
--- /dev/null
+++ b/include/notifications/sound.h
@@ -0,0 +1,60 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef UNITY_INDICATOR_NOTIFICATIONS_SOUND_H
+#define UNITY_INDICATOR_NOTIFICATIONS_SOUND_H
+
+#include <memory>
+#include <string>
+
+namespace unity {
+namespace indicator {
+namespace notifications {
+
+/***
+****
+***/
+
+/**
+ * Plays a sound, possibly looping.
+ *
+ * @param uri the file to play
+ * @param volume the volume at which to play the sound, [0..100]
+ * @param loop if true, loop the sound for the lifespan of the object
+ */
+class Sound
+{
+public:
+ Sound(const std::string& uri, unsigned int volume, bool loop);
+ ~Sound();
+
+private:
+ class Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+/***
+****
+***/
+
+} // namespace notifications
+} // namespace indicator
+} // namespace unity
+
+#endif // UNITY_INDICATOR_NOTIFICATIONS_SOUND_H
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index af09c71..a466a48 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -11,6 +11,7 @@ set (SERVICE_CXX_SOURCES
actions.cpp
actions-live.cpp
alarm-queue-simple.cpp
+ awake.cpp
appointment.cpp
clock.cpp
clock-live.cpp
@@ -19,14 +20,17 @@ set (SERVICE_CXX_SOURCES
exporter.cpp
formatter.cpp
formatter-desktop.cpp
+ haptic.cpp
locations.cpp
locations-settings.cpp
menu.cpp
+ notifications.cpp
planner-month.cpp
planner-range.cpp
planner-upcoming.cpp
settings-live.cpp
snap.cpp
+ sound.cpp
timezone-file.cpp
timezone-geoclue.cpp
timezones-live.cpp
diff --git a/src/awake.cpp b/src/awake.cpp
new file mode 100644
index 0000000..57358ab
--- /dev/null
+++ b/src/awake.cpp
@@ -0,0 +1,256 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <notifications/awake.h>
+#include <notifications/dbus-shared.h>
+
+#include <gio/gio.h>
+
+#include <limits>
+
+namespace unity {
+namespace indicator {
+namespace notifications {
+
+/***
+****
+***/
+
+class Awake::Impl
+{
+public:
+
+ Impl(const std::string& app_name):
+ m_app_name(app_name),
+ m_cancellable(g_cancellable_new())
+ {
+ g_bus_get(G_BUS_TYPE_SYSTEM, m_cancellable, on_system_bus_ready, this);
+ }
+
+ ~Impl()
+ {
+ g_cancellable_cancel (m_cancellable);
+ g_object_unref (m_cancellable);
+
+ if (m_system_bus != nullptr)
+ {
+ unforce_awake ();
+ unforce_screen ();
+ g_object_unref (m_system_bus);
+ }
+ }
+
+private:
+
+ 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<Impl*>(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)", self->m_app_name.c_str(), 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_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))
+ {
+ g_warning ("Unable to inhibit sleep: %s", error->message);
+ }
+
+ g_error_free (error);
+ }
+ else
+ {
+ auto self = static_cast<Impl*>(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_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))
+ {
+ g_warning ("Unable to turn on the screen: %s", error->message);
+ }
+
+ g_error_free (error);
+ }
+ else
+ {
+ auto self = static_cast<Impl*>(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;
+ }
+ }
+
+ const std::string m_app_name;
+ 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<int32_t>::min() };
+};
+
+/***
+****
+***/
+
+Awake::Awake(const std::string& app_name):
+ impl(new Impl (app_name))
+{
+}
+
+Awake::~Awake()
+{
+}
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
diff --git a/src/exporter.cpp b/src/exporter.cpp
index 88aee2f..1d45705 100644
--- a/src/exporter.cpp
+++ b/src/exporter.cpp
@@ -144,6 +144,7 @@ private:
bind_uint_property(m_alarm_props, "duration", m_settings->alarm_duration);
bind_uint_property(m_alarm_props, "default-volume", m_settings->alarm_volume);
bind_string_property(m_alarm_props, "default-sound", m_settings->alarm_sound);
+ bind_string_property(m_alarm_props, "haptic-feedback", m_settings->alarm_haptic);
}
/***
diff --git a/src/haptic.cpp b/src/haptic.cpp
new file mode 100644
index 0000000..54a7d49
--- /dev/null
+++ b/src/haptic.cpp
@@ -0,0 +1,170 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <notifications/dbus-shared.h>
+#include <notifications/haptic.h>
+
+#include <gio/gio.h>
+
+#include <numeric>
+#include <vector>
+
+namespace unity {
+namespace indicator {
+namespace notifications {
+
+/***
+****
+***/
+
+class Haptic::Impl
+{
+public:
+
+ Impl(const Mode& mode):
+ m_mode(mode),
+ m_cancellable(g_cancellable_new())
+ {
+ g_bus_get (G_BUS_TYPE_SESSION, m_cancellable, on_bus_ready, this);
+ }
+
+ ~Impl()
+ {
+ if (m_tag)
+ g_source_remove(m_tag);
+
+ g_cancellable_cancel (m_cancellable);
+ g_object_unref (m_cancellable);
+
+ g_clear_object (&m_bus);
+ }
+
+private:
+
+ static void on_bus_ready (GObject*, GAsyncResult* res, gpointer gself)
+ {
+ GError * error;
+ GDBusConnection * bus;
+
+ error = nullptr;
+ 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 (bus != nullptr)
+ {
+ auto self = static_cast<Impl*>(gself);
+
+ self->m_bus = G_DBUS_CONNECTION (g_object_ref (bus));
+ self->start_vibrating();
+
+ g_object_unref (bus);
+ }
+ }
+
+ void start_vibrating()
+ {
+ g_return_if_fail (m_tag == 0);
+
+ switch (m_mode)
+ {
+ case MODE_PULSE: // the only mode currently supported... :)
+
+ // one second on, one second off.
+ m_pattern = std::vector<uint32_t>({1000u, 1000u});
+ break;
+ }
+
+ // Set up a loop to keep repeating the pattern
+ auto msec = std::accumulate(m_pattern.begin(), m_pattern.end(), 0u);
+ m_tag = g_timeout_add(msec, call_vibrate_pattern_static, this);
+ call_vibrate_pattern();
+ }
+
+ static gboolean call_vibrate_pattern_static (gpointer gself)
+ {
+ static_cast<Impl*>(gself)->call_vibrate_pattern();
+ return G_SOURCE_CONTINUE;
+ }
+
+ void call_vibrate_pattern()
+ {
+ // build the vibrate pattern
+ GVariantBuilder builder;
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+ for (const auto& msec : m_pattern)
+ g_variant_builder_add_value (&builder, g_variant_new_uint32(msec));
+ auto pattern_array = g_variant_builder_end (&builder);
+
+ /* Use a repeat_count of 1 here because we handle looping ourselves.
+ NB: VibratePattern could do it for us, but doesn't let us cancel
+ a running loop -- we could keep vibrating even after "this" was
+ destructed */
+ auto repeat_count = g_variant_new_uint32 (1u);
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
+ g_variant_builder_add_value (&builder, pattern_array);
+ g_variant_builder_add_value (&builder, repeat_count);
+ auto vibrate_pattern_args = g_variant_builder_end (&builder);
+
+ g_dbus_connection_call (m_bus,
+ BUS_HAPTIC_NAME,
+ BUS_HAPTIC_PATH,
+ BUS_HAPTIC_INTERFACE,
+ "VibratePattern",
+ vibrate_pattern_args,
+ nullptr,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ m_cancellable,
+ nullptr,
+ nullptr);
+ }
+
+ const Mode m_mode;
+ GCancellable * m_cancellable = nullptr;
+ GDBusConnection * m_bus = nullptr;
+ std::vector<uint32_t> m_pattern;
+ 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/main.cpp b/src/main.cpp
index cc81cd7..eb90020 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -32,6 +32,7 @@
#include <datetime/timezone-file.h>
#include <datetime/timezones-live.h>
#include <datetime/wakeup-timer-mainloop.h>
+#include <notifications/notifications.h>
#ifdef HAVE_UBUNTU_HW_ALARM_H
#include <datetime/wakeup-timer-uha.h>
@@ -45,6 +46,8 @@
#include <locale.h>
#include <cstdlib> // exit()
+namespace uin = unity::indicator::notifications;
+
using namespace unity::indicator::datetime;
namespace
@@ -141,7 +144,8 @@ main(int /*argc*/, char** /*argv*/)
MenuFactory factory(actions, state);
// set up the snap decisions
- Snap snap (state->clock, state->settings);
+ auto notification_engine = std::make_shared<uin::Engine>("indicator-datetime-service");
+ std::unique_ptr<Snap> snap (new Snap(notification_engine, state->settings));
auto alarm_queue = create_simple_alarm_queue(state->clock, engine, timezone);
alarm_queue->alarm_reached().connect([&snap](const Appointment& appt){
auto snap_show = [](const Appointment& a){
@@ -153,7 +157,7 @@ main(int /*argc*/, char** /*argv*/)
url_dispatch_send(url, nullptr, nullptr);
};
auto snap_dismiss = [](const Appointment&){};
- snap(appt, snap_show, snap_dismiss);
+ (*snap)(appt, snap_show, snap_dismiss);
});
// create the menus
@@ -170,6 +174,7 @@ main(int /*argc*/, char** /*argv*/)
});
exporter.publish(actions, menus);
g_main_loop_run(loop);
+
g_main_loop_unref(loop);
return 0;
}
diff --git a/src/notifications.cpp b/src/notifications.cpp
new file mode 100644
index 0000000..18f15d9
--- /dev/null
+++ b/src/notifications.cpp
@@ -0,0 +1,370 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <notifications/notifications.h>
+
+#include <libnotify/notify.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+namespace unity {
+namespace indicator {
+namespace notifications {
+
+static G_DEFINE_QUARK(NotificationKey, notification_key)
+
+static G_DEFINE_QUARK(NotificationAction, notification_action)
+
+/***
+****
+***/
+
+class Builder::Impl
+{
+public:
+ std::string m_title;
+ std::string m_body;
+ std::string m_icon_name;
+ std::chrono::seconds m_duration;
+ 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;
+};
+
+Builder::Builder():
+ impl(new Impl())
+{
+}
+
+Builder::~Builder()
+{
+}
+
+void
+Builder::set_title (const std::string& title)
+{
+ impl->m_title = title;
+}
+
+void
+Builder::set_body (const std::string& body)
+{
+ impl->m_body = body;
+}
+
+void
+Builder::set_icon_name (const std::string& icon_name)
+{
+ impl->m_icon_name = icon_name;
+}
+
+void
+Builder::set_timeout (const std::chrono::seconds& duration)
+{
+ impl->m_duration = duration;
+}
+
+void
+Builder::add_hint (const std::string& name)
+{
+ impl->m_string_hints.insert (name);
+}
+
+void
+Builder::add_action (const std::string& action, const std::string& label)
+{
+ impl->m_actions.push_back(std::pair<std::string,std::string>(action,label));
+}
+
+void
+Builder::set_closed_callback (std::function<void (const std::string&)> cb)
+{
+ impl->m_closed_callback.swap (cb);
+}
+
+/***
+****
+***/
+
+class Engine::Impl
+{
+ struct notification_data
+ {
+ std::shared_ptr<NotifyNotification> nn;
+ std::function<void(const std::string&)> closed_callback;
+ };
+
+public:
+
+ Impl(const std::string& app_name):
+ m_app_name(app_name)
+ {
+ if (!notify_init(app_name.c_str()))
+ g_critical("Unable to initialize libnotify!");
+ }
+
+ ~Impl()
+ {
+ close_all ();
+
+ notify_uninit ();
+ }
+
+ const std::string& app_name() const
+ {
+ return m_app_name;
+ }
+
+ bool supports_actions() const
+ {
+ return server_caps().count("actions") != 0;
+ }
+
+ void close_all ()
+ {
+ // call close() on all our keys
+
+ std::set<int> keys;
+ for (const auto& it : m_notifications)
+ keys.insert (it.first);
+
+ for (const int key : keys)
+ close (key);
+ }
+
+ void close (int key)
+ {
+ auto it = m_notifications.find(key);
+ if (it != m_notifications.end())
+ {
+ // tell the server to close the notification
+ GError * error = nullptr;
+ if (!notify_notification_close (it->second.nn.get(), &error))
+ {
+ g_warning ("Unable to close notification %d: %s", key, error->message);
+ g_error_free (error);
+ }
+
+ // call the user callback and remove it from our bookkeeping
+ remove_closed_notification (key);
+ }
+ }
+
+ int show (const Builder& builder)
+ {
+ int ret = -1;
+ const auto& info = *builder.impl;
+
+ std::shared_ptr<NotifyNotification> nn (
+ notify_notification_new(info.m_title.c_str(),
+ info.m_body.c_str(),
+ info.m_icon_name.c_str()),
+ [this](NotifyNotification * n) {
+ g_signal_handlers_disconnect_by_data(n, this);
+ g_object_unref (G_OBJECT(n));
+ }
+ );
+
+ if (info.m_duration.count() != 0)
+ {
+ const auto& d= info.m_duration;
+ auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(d);
+
+ notify_notification_set_hint (nn.get(),
+ HINT_TIMEOUT,
+ g_variant_new_int32(ms.count()));
+ }
+
+ for (const auto& hint : info.m_string_hints)
+ {
+ notify_notification_set_hint (nn.get(),
+ hint.c_str(),
+ g_variant_new_boolean(true));
+ }
+
+ for (const auto& action : info.m_actions)
+ {
+ notify_notification_add_action (nn.get(),
+ action.first.c_str(),
+ action.second.c_str(),
+ on_notification_clicked,
+ nullptr,
+ nullptr);
+ }
+
+ static int next_key = 1;
+ const int key = next_key++;
+ g_object_set_qdata (G_OBJECT(nn.get()),
+ notification_key_quark(),
+ GINT_TO_POINTER(key));
+
+ m_notifications[key] = { nn, info.m_closed_callback };
+ g_signal_connect (nn.get(), "closed",
+ G_CALLBACK(on_notification_closed), this);
+
+ GError * error = nullptr;
+ if (notify_notification_show(nn.get(), &error))
+ {
+ ret = key;
+ }
+ else
+ {
+ g_critical ("Unable to show notification for '%s': %s",
+ info.m_title.c_str(),
+ error->message);
+ g_error_free (error);
+ m_notifications.erase(key);
+ }
+
+ return ret;
+ }
+
+private:
+
+ const std::set<std::string>& server_caps() const
+ {
+ if (G_UNLIKELY(m_lazy_caps.empty()))
+ {
+ auto caps_gl = notify_get_server_caps();
+ std::string caps_str;
+ for(auto l=caps_gl; l!=nullptr; l=l->next)
+ {
+ m_lazy_caps.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 m_lazy_caps;
+ }
+
+ static void on_notification_clicked (NotifyNotification * nn,
+ char * action,
+ gpointer)
+ {
+ g_object_set_qdata_full (G_OBJECT(nn),
+ notification_action_quark(),
+ g_strdup(action),
+ g_free);
+ }
+
+ static void on_notification_closed (NotifyNotification * nn, gpointer gself)
+ {
+ const GQuark q = notification_key_quark();
+ const gpointer gkey = g_object_get_qdata(G_OBJECT(nn), q);
+ static_cast<Impl*>(gself)->remove_closed_notification(GPOINTER_TO_INT(gkey));
+ }
+
+ void remove_closed_notification (int key)
+ {
+ auto it = m_notifications.find(key);
+ g_return_if_fail (it != m_notifications.end());
+
+ const auto& ndata = it->second;
+ auto nn = ndata.nn.get();
+ if (ndata.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);
+ }
+
+ m_notifications.erase(it);
+ }
+
+ /***
+ ****
+ ***/
+
+ const std::string m_app_name;
+
+ // key-to-data
+ std::map<int,notification_data> m_notifications;
+
+ // server capabilities.
+ // as the name indicates, don't use this directly: use server_caps() instead
+ mutable std::set<std::string> m_lazy_caps;
+
+ static constexpr char const * HINT_TIMEOUT {"x-canonical-snap-decisions-timeout"};
+};
+
+/***
+****
+***/
+
+Engine::Engine(const std::string& app_name):
+ impl(new Impl(app_name))
+{
+}
+
+Engine::~Engine()
+{
+}
+
+bool
+Engine::supports_actions() const
+{
+ return impl->supports_actions();
+}
+
+int
+Engine::show(const Builder& builder)
+{
+ return impl->show(builder);
+}
+
+void
+Engine::close_all()
+{
+ impl->close_all();
+}
+
+void
+Engine::close(int key)
+{
+ impl->close(key);
+}
+
+const std::string&
+Engine::app_name() const
+{
+ return impl->app_name();
+}
+
+/***
+****
+***/
+
+} // namespace notifications
+} // namespace indicator
+} // namespace unity
+
diff --git a/src/settings-live.cpp b/src/settings-live.cpp
index 71bbd96..a8338ed 100644
--- a/src/settings-live.cpp
+++ b/src/settings-live.cpp
@@ -55,6 +55,7 @@ LiveSettings::LiveSettings():
update_alarm_sound();
update_alarm_volume();
update_alarm_duration();
+ update_alarm_haptic();
// now listen for clients to change the properties s.t. we can sync update GSettings
@@ -130,6 +131,10 @@ LiveSettings::LiveSettings():
alarm_duration.changed().connect([this](unsigned int value){
g_settings_set_uint(m_settings, SETTINGS_ALARM_DURATION_S, value);
});
+
+ alarm_haptic.changed().connect([this](const std::string& value){
+ g_settings_set_string(m_settings, SETTINGS_ALARM_HAPTIC_S, value.c_str());
+ });
}
/***
@@ -237,6 +242,13 @@ void LiveSettings::update_alarm_duration()
alarm_duration.set(g_settings_get_uint(m_settings, SETTINGS_ALARM_DURATION_S));
}
+void LiveSettings::update_alarm_haptic()
+{
+ auto val = g_settings_get_string(m_settings, SETTINGS_ALARM_HAPTIC_S);
+ alarm_haptic.set(val);
+ g_free(val);
+}
+
/***
****
***/
@@ -284,6 +296,8 @@ void LiveSettings::update_key(const std::string& key)
update_alarm_volume();
else if (key == SETTINGS_ALARM_DURATION_S)
update_alarm_duration();
+ else if (key == SETTINGS_ALARM_HAPTIC_S)
+ update_alarm_haptic();
}
/***
diff --git a/src/snap.cpp b/src/snap.cpp
index af39ec6..0b2322a 100644
--- a/src/snap.cpp
+++ b/src/snap.cpp
@@ -17,25 +17,22 @@
* Charles Kerr <charles.kerr@canonical.com>
*/
-#include <datetime/appointment.h>
-#include <datetime/dbus-shared.h>
-#include <datetime/formatter.h>
#include <datetime/snap.h>
-#include <core/signal.h>
+#include <notifications/awake.h>
+#include <notifications/haptic.h>
+#include <notifications/sound.h>
#include <gst/gst.h>
-#include <libnotify/notify.h>
#include <glib/gi18n.h>
-#include <glib.h>
#include <chrono>
-#include <limits>
-#include <mutex> // std::call_once()
#include <set>
#include <string>
+namespace uin = unity::indicator::notifications;
+
namespace unity {
namespace indicator {
namespace datetime {
@@ -44,631 +41,145 @@ namespace datetime {
****
***/
-namespace
-{
-
-static constexpr char const * APP_NAME = {"indicator-datetime-service"};
-
-/**
- * Plays a sound, possibly looping.
- */
-class Sound
+class Snap::Impl
{
- typedef Sound Self;
-
public:
- Sound(const std::shared_ptr<Clock>& 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()
+ Impl(const std::shared_ptr<unity::indicator::notifications::Engine>& engine,
+ const std::shared_ptr<const Settings>& settings):
+ m_engine(engine),
+ m_settings(settings)
{
- 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
+ ~Impl()
{
- 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));
+ for (const auto& key : m_notifications)
+ m_engine->close (key);
}
- static gboolean bus_callback(GstBus*, GstMessage* msg, gpointer gself)
+ void operator()(const Appointment& appointment,
+ appointment_func show,
+ appointment_func dismiss)
{
- auto self = static_cast<Sound*>(gself);
-
- if ((GST_MESSAGE_TYPE(msg) == GST_MESSAGE_EOS) &&
- (self->m_loop) &&
- (self->m_clock->localtime() < self->m_loop_end_time))
+ if (!appointment.has_alarms)
{
- 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<Clock> 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<Clock>& 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;}
+ dismiss(appointment);
+ return;
+ }
+
+ // force the system to stay awake
+ auto awake = std::make_shared<uin::Awake>(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<uin::Sound>(uri, volume, loop);
+
+ // create the haptic feedback...
+ const auto haptic_mode = m_settings->alarm_haptic.get();
+ std::shared_ptr<uin::Haptic> haptic;
+ if (haptic_mode == "pulse")
+ haptic = std::make_shared<uin::Haptic>(uin::Haptic::MODE_PULSE);
+
+ // 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);
+ b.add_hint (uin::Builder::HINT_NONSHAPEDICON);
+ 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<std::chrono::seconds>(minutes));
+ if (interactive) {
+ b.add_action ("show", _("Show"));
+ b.add_action ("dismiss", _("Dismiss"));
+ }
+
+ // 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, haptic]
+ (const std::string& action){
+ if (action == "show")
+ show(appointment);
+ else
+ dismiss(appointment);
+ });
- Sound* operator()() {
- return new Sound (m_clock,
- m_uri,
- m_volume,
- m_duration_minutes,
- m_looping);
+ const auto key = m_engine->show(b);
+ if (key)
+ m_notifications.insert (key);
+ else
+ show(appointment);
}
private:
- std::shared_ptr<Clock> 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<const Settings>& settings)
-{
- const char* FALLBACK {"/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg"};
+ std::string get_alarm_uri(const Appointment& appointment,
+ const std::shared_ptr<const Settings>& 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 };
+ const std::string candidates[] = { appointment.audio_url,
+ settings->alarm_sound.get(),
+ FALLBACK };
- std::string uri;
+ std::string uri;
- for(const auto& candidate : candidates)
- {
- if (gst_uri_is_valid (candidate.c_str()))
- {
- uri = candidate;
- break;
- }
- else if (g_file_test(candidate.c_str(), G_FILE_TEST_EXISTS))
+ 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;
-}
-
-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>& 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<std::chrono::milliseconds>(duration).count()));
- notify_notification_set_hint(m_nn, HINT_NONSHAPEDICON,
- g_variant_new_boolean(true));
-
- /// 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<Self*>(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<Self*>(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<Self*>(gself);
- self->m_sound.reset();
- self->m_response(self->m_response_value);
- }
-
- /***
- **** Interactive
- ***/
-
- static std::set<std::string> get_server_caps()
- {
- std::set<std::string> 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<Popup*>(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<Popup*>(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<Popup*>(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;
- }
+ return uri;
}
- /***
- ****
- ***/
-
- typedef Popup Self;
-
- const Appointment m_appointment;
- const bool m_interactive;
- SoundBuilder m_sound_builder;
- std::unique_ptr<Sound> m_sound;
- core::Signal<Response> 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<int32_t>::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"};
- static constexpr char const * HINT_NONSHAPEDICON {"x-canonical-non-shaped-icon"};
+ const std::shared_ptr<unity::indicator::notifications::Engine> m_engine;
+ const std::shared_ptr<const Settings> m_settings;
+ std::set<int> m_notifications;
};
/***
****
***/
-Snap::Snap(const std::shared_ptr<Clock>& clock,
+Snap::Snap(const std::shared_ptr<unity::indicator::notifications::Engine>& engine,
const std::shared_ptr<const Settings>& settings):
- m_clock(clock),
- m_settings(settings)
+ impl(new Impl(engine, 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();
}
-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;
- }
-
- // 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<Popup*>(gdata);
- return G_SOURCE_REMOVE;
- }, popup);
-
- // maybe notify the client code that the popup's done
- if (response == Popup::RESPONSE_SHOW)
- show(appointment);
- else if (response == Popup::RESPONSE_DISMISS)
- dismiss(appointment);
- });
+ (*impl)(appointment, show, dismiss);
}
/***
diff --git a/src/sound.cpp b/src/sound.cpp
new file mode 100644
index 0000000..d13c854
--- /dev/null
+++ b/src/sound.cpp
@@ -0,0 +1,143 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <notifications/sound.h>
+
+#include <gst/gst.h>
+
+#include <mutex> // std::call_once()
+
+namespace unity {
+namespace indicator {
+namespace notifications {
+
+/***
+****
+***/
+
+/**
+ * Plays a sound, possibly looping.
+ */
+class Sound::Impl
+{
+public:
+
+ Impl(const std::string& uri,
+ unsigned int volume,
+ bool loop):
+ m_uri(uri),
+ m_volume(volume),
+ m_loop(loop)
+ {
+ // init GST once
+ static std::once_flag once;
+ std::call_once(once, [](){
+ GError* error = nullptr;
+ if (!gst_init_check (nullptr, nullptr, &error))
+ {
+ g_critical("Unable to play alarm sound: %s", error->message);
+ g_error_free(error);
+ }
+ });
+
+ 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);
+
+ g_debug("Playing '%s'", m_uri.c_str());
+ 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()
+ {
+ 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:
+
+ // 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<Impl*>(gself);
+
+ if ((GST_MESSAGE_TYPE(msg) == GST_MESSAGE_EOS) && (self->m_loop))
+ {
+ 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::string m_uri;
+ const unsigned int m_volume;
+ const bool m_loop;
+ guint m_watch_source = 0;
+ GstElement* m_play = nullptr;
+};
+
+Sound::Sound(const std::string& uri, unsigned int volume, bool loop):
+ impl (new Impl(uri, volume, loop))
+{
+}
+
+Sound::~Sound()
+{
+}
+
+/***
+****
+***/
+
+} // namespace notifications
+} // namespace indicator
+} // namespace unity
diff --git a/tests/manual b/tests/manual
index be64863..c932004 100644
--- a/tests/manual
+++ b/tests/manual
@@ -30,6 +30,7 @@ Test-case indicator-datetime/new-alarm-wakeup
(Note: if in doubt about sleep you can see in the syslog whether the
device actually suspended or whether the suspend was aborted)</dd>
<dd>Confirm that the screen comes on when the alarm is triggered.<dd>
+ <dd>If the device supports haptic feedback, confirm the alarm vibrates.</dd>
</dl>
Test-case indicator-datetime/edited-alarm-wakeup
@@ -40,6 +41,7 @@ Test-case indicator-datetime/edited-alarm-wakeup
(Note: if in doubt about sleep you can see in the syslog whether the
device actually suspended or whether the suspend was aborted)</dd>
<dd>Confirm that the screen comes on when the alarm is triggered.<dd>
+ <dd>If the device supports haptic feedback, confirm the alarm vibrates.</dd>
</dl>
Test-case indicator-datetime/tell-snap-decision-to-dismiss
diff --git a/tests/manual-test-snap.cpp b/tests/manual-test-snap.cpp
index cc24a67..7d4403d 100644
--- a/tests/manual-test-snap.cpp
+++ b/tests/manual-test-snap.cpp
@@ -22,13 +22,13 @@
#include <datetime/settings-live.h>
#include <datetime/snap.h>
#include <datetime/timezones-live.h>
+#include <notifications/notifications.h>
#include <glib.h>
using namespace unity::indicator::datetime;
-#define TIMEZONE_FILE ("/etc/timezone")
-
+namespace uin = unity::indicator::notifications;
/***
****
@@ -94,11 +94,12 @@ int main(int argc, const char* argv[])
auto settings = std::make_shared<LiveSettings>();
settings->alarm_volume.set(volume);
- auto timezones = std::make_shared<LiveTimezones>(settings, TIMEZONE_FILE);
- auto clock = std::make_shared<LiveClock>(timezones);
- Snap snap (clock, settings);
+
+ auto notification_engine = std::make_shared<uin::Engine>("indicator-datetime-service");
+ Snap snap (notification_engine, settings);
snap(a, show, dismiss);
g_main_loop_run(loop);
+
g_main_loop_unref(loop);
return 0;
}
diff --git a/tests/test-exporter.cpp b/tests/test-exporter.cpp
index 2e3411a..96665cf 100644
--- a/tests/test-exporter.cpp
+++ b/tests/test-exporter.cpp
@@ -186,26 +186,35 @@ TEST_F(ExporterFixture, AlarmProperties)
auto expected_volume = 1;
int expected_duration = 60;
const char * expected_sound = "/tmp/foo.wav";
+ const char * expected_haptic = "pulse";
settings->alarm_volume.set(expected_volume);
settings->alarm_duration.set(expected_duration);
settings->alarm_sound.set(expected_sound);
+ settings->alarm_haptic.set(expected_haptic);
wait_msec();
static constexpr const char* const SOUND_PROP {"default-sound"};
static constexpr const char* const VOLUME_PROP {"default-volume"};
static constexpr const char* const DURATION_PROP {"duration"};
+ static constexpr const char* const HAPTIC_PROP {"haptic-feedback"};
char* sound = nullptr;
+ char* haptic = nullptr;
int volume = -1;
int duration = -1;
g_object_get(proxy, SOUND_PROP, &sound,
+ HAPTIC_PROP, &haptic,
VOLUME_PROP, &volume,
DURATION_PROP, &duration,
nullptr);
EXPECT_STREQ(expected_sound, sound);
+ EXPECT_STREQ(expected_haptic, haptic);
EXPECT_EQ(expected_volume, volume);
EXPECT_EQ(expected_duration, duration);
+ g_clear_pointer (&sound, g_free);
+ g_clear_pointer (&haptic, g_free);
+
/***
**** Try chaning the DBus properties -- do the Settings change to match it?
***/
@@ -213,13 +222,16 @@ TEST_F(ExporterFixture, AlarmProperties)
expected_volume = 100;
expected_duration = 30;
expected_sound = "/tmp/bar.wav";
+ expected_haptic = "none";
g_object_set(proxy, SOUND_PROP, expected_sound,
+ HAPTIC_PROP, expected_haptic,
VOLUME_PROP, expected_volume,
DURATION_PROP, expected_duration,
nullptr);
wait_msec();
EXPECT_STREQ(expected_sound, settings->alarm_sound.get().c_str());
+ EXPECT_STREQ(expected_haptic, settings->alarm_haptic.get().c_str());
EXPECT_EQ(expected_volume, settings->alarm_volume.get());
EXPECT_EQ(expected_duration, settings->alarm_duration.get());
diff --git a/tests/test-settings.cpp b/tests/test-settings.cpp
index 44a0252..4fb0a08 100644
--- a/tests/test-settings.cpp
+++ b/tests/test-settings.cpp
@@ -159,6 +159,7 @@ TEST_F(SettingsFixture, StringProperties)
TestStringProperty(m_settings->custom_time_format, SETTINGS_CUSTOM_TIME_FORMAT_S);
TestStringProperty(m_settings->timezone_name, SETTINGS_TIMEZONE_NAME_S);
TestStringProperty(m_settings->alarm_sound, SETTINGS_ALARM_SOUND_S);
+ TestStringProperty(m_settings->alarm_haptic, SETTINGS_ALARM_HAPTIC_S);
}
TEST_F(SettingsFixture, TimeFormatMode)
diff --git a/tests/test-snap.cpp b/tests/test-snap.cpp
index f9e7a66..5afaab1 100644
--- a/tests/test-snap.cpp
+++ b/tests/test-snap.cpp
@@ -21,11 +21,11 @@
#include <datetime/dbus-shared.h>
#include <datetime/settings.h>
#include <datetime/snap.h>
-#include <datetime/timezones.h>
-#include <libdbustest/dbus-test.h>
+#include <notifications/dbus-shared.h>
+#include <notifications/notifications.h>
-#include <libnotify/notify.h>
+#include <libdbustest/dbus-test.h>
#include <glib.h>
@@ -37,6 +37,11 @@ using namespace unity::indicator::datetime;
****
***/
+namespace
+{
+ static constexpr char const * APP_NAME {"indicator-datetime-service"};
+}
+
using namespace unity::indicator::datetime;
class SnapFixture: public GlibFixture
@@ -51,6 +56,8 @@ private:
protected:
+ static constexpr char const * HAPTIC_METHOD_VIBRATE_PATTERN {"VibratePattern"};
+
static constexpr int SCREEN_COOKIE {8675309};
static constexpr char const * SCREEN_METHOD_KEEP_DISPLAY_ON {"keepDisplayOn"};
static constexpr char const * SCREEN_METHOD_REMOVE_DISPLAY_ON_REQUEST {"removeDisplayOnRequest"};
@@ -60,18 +67,19 @@ protected:
static constexpr char const * POWERD_METHOD_REQUEST_SYS_STATE {"requestSysState"};
static constexpr char const * POWERD_METHOD_CLEAR_SYS_STATE {"clearSysState"};
- static constexpr int NOTIFY_ID {1234};
+ static constexpr int FIRST_NOTIFY_ID {1000};
static constexpr int NOTIFICATION_CLOSED_EXPIRED {1};
static constexpr int NOTIFICATION_CLOSED_DISMISSED {2};
static constexpr int NOTIFICATION_CLOSED_API {3};
static constexpr int NOTIFICATION_CLOSED_UNDEFINED {4};
- static constexpr char const * APP_NAME {"indicator-datetime-service"};
-
- static constexpr char const * METHOD_NOTIFY {"Notify"};
+ static constexpr char const * METHOD_CLOSE {"CloseNotification"};
static constexpr char const * METHOD_GET_CAPS {"GetCapabilities"};
static constexpr char const * METHOD_GET_INFO {"GetServerInformation"};
+ static constexpr char const * METHOD_NOTIFY {"Notify"};
+
+ static constexpr char const * SIGNAL_CLOSED {"NotificationClosed"};
static constexpr char const * HINT_TIMEOUT {"x-canonical-snap-decisions-timeout"};
@@ -82,9 +90,11 @@ protected:
DbusTestDbusMock * notify_mock = nullptr;
DbusTestDbusMock * powerd_mock = nullptr;
DbusTestDbusMock * screen_mock = nullptr;
+ DbusTestDbusMock * haptic_mock = nullptr;
DbusTestDbusMockObject * notify_obj = nullptr;
DbusTestDbusMockObject * powerd_obj = nullptr;
DbusTestDbusMockObject * screen_obj = nullptr;
+ DbusTestDbusMockObject * haptic_obj = nullptr;
void SetUp()
{
@@ -118,7 +128,8 @@ protected:
NOTIFY_INTERFACE,
&error);
g_assert_no_error(error);
-
+
+ // METHOD_GET_INFO
str = g_strdup("ret = ('mock-notify', 'test vendor', '1.0', '1.1')");
dbus_test_dbus_mock_object_add_method(notify_mock,
notify_obj,
@@ -130,7 +141,14 @@ protected:
g_assert_no_error (error);
g_free (str);
- str = g_strdup_printf ("ret = %d", NOTIFY_ID);
+ // METHOD_NOTIFY
+ str = g_strdup_printf("try:\n"
+ " self.NextNotifyId\n"
+ "except AttributeError:\n"
+ " self.NextNotifyId = %d\n"
+ "ret = self.NextNotifyId\n"
+ "self.NextNotifyId += 1\n",
+ FIRST_NOTIFY_ID);
dbus_test_dbus_mock_object_add_method(notify_mock,
notify_obj,
METHOD_NOTIFY,
@@ -141,6 +159,21 @@ protected:
g_assert_no_error (error);
g_free (str);
+ // METHOD_CLOSE
+ str = g_strdup_printf("self.EmitSignal('%s', '%s', 'uu', [ args[0], %d ])",
+ NOTIFY_INTERFACE,
+ SIGNAL_CLOSED,
+ NOTIFICATION_CLOSED_API);
+ dbus_test_dbus_mock_object_add_method(notify_mock,
+ notify_obj,
+ METHOD_CLOSE,
+ G_VARIANT_TYPE("(u)"),
+ nullptr,
+ str,
+ &error);
+ g_assert_no_error (error);
+ g_free (str);
+
dbus_test_service_add_task(service, DBUS_TEST_TASK(notify_mock));
///
@@ -206,9 +239,28 @@ protected:
"",
&error);
g_assert_no_error (error);
-
dbus_test_service_add_task(service, DBUS_TEST_TASK(screen_mock));
+ ///
+ /// Add the haptic mock
+ ///
+
+ haptic_mock = dbus_test_dbus_mock_new(BUS_HAPTIC_NAME);
+ haptic_obj = dbus_test_dbus_mock_get_object(haptic_mock,
+ BUS_HAPTIC_PATH,
+ BUS_HAPTIC_INTERFACE,
+ &error);
+
+ dbus_test_dbus_mock_object_add_method(haptic_mock,
+ haptic_obj,
+ HAPTIC_METHOD_VIBRATE_PATTERN,
+ G_VARIANT_TYPE("(auu)"),
+ nullptr,
+ "",
+ &error);
+ g_assert_no_error (error);
+ dbus_test_service_add_task(service, DBUS_TEST_TASK(haptic_mock));
+
// start 'em up.
// make the system bus work off the mock bus too, since that's
@@ -226,14 +278,11 @@ protected:
ASSERT_NE(nullptr, system_bus);
g_dbus_connection_set_exit_on_close(system_bus, FALSE);
g_object_add_weak_pointer(G_OBJECT(system_bus), (gpointer *)&system_bus);
-
- notify_init(APP_NAME);
}
virtual void TearDown()
{
- notify_uninit();
-
+ g_clear_object(&haptic_mock);
g_clear_object(&screen_mock);
g_clear_object(&powerd_mock);
g_clear_object(&notify_mock);
@@ -291,10 +340,8 @@ TEST_F(SnapFixture, InteractiveDuration)
static constexpr int duration_minutes = 120;
auto settings = std::make_shared<Settings>();
settings->alarm_duration.set(duration_minutes);
- auto timezones = std::make_shared<Timezones>();
- auto clock = std::make_shared<LiveClock>(timezones);
-
- Snap snap (clock, settings);
+ auto ne = std::make_shared<unity::indicator::notifications::Engine>(APP_NAME);
+ Snap snap (ne, settings);
make_interactive();
@@ -333,6 +380,7 @@ TEST_F(SnapFixture, InteractiveDuration)
const auto duration = std::chrono::minutes(duration_minutes);
EXPECT_EQ(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(), i32);
g_variant_unref(hints);
+ ne.reset();
}
/***
@@ -341,12 +389,9 @@ TEST_F(SnapFixture, InteractiveDuration)
TEST_F(SnapFixture, InhibitSleep)
{
- //static constexpr int duration_minutes = 120;
auto settings = std::make_shared<Settings>();
- //settings->alarm_duration.set(duration_minutes);
- auto timezones = std::make_shared<Timezones>();
- auto clock = std::make_shared<LiveClock>(timezones);
- auto snap = new Snap (clock, settings);
+ auto ne = std::make_shared<unity::indicator::notifications::Engine>(APP_NAME);
+ auto snap = new Snap (ne, settings);
make_interactive();
@@ -372,6 +417,7 @@ TEST_F(SnapFixture, InhibitSleep)
&error));
// force-close the snap
+ wait_msec(100);
delete snap;
wait_msec(100);
@@ -398,12 +444,9 @@ TEST_F(SnapFixture, InhibitSleep)
TEST_F(SnapFixture, ForceScreen)
{
- //static constexpr int duration_minutes = 120;
auto settings = std::make_shared<Settings>();
- //settings->alarm_duration.set(duration_minutes);
- auto timezones = std::make_shared<Timezones>();
- auto clock = std::make_shared<LiveClock>(timezones);
- auto snap = new Snap (clock, settings);
+ auto ne = std::make_shared<unity::indicator::notifications::Engine>(APP_NAME);
+ auto snap = new Snap (ne, settings);
make_interactive();
@@ -423,6 +466,7 @@ TEST_F(SnapFixture, ForceScreen)
g_assert_no_error(error);
// force-close the snap
+ wait_msec(100);
delete snap;
wait_msec(100);
@@ -434,3 +478,45 @@ TEST_F(SnapFixture, ForceScreen)
&error));
g_assert_no_error(error);
}
+
+/***
+****
+***/
+
+TEST_F(SnapFixture, HapticModes)
+{
+ auto settings = std::make_shared<Settings>();
+ auto ne = std::make_shared<unity::indicator::notifications::Engine>(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);
+}