aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCharles Kerr <charles.kerr@canonical.com>2014-06-11 12:36:41 +0000
committerCI bot <ps-jenkins@lists.canonical.com>2014-06-11 12:36:41 +0000
commit9eee6f013103e856f66f16e17e156b1f6103695a (patch)
tree3678963f3d3520e8703732d7d0bb8489f23b7f49 /src
parent5e6083e752f7dd50571f87e5910dca6302ce8113 (diff)
parentdb2898b2da7231490fe77ebcef0fb373ce1f2776 (diff)
downloadayatana-indicator-datetime-9eee6f013103e856f66f16e17e156b1f6103695a.tar.gz
ayatana-indicator-datetime-9eee6f013103e856f66f16e17e156b1f6103695a.tar.bz2
ayatana-indicator-datetime-9eee6f013103e856f66f16e17e156b1f6103695a.zip
Prefer to use ubuntu-platform-hardware-api for wakeups when possible s.t. user-defined alarms/appointments can wake up the phone from sleep to give a snap decision. Fixes: 1299916
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt55
-rw-r--r--src/alarm-queue-simple.cpp159
-rw-r--r--src/clock-watcher.cpp81
-rw-r--r--src/date-time.cpp5
-rw-r--r--src/engine-eds.cpp12
-rw-r--r--src/main.cpp115
-rw-r--r--src/planner-range.cpp2
-rw-r--r--src/wakeup-timer-mainloop.cpp138
-rw-r--r--src/wakeup-timer-uha.cpp175
9 files changed, 607 insertions, 135 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9bc22f2..ffa1523 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -7,30 +7,37 @@ SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g ${CXX_WARNING_ARGS} ${GCO
add_definitions (-DTIMEZONE_FILE="/etc/timezone"
-DG_LOG_DOMAIN="Indicator-Datetime")
-add_library (${SERVICE_LIB} STATIC
- actions.cpp
- actions-live.cpp
- appointment.cpp
- clock.cpp
- clock-live.cpp
- clock-watcher.cpp
- date-time.cpp
- engine-eds.cpp
- exporter.cpp
- formatter.cpp
- formatter-desktop.cpp
- locations.cpp
- locations-settings.cpp
- menu.cpp
- planner-month.cpp
- planner-range.cpp
- planner-upcoming.cpp
- settings-live.cpp
- snap.cpp
- timezone-file.cpp
- timezone-geoclue.cpp
- timezones-live.cpp
- utils.c)
+set (SERVICE_SOURCES
+ actions.cpp
+ actions-live.cpp
+ alarm-queue-simple.cpp
+ appointment.cpp
+ clock.cpp
+ clock-live.cpp
+ date-time.cpp
+ engine-eds.cpp
+ exporter.cpp
+ formatter.cpp
+ formatter-desktop.cpp
+ locations.cpp
+ locations-settings.cpp
+ menu.cpp
+ planner-month.cpp
+ planner-range.cpp
+ planner-upcoming.cpp
+ settings-live.cpp
+ snap.cpp
+ timezone-file.cpp
+ timezone-geoclue.cpp
+ timezones-live.cpp
+ utils.c
+ wakeup-timer-mainloop.cpp)
+
+if (HAVE_UBUNTU_HW_ALARM_H)
+ set (SERVICE_SOURCES ${SERVICE_SOURCES} wakeup-timer-uha.cpp)
+endif ()
+
+add_library (${SERVICE_LIB} STATIC ${SERVICE_SOURCES})
include_directories (${CMAKE_SOURCE_DIR})
link_directories (${SERVICE_DEPS_LIBRARY_DIRS})
diff --git a/src/alarm-queue-simple.cpp b/src/alarm-queue-simple.cpp
new file mode 100644
index 0000000..fa6c0bc
--- /dev/null
+++ b/src/alarm-queue-simple.cpp
@@ -0,0 +1,159 @@
+/*
+ * 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 <datetime/alarm-queue-simple.h>
+
+#include <cmath>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+**** Public API
+***/
+
+SimpleAlarmQueue::SimpleAlarmQueue(const std::shared_ptr<Clock>& clock,
+ const std::shared_ptr<Planner>& planner,
+ const std::shared_ptr<WakeupTimer>& timer):
+ m_clock(clock),
+ m_planner(planner),
+ m_timer(timer),
+ m_datetime(clock->localtime())
+{
+ m_planner->appointments().changed().connect([this](const std::vector<Appointment>&){
+ g_debug("AlarmQueue %p calling requeue() due to appointments changed", this);
+ requeue();
+ });
+
+ m_clock->minute_changed.connect([=]{
+ const auto now = m_clock->localtime();
+ constexpr auto skew_threshold_usec = int64_t{90} * G_USEC_PER_SEC;
+ const bool clock_jumped = std::abs(now - m_datetime) > skew_threshold_usec;
+ m_datetime = now;
+ if (clock_jumped) {
+ g_debug("AlarmQueue %p calling requeue() due to clock skew", this);
+ requeue();
+ }
+ });
+
+ m_timer->timeout().connect([this](){
+ g_debug("AlarmQueue %p calling requeue() due to timeout", this);
+ requeue();
+ });
+
+ requeue();
+}
+
+SimpleAlarmQueue::~SimpleAlarmQueue()
+{
+}
+
+core::Signal<const Appointment&>& SimpleAlarmQueue::alarm_reached()
+{
+ return m_alarm_reached;
+}
+
+/***
+****
+***/
+
+void SimpleAlarmQueue::requeue()
+{
+ // kick any current alarms
+ for (auto current : find_current_alarms())
+ {
+ const std::pair<std::string,DateTime> trig {current.uid, current.begin};
+ m_triggered.insert(trig);
+ m_alarm_reached(current);
+ }
+
+ // idle until the next alarm
+ Appointment next;
+ if (find_next_alarm(next))
+ {
+ g_debug ("setting timer to wake up for next appointment '%s' at %s",
+ next.summary.c_str(),
+ next.begin.format("%F %T").c_str());
+
+ m_timer->set_wakeup_time(next.begin);
+ }
+}
+
+// find the next alarm that will kick now or in the future
+bool SimpleAlarmQueue::find_next_alarm(Appointment& setme) const
+{
+ bool found = false;
+ Appointment tmp;
+ const auto now = m_clock->localtime();
+ const auto beginning_of_minute = now.add_full (0, 0, 0, 0, 0, -now.seconds());
+
+ const auto appointments = m_planner->appointments().get();
+ g_debug ("planner has %zu appointments in it", (size_t)appointments.size());
+
+ for(const auto& walk : appointments)
+ {
+ const std::pair<std::string,DateTime> trig {walk.uid, walk.begin};
+ if (m_triggered.count(trig))
+ continue;
+
+ if (walk.begin < beginning_of_minute) // has this one already passed?
+ continue;
+
+ if (found && (tmp.begin < walk.begin)) // do we already have a better match?
+ continue;
+
+ tmp = walk;
+ found = true;
+ }
+
+ if (found)
+ setme = tmp;
+
+ return found;
+}
+
+// find the alarm(s) that should kick right now
+std::vector<Appointment> SimpleAlarmQueue::find_current_alarms() const
+{
+ std::vector<Appointment> appointments;
+
+ const auto now = m_clock->localtime();
+
+ for(const auto& walk : m_planner->appointments().get())
+ {
+ const std::pair<std::string,DateTime> trig {walk.uid, walk.begin};
+ if (m_triggered.count(trig)) // did we already use this one?
+ continue;
+ if (!DateTime::is_same_minute(now, walk.begin))
+ continue;
+
+ appointments.push_back(walk);
+ }
+
+ return appointments;
+}
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
diff --git a/src/clock-watcher.cpp b/src/clock-watcher.cpp
deleted file mode 100644
index 5da66c8..0000000
--- a/src/clock-watcher.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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 <datetime/clock-watcher.h>
-
-namespace unity {
-namespace indicator {
-namespace datetime {
-
-/***
-****
-***/
-
-ClockWatcherImpl::ClockWatcherImpl(const std::shared_ptr<Clock>& clock,
- const std::shared_ptr<UpcomingPlanner>& upcoming_planner):
- m_clock(clock),
- m_upcoming_planner(upcoming_planner)
-{
- m_clock->date_changed.connect([this](){
- const auto now = m_clock->localtime();
- g_debug("ClockWatcher %p refretching appointments due to date change: %s", this, now.format("%F %T").c_str());
- m_upcoming_planner->date().set(now);
- });
-
- m_clock->minute_changed.connect([this](){
- g_debug("ClockWatcher %p calling pulse() due to clock minute_changed", this);
- pulse();
- });
-
- m_upcoming_planner->appointments().changed().connect([this](const std::vector<Appointment>&){
- g_debug("ClockWatcher %p calling pulse() due to appointments changed", this);
- pulse();
- });
-
- pulse();
-}
-
-core::Signal<const Appointment&>& ClockWatcherImpl::alarm_reached()
-{
- return m_alarm_reached;
-}
-
-void ClockWatcherImpl::pulse()
-{
- const auto now = m_clock->localtime();
-
- for(const auto& appointment : m_upcoming_planner->appointments().get())
- {
- if (m_triggered.count(appointment.uid))
- continue;
- if (!DateTime::is_same_minute(now, appointment.begin))
- continue;
-
- m_triggered.insert(appointment.uid);
- m_alarm_reached(appointment);
- }
-}
-
-/***
-****
-***/
-
-} // namespace datetime
-} // namespace indicator
-} // namespace unity
diff --git a/src/date-time.cpp b/src/date-time.cpp
index a1c1d1b..a139ea9 100644
--- a/src/date-time.cpp
+++ b/src/date-time.cpp
@@ -174,6 +174,11 @@ bool DateTime::operator==(const DateTime& that) const
return g_date_time_compare(get(), that.get()) == 0;
}
+int64_t DateTime::operator- (const DateTime& that) const
+{
+ return g_date_time_difference(get(), that.get());
+}
+
bool DateTime::is_same_day(const DateTime& a, const DateTime& b)
{
// it's meaningless to compare uninitialized dates
diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp
index da93206..1949193 100644
--- a/src/engine-eds.cpp
+++ b/src/engine-eds.cpp
@@ -418,6 +418,14 @@ private:
auto status = ICAL_STATUS_NONE;
e_cal_component_get_status(component, &status);
+ const auto begin_dt = DateTime(begin);
+ const auto end_dt = DateTime(end);
+ g_debug ("got appointment from %s to %s, uid %s status %d",
+ begin_dt.format("%F %T").c_str(),
+ end_dt.format("%F %T").c_str(),
+ uid,
+ (int)status);
+
if ((uid != nullptr) &&
(status != ICAL_STATUS_COMPLETED) &&
(status != ICAL_STATUS_CANCELLED))
@@ -430,8 +438,8 @@ private:
if (text.value)
appointment.summary = text.value;
- appointment.begin = DateTime(begin);
- appointment.end = DateTime(end);
+ appointment.begin = begin_dt;
+ appointment.end = end_dt;
appointment.color = subtask->color;
appointment.uid = uid;
diff --git a/src/main.cpp b/src/main.cpp
index c7b35e5..079fe35 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -18,8 +18,8 @@
*/
#include <datetime/actions-live.h>
+#include <datetime/alarm-queue-simple.h>
#include <datetime/clock.h>
-#include <datetime/clock-watcher.h>
#include <datetime/engine-mock.h>
#include <datetime/engine-eds.h>
#include <datetime/exporter.h>
@@ -31,6 +31,11 @@
#include <datetime/state.h>
#include <datetime/timezone-file.h>
#include <datetime/timezones-live.h>
+#include <datetime/wakeup-timer-mainloop.h>
+
+#ifdef HAVE_UBUNTU_HW_ALARM_H
+ #include <datetime/wakeup-timer-uha.h>
+#endif
#include <glib/gi18n.h> // bindtextdomain()
#include <gio/gio.h>
@@ -42,6 +47,81 @@
using namespace unity::indicator::datetime;
+namespace
+{
+ std::shared_ptr<Engine> create_engine()
+ {
+ std::shared_ptr<Engine> engine;
+
+ // we don't show appointments in the greeter,
+ // so no need to connect to EDS there...
+ if (!g_strcmp0("lightdm", g_get_user_name()))
+ engine.reset(new MockEngine);
+ else
+ engine.reset(new EdsEngine);
+
+ return engine;
+ }
+
+ std::shared_ptr<WakeupTimer> create_wakeup_timer(const std::shared_ptr<Clock>& clock)
+ {
+ std::shared_ptr<WakeupTimer> wakeup_timer;
+
+#ifdef HAVE_UBUNTU_HW_ALARM_H
+ if (UhaWakeupTimer::is_supported()) // prefer to use the platform API
+ wakeup_timer = std::make_shared<UhaWakeupTimer>(clock);
+ else
+#endif
+ wakeup_timer = std::make_shared<MainloopWakeupTimer>(clock);
+
+ return wakeup_timer;
+ }
+
+ std::shared_ptr<State> create_state(const std::shared_ptr<Engine>& engine,
+ const std::shared_ptr<Timezone>& tz)
+ {
+ // create the live objects
+ auto live_settings = std::make_shared<LiveSettings>();
+ auto live_timezones = std::make_shared<LiveTimezones>(live_settings, TIMEZONE_FILE);
+ auto live_clock = std::make_shared<LiveClock>(live_timezones);
+
+ // create a full-month planner currently pointing to the current month
+ const auto now = live_clock->localtime();
+ auto range_planner = std::make_shared<SimpleRangePlanner>(engine, tz);
+ auto calendar_month = std::make_shared<MonthPlanner>(range_planner, now);
+
+ // create an upcoming-events planner currently pointing to the current date
+ range_planner = std::make_shared<SimpleRangePlanner>(engine, tz);
+ auto calendar_upcoming = std::make_shared<UpcomingPlanner>(range_planner, now);
+
+ // create the state
+ auto state = std::make_shared<State>();
+ state->settings = live_settings;
+ state->clock = live_clock;
+ state->locations = std::make_shared<SettingsLocations>(live_settings, live_timezones);
+ state->calendar_month = calendar_month;
+ state->calendar_upcoming = calendar_upcoming;
+ return state;
+ }
+
+ std::shared_ptr<AlarmQueue> create_simple_alarm_queue(const std::shared_ptr<Clock>& clock,
+ const std::shared_ptr<Engine>& engine,
+ const std::shared_ptr<Timezone>& tz)
+ {
+ // create an upcoming-events planner that =always= tracks the clock's date
+ auto range_planner = std::make_shared<SimpleRangePlanner>(engine, tz);
+ auto upcoming_planner = std::make_shared<UpcomingPlanner>(range_planner, clock->localtime());
+ clock->date_changed.connect([clock,upcoming_planner](){
+ const auto now = clock->localtime();
+ g_debug("refretching appointments due to date change: %s", now.format("%F %T").c_str());
+ upcoming_planner->date().set(now);
+ });
+
+ auto wakeup_timer = create_wakeup_timer(clock);
+ return std::make_shared<SimpleAlarmQueue>(clock, upcoming_planner, wakeup_timer);
+ }
+}
+
int
main(int /*argc*/, char** /*argv*/)
{
@@ -54,35 +134,16 @@ main(int /*argc*/, char** /*argv*/)
bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR);
textdomain(GETTEXT_PACKAGE);
- // we don't show appointments in the greeter,
- // so no need to connect to EDS there...
- std::shared_ptr<Engine> engine;
- if (!g_strcmp0("lightdm", g_get_user_name()))
- engine.reset(new MockEngine);
- else
- engine.reset(new EdsEngine);
-
- // build the state, actions, and menufactory
- std::shared_ptr<State> state(new State);
- std::shared_ptr<Settings> live_settings(new LiveSettings);
- std::shared_ptr<Timezones> live_timezones(new LiveTimezones(live_settings, TIMEZONE_FILE));
- std::shared_ptr<Clock> live_clock(new LiveClock(live_timezones));
- std::shared_ptr<Timezone> file_timezone(new FileTimezone(TIMEZONE_FILE));
- const auto now = live_clock->localtime();
- state->settings = live_settings;
- state->clock = live_clock;
- state->locations.reset(new SettingsLocations(live_settings, live_timezones));
- auto calendar_month = new MonthPlanner(std::shared_ptr<RangePlanner>(new SimpleRangePlanner(engine, file_timezone)), now);
- state->calendar_month.reset(calendar_month);
- state->calendar_upcoming.reset(new UpcomingPlanner(std::shared_ptr<RangePlanner>(new SimpleRangePlanner(engine, file_timezone)), now));
- std::shared_ptr<Actions> actions(new LiveActions(state));
+ auto engine = create_engine();
+ auto timezone = std::make_shared<FileTimezone>(TIMEZONE_FILE);
+ auto state = create_state(engine, timezone);
+ auto actions = std::make_shared<LiveActions>(state);
MenuFactory factory(actions, state);
- // snap decisions
- std::shared_ptr<UpcomingPlanner> upcoming_planner(new UpcomingPlanner(std::shared_ptr<RangePlanner>(new SimpleRangePlanner(engine, file_timezone)), now));
- ClockWatcherImpl clock_watcher(live_clock, upcoming_planner);
+ // set up the snap decisions
Snap snap;
- clock_watcher.alarm_reached().connect([&snap](const Appointment& appt){
+ 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){
const char* url;
if(!a.url.empty())
diff --git a/src/planner-range.cpp b/src/planner-range.cpp
index 93946e0..41b0f56 100644
--- a/src/planner-range.cpp
+++ b/src/planner-range.cpp
@@ -34,7 +34,7 @@ SimpleRangePlanner::SimpleRangePlanner(const std::shared_ptr<Engine>& engine,
m_range(std::pair<DateTime,DateTime>(DateTime::NowLocal(), DateTime::NowLocal()))
{
engine->changed().connect([this](){
- g_debug("RangePlanner %p rebuilding soon because Engine %p emitted 'changed' signal%p", this, m_engine.get());
+ g_debug("RangePlanner %p rebuilding soon because Engine %p emitted 'changed' signal", this, m_engine.get());
rebuild_soon();
});
diff --git a/src/wakeup-timer-mainloop.cpp b/src/wakeup-timer-mainloop.cpp
new file mode 100644
index 0000000..4f99c8c
--- /dev/null
+++ b/src/wakeup-timer-mainloop.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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 <datetime/wakeup-timer-mainloop.h>
+
+#include <glib.h>
+
+#include <cstdlib> // abs()
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+class MainloopWakeupTimer::Impl
+{
+
+public:
+
+ Impl(const std::shared_ptr<Clock>& clock):
+ m_clock(clock)
+ {
+ }
+
+ ~Impl()
+ {
+ cancel_timer();
+ }
+
+ void set_wakeup_time(const DateTime& d)
+ {
+ m_wakeup_time = d;
+
+ rebuild_timer();
+ }
+
+ core::Signal<>& timeout() { return m_timeout; }
+
+private:
+
+ void rebuild_timer()
+ {
+ cancel_timer();
+
+ g_return_if_fail(m_wakeup_time.is_set());
+
+ const auto now = m_clock->localtime();
+ const auto difference_usec = g_date_time_difference(m_wakeup_time.get(), now.get());
+ const guint interval_msec = std::abs(difference_usec) / 1000u;
+ g_debug("%s setting wakeup timer to kick at %s, which is in %zu seconds",
+ G_STRFUNC,
+ m_wakeup_time.format("%F %T").c_str(),
+ size_t{interval_msec/1000});
+
+ m_timeout_tag = g_timeout_add_full(G_PRIORITY_HIGH,
+ interval_msec,
+ on_timeout,
+ this,
+ nullptr);
+ }
+
+ static gboolean on_timeout(gpointer gself)
+ {
+ g_debug("%s %s", G_STRLOC, G_STRFUNC);
+ static_cast<Impl*>(gself)->on_timeout();
+ return G_SOURCE_REMOVE;
+ }
+
+ void on_timeout()
+ {
+ cancel_timer();
+ m_timeout();
+ }
+
+ void cancel_timer()
+ {
+ if (m_timeout_tag != 0)
+ {
+ g_source_remove(m_timeout_tag);
+ m_timeout_tag = 0;
+ }
+ }
+
+ core::Signal<> m_timeout;
+ const std::shared_ptr<Clock>& m_clock;
+ guint m_timeout_tag = 0;
+ DateTime m_wakeup_time;
+};
+
+/***
+****
+***/
+
+MainloopWakeupTimer::MainloopWakeupTimer(const std::shared_ptr<Clock>& clock):
+ p(new Impl(clock))
+{
+}
+
+MainloopWakeupTimer::~MainloopWakeupTimer()
+{
+}
+
+void MainloopWakeupTimer::set_wakeup_time(const DateTime& d)
+{
+ p->set_wakeup_time(d);
+}
+
+core::Signal<>& MainloopWakeupTimer::timeout()
+{
+ return p->timeout();
+}
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
diff --git a/src/wakeup-timer-uha.cpp b/src/wakeup-timer-uha.cpp
new file mode 100644
index 0000000..437eda2
--- /dev/null
+++ b/src/wakeup-timer-uha.cpp
@@ -0,0 +1,175 @@
+/*
+ * 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 <datetime/wakeup-timer-uha.h>
+
+#include <ubuntu/hardware/alarm.h>
+
+#include <glib.h>
+
+#include <unistd.h>
+
+#include <ctime> // struct timespec
+#include <mutex>
+#include <thread>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+class UhaWakeupTimer::Impl
+{
+
+public:
+
+ Impl(const std::shared_ptr<Clock>& clock):
+ m_clock(clock),
+ m_hardware_alarm(u_hardware_alarm_create())
+ {
+ // fire up a worker thread that initially just sleeps
+ set_wakeup_time_to_the_distant_future();
+ m_thread = std::move(std::thread([&](){threadfunc();}));
+ }
+
+ ~Impl()
+ {
+ // tell the worker thread to wake up and exit
+ m_yielding = true;
+ set_wakeup_time(m_clock->localtime().add_full(0,0,0,0,0,0.1));
+
+ // wait for it to happen
+ if (m_thread.joinable())
+ m_thread.join();
+
+ g_idle_remove_by_data(this);
+
+ u_hardware_alarm_unref(m_hardware_alarm);
+ }
+
+ void set_wakeup_time(const DateTime& d)
+ {
+ g_debug("%s %s", G_STRLOC, G_STRFUNC);
+ std::lock_guard<std::recursive_mutex> lg(m_mutex);
+
+ const auto wakeup_time = d.to_unix();
+
+ // simple sanity check: don't try to wait for something that's already passed
+ const auto now = m_clock->localtime().to_unix();
+ g_return_if_fail (wakeup_time >= now);
+
+ struct timespec sleep_interval;
+ sleep_interval.tv_sec = wakeup_time;
+ sleep_interval.tv_nsec = 0;
+ g_debug("%s %s setting hardware wakeup time to %s (%zu seconds from now)",
+ G_STRLOC, G_STRFUNC,
+ d.format("%F %T").c_str(),
+ (size_t)(wakeup_time - now));
+ u_hardware_alarm_set_relative_to_with_behavior(m_hardware_alarm,
+ U_HARDWARE_ALARM_TIME_REFERENCE_RTC,
+ U_HARDWARE_ALARM_SLEEP_BEHAVIOR_WAKEUP_DEVICE,
+ &sleep_interval);
+ }
+
+ core::Signal<>& timeout() { return m_timeout; }
+
+private:
+
+ void set_wakeup_time_to_the_distant_future()
+ {
+ const auto tomorrow = m_clock->localtime().add_full(0,0,1,0,0,0);
+ set_wakeup_time(tomorrow);
+ }
+
+ static gboolean kick_idle (gpointer gself)
+ {
+ static_cast<Impl*>(gself)->m_timeout();
+
+ return G_SOURCE_REMOVE;
+ }
+
+ void threadfunc()
+ {
+ while (!m_yielding)
+ {
+ // wait for the next hw alarm
+ UHardwareAlarmWaitResult wait_result;
+ g_debug ("calling wait_for_next_alarm");
+ auto rc = u_hardware_alarm_wait_for_next_alarm(m_hardware_alarm, &wait_result);
+ g_return_if_fail (rc == U_STATUS_SUCCESS);
+
+ // set a long wakeup interval for the next iteration of the loop.
+ // if there's another Appointment queued up by the Planner,
+ // our timeout() listener will call set_wakeup_time() to set the
+ // real wakeup interval.
+ set_wakeup_time_to_the_distant_future();
+
+ // delegate the kick back to the main thread
+ g_idle_add (kick_idle, this);
+ }
+ }
+
+ core::Signal<> m_timeout;
+ std::recursive_mutex m_mutex;
+ bool m_yielding = false;
+ const std::shared_ptr<Clock>& m_clock;
+ UHardwareAlarm m_hardware_alarm = nullptr;
+ std::thread m_thread;
+};
+
+/***
+****
+***/
+
+UhaWakeupTimer::UhaWakeupTimer(const std::shared_ptr<Clock>& clock):
+ p(new Impl(clock))
+{
+}
+
+UhaWakeupTimer::~UhaWakeupTimer()
+{
+}
+
+bool UhaWakeupTimer::is_supported()
+{
+ auto hw_alarm = u_hardware_alarm_create();
+ g_debug ("%s hardware alarm %p", G_STRFUNC, hw_alarm);
+ return hw_alarm != nullptr;
+}
+
+void UhaWakeupTimer::set_wakeup_time(const DateTime& d)
+{
+ p->set_wakeup_time(d);
+}
+
+core::Signal<>& UhaWakeupTimer::timeout()
+{
+ return p->timeout();
+}
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity