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/datetime/settings-live.h1
-rw-r--r--include/datetime/settings-shared.h1
-rw-r--r--include/datetime/settings.h1
-rw-r--r--include/notifications/dbus-shared.h4
-rw-r--r--include/notifications/haptic.h60
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/exporter.cpp1
-rw-r--r--src/haptic.cpp170
-rw-r--r--src/settings-live.cpp14
-rw-r--r--src/snap.cpp11
-rw-r--r--tests/manual2
-rw-r--r--tests/test-exporter.cpp12
-rw-r--r--tests/test-settings.cpp1
-rw-r--r--tests/test-snap.cpp68
16 files changed, 361 insertions, 3 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/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/notifications/dbus-shared.h b/include/notifications/dbus-shared.h
index 7738cb7..af714e7 100644
--- a/include/notifications/dbus-shared.h
+++ b/include/notifications/dbus-shared.h
@@ -29,4 +29,8 @@
#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/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/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/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 1506008..0b2322a 100644
--- a/src/snap.cpp
+++ b/src/snap.cpp
@@ -20,6 +20,7 @@
#include <datetime/snap.h>
#include <notifications/awake.h>
+#include <notifications/haptic.h>
#include <notifications/sound.h>
#include <gst/gst.h>
@@ -76,6 +77,12 @@ public:
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();
@@ -95,10 +102,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);
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/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 9a049fa..5afaab1 100644
--- a/tests/test-snap.cpp
+++ b/tests/test-snap.cpp
@@ -56,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"};
@@ -88,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()
{
@@ -235,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
@@ -259,6 +282,7 @@ protected:
virtual void TearDown()
{
+ g_clear_object(&haptic_mock);
g_clear_object(&screen_mock);
g_clear_object(&powerd_mock);
g_clear_object(&notify_mock);
@@ -454,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);
+}