diff options
-rw-r--r-- | CMakeLists.txt | 72 | ||||
-rw-r--r-- | cmake/FindGMock.cmake | 10 | ||||
-rw-r--r-- | cmake/GCov.cmake | 51 | ||||
-rw-r--r-- | cmake/GdbusCodegen.cmake | 36 | ||||
-rw-r--r-- | cmake/Translations.cmake | 37 | ||||
-rw-r--r-- | data/CMakeLists.txt | 74 | ||||
-rw-r--r-- | data/com.canonical.indicator.rotation_lock | 11 | ||||
-rw-r--r-- | data/indicator-display.conf.in | 9 | ||||
-rw-r--r-- | data/indicator-display.desktop.in | 9 | ||||
-rw-r--r-- | data/indicator-display.upstart.desktop.in | 9 | ||||
-rw-r--r-- | debian/changelog | 6 | ||||
-rw-r--r-- | debian/compat | 1 | ||||
-rw-r--r-- | debian/control | 34 | ||||
-rw-r--r-- | debian/copyright | 21 | ||||
-rwxr-xr-x | debian/rules | 10 | ||||
-rw-r--r-- | po/CMakeLists.txt | 3 | ||||
-rw-r--r-- | po/POTFILES.in | 1 | ||||
-rw-r--r-- | src/CMakeLists.txt | 29 | ||||
-rw-r--r-- | src/exporter.cpp | 217 | ||||
-rw-r--r-- | src/exporter.h | 40 | ||||
-rw-r--r-- | src/indicator.h | 88 | ||||
-rw-r--r-- | src/main.cpp | 62 | ||||
-rw-r--r-- | src/rotation-lock.cpp | 197 | ||||
-rw-r--r-- | src/rotation-lock.h | 42 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 35 | ||||
-rw-r--r-- | tests/glib-fixture.h | 143 | ||||
-rw-r--r-- | tests/gtestdbus-fixture.h | 102 | ||||
-rw-r--r-- | tests/manual | 16 | ||||
-rw-r--r-- | tests/test-rotation-lock.cpp | 61 |
29 files changed, 1426 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8a35d95 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,72 @@ +project (indicator-display) +cmake_minimum_required (VERSION 2.8.9) + +list (APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) + +set (PROJECT_VERSION "14.10.0") +set (PACKAGE ${CMAKE_PROJECT_NAME}) +set (GETTEXT_PACKAGE indicator-display) +add_definitions (-DGETTEXT_PACKAGE="${GETTEXT_PACKAGE}" + -DGNOMELOCALEDIR="${CMAKE_INSTALL_FULL_LOCALEDIR}") + +set (SERVICE_LIB ${PACKAGE}) +set (SERVICE_EXEC "${PACKAGE}-service") + +option (enable_tests "Build the package's automatic tests." ON) +option (enable_lcov "Generate lcov code coverage reports." ON) + +## +## GNU standard paths +## +include (GNUInstallDirs) +if (EXISTS "/etc/debian_version") # Workaround for libexecdir on debian + set (CMAKE_INSTALL_LIBEXECDIR "${CMAKE_INSTALL_LIBDIR}") + set (CMAKE_INSTALL_FULL_LIBEXECDIR "${CMAKE_INSTALL_FULL_LIBDIR}") +endif () +set (CMAKE_INSTALL_PKGLIBEXECDIR "${CMAKE_INSTALL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}") +set (CMAKE_INSTALL_FULL_PKGLIBEXECDIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}") + +## +## Check for prerequisites +## + +find_package (PkgConfig REQUIRED) + +include (FindPkgConfig) +pkg_check_modules (SERVICE_DEPS REQUIRED + gio-unix-2.0>=2.36 + glib-2.0>=2.36) +include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS}) + +## +## +## + +set (CMAKE_INCLUDE_CURRENT_DIR OFF) +include_directories (${CMAKE_CURRENT_SOURCE_DIR}) + +# set the compiler warnings +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set (CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Weverything -Wno-c++98-compat") +else() + set (CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Wall -Wextra -Wpedantic") +endif() +set (CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Wno-missing-field-initializers") # GActionEntry + +# testing & coverage +if (${enable_tests}) + set (GTEST_SOURCE_DIR /usr/src/gtest/src) + set (GTEST_INCLUDE_DIR ${GTEST_SOURCE_DIR}/..) + set (GTEST_LIBS -lpthread) + enable_testing () + if (${enable_lcov}) + include(GCov) + endif () +endif () + +add_subdirectory (src) +add_subdirectory (data) +add_subdirectory (po) +if (${enable_tests}) + add_subdirectory (tests) +endif () diff --git a/cmake/FindGMock.cmake b/cmake/FindGMock.cmake new file mode 100644 index 0000000..74a1c15 --- /dev/null +++ b/cmake/FindGMock.cmake @@ -0,0 +1,10 @@ +# Build with system gmock and embedded gtest +set (GMOCK_INCLUDE_DIRS "/usr/include/gmock/include" CACHE PATH "gmock source include directory") +set (GMOCK_SOURCE_DIR "/usr/src/gmock" CACHE PATH "gmock source directory") +set (GTEST_INCLUDE_DIRS "${GMOCK_SOURCE_DIR}/gtest/include" CACHE PATH "gtest source include directory") + +add_subdirectory(${GMOCK_SOURCE_DIR} "${CMAKE_CURRENT_BINARY_DIR}/gmock") + +set(GTEST_LIBRARIES gtest) +set(GTEST_MAIN_LIBRARIES gtest_main) +set(GMOCK_LIBRARIES gmock gmock_main) diff --git a/cmake/GCov.cmake b/cmake/GCov.cmake new file mode 100644 index 0000000..81c0c40 --- /dev/null +++ b/cmake/GCov.cmake @@ -0,0 +1,51 @@ +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/GdbusCodegen.cmake b/cmake/GdbusCodegen.cmake new file mode 100644 index 0000000..ddb2995 --- /dev/null +++ b/cmake/GdbusCodegen.cmake @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.6) +if(POLICY CMP0011) + cmake_policy(SET CMP0011 NEW) +endif(POLICY CMP0011) + +find_program(GDBUS_CODEGEN NAMES gdbus-codegen DOC "gdbus-codegen executable") +if(NOT GDBUS_CODEGEN) + message(FATAL_ERROR "Excutable gdbus-codegen not found") +endif() + +macro(add_gdbus_codegen outfiles name prefix service_xml) + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${name}.h" "${CMAKE_CURRENT_BINARY_DIR}/${name}.c" + COMMAND "${GDBUS_CODEGEN}" + --interface-prefix "${prefix}" + --generate-c-code "${name}" + "${service_xml}" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${ARGN} "${service_xml}" + ) + list(APPEND ${outfiles} "${CMAKE_CURRENT_BINARY_DIR}/${name}.c") +endmacro(add_gdbus_codegen) + +macro(add_gdbus_codegen_with_namespace outfiles name prefix namespace service_xml) + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${name}.h" "${CMAKE_CURRENT_BINARY_DIR}/${name}.c" + COMMAND "${GDBUS_CODEGEN}" + --interface-prefix "${prefix}" + --generate-c-code "${name}" + --c-namespace "${namespace}" + "${service_xml}" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${ARGN} "${service_xml}" + ) + list(APPEND ${outfiles} "${CMAKE_CURRENT_BINARY_DIR}/${name}.c") +endmacro(add_gdbus_codegen_with_namespace) diff --git a/cmake/Translations.cmake b/cmake/Translations.cmake new file mode 100644 index 0000000..b51c39d --- /dev/null +++ b/cmake/Translations.cmake @@ -0,0 +1,37 @@ +# 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 new file mode 100644 index 0000000..7edaa44 --- /dev/null +++ b/data/CMakeLists.txt @@ -0,0 +1,74 @@ +## +## Upstart Job File +## + +# where to install +set (UPSTART_JOBS_DIR "${CMAKE_INSTALL_FULL_DATADIR}/upstart/sessions") +message (STATUS "${UPSTART_JOBS_DIR} is the Upstart Jobs File install dir") + +set (UPSTART_JOB_NAME "${CMAKE_PROJECT_NAME}.conf") +set (UPSTART_JOB_FILE "${CMAKE_CURRENT_BINARY_DIR}/${UPSTART_JOB_NAME}") +set (UPSTART_JOB_FILE_IN "${CMAKE_CURRENT_SOURCE_DIR}/${UPSTART_JOB_NAME}.in") + +# build it +set (pkglibexecdir "${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}") +configure_file ("${UPSTART_JOB_FILE_IN}" "${UPSTART_JOB_FILE}") + +# install it +install (FILES "${UPSTART_JOB_FILE}" + DESTINATION "${UPSTART_JOBS_DIR}") + +## +## XDG Autostart File +## + +# where to install +set (XDG_AUTOSTART_DIR "/etc/xdg/autostart") +message (STATUS "${XDG_AUTOSTART_DIR} is the XDG Autostart install dir") + +set (XDG_AUTOSTART_NAME "${CMAKE_PROJECT_NAME}.desktop") +set (XDG_AUTOSTART_FILE "${CMAKE_CURRENT_BINARY_DIR}/${XDG_AUTOSTART_NAME}") +set (XDG_AUTOSTART_FILE_IN "${CMAKE_CURRENT_SOURCE_DIR}/${XDG_AUTOSTART_NAME}.in") + +# build it +set (pkglibexecdir "${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}") +configure_file ("${XDG_AUTOSTART_FILE_IN}" "${XDG_AUTOSTART_FILE}") + +# install it +install (FILES "${XDG_AUTOSTART_FILE}" + DESTINATION "${XDG_AUTOSTART_DIR}") + +## +## Upstart XDG Autostart Override +## + +# where to install +set (UPSTART_XDG_AUTOSTART_DIR "${CMAKE_INSTALL_FULL_DATAROOTDIR}/upstart/xdg/autostart") +message (STATUS "${UPSTART_XDG_AUTOSTART_DIR} is the Upstart XDG autostart override dir") + +set (UPSTART_XDG_AUTOSTART_NAME "${CMAKE_PROJECT_NAME}.upstart.desktop") +set (UPSTART_XDG_AUTOSTART_FILE "${CMAKE_CURRENT_BINARY_DIR}/${UPSTART_XDG_AUTOSTART_NAME}") +set (UPSTART_XDG_AUTOSTART_FILE_IN "${CMAKE_CURRENT_SOURCE_DIR}/${UPSTART_XDG_AUTOSTART_NAME}.in") + +# build it +set (pkglibexecdir "${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}") +configure_file ("${UPSTART_XDG_AUTOSTART_FILE_IN}" "${UPSTART_XDG_AUTOSTART_FILE}") + +# install it +install (FILES "${UPSTART_XDG_AUTOSTART_FILE}" + DESTINATION "${UPSTART_XDG_AUTOSTART_DIR}" + RENAME "${XDG_AUTOSTART_NAME}") + +## +## Unity Indicator File +## + +# where to install +set (UNITY_INDICATOR_DIR "${CMAKE_INSTALL_FULL_DATAROOTDIR}/unity/indicators") +message (STATUS "${UNITY_INDICATOR_DIR} is the Unity Indicator install dir") + +set (UNITY_INDICATOR_NAME "com.canonical.indicator.rotation_lock") +set (UNITY_INDICATOR_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${UNITY_INDICATOR_NAME}") + +install (FILES "${UNITY_INDICATOR_FILE}" + DESTINATION "${UNITY_INDICATOR_DIR}") diff --git a/data/com.canonical.indicator.rotation_lock b/data/com.canonical.indicator.rotation_lock new file mode 100644 index 0000000..7740db7 --- /dev/null +++ b/data/com.canonical.indicator.rotation_lock @@ -0,0 +1,11 @@ +[Indicator Service] +Name=indicator-rotation-lock +ObjectPath=/com/canonical/indicator/rotation_lock +Position=90 + +[phone] +ObjectPath=/com/canonical/indicator/rotation_lock/phone + +[phone_greeter] +ObjectPath=/com/canonical/indicator/rotation_lock/phone + diff --git a/data/indicator-display.conf.in b/data/indicator-display.conf.in new file mode 100644 index 0000000..2fbabc4 --- /dev/null +++ b/data/indicator-display.conf.in @@ -0,0 +1,9 @@ +description "Indicator Display Backend" + +start on indicator-services-start +stop on desktop-end or indicator-services-end + +respawn +respawn limit 2 10 + +exec @pkglibexecdir@/indicator-display-service diff --git a/data/indicator-display.desktop.in b/data/indicator-display.desktop.in new file mode 100644 index 0000000..b0017b1 --- /dev/null +++ b/data/indicator-display.desktop.in @@ -0,0 +1,9 @@ +[Desktop Entry] +Type=Application +Name=Indicator Display +Exec=@pkglibexecdir@/indicator-display-service +OnlyShowIn=Unity;GNOME; +NoDisplay=true +StartupNotify=false +Terminal=false +AutostartCondition=GNOME3 unless-session gnome diff --git a/data/indicator-display.upstart.desktop.in b/data/indicator-display.upstart.desktop.in new file mode 100644 index 0000000..7e6cfe6 --- /dev/null +++ b/data/indicator-display.upstart.desktop.in @@ -0,0 +1,9 @@ +[Desktop Entry] +Type=Application +Name=Indicator Display +Exec=@pkglibexecdir@/indicator-display-service +OnlyShowIn=Unity; +NoDisplay=true +StartupNotify=false +Terminal=false +Hidden=true diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..58b5072 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,6 @@ +indicator-display (0.1-0ubuntu1) utopic; urgency=medium + + * Initial release. + + -- Charles Kerr <charles.kerr@canonical.com> Wed, 20 Aug 2014 15:29:27 -0500 + diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..3c274ac --- /dev/null +++ b/debian/control @@ -0,0 +1,34 @@ +Source: indicator-display +Section: misc +Priority: optional +Maintainer: Charles Kerr <charles.kerr@canonical.com> +Build-Depends: cmake, + dbus, +# make g++ version explicit for ABI safety <https://wiki.ubuntu.com/cpp-11> + g++-4.9, + libglib2.0-dev (>= 2.36), + libproperties-cpp-dev, +# for coverage reports + lcov, +# for tests + cppcheck, + libgtest-dev, + google-mock (>= 1.6.0+svn437), + gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140813), +# for packaging + debhelper (>= 9), + dh-translations, +Standards-Version: 3.9.5 +Homepage: http://launchpad.net/indicator-display/ +# If you aren't a member of ~indicator-applet-developers but need to upload +# packaging changes, just go ahead. ~indicator-applet-developers will notice and +# sync up the code again. +Vcs-Bzr: https://code.launchpad.net/~indicator-applet-developers/indicator-display/trunk.14.10 + +Package: indicator-display +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, + gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140813), +Description: Collection of small indicators + Indicators too small to merit separate codebases, such as Rotation Lock diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..7809d7c --- /dev/null +++ b/debian/copyright @@ -0,0 +1,21 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: indicator-display +Source: http://launchpad.net/indicator-display + +Files: * +Copyright: 2014, Canonical Ltd. +License: GPL-3 + This package is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 3 of the License. + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY 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/> + . + On Debian systems, the complete text of the GNU General + Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..e39944a --- /dev/null +++ b/debian/rules @@ -0,0 +1,10 @@ +#!/usr/bin/make -f + +# Explicitly selecting a G{CC,++}-version here to avoid ABI breaks +# introduced by toolchain updates. <https://wiki.ubuntu.com/cpp-11> +export CC=$(DEB_HOST_GNU_TYPE)-gcc-4.9 +export CXX=$(DEB_HOST_GNU_TYPE)-g++-4.9 + +%: + dh $@ --with translations + diff --git a/po/CMakeLists.txt b/po/CMakeLists.txt new file mode 100644 index 0000000..786573b --- /dev/null +++ b/po/CMakeLists.txt @@ -0,0 +1,3 @@ +include (Translations) +add_translations_directory("${GETTEXT_PACKAGE}") +add_translations_catalog("${GETTEXT_PACKAGE}" ../src/) diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 0000000..f8bd80a --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1 @@ +src/rotation-lock.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..982aa49 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,29 @@ +set (SERVICE_LIB "indicatordisplayservice") +set (SERVICE_EXEC "indicator-display-service") + +add_definitions (-DG_LOG_DOMAIN="${CMAKE_PROJECT_NAME}") + +# handwritten source code... +set (SERVICE_LIB_HANDWRITTEN_SOURCES + exporter.cpp + rotation-lock.cpp) + +add_library (${SERVICE_LIB} STATIC + ${SERVICE_LIB_HANDWRITTEN_SOURCES}) + +# add the bin dir to the include path so that +# the compiler can find the generated header files +include_directories (${CMAKE_CURRENT_BINARY_DIR}) + +link_directories (${SERVICE_DEPS_LIBRARY_DIRS}) + +set (SERVICE_EXEC_HANDWRITTEN_SOURCES main.cpp) +add_executable (${SERVICE_EXEC} ${SERVICE_EXEC_HANDWRITTEN_SOURCES}) +target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} ${GCOV_LIBS}) +install (TARGETS ${SERVICE_EXEC} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}) + +# add warnings/coverage info on handwritten files +# but not the generated ones... +set_property (SOURCE ${SERVICE_LIB_HANDWRITTEN_SOURCES} ${SERVICE_EXEC_HANDWRITTEN_SOURCES} + APPEND_STRING PROPERTY COMPILE_FLAGS " -std=c++11 -g ${CXX_WARNING_ARGS} ${GCOV_FLAGS}") + diff --git a/src/exporter.cpp b/src/exporter.cpp new file mode 100644 index 0000000..f45d9d6 --- /dev/null +++ b/src/exporter.cpp @@ -0,0 +1,217 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <src/exporter.h> + +class Exporter::Impl +{ +public: + + Impl(const std::shared_ptr<Indicator>& indicator): + m_indicator(indicator) + { + auto bus_name = g_strdup_printf("com.canonical.indicator.%s", indicator->name()); + m_own_id = g_bus_own_name(G_BUS_TYPE_SESSION, + bus_name, + G_BUS_NAME_OWNER_FLAGS_NONE, + on_bus_acquired, + nullptr, + on_name_lost, + this, + nullptr); + + g_free(bus_name); + } + + ~Impl() + { + if (m_bus != nullptr) + { + for(const auto& id : m_exported_menu_ids) + g_dbus_connection_unexport_menu_model(m_bus, id); + + if (m_exported_actions_id) + g_dbus_connection_unexport_action_group(m_bus, m_exported_actions_id); + } + + if (m_own_id) + g_bus_unown_name(m_own_id); + + g_clear_object(&m_bus); + } + + core::Signal<std::string>& name_lost() + { + return m_name_lost; + } + +private: + + void emit_name_lost(const char* bus_name) + { + m_name_lost(bus_name); + } + + static void on_bus_acquired(GDBusConnection * connection, + const gchar * name, + gpointer gself) + { + static_cast<Impl*>(gself)->on_bus_acquired(connection, name); + } + + void on_bus_acquired(GDBusConnection* connection, const gchar* /*name*/) + { + m_bus = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(connection))); + + export_actions(m_indicator); + + for (auto& profile : m_indicator->profiles()) + export_profile(m_indicator, profile); + } + + void export_actions(const std::shared_ptr<Indicator>& indicator) + { + GError* error; + char* object_path; + guint id; + + // export the actions + + error = nullptr; + object_path = g_strdup_printf("/com/canonical/indicator/%s", indicator->name()); + id = g_dbus_connection_export_action_group(m_bus, + object_path, + G_ACTION_GROUP(indicator->action_group()), + &error); + if (id) + m_exported_actions_id = id; + else + g_warning("Can't export action group to '%s': %s", object_path, error->message); + + g_clear_error(&error); + g_free(object_path); + } + + static GVariant* create_header_state(const Header& h) + { + GVariantBuilder b; + g_variant_builder_init(&b, G_VARIANT_TYPE_VARDICT); + + g_variant_builder_add(&b, "{sv}", "visible", g_variant_new_boolean(h.is_visible)); + + if (!h.title.empty()) + g_variant_builder_add(&b, "{sv}", "title", g_variant_new_string(h.title.c_str())); + + if (!h.label.empty()) + g_variant_builder_add(&b, "{sv}", "label", g_variant_new_string(h.label.c_str())); + + if (!h.title.empty() || !h.label.empty()) + g_variant_builder_add(&b, "{sv}", "accessible-desc", g_variant_new_string(!h.label.empty() ? h.label.c_str() : h.title.c_str())); + + if (h.icon) + g_variant_builder_add(&b, "{sv}", "icon", g_icon_serialize(h.icon.get())); + + return g_variant_builder_end (&b); + } + + void export_profile(const std::shared_ptr<Indicator>& indicator, + const std::shared_ptr<Profile>& profile) + { + // build the header action + auto action_group = indicator->action_group(); + std::string action_name = profile->name() + "-header"; + auto a = g_simple_action_new_stateful(action_name.c_str(), nullptr, create_header_state(profile->header())); + g_action_map_add_action(G_ACTION_MAP(action_group), G_ACTION(a)); + profile->header().changed().connect([action_group,action_name](const Header& header){ + auto state = create_header_state(header); + auto tmp = g_variant_print(state, true); + g_message("header changed; updating action state to '%s'", tmp); + g_action_group_change_action_state(G_ACTION_GROUP(action_group), + action_name.c_str(), + create_header_state(header)); + g_free(tmp); + }); + + // build the header menu + auto detailed_action = g_strdup_printf("indicator.%s", action_name.c_str()); + GMenuItem* header = g_menu_item_new(nullptr, detailed_action); + g_menu_item_set_attribute(header, "x-canonical-type", "s", "com.canonical.indicator.root"); + g_menu_item_set_submenu(header, profile->menu_model().get()); + g_free(detailed_action); + + // build the menu + auto menu = g_menu_new(); + g_menu_append_item(menu, header); + g_object_unref(header); + + // export the menu + auto object_path = g_strdup_printf("/com/canonical/indicator/%s/%s", + indicator->name(), + profile->name().c_str()); + GError* error = nullptr; + auto id = g_dbus_connection_export_menu_model(m_bus, object_path, G_MENU_MODEL(menu), &error); + if (id) + m_exported_menu_ids.insert(id); + else if (error != nullptr) + g_warning("cannot export '%s': %s", object_path, error->message); + + g_free(object_path); + g_clear_error(&error); + //g_object_unref(menu); + } + + static void on_name_lost(GDBusConnection * /*connection*/, + const gchar * name, + gpointer gthis) + { + static_cast<Impl*>(gthis)->emit_name_lost(name); + } + + const std::string m_bus_name; + core::Signal<std::string> m_name_lost; + std::shared_ptr<Indicator> m_indicator; + std::set<guint> m_exported_menu_ids; + guint m_own_id = 0; + guint m_exported_actions_id = 0; + GDBusConnection * m_bus = nullptr; +}; + +/*** +**** +***/ + +Exporter::Exporter(const std::shared_ptr<Indicator>& indicator): + impl(new Impl(indicator)) +{ +} + +Exporter::~Exporter() +{ +} + +core::Signal<std::string>& +Exporter::name_lost() +{ + return impl->name_lost(); +} + +/*** +**** +***/ + diff --git a/src/exporter.h b/src/exporter.h new file mode 100644 index 0000000..6367f3a --- /dev/null +++ b/src/exporter.h @@ -0,0 +1,40 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <src/indicator.h> + +#include <core/signal.h> + +#include <memory> + +class Exporter +{ +public: + Exporter(const std::shared_ptr<Indicator>& indicator); + ~Exporter(); + core::Signal<std::string>& name_lost(); + +private: + class Impl; + std::unique_ptr<Impl> impl; + + Exporter(const Exporter&) =delete; + Exporter& operator=(const Exporter&) =delete; +}; + diff --git a/src/indicator.h b/src/indicator.h new file mode 100644 index 0000000..dc4df09 --- /dev/null +++ b/src/indicator.h @@ -0,0 +1,88 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#ifndef INDICATOR_DISPLAY_INDICATOR_H +#define INDICATOR_DISPLAY_INDICATOR_H + +#include <core/property.h> + +#include <gio/gio.h> // GIcon + +#include <string> +#include <vector> + +struct Header +{ + bool is_visible = false; + std::string title; + std::string label; + std::string a11y; + std::shared_ptr<GIcon> icon; + + bool operator== (const Header& that) const { + return (is_visible == that.is_visible) && + (title == that.title) && + (label == that.label) && + (a11y == that.a11y) && + (icon == that.icon); + } + bool operator!= (const Header& that) const { return !(*this == that);} +}; + + +class Profile +{ +public: + virtual std::string name() const =0; + virtual const core::Property<Header>& header() const =0; + virtual std::shared_ptr<GMenuModel> menu_model() const =0; + +protected: + Profile() =default; +}; + + +class SimpleProfile: public Profile +{ +public: + SimpleProfile(const char* name, const std::shared_ptr<GMenuModel>& menu): m_name(name), m_menu(menu) {} + + std::string name() const {return m_name;} + core::Property<Header>& header() {return m_header;} + const core::Property<Header>& header() const {return m_header;} + std::shared_ptr<GMenuModel> menu_model() const {return m_menu;} + +protected: + const std::string m_name; + core::Property<Header> m_header; + std::shared_ptr<GMenuModel> m_menu; +}; + + +class Indicator +{ +public: + virtual ~Indicator() =default; + + virtual const char* name() const =0; + virtual GSimpleActionGroup* action_group() const =0; + virtual std::vector<std::shared_ptr<Profile>> profiles() const =0; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..86bdeb3 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,62 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <src/exporter.h> +#include <src/rotation-lock.h> + +#include <glib/gi18n.h> // bindtextdomain() +#include <gio/gio.h> + +#include <locale.h> + +int +main(int /*argc*/, char** /*argv*/) +{ + // Work around a deadlock in glib's type initialization. + // It can be removed when https://bugzilla.gnome.org/show_bug.cgi?id=674885 is fixed. + g_type_ensure(G_TYPE_DBUS_CONNECTION); + + // boilerplate i18n + setlocale(LC_ALL, ""); + bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR); + textdomain(GETTEXT_PACKAGE); + + auto loop = g_main_loop_new(nullptr, false); + auto on_name_lost = [loop](const std::string& name){ + g_warning("busname lost: '%s'", name.c_str()); + g_main_loop_quit(loop); + }; + + // build all our indicators. + // Right now we've only got one -- rotation lock -- but hey, we can dream. + std::vector<std::shared_ptr<Indicator>> indicators; + std::vector<std::shared_ptr<Exporter>> exporters; + indicators.push_back(std::make_shared<RotationLockIndicator>()); + for (auto& indicator : indicators) { + auto exporter = std::make_shared<Exporter>(indicator); + exporter->name_lost().connect(on_name_lost); + exporters.push_back(exporter); + } + + g_main_loop_run(loop); + + // cleanup + g_main_loop_unref(loop); + return 0; +} diff --git a/src/rotation-lock.cpp b/src/rotation-lock.cpp new file mode 100644 index 0000000..3bbe12a --- /dev/null +++ b/src/rotation-lock.cpp @@ -0,0 +1,197 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <src/rotation-lock.h> + +#include <glib/gi18n.h> + +class RotationLockIndicator::Impl +{ +public: + + Impl(): + m_settings(g_settings_new(m_schema_name)), + m_action_group(create_action_group()) + { + // build the rotation lock icon + auto icon = g_themed_icon_new_with_default_fallbacks("orientation-lock"); + auto icon_deleter = [](GIcon* o){g_object_unref(G_OBJECT(o));}; + m_icon.reset(icon, icon_deleter); + + // build the phone profile + auto menu_model_deleter = [](GMenuModel* o){g_object_unref(G_OBJECT(o));}; + std::shared_ptr<GMenuModel> phone_menu (create_phone_menu(), menu_model_deleter); + m_phone = std::make_shared<SimpleProfile>("phone", phone_menu); + update_phone_header(); + } + + ~Impl() + { + g_clear_object(&m_action_group); + g_clear_object(&m_settings); + } + + GSimpleActionGroup* action_group() const + { + return m_action_group; + } + + std::vector<std::shared_ptr<Profile>> profiles() + { + std::vector<std::shared_ptr<Profile>> ret; + ret.push_back(m_phone); + return ret; + } + +private: + + /*** + **** Actions + ***/ + + static gboolean settings_to_action_state(GValue *value, + GVariant *variant, + gpointer /*unused*/) + { + bool is_locked = g_strcmp0(g_variant_get_string(variant, nullptr), "none"); + g_value_set_variant(value, g_variant_new_boolean(is_locked)); + return TRUE; + } + + static GVariant* action_state_to_settings(const GValue *value, + const GVariantType * /*expected_type*/, + gpointer /*unused*/) + { + // Toggling to 'on' *should* lock to the screen's current orientation. + // We don't have any way of knowing Screen.orientation in this service, + // so just pick one at random to satisfy the binding's needs. + // + // In practice this doesn't matter since the indicator isn't visible + // when the lock mode is 'none' so the end user won't ever be able + // to toggle the menuitem from None to anything else. + + auto state_is_true = g_variant_get_boolean(g_value_get_variant(value)); + return g_variant_new_string(state_is_true ? "PrimaryOrientation" : "none"); + } + + GSimpleActionGroup* create_action_group() + { + GSimpleActionGroup* group; + GSimpleAction* action; + + group = g_simple_action_group_new(); + action = g_simple_action_new_stateful("rotation-lock", + nullptr, + g_variant_new_boolean(false)); + g_settings_bind_with_mapping(m_settings, "orientation-lock", + action, "state", + G_SETTINGS_BIND_DEFAULT, + settings_to_action_state, + action_state_to_settings, + nullptr, + nullptr); + g_action_map_add_action(G_ACTION_MAP(group), G_ACTION(action)); + g_object_unref(G_OBJECT(action)); + g_signal_connect_swapped(m_settings, "changed::orientation-lock", + G_CALLBACK(on_orientation_lock_setting_changed), this); + + return group; + } + + /*** + **** Phone profile + ***/ + + static void on_orientation_lock_setting_changed (gpointer gself) + { + static_cast<Impl*>(gself)->update_phone_header(); + } + + GMenuModel* create_phone_menu() + { + GMenu* menu; + GMenuItem* menu_item; + + menu = g_menu_new(); + + menu_item = g_menu_item_new(_("Rotation Lock"), "indicator.rotation-lock"); + g_menu_item_set_attribute(menu_item, "x-canonical-type", "s", "com.canonical.indicator.switch"); + g_menu_append_item(menu, menu_item); + g_object_unref(menu_item); + + return G_MENU_MODEL(menu); + } + + void update_phone_header() + { + Header h; + h.title = _("Rotation lock"); + h.a11y = h.title; + h.is_visible = g_settings_get_enum(m_settings, "orientation-lock") != 0; + h.icon = m_icon; + m_phone->header().set(h); + } + + /*** + **** + ***/ + + static constexpr char const * m_schema_name {"com.ubuntu.touch.system"}; + static constexpr char const * m_orientation_lock_icon_name {"orientation-lock"}; + GSettings* m_settings = nullptr; + GSimpleActionGroup* m_action_group = nullptr; + std::shared_ptr<SimpleProfile> m_phone; + std::shared_ptr<GIcon> m_icon; +}; + +/*** +**** +***/ + +RotationLockIndicator::RotationLockIndicator(): + impl(new Impl()) +{ +} + +RotationLockIndicator::~RotationLockIndicator() +{ +} + +std::vector<std::shared_ptr<Profile>> +RotationLockIndicator::profiles() const +{ + return impl->profiles(); +} + +GSimpleActionGroup* +RotationLockIndicator::action_group() const +{ + return impl->action_group(); +} + +const char* +RotationLockIndicator::name() const +{ + return "rotation_lock"; +} + +/*** +**** +***/ + diff --git a/src/rotation-lock.h b/src/rotation-lock.h new file mode 100644 index 0000000..2354c4a --- /dev/null +++ b/src/rotation-lock.h @@ -0,0 +1,42 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#ifndef INDICATOR_DISPLAY_ROTATION_LOCK_H +#define INDICATOR_DISPLAY_ROTATION_LOCK_H + +#include <src/indicator.h> + +#include <memory> // std::unique_ptr + +class RotationLockIndicator: public Indicator +{ +public: + RotationLockIndicator(); + ~RotationLockIndicator(); + + const char* name() const; + GSimpleActionGroup* action_group() const; + std::vector<std::shared_ptr<Profile>> profiles() const; + +protected: + class Impl; + std::unique_ptr<Impl> impl; +}; + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..054a676 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,35 @@ +include(FindGMock) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${GMOCK_INCLUDE_DIRS}) +include_directories(${GTEST_INCLUDE_DIRS}) + +# 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) + +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + # turn off the warnings that break Google Test + set (CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Wno-global-constructors -Wno-weak-vtables -Wno-undef -Wno-c++98-compat-pedantic -Wno-missing-noreturn -Wno-used-but-marked-unused -Wno-padded -Wno-deprecated -Wno-sign-compare -Wno-shift-sign-overflow") +endif() + +SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g ${CXX_WARNING_ARGS}") + +# look for headers in our src dir, and also in the directories where we autogenerate files... +include_directories (${CMAKE_SOURCE_DIR}/src) +include_directories (${CMAKE_CURRENT_BINARY_DIR}) +include_directories (${DBUSTEST_INCLUDE_DIRS}) + +function(add_test_by_name name) + set (TEST_NAME ${name}) + add_executable (${TEST_NAME} ${TEST_NAME}.cpp) + add_test (${TEST_NAME} ${TEST_NAME}) + add_dependencies (${TEST_NAME} libindicatordisplayservice) + target_link_libraries (${TEST_NAME} indicatordisplayservice ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) +endfunction() +add_test_by_name(test-rotation-lock) + +add_test (cppcheck cppcheck --enable=all -q --error-exitcode=2 --inline-suppr -I${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/tests) + diff --git a/tests/glib-fixture.h b/tests/glib-fixture.h new file mode 100644 index 0000000..a8f3a76 --- /dev/null +++ b/tests/glib-fixture.h @@ -0,0 +1,143 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <map> + +#include <glib.h> +#include <glib/gstdio.h> +#include <gio/gio.h> + +#include <gtest/gtest.h> + +#include <locale.h> // setlocale() + +class GlibFixture : public ::testing::Test +{ + private: + + GLogFunc realLogHandler; + + std::map<GLogLevelFlags,size_t> expected_log; + std::map<GLogLevelFlags,std::vector<std::string>> log; + + void test_log_counts() + { + const GLogLevelFlags levels_to_test[] = { G_LOG_LEVEL_ERROR, + G_LOG_LEVEL_CRITICAL, + G_LOG_LEVEL_MESSAGE, + G_LOG_LEVEL_WARNING }; + + for(const auto& level : levels_to_test) + { + const auto& v = log[level]; + const auto n = v.size(); + + EXPECT_EQ(expected_log[level], n); + + if (expected_log[level] != n) + for (size_t i=0; i<n; ++i) + g_message("%d %s", (n+1), v[i].c_str()); + } + + expected_log.clear(); + log.clear(); + } + + static void default_log_handler(const gchar * log_domain, + GLogLevelFlags log_level, + const gchar * message, + gpointer self) + { + auto tmp = g_strdup_printf ("%s:%d \"%s\"", log_domain, (int)log_level, message); + static_cast<GlibFixture*>(self)->log[log_level].push_back(tmp); + g_free(tmp); + } + + protected: + + void increment_expected_errors(GLogLevelFlags level, size_t n=1) + { + expected_log[level] += n; + } + + virtual void SetUp() + { + setlocale(LC_ALL, "C.UTF-8"); + + loop = g_main_loop_new(nullptr, false); + + g_log_set_default_handler(default_log_handler, this); + + g_assert(g_setenv("GSETTINGS_BACKEND", "memory", true)); + + g_unsetenv("DISPLAY"); + } + + virtual void TearDown() + { + test_log_counts(); + + g_log_set_default_handler(realLogHandler, this); + + g_clear_pointer(&loop, g_main_loop_unref); + } + + private: + + static gboolean + wait_for_signal__timeout(gpointer name) + { + g_error("%s: timed out waiting for signal '%s'", G_STRLOC, (char*)name); + return G_SOURCE_REMOVE; + } + + static gboolean + wait_msec__timeout(gpointer loop) + { + g_main_loop_quit(static_cast<GMainLoop*>(loop)); + return G_SOURCE_CONTINUE; + } + + protected: + + /* convenience func to loop while waiting for a GObject's signal */ + void wait_for_signal(gpointer o, const gchar * signal, const guint timeout_seconds=5) + { + // wait for the signal or for timeout, whichever comes first + const auto handler_id = g_signal_connect_swapped(o, signal, + G_CALLBACK(g_main_loop_quit), + loop); + const auto timeout_id = g_timeout_add_seconds(timeout_seconds, + wait_for_signal__timeout, + loop); + g_main_loop_run(loop); + g_source_remove(timeout_id); + g_signal_handler_disconnect(o, handler_id); + } + + /* convenience func to loop for N msec */ + void wait_msec(guint msec=50) + { + const auto id = g_timeout_add(msec, wait_msec__timeout, loop); + g_main_loop_run(loop); + g_source_remove(id); + } + + GMainLoop * loop; +}; diff --git a/tests/gtestdbus-fixture.h b/tests/gtestdbus-fixture.h new file mode 100644 index 0000000..541050e --- /dev/null +++ b/tests/gtestdbus-fixture.h @@ -0,0 +1,102 @@ +/* + * Copyright 2013 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" + +/*** +**** +***/ + +class GTestDBusFixture: public GlibFixture +{ + public: + + GTestDBusFixture() {} + + GTestDBusFixture(const std::vector<std::string>& service_dirs_in): service_dirs(service_dirs_in) {} + + private: + + typedef GlibFixture super; + + static void + on_bus_opened (GObject* /*object*/, GAsyncResult * res, gpointer gself) + { + auto self = static_cast<GTestDBusFixture*>(gself); + + GError * err = 0; + self->bus = g_bus_get_finish (res, &err); + g_assert_no_error (err); + + g_main_loop_quit (self->loop); + } + + static void + on_bus_closed (GObject* /*object*/, GAsyncResult * res, gpointer gself) + { + auto self = static_cast<GTestDBusFixture*>(gself); + + GError * err = 0; + g_dbus_connection_close_finish (self->bus, res, &err); + g_assert_no_error (err); + + g_main_loop_quit (self->loop); + } + + protected: + + GTestDBus * test_dbus = nullptr; + GDBusConnection * bus = nullptr; + const std::vector<std::string> service_dirs; + + virtual void SetUp () + { + super::SetUp (); + + // pull up a test dbus + test_dbus = g_test_dbus_new (G_TEST_DBUS_NONE); + for (const auto& dir : service_dirs) + g_test_dbus_add_service_dir (test_dbus, dir.c_str()); + g_test_dbus_up (test_dbus); + const char * address = g_test_dbus_get_bus_address (test_dbus); + 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); + + // wait for the GDBusConnection before returning + g_bus_get (G_BUS_TYPE_SYSTEM, nullptr, on_bus_opened, this); + g_main_loop_run (loop); + } + + virtual void TearDown () + { + wait_msec(); + + // close the system bus + g_dbus_connection_close(bus, nullptr, on_bus_closed, this); + g_main_loop_run(loop); + g_clear_object(&bus); + + // tear down the test dbus + g_test_dbus_down(test_dbus); + g_clear_object(&test_dbus); + + super::TearDown(); + } +}; diff --git a/tests/manual b/tests/manual new file mode 100644 index 0000000..f175b22 --- /dev/null +++ b/tests/manual @@ -0,0 +1,16 @@ + +Test-case indicator-display/rotation-indicator +<dl> + <dt>On the phone, enable the orientation lock in ubuntu-system-settings.</dt> + <dd>The rotation lock indicator should appear, and its switch menuitem should be set to 'true'.</dd> + + <dt>With the orientation locked, click on the indicator's switch menuitem to toggle from locked to unlocked.</dd> + <dd>The rotation lock indicator should disappear</dd> + <dd>In ubuntu-system-settings, the orientation lock control should change to 'none'.</dd> +</dl> + + +<strong> + If all actions produce the expected results listed, please <a href="results#add_result">submit</a> a 'passed' result. + If an action fails, or produces an unexpected result, please <a href="results#add_result">submit</a> a 'failed' result and <a href="../../buginstructions">file a bug</a>. Please be sure to include the bug number when you <a href="results#add_result">submit</a> your result</strong>. + diff --git a/tests/test-rotation-lock.cpp b/tests/test-rotation-lock.cpp new file mode 100644 index 0000000..946b1dd --- /dev/null +++ b/tests/test-rotation-lock.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include "gtestdbus-fixture.h" + +#include <src/rotation-lock.h> + +class RotationLockFixture: public GTestDBusFixture +{ +private: + typedef GTestDBusFixture super; + +protected: + + void SetUp() + { + super::SetUp(); + } + + void TearDown() + { + super::TearDown(); + } +}; + +/*** +**** +***/ + +TEST_F(RotationLockFixture, CheckIndicator) +{ + RotationLockIndicator indicator; + + ASSERT_STREQ("rotation_lock", indicator.name()); + auto actions = indicator.action_group(); + ASSERT_TRUE(actions != nullptr); + ASSERT_TRUE(g_action_group_has_action(G_ACTION_GROUP(actions), "rotation-lock")); + + std::vector<std::shared_ptr<Profile>> profiles = indicator.profiles(); + ASSERT_EQ(1, profiles.size()); + std::shared_ptr<Profile> phone = profiles[0]; + ASSERT_EQ(std::string("phone"), phone->name()); + ASSERT_FALSE(phone->header()->is_visible); +} + |