aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/datetime/clock-mock.h12
-rw-r--r--include/datetime/clock.h9
-rw-r--r--include/datetime/dbus-shared.h5
-rw-r--r--src/clock.cpp221
-rw-r--r--src/snap.cpp11
-rw-r--r--tests/manual7
-rw-r--r--tests/test-clock.cpp111
7 files changed, 308 insertions, 68 deletions
diff --git a/include/datetime/clock-mock.h b/include/datetime/clock-mock.h
index fb9b52f..0e24377 100644
--- a/include/datetime/clock-mock.h
+++ b/include/datetime/clock-mock.h
@@ -41,15 +41,23 @@ public:
DateTime localtime() const { return m_localtime; }
- void set_localtime(const DateTime& dt) {
+ void set_localtime(const DateTime& dt)
+ {
const auto old = m_localtime;
- m_localtime = dt;
+
+ set_localtime_quietly(dt);
+
if (!DateTime::is_same_minute(old, m_localtime))
minute_changed();
if (!DateTime::is_same_day(old, m_localtime))
date_changed();
}
+ void set_localtime_quietly(const DateTime& dt)
+ {
+ m_localtime = dt;
+ }
+
private:
DateTime m_localtime;
};
diff --git a/include/datetime/clock.h b/include/datetime/clock.h
index 1d488d1..0b2a543 100644
--- a/include/datetime/clock.h
+++ b/include/datetime/clock.h
@@ -55,12 +55,9 @@ protected:
void maybe_emit (const DateTime& a, const DateTime& b);
private:
- static void on_system_bus_ready(GObject*, GAsyncResult*, gpointer);
- static void on_prepare_for_sleep(GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant*, gpointer);
-
- GCancellable * m_cancellable = nullptr;
- GDBusConnection * m_system_bus = nullptr;
- unsigned int m_sleep_subscription_id = 0;
+ class Impl;
+ friend class Impl;
+ std::unique_ptr<Impl> m_impl;
// we've got raw pointers and GSignal tags in here, so disable copying
Clock(const Clock&) =delete;
diff --git a/include/datetime/dbus-shared.h b/include/datetime/dbus-shared.h
index db10c1d..c09caa2 100644
--- a/include/datetime/dbus-shared.h
+++ b/include/datetime/dbus-shared.h
@@ -24,4 +24,9 @@
#define BUS_DATETIME_NAME "com.canonical.indicator.datetime"
#define BUS_DATETIME_PATH "/com/canonical/indicator/datetime"
+#define BUS_POWERD_NAME "com.canonical.powerd"
+#define BUS_POWERD_PATH "/com/canonical/powerd"
+#define BUS_POWERD_INTERFACE "com.canonical.powerd"
+
+
#endif /* _INDICATOR_DATETIME_DBUS_SHARED_H_ */
diff --git a/src/clock.cpp b/src/clock.cpp
index f41a0cc..a04e074 100644
--- a/src/clock.cpp
+++ b/src/clock.cpp
@@ -18,10 +18,16 @@
*/
#include <datetime/clock.h>
+#include <datetime/dbus-shared.h>
#include <glib.h>
#include <gio/gio.h>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
namespace unity {
namespace indicator {
namespace datetime {
@@ -30,58 +36,191 @@ namespace datetime {
****
***/
-Clock::Clock():
- m_cancellable(g_cancellable_new())
+class Clock::Impl
{
- g_bus_get(G_BUS_TYPE_SYSTEM, m_cancellable, on_system_bus_ready, this);
-}
+public:
-Clock::~Clock()
-{
- g_cancellable_cancel(m_cancellable);
- g_clear_object(&m_cancellable);
+ Impl(Clock& owner):
+ m_owner(owner),
+ m_cancellable(g_cancellable_new())
+ {
+ g_bus_get(G_BUS_TYPE_SYSTEM, m_cancellable, on_bus_ready, this);
+ }
- if (m_sleep_subscription_id)
- g_dbus_connection_signal_unsubscribe(m_system_bus, m_sleep_subscription_id);
+ ~Impl()
+ {
+ g_cancellable_cancel(m_cancellable);
+ g_object_unref(m_cancellable);
- g_clear_object(&m_system_bus);
-}
+ for(const auto& tag : m_watched_names)
+ g_bus_unwatch_name(tag);
+ }
-void
-Clock::on_system_bus_ready(GObject*, GAsyncResult * res, gpointer gself)
-{
- GDBusConnection * system_bus;
+private:
+
+ static void on_bus_ready(GObject * /*source_object*/,
+ GAsyncResult * res,
+ gpointer gself)
+ {
+ GError * error = NULL;
+ GDBusConnection * bus;
+
+ if ((bus = g_bus_get_finish(res, &error)))
+ {
+ auto self = static_cast<Impl*>(gself);
+
+ auto tag = g_bus_watch_name_on_connection(bus,
+ "org.freedesktop.login1",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ on_login1_appeared,
+ on_login1_vanished,
+ gself, nullptr);
+ self->m_watched_names.insert(tag);
+
+ tag = g_bus_watch_name_on_connection(bus,
+ BUS_POWERD_NAME,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ on_powerd_appeared,
+ on_powerd_vanished,
+ gself, nullptr);
+ self->m_watched_names.insert(tag);
+
+ g_object_unref(bus);
+ }
+ else if (error != nullptr)
+ {
+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning("%s Couldn't get system bus: %s", G_STRLOC, error->message);
+
+ g_error_free(error);
+ }
+ }
+
+ void remember_subscription(const std::string & name,
+ GDBusConnection * bus,
+ guint tag)
+ {
+ g_object_ref(bus);
+
+ auto deleter = [tag](GDBusConnection* bus){
+ g_dbus_connection_signal_unsubscribe(bus, tag);
+ g_object_unref(G_OBJECT(bus));
+ };
+
+ m_subscriptions[name].push_back(std::shared_ptr<GDBusConnection>(bus, deleter));
+ }
+
+ /**
+ *** DBus Chatter: org.freedesktop.login1
+ ***
+ *** Fire Clock::minute_changed() signal on login1's PrepareForSleep signal
+ **/
+
+ static void on_login1_appeared(GDBusConnection * bus,
+ const gchar * name,
+ const gchar * name_owner,
+ gpointer gself)
+ {
+ auto tag = g_dbus_connection_signal_subscribe(bus,
+ name_owner,
+ "org.freedesktop.login1.Manager", // interface
+ "PrepareForSleep", // signal name
+ "/org/freedesktop/login1", // object path
+ nullptr, // arg0
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ on_prepare_for_sleep,
+ gself,
+ nullptr);
- if ((system_bus = g_bus_get_finish(res, nullptr)))
+ static_cast<Impl*>(gself)->remember_subscription(name, bus, tag);
+ }
+
+ static void on_login1_vanished(GDBusConnection * /*system_bus*/,
+ const gchar * name,
+ gpointer gself)
+ {
+ static_cast<Impl*>(gself)->m_subscriptions[name].clear();
+ }
+
+ static void on_prepare_for_sleep(GDBusConnection* /*connection*/,
+ const gchar* /*sender_name*/,
+ const gchar* /*object_path*/,
+ const gchar* /*interface_name*/,
+ const gchar* /*signal_name*/,
+ GVariant* /*parameters*/,
+ gpointer gself)
+ {
+ g_debug("firing clock.minute_changed() due to PrepareForSleep");
+ static_cast<Impl*>(gself)->m_owner.minute_changed();
+ }
+
+ /**
+ *** DBus Chatter: com.canonical.powerd
+ ***
+ *** Fire Clock::minute_changed() signal when powerd says the system's
+ *** has awoken from sleep -- the old timestamp is likely out-of-date
+ **/
+
+ static void on_powerd_appeared(GDBusConnection * bus,
+ const gchar * name,
+ const gchar * name_owner,
+ gpointer gself)
+ {
+ auto tag = g_dbus_connection_signal_subscribe(bus,
+ name_owner,
+ BUS_POWERD_INTERFACE,
+ "SysPowerStateChange",
+ BUS_POWERD_PATH,
+ nullptr, // arg0
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ on_sys_power_state_change,
+ gself, // user_data
+ nullptr); // user_data closure
+
+
+ static_cast<Impl*>(gself)->remember_subscription(name, bus, tag);
+ }
+
+ static void on_powerd_vanished(GDBusConnection * /*bus*/,
+ const gchar * name,
+ gpointer gself)
{
- auto self = static_cast<Clock*>(gself);
-
- self->m_system_bus = system_bus;
-
- self->m_sleep_subscription_id = g_dbus_connection_signal_subscribe(
- system_bus,
- nullptr,
- "org.freedesktop.login1.Manager", // interface
- "PrepareForSleep", // signal name
- "/org/freedesktop/login1", // object path
- nullptr, // arg0
- G_DBUS_SIGNAL_FLAGS_NONE,
- on_prepare_for_sleep,
- self,
- nullptr);
+ static_cast<Impl*>(gself)->m_subscriptions[name].clear();
}
+
+ static void on_sys_power_state_change(GDBusConnection* /*connection*/,
+ const gchar* /*sender_name*/,
+ const gchar* /*object_path*/,
+ const gchar* /*interface_name*/,
+ const gchar* /*signal_name*/,
+ GVariant* /*parameters*/,
+ gpointer gself)
+ {
+ g_debug("firing clock.minute_changed() due to state change");
+ static_cast<Impl*>(gself)->m_owner.minute_changed();
+ }
+
+ /***
+ ****
+ ***/
+
+ Clock& m_owner;
+ GCancellable * m_cancellable = nullptr;
+ std::set<guint> m_watched_names;
+ std::map<std::string,std::vector<std::shared_ptr<GDBusConnection>>> m_subscriptions;
+};
+
+/***
+****
+***/
+
+Clock::Clock():
+ m_impl(new Impl{*this})
+{
}
-void
-Clock::on_prepare_for_sleep(GDBusConnection* /*connection*/,
- const gchar* /*sender_name*/,
- const gchar* /*object_path*/,
- const gchar* /*interface_name*/,
- const gchar* /*signal_name*/,
- GVariant* /*parameters*/,
- gpointer gself)
+Clock::~Clock()
{
- static_cast<Clock*>(gself)->minute_changed();
}
/***
diff --git a/src/snap.cpp b/src/snap.cpp
index b3368a1..c2cbc0a 100644
--- a/src/snap.cpp
+++ b/src/snap.cpp
@@ -94,7 +94,16 @@ public:
b.add_hint (uin::Builder::HINT_TINT);
b.add_hint (uin::Builder::HINT_NONSHAPEDICON);
- const auto timefmt = is_locale_12h() ? _("%a, %l:%M %p") : _("%a, %H:%M");
+ const char * timefmt;
+ if (is_locale_12h()) {
+ /** strftime(3) format for abbreviated weekday,
+ hours, minutes in a 12h locale; e.g. Wed, 2:00 PM */
+ timefmt = _("%a, %l:%M %p");
+ } else {
+ /** A strftime(3) format for abbreviated weekday,
+ hours, minutes in a 24h locale; e.g. Wed, 14:00 */
+ timefmt = _("%a, %H:%M");
+ }
const auto timestr = appointment.begin.format(timefmt);
auto title = g_strdup_printf(_("Alarm %s"), timestr.c_str());
b.set_title (title);
diff --git a/tests/manual b/tests/manual
index e9f858e..2b16841 100644
--- a/tests/manual
+++ b/tests/manual
@@ -22,6 +22,13 @@ Test-case indicator-datetime/unity8-items-check
<dd>The menu is populated with items</dd>
</dl>
+Test-case indicator-datetime/timestamp-wakeup
+<dl>
+ <dt>Unplug the phone from any USB connection and put it to sleep</dt>
+ <dd>Reawaken the device.</dt>
+ <dd>The indicator should be showing the correct time.</dd>
+</dl>
+
Test-case indicator-datetime/new-alarm-wakeup
<dl>
<dt>Create and save an upcoming alarm in ubuntu-clock-app</dt>
diff --git a/tests/test-clock.cpp b/tests/test-clock.cpp
index a4924b3..62281fb 100644
--- a/tests/test-clock.cpp
+++ b/tests/test-clock.cpp
@@ -18,8 +18,11 @@
*/
#include <datetime/clock.h>
+#include <datetime/clock-mock.h>
#include <datetime/timezones.h>
+#include <notifications/dbus-shared.h>
+
#include "test-dbus-fixture.h"
/***
@@ -32,18 +35,6 @@ class ClockFixture: public TestDBusFixture
{
private:
typedef TestDBusFixture super;
-
- public:
- void emitPrepareForSleep()
- {
- g_dbus_connection_emit_signal(g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr),
- nullptr,
- "/org/freedesktop/login1", // object path
- "org.freedesktop.login1.Manager", // interface
- "PrepareForSleep", // signal name
- g_variant_new("(b)", FALSE),
- nullptr);
- }
};
TEST_F(ClockFixture, MinuteChangedSignalShouldTriggerOncePerMinute)
@@ -113,6 +104,27 @@ TEST_F(ClockFixture, TimezoneChangeTriggersSkew)
g_time_zone_unref(tz_la);
}
+/***
+****
+***/
+
+namespace
+{
+ void on_login1_name_acquired(GDBusConnection * connection,
+ const gchar * /*name*/,
+ gpointer /*user_data*/)
+ {
+ g_dbus_connection_emit_signal(connection,
+ nullptr,
+ "/org/freedesktop/login1", // object path
+ "org.freedesktop.login1.Manager", // interface
+ "PrepareForSleep", // signal name
+ g_variant_new("(b)", FALSE),
+ nullptr);
+ }
+}
+
+
/**
* Confirm that a "PrepareForSleep" event wil trigger a skew event
*/
@@ -121,7 +133,7 @@ TEST_F(ClockFixture, SleepTriggersSkew)
std::shared_ptr<Timezones> zones(new Timezones);
zones->timezone.set("America/New_York");
LiveClock clock(zones);
- wait_msec(500); // wait for the bus to set up
+ wait_msec(250); // wait for the bus to set up
bool skewed = false;
clock.minute_changed.connect([&skewed, this](){
@@ -130,11 +142,74 @@ TEST_F(ClockFixture, SleepTriggersSkew)
return G_SOURCE_REMOVE;
});
- g_idle_add([](gpointer gself){
- static_cast<ClockFixture*>(gself)->emitPrepareForSleep();
- return G_SOURCE_REMOVE;
- }, this);
-
+ auto name_tag = g_bus_own_name(G_BUS_TYPE_SYSTEM,
+ "org.freedesktop.login1",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ nullptr /* bus acquired */,
+ on_login1_name_acquired,
+ nullptr /* name lost */,
+ nullptr /* user_data */,
+ nullptr /* user_data closure */);
g_main_loop_run(loop);
EXPECT_TRUE(skewed);
+
+ g_bus_unown_name(name_tag);
+}
+
+namespace
+{
+ void on_powerd_name_acquired(GDBusConnection * /*connection*/,
+ const gchar * /*name*/,
+ gpointer is_owned)
+ {
+ *static_cast<bool*>(is_owned) = true;
+ }
+}
+
+/**
+ * Confirm that powerd's SysPowerStateChange triggers
+ * a timestamp change
+ */
+TEST_F(ClockFixture, SysPowerStateChange)
+{
+ // set up the mock clock
+ bool minute_changed = false;
+ auto clock = std::make_shared<MockClock>(DateTime::NowLocal());
+ clock->minute_changed.connect([&minute_changed]() {
+ minute_changed = true;
+ });
+
+ // control test -- minute_changed shouldn't get triggered
+ // when the clock is silently changed
+ gboolean is_owned = false;
+ auto tag = g_bus_own_name_on_connection(system_bus,
+ BUS_POWERD_NAME,
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ on_powerd_name_acquired,
+ nullptr,
+ &is_owned /* user_data */,
+ nullptr /* user_data closure */);
+ const DateTime not_now {DateTime::Local(1999, 12, 31, 23, 59, 59)};
+ clock->set_localtime_quietly(not_now);
+ wait_msec();
+ ASSERT_TRUE(is_owned);
+ ASSERT_FALSE(minute_changed);
+
+ // now for the actual test,
+ // confirm that SysPowerStateChange triggers a minute_changed() signal
+ GError * error = nullptr;
+ auto emitted = g_dbus_connection_emit_signal(system_bus,
+ nullptr,
+ BUS_POWERD_PATH,
+ BUS_POWERD_INTERFACE,
+ "SysPowerStateChange",
+ g_variant_new("(i)", 1),
+ &error);
+ wait_msec();
+ EXPECT_TRUE(emitted);
+ EXPECT_EQ(nullptr, error);
+ EXPECT_TRUE(minute_changed);
+
+ // cleanup
+ g_bus_unown_name(tag);
}