diff options
author | Charles Kerr <charles.kerr@canonical.com> | 2014-04-24 22:43:15 -0500 |
---|---|---|
committer | Charles Kerr <charles.kerr@canonical.com> | 2014-04-24 22:43:15 -0500 |
commit | 27b500307fe8035defb510a0d694fa1fa7fa3950 (patch) | |
tree | b5108a91beabf803c4ea4d71a99129718c5a7026 | |
parent | 4008e0a91ebdc1cf5c5715a122fed318124c8802 (diff) | |
parent | a396e6af3cd16530202f6cbecbd45c7a3f6ac893 (diff) | |
download | ayatana-indicator-datetime-27b500307fe8035defb510a0d694fa1fa7fa3950.tar.gz ayatana-indicator-datetime-27b500307fe8035defb510a0d694fa1fa7fa3950.tar.bz2 ayatana-indicator-datetime-27b500307fe8035defb510a0d694fa1fa7fa3950.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.
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | include/datetime/alarm-queue-simple.h | 63 | ||||
-rw-r--r-- | include/datetime/alarm-queue.h (renamed from include/datetime/clock-watcher.h) | 40 | ||||
-rw-r--r-- | include/datetime/date-time.h | 3 | ||||
-rw-r--r-- | include/datetime/planner-range.h | 2 | ||||
-rw-r--r-- | include/datetime/wakeup-timer-mainloop.h | 62 | ||||
-rw-r--r-- | include/datetime/wakeup-timer-uha.h | 64 | ||||
-rw-r--r-- | include/datetime/wakeup-timer.h | 55 | ||||
-rw-r--r-- | src/CMakeLists.txt | 6 | ||||
-rw-r--r-- | src/alarm-queue-simple.cpp | 153 | ||||
-rw-r--r-- | src/clock-watcher.cpp | 81 | ||||
-rw-r--r-- | src/date-time.cpp | 5 | ||||
-rw-r--r-- | src/main.cpp | 110 | ||||
-rw-r--r-- | src/planner-range.cpp | 2 | ||||
-rw-r--r-- | src/wakeup-timer-mainloop.cpp | 136 | ||||
-rw-r--r-- | src/wakeup-timer-uha.cpp | 167 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/test-alarm-queue.cpp (renamed from tests/test-clock-watcher.cpp) | 32 |
18 files changed, 829 insertions, 155 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 378dc03..9e068f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,7 @@ pkg_check_modules (SERVICE_DEPS REQUIRED url-dispatcher-1>=1 properties-cpp>=0.0.1) include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS}) +set (SERVICE_DEPS_LIBRARIES -lubuntu_platform_hardware_api ${SERVICE_DEPS_LIBRARIES}) ## ## custom targets diff --git a/include/datetime/alarm-queue-simple.h b/include/datetime/alarm-queue-simple.h new file mode 100644 index 0000000..8461227 --- /dev/null +++ b/include/datetime/alarm-queue-simple.h @@ -0,0 +1,63 @@ +/* + * 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 INDICATOR_DATETIME_ALARM_QUEUE_SIMPLE_H +#define INDICATOR_DATETIME_ALARM_QUEUE_SIMPLE_H + +#include <datetime/alarm-queue.h> +#include <datetime/clock.h> +#include <datetime/planner.h> +#include <datetime/wakeup-timer.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief A #AlarmQueue implementation + */ +class SimpleAlarmQueue: public AlarmQueue +{ +public: + SimpleAlarmQueue(const std::shared_ptr<Clock>& clock, + const std::shared_ptr<Planner>& upcoming_planner, + const std::shared_ptr<WakeupTimer>& timer); + ~SimpleAlarmQueue(); + core::Signal<const Appointment&>& alarm_reached(); + +private: + void requeue(); + bool find_next_alarm(Appointment& setme) const; + std::vector<Appointment> find_current_alarms() const; + void check_alarms(); + + std::set<std::string> m_triggered; + const std::shared_ptr<Clock> m_clock; + const std::shared_ptr<Planner> m_planner; + const std::shared_ptr<WakeupTimer> m_timer; + core::Signal<const Appointment&> m_alarm_reached; + DateTime m_time; +}; + + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_ALARM_QUEUE_H diff --git a/include/datetime/clock-watcher.h b/include/datetime/alarm-queue.h index 90bbb63..ea51957 100644 --- a/include/datetime/clock-watcher.h +++ b/include/datetime/alarm-queue.h @@ -17,12 +17,10 @@ * Charles Kerr <charles.kerr@canonical.com> */ -#ifndef INDICATOR_DATETIME_CLOCK_WATCHER_H -#define INDICATOR_DATETIME_CLOCK_WATCHER_H +#ifndef INDICATOR_DATETIME_ALARM_QUEUE_H +#define INDICATOR_DATETIME_ALARM_QUEUE_H #include <datetime/appointment.h> -#include <datetime/clock.h> -#include <datetime/planner-upcoming.h> #include <core/signal.h> @@ -34,42 +32,28 @@ namespace unity { namespace indicator { namespace datetime { +/*** +**** +***/ /** * \brief Watches the clock and appointments to notify when an * appointment's time is reached. */ -class ClockWatcher +class AlarmQueue { public: - ClockWatcher() =default; - virtual ~ClockWatcher() =default; + AlarmQueue() =default; + virtual ~AlarmQueue() =default; virtual core::Signal<const Appointment&>& alarm_reached() = 0; }; - -/** - * \brief A #ClockWatcher implementation - */ -class ClockWatcherImpl: public ClockWatcher -{ -public: - ClockWatcherImpl(const std::shared_ptr<Clock>& clock, - const std::shared_ptr<UpcomingPlanner>& upcoming_planner); - ~ClockWatcherImpl() =default; - core::Signal<const Appointment&>& alarm_reached(); - -private: - void pulse(); - std::set<std::string> m_triggered; - const std::shared_ptr<Clock> m_clock; - const std::shared_ptr<UpcomingPlanner> m_upcoming_planner; - core::Signal<const Appointment&> m_alarm_reached; -}; - +/*** +**** +***/ } // namespace datetime } // namespace indicator } // namespace unity -#endif // INDICATOR_DATETIME_CLOCK_WATCHER_H +#endif // INDICATOR_DATETIME_ALARM_QUEUE_H diff --git a/include/datetime/date-time.h b/include/datetime/date-time.h index f861c2e..490ed40 100644 --- a/include/datetime/date-time.h +++ b/include/datetime/date-time.h @@ -61,10 +61,13 @@ public: bool operator<=(const DateTime& that) const; bool operator!=(const DateTime& that) const; bool operator==(const DateTime& that) const; + int64_t operator- (const DateTime& that) const; static bool is_same_day(const DateTime& a, const DateTime& b); static bool is_same_minute(const DateTime& a, const DateTime& b); + bool is_set() const { return m_dt.get() != nullptr; } + private: std::shared_ptr<GDateTime> m_dt; }; diff --git a/include/datetime/planner-range.h b/include/datetime/planner-range.h index 25334a6..2ee2fa0 100644 --- a/include/datetime/planner-range.h +++ b/include/datetime/planner-range.h @@ -53,7 +53,7 @@ class SimpleRangePlanner: public RangePlanner { public: SimpleRangePlanner(const std::shared_ptr<Engine>& engine, - const std::shared_ptr<Timezone>& timezone); + const std::shared_ptr<Timezone>& timezone); virtual ~SimpleRangePlanner(); core::Property<std::vector<Appointment>>& appointments(); diff --git a/include/datetime/wakeup-timer-mainloop.h b/include/datetime/wakeup-timer-mainloop.h new file mode 100644 index 0000000..480d728 --- /dev/null +++ b/include/datetime/wakeup-timer-mainloop.h @@ -0,0 +1,62 @@ +/* + * 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 INDICATOR_DATETIME_WAKEUP_TIMER_MAINLOOP_H +#define INDICATOR_DATETIME_WAKEUP_TIMER_MAINLOOP_H + +#include <datetime/clock.h> +#include <datetime/wakeup-timer.h> + +#include <memory> // std::unique_ptr, std::shared_ptr + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +/** + * \brief a WakeupTimer implemented with g_timeout_add() + */ +class MainloopWakeupTimer: public WakeupTimer +{ +public: + MainloopWakeupTimer(const std::shared_ptr<Clock>&); + ~MainloopWakeupTimer(); + void set_wakeup_time (const DateTime&); + core::Signal<>& timeout(); + +private: + MainloopWakeupTimer(const MainloopWakeupTimer&) =delete; + MainloopWakeupTimer& operator= (const MainloopWakeupTimer&) =delete; + class Impl; + std::unique_ptr<Impl> p; +}; + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_WAKEUP_TIMER_MAINLOOP_H diff --git a/include/datetime/wakeup-timer-uha.h b/include/datetime/wakeup-timer-uha.h new file mode 100644 index 0000000..093548b --- /dev/null +++ b/include/datetime/wakeup-timer-uha.h @@ -0,0 +1,64 @@ +/* + * 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 INDICATOR_DATETIME_WAKEUP_TIMER_UHA_H +#define INDICATOR_DATETIME_WAKEUP_TIMER_UHA_H + +#include <datetime/clock.h> +#include <datetime/wakeup-timer.h> + +#include <memory> // std::unique_ptr, std::shared_ptr + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +/** + * \brief a WakeupTimer implemented the UbuntuHardwareAlarm API + */ +class UhaWakeupTimer: public WakeupTimer +{ +public: + UhaWakeupTimer(const std::shared_ptr<Clock>&); + ~UhaWakeupTimer(); + void set_wakeup_time (const DateTime&); + core::Signal<>& timeout(); + + static bool is_supported(); + +private: + UhaWakeupTimer(const UhaWakeupTimer&) =delete; + UhaWakeupTimer& operator= (const UhaWakeupTimer&) =delete; + class Impl; + std::unique_ptr<Impl> p; +}; + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_WAKEUP_TIMER_UHA_H diff --git a/include/datetime/wakeup-timer.h b/include/datetime/wakeup-timer.h new file mode 100644 index 0000000..0a9923c --- /dev/null +++ b/include/datetime/wakeup-timer.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 INDICATOR_DATETIME_WAKEUP_TIMER_H +#define INDICATOR_DATETIME_WAKEUP_TIMER_H + +#include <datetime/date-time.h> + +#include <core/signal.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +/** + * \brief A one-shot timer that emits a signal when its timeout is reached. + */ +class WakeupTimer +{ +public: + WakeupTimer() =default; + virtual ~WakeupTimer() =default; + virtual void set_wakeup_time (const DateTime&) =0; + virtual core::Signal<>& timeout() = 0; +}; + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_WAKEUP_TIMER_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9bc22f2..924e538 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,10 +10,10 @@ add_definitions (-DTIMEZONE_FILE="/etc/timezone" add_library (${SERVICE_LIB} STATIC actions.cpp actions-live.cpp + alarm-queue-simple.cpp appointment.cpp clock.cpp clock-live.cpp - clock-watcher.cpp date-time.cpp engine-eds.cpp exporter.cpp @@ -30,7 +30,9 @@ add_library (${SERVICE_LIB} STATIC timezone-file.cpp timezone-geoclue.cpp timezones-live.cpp - utils.c) + utils.c + wakeup-timer-uha.cpp + wakeup-timer-mainloop.cpp) 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..1c871bf --- /dev/null +++ b/src/alarm-queue-simple.cpp @@ -0,0 +1,153 @@ +/* + * 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_time(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_time) > skew_threshold_usec; + m_time = 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()) + { + m_triggered.insert(current.uid); + 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()); + + for(const auto& walk : m_planner->appointments().get()) + { + if (m_triggered.count(walk.uid)) // did we already use this one? + 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()) + { + if (m_triggered.count(walk.uid)) // 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/main.cpp b/src/main.cpp index c7b35e5..e35c5da 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,8 @@ #include <datetime/state.h> #include <datetime/timezone-file.h> #include <datetime/timezones-live.h> +#include <datetime/wakeup-timer-mainloop.h> +#include <datetime/wakeup-timer-uha.h> #include <glib/gi18n.h> // bindtextdomain() #include <gio/gio.h> @@ -42,6 +44,79 @@ 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; + + if (UhaWakeupTimer::is_supported()) // prefer to use the platform API + wakeup_timer = std::make_shared<UhaWakeupTimer>(clock); + else + 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 +129,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..dfef037 --- /dev/null +++ b/src/wakeup-timer-mainloop.cpp @@ -0,0 +1,136 @@ +/* + * 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> + +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 = (guint)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..b634f52 --- /dev/null +++ b/src/wakeup-timer-uha.cpp @@ -0,0 +1,167 @@ +/* + * 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 diff_usec = d - m_clock->localtime(); + struct timespec ts; + ts.tv_sec = diff_usec / G_USEC_PER_SEC; + ts.tv_nsec = (diff_usec % G_USEC_PER_SEC) * 1000; + g_debug("%s setting hardware wakeup time to %s (%zu seconds from now)", + G_STRFUNC, (size_t)ts.tv_sec, d.format("%F %T").c_str()); + u_hardware_alarm_set_relative_to_with_behavior(m_hardware_alarm, + U_HARDWARE_ALARM_TIME_REFERENCE_NOW, + U_HARDWARE_ALARM_SLEEP_BEHAVIOR_WAKEUP_DEVICE, + &ts); + } + + core::Signal<>& timeout() { return m_timeout; } + +private: + + void set_wakeup_time_to_the_distant_future() + { + const auto next_year = m_clock->localtime().add_full(1,0,0,0,0,0); + set_wakeup_time(next_year); + } + + static gboolean kick_idle (gpointer gself) + { + static_cast<Impl*>(gself)->timeout(); + + return G_SOURCE_REMOVE; + } + + void threadfunc() + { + while (!m_yielding) + { + // wait for the next hw alarm + UHardwareAlarmWaitResult wait_result; + 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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7d590c9..fe6d7eb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -41,8 +41,8 @@ function(add_test_by_name name) target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS}) endfunction() add_test_by_name(test-actions) +add_test_by_name(test-alarm-queue) add_test_by_name(test-clock) -add_test_by_name(test-clock-watcher) add_test_by_name(test-exporter) add_test_by_name(test-formatter) add_test_by_name(test-live-actions) diff --git a/tests/test-clock-watcher.cpp b/tests/test-alarm-queue.cpp index 2425fe8..d2de8ac 100644 --- a/tests/test-clock-watcher.cpp +++ b/tests/test-alarm-queue.cpp @@ -17,7 +17,8 @@ * Charles Kerr <charles.kerr@canonical.com> */ -#include <datetime/clock-watcher.h> +#include <datetime/alarm-queue-simple.h> +#include <datetime/wakeup-timer-mainloop.h> #include <gtest/gtest.h> @@ -25,7 +26,7 @@ using namespace unity::indicator::datetime; -class ClockWatcherFixture: public StateFixture +class AlarmQueueFixture: public StateFixture { private: @@ -34,7 +35,8 @@ private: protected: std::vector<std::string> m_triggered; - std::unique_ptr<ClockWatcher> m_watcher; + std::shared_ptr<WakeupTimer> m_wakeup_timer; + std::unique_ptr<AlarmQueue> m_watcher; std::shared_ptr<RangePlanner> m_range_planner; std::shared_ptr<UpcomingPlanner> m_upcoming; @@ -42,9 +44,10 @@ protected: { super::SetUp(); + m_wakeup_timer.reset(new MainloopWakeupTimer(m_state->clock)); m_range_planner.reset(new MockRangePlanner); m_upcoming.reset(new UpcomingPlanner(m_range_planner, m_state->clock->localtime())); - m_watcher.reset(new ClockWatcherImpl(m_state->clock, m_upcoming)); + m_watcher.reset(new SimpleAlarmQueue(m_state->clock, m_upcoming, m_wakeup_timer)); m_watcher->alarm_reached().connect([this](const Appointment& appt){ m_triggered.push_back(appt.uid); }); @@ -108,7 +111,7 @@ protected: **** ***/ -TEST_F(ClockWatcherFixture, AppointmentsChanged) +TEST_F(AlarmQueueFixture, AppointmentsChanged) { // Add some appointments to the planner. // One of these matches our state's localtime, so that should get triggered. @@ -117,12 +120,12 @@ TEST_F(ClockWatcherFixture, AppointmentsChanged) m_range_planner->appointments().set(a); // Confirm that it got fired - EXPECT_EQ(1, m_triggered.size()); + ASSERT_EQ(1, m_triggered.size()); EXPECT_EQ(a[0].uid, m_triggered[0]); } -TEST_F(ClockWatcherFixture, TimeChanged) +TEST_F(AlarmQueueFixture, TimeChanged) { // Add some appointments to the planner. // Neither of these match the state's localtime, so nothing should be triggered. @@ -132,26 +135,27 @@ TEST_F(ClockWatcherFixture, TimeChanged) // Set the state's clock to a time that matches one of the appointments(). // That appointment should get triggered. +g_message ("%s setting clock to %s", G_STRLOC, a[1].begin.format("%F %T").c_str()); m_mock_state->mock_clock->set_localtime(a[1].begin); - EXPECT_EQ(1, m_triggered.size()); + ASSERT_EQ(1, m_triggered.size()); EXPECT_EQ(a[1].uid, m_triggered[0]); } -TEST_F(ClockWatcherFixture, MoreThanOne) +TEST_F(AlarmQueueFixture, MoreThanOne) { const auto now = m_state->clock->localtime(); std::vector<Appointment> a = build_some_appointments(); a[0].begin = a[1].begin = now; m_range_planner->appointments().set(a); - EXPECT_EQ(2, m_triggered.size()); + ASSERT_EQ(2, m_triggered.size()); EXPECT_EQ(a[0].uid, m_triggered[0]); EXPECT_EQ(a[1].uid, m_triggered[1]); } -TEST_F(ClockWatcherFixture, NoDuplicates) +TEST_F(AlarmQueueFixture, NoDuplicates) { // Setup: add an appointment that gets triggered. const auto now = m_state->clock->localtime(); @@ -160,13 +164,13 @@ TEST_F(ClockWatcherFixture, NoDuplicates) a.push_back(appointments[0]); a[0].begin = now; m_range_planner->appointments().set(a); - EXPECT_EQ(1, m_triggered.size()); + ASSERT_EQ(1, m_triggered.size()); EXPECT_EQ(a[0].uid, m_triggered[0]); // Now change the appointment vector by adding one to it. - // Confirm that the ClockWatcher doesn't re-trigger a[0] + // Confirm that the AlarmQueue doesn't re-trigger a[0] a.push_back(appointments[1]); m_range_planner->appointments().set(a); - EXPECT_EQ(1, m_triggered.size()); + ASSERT_EQ(1, m_triggered.size()); EXPECT_EQ(a[0].uid, m_triggered[0]); } |