aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Tari <robert@tari.in>2021-08-30 01:26:19 +0200
committerRobert Tari <robert@tari.in>2021-08-30 01:26:19 +0200
commit22e66866c7b17fc655479ca911269b86cb80a744 (patch)
treee0aa6e6a8f50fd8451e03efc17b89d8c8c3de781
parent1f8263dedf9b7e6f9e06492bd69f2436e36171a2 (diff)
parent38e5efecbb3154a83a70c1c762802ec7927b3caa (diff)
downloadayatana-indicator-datetime-22e66866c7b17fc655479ca911269b86cb80a744.tar.gz
ayatana-indicator-datetime-22e66866c7b17fc655479ca911269b86cb80a744.tar.bz2
ayatana-indicator-datetime-22e66866c7b17fc655479ca911269b86cb80a744.zip
Merge branch 'tari01-pr/ubports-patches'
Attributes GH PR #46: https://github.com/AyatanaIndicators/ayatana-indicator-datetime/pull/46
-rw-r--r--.build.yml23
-rw-r--r--.travis.yml2
-rw-r--r--CMakeLists.txt56
-rw-r--r--README.md2
-rw-r--r--cmake/FindIntltool.cmake23
-rw-r--r--cmake/GCov.cmake51
-rw-r--r--cmake/Translations.cmake37
-rw-r--r--data/CMakeLists.txt2
-rw-r--r--data/ayatana-indicator-datetime.desktop.in1
-rw-r--r--debian/control2
-rw-r--r--include/datetime/actions-live.h17
-rw-r--r--include/datetime/actions.h12
-rw-r--r--include/datetime/appointment.h1
-rw-r--r--include/datetime/date-time.h2
-rw-r--r--include/datetime/dbus-shared.h40
-rw-r--r--include/datetime/engine-eds.h3
-rw-r--r--include/datetime/menu.h6
-rw-r--r--include/datetime/settings-live.h16
-rw-r--r--include/datetime/settings-shared.h10
-rw-r--r--include/datetime/settings.h6
-rw-r--r--include/datetime/snap.h13
-rw-r--r--include/datetime/timezone-timedated.h8
-rw-r--r--include/datetime/timezones-live.h5
-rw-r--r--include/notifications/awake.h4
-rw-r--r--include/notifications/dbus-shared.h6
-rw-r--r--include/notifications/haptic.h2
-rw-r--r--include/notifications/notifications.h15
-rw-r--r--m4/gcov.m486
-rw-r--r--m4/gtest.m463
-rw-r--r--po/CMakeLists.txt9
-rw-r--r--po/Makevars7
-rw-r--r--src/CMakeLists.txt8
-rw-r--r--src/actions-live.cpp138
-rw-r--r--src/actions.cpp73
-rw-r--r--src/awake.cpp184
-rw-r--r--src/clock.cpp2
-rw-r--r--src/date-time.cpp10
-rw-r--r--src/engine-eds.cpp5
-rw-r--r--src/haptic.cpp20
-rw-r--r--src/main.cpp38
-rw-r--r--src/menu.cpp116
-rw-r--r--src/myself.cpp2
-rw-r--r--src/notifications.cpp207
-rw-r--r--src/planner-snooze.cpp4
-rw-r--r--src/planner-upcoming.cpp2
-rw-r--r--src/settings-live.cpp135
-rw-r--r--src/snap.cpp142
-rw-r--r--src/timezone-timedated.cpp236
-rw-r--r--src/timezones-live.cpp11
-rw-r--r--tests/CMakeLists.txt41
-rw-r--r--tests/accounts.dbbin0 -> 19456 bytes
-rw-r--r--tests/actions-mock.h42
-rw-r--r--tests/glib-fixture.h120
-rw-r--r--tests/libdbusmock-fixture.h148
-rw-r--r--tests/manual-test-snap.cpp21
-rw-r--r--tests/notification-fixture.h (renamed from tests/test-snap.cpp)307
-rw-r--r--tests/print-to.h10
-rwxr-xr-xtests/run-eds-ics-test.sh8
-rw-r--r--tests/test-actions.cpp20
-rw-r--r--tests/test-eds-ics-all-day-events.cpp3
-rw-r--r--tests/test-eds-ics-missing-trigger.cpp7
-rw-r--r--tests/test-eds-ics-non-attending-alarms.cpp89
-rw-r--r--tests/test-eds-ics-non-attending-alarms.ics.in53
-rw-r--r--tests/test-eds-ics-non-selected-source.cpp94
-rw-r--r--tests/test-eds-ics-non-selected-source.ics.in28
-rw-r--r--tests/test-eds-ics-nonrepeating-events.cpp5
-rw-r--r--tests/test-eds-ics-repeating-events-with-individual-change.cpp110
-rw-r--r--tests/test-eds-ics-repeating-events-with-individual-change.ics.in969
-rw-r--r--tests/test-eds-ics-repeating-events.cpp5
-rw-r--r--tests/test-eds-ics-repeating-valarms.cpp3
-rw-r--r--tests/test-eds-ics-tzids-2.cpp3
-rw-r--r--tests/test-eds-ics-tzids-utc.cpp98
-rw-r--r--tests/test-eds-ics-tzids-utc.ics.in15
-rw-r--r--tests/test-eds-ics-tzids.cpp3
-rw-r--r--tests/test-formatter.cpp11
-rw-r--r--tests/test-live-actions.cpp124
-rw-r--r--tests/test-menu-appointments.cpp263
-rw-r--r--tests/test-notification-response.cpp144
-rw-r--r--tests/test-notification.cpp198
-rw-r--r--tests/test-settings.cpp92
-rw-r--r--tests/test-sound.cpp182
-rw-r--r--tests/test-timezone-timedated.cpp131
-rw-r--r--tests/timedated-fixture.h331
-rwxr-xr-xtrim-lcov.py57
84 files changed, 3928 insertions, 1670 deletions
diff --git a/.build.yml b/.build.yml
index 553e339..a348dcc 100644
--- a/.build.yml
+++ b/.build.yml
@@ -25,6 +25,9 @@ requires:
- evolution-data-server
- gsettings-desktop-schemas
- properties-cpp
+# - ayatana-indicator-messages
+ - gtk-doc-tools
+ - libaccountsservice
debian:
# Useful URL: https://salsa.debian.org/debian-ayatana-team/ayatana-ido
@@ -51,6 +54,10 @@ requires:
- libical-dev
- libedataserver1.2-dev
- libproperties-cpp-dev
+# - libmessaging-menu-dev
+ - gtk-doc-tools
+ - libaccountsservice-dev
+ - liblomiri-url-dispatcher-dev
# for the test harness:
- libgtest-dev
- libdbustest1-dev
@@ -87,8 +94,10 @@ requires:
- libecal2.0-dev
- libical-dev
- libedataserver1.2-dev
- - liblomiri-url-dispatcher-dev
- libproperties-cpp-dev
+# - libmessaging-menu-dev
+ - gtk-doc-tools
+ - libaccountsservice-dev
ubuntu:focal:
- autopoint
@@ -113,6 +122,9 @@ requires:
- libical-dev
- libedataserver1.2-dev
- libproperties-cpp-dev
+# - libmessaging-menu-dev
+ - gtk-doc-tools
+ - libaccountsservice-dev
variables:
- 'CHECKERS="
@@ -141,6 +153,15 @@ before_scripts:
- cmake . -DCMAKE_INSTALL_PREFIX=/usr
- make
- make install
+ - cd ${START_DIR}
+ - if [ ! -d ayatana-indicator-messages-build ]; then
+ - git clone --depth 1 https://github.com/AyatanaIndicators/ayatana-indicator-messages.git ayatana-indicator-messages-build
+ - fi
+ - cd ayatana-indicator-messages-build
+ - NOCONFIGURE=1 ./autogen.sh
+ - ./configure --disable-tests --prefix=/usr
+ - make
+ - make install
build_scripts:
- if [ ${DISTRO_NAME} == "debian" ];then
diff --git a/.travis.yml b/.travis.yml
index 5382638..51db47f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,7 +18,7 @@ addons:
before_install:
# let's use the MATE project's docker build script...
- - curl -Ls -o docker-build https://github.com/AyatanaIndicators/ayatana-dev-scripts/raw/master/travis/docker-build
+ - curl -Ls -o docker-build https://github.com/AyatanaIndicators/ayatana-dev-scripts/raw/main/travis/docker-build
- chmod +x docker-build
install:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 33781a0..8e682ea 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -66,16 +66,15 @@ pkg_check_modules (SERVICE_DEPS REQUIRED
gstreamer-1.0>=1.2
libnotify>=0.7.6
properties-cpp>=0.0.1
- libaccounts-glib>=1.18)
+ libaccounts-glib>=1.18
+ messaging-menu>=0.8.2)
include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS})
-set(URL_DISPATCHER_REQUIRED_VERSION 0)
-pkg_check_modules(URLDISPATCHER
- lomiri-url-dispatcher>=${URL_DISPATCHER_REQUIRED_VERSION}
-)
+# lomiri-url-dispatcher
+
+pkg_check_modules(URLDISPATCHER lomiri-url-dispatcher>=0)
include_directories(${URLDISPATCHER_INCLUDE_DIRS})
-# url-dispatcher support is optional...
if(URLDISPATCHER_FOUND)
add_definitions( -DHAS_URLDISPATCHER )
endif()
@@ -96,9 +95,6 @@ add_custom_target (dist
COMMAND bzr export --root=${ARCHIVE_NAME} ${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar.gz
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-add_custom_target (clean-coverage
- COMMAND find ${CMAKE_BINARY_DIR} -name '*.gcda' | xargs rm -f)
-
add_custom_target (cppcheck COMMAND cppcheck --enable=all -q --error-exitcode=2 --inline-suppr
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/tests)
@@ -123,32 +119,32 @@ endif()
include_directories (${CMAKE_CURRENT_SOURCE_DIR}/include)
include_directories (${CMAKE_CURRENT_BINARY_DIR}/include)
-# testing & coverage
-if (${ENABLE_TESTS})
- pkg_check_modules (DBUSTEST REQUIRED dbustest-1>=14.04.0)
-
- if (EXISTS /usr/src/googletest/src)
- set (GTEST_SOURCE_DIR /usr/src/googletest/src)
- else ()
- set (GTEST_SOURCE_DIR /usr/src/gtest/src)
- endif ()
-
- set (GTEST_INCLUDE_DIR ${GTEST_SOURCE_DIR}/..)
- set (GTEST_LIBS -lpthread)
- enable_testing ()
- if (${ENABLE_COVERAGE})
- include(GCov)
- endif ()
-endif ()
-
# actually build things
add_subdirectory(include)
add_subdirectory(src)
add_subdirectory(data)
add_subdirectory(po)
-if (${ENABLE_TESTS})
- add_subdirectory(tests)
-endif ()
+
+# testing & coverage
+if (ENABLE_TESTS)
+
+ include(CTest)
+ pkg_check_modules (DBUSTEST REQUIRED dbustest-1>=14.04.0)
+ enable_testing()
+ add_subdirectory(tests)
+
+ if (ENABLE_COVERAGE)
+
+ find_package(CoverageReport)
+ ENABLE_COVERAGE_REPORT(
+ TARGETS indicatordatetimeservice ayatana-indicator-datetime-service
+ TESTS ${COVERAGE_TEST_TARGETS}
+ FILTER /usr/include ${CMAKE_BINARY_DIR}/*
+ )
+
+ endif()
+
+endif()
# Display config info
diff --git a/README.md b/README.md
index ce7c403..96ba2ba 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Ayatana System Indicator &mdash; DateTime [![Build Status](https://travis-ci.com/AyatanaIndicators/ayatana-indicator-datetime.svg)](https://travis-ci.com/AyatanaIndicators/ayatana-indicator-datetime)
+# Ayatana System Indicator &mdash; DateTime [![Build Status](https://api.travis-ci.com/AyatanaIndicators/ayatana-indicator-datetime.svg)](https://travis-ci.com/github/AyatanaIndicators/ayatana-indicator-datetime)
## About Ayatana Indicators
diff --git a/cmake/FindIntltool.cmake b/cmake/FindIntltool.cmake
deleted file mode 100644
index 69ffab9..0000000
--- a/cmake/FindIntltool.cmake
+++ /dev/null
@@ -1,23 +0,0 @@
-# FindIntltool.cmake
-#
-# Jim Nelson <jim@yorba.org>
-# Copyright 2012 Yorba Foundation
-
-find_program (INTLTOOL_MERGE_EXECUTABLE intltool-merge)
-
-if (INTLTOOL_MERGE_EXECUTABLE)
- set (INTLTOOL_MERGE_FOUND TRUE)
-else (INTLTOOL_MERGE_EXECUTABLE)
- set (INTLTOOL_MERGE_FOUND FALSE)
-endif (INTLTOOL_MERGE_EXECUTABLE)
-
-if (INTLTOOL_MERGE_FOUND)
- macro (INTLTOOL_MERGE_DESKTOP desktop_id po_dir)
- add_custom_target (geary.desktop ALL
- ${INTLTOOL_MERGE_EXECUTABLE} --desktop-style ${CMAKE_SOURCE_DIR}/${po_dir}
- ${CMAKE_CURRENT_SOURCE_DIR}/${desktop_id}.desktop.in ${desktop_id}.desktop
- )
- install (FILES ${CMAKE_CURRENT_BINARY_DIR}/geary.desktop DESTINATION /usr/share/applications)
- endmacro (INTLTOOL_MERGE_DESKTOP desktop_id po_dir)
-endif (INTLTOOL_MERGE_FOUND)
-
diff --git a/cmake/GCov.cmake b/cmake/GCov.cmake
deleted file mode 100644
index 9a5086e..0000000
--- a/cmake/GCov.cmake
+++ /dev/null
@@ -1,51 +0,0 @@
-if (CMAKE_BUILD_TYPE MATCHES coverage)
- set(GCOV_FLAGS "${GCOV_FLAGS} --coverage")
- set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GCOV_FLAGS}")
- set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${GCOV_FLAGS}")
- set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GCOV_FLAGS}")
- set(GCOV_LIBS ${GCOV_LIBS} gcov)
-
- find_program(GCOVR_EXECUTABLE gcovr HINTS ${GCOVR_ROOT} "${GCOVR_ROOT}/bin")
- if (NOT GCOVR_EXECUTABLE)
- message(STATUS "Gcovr binary was not found, can not generate XML coverage info.")
- else ()
- message(STATUS "Gcovr found, can generate XML coverage info.")
- add_custom_target (coverage-xml
- WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
- COMMAND "${GCOVR_EXECUTABLE}" --exclude="test.*" -x -r "${CMAKE_SOURCE_DIR}"
- --object-directory=${CMAKE_BINARY_DIR} -o coverage.xml)
- endif()
-
- find_program(LCOV_EXECUTABLE lcov HINTS ${LCOV_ROOT} "${GCOVR_ROOT}/bin")
- find_program(GENHTML_EXECUTABLE genhtml HINTS ${GENHTML_ROOT})
- if (NOT LCOV_EXECUTABLE)
- message(STATUS "Lcov binary was not found, can not generate HTML coverage info.")
- else ()
- if(NOT GENHTML_EXECUTABLE)
- message(STATUS "Genthml binary not found, can not generate HTML coverage info.")
- else()
- message(STATUS "Lcov and genhtml found, can generate HTML coverage info.")
- add_custom_target (coverage-html
- WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
- COMMAND "${CMAKE_CTEST_COMMAND}" --force-new-ctest-process --verbose
- COMMAND "${LCOV_EXECUTABLE}" --directory ${CMAKE_BINARY_DIR} --capture | ${CMAKE_SOURCE_DIR}/trim-lcov.py > dconf-lcov.info
- COMMAND "${LCOV_EXECUTABLE}" -r dconf-lcov.info /usr/include/\\* -o nosys-lcov.info
- COMMAND LANG=C "${GENHTML_EXECUTABLE}" --prefix ${CMAKE_BINARY_DIR} --output-directory lcov-html --legend --show-details nosys-lcov.info
- COMMAND ${CMAKE_COMMAND} -E echo ""
- COMMAND ${CMAKE_COMMAND} -E echo "file://${CMAKE_BINARY_DIR}/lcov-html/index.html"
- COMMAND ${CMAKE_COMMAND} -E echo "")
- #COMMAND "${LCOV_EXECUTABLE}" --directory ${CMAKE_BINARY_DIR} --capture --output-file coverage.info --no-checksum
- #COMMAND "${GENHTML_EXECUTABLE}" --prefix ${CMAKE_BINARY_DIR} --output-directory coveragereport --title "Code Coverage" --legend --show-details coverage.info
- #COMMAND ${CMAKE_COMMAND} -E echo "\\#define foo \\\"bar\\\""
- #)
- endif()
- endif()
-endif()
-
-
- #$(MAKE) $(AM_MAKEFLAGS) check
- #lcov --directory $(top_builddir) --capture --test-name dconf | $(top_srcdir)/trim-lcov.py > dconf-lcov.info
- #LANG=C genhtml --prefix $(top_builddir) --output-directory lcov-html --legend --show-details dconf-lcov.info
- #@echo
- #@echo " file://$(abs_top_builddir)/lcov-html/index.html"
- #@echo
diff --git a/cmake/Translations.cmake b/cmake/Translations.cmake
deleted file mode 100644
index b51c39d..0000000
--- a/cmake/Translations.cmake
+++ /dev/null
@@ -1,37 +0,0 @@
-# Translations.cmake, CMake macros written for Marlin, feel free to re-use them
-
-macro(add_translations_directory NLS_PACKAGE)
- add_custom_target (i18n ALL)
- find_program (MSGFMT_EXECUTABLE msgfmt)
- file (GLOB PO_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.po)
- foreach (PO_INPUT ${PO_FILES})
- get_filename_component (PO_INPUT_BASE ${PO_INPUT} NAME_WE)
- set (MO_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PO_INPUT_BASE}.mo)
- add_custom_command (TARGET i18n COMMAND ${MSGFMT_EXECUTABLE} -o ${MO_OUTPUT} ${PO_INPUT})
-
- install (FILES ${MO_OUTPUT} DESTINATION
- ${CMAKE_INSTALL_LOCALEDIR}/${PO_INPUT_BASE}/LC_MESSAGES
- RENAME ${NLS_PACKAGE}.mo)
- endforeach (PO_INPUT ${PO_FILES})
-endmacro(add_translations_directory)
-
-
-macro(add_translations_catalog NLS_PACKAGE)
- add_custom_target (pot COMMENT “Building translation catalog.”)
- find_program (XGETTEXT_EXECUTABLE xgettext)
-
- # init this list, which will hold all the sources across all dirs
- set(SOURCES "")
-
- # add each directory's sources to the overall sources list
- foreach(FILES_INPUT ${ARGN})
- set (DIR ${CMAKE_CURRENT_SOURCE_DIR}/${FILES_INPUT})
- file (GLOB_RECURSE DIR_SOURCES ${DIR}/*.c ${DIR}/*.cc ${DIR}/*.cpp ${DIR}/*.cxx ${DIR}/*.vala)
- set (SOURCES ${SOURCES} ${DIR_SOURCES})
- endforeach()
-
- add_custom_command (TARGET pot COMMAND
- ${XGETTEXT_EXECUTABLE} -d ${NLS_PACKAGE} -o ${CMAKE_CURRENT_SOURCE_DIR}/${NLS_PACKAGE}.pot
- ${SOURCES} --keyword="_" --keyword="N_" --from-code=UTF-8
- )
-endmacro()
diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt
index c460586..d7ab10f 100644
--- a/data/CMakeLists.txt
+++ b/data/CMakeLists.txt
@@ -55,7 +55,7 @@ set (XDG_AUTOSTART_FILE_IN "${CMAKE_CURRENT_SOURCE_DIR}/${XDG_AUTOSTART_NAME}.in
set (pkglibexecdir "${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}")
configure_file ("${XDG_AUTOSTART_FILE_IN}" "${XDG_AUTOSTART_FILE}")
-# install it
+# install XDG autostart
install (FILES "${XDG_AUTOSTART_FILE}"
DESTINATION "${XDG_AUTOSTART_DIR}")
diff --git a/data/ayatana-indicator-datetime.desktop.in b/data/ayatana-indicator-datetime.desktop.in
index 70df0d7..6d27166 100644
--- a/data/ayatana-indicator-datetime.desktop.in
+++ b/data/ayatana-indicator-datetime.desktop.in
@@ -6,3 +6,4 @@ OnlyShowIn=MATE;Unity;XFCE;Pantheon;
NoDisplay=true
StartupNotify=false
Terminal=false
+Icon=@messaging_menu_icon@
diff --git a/debian/control b/debian/control
index 946b4c2..93d2b76 100644
--- a/debian/control
+++ b/debian/control
@@ -17,8 +17,8 @@ Build-Depends: cmake,
libical-dev (>= 1.0),
libedataserver1.2-dev (>= 3.5),
accountsservice-ubuntu-schemas | hello,
- liblomiri-url-dispatcher-dev | hello,
libproperties-cpp-dev,
+ liblomiri-url-dispatcher-dev | hello,
# for the test harness:
libgtest-dev <!nocheck>,
libdbustest1-dev <!nocheck>,
diff --git a/include/datetime/actions-live.h b/include/datetime/actions-live.h
index 2c348c6..6f536c4 100644
--- a/include/datetime/actions-live.h
+++ b/include/datetime/actions-live.h
@@ -40,21 +40,12 @@ public:
virtual ~LiveActions() =default;
bool desktop_has_calendar_app() const override;
- void desktop_open_alarm_app() override;
- void desktop_open_appointment(const Appointment&) override;
- void desktop_open_calendar_app(const DateTime&) override;
- void desktop_open_settings_app() override;
-
- void phone_open_alarm_app() override;
- void phone_open_appointment(const Appointment&) override;
- void phone_open_calendar_app(const DateTime&) override;
- void phone_open_settings_app() override;
+ void open_alarm_app() override;
+ void open_appointment(const Appointment&, const DateTime&) override;
+ void open_calendar_app(const DateTime&) override;
+ void open_settings_app() override;
void set_location(const std::string& zone, const std::string& name) override;
-
-protected:
- virtual void execute_command(const std::string& command);
- virtual void dispatch_url(const std::string& url);
};
} // namespace datetime
diff --git a/include/datetime/actions.h b/include/datetime/actions.h
index 47931ac..d866b00 100644
--- a/include/datetime/actions.h
+++ b/include/datetime/actions.h
@@ -44,15 +44,11 @@ class Actions
public:
virtual bool desktop_has_calendar_app() const =0;
- virtual void desktop_open_alarm_app() =0;
- virtual void desktop_open_appointment(const Appointment&) =0;
- virtual void desktop_open_calendar_app(const DateTime&) =0;
- virtual void desktop_open_settings_app() =0;
- virtual void phone_open_alarm_app() =0;
- virtual void phone_open_appointment(const Appointment&) =0;
- virtual void phone_open_calendar_app(const DateTime&) =0;
- virtual void phone_open_settings_app() =0;
+ virtual void open_alarm_app() =0;
+ virtual void open_appointment(const Appointment&, const DateTime&) =0;
+ virtual void open_calendar_app(const DateTime&) =0;
+ virtual void open_settings_app() =0;
virtual void set_location(const std::string& zone, const std::string& name)=0;
diff --git a/include/datetime/appointment.h b/include/datetime/appointment.h
index 950f4bb..faf8a18 100644
--- a/include/datetime/appointment.h
+++ b/include/datetime/appointment.h
@@ -43,7 +43,6 @@ struct Alarm
bool has_text() const;
};
-
/**
* \brief An instance of an appointment; e.g. a calendar event or clock-app alarm
*
diff --git a/include/datetime/date-time.h b/include/datetime/date-time.h
index fc83388..845716d 100644
--- a/include/datetime/date-time.h
+++ b/include/datetime/date-time.h
@@ -68,7 +68,9 @@ public:
int64_t to_unix() const;
bool operator<(const DateTime& that) const;
+ bool operator>(const DateTime& that) const;
bool operator<=(const DateTime& that) const;
+ 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;
diff --git a/include/datetime/dbus-shared.h b/include/datetime/dbus-shared.h
index 057ac6b..0b6aa95 100644
--- a/include/datetime/dbus-shared.h
+++ b/include/datetime/dbus-shared.h
@@ -24,9 +24,43 @@
#define BUS_DATETIME_NAME "org.ayatana.indicator.datetime"
#define BUS_DATETIME_PATH "/org/ayatana/indicator/datetime"
-#define BUS_POWERD_NAME "com.canonical.powerd"
-#define BUS_POWERD_PATH "/com/canonical/powerd"
-#define BUS_POWERD_INTERFACE "com.canonical.powerd"
+#define BUS_POWERD_NAME "com.lomiri.Repowerd"
+#define BUS_POWERD_PATH "/com/lomiri/Repowerd"
+#define BUS_POWERD_INTERFACE "com.lomiri.Repowerd"
+namespace Bus
+{
+ namespace Timedate1
+ {
+ static constexpr char const * BUSNAME {"org.freedesktop.timedate1"};
+ static constexpr char const * ADDR {"/org/freedesktop/timedate1"};
+ static constexpr char const * IFACE {"org.freedesktop.timedate1"};
+
+ namespace Properties
+ {
+ static constexpr char const * TIMEZONE {"Timezone"};
+ }
+
+ namespace Methods
+ {
+ static constexpr char const * SET_TIMEZONE {"SetTimezone"};
+ }
+ }
+
+ namespace Properties
+ {
+ static constexpr char const * IFACE {"org.freedesktop.DBus.Properties"};
+
+ namespace Methods
+ {
+ static constexpr char const * GET {"Get"};
+ }
+
+ namespace Signals
+ {
+ static constexpr char const * PROPERTIES_CHANGED {"PropertiesChanged"};
+ }
+ }
+}
#endif /* INDICATOR_DATETIME_DBUS_SHARED_H */
diff --git a/include/datetime/engine-eds.h b/include/datetime/engine-eds.h
index 12425b3..96b0f76 100644
--- a/include/datetime/engine-eds.h
+++ b/include/datetime/engine-eds.h
@@ -47,8 +47,7 @@ class Myself;
class EdsEngine: public Engine
{
public:
- EdsEngine();
- explicit EdsEngine(const std::shared_ptr<Myself> &myself);
+ EdsEngine(const std::shared_ptr<Myself> &myself);
~EdsEngine();
void get_appointments(const DateTime& begin,
diff --git a/include/datetime/menu.h b/include/datetime/menu.h
index acd9ed8..0074ea5 100644
--- a/include/datetime/menu.h
+++ b/include/datetime/menu.h
@@ -21,6 +21,7 @@
#define INDICATOR_DATETIME_MENU_H
#include <datetime/actions.h>
+#include <datetime/appointment.h>
#include <datetime/state.h>
#include <memory> // std::shared_ptr
@@ -49,6 +50,11 @@ public:
Profile profile() const;
GMenuModel* menu_model();
+ static std::vector<Appointment> get_display_appointments(
+ const std::vector<Appointment>&,
+ const DateTime& start,
+ unsigned int max_items=5);
+
protected:
Menu (Profile profile_in, const std::string& name_in);
virtual ~Menu() =default;
diff --git a/include/datetime/settings-live.h b/include/datetime/settings-live.h
index ccf7122..85071ce 100644
--- a/include/datetime/settings-live.h
+++ b/include/datetime/settings-live.h
@@ -38,8 +38,12 @@ public:
virtual ~LiveSettings();
private:
- static void on_changed(GSettings*, gchar*, gpointer);
- void update_key(const std::string& key);
+ static void on_changed_ccid(GSettings*, gchar*, gpointer);
+ static void on_changed_cal_notification(GSettings*, gchar*, gpointer);
+ static void on_changed_general_notification(GSettings*, gchar*, gpointer);
+ void update_key_ccid(const std::string& key);
+ void update_key_cal_notification(const std::string& key);
+ void update_key_general_notification(const std::string& key);
void update_custom_time_format();
void update_locations();
@@ -60,8 +64,16 @@ private:
void update_alarm_duration();
void update_alarm_haptic();
void update_snooze_duration();
+ void update_cal_notification_enabled();
+ void update_cal_notification_sounds();
+ void update_cal_notification_vibrations();
+ void update_cal_notification_bubbles();
+ void update_cal_notification_list();
+ void update_vibrate_silent_mode();
GSettings* m_settings;
+ GSettings* m_settings_cal_notification;
+ GSettings* m_settings_general_notification;
// we've got a raw pointer here, so disable copying
LiveSettings(const LiveSettings&) =delete;
diff --git a/include/datetime/settings-shared.h b/include/datetime/settings-shared.h
index bd84a2d..7280c16 100644
--- a/include/datetime/settings-shared.h
+++ b/include/datetime/settings-shared.h
@@ -51,4 +51,14 @@ TimeFormatMode;
#define SETTINGS_ALARM_HAPTIC_S "alarm-haptic-feedback"
#define SETTINGS_SNOOZE_DURATION_S "snooze-duration-minutes"
+#define SETTINGS_NOTIFY_APPS_SCHEMA_ID "com.lomiri.notifications.settings.applications"
+#define SETTINGS_VIBRATE_SILENT_KEY "vibrate-silent-mode"
+#define SETTINGS_NOTIFY_SCHEMA_ID "com.lomiri.notifications.settings"
+#define SETTINGS_NOTIFY_CALENDAR_PATH "/com/lomiri/NotificationSettings/com.lomiri.calendar/calendar/"
+#define SETTINGS_NOTIFY_ENABLED_KEY "enable-notifications"
+#define SETTINGS_NOTIFY_SOUNDS_KEY "use-sounds-notifications"
+#define SETTINGS_NOTIFY_VIBRATIONS_KEY "use-vibrations-notifications"
+#define SETTINGS_NOTIFY_BUBBLES_KEY "use-bubbles-notifications"
+#define SETTINGS_NOTIFY_LIST_KEY "use-list-notifications"
+
#endif // INDICATOR_DATETIME_SETTINGS_SHARED
diff --git a/include/datetime/settings.h b/include/datetime/settings.h
index 253a00a..af9227d 100644
--- a/include/datetime/settings.h
+++ b/include/datetime/settings.h
@@ -61,6 +61,12 @@ public:
core::Property<unsigned int> alarm_volume;
core::Property<unsigned int> alarm_duration;
core::Property<unsigned int> snooze_duration;
+ core::Property<bool> cal_notification_enabled;
+ core::Property<bool> cal_notification_sounds;
+ core::Property<bool> cal_notification_vibrations;
+ core::Property<bool> cal_notification_bubbles;
+ core::Property<bool> cal_notification_list;
+ core::Property<bool> vibrate_silent_mode;
};
} // namespace datetime
diff --git a/include/datetime/snap.h b/include/datetime/snap.h
index a295d9f..baa765b 100644
--- a/include/datetime/snap.h
+++ b/include/datetime/snap.h
@@ -24,6 +24,9 @@
#include <datetime/settings.h>
#include <notifications/notifications.h>
+#include <notifications/sound.h>
+
+#include <gio/gio.h> // GDBusConnection
#include <functional>
#include <memory>
@@ -39,14 +42,16 @@ class Snap
{
public:
Snap(const std::shared_ptr<ayatana::indicator::notifications::Engine>& engine,
- const std::shared_ptr<const Settings>& settings);
+ const std::shared_ptr<ayatana::indicator::notifications::SoundBuilder>& sound_builder,
+ const std::shared_ptr<const Settings>& settings,
+ GDBusConnection* system_bus);
virtual ~Snap();
- typedef std::function<void(const Appointment&, const Alarm&)> appointment_func;
+ enum class Response { None, Snooze, ShowApp };
+ typedef std::function<void(const Appointment&, const Alarm&, const Response&)> response_func;
void operator()(const Appointment& appointment,
const Alarm& alarm,
- appointment_func snooze,
- appointment_func ok);
+ response_func on_response);
private:
class Impl;
diff --git a/include/datetime/timezone-timedated.h b/include/datetime/timezone-timedated.h
index 336a148..e0af184 100644
--- a/include/datetime/timezone-timedated.h
+++ b/include/datetime/timezone-timedated.h
@@ -20,10 +20,10 @@
#ifndef INDICATOR_DATETIME_TIMEDATED_TIMEZONE_H
#define INDICATOR_DATETIME_TIMEDATED_TIMEZONE_H
-#define DEFAULT_FILENAME "/etc/timezone"
-
#include <datetime/timezone.h> // base class
+#include <gio/gio.h> // GDBusConnection*
+
#include <string> // std::string
namespace ayatana {
@@ -31,12 +31,12 @@ namespace indicator {
namespace datetime {
/**
- * \brief A #Timezone that gets its information from monitoring a file, such as /etc/timezone
+ * \brief A #Timezone that gets its information from org.freedesktop.timedate1
*/
class TimedatedTimezone: public Timezone
{
public:
- TimedatedTimezone(std::string filename = DEFAULT_FILENAME);
+ TimedatedTimezone(GDBusConnection* connection);
~TimedatedTimezone();
private:
diff --git a/include/datetime/timezones-live.h b/include/datetime/timezones-live.h
index e722576..8b8b5fa 100644
--- a/include/datetime/timezones-live.h
+++ b/include/datetime/timezones-live.h
@@ -23,7 +23,6 @@
#include <datetime/settings.h>
#include <datetime/timezones.h>
#include <datetime/timezone-geoclue.h>
-#include <datetime/timezone-timedated.h>
#include <memory> // shared_ptr<>
@@ -38,13 +37,13 @@ namespace datetime {
class LiveTimezones: public Timezones
{
public:
- LiveTimezones(const std::shared_ptr<const Settings>& settings);
+ LiveTimezones(const std::shared_ptr<const Settings>& settings, const std::shared_ptr<Timezone>& primary_timezone);
private:
void update_geolocation();
void update_timezones();
- TimedatedTimezone m_file;
+ std::shared_ptr<Timezone> m_primary_timezone;
std::shared_ptr<const Settings> m_settings;
std::shared_ptr<GeoclueTimezone> m_geo;
};
diff --git a/include/notifications/awake.h b/include/notifications/awake.h
index b441692..d0b46eb 100644
--- a/include/notifications/awake.h
+++ b/include/notifications/awake.h
@@ -22,6 +22,8 @@
#include <memory>
+#include <gio/gio.h>
+
namespace ayatana {
namespace indicator {
namespace notifications {
@@ -36,7 +38,7 @@ namespace notifications {
class Awake
{
public:
- explicit Awake(const std::string& app_name);
+ explicit Awake(GDBusConnection* system_bus, const std::string& app_name);
~Awake();
private:
diff --git a/include/notifications/dbus-shared.h b/include/notifications/dbus-shared.h
index 523fb2a..31df219 100644
--- a/include/notifications/dbus-shared.h
+++ b/include/notifications/dbus-shared.h
@@ -25,9 +25,9 @@
#define BUS_SCREEN_PATH "/com/canonical/Unity/Screen"
#define BUS_SCREEN_INTERFACE "com.canonical.Unity.Screen"
-#define BUS_POWERD_NAME "com.canonical.powerd"
-#define BUS_POWERD_PATH "/com/canonical/powerd"
-#define BUS_POWERD_INTERFACE "com.canonical.powerd"
+#define BUS_POWERD_NAME "com.lomiri.Repowerd"
+#define BUS_POWERD_PATH "/com/lomiri/Repowerd"
+#define BUS_POWERD_INTERFACE "com.lomiri.Repowerd"
#define BUS_HAPTIC_NAME "com.canonical.usensord"
#define BUS_HAPTIC_PATH "/com/canonical/usensord/haptic"
diff --git a/include/notifications/haptic.h b/include/notifications/haptic.h
index 535074d..2f4008a 100644
--- a/include/notifications/haptic.h
+++ b/include/notifications/haptic.h
@@ -41,7 +41,7 @@ public:
MODE_PULSE
};
- explicit Haptic(const Mode& mode = MODE_PULSE);
+ explicit Haptic(const Mode& mode = MODE_PULSE, bool repeat = false);
~Haptic();
private:
diff --git a/include/notifications/notifications.h b/include/notifications/notifications.h
index 0de1e23..af6d21f 100644
--- a/include/notifications/notifications.h
+++ b/include/notifications/notifications.h
@@ -50,6 +50,8 @@ public:
void set_icon_name (const std::string& icon_name);
+ void set_start_time(uint64_t time);
+
/* Set an interval, after which the notification will automatically
be closed. If not set, the notification server's default timeout
is used. */
@@ -62,15 +64,26 @@ public:
static constexpr char const * HINT_NONSHAPED_ICON {"x-canonical-non-shaped-icon"};
static constexpr char const * HINT_AFFIRMATIVE_HINT {"x-canonical-private-affirmative-tint"};
static constexpr char const * HINT_REJECTION_TINT {"x-canonical-private-rejection-tint"};
+ static constexpr char const * HINT_INTERACTIVE {"x-canonical-switch-to-application"};
/* Add an action button.
This may fail if the Engine doesn't support actions.
@see Engine::supports_actions() */
void add_action (const std::string& action, const std::string& label);
- /** Sets the closed callback. This will be called exactly once. */
+ /** Sets the closed callback. This will be called exactly once. After notification disappears */
void set_closed_callback (std::function<void(const std::string& action)>);
+ /** Sets the time-out callback. This will be called exactly once. */
+ void set_timeout_callback (std::function<void()>);
+
+ /** Sets if a notification bubble should be displayed. */
+ void set_show_notification_bubble (bool show);
+
+ /** Sets if notification should be posted to messaging menu after it is closed. */
+ void set_post_to_messaging_menu (bool post);
+
+
private:
friend class Engine;
class Impl;
diff --git a/m4/gcov.m4 b/m4/gcov.m4
deleted file mode 100644
index d243eeb..0000000
--- a/m4/gcov.m4
+++ /dev/null
@@ -1,86 +0,0 @@
-# Checks for existence of coverage tools:
-# * gcov
-# * lcov
-# * genhtml
-# * gcovr
-#
-# Sets ac_cv_check_gcov to yes if tooling is present
-# and reports the executables to the variables LCOV, GCOVR and GENHTML.
-AC_DEFUN([AC_TDD_GCOV],
-[
- AC_ARG_ENABLE(gcov,
- AS_HELP_STRING([--enable-gcov],
- [enable coverage testing with gcov]),
- [use_gcov=$enableval], [use_gcov=no])
-
- if test "x$use_gcov" = "xyes"; then
- # we need gcc:
- if test "$GCC" != "yes"; then
- AC_MSG_ERROR([GCC is required for --enable-gcov])
- fi
-
- # Check if ccache is being used
- AC_CHECK_PROG(SHTOOL, shtool, shtool)
- case `$SHTOOL path $CC` in
- *ccache*[)] gcc_ccache=yes;;
- *[)] gcc_ccache=no;;
- esac
-
- if test "$gcc_ccache" = "yes" && (test -z "$CCACHE_DISABLE" || test "$CCACHE_DISABLE" != "1"); then
- AC_MSG_ERROR([ccache must be disabled when --enable-gcov option is used. You can disable ccache by setting environment variable CCACHE_DISABLE=1.])
- fi
-
- lcov_version_list="1.6 1.7 1.8 1.9"
- AC_CHECK_PROG(LCOV, lcov, lcov)
- AC_CHECK_PROG(GENHTML, genhtml, genhtml)
-
- if test "$LCOV"; then
- AC_CACHE_CHECK([for lcov version], glib_cv_lcov_version, [
- glib_cv_lcov_version=invalid
- lcov_version=`$LCOV -v 2>/dev/null | $SED -e 's/^.* //'`
- for lcov_check_version in $lcov_version_list; do
- if test "$lcov_version" = "$lcov_check_version"; then
- glib_cv_lcov_version="$lcov_check_version (ok)"
- fi
- done
- ])
- else
- lcov_msg="To enable code coverage reporting you must have one of the following lcov versions installed: $lcov_version_list"
- AC_MSG_ERROR([$lcov_msg])
- fi
-
- case $glib_cv_lcov_version in
- ""|invalid[)]
- lcov_msg="You must have one of the following versions of lcov: $lcov_version_list (found: $lcov_version)."
- AC_MSG_ERROR([$lcov_msg])
- LCOV="exit 0;"
- ;;
- esac
-
- if test -z "$GENHTML"; then
- AC_MSG_ERROR([Could not find genhtml from the lcov package])
- fi
-
- ac_cv_check_gcov=yes
- ac_cv_check_lcov=yes
-
- # Remove all optimization flags from CFLAGS
- changequote({,})
- CFLAGS=`echo "$CFLAGS" | $SED -e 's/-O[0-9]*//g'`
- changequote([,])
-
- # Add the special gcc flags
- COVERAGE_CFLAGS="-O0 -fprofile-arcs -ftest-coverage"
- COVERAGE_CXXFLAGS="-O0 -fprofile-arcs -ftest-coverage"
- COVERAGE_LDFLAGS="-lgcov"
-
- # Check availability of gcovr
- AC_CHECK_PROG(GCOVR, gcovr, gcovr)
- if test -z "$GCOVR"; then
- ac_cv_check_gcovr=no
- else
- ac_cv_check_gcovr=yes
- fi
-
-fi
-]) # AC_TDD_GCOV
diff --git a/m4/gtest.m4 b/m4/gtest.m4
deleted file mode 100644
index 2de334c..0000000
--- a/m4/gtest.m4
+++ /dev/null
@@ -1,63 +0,0 @@
-# Copyright (C) 2012 Canonical, Ltd.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice (including the next
-# paragraph) shall be included in all copies or substantial portions of the
-# Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-# Checks whether the gtest source is available on the system. Allows for
-# adjusting the include and source path. Sets have_gtest=yes if the source is
-# present. Sets GTEST_CPPFLAGS and GTEST_SOURCE to the preprocessor flags and
-# source location respectively.
-AC_DEFUN([CHECK_GTEST],
-[
- AC_ARG_WITH([gtest-include-path],
- [AS_HELP_STRING([--with-gtest-include-path],
- [location of the Google test headers])],
- [GTEST_CPPFLAGS="-I$withval"])
-
- AC_ARG_WITH([gtest-source-path],
- [AS_HELP_STRING([--with-gtest-source-path],
- [location of the Google test sources, defaults to /usr/src/gtest])],
- [GTEST_SOURCE="$withval"],
- [GTEST_SOURCE="/usr/src/gtest"])
-
- GTEST_CPPFLAGS="$GTEST_CPPFLAGS -I$GTEST_SOURCE"
-
- AC_LANG_PUSH([C++])
-
- tmp_CPPFLAGS="$CPPFLAGS"
- CPPFLAGS="$CPPFLAGS $GTEST_CPPFLAGS"
-
- AC_CHECK_HEADER([gtest/gtest.h])
-
- CPPFLAGS="$tmp_CPPFLAGS"
-
- AC_LANG_POP
-
- AC_CHECK_FILES([$GTEST_SOURCE/src/gtest-all.cc]
- [$GTEST_SOURCE/src/gtest_main.cc],
- [have_gtest_source=yes],
- [have_gtest_source=no])
-
- AS_IF([test "x$ac_cv_header_gtest_gtest_h" = xyes -a \
- "x$have_gtest_source" = xyes],
- [have_gtest=yes]
- [AC_SUBST(GTEST_CPPFLAGS)]
- [AC_SUBST(GTEST_SOURCE)],
- [have_gtest=no])
-]) # CHECK_GTEST
diff --git a/po/CMakeLists.txt b/po/CMakeLists.txt
index 8325f6e..0b2b2a1 100644
--- a/po/CMakeLists.txt
+++ b/po/CMakeLists.txt
@@ -1,3 +1,6 @@
-include (Translations)
-add_translations_directory ("${GETTEXT_PACKAGE}")
-add_translations_catalog ("${GETTEXT_PACKAGE}" ../src/)
+find_package(Intltool REQUIRED)
+
+intltool_install_translations(
+ ALL
+ GETTEXT_PACKAGE ${GETTEXT_PACKAGE}
+)
diff --git a/po/Makevars b/po/Makevars
deleted file mode 100644
index e877468..0000000
--- a/po/Makevars
+++ /dev/null
@@ -1,7 +0,0 @@
-DOMAIN = $(PACKAGE)
-subdir = po
-top_builddir = ..
-XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ --keyword=C_:1c,2 --keyword=T_ --from-code=UTF-8
-COPYRIGHT_HOLDER = Canonical Ltd.
-MSGID_BUGS_ADDRESS = ted@canonical.com
-EXTRA_LOCALE_CATEGORIES =
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 96284fb..a64a50e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -57,9 +57,9 @@ endif()
# add warnings/coverage info on handwritten files
# but not the autogenerated ones...
set_source_files_properties(${SERVICE_CXX_SOURCES}
- PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} ${GCOV_FLAGS} -g -std=c++11")
+ PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -std=c++11")
set_source_files_properties(${SERVICE_C_SOURCES}
- PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} ${GCOV_FLAGS} -g -std=c99")
+ PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -std=c99")
# add the bin dir to our include path so our code can find the generated header files
include_directories (${CMAKE_CURRENT_BINARY_DIR})
@@ -69,6 +69,6 @@ include_directories (${CMAKE_SOURCE_DIR})
link_directories (${SERVICE_DEPS_LIBRARY_DIRS})
add_executable (${SERVICE_EXEC} main.cpp)
-set_source_files_properties(${SERVICE_SOURCES} main.cpp PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -g -std=c++11")
-target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} ${GCOV_LIBS} ${URLDISPATCHER_LIBRARIES})
+set_source_files_properties(${SERVICE_SOURCES} main.cpp PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -std=c++11")
+target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES})
install (TARGETS ${SERVICE_EXEC} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR})
diff --git a/src/actions-live.cpp b/src/actions-live.cpp
index 271d2f3..5c49bc4 100644
--- a/src/actions-live.cpp
+++ b/src/actions-live.cpp
@@ -17,14 +17,18 @@
* Charles Kerr <charles.kerr@canonical.com>
*/
+#include <datetime/dbus-shared.h>
#include <datetime/actions-live.h>
-#ifdef HAS_URLDISPATCHER
-#include <lomiri-url-dispatcher.h>
-#endif
-
#include <glib.h>
+#include <sstream>
+
+extern "C"
+{
+ #include <ayatana/common/utils.h>
+}
+
namespace ayatana {
namespace indicator {
namespace datetime {
@@ -38,53 +42,55 @@ LiveActions::LiveActions(const std::shared_ptr<State>& state_in):
{
}
-void LiveActions::execute_command(const std::string& cmdstr)
-{
- const auto cmd = cmdstr.c_str();
- g_debug("Issuing command '%s'", cmd);
+/***
+****
+***/
- GError* error = nullptr;
- if (!g_spawn_command_line_async(cmd, &error))
+void LiveActions::open_alarm_app()
+{
+ if (ayatana_common_utils_is_lomiri())
{
- g_warning("Unable to start \"%s\": %s", cmd, error->message);
- g_error_free(error);
+ ayatana_common_utils_open_url("alarm://");
+ }
+ else
+ {
+ ayatana_common_utils_execute_command("evolution -c calendar");
}
}
-void LiveActions::dispatch_url(const std::string& url)
+void LiveActions::open_calendar_app(const DateTime& dt)
{
- g_debug("Dispatching url '%s'", url.c_str());
-#ifdef HAS_URLDISPATCHER
- lomiri_url_dispatch_send(url.c_str(), nullptr, nullptr);
-#else
- // FIXME: Deal with this, if we build without liburl-dispatcher...
-#endif
+ if (ayatana_common_utils_is_lomiri())
+ {
+ const auto utc = dt.to_timezone("UTC");
+ auto cmd = utc.format("calendar://startdate=%Y-%m-%dT%H:%M:%S+00:00");
+ ayatana_common_utils_open_url(cmd.c_str());
+ }
+ else
+ {
+ const auto utc = dt.start_of_day().to_timezone("UTC");
+ auto cmd = utc.format("evolution \"calendar:///?startdate=%Y%m%dT%H%M%SZ\"");
+ ayatana_common_utils_execute_command(cmd.c_str());
+ }
}
-/***
-****
-***/
-
-void LiveActions::desktop_open_settings_app()
+void LiveActions::open_settings_app()
{
- if (g_getenv ("MIR_SOCKET") != nullptr)
+ if (ayatana_common_utils_is_lomiri())
+ {
+ ayatana_common_utils_open_url("settings:///system/time-date");
+ }
+ else if (ayatana_common_utils_is_unity())
{
- dispatch_url("settings:///system/time-date");
+ ayatana_common_utils_execute_command("unity-control-center datetime");
+ }
+ else if (ayatana_common_utils_is_mate())
+ {
+ ayatana_common_utils_execute_command("mate-time-admin");
}
else
{
- if ((g_strcmp0 (g_getenv ("XDG_CURRENT_DESKTOP"), "Unity") == 0))
- {
- execute_command("unity-control-center datetime");
- }
- else if ((g_strcmp0 (g_getenv ("XDG_CURRENT_DESKTOP"), "MATE") == 0))
- {
- execute_command("mate-time-admin");
- }
- else
- {
- execute_command("gnome-control-center datetime");
- }
+ ayatana_common_utils_execute_command("gnome-control-center datetime");
}
}
@@ -120,61 +126,25 @@ bool LiveActions::desktop_has_calendar_app() const
return have_calendar;
}
-void LiveActions::desktop_open_alarm_app()
-{
- execute_command("evolution -c calendar");
-}
-
-void LiveActions::desktop_open_appointment(const Appointment& appt)
+void LiveActions::open_appointment(const Appointment& appt, const DateTime& date)
{
- desktop_open_calendar_app(appt.begin);
-}
-
-void LiveActions::desktop_open_calendar_app(const DateTime& dt)
-{
- const auto utc = dt.start_of_day().to_timezone("UTC");
- auto cmd = utc.format("evolution \"calendar:///?startdate=%Y%m%dT%H%M%SZ\"");
- execute_command(cmd.c_str());
-}
-
-/***
-****
-***/
-
-void LiveActions::phone_open_alarm_app()
-{
- dispatch_url("appid://com.ubuntu.clock/clock/current-user-version");
-}
-
-void LiveActions::phone_open_appointment(const Appointment& appt)
-{
-
if (!appt.activation_url.empty())
{
- dispatch_url(appt.activation_url);
+ ayatana_common_utils_open_url(appt.activation_url.c_str());
}
else switch (appt.type)
{
case Appointment::UBUNTU_ALARM:
- phone_open_alarm_app();
+ open_alarm_app();
break;
+ case Appointment::EVENT:
default:
- phone_open_calendar_app(appt.begin);
+ open_calendar_app(date);
+ break;
}
}
-void LiveActions::phone_open_calendar_app(const DateTime&)
-{
- // does calendar app have a mechanism for specifying dates?
- dispatch_url("appid://com.ubuntu.calendar/calendar/current-user-version");
-}
-
-void LiveActions::phone_open_settings_app()
-{
- dispatch_url("settings:///system/time-date");
-}
-
/***
****
***/
@@ -235,7 +205,7 @@ on_datetime1_proxy_ready (GObject * object G_GNUC_UNUSED,
else
{
g_dbus_proxy_call(proxy,
- "SetTimezone",
+ Bus::Timedate1::Methods::SET_TIMEZONE,
g_variant_new ("(sb)", data->tzid.c_str(), TRUE),
G_DBUS_CALL_FLAGS_NONE,
-1,
@@ -263,9 +233,9 @@ void LiveActions::set_location(const std::string& tzid, const std::string& name)
g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_NONE,
nullptr,
- "org.freedesktop.timedate1",
- "/org/freedesktop/timedate1",
- "org.freedesktop.timedate1",
+ Bus::Timedate1::BUSNAME,
+ Bus::Timedate1::ADDR,
+ Bus::Timedate1::IFACE,
nullptr,
on_datetime1_proxy_ready,
data);
diff --git a/src/actions.cpp b/src/actions.cpp
index 93629a0..315340a 100644
--- a/src/actions.cpp
+++ b/src/actions.cpp
@@ -48,13 +48,8 @@ DateTime datetime_from_timet_variant(GVariant* v)
return DateTime::NowLocal();
}
-bool lookup_appointment_by_uid_variant(const std::shared_ptr<State>& state, GVariant* vuid, Appointment& setme)
+bool lookup_appointment_by_uid(const std::shared_ptr<State>& state, const gchar* uid, Appointment& setme)
{
- g_return_val_if_fail(vuid != nullptr, false);
- g_return_val_if_fail(g_variant_type_equal(G_VARIANT_TYPE_STRING,g_variant_get_type(vuid)), false);
- const auto uid = g_variant_get_string(vuid, nullptr);
- g_return_val_if_fail(uid && *uid, false);
-
for(const auto& appt : state->calendar_upcoming->appointments().get())
{
if (appt.uid == uid)
@@ -67,46 +62,28 @@ bool lookup_appointment_by_uid_variant(const std::shared_ptr<State>& state, GVar
return false;
}
-void on_desktop_appointment_activated (GSimpleAction*, GVariant *vuid, gpointer gself)
+void on_appointment_activated (GSimpleAction*, GVariant *vdata, gpointer gself)
{
auto self = static_cast<Actions*>(gself);
Appointment appt;
- if (lookup_appointment_by_uid_variant(self->state(), vuid, appt))
- self->desktop_open_appointment(appt);
+ const gchar* uid = nullptr;
+ gint64 time = 0;
+ g_variant_get(vdata, "(&sx)", &uid, &time);
+ if (lookup_appointment_by_uid(self->state(), uid, appt))
+ self->open_appointment(appt, DateTime::Local(time));
}
-void on_desktop_alarm_activated (GSimpleAction*, GVariant*, gpointer gself)
+void on_alarm_activated (GSimpleAction*, GVariant*, gpointer gself)
{
- static_cast<Actions*>(gself)->desktop_open_alarm_app();
+ static_cast<Actions*>(gself)->open_alarm_app();
}
-void on_desktop_calendar_activated (GSimpleAction*, GVariant* vt, gpointer gself)
+void on_calendar_activated (GSimpleAction*, GVariant* vt, gpointer gself)
{
const auto dt = datetime_from_timet_variant(vt);
- static_cast<Actions*>(gself)->desktop_open_calendar_app(dt);
+ static_cast<Actions*>(gself)->open_calendar_app(dt);
}
-void on_desktop_settings_activated (GSimpleAction*, GVariant*, gpointer gself)
+void on_settings_activated (GSimpleAction*, GVariant*, gpointer gself)
{
- static_cast<Actions*>(gself)->desktop_open_settings_app();
-}
-
-void on_phone_appointment_activated (GSimpleAction*, GVariant *vuid, gpointer gself)
-{
- auto self = static_cast<Actions*>(gself);
- Appointment appt;
- if (lookup_appointment_by_uid_variant(self->state(), vuid, appt))
- self->phone_open_appointment(appt);
-}
-void on_phone_alarm_activated (GSimpleAction*, GVariant*, gpointer gself)
-{
- static_cast<Actions*>(gself)->phone_open_alarm_app();
-}
-void on_phone_calendar_activated (GSimpleAction*, GVariant* vt, gpointer gself)
-{
- const auto dt = datetime_from_timet_variant(vt);
- static_cast<Actions*>(gself)->phone_open_calendar_app(dt);
-}
-void on_phone_settings_activated (GSimpleAction*, GVariant*, gpointer gself)
-{
- static_cast<Actions*>(gself)->phone_open_settings_app();
+ static_cast<Actions*>(gself)->open_settings_app();
}
void on_set_location(GSimpleAction * /*action*/,
@@ -134,9 +111,9 @@ void on_calendar_active_changed(GSimpleAction * /*action*/,
}
}
-void on_calendar_activated(GSimpleAction * /*action*/,
- GVariant * state,
- gpointer gself)
+void on_calendar_date_activated(GSimpleAction * /*action*/,
+ GVariant * state,
+ gpointer gself)
{
const time_t t = g_variant_get_int64(state);
@@ -198,15 +175,15 @@ Actions::Actions(const std::shared_ptr<State>& state):
{
GActionEntry entries[] = {
- { "desktop.open-appointment", on_desktop_appointment_activated, "s", nullptr },
- { "desktop.open-alarm-app", on_desktop_alarm_activated },
- { "desktop.open-calendar-app", on_desktop_calendar_activated, "x", nullptr },
- { "desktop.open-settings-app", on_desktop_settings_activated },
+ { "desktop.open-appointment", on_appointment_activated, "(sx)", nullptr },
+ { "desktop.open-alarm-app", on_alarm_activated },
+ { "desktop.open-calendar-app", on_calendar_activated, "x", nullptr },
+ { "desktop.open-settings-app", on_settings_activated },
- { "phone.open-appointment", on_phone_appointment_activated, "s", nullptr },
- { "phone.open-alarm-app", on_phone_alarm_activated },
- { "phone.open-calendar-app", on_phone_calendar_activated, "x", nullptr },
- { "phone.open-settings-app", on_phone_settings_activated },
+ { "phone.open-appointment", on_appointment_activated, "(sx)", nullptr },
+ { "phone.open-alarm-app", on_alarm_activated },
+ { "phone.open-calendar-app", on_calendar_activated, "x", nullptr },
+ { "phone.open-settings-app", on_settings_activated },
{ "calendar-active", nullptr, nullptr, "false", on_calendar_active_changed },
{ "set-location", on_set_location, "s" }
@@ -240,7 +217,7 @@ Actions::Actions(const std::shared_ptr<State>& state):
v = create_calendar_state(state);
a = g_simple_action_new_stateful("calendar", G_VARIANT_TYPE_INT64, v);
g_action_map_add_action(gam, G_ACTION(a));
- g_signal_connect(a, "activate", G_CALLBACK(on_calendar_activated), this);
+ g_signal_connect(a, "activate", G_CALLBACK(on_calendar_date_activated), this);
g_object_unref(a);
///
diff --git a/src/awake.cpp b/src/awake.cpp
index dd41c35..9ee337e 100644
--- a/src/awake.cpp
+++ b/src/awake.cpp
@@ -36,89 +36,38 @@ class Awake::Impl
{
public:
- Impl(const std::string& app_name):
+ Impl(GDBusConnection* bus, const std::string& app_name):
m_app_name(app_name),
- m_cancellable(g_cancellable_new())
+ m_cancellable(g_cancellable_new()),
+ m_system_bus{G_DBUS_CONNECTION(g_object_ref(bus))}
{
- g_bus_get(G_BUS_TYPE_SYSTEM, m_cancellable, on_system_bus_ready, this);
+ // ask repowerd to keep the system awake
+ static constexpr int32_t POWERD_SYS_STATE_ACTIVE = 1;
+ g_dbus_connection_call (m_system_bus,
+ BUS_POWERD_NAME,
+ BUS_POWERD_PATH,
+ BUS_POWERD_INTERFACE,
+ "requestSysState",
+ g_variant_new("(si)", m_app_name.c_str(), POWERD_SYS_STATE_ACTIVE),
+ G_VARIANT_TYPE("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ m_cancellable,
+ on_force_awake_response,
+ this);
+
}
~Impl()
{
g_cancellable_cancel (m_cancellable);
g_object_unref (m_cancellable);
-
- if (m_display_on_timer)
- {
- g_source_remove (m_display_on_timer);
- m_display_on_timer = 0;
- }
-
- if (m_system_bus != nullptr)
- {
- unforce_awake ();
- remove_display_on_request ();
- g_object_unref (m_system_bus);
- }
+ unforce_awake ();
+ g_clear_object (&m_system_bus);
}
private:
- static void on_system_bus_ready (GObject *,
- GAsyncResult *res,
- gpointer gself)
- {
- GError * error;
- GDBusConnection * system_bus;
-
- error = nullptr;
- system_bus = g_bus_get_finish (res, &error);
- if (error != nullptr)
- {
- if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
- g_warning ("Unable to get bus: %s", error->message);
-
- g_error_free (error);
- }
- else if (system_bus != nullptr)
- {
- auto self = static_cast<Impl*>(gself);
-
- self->m_system_bus = G_DBUS_CONNECTION (g_object_ref (system_bus));
-
- // ask powerd to keep the system awake
- static constexpr int32_t POWERD_SYS_STATE_ACTIVE = 1;
- g_dbus_connection_call (system_bus,
- BUS_POWERD_NAME,
- BUS_POWERD_PATH,
- BUS_POWERD_INTERFACE,
- "requestSysState",
- g_variant_new("(si)", self->m_app_name.c_str(), POWERD_SYS_STATE_ACTIVE),
- G_VARIANT_TYPE("(s)"),
- G_DBUS_CALL_FLAGS_NONE,
- -1,
- self->m_cancellable,
- on_force_awake_response,
- self);
-
- // ask unity-system-compositor to turn on the screen
- g_dbus_connection_call (system_bus,
- BUS_SCREEN_NAME,
- BUS_SCREEN_PATH,
- BUS_SCREEN_INTERFACE,
- "keepDisplayOn",
- nullptr,
- G_VARIANT_TYPE("(i)"),
- G_DBUS_CALL_FLAGS_NONE,
- -1,
- self->m_cancellable,
- on_keep_display_on_response,
- self);
-
- g_object_unref (system_bus);
- }
- }
-
static void on_force_awake_response (GObject * connection,
GAsyncResult * res,
gpointer gself)
@@ -146,60 +95,11 @@ private:
g_clear_pointer (&self->m_awake_cookie, g_free);
g_variant_get (args, "(s)", &self->m_awake_cookie);
- g_debug ("m_awake_cookie is now '%s'", self->m_awake_cookie);
-
- g_variant_unref (args);
- }
- }
-
- static void on_keep_display_on_response (GObject * connection,
- GAsyncResult * res,
- gpointer gself)
- {
- GError * error;
- GVariant * args;
-
- error = nullptr;
- args = g_dbus_connection_call_finish (G_DBUS_CONNECTION(connection),
- res,
- &error);
- if (error != nullptr)
- {
- if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
- !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))
- {
- g_warning ("Unable to turn on the screen: %s", error->message);
- }
-
- g_error_free (error);
- }
- else
- {
- auto self = static_cast<Impl*>(gself);
-
- self->m_display_on_cookie = NO_DISPLAY_ON_COOKIE;
- g_variant_get (args, "(i)", &self->m_display_on_cookie);
- g_debug ("m_display_on_cookie is now '%d'", self->m_display_on_cookie);
-
- self->m_display_on_timer = g_timeout_add_seconds (self->m_display_on_seconds,
- on_display_on_timer,
- gself);
g_variant_unref (args);
}
}
- static gboolean on_display_on_timer (gpointer gself)
- {
- auto self = static_cast<Impl*>(gself);
-
- self->m_display_on_timer = 0;
- self->remove_display_on_request();
-
- return G_SOURCE_REMOVE;
- }
-
-
void unforce_awake ()
{
g_return_if_fail (G_IS_DBUS_CONNECTION(m_system_bus));
@@ -215,7 +115,7 @@ private:
nullptr,
G_DBUS_CALL_FLAGS_NONE,
-1,
- nullptr,
+ m_cancellable,
nullptr,
nullptr);
@@ -223,56 +123,18 @@ private:
}
}
- void remove_display_on_request ()
- {
- g_return_if_fail (G_IS_DBUS_CONNECTION(m_system_bus));
-
- if (m_display_on_cookie != NO_DISPLAY_ON_COOKIE)
- {
- g_dbus_connection_call (m_system_bus,
- BUS_SCREEN_NAME,
- BUS_SCREEN_PATH,
- BUS_SCREEN_INTERFACE,
- "removeDisplayOnRequest",
- g_variant_new("(i)", m_display_on_cookie),
- nullptr,
- G_DBUS_CALL_FLAGS_NONE,
- -1,
- nullptr,
- nullptr,
- nullptr);
-
- m_display_on_cookie = NO_DISPLAY_ON_COOKIE;
- }
- }
-
const std::string m_app_name;
GCancellable * m_cancellable = nullptr;
GDBusConnection * m_system_bus = nullptr;
char * m_awake_cookie = nullptr;
-
- /**
- * As described by bug #1434637, alarms should have the display turn on,
- * dim, and turn off "just like it would if you'd woken it up yourself".
- * USC may be adding an intent-based bus API to handle this use case,
- * e.g. turnDisplayOnTemporarily(intent), but there's no timeframe for it.
- *
- * Until that's avaialble, we can get close to Design's specs by
- * requesting a display-on cookie and then releasing the cookie
- * a moment later. */
- const guint m_display_on_seconds = 1;
- guint m_display_on_timer = 0;
- int32_t m_display_on_cookie = NO_DISPLAY_ON_COOKIE;
-
- static constexpr int32_t NO_DISPLAY_ON_COOKIE { std::numeric_limits<int32_t>::min() };
};
/***
****
***/
-Awake::Awake(const std::string& app_name):
- impl(new Impl(app_name))
+Awake::Awake(GDBusConnection* system_bus, const std::string& app_name):
+ impl{new Impl{system_bus, app_name}}
{
}
diff --git a/src/clock.cpp b/src/clock.cpp
index a2ef387..47eebe6 100644
--- a/src/clock.cpp
+++ b/src/clock.cpp
@@ -155,7 +155,7 @@ private:
}
/**
- *** DBus Chatter: com.canonical.powerd
+ *** DBus Chatter: com.lomiri.Repowerd
***
*** Fire Clock::minute_changed() signal when powerd says the system's
*** has awoken from sleep -- the old timestamp is likely out-of-date
diff --git a/src/date-time.cpp b/src/date-time.cpp
index 169426c..911fb7a 100644
--- a/src/date-time.cpp
+++ b/src/date-time.cpp
@@ -245,11 +245,21 @@ bool DateTime::operator<(const DateTime& that) const
return g_date_time_compare(get(), that.get()) < 0;
}
+bool DateTime::operator>(const DateTime& that) const
+{
+ return g_date_time_compare(get(), that.get()) > 0;
+}
+
bool DateTime::operator<=(const DateTime& that) const
{
return g_date_time_compare(get(), that.get()) <= 0;
}
+bool DateTime::operator>=(const DateTime& that) const
+{
+ return g_date_time_compare(get(), that.get()) >= 0;
+}
+
bool DateTime::operator!=(const DateTime& that) const
{
// return true if this isn't set, or if it's not equal
diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp
index fc6a45b..4228258 100644
--- a/src/engine-eds.cpp
+++ b/src/engine-eds.cpp
@@ -1260,11 +1260,6 @@ private:
****
***/
-EdsEngine::EdsEngine():
- p(new Impl(std::shared_ptr<Myself>(new Myself)))
-{
-}
-
EdsEngine::EdsEngine(const std::shared_ptr<Myself> &myself):
p(new Impl(myself))
{
diff --git a/src/haptic.cpp b/src/haptic.cpp
index 7430c04..dc2cb82 100644
--- a/src/haptic.cpp
+++ b/src/haptic.cpp
@@ -37,9 +37,10 @@ class Haptic::Impl
{
public:
- Impl(const Mode& mode):
+ Impl(const Mode& mode, bool repeat):
m_mode(mode),
- m_cancellable(g_cancellable_new())
+ m_cancellable(g_cancellable_new()),
+ m_repeat(repeat)
{
g_bus_get (G_BUS_TYPE_SESSION, m_cancellable, on_bus_ready, this);
}
@@ -93,11 +94,15 @@ private:
// 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);
+ if (m_repeat)
+ {
+ // 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();
}
@@ -146,14 +151,15 @@ private:
GDBusConnection * m_bus = nullptr;
std::vector<uint32_t> m_pattern;
guint m_tag = 0;
+ bool m_repeat = false;
};
/***
****
***/
-Haptic::Haptic(const Mode& mode):
- impl(new Impl (mode))
+Haptic::Haptic(const Mode& mode, bool repeat):
+ impl(new Impl (mode, repeat))
{
}
diff --git a/src/main.cpp b/src/main.cpp
index fdd84b5..6cad190 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -25,6 +25,7 @@
#include <datetime/exporter.h>
#include <datetime/locations-settings.h>
#include <datetime/menu.h>
+#include <datetime/myself.h>
#include <datetime/planner-aggregate.h>
#include <datetime/planner-snooze.h>
#include <datetime/planner-range.h>
@@ -60,7 +61,7 @@ namespace
if (!g_strcmp0("lightdm", g_get_user_name()))
engine.reset(new MockEngine);
else
- engine.reset(new EdsEngine);
+ engine.reset(new EdsEngine(std::shared_ptr<Myself>(new Myself)));
return engine;
}
@@ -70,7 +71,7 @@ namespace
{
// create the live objects
auto live_settings = std::make_shared<LiveSettings>();
- auto live_timezones = std::make_shared<LiveTimezones>(live_settings);
+ auto live_timezones = std::make_shared<LiveTimezones>(live_settings, timezone_);
auto live_clock = std::make_shared<LiveClock>(timezone_);
// create a full-month planner currently pointing to the current month
@@ -131,8 +132,17 @@ main(int /*argc*/, char** /*argv*/)
bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
textdomain(GETTEXT_PACKAGE);
+ // get the system bus
+ GError* error {};
+ auto system_bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, &error);
+ if (error != nullptr) {
+ g_critical("Unable to get system bus: %s", error->message);
+ g_clear_error(&error);
+ return 0;
+ }
+
auto engine = create_engine();
- auto timezone_ = std::make_shared<TimedatedTimezone>();
+ auto timezone_ = std::make_shared<TimedatedTimezone>(system_bus);
auto state = create_state(engine, timezone_);
auto actions = std::make_shared<LiveActions>(state);
MenuFactory factory(actions, state);
@@ -141,14 +151,23 @@ main(int /*argc*/, char** /*argv*/)
// set up the snap decisions
auto snooze_planner = std::make_shared<SnoozePlanner>(state->settings, state->clock);
auto notification_engine = std::make_shared<ain::Engine>("ayatana-indicator-datetime-service");
- std::unique_ptr<Snap> snap (new Snap(notification_engine, state->settings));
+ auto sound_builder = std::make_shared<uin::DefaultSoundBuilder>();
+ std::unique_ptr<Snap> snap (new Snap(notification_engine, sound_builder, state->settings, system_bus));
auto alarm_queue = create_simple_alarm_queue(state->clock, snooze_planner, engine, timezone_);
- auto on_snooze = [snooze_planner](const Appointment& appointment, const Alarm& alarm) {
- snooze_planner->add(appointment, alarm);
+ auto on_response = [snooze_planner, actions](const Appointment& appointment, const Alarm& alarm, const Snap::Response& response) {
+ switch(response) {
+ case Snap::Response::Snooze:
+ snooze_planner->add(appointment, alarm);
+ break;
+ case Snap::Response::ShowApp:
+ actions->open_appointment(appointment, appointment.begin);
+ break;
+ case Snap::Response::None:
+ break;
+ }
};
- auto on_ok = [](const Appointment&, const Alarm&){};
- auto on_alarm_reached = [&engine, &snap, &on_snooze, &on_ok](const Appointment& appointment, const Alarm& alarm) {
- (*snap)(appointment, alarm, on_snooze, on_ok);
+ auto on_alarm_reached = [&engine, &snap, &on_response](const Appointment& appointment, const Alarm& alarm) {
+ (*snap)(appointment, alarm, on_response);
engine->disable_ubuntu_alarm(appointment);
};
alarm_queue->alarm_reached().connect(on_alarm_reached);
@@ -170,5 +189,6 @@ main(int /*argc*/, char** /*argv*/)
g_main_loop_run(loop);
g_main_loop_unref(loop);
+ g_clear_object(&system_bus);
return 0;
}
diff --git a/src/menu.cpp b/src/menu.cpp
index d19ad73..b1ac75c 100644
--- a/src/menu.cpp
+++ b/src/menu.cpp
@@ -22,6 +22,8 @@
#include <datetime/state.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
+#include <algorithm>
+#include <iterator>
#include <vector>
extern "C"
@@ -58,11 +60,90 @@ GMenuModel* Menu::menu_model()
return G_MENU_MODEL(m_menu);
}
+/**
+ * To avoid a giant menu on the PC, and to avoid pushing lower menu items
+ * off-screen on the phone, the menu should show the
+ * next five calendar events, if any.
+ *
+ * The list might include multiple occurrences of the same event (bug 1515821).
+ */
+std::vector<Appointment>
+Menu::get_display_appointments(const std::vector<Appointment>& appointments_in,
+ const DateTime& now,
+ unsigned int max_items)
+{
+ std::vector<Appointment> appointments;
+ std::copy_if(appointments_in.begin(),
+ appointments_in.end(),
+ std::back_inserter(appointments),
+ [now](const Appointment& a){return a.end >= now;});
+
+ if (appointments.size() > max_items)
+ {
+ const auto next_minute = now.add_full(0,0,0,0,1,-now.seconds());
+ const auto start_of_day = now.start_of_day();
+ const auto end_of_day = now.end_of_day();
+
+ /*
+ * If there are more than five, the events shown should be, in order of priority:
+ * 1. any events that start or end (bug 1329048) after the current minute today;
+ * 2. any full-day events that span all of today (bug 1302004);
+ * 3. any events that start or end tomorrow;
+ * 4. any events that start or end the day after tomorrow; and so on.
+ */
+ auto compare = [next_minute, start_of_day, end_of_day](
+ const Appointment& a,
+ const Appointment& b)
+ {
+ const bool a_later_today = (a.begin >= next_minute) || (a.end <= end_of_day);
+ const bool b_later_today = (b.begin >= next_minute) || (b.end <= end_of_day);
+ if (a_later_today != b_later_today)
+ return a_later_today;
+
+ const bool a_full_day_today = (a.begin <= start_of_day) && (end_of_day <= a.end);
+ const bool b_full_day_today = (b.begin <= start_of_day) && (end_of_day <= b.end);
+ if (a_full_day_today != b_full_day_today)
+ return a_full_day_today;
+
+ const bool a_after_today = (a.begin > end_of_day) || (a.end > end_of_day);
+ const bool b_after_today = (a.begin > end_of_day) || (a.end > end_of_day);
+ if (a_after_today != b_after_today)
+ return a_after_today;
+ if (a.begin != b.begin)
+ return a.begin < b.begin;
+ if (b.end != b.end)
+ return a.end < b.end;
+
+ return false;
+ };
+ std::sort(appointments.begin(), appointments.end(), compare);
+ appointments.resize(max_items);
+ }
+
+ /*
+ * However, the display order should be the reverse: full-day events
+ * first (since they start first), part-day events afterward in
+ * chronological order. If multiple events have exactly the same start+end
+ * time, they should be sorted alphabetically.
+ */
+ auto compare = [](const Appointment& a, const Appointment& b)
+ {
+ if (a.begin != b.begin)
+ return a.begin < b.begin;
+
+ if (a.end != b.end)
+ return a.end < b.end;
+
+ return a.summary < b.summary;
+ };
+ std::sort(appointments.begin(), appointments.end(), compare);
+ return appointments;
+}
+
/****
*****
****/
-
#define ALARM_ICON_NAME "alarm-clock"
#define CALENDAR_ICON_NAME "calendar"
@@ -150,25 +231,19 @@ protected:
void update_upcoming()
{
- // The usual case is on desktop (and /only/ case on phone)
- // is that we're looking at the current date and want to see
- // "the next five calendar events, if any."
- //
- // However on the Desktop when the user clicks onto a different
- // calendar date, show the next five calendar events starting
- // from the beginning of that clicked day.
- DateTime begin;
+ // The usual case is to show events germane to the current time.
+ // However when the user clicks onto a different calendar date,
+ // we pick events starting from the beginning of that clicked day.
const auto now = m_state->clock->localtime();
const auto calendar_day = m_state->calendar_month->month().get();
- if ((profile() == Desktop) && !DateTime::is_same_day(now, calendar_day))
- begin = calendar_day.start_of_day();
- else
- begin = now.start_of_minute();
+ const auto begin = DateTime::is_same_day(now, calendar_day)
+ ? now.start_of_minute()
+ : calendar_day.start_of_day();
- std::vector<Appointment> upcoming;
- for(const auto& a : m_state->calendar_upcoming->appointments().get())
- if (begin <= a.begin)
- upcoming.push_back(a);
+ auto upcoming = get_display_appointments(
+ m_state->calendar_upcoming->appointments().get(),
+ begin
+ );
if (m_upcoming != upcoming)
{
@@ -339,9 +414,12 @@ private:
if (!appt.color.empty())
g_menu_item_set_attribute (menu_item, "x-ayatana-color", "s", appt.color.c_str());
- if (action_name != nullptr)
+ if (action_name != nullptr) {
g_menu_item_set_action_and_target_value (menu_item, action_name,
- g_variant_new_string (appt.uid.c_str()));
+ g_variant_new ("(sx)",
+ appt.uid.c_str(),
+ unix_time));
+ }
g_menu_append_item (menu, menu_item);
g_object_unref (menu_item);
diff --git a/src/myself.cpp b/src/myself.cpp
index 9c02054..ae2f061 100644
--- a/src/myself.cpp
+++ b/src/myself.cpp
@@ -25,6 +25,8 @@
#include <libaccounts-glib/accounts-glib.h>
#endif
+#include <libaccounts-glib/ag-account.h>
+
#include <algorithm>
namespace ayatana {
diff --git a/src/notifications.cpp b/src/notifications.cpp
index 051653d..b36227b 100644
--- a/src/notifications.cpp
+++ b/src/notifications.cpp
@@ -21,10 +21,23 @@
#include <libnotify/notify.h>
+#include <messaging-menu/messaging-menu-app.h>
+#include <messaging-menu/messaging-menu-message.h>
+
+#ifdef HAS_URLDISPATCHER
+#include <lomiri-url-dispatcher.h>
+#endif
+
+#include <uuid/uuid.h>
+
+#include <gio/gdesktopappinfo.h>
+
#include <map>
#include <set>
#include <string>
#include <vector>
+#include <memory>
+
namespace ayatana {
namespace indicator {
@@ -45,9 +58,13 @@ public:
std::string m_body;
std::string m_icon_name;
std::chrono::seconds m_duration;
+ gint64 m_start_time {};
std::set<std::string> m_string_hints;
std::vector<std::pair<std::string,std::string>> m_actions;
std::function<void(const std::string&)> m_closed_callback;
+ std::function<void()> m_timeout_callback;
+ bool m_show_notification_bubble;
+ bool m_post_to_messaging_menu;
};
Builder::Builder():
@@ -101,6 +118,30 @@ Builder::set_closed_callback (std::function<void (const std::string&)> cb)
impl->m_closed_callback.swap (cb);
}
+void
+Builder::set_timeout_callback (std::function<void()> cb)
+{
+ impl->m_timeout_callback.swap (cb);
+}
+
+void
+Builder::set_start_time (uint64_t time)
+{
+ impl->m_start_time = time;
+}
+
+void
+Builder::set_show_notification_bubble (bool show)
+{
+ impl->m_show_notification_bubble = show;
+}
+
+void
+Builder::set_post_to_messaging_menu (bool post)
+{
+ impl->m_post_to_messaging_menu = post;
+}
+
/***
****
***/
@@ -110,7 +151,14 @@ class Engine::Impl
struct notification_data
{
std::shared_ptr<NotifyNotification> nn;
- std::function<void(const std::string&)> closed_callback;
+ Builder::Impl data;
+ };
+
+ struct messaging_menu_data
+ {
+ std::string msg_id;
+ std::function<void()> callback;
+ Engine::Impl *self;
};
public:
@@ -120,13 +168,23 @@ public:
{
if (!notify_init(app_name.c_str()))
g_critical("Unable to initialize libnotify!");
+
+ // messaging menu
+ auto app_id = calendar_app_id();
+ if (!app_id.empty()) {
+ m_messaging_app.reset(messaging_menu_app_new(app_id.c_str()), g_object_unref);
+ messaging_menu_app_register(m_messaging_app.get());
+ }
}
~Impl()
{
close_all ();
+ remove_all ();
notify_uninit ();
+ if (m_messaging_app)
+ messaging_menu_app_unregister (m_messaging_app.get());
}
const std::string& app_name() const
@@ -217,10 +275,15 @@ public:
notification_key_quark(),
GINT_TO_POINTER(key));
- m_notifications[key] = { nn, info.m_closed_callback };
+ m_notifications[key] = { nn, info };
g_signal_connect (nn.get(), "closed",
G_CALLBACK(on_notification_closed), this);
+ if (!info.m_show_notification_bubble) {
+ post(info);
+ return ret;
+ }
+
GError * error = nullptr;
if (notify_notification_show(nn.get(), &error))
{
@@ -238,6 +301,76 @@ public:
return ret;
}
+ std::string post(const Builder::Impl& data)
+ {
+ if (!data.m_post_to_messaging_menu) {
+ return "";
+ }
+
+ if (!m_messaging_app) {
+ return std::string();
+ }
+ uuid_t message_uuid;
+ uuid_generate(message_uuid);
+
+ char uuid_buf[37];
+ uuid_unparse(message_uuid, uuid_buf);
+ const std::string message_id(uuid_buf);
+
+ // use full icon path name, "calendar-app" does not work with themed icons
+ auto icon_file = g_file_new_for_path(calendar_app_icon().c_str());
+ // messaging_menu_message_new: will take control of icon object
+ GIcon *icon = g_file_icon_new(icon_file);
+ g_object_unref(icon_file);
+
+ // check if source exists
+ if (!messaging_menu_app_has_source(m_messaging_app.get(), m_app_name.c_str()))
+ messaging_menu_app_append_source(m_messaging_app.get(), m_app_name.c_str(), nullptr, "Calendar");
+
+ auto msg = messaging_menu_message_new(message_id.c_str(),
+ icon,
+ data.m_title.c_str(),
+ nullptr,
+ data.m_body.c_str(),
+ data.m_start_time * G_USEC_PER_SEC); // secs -> microsecs
+ if (msg)
+ {
+ std::shared_ptr<messaging_menu_data> msg_data(new messaging_menu_data{message_id, data.m_timeout_callback, this});
+ m_messaging_messages[message_id] = msg_data;
+ g_signal_connect(G_OBJECT(msg), "activate",
+ G_CALLBACK(on_message_activated), msg_data.get());
+ messaging_menu_app_append_message(m_messaging_app.get(), msg, m_app_name.c_str(), false);
+
+ // we use that to keep track of messaging, in case of message get cleared from menu
+ g_object_set_data_full(G_OBJECT(msg), "destroy-notify", msg_data.get(), on_message_destroyed);
+ // keep the message control with message_menu
+ g_object_unref(msg);
+
+ return message_id;
+ } else {
+ g_warning("Fail to create messaging menu message");
+ }
+ return "";
+ }
+
+ void remove (const std::string &key)
+ {
+ auto it = m_messaging_messages.find(key);
+ if (it != m_messaging_messages.end())
+ {
+ // tell the server to remove message
+ messaging_menu_app_remove_message_by_id(m_messaging_app.get(), it->second->msg_id.c_str());
+ // message will be remove by on_message_destroyed cb.
+ }
+ }
+
+ void remove_all ()
+ {
+ // call remove() on all our keys
+ while (!m_messaging_messages.empty())
+ remove(m_messaging_messages.begin()->first);
+ }
+
private:
const std::set<std::string>& server_caps() const
@@ -279,6 +412,28 @@ private:
static_cast<Impl*>(gself)->remove_closed_notification(GPOINTER_TO_INT(gkey));
}
+ static void on_message_activated (MessagingMenuMessage *,
+ const char *,
+ GVariant *,
+ gpointer data)
+ {
+ auto msg_data = static_cast<messaging_menu_data*>(data);
+ auto it = msg_data->self->m_messaging_messages.find(msg_data->msg_id);
+ g_return_if_fail (it != msg_data->self->m_messaging_messages.end());
+ const auto& ndata = it->second;
+
+ if (ndata->callback)
+ ndata->callback();
+ }
+
+ static void on_message_destroyed(gpointer data)
+ {
+ auto msg_data = static_cast<messaging_menu_data*>(data);
+ auto it = msg_data->self->m_messaging_messages.find(msg_data->msg_id);
+ if (it != msg_data->self->m_messaging_messages.end())
+ msg_data->self->m_messaging_messages.erase(it);
+ }
+
void remove_closed_notification (int key)
{
auto it = m_notifications.find(key);
@@ -286,25 +441,67 @@ private:
const auto& ndata = it->second;
auto nn = ndata.nn.get();
- if (ndata.closed_callback)
+
+ if (ndata.data.m_closed_callback)
{
std::string action;
-
const GQuark q = notification_action_quark();
const gpointer p = g_object_get_qdata(G_OBJECT(nn), q);
if (p != nullptr)
action = static_cast<const char*>(p);
- ndata.closed_callback (action);
+ ndata.data.m_closed_callback (action);
+ // empty action means that the notification got timeout
+ // post a message on messaging menu
+ if (action.empty())
+ post(ndata.data);
}
m_notifications.erase(it);
}
+ static std::string calendar_app_id()
+ {
+#ifdef HAS_URLDISPATCHER
+ auto urls = g_strsplit("calendar://", ",", 0);
+ auto appids = lomiri_url_dispatch_url_appid(const_cast<const gchar**>(urls));
+ g_strfreev(urls);
+ std::string result;
+ if (appids != nullptr) {
+ // Due the use of old API by messaging_menu we need append a extra ".desktop" to the app_id.
+ result = std::string(appids[0]) + ".desktop";
+ g_strfreev(appids);
+ }
+ return result;
+#else
+ return std::string();
+#endif
+ }
+
+ static std::string calendar_app_icon()
+ {
+ auto app_desktop = g_desktop_app_info_new(calendar_app_id().c_str());
+ if (app_desktop != nullptr) {
+ auto icon_name = g_desktop_app_info_get_string(app_desktop, "Icon");
+ g_object_unref(app_desktop);
+ if (icon_name) {
+ std::string result(icon_name);
+ g_free(icon_name);
+ return result;
+ }
+ }
+ g_warning("Fail to get calendar icon");
+ return std::string();
+ }
+
/***
****
***/
+ // messaging menu
+ std::shared_ptr<MessagingMenuApp> m_messaging_app;
+ std::map<std::string, std::shared_ptr<messaging_menu_data> > m_messaging_messages;
+
const std::string m_app_name;
// key-to-data
diff --git a/src/planner-snooze.cpp b/src/planner-snooze.cpp
index cb365ca..b81c912 100644
--- a/src/planner-snooze.cpp
+++ b/src/planner-snooze.cpp
@@ -59,7 +59,9 @@ public:
appt.alarms.push_back(alarm);
// reschedule the alarm to go off N minutes from now
- const auto offset = std::chrono::minutes(m_settings->snooze_duration.get());
+ // also take into count every whole minute since the alarm went off
+ const auto offset_to_now = std::chrono::duration_cast<std::chrono::minutes>(std::chrono::microseconds(DateTime::NowLocal() - appt.begin));
+ const auto offset = offset_to_now + std::chrono::minutes(m_settings->snooze_duration.get());
appt.begin += offset;
appt.end += offset;
appt.alarms[0].time += offset;
diff --git a/src/planner-upcoming.cpp b/src/planner-upcoming.cpp
index 918ebbe..149ac09 100644
--- a/src/planner-upcoming.cpp
+++ b/src/planner-upcoming.cpp
@@ -33,7 +33,7 @@ UpcomingPlanner::UpcomingPlanner(const std::shared_ptr<RangePlanner>& range_plan
{
date().changed().connect([this](const DateTime& dt){
// set the range to the upcoming month
- const auto b = dt.add_days(-1).start_of_day();
+ const auto b = dt.start_of_day();
const auto e = b.add_full(0, 1, 0, 0, 0, 0);
g_debug("%p setting date range to [%s..%s]", this, b.format("%F %T").c_str(), e.format("%F %T").c_str());
m_range_planner->range().set(std::pair<DateTime,DateTime>(b,e));
diff --git a/src/settings-live.cpp b/src/settings-live.cpp
index 5c2addb..dc90d0e 100644
--- a/src/settings-live.cpp
+++ b/src/settings-live.cpp
@@ -19,6 +19,11 @@
#include <datetime/settings-live.h>
+extern "C"
+{
+ #include <ayatana/common/utils.h>
+}
+
namespace ayatana {
namespace indicator {
namespace datetime {
@@ -29,13 +34,15 @@ namespace datetime {
LiveSettings::~LiveSettings()
{
+ g_clear_object(&m_settings_general_notification);
+ g_clear_object(&m_settings_cal_notification);
g_clear_object(&m_settings);
}
LiveSettings::LiveSettings():
m_settings(g_settings_new(SETTINGS_INTERFACE))
{
- g_signal_connect (m_settings, "changed", G_CALLBACK(on_changed), this);
+ g_signal_connect (m_settings, "changed", G_CALLBACK(on_changed_ccid), this);
// init the Properties from the GSettings backend
update_custom_time_format();
@@ -58,6 +65,25 @@ LiveSettings::LiveSettings():
update_alarm_haptic();
update_snooze_duration();
+ if (ayatana_common_utils_is_lomiri())
+ {
+ m_settings_cal_notification = g_settings_new_with_path(SETTINGS_NOTIFY_SCHEMA_ID, SETTINGS_NOTIFY_CALENDAR_PATH);
+ m_settings_general_notification = g_settings_new(SETTINGS_NOTIFY_APPS_SCHEMA_ID);
+ g_signal_connect (m_settings_cal_notification, "changed", G_CALLBACK(on_changed_cal_notification), this);
+ g_signal_connect (m_settings_general_notification, "changed", G_CALLBACK(on_changed_general_notification), this);
+ update_cal_notification_enabled();
+ update_cal_notification_sounds();
+ update_cal_notification_vibrations();
+ update_cal_notification_bubbles();
+ update_cal_notification_list();
+ update_vibrate_silent_mode();
+ }
+ else
+ {
+ m_settings_cal_notification = NULL;
+ m_settings_general_notification = NULL;
+ }
+
// now listen for clients to change the properties s.t. we can sync update GSettings
custom_time_format.changed().connect([this](const std::string& value){
@@ -140,6 +166,30 @@ LiveSettings::LiveSettings():
snooze_duration.changed().connect([this](unsigned int value){
g_settings_set_uint(m_settings, SETTINGS_SNOOZE_DURATION_S, value);
});
+
+ cal_notification_enabled.changed().connect([this](bool value){
+ g_settings_set_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_ENABLED_KEY, value);
+ });
+
+ cal_notification_sounds.changed().connect([this](bool value){
+ g_settings_set_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_SOUNDS_KEY, value);
+ });
+
+ cal_notification_vibrations.changed().connect([this](bool value){
+ g_settings_set_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_VIBRATIONS_KEY, value);
+ });
+
+ cal_notification_bubbles.changed().connect([this](bool value){
+ g_settings_set_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_BUBBLES_KEY, value);
+ });
+
+ cal_notification_list.changed().connect([this](bool value){
+ g_settings_set_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_LIST_KEY, value);
+ });
+
+ vibrate_silent_mode.changed().connect([this](bool value){
+ g_settings_set_boolean(m_settings_general_notification, SETTINGS_VIBRATE_SILENT_KEY, value);
+ });
}
/***
@@ -261,18 +311,91 @@ void LiveSettings::update_snooze_duration()
snooze_duration.set(g_settings_get_uint(m_settings, SETTINGS_SNOOZE_DURATION_S));
}
+void LiveSettings::update_cal_notification_enabled()
+{
+ cal_notification_enabled.set(g_settings_get_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_ENABLED_KEY));
+}
+
+void LiveSettings::update_cal_notification_sounds()
+{
+ cal_notification_sounds.set(g_settings_get_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_SOUNDS_KEY));
+}
+
+void LiveSettings::update_cal_notification_vibrations()
+{
+ cal_notification_vibrations.set(g_settings_get_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_VIBRATIONS_KEY));
+}
+
+void LiveSettings::update_cal_notification_bubbles()
+{
+ cal_notification_bubbles.set(g_settings_get_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_BUBBLES_KEY));
+}
+
+void LiveSettings::update_cal_notification_list()
+{
+ cal_notification_list.set(g_settings_get_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_LIST_KEY));
+}
+
+void LiveSettings::update_vibrate_silent_mode()
+{
+ vibrate_silent_mode.set(g_settings_get_boolean(m_settings_general_notification, SETTINGS_VIBRATE_SILENT_KEY));
+}
+
+/***
+****
+***/
+
+void LiveSettings::on_changed_cal_notification(GSettings* /*settings*/,
+ gchar* key,
+ gpointer gself)
+{
+ static_cast<LiveSettings*>(gself)->update_key_cal_notification(key);
+}
+
+
+void LiveSettings::update_key_cal_notification(const std::string& key)
+{
+ if (key == SETTINGS_NOTIFY_ENABLED_KEY)
+ update_cal_notification_enabled();
+ else if (key == SETTINGS_NOTIFY_SOUNDS_KEY)
+ update_cal_notification_sounds();
+ else if (key == SETTINGS_NOTIFY_VIBRATIONS_KEY)
+ update_cal_notification_vibrations();
+ else if (key == SETTINGS_NOTIFY_BUBBLES_KEY)
+ update_cal_notification_bubbles();
+ else if (key == SETTINGS_NOTIFY_LIST_KEY)
+ update_cal_notification_list();
+}
+
+/***
+****
+***/
+
+void LiveSettings::on_changed_general_notification(GSettings* /*settings*/,
+ gchar* key,
+ gpointer gself)
+{
+ static_cast<LiveSettings*>(gself)->update_key_general_notification(key);
+}
+
+void LiveSettings::update_key_general_notification(const std::string& key)
+{
+ if (key == SETTINGS_VIBRATE_SILENT_KEY)
+ update_vibrate_silent_mode();
+}
+
/***
****
***/
-void LiveSettings::on_changed(GSettings* /*settings*/,
- gchar* key,
- gpointer gself)
+void LiveSettings::on_changed_ccid(GSettings* /*settings*/,
+ gchar* key,
+ gpointer gself)
{
- static_cast<LiveSettings*>(gself)->update_key(key);
+ static_cast<LiveSettings*>(gself)->update_key_ccid(key);
}
-void LiveSettings::update_key(const std::string& key)
+void LiveSettings::update_key_ccid(const std::string& key)
{
if (key == SETTINGS_LOCATIONS_S)
update_locations();
diff --git a/src/snap.cpp b/src/snap.cpp
index f0300af..00c4743 100644
--- a/src/snap.cpp
+++ b/src/snap.cpp
@@ -53,19 +53,25 @@ class Snap::Impl
public:
Impl(const std::shared_ptr<ayatana::indicator::notifications::Engine>& engine,
- const std::shared_ptr<const Settings>& settings):
+ const std::shared_ptr<ayatana::indicator::notifications::SoundBuilder>& sound_builder,
+ const std::shared_ptr<const Settings>& settings,
+ GDBusConnection* system_bus):
m_engine(engine),
+ m_sound_builder(sound_builder),
m_settings(settings),
- m_cancellable(g_cancellable_new())
+ m_cancellable(g_cancellable_new()),
+ m_system_bus{G_DBUS_CONNECTION(g_object_ref(system_bus))}
{
auto object_path = g_strdup_printf("/org/freedesktop/Accounts/User%lu", (gulong)getuid());
- accounts_service_sound_proxy_new_for_bus(G_BUS_TYPE_SYSTEM,
- G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES,
- "org.freedesktop.Accounts",
- object_path,
- m_cancellable,
- on_sound_proxy_ready,
- this);
+
+
+ accounts_service_sound_proxy_new(m_system_bus,
+ G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES,
+ "org.freedesktop.Accounts",
+ object_path,
+ m_cancellable,
+ on_sound_proxy_ready,
+ this);
g_free(object_path);
}
@@ -74,6 +80,7 @@ public:
g_cancellable_cancel(m_cancellable);
g_clear_object(&m_cancellable);
g_clear_object(&m_accounts_service_sound_proxy);
+ g_clear_object(&m_system_bus);
for (const auto& key : m_notifications)
m_engine->close (key);
@@ -81,9 +88,14 @@ public:
void operator()(const Appointment& appointment,
const Alarm& alarm,
- appointment_func snooze,
- appointment_func ok)
+ response_func on_response)
{
+ // If calendar notifications are disabled, don't show them
+ if (!appointment.is_ubuntu_alarm() && !calendar_notifications_are_enabled()) {
+ g_debug("Skipping disabled calendar event '%s' notification", appointment.summary.c_str());
+ return;
+ }
+
/* Alarms and calendar events are treated differently.
Alarms should require manual intervention to dismiss.
Calendar events are less urgent and shouldn't require manual
@@ -91,11 +103,14 @@ public:
const bool interactive = appointment.is_ubuntu_alarm() && m_engine->supports_actions();
// force the system to stay awake
- auto awake = std::make_shared<ain::Awake>(m_engine->app_name());
+ std::shared_ptr<ain::Awake> awake;
+ if (appointment.is_ubuntu_alarm() || calendar_bubbles_enabled() || calendar_list_enabled()) {
+ awake = std::make_shared<ain::Awake>(m_system_bus, m_engine->app_name());
+ }
// calendar events are muted in silent mode; alarm clocks never are
std::shared_ptr<ain::Sound> sound;
- if (appointment.is_ubuntu_alarm() || !silent_mode()) {
+ if (appointment.is_ubuntu_alarm() || (calendar_sounds_enabled() && !silent_mode())) {
// create the sound.
const auto role = appointment.is_ubuntu_alarm() ? "alarm" : "alert";
const auto uri = get_alarm_uri(appointment, alarm, m_settings);
@@ -106,18 +121,22 @@ public:
// create the haptic feedback...
std::shared_ptr<ain::Haptic> haptic;
- if (should_vibrate()) {
- const auto haptic_mode = m_settings->alarm_haptic.get();
- if (haptic_mode == "pulse")
- haptic = std::make_shared<ain::Haptic>(ain::Haptic::MODE_PULSE);
+ if (should_vibrate() && (appointment.is_ubuntu_alarm() || calendar_vibrations_enabled())) {
+ // when in silent mode should only vibrate if user defined so
+ if (!silent_mode() || vibrate_in_silent_mode_enabled()) {
+ const auto haptic_mode = m_settings->alarm_haptic.get();
+ if (haptic_mode == "pulse")
+ haptic = std::make_shared<ain::Haptic>(ain::Haptic::MODE_PULSE, appointment.is_ubuntu_alarm());
+ }
}
// show a notification...
const auto minutes = std::chrono::minutes(m_settings->alarm_duration.get());
ain::Builder b;
b.set_body (appointment.summary);
- b.set_icon_name ("alarm-clock");
+ b.set_icon_name (appointment.is_ubuntu_alarm() ? "alarm-clock" : "calendar-app");
b.add_hint (ain::Builder::HINT_NONSHAPED_ICON);
+ b.set_start_time (appointment.begin.to_unix());
const char * timefmt;
if (is_locale_12h()) {
@@ -130,28 +149,53 @@ public:
timefmt = _("%a, %H:%M");
}
const auto timestr = appointment.begin.format(timefmt);
- auto title = g_strdup_printf(_("Alarm %s"), timestr.c_str());
+
+ const char * titlefmt;
+ if (appointment.is_ubuntu_alarm()) {
+ titlefmt = _("Alarm %s");
+ } else {
+ titlefmt = _("Event %s");
+ }
+ auto title = g_strdup_printf(titlefmt, timestr.c_str());
b.set_title (title);
g_free (title);
b.set_timeout (std::chrono::duration_cast<std::chrono::seconds>(minutes));
if (interactive) {
b.add_hint (ain::Builder::HINT_SNAP);
b.add_hint (ain::Builder::HINT_AFFIRMATIVE_HINT);
- b.add_action ("ok", _("OK"));
- b.add_action ("snooze", _("Snooze"));
+ b.add_action (ACTION_NONE, _("OK"));
+ b.add_action (ACTION_SNOOZE, _("Snooze"));
+ } else {
+ b.add_hint (ain::Builder::HINT_INTERACTIVE);
+ b.add_action (ACTION_SHOW_APP, _("OK"));
}
// 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, alarm, snooze, ok, sound, awake, haptic]
+ b.set_closed_callback([appointment, alarm, on_response, sound, awake, haptic]
(const std::string& action){
- if (action == "snooze")
- snooze(appointment, alarm);
+ Snap::Response response;
+ if ((action == ACTION_SNOOZE) || (appointment.is_ubuntu_alarm() && action.empty()))
+ response = Snap::Response::Snooze;
+ else if (action == ACTION_SHOW_APP)
+ response = Snap::Response::ShowApp;
else
- ok(appointment, alarm);
+ response = Snap::Response::None;
+
+ on_response(appointment, alarm, response);
});
+ //TODO: we need to extend it to support alarms appointments
+ if (!appointment.is_ubuntu_alarm()) {
+ b.set_timeout_callback([appointment, alarm, on_response](){
+ on_response(appointment, alarm, Snap::Response::ShowApp);
+ });
+ }
+
+ b.set_show_notification_bubble(appointment.is_ubuntu_alarm() || calendar_bubbles_enabled());
+ b.set_post_to_messaging_menu(appointment.is_ubuntu_alarm() || calendar_list_enabled());
+
const auto key = m_engine->show(b);
if (key)
m_notifications.insert (key);
@@ -159,12 +203,42 @@ public:
private:
+ bool calendar_notifications_are_enabled() const
+ {
+ return m_settings->cal_notification_enabled.get();
+ }
+
+ bool calendar_sounds_enabled() const
+ {
+ return m_settings->cal_notification_sounds.get();
+ }
+
+ bool calendar_vibrations_enabled() const
+ {
+ return m_settings->cal_notification_vibrations.get();
+ }
+
+ bool calendar_bubbles_enabled() const
+ {
+ return m_settings->cal_notification_bubbles.get();
+ }
+
+ bool calendar_list_enabled() const
+ {
+ return m_settings->cal_notification_list.get();
+ }
+
+ bool vibrate_in_silent_mode_enabled() const
+ {
+ return m_settings->vibrate_silent_mode.get();
+ }
+
static void on_sound_proxy_ready(GObject* /*source_object*/, GAsyncResult* res, gpointer gself)
{
GError * error;
error = nullptr;
- auto proxy = accounts_service_sound_proxy_new_for_bus_finish (res, &error);
+ auto proxy = accounts_service_sound_proxy_new_finish (res, &error);
if (error != nullptr)
{
if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
@@ -230,6 +304,11 @@ private:
std::set<int> m_notifications;
GCancellable * m_cancellable {nullptr};
AccountsServiceSound * m_accounts_service_sound_proxy {nullptr};
+ GDBusConnection * m_system_bus {nullptr};
+
+ static constexpr char const * ACTION_NONE {"none"};
+ static constexpr char const * ACTION_SNOOZE {"snooze"};
+ static constexpr char const * ACTION_SHOW_APP {"show-app"};
};
/***
@@ -237,8 +316,10 @@ private:
***/
Snap::Snap(const std::shared_ptr<ayatana::indicator::notifications::Engine>& engine,
- const std::shared_ptr<const Settings>& settings):
- impl(new Impl(engine, settings))
+ const std::shared_ptr<ayatana::indicator::notifications::SoundBuilder>& sound_builder,
+ const std::shared_ptr<const Settings>& settings,
+ GDBusConnection* system_bus):
+ impl(new Impl(engine, sound_builder, settings, system_bus))
{
}
@@ -249,10 +330,9 @@ Snap::~Snap()
void
Snap::operator()(const Appointment& appointment,
const Alarm& alarm,
- appointment_func show,
- appointment_func ok)
+ response_func on_response)
{
- (*impl)(appointment, alarm, show, ok);
+ (*impl)(appointment, alarm, on_response);
}
/***
diff --git a/src/timezone-timedated.cpp b/src/timezone-timedated.cpp
index d38557b..1b6497e 100644
--- a/src/timezone-timedated.cpp
+++ b/src/timezone-timedated.cpp
@@ -17,6 +17,7 @@
* Charles Kerr <charles.kerr@canonical.com>
*/
+#include <datetime/dbus-shared.h>
#include <datetime/timezone-timedated.h>
#include <gio/gio.h>
@@ -36,166 +37,169 @@ class TimedatedTimezone::Impl
{
public:
- Impl(TimedatedTimezone& owner, std::string filename):
- m_owner(owner),
- m_filename(filename)
+ Impl(TimedatedTimezone& owner, GDBusConnection* connection):
+ m_owner{owner},
+ m_connection{G_DBUS_CONNECTION(g_object_ref(G_OBJECT(connection)))},
+ m_cancellable{g_cancellable_new()}
{
- g_debug("Filename is '%s'", filename.c_str());
- monitor_timezone_property();
+ // set the fallback value
+ m_owner.timezone.set("Etc/Utc");
+
+ // watch for timedate1 on the bus
+ m_watcher_id = g_bus_watch_name_on_connection(
+ m_connection,
+ Bus::Timedate1::BUSNAME,
+ G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
+ on_timedate1_appeared,
+ on_timedate1_vanished,
+ this,
+ nullptr);
+
+ // listen for changed properties
+ m_signal_subscription_id = g_dbus_connection_signal_subscribe(
+ m_connection,
+ Bus::Timedate1::IFACE,
+ Bus::Properties::IFACE,
+ Bus::Properties::Signals::PROPERTIES_CHANGED,
+ Bus::Timedate1::ADDR,
+ nullptr,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ on_properties_changed,
+ this,
+ nullptr);
}
~Impl()
{
- clear();
- }
+ g_cancellable_cancel(m_cancellable);
+ g_clear_object(&m_cancellable);
-private:
+ g_bus_unwatch_name(m_watcher_id);
- void clear()
- {
- if (m_connection && m_signal_subscription_id)
- {
- g_dbus_connection_signal_unsubscribe (m_connection, m_signal_subscription_id);
- m_signal_subscription_id = 0;
- }
+ g_dbus_connection_signal_unsubscribe(m_connection, m_signal_subscription_id);
g_clear_object(&m_connection);
}
- static void on_properties_changed (GDBusConnection *connection G_GNUC_UNUSED,
- const gchar *sender_name G_GNUC_UNUSED,
- const gchar *object_path G_GNUC_UNUSED,
- const gchar *interface_name G_GNUC_UNUSED,
- const gchar *signal_name G_GNUC_UNUSED,
- GVariant *parameters,
- gpointer gself)
- {
- auto self = static_cast<Impl*>(gself);
- const char *tz;
- GVariant *changed_properties;
- gchar **invalidated_properties;
+private:
- g_variant_get (parameters, "(s@a{sv}^as)", NULL, &changed_properties, &invalidated_properties);
+ static void on_timedate1_appeared(GDBusConnection * /*connection*/,
+ const gchar * name,
+ const gchar * /*name_owner*/,
+ gpointer gself)
+ {
+ g_debug("%s appeared on bus", name);
- if (g_variant_lookup(changed_properties, "Timezone", "&s", &tz, NULL))
- self->notify_timezone(tz);
- else if (g_strv_contains (invalidated_properties, "Timezone"))
- self->notify_timezone(self->get_timezone_from_file(self->m_filename));
+ static_cast<Impl*>(gself)->ask_for_timezone();
+ }
- g_variant_unref (changed_properties);
- g_strfreev (invalidated_properties);
+ static void on_timedate1_vanished(GDBusConnection * /*connection*/,
+ const gchar * name,
+ gpointer /*gself*/)
+ {
+ g_debug("%s not present on bus", name);
}
- void monitor_timezone_property()
+ static void on_properties_changed(GDBusConnection * /*connection*/,
+ const gchar * /*sender_name*/,
+ const gchar * /*object_path*/,
+ const gchar * /*interface_name*/,
+ const gchar * /*signal_name*/,
+ GVariant * parameters,
+ gpointer gself)
{
- GError *err = nullptr;
-
- /*
- * There is an unlikely race which happens if there is an activation
- * and timezone change before our match rule is added.
- */
- notify_timezone(get_timezone_from_file(m_filename));
-
- /*
- * Make sure the bus is around at least until we add the match rules,
- * otherwise things (tests) are sad.
- */
- m_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM,
- nullptr,
- &err);
-
- if (err)
+ auto self = static_cast<Impl*>(gself);
+
+ GVariant* changed_properties {};
+ gchar** invalidated_properties {};
+ g_variant_get(parameters, "(s@a{sv}^as)", NULL, &changed_properties, &invalidated_properties);
+
+ const char* tz {};
+ if (g_variant_lookup(changed_properties, Bus::Timedate1::Properties::TIMEZONE, "&s", &tz, NULL))
+ {
+ if (tz != nullptr)
+ self->set_timezone(tz);
+ else
+ g_warning("%s no timezone found", G_STRLOC);
+ }
+ else if (g_strv_contains(invalidated_properties, Bus::Timedate1::Properties::TIMEZONE))
{
- g_warning("Couldn't get bus connection: '%s'", err->message);
- g_error_free(err);
- return;
+ self->ask_for_timezone();
}
- m_signal_subscription_id = g_dbus_connection_signal_subscribe(m_connection,
- "org.freedesktop.timedate1",
- "org.freedesktop.DBus.Properties",
- "PropertiesChanged",
- "/org/freedesktop/timedate1",
- NULL, G_DBUS_SIGNAL_FLAGS_NONE,
- on_properties_changed,
- this, nullptr);
+ g_variant_unref(changed_properties);
+ g_strfreev(invalidated_properties);
}
- void notify_timezone(std::string new_timezone)
+ void ask_for_timezone()
{
- g_debug("notify_timezone '%s'", new_timezone.c_str());
- if (!new_timezone.empty())
- m_owner.timezone.set(new_timezone);
+ g_dbus_connection_call(
+ m_connection,
+ Bus::Timedate1::BUSNAME,
+ Bus::Timedate1::ADDR,
+ Bus::Properties::IFACE,
+ Bus::Properties::Methods::GET,
+ g_variant_new("(ss)", Bus::Timedate1::IFACE, Bus::Timedate1::Properties::TIMEZONE),
+ G_VARIANT_TYPE("(v)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ m_cancellable,
+ on_get_timezone_ready,
+ this);
}
- std::string get_timezone_from_file(const std::string& filename)
+ static void on_get_timezone_ready(GObject * connection,
+ GAsyncResult * res,
+ gpointer gself)
{
- GError * error;
- GIOChannel * io_channel;
- std::string ret;
-
- // read through filename line-by-line until we fine a nonempty non-comment line
- error = nullptr;
- io_channel = g_io_channel_new_file(filename.c_str(), "r", &error);
- if (error == nullptr)
+ GError* error {};
+ GVariant* v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(connection), res, &error);
+ if (error != nullptr)
{
- auto line = g_string_new(nullptr);
-
- while(ret.empty())
- {
- const auto io_status = g_io_channel_read_line_string(io_channel, line, nullptr, &error);
- if ((io_status == G_IO_STATUS_EOF) || (io_status == G_IO_STATUS_ERROR))
- break;
- if (error != nullptr)
- break;
-
- g_strstrip(line->str);
-
- if (!line->len) // skip empty lines
- continue;
-
- if (*line->str=='#') // skip comments
- continue;
-
- ret = line->str;
- }
+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning("%s Couldn't get timezone: %s", G_STRLOC, error->message);
+ }
+ else if (v != nullptr)
+ {
+ GVariant* tzv {};
+ g_variant_get(v, "(v)", &tzv);
+ const char* tz = g_variant_get_string(tzv, nullptr);
- g_string_free(line, true);
- } else
- /* Default to UTC */
- ret = "Etc/Utc";
+ if (tz != nullptr)
+ static_cast<Impl*>(gself)->set_timezone(tz);
+ else
+ g_warning("%s no timezone found", G_STRLOC);
- if (io_channel != nullptr)
- {
- g_io_channel_shutdown(io_channel, false, nullptr);
- g_io_channel_unref(io_channel);
+ g_clear_pointer(&tzv, g_variant_unref);
+ g_clear_pointer(&v, g_variant_unref);
}
+ }
- if (error != nullptr)
- {
- g_warning("%s Unable to read timezone file '%s': %s", G_STRLOC, filename.c_str(), error->message);
- g_error_free(error);
- }
+ void set_timezone(const std::string& tz)
+ {
+ g_return_if_fail(!tz.empty());
- return ret;
+ g_debug("set timezone: '%s'", tz.c_str());
+ m_owner.timezone.set(tz);
}
/***
****
***/
- TimedatedTimezone & m_owner;
- GDBusConnection *m_connection = nullptr;
- unsigned long m_signal_subscription_id = 0;
- std::string m_filename;
+ TimedatedTimezone& m_owner;
+ GDBusConnection* m_connection {};
+ GCancellable* m_cancellable {};
+ unsigned long m_signal_subscription_id {};
+ unsigned int m_watcher_id {};
};
/***
****
***/
-TimedatedTimezone::TimedatedTimezone(std::string filename):
- impl(new Impl{*this, filename})
+TimedatedTimezone::TimedatedTimezone(GDBusConnection* connection):
+ impl{new Impl{*this, connection}}
{
}
diff --git a/src/timezones-live.cpp b/src/timezones-live.cpp
index 2979036..f3bd02d 100644
--- a/src/timezones-live.cpp
+++ b/src/timezones-live.cpp
@@ -25,11 +25,14 @@ namespace ayatana {
namespace indicator {
namespace datetime {
-LiveTimezones::LiveTimezones(const std::shared_ptr<const Settings>& settings):
- m_file(),
+LiveTimezones::LiveTimezones(
+ const std::shared_ptr<const Settings>& settings,
+ const std::shared_ptr<Timezone>& primary_timezone
+):
+ m_primary_timezone(primary_timezone),
m_settings(settings)
{
- m_file.timezone.changed().connect([this](const std::string&){update_timezones();});
+ m_primary_timezone->timezone.changed().connect([this](const std::string&){update_timezones();});
m_settings->show_detected_location.changed().connect([this](bool){update_geolocation();});
update_geolocation();
@@ -53,7 +56,7 @@ void LiveTimezones::update_geolocation()
void LiveTimezones::update_timezones()
{
- const auto a = m_file.timezone.get();
+ const auto a = m_primary_timezone->timezone.get();
const auto b = m_geo ? m_geo->timezone.get() : "";
timezone.set(a.empty() ? b : a);
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 5206259..4a38890 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -1,11 +1,6 @@
-# build libgtest
-add_library (gtest STATIC
- ${GTEST_SOURCE_DIR}/gtest-all.cc
- ${GTEST_SOURCE_DIR}/gtest_main.cc)
-set_target_properties (gtest PROPERTIES INCLUDE_DIRECTORIES ${INCLUDE_DIRECTORIES} ${GTEST_INCLUDE_DIR})
-set_target_properties (gtest PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -w")
+find_package(GMock REQUIRED)
-SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g ${COMPILE_FLAGS}")
+SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ${COMPILE_FLAGS}")
# dbustest
pkg_check_modules(DBUSTEST REQUIRED
@@ -42,13 +37,17 @@ add_definitions (-DG_LOG_DOMAIN="ayatana-indicator-datetime")
function(add_test_by_name name)
set (TEST_NAME ${name})
+ set (COVERAGE_TEST_TARGETS ${COVERAGE_TEST_TARGETS} ${TEST_NAME} PARENT_SCOPE)
add_executable (${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled)
+ target_link_options(${TEST_NAME} PRIVATE -no-pie)
add_test (${TEST_NAME} ${TEST_NAME})
- target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS} ${URLDISPATCHER_LIBRARIES})
+ target_link_libraries (${TEST_NAME} indicatordatetimeservice ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES})
endfunction()
add_test_by_name(test-datetime)
if(HAVE_UT_ACCTSERVICE_SYSTEMSOUND_SETTINGS)
-add_test_by_name(test-snap)
+add_test_by_name(test-sound)
+add_test_by_name(test-notification)
+add_test_by_name(test-notification-response)
endif()
add_test_by_name(test-actions)
add_test_by_name(test-alarm-queue)
@@ -58,6 +57,7 @@ add_test_by_name(test-exporter)
add_test_by_name(test-formatter)
add_test_by_name(test-live-actions)
add_test_by_name(test-locations)
+add_test_by_name(test-menu-appointments)
add_test_by_name(test-menus)
add_test_by_name(test-planner)
add_test_by_name(test-settings)
@@ -66,8 +66,10 @@ add_test_by_name(test-utils)
if(HAVE_UT_ACCTSERVICE_SYSTEMSOUND_SETTINGS)
set (TEST_NAME manual-test-snap)
+ set (COVERAGE_TEST_TARGETS ${COVERAGE_TEST_TARGETS} ${TEST_NAME})
add_executable (${TEST_NAME} ${TEST_NAME}.cpp)
- target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS})
+ target_link_options(${TEST_NAME} PRIVATE -no-pie)
+ target_link_libraries (${TEST_NAME} indicatordatetimeservice ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES})
endif()
##
@@ -78,17 +80,20 @@ find_program(DBUS_RUNNER dbus-test-runner)
function(add_eds_ics_test_by_name name)
set (TEST_NAME ${name})
+ set (COVERAGE_TEST_TARGETS ${COVERAGE_TEST_TARGETS} ${TEST_NAME} PARENT_SCOPE)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME}.ics.in"
"${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}.ics")
add_executable(${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled)
- target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS})
+ target_link_options(${TEST_NAME} PRIVATE -no-pie)
+ target_link_libraries (${TEST_NAME} indicatordatetimeservice ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES})
add_test (${TEST_NAME}
${CMAKE_CURRENT_SOURCE_DIR}/run-eds-ics-test.sh
${DBUS_RUNNER} # arg1: dbus-test-runner exec
${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME} # arg2: test executable path
${TEST_NAME} # arg3: test name
${CMAKE_CURRENT_SOURCE_DIR}/test-eds-ics-config-files # arg4: base directory for config file template
- ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}.ics) # arg5: the ical file for this test
+ ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}.ics # arg5: the ical file for this test
+ ${CMAKE_CURRENT_SOURCE_DIR}/accounts.db) # arg6: online accounts database
endfunction()
add_eds_ics_test_by_name(test-eds-ics-all-day-events)
add_eds_ics_test_by_name(test-eds-ics-repeating-events)
@@ -97,15 +102,25 @@ add_eds_ics_test_by_name(test-eds-ics-repeating-valarms)
add_eds_ics_test_by_name(test-eds-ics-missing-trigger)
add_eds_ics_test_by_name(test-eds-ics-tzids)
add_eds_ics_test_by_name(test-eds-ics-tzids-2)
+add_eds_ics_test_by_name(test-eds-ics-tzids-utc)
+add_eds_ics_test_by_name(test-eds-ics-non-attending-alarms)
+add_eds_ics_test_by_name(test-eds-ics-repeating-events-with-individual-change)
# disabling the timezone unit tests because they require
# https://code.launchpad.net/~ted/dbus-test-runner/multi-interface-test/+merge/199724
# which hasn't landed yet. These can be re-enabled as soon as that lands.
#function(add_dbusmock_test_by_name name)
# set (TEST_NAME ${name})
+# set (COVERAGE_TEST_TARGETS ${COVERAGE_TEST_TARGETS} ${TEST_NAME} PARENT_SCOPE)
# add_executable (${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled)
# add_test (${TEST_NAME} ${TEST_NAME})
-# target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${DBUSTEST_LIBRARIES} ${GTEST_LIBS})
+# target_link_libraries (${TEST_NAME} indicatordatetimeservice ${SERVICE_DEPS_LIBRARIES} ${DBUSTEST_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES})
#endfunction()
#add_dbusmock_test_by_name(test-timezone-geoclue)
#add_dbusmock_test_by_name(test-timezones)
+
+set(
+ COVERAGE_TEST_TARGETS
+ ${COVERAGE_TEST_TARGETS}
+ PARENT_SCOPE
+)
diff --git a/tests/accounts.db b/tests/accounts.db
new file mode 100644
index 0000000..ece5b2f
--- /dev/null
+++ b/tests/accounts.db
Binary files differ
diff --git a/tests/actions-mock.h b/tests/actions-mock.h
index 59a0912..a02a7e2 100644
--- a/tests/actions-mock.h
+++ b/tests/actions-mock.h
@@ -34,14 +34,10 @@ public:
explicit MockActions(const std::shared_ptr<State>& state_in): Actions(state_in) {}
~MockActions() =default;
- enum Action { DesktopOpenAlarmApp,
- DesktopOpenAppt,
- DesktopOpenCalendarApp,
- DesktopOpenSettingsApp,
- PhoneOpenAlarmApp,
- PhoneOpenAppt,
- PhoneOpenCalendarApp,
- PhoneOpenSettingsApp,
+ enum Action { OpenAlarmApp,
+ OpenAppt,
+ OpenCalendarApp,
+ OpenSettingsApp,
SetLocation };
const std::vector<Action>& history() const { return m_history; }
@@ -54,34 +50,20 @@ public:
bool desktop_has_calendar_app() const {
return m_desktop_has_calendar_app;
}
- void desktop_open_alarm_app() {
- m_history.push_back(DesktopOpenAlarmApp);
+ void open_alarm_app() {
+ m_history.push_back(OpenAlarmApp);
}
- void desktop_open_appointment(const Appointment& appt) {
+ void open_appointment(const Appointment& appt, const DateTime& dt) {
m_appt = appt;
- m_history.push_back(DesktopOpenAppt);
- }
- void desktop_open_calendar_app(const DateTime& dt) {
m_date_time = dt;
- m_history.push_back(DesktopOpenCalendarApp);
- }
- void desktop_open_settings_app() {
- m_history.push_back(DesktopOpenSettingsApp);
- }
-
- void phone_open_alarm_app() {
- m_history.push_back(PhoneOpenAlarmApp);
- }
- void phone_open_appointment(const Appointment& appt) {
- m_appt = appt;
- m_history.push_back(PhoneOpenAppt);
+ m_history.push_back(OpenAppt);
}
- void phone_open_calendar_app(const DateTime& dt) {
+ void open_calendar_app(const DateTime& dt) {
m_date_time = dt;
- m_history.push_back(PhoneOpenCalendarApp);
+ m_history.push_back(OpenCalendarApp);
}
- void phone_open_settings_app() {
- m_history.push_back(PhoneOpenSettingsApp);
+ void open_settings_app() {
+ m_history.push_back(OpenSettingsApp);
}
void set_location(const std::string& zone_, const std::string& name_) {
diff --git a/tests/glib-fixture.h b/tests/glib-fixture.h
index 4d309e6..88ee384 100644
--- a/tests/glib-fixture.h
+++ b/tests/glib-fixture.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 Canonical Ltd.
+ * Copyright 2013-2016 Canonical Ltd.
*
* Authors:
* Charles Kerr <charles.kerr@canonical.com>
@@ -17,10 +17,12 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef INDICATOR_DATETIME_TESTS_GLIB_FIXTURE_H
-#define INDICATOR_DATETIME_TESTS_GLIB_FIXTURE_H
+#pragma once
+#include <chrono>
+#include <functional> // std::function
#include <map>
+#include <memory> // std::shared_ptr
#include <glib.h>
#include <glib/gstdio.h>
@@ -109,7 +111,115 @@ class GlibFixture : public ::testing::Test
g_source_remove(id);
}
- GMainLoop * loop;
+ bool wait_for(std::function<bool()> test_function, guint timeout_msec=1000)
+ {
+ auto timer = std::shared_ptr<GTimer>(g_timer_new(), [](GTimer* t){g_timer_destroy(t);});
+ const auto timeout_sec = timeout_msec / 1000.0;
+ for (;;) {
+ if (test_function())
+ return true;
+ //g_message("%f ... %f", g_timer_elapsed(timer.get(), nullptr), timeout_sec);
+ if (g_timer_elapsed(timer.get(), nullptr) >= timeout_sec)
+ return false;
+ wait_msec();
+ }
+ }
+
+ bool wait_for_name_owned(GDBusConnection* connection,
+ const gchar* name,
+ guint timeout_msec=1000,
+ GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START)
+ {
+ struct Data {
+ GMainLoop* loop = nullptr;
+ bool owned = false;
+ };
+ Data data;
+
+ auto on_name_appeared = [](GDBusConnection* /*connection*/,
+ const gchar* /*name_*/,
+ const gchar* name_owner,
+ gpointer gdata)
+ {
+ if (name_owner == nullptr)
+ return;
+ auto tmp = static_cast<Data*>(gdata);
+ tmp->owned = true;
+ g_main_loop_quit(tmp->loop);
+ };
+
+ const auto timeout_id = g_timeout_add(timeout_msec, wait_msec__timeout, loop);
+ data.loop = loop;
+ const auto watch_id = g_bus_watch_name_on_connection(connection,
+ name,
+ flags,
+ on_name_appeared,
+ nullptr, /* name_vanished */
+ &data,
+ nullptr); /* user_data_free_func */
+ g_main_loop_run(loop);
+
+ g_bus_unwatch_name(watch_id);
+ g_source_remove(timeout_id);
+
+ return data.owned;
+ }
+
+ void EXPECT_NAME_OWNED_EVENTUALLY(GDBusConnection* connection,
+ const gchar* name,
+ guint timeout_msec=1000,
+ GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START)
+ {
+ EXPECT_TRUE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name;
+ }
+
+ void EXPECT_NAME_NOT_OWNED_EVENTUALLY(GDBusConnection* connection,
+ const gchar* name,
+ guint timeout_msec=1000,
+ GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START)
+ {
+ EXPECT_FALSE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name;
+ }
+
+ void ASSERT_NAME_OWNED_EVENTUALLY(GDBusConnection* connection,
+ const gchar* name,
+ guint timeout_msec=1000,
+ GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START)
+ {
+ ASSERT_TRUE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name;
+ }
+
+ void ASSERT_NAME_NOT_OWNED_EVENTUALLY(GDBusConnection* connection,
+ const gchar* name,
+ guint timeout_msec=1000,
+ GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START)
+ {
+ ASSERT_FALSE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name;
+ }
+
+ using source_func = std::function<gboolean()>;
+
+ guint idle_add(source_func&& func)
+ {
+ return g_idle_add_full(
+ G_PRIORITY_DEFAULT_IDLE,
+ [](gpointer gf){return (*static_cast<source_func*>(gf))();},
+ new std::function<gboolean()>(func),
+ [](gpointer gf){delete static_cast<source_func*>(gf);}
+ );
+ }
+
+ guint timeout_add(source_func&& func, std::chrono::milliseconds msec)
+ {
+ return g_timeout_add_full(
+ G_PRIORITY_DEFAULT,
+ msec.count(),
+ [](gpointer gf){return (*static_cast<source_func*>(gf))();},
+ new std::function<gboolean()>(func),
+ [](gpointer gf){delete static_cast<source_func*>(gf);}
+ );
+ }
+
+ GMainLoop* loop {};
};
-#endif /* INDICATOR_DATETIME_TESTS_GLIB_FIXTURE_H */
diff --git a/tests/libdbusmock-fixture.h b/tests/libdbusmock-fixture.h
new file mode 100644
index 0000000..7301042
--- /dev/null
+++ b/tests/libdbusmock-fixture.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2014-2016 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>
+ */
+
+#pragma once
+
+#include "glib-fixture.h"
+
+#include <libdbustest/dbus-test.h>
+
+/***
+****
+***/
+
+class LibdbusmockFixture: public GlibFixture
+{
+private:
+
+ typedef GlibFixture super;
+
+protected:
+
+ GDBusConnection * system_bus {};
+ GDBusConnection * session_bus {};
+ DbusTestService * service {};
+
+ void SetUp() override
+ {
+
+ super::SetUp();
+
+ service = dbus_test_service_new(nullptr);
+ }
+
+ void startDbusMock()
+ {
+ // start 'em up.
+ // make the system bus work off the mock bus too, since that's
+ // where the upower and screen are on the system bus...
+
+ dbus_test_service_start_tasks(service);
+ g_setenv("DBUS_SYSTEM_BUS_ADDRESS", g_getenv("DBUS_SESSION_BUS_ADDRESS"), TRUE);
+
+ session_bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
+ ASSERT_NE(nullptr, session_bus);
+ g_dbus_connection_set_exit_on_close(session_bus, false);
+ g_object_add_weak_pointer(G_OBJECT(session_bus), (gpointer *)&session_bus);
+
+ system_bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr);
+ ASSERT_NE(nullptr, system_bus);
+ g_dbus_connection_set_exit_on_close(system_bus, FALSE);
+ g_object_add_weak_pointer(G_OBJECT(system_bus), (gpointer *)&system_bus);
+ }
+
+ void TearDown() override
+ {
+ g_clear_object(&service);
+ g_object_unref(session_bus);
+ g_object_unref(system_bus);
+
+ // wait a little while for the scaffolding to shut down,
+ // but don't block on it forever...
+ wait_for([this](){return system_bus==nullptr && session_bus==nullptr;}, 5000);
+
+ super::TearDown();
+ }
+
+ bool wait_for_method_call(DbusTestDbusMock* mock,
+ DbusTestDbusMockObject* obj,
+ const gchar* method,
+ GVariant* params=nullptr,
+ guint timeout_msec=100)
+ {
+ if (params != nullptr)
+ g_variant_ref_sink(params);
+
+ auto test_function = [mock, obj, method, params]() {
+ GError* error {};
+ const auto called = dbus_test_dbus_mock_object_check_method_call(mock,
+ obj,
+ method,
+ params,
+ &error);
+ if (error != nullptr) {
+ g_critical("Error looking for method call '%s': %s", method, error->message);
+ g_clear_error(&error);
+ }
+
+ return called;
+ };
+
+ const auto ret = wait_for(test_function, timeout_msec);
+ g_clear_pointer(&params, g_variant_unref);
+ return ret;
+ }
+
+ void EXPECT_METHOD_CALLED_EVENTUALLY(DbusTestDbusMock* mock,
+ DbusTestDbusMockObject* obj,
+ const gchar* method,
+ GVariant* params=nullptr,
+ guint timeout_msec=1000)
+ {
+ EXPECT_TRUE(wait_for_method_call(mock, obj, method, params, timeout_msec)) << "method: " << method;
+ }
+
+ void EXPECT_METHOD_NOT_CALLED_EVENTUALLY(DbusTestDbusMock* mock,
+ DbusTestDbusMockObject* obj,
+ const gchar* method,
+ GVariant* params=nullptr,
+ guint timeout_msec=1000)
+ {
+ EXPECT_FALSE(wait_for_method_call(mock, obj, method, params, timeout_msec)) << "method: " << method;
+ }
+
+ void ASSERT_METHOD_CALLED_EVENTUALLY(DbusTestDbusMock* mock,
+ DbusTestDbusMockObject* obj,
+ const gchar* method,
+ GVariant* params=nullptr,
+ guint timeout_msec=1000)
+ {
+ ASSERT_TRUE(wait_for_method_call(mock, obj, method, params, timeout_msec)) << "method: " << method;
+ }
+
+ void ASSERT_METHOD_NOT_CALLED_EVENTUALLY(DbusTestDbusMock* mock,
+ DbusTestDbusMockObject* obj,
+ const gchar* method,
+ GVariant* params=nullptr,
+ guint timeout_msec=1000)
+ {
+ ASSERT_FALSE(wait_for_method_call(mock, obj, method, params, timeout_msec)) << "method: " << method;
+ }
+};
+
diff --git a/tests/manual-test-snap.cpp b/tests/manual-test-snap.cpp
index a0f80f2..72fd374 100644
--- a/tests/manual-test-snap.cpp
+++ b/tests/manual-test-snap.cpp
@@ -74,12 +74,14 @@ int main(int argc, const char* argv[])
a.alarms.push_back(Alarm{"Alarm Text", "", a.begin});
auto loop = g_main_loop_new(nullptr, false);
- auto on_snooze = [loop](const Appointment& appt, const Alarm&){
- g_message("You clicked 'Snooze' for appt url '%s'", appt.summary.c_str());
- g_idle_add(quit_idle, loop);
- };
- auto on_ok = [loop](const Appointment&, const Alarm&){
- g_message("You clicked 'OK'");
+ auto on_response = [loop](const Appointment& appt, const Alarm&, const Snap::Response& response){
+ const char* str {""};
+ switch(response) {
+ case Snap::Response::ShowApp: str = "show-app"; break;
+ case Snap::Response::Snooze: str = "snooze"; break;
+ case Snap::Response::None: str = "no-action"; break;
+ };
+ g_message("You clicked '%s' for appt url '%s'", str, appt.summary.c_str());
g_idle_add(quit_idle, loop);
};
@@ -92,10 +94,13 @@ int main(int argc, const char* argv[])
settings->alarm_volume.set(volume);
auto notification_engine = std::make_shared<ain::Engine>("ayatana-indicator-datetime-service");
- Snap snap (notification_engine, settings);
- snap(a, a.alarms.front(), on_snooze, on_ok);
+ auto sound_builder = std::make_shared<ain::DefaultSoundBuilder>();
+ auto system_bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr);
+ Snap snap (notification_engine, sound_builder, settings, system_bus);
+ snap(a, a.alarms.front(), on_response);
g_main_loop_run(loop);
g_main_loop_unref(loop);
+ g_clear_object(&system_bus);
return 0;
}
diff --git a/tests/test-snap.cpp b/tests/notification-fixture.h
index afee297..cbce9ff 100644
--- a/tests/test-snap.cpp
+++ b/tests/notification-fixture.h
@@ -1,8 +1,5 @@
/*
- * Copyright 2014 Canonical Ltd.
- *
- * Authors:
- * Charles Kerr <charles.kerr@canonical.com>
+ * Copyright 2014-2016 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
@@ -15,8 +12,15 @@
*
* 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>
*/
+#pragma once
+
+#include "libdbusmock-fixture.h"
+
#include <datetime/appointment.h>
#include <datetime/dbus-shared.h>
#include <datetime/settings.h>
@@ -27,38 +31,25 @@
#include <libdbustest/dbus-test.h>
-#include <glib.h>
-
#include <unistd.h> // getuid()
#include <sys/types.h> // getuid()
-using namespace ayatana::indicator::datetime;
-
-#include "glib-fixture.h"
-
/***
****
***/
-namespace
-{
- static constexpr char const * APP_NAME {"ayatana-indicator-datetime-service"};
-}
-
-using namespace ayatana::indicator::datetime;
-
-class SnapFixture: public GlibFixture
+class NotificationFixture: public LibdbusmockFixture
{
private:
- typedef GlibFixture super;
+ typedef LibdbusmockFixture super;
+
+protected:
static constexpr char const * NOTIFY_BUSNAME {"org.freedesktop.Notifications"};
static constexpr char const * NOTIFY_INTERFACE {"org.freedesktop.Notifications"};
static constexpr char const * NOTIFY_PATH {"/org/freedesktop/Notifications"};
-protected:
-
static constexpr char const * HAPTIC_METHOD_VIBRATE_PATTERN {"VibratePattern"};
static constexpr int SCREEN_COOKIE {8675309};
@@ -87,15 +78,13 @@ protected:
static constexpr char const * HINT_TIMEOUT {"x-canonical-snap-decisions-timeout"};
static constexpr char const * AS_BUSNAME {"org.freedesktop.Accounts"};
- static constexpr char const * AS_INTERFACE {"com.ubuntu.touch.AccountsService.Sound"};
+ static constexpr char const * AS_INTERFACE {"com.lomiri.touch.AccountsService.Sound"};
static constexpr char const * PROP_OTHER_VIBRATIONS {"OtherVibrate"};
static constexpr char const * PROP_SILENT_MODE {"SilentMode"};
- Appointment appt;
- Appointment ualarm;
- GDBusConnection * system_bus = nullptr;
- GDBusConnection * session_bus = nullptr;
- DbusTestService * service = nullptr;
+ ayatana::indicator::datetime::Appointment appt;
+ ayatana::indicator::datetime::Appointment ualarm;
+
DbusTestDbusMock * as_mock = nullptr;
DbusTestDbusMock * notify_mock = nullptr;
DbusTestDbusMock * powerd_mock = nullptr;
@@ -118,23 +107,21 @@ protected:
appt.color = "green";
appt.summary = "Christmas";
appt.uid = "D4B57D50247291478ED31DED17FF0A9838DED402";
- appt.type = Appointment::EVENT;
- const auto christmas = DateTime::Local(2015,12,25,0,0,0);
+ appt.type = ayatana::indicator::datetime::Appointment::EVENT;
+ const auto christmas = ayatana::indicator::datetime::DateTime::Local(2015,12,25,0,0,0);
appt.begin = christmas.start_of_day();
appt.end = christmas.end_of_day();
- appt.alarms.push_back(Alarm{"Ho Ho Ho!", "", appt.begin});
+ appt.alarms.push_back(ayatana::indicator::datetime::Alarm{"Ho Ho Ho!", CALENDAR_DEFAULT_SOUND, appt.begin});
- // init an Ubuntu Alarm
+ // init a Lomiri Alarm
ualarm.color = "red";
ualarm.summary = "Wakeup";
ualarm.uid = "E4B57D50247291478ED31DED17FF0A9838DED403";
- ualarm.type = Appointment::UBUNTU_ALARM;
- const auto tomorrow = DateTime::NowLocal().add_days(1);
+ ualarm.type = ayatana::indicator::datetime::Appointment::UBUNTU_ALARM;
+ const auto tomorrow = ayatana::indicator::datetime::DateTime::NowLocal().add_days(1);
ualarm.begin = tomorrow;
ualarm.end = tomorrow;
- ualarm.alarms.push_back(Alarm{"It's Tomorrow!", "", appt.begin});
-
- service = dbus_test_service_new(nullptr);
+ ualarm.alarms.push_back(ayatana::indicator::datetime::Alarm{"It's Tomorrow!", "", appt.begin});
///
/// Add the AccountsService mock
@@ -311,23 +298,7 @@ protected:
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
- // where the upower and screen are on the system bus...
-
- dbus_test_service_start_tasks(service);
- g_setenv("DBUS_SYSTEM_BUS_ADDRESS", g_getenv("DBUS_SESSION_BUS_ADDRESS"), TRUE);
-
- session_bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
- ASSERT_NE(nullptr, session_bus);
- g_dbus_connection_set_exit_on_close(session_bus, false);
- g_object_add_weak_pointer(G_OBJECT(session_bus), (gpointer *)&session_bus);
-
- system_bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr);
- ASSERT_NE(nullptr, system_bus);
- g_dbus_connection_set_exit_on_close(system_bus, FALSE);
- g_object_add_weak_pointer(G_OBJECT(system_bus), (gpointer *)&system_bus);
+ startDbusMock();
}
void TearDown() override
@@ -337,20 +308,6 @@ protected:
g_clear_object(&powerd_mock);
g_clear_object(&notify_mock);
g_clear_object(&as_mock);
- g_clear_object(&service);
- g_object_unref(session_bus);
- g_object_unref(system_bus);
-
- // wait a little while for the scaffolding to shut down,
- // but don't block on it forever...
- unsigned int cleartry = 0;
- while (((system_bus != nullptr) || (session_bus != nullptr)) && (cleartry < 50))
- {
- g_usleep(100000);
- while (g_main_context_pending(nullptr))
- g_main_context_iteration(nullptr, true);
- cleartry++;
- }
super::TearDown();
}
@@ -371,213 +328,15 @@ protected:
&error);
g_assert_no_error (error);
}
-};
-/***
-****
-***/
-
-namespace
-{
- gboolean quit_idle (gpointer gloop)
+ std::shared_ptr<ayatana::indicator::datetime::Snap>
+ create_snap(const std::shared_ptr<ayatana::indicator::notifications::Engine>& ne,
+ const std::shared_ptr<ayatana::indicator::notifications::SoundBuilder>& sb,
+ const std::shared_ptr<ayatana::indicator::datetime::Settings>& settings)
{
- g_main_loop_quit(static_cast<GMainLoop*>(gloop));
- return G_SOURCE_REMOVE;
- };
-}
-
-TEST_F(SnapFixture, InteractiveDuration)
-{
- static constexpr int duration_minutes = 120;
- auto settings = std::make_shared<Settings>();
- settings->alarm_duration.set(duration_minutes);
- auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME);
- Snap snap (ne, settings);
-
- make_interactive();
-
- // call the Snap Decision
- auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);};
- snap(appt, appt.alarms.front(), func, func);
-
- // confirm that Notify got called once
- guint len = 0;
- GError * error = nullptr;
- const auto calls = dbus_test_dbus_mock_object_get_method_calls (notify_mock,
- notify_obj,
- METHOD_NOTIFY,
- &len,
- &error);
- g_assert_no_error(error);
- ASSERT_EQ(1, len);
-
- // confirm that the app_name passed to Notify was APP_NAME
- const auto& params = calls[0].params;
- ASSERT_EQ(8, g_variant_n_children(params));
- const char * str = nullptr;
- g_variant_get_child (params, 0, "&s", &str);
- ASSERT_STREQ(APP_NAME, str);
-
- // confirm that the icon passed to Notify was "alarm-clock"
- g_variant_get_child (params, 2, "&s", &str);
- ASSERT_STREQ("alarm-clock", str);
-
- // confirm that the hints passed to Notify included a timeout matching duration_minutes
- int32_t i32;
- bool b;
- auto hints = g_variant_get_child_value (params, 6);
- b = g_variant_lookup (hints, HINT_TIMEOUT, "i", &i32);
- EXPECT_TRUE(b);
- const auto duration = std::chrono::minutes(duration_minutes);
- EXPECT_EQ(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(), i32);
- g_variant_unref(hints);
- ne.reset();
-}
-
-/***
-****
-***/
-
-TEST_F(SnapFixture, InhibitSleep)
-{
- auto settings = std::make_shared<Settings>();
- auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME);
- auto snap = new Snap (ne, settings);
-
- make_interactive();
-
- // invoke the notification
- auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);};
- (*snap)(appt, appt.alarms.front(), func, func);
-
- wait_msec(1000);
-
- // confirm that sleep got inhibited
- GError * error = nullptr;
- EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (powerd_mock,
- powerd_obj,
- POWERD_METHOD_REQUEST_SYS_STATE,
- g_variant_new("(si)", APP_NAME, POWERD_SYS_STATE_ACTIVE),
- &error));
-
- // confirm that the screen got forced on
- EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (screen_mock,
- screen_obj,
- SCREEN_METHOD_KEEP_DISPLAY_ON,
- nullptr,
- &error));
-
- // force-close the snap
- wait_msec(100);
- delete snap;
- wait_msec(100);
-
- // confirm that sleep got uninhibted
- EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (powerd_mock,
- powerd_obj,
- POWERD_METHOD_CLEAR_SYS_STATE,
- g_variant_new("(s)", POWERD_COOKIE),
- &error));
-
- // confirm that the screen's no longer forced on
- EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (screen_mock,
- screen_obj,
- SCREEN_METHOD_REMOVE_DISPLAY_ON_REQUEST,
- g_variant_new("(i)", SCREEN_COOKIE),
- &error));
-
- g_assert_no_error (error);
-}
-
-/***
-****
-***/
-
-TEST_F(SnapFixture, ForceScreen)
-{
- auto settings = std::make_shared<Settings>();
- auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME);
- auto snap = new Snap (ne, settings);
-
- make_interactive();
-
- // invoke the notification
- auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);};
- (*snap)(appt, appt.alarms.front(), func, func);
-
- wait_msec(1000);
-
- // confirm that sleep got inhibited
- GError * error = nullptr;
- EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (powerd_mock,
- powerd_obj,
- POWERD_METHOD_REQUEST_SYS_STATE,
- g_variant_new("(si)", APP_NAME, POWERD_SYS_STATE_ACTIVE),
- &error));
- g_assert_no_error(error);
-
- // force-close the snap
- wait_msec(100);
- delete snap;
- wait_msec(100);
-
- // confirm that sleep got uninhibted
- EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (powerd_mock,
- powerd_obj,
- POWERD_METHOD_CLEAR_SYS_STATE,
- g_variant_new("(s)", POWERD_COOKIE),
- &error));
- g_assert_no_error(error);
-}
-
-/***
-****
-***/
-
-TEST_F(SnapFixture,Vibrate)
-{
- auto settings = std::make_shared<Settings>();
- auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME);
- auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);};
- GError * error = nullptr;
-
- struct {
- bool other_vibrations; // the com.ubuntu.touch.AccountsService.Sound "other vibrations" setting
- const char* haptic_mode; // supported values: "none", "pulse"
- bool expected_vibrate_called; // do we expect the phone to vibrate?
- } test_cases[] = {
- { false, "none", false },
- { true, "none", false },
- { false, "pulse", false },
- { true, "pulse", true }
- };
-
- auto snap = std::make_shared<Snap>(ne, settings);
-
- for(const auto& test_case : test_cases)
- {
- // clear out any previous iterations' noise
- dbus_test_dbus_mock_object_clear_method_calls(haptic_mock, haptic_obj, &error);
-
- // set the properties to match the test case
- settings->alarm_haptic.set(test_case.haptic_mode);
- dbus_test_dbus_mock_object_update_property(as_mock,
- as_obj,
- PROP_OTHER_VIBRATIONS,
- g_variant_new_boolean(test_case.other_vibrations),
- &error);
- g_assert_no_error(error);
- wait_msec(100);
-
- // run the test
- (*snap)(appt, appt.alarms.front(), func, func);
- wait_msec(100);
- const bool vibrate_called = dbus_test_dbus_mock_object_check_method_call(haptic_mock,
- haptic_obj,
- HAPTIC_METHOD_VIBRATE_PATTERN,
- nullptr,
- &error);
- g_assert_no_error(error);
- EXPECT_EQ(test_case.expected_vibrate_called, vibrate_called);
+ auto snap = std::make_shared<ayatana::indicator::datetime::Snap>(ne, sb, settings, system_bus);
+ wait_msec(100); // wait a moment for the Snap to finish its async dbus bootstrapping
+ return snap;
}
-}
+};
+
diff --git a/tests/print-to.h b/tests/print-to.h
index 19367ac..652da52 100644
--- a/tests/print-to.h
+++ b/tests/print-to.h
@@ -21,6 +21,7 @@
#define INDICATOR_DATETIME_TESTS_PRINT_TO
#include <algorithm>
+#include <vector>
#include <datetime/appointment.h>
@@ -71,6 +72,15 @@ PrintTo(const Appointment& appointment, std::ostream* os)
*os << '}';
}
+void
+PrintTo(const std::vector<Appointment>& appointments, std::ostream* os)
+{
+ *os << '{';
+ for (const auto& appointment : appointments)
+ PrintTo(appointment, os);
+ *os << '}';
+}
+
} // namespace datetime
} // namespace indicator
} // namespace ayatana
diff --git a/tests/run-eds-ics-test.sh b/tests/run-eds-ics-test.sh
index 13c1617..b38fe77 100755
--- a/tests/run-eds-ics-test.sh
+++ b/tests/run-eds-ics-test.sh
@@ -6,6 +6,7 @@ TEST_EXEC=$2 # full executable path of test app
TEST_NAME=$3 # test name
CONFIG_DIR=$4 # config files
ICS_FILE=$5 # ical file holding test data
+ACCOUNTS_DB=$6 # online account database
echo "this script: ${SELF}"
echo "test-runner: ${TEST_RUNNER}"
@@ -55,6 +56,13 @@ if [ -e ${ICS_FILE} ]; then
cp --verbose --archive ${ICS_FILE} ${XDG_DATA_HOME}/evolution/tasks/system/tasks.ics
fi
+# prepare online accounts database
+if [ -e ${ACCOUNTS_DB} ]; then
+ echo "copying ${ACCOUNTS_DB} into $HOME"
+ mkdir -p ${XDG_CONFIG_HOME}/libaccounts-glib/
+ cp --verbose --archive ${ACCOUNTS_DB} ${XDG_CONFIG_HOME}/libaccounts-glib/accounts.db
+fi
+
# run the test
${TEST_RUNNER} --keep-env --max-wait=90 --task ${TEST_EXEC} --task-name ${TEST_NAME} --wait-until-complete
rv=$?
diff --git a/tests/test-actions.cpp b/tests/test-actions.cpp
index aa608a8..a01fb83 100644
--- a/tests/test-actions.cpp
+++ b/tests/test-actions.cpp
@@ -116,7 +116,7 @@ protected:
m_mock_state->mock_range_planner->appointments().set(appointments);
// activate the action
- auto v = g_variant_new_string(appointments[0].uid.c_str());
+ auto v = g_variant_new("(sx)", appointments[0].uid.c_str(), 0);
g_action_group_activate_action(action_group, action_name, v);
// test the results
@@ -134,7 +134,7 @@ protected:
EXPECT_TRUE(m_mock_actions->history().empty());
// activate the action
- v = g_variant_new_string("this-uid-is-not-one-that-we-have");
+ v = g_variant_new("(sx)", "this-uid-is-not-one-that-we-have", 0);
g_action_group_activate_action(action_group, action_name, v);
// test the results
@@ -176,25 +176,25 @@ TEST_F(ActionsFixture, ActionsExist)
TEST_F(ActionsFixture, DesktopOpenAlarmApp)
{
test_action_with_no_args("desktop.open-alarm-app",
- MockActions::DesktopOpenAlarmApp);
+ MockActions::OpenAlarmApp);
}
TEST_F(ActionsFixture, DesktopOpenAppointment)
{
test_action_with_appt_arg("desktop.open-appointment",
- MockActions::DesktopOpenAppt);
+ MockActions::OpenAppt);
}
TEST_F(ActionsFixture, DesktopOpenCalendarApp)
{
test_action_with_time_arg("desktop.open-calendar-app",
- MockActions::DesktopOpenCalendarApp);
+ MockActions::OpenCalendarApp);
}
TEST_F(ActionsFixture, DesktopOpenSettingsApp)
{
test_action_with_no_args("desktop.open-settings-app",
- MockActions::DesktopOpenSettingsApp);
+ MockActions::OpenSettingsApp);
}
/***
@@ -204,25 +204,25 @@ TEST_F(ActionsFixture, DesktopOpenSettingsApp)
TEST_F(ActionsFixture, PhoneOpenAlarmApp)
{
test_action_with_no_args("phone.open-alarm-app",
- MockActions::PhoneOpenAlarmApp);
+ MockActions::OpenAlarmApp);
}
TEST_F(ActionsFixture, PhoneOpenAppointment)
{
test_action_with_appt_arg("phone.open-appointment",
- MockActions::PhoneOpenAppt);
+ MockActions::OpenAppt);
}
TEST_F(ActionsFixture, PhoneOpenCalendarApp)
{
test_action_with_time_arg("phone.open-calendar-app",
- MockActions::PhoneOpenCalendarApp);
+ MockActions::OpenCalendarApp);
}
TEST_F(ActionsFixture, PhoneOpenSettingsApp)
{
test_action_with_no_args("phone.open-settings-app",
- MockActions::PhoneOpenSettingsApp);
+ MockActions::OpenSettingsApp);
}
/***
diff --git a/tests/test-eds-ics-all-day-events.cpp b/tests/test-eds-ics-all-day-events.cpp
index 68a3c95..5d7cdc6 100644
--- a/tests/test-eds-ics-all-day-events.cpp
+++ b/tests/test-eds-ics-all-day-events.cpp
@@ -24,6 +24,7 @@
#include <datetime/alarm-queue-simple.h>
#include <datetime/clock-mock.h>
#include <datetime/engine-eds.h>
+#include <datetime/myself.h>
#include <datetime/planner-range.h>
#include <gtest/gtest.h>
@@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture;
TEST_F(VAlarmFixture, MultipleAppointments)
{
// start the EDS engine
- auto engine = std::make_shared<EdsEngine>();
+ auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>());
// we need a consistent timezone for the planner and our local DateTimes
constexpr char const * zone_str {"America/Chicago"};
diff --git a/tests/test-eds-ics-missing-trigger.cpp b/tests/test-eds-ics-missing-trigger.cpp
index 0aa00c6..70bbccb 100644
--- a/tests/test-eds-ics-missing-trigger.cpp
+++ b/tests/test-eds-ics-missing-trigger.cpp
@@ -21,9 +21,10 @@
#include <algorithm>
-#include <datetime/engine-eds.h>
#include <datetime/alarm-queue-simple.h>
#include <datetime/clock-mock.h>
+#include <datetime/engine-eds.h>
+#include <datetime/myself.h>
#include <datetime/planner-range.h>
#include <gtest/gtest.h>
@@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture;
TEST_F(VAlarmFixture, MissingTriggers)
{
// start the EDS engine
- auto engine = std::make_shared<EdsEngine>();
+ auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>());
// we need a consistent timezone for the planner and our local DateTimes
constexpr char const * zone_str {"America/Chicago"};
@@ -62,7 +63,7 @@ TEST_F(VAlarmFixture, MissingTriggers)
// make a planner that looks at the first half of 2015 in EDS
auto planner = std::make_shared<SimpleRangePlanner>(engine, tz);
const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0};
- const DateTime range_end {gtz, 2015,6,30,23,59,59.5};
+ const DateTime range_end {gtz, 2015,7,1,23,59,59.5};
planner->range().set(std::make_pair(range_begin, range_end));
// give EDS a moment to load
diff --git a/tests/test-eds-ics-non-attending-alarms.cpp b/tests/test-eds-ics-non-attending-alarms.cpp
new file mode 100644
index 0000000..bfa3ac3
--- /dev/null
+++ b/tests/test-eds-ics-non-attending-alarms.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2015 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 <algorithm>
+
+#include <datetime/alarm-queue-simple.h>
+#include <datetime/clock-mock.h>
+#include <datetime/engine-eds.h>
+#include <datetime/myself.h>
+#include <datetime/planner-range.h>
+
+#include <gtest/gtest.h>
+
+#include "glib-fixture.h"
+#include "print-to.h"
+#include "timezone-mock.h"
+#include "wakeup-timer-mock.h"
+
+using namespace ayatana::indicator::datetime;
+using VAlarmFixture = GlibFixture;
+
+/***
+****
+***/
+
+TEST_F(VAlarmFixture, NonAttendingEvent)
+{
+ // start the EDS engine
+ auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>());
+
+ // we need a consistent timezone for the planner and our local DateTimes
+ constexpr char const * zone_str {"America/Recife"};
+ auto tz = std::make_shared<MockTimezone>(zone_str);
+
+ #if GLIB_CHECK_VERSION(2, 68, 0)
+ auto gtz = g_time_zone_new_identifier(zone_str);
+
+ if (gtz == NULL)
+ {
+ gtz = g_time_zone_new_utc();
+ }
+ #else
+ auto gtz = g_time_zone_new(zone_str);
+ #endif
+
+ // make a planner that looks at the first half of 2016 in EDS
+ auto planner = std::make_shared<SimpleRangePlanner>(engine, tz);
+ const DateTime range_begin {gtz, 2016,1, 1, 0, 0, 0.0};
+ const DateTime range_end {gtz, 2016,6,30,23,59,59.5};
+ planner->range().set(std::make_pair(range_begin, range_end));
+
+ // give EDS a moment to load
+ if (planner->appointments().get().empty()) {
+ g_message("waiting a moment for EDS to load...");
+ auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){
+ g_message("ah, they loaded");
+ if (!appointments.empty())
+ g_main_loop_quit(loop);
+ };
+ core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed));
+ constexpr int max_wait_sec = 10;
+ wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND);
+ }
+
+ // the planner should match what we've got in the calendar.ics file
+ const auto appts = planner->appointments().get();
+ EXPECT_EQ(2, appts.size());
+ EXPECT_EQ(appts[0].begin, DateTime(gtz, 2016, 4, 4, 16, 0, 0));
+ EXPECT_EQ(appts[1].begin, DateTime(gtz, 2016, 4, 6, 16, 0, 0));
+
+ // cleanup
+ g_time_zone_unref(gtz);
+}
diff --git a/tests/test-eds-ics-non-attending-alarms.ics.in b/tests/test-eds-ics-non-attending-alarms.ics.in
new file mode 100644
index 0000000..7adc8ab
--- /dev/null
+++ b/tests/test-eds-ics-non-attending-alarms.ics.in
@@ -0,0 +1,53 @@
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Ximian//NONSGML Evolution Calendar//EN
+VERSION:2.0
+X-EVOLUTION-DATA-REVISION:2015-04-05T21:32:47.354433Z(2)
+BEGIN:VEVENT
+STATUS:CONFIRMED
+DTSTAMP:20160405T152128Z
+CREATED:20160405T152128Z
+UID:ddtvl069dn2cquo8dhg3j9c360@google.com
+SEQUENCE:1
+TRANSP:OPAQUE
+SUMMARY:Every day at 4PM
+DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife:
+ 20160404T160000
+RRULE:FREQ=DAILY;UNTIL=20160406T190000Z
+DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife:
+ 20160404T170000
+ATTENDEE;CN=Uphablet;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;
+ CUTYPE=INDIVIDUAL:mailto:uphablet@lomiri.com
+LAST-MODIFIED:20160405T152128Z
+BEGIN:VALARM
+TRIGGER;VALUE=DURATION:-PT30M
+ACTION:EMAIL
+DESCRIPTION:This is an event reminder
+X-EVOLUTION-ALARM-UID:20160405T152128Z-2848-32011-1844-65@lomiri-phablet
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+STATUS:CONFIRMED
+DTSTAMP:20160405T152128Z
+CREATED:20160405T151054Z
+UID:ddtvl069dn2cquo8dhg3j9c360@google.com
+SEQUENCE:1
+TRANSP:OPAQUE
+SUMMARY::Every day at 4PM
+DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Fortaleza:
+ 20160405T160000
+RECURRENCE-ID;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife:
+ 20160405T160000
+DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Fortaleza:
+ 20160405T170000
+ATTENDEE;CN=Uphablet;PARTSTAT=DECLINED;ROLE=REQ-PARTICIPANT;
+ CUTYPE=INDIVIDUAL:mailto:uphablet@lomiri.com
+LAST-MODIFIED:20160405T152128Z
+BEGIN:VALARM
+TRIGGER;VALUE=DURATION:-PT30M
+ACTION:EMAIL
+DESCRIPTION:This is an event reminder
+X-EVOLUTION-ALARM-UID:20160405T152128Z-2848-32011-1844-66@lomiri-phablet
+END:VALARM
+END:VEVENT
+END:VCALENDAR
diff --git a/tests/test-eds-ics-non-selected-source.cpp b/tests/test-eds-ics-non-selected-source.cpp
new file mode 100644
index 0000000..5101b32
--- /dev/null
+++ b/tests/test-eds-ics-non-selected-source.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2015 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:
+ * Renato Araujo Oliveira Filh <renato.filho@canonical.com>
+ */
+
+#include <algorithm>
+
+#include <datetime/engine-eds.h>
+#include <datetime/myself.h>
+#include <datetime/planner-range.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include <gtest/gtest.h>
+
+#include "glib-fixture.h"
+#include "timezone-mock.h"
+
+using namespace ayatana::indicator::datetime;
+using VAlarmFixture = GlibFixture;
+
+/***
+****
+***/
+
+TEST_F(VAlarmFixture, NonSelectedSources)
+{
+ // start the EDS engine
+ auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>());
+
+ // we need a consistent timezone for the planner and our local DateTimes
+ constexpr char const * zone_str {"America/Chicago"};
+ auto tz = std::make_shared<MockTimezone>(zone_str);
+ auto gtz = g_time_zone_new(zone_str);
+
+ // make a planner that looks at the first half of 2015 in EDS
+ auto planner = std::make_shared<SimpleRangePlanner>(engine, tz);
+ const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0};
+ const DateTime range_end {gtz, 2015,6,31,23,59,59.5};
+ planner->range().set(std::make_pair(range_begin, range_end));
+
+ // give EDS a moment to load
+ if (planner->appointments().get().empty()) {
+ g_message("waiting a moment for EDS to load...");
+ auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){
+ g_message("ah, they loaded");
+ if (!appointments.empty())
+ g_main_loop_quit(loop);
+ };
+ core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed));
+ constexpr int max_wait_sec = 10;
+ wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND);
+ }
+
+ // appointmes are visible
+ auto appts = planner->appointments().get();
+ EXPECT_TRUE(appts.size() > 0);
+
+ // Unselect all sources
+ auto registry = e_source_registry_new_sync(NULL, NULL);
+ auto sources = e_source_registry_list_sources(registry, E_SOURCE_EXTENSION_TASK_LIST);
+ for (auto l=sources; l!=nullptr; l=l->next) {
+ auto source = static_cast<ESource*>(l->data);
+ auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR);
+ e_source_selectable_set_selected(E_SOURCE_SELECTABLE(extension), FALSE);
+ e_source_write_sync(source, NULL, NULL);
+ }
+
+ g_list_free_full(sources, g_object_unref);
+ g_object_unref(registry);
+
+ // give some time to planner update
+ wait_msec(5 * G_TIME_SPAN_MILLISECOND);
+
+ // the planner should be empty at this point
+ appts = planner->appointments().get();
+ EXPECT_TRUE(appts.size() == 0);
+ // cleanup
+ g_time_zone_unref(gtz);
+}
diff --git a/tests/test-eds-ics-non-selected-source.ics.in b/tests/test-eds-ics-non-selected-source.ics.in
new file mode 100644
index 0000000..19f93d7
--- /dev/null
+++ b/tests/test-eds-ics-non-selected-source.ics.in
@@ -0,0 +1,28 @@
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Ximian//NONSGML Evolution Calendar//EN
+VERSION:2.0
+X-EVOLUTION-DATA-REVISION:2015-05-07T21:14:49.315443Z(0)
+BEGIN:VTODO
+UID:20150507T211449Z-4262-32011-1418-1@lomiri-phablet
+DTSTAMP:20150508T211449Z
+DTSTART:20150508T164000
+RRULE:FREQ=WEEKLY;BYDAY=FR
+SUMMARY:Alarm
+CATEGORIES:x-lomiri-alarm
+CREATED:20150507T211449Z
+LAST-MODIFIED:20150507T211449Z
+BEGIN:VALARM
+X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-2@lomiri-phablet
+ACTION:AUDIO
+ATTACH:file://@ALARM_DEFAULT_SOUND@
+TRIGGER;VALUE=DURATION;RELATED=START:PT0S
+END:VALARM
+BEGIN:VALARM
+X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-3@lomiri-phablet
+ACTION:DISPLAY
+DESCRIPTION:Alarm
+TRIGGER;VALUE=DURATION;RELATED=START:PT0S
+END:VALARM
+END:VTODO
+END:VCALENDAR
diff --git a/tests/test-eds-ics-nonrepeating-events.cpp b/tests/test-eds-ics-nonrepeating-events.cpp
index e79ab1a..8aa2b82 100644
--- a/tests/test-eds-ics-nonrepeating-events.cpp
+++ b/tests/test-eds-ics-nonrepeating-events.cpp
@@ -24,6 +24,7 @@
#include <datetime/alarm-queue-simple.h>
#include <datetime/clock-mock.h>
#include <datetime/engine-eds.h>
+#include <datetime/myself.h>
#include <datetime/planner-range.h>
#include <gtest/gtest.h>
@@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture;
TEST_F(VAlarmFixture, MultipleAppointments)
{
// start the EDS engine
- auto engine = std::make_shared<EdsEngine>();
+ auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>());
// we need a consistent timezone for the planner and our local DateTimes
constexpr char const * zone_str {"America/Chicago"};
@@ -80,7 +81,7 @@ TEST_F(VAlarmFixture, MultipleAppointments)
// what we expect to get...
Appointment expected_appt;
- expected_appt.uid = "20150520T000726Z-3878-32011-1770-81@ubuntu-phablet";
+ expected_appt.uid = "20150520T000726Z-3878-32011-1770-81@lomiri-phablet";
expected_appt.color = "#becedd";
expected_appt.summary = "Alarm";
std::array<Alarm,1> expected_alarms = {
diff --git a/tests/test-eds-ics-repeating-events-with-individual-change.cpp b/tests/test-eds-ics-repeating-events-with-individual-change.cpp
new file mode 100644
index 0000000..a4a85c0
--- /dev/null
+++ b/tests/test-eds-ics-repeating-events-with-individual-change.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2016 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:
+ * Renato Araujo Oliveira Filho <renato.filho@canonical.com>
+ */
+
+#include <algorithm>
+
+#include <datetime/alarm-queue-simple.h>
+#include <datetime/clock-mock.h>
+#include <datetime/engine-eds.h>
+#include <datetime/myself.h>
+#include <datetime/planner-range.h>
+
+#include <gtest/gtest.h>
+
+#include "glib-fixture.h"
+#include "print-to.h"
+#include "timezone-mock.h"
+#include "wakeup-timer-mock.h"
+
+using namespace ayatana::indicator::datetime;
+using VAlarmFixture = GlibFixture;
+
+/***
+****
+***/
+
+TEST_F(VAlarmFixture, RepeatingEventsWithIndividualChange)
+{
+ // start the EDS engine
+ auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>());
+
+ // we need a consistent timezone for the planner and our local DateTimes
+ constexpr char const * zone_str {"America/Recife"};
+ auto tz = std::make_shared<MockTimezone>(zone_str);
+ #if GLIB_CHECK_VERSION(2, 68, 0)
+ auto gtz = g_time_zone_new_identifier(zone_str);
+
+ if (gtz == NULL)
+ {
+ gtz = g_time_zone_new_utc();
+ }
+ #else
+ auto gtz = g_time_zone_new(zone_str);
+ #endif
+
+ // make a planner that looks at the year of 2016 in EDS
+ auto planner = std::make_shared<SimpleRangePlanner>(engine, tz);
+ const DateTime range_begin {gtz, 2016,1, 1, 0, 0, 0.0};
+ const DateTime range_end {gtz, 2016,12,31,23,59,59.5};
+ planner->range().set(std::make_pair(range_begin, range_end));
+
+ // give EDS a moment to load
+ if (planner->appointments().get().empty()) {
+ g_message("waiting a moment for EDS to load...");
+ auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){
+ g_message("ah, they loaded");
+ if (!appointments.empty())
+ g_main_loop_quit(loop);
+ };
+ core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed));
+ constexpr int max_wait_sec = 10;
+ wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND);
+ }
+
+ // what we expect to get...
+ Appointment expected_appt;
+ expected_appt.summary = "Alarm";
+ std::array<DateTime,10> expected_times = {
+ DateTime(gtz,2016,6, 20,10,00,0),
+ DateTime(gtz,2016,6, 21,10,00,0),
+ DateTime(gtz,2016,6, 22,10,00,0),
+ DateTime(gtz,2016,6, 23,10,00,0),
+ DateTime(gtz,2016,6, 24,20,00,0),
+ DateTime(gtz,2016,6, 25,10,00,0),
+ DateTime(gtz,2016,6, 26,10,00,0),
+ DateTime(gtz,2016,6, 27,10,00,0),
+ DateTime(gtz,2016,6, 28,10,00,0),
+ DateTime(gtz,2016,6, 29,10,00,0)
+ };
+
+ // compare it to what we actually loaded...
+ const auto appts = planner->appointments().get();
+ EXPECT_EQ(expected_times.size(), appts.size());
+ for (size_t i=0, n=expected_times.size(); i<n; i++) {
+ const auto& appt = appts[i];
+ if (i != 4)
+ EXPECT_EQ("Every day and every night", appt.summary);
+ else
+ EXPECT_EQ("At night", appt.summary);
+ EXPECT_EQ(expected_times[i], appt.begin);
+ }
+
+ // cleanup
+ g_time_zone_unref(gtz);
+}
diff --git a/tests/test-eds-ics-repeating-events-with-individual-change.ics.in b/tests/test-eds-ics-repeating-events-with-individual-change.ics.in
new file mode 100644
index 0000000..3723399
--- /dev/null
+++ b/tests/test-eds-ics-repeating-events-with-individual-change.ics.in
@@ -0,0 +1,969 @@
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Ximian//NONSGML Evolution Calendar//EN
+VERSION:2.0
+X-EVOLUTION-DATA-REVISION:2016-06-21T21:42:08.507434Z(7)
+BEGIN:VTIMEZONE
+TZID:/freeassociation.sourceforge.net/Tzfile/America/Recife
+X-LIC-LOCATION:America/Recife
+BEGIN:STANDARD
+TZNAME:BRT
+DTSTART:19680301T000000
+TZOFFSETFROM:-0200
+TZOFFSETTO:-0300
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:BRST
+DTSTART:19851102T000000
+TZOFFSETFROM:-0300
+TZOFFSETTO:-0200
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:BRT
+DTSTART:19860315T000000
+TZOFFSETFROM:-0200
+TZOFFSETTO:-0300
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:BRST
+DTSTART:19861025T000000
+TZOFFSETFROM:-0300
+TZOFFSETTO:-0200
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:BRT
+DTSTART:19870214T000000
+TZOFFSETFROM:-0200
+TZOFFSETTO:-0300
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:BRST
+DTSTART:19871025T000000
+TZOFFSETFROM:-0300
+TZOFFSETTO:-0200
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:BRT
+DTSTART:19880207T000000
+TZOFFSETFROM:-0200
+TZOFFSETTO:-0300
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:BRST
+DTSTART:19881016T000000
+TZOFFSETFROM:-0300
+TZOFFSETTO:-0200
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:BRT
+DTSTART:19890129T000000
+TZOFFSETFROM:-0200
+TZOFFSETTO:-0300
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:BRST
+DTSTART:19891015T000000
+TZOFFSETFROM:-0300
+TZOFFSETTO:-0200
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:BRT
+DTSTART:19900211T000000
+TZOFFSETFROM:-0200
+TZOFFSETTO:-0300
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:BRST
+DTSTART:19991003T000000
+TZOFFSETFROM:-0300
+TZOFFSETTO:-0200
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:BRT
+DTSTART:20000227T000000
+TZOFFSETFROM:-0200
+TZOFFSETTO:-0300
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:BRST
+DTSTART:20001008T000000
+TZOFFSETFROM:-0300
+TZOFFSETTO:-0200
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:BRT
+DTSTART:20001015T000000
+TZOFFSETFROM:-0200
+TZOFFSETTO:-0300
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:BRST
+DTSTART:20011014T000000
+TZOFFSETFROM:-0300
+TZOFFSETTO:-0200
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:BRT
+DTSTART:20020217T000000
+TZOFFSETFROM:-0200
+TZOFFSETTO:-0300
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VTIMEZONE
+TZID:/freeassociation.sourceforge.net/Tzfile/America/New_York
+X-LIC-LOCATION:America/New_York
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19691026T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19700426T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19701025T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19710425T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19711031T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19720430T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19721029T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19730429T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19731028T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19740106T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19741027T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19750223T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19751026T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19760425T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19761031T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19770424T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19771030T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19780430T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19781029T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19790429T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19791028T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19800427T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19801026T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19810426T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19811025T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19820425T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19821031T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19830424T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19831030T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19840429T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19841028T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19850428T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19851027T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19860427T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19861026T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19870405T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19871025T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19880403T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19881030T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19890402T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19891029T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19900401T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19901028T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19910407T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19911027T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19920405T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19921025T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19930404T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19931031T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19940403T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19941030T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19950402T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19951029T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19960407T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19961027T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19970406T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19971026T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19980405T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19981025T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:19990404T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:19991031T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20000402T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20001029T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20010401T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20011028T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20020407T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20021027T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20030406T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20031026T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20040404T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20041031T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20050403T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20051030T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20060402T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20061029T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20070311T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20071104T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20080309T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20081102T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20090308T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20091101T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20100314T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20101107T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20110313T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20111106T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20120311T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20121104T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20130310T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20131103T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20140309T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20141102T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20150308T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20151101T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20160313T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20161106T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20170312T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20171105T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20180311T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20181104T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20190310T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20191103T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20200308T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20201101T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20210314T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20211107T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20220313T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20221106T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20230312T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20231105T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20240310T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20241103T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20250309T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20251102T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20260308T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20261101T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20270314T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20271107T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20280312T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20281105T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20290311T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20291104T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20300310T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20301103T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20310309T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20311102T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20320314T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20321107T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20330313T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20331106T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20340312T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20341105T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20350311T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20351104T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20360309T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20361102T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:EDT
+DTSTART:20370308T020000
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:EST
+DTSTART:20371101T020000
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:20160621T214054Z-31488-1000-4151-27@rento-lomiri
+DTSTAMP:20160621T123718Z
+DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife:
+ 20160620T100000
+DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife:
+ 20160620T103000
+TRANSP:OPAQUE
+SEQUENCE:2
+SUMMARY:Every day and every night
+CLASS:PUBLIC
+RRULE;X-EVOLUTION-ENDDATE=20160629T130000Z:FREQ=DAILY;COUNT=10
+CREATED:20160621T214124Z
+LAST-MODIFIED:20160621T214124Z
+END:VEVENT
+BEGIN:VEVENT
+UID:20160621T214054Z-31488-1000-4151-27@rento-lomiri
+DTSTAMP:20160621T123718Z
+DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife:
+ 20160624T200000
+DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife:
+ 20160624T203000
+TRANSP:OPAQUE
+SEQUENCE:3
+SUMMARY:At night
+CLASS:PUBLIC
+CREATED:20160621T214124Z
+LAST-MODIFIED:20160621T214208Z
+RECURRENCE-ID;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife:
+ 20160624T100000
+END:VEVENT
+END:VCALENDAR
diff --git a/tests/test-eds-ics-repeating-events.cpp b/tests/test-eds-ics-repeating-events.cpp
index d4f0026..4125623 100644
--- a/tests/test-eds-ics-repeating-events.cpp
+++ b/tests/test-eds-ics-repeating-events.cpp
@@ -24,6 +24,7 @@
#include <datetime/alarm-queue-simple.h>
#include <datetime/clock-mock.h>
#include <datetime/engine-eds.h>
+#include <datetime/myself.h>
#include <datetime/planner-range.h>
#include <gtest/gtest.h>
@@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture;
TEST_F(VAlarmFixture, MultipleAppointments)
{
// start the EDS engine
- auto engine = std::make_shared<EdsEngine>();
+ auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>());
// we need a consistent timezone for the planner and our local DateTimes
constexpr char const * zone_str {"America/Chicago"};
@@ -80,7 +81,7 @@ TEST_F(VAlarmFixture, MultipleAppointments)
// what we expect to get...
Appointment expected_appt;
- expected_appt.uid = "20150507T211449Z-4262-32011-1418-1@ubuntu-phablet";
+ expected_appt.uid = "20150507T211449Z-4262-32011-1418-1@lomiri-phablet";
expected_appt.color = "#becedd";
expected_appt.summary = "Alarm";
std::array<Alarm,8> expected_alarms = {
diff --git a/tests/test-eds-ics-repeating-valarms.cpp b/tests/test-eds-ics-repeating-valarms.cpp
index 2132b71..1584983 100644
--- a/tests/test-eds-ics-repeating-valarms.cpp
+++ b/tests/test-eds-ics-repeating-valarms.cpp
@@ -24,6 +24,7 @@
#include <datetime/alarm-queue-simple.h>
#include <datetime/clock-mock.h>
#include <datetime/engine-eds.h>
+#include <datetime/myself.h>
#include <datetime/planner-range.h>
#include <gtest/gtest.h>
@@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture;
TEST_F(VAlarmFixture, MultipleAppointments)
{
// start the EDS engine
- auto engine = std::make_shared<EdsEngine>();
+ auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>());
// we need a consistent timezone for the planner and our local DateTimes
constexpr char const * zone_str {"America/Chicago"};
diff --git a/tests/test-eds-ics-tzids-2.cpp b/tests/test-eds-ics-tzids-2.cpp
index c8b0370..a1d2f5a 100644
--- a/tests/test-eds-ics-tzids-2.cpp
+++ b/tests/test-eds-ics-tzids-2.cpp
@@ -24,6 +24,7 @@
#include <datetime/alarm-queue-simple.h>
#include <datetime/clock-mock.h>
#include <datetime/engine-eds.h>
+#include <datetime/myself.h>
#include <datetime/planner-range.h>
#include <gtest/gtest.h>
@@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture;
TEST_F(VAlarmFixture, MultipleAppointments)
{
// start the EDS engine
- auto engine = std::make_shared<EdsEngine>();
+ auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>());
// we need a consistent timezone for the planner and our local DateTimes
constexpr char const * zone_str {"America/Los_Angeles"};
diff --git a/tests/test-eds-ics-tzids-utc.cpp b/tests/test-eds-ics-tzids-utc.cpp
new file mode 100644
index 0000000..f79bf3e
--- /dev/null
+++ b/tests/test-eds-ics-tzids-utc.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2015 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 <algorithm>
+
+#include <datetime/alarm-queue-simple.h>
+#include <datetime/clock-mock.h>
+#include <datetime/engine-eds.h>
+#include <datetime/myself.h>
+#include <datetime/planner-range.h>
+
+#include <gtest/gtest.h>
+
+#include "glib-fixture.h"
+#include "print-to.h"
+#include "timezone-mock.h"
+#include "wakeup-timer-mock.h"
+
+using namespace ayatana::indicator::datetime;
+using VAlarmFixture = GlibFixture;
+
+/***
+****
+***/
+
+TEST_F(VAlarmFixture, UTCAppointments)
+{
+ // start the EDS engine
+ auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>());
+
+ // we need a consistent timezone for the planner and our local DateTimes
+ constexpr char const * zone_str {"America/Recife"};
+ auto tz = std::make_shared<MockTimezone>(zone_str);
+
+ #if GLIB_CHECK_VERSION(2, 68, 0)
+ auto gtz = g_time_zone_new_identifier(zone_str);
+
+ if (gtz == NULL)
+ {
+ gtz = g_time_zone_new_utc();
+ }
+ #else
+ auto gtz = g_time_zone_new(zone_str);
+ #endif
+
+ // make a planner that looks at the first half of 2015 in EDS
+ auto planner = std::make_shared<SimpleRangePlanner>(engine, tz);
+ const DateTime range_begin {gtz, 2016,1, 1, 0, 0, 0.0};
+ const DateTime range_end {gtz, 2017,1, 1, 0, 0, 0.0};
+ planner->range().set(std::make_pair(range_begin, range_end));
+
+ // give EDS a moment to load
+ if (planner->appointments().get().empty()) {
+ g_message("waiting a moment for EDS to load...");
+ auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){
+ g_message("ah, they loaded");
+ if (!appointments.empty())
+ g_main_loop_quit(loop);
+ };
+ core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed));
+ constexpr int max_wait_sec = 10;
+ wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND);
+ }
+
+ // what we expect to get...
+ std::array<Appointment,1> expected_appts;
+ auto appt = &expected_appts[0];
+ appt->uid = "20160322T132738Z";
+ appt->color = "#becedd";
+ appt->summary = "UTC event";
+ appt->begin = DateTime{gtz,2016,3,22,15,0,0};
+ appt->end = DateTime{gtz,2016,3,22,16,0,0};
+
+ // compare it to what we actually loaded...
+ const auto appts = planner->appointments().get();
+ EXPECT_EQ(expected_appts.size(), appts.size());
+ for (size_t i=0, n=std::min(appts.size(),expected_appts.size()); i<n; i++)
+ EXPECT_EQ(expected_appts[i], appts[i]);
+
+ // cleanup
+ g_time_zone_unref(gtz);
+}
diff --git a/tests/test-eds-ics-tzids-utc.ics.in b/tests/test-eds-ics-tzids-utc.ics.in
new file mode 100644
index 0000000..da607d6
--- /dev/null
+++ b/tests/test-eds-ics-tzids-utc.ics.in
@@ -0,0 +1,15 @@
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Ximian//NONSGML Evolution Calendar//EN
+VERSION:2.0
+X-EVOLUTION-DATA-REVISION:2016-03-22T16:13:53.714952Z(2)
+BEGIN:VEVENT
+UID:20160322T132738Z
+DTSTAMP:20160322T133534Z
+DTSTART:20160322T180000Z
+DTEND:20160322T190000Z
+SUMMARY:UTC event
+SEQUENCE:1
+LAST-MODIFIED:20160322T133534Z
+END:VEVENT
+END:VCALENDAR
diff --git a/tests/test-eds-ics-tzids.cpp b/tests/test-eds-ics-tzids.cpp
index c80feb2..11d44b7 100644
--- a/tests/test-eds-ics-tzids.cpp
+++ b/tests/test-eds-ics-tzids.cpp
@@ -24,6 +24,7 @@
#include <datetime/alarm-queue-simple.h>
#include <datetime/clock-mock.h>
#include <datetime/engine-eds.h>
+#include <datetime/myself.h>
#include <datetime/planner-range.h>
#include <gtest/gtest.h>
@@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture;
TEST_F(VAlarmFixture, MultipleAppointments)
{
// start the EDS engine
- auto engine = std::make_shared<EdsEngine>();
+ auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>());
// we need a consistent timezone for the planner and our local DateTimes
constexpr char const * zone_str {"Europe/Berlin"};
diff --git a/tests/test-formatter.cpp b/tests/test-formatter.cpp
index 855d4c5..87c6475 100644
--- a/tests/test-formatter.cpp
+++ b/tests/test-formatter.cpp
@@ -66,15 +66,14 @@ class FormatterFixture: public GlibFixture
bool SetLocale(const char* expected_locale, const char* name)
{
- setlocale(LC_TIME, expected_locale);
- const auto actual_locale = setlocale(LC_TIME, nullptr);
- if (!g_strcmp0(expected_locale, actual_locale))
+ if (setlocale(LC_TIME, expected_locale) != nullptr)
{
return true;
}
else
{
- g_message("Unable to set locale to %s, actual locale is %s; skipping %s locale tests.", expected_locale, actual_locale, name);
+ g_warning("Unable to set locale to %s; skipping %s locale tests. (Current LC_TIME: %s)",
+ expected_locale, name, setlocale(LC_TIME, nullptr));
return false;
}
}
@@ -87,7 +86,7 @@ class FormatterFixture: public GlibFixture
/**
* Test the phone header format
*/
-TEST_F(FormatterFixture, TestPhoneHeader)
+TEST_F(FormatterFixture, DISABLED_TestPhoneHeader)
{
auto now = DateTime::Local(2020, 10, 31, 18, 30, 59);
auto clock = std::make_shared<MockClock>(now);
@@ -114,7 +113,7 @@ TEST_F(FormatterFixture, TestPhoneHeader)
/**
* Test the default values of the desktop header format
*/
-TEST_F(FormatterFixture, TestDesktopHeader)
+TEST_F(FormatterFixture, DISABLED_TestDesktopHeader)
{
struct {
bool is_12h;
diff --git a/tests/test-live-actions.cpp b/tests/test-live-actions.cpp
index 9f17001..2d6ac9b 100644
--- a/tests/test-live-actions.cpp
+++ b/tests/test-live-actions.cpp
@@ -17,18 +17,72 @@
* Charles Kerr <charles.kerr@canonical.com>
*/
+#include "state-mock.h"
#include "timedated-fixture.h"
+#include <datetime/actions-live.h>
+
+using namespace ayatana::indicator::datetime;
+
+class MockLiveActions: public LiveActions
+{
+public:
+ std::string last_cmd;
+ std::string last_url;
+
+ explicit MockLiveActions(const std::shared_ptr<State>& state_in): LiveActions(state_in) {}
+ ~MockLiveActions() {}
+};
+
+class TestLiveActionsFixture: public TimedatedFixture
+{
+private:
+
+ using super = TimedatedFixture;
+
+protected:
+
+ std::shared_ptr<MockState> m_mock_state;
+ std::shared_ptr<State> m_state;
+ std::shared_ptr<MockLiveActions> m_live_actions;
+ std::shared_ptr<Actions> m_actions;
+
+ void SetUp() override
+ {
+ super::SetUp();
+
+ // create the State and Actions
+ m_mock_state.reset(new MockState);
+ m_mock_state->settings.reset(new Settings);
+ m_state = std::dynamic_pointer_cast<State>(m_mock_state);
+ m_live_actions.reset(new MockLiveActions(m_state));
+ m_actions = std::dynamic_pointer_cast<Actions>(m_live_actions);
+
+ // start the timedate1 dbusmock
+ start_timedate1("Etc/Utc");
+ }
+
+ void TearDown() override
+ {
+ m_actions.reset();
+ m_live_actions.reset();
+ m_state.reset();
+ m_mock_state.reset();
+
+ super::TearDown();
+ }
+};
+
/***
****
***/
-TEST_F(TimedateFixture, HelloWorld)
+TEST_F(TestLiveActionsFixture, HelloWorld)
{
EXPECT_TRUE(true);
}
-TEST_F(TimedateFixture, SetLocation)
+TEST_F(TestLiveActionsFixture, SetLocation)
{
const std::string tzid = "America/Chicago";
const std::string name = "Oklahoma City";
@@ -36,49 +90,50 @@ TEST_F(TimedateFixture, SetLocation)
EXPECT_NE(expected, m_state->settings->timezone_name.get());
- m_actions->set_location(tzid, name);
+ std::string new_name;
m_state->settings->timezone_name.changed().connect(
- [this](const std::string&){
- g_main_loop_quit(loop);
- });
- g_main_loop_run(loop);
- EXPECT_EQ(attempted_tzid, tzid);
- wait_msec();
+ [&new_name](const std::string& n){new_name = n;}
+ );
+
+ m_actions->set_location(tzid, name);
+ EXPECT_TRUE(wait_for([&new_name](){return !new_name.empty();}));
+ EXPECT_EQ(expected, new_name);
EXPECT_EQ(expected, m_state->settings->timezone_name.get());
+ EXPECT_EQ(tzid, get_timedate1_timezone());
}
/***
****
***/
-TEST_F(TimedateFixture, DesktopOpenAlarmApp)
+TEST_F(TestLiveActionsFixture, DesktopOpenAlarmApp)
{
- m_actions->desktop_open_alarm_app();
+ m_actions->open_alarm_app();
const std::string expected = "evolution -c calendar";
EXPECT_EQ(expected, m_live_actions->last_cmd);
}
-TEST_F(TimedateFixture, DesktopOpenAppointment)
+TEST_F(TestLiveActionsFixture, DesktopOpenAppointment)
{
Appointment a;
a.uid = "some-uid";
a.begin = DateTime::NowLocal();
- m_actions->desktop_open_appointment(a);
+ m_actions->open_appointment(a, a.begin);
const std::string expected_substr = "evolution \"calendar:///?startdate=";
EXPECT_NE(m_live_actions->last_cmd.find(expected_substr), std::string::npos);
}
-TEST_F(TimedateFixture, DesktopOpenCalendarApp)
+TEST_F(TestLiveActionsFixture, DesktopOpenCalendarApp)
{
- m_actions->desktop_open_calendar_app(DateTime::NowLocal());
+ m_actions->open_calendar_app(DateTime::NowLocal());
const std::string expected_substr = "evolution \"calendar:///?startdate=";
EXPECT_NE(m_live_actions->last_cmd.find(expected_substr), std::string::npos);
}
-TEST_F(TimedateFixture, DesktopOpenSettingsApp)
+TEST_F(TestLiveActionsFixture, DesktopOpenSettingsApp)
{
- m_actions->desktop_open_settings_app();
+ m_actions->open_settings_app();
const std::string expected_substr = "control-center";
EXPECT_NE(m_live_actions->last_cmd.find(expected_substr), std::string::npos);
}
@@ -89,42 +144,45 @@ TEST_F(TimedateFixture, DesktopOpenSettingsApp)
namespace
{
- const std::string clock_app_url = "appid://com.ubuntu.clock/clock/current-user-version";
-
- const std::string calendar_app_url = "appid://com.ubuntu.calendar/calendar/current-user-version";
+ const std::string clock_app_url = "alarm://";
}
-TEST_F(TimedateFixture, PhoneOpenAlarmApp)
+TEST_F(TestLiveActionsFixture, PhoneOpenAlarmApp)
{
- m_actions->phone_open_alarm_app();
+ m_actions->open_alarm_app();
EXPECT_EQ(clock_app_url, m_live_actions->last_url);
}
-TEST_F(TimedateFixture, PhoneOpenAppointment)
+TEST_F(TestLiveActionsFixture, PhoneOpenAppointment)
{
Appointment a;
- a.uid = "some-uid";
+ a.uid = "event-uid";
+ a.source_uid = "source-uid";
a.begin = DateTime::NowLocal();
a.type = Appointment::EVENT;
- m_actions->phone_open_appointment(a);
- EXPECT_EQ(calendar_app_url, m_live_actions->last_url);
+ auto ocurrenceDate = DateTime::Local(2014, 1, 1, 0, 0, 0);
+ m_actions->open_appointment(a, ocurrenceDate);
+ const std::string appointment_app_url = ocurrenceDate.to_timezone("UTC").format("calendar://startdate=%Y-%m-%dT%H:%M:%S+00:00");
+ EXPECT_EQ(appointment_app_url, m_live_actions->last_url);
a.type = Appointment::UBUNTU_ALARM;
- m_actions->phone_open_appointment(a);
+ m_actions->open_appointment(a, a.begin);
EXPECT_EQ(clock_app_url, m_live_actions->last_url);
}
-TEST_F(TimedateFixture, PhoneOpenCalendarApp)
+TEST_F(TestLiveActionsFixture, PhoneOpenCalendarApp)
{
- m_actions->phone_open_calendar_app(DateTime::NowLocal());
- const std::string expected = "appid://com.ubuntu.calendar/calendar/current-user-version";
+ auto now = DateTime::NowLocal();
+ m_actions->open_calendar_app(now);
+ const std::string expected = now.to_timezone("UTC").format("calendar://startdate=%Y-%m-%dT%H:%M:%S+00:00");
EXPECT_EQ(expected, m_live_actions->last_url);
}
-TEST_F(TimedateFixture, PhoneOpenSettingsApp)
+
+TEST_F(TestLiveActionsFixture, PhoneOpenSettingsApp)
{
- m_actions->phone_open_settings_app();
+ m_actions->open_settings_app();
const std::string expected = "settings:///system/time-date";
EXPECT_EQ(expected, m_live_actions->last_url);
}
@@ -133,7 +191,7 @@ TEST_F(TimedateFixture, PhoneOpenSettingsApp)
****
***/
-TEST_F(TimedateFixture, CalendarState)
+TEST_F(TestLiveActionsFixture, CalendarState)
{
// init the clock
auto now = DateTime::Local(2014, 1, 1, 0, 0, 0);
diff --git a/tests/test-menu-appointments.cpp b/tests/test-menu-appointments.cpp
new file mode 100644
index 0000000..91a4b42
--- /dev/null
+++ b/tests/test-menu-appointments.cpp
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2016 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 "glib-fixture.h"
+#include "print-to.h"
+
+#include <datetime/appointment.h>
+#include <datetime/menu.h>
+
+#include <vector>
+
+using MenuAppointmentFixture = GlibFixture;
+
+using namespace ayatana::indicator::datetime;
+
+namespace
+{
+ Appointment create_appointment(
+ const Appointment::Type& type,
+ const std::string& uid,
+ const std::string& summary,
+ const DateTime& begin,
+ const DateTime& end)
+ {
+ Appointment a;
+ a.type = type;
+ a.uid = uid;
+ a.summary = summary;
+ a.begin = begin;
+ a.end = end;
+ return a;
+ }
+}
+
+TEST_F(MenuAppointmentFixture, DisplayEvents)
+{
+ const auto airport = create_appointment(
+ Appointment::UBUNTU_ALARM,
+ "uid-airport",
+ "Pick Aunt Mabel up at the airport",
+ DateTime::Local(2016,12,24,10,0,0),
+ DateTime::Local(2016,12,24,10,0,0)
+ );
+
+ const auto christmas_eve_candle_service = create_appointment(
+ Appointment::EVENT,
+ "uid-christmas-eve-candle-service",
+ "Christmas Eve Candle Service",
+ DateTime::Local(2016,12,24,22,0,0),
+ DateTime::Local(2016,12,24,23,0,0)
+ );
+
+ const auto christmas = create_appointment(
+ Appointment::EVENT,
+ "uid-christmas",
+ "Christmas",
+ DateTime::Local(2016,12,25,0,0,0),
+ DateTime::Local(2016,12,26,0,0,0)
+ );
+
+ const auto santa = create_appointment(
+ Appointment::UBUNTU_ALARM,
+ "uid-santa",
+ "Time to set out cookies and milk for Santa",
+ DateTime::Local(2016,12,25,1,0,0),
+ DateTime::Local(2016,12,25,1,0,0)
+ );
+
+ const auto bike = create_appointment(
+ Appointment::UBUNTU_ALARM,
+ "uid-bike",
+ "Remember to put out the bike, it's in the garage",
+ DateTime::Local(2016,12,25,1,0,0),
+ DateTime::Local(2016,12,25,1,0,0)
+ );
+
+
+ const auto christmas_lunch = create_appointment(
+ Appointment::EVENT,
+ "uid-christmas-lunch",
+ "Christmas Lunch at Grandma's",
+ DateTime::Local(2016,12,25,12,0,0),
+ DateTime::Local(2016,12,25,14,0,0)
+ );
+
+ const auto boxing_day = create_appointment(
+ Appointment::EVENT,
+ "uid-boxing-day",
+ "Boxing Day",
+ DateTime::Local(2016,12,26,0,0,0),
+ DateTime::Local(2016,12,27,0,0,0)
+ );
+
+ const auto new_years_eve = create_appointment(
+ Appointment::EVENT,
+ "uid-new-years-eve",
+ "New Years' Eve",
+ DateTime::Local(2016,12,31,0,0,0),
+ DateTime::Local(2017,1,1,0,0,0)
+ );
+
+ const auto nye_party = create_appointment(
+ Appointment::EVENT,
+ "uid-new-years-party",
+ "New Year Party at Ted's",
+ DateTime::Local(2016,12,31,19,0,0),
+ DateTime::Local(2017, 1, 1, 2,0,0)
+ );
+
+ const auto new_years_day = create_appointment(
+ Appointment::EVENT,
+ "uid-new-years-day",
+ "New Years' Day",
+ DateTime::Local(2017,1,1,0,0,0),
+ DateTime::Local(2017,1,2,0,0,0)
+ );
+
+ const auto weekday_wakeup_alarm = create_appointment(
+ Appointment::UBUNTU_ALARM,
+ "wakeup-alarm",
+ "Wake Up",
+ DateTime::Local(2017,1,3,6,0,0),
+ DateTime::Local(2017,1,3,6,0,0)
+ );
+
+ const auto dentist_appointment = create_appointment(
+ Appointment::EVENT,
+ "dentist",
+ "Dentist",
+ DateTime::Local(2017,1,5,14,0,0),
+ DateTime::Local(2017,1,5,15,0,0)
+ );
+
+ const auto marcus_birthday = create_appointment(
+ Appointment::EVENT,
+ "uid-mlk",
+ "Marcus' Birthday",
+ DateTime::Local(2017,1,8,0,0,0),
+ DateTime::Local(2017,1,9,0,0,0)
+ );
+
+ const auto mlk_day = create_appointment(
+ Appointment::EVENT,
+ "uid-mlk",
+ "Martin Luther King Day",
+ DateTime::Local(2017,1,16,0,0,0),
+ DateTime::Local(2017,1,17,0,0,0)
+ );
+
+ const auto rodney_party = create_appointment(
+ Appointment::EVENT,
+ "uid-rodney",
+ "Rodney's Party",
+ DateTime::Local(2017,1,30,19,0,0),
+ DateTime::Local(2017,1,30,23,0,0)
+ );
+
+ const auto pub_with_pawel = create_appointment(
+ Appointment::UBUNTU_ALARM,
+ "uid-pawel",
+ "Meet Pawel at the Pub",
+ DateTime::Local(2017,2,4,19,0,0),
+ DateTime::Local(2017,2,4,19,0,0)
+ );
+
+ const struct
+ {
+ const char* const description;
+ DateTime start;
+ std::vector<Appointment> appointments;
+ std::vector<Appointment> expected_display_appointments;
+ int max_items;
+ }
+ tests[] =
+ {
+ {
+ "test presentation order: full-day events come before part-day events",
+ DateTime::Local(2016,12,25,6,0,0),
+ std::vector<Appointment>({christmas, christmas_lunch, boxing_day, new_years_eve}),
+ std::vector<Appointment>({christmas, christmas_lunch, boxing_day, new_years_eve}),
+ 5
+ },
+ {
+ "test presentation order: part-day events in chronological order",
+ DateTime::Local(2016,12,24,0,0,0),
+ std::vector<Appointment>({airport, christmas_eve_candle_service, christmas, santa, christmas_lunch}),
+ std::vector<Appointment>({airport, christmas_eve_candle_service, christmas, santa, christmas_lunch}),
+ 5
+ },
+ {
+ "test presentation order: multiple events with the same start+end sorted alphabetically",
+ DateTime::Local(2016,12,25,0,0,0),
+ std::vector<Appointment>({christmas, bike, santa, christmas_lunch}),
+ std::vector<Appointment>({christmas, bike, santa, christmas_lunch}),
+ 5
+ },
+ {
+ "test culling priority: today's part-day events outrank today's full-day events",
+ DateTime::Local(2016,12,25,1,0,0),
+ std::vector<Appointment>({christmas, santa, christmas_lunch}),
+ std::vector<Appointment>({santa, christmas_lunch}),
+ 2
+ },
+ {
+ "test culling priority: events later today outrank both tomorrow's full-day and part-day events",
+ DateTime::Local(2016,12,24,0,0,0),
+ std::vector<Appointment>({christmas_eve_candle_service, christmas, santa}),
+ std::vector<Appointment>({christmas_eve_candle_service}),
+ 1
+ },
+ {
+ "test edge cases: confirm <max events works ok",
+ DateTime::Local(2016,12,24,0,0,0),
+ std::vector<Appointment>({christmas, christmas_lunch}),
+ std::vector<Appointment>({christmas, christmas_lunch}),
+ 5
+ },
+ {
+ "test edge cases: confirm 0 events works ok",
+ DateTime::Local(2016,12,24,0,0,0),
+ std::vector<Appointment>({}),
+ std::vector<Appointment>({}),
+ 5
+ },
+ {
+ "test edge cases: confirm max-events of 0 doesn't crash",
+ DateTime::Local(2016,12,24,0,0,0),
+ std::vector<Appointment>({christmas, bike, santa, christmas_lunch}),
+ std::vector<Appointment>({}),
+ 0
+ }
+ };
+
+ for (const auto& test : tests)
+ {
+ g_debug("running test: %s", test.description);
+
+ // run the test...
+ ASSERT_EQ(test.expected_display_appointments, Menu::get_display_appointments(test.appointments, test.start, test.max_items));
+
+ // ...and again with a reversed vector to confirm input order doesn't matter
+ auto reversed = test.appointments;
+ std::reverse(reversed.begin(), reversed.end());
+ ASSERT_EQ(test.expected_display_appointments, Menu::get_display_appointments(reversed, test.start, test.max_items));
+ }
+}
+
diff --git a/tests/test-notification-response.cpp b/tests/test-notification-response.cpp
new file mode 100644
index 0000000..fd40ed8
--- /dev/null
+++ b/tests/test-notification-response.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2014-2016 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/appointment.h>
+#include <datetime/settings.h>
+#include <datetime/snap.h>
+
+#include "notification-fixture.h"
+
+/***
+****
+***/
+
+using namespace ayatana::indicator::datetime;
+
+namespace
+{
+ static constexpr char const * APP_NAME {"indicator-datetime-service"};
+
+ gboolean quit_idle (gpointer gloop)
+ {
+ g_main_loop_quit(static_cast<GMainLoop*>(gloop));
+ return G_SOURCE_REMOVE;
+ }
+}
+
+/***
+****
+***/
+
+TEST_F(NotificationFixture,Response)
+{
+ // create the world
+ make_interactive();
+ auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME);
+ auto sb = std::make_shared<ayatana::indicator::notifications::DefaultSoundBuilder>();
+ auto settings = std::make_shared<Settings>();
+ int next_notification_id {FIRST_NOTIFY_ID};
+
+ // set a response callback that remembers what response we got
+ bool on_response_called {};
+ Snap::Response response {};
+ auto on_response = [this, &on_response_called, &response]
+ (const Appointment&, const Alarm&, const Snap::Response& r){
+ on_response_called = true;
+ response = r;
+ g_idle_add(quit_idle, loop);
+ };
+
+ // our tests!
+ const struct {
+ Appointment appt;
+ std::vector<std::string> expected_actions;
+ std::string action;
+ Snap::Response expected_response;
+ } tests[] = {
+ { appt, {"show-app"}, "show-app", Snap::Response::ShowApp },
+ { ualarm, {"none", "snooze"}, "snooze", Snap::Response::Snooze },
+ { ualarm, {"none", "snooze"}, "none", Snap::Response::None }
+ };
+
+
+ settings->cal_notification_enabled.set(true);
+ settings->cal_notification_sounds.set(true);
+ settings->cal_notification_vibrations.set(true);
+ settings->cal_notification_bubbles.set(true);
+ settings->cal_notification_list.set(true);
+
+ // walk through the tests
+ for (const auto& test : tests)
+ {
+ // wait for previous iterations' bus noise to finish and reset the counters
+ GError* error {};
+ wait_msec(500);
+ dbus_test_dbus_mock_object_clear_method_calls(notify_mock, notify_obj, &error);
+ g_assert_no_error(error);
+ on_response_called = false;
+
+ // create a snap decision
+ auto snap = create_snap(ne, sb, settings);
+ (*snap)(test.appt, test.appt.alarms.front(), on_response);
+
+ // wait for the notification to show up
+ EXPECT_METHOD_CALLED_EVENTUALLY(notify_mock, notify_obj, METHOD_NOTIFY);
+
+ // test that Notify was called the right number of times
+ static constexpr int expected_num_notify_calls {1};
+ guint num_notify_calls {};
+ const auto notify_calls = dbus_test_dbus_mock_object_get_method_calls(
+ notify_mock,
+ notify_obj,
+ METHOD_NOTIFY,
+ &num_notify_calls,
+ &error);
+ g_assert_no_error(error);
+ EXPECT_EQ(expected_num_notify_calls, num_notify_calls);
+
+ // test that Notify was called with the correct list of actions
+ if (num_notify_calls > 0) {
+ std::vector<std::string> actions;
+ const gchar** as {nullptr};
+ g_variant_get_child(notify_calls[0].params, 5, "^a&s", &as);
+ for (int i=0; as && as[i]; i+=2) // actions are in pairs of (name, i18n), skip the i18n
+ actions.push_back(as[i]);
+ EXPECT_EQ(test.expected_actions, actions);
+ }
+
+ // make the notification mock tell the world that the user invoked an action
+ const auto notification_id = next_notification_id++;
+ idle_add([this, notification_id, test](){
+ GError* err {};
+ dbus_test_dbus_mock_object_emit_signal(notify_mock, notify_obj, "ActionInvoked",
+ G_VARIANT_TYPE("(us)"),
+ g_variant_new("(us)", guint(notification_id), test.action.c_str()),
+ &err);
+ dbus_test_dbus_mock_object_emit_signal(notify_mock, notify_obj, "NotificationClosed",
+ G_VARIANT_TYPE("(uu)"),
+ g_variant_new("(uu)", guint(notification_id), guint(NOTIFICATION_CLOSED_DISMISSED)),
+ &err);
+ g_assert_no_error(err);
+ return G_SOURCE_REMOVE;
+ });
+
+ // confirm that the response callback got the right response
+ EXPECT_TRUE(wait_for([&on_response_called](){return on_response_called;}));
+ EXPECT_EQ(int(test.expected_response), int(response)) << "notification_id " << notification_id;
+ }
+}
diff --git a/tests/test-notification.cpp b/tests/test-notification.cpp
new file mode 100644
index 0000000..83a021f
--- /dev/null
+++ b/tests/test-notification.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2014-2016 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/appointment.h>
+#include <datetime/settings.h>
+#include <datetime/snap.h>
+
+#include "notification-fixture.h"
+
+/***
+****
+***/
+
+using namespace ayatana::indicator::datetime;
+
+namespace
+{
+ static constexpr char const * APP_NAME {"indicator-datetime-service"};
+
+ gboolean quit_idle (gpointer gloop)
+ {
+ g_main_loop_quit(static_cast<GMainLoop*>(gloop));
+ return G_SOURCE_REMOVE;
+ }
+}
+
+/***
+****
+***/
+
+TEST_F(NotificationFixture,Notification)
+{
+ // Feed different combinations of system settings,
+ // indicator-datetime settings, and event types,
+ // then see if notifications and haptic feedback behave as expected.
+
+ auto settings = std::make_shared<Settings>();
+ auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME);
+ auto sb = std::make_shared<ayatana::indicator::notifications::DefaultSoundBuilder>();
+ auto func = [this](const Appointment&, const Alarm&, const Snap::Response&){g_idle_add(quit_idle, loop);};
+
+ // combinatorial factor #1: event type
+ struct {
+ Appointment appt;
+ const char* icon_name;
+ const char* prefix;
+ bool expected_notify_called;
+ bool expected_vibrate_called;
+ } test_appts[] = {
+ { appt, "calendar-app", "Event", true, true },
+ { ualarm, "alarm-clock", "Alarm", true, true }
+ };
+
+ // combinatorial factor #2: indicator-datetime's haptic mode
+ struct {
+ const char* haptic_mode;
+ bool expected_notify_called;
+ bool expected_vibrate_called;
+ } test_haptics[] = {
+ { "none", true, false },
+ { "pulse", true, true }
+ };
+
+ // combinatorial factor #3: system settings' "other vibrations" enabled
+ struct {
+ bool other_vibrations;
+ bool expected_notify_called;
+ bool expected_vibrate_called;
+ } test_other_vibrations[] = {
+ { true, true, true },
+ { false, true, false }
+ };
+
+ // combinatorial factor #4: system settings' notifications disabled
+ struct {
+ bool cal_notification_enabled; // calendar app can trigger notifications
+ std::set<Appointment::Type> expected_notify_called; // do we expect the notification to show?
+ std::set<Appointment::Type> expected_vibrate_called; // do we expect the phone to vibrate?
+ } test_cal_disabled[] = {
+ { true, std::set<Appointment::Type>{ Appointment::Type::UBUNTU_ALARM, Appointment::Type::EVENT },
+ std::set<Appointment::Type>{ Appointment::Type::UBUNTU_ALARM, Appointment::Type::EVENT } },
+ { false, std::set<Appointment::Type>{ Appointment::Type::UBUNTU_ALARM },
+ std::set<Appointment::Type>{ Appointment::Type::UBUNTU_ALARM } }
+ };
+
+ for (const auto& test_appt : test_appts)
+ {
+ for (const auto& test_haptic : test_haptics)
+ {
+ for (const auto& test_vibes : test_other_vibrations)
+ {
+ for (const auto& test_disabled : test_cal_disabled)
+ {
+ const bool expected_notify_called = test_appt.expected_notify_called
+ && test_vibes.expected_notify_called
+ && (test_disabled.expected_notify_called.count(test_appt.appt.type) > 0)
+ && test_haptic.expected_notify_called;
+
+ const bool expected_vibrate_called = test_appt.expected_vibrate_called
+ && test_vibes.expected_vibrate_called
+ && (test_disabled.expected_vibrate_called.count(test_appt.appt.type) > 0)
+ && test_haptic.expected_vibrate_called;
+
+ // set test case properties: cal_notification_enabled
+ settings->cal_notification_enabled.set(test_disabled.cal_notification_enabled);
+ settings->cal_notification_sounds.set(test_disabled.cal_notification_enabled);
+ settings->cal_notification_vibrations.set(test_disabled.cal_notification_enabled);
+ settings->cal_notification_bubbles.set(test_disabled.cal_notification_enabled);
+ settings->cal_notification_list.set(test_disabled.cal_notification_enabled);
+
+ // set test case properties: haptic mode
+ settings->alarm_haptic.set(test_haptic.haptic_mode);
+
+ // set test case properties: other-vibrations flag
+ // (and wait for the PropertiesChanged signal so we know the dbusmock got it)
+ GError * error {};
+ dbus_test_dbus_mock_object_update_property(as_mock,
+ as_obj,
+ PROP_OTHER_VIBRATIONS,
+ g_variant_new_boolean(test_vibes.other_vibrations),
+ &error);
+ g_assert_no_error(error);
+
+ // wait for previous iterations' bus noise to finish and reset the counters
+ wait_msec(500);
+ dbus_test_dbus_mock_object_clear_method_calls(haptic_mock, haptic_obj, &error);
+ dbus_test_dbus_mock_object_clear_method_calls(notify_mock, notify_obj, &error);
+ g_assert_no_error(error);
+
+ // run the test
+ auto snap = create_snap(ne, sb, settings);
+ (*snap)(test_appt.appt, appt.alarms.front(), func);
+
+ // confirm that the notification was as expected
+ if (expected_notify_called) {
+ EXPECT_METHOD_CALLED_EVENTUALLY(notify_mock, notify_obj, METHOD_NOTIFY);
+ } else {
+ EXPECT_METHOD_NOT_CALLED_EVENTUALLY(notify_mock, notify_obj, METHOD_NOTIFY);
+ }
+
+ // confirm that the vibration was as expected
+ if (expected_vibrate_called) {
+ EXPECT_METHOD_CALLED_EVENTUALLY(haptic_mock, haptic_obj, HAPTIC_METHOD_VIBRATE_PATTERN);
+ } else {
+ EXPECT_METHOD_NOT_CALLED_EVENTUALLY(haptic_mock, haptic_obj, HAPTIC_METHOD_VIBRATE_PATTERN);
+ }
+
+ // confirm that the notification was as expected
+ guint num_notify_calls = 0;
+ const auto notify_calls = dbus_test_dbus_mock_object_get_method_calls(notify_mock,
+ notify_obj,
+ METHOD_NOTIFY,
+ &num_notify_calls,
+ &error);
+ g_assert_no_error(error);
+ if (num_notify_calls > 0)
+ {
+ // test that Notify was called with the app_name
+ const gchar* app_name {nullptr};
+ g_variant_get_child(notify_calls[0].params, 0, "&s", &app_name);
+ ASSERT_STREQ(APP_NAME, app_name);
+
+ // test that Notify was called with the type-appropriate icon
+ const gchar* icon_name {nullptr};
+ g_variant_get_child(notify_calls[0].params, 2, "&s", &icon_name);
+ ASSERT_STREQ(test_appt.icon_name, icon_name);
+
+ // test that the Notification title has the correct prefix
+ const gchar* title {nullptr};
+ g_variant_get_child(notify_calls[0].params, 3, "&s", &title);
+ ASSERT_TRUE(g_str_has_prefix(title, test_appt.prefix));
+
+ // test that Notify was called with the appointment's body
+ const gchar* body {nullptr};
+ g_variant_get_child(notify_calls[0].params, 4, "&s", &body);
+ ASSERT_STREQ(test_appt.appt.summary.c_str(), body);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/test-settings.cpp b/tests/test-settings.cpp
index 3af9eab..b9658f4 100644
--- a/tests/test-settings.cpp
+++ b/tests/test-settings.cpp
@@ -22,6 +22,11 @@
#include <datetime/settings-live.h>
#include <datetime/settings-shared.h>
+extern "C"
+{
+ #include <ayatana/common/utils.h>
+}
+
using namespace ayatana::indicator::datetime;
/***
@@ -37,19 +42,23 @@ protected:
std::shared_ptr<LiveSettings> m_live;
std::shared_ptr<Settings> m_settings;
- GSettings * m_gsettings;
+ GSettings * m_gsettings {};
+ GSettings * m_gsettings_cal_notification {};
void SetUp() override
{
super::SetUp();
m_gsettings = g_settings_new(SETTINGS_INTERFACE);
+ m_gsettings_cal_notification = g_settings_new_with_path(SETTINGS_NOTIFY_SCHEMA_ID, SETTINGS_NOTIFY_CALENDAR_PATH);
+
m_live.reset(new LiveSettings);
m_settings = std::dynamic_pointer_cast<Settings>(m_live);
}
void TearDown() override
{
+ g_clear_object(&m_gsettings_cal_notification);
g_clear_object(&m_gsettings);
m_settings.reset();
m_live.reset();
@@ -57,62 +66,62 @@ protected:
super::TearDown();
}
- void TestBoolProperty(core::Property<bool>& property, const gchar* key)
+ void TestBoolProperty(GSettings* gsettings, core::Property<bool>& property, const gchar* key)
{
- EXPECT_EQ(g_settings_get_boolean(m_gsettings, key), property.get());
- g_settings_set_boolean(m_gsettings, key, false);
+ EXPECT_EQ(g_settings_get_boolean(gsettings, key), property.get());
+ g_settings_set_boolean(gsettings, key, false);
EXPECT_FALSE(property.get());
- g_settings_set_boolean(m_gsettings, key, true);
+ g_settings_set_boolean(gsettings, key, true);
EXPECT_TRUE(property.get());
property.set(false);
- EXPECT_FALSE(g_settings_get_boolean(m_gsettings, key));
+ EXPECT_FALSE(g_settings_get_boolean(gsettings, key));
property.set(true);
- EXPECT_TRUE(g_settings_get_boolean(m_gsettings, key));
+ EXPECT_TRUE(g_settings_get_boolean(gsettings, key));
}
- void TestStringProperty(core::Property<std::string>& property, const gchar* key)
+ void TestStringProperty(GSettings* gsettings, core::Property<std::string>& property, const gchar* key)
{
gchar* tmp;
std::string str;
- tmp = g_settings_get_string(m_gsettings, key);
+ tmp = g_settings_get_string(gsettings, key);
EXPECT_EQ(tmp, property.get());
g_clear_pointer(&tmp, g_free);
str = "a";
- g_settings_set_string(m_gsettings, key, str.c_str());
+ g_settings_set_string(gsettings, key, str.c_str());
EXPECT_EQ(str, property.get());
str = "b";
- g_settings_set_string(m_gsettings, key, str.c_str());
+ g_settings_set_string(gsettings, key, str.c_str());
EXPECT_EQ(str, property.get());
str = "a";
property.set(str);
- tmp = g_settings_get_string(m_gsettings, key);
+ tmp = g_settings_get_string(gsettings, key);
EXPECT_EQ(str, tmp);
g_clear_pointer(&tmp, g_free);
str = "b";
property.set(str);
- tmp = g_settings_get_string(m_gsettings, key);
+ tmp = g_settings_get_string(gsettings, key);
EXPECT_EQ(str, tmp);
g_clear_pointer(&tmp, g_free);
}
- void TestUIntProperty(core::Property<unsigned int>& property, const gchar* key)
+ void TestUIntProperty(GSettings* gsettings, core::Property<unsigned int>& property, const gchar* key)
{
- EXPECT_EQ(g_settings_get_uint(m_gsettings, key), property.get());
+ EXPECT_EQ(g_settings_get_uint(gsettings, key), property.get());
unsigned int expected_values[] = { 1, 2, 3 };
// modify GSettings and confirm that the new value is propagated
for(const auto& expected_value : expected_values)
{
- g_settings_set_uint(m_gsettings, key, expected_value);
+ g_settings_set_uint(gsettings, key, expected_value);
EXPECT_EQ(expected_value, property.get());
- EXPECT_EQ(expected_value, g_settings_get_uint(m_gsettings, key));
+ EXPECT_EQ(expected_value, g_settings_get_uint(gsettings, key));
}
// modify the property and confirm that the new value is propagated
@@ -120,7 +129,7 @@ protected:
{
property.set(expected_value);
EXPECT_EQ(expected_value, property.get());
- EXPECT_EQ(expected_value, g_settings_get_uint(m_gsettings, key));
+ EXPECT_EQ(expected_value, g_settings_get_uint(gsettings, key));
}
}
};
@@ -136,31 +145,31 @@ TEST_F(SettingsFixture, HelloWorld)
TEST_F(SettingsFixture, BoolProperties)
{
- TestBoolProperty(m_settings->show_seconds, SETTINGS_SHOW_SECONDS_S);
- TestBoolProperty(m_settings->show_calendar, SETTINGS_SHOW_CALENDAR_S);
- TestBoolProperty(m_settings->show_date, SETTINGS_SHOW_DATE_S);
- TestBoolProperty(m_settings->show_day, SETTINGS_SHOW_DAY_S);
- TestBoolProperty(m_settings->show_detected_location, SETTINGS_SHOW_DETECTED_S);
- TestBoolProperty(m_settings->show_events, SETTINGS_SHOW_EVENTS_S);
- TestBoolProperty(m_settings->show_locations, SETTINGS_SHOW_LOCATIONS_S);
- TestBoolProperty(m_settings->show_week_numbers, SETTINGS_SHOW_WEEK_NUMBERS_S);
- TestBoolProperty(m_settings->show_year, SETTINGS_SHOW_YEAR_S);
+ TestBoolProperty(m_gsettings, m_settings->show_seconds, SETTINGS_SHOW_SECONDS_S);
+ TestBoolProperty(m_gsettings, m_settings->show_calendar, SETTINGS_SHOW_CALENDAR_S);
+ TestBoolProperty(m_gsettings, m_settings->show_date, SETTINGS_SHOW_DATE_S);
+ TestBoolProperty(m_gsettings, m_settings->show_day, SETTINGS_SHOW_DAY_S);
+ TestBoolProperty(m_gsettings, m_settings->show_detected_location, SETTINGS_SHOW_DETECTED_S);
+ TestBoolProperty(m_gsettings, m_settings->show_events, SETTINGS_SHOW_EVENTS_S);
+ TestBoolProperty(m_gsettings, m_settings->show_locations, SETTINGS_SHOW_LOCATIONS_S);
+ TestBoolProperty(m_gsettings, m_settings->show_week_numbers, SETTINGS_SHOW_WEEK_NUMBERS_S);
+ TestBoolProperty(m_gsettings, m_settings->show_year, SETTINGS_SHOW_YEAR_S);
}
TEST_F(SettingsFixture, UIntProperties)
{
- TestUIntProperty(m_settings->alarm_duration, SETTINGS_ALARM_DURATION_S);
- TestUIntProperty(m_settings->alarm_volume, SETTINGS_ALARM_VOLUME_S);
- TestUIntProperty(m_settings->snooze_duration, SETTINGS_SNOOZE_DURATION_S);
+ TestUIntProperty(m_gsettings, m_settings->alarm_duration, SETTINGS_ALARM_DURATION_S);
+ TestUIntProperty(m_gsettings, m_settings->alarm_volume, SETTINGS_ALARM_VOLUME_S);
+ TestUIntProperty(m_gsettings, m_settings->snooze_duration, SETTINGS_SNOOZE_DURATION_S);
}
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->calendar_sound, SETTINGS_CALENDAR_SOUND_S);
- TestStringProperty(m_settings->alarm_haptic, SETTINGS_ALARM_HAPTIC_S);
+ TestStringProperty(m_gsettings, m_settings->custom_time_format, SETTINGS_CUSTOM_TIME_FORMAT_S);
+ TestStringProperty(m_gsettings, m_settings->timezone_name, SETTINGS_TIMEZONE_NAME_S);
+ TestStringProperty(m_gsettings, m_settings->alarm_sound, SETTINGS_ALARM_SOUND_S);
+ TestStringProperty(m_gsettings, m_settings->calendar_sound, SETTINGS_CALENDAR_SOUND_S);
+ TestStringProperty(m_gsettings, m_settings->alarm_haptic, SETTINGS_ALARM_HAPTIC_S);
}
TEST_F(SettingsFixture, TimeFormatMode)
@@ -221,3 +230,16 @@ TEST_F(SettingsFixture, Locations)
g_strfreev(tmp);
EXPECT_EQ(bv, vtmp);
}
+
+TEST_F(SettingsFixture, MutedApps)
+{
+ if (!m_gsettings_cal_notification) {
+ return;
+ }
+
+ TestBoolProperty(m_gsettings_cal_notification, m_settings->cal_notification_enabled, SETTINGS_NOTIFY_ENABLED_KEY);
+ TestBoolProperty(m_gsettings_cal_notification, m_settings->cal_notification_sounds, SETTINGS_NOTIFY_SOUNDS_KEY);
+ TestBoolProperty(m_gsettings_cal_notification, m_settings->cal_notification_vibrations, SETTINGS_NOTIFY_VIBRATIONS_KEY);
+ TestBoolProperty(m_gsettings_cal_notification, m_settings->cal_notification_bubbles, SETTINGS_NOTIFY_BUBBLES_KEY);
+ TestBoolProperty(m_gsettings_cal_notification, m_settings->cal_notification_list, SETTINGS_NOTIFY_LIST_KEY);
+}
diff --git a/tests/test-sound.cpp b/tests/test-sound.cpp
new file mode 100644
index 0000000..f808db6
--- /dev/null
+++ b/tests/test-sound.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2014-2016 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/appointment.h>
+#include <datetime/settings.h>
+#include <datetime/snap.h>
+
+#include "notification-fixture.h"
+
+using namespace ayatana::indicator::datetime;
+
+namespace uin = ayatana::indicator::notifications;
+
+/***
+****
+***/
+
+namespace
+{
+ static constexpr char const * APP_NAME {"indicator-datetime-service"};
+}
+
+
+namespace
+{
+ gboolean quit_idle (gpointer gloop)
+ {
+ g_main_loop_quit(static_cast<GMainLoop*>(gloop));
+ return G_SOURCE_REMOVE;
+ };
+}
+
+/***
+****
+***/
+
+TEST_F(NotificationFixture, InteractiveDuration)
+{
+ static constexpr int duration_minutes = 120;
+ auto settings = std::make_shared<Settings>();
+ settings->alarm_duration.set(duration_minutes);
+ auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME);
+ auto sb = std::make_shared<ayatana::indicator::notifications::DefaultSoundBuilder>();
+ auto snap = create_snap(ne, sb, settings);
+
+ settings->cal_notification_enabled.set(true);
+ settings->cal_notification_sounds.set(true);
+ settings->cal_notification_vibrations.set(true);
+ settings->cal_notification_bubbles.set(true);
+ settings->cal_notification_list.set(true);
+
+ make_interactive();
+
+ // call the Snap Decision
+ auto func = [this](const Appointment&, const Alarm&, const Snap::Response&){g_idle_add(quit_idle, loop);};
+ (*snap)(appt, appt.alarms.front(), func);
+
+ // confirm that Notify got called once
+ guint len = 0;
+ GError * error = nullptr;
+ const auto calls = dbus_test_dbus_mock_object_get_method_calls (notify_mock,
+ notify_obj,
+ METHOD_NOTIFY,
+ &len,
+ &error);
+ g_assert_no_error(error);
+ ASSERT_EQ(1, len);
+
+ // confirm that the app_name passed to Notify was APP_NAME
+ const auto& params = calls[0].params;
+ ASSERT_EQ(8, g_variant_n_children(params));
+ const char * str = nullptr;
+ g_variant_get_child (params, 0, "&s", &str);
+ ASSERT_STREQ(APP_NAME, str);
+
+ // confirm that the icon passed to Notify was "alarm-clock"
+ g_variant_get_child (params, 2, "&s", &str);
+ ASSERT_STREQ("calendar-app", str);
+
+ // confirm that the hints passed to Notify included a timeout matching duration_minutes
+ int32_t i32;
+ bool b;
+ auto hints = g_variant_get_child_value (params, 6);
+ b = g_variant_lookup (hints, HINT_TIMEOUT, "i", &i32);
+ EXPECT_TRUE(b);
+ const auto duration = std::chrono::minutes(duration_minutes);
+ EXPECT_EQ(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(), i32);
+ g_variant_unref(hints);
+ ne.reset();
+}
+
+/***
+****
+***/
+
+/**
+ * A DefaultSoundBuilder wrapper which remembers the parameters of the last sound created.
+ */
+class TestSoundBuilder: public uin::SoundBuilder
+{
+public:
+ TestSoundBuilder() =default;
+ ~TestSoundBuilder() =default;
+
+ virtual std::shared_ptr<uin::Sound> create(const std::string& role, const std::string& uri, unsigned int volume, bool loop) override {
+ m_role = role;
+ m_uri = uri;
+ m_volume = volume;
+ m_loop = loop;
+ return m_impl.create(role, uri, volume, loop);
+ }
+
+ const std::string& role() { return m_role; }
+ const std::string& uri() { return m_uri; }
+ unsigned int volume() { return m_volume; }
+ bool loop() { return m_loop; }
+
+private:
+ std::string m_role;
+ std::string m_uri;
+ unsigned int m_volume;
+ bool m_loop;
+ uin::DefaultSoundBuilder m_impl;
+};
+
+std::string path_to_uri(const std::string& path)
+{
+ auto file = g_file_new_for_path(path.c_str());
+ auto uri_cstr = g_file_get_uri(file);
+ std::string uri = uri_cstr;
+ g_free(uri_cstr);
+ g_clear_pointer(&file, g_object_unref);
+ return uri;
+}
+
+TEST_F(NotificationFixture,DefaultSounds)
+{
+ auto settings = std::make_shared<Settings>();
+ auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME);
+ auto sb = std::make_shared<TestSoundBuilder>();
+ auto func = [this](const Appointment&, const Alarm&, const Snap::Response&){g_idle_add(quit_idle, loop);};
+
+ settings->cal_notification_enabled.set(true);
+ settings->cal_notification_sounds.set(true);
+ settings->cal_notification_vibrations.set(true);
+ settings->cal_notification_bubbles.set(true);
+ settings->cal_notification_list.set(true);
+
+ const struct {
+ Appointment appointment;
+ std::string expected_role;
+ std::string expected_uri;
+ } test_cases[] = {
+ { ualarm, "alarm", path_to_uri(ALARM_DEFAULT_SOUND) }
+ // No sound for appointments
+ // { appt, "alert", path_to_uri(CALENDAR_DEFAULT_SOUND) }
+ };
+
+ auto snap = create_snap(ne, sb, settings);
+ for(const auto& test_case : test_cases)
+ {
+ (*snap)(test_case.appointment, test_case.appointment.alarms.front(), func);
+ EXPECT_EQ(test_case.expected_uri, sb->uri());
+ EXPECT_EQ(test_case.expected_role, sb->role());
+ }
+}
diff --git a/tests/test-timezone-timedated.cpp b/tests/test-timezone-timedated.cpp
index 2fdec12..c686c39 100644
--- a/tests/test-timezone-timedated.cpp
+++ b/tests/test-timezone-timedated.cpp
@@ -1,9 +1,5 @@
-
/*
- * Copyright 2013 Canonical Ltd.
- *
- * Authors:
- * Charles Kerr <charles.kerr@canonical.com>
+ * Copyright © 2014-2016 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
@@ -16,127 +12,76 @@
*
* 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>
+ * Ted Gould <ted.gould@canonical.com>
*/
#include "timedated-fixture.h"
#include <datetime/timezone-timedated.h>
-using ayatana::indicator::datetime::TimedatedTimezone;
+using namespace ayatana::indicator::datetime;
+
+using TestTimedatedFixture = TimedatedFixture;
/***
****
***/
-#define TIMEZONE_FILE (SANDBOX"/timezone")
-
-class TimezoneFixture: public TimedateFixture
+TEST_F(TestTimedatedFixture, HelloWorld)
{
- private:
-
- typedef TimedateFixture super;
-
- protected:
-
- void SetUp() override
- {
- super::SetUp();
- }
-
- void TearDown() override
- {
- super::TearDown();
- }
-
- public:
-
- /* convenience func to set the timezone file */
- void set_file(const std::string& text)
- {
- g_debug("set_file %s %s", TIMEZONE_FILE, text.c_str());
- auto fp = fopen(TIMEZONE_FILE, "w+");
- fprintf(fp, "%s\n", text.c_str());
- fclose(fp);
- sync();
- }
-};
+}
/**
- * Test that timezone-timedated warns, but doesn't crash, if the timezone file doesn't exist
+ * Test that the tzid is right if timedated isn't available
*/
-TEST_F(TimezoneFixture, NoFile)
+TEST_F(TestTimedatedFixture, DefaultTimezone)
{
- remove(TIMEZONE_FILE);
- ASSERT_FALSE(g_file_test(TIMEZONE_FILE, G_FILE_TEST_EXISTS));
+ const std::string expected_tzid{"Etc/Utc"};
- expectLogMessage(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*No such file or directory*");
- TimedatedTimezone tz(TIMEZONE_FILE);
+ TimedatedTimezone tmp {m_bus};
+ EXPECT_TZID(expected_tzid, tmp);
}
/**
- * Test that timezone-timedated gives a default of UTC if the file doesn't exist
+ * Test that the tzid is right if timedated shows BEFORE we start
*/
-TEST_F(TimezoneFixture, DefaultValueNoFile)
+TEST_F(TestTimedatedFixture, Timedate1First)
{
- const std::string expected_timezone = "Etc/Utc";
- remove(TIMEZONE_FILE);
- ASSERT_FALSE(g_file_test(TIMEZONE_FILE, G_FILE_TEST_EXISTS));
+ const std::string expected_tzid{"America/Chicago"};
- expectLogMessage(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*No such file or directory*");
- TimedatedTimezone tz(TIMEZONE_FILE);
- ASSERT_EQ(expected_timezone, tz.timezone.get());
+ start_timedate1(expected_tzid);
+ TimedatedTimezone tmp {m_bus};
+ EXPECT_TZID(expected_tzid, tmp);
}
/**
- * Test that timezone-timedated picks up the initial value
+ * Test that the tzid is right if timedated shows AFTER we start
*/
-TEST_F(TimezoneFixture, InitialValue)
+TEST_F(TestTimedatedFixture, Timedate1Last)
{
- const std::string expected_timezone = "America/Chicago";
- set_file(expected_timezone);
- TimedatedTimezone tz(TIMEZONE_FILE);
+ const std::string expected_tzid("America/Los_Angeles");
+
+ TimedatedTimezone tmp {m_bus};
+ start_timedate1(expected_tzid);
+ EXPECT_TZID(expected_tzid, tmp);
}
/**
- * Test that changing the tz after we are running works.
+ * Test that the tzid is right if timedated's property changes
*/
-TEST_F(TimezoneFixture, ChangedValue)
+TEST_F(TestTimedatedFixture, TimezoneChange)
{
- const std::string initial_timezone = "America/Chicago";
- const std::string changed_timezone = "America/New_York";
-
- set_file(initial_timezone);
-
- TimedatedTimezone tz(TIMEZONE_FILE);
- ASSERT_EQ(initial_timezone, tz.timezone.get());
-
- bool changed = false;
- tz.timezone.changed().connect(
- [&changed, this](const std::string& s){
- g_message("timezone changed to %s", s.c_str());
- changed = true;
- g_main_loop_quit(loop);
- });
+ const std::vector<std::string> expected_tzids{"America/Los_Angeles", "America/Chicago", "Etc/Utc"};
- g_idle_add([](gpointer gself){
- static_cast<TimedateFixture*>(gself)->set_timezone("America/New_York");
- return G_SOURCE_REMOVE;
- }, this);
+ TimedatedTimezone tmp {m_bus};
+ start_timedate1("America/New_York");
- g_main_loop_run(loop);
-
- ASSERT_TRUE(changed);
- ASSERT_EQ(changed_timezone, tz.timezone.get());
-}
-
-/**
- * Test that timezone-timedated picks up the initial value
- */
-TEST_F(TimezoneFixture, IgnoreComments)
-{
- const std::string comment = "# Created by cloud-init v. 0.7.5 on Thu, 24 Apr 2014 14:03:29 +0000";
- const std::string expected_timezone = "Europe/Berlin";
- set_file(comment + "\n" + expected_timezone);
- TimedatedTimezone tz(TIMEZONE_FILE);
- ASSERT_EQ(expected_timezone, tz.timezone.get());
+ for(const auto& expected_tzid : expected_tzids)
+ {
+ set_timedate1_timezone(expected_tzid);
+ EXPECT_TZID(expected_tzid, tmp);
+ }
}
diff --git a/tests/timedated-fixture.h b/tests/timedated-fixture.h
index 00269e0..3aff986 100644
--- a/tests/timedated-fixture.h
+++ b/tests/timedated-fixture.h
@@ -17,285 +17,132 @@
* Charles Kerr <charles.kerr@canonical.com>
*/
-#ifndef INDICATOR_DATETIME_TESTS_TIMEDATED_FIXTURE_H
-#define INDICATOR_DATETIME_TESTS_TIMEDATED_FIXTURE_H
+#pragma once
#include <datetime/actions-live.h>
-#include "state-mock.h"
#include "glib-fixture.h"
-using namespace ayatana::indicator::datetime;
-
-class MockLiveActions: public LiveActions
-{
-public:
- std::string last_cmd;
- std::string last_url;
- explicit MockLiveActions(const std::shared_ptr<State>& state_in): LiveActions(state_in) {}
- ~MockLiveActions() {}
-
-protected:
- void dispatch_url(const std::string& url) override { last_url = url; }
- void execute_command(const std::string& cmd) override { last_cmd = cmd; }
-};
+#include <datetime/dbus-shared.h>
+#include <datetime/timezone.h>
/***
****
***/
-using namespace ayatana::indicator::datetime;
-
-class TimedateFixture: public GlibFixture
+struct TimedatedFixture: public GlibFixture
{
private:
- typedef GlibFixture super;
-
- static GVariant * timedate1_get_properties (GDBusConnection * /*connection*/ ,
- const gchar * /*sender*/,
- const gchar * /*object_path*/,
- const gchar * /*interface_name*/,
- const gchar *property_name,
- GError ** /*error*/,
- gpointer gself)
+ using super = GlibFixture;
- {
- auto self = static_cast<TimedateFixture*>(gself);
- g_debug("get_properties called");
- if (g_strcmp0(property_name, "Timezone") == 0)
- {
- g_debug("timezone requested, giving '%s'",
- self->attempted_tzid.c_str());
- return g_variant_new_string(self->attempted_tzid.c_str());
- }
- return nullptr;
- }
+protected:
+ GDBusConnection* m_bus {};
+ GTestDBus* m_test_bus {};
- static void on_bus_acquired(GDBusConnection* conn,
- const gchar* name,
- gpointer gself)
+ virtual void SetUp() override
{
- auto self = static_cast<TimedateFixture*>(gself);
- g_debug("bus acquired: %s, connection is %p", name, conn);
-
- /* Set up a fake timedated which handles setting and getting the
- ** timezone
- */
- static const GDBusInterfaceVTable vtable = {
- timedate1_handle_method_call,
- timedate1_get_properties, /* GetProperty */
- nullptr, /* SetProperty */
- };
+ super::SetUp();
- self->connection = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(conn)));
+ // use a fake bus
+ m_test_bus = g_test_dbus_new(G_TEST_DBUS_NONE);
+ g_test_dbus_up(m_test_bus);
+ const char * address = g_test_dbus_get_bus_address(m_test_bus);
+ g_setenv("DBUS_SYSTEM_BUS_ADDRESS", address, true);
+ g_setenv("DBUS_SESSION_BUS_ADDRESS", address, true);
+ g_debug("test_dbus's address is %s", address);
- GError* error = nullptr;
- self->object_register_id = g_dbus_connection_register_object(
- conn,
- "/org/freedesktop/timedate1",
- self->node_info->interfaces[0],
- &vtable,
- self,
- nullptr,
- &error);
- g_assert_no_error(error);
+ // get the bus
+ m_bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
+ g_dbus_connection_set_exit_on_close(m_bus, FALSE);
+ g_object_add_weak_pointer(G_OBJECT(m_bus), (gpointer*)&m_bus);
}
- static void on_name_acquired(GDBusConnection* conn,
- const gchar* name,
- gpointer gself)
+ virtual void TearDown() override
{
- g_debug("on_name_acquired");
- auto self = static_cast<TimedateFixture*>(gself);
- self->name_acquired = true;
- self->proxy = g_dbus_proxy_new_sync(conn,
- G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
- nullptr,
- name,
- "/org/freedesktop/timedate1",
- "org.freedesktop.timedate1",
- nullptr,
- nullptr);
- g_main_loop_quit(self->loop);
- }
+ g_clear_object(&m_bus);
+ g_clear_object(&m_test_bus);
- static void on_name_lost(GDBusConnection* /*conn*/,
- const gchar* /*name*/,
- gpointer gself)
- {
- g_debug("on_name_lost");
- auto self = static_cast<TimedateFixture*>(gself);
- self->name_acquired = false;
+ super::TearDown();
}
- static void on_bus_closed(GObject* /*object*/,
- GAsyncResult* res,
- gpointer gself)
+ void start_timedate1(const std::string& tzid)
{
- g_debug("on_bus_closed");
- auto self = static_cast<TimedateFixture*>(gself);
- GError* err = nullptr;
- g_dbus_connection_close_finish(self->connection, res, &err);
- g_assert_no_error(err);
- g_main_loop_quit(self->loop);
+ // start dbusmock with the timedated template
+ auto json_parameters = g_strdup_printf("{\"Timezone\": \"%s\"}", tzid.c_str());
+ const gchar* child_argv[] = {
+ "python3", "-m", "dbusmock",
+ "--template", "timedated",
+ "--parameters", json_parameters,
+ nullptr
+ };
+ GError* error = nullptr;
+ g_spawn_async(nullptr, (gchar**)child_argv, nullptr, G_SPAWN_SEARCH_PATH, nullptr, nullptr, nullptr, &error);
+ g_assert_no_error(error);
+ g_free(json_parameters);
+
+ // wait for it to appear on the bus
+ wait_for_name_owned(m_bus, Bus::Timedate1::BUSNAME);
}
- static void
- timedate1_handle_method_call(GDBusConnection * connection,
- const gchar * /*sender*/,
- const gchar * object_path,
- const gchar * interface_name,
- const gchar * method_name,
- GVariant * parameters,
- GDBusMethodInvocation * invocation,
- gpointer gself)
+ bool wait_for_tzid(const std::string& tzid, ayatana::indicator::datetime::Timezone& tz)
{
- g_assert(!g_strcmp0(method_name, "SetTimezone"));
- g_assert(g_variant_is_of_type(parameters, G_VARIANT_TYPE_TUPLE));
- g_assert(2 == g_variant_n_children(parameters));
-
- auto child = g_variant_get_child_value(parameters, 0);
- g_assert(g_variant_is_of_type(child, G_VARIANT_TYPE_STRING));
- auto self = static_cast<TimedateFixture*>(gself);
- self->attempted_tzid = g_variant_get_string(child, nullptr);
- g_debug("set tz (dbus side): '%s'", self->attempted_tzid.c_str());
- g_dbus_method_invocation_return_value(invocation, nullptr);
-
- /* Send PropertiesChanged */
- GError * local_error = nullptr;
- auto builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
- g_variant_builder_add (builder,
- "{sv}",
- "Timezone",
- g_variant_new_string(
- self->attempted_tzid.c_str()));
- g_dbus_connection_emit_signal (connection,
- NULL,
- object_path,
- "org.freedesktop.DBus.Properties",
- "PropertiesChanged",
- g_variant_new ("(sa{sv}as)",
- interface_name,
- builder,
- NULL),
- &local_error);
- g_assert_no_error (local_error);
- g_variant_unref(child);
+ return wait_for([&tzid, &tz](){return tzid == tz.timezone.get();});
}
-protected:
-
- std::shared_ptr<MockState> m_mock_state;
- std::shared_ptr<State> m_state;
- std::shared_ptr<MockLiveActions> m_live_actions;
- std::shared_ptr<Actions> m_actions;
-
- bool name_acquired;
- std::string attempted_tzid;
-
- GTestDBus* bus;
- guint own_name;
- GDBusConnection* connection;
- GDBusNodeInfo* node_info;
- int object_register_id;
- GDBusProxy *proxy;
-
- void SetUp()
+ void set_timedate1_timezone(const std::string& tzid)
{
- super::SetUp();
- g_debug("SetUp");
-
- name_acquired = false;
- attempted_tzid.clear();
- connection = nullptr;
- node_info = nullptr;
- object_register_id = 0;
- own_name = 0;
- proxy = nullptr;
-
- // bring up the test bus
- bus = g_test_dbus_new(G_TEST_DBUS_NONE);
- g_test_dbus_up(bus);
- const auto address = g_test_dbus_get_bus_address(bus);
- g_setenv("DBUS_SYSTEM_BUS_ADDRESS", address, true);
- g_setenv("DBUS_SESSION_BUS_ADDRESS", address, true);
- g_debug("test_dbus's address is %s", address);
-
- // parse the org.freedesktop.timedate1 interface
- const gchar introspection_xml[] =
- "<node>"
- " <interface name='org.freedesktop.timedate1'>"
- " <property name='Timezone' type='s' access='read' />"
- " <method name='SetTimezone'>"
- " <arg name='timezone' type='s' direction='in'/>"
- " <arg name='user_interaction' type='b' direction='in'/>"
- " </method>"
- " </interface>"
- "</node>";
- node_info = g_dbus_node_info_new_for_xml(introspection_xml, nullptr);
- ASSERT_TRUE(node_info != nullptr);
- ASSERT_TRUE(node_info->interfaces != nullptr);
- ASSERT_TRUE(node_info->interfaces[0] != nullptr);
- ASSERT_TRUE(node_info->interfaces[1] == nullptr);
- ASSERT_STREQ("org.freedesktop.timedate1", node_info->interfaces[0]->name);
-
- // own the bus
- own_name = g_bus_own_name(G_BUS_TYPE_SYSTEM,
- "org.freedesktop.timedate1",
- G_BUS_NAME_OWNER_FLAGS_NONE,
- on_bus_acquired, on_name_acquired, on_name_lost,
- this, nullptr);
- ASSERT_TRUE(object_register_id == 0);
- ASSERT_FALSE(name_acquired);
- ASSERT_TRUE(connection == nullptr);
- g_main_loop_run(loop);
- ASSERT_TRUE(object_register_id != 0);
- ASSERT_TRUE(name_acquired);
- ASSERT_TRUE(G_IS_DBUS_CONNECTION(connection));
+ GError* error {};
+ auto v = g_dbus_connection_call_sync(
+ m_bus,
+ Bus::Timedate1::BUSNAME,
+ Bus::Timedate1::ADDR,
+ Bus::Timedate1::IFACE,
+ Bus::Timedate1::Methods::SET_TIMEZONE,
+ g_variant_new("(sb)", tzid.c_str(), FALSE),
+ nullptr,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ nullptr,
+ &error);
+ g_assert_no_error(error);
- // create the State and Actions
- m_mock_state.reset(new MockState);
- m_mock_state->settings.reset(new Settings);
- m_state = std::dynamic_pointer_cast<State>(m_mock_state);
- m_live_actions.reset(new MockLiveActions(m_state));
- m_actions = std::dynamic_pointer_cast<Actions>(m_live_actions);
+ g_clear_pointer(&v, g_variant_unref);
}
- void TearDown()
+ std::string get_timedate1_timezone()
{
- g_debug("TearDown");
- m_actions.reset();
- m_live_actions.reset();
- m_state.reset();
- m_mock_state.reset();
- g_dbus_connection_unregister_object(connection, object_register_id);
- g_object_unref(proxy);
- g_dbus_node_info_unref(node_info);
- g_bus_unown_name(own_name);
- g_dbus_connection_close(connection, nullptr, on_bus_closed, this);
- g_main_loop_run(loop);
- g_clear_object(&connection);
- g_test_dbus_down(bus);
- g_clear_object(&bus);
+ GError* error {};
+ auto v = g_dbus_connection_call_sync(
+ m_bus,
+ Bus::Timedate1::BUSNAME,
+ Bus::Timedate1::ADDR,
+ Bus::Properties::IFACE,
+ Bus::Properties::Methods::GET,
+ g_variant_new("(ss)", Bus::Timedate1::IFACE, Bus::Timedate1::Properties::TIMEZONE),
+ G_VARIANT_TYPE("(v)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ nullptr,
+ &error);
+ g_assert_no_error(error);
- super::TearDown();
- }
-public:
- void set_timezone(std::string tz)
- {
- g_debug("set_timezone: '%s'", tz.c_str());
- g_dbus_proxy_call_sync(proxy,
- "SetTimezone",
- g_variant_new("(sb)",
- tz.c_str(),
- FALSE),
- G_DBUS_CALL_FLAGS_NONE,
- 500,
- nullptr,
- nullptr);
+ GVariant* tzv {};
+ g_variant_get(v, "(v)", &tzv);
+ std::string tzid;
+ const char* tz = g_variant_get_string(tzv, nullptr);
+ if (tz != nullptr)
+ tzid = tz;
+
+ g_clear_pointer(&tzv, g_variant_unref);
+ g_clear_pointer(&v, g_variant_unref);
+ return tzid;
}
};
-#endif
+#define EXPECT_TZID(expected_tzid, tmp) \
+ EXPECT_TRUE(wait_for_tzid(expected_tzid, tmp)) \
+ << "expected " << expected_tzid \
+ << " got " << tmp.timezone.get();
+
diff --git a/trim-lcov.py b/trim-lcov.py
deleted file mode 100755
index 2cd8dee..0000000
--- a/trim-lcov.py
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# This script removes branch and/or line coverage data for lines that
-# contain a particular substring.
-#
-# In the interest of "fairness" it removes all branch or coverage data
-# when a match is found -- not just negative data. It is therefore
-# likely that running this script will actually reduce the total number
-# of lines and branches that are marked as covered (in absolute terms).
-#
-# This script intentionally avoids checking for errors. Any exceptions
-# will trigger make to fail.
-#
-# Author: Ryan Lortie <desrt@desrt.ca>
-
-import sys
-
-line_suppress = ['g_assert_not_reached']
-branch_suppress = ['g_assert', 'g_return_if_fail', 'g_clear_object', 'g_clear_pointer', 'g_return_val_if_fail', 'G_DEFINE_TYPE']
-
-def check_suppress(suppressions, source, data):
- line, _, rest = data.partition(',')
- line = int(line) - 1
-
- assert line < len(source)
-
- for suppression in suppressions:
- if suppression in source[line]:
- return True
-
- return False
-
-source = []
-for line in sys.stdin:
- line = line[:-1]
-
- keyword, _, rest = line.partition(':')
-
- # Source file
- if keyword == 'SF':
-
- with open(rest, 'r') as pFile:
-
- source = pFile.readlines()
-
- # Branch coverage data
- elif keyword == 'BRDA':
- if check_suppress(branch_suppress, source, rest):
- continue
-
- # Line coverage data
- elif keyword == 'DA':
- if check_suppress(line_suppress, source, rest):
- continue
-
- print(line)