aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt72
-rw-r--r--cmake/FindGMock.cmake10
-rw-r--r--cmake/GCov.cmake51
-rw-r--r--cmake/GdbusCodegen.cmake36
-rw-r--r--cmake/Translations.cmake37
-rw-r--r--data/CMakeLists.txt74
-rw-r--r--data/com.canonical.indicator.rotation_lock11
-rw-r--r--data/indicator-display.conf.in9
-rw-r--r--data/indicator-display.desktop.in9
-rw-r--r--data/indicator-display.upstart.desktop.in9
-rw-r--r--debian/changelog6
-rw-r--r--debian/compat1
-rw-r--r--debian/control34
-rw-r--r--debian/copyright21
-rwxr-xr-xdebian/rules10
-rw-r--r--po/CMakeLists.txt3
-rw-r--r--po/POTFILES.in1
-rw-r--r--src/CMakeLists.txt29
-rw-r--r--src/exporter.cpp217
-rw-r--r--src/exporter.h40
-rw-r--r--src/indicator.h88
-rw-r--r--src/main.cpp62
-rw-r--r--src/rotation-lock.cpp197
-rw-r--r--src/rotation-lock.h42
-rw-r--r--tests/CMakeLists.txt35
-rw-r--r--tests/glib-fixture.h143
-rw-r--r--tests/gtestdbus-fixture.h102
-rw-r--r--tests/manual16
-rw-r--r--tests/test-rotation-lock.cpp61
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);
+}
+