diff options
author | Ted Gould <ted@gould.cx> | 2014-09-08 22:58:48 -0500 |
---|---|---|
committer | Ted Gould <ted@gould.cx> | 2014-09-08 22:58:48 -0500 |
commit | 334e3b3130cbde5a96e6da5323d40418db80f8ef (patch) | |
tree | 299d5e2a5cdaab892fe7c92b9405f721635124e3 | |
parent | cc51d0fc02f2980cc08fcd6cfc929ea61ff9d93a (diff) | |
parent | 059246d136ed8c24c5e78e6a966bf2dfc945fe4e (diff) | |
download | ayatana-indicator-power-334e3b3130cbde5a96e6da5323d40418db80f8ef.tar.gz ayatana-indicator-power-334e3b3130cbde5a96e6da5323d40418db80f8ef.tar.bz2 ayatana-indicator-power-334e3b3130cbde5a96e6da5323d40418db80f8ef.zip |
Merge trunk
45 files changed, 3111 insertions, 1065 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..569100d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,96 @@ +project(indicator-power C CXX) +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-power") +add_definitions (-DGETTEXT_PACKAGE="${GETTEXT_PACKAGE}" + -DGNOMELOCALEDIR="${CMAKE_INSTALL_FULL_LOCALEDIR}") + +option (enable_tests "Build the package's automatic tests." ON) +option (enable_lcov "Generate lcov code coverage reports." ON) + +## +## GNU standard installation directories +## + +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 (CheckIncludeFile) +include (FindPkgConfig) + +pkg_check_modules(SERVICE_DEPS REQUIRED + glib-2.0>=2.36 + gio-2.0>=2.36 + gio-unix-2.0>=2.36 + gudev-1.0>=204 + libnotify>=0.7.6 + url-dispatcher-1>=1) + +include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS}) + +## +## custom targets +## + +set (ARCHIVE_NAME ${CMAKE_PROJECT_NAME}-${PROJECT_VERSION}) +add_custom_target (dist + COMMAND bzr export --root=${ARCHIVE_NAME} ${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar.gz + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + +add_custom_target (clean-coverage + COMMAND find ${CMAKE_BINARY_DIR} -name '*.gcda' | xargs rm -f) + +add_custom_target (cppcheck COMMAND cppcheck --enable=all -q --error-exitcode=2 --inline-suppr + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/tests) + +## +## Actual building +## + +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(C_WARNING_ARGS "${C_WARNING_ARGS} -Weverything") + set(C_WARNING_ARGS "${C_WARNING_ARGS} -Wno-c++98-compat -Wno-padded") # these are annoying + set(C_WARNING_ARGS "${C_WARNING_ARGS} -Wno-documentation") # gtk-doc != doxygen +else() + set(C_WARNING_ARGS "${C_WARNING_ARGS} -Wall -Wextra -Wpedantic -Wformat=2") +endif() +set(C_WARNING_ARGS "${C_WARNING_ARGS} -Wno-missing-field-initializers") # GActionEntry + + +include_directories (${CMAKE_CURRENT_SOURCE_DIR}/include) +include_directories (${CMAKE_CURRENT_BINARY_DIR}/include) + +# 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 () + +# actually build things +add_subdirectory(src) +add_subdirectory(data) +add_subdirectory(po) +if (${enable_tests}) + add_subdirectory(tests) +endif () + diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index fbf05ca..0000000 --- a/Makefile.am +++ /dev/null @@ -1,43 +0,0 @@ -SUBDIRS = po data src - -if BUILD_TESTS -SUBDIRS += tests -# build src first -tests: src -endif - - -############################################################ - -dist_noinst_SCRIPTS = \ - autogen.sh - -############################################################ - -dist-hook: - @if test -d "$(top_srcdir)/.bzr"; \ - then \ - echo Creating ChangeLog && \ - ( cd "$(top_srcdir)" && \ - echo '# Generated by Makefile. Do not edit.'; echo; \ - $(top_srcdir)/build-aux/missing --run bzr log --gnu-changelog ) > ChangeLog.tmp \ - && mv -f ChangeLog.tmp $(top_distdir)/ChangeLog \ - || (rm -f ChangeLog.tmp; \ - echo Failed to generate ChangeLog >&2 ); \ - else \ - echo Failed to generate ChangeLog: not a branch >&2; \ - fi - @if test -d "$(top_srcdir)/.bzr"; \ - then \ - echo Creating AUTHORS && \ - ( cd "$(top_srcdir)" && \ - echo '# Generated by Makefile. Do not edit.'; echo; \ - $(top_srcdir)/build-aux/missing --run bzr log --long --levels=0 | grep -e "^\s*author:" -e "^\s*committer:" | cut -d ":" -f 2 | cut -d "<" -f 1 | sort -u) > AUTHORS.tmp \ - && mv -f AUTHORS.tmp $(top_distdir)/AUTHORS \ - || (rm -f AUTHORS.tmp; \ - echo Failed to generate AUTHORS >&2 ); \ - else \ - echo Failed to generate AUTHORS: not a branch >&2; \ - fi - -include $(top_srcdir)/Makefile.am.coverage diff --git a/Makefile.am.coverage b/Makefile.am.coverage deleted file mode 100644 index fb97747..0000000 --- a/Makefile.am.coverage +++ /dev/null @@ -1,48 +0,0 @@ - -# Coverage targets - -.PHONY: clean-gcno clean-gcda \ - coverage-html generate-coverage-html clean-coverage-html \ - coverage-gcovr generate-coverage-gcovr clean-coverage-gcovr - -clean-local: clean-gcno clean-coverage-html clean-coverage-gcovr - -if HAVE_GCOV - -clean-gcno: - @echo Removing old coverage instrumentation - -find -name '*.gcno' -print | xargs -r rm - -clean-gcda: - @echo Removing old coverage results - -find -name '*.gcda' -print | xargs -r rm - -coverage-html: clean-gcda - -$(MAKE) $(AM_MAKEFLAGS) -k check - $(MAKE) $(AM_MAKEFLAGS) generate-coverage-html - -generate-coverage-html: - @echo Collecting coverage data - $(LCOV) --directory $(top_builddir) --capture --output-file coverage.info --no-checksum --compat-libtool - LANG=C $(GENHTML) --prefix $(top_builddir) --output-directory coveragereport --title "Code Coverage" --legend --show-details coverage.info - -clean-coverage-html: clean-gcda - -$(LCOV) --directory $(top_builddir) -z - -rm -rf coverage.info coveragereport - -if HAVE_GCOVR - -coverage-gcovr: clean-gcda - -$(MAKE) $(AM_MAKEFLAGS) -k check - $(MAKE) $(AM_MAKEFLAGS) generate-coverage-gcovr - -generate-coverage-gcovr: - @echo Generating coverage GCOVR report - $(GCOVR) -x -r $(top_builddir) -o $(top_builddir)/coverage.xml - -clean-coverage-gcovr: clean-gcda - -rm -rf $(top_builddir)/coverage.xml - -endif # HAVE_GCOVR - -endif # HAVE_GCOV diff --git a/autogen.sh b/autogen.sh deleted file mode 100755 index 1912c87..0000000 --- a/autogen.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh - -test -n "$srcdir" || srcdir=`dirname "$0"` -test -n "$srcdir" || srcdir=. - -olddir=`pwd` -cd $srcdir - -AUTORECONF=`which autoreconf` -if test -z $AUTORECONF; then - echo "*** No autoreconf found, please intall it ***" - exit 1 -fi - -INTLTOOLIZE=`which intltoolize` -if test -z $INTLTOOLIZE; then - echo "*** No intltoolize found, please install the intltool package ***" - exit 1 -fi - -mkdir -p build-aux - -autopoint --force -AUTOPOINT='intltoolize --automake --copy' autoreconf --force --install --verbose - -cd $olddir -test -n "$NOCONFIGURE" || "$srcdir/configure" "$@" 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/cmake/UseGSettings.cmake b/cmake/UseGSettings.cmake new file mode 100644 index 0000000..3b61523 --- /dev/null +++ b/cmake/UseGSettings.cmake @@ -0,0 +1,23 @@ +# GSettings.cmake, CMake macros written for Marlin, feel free to re-use them. + +macro(add_schema SCHEMA_NAME) + + set(PKG_CONFIG_EXECUTABLE pkg-config) + set(GSETTINGS_DIR "${CMAKE_INSTALL_FULL_DATAROOTDIR}/glib-2.0/schemas") + + # Run the validator and error if it fails + execute_process (COMMAND ${PKG_CONFIG_EXECUTABLE} gio-2.0 --variable glib_compile_schemas OUTPUT_VARIABLE _glib_compile_schemas OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process (COMMAND ${_glib_compile_schemas} --dry-run --schema-file=${SCHEMA_NAME} ERROR_VARIABLE _schemas_invalid OUTPUT_STRIP_TRAILING_WHITESPACE) + + if (_schemas_invalid) + message (SEND_ERROR "Schema validation error: ${_schemas_invalid}") + endif (_schemas_invalid) + + # Actually install and recomple schemas + message (STATUS "${GSETTINGS_DIR} is the GSettings install dir") + install (FILES ${SCHEMA_NAME} DESTINATION ${GSETTINGS_DIR} OPTIONAL) + + install (CODE "message (STATUS \"Compiling GSettings schemas\")") + install (CODE "execute_process (COMMAND ${_glib_compile_schemas} ${GSETTINGS_DIR})") +endmacro() + diff --git a/configure.ac b/configure.ac deleted file mode 100644 index 30aa0d9..0000000 --- a/configure.ac +++ /dev/null @@ -1,139 +0,0 @@ -AC_INIT([indicator-power], - [13.10.0], - [http://bugs.launchpad.net/indicator-power], - [indicator-power], - [http://launchpad.net/indicator-power]) -AC_COPYRIGHT([Copyright 2011-2013 Canonical]) - -AC_PREREQ([2.64]) - -AC_CONFIG_HEADERS([config.h]) -AC_CONFIG_SRCDIR([src/device.c]) -AC_CONFIG_MACRO_DIR([m4]) -AC_CONFIG_AUX_DIR([build-aux]) - -AM_INIT_AUTOMAKE([1.11 -Wall foreign dist-xz check-news]) -AM_MAINTAINER_MODE([enable]) - -AM_SILENT_RULES([yes]) - -# Check for programs -AC_PROG_CC -AM_PROG_CC_C_O -AC_PROG_CXX -AM_PROG_AR - -# Initialize libtool -LT_PREREQ([2.2.6]) -LT_INIT - - -########################### -# Dependencies -########################### - -GLIB_REQUIRED_VERSION=2.35.4 -GIO_REQUIRED_VERSION=2.26 -GIO_UNIX_REQUIRED_VERSION=2.26 -GUDEV_REQUIRED_VERSION=204 - -PKG_CHECK_MODULES([SERVICE_DEPS],[glib-2.0 >= $GLIB_REQUIRED_VERSION - gio-2.0 >= $GIO_REQUIRED_VERSION - gio-unix-2.0 >= $GIO_UNIX_REQUIRED_VERSION - gudev-1.0 >= $GUDEV_REQUIRED_VERSION - url-dispatcher-1]) - -########################### -# GSETTINGS -########################### - -GLIB_GSETTINGS - -########################### -# Google Test framework -########################### - -AC_ARG_ENABLE([tests], - [AS_HELP_STRING([--disable-tests], [Disable test scripts and tools (default=auto)])], - [enable_tests=${enableval}], - [enable_tests=auto]) -if test "x$enable_tests" != "xno"; then - m4_include([m4/gtest.m4]) - CHECK_GTEST - if test "x$enable_tests" = "xauto"; then - enable_tests=${have_gtest} - elif test "x$enable_tests" = "xyes" && test "x$have_gtest" != "xyes"; then - AC_MSG_ERROR([tests were requested but gtest is not installed.]) - fi -fi -AM_CONDITIONAL([BUILD_TESTS],[test "x$enable_tests" = "xyes"]) - -########################### -# gcov coverage reporting -########################### - -m4_include([m4/gcov.m4]) -AC_TDD_GCOV -AM_CONDITIONAL([HAVE_GCOV], [test "x$ac_cv_check_gcov" = xyes]) -AM_CONDITIONAL([HAVE_LCOV], [test "x$ac_cv_check_lcov" = xyes]) -AM_CONDITIONAL([HAVE_GCOVR], [test "x$ac_cv_check_gcovr" = xyes]) -AC_SUBST(COVERAGE_CFLAGS) -AC_SUBST(COVERAGE_CXXFLAGS) -AC_SUBST(COVERAGE_LDFLAGS) - -############################## -# Custom Junk -############################## - -AC_DEFUN([AC_DEFINE_PATH], [ - test "x$prefix" = xNONE && prefix="$ac_default_prefix" - test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' - ac_define_path=`eval echo [$]$2` - ac_define_path=`eval echo [$]ac_define_path` - $1="$ac_define_path" - AC_SUBST($1) - ifelse($3, , - AC_DEFINE_UNQUOTED($1, "$ac_define_path"), - AC_DEFINE_UNQUOTED($1, "$ac_define_path", $3)) -]) - -########################### -# Internationalization -########################### - -IT_PROG_INTLTOOL([0.50.0]) - -AM_GNU_GETTEXT([external]) -AM_GNU_GETTEXT_VERSION([0.17]) - -AC_SUBST([GETTEXT_PACKAGE],[${PACKAGE_TARNAME}]) -AC_DEFINE([GETTEXT_PACKAGE],[PACKAGE_TARNAME],[Define to the gettext package name.]) -AC_DEFINE_PATH([GNOMELOCALEDIR],"${datadir}/locale",[locale directory]) - -########################### -# Files -########################### - -AC_CONFIG_FILES([ -Makefile -po/Makefile.in -data/Makefile -data/upstart/Makefile -src/Makefile -tests/Makefile -]) -AC_OUTPUT - -########################### -# Results -########################### - -AC_MSG_NOTICE([ - -Power Indicator Configuration: - - Prefix: $prefix - Unit Tests: $enable_tests - gcov: $use_gcov - -]) diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt new file mode 100644 index 0000000..0750128 --- /dev/null +++ b/data/CMakeLists.txt @@ -0,0 +1,90 @@ +## +## GSettings schema +## + +include (UseGSettings) +set (SCHEMA_NAME "com.canonical.indicator.power.gschema.xml") +set (SCHEMA_FILE "${CMAKE_CURRENT_BINARY_DIR}/${SCHEMA_NAME}") +set (SCHEMA_FILE_IN "${CMAKE_CURRENT_SOURCE_DIR}/${SCHEMA_NAME}.in") + +# generate the .xml file using intltool +set (ENV{LC_ALL} "C") +execute_process (COMMAND intltool-merge -quiet --xml-style --utf8 --no-translations "${SCHEMA_FILE_IN}" "${SCHEMA_FILE}") + +# let UseGSettings do the rest +add_schema (${SCHEMA_FILE}) + +## +## Upstart Job File +## + +# where to install +set (UPSTART_JOB_DIR "${CMAKE_INSTALL_FULL_DATADIR}/upstart/sessions") +message (STATUS "${UPSTART_JOB_DIR} is the Upstart Job 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_JOB_DIR}") + +## +## XDG Autostart File +## + +# where to install +set (XDG_AUTOSTART_DIR "/etc/xdg/autostart") +message (STATUS "${XDG_AUTOSTART_DIR} is the DBus Service File 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.power") +set (UNITY_INDICATOR_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${UNITY_INDICATOR_NAME}") + +install (FILES "${UNITY_INDICATOR_FILE}" + DESTINATION "${UNITY_INDICATOR_DIR}") diff --git a/data/Makefile.am b/data/Makefile.am deleted file mode 100644 index 6ecd4c5..0000000 --- a/data/Makefile.am +++ /dev/null @@ -1,60 +0,0 @@ -BUILT_SOURCES= -CLEANFILES= -EXTRA_DIST= - -SUBDIRS = upstart - -# -# the indicator bus file -# - -indicatorsdir = $(prefix)/share/unity/indicators -dist_indicators_DATA = com.canonical.indicator.power - -# -# the upstart job file -# - -upstart_jobsdir = $(datadir)/upstart/sessions -upstart_jobs_DATA = indicator-power.conf -upstart_jobs_in = $(upstart_jobs_DATA:.conf=.conf.in) -$(upstart_jobs_DATA): $(upstart_jobs_in) - $(AM_V_GEN) $(SED) -e "s|\@pkglibexecdir\@|$(pkglibexecdir)|" $< > $@ -BUILT_SOURCES += $(upstart_jobs_DATA) -CLEANFILES += $(upstart_jobs_DATA) -EXTRA_DIST += $(upstart_jobs_in) - -# -# the xdg autostart job file -# - -xdg_autostartdir = /etc/xdg/autostart -xdg_autostart_DATA = indicator-power.desktop -xdg_autostart_in = $(xdg_autostart_DATA:.desktop=.desktop.in) -$(xdg_autostart_DATA): $(xdg_autostart_in) - $(AM_V_GEN) $(SED) -e "s|\@pkglibexecdir\@|$(pkglibexecdir)|" $< > $@ -BUILT_SOURCES += $(xdg_autostart_DATA) -CLEANFILES += $(xdg_autostart_DATA) -EXTRA_DIST += $(xdg_autostart_in) - -# -# the gettings -# - -gsettings_in_file = com.canonical.indicator.power.gschema.xml.in -gsettings_SCHEMAS = $(gsettings_in_file:.xml.in=.xml) -CLEANFILES += $(gsettings_SCHEMAS) - -@INTLTOOL_XML_NOMERGE_RULE@ - -@GSETTINGS_RULES@ - -dist_noinst_DATA = \ - com.canonical.indicator.power.gschema.xml \ - $(gsettings_in_file) - -CLEANFILES += \ - $(gsettings_SCHEMAS) - -MAINTAINERCLEANFILES = \ - $(gsettings_SCHEMAS:.xml=.valid) diff --git a/data/com.canonical.indicator.power b/data/com.canonical.indicator.power index 65fd646..12e158a 100644 --- a/data/com.canonical.indicator.power +++ b/data/com.canonical.indicator.power @@ -12,5 +12,11 @@ ObjectPath=/com/canonical/indicator/power/desktop [desktop_greeter] ObjectPath=/com/canonical/indicator/power/desktop_greeter +[desktop_lockscreen] +ObjectPath=/com/canonical/indicator/power/desktop_greeter + [phone_greeter] ObjectPath=/com/canonical/indicator/power/desktop_greeter + +[ubiquity] +ObjectPath=/com/canonical/indicator/power/desktop_greeter diff --git a/data/com.canonical.indicator.power.Battery.xml b/data/com.canonical.indicator.power.Battery.xml new file mode 100644 index 0000000..eca4524 --- /dev/null +++ b/data/com.canonical.indicator.power.Battery.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd"> + <interface name="com.canonical.indicator.power.Battery"> + + <property name="PowerLevel" type="s" access="read"> + <doc:doc> + <doc:description> + <doc:para>The battery's power level. Possible values: 'ok', 'low', 'very_low', 'critical'</doc:para> + </doc:description> + </doc:doc> + </property> + + <property name="IsWarning" type="b" access="read"> + <doc:doc> + <doc:description> + <doc:para>Whether or not indicator-power-service is warning the user about low battery power.</doc:para> + </doc:description> + </doc:doc> + </property> + + </interface> +</node> diff --git a/data/upstart/indicator-power.desktop.in b/data/indicator-power.upstart.desktop.in index 08ce2fa..5f95f8e 100644 --- a/data/upstart/indicator-power.desktop.in +++ b/data/indicator-power.upstart.desktop.in @@ -2,8 +2,9 @@ Type=Application Name=Indicator Power Exec=@pkglibexecdir@/indicator-power-service -OnlyShowIn=Unity; +OnlyShowIn=Unity;GNOME; NoDisplay=true StartupNotify=false Terminal=false +AutostartCondition=GNOME3 unless-session gnome Hidden=true diff --git a/data/upstart/Makefile.am b/data/upstart/Makefile.am deleted file mode 100644 index 1259a88..0000000 --- a/data/upstart/Makefile.am +++ /dev/null @@ -1,17 +0,0 @@ -BUILT_SOURCES= -CLEANFILES= -EXTRA_DIST= - -# -# the upstart xdg autostart override -# - -xdg_autostartdir = $(datadir)/upstart/xdg/autostart -xdg_autostart_DATA = indicator-power.desktop -xdg_autostart_in = $(xdg_autostart_DATA:.desktop=.desktop.in) -$(xdg_autostart_DATA): $(xdg_autostart_in) - $(AM_V_GEN) $(SED) -e "s|\@pkglibexecdir\@|$(pkglibexecdir)|" $< > $@ -BUILT_SOURCES += $(xdg_autostart_DATA) -CLEANFILES += $(xdg_autostart_DATA) -EXTRA_DIST += $(xdg_autostart_in) - diff --git a/debian/changelog b/debian/changelog index e108d70..a560af3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,117 @@ +indicator-power (12.10.6+14.10.20140822-0ubuntu1) utopic; urgency=low + + [ Charles Kerr ] + * Choose the icon that's closest to the current battery charge + percentage (LP: #1186181) + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 22 Aug 2014 16:15:51 +0000 + +indicator-power (12.10.6+14.10.20140814-0ubuntu1) utopic; urgency=low + + [ Charles Kerr ] + * Re-use the same Translations.cmake file across indicators (LP: + #1354058) + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 14 Aug 2014 14:51:02 +0000 + +indicator-power (12.10.6+14.10.20140730-0ubuntu1) utopic; urgency=low + + [ Charles Kerr ] + * Add low-battery notifications. (LP: #1317858) + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 30 Jul 2014 10:52:39 +0000 + +indicator-power (12.10.6+14.10.20140718-0ubuntu1) utopic; urgency=low + + [ Charles Kerr ] + * Switch from automake/autoconf to CMake. + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 18 Jul 2014 21:33:30 +0000 + +indicator-power (12.10.6+14.10.20140624-0ubuntu1) utopic; urgency=low + + [ Alberto Aguirre ] + * Changes to address setBrightness interface moving from powerd to + unity-system-compositor + + [ Iain Lane ] + * Drop powerd and u-s-c to suggests, since the code handles them not + being present and we don't need them on desktop (and they are in + universe). + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 24 Jun 2014 09:45:56 +0000 + +indicator-power (12.10.6+14.10.20140611-0ubuntu1) utopic; urgency=low + + [ Charles Kerr ] + * Prefer the 'battery-XXX-charging' (eg, 'battery-020-charging') icons + over the 'battery-low-charging' ones because the former are more + precise and likely closer to the actual battery level. (LP: + #1186181) + + [ Iain Lane ] + * Remove the brightness slider from the phone menu. (LP: #1289470) + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 11 Jun 2014 17:21:30 +0000 + +indicator-power (12.10.6+14.10.20140428-0ubuntu1) utopic; urgency=low + + [ Ricardo Salveti de Araujo ] + * Updating code to reflect latest powerd dbus API changes + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 28 Apr 2014 23:25:53 +0000 + +indicator-power (12.10.6+14.04.20140411-0ubuntu1) trusty; urgency=low + + [ Sebastien Bacher ] + * export an ubiquity profile, reusing the desktop_greeter object (LP: + #1306604) + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 11 Apr 2014 14:01:36 +0000 + +indicator-power (12.10.6+14.04.20140328-0ubuntu1) trusty; urgency=low + + [ Charles Kerr ] + * If there are two batteries detected, combine their percentages and + their time-remainings as per the revised spec. (LP: #880881) + + [ Lars Uebernickel ] + * Use com.canonical.indicator.basic menu item for device items That + menu item can handle non-square icons. (LP: #1263228) + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 28 Mar 2014 16:10:45 +0000 + +indicator-power (12.10.6+14.04.20140326-0ubuntu1) trusty; urgency=low + + [ Y.C cheng ] + * Set brightness via powerd if it exist (using dbus) (LP: #1287599) + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 26 Mar 2014 18:50:46 +0000 + +indicator-power (12.10.6+14.04.20140314-0ubuntu1) trusty; urgency=low + + [ Charles Kerr ] + * Change the implementation of the title text / title accessible text + / menuitem text to follow the spec changes in + https://wiki.ubuntu.com/Power?action=diff&rev2=44&rev1=43#Title and + update the tests accordingly. (LP: #1234458) + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 14 Mar 2014 14:14:07 +0000 + +indicator-power (12.10.6+14.04.20140311-0ubuntu1) trusty; urgency=low + + [ Lars Uebernickel ] + * Add desktop_lockscreen profile + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 11 Mar 2014 18:33:10 +0000 + +indicator-power (12.10.6+14.04.20140306-0ubuntu1) trusty; urgency=low + + [ Simon Steinbeiß ] + * Add xfce4-powermanager as alternative recommend + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 06 Mar 2014 11:51:54 +0000 + indicator-power (12.10.6+14.04.20140207-0ubuntu1) trusty; urgency=low [ Ted Gould ] diff --git a/debian/control b/debian/control index d64c903..ec440a8 100644 --- a/debian/control +++ b/debian/control @@ -2,16 +2,22 @@ Source: indicator-power Section: gnome Priority: optional Maintainer: Ubuntu Core Developers <ubuntu-devel-discuss@lists.ubuntu.com> -Build-Depends: debhelper (>= 9), - dh-autoreconf, - autopoint, - intltool, - libgtest-dev, +Build-Depends: cmake, + libnotify-dev (>= 0.7.6), libglib2.0-dev (>= 2.36), libgudev-1.0-dev, liburl-dispatcher1-dev, - python, -Standards-Version: 3.9.2 + python:any, +# for packaging + debhelper (>= 9), + dh-translations, + intltool, +# for tests + libgtest-dev, + python3-dbusmock, + dbus-test-runner, + libdbustest1-dev, +Standards-Version: 3.9.5 Homepage: https://launchpad.net/indicator-power # If you aren't a member of ~indicator-applet-developers but need to upload # packaging changes, just go ahead. ~indicator-applet-developers will notice @@ -24,8 +30,10 @@ Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, upower, -Recommends: unity-control-center | gnome-control-center (>= 3.1) | ubuntu-system-settings, +Recommends: unity-control-center | gnome-control-center (>= 3.1) | ubuntu-system-settings | xfce4-power-manager, indicator-applet (>= 0.2) | indicator-renderer, +Suggests: powerd, + unity-system-compositor (>= 0.0.4), Description: Indicator showing power state. This indicator displays current power management information and gives the user a way to access power management preferences. diff --git a/debian/rules b/debian/rules index 83edd94..885b94c 100755 --- a/debian/rules +++ b/debian/rules @@ -7,10 +7,7 @@ export DPKG_GENSYMBOLS_CHECK_LEVEL=4 %: - dh $@ --with autoreconf - -override_dh_autoreconf: - NOCONFIGURE=1 dh_autoreconf ./autogen.sh + dh $@ --with translations override_dh_install: find debian/indicator-power -name \*.la -delete diff --git a/m4/gcov.m4 b/m4/gcov.m4 deleted file mode 100644 index bd96386..0000000 --- a/m4/gcov.m4 +++ /dev/null @@ -1,86 +0,0 @@ -# Checks for existence of coverage tools: -# * gcov -# * lcov -# * genhtml -# * gcovr -# -# Sets ac_cv_check_gcov to yes if tooling is present -# and reports the executables to the variables LCOV, GCOVR and GENHTML. -AC_DEFUN([AC_TDD_GCOV], -[ - AC_ARG_ENABLE(gcov, - AS_HELP_STRING([--enable-gcov], - [enable coverage testing with gcov]), - [use_gcov=$enableval], [use_gcov=no]) - - if test "x$use_gcov" = "xyes"; then - # we need gcc: - if test "$GCC" != "yes"; then - AC_MSG_ERROR([GCC is required for --enable-gcov]) - fi - - # Check if ccache is being used - AC_CHECK_PROG(SHTOOL, shtool, shtool) - case `$SHTOOL path $CC` in - *ccache*[)] gcc_ccache=yes;; - *[)] gcc_ccache=no;; - esac - - if test "$gcc_ccache" = "yes" && (test -z "$CCACHE_DISABLE" || test "$CCACHE_DISABLE" != "1"); then - AC_MSG_ERROR([ccache must be disabled when --enable-gcov option is used. You can disable ccache by setting environment variable CCACHE_DISABLE=1.]) - fi - - lcov_version_list="1.6 1.7 1.8 1.9 1.10 1.11" - AC_CHECK_PROG(LCOV, lcov, lcov) - AC_CHECK_PROG(GENHTML, genhtml, genhtml) - - if test "$LCOV"; then - AC_CACHE_CHECK([for lcov version], glib_cv_lcov_version, [ - glib_cv_lcov_version=invalid - lcov_version=`$LCOV -v 2>/dev/null | $SED -e 's/^.* //'` - for lcov_check_version in $lcov_version_list; do - if test "$lcov_version" = "$lcov_check_version"; then - glib_cv_lcov_version="$lcov_check_version (ok)" - fi - done - ]) - else - lcov_msg="To enable code coverage reporting you must have one of the following lcov versions installed: $lcov_version_list" - AC_MSG_ERROR([$lcov_msg]) - fi - - case $glib_cv_lcov_version in - ""|invalid[)] - lcov_msg="You must have one of the following versions of lcov: $lcov_version_list (found: $lcov_version)." - AC_MSG_ERROR([$lcov_msg]) - LCOV="exit 0;" - ;; - esac - - if test -z "$GENHTML"; then - AC_MSG_ERROR([Could not find genhtml from the lcov package]) - fi - - ac_cv_check_gcov=yes - ac_cv_check_lcov=yes - - # Remove all optimization flags from CFLAGS - changequote({,}) - CFLAGS=`echo "$CFLAGS" | $SED -e 's/-O[0-9]*//g'` - changequote([,]) - - # Add the special gcc flags - COVERAGE_CFLAGS="-O0 -fprofile-arcs -ftest-coverage" - COVERAGE_CXXFLAGS="-O0 -fprofile-arcs -ftest-coverage" - COVERAGE_LDFLAGS="-lgcov" - - # Check availability of gcovr - AC_CHECK_PROG(GCOVR, gcovr, gcovr) - if test -z "$GCOVR"; then - ac_cv_check_gcovr=no - else - ac_cv_check_gcovr=yes - fi - -fi -]) # AC_TDD_GCOV diff --git a/m4/gtest.m4 b/m4/gtest.m4 deleted file mode 100644 index 2de334c..0000000 --- a/m4/gtest.m4 +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright (C) 2012 Canonical, Ltd. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice (including the next -# paragraph) shall be included in all copies or substantial portions of the -# Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -# Checks whether the gtest source is available on the system. Allows for -# adjusting the include and source path. Sets have_gtest=yes if the source is -# present. Sets GTEST_CPPFLAGS and GTEST_SOURCE to the preprocessor flags and -# source location respectively. -AC_DEFUN([CHECK_GTEST], -[ - AC_ARG_WITH([gtest-include-path], - [AS_HELP_STRING([--with-gtest-include-path], - [location of the Google test headers])], - [GTEST_CPPFLAGS="-I$withval"]) - - AC_ARG_WITH([gtest-source-path], - [AS_HELP_STRING([--with-gtest-source-path], - [location of the Google test sources, defaults to /usr/src/gtest])], - [GTEST_SOURCE="$withval"], - [GTEST_SOURCE="/usr/src/gtest"]) - - GTEST_CPPFLAGS="$GTEST_CPPFLAGS -I$GTEST_SOURCE" - - AC_LANG_PUSH([C++]) - - tmp_CPPFLAGS="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $GTEST_CPPFLAGS" - - AC_CHECK_HEADER([gtest/gtest.h]) - - CPPFLAGS="$tmp_CPPFLAGS" - - AC_LANG_POP - - AC_CHECK_FILES([$GTEST_SOURCE/src/gtest-all.cc] - [$GTEST_SOURCE/src/gtest_main.cc], - [have_gtest_source=yes], - [have_gtest_source=no]) - - AS_IF([test "x$ac_cv_header_gtest_gtest_h" = xyes -a \ - "x$have_gtest_source" = xyes], - [have_gtest=yes] - [AC_SUBST(GTEST_CPPFLAGS)] - [AC_SUBST(GTEST_SOURCE)], - [have_gtest=no]) -]) # CHECK_GTEST diff --git a/po/CMakeLists.txt b/po/CMakeLists.txt new file mode 100644 index 0000000..8325f6e --- /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/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..7a4a297 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,50 @@ +set (SERVICE_LIB "indicatorpowerservice") +set (SERVICE_EXEC "indicator-power-service") + +add_definitions(-DG_LOG_DOMAIN="Indicator-Power") + +# handwritten sources +set(SERVICE_MANUAL_SOURCES + device-provider-upower.c + ib-brightness-control.c + ib-brightness-uscreen-control.c + device-provider.c + device.c + notifier.c + service.c) + +# generated sources +include(GdbusCodegen) +set(SERVICE_GENERATED_SOURCES) +add_gdbus_codegen_with_namespace(SERVICE_GENERATED_SOURCES dbus-upower + org.freedesktop + Dbus + ${CMAKE_CURRENT_SOURCE_DIR}/org.freedesktop.UPower.xml) +add_gdbus_codegen_with_namespace(SERVICE_GENERATED_SOURCES dbus-battery + com.canonical.indicator.power + Dbus + ${CMAKE_SOURCE_DIR}/data/com.canonical.indicator.power.Battery.xml) +# add the bin dir to our include path so the code can find the generated header files +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + + +# add warnings/coverage info on handwritten files +# but not the autogenerated ones... +set(C_WARNING_ARGS "${C_WARNING_ARGS} -Wno-bad-function-cast") # g_clear_object() +set(C_WARNING_ARGS "${C_WARNING_ARGS} -Wno-disabled-macro-expansion") # G_DEFINE_TYPE +set(C_WARNING_ARGS "${C_WARNING_ARGS} -Wno-assign-enum") # GParamFlags +set(C_WARNING_ARGS "${C_WARNING_ARGS} -Wno-switch-enum") +set_source_files_properties(${SERVICE_MANUAL_SOURCES} + PROPERTIES COMPILE_FLAGS "${C_WARNING_ARGS} ${GCOV_FLAGS} -g -std=c99") + +# the service library for tests to link against (basically, everything except main()) +add_library(${SERVICE_LIB} STATIC ${SERVICE_MANUAL_SOURCES} ${SERVICE_GENERATED_SOURCES}) +include_directories(${CMAKE_SOURCE_DIR}) +link_directories(${SERVICE_DEPS_LIBRARY_DIRS}) + +# the executable: lib + main() +add_executable (${SERVICE_EXEC} main.c) +set_source_files_properties(${SERVICE_SOURCES} main.c PROPERTIES COMPILE_FLAGS "${C_WARNING_ARGS} -g -std=c99") +target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} ${GCOV_LIBS}) +install (TARGETS ${SERVICE_EXEC} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}) + diff --git a/src/Makefile.am b/src/Makefile.am deleted file mode 100644 index be746db..0000000 --- a/src/Makefile.am +++ /dev/null @@ -1,81 +0,0 @@ -BUILT_SOURCES = -EXTRA_DIST = -CLEANFILES = - -SHARED_CFLAGS = \ - -Wall -Wextra -Werror \ - $(SERVICE_DEPS_CFLAGS) \ - -DG_LOG_DOMAIN=\"Indicator-Power\" - -### -### - -upower_dbus_sources = \ - dbus-upower.c \ - dbus-upower.h - -$(upower_dbus_sources): org.freedesktop.UPower.xml - $(AM_V_GEN) gdbus-codegen \ - --c-namespace Dbus \ - --interface-prefix org.freedesktop \ - --generate-c-code dbus-upower \ - $^ - -BUILT_SOURCES += $(upower_dbus_sources) -CLEANFILES += $(upower_dbus_sources) -EXTRA_DIST += org.freedesktop.UPower.xml - -### -### -### - -noinst_LIBRARIES = libindicatorpower-upower.a libindicatorpower-service.a - -libindicatorpower_upower_a_SOURCES = \ - $(upower_dbus_sources) \ - device-provider-upower.c \ - device-provider-upower.h - -libindicatorpower_upower_a_CFLAGS = \ - $(SHARED_CFLAGS) \ - -Wno-unused-parameter \ - $(COVERAGE_CFLAGS) - -libindciatorpower_upower_a_LDFLAGS = $(COVERAGE_LDFLAGS) - -libindicatorpower_service_a_SOURCES = \ - ib-brightness-control.c \ - ib-brightness-control.h \ - device-provider.c \ - device-provider.h \ - device.c \ - device.h \ - service.c \ - service.h - -libindicatorpower_service_a_CFLAGS = \ - $(SHARED_CFLAGS) \ - -Wno-missing-field-initializers \ - $(COVERAGE_CFLAGS) - -libindicatorpower_service_a_LDFLAGS = $(COVERAGE_LDFLAGS) - -### -### -### - -pkglibexec_PROGRAMS = indicator-power-service - -indicator_power_service_SOURCES = main.c - -indicator_power_service_CFLAGS = \ - $(SHARED_CFLAGS) \ - $(COVERAGE_CFLAGS) - -indicator_power_service_LDADD = \ - libindicatorpower-upower.a \ - libindicatorpower-service.a \ - $(SERVICE_DEPS_LIBS) - -indicator_power_service_LDFLAGS = \ - $(COVERAGE_LDFLAGS) diff --git a/src/dbus-shared.h b/src/dbus-shared.h new file mode 100644 index 0000000..bf54034 --- /dev/null +++ b/src/dbus-shared.h @@ -0,0 +1,28 @@ +/* + * 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> + * Ted Gould <ted@canonical.com> + */ + +#ifndef DBUS_SHARED_H +#define DBUS_SHARED_H + +#define BUS_NAME "com.canonical.indicator.power" +#define BUS_PATH "/com/canonical/indicator/power" + +#endif /* DBUS_SHARED_H */ + diff --git a/src/device-provider-upower.c b/src/device-provider-upower.c index 7c12beb..400a060 100644 --- a/src/device-provider-upower.c +++ b/src/device-provider-upower.c @@ -17,8 +17,6 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "config.h" - #include "dbus-upower.h" #include "device.h" #include "device-provider.h" @@ -60,7 +58,7 @@ G_DEFINE_TYPE_WITH_CODE ( indicator_power_device_provider_upower, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (INDICATOR_TYPE_POWER_DEVICE_PROVIDER, - indicator_power_device_provider_interface_init)); + indicator_power_device_provider_interface_init)) /*** **** UPOWER DBUS @@ -102,7 +100,7 @@ on_device_properties_ready (GObject * o, GAsyncResult * res, gpointer gdata) gdouble percentage = 0; gint64 time_to_empty = 0; gint64 time_to_full = 0; - time_t time; + gint64 time; IndicatorPowerDevice * device; IndicatorPowerDeviceProviderUPowerPriv * p = data->self->priv; GVariant * dict = g_variant_get_child_value (response, 0); @@ -120,7 +118,7 @@ on_device_properties_ready (GObject * o, GAsyncResult * res, gpointer gdata) INDICATOR_POWER_DEVICE_STATE, (gint)state, INDICATOR_POWER_DEVICE_OBJECT_PATH, data->path, INDICATOR_POWER_DEVICE_PERCENTAGE, percentage, - INDICATOR_POWER_DEVICE_TIME, (guint64)time, + INDICATOR_POWER_DEVICE_TIME, time, NULL); } else @@ -129,7 +127,7 @@ on_device_properties_ready (GObject * o, GAsyncResult * res, gpointer gdata) kind, percentage, state, - time); + (time_t)time); g_hash_table_insert (p->devices, g_strdup (data->path), diff --git a/src/device-provider-upower.h b/src/device-provider-upower.h index 7bdd5d5..7bfecd9 100644 --- a/src/device-provider-upower.h +++ b/src/device-provider-upower.h @@ -65,6 +65,8 @@ struct _IndicatorPowerDeviceProviderUPowerClass GObjectClass parent_class; }; +GType indicator_power_device_provider_upower_get_type (void); + IndicatorPowerDeviceProvider * indicator_power_device_provider_upower_new (void); G_END_DECLS diff --git a/src/device-provider.c b/src/device-provider.c index 81a8eec..46fcfad 100644 --- a/src/device-provider.c +++ b/src/device-provider.c @@ -29,7 +29,7 @@ static guint signals[SIGNAL_LAST] = { 0 }; G_DEFINE_INTERFACE (IndicatorPowerDeviceProvider, indicator_power_device_provider, - 0); + 0) static void indicator_power_device_provider_default_init (IndicatorPowerDeviceProviderInterface * klass) diff --git a/src/device.c b/src/device.c index 7f1b14f..eff76d1 100644 --- a/src/device.c +++ b/src/device.c @@ -37,9 +37,12 @@ struct _IndicatorPowerDevicePrivate gchar * object_path; gdouble percentage; time_t time; -}; -#define INDICATOR_POWER_DEVICE_GET_PRIVATE(o) (INDICATOR_POWER_DEVICE(o)->priv) + /* Timestamp of when when we first noticed that upower couldn't estimate + the time-remaining field for this device, or 0 if not applicable. + This is used when generating the time-remaining string. */ + GTimer * inestimable; +}; /* Properties */ /* Enum for the properties so that they can be quickly found and looked up. */ @@ -64,7 +67,7 @@ static void set_property (GObject*, guint prop_id, const GValue*, GParamSpec* ); static void get_property (GObject*, guint prop_id, GValue*, GParamSpec* ); /* LCOV_EXCL_START */ -G_DEFINE_TYPE (IndicatorPowerDevice, indicator_power_device, G_TYPE_OBJECT); +G_DEFINE_TYPE (IndicatorPowerDevice, indicator_power_device, G_TYPE_OBJECT) /* LCOV_EXCL_STOP */ static void @@ -136,6 +139,11 @@ indicator_power_device_init (IndicatorPowerDevice *self) static void indicator_power_device_dispose (GObject *object) { + IndicatorPowerDevice * self = INDICATOR_POWER_DEVICE(object); + IndicatorPowerDevicePrivate * p = self->priv; + + g_clear_pointer (&p->inestimable, g_timer_destroy); + G_OBJECT_CLASS (indicator_power_device_parent_class)->dispose (object); } @@ -179,7 +187,7 @@ get_property (GObject * o, guint prop_id, GValue * value, GParamSpec * pspec) break; case PROP_TIME: - g_value_set_uint64 (value, priv->time); + g_value_set_uint64 (value, (guint64)priv->time); break; default: @@ -192,35 +200,54 @@ static void set_property (GObject * o, guint prop_id, const GValue * value, GParamSpec * pspec) { IndicatorPowerDevice * self = INDICATOR_POWER_DEVICE(o); - IndicatorPowerDevicePrivate * priv = self->priv; + IndicatorPowerDevicePrivate * p = self->priv; switch (prop_id) { case PROP_KIND: - priv->kind = g_value_get_int (value); + p->kind = (UpDeviceKind) g_value_get_int (value); break; case PROP_STATE: - priv->state = g_value_get_int (value); + p->state = (UpDeviceState) g_value_get_int (value); break; case PROP_OBJECT_PATH: - g_free (priv->object_path); - priv->object_path = g_value_dup_string (value); + g_free (p->object_path); + p->object_path = g_value_dup_string (value); break; case PROP_PERCENTAGE: - priv->percentage = g_value_get_double (value); + p->percentage = g_value_get_double (value); break; case PROP_TIME: - priv->time = g_value_get_uint64(value); + p->time = (time_t) g_value_get_uint64(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(o, prop_id, pspec); break; } + + /** + * Check to see if the time-remaining value is estimable. + * When it first becomes inestimable, kick off a timer because + * we need to track that to generate the appropriate title text. + */ + + const gboolean is_inestimable = (p->time == 0) + && (p->state != UP_DEVICE_STATE_FULLY_CHARGED) + && (p->percentage > 0); + + if (!is_inestimable) + { + g_clear_pointer (&p->inestimable, g_timer_destroy); + } + else if (p->inestimable == NULL) + { + p->inestimable = g_timer_new (); + } } /*** @@ -326,13 +353,6 @@ device_kind_to_string (UpDeviceKind kind) indicator_power_device_get_icon_names: @device: #IndicatorPowerDevice from which to generate the icon names - This function's logic differs from GSD's power plugin in some ways: - - 1. For discharging batteries, we decide whether or not to use the 'caution' - icon based on whether or not we have <= 30 minutes remaining, rather than - looking at the battery's percentage left. - <https://bugs.launchpad.net/indicator-power/+bug/743823> - See also indicator_power_device_get_gicon(). Return value: (array zero-terminated=1) (transfer full): @@ -387,21 +407,15 @@ indicator_power_device_get_icon_names (const IndicatorPowerDevice * device) case UP_DEVICE_STATE_CHARGING: suffix_str = get_device_icon_suffix (percentage); index_str = get_device_icon_index (percentage); - g_ptr_array_add (names, g_strdup_printf ("%s-%s-charging-symbolic", kind_str, suffix_str)); + g_ptr_array_add (names, g_strdup_printf ("%s-%s-charging", kind_str, index_str)); g_ptr_array_add (names, g_strdup_printf ("gpm-%s-%s-charging", kind_str, index_str)); + g_ptr_array_add (names, g_strdup_printf ("%s-%s-charging-symbolic", kind_str, suffix_str)); g_ptr_array_add (names, g_strdup_printf ("%s-%s-charging", kind_str, suffix_str)); break; case UP_DEVICE_STATE_PENDING_CHARGE: case UP_DEVICE_STATE_DISCHARGING: case UP_DEVICE_STATE_PENDING_DISCHARGE: - /* Don't show the caution/red icons unless we have <=30 min left. - <https://bugs.launchpad.net/indicator-power/+bug/743823> - Themes use the caution color when the percentage is 0% or 20%, - so if we have >30 min left, use 30% as the icon's percentage floor */ - if (indicator_power_device_get_time (device) > (30*60)) - percentage = MAX(percentage, 30); - suffix_str = get_device_icon_suffix (percentage); index_str = get_device_icon_index (percentage); g_ptr_array_add (names, g_strdup_printf ("%s-%s", kind_str, index_str)); @@ -442,57 +456,6 @@ indicator_power_device_get_gicon (const IndicatorPowerDevice * device) **** ***/ -/* Format time remaining for reading ("H:MM") and speech ("H hours, MM minutes") */ -static void -get_timestring (guint64 time_secs, - gchar **readable_timestring, - gchar **accessible_timestring) -{ - gint hours; - gint minutes; - - /* Add 0.5 to do rounding */ - minutes = (int) ( ( time_secs / 60.0 ) + 0.5 ); - - if (minutes == 0) - { - *readable_timestring = g_strdup (_("Unknown time")); - *accessible_timestring = g_strdup (_("Unknown time")); - - return; - } - - if (minutes < 60) - { - *readable_timestring = g_strdup_printf ("0:%.2i", minutes); - *accessible_timestring = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%i minute", - "%i minutes", - minutes), minutes); - return; - } - - hours = minutes / 60; - minutes = minutes % 60; - - *readable_timestring = g_strdup_printf ("%i:%.2i", hours, minutes); - - if (minutes == 0) - { - *accessible_timestring = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, - "%i hour", - "%i hours", - hours), hours); - } - else - { - /* TRANSLATOR: "%i %s %i %s" are "%i hours %i minutes" - * Swap order with "%2$s %2$i %1$s %1$i if needed */ - *accessible_timestring = g_strdup_printf (_("%i %s %i %s"), - hours, g_dngettext (GETTEXT_PACKAGE, "hour", "hours", hours), - minutes, g_dngettext (GETTEXT_PACKAGE, "minute", "minutes", minutes)); - } -} - static const gchar * device_kind_to_localised_string (UpDeviceKind kind) { @@ -555,149 +518,314 @@ device_kind_to_localised_string (UpDeviceKind kind) return text; } +/** + * The '''brief time-remaining string''' for a component should be: + * * the time remaining for it to empty or fully charge, + * if estimable, in H:MM format; otherwise + * * “estimating…” if the time remaining has been inestimable for + * less than 30 seconds; otherwise + * * “unknown” if the time remaining has been inestimable for + * between 30 seconds and one minute; otherwise + * * the empty string. + */ static char * -join_strings (const char * name, const char * time, const char * percent) +get_brief_time_remaining (const IndicatorPowerDevice * device) { - char * str; - const gboolean have_name = name && *name; - const gboolean have_time = time && *time; - const gboolean have_percent = percent && *percent; - - if (have_name && have_time && have_percent) - str = g_strdup_printf (_("%s (%s, %s)"), name, time, percent); - else if (have_name && have_time) - str = g_strdup_printf (_("%s (%s)"), name, time); - else if (have_name && have_percent) - str = g_strdup_printf (_("%s (%s)"), name, percent); - else if (have_name) - str = g_strdup (name); - else if (have_time && have_percent) - str = g_strdup_printf (_("(%s, %s)"), time, percent); - else if (have_time) - str = g_strdup_printf (_("(%s)"), time); - else if (have_percent) - str = g_strdup_printf (_("(%s)"), percent); - else - str = g_strdup (""); + gchar * str = NULL; + const IndicatorPowerDevicePrivate * p = device->priv; + + if (p->time > 0) + { + int minutes = p->time / 60; + const int hours = minutes / 60; + minutes %= 60; + + str = g_strdup_printf("%0d:%02d", hours, minutes); + } + else if (p->inestimable != NULL) + { + const double elapsed = g_timer_elapsed (p->inestimable, NULL); + + if (elapsed < 30) + { + str = g_strdup_printf (_("estimating…")); + } + else if (elapsed < 60) + { + str = g_strdup_printf (_("unknown")); + } + } return str; } -static void -indicator_power_device_get_text (const IndicatorPowerDevice * device, - gboolean show_time_in_header, - gboolean show_percentage_in_header, - gchar ** header, - gchar ** label, - gchar ** a11y) +/** + * The '''expanded time-remaining string''' for a component should + * be the same as the brief time-remaining string, except that if + * the time is estimable: + * * if the component is charging, it should be “H:MM to charge” + * * if the component is discharging, it should be “H:MM left”. + */ +static char* +get_expanded_time_remaining (const IndicatorPowerDevice * device) { - if (!INDICATOR_IS_POWER_DEVICE(device)) + char * str = NULL; + const IndicatorPowerDevicePrivate * p = device->priv; + + if (p->time && ((p->state == UP_DEVICE_STATE_CHARGING) || (p->state == UP_DEVICE_STATE_DISCHARGING))) + { + int minutes = p->time / 60; + const int hours = minutes / 60; + minutes %= 60; + + if (p->state == UP_DEVICE_STATE_CHARGING) + { + /* TRANSLATORS: H:MM (hours, minutes) to charge the battery. Example: "1:30 to charge" */ + str = g_strdup_printf (_("%0d:%02d to charge"), hours, minutes); + } + else // discharging + { + /* TRANSLATORS: H:MM (hours, minutes) to discharge the battery. Example: "1:30 left"*/ + str = g_strdup_printf (_("%0d:%02d left"), hours, minutes); + } + } + else { - if (a11y != NULL) *a11y = NULL; - if (label != NULL) *label = NULL; - if (header != NULL) *header = NULL; - g_warning ("%s: %p is not an IndicatorPowerDevice", G_STRFUNC, device); - return; + str = get_brief_time_remaining (device); } - const time_t time = indicator_power_device_get_time (device); - const UpDeviceState state = indicator_power_device_get_state (device); - const UpDeviceKind kind = indicator_power_device_get_kind (device); - const gchar * device_name = device_kind_to_localised_string (kind); - const gdouble percentage = indicator_power_device_get_percentage (device); - char pctstr[32] = { '\0' }; - g_snprintf (pctstr, sizeof(pctstr), "%.0lf%%", percentage); + return str; +} - GString * terse_time = g_string_new (NULL); - GString * verbose_time = g_string_new (NULL); - GString * accessible_time = g_string_new (NULL); +/** + * The '''accessible time-remaining string''' for a component + * should be the same as the expanded time-remaining string, + * except the H:MM time should be rendered as “''H'' hours ''M'' minutes”, + * or just as “''M'' minutes” if the time is less than one hour. + */ +static char * +get_accessible_time_remaining (const IndicatorPowerDevice * device) +{ + char * str = NULL; + const IndicatorPowerDevicePrivate * p = device->priv; - if (time > 0) + if (p->time && ((p->state == UP_DEVICE_STATE_CHARGING) || (p->state == UP_DEVICE_STATE_DISCHARGING))) { - char * readable_timestr = NULL; - char * accessible_timestr = NULL; - get_timestring (time, &readable_timestr, &accessible_timestr); + guint minutes = (guint)p->time / 60u; + const guint hours = minutes / 60u; + minutes %= 60; - if (state == UP_DEVICE_STATE_CHARGING) - { - g_string_assign (terse_time, readable_timestr); - g_string_printf (verbose_time, _("%s to charge"), readable_timestr); - g_string_printf (accessible_time, _("%s to charge"), accessible_timestr); - } - else if ((state == UP_DEVICE_STATE_DISCHARGING) && (time <= (60*60*24))) + if (p->state == UP_DEVICE_STATE_CHARGING) { - g_string_assign (terse_time, readable_timestr); - g_string_printf (verbose_time, _("%s left"), readable_timestr); - g_string_printf (accessible_time, _("%s left"), accessible_timestr); + if (hours > 0) + { + /* TRANSLATORS: "X (hour,hours) Y (minute,minutes) to charge" the battery. + Example: "1 hour 10 minutes to charge" */ + str = g_strdup_printf (_("%d %s %d %s to charge"), + hours, g_dngettext (NULL, "hour", "hours", hours), + minutes, g_dngettext (NULL, "minute", "minutes", minutes)); + } + else + { + /* TRANSLATORS: "Y (minute,minutes) to charge" the battery. + Example: "59 minutes to charge" */ + str = g_strdup_printf (_("%d %s to charge"), + minutes, g_dngettext (NULL, "minute", "minutes", minutes)); + } } - else + else // discharging { - /* if there's more than 24 hours remaining, we don't show it */ + if (hours > 0) + { + /* TRANSLATORS: "X (hour,hours) Y (minute,minutes) left" until the battery's empty. + Example: "1 hour 10 minutes left" */ + str = g_strdup_printf (_("%d %s %d %s left"), + hours, g_dngettext (NULL, "hour", "hours", hours), + minutes, g_dngettext (NULL, "minute", "minutes", minutes)); + } + else + { + /* TRANSLATORS: "Y (minute,minutes) left" until the battery's empty. + Example: "59 minutes left" */ + str = g_strdup_printf (_("%d %s left"), + minutes, g_dngettext (NULL, "minute", "minutes", minutes)); + } } - - g_free (readable_timestr); - g_free (accessible_timestr); } - else if (state == UP_DEVICE_STATE_FULLY_CHARGED) + else { - g_string_assign (verbose_time, _("charged")); - g_string_assign (accessible_time, _("charged")); + str = get_brief_time_remaining (device); } - else if (percentage > 0) + + return str; +} + +/** + * The time is relevant for a device if either (a) the component is charging, + * or (b) the component is discharging and the estimated time is less than + * 24 hours. (A time greater than 24 hours is probably a mistaken calculation.) + */ +static gboolean +time_is_relevant (const IndicatorPowerDevice * device) +{ + const IndicatorPowerDevicePrivate * p = device->priv; + + if (p->state == UP_DEVICE_STATE_CHARGING) + return TRUE; + + if ((p->state == UP_DEVICE_STATE_DISCHARGING) && (p->time<(24*60*60))) + return TRUE; + + return FALSE; +} + +/** + * The menu item for each chargeable component should consist of ... + * Text representing the name of the component (“Battery”, “Mouse”, + * “UPS”, “Alejandra’s iPod”, etc) and the charge status in brackets: + * + * * “X (charged)” if it is fully charged and not discharging; + * * “X (expanded time-remaining string)” if it is charging, + * or discharging with less than 24 hours left; + * * “X” if it is discharging with 24 hours or more left. + * + * The accessible label for the menu item should be the same as the + * visible label, except with the accessible time-remaining string + * instead of the expanded time-remaining string. + */ +static char * +get_menuitem_text (const IndicatorPowerDevice * device, + gboolean accessible) +{ + char * str = NULL; + const IndicatorPowerDevicePrivate * p = device->priv; + const char * kind_str = device_kind_to_localised_string (p->kind); + + if (p->state == UP_DEVICE_STATE_FULLY_CHARGED) { - g_string_assign (terse_time, _("estimating…")); - g_string_assign (verbose_time, _("estimating…")); - g_string_assign (accessible_time, _("estimating…")); + /* TRANSLATORS: example: "battery (charged)" */ + str = g_strdup_printf (_("%s (charged)"), kind_str); } else { - *pctstr = '\0'; + char * time_str = NULL; + + if (time_is_relevant (device)) + { + if (accessible) + time_str = get_accessible_time_remaining (device); + else + time_str = get_expanded_time_remaining (device); + } - if (kind != UP_DEVICE_KIND_LINE_POWER) + if (time_str && *time_str) { - g_string_assign (verbose_time, _("not present")); - g_string_assign (accessible_time, _("not present")); + /* TRANSLATORS: example: "battery (time remaining)" */ + str = g_strdup_printf (_("%s (%s)"), kind_str, time_str); } + else + { + str = g_strdup (kind_str); + } + + g_free (time_str); } - if (header != NULL) - *header = join_strings (NULL, - show_time_in_header ? terse_time->str : "", - show_percentage_in_header ? pctstr : ""); + return str; +} + +char * +indicator_power_device_get_readable_text (const IndicatorPowerDevice * device) +{ + g_return_val_if_fail (INDICATOR_IS_POWER_DEVICE(device), NULL); - if (label != NULL) - *label = join_strings (device_name, - verbose_time->str, - NULL); + return get_menuitem_text (device, FALSE); +} - if (a11y != NULL) - *a11y = join_strings (device_name, - accessible_time->str, - pctstr); +char * +indicator_power_device_get_accessible_text (const IndicatorPowerDevice * device) +{ + g_return_val_if_fail (INDICATOR_IS_POWER_DEVICE(device), NULL); - g_string_free (terse_time, TRUE); - g_string_free (verbose_time, TRUE); - g_string_free (accessible_time, TRUE); + return get_menuitem_text (device, TRUE); } -gchar * -indicator_power_device_get_label (const IndicatorPowerDevice * device) +/** + * If the time is relevant and/or “Show Percentage in Menu Bar” is checked, + * the icon should be followed by brackets. + * + * If the time is relevant, the brackets should contain the time-remaining + * string for that component. + * + * If “Show Percentage in Menu Bar” is checked (as it should not be by default), + * the brackets should contain the percentage charge for that device. + * + * If both conditions are true, the time and percentage should be separated by a space. + */ +char* +indicator_power_device_get_readable_title (const IndicatorPowerDevice * device, + gboolean want_time, + gboolean want_percent) { - gchar * label = NULL; - indicator_power_device_get_text (device, FALSE, FALSE, - NULL, &label, NULL); - return label; + char * str = NULL; + char * time_str = NULL; + const IndicatorPowerDevicePrivate * p; + + g_return_val_if_fail (INDICATOR_IS_POWER_DEVICE(device), NULL); + + p = device->priv; + + // if we can't provide time-remaining, turn off the time flag + if (want_time && !time_is_relevant (device)) + want_time = FALSE; + + // if we can't provide percent, turn off the percent flag + if (p->percentage < 0.01) + want_percent = FALSE; + + // try to build the time-remaining string + if (want_time) + { + time_str = get_brief_time_remaining (device); + want_time = time_str && *time_str; + } + + if (want_time && want_percent) + { + /* TRANSLATORS: after the icon, a time-remaining string + battery %. Example: "(0:59, 33%)" */ + str = g_strdup_printf (_("(%s, %.0lf%%)"), time_str, p->percentage); + } + else if (want_time) + { + /* TRANSLATORS: after the icon, a time-remaining string Example: "(0:59)" */ + str = g_strdup_printf (_("(%s)"), time_str); + } + else if (want_percent) + { + /* TRANSLATORS: after the icon, a battery %. Example: "(33%)" */ + str = g_strdup_printf (_("(%.0lf%%)"), p->percentage); + } + else + { + str = NULL; + } + + g_free (time_str); + return str; } -void -indicator_power_device_get_header (const IndicatorPowerDevice * device, - gboolean show_time, - gboolean show_percentage, - gchar ** header, - gchar ** a11y) +/** + * Regardless, the accessible name for the whole menu title should be the same + * as the accessible name for that thing’s component inside the menu itself. + */ +char * +indicator_power_device_get_accessible_title (const IndicatorPowerDevice * device, + gboolean want_time G_GNUC_UNUSED, + gboolean want_percent G_GNUC_UNUSED) { - indicator_power_device_get_text (device, show_time, show_percentage, - header, NULL, a11y); + g_return_val_if_fail (INDICATOR_IS_POWER_DEVICE(device), NULL); + + return indicator_power_device_get_accessible_text (device); } /*** @@ -745,5 +873,5 @@ indicator_power_device_new_from_variant (GVariant * v) kind, percentage, state, - time); + (time_t)time); } diff --git a/src/device.h b/src/device.h index 1f395a1..d867707 100644 --- a/src/device.h +++ b/src/device.h @@ -24,7 +24,7 @@ License along with this library. If not, see #ifndef __INDICATOR_POWER_DEVICE_H__ #define __INDICATOR_POWER_DEVICE_H__ -#include <glib-object.h> +#include <gio/gio.h> /* GIcon */ G_BEGIN_DECLS @@ -116,22 +116,27 @@ IndicatorPowerDevice* indicator_power_device_new (const gchar * object_path, IndicatorPowerDevice* indicator_power_device_new_from_variant (GVariant * variant); -UpDeviceKind indicator_power_device_get_kind (const IndicatorPowerDevice * device); -UpDeviceState indicator_power_device_get_state (const IndicatorPowerDevice * device); -const gchar * indicator_power_device_get_object_path (const IndicatorPowerDevice * device); -gdouble indicator_power_device_get_percentage (const IndicatorPowerDevice * device); -time_t indicator_power_device_get_time (const IndicatorPowerDevice * device); +UpDeviceKind indicator_power_device_get_kind (const IndicatorPowerDevice * device); +UpDeviceState indicator_power_device_get_state (const IndicatorPowerDevice * device); +const gchar * indicator_power_device_get_object_path (const IndicatorPowerDevice * device); +gdouble indicator_power_device_get_percentage (const IndicatorPowerDevice * device); +time_t indicator_power_device_get_time (const IndicatorPowerDevice * device); -GStrv indicator_power_device_get_icon_names (const IndicatorPowerDevice * device); -GIcon * indicator_power_device_get_gicon (const IndicatorPowerDevice * device); +GStrv indicator_power_device_get_icon_names (const IndicatorPowerDevice * device); +GIcon * indicator_power_device_get_gicon (const IndicatorPowerDevice * device); -gchar * indicator_power_device_get_label (const IndicatorPowerDevice * device); -void indicator_power_device_get_header (const IndicatorPowerDevice * device, - gboolean show_time, - gboolean show_percentage, - gchar ** header, - gchar ** a11y); +char * indicator_power_device_get_readable_text (const IndicatorPowerDevice * device); + +char * indicator_power_device_get_accessible_text (const IndicatorPowerDevice * device); + +char * indicator_power_device_get_readable_title (const IndicatorPowerDevice * device, + gboolean want_time, + gboolean want_percent); + +char * indicator_power_device_get_accessible_title (const IndicatorPowerDevice * device, + gboolean want_time, + gboolean want_percent); G_END_DECLS diff --git a/src/ib-brightness-control.c b/src/ib-brightness-control.c index 4fb6bc5..67da10c 100644 --- a/src/ib-brightness-control.c +++ b/src/ib-brightness-control.c @@ -76,7 +76,7 @@ ib_brightness_control_set_value (IbBrightnessControl* self, gint value) gint fd; gchar *filename; gchar *svalue; - gint length; + size_t length; gint err; if (self->path == NULL) @@ -95,7 +95,7 @@ ib_brightness_control_set_value (IbBrightnessControl* self, gint value) err = errno; errno = 0; - if (write (fd, svalue, length) != length) { + if (write (fd, svalue, length) != (ssize_t)length) { g_warning ("Fail to write brightness information: %s", g_strerror(errno)); } errno = err; @@ -105,7 +105,7 @@ ib_brightness_control_set_value (IbBrightnessControl* self, gint value) g_free (filename); } -gint +static gint ib_brightness_control_get_value_from_file (IbBrightnessControl *self, const gchar *file) { GError *error; diff --git a/src/ib-brightness-uscreen-control.c b/src/ib-brightness-uscreen-control.c new file mode 100644 index 0000000..ad2c155 --- /dev/null +++ b/src/ib-brightness-uscreen-control.c @@ -0,0 +1,202 @@ +/* + * 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: + * Yuan-Chen Cheng <yc.cheng@canonical.com> + */ + +#include "ib-brightness-uscreen-control.h" + +static gboolean getBrightnessParams(GDBusProxy* powerd_proxy, int *dim, int *min, + int *max, int *dflt, gboolean *ab_supported); + +GDBusProxy* +uscreen_get_proxy(brightness_params_t *params) +{ + GError *error = NULL; + gboolean ret; + + g_return_val_if_fail (params != NULL, NULL); + + /* For now we still need to obtain the brigthness params from powerd */ + GDBusProxy* powerd_proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "com.canonical.powerd", + "/com/canonical/powerd", + "com.canonical.powerd", + NULL, + &error); + + if (error != NULL) + { + g_debug ("could not connect to powerd: %s", error->message); + g_error_free (error); + return NULL; + } + + ret = getBrightnessParams(powerd_proxy, &(params->dim), &(params->min), + &(params->max), &(params->dflt), &(params->ab_supported)); + + if (! ret) + { + g_debug ("can't get brightness parameters from powerd"); + g_object_unref (powerd_proxy); + return NULL; + } + + g_clear_object (&powerd_proxy); + + GDBusProxy* uscreen_proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "com.canonical.Unity.Screen", + "/com/canonical/Unity/Screen", + "com.canonical.Unity.Screen", + NULL, + &error); + + if (error != NULL) + { + g_debug ("could not connect to unity screen: %s", error->message); + g_error_free (error); + return NULL; + } + + return uscreen_proxy; +} + + +static gboolean +getBrightnessParams(GDBusProxy* powerd_proxy, int *dim, int *min, int *max, int *dflt, gboolean *ab_supported) +{ + GVariant *ret = NULL; + GError *error = NULL; + + ret = g_dbus_proxy_call_sync(powerd_proxy, + "getBrightnessParams", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 400, NULL, &error); // timeout: 400 ms + if (!ret) + { + if (error != NULL) + { + if (!g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) + { + g_warning("getBrightnessParams from powerd failed: %s", error->message); + } + g_error_free(error); + } + return FALSE; + } + + g_variant_get(ret, "((iiiib))", dim, min, max, dflt, ab_supported); + g_variant_unref(ret); + return TRUE; +} + +static gboolean setUserBrightness(GDBusProxy* uscreen_proxy, GCancellable *gcancel, int brightness) +{ + GVariant *ret = NULL; + GError *error = NULL; + + ret = g_dbus_proxy_call_sync(uscreen_proxy, + "setUserBrightness", + g_variant_new("(i)", brightness), + G_DBUS_CALL_FLAGS_NONE, + -1, gcancel, &error); + if (!ret) { + g_warning("setUserBrightness via unity.screen failed: %s", error->message); + g_error_free(error); + return FALSE; + } else { + g_variant_unref(ret); + return TRUE; + } +} + +struct _IbBrightnessUScreenControl +{ + GDBusProxy *uscreen_proxy; + GCancellable *gcancel; + + int dim; + int min; + int max; + int dflt; // defalut value + gboolean ab_supported; + + int current; +}; + +IbBrightnessUscreenControl* +ib_brightness_uscreen_control_new (GDBusProxy* uscreen_proxy, brightness_params_t params) +{ + IbBrightnessUscreenControl *control; + + control = g_new0 (IbBrightnessUscreenControl, 1); + control->uscreen_proxy = uscreen_proxy; + control->gcancel = g_cancellable_new(); + + control->dim = params.dim; + control->min = params.min; + control->max = params.max; + control->dflt = params.dflt; + control->ab_supported = params.ab_supported; + + // XXX: set the brightness value is the only way to sync the brightness value with + // unity.screen, and we should set the user prefered / last set brightness value upon startup. + // Before we have code to store last set brightness value or other mechanism, we set + // it to default brightness that powerd proposed. + ib_brightness_uscreen_control_set_value(control, control->dflt); + + return control; +} + +void +ib_brightness_uscreen_control_set_value (IbBrightnessUscreenControl* self, gint value) +{ + gboolean ret; + + value = CLAMP(value, self->min, self->max); + ret = setUserBrightness(self->uscreen_proxy, self->gcancel, value); + if (ret) + { + self->current = value; + } +} + +gint +ib_brightness_uscreen_control_get_value (IbBrightnessUscreenControl* self) +{ + return self->current; +} + +gint +ib_brightness_uscreen_control_get_max_value (IbBrightnessUscreenControl* self) +{ + return self->max; +} + +void +ib_brightness_uscreen_control_free (IbBrightnessUscreenControl *self) +{ + g_cancellable_cancel (self->gcancel); + g_object_unref (self->gcancel); + g_object_unref (self->uscreen_proxy); + g_free (self); +} + diff --git a/src/ib-brightness-uscreen-control.h b/src/ib-brightness-uscreen-control.h new file mode 100644 index 0000000..3d026a9 --- /dev/null +++ b/src/ib-brightness-uscreen-control.h @@ -0,0 +1,43 @@ +/* + * 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: + * Y.C Cheng <yc.cheng@canonical.com> + */ + +#ifndef __IB_BRIGHTNESS_USCREEN_CONTROL_H__ +#define __IB_BRIGHTNESS_USCREEN_CONTROL_H__ + +#include <gio/gio.h> + +typedef struct { + int dim; + int min; + int max; + int dflt; + gboolean ab_supported; +} brightness_params_t; + +GDBusProxy* uscreen_get_proxy(brightness_params_t *); + +typedef struct _IbBrightnessUScreenControl IbBrightnessUscreenControl; + +IbBrightnessUscreenControl* ib_brightness_uscreen_control_new (GDBusProxy* uscreen_proxy, brightness_params_t params); +void ib_brightness_uscreen_control_set_value (IbBrightnessUscreenControl* self, gint value); +gint ib_brightness_uscreen_control_get_value (IbBrightnessUscreenControl* self); +gint ib_brightness_uscreen_control_get_max_value (IbBrightnessUscreenControl* self); +void ib_brightness_uscreen_control_free (IbBrightnessUscreenControl *self); + +#endif @@ -17,8 +17,6 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "config.h" - #include <locale.h> #include <stdlib.h> /* exit() */ @@ -43,9 +41,9 @@ on_name_lost (gpointer instance G_GNUC_UNUSED, gpointer loop) int main (int argc G_GNUC_UNUSED, char ** argv G_GNUC_UNUSED) { - GMainLoop * loop; - IndicatorPowerService * service; IndicatorPowerDeviceProvider * device_provider; + IndicatorPowerService * service; + GMainLoop * loop; /* boilerplate i18n */ setlocale (LC_ALL, ""); @@ -61,8 +59,8 @@ main (int argc G_GNUC_UNUSED, char ** argv G_GNUC_UNUSED) g_main_loop_run (loop); /* cleanup */ - g_clear_object (&device_provider); - g_clear_object (&service); g_main_loop_unref (loop); + g_clear_object (&service); + g_clear_object (&device_provider); return 0; } diff --git a/src/notifier.c b/src/notifier.c new file mode 100644 index 0000000..81cd6f1 --- /dev/null +++ b/src/notifier.c @@ -0,0 +1,445 @@ +/* + * 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 "dbus-battery.h" +#include "dbus-shared.h" +#include "notifier.h" + +#include <libnotify/notify.h> + +#include <glib/gi18n.h> + +#define HINT_INTERACTIVE "x-canonical-switch-to-application" + +typedef enum +{ + POWER_LEVEL_CRITICAL, + POWER_LEVEL_VERY_LOW, + POWER_LEVEL_LOW, + POWER_LEVEL_OK +} +PowerLevel; + +/** +*** GObject Properties +**/ + +enum +{ + PROP_0, + PROP_BATTERY, + LAST_PROP +}; + +#define PROP_BATTERY_NAME "battery" + +static GParamSpec * properties[LAST_PROP]; + +static int instance_count = 0; + +/** +*** +**/ + +typedef struct +{ + /* The battery we're currently watching. + This may be a physical battery or it may be an aggregated + battery from multiple batteries present on the device. + See indicator_power_service_choose_primary_device() and + bug #880881 */ + IndicatorPowerDevice * battery; + PowerLevel power_level; + gboolean discharging; + + NotifyNotification * notify_notification; + + GDBusConnection * bus; + DbusBattery * dbus_battery; /* com.canonical.indicator.power.Battery skeleton */ +} +IndicatorPowerNotifierPrivate; + +typedef IndicatorPowerNotifierPrivate priv_t; + +G_DEFINE_TYPE_WITH_PRIVATE(IndicatorPowerNotifier, + indicator_power_notifier, + G_TYPE_OBJECT) + +#define get_priv(o) ((priv_t*)indicator_power_notifier_get_instance_private(o)) + +/*** +**** +***/ + +static const char * +power_level_to_dbus_string (const PowerLevel power_level) +{ + switch (power_level) + { + case POWER_LEVEL_LOW: return POWER_LEVEL_STR_LOW; + case POWER_LEVEL_VERY_LOW: return POWER_LEVEL_STR_VERY_LOW; + case POWER_LEVEL_CRITICAL: return POWER_LEVEL_STR_CRITICAL; + default: return POWER_LEVEL_STR_OK; + } +} + +PowerLevel +get_battery_power_level (IndicatorPowerDevice * battery) +{ + static const double percent_critical = 2.0; + static const double percent_very_low = 5.0; + static const double percent_low = 10.0; + gdouble p; + PowerLevel ret; + + g_return_val_if_fail(battery != NULL, POWER_LEVEL_OK); + g_return_val_if_fail(indicator_power_device_get_kind(battery) == UP_DEVICE_KIND_BATTERY, POWER_LEVEL_OK); + + p = indicator_power_device_get_percentage(battery); + + if (p <= percent_critical) + ret = POWER_LEVEL_CRITICAL; + else if (p <= percent_very_low) + ret = POWER_LEVEL_VERY_LOW; + else if (p <= percent_low) + ret = POWER_LEVEL_LOW; + else + ret = POWER_LEVEL_OK; + + return ret; +} + +/*** +**** Notifications +***/ + +static void +on_notify_notification_finalized (gpointer gself, GObject * dead) +{ + IndicatorPowerNotifier * const self = INDICATOR_POWER_NOTIFIER(gself); + priv_t * const p = get_priv(self); + g_return_if_fail ((void*)(p->notify_notification) == (void*)dead); + p->notify_notification = NULL; + dbus_battery_set_is_warning (p->dbus_battery, FALSE); +} + +static void +notification_clear (IndicatorPowerNotifier * self) +{ + priv_t * const p = get_priv(self); + NotifyNotification * nn; + + if ((nn = p->notify_notification)) + { + GError * error = NULL; + + g_object_weak_unref(G_OBJECT(nn), on_notify_notification_finalized, self); + + if (!notify_notification_close(nn, &error)) + { + g_warning("Unable to close notification: %s", error->message); + g_error_free(error); + } + + p->notify_notification = NULL; + dbus_battery_set_is_warning (p->dbus_battery, FALSE); + } +} + +static void +notification_show(IndicatorPowerNotifier * self) +{ + priv_t * const p = get_priv(self); + gdouble pct; + char * body; + NotifyNotification * nn; + GError * error; + + notification_clear(self); + + /* create the notification */ + pct = indicator_power_device_get_percentage(p->battery); + body = g_strdup_printf(_("%.0f%% charge remaining"), pct); + nn = notify_notification_new(_("Battery Low"), body, NULL); + g_free (body); + /*notify_notification_set_hint(nn, HINT_INTERACTIVE, g_variant_new_boolean(TRUE));*/ + + /* if we can show it, keep it */ + error = NULL; + if (notify_notification_show(nn, &error)) + { + p->notify_notification = nn; + g_signal_connect(nn, "closed", G_CALLBACK(g_object_unref), NULL); + g_object_weak_ref(G_OBJECT(nn), on_notify_notification_finalized, self); + dbus_battery_set_is_warning (p->dbus_battery, TRUE); + } + else + { + g_critical("Unable to show snap decision for '%s': %s", body, error->message); + g_error_free(error); + g_object_unref(nn); + } +} + +/*** +**** +***/ + +static void +on_battery_property_changed (IndicatorPowerNotifier * self) +{ + priv_t * p; + PowerLevel old_power_level; + PowerLevel new_power_level; + gboolean old_discharging; + gboolean new_discharging; + + g_return_if_fail(INDICATOR_IS_POWER_NOTIFIER(self)); + p = get_priv (self); + g_return_if_fail(INDICATOR_IS_POWER_DEVICE(p->battery)); + + old_power_level = p->power_level; + new_power_level = get_battery_power_level (p->battery); + + old_discharging = p->discharging; + new_discharging = indicator_power_device_get_state(p->battery) == UP_DEVICE_STATE_DISCHARGING; + + /* pop up a 'low battery' notification if either: + a) it's already discharging, and its PowerLevel worsens, OR + b) it's already got a bad PowerLevel and its state becomes 'discharging */ + if ((new_discharging && (old_power_level > new_power_level)) || + ((new_power_level != POWER_LEVEL_OK) && new_discharging && !old_discharging)) + { + notification_show (self); + } + else if (!new_discharging || (new_power_level == POWER_LEVEL_OK)) + { + notification_clear (self); + } + + dbus_battery_set_power_level (p->dbus_battery, power_level_to_dbus_string (new_power_level)); + p->power_level = new_power_level; + p->discharging = new_discharging; +} + +/*** +**** GObject virtual functions +***/ + +static void +my_get_property (GObject * o, + guint property_id, + GValue * value, + GParamSpec * pspec) +{ + IndicatorPowerNotifier * const self = INDICATOR_POWER_NOTIFIER (o); + priv_t * const p = get_priv (self); + + switch (property_id) + { + case PROP_BATTERY: + g_value_set_object (value, p->battery); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); + } +} + +static void +my_set_property (GObject * o, + guint property_id, + const GValue * value, + GParamSpec * pspec) +{ + IndicatorPowerNotifier * const self = INDICATOR_POWER_NOTIFIER (o); + + switch (property_id) + { + case PROP_BATTERY: + indicator_power_notifier_set_battery (self, g_value_get_object(value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); + } +} + +static void +my_dispose (GObject * o) +{ + IndicatorPowerNotifier * const self = INDICATOR_POWER_NOTIFIER(o); + priv_t * const p = get_priv (self); + + indicator_power_notifier_set_bus (self, NULL); + notification_clear (self); + indicator_power_notifier_set_battery (self, NULL); + g_clear_object (&p->dbus_battery); + + G_OBJECT_CLASS (indicator_power_notifier_parent_class)->dispose (o); +} + +static void +my_finalize (GObject * o G_GNUC_UNUSED) +{ + /* FIXME: This is an awkward place to put this. + Ordinarily something like this would go in main(), but we need libnotify + to clean itself up before shutting down the bus in the unit tests as well. */ + if (!--instance_count) + notify_uninit(); +} + +/*** +**** Instantiation +***/ + +static void +indicator_power_notifier_init (IndicatorPowerNotifier * self) +{ + priv_t * const p = get_priv (self); + + /* bind the read-only properties so they'll get pushed to the bus */ + + p->dbus_battery = dbus_battery_skeleton_new (); + + p->power_level = POWER_LEVEL_OK; + + if (!instance_count++) + { + if (!notify_init("indicator-power-service")) + { + g_critical("Unable to initialize libnotify! Notifications might not be shown."); + } + } +} + +static void +indicator_power_notifier_class_init (IndicatorPowerNotifierClass * klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = my_dispose; + object_class->finalize = my_finalize; + object_class->get_property = my_get_property; + object_class->set_property = my_set_property; + + properties[PROP_BATTERY] = g_param_spec_object ( + PROP_BATTERY_NAME, + "Battery", + "The current battery", + G_TYPE_OBJECT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, LAST_PROP, properties); +} + +/*** +**** Public API +***/ + +IndicatorPowerNotifier * +indicator_power_notifier_new (void) +{ + GObject * o = g_object_new (INDICATOR_TYPE_POWER_NOTIFIER, NULL); + + return INDICATOR_POWER_NOTIFIER (o); +} + +void +indicator_power_notifier_set_battery (IndicatorPowerNotifier * self, + IndicatorPowerDevice * battery) +{ + priv_t * p; + + g_return_if_fail(INDICATOR_IS_POWER_NOTIFIER(self)); + g_return_if_fail((battery == NULL) || INDICATOR_IS_POWER_DEVICE(battery)); + g_return_if_fail((battery == NULL) || (indicator_power_device_get_kind(battery) == UP_DEVICE_KIND_BATTERY)); + + p = get_priv (self); + + if (p->battery == battery) + return; + + if (p->battery != NULL) + { + g_signal_handlers_disconnect_by_data (p->battery, self); + g_clear_object (&p->battery); + dbus_battery_set_power_level (p->dbus_battery, power_level_to_dbus_string (POWER_LEVEL_OK)); + notification_clear (self); + } + + if (battery != NULL) + { + p->battery = g_object_ref (battery); + g_signal_connect_swapped (p->battery, "notify::"INDICATOR_POWER_DEVICE_PERCENTAGE, + G_CALLBACK(on_battery_property_changed), self); + g_signal_connect_swapped (p->battery, "notify::"INDICATOR_POWER_DEVICE_STATE, + G_CALLBACK(on_battery_property_changed), self); + on_battery_property_changed (self); + } +} + +void +indicator_power_notifier_set_bus (IndicatorPowerNotifier * self, + GDBusConnection * bus) +{ + priv_t * p; + GDBusInterfaceSkeleton * skel; + + g_return_if_fail(INDICATOR_IS_POWER_NOTIFIER(self)); + g_return_if_fail((bus == NULL) || G_IS_DBUS_CONNECTION(bus)); + + p = get_priv (self); + + if (p->bus == bus) + return; + + skel = G_DBUS_INTERFACE_SKELETON(p->dbus_battery); + + if (p->bus != NULL) + { + if (skel != NULL) + g_dbus_interface_skeleton_unexport (skel); + + g_clear_object (&p->bus); + } + + if (bus != NULL) + { + GError * error; + + p->bus = g_object_ref (bus); + + error = NULL; + if (!g_dbus_interface_skeleton_export(skel, + bus, + BUS_PATH"/Battery", + &error)) + { + g_warning ("Unable to export LowBattery properties: %s", error->message); + g_error_free (error); + } + } +} + +const char * +indicator_power_notifier_get_power_level (IndicatorPowerDevice * battery) +{ + return power_level_to_dbus_string (get_battery_power_level (battery)); +} diff --git a/src/notifier.h b/src/notifier.h new file mode 100644 index 0000000..18e25d7 --- /dev/null +++ b/src/notifier.h @@ -0,0 +1,74 @@ +/* + * 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_POWER_NOTIFIER_H__ +#define __INDICATOR_POWER_NOTIFIER_H__ + +#include <gio/gio.h> + +#include "device.h" + +G_BEGIN_DECLS + +/* standard GObject macros */ +#define INDICATOR_POWER_NOTIFIER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), INDICATOR_TYPE_POWER_NOTIFIER, IndicatorPowerNotifier)) +#define INDICATOR_TYPE_POWER_NOTIFIER (indicator_power_notifier_get_type()) +#define INDICATOR_IS_POWER_NOTIFIER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), INDICATOR_TYPE_POWER_NOTIFIER)) +#define INDICATOR_POWER_NOTIFIER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), INDICATOR_TYPE_POWER_NOTIFIER, IndicatorPowerNotifierClass)) + +typedef struct _IndicatorPowerNotifier IndicatorPowerNotifier; +typedef struct _IndicatorPowerNotifierClass IndicatorPowerNotifierClass; + +/** + * The Indicator Power Notifier. + */ +struct _IndicatorPowerNotifier +{ + /*< private >*/ + GObject parent; +}; + +struct _IndicatorPowerNotifierClass +{ + GObjectClass parent_class; +}; + +/*** +**** +***/ + +GType indicator_power_notifier_get_type (void); + +IndicatorPowerNotifier * indicator_power_notifier_new (void); + +void indicator_power_notifier_set_bus (IndicatorPowerNotifier * self, + GDBusConnection * connection); + +void indicator_power_notifier_set_battery (IndicatorPowerNotifier * self, + IndicatorPowerDevice * battery); + +#define POWER_LEVEL_STR_OK "ok" +#define POWER_LEVEL_STR_LOW "low" +#define POWER_LEVEL_STR_VERY_LOW "very_low" +#define POWER_LEVEL_STR_CRITICAL "critical" +const char * indicator_power_notifier_get_power_level (IndicatorPowerDevice * battery); + +G_END_DECLS + +#endif /* __INDICATOR_POWER_NOTIFIER_H__ */ diff --git a/src/service.c b/src/service.c index 982a24e..0cd448b 100644 --- a/src/service.c +++ b/src/service.c @@ -18,15 +18,16 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "config.h" - #include <glib/gi18n.h> #include <gio/gio.h> #include <url-dispatcher.h> +#include "dbus-shared.h" #include "device.h" #include "device-provider.h" +#include "notifier.h" #include "ib-brightness-control.h" +#include "ib-brightness-uscreen-control.h" #include "service.h" #define BUS_NAME "com.canonical.indicator.power" @@ -104,6 +105,7 @@ struct _IndicatorPowerServicePrivate GSettings * settings; IbBrightnessControl * brightness_control; + IbBrightnessUscreenControl * brightness_uscreen_control; guint own_id; guint actions_export_id; @@ -120,6 +122,7 @@ struct _IndicatorPowerServicePrivate GList * devices; /* IndicatorPowerDevice */ IndicatorPowerDeviceProvider * device_provider; + IndicatorPowerNotifier * notifier; }; typedef IndicatorPowerServicePrivate priv_t; @@ -312,21 +315,7 @@ static GVariant * create_header_state (IndicatorPowerService * self) { GVariantBuilder b; - gchar * label = NULL; - gchar * a11y = NULL; - GIcon * icon = NULL; - priv_t * p = self->priv; - - if (p->primary_device != NULL) - { - indicator_power_device_get_header (p->primary_device, - g_settings_get_boolean (p->settings, SETTINGS_SHOW_TIME_S), - g_settings_get_boolean (p->settings, SETTINGS_SHOW_PERCENTAGE_S), - &label, - &a11y); - - icon = indicator_power_device_get_gicon (p->primary_device); - } + const priv_t * const p = self->priv; g_variant_builder_init (&b, G_VARIANT_TYPE("a{sv}")); @@ -335,24 +324,48 @@ create_header_state (IndicatorPowerService * self) g_variant_builder_add (&b, "{sv}", "visible", g_variant_new_boolean (should_be_visible (self))); - if (label != NULL) - g_variant_builder_add (&b, "{sv}", "label", g_variant_new_take_string (label)); - - if (icon != NULL) + if (p->primary_device != NULL) { - GVariant * v; + char * title; + GIcon * icon; + const gboolean want_time = g_settings_get_boolean (p->settings, SETTINGS_SHOW_TIME_S); + const gboolean want_percent = g_settings_get_boolean (p->settings, SETTINGS_SHOW_PERCENTAGE_S); + + title = indicator_power_device_get_readable_title (p->primary_device, + want_time, + want_percent); + if (title) + { + if (*title) + g_variant_builder_add (&b, "{sv}", "label", g_variant_new_take_string (title)); + else + g_free (title); + } - if ((v = g_icon_serialize (icon))) + title = indicator_power_device_get_accessible_title (p->primary_device, + want_time, + want_percent); + if (title) { - g_variant_builder_add (&b, "{sv}", "icon", v); - g_variant_unref (v); + if (*title) + g_variant_builder_add (&b, "{sv}", "accessible-desc", g_variant_new_take_string (title)); + else + g_free (title); } - g_object_unref (icon); - } + if ((icon = indicator_power_device_get_gicon (p->primary_device))) + { + GVariant * serialized_icon = g_icon_serialize (icon); - if (a11y != NULL) - g_variant_builder_add (&b, "{sv}", "accessible-desc", g_variant_new_take_string (a11y)); + if (serialized_icon != NULL) + { + g_variant_builder_add (&b, "{sv}", "icon", serialized_icon); + g_variant_unref (serialized_icon); + } + + g_object_unref (icon); + } + } return g_variant_builder_end (&b); } @@ -365,7 +378,7 @@ create_header_state (IndicatorPowerService * self) ***/ static void -append_device_to_menu (GMenu * menu, const IndicatorPowerDevice * device) +append_device_to_menu (GMenu * menu, const IndicatorPowerDevice * device, int profile) { const UpDeviceKind kind = indicator_power_device_get_kind (device); @@ -375,24 +388,33 @@ append_device_to_menu (GMenu * menu, const IndicatorPowerDevice * device) GMenuItem * item; GIcon * icon; - label = indicator_power_device_get_label (device); - item = g_menu_item_new (label, "indicator.activate-statistics"); + label = indicator_power_device_get_readable_text (device); + item = g_menu_item_new (label, NULL); g_free (label); - g_menu_item_set_action_and_target(item, "indicator.activate-statistics", "s", - indicator_power_device_get_object_path (device)); + + g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.basic"); if ((icon = indicator_power_device_get_gicon (device))) { - GVariant * v; - if ((v = g_icon_serialize (icon))) + GVariant * serialized_icon = g_icon_serialize (icon); + + if (serialized_icon != NULL) { - g_menu_item_set_attribute_value (item, G_MENU_ATTRIBUTE_ICON, v); - g_variant_unref (v); + g_menu_item_set_attribute_value (item, + G_MENU_ATTRIBUTE_ICON, + serialized_icon); + g_variant_unref (serialized_icon); } g_object_unref (icon); } + if (profile == PROFILE_DESKTOP) + { + g_menu_item_set_action_and_target(item, "indicator.activate-statistics", "s", + indicator_power_device_get_object_path (device)); + } + g_menu_append_item (menu, item); g_object_unref (item); } @@ -400,13 +422,13 @@ append_device_to_menu (GMenu * menu, const IndicatorPowerDevice * device) static GMenuModel * -create_desktop_devices_section (IndicatorPowerService * self) +create_desktop_devices_section (IndicatorPowerService * self, int profile) { GList * l; GMenu * menu = g_menu_new (); for (l=self->priv->devices; l!=NULL; l=l->next) - append_device_to_menu (menu, l->data); + append_device_to_menu (menu, l->data, profile); return G_MENU_MODEL (menu); } @@ -440,8 +462,17 @@ create_phone_devices_section (IndicatorPowerService * self G_GNUC_UNUSED) static void get_brightness_range (IndicatorPowerService * self, gint * low, gint * high) { - const int max = ib_brightness_control_get_max_value (self->priv->brightness_control); - *low = max * 0.05; /* 5% minimum -- don't let the screen go completely dark */ + priv_t * p = self->priv; + int max = 0; + if (p->brightness_control) + { + max = ib_brightness_control_get_max_value (self->priv->brightness_control); + } + else if (p->brightness_uscreen_control) + { + max = ib_brightness_uscreen_control_get_max_value (self->priv->brightness_uscreen_control); + } + *low = (gint)(max * 0.05); /* 5% minimum -- don't let the screen go completely dark */ *high = max; } @@ -461,29 +492,19 @@ percentage_to_brightness (IndicatorPowerService * self, double percentage) return (int)(lo + (percentage*(hi-lo))); } -static GMenuItem * -create_brightness_menuitem (IndicatorPowerService * self) -{ - int lo, hi; - GMenuItem * item; - - get_brightness_range (self, &lo, &hi); - - item = g_menu_item_new (NULL, "indicator.brightness"); - g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.unity.slider"); - g_menu_item_set_attribute (item, "min-value", "d", brightness_to_percentage (self, lo)); - g_menu_item_set_attribute (item, "max-value", "d", brightness_to_percentage (self, hi)); - g_menu_item_set_attribute (item, "min-icon", "s", "torch-off" ); - g_menu_item_set_attribute (item, "max-icon", "s", "torch-on" ); - - return item; -} - static GVariant * action_state_for_brightness (IndicatorPowerService * self) { priv_t * p = self->priv; - const gint brightness = ib_brightness_control_get_value (p->brightness_control); + gint brightness = 0; + if (p->brightness_control) + { + brightness = ib_brightness_control_get_value (p->brightness_control); + } + else if (p->brightness_uscreen_control) + { + brightness = ib_brightness_uscreen_control_get_value (p->brightness_uscreen_control); + } return g_variant_new_double (brightness_to_percentage (self, brightness)); } @@ -502,7 +523,16 @@ on_brightness_change_requested (GSimpleAction * action G_GNUC_UNUSED, IndicatorPowerService * self = INDICATOR_POWER_SERVICE (gself); const gdouble percentage = g_variant_get_double (parameter); const int brightness = percentage_to_brightness (self, percentage); - ib_brightness_control_set_value (self->priv->brightness_control, brightness); + + if (self->priv->brightness_control) + { + ib_brightness_control_set_value (self->priv->brightness_control, brightness); + } + else if (self->priv->brightness_uscreen_control) + { + ib_brightness_uscreen_control_set_value (self->priv->brightness_uscreen_control, brightness); + } + update_brightness_action_state (self); } @@ -527,18 +557,12 @@ create_desktop_settings_section (IndicatorPowerService * self G_GNUC_UNUSED) } static GMenuModel * -create_phone_settings_section (IndicatorPowerService * self G_GNUC_UNUSED) +create_phone_settings_section (IndicatorPowerService * self) { GMenu * section; - GMenuItem * item; section = g_menu_new (); - - item = create_brightness_menuitem (self); - g_menu_append_item (section, item); update_brightness_action_state (self); - g_object_unref (item); - g_menu_append (section, _("Battery settings…"), "indicator.activate-phone-settings"); return G_MENU_MODEL (section); @@ -571,18 +595,18 @@ rebuild_now (IndicatorPowerService * self, guint sections) struct ProfileMenuInfo * desktop = &p->menus[PROFILE_DESKTOP]; struct ProfileMenuInfo * greeter = &p->menus[PROFILE_DESKTOP_GREETER]; - if (p->conn == NULL) /* we haven't built the menus yet */ - return; - if (sections & SECTION_HEADER) { g_simple_action_set_state (p->header_action, create_header_state (self)); } + if (p->conn == NULL) /* we haven't built the menus yet */ + return; + if (sections & SECTION_DEVICES) { - rebuild_section (desktop->submenu, 0, create_desktop_devices_section (self)); - rebuild_section (greeter->submenu, 0, create_desktop_devices_section (self)); + rebuild_section (desktop->submenu, 0, create_desktop_devices_section (self, PROFILE_DESKTOP)); + rebuild_section (greeter->submenu, 0, create_desktop_devices_section (self, PROFILE_DESKTOP_GREETER)); } if (sections & SECTION_SETTINGS) @@ -598,18 +622,6 @@ rebuild_header_now (IndicatorPowerService * self) rebuild_now (self, SECTION_HEADER); } -static inline void -rebuild_devices_section_now (IndicatorPowerService * self) -{ - rebuild_now (self, SECTION_DEVICES); -} - -static inline void -rebuild_settings_section_now (IndicatorPowerService * self) -{ - rebuild_now (self, SECTION_SETTINGS); -} - static void create_menu (IndicatorPowerService * self, int profile) { @@ -633,12 +645,12 @@ create_menu (IndicatorPowerService * self, int profile) break; case PROFILE_DESKTOP: - sections[n++] = create_desktop_devices_section (self); + sections[n++] = create_desktop_devices_section (self, PROFILE_DESKTOP); sections[n++] = create_desktop_settings_section (self); break; case PROFILE_DESKTOP_GREETER: - sections[n++] = create_desktop_devices_section (self); + sections[n++] = create_desktop_devices_section (self, PROFILE_DESKTOP_GREETER); break; } @@ -812,6 +824,9 @@ on_bus_acquired (GDBusConnection * connection, p->conn = g_object_ref (G_OBJECT (connection)); + /* export the battery properties */ + indicator_power_notifier_set_bus (p->notifier, connection); + /* export the actions */ if ((id = g_dbus_connection_export_action_group (connection, BUS_PATH, @@ -911,11 +926,17 @@ on_devices_changed (IndicatorPowerService * self) g_clear_object (&p->primary_device); p->primary_device = indicator_power_service_choose_primary_device (p->devices); + /* update the notifier's battery */ + if ((p->primary_device != NULL) && (indicator_power_device_get_kind(p->primary_device) == UP_DEVICE_KIND_BATTERY)) + indicator_power_notifier_set_battery (p->notifier, p->primary_device); + else + indicator_power_notifier_set_battery (p->notifier, NULL); + /* update the battery-level action's state */ if (p->primary_device == NULL) battery_level = 0; else - battery_level = (int)(indicator_power_device_get_percentage (p->primary_device) + 0.5); + battery_level = (guint32)(indicator_power_device_get_percentage (p->primary_device) + 0.5); g_simple_action_set_state (p->battery_level_action, g_variant_new_uint32 (battery_level)); rebuild_now (self, SECTION_HEADER | SECTION_DEVICES); @@ -992,6 +1013,7 @@ my_dispose (GObject * o) g_clear_object (&p->settings); } + g_clear_object (&p->notifier); g_clear_object (&p->brightness_action); g_clear_object (&p->battery_level_action); g_clear_object (&p->header_action); @@ -999,7 +1021,9 @@ my_dispose (GObject * o) g_clear_object (&p->conn); + // g_clear_pointer has NULL check inside. g_clear_pointer (&p->brightness_control, ib_brightness_control_free); + g_clear_pointer (&p->brightness_uscreen_control, ib_brightness_uscreen_control_free); indicator_power_service_set_device_provider (self, NULL); @@ -1013,6 +1037,8 @@ my_dispose (GObject * o) static void indicator_power_service_init (IndicatorPowerService * self) { + GDBusProxy *uscreen_proxy; + brightness_params_t brightness_params; priv_t * p = G_TYPE_INSTANCE_GET_PRIVATE (self, INDICATOR_TYPE_POWER_SERVICE, IndicatorPowerServicePrivate); @@ -1022,7 +1048,17 @@ indicator_power_service_init (IndicatorPowerService * self) p->settings = g_settings_new ("com.canonical.indicator.power"); - p->brightness_control = ib_brightness_control_new (); + p->notifier = indicator_power_notifier_new (); + + uscreen_proxy = uscreen_get_proxy(&brightness_params); + if (uscreen_proxy != NULL) + { + p->brightness_uscreen_control = ib_brightness_uscreen_control_new(uscreen_proxy, brightness_params); + } + else + { + p->brightness_control = ib_brightness_control_new (); + } init_gactions (self); @@ -1096,9 +1132,8 @@ indicator_power_service_set_device_provider (IndicatorPowerService * self, if (p->device_provider != NULL) { - g_signal_handlers_disconnect_by_func (p->device_provider, - G_CALLBACK(on_devices_changed), - self); + g_signal_handlers_disconnect_by_data (p->device_provider, self); + g_clear_object (&p->device_provider); g_clear_object (&p->primary_device); @@ -1118,6 +1153,129 @@ indicator_power_service_set_device_provider (IndicatorPowerService * self, } } +/* If a device has multiple batteries and uses only one of them at a time, + they should be presented as separate items inside the battery menu, + but everywhere else they should be aggregated (bug 880881). + Their percentages should be averaged. If any are discharging, + the aggregated time remaining should be the maximum of the times + for all those that are discharging, plus the sum of the times + for all those that are idle. Otherwise, the aggregated time remaining + should be the the maximum of the times for all those that are charging. */ +static IndicatorPowerDevice * +create_totalled_battery_device (const GList * devices) +{ + const GList * l; + guint n_charged = 0; + guint n_charging = 0; + guint n_discharging = 0; + guint n_batteries = 0; + double sum_percent = 0; + time_t max_discharge_time = 0; + time_t max_charge_time = 0; + time_t sum_charged_time = 0; + IndicatorPowerDevice * device = NULL; + + for (l=devices; l!=NULL; l=l->next) + { + const IndicatorPowerDevice * walk = INDICATOR_POWER_DEVICE(l->data); + + if (indicator_power_device_get_kind(walk) == UP_DEVICE_KIND_BATTERY) + { + const double percent = indicator_power_device_get_percentage (walk); + const time_t t = indicator_power_device_get_time (walk); + const UpDeviceState state = indicator_power_device_get_state (walk); + + ++n_batteries; + + if (percent > 0.01) + sum_percent += percent; + + if (state == UP_DEVICE_STATE_CHARGING) + { + ++n_charging; + max_charge_time = MAX(max_charge_time, t); + } + else if (state == UP_DEVICE_STATE_DISCHARGING) + { + ++n_discharging; + max_discharge_time = MAX(max_discharge_time, t); + } + else if (state == UP_DEVICE_STATE_FULLY_CHARGED) + { + ++n_charged; + sum_charged_time += t; + } + } + } + + if (n_batteries > 1) + { + const double percent = sum_percent / n_batteries; + UpDeviceState state; + time_t time_left; + + if (n_discharging > 0) + { + state = UP_DEVICE_STATE_DISCHARGING; + time_left = max_discharge_time + sum_charged_time; + } + else if (n_charging > 0) + { + state = UP_DEVICE_STATE_CHARGING; + time_left = max_charge_time; + } + else if (n_charged > 0) + { + state = UP_DEVICE_STATE_FULLY_CHARGED; + time_left = 0; + } + else + { + state = UP_DEVICE_STATE_UNKNOWN; + time_left = 0; + } + + device = indicator_power_device_new (NULL, + UP_DEVICE_KIND_BATTERY, + percent, + state, + time_left); + } + + return device; +} + +/** + * If there are multiple UP_DEVICE_KIND_BATTERY devices in the list, + * they're merged into a new 'totalled' device representing the sum of them. + * + * Returns: (element-type IndicatorPowerDevice)(transfer full): a list of devices + */ +static GList* +merge_batteries_together (GList * devices) +{ + GList * ret; + IndicatorPowerDevice * merged_device; + + if ((merged_device = create_totalled_battery_device (devices))) + { + GList * l; + + ret = g_list_append (NULL, merged_device); + + for (l=devices; l!=NULL; l=l->next) + if (indicator_power_device_get_kind(INDICATOR_POWER_DEVICE(l->data)) != UP_DEVICE_KIND_BATTERY) + ret = g_list_append (ret, g_object_ref(l->data)); + } + else /* not enough batteries to merge */ + { + ret = g_list_copy (devices); + g_list_foreach (ret, (GFunc)g_object_ref, NULL); + } + + return ret; +} + IndicatorPowerDevice * indicator_power_service_choose_primary_device (GList * devices) { @@ -1125,13 +1283,10 @@ indicator_power_service_choose_primary_device (GList * devices) if (devices != NULL) { - GList * tmp; - - tmp = g_list_copy (devices); + GList * tmp = merge_batteries_together (devices); tmp = g_list_sort (tmp, device_compare_func); primary = g_object_ref (tmp->data); - - g_list_free (tmp); + g_list_free_full (tmp, (GDestroyNotify)g_object_unref); } return primary; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..a0d24af --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,61 @@ +# 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) + +# dbustest +pkg_check_modules(DBUSTEST REQUIRED + dbustest-1>=14.04.0) +include_directories (SYSTEM ${DBUSTEST_INCLUDE_DIRS}) + +# add warnings +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g ${C_WARNING_ARGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-weak-vtables -Wno-global-constructors") # Google Test + +# build the necessary schemas +set_directory_properties (PROPERTIES + ADDITIONAL_MAKE_CLEAN_FILES gschemas.compiled) +set_source_files_properties (gschemas.compiled GENERATED) + +# GSettings: +# compile the indicator-power schema into a gschemas.compiled file in this directory, +# and help the tests to find that file by setting -DSCHEMA_DIR +set (SCHEMA_DIR ${CMAKE_CURRENT_BINARY_DIR}) +add_definitions(-DSCHEMA_DIR="${SCHEMA_DIR}") +execute_process (COMMAND ${PKG_CONFIG_EXECUTABLE} gio-2.0 --variable glib_compile_schemas + OUTPUT_VARIABLE COMPILE_SCHEMA_EXECUTABLE + OUTPUT_STRIP_TRAILING_WHITESPACE) +add_custom_command (OUTPUT gschemas.compiled + DEPENDS ${CMAKE_BINARY_DIR}/data/com.canonical.indicator.power.gschema.xml + COMMAND cp -f ${CMAKE_BINARY_DIR}/data/*gschema.xml ${SCHEMA_DIR} + COMMAND ${COMPILE_SCHEMA_EXECUTABLE} ${SCHEMA_DIR}) + +# 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_BINARY_DIR}/src) +include_directories (${CMAKE_CURRENT_BINARY_DIR}) + +### +### + +function(add_test_by_name name) + set (TEST_NAME ${name}) + add_executable (${TEST_NAME} ${TEST_NAME}.cc gschemas.compiled) + add_test (${TEST_NAME} ${TEST_NAME}) + add_dependencies (${TEST_NAME} libindicatorpowerservice) + target_link_libraries (${TEST_NAME} indicatorpowerservice gtest ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS}) +endfunction() +add_test_by_name(test-notify) +add_test(NAME dear-reader-the-next-test-takes-80-seconds COMMAND true) +add_test_by_name(test-device) + +### +### + +set (APP_NAME indicator-power-service-cmdline-battery) +add_executable (${APP_NAME} ${APP_NAME}.cc device-provider-mock.c) +add_dependencies (${APP_NAME} libindicatorpowerservice) +target_link_libraries (${APP_NAME} indicatorpowerservice ${SERVICE_DEPS_LIBRARIES}) + diff --git a/tests/Makefile.am b/tests/Makefile.am deleted file mode 100644 index 5c7d802..0000000 --- a/tests/Makefile.am +++ /dev/null @@ -1,64 +0,0 @@ -TESTS = -CLEANFILES = -BUILT_SOURCES = -check_PROGRAMS = - -### -### tests: stock tests on user-visible strings -### - -include $(srcdir)/Makefile.am.strings - -### -### gtest library -### - -check_LIBRARIES = libgtest.a -nodist_libgtest_a_SOURCES = \ - $(GTEST_SOURCE)/src/gtest-all.cc \ - $(GTEST_SOURCE)/src/gtest_main.cc - -AM_CPPFLAGS = $(GTEST_CPPFLAGS) -I${top_srcdir}/src -Wall -Werror -AM_CXXFLAGS = $(GTEST_CXXFLAGS) - -### -### tests: indicator-power-device -### - -TEST_LIBS = $(COVERAGE_LDFLAGS) libgtest.a -lpthread -TEST_CPPFLAGS = $(SERVICE_DEPS_CFLAGS) $(AM_CPPFLAGS) - -TESTS += test-device -check_PROGRAMS += test-device -test_device_SOURCES = test-device.cc -test_device_CPPFLAGS = $(TEST_CPPFLAGS) -Wall -Werror -Wextra -test_device_LDADD = \ - $(top_builddir)/src/libindicatorpower-service.a \ - $(SERVICE_DEPS_LIBS) \ - $(TEST_LIBS) - - -# (FIXME: incomplete) -# -### -### tests: indicator-power-service -### -# -# build a local copy of the GSettings schemas -#BUILT_SOURCES += gschemas.compiled -#CLEANFILES += gschemas.compiled -#gschemas.compiled: Makefile -# @glib-compile-schemas --targetdir=$(abs_builddir) $(top_builddir)/data -# -#TESTS += test-service -#check_PROGRAMS += test-service -#test_service_SOURCES = test-service.cc -#test_service_LDADD = $(TEST_LIBS) -#test_service_CPPFLAGS = $(TEST_CPPFLAGS) -DSCHEMA_DIR="\"$(top_builddir)/tests/\"" -# -#TESTS += test-dbus-listener -#check_PROGRAMS += test-dbus-listener -#test_dbus_listener_SOURCES = test-dbus-listener.cc -#test_dbus_listener_LDADD = $(TEST_LIBS) -#test_dbus_listener_CPPFLAGS = $(TEST_CPPFLAGS) -# diff --git a/tests/device-provider-mock.c b/tests/device-provider-mock.c new file mode 100644 index 0000000..afca178 --- /dev/null +++ b/tests/device-provider-mock.c @@ -0,0 +1,107 @@ +/* + * 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 "device.h" +#include "device-provider.h" +#include "device-provider-mock.h" + +/*** +**** GObject boilerplate +***/ + +static void indicator_power_device_provider_interface_init ( + IndicatorPowerDeviceProviderInterface * iface); + +G_DEFINE_TYPE_WITH_CODE ( + IndicatorPowerDeviceProviderMock, + indicator_power_device_provider_mock, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (INDICATOR_TYPE_POWER_DEVICE_PROVIDER, + indicator_power_device_provider_interface_init)) + +/*** +**** IndicatorPowerDeviceProvider virtual functions +***/ + +static GList * +my_get_devices (IndicatorPowerDeviceProvider * provider) +{ + IndicatorPowerDeviceProviderMock * self = INDICATOR_POWER_DEVICE_PROVIDER_MOCK(provider); + + return g_list_copy_deep (self->devices, (GCopyFunc)g_object_ref, NULL); +} + +/*** +**** GObject virtual functions +***/ + +static void +my_dispose (GObject * o) +{ + IndicatorPowerDeviceProviderMock * self = INDICATOR_POWER_DEVICE_PROVIDER_MOCK(o); + + g_list_free_full (self->devices, g_object_unref); + + G_OBJECT_CLASS (indicator_power_device_provider_mock_parent_class)->dispose (o); +} + +/*** +**** Instantiation +***/ + +static void +indicator_power_device_provider_mock_class_init (IndicatorPowerDeviceProviderMockClass * klass) +{ + GObjectClass * object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = my_dispose; +} + +static void +indicator_power_device_provider_interface_init (IndicatorPowerDeviceProviderInterface * iface) +{ + iface->get_devices = my_get_devices; +} + +static void +indicator_power_device_provider_mock_init (IndicatorPowerDeviceProviderMock * self) +{ +} + +/*** +**** Public API +***/ + +IndicatorPowerDeviceProvider * +indicator_power_device_provider_mock_new (void) +{ + gpointer o = g_object_new (INDICATOR_TYPE_POWER_DEVICE_PROVIDER_MOCK, NULL); + + return INDICATOR_POWER_DEVICE_PROVIDER (o); +} + +void +indicator_power_device_provider_add_device (IndicatorPowerDeviceProviderMock * provider, + IndicatorPowerDevice * device) +{ + provider->devices = g_list_append (provider->devices, g_object_ref(device)); + + g_signal_connect_swapped (device, "notify", G_CALLBACK(indicator_power_device_provider_emit_devices_changed), provider); +} diff --git a/tests/device-provider-mock.h b/tests/device-provider-mock.h new file mode 100644 index 0000000..4d06924 --- /dev/null +++ b/tests/device-provider-mock.h @@ -0,0 +1,79 @@ +/* + * 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_POWER_DEVICE_PROVIDER_MOCK__H__ +#define __INDICATOR_POWER_DEVICE_PROVIDER_MOCK__H__ + +#include <glib-object.h> /* parent class */ + +#include "device.h" +#include "device-provider.h" + +G_BEGIN_DECLS + +#define INDICATOR_TYPE_POWER_DEVICE_PROVIDER_MOCK \ + (indicator_power_device_provider_mock_get_type()) + +#define INDICATOR_POWER_DEVICE_PROVIDER_MOCK(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), \ + INDICATOR_TYPE_POWER_DEVICE_PROVIDER_MOCK, \ + IndicatorPowerDeviceProviderMock)) + +#define INDICATOR_POWER_DEVICE_PROVIDER_MOCK_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), \ + INDICATOR_TYPE_POWER_DEVICE_PROVIDER_MOCK, \ + IndicatorPowerDeviceProviderMockClass)) + +#define INDICATOR_IS_POWER_DEVICE_PROVIDER_MOCK(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), \ + INDICATOR_TYPE_POWER_DEVICE_PROVIDER_MOCK)) + +typedef struct _IndicatorPowerDeviceProviderMock + IndicatorPowerDeviceProviderMock; +typedef struct _IndicatorPowerDeviceProviderMockPriv + IndicatorPowerDeviceProviderMockPriv; +typedef struct _IndicatorPowerDeviceProviderMockClass + IndicatorPowerDeviceProviderMockClass; + +/** + * An IndicatorPowerDeviceProvider which gets its devices from Mock. + */ +struct _IndicatorPowerDeviceProviderMock +{ + GObject parent_instance; + + /*< private >*/ + GList * devices; +}; + +struct _IndicatorPowerDeviceProviderMockClass +{ + GObjectClass parent_class; +}; + +GType indicator_power_device_provider_mock_get_type (void); + +IndicatorPowerDeviceProvider * indicator_power_device_provider_mock_new (void); + +void indicator_power_device_provider_add_device (IndicatorPowerDeviceProviderMock * provider, + IndicatorPowerDevice * device); + +G_END_DECLS + +#endif /* __INDICATOR_POWER_DEVICE_PROVIDER_MOCK__H__ */ diff --git a/tests/glib-fixture.h b/tests/glib-fixture.h new file mode 100644 index 0000000..d333ab2 --- /dev/null +++ b/tests/glib-fixture.h @@ -0,0 +1,141 @@ +/* + * 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_print("%d %s\n", (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_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/indicator-power-service-cmdline-battery.cc b/tests/indicator-power-service-cmdline-battery.cc new file mode 100644 index 0000000..a7a86a1 --- /dev/null +++ b/tests/indicator-power-service-cmdline-battery.cc @@ -0,0 +1,124 @@ +/* + * 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 <cstdlib> + +#include <locale.h> // setlocale() +#include <libintl.h> // bindtextdomain() +#include <unistd.h> // STDIN_FILENO + +#include <gio/gio.h> + +#include "device-provider-mock.h" + +#include "service.h" + +/*** +**** +***/ + +static void +on_name_lost (gpointer instance G_GNUC_UNUSED, gpointer loop) +{ + g_message ("exiting: service couldn't acquire or lost ownership of busname"); + g_main_loop_quit ((GMainLoop*)loop); +} + +static IndicatorPowerDevice * battery = nullptr; + +static GMainLoop * loop = nullptr; + +static gboolean on_command_stream_available (GIOChannel *source, + GIOCondition /*condition*/, + gpointer /*user_data*/) +{ + gchar * str = nullptr; + GError * error = nullptr; + auto status = g_io_channel_read_line (source, &str, nullptr, nullptr, &error); + g_assert_no_error (error); + + if (status == G_IO_STATUS_NORMAL) + { + g_strstrip (str); + + if (!g_strcmp0 (str, "charging")) + { + g_object_set (battery, INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_CHARGING, nullptr); + } + else if (!g_strcmp0 (str, "discharging")) + { + g_object_set (battery, INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_DISCHARGING, nullptr); + } + else + { + g_object_set (battery, INDICATOR_POWER_DEVICE_PERCENTAGE, atof(str), nullptr); + } + } + else if (status == G_IO_STATUS_EOF) + { + g_main_loop_quit (loop); + } + + g_free (str); + return G_SOURCE_CONTINUE; +} + +/* this is basically indicator-power-service with a custom provider */ +int +main (int argc G_GNUC_UNUSED, char ** argv G_GNUC_UNUSED) +{ + g_message ("This app is basically the same as indicator-power-service but,\n" + "instead of the system's real devices, sees a single fake battery\n" + "which can be manipulated by typing commands:\n" + "'charging', 'discharging', a charge percentage, or ctrl-c."); + + IndicatorPowerDeviceProvider * device_provider; + IndicatorPowerService * service; + + g_assert(g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, true)); + g_assert(g_setenv("GSETTINGS_BACKEND", "memory", true)); + + /* boilerplate i18n */ + setlocale (LC_ALL, ""); + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + textdomain (GETTEXT_PACKAGE); + + /* read lines from the command line */ + auto channel = g_io_channel_unix_new (STDIN_FILENO); + auto watch_tag = g_io_add_watch (channel, G_IO_IN, on_command_stream_available, nullptr); + + /* run */ + battery = indicator_power_device_new ("/some/path", UP_DEVICE_KIND_BATTERY, 50.0, UP_DEVICE_STATE_DISCHARGING, 30*60); + device_provider = indicator_power_device_provider_mock_new (); + indicator_power_device_provider_add_device (INDICATOR_POWER_DEVICE_PROVIDER_MOCK(device_provider), battery); + service = indicator_power_service_new (device_provider); + loop = g_main_loop_new (NULL, FALSE); + g_signal_connect (service, INDICATOR_POWER_SERVICE_SIGNAL_NAME_LOST, + G_CALLBACK(on_name_lost), loop); + g_main_loop_run (loop); + + /* cleanup */ + g_main_loop_unref (loop); + g_source_remove (watch_tag); + g_io_channel_unref (channel); + g_clear_object (&service); + g_clear_object (&device_provider); + g_clear_object (&battery); + return 0; +} diff --git a/tests/manual b/tests/manual index d3a22e1..a542cac 100644 --- a/tests/manual +++ b/tests/manual @@ -22,3 +22,20 @@ Test-case indicator-power/unity8-items-check <dd>The menu is populated with items</dd> </dl> +Test-case indicator-power/low-battery-popups +<dl> + <dt>Open a terminal</dt> + <dt>Stop the currently-running power indicator: "stop indicator-power"</dt> + <dt>Start the fake battery harness in the tests/build/ directory: "indicator-power-service-cmdline-battery"</dt> + <dd>Battery indicator should update, showing a discharging battery with a 50% charge</dd> + <dt>Type: "10" (no quotes) and press Enter</dt> + <dd>A popup should appear saying 'Battery low - 10% charge remaining'</dd> + <dd>Battery indicator's icon should show a low charge</dd> + <dd>Battery indicator's "Charge level" menuitem should show a 10% charge</dd> + <dt>Type: "9" (no quotes) and press Enter</dt> + <dd>The 'Battery low' popup should NOT appear, since we've already been notified</dd> + <dd>Battery indicator's "Charge level" menuitem should show a 9% charge</dd> + <dt>Type: "5" (no quotes) and press Enter</dt> + <dd>No 'Battery low' popup SHOULD appear, since 5% is the next warning threshold</dd> + <dd>Battery indicator's "Charge level" menuitem should show a 5% charge</dd> +</dl> diff --git a/tests/test-device.cc b/tests/test-device.cc index 2762d4a..85ba237 100644 --- a/tests/test-device.cc +++ b/tests/test-device.cc @@ -19,6 +19,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include <gio/gio.h> #include <gtest/gtest.h> +#include <cmath> // ceil() #include "device.h" #include "service.h" @@ -72,11 +73,8 @@ class DeviceTest : public ::testing::Test void check_label (const IndicatorPowerDevice * device, const char * expected_label) { - char * label; - - label = indicator_power_device_get_label (device); + char * label = indicator_power_device_get_readable_text (device); EXPECT_STREQ (expected_label, label); - g_free (label); } @@ -86,31 +84,39 @@ class DeviceTest : public ::testing::Test const char * expected_percent, const char * expected_a11y) { - char * label; - char * a11y; - - indicator_power_device_get_header (device, true, true, &label, &a11y); - EXPECT_STREQ (expected_time_and_percent, label); - EXPECT_STREQ (expected_a11y, a11y); - g_free (label); - g_free (a11y); - - indicator_power_device_get_header (device, true, false, &label, &a11y); - EXPECT_STREQ (expected_time, label); - EXPECT_STREQ (expected_a11y, a11y); - g_free (label); - g_free (a11y); - - indicator_power_device_get_header (device, false, true, &label, &a11y); - EXPECT_STREQ (expected_percent, label); - EXPECT_STREQ (expected_a11y, a11y); - g_free (label); - g_free (a11y); - - indicator_power_device_get_header (device, false, false, &label, &a11y); - ASSERT_TRUE (!label || !*label); - EXPECT_STREQ (expected_a11y, a11y); - g_free (label); + char * a11y = NULL; + char * title = NULL; + + title = indicator_power_device_get_readable_title (device, true, true); + if (expected_time_and_percent) + EXPECT_STREQ (expected_time_and_percent, title); + else + EXPECT_EQ(NULL, title); + g_free (title); + + title = indicator_power_device_get_readable_title (device, true, false); + if (expected_time) + EXPECT_STREQ (expected_time, title); + else + EXPECT_EQ(NULL, title); + g_free (title); + + title = indicator_power_device_get_readable_title (device, false, true); + if (expected_percent) + EXPECT_STREQ (expected_percent, title); + else + EXPECT_EQ(NULL, title); + g_free (title); + + title = indicator_power_device_get_readable_title (device, false, false); + EXPECT_EQ(NULL, title); + g_free (title); + + a11y = indicator_power_device_get_accessible_title (device, false, false); + if (expected_a11y) + EXPECT_STREQ (expected_a11y, a11y); + else + EXPECT_EQ(NULL, a11y); g_free (a11y); } }; @@ -318,8 +324,9 @@ TEST_F(DeviceTest, IconNames) INDICATOR_POWER_DEVICE_PERCENTAGE, 95.0, NULL); - g_string_append_printf (expected, "%s-full-charging-symbolic;", kind_str); + g_string_append_printf (expected, "%s-100-charging;", kind_str); g_string_append_printf (expected, "gpm-%s-100-charging;", kind_str); + g_string_append_printf (expected, "%s-full-charging-symbolic;", kind_str); g_string_append_printf (expected, "%s-full-charging", kind_str); check_icon_names (device, expected->str); g_string_truncate (expected, 0); @@ -329,8 +336,9 @@ TEST_F(DeviceTest, IconNames) INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_CHARGING, INDICATOR_POWER_DEVICE_PERCENTAGE, 85.0, NULL); - g_string_append_printf (expected, "%s-full-charging-symbolic;", kind_str); + g_string_append_printf (expected, "%s-080-charging;", kind_str); g_string_append_printf (expected, "gpm-%s-080-charging;", kind_str); + g_string_append_printf (expected, "%s-full-charging-symbolic;", kind_str); g_string_append_printf (expected, "%s-full-charging", kind_str); check_icon_names (device, expected->str); g_string_truncate (expected, 0); @@ -340,8 +348,9 @@ TEST_F(DeviceTest, IconNames) INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_CHARGING, INDICATOR_POWER_DEVICE_PERCENTAGE, 50.0, NULL); - g_string_append_printf (expected, "%s-good-charging-symbolic;", kind_str); + g_string_append_printf (expected, "%s-060-charging;", kind_str); g_string_append_printf (expected, "gpm-%s-060-charging;", kind_str); + g_string_append_printf (expected, "%s-good-charging-symbolic;", kind_str); g_string_append_printf (expected, "%s-good-charging", kind_str); check_icon_names (device, expected->str); g_string_truncate (expected, 0); @@ -351,8 +360,9 @@ TEST_F(DeviceTest, IconNames) INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_CHARGING, INDICATOR_POWER_DEVICE_PERCENTAGE, 25.0, NULL); - g_string_append_printf (expected, "%s-low-charging-symbolic;", kind_str); + g_string_append_printf (expected, "%s-020-charging;", kind_str); g_string_append_printf (expected, "gpm-%s-020-charging;", kind_str); + g_string_append_printf (expected, "%s-low-charging-symbolic;", kind_str); g_string_append_printf (expected, "%s-low-charging", kind_str); check_icon_names (device, expected->str); g_string_truncate (expected, 0); @@ -362,8 +372,9 @@ TEST_F(DeviceTest, IconNames) INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_CHARGING, INDICATOR_POWER_DEVICE_PERCENTAGE, 5.0, NULL); - g_string_append_printf (expected, "%s-caution-charging-symbolic;", kind_str); + g_string_append_printf (expected, "%s-000-charging;", kind_str); g_string_append_printf (expected, "gpm-%s-000-charging;", kind_str); + g_string_append_printf (expected, "%s-caution-charging-symbolic;", kind_str); g_string_append_printf (expected, "%s-caution-charging", kind_str); check_icon_names (device, expected->str); g_string_truncate (expected, 0); @@ -411,10 +422,10 @@ TEST_F(DeviceTest, IconNames) INDICATOR_POWER_DEVICE_PERCENTAGE, 25.0, INDICATOR_POWER_DEVICE_TIME, (guint64)(60*60), NULL); - g_string_append_printf (expected, "%s-040;", kind_str); - g_string_append_printf (expected, "gpm-%s-040;", kind_str); - g_string_append_printf (expected, "%s-good-symbolic;", kind_str); - g_string_append_printf (expected, "%s-good", kind_str); + g_string_append_printf (expected, "%s-020;", kind_str); + g_string_append_printf (expected, "gpm-%s-020;", kind_str); + g_string_append_printf (expected, "%s-low-symbolic;", kind_str); + g_string_append_printf (expected, "%s-low", kind_str); check_icon_names (device, expected->str); g_string_truncate (expected, 0); @@ -437,10 +448,10 @@ TEST_F(DeviceTest, IconNames) INDICATOR_POWER_DEVICE_PERCENTAGE, 5.0, INDICATOR_POWER_DEVICE_TIME, (guint64)(60*60), NULL); - g_string_append_printf (expected, "%s-040;", kind_str); - g_string_append_printf (expected, "gpm-%s-040;", kind_str); - g_string_append_printf (expected, "%s-good-symbolic;", kind_str); - g_string_append_printf (expected, "%s-good", kind_str); + g_string_append_printf (expected, "%s-000;", kind_str); + g_string_append_printf (expected, "gpm-%s-000;", kind_str); + g_string_append_printf (expected, "%s-caution-symbolic;", kind_str); + g_string_append_printf (expected, "%s-caution", kind_str); check_icon_names (device, expected->str); g_string_truncate (expected, 0); @@ -481,14 +492,16 @@ TEST_F(DeviceTest, Labels) g_setenv ("LANG", "en_US.UTF-8", TRUE); // bad args: NULL device - log_count_ipower_expected += 5; + log_count_ipower_expected++; check_label (NULL, NULL); + log_count_ipower_expected += 5; check_header (NULL, NULL, NULL, NULL, NULL); // bad args: a GObject that isn't a device - log_count_ipower_expected += 5; GObject * o = G_OBJECT(g_cancellable_new()); + log_count_ipower_expected++; check_label ((IndicatorPowerDevice*)o, NULL); + log_count_ipower_expected += 5; check_header (NULL, NULL, NULL, NULL, NULL); g_object_unref (o); @@ -509,7 +522,7 @@ TEST_F(DeviceTest, Labels) check_header (device, "(1:01, 50%)", "(1:01)", "(50%)", - "Battery (1 hour 1 minute to charge, 50%)"); + "Battery (1 hour 1 minute to charge)"); // discharging, < 12 hours left g_object_set (o, INDICATOR_POWER_DEVICE_KIND, UP_DEVICE_KIND_BATTERY, @@ -521,7 +534,7 @@ TEST_F(DeviceTest, Labels) check_header (device, "(1:01, 50%)", "(1:01)", "(50%)", - "Battery (1 hour 1 minute left, 50%)"); + "Battery (1 hour 1 minute left)"); // discharging, > 24 hours left // we don't show the clock time when > 24 hours discharging @@ -532,9 +545,9 @@ TEST_F(DeviceTest, Labels) NULL); check_label (device, "Battery"); check_header (device, "(50%)", - "", + NULL, "(50%)", - "Battery (50%)"); + "Battery"); // fully charged g_object_set (o, INDICATOR_POWER_DEVICE_KIND, UP_DEVICE_KIND_BATTERY, @@ -544,9 +557,9 @@ TEST_F(DeviceTest, Labels) NULL); check_label (device, "Battery (charged)"); check_header (device, "(100%)", - "", + NULL, "(100%)", - "Battery (charged, 100%)"); + "Battery (charged)"); // percentage but no time estimate g_object_set (o, INDICATOR_POWER_DEVICE_KIND, UP_DEVICE_KIND_BATTERY, @@ -558,7 +571,7 @@ TEST_F(DeviceTest, Labels) check_header (device, "(estimating…, 50%)", "(estimating…)", "(50%)", - "Battery (estimating…, 50%)"); + "Battery (estimating…)"); // no percentage, no time estimate g_object_set (o, INDICATOR_POWER_DEVICE_KIND, UP_DEVICE_KIND_BATTERY, @@ -566,8 +579,8 @@ TEST_F(DeviceTest, Labels) INDICATOR_POWER_DEVICE_PERCENTAGE, 0.0, INDICATOR_POWER_DEVICE_TIME, guint64(0), NULL); - check_label (device, "Battery (not present)"); - check_header (device, "", "", "", "Battery (not present)"); + check_label (device, "Battery"); + check_header (device, NULL, NULL, NULL, "Battery"); // power line g_object_set (o, INDICATOR_POWER_DEVICE_KIND, UP_DEVICE_KIND_LINE_POWER, @@ -576,7 +589,7 @@ TEST_F(DeviceTest, Labels) INDICATOR_POWER_DEVICE_TIME, guint64(0), NULL); check_label (device, "AC Adapter"); - check_header (device, "", "", "", "AC Adapter"); + check_header (device, NULL, NULL, NULL, "AC Adapter"); // cleanup g_object_unref(o); @@ -584,74 +597,183 @@ TEST_F(DeviceTest, Labels) g_free (real_lang); } -/* The menu title should tell you at a glance what you need to know most: what - device will lose power soonest (and optionally when), or otherwise which - device will take longest to charge (and optionally how long it will take). */ -TEST_F(DeviceTest, ChoosePrimary) + +TEST_F(DeviceTest, Inestimable___this_takes_80_seconds) { - GList * device_list; - IndicatorPowerDevice * a; - IndicatorPowerDevice * b; - - a = indicator_power_device_new ("/org/freedesktop/UPower/devices/mouse", - UP_DEVICE_KIND_MOUSE, - 0.0, - UP_DEVICE_STATE_DISCHARGING, - 0); - b = indicator_power_device_new ("/org/freedesktop/UPower/devices/battery", - UP_DEVICE_KIND_BATTERY, - 0.0, - UP_DEVICE_STATE_DISCHARGING, - 0); - - /* device states + time left to {discharge,charge} + % of charge left, - sorted in order of preference wrt the spec's criteria. - So tests[i] should be picked over any test with an index greater than i */ - struct { - int kind; - int state; - guint64 time; - double percentage; - } tests[] = { - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 49, 50.0 }, - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 50, 50.0 }, - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 50, 100.0 }, - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 51, 50.0 }, - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 50, 50.0 }, - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 49, 50.0 }, - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 49, 100.0 }, - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 48, 50.0 }, - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_FULLY_CHARGED, 0, 50.0 }, - { UP_DEVICE_KIND_KEYBOARD, UP_DEVICE_STATE_FULLY_CHARGED, 0, 50.0 }, - { UP_DEVICE_KIND_LINE_POWER, UP_DEVICE_STATE_UNKNOWN, 0, 0.0 } + // set our language so that i18n won't break these tests + auto real_lang = g_strdup(g_getenv ("LANG")); + g_setenv ("LANG", "en_US.UTF-8", true); + + // set up the main loop + auto loop = g_main_loop_new (nullptr, false); + auto loop_quit_sourcefunc = [](gpointer l){ + g_main_loop_quit(static_cast<GMainLoop*>(l)); + return G_SOURCE_REMOVE; }; - device_list = NULL; - device_list = g_list_append (device_list, a); - device_list = g_list_append (device_list, b); + auto device = INDICATOR_POWER_DEVICE (g_object_new (INDICATOR_POWER_DEVICE_TYPE, nullptr)); + auto o = G_OBJECT(device); - for (int i=0, n=G_N_ELEMENTS(tests); i<n; i++) + // percentage but no time estimate + auto timer = g_timer_new (); + g_object_set (o, INDICATOR_POWER_DEVICE_KIND, UP_DEVICE_KIND_BATTERY, + INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_DISCHARGING, + INDICATOR_POWER_DEVICE_PERCENTAGE, 50.0, + INDICATOR_POWER_DEVICE_TIME, guint64(0), + nullptr); + + /* + * “estimating…” if the time remaining has been inestimable for + * less than 30 seconds; otherwise “unknown” if the time remaining + * has been inestimable for between 30 seconds and one minute; + * otherwise the empty string. + */ + for (;;) { - for (int j=i+1; j<n; j++) + g_timeout_add_seconds (1, loop_quit_sourcefunc, loop); + g_main_loop_run (loop); + + const auto elapsed = g_timer_elapsed (timer, nullptr); + + if (elapsed < 30) { - g_object_set (a, INDICATOR_POWER_DEVICE_KIND, tests[i].kind, - INDICATOR_POWER_DEVICE_STATE, tests[i].state, - INDICATOR_POWER_DEVICE_TIME, guint64(tests[i].time), - INDICATOR_POWER_DEVICE_PERCENTAGE, tests[i].percentage, - NULL); - g_object_set (b, INDICATOR_POWER_DEVICE_KIND, tests[j].kind, - INDICATOR_POWER_DEVICE_STATE, tests[j].state, - INDICATOR_POWER_DEVICE_TIME, guint64(tests[j].time), - INDICATOR_POWER_DEVICE_PERCENTAGE, tests[j].percentage, - NULL); - ASSERT_EQ (a, indicator_power_service_choose_primary_device(device_list)); - - /* reverse the list to check that list order doesn't matter */ - device_list = g_list_reverse (device_list); - ASSERT_EQ (a, indicator_power_service_choose_primary_device(device_list)); + check_label (device, "Battery (estimating…)"); + check_header (device, "(estimating…, 50%)", + "(estimating…)", + "(50%)", + "Battery (estimating…)"); + } + else if (elapsed < 60) + { + check_label (device, "Battery (unknown)"); + check_header (device, "(unknown, 50%)", + "(unknown)", + "(50%)", + "Battery (unknown)"); + } + else if (elapsed < 80) + { + check_label (device, "Battery"); + check_header (device, "(50%)", + NULL, + "(50%)", + "Battery"); + } + else + { + break; } } + g_main_loop_unref (loop); + // cleanup - g_list_free_full (device_list, g_object_unref); + g_timer_destroy (timer); + g_object_unref (o); + g_setenv ("LANG", real_lang, TRUE); + g_free (real_lang); +} + +/* If a device has multiple batteries and uses only one of them at a time, + they should be presented as separate items inside the battery menu, + but everywhere else they should be aggregated (bug 880881). + Their percentages should be averaged. If any are discharging, + the aggregated time remaining should be the maximum of the times + for all those that are discharging, plus the sum of the times + for all those that are idle. Otherwise, the aggregated time remaining + should be the the maximum of the times for all those that are charging. */ +TEST_F(DeviceTest, ChoosePrimary) +{ + struct Description + { + const char * path; + UpDeviceKind kind; + UpDeviceState state; + guint64 time; + double percentage; + }; + + const Description descriptions[] = { + { "/some/path/d0", UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 10, 60.0 }, // 0 + { "/some/path/d1", UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 20, 80.0 }, // 1 + { "/some/path/d2", UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 30, 100.0 }, // 2 + + { "/some/path/c0", UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 10, 60.0 }, // 3 + { "/some/path/c1", UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 20, 80.0 }, // 4 + { "/some/path/c2", UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 30, 100.0 }, // 5 + + { "/some/path/f0", UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_FULLY_CHARGED, 0, 100.0 }, // 6 + { "/some/path/m0", UP_DEVICE_KIND_MOUSE, UP_DEVICE_STATE_DISCHARGING, 20, 80.0 }, // 7 + { "/some/path/m1", UP_DEVICE_KIND_MOUSE, UP_DEVICE_STATE_FULLY_CHARGED, 0, 100.0 }, // 8 + { "/some/path/pw", UP_DEVICE_KIND_LINE_POWER, UP_DEVICE_STATE_UNKNOWN, 0, 0.0 } // 9 + }; + + std::vector<IndicatorPowerDevice*> devices; + for(const auto& desc : descriptions) + devices.push_back(indicator_power_device_new(desc.path, desc.kind, desc.percentage, desc.state, (time_t)desc.time)); + + const struct { + std::vector<unsigned int> device_indices; + Description expected; + } tests[] = { + + { { 0 }, descriptions[0] }, // 1 discharging + { { 0, 1 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 20, 70.0 } }, // 2 discharging + { { 1, 2 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 30, 90.0 } }, // 2 discharging + { { 0, 1, 2 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 30, 80.0 } }, // 3 discharging + + { { 3 }, descriptions[3] }, // 1 charging + { { 3, 4 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 20, 70.0 } }, // 2 charging + { { 4, 5 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 30, 90.0 } }, // 2 charging + { { 3, 4, 5 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 30, 80.0 } }, // 3 charging + + { { 6 }, descriptions[6] }, // 1 charged + { { 6, 0 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 10, 80.0 } }, // 1 charged, 1 discharging + { { 6, 3 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 10, 80.0 } }, // 1 charged, 1 charging + { { 6, 0, 3 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 10, 73.3 } }, // 1 charged, 1 charging, 1 discharging + + { { 0, 7 }, descriptions[0] }, // 1 discharging battery, 1 discharging mouse. pick the one with the least time left. + { { 2, 7 }, descriptions[7] }, // 1 discharging battery, 1 discharging mouse. pick the one with the least time left. + + { { 0, 8 }, descriptions[0] }, // 1 discharging battery, 1 fully-charged mouse. pick the one that's discharging. + { { 6, 7 }, descriptions[7] }, // 1 discharging mouse, 1 fully-charged battery. pick the one that's discharging. + + { { 0, 9 }, descriptions[0] }, // everything comes before power lines + { { 3, 9 }, descriptions[3] }, + { { 7, 9 }, descriptions[7] } + }; + + for(const auto& test : tests) + { + const auto& x = test.expected; + + GList * device_glist = nullptr; + for(const auto& i : test.device_indices) + device_glist = g_list_append(device_glist, devices[i]); + + auto primary = indicator_power_service_choose_primary_device(device_glist); + EXPECT_STREQ(x.path, indicator_power_device_get_object_path(primary)); + EXPECT_EQ(x.kind, indicator_power_device_get_kind(primary)); + EXPECT_EQ(x.state, indicator_power_device_get_state(primary)); + EXPECT_EQ(x.time, indicator_power_device_get_time(primary)); + EXPECT_EQ((int)ceil(x.percentage), (int)ceil(indicator_power_device_get_percentage(primary))); + g_object_unref(primary); + + // reverse the list and repeat the test + // to confirm that list order doesn't matter + device_glist = g_list_reverse (device_glist); + primary = indicator_power_service_choose_primary_device (device_glist); + EXPECT_STREQ(x.path, indicator_power_device_get_object_path(primary)); + EXPECT_EQ(x.kind, indicator_power_device_get_kind(primary)); + EXPECT_EQ(x.state, indicator_power_device_get_state(primary)); + EXPECT_EQ(x.time, indicator_power_device_get_time(primary)); + EXPECT_EQ((int)ceil(x.percentage), (int)ceil(indicator_power_device_get_percentage(primary))); + g_object_unref(primary); + + // cleanup + g_list_free(device_glist); + } + + for (auto& device : devices) + g_object_unref (device); } diff --git a/tests/test-notify.cc b/tests/test-notify.cc new file mode 100644 index 0000000..b5166a0 --- /dev/null +++ b/tests/test-notify.cc @@ -0,0 +1,410 @@ +/* + * 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 "glib-fixture.h" + +#include "dbus-shared.h" +#include "device.h" +#include "notifier.h" + +#include <gtest/gtest.h> + +#include <libdbustest/dbus-test.h> + +#include <libnotify/notify.h> + +#include <glib.h> +#include <gio/gio.h> + +/*** +**** +***/ + +class NotifyFixture: public GlibFixture +{ +private: + + typedef GlibFixture super; + + static constexpr char const * NOTIFY_BUSNAME {"org.freedesktop.Notifications"}; + static constexpr char const * NOTIFY_INTERFACE {"org.freedesktop.Notifications"}; + static constexpr char const * NOTIFY_PATH {"/org/freedesktop/Notifications"}; + +protected: + + DbusTestService * service = nullptr; + DbusTestDbusMock * mock = nullptr; + DbusTestDbusMockObject * obj = nullptr; + GDBusConnection * bus = nullptr; + + static constexpr int FIRST_NOTIFY_ID {1234}; + + static constexpr int NOTIFICATION_CLOSED_EXPIRED {1}; + static constexpr int NOTIFICATION_CLOSED_DISMISSED {2}; + static constexpr int NOTIFICATION_CLOSED_API {3}; + static constexpr int NOTIFICATION_CLOSED_UNDEFINED {4}; + + static constexpr char const * APP_NAME {"indicator-power-service"}; + + static constexpr char const * METHOD_CLOSE {"CloseNotification"}; + static constexpr char const * METHOD_NOTIFY {"Notify"}; + static constexpr char const * METHOD_GET_CAPS {"GetCapabilities"}; + static constexpr char const * METHOD_GET_INFO {"GetServerInformation"}; + static constexpr char const * SIGNAL_CLOSED {"NotificationClosed"}; + + static constexpr char const * HINT_TIMEOUT {"x-canonical-snap-decisions-timeout"}; + +protected: + + void SetUp() + { + super::SetUp(); + + // init DBusMock / dbus-test-runner + + service = dbus_test_service_new(nullptr); + + GError * error = nullptr; + mock = dbus_test_dbus_mock_new(NOTIFY_BUSNAME); + obj = dbus_test_dbus_mock_get_object(mock, + NOTIFY_PATH, + NOTIFY_INTERFACE, + &error); + g_assert_no_error (error); + + // METHOD_GET_INFO + dbus_test_dbus_mock_object_add_method(mock, obj, METHOD_GET_INFO, + nullptr, + G_VARIANT_TYPE("(ssss)"), + "ret = ('mock-notify', 'test vendor', '1.0', '1.1')", + &error); + g_assert_no_error (error); + + // METHOD_NOTIFY + auto str = g_strdup_printf("try:\n" + " self.NextNotifyId\n" + "except AttributeError:\n" + " self.NextNotifyId = %d\n" + "ret = self.NextNotifyId\n" + "self.NextNotifyId += 1\n", + FIRST_NOTIFY_ID); + dbus_test_dbus_mock_object_add_method(mock, obj, METHOD_NOTIFY, + G_VARIANT_TYPE("(susssasa{sv}i)"), + G_VARIANT_TYPE_UINT32, + str, + &error); + g_assert_no_error (error); + g_free (str); + + // METHOD_CLOSE + str = g_strdup_printf("self.EmitSignal('%s', '%s', 'uu', [ args[0], %d ])", + NOTIFY_INTERFACE, + SIGNAL_CLOSED, + NOTIFICATION_CLOSED_API); + dbus_test_dbus_mock_object_add_method(mock, obj, METHOD_CLOSE, + G_VARIANT_TYPE("(u)"), + nullptr, + str, + &error); + g_assert_no_error (error); + g_free (str); + + dbus_test_service_add_task(service, DBUS_TEST_TASK(mock)); + dbus_test_service_start_tasks(service); + + bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr); + g_dbus_connection_set_exit_on_close(bus, FALSE); + g_object_add_weak_pointer(G_OBJECT(bus), (gpointer *)&bus); + + notify_init(APP_NAME); + } + + virtual void TearDown() + { + notify_uninit(); + + g_clear_object(&mock); + g_clear_object(&service); + g_object_unref(bus); + + // wait a little while for the scaffolding to shut down, + // but don't block on it forever... + unsigned int cleartry = 0; + while ((bus != nullptr) && (cleartry < 50)) + { + g_usleep(100000); + while (g_main_pending()) + g_main_iteration(true); + cleartry++; + } + + super::TearDown(); + } +}; + +/*** +**** +***/ + +// simple test to confirm the NotifyFixture plumbing all works +TEST_F(NotifyFixture, HelloWorld) +{ +} + +/*** +**** +***/ + + +namespace +{ + static constexpr double percent_critical {2.0}; + static constexpr double percent_very_low {5.0}; + static constexpr double percent_low {10.0}; + + void set_battery_percentage (IndicatorPowerDevice * battery, gdouble p) + { + g_object_set (battery, INDICATOR_POWER_DEVICE_PERCENTAGE, p, nullptr); + } +} + +TEST_F(NotifyFixture, PercentageToLevel) +{ + auto battery = indicator_power_device_new ("/object/path", + UP_DEVICE_KIND_BATTERY, + 50.0, + UP_DEVICE_STATE_DISCHARGING, + 30); + + // confirm that the power levels trigger at the right percentages + for (int i=100; i>=0; --i) + { + set_battery_percentage (battery, i); + const auto level = indicator_power_notifier_get_power_level(battery); + + if (i <= percent_critical) + EXPECT_STREQ (POWER_LEVEL_STR_CRITICAL, level); + else if (i <= percent_very_low) + EXPECT_STREQ (POWER_LEVEL_STR_VERY_LOW, level); + else if (i <= percent_low) + EXPECT_STREQ (POWER_LEVEL_STR_LOW, level); + else + EXPECT_STREQ (POWER_LEVEL_STR_OK, level); + } + + g_object_unref (battery); +} + +/*** +**** +***/ + +// scaffolding to monitor PropertyChanged signals +namespace +{ + enum + { + FIELD_POWER_LEVEL = (1<<0), + FIELD_IS_WARNING = (1<<1) + }; + + struct ChangedParams + { + std::string power_level = POWER_LEVEL_STR_OK; + bool is_warning = false; + uint32_t fields = 0; + }; + + void on_battery_property_changed (GDBusConnection *connection G_GNUC_UNUSED, + const gchar *sender_name G_GNUC_UNUSED, + const gchar *object_path G_GNUC_UNUSED, + const gchar *interface_name G_GNUC_UNUSED, + const gchar *signal_name G_GNUC_UNUSED, + GVariant *parameters, + gpointer gchanged_params) + { + g_return_if_fail (g_variant_n_children (parameters) == 3); + auto dict = g_variant_get_child_value (parameters, 1); + g_return_if_fail (g_variant_is_of_type (dict, G_VARIANT_TYPE_DICTIONARY)); + auto changed_params = static_cast<ChangedParams*>(gchanged_params); + + const char * power_level; + if (g_variant_lookup (dict, "PowerLevel", "&s", &power_level, nullptr)) + { + changed_params->power_level = power_level; + changed_params->fields |= FIELD_POWER_LEVEL; + } + + gboolean is_warning; + if (g_variant_lookup (dict, "IsWarning", "b", &is_warning, nullptr)) + { + changed_params->is_warning = is_warning; + changed_params->fields |= FIELD_IS_WARNING; + } + + g_variant_unref (dict); + } +} + +TEST_F(NotifyFixture, LevelsDuringBatteryDrain) +{ + auto battery = indicator_power_device_new ("/object/path", + UP_DEVICE_KIND_BATTERY, + 50.0, + UP_DEVICE_STATE_DISCHARGING, + 30); + + // set up a notifier and give it the battery so changing the battery's + // charge should show up on the bus. + auto notifier = indicator_power_notifier_new (); + indicator_power_notifier_set_battery (notifier, battery); + indicator_power_notifier_set_bus (notifier, bus); + wait_msec(); + + ChangedParams changed_params; + auto sub_tag = g_dbus_connection_signal_subscribe (bus, + nullptr, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + BUS_PATH"/Battery", + nullptr, + G_DBUS_SIGNAL_FLAGS_NONE, + on_battery_property_changed, + &changed_params, + nullptr); + + // confirm that draining the battery puts + // the power_level change through its paces + for (int i=100; i>=0; --i) + { + changed_params = ChangedParams(); + EXPECT_TRUE (changed_params.fields == 0); + + const auto old_level = indicator_power_notifier_get_power_level(battery); + set_battery_percentage (battery, i); + const auto new_level = indicator_power_notifier_get_power_level(battery); + wait_msec(); + + if (old_level == new_level) + { + EXPECT_EQ (0, (changed_params.fields & FIELD_POWER_LEVEL)); + } + else + { + EXPECT_EQ (FIELD_POWER_LEVEL, (changed_params.fields & FIELD_POWER_LEVEL)); + EXPECT_EQ (new_level, changed_params.power_level); + } + } + + // cleanup + g_dbus_connection_signal_unsubscribe (bus, sub_tag); + g_object_unref (notifier); + g_object_unref (battery); +} + +/*** +**** +***/ + +TEST_F(NotifyFixture, EventsThatChangeNotifications) +{ + // GetCapabilities returns an array containing 'actions', so that we'll + // get snap decisions and the 'IsWarning' property + GError * error = nullptr; + dbus_test_dbus_mock_object_add_method (mock, + obj, + METHOD_GET_CAPS, + nullptr, + G_VARIANT_TYPE_STRING_ARRAY, + "ret = ['actions', 'body']", + &error); + g_assert_no_error (error); + + auto battery = indicator_power_device_new ("/object/path", + UP_DEVICE_KIND_BATTERY, + percent_low + 1.0, + UP_DEVICE_STATE_DISCHARGING, + 30); + + // set up a notifier and give it the battery so changing the battery's + // charge should show up on the bus. + auto notifier = indicator_power_notifier_new (); + indicator_power_notifier_set_battery (notifier, battery); + indicator_power_notifier_set_bus (notifier, bus); + ChangedParams changed_params; + auto sub_tag = g_dbus_connection_signal_subscribe (bus, + nullptr, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + BUS_PATH"/Battery", + nullptr, + G_DBUS_SIGNAL_FLAGS_NONE, + on_battery_property_changed, + &changed_params, + nullptr); + + // test setup case + wait_msec(); + EXPECT_STREQ (POWER_LEVEL_STR_OK, changed_params.power_level.c_str()); + + // change the percent past the 'low' threshold and confirm that + // a) the power level changes + // b) we get a notification + changed_params = ChangedParams(); + set_battery_percentage (battery, percent_low); + wait_msec(); + EXPECT_EQ (FIELD_POWER_LEVEL|FIELD_IS_WARNING, changed_params.fields); + EXPECT_EQ (indicator_power_notifier_get_power_level(battery), changed_params.power_level); + EXPECT_TRUE (changed_params.is_warning); + + // now test that the warning changes if the level goes down even lower... + changed_params = ChangedParams(); + set_battery_percentage (battery, percent_very_low); + wait_msec(); + EXPECT_EQ (FIELD_POWER_LEVEL, changed_params.fields); + EXPECT_STREQ (POWER_LEVEL_STR_VERY_LOW, changed_params.power_level.c_str()); + + // ...and that the warning is taken down if the battery is plugged back in... + changed_params = ChangedParams(); + g_object_set (battery, INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_CHARGING, nullptr); + wait_msec(); + EXPECT_EQ (FIELD_IS_WARNING, changed_params.fields); + EXPECT_FALSE (changed_params.is_warning); + + // ...and that it comes back if we unplug again... + changed_params = ChangedParams(); + g_object_set (battery, INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_DISCHARGING, nullptr); + wait_msec(); + EXPECT_EQ (FIELD_IS_WARNING, changed_params.fields); + EXPECT_TRUE (changed_params.is_warning); + + // ...and that it's taken down if the power level is OK + changed_params = ChangedParams(); + set_battery_percentage (battery, percent_low+1); + wait_msec(); + EXPECT_EQ (FIELD_POWER_LEVEL|FIELD_IS_WARNING, changed_params.fields); + EXPECT_STREQ (POWER_LEVEL_STR_OK, changed_params.power_level.c_str()); + EXPECT_FALSE (changed_params.is_warning); + + // cleanup + g_dbus_connection_signal_unsubscribe (bus, sub_tag); + g_object_unref (notifier); + g_object_unref (battery); +} |