From f9a39b44803e6347c72c1edd8ba46f633e99dd57 Mon Sep 17 00:00:00 2001 From: Robert Tari Date: Sun, 8 Nov 2020 13:50:13 +0100 Subject: Rewrite to indicator-ng, simplify, drop all GTK references, move to CMake fixes #9, fixes #12 --- CMakeLists.txt | 79 ++ Makefile.am | 24 - autogen.sh | 11 - cmake/GdbusCodegen.cmake | 36 + cmake/UseGSettings.cmake | 23 + configure.ac | 86 -- data/CMakeLists.txt | 83 ++ data/Makefile.am | 21 - data/ayatana-indicator-notifications.desktop.in | 8 + data/ayatana-indicator-notifications.service.in | 11 + data/icons/Makefile.am | 11 - ...atana-indicator-notification-close-deselect.svg | 79 -- ...ayatana-indicator-notification-close-select.svg | 61 -- .../ayatana-indicator-notification-read-dnd.svg | 155 ---- data/icons/ayatana-indicator-notification-read.svg | 133 --- .../ayatana-indicator-notification-unread-dnd.svg | 218 ----- .../ayatana-indicator-notification-unread.svg | 224 ----- .../ayatana-indicator-notification-read-dnd.svg | 155 ++++ .../status/ayatana-indicator-notification-read.svg | 133 +++ .../ayatana-indicator-notification-unread-dnd.svg | 218 +++++ .../ayatana-indicator-notification-unread.svg | 224 +++++ data/org.ayatana.indicator.notifications | 11 + ....ayatana.indicator.notifications.gschema.xml.in | 25 + debian/control | 24 +- debian/rules | 32 +- po/CMakeLists.txt | 6 + src/CMakeLists.txt | 41 + src/Makefile.am | 36 - src/indicator-notifications-settings.c | 438 ---------- src/indicator-notifications.c | 897 --------------------- src/main.c | 52 ++ src/notification-menuitem.c | 407 ---------- src/notification-menuitem.h | 52 -- src/service.c | 867 ++++++++++++++++++++ src/service.h | 65 ++ src/settings.h | 20 - tests/Makefile.am | 0 37 files changed, 2061 insertions(+), 2905 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 Makefile.am delete mode 100755 autogen.sh create mode 100644 cmake/GdbusCodegen.cmake create mode 100644 cmake/UseGSettings.cmake delete mode 100644 configure.ac create mode 100644 data/CMakeLists.txt delete mode 100644 data/Makefile.am create mode 100644 data/ayatana-indicator-notifications.desktop.in create mode 100644 data/ayatana-indicator-notifications.service.in delete mode 100644 data/icons/Makefile.am delete mode 100644 data/icons/ayatana-indicator-notification-close-deselect.svg delete mode 100644 data/icons/ayatana-indicator-notification-close-select.svg delete mode 100644 data/icons/ayatana-indicator-notification-read-dnd.svg delete mode 100644 data/icons/ayatana-indicator-notification-read.svg delete mode 100644 data/icons/ayatana-indicator-notification-unread-dnd.svg delete mode 100644 data/icons/ayatana-indicator-notification-unread.svg create mode 100644 data/icons/scalable/status/ayatana-indicator-notification-read-dnd.svg create mode 100644 data/icons/scalable/status/ayatana-indicator-notification-read.svg create mode 100644 data/icons/scalable/status/ayatana-indicator-notification-unread-dnd.svg create mode 100644 data/icons/scalable/status/ayatana-indicator-notification-unread.svg create mode 100644 data/org.ayatana.indicator.notifications create mode 100644 data/org.ayatana.indicator.notifications.gschema.xml.in create mode 100644 po/CMakeLists.txt create mode 100644 src/CMakeLists.txt delete mode 100644 src/Makefile.am delete mode 100644 src/indicator-notifications-settings.c delete mode 100644 src/indicator-notifications.c create mode 100644 src/main.c delete mode 100644 src/notification-menuitem.c delete mode 100644 src/notification-menuitem.h create mode 100644 src/service.c create mode 100644 src/service.h delete mode 100644 src/settings.h delete mode 100644 tests/Makefile.am diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7802cfd --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,79 @@ +project(ayatana-indicator-notifications C CXX) +cmake_minimum_required(VERSION 2.8.9) + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) + +set(PROJECT_VERSION "0.8.1") +set(PACKAGE ${CMAKE_PROJECT_NAME}) + +## +## GNU standard installation directories +## + +include (GNUInstallDirs) +set (CMAKE_INSTALL_PKGLIBEXECDIR "${CMAKE_INSTALL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}") +set (CMAKE_INSTALL_FULL_PKGLIBEXECDIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}") + +## +## Gettext +## + +set(GETTEXT_PACKAGE "ayatana-indicator-notifications") +add_definitions (-DGETTEXT_PACKAGE="${GETTEXT_PACKAGE}" + -DLOCALEDIR="${CMAKE_INSTALL_FULL_LOCALEDIR}") + +## +## 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) + +include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS}) + +set(URL_DISPATCHER_1_REQUIRED_VERSION 1) +pkg_check_modules( + URLDISPATCHER + url-dispatcher-1>=${URL_DISPATCHER_1_REQUIRED_VERSION} +) +include_directories(${URLDISPATCHER_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 (cppcheck COMMAND cppcheck --enable=all -q --error-exitcode=2 --inline-suppr + ${CMAKE_SOURCE_DIR}/src) + +## +## 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 -Wformat=2") # Use -Wextra and -Wpedantic explicitly if needed +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) + +# actually build things +add_subdirectory(src) +add_subdirectory(data) +add_subdirectory(po) diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index 77cce5a..0000000 --- a/Makefile.am +++ /dev/null @@ -1,24 +0,0 @@ -SUBDIRS = \ - src \ - data \ - po \ - tests - -EXTRA_DIST = autogen.sh - -DISTCLEANFILES = \ - INSTALL \ - Makefile.in \ - aclocal.m4 \ - compile \ - configure \ - config.h.in \ - config.h \ - config.sub \ - config.guess \ - depcomp \ - install-sh \ - ltmain.sh \ - missing \ - po/Makefile.in.in - diff --git a/autogen.sh b/autogen.sh deleted file mode 100755 index 8004621..0000000 --- a/autogen.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -PKG_NAME="ayatana-indicator-notifications" - -which mate-autogen || { - echo "You need mate-common from https://git.mate-desktop.org" - exit 1 -} - -USE_GNOME2_MACROS=1 \ -. mate-autogen $@ 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/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 9433de0..0000000 --- a/configure.ac +++ /dev/null @@ -1,86 +0,0 @@ -AC_INIT([ayatana-indicator-notifications], [0.8.2], [jason.conti@gmail.com]) - -AM_INIT_AUTOMAKE([-Wall -Werror]) - -AM_SILENT_RULES([yes]) - -# Required compilers - -AC_ISC_POSIX -AC_PROG_CC -AM_PROG_CC_C_O -AM_PROG_AR -AC_STDC_HEADERS -AC_DISABLE_STATIC -AC_PROG_LIBTOOL - -# Setup gettext with intltool - -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)) -]) - -IT_PROG_INTLTOOL -GETTEXT_PACKAGE=ayatana-indicator-notifications -AC_SUBST(GETTEXT_PACKAGE) -AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], ["$GETTEXT_PACKAGE"], [Gettext package]) -AC_DEFINE_PATH([LOCALEDIR], "${datadir}/locale", [locale directory]) -AM_GLIB_GNU_GETTEXT - -# Helpers for GSettings - -GLIB_GSETTINGS - -# Required libraries from pkg-config - -INDICATOR_REQUIRED_VERSION=0.3.19 -GTK3_REQUIRED_VERSION=3.0 - -INDICATOR3_PKG_NAME=ayatana-indicator3-0.4 - -PKG_CHECK_MODULES(INDICATOR, $INDICATOR3_PKG_NAME >= $INDICATOR_REQUIRED_VERSION - gtk+-3.0 >= GTK3_REQUIRED_VERSION) - -AC_SUBST(INDICATOR_CFLAGS) -AC_SUBST(INDICATOR_LIBS) - -# Library directories from pkg-config - -INDICATORDIR=`$PKG_CONFIG --variable=indicatordir $INDICATOR3_PKG_NAME` -INDICATORICONSDIR="${datadir}/pixmaps/" - -AC_SUBST(INDICATORDIR) -AC_SUBST(INDICATORICONSDIR) - -PKG_CHECK_MODULES(SETTINGS, gtk+-3.0 >= GTK3_REQUIRED_VERSION) - -AC_SUBST(SETTINGS_CFLAGS) -AC_SUBST(SETTINGS_LIBS) - -AC_CONFIG_HEADERS([config.h]) -AC_CONFIG_FILES([ - Makefile - src/Makefile - data/Makefile - data/icons/Makefile - po/Makefile.in - tests/Makefile -]) - -AC_OUTPUT - -AC_MSG_NOTICE([ - -Recent Notifications Indicator Configuration: - - Prefix: $prefix - Indicator Dir: $INDICATORDIR -]) diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt new file mode 100644 index 0000000..b806dd1 --- /dev/null +++ b/data/CMakeLists.txt @@ -0,0 +1,83 @@ +## +## GSettings schema +## + +include (UseGSettings) +set (SCHEMA_NAME "org.ayatana.indicator.notifications.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}) + +## +## Systemd Unit File +## + +pkg_check_modules(SYSTEMD systemd) +if (${SYSTEMD_FOUND}) + pkg_get_variable(SYSTEMD_USER_DIR systemd systemduserunitdir) + message (STATUS "${SYSTEMD_USER_DIR} is the systemd user unit file install dir") + + set (SYSTEMD_USER_NAME "${CMAKE_PROJECT_NAME}.service") + set (SYSTEMD_USER_FILE "${CMAKE_CURRENT_BINARY_DIR}/${SYSTEMD_USER_NAME}") + set (SYSTEMD_USER_FILE_IN "${CMAKE_CURRENT_SOURCE_DIR}/${SYSTEMD_USER_NAME}.in") + + # build it + set (pkglibexecdir "${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}") + configure_file ("${SYSTEMD_USER_FILE_IN}" "${SYSTEMD_USER_FILE}") + + # install it + install (FILES "${SYSTEMD_USER_FILE}" + DESTINATION "${SYSTEMD_USER_DIR}") +endif() + +## +## 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}") + +## Ayatana Indicators File +## + +# where to install +set (AYATANA_INDICATOR_DIR "${CMAKE_INSTALL_FULL_DATAROOTDIR}/ayatana/indicators") +message (STATUS "${AYATANA_INDICATOR_DIR} is the Ayatana Indicators install dir") + +set (AYATANA_INDICATOR_NAME "org.ayatana.indicator.notifications") +set (AYATANA_INDICATOR_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${AYATANA_INDICATOR_NAME}") + +install (FILES "${AYATANA_INDICATOR_FILE}" + DESTINATION "${AYATANA_INDICATOR_DIR}") + +## +## Icons +## + +# where to install +set (ICON_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/icons/hicolor") +message (STATUS "${ICON_DIR} is the Icon install dir") + +install (DIRECTORY icons/scalable DESTINATION "${ICON_DIR}" FILES_MATCHING PATTERN "*.svg") + +install(CODE "execute_process (COMMAND gtk-update-icon-cache -t -f ${ICON_DIR})" + CODE "message (STATUS \"Updating icon cache\")") diff --git a/data/Makefile.am b/data/Makefile.am deleted file mode 100644 index 1960ad7..0000000 --- a/data/Makefile.am +++ /dev/null @@ -1,21 +0,0 @@ -SUBDIRS = icons - -gsettings_file = org.ayatana.indicator.notifications.gschema.xml -gsettings_SCHEMAS = $(gsettings_file) - -@INTLTOOL_XML_NOMERGE_RULE@ - -$(gsettings_file).in: $(gsettings_file).in.in - sed \ - -e "s|\@GETTEXT_PACKAGE\@|$(GETTEXT_PACKAGE)|" \ - $< > $@ - -@GSETTINGS_RULES@ - -EXTRA_DIST = \ - $(gsettings_file).in.in - -CLEANFILES = \ - $(gsettings_file) \ - $(gsettings_file).in \ - *.gschema.valid diff --git a/data/ayatana-indicator-notifications.desktop.in b/data/ayatana-indicator-notifications.desktop.in new file mode 100644 index 0000000..7dac4fd --- /dev/null +++ b/data/ayatana-indicator-notifications.desktop.in @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Name=Ayatana Indicator Notifications +Exec=@pkglibexecdir@/ayatana-indicator-notifications-service +OnlyShowIn=Unity;MATE;XFCE; +NoDisplay=true +StartupNotify=false +Terminal=false diff --git a/data/ayatana-indicator-notifications.service.in b/data/ayatana-indicator-notifications.service.in new file mode 100644 index 0000000..223cea9 --- /dev/null +++ b/data/ayatana-indicator-notifications.service.in @@ -0,0 +1,11 @@ +[Unit] +Description=Ayatana Indicator Notifications +PartOf=graphical-session.target +PartOf=ayatana-indicators.target + +[Service] +ExecStart=@pkglibexecdir@/ayatana-indicator-notifications-service +Restart=on-failure + +[Install] +WantedBy=ayatana-indicators.target diff --git a/data/icons/Makefile.am b/data/icons/Makefile.am deleted file mode 100644 index 47d1ac1..0000000 --- a/data/icons/Makefile.am +++ /dev/null @@ -1,11 +0,0 @@ -iconsdir = $(INDICATORICONSDIR) - -icons_DATA = \ - ayatana-indicator-notification-close-select.svg \ - ayatana-indicator-notification-close-deselect.svg \ - ayatana-indicator-notification-read.svg \ - ayatana-indicator-notification-read-dnd.svg \ - ayatana-indicator-notification-unread.svg \ - ayatana-indicator-notification-unread-dnd.svg - -EXTRA_DIST = $(icons_DATA) diff --git a/data/icons/ayatana-indicator-notification-close-deselect.svg b/data/icons/ayatana-indicator-notification-close-deselect.svg deleted file mode 100644 index 3e92220..0000000 --- a/data/icons/ayatana-indicator-notification-close-deselect.svg +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/data/icons/ayatana-indicator-notification-close-select.svg b/data/icons/ayatana-indicator-notification-close-select.svg deleted file mode 100644 index 21f5026..0000000 --- a/data/icons/ayatana-indicator-notification-close-select.svg +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/data/icons/ayatana-indicator-notification-read-dnd.svg b/data/icons/ayatana-indicator-notification-read-dnd.svg deleted file mode 100644 index d262e90..0000000 --- a/data/icons/ayatana-indicator-notification-read-dnd.svg +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - diff --git a/data/icons/ayatana-indicator-notification-read.svg b/data/icons/ayatana-indicator-notification-read.svg deleted file mode 100644 index 97e1f1e..0000000 --- a/data/icons/ayatana-indicator-notification-read.svg +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/data/icons/ayatana-indicator-notification-unread-dnd.svg b/data/icons/ayatana-indicator-notification-unread-dnd.svg deleted file mode 100644 index 23b859e..0000000 --- a/data/icons/ayatana-indicator-notification-unread-dnd.svg +++ /dev/null @@ -1,218 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - diff --git a/data/icons/ayatana-indicator-notification-unread.svg b/data/icons/ayatana-indicator-notification-unread.svg deleted file mode 100644 index beb2292..0000000 --- a/data/icons/ayatana-indicator-notification-unread.svg +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - diff --git a/data/icons/scalable/status/ayatana-indicator-notification-read-dnd.svg b/data/icons/scalable/status/ayatana-indicator-notification-read-dnd.svg new file mode 100644 index 0000000..d262e90 --- /dev/null +++ b/data/icons/scalable/status/ayatana-indicator-notification-read-dnd.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/data/icons/scalable/status/ayatana-indicator-notification-read.svg b/data/icons/scalable/status/ayatana-indicator-notification-read.svg new file mode 100644 index 0000000..97e1f1e --- /dev/null +++ b/data/icons/scalable/status/ayatana-indicator-notification-read.svg @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/data/icons/scalable/status/ayatana-indicator-notification-unread-dnd.svg b/data/icons/scalable/status/ayatana-indicator-notification-unread-dnd.svg new file mode 100644 index 0000000..23b859e --- /dev/null +++ b/data/icons/scalable/status/ayatana-indicator-notification-unread-dnd.svg @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/data/icons/scalable/status/ayatana-indicator-notification-unread.svg b/data/icons/scalable/status/ayatana-indicator-notification-unread.svg new file mode 100644 index 0000000..beb2292 --- /dev/null +++ b/data/icons/scalable/status/ayatana-indicator-notification-unread.svg @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/data/org.ayatana.indicator.notifications b/data/org.ayatana.indicator.notifications new file mode 100644 index 0000000..ec72e17 --- /dev/null +++ b/data/org.ayatana.indicator.notifications @@ -0,0 +1,11 @@ +[Indicator Service] +Name=ayatana-indicator-notifications +ObjectPath=/org/ayatana/indicator/notifications +Position=100 + +[phone] +ObjectPath=/org/ayatana/indicator/notifications/phone +Position=25 + +[desktop] +ObjectPath=/org/ayatana/indicator/notifications/desktop diff --git a/data/org.ayatana.indicator.notifications.gschema.xml.in b/data/org.ayatana.indicator.notifications.gschema.xml.in new file mode 100644 index 0000000..b85f14b --- /dev/null +++ b/data/org.ayatana.indicator.notifications.gschema.xml.in @@ -0,0 +1,25 @@ + + + + [] + <_summary>Discard notifications by application name + <_description>If an application name is in the filter list, all notifications matching the application name will be discarded. + + + [] + Recent application names to suggest for the filter list + Keeps track of recent application names so we can suggest them in the settings. + + + false + Enable do-not-disturb mode + On supported desktops enables do-not-disturb mode on the notification daemon. + + + + 5 + <_summary>Maximum number of visible items + <_description>The indicator will only display at most the number of notifications indicated by this value. + + + diff --git a/debian/control b/debian/control index f5db253..b35a063 100644 --- a/debian/control +++ b/debian/control @@ -4,17 +4,19 @@ Priority: optional Maintainer: Jason Conti Uploaders: Mike Gabriel -Build-Depends: debhelper (>= 9), - dh-autoreconf | debhelper (>= 9.20160403~), - dh-systemd | debhelper (>= 10.2~), +Build-Depends: cmake, + cmake-extras (>= 0.10), + gcovr, + lcov, + libnotify-dev (>= 0.7.6), + libglib2.0-dev (>= 2.36), + liburl-dispatcher1-dev | hello, +# for packaging + debhelper (>= 9), + dpkg-dev (>= 1.16.1.1), intltool, - libayatana-indicator-dev (>= 0.3.93), - libayatana-indicator3-dev (>= 0.3.93), - libglib2.0-dev (>= 2.29), - libgdk-pixbuf2.0-dev (>= 2.22), - libgtk-3-dev (>= 3.1), - mate-common, - pkg-config, + +# for systemd unit systemd [linux-any], Standards-Version: 4.1.3 Homepage: https://github.com/AyatanaIndicators/ayatana-indicator-notifications @@ -25,7 +27,7 @@ Package: ayatana-indicator-notifications Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, - libglib2.0-bin, + ayatana-indicator-common, Description: Ayatana Indicator for viewing recent notifications An Ayatana Indicator applet to display recent notifications sent to a notification daemon such as notify-osd. diff --git a/debian/rules b/debian/rules index 7382e4d..90fda29 100755 --- a/debian/rules +++ b/debian/rules @@ -1,35 +1,25 @@ #!/usr/bin/make -f +# -*- makefile -*- -DPKG_GENSYMBOLS_CHECK_LEVEL=4 +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +export DPKG_GENSYMBOLS_CHECK_LEVEL=4 export DEB_BUILD_MAINT_OPTIONS = hardening=+all DPKG_EXPORT_BUILDFLAGS = 1 include /usr/share/dpkg/buildflags.mk %: - dh $@ --with autoreconf,systemd - -override_dh_autoreconf: - if [ ! -e po/ayatana-indicator-notifications.pot.bak ]; then \ - cp po/ayatana-indicator-notifications.pot po/ayatana-indicator-notifications.pot.bak; \ - fi - NOCONFIGURE=1 dh_autoreconf ./autogen.sh - -override_dh_auto_configure: - dh_auto_configure -- \ - --disable-static + dh $@ --with systemd -override_dh_auto_install: - dh_auto_install +override_dh_install: find debian/ayatana-indicator-notifications -name \*.la -delete + find debian/ayatana-indicator-notifications -name \*.a -delete + dh_install --fail-missing override_dh_installchangelogs: dh_installchangelogs NEWS -override_dh_auto_clean: - dh_auto_clean - if [ -e po/ayatana-indicator-notifications.pot.bak ]; then \ - mv -f po/ayatana-indicator-notifications.pot.bak po/ayatana-indicator-notifications.pot; \ - fi - rm -f config.guess - rm -f config.sub +get-orig-source: + uscan --noconf --force-download --rename --download-current-version --destdir=.. diff --git a/po/CMakeLists.txt b/po/CMakeLists.txt new file mode 100644 index 0000000..0b2b2a1 --- /dev/null +++ b/po/CMakeLists.txt @@ -0,0 +1,6 @@ +find_package(Intltool REQUIRED) + +intltool_install_translations( + ALL + GETTEXT_PACKAGE ${GETTEXT_PACKAGE} +) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..eaebc58 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,41 @@ +set (SERVICE_LIB "ayatanaindicatornotificationsservice") +set (SERVICE_EXEC "ayatana-indicator-notifications-service") + +add_definitions(-DG_LOG_DOMAIN="ayatana-indicator-notifications") + +if(URLDISPATCHER_FOUND) + add_definitions( -DHAS_URLDISPATCHER ) +endif() + +# handwritten sources +set(SERVICE_MANUAL_SOURCES + urlregex.c + notification.c + dbus-spy.c + service.c) + +# generated sources +include(GdbusCodegen) +set(SERVICE_GENERATED_SOURCES) + +# 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-switch-enum") +set_source_files_properties(${SERVICE_MANUAL_SOURCES} + PROPERTIES COMPILE_FLAGS "${C_WARNING_ARGS} -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} -std=c99") +target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} ${URLDISPATCHER_LIBRARIES}) +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 e89ade2..0000000 --- a/src/Makefile.am +++ /dev/null @@ -1,36 +0,0 @@ -ayatananotificationslibdir = $(INDICATORDIR) -ayatananotificationslib_LTLIBRARIES = libayatana-notifications.la -libayatana_notifications_la_SOURCES = \ - dbus-spy.c \ - dbus-spy.h \ - urlregex.c \ - urlregex.h \ - notification-menuitem.c \ - notification-menuitem.h \ - settings.h \ - indicator-notifications.c \ - notification.c \ - notification.h -libayatana_notifications_la_CFLAGS = \ - -DSETTINGS_PATH=\""$(libexecdir)/$(PACKAGE)/indicator-notifications-settings"\" \ - $(INDICATOR_CFLAGS) \ - -Wall \ - -DG_LOG_DOMAIN=\"ayatana-indicator-notifications\" -libayatana_notifications_la_LIBADD = \ - $(INDICATOR_LIBS) -libayatana_notifications_la_LDFLAGS = \ - -module \ - -avoid-version - -pkglibexec_PROGRAMS = indicator-notifications-settings - -indicator_notifications_settings_SOURCES = \ - settings.h \ - indicator-notifications-settings.c - -indicator_notifications_settings_CFLAGS = \ - $(SETTINGS_CFLAGS) \ - -Wall - -indicator_notifications_settings_LDADD = \ - $(SETTINGS_LIBS) diff --git a/src/indicator-notifications-settings.c b/src/indicator-notifications-settings.c deleted file mode 100644 index 510a0ec..0000000 --- a/src/indicator-notifications-settings.c +++ /dev/null @@ -1,438 +0,0 @@ -/* - * indicator-notifications-settings.c - UI for indicator settings - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include -#include - -#include "settings.h" - -#define SCHEMA_KEY "schema-key" - -#define COLUMN_APPNAME 0 - -typedef struct -{ - GtkApplication parent_instance; - - GSettings *settings; - - GtkWidget *filter_list_treeview; - GtkWidget *filter_list_entry; - - /* GtkTreeModel foreach variables */ - gboolean result; - gchar *text; - GPtrArray *array; -} IndicatorNotificationsSettings; - -typedef GtkApplicationClass IndicatorNotificationsSettingsClass; - -G_DEFINE_TYPE(IndicatorNotificationsSettings, indicator_notifications_settings, GTK_TYPE_APPLICATION) - -/* Class Functions */ -static void indicator_notifications_settings_class_init(IndicatorNotificationsSettingsClass *klass); -static void indicator_notifications_settings_init(IndicatorNotificationsSettings *self); -static void indicator_notifications_settings_dispose(GObject *object); - -/* GtkApplication Signals */ -static void indicator_notifications_settings_activate(GApplication *app); - -/* Utility Functions */ -static void load_filter_list(IndicatorNotificationsSettings *self); -static void load_filter_list_hints(IndicatorNotificationsSettings *self); -static void save_filter_list(IndicatorNotificationsSettings *self); -static gboolean foreach_check_duplicates(GtkTreeModel *model, GtkTreePath *path, - GtkTreeIter *iter, gpointer user_data); -static gboolean foreach_build_array(GtkTreeModel *model, GtkTreePath *path, - GtkTreeIter *iter, gpointer user_data); - -/* Callbacks */ -static void filter_list_add_clicked_cb(GtkButton *button, gpointer user_data); -static void filter_list_remove_clicked_cb(GtkButton *button, gpointer user_data); -static void button_toggled_cb(GtkToggleButton *button, gpointer user_data); -static void max_items_changed_cb(GtkSpinButton *button, gpointer user_data); -static gboolean filter_list_entry_focus_in_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data); - -static void -load_filter_list(IndicatorNotificationsSettings *self) -{ - GtkListStore *list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(self->filter_list_treeview))); - GtkTreeIter iter; - gchar **items; - - gtk_list_store_clear(list); - - items = g_settings_get_strv(self->settings, NOTIFICATIONS_KEY_FILTER_LIST); - - for (int i = 0; items[i] != NULL; i++) { - gtk_list_store_append(list, &iter); - gtk_list_store_set(list, &iter, COLUMN_APPNAME, items[i], -1); - } - - g_strfreev(items); -} - -static void -load_filter_list_hints(IndicatorNotificationsSettings *self) -{ - GtkEntryCompletion *completion = gtk_entry_get_completion(GTK_ENTRY(self->filter_list_entry)); - GtkListStore *list = GTK_LIST_STORE(gtk_entry_completion_get_model(completion)); - GtkTreeIter iter; - gchar **items; - - gtk_list_store_clear(list); - - items = g_settings_get_strv(self->settings, NOTIFICATIONS_KEY_FILTER_LIST_HINTS); - - for (int i = 0; items[i] != NULL; i++) { - gtk_list_store_append(list, &iter); - gtk_list_store_set(list, &iter, 0, items[i], -1); - } - - g_strfreev(items); -} - -static void -save_filter_list(IndicatorNotificationsSettings *self) -{ - GtkListStore *list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(self->filter_list_treeview))); - gchar **items; - - /* build an array of the filter list items */ - self->array = g_ptr_array_new(); - gtk_tree_model_foreach(GTK_TREE_MODEL(list), foreach_build_array, self); - g_ptr_array_add(self->array, NULL); - items = (gchar **) g_ptr_array_free(self->array, FALSE); - self->array = NULL; - - g_settings_set_strv(self->settings, NOTIFICATIONS_KEY_FILTER_LIST, (const gchar **) items); - - g_strfreev(items); -} - -static gboolean -foreach_check_duplicates(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) -{ - IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) user_data; - gchar *appname; - gboolean result = FALSE; - - gtk_tree_model_get(model, iter, COLUMN_APPNAME, &appname, -1); - - if (g_strcmp0(appname, self->text) == 0) { - result = TRUE; - self->result = TRUE; - } - - g_free(appname); - - return result; -} - -static gboolean -foreach_build_array(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) -{ - IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) user_data; - gchar *appname; - - gtk_tree_model_get(model, iter, COLUMN_APPNAME, &appname, -1); - - g_ptr_array_add(self->array, appname); - - return FALSE; -} - -static void -filter_list_add_clicked_cb(GtkButton *button, gpointer user_data) -{ - IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) user_data; - GtkListStore *list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(self->filter_list_treeview))); - GtkTreeIter iter; - - /* strip off the leading and trailing whitespace in case of user error */ - self->text = g_strdup(gtk_entry_get_text(GTK_ENTRY(self->filter_list_entry))); - g_strstrip(self->text); - - if (strlen(self->text) > 0) { - /* check for duplicates first */ - self->result = FALSE; - gtk_tree_model_foreach(GTK_TREE_MODEL(list), foreach_check_duplicates, self); - - if (self->result == FALSE) { - gtk_list_store_append(list, &iter); - gtk_list_store_set(list, &iter, COLUMN_APPNAME, self->text, -1); - save_filter_list(self); - } - } - - /* clear the entry */ - gtk_entry_set_text(GTK_ENTRY(self->filter_list_entry), ""); - - /* cleanup text */ - g_free(self->text); - self->text = ""; -} - -static void -filter_list_remove_clicked_cb(GtkButton *button, gpointer user_data) -{ - IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) user_data; - GtkListStore *list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(self->filter_list_treeview))); - GtkTreeIter iter; - GtkTreeSelection *selection; - - selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self->filter_list_treeview)); - - if (gtk_tree_selection_get_selected(selection, NULL, &iter) == TRUE) { - gtk_list_store_remove(list, &iter); - - save_filter_list(self); - } -} - -static void -button_toggled_cb(GtkToggleButton *button, gpointer user_data) -{ - GSettings *settings = G_SETTINGS(user_data); - char *schema_key = (char *) g_object_get_data(G_OBJECT(button), SCHEMA_KEY); - - g_settings_set_boolean(settings, schema_key, gtk_toggle_button_get_active(button)); -} - -static void -max_items_changed_cb(GtkSpinButton *button, gpointer user_data) -{ - GSettings *settings = G_SETTINGS(user_data); - - int value = gtk_spin_button_get_value_as_int(button); - g_settings_set_int(settings, NOTIFICATIONS_KEY_MAX_ITEMS, value); -} - -static gboolean -filter_list_entry_focus_in_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) -{ - IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) user_data; - load_filter_list_hints(self); - g_signal_emit_by_name(widget, "changed", NULL); - return FALSE; -} - -static void -indicator_notifications_settings_activate(GApplication *app) -{ - GtkWidget *window; - GtkWidget *frame; - GtkWidget *vbox; - GtkWidget *button_cmc; - GtkWidget *button_hide_ind; - GtkWidget *button_dnd; - GtkWidget *button_swap_clr_s; - GtkWidget *spin; - GtkWidget *spin_label; - GtkWidget *filter_list_label; - GtkListStore *filter_list_list; - GtkWidget *filter_list_scroll; - GtkTreeViewColumn *column; - GtkCellRenderer *renderer; - GtkWidget *hbox; - GtkWidget *button_filter_list_rem; - GtkWidget *button_filter_list_add; - GtkEntryCompletion *entry_completion; - GtkListStore *entry_list; - - IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) app; - - /* Check for a pre-existing window */ - GtkWindow *old_window = gtk_application_get_active_window(GTK_APPLICATION(app)); - if (old_window != NULL) { - gtk_window_present_with_time(old_window, GDK_CURRENT_TIME); - return; - } - - /* GSettings */ - self->settings = g_settings_new(NOTIFICATIONS_SCHEMA); - - /* Main Window */ - window = gtk_application_window_new(GTK_APPLICATION(app)); - gtk_window_set_title(GTK_WINDOW(window), _("Indicator Notifications Settings")); - gtk_window_set_default_size(GTK_WINDOW(window), 400, 400); - gtk_container_set_border_width(GTK_CONTAINER(window), 10); - gtk_widget_show(window); - - /* Window Frame */ - frame = gtk_frame_new(_("Indicator Notifications Settings")); - gtk_container_add(GTK_CONTAINER(window), frame); - gtk_widget_show(frame); - - /* Main Vertical Box */ - vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_container_set_border_width(GTK_CONTAINER(vbox), 10); - gtk_container_add(GTK_CONTAINER(frame), vbox); - gtk_widget_show(vbox); - - /* clear-on-middle-click */ - button_cmc = gtk_check_button_new_with_label(_("Clear notifications on middle click")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button_cmc), - g_settings_get_boolean(self->settings, NOTIFICATIONS_KEY_CLEAR_MC)); - g_object_set_data(G_OBJECT(button_cmc), SCHEMA_KEY, NOTIFICATIONS_KEY_CLEAR_MC); - g_signal_connect(button_cmc, "toggled", G_CALLBACK(button_toggled_cb), self->settings); - gtk_box_pack_start(GTK_BOX(vbox), button_cmc, FALSE, FALSE, 4); - gtk_widget_show(button_cmc); - - /* hide-indicator */ - button_hide_ind = gtk_check_button_new_with_label(_("Hide indicator")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button_hide_ind), - g_settings_get_boolean(self->settings, NOTIFICATIONS_KEY_HIDE_INDICATOR)); - g_object_set_data(G_OBJECT(button_hide_ind), SCHEMA_KEY, NOTIFICATIONS_KEY_HIDE_INDICATOR); - g_signal_connect(button_hide_ind, "toggled", G_CALLBACK(button_toggled_cb), self->settings); - gtk_box_pack_start(GTK_BOX(vbox), button_hide_ind, FALSE, FALSE, 4); - gtk_widget_show(button_hide_ind); - - /* do-not-disturb */ - button_dnd = gtk_check_button_new_with_label(_("Do not disturb")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button_dnd), - g_settings_get_boolean(self->settings, NOTIFICATIONS_KEY_DND)); - g_object_set_data(G_OBJECT(button_dnd), SCHEMA_KEY, NOTIFICATIONS_KEY_DND); - g_signal_connect(button_dnd, "toggled", G_CALLBACK(button_toggled_cb), self->settings); - gtk_box_pack_start(GTK_BOX(vbox), button_dnd, FALSE, FALSE, 4); - gtk_widget_show(button_dnd); - - /* swap-clear-settings */ - button_swap_clr_s = gtk_check_button_new_with_label(_("Swap \"Clear\" and \"Settings\" items")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button_swap_clr_s), - g_settings_get_boolean(self->settings, NOTIFICATIONS_KEY_SWAP_CLEAR_SETTINGS)); - g_object_set_data(G_OBJECT(button_swap_clr_s), SCHEMA_KEY, NOTIFICATIONS_KEY_SWAP_CLEAR_SETTINGS); - g_signal_connect(button_swap_clr_s, "toggled", G_CALLBACK(button_toggled_cb), self->settings); - gtk_box_pack_start(GTK_BOX(vbox), button_swap_clr_s, FALSE, FALSE, 4); - gtk_widget_show(button_swap_clr_s); - - /* max-items */ - /* FIXME: indicator does not change max items until restart... */ - spin_label = gtk_label_new(_("Maximum number of visible notifications")); - gtk_box_pack_start(GTK_BOX(vbox), spin_label, FALSE, FALSE, 4); - gtk_widget_show(spin_label); - - spin = gtk_spin_button_new_with_range(1, 10, 1); - gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), g_settings_get_int(self->settings, NOTIFICATIONS_KEY_MAX_ITEMS)); - g_signal_connect(spin, "value-changed", G_CALLBACK(max_items_changed_cb), self->settings); - gtk_box_pack_start(GTK_BOX(vbox), spin, FALSE, FALSE, 4); - gtk_widget_show(spin); - - /* filter-list */ - filter_list_label = gtk_label_new(_("Discard notifications by application name")); - gtk_box_pack_start(GTK_BOX(vbox), filter_list_label, FALSE, FALSE, 4); - gtk_widget_show(filter_list_label); - - filter_list_scroll = gtk_scrolled_window_new(NULL, NULL); - gtk_box_pack_start(GTK_BOX(vbox), filter_list_scroll, TRUE, TRUE, 4); - gtk_widget_show(filter_list_scroll); - - filter_list_list = gtk_list_store_new(1, G_TYPE_STRING); - - renderer = gtk_cell_renderer_text_new(); - column = gtk_tree_view_column_new_with_attributes("appname", renderer, "text", COLUMN_APPNAME, NULL); - - self->filter_list_treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(filter_list_list)); - g_object_unref(filter_list_list); - gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(self->filter_list_treeview), FALSE); - gtk_tree_view_append_column(GTK_TREE_VIEW(self->filter_list_treeview), column); - load_filter_list(self); - gtk_container_add(GTK_CONTAINER(filter_list_scroll), self->filter_list_treeview); - gtk_widget_show(self->filter_list_treeview); - - hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); - gtk_widget_show(hbox); - - button_filter_list_rem = gtk_button_new_with_label(_("Remove")); - g_signal_connect(button_filter_list_rem, "clicked", G_CALLBACK(filter_list_remove_clicked_cb), self); - gtk_box_pack_start(GTK_BOX(hbox), button_filter_list_rem, FALSE, FALSE, 2); - gtk_widget_show(button_filter_list_rem); - - button_filter_list_add = gtk_button_new_with_label(_("Add")); - g_signal_connect(button_filter_list_add, "clicked", G_CALLBACK(filter_list_add_clicked_cb), self); - gtk_box_pack_start(GTK_BOX(hbox), button_filter_list_add, FALSE, FALSE, 2); - gtk_widget_show(button_filter_list_add); - - self->filter_list_entry = gtk_entry_new(); - gtk_box_pack_start(GTK_BOX(hbox), self->filter_list_entry, TRUE, TRUE, 0); - gtk_widget_show(self->filter_list_entry); - - entry_completion = gtk_entry_completion_new(); - entry_list = gtk_list_store_new(1, G_TYPE_STRING); - gtk_entry_completion_set_model(entry_completion, GTK_TREE_MODEL(entry_list)); - gtk_entry_completion_set_text_column(entry_completion, 0); - gtk_entry_completion_set_minimum_key_length(entry_completion, 0); - gtk_entry_set_completion(GTK_ENTRY(self->filter_list_entry), entry_completion); - /* When we focus the entry, emit the changed signal so we get the hints immediately */ - /* also update the filter list hints from gsettings */ - g_signal_connect(self->filter_list_entry, "focus-in-event", G_CALLBACK(filter_list_entry_focus_in_cb), self); -} - -static void -indicator_notifications_settings_init (IndicatorNotificationsSettings *self) -{ -} - -static void -indicator_notifications_settings_class_init (IndicatorNotificationsSettingsClass *class) -{ - GApplicationClass *application_class = G_APPLICATION_CLASS (class); - GObjectClass *object_class = G_OBJECT_CLASS (class); - - application_class->activate = indicator_notifications_settings_activate; - - object_class->dispose = indicator_notifications_settings_dispose; -} - -static void -indicator_notifications_settings_dispose(GObject *object) -{ - IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) object; - - if(self->settings != NULL) { - g_object_unref(G_OBJECT(self->settings)); - self->settings = NULL; - } - - G_OBJECT_CLASS(indicator_notifications_settings_parent_class)->dispose(object); -} - -IndicatorNotificationsSettings * -indicator_notifications_settings_new (void) -{ - IndicatorNotificationsSettings *self; - - g_set_application_name(_("Indicator Notifications Settings")); - - self = g_object_new(indicator_notifications_settings_get_type(), - "application-id", NOTIFICATIONS_SCHEMA ".settings", - "flags", G_APPLICATION_FLAGS_NONE, - NULL); - - return self; -} - -int -main(int argc, char **argv) -{ - IndicatorNotificationsSettings *self; - int status; - - setlocale(LC_ALL, ""); - bindtextdomain( GETTEXT_PACKAGE, LOCALEDIR ); - textdomain( GETTEXT_PACKAGE ); - - self = indicator_notifications_settings_new(); - - status = g_application_run(G_APPLICATION(self), argc, argv); - - g_object_unref(self); - - return status; -} diff --git a/src/indicator-notifications.c b/src/indicator-notifications.c deleted file mode 100644 index dffa57c..0000000 --- a/src/indicator-notifications.c +++ /dev/null @@ -1,897 +0,0 @@ -/* -An indicator to display recent notifications. - -Adapted from: indicator-datetime/src/indicator-datetime.c by - Ted Gould - -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 . -*/ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -/* GStuff */ -#include -#include -#include - -/* Indicator Stuff */ -#include -#include -#include - -#include "dbus-spy.h" -#include "notification-menuitem.h" - -#define INDICATOR_NOTIFICATIONS_TYPE (indicator_notifications_get_type ()) -#define INDICATOR_NOTIFICATIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INDICATOR_NOTIFICATIONS_TYPE, IndicatorNotifications)) -#define INDICATOR_NOTIFICATIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INDICATOR_NOTIFICATIONS_TYPE, IndicatorNotificationsClass)) -#define IS_INDICATOR_NOTIFICATIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INDICATOR_NOTIFICATIONS_TYPE)) -#define IS_INDICATOR_NOTIFICATIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INDICATOR_NOTIFICATIONS_TYPE)) -#define INDICATOR_NOTIFICATIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), INDICATOR_NOTIFICATIONS_TYPE, IndicatorNotificationsClass)) - -typedef struct _IndicatorNotifications IndicatorNotifications; -typedef struct _IndicatorNotificationsClass IndicatorNotificationsClass; -typedef struct _IndicatorNotificationsPrivate IndicatorNotificationsPrivate; - -struct _IndicatorNotificationsClass { - IndicatorObjectClass parent_class; -}; - -struct _IndicatorNotifications { - IndicatorObject parent; - IndicatorNotificationsPrivate *priv; -}; - -struct _IndicatorNotificationsPrivate { - GtkImage *image; - - GList *visible_items; - GList *hidden_items; - - gboolean clear_on_middle_click; - gboolean do_not_disturb; - gboolean have_unread; - gboolean hide_indicator; - gboolean swap_clear_settings; - - gint max_items; - - GtkMenu *menu; - GtkWidget *clear_item; - GtkWidget *clear_item_label; - GtkWidget *settings_item; - GtkWidget *pSeparator; - - gchar *accessible_desc; - - DBusSpy *spy; - - GHashTable *filter_list; - - GList *filter_list_hints; - - GSettings *settings; -}; - -#include "settings.h" - -#define INDICATOR_ICON_SIZE 22 -#define INDICATOR_ICON_READ "ayatana-indicator-notification-read" -#define INDICATOR_ICON_UNREAD "ayatana-indicator-notification-unread" -#define INDICATOR_ICON_READ_DND "ayatana-indicator-notification-read-dnd" -#define INDICATOR_ICON_UNREAD_DND "ayatana-indicator-notification-unread-dnd" - -#define HINT_MAX 10 - -GType indicator_notifications_get_type(void); - -/* Indicator Class Functions */ -static void indicator_notifications_class_init(IndicatorNotificationsClass *klass); -static void indicator_notifications_init(IndicatorNotifications *self); -static void indicator_notifications_dispose(GObject *object); -static void indicator_notifications_finalize(GObject *object); - -/* Indicator Standard Methods */ -static GtkImage *get_image(IndicatorObject *io); -static GtkMenu *get_menu(IndicatorObject *io); -static const gchar *get_accessible_desc(IndicatorObject *io); -static void indicator_notifications_middle_click(IndicatorObject *io, - IndicatorObjectEntry *entry, - guint time, - gpointer user_data); - -/* Utility Functions */ -static void clear_menuitems(IndicatorNotifications *self); -static void insert_menuitem(IndicatorNotifications *self, GtkWidget *item); -static void remove_menuitem(IndicatorNotifications *self, GtkWidget *item); -static void set_unread(IndicatorNotifications *self, gboolean unread); -static void update_unread(IndicatorNotifications *self); -static void update_filter_list(IndicatorNotifications *self); -static void update_clear_item_markup(IndicatorNotifications *self); -static void update_indicator_visibility(IndicatorNotifications *self); -static void load_filter_list_hints(IndicatorNotifications *self); -static void save_filter_list_hints(IndicatorNotifications *self); -static void update_filter_list_hints(IndicatorNotifications *self, Notification *notification); -static void update_do_not_disturb(IndicatorNotifications *self); -static void settings_try_set_boolean(const gchar *schema, const gchar *key, gboolean value); -static void swap_clear_settings_items(IndicatorNotifications *self); - -/* Callbacks */ -static void clear_item_activated_cb(GtkMenuItem *menuitem, gpointer user_data); -static void menu_visible_notify_cb(GtkWidget *menu, GParamSpec *pspec, gpointer user_data); -static void message_received_cb(DBusSpy *spy, Notification *note, gpointer user_data); -static void notification_clicked_cb(NotificationMenuItem *menuitem, guint button, gpointer user_data); -static void setting_changed_cb(GSettings *settings, gchar *key, gpointer user_data); -static void settings_item_activated_cb(GtkMenuItem *menuitem, gpointer user_data); - -/* Indicator Module Config */ -INDICATOR_SET_VERSION -INDICATOR_SET_TYPE(INDICATOR_NOTIFICATIONS_TYPE) - -G_DEFINE_TYPE_WITH_PRIVATE(IndicatorNotifications, indicator_notifications, INDICATOR_OBJECT_TYPE); - -static void -indicator_notifications_class_init(IndicatorNotificationsClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - setlocale(LC_ALL, ""); - bindtextdomain( GETTEXT_PACKAGE, LOCALEDIR ); - textdomain( GETTEXT_PACKAGE ); - - object_class->dispose = indicator_notifications_dispose; - object_class->finalize = indicator_notifications_finalize; - - IndicatorObjectClass *io_class = INDICATOR_OBJECT_CLASS(klass); - - io_class->get_image = get_image; - io_class->get_menu = get_menu; - io_class->get_accessible_desc = get_accessible_desc; - io_class->secondary_activate = indicator_notifications_middle_click; - - return; -} - -static void -indicator_notifications_init(IndicatorNotifications *self) -{ - self->priv = indicator_notifications_get_instance_private(self); - - self->priv->menu = NULL; - - self->priv->image = NULL; - - self->priv->have_unread = FALSE; - - self->priv->accessible_desc = _("Notifications"); - - self->priv->visible_items = NULL; - self->priv->hidden_items = NULL; - - self->priv->menu = GTK_MENU(gtk_menu_new()); - g_signal_connect(self->priv->menu, "notify::visible", G_CALLBACK(menu_visible_notify_cb), self); - - /* Create the settings menuitem */ - self->priv->settings_item = gtk_menu_item_new_with_label(_("Settings…")); - g_signal_connect(self->priv->settings_item, "activate", G_CALLBACK(settings_item_activated_cb), NULL); - gtk_widget_show(self->priv->settings_item); - - gtk_menu_shell_prepend(GTK_MENU_SHELL(self->priv->menu), self->priv->settings_item); - - /* Create the clear menuitem */ - self->priv->clear_item_label = gtk_label_new(NULL); - gtk_label_set_xalign(GTK_LABEL(self->priv->clear_item_label), 0); - gtk_label_set_yalign(GTK_LABEL(self->priv->clear_item_label), 0); - gtk_label_set_use_markup(GTK_LABEL(self->priv->clear_item_label), TRUE); - update_clear_item_markup(self); - gtk_widget_show(self->priv->clear_item_label); - - self->priv->clear_item = gtk_menu_item_new(); - g_signal_connect(self->priv->clear_item, "activate", G_CALLBACK(clear_item_activated_cb), self); - gtk_container_add(GTK_CONTAINER(self->priv->clear_item), self->priv->clear_item_label); - gtk_widget_show(self->priv->clear_item); - - gtk_menu_shell_prepend(GTK_MENU_SHELL(self->priv->menu), self->priv->clear_item); - - self->priv->pSeparator = gtk_separator_menu_item_new(); - gtk_menu_shell_prepend(GTK_MENU_SHELL(self->priv->menu), self->priv->pSeparator); - - /* Watch for notifications from dbus */ - self->priv->spy = dbus_spy_new(); - g_signal_connect(self->priv->spy, DBUS_SPY_SIGNAL_MESSAGE_RECEIVED, G_CALLBACK(message_received_cb), self); - - /* Initialize an empty filter list */ - self->priv->filter_list = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); - - /* Connect to GSettings */ - self->priv->settings = g_settings_new(NOTIFICATIONS_SCHEMA); - self->priv->clear_on_middle_click = g_settings_get_boolean(self->priv->settings, NOTIFICATIONS_KEY_CLEAR_MC); - self->priv->do_not_disturb = g_settings_get_boolean(self->priv->settings, NOTIFICATIONS_KEY_DND); - self->priv->hide_indicator = g_settings_get_boolean(self->priv->settings, NOTIFICATIONS_KEY_HIDE_INDICATOR); - self->priv->max_items = g_settings_get_int(self->priv->settings, NOTIFICATIONS_KEY_MAX_ITEMS); - update_filter_list(self); - self->priv->swap_clear_settings = g_settings_get_boolean(self->priv->settings, NOTIFICATIONS_KEY_SWAP_CLEAR_SETTINGS); - if(self->priv->swap_clear_settings) - swap_clear_settings_items(self); - g_signal_connect(self->priv->settings, "changed", G_CALLBACK(setting_changed_cb), self); - - /* Set up filter-list hints */ - self->priv->filter_list_hints = NULL; - load_filter_list_hints(self); -} - -static void -indicator_notifications_dispose(GObject *object) -{ - IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(object); - - if(self->priv->image != NULL) { - g_object_unref(G_OBJECT(self->priv->image)); - self->priv->image = NULL; - } - - if(self->priv->visible_items != NULL) { - g_list_free_full(self->priv->visible_items, g_object_unref); - self->priv->visible_items = NULL; - } - - if(self->priv->hidden_items != NULL) { - g_list_free_full(self->priv->hidden_items, g_object_unref); - self->priv->hidden_items = NULL; - } - - if(self->priv->menu != NULL) { - g_object_unref(G_OBJECT(self->priv->menu)); - self->priv->menu = NULL; - } - - if(self->priv->spy != NULL) { - g_object_unref(G_OBJECT(self->priv->spy)); - self->priv->spy = NULL; - } - - if(self->priv->settings != NULL) { - g_object_unref(G_OBJECT(self->priv->settings)); - self->priv->settings = NULL; - } - - if(self->priv->filter_list != NULL) { - g_hash_table_unref(self->priv->filter_list); - self->priv->filter_list = NULL; - } - - if(self->priv->filter_list_hints != NULL) { - g_list_free_full(self->priv->filter_list_hints, g_free); - self->priv->filter_list_hints = NULL; - } - - G_OBJECT_CLASS (indicator_notifications_parent_class)->dispose (object); - return; -} - -static void -indicator_notifications_finalize(GObject *object) -{ - G_OBJECT_CLASS (indicator_notifications_parent_class)->finalize (object); - return; -} - -static GtkImage * -get_image(IndicatorObject *io) -{ - IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(io); - - if(self->priv->image == NULL) { - self->priv->image = GTK_IMAGE(gtk_image_new()); - /* We have to wait until the image is created to update do-not-disturb the first time */ - update_do_not_disturb(self); - update_indicator_visibility(self); - } - - return self->priv->image; -} - -static GtkMenu * -get_menu(IndicatorObject *io) -{ - IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(io); - - return GTK_MENU(self->priv->menu); -} - -static const gchar * -get_accessible_desc(IndicatorObject *io) -{ - IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(io); - - return self->priv->accessible_desc; -} - -static void -indicator_notifications_middle_click(IndicatorObject *io, IndicatorObjectEntry *entry, - guint time, gpointer user_data) -{ - IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(io); - - /* Clear the notifications */ - if(self->priv->clear_on_middle_click) { - clear_menuitems(self); - set_unread(self, FALSE); - } - /* Otherwise toggle unread status */ - else { - if(g_list_length(self->priv->visible_items) > 0) - set_unread(self, !self->priv->have_unread); - } -} - -/** - * clear_menuitems: - * @self: the indicator - * - * Clear all notification menuitems from the menu and the visible/hidden lists. - **/ -static void -clear_menuitems(IndicatorNotifications *self) -{ - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self)); - GList *item; - - /* Remove each visible item from the menu */ - for(item = self->priv->visible_items; item; item = item->next) { - gtk_container_remove(GTK_CONTAINER(self->priv->menu), GTK_WIDGET(item->data)); - } - - /* Clear the lists */ - g_list_free_full(self->priv->visible_items, g_object_unref); - self->priv->visible_items = NULL; - - g_list_free_full(self->priv->hidden_items, g_object_unref); - self->priv->hidden_items = NULL; - - update_clear_item_markup(self); -} - -/** - * insert_menuitem: - * @self: the indicator - * @item: the menuitem to insert - * - * Inserts a menuitem into the indicator's menu and updates the visible and - * hidden lists. - **/ -static void -insert_menuitem(IndicatorNotifications *self, GtkWidget *item) -{ - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self)); - g_return_if_fail(GTK_IS_MENU_ITEM(item)); - GList *last_item; - GtkWidget *last_widget; - - /* List holds a ref to the menuitem */ - self->priv->visible_items = g_list_prepend(self->priv->visible_items, g_object_ref(item)); - gtk_menu_shell_prepend(GTK_MENU_SHELL(self->priv->menu), item); - - /* Move items that overflow to the hidden list */ - while(g_list_length(self->priv->visible_items) > self->priv->max_items) { - last_item = g_list_last(self->priv->visible_items); - last_widget = GTK_WIDGET(last_item->data); - /* Steal the ref from the visible list */ - self->priv->visible_items = g_list_delete_link(self->priv->visible_items, last_item); - self->priv->hidden_items = g_list_prepend(self->priv->hidden_items, last_widget); - gtk_container_remove(GTK_CONTAINER(self->priv->menu), last_widget); - last_item = NULL; - last_widget = NULL; - } - - update_clear_item_markup(self); -} - -/** - * remove_menuitem: - * @self: the indicator object - * @item: the menuitem - * - * Removes a menuitem from the indicator menu and the visible list. - **/ -static void -remove_menuitem(IndicatorNotifications *self, GtkWidget *item) -{ - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self)); - g_return_if_fail(GTK_IS_MENU_ITEM(item)); - - GList *list_item = g_list_find(self->priv->visible_items, item); - - if(list_item == NULL) { - g_warning("Attempt to remove menuitem not in visible list"); - return; - } - - /* Remove the item */ - gtk_container_remove(GTK_CONTAINER(self->priv->menu), item); - self->priv->visible_items = g_list_delete_link(self->priv->visible_items, list_item); - g_object_unref(item); - - /* Add an item from the hidden list, if available */ - if(g_list_length(self->priv->hidden_items) > 0) { - list_item = g_list_first(self->priv->hidden_items); - GtkWidget *list_widget = GTK_WIDGET(list_item->data); - self->priv->hidden_items = g_list_delete_link(self->priv->hidden_items, list_item); - gtk_menu_shell_insert(GTK_MENU_SHELL(self->priv->menu), list_widget, - g_list_length(self->priv->visible_items)); - /* Steal the ref back from the hidden list */ - self->priv->visible_items = g_list_append(self->priv->visible_items, list_widget); - } - - update_clear_item_markup(self); -} - -/** - * set_unread: - * @self: the indicator object - * @unread: the unread status - * - * Sets the unread status of the indicator and updates the icons. - **/ -static void -set_unread(IndicatorNotifications *self, gboolean unread) -{ - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self)); - - self->priv->have_unread = unread; - update_unread(self); -} - -/** - * update_unread: - * @self: the indicator object - * - * Updates the indicator icons. - **/ -static void -update_unread(IndicatorNotifications *self) -{ - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self)); - - if(self->priv->have_unread) { - if (self->priv->do_not_disturb) { - gtk_image_set_from_icon_name(self->priv->image, INDICATOR_ICON_UNREAD_DND, GTK_ICON_SIZE_MENU); - } - else { - gtk_image_set_from_icon_name(self->priv->image, INDICATOR_ICON_UNREAD, GTK_ICON_SIZE_MENU); - } - } - else { - if (self->priv->do_not_disturb) { - gtk_image_set_from_icon_name(self->priv->image, INDICATOR_ICON_READ_DND, GTK_ICON_SIZE_MENU); - } - else { - gtk_image_set_from_icon_name(self->priv->image, INDICATOR_ICON_READ, GTK_ICON_SIZE_MENU); - } - } -} - -/** - * update_filter_list: - * @self: the indicator object - * - * Updates the filter list from GSettings. This currently does not filter already - * allowed messages. It only applies to messages received in the future. - **/ -static void -update_filter_list(IndicatorNotifications *self) -{ - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self)); - g_return_if_fail(self->priv->filter_list != NULL); - - g_hash_table_remove_all(self->priv->filter_list); - gchar **items = g_settings_get_strv(self->priv->settings, NOTIFICATIONS_KEY_FILTER_LIST); - int i; - - for(i = 0; items[i] != NULL; i++) { - g_hash_table_insert(self->priv->filter_list, g_strdup(items[i]), NULL); - } - - g_strfreev(items); -} - -/** - * update_clear_item_markup: - * @self: the indicator object - * - * Updates the clear menuitem's label markup based on the number of - * notifications available. - **/ -static void -update_clear_item_markup(IndicatorNotifications *self) -{ - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self)); - guint visible_length = g_list_length(self->priv->visible_items); - guint hidden_length = g_list_length(self->priv->hidden_items); - guint total_length = visible_length + hidden_length; - - gchar *markup = g_strdup_printf(ngettext( - "Clear (%d Notification)", - "Clear (%d Notifications)", - total_length), - total_length); - - gtk_label_set_markup(GTK_LABEL(self->priv->clear_item_label), markup); - g_free(markup); - - if (total_length == 0) - { - gtk_widget_hide(self->priv->pSeparator); - gtk_menu_shell_deactivate(GTK_MENU_SHELL(self->priv->menu)); - } - else - { - gtk_widget_show(self->priv->pSeparator); - } -} - -/** - * update_indicator_visibility: - * @self: the indicator object - * - * Changes the visibility of the indicator image based on the value - * of hide_indicator. - **/ -static void -update_indicator_visibility(IndicatorNotifications *self) -{ - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self)); - - if(self->priv->image != NULL) { - if(self->priv->hide_indicator) - gtk_widget_hide(GTK_WIDGET(self->priv->image)); - else - gtk_widget_show(GTK_WIDGET(self->priv->image)); - } -} - -/** - * load_filter_list_hints: - * @self: the indicator object - * - * Loads the filter list hints from gsettings - **/ -static void -load_filter_list_hints(IndicatorNotifications *self) -{ - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self)); - g_return_if_fail(self->priv->filter_list_hints == NULL); - - gchar **items = g_settings_get_strv(self->priv->settings, NOTIFICATIONS_KEY_FILTER_LIST_HINTS); - int i; - - for (i = 0; items[i] != NULL; i++) { - self->priv->filter_list_hints = g_list_prepend(self->priv->filter_list_hints, items[i]); - } - - g_free(items); -} - -/** - * save_filter_list_hints: - * @self: the indicator object - * - * Saves the filter list hints to gsettings - **/ -static void -save_filter_list_hints(IndicatorNotifications *self) -{ - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self)); - - gchar *hints[HINT_MAX + 1]; - int i = 0; - - GList *l; - for (l = self->priv->filter_list_hints; (l != NULL) && (i < HINT_MAX); l = l->next, i++) { - hints[i] = (gchar *) l->data; - } - - hints[i] = NULL; - - g_settings_set_strv(self->priv->settings, NOTIFICATIONS_KEY_FILTER_LIST_HINTS, (const gchar **) hints); -} - -/** - * update_filter_list_hints: - * @self: the indicator object - * - * Adds an application name to the hints - **/ -static void -update_filter_list_hints(IndicatorNotifications *self, Notification *notification) -{ - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self)); - g_return_if_fail(IS_NOTIFICATION(notification)); - - const gchar *appname = notification_get_app_name(notification); - - /* Avoid duplicates */ - GList *l; - for (l = self->priv->filter_list_hints; l != NULL; l = l->next) { - if (g_strcmp0(appname, (const gchar *) l->data) == 0) - return; - } - - /* Add the appname */ - self->priv->filter_list_hints = g_list_prepend(self->priv->filter_list_hints, g_strdup(appname)); - - /* Keep only a reasonable number */ - while (g_list_length(self->priv->filter_list_hints) > HINT_MAX) { - GList *last = g_list_last(self->priv->filter_list_hints); - g_free(last->data); - self->priv->filter_list_hints = g_list_delete_link(self->priv->filter_list_hints, last); - } - - /* Save the hints */ - /* FIXME: maybe don't do this every update */ - save_filter_list_hints(self); -} - -/** - * update_do_not_disturb: - * @self: the indicator object - * - * Updates the icon with the do-not-disturb version and sets do-not-disturb options - * on external notification daemons that are supported. - **/ -static void -update_do_not_disturb(IndicatorNotifications *self) -{ - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self)); - - update_unread(self); - - /* Mate do-not-disturb support */ - settings_try_set_boolean(MATE_SCHEMA, MATE_KEY_DND, self->priv->do_not_disturb); -} - -/** - * settings_try_set_boolean: - * @schema: the GSettings schema - * @key: the GSettings key - * @value: the boolean value - * - * Checks to see if the schema and key exist before setting the value. - */ -static void -settings_try_set_boolean(const gchar *schema, const gchar *key, gboolean value) -{ - /* Check if we can access the schema */ - GSettingsSchemaSource *source = g_settings_schema_source_get_default(); - if (source == NULL) { - return; - } - - /* Lookup the schema */ - GSettingsSchema *source_schema = g_settings_schema_source_lookup(source, schema, TRUE); - - /* Couldn't find the schema */ - if (source_schema == NULL) { - return; - } - - /* Found the schema, make sure we have the key */ - if (g_settings_schema_has_key(source_schema, key)) { - /* Make sure the key is of boolean type */ - GSettingsSchemaKey *source_key = g_settings_schema_get_key(source_schema, key); - - if (g_variant_type_equal(g_settings_schema_key_get_value_type(source_key), G_VARIANT_TYPE_BOOLEAN)) { - /* Set the value */ - GSettings *settings = g_settings_new(schema); - g_settings_set_boolean(settings, key, value); - g_object_unref(settings); - } - - g_settings_schema_key_unref(source_key); - } - g_settings_schema_unref(source_schema); -} - -/** - * swap_clear_settings_items: - * @self: the indicator object - * - * Swaps the position of the clear and settings items. - **/ -static void -swap_clear_settings_items(IndicatorNotifications *self) -{ - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self)); - - // Pick which widget to move - GtkWidget *widget = self->priv->settings_item; - if(self->priv->swap_clear_settings) - widget = self->priv->clear_item; - - gtk_container_remove(GTK_CONTAINER(self->priv->menu), g_object_ref(widget)); - gtk_menu_shell_append(GTK_MENU_SHELL(self->priv->menu), widget); - g_object_unref(widget); -} - -/** - * clear_item_activated_cb: - * @menuitem: the clear menuitem - * @user_data: the indicator object - * - * Called when the clear menuitem is activated. - **/ -static void -clear_item_activated_cb(GtkMenuItem *menuitem, gpointer user_data) -{ - g_return_if_fail(GTK_IS_MENU_ITEM(menuitem)); - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(user_data)); - IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(user_data); - - clear_menuitems(self); -} - -/** - * settings_item_activated_cb: - * @menuitem: the settings menuitem - * @user_data: the indicator object - * - * Called when the settings menuitem is activated. - **/ -static void -settings_item_activated_cb(GtkMenuItem *menuitem, gpointer user_data) -{ - g_return_if_fail(GTK_IS_MENU_ITEM(menuitem)); - - GError *error = NULL; - - gchar *argv[] = { SETTINGS_PATH, NULL }; - - GPid pid; - - g_spawn_async(NULL, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, &pid, &error); - - if (error != NULL) { - g_message("%s", error->message); - g_error_free(error); - } - else { - g_spawn_close_pid(pid); - } -} - -/** - * setting_changed_cb: - * @settings: the GSettings object - * @key: the GSettings key - * @user_data: the indicator object - * - * Called when a GSettings key is changed. - **/ -static void -setting_changed_cb(GSettings *settings, gchar *key, gpointer user_data) -{ - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(user_data)); - IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(user_data); - - if(g_strcmp0(key, NOTIFICATIONS_KEY_HIDE_INDICATOR) == 0) { - self->priv->hide_indicator = g_settings_get_boolean(settings, NOTIFICATIONS_KEY_HIDE_INDICATOR); - update_indicator_visibility(self); - } - else if(g_strcmp0(key, NOTIFICATIONS_KEY_DND) == 0) { - self->priv->do_not_disturb = g_settings_get_boolean(settings, NOTIFICATIONS_KEY_DND); - update_do_not_disturb(self); - } - else if(g_strcmp0(key, NOTIFICATIONS_KEY_CLEAR_MC) == 0) { - self->priv->clear_on_middle_click = g_settings_get_boolean(self->priv->settings, NOTIFICATIONS_KEY_CLEAR_MC); - } - else if(g_strcmp0(key, NOTIFICATIONS_KEY_FILTER_LIST) == 0) { - update_filter_list(self); - } - else if(g_strcmp0(key, NOTIFICATIONS_KEY_SWAP_CLEAR_SETTINGS) == 0) { - self->priv->swap_clear_settings = g_settings_get_boolean(self->priv->settings, NOTIFICATIONS_KEY_SWAP_CLEAR_SETTINGS); - swap_clear_settings_items(self); - } - /* TODO: Trim or extend the notifications list based on "max-items" key - * (Currently requires a restart) */ -} - -/** - * menu_visible_notify_cb: - * @menu: the menu - * @pspec: unused - * @user_data: the indicator object - * - * Called when the indicator's menu is shown or hidden. - **/ -static void -menu_visible_notify_cb(GtkWidget *menu, G_GNUC_UNUSED GParamSpec *pspec, gpointer user_data) -{ - g_return_if_fail(GTK_IS_MENU(menu)); - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(user_data)); - IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(user_data); - - gboolean visible; - g_object_get(G_OBJECT(menu), "visible", &visible, NULL); - if(!visible) { - set_unread(self, FALSE); - } -} - -/** - * message_received_cb: - * @spy: the dbus notification monitor - * @note: the notification received - * @user_data: the indicator object - * - * Called when a notification arrives on dbus. - **/ -static void -message_received_cb(DBusSpy *spy, Notification *note, gpointer user_data) -{ - g_return_if_fail(IS_DBUS_SPY(spy)); - g_return_if_fail(IS_NOTIFICATION(note)); - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(user_data)); - IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(user_data); - - /* Discard notifications if we are hidden */ - if(self->priv->hide_indicator) { - g_object_unref(note); - return; - } - - /* Discard useless notifications */ - if(notification_is_private(note) || notification_is_empty(note)) { - g_object_unref(note); - return; - } - - /* Discard notifications on the filter list */ - if(self->priv->filter_list != NULL && g_hash_table_contains(self->priv->filter_list, - notification_get_app_name(note))) { - g_object_unref(note); - return; - } - - /* Save a hint for the appname */ - update_filter_list_hints(self, note); - - /* Create the menuitem */ - GtkWidget *item = notification_menuitem_new(); - notification_menuitem_set_from_notification(NOTIFICATION_MENUITEM(item), note); - g_signal_connect(item, NOTIFICATION_MENUITEM_SIGNAL_CLICKED, G_CALLBACK(notification_clicked_cb), self); - gtk_widget_show(item); - g_object_unref(note); - - insert_menuitem(self, item); - - set_unread(self, TRUE); -} - -/** - * notification_clicked_cb: - * @widget: the menuitem - * @user_data: the indicator object - * - * Remove the menuitem when clicked. - **/ -static void -notification_clicked_cb(NotificationMenuItem *menuitem, guint button, gpointer user_data) -{ - g_return_if_fail(IS_NOTIFICATION_MENUITEM(menuitem)); - g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(user_data)); - IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(user_data); - - remove_menuitem(self, GTK_WIDGET(menuitem)); -} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..f57237a --- /dev/null +++ b/src/main.c @@ -0,0 +1,52 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr + * + * 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 . + */ + +#include +#include +#include +#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); +} + +int main (int argc G_GNUC_UNUSED, char ** argv G_GNUC_UNUSED) +{ + IndicatorNotificationsService * service; + GMainLoop * loop; + + /* boilerplate i18n */ + setlocale (LC_ALL, ""); + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + textdomain (GETTEXT_PACKAGE); + + /* run */ + service = indicator_notifications_service_new (NULL); + loop = g_main_loop_new (NULL, FALSE); + g_signal_connect (service, INDICATOR_NOTIFICATIONS_SERVICE_SIGNAL_NAME_LOST, G_CALLBACK(on_name_lost), loop); + g_main_loop_run (loop); + + /* cleanup */ + g_main_loop_unref (loop); + g_clear_object (&service); + + return 0; +} diff --git a/src/notification-menuitem.c b/src/notification-menuitem.c deleted file mode 100644 index 392d9d3..0000000 --- a/src/notification-menuitem.c +++ /dev/null @@ -1,407 +0,0 @@ -/* - * notification-menuitem.h - A menuitem to display notifications. - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include -#include "notification-menuitem.h" -#include "urlregex.h" - -#define NOTIFICATION_MENUITEM_MAX_CHARS 42 -#define NOTIFICATION_MENUITEM_CLOSE_SELECT "ayatana-indicator-notification-close-select" -#define NOTIFICATION_MENUITEM_CLOSE_DESELECT "ayatana-indicator-notification-close-deselect" - -enum { - CLICKED, - LAST_SIGNAL -}; - -static void notification_menuitem_class_init(NotificationMenuItemClass *klass); -static void notification_menuitem_init(NotificationMenuItem *self); - -static void notification_menuitem_activate(GtkMenuItem *menuitem); -static gboolean notification_menuitem_motion(GtkWidget *widget, GdkEventMotion *event); -static gboolean notification_menuitem_leave(GtkWidget *widget, GdkEventCrossing *event); -static gboolean notification_menuitem_button_press(GtkWidget *widget, GdkEventButton *event); -static gboolean notification_menuitem_button_release(GtkWidget *widget, GdkEventButton *event); -static void notification_menuitem_select(GtkMenuItem *item); -static void notification_menuitem_deselect(GtkMenuItem *item); - -static gboolean notification_menuitem_activate_link_cb(GtkLabel *label, gchar *uri, gpointer user_data); -static gchar *notification_menuitem_markup_body(const gchar *body); - -static gboolean widget_contains_event(GtkWidget *widget, GdkEventButton *event); - -static guint notification_menuitem_signals[LAST_SIGNAL] = { 0 }; - -G_DEFINE_TYPE_WITH_PRIVATE(NotificationMenuItem, notification_menuitem, GTK_TYPE_MENU_ITEM); - -static void -notification_menuitem_class_init(NotificationMenuItemClass *klass) -{ - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); - GtkMenuItemClass *menu_item_class = GTK_MENU_ITEM_CLASS(klass); - - widget_class->leave_notify_event = notification_menuitem_leave; - widget_class->motion_notify_event = notification_menuitem_motion; - widget_class->button_press_event = notification_menuitem_button_press; - widget_class->button_release_event = notification_menuitem_button_release; - - menu_item_class->hide_on_activate = FALSE; - menu_item_class->activate = notification_menuitem_activate; - menu_item_class->select = notification_menuitem_select; - menu_item_class->deselect = notification_menuitem_deselect; - - /* Compile the urlregex patterns */ - urlregex_init(); - - notification_menuitem_signals[CLICKED] = - g_signal_new(NOTIFICATION_MENUITEM_SIGNAL_CLICKED, - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET(NotificationMenuItemClass, clicked), - NULL, NULL, - g_cclosure_marshal_VOID__UINT, - G_TYPE_NONE, 1, G_TYPE_UINT); -} - -static void -notification_menuitem_init(NotificationMenuItem *self) -{ - self->priv = notification_menuitem_get_instance_private(self); - - self->priv->pressed_close_image = FALSE; - - self->priv->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - - self->priv->label = gtk_label_new(NULL); - gtk_widget_set_halign(self->priv->label, GTK_ALIGN_START); - gtk_widget_set_valign(self->priv->label, GTK_ALIGN_START); - gtk_label_set_use_markup(GTK_LABEL(self->priv->label), TRUE); - gtk_label_set_line_wrap(GTK_LABEL(self->priv->label), TRUE); - gtk_label_set_line_wrap_mode(GTK_LABEL(self->priv->label), PANGO_WRAP_WORD_CHAR); - gtk_label_set_max_width_chars(GTK_LABEL(self->priv->label), NOTIFICATION_MENUITEM_MAX_CHARS); - gtk_label_set_track_visited_links(GTK_LABEL(self->priv->label), TRUE); - - g_signal_connect(self->priv->label, "activate-link", G_CALLBACK(notification_menuitem_activate_link_cb), self); - - gtk_box_pack_start(GTK_BOX(self->priv->hbox), self->priv->label, TRUE, TRUE, 0); - gtk_widget_show(self->priv->label); - - self->priv->close_image = gtk_image_new_from_icon_name(NOTIFICATION_MENUITEM_CLOSE_DESELECT, GTK_ICON_SIZE_MENU); - gtk_widget_show(self->priv->close_image); - gtk_box_pack_start(GTK_BOX(self->priv->hbox), self->priv->close_image, FALSE, FALSE, 0); - - gtk_container_add(GTK_CONTAINER(self), self->priv->hbox); - gtk_widget_show(self->priv->hbox); -} - -GtkWidget * -notification_menuitem_new(void) -{ - return g_object_new(NOTIFICATION_MENUITEM_TYPE, NULL); -} - -/** - * notification_menuitem_set_from_notification: - * @self - the notification menuitem - * @note - the notification object - * - * Sets the markup in the notification menuitem to display information about - * the notification, as well as marking any links within the message body. - **/ -void -notification_menuitem_set_from_notification(NotificationMenuItem *self, Notification *note) -{ - g_return_if_fail(IS_NOTIFICATION(note)); - gchar *unescaped_timestamp_string = notification_timestamp_for_locale(note); - - gchar *app_name = g_markup_escape_text(notification_get_app_name(note), -1); - gchar *summary = g_markup_escape_text(notification_get_summary(note), -1); - gchar *body = notification_menuitem_markup_body(notification_get_body(note)); - gchar *timestamp_string = g_markup_escape_text(unescaped_timestamp_string, -1); - - gchar *markup = g_strdup_printf("%s\n%s\n%s %s %s", - summary, body, timestamp_string, _("from"), app_name); - - g_free(app_name); - g_free(summary); - g_free(body); - g_free(unescaped_timestamp_string); - g_free(timestamp_string); - - gtk_label_set_markup(GTK_LABEL(self->priv->label), markup); - - g_free(markup); -} - -/** - * notification_menuitem_activate: - * @menuitem: the menuitem - * - * Emit a clicked event for the case where a keyboard activates a menuitem. - **/ -static void -notification_menuitem_activate(GtkMenuItem *menuitem) -{ - g_return_if_fail(IS_NOTIFICATION_MENUITEM(menuitem)); - - g_signal_emit(NOTIFICATION_MENUITEM(menuitem), notification_menuitem_signals[CLICKED], 0); -} - -/** - * notification_menuitem_leave: - * @widget - the widget - * @event - the event - * - * Handle the leave-notify-event, by simply passing it on to the GtkLabel of - * this menuitem. - **/ -static gboolean -notification_menuitem_leave(GtkWidget *widget, GdkEventCrossing *event) -{ - g_return_val_if_fail(IS_NOTIFICATION_MENUITEM(widget), FALSE); - - NotificationMenuItem *self = NOTIFICATION_MENUITEM(widget); - - gtk_widget_event(self->priv->label, (GdkEvent *)event); - return FALSE; -} - -/** - * notification_menuitem_motion: - * @widget - the widget - * @event - the event - * - * Handle the motion-notify-event. It is passed on to the GtkLabel, but the - * event (x, y) is mapped to the whole menuitem not the label so we have to - * shift it over a bit into the label's allocation. - **/ -static gboolean -notification_menuitem_motion(GtkWidget *widget, GdkEventMotion *event) -{ - g_return_val_if_fail(IS_NOTIFICATION_MENUITEM(widget), FALSE); - - NotificationMenuItem *self = NOTIFICATION_MENUITEM(widget); - - GtkAllocation self_alloc; - GtkAllocation label_alloc; - - gtk_widget_get_allocation(GTK_WIDGET(self), &self_alloc); - gtk_widget_get_allocation(self->priv->label, &label_alloc); - - /* The event is mapped to the menu item's allocation, so we need to shift it - * to the label's allocation so that links are probably selected. - */ - GdkEventMotion *e = (GdkEventMotion *)gdk_event_copy((GdkEvent *)event); - e->x = event->x - (label_alloc.x - self_alloc.x); - e->y = event->y - (label_alloc.y - self_alloc.y); - - gtk_widget_event(self->priv->label, (GdkEvent *)e); - - gdk_event_free((GdkEvent *)e); - return FALSE; -} - -/** - * notification_menuitem_button_press: - * @widget: the menuitem - * @event: the button press event - * - * Override the menuitem button-press-event. - **/ -static gboolean -notification_menuitem_button_press(GtkWidget *widget, GdkEventButton *event) -{ - g_return_val_if_fail(IS_NOTIFICATION_MENUITEM(widget), FALSE); - - NotificationMenuItem *self = NOTIFICATION_MENUITEM(widget); - - /* The context menu breaks everything so disable it for now */ - if (event->button == GDK_BUTTON_PRIMARY && widget_contains_event(self->priv->label, event)) { - gtk_widget_event(self->priv->label, (GdkEvent *)event); - } - else if (widget_contains_event(self->priv->close_image, event)) { - self->priv->pressed_close_image = TRUE; - } - return TRUE; -} - -/** - * notification_menuitem_button_release: - * @widget: the menuitem - * @event: the button release event - * - * Override the menuitem button-release-event so that the menu isn't hidden - * when the item is removed. Also the event is passed on to the label so we can - * get an activate-link signal when a link is clicked. - **/ -static gboolean -notification_menuitem_button_release(GtkWidget *widget, GdkEventButton *event) -{ - g_return_val_if_fail(IS_NOTIFICATION_MENUITEM(widget), FALSE); - - NotificationMenuItem *self = NOTIFICATION_MENUITEM(widget); - - if (widget_contains_event(self->priv->close_image, event)) { - if (self->priv->pressed_close_image) - g_signal_emit(NOTIFICATION_MENUITEM(widget), notification_menuitem_signals[CLICKED], 0, event->button); - } - else { - /* The context menu breaks everything so disable it for now */ - if (event->button == GDK_BUTTON_PRIMARY) { - gtk_widget_event(self->priv->label, (GdkEvent *)event); - } - } - self->priv->pressed_close_image = FALSE; - return TRUE; -} - -/** - * notification_menuitem_select: - * @menuitem - the menuitem - * - * Handle the menuitem select signal. We don't want to set PRELIGHT on the - * menuitem because in various themes it becomes very hard to see the links. - * Instead we set a special close image to show that the notification is - * selected for keyboard navigation. - **/ -static void -notification_menuitem_select(GtkMenuItem *menuitem) -{ - g_return_if_fail(IS_NOTIFICATION_MENUITEM(menuitem)); - - NotificationMenuItem *self = NOTIFICATION_MENUITEM(menuitem); - - gtk_image_set_from_icon_name(GTK_IMAGE(self->priv->close_image), - NOTIFICATION_MENUITEM_CLOSE_SELECT, - GTK_ICON_SIZE_MENU); -} - -/** - * notification_menuitem_deselect: - * @menuitem - the menuitem - * - * Same as notification_menuitem_select, but sets the opposite close image. - **/ -static void -notification_menuitem_deselect(GtkMenuItem *menuitem) -{ - g_return_if_fail(IS_NOTIFICATION_MENUITEM(menuitem)); - - NotificationMenuItem *self = NOTIFICATION_MENUITEM(menuitem); - - gtk_image_set_from_icon_name(GTK_IMAGE(self->priv->close_image), - NOTIFICATION_MENUITEM_CLOSE_DESELECT, - GTK_ICON_SIZE_MENU); -} - -/** - * notification_menuitem_activate_link_cb: - * @label - the label - * @uri - the link that was activated - * @user_data - the notification menuitem - * - * We override the activate-link signal of the GtkLabel because we need to - * deactivate the menu shell when it is clicked, otherwise it stays stuck to - * the screen as the browser loads. - **/ -static gboolean -notification_menuitem_activate_link_cb(GtkLabel *label, gchar *uri, gpointer user_data) -{ - g_return_val_if_fail(IS_NOTIFICATION_MENUITEM(user_data), FALSE); - - NotificationMenuItem *self = NOTIFICATION_MENUITEM(user_data); - - /* Show the link */ - GError *error = NULL; - - if (!gtk_show_uri_on_window(NULL, uri, gtk_get_current_event_time(), &error)) - { - g_warning("Unable to show '%s': %s", uri, error->message); - g_error_free(error); - } - - /* Deactivate the menu shell so it doesn't block the screen */ - GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(self)); - if (GTK_IS_MENU_SHELL(parent)) { - gtk_menu_shell_deactivate(GTK_MENU_SHELL(parent)); - } - - return TRUE; -} - -/** - * notification_menuitem_markup_body: - * @body - the body of a notification - * - * Scans through the body text escaping everything that isn't a link. The links - * are marked up as anchors with hrefs. - **/ -static gchar * -notification_menuitem_markup_body(const gchar *body) -{ - GList *list = urlregex_split_all(body); - guint len = g_list_length(list); - gchar **str_array = g_new0(gchar *, len + 1); - guint i = 0; - GList *item; - gchar *escaped_text; - gchar *escaped_expanded; - - for (item = list; item; item = item->next, i++) { - MatchGroup *group = (MatchGroup *)item->data; - if (group->type == MATCHED) { - escaped_text = g_markup_escape_text(group->text, -1); - escaped_expanded = g_markup_escape_text(group->expanded, -1); - str_array[i] = g_strdup_printf("%s", escaped_expanded, escaped_text); - g_free(escaped_text); - g_free(escaped_expanded); - } - else { - str_array[i] = g_markup_escape_text(group->text, -1); - } - } - - urlregex_matchgroup_list_free(list); - gchar *result = g_strjoinv(NULL, str_array); - g_strfreev(str_array); - return result; -} - -/** - * widget_contains_event: - * @widget - the widget - * @event - the event - * - * Determines whether the (x, y) coordinates of the event fall inside the - * widget. - **/ -static gboolean -widget_contains_event(GtkWidget *widget, GdkEventButton *event) -{ - if (gtk_widget_get_window(widget) == NULL) - return FALSE; - - GtkAllocation allocation; - - gtk_widget_get_allocation(widget, &allocation); - - GdkWindow *window = gtk_widget_get_window(widget); - - int xwin, ywin; - - gdk_window_get_origin(window, &xwin, &ywin); - - int xmin = allocation.x; - int xmax = allocation.x + allocation.width; - int ymin = allocation.y; - int ymax = allocation.y + allocation.height; - int x = event->x_root - xwin; - int y = event->y_root - ywin; - - return x >= xmin && x <= xmax && y >= ymin && y <= ymax; -} diff --git a/src/notification-menuitem.h b/src/notification-menuitem.h deleted file mode 100644 index afb2b98..0000000 --- a/src/notification-menuitem.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * notification-menuitem.h - A menuitem to display notifications. - */ - -#ifndef __NOTIFICATION_MENUITEM_H__ -#define __NOTIFICATION_MENUITEM_H__ - -#include -#include "notification.h" - -G_BEGIN_DECLS - -#define NOTIFICATION_MENUITEM_TYPE (notification_menuitem_get_type ()) -#define NOTIFICATION_MENUITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NOTIFICATION_MENUITEM_TYPE, NotificationMenuItem)) -#define NOTIFICATION_MENUITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NOTIFICATION_MENUITEM_TYPE, NotificationMenuItemClass)) -#define IS_NOTIFICATION_MENUITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NOTIFICATION_MENUITEM_TYPE)) -#define IS_NOTIFICATION_MENUITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NOTIFICATION_MENUITEM_TYPE)) - -typedef struct _NotificationMenuItem NotificationMenuItem; -typedef struct _NotificationMenuItemClass NotificationMenuItemClass; -typedef struct _NotificationMenuItemPrivate NotificationMenuItemPrivate; - -struct _NotificationMenuItem -{ - GtkMenuItem parent_instance; - NotificationMenuItemPrivate *priv; -}; - -struct _NotificationMenuItemClass -{ - GtkMenuItemClass parent_class; - - void (* clicked) (NotificationMenuItem *menuitem, guint button); -}; - -struct _NotificationMenuItemPrivate { - GtkWidget *close_image; - GtkWidget *hbox; - GtkWidget *label; - - gboolean pressed_close_image; -}; - -#define NOTIFICATION_MENUITEM_SIGNAL_CLICKED "clicked" - -GType notification_menuitem_get_type(void); -GtkWidget *notification_menuitem_new(void); -void notification_menuitem_set_from_notification(NotificationMenuItem *self, Notification *note); - -G_END_DECLS - -#endif /* __NOTIFICATION_MENUITEM_H__ */ diff --git a/src/service.c b/src/service.c new file mode 100644 index 0000000..2de2613 --- /dev/null +++ b/src/service.c @@ -0,0 +1,867 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr + * Ted Gould + * + * 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 . + */ + +#include +#include +#include "service.h" +#include "dbus-spy.h" +#include "urlregex.h" + +#define BUS_NAME "org.ayatana.indicator.notifications" +#define BUS_PATH "/org/ayatana/indicator/notifications" +#define HINT_MAX 10 + +static guint m_nSignal = 0; + +enum +{ + SECTION_HEADER = (1<<0), + SECTION_NOTIFICATIONS = (1<<1), + SECTION_DO_NOT_DISTURB = (1<<2), + SECTION_CLEAR = (1<<3) +}; + +enum +{ + PROFILE_PHONE, + PROFILE_DESKTOP, + N_PROFILES +}; + +static const char * const menu_names[N_PROFILES] = +{ + "phone", + "desktop" +}; + +struct ProfileMenuInfo +{ + GMenu *pMenu; + GMenu *pSubmenu; + guint nExportId; +}; + +struct _IndicatorNotificationsServicePrivate +{ + GCancellable *pCancellable; + GSettings *pSettings; + guint nOwnId; + guint nActionsId; + GDBusConnection *pConnection; + gboolean bMenusBuilt; + struct ProfileMenuInfo lMenus[N_PROFILES]; + GSimpleActionGroup *pActionGroup; + GSimpleAction *pHeaderAction; + GSimpleAction *pClearAction; + GSimpleAction *pRemoveAction; + GSimpleAction *pDoNotDisturbAction; + GList *lVisibleItems; + GList *lHiddenItems; + gboolean bDoNotDisturb; + gboolean bHasUnread; + gint nMaxItems; + DBusSpy *pBusSpy; + GHashTable *lFilters; + GList *lHints; + GMenu *pNotificationsSection; + gboolean bHasDoNotDisturb; +}; + +typedef IndicatorNotificationsServicePrivate priv_t; + +G_DEFINE_TYPE_WITH_PRIVATE(IndicatorNotificationsService, indicator_notifications_service, G_TYPE_OBJECT) + +static void rebuildNow(IndicatorNotificationsService *self, guint nSections); +static void updateFilters(IndicatorNotificationsService *self); + +static void saveHints(IndicatorNotificationsService *self) +{ + gchar *hints[HINT_MAX + 1]; + int i = 0; + GList *l; + + for (l = self->priv->lHints; (l != NULL) && (i < HINT_MAX); l = l->next, i++) + { + hints[i] = (gchar *) l->data; + } + + hints[i] = NULL; + + g_settings_set_strv(self->priv->pSettings, "filter-list-hints", (const gchar **) hints); +} + +static void updateHints(IndicatorNotificationsService *self, Notification *notification) +{ + g_return_if_fail(IS_NOTIFICATION(notification)); + + const gchar *appname = notification_get_app_name(notification); + + // Avoid duplicates + GList *l; + for (l = self->priv->lHints; l != NULL; l = l->next) + { + if (g_strcmp0(appname, (const gchar *) l->data) == 0) + { + return; + } + } + + // Add the appname + self->priv->lHints = g_list_prepend(self->priv->lHints, g_strdup(appname)); + + // Keep only a reasonable number + while (g_list_length(self->priv->lHints) > HINT_MAX) + { + GList *last = g_list_last(self->priv->lHints); + g_free(last->data); + self->priv->lHints = g_list_delete_link(self->priv->lHints, last); + } + + // Save the hints + saveHints(self); +} + +static gchar *createMarkup(const gchar *body) +{ + GList *list = urlregex_split_all(body); + guint len = g_list_length(list); + gchar **str_array = g_new0(gchar *, len + 1); + guint i = 0; + GList *item; + gchar *escaped_text; + gchar *escaped_expanded; + + for (item = list; item; item = item->next, i++) + { + MatchGroup *group = (MatchGroup *)item->data; + + if (group->type == MATCHED) + { + escaped_text = g_markup_escape_text(group->text, -1); + escaped_expanded = g_markup_escape_text(group->expanded, -1); + str_array[i] = g_strdup_printf("%s", escaped_expanded, escaped_text); + g_free(escaped_text); + g_free(escaped_expanded); + } + else + { + str_array[i] = g_markup_escape_text(group->text, -1); + } + } + + urlregex_matchgroup_list_free(list); + gchar *result = g_strjoinv(NULL, str_array); + g_strfreev(str_array); + + return result; +} + +static void updateClearItem(IndicatorNotificationsService *self) +{ + guint visible_length = g_list_length(self->priv->lVisibleItems); + guint hidden_length = g_list_length(self->priv->lHiddenItems); + guint total_length = visible_length + hidden_length; + + g_simple_action_set_enabled(self->priv->pClearAction, total_length != 0); +} + +static void setUnread(IndicatorNotificationsService *self, gboolean unread) +{ + self->priv->bHasUnread = unread; + rebuildNow(self, SECTION_HEADER); +} + +static void onMessageReceived(DBusSpy *pBusSpy, Notification *note, gpointer user_data) +{ + g_return_if_fail(IS_DBUS_SPY(pBusSpy)); + g_return_if_fail(IS_NOTIFICATION(note)); + IndicatorNotificationsService *self = INDICATOR_NOTIFICATIONS_SERVICE(user_data); + + // Discard useless notifications + if(notification_is_private(note) || notification_is_empty(note)) + { + g_object_unref(note); + + return; + } + + // Discard notifications on the filter list + if(self->priv->lFilters != NULL && g_hash_table_contains(self->priv->lFilters, notification_get_app_name(note))) + { + g_object_unref(note); + + return; + } + + updateHints(self, note); + + gchar *unescaped_timestamp_string = notification_timestamp_for_locale(note); + gchar *app_name = g_markup_escape_text(notification_get_app_name(note), -1); + gchar *summary = g_markup_escape_text(notification_get_summary(note), -1); + gchar *body = createMarkup(notification_get_body(note)); + g_object_unref(note); + gchar *timestamp_string = g_markup_escape_text(unescaped_timestamp_string, -1); + gchar *markup = g_strdup_printf("%s\n%s\n%s %s %s", summary, body, timestamp_string, _("from"), app_name); + g_free(app_name); + g_free(summary); + g_free(body); + g_free(unescaped_timestamp_string); + g_free(timestamp_string); + GMenuItem * item = g_menu_item_new(markup, NULL); + g_free(markup); + gint64 nTimestamp = notification_get_timestamp(note); + g_menu_item_set_action_and_target_value(item, "indicator.remove-notification", g_variant_new_int64(nTimestamp)); + g_menu_item_set_attribute_value(item, "x-ayatana-timestamp", g_variant_new_int64(nTimestamp)); + g_menu_item_set_attribute_value(item, "x-ayatana-use-markup", g_variant_new_boolean(TRUE)); + g_menu_item_set_attribute(item, "x-ayatana-type", "s", "org.ayatana.indicator.basic"); + + GList *last_item; + GMenuItem *last_menu_item; + + // List holds a ref to the menuitem + self->priv->lVisibleItems = g_list_prepend(self->priv->lVisibleItems, g_object_ref(item)); + g_menu_prepend_item (self->priv->pNotificationsSection, item); + g_object_unref(item); + + // Move items that overflow to the hidden list + while (g_list_length(self->priv->lVisibleItems) > self->priv->nMaxItems) + { + last_item = g_list_last(self->priv->lVisibleItems); + last_menu_item = G_MENU_ITEM(last_item->data); + + // Steal the ref from the visible list + self->priv->lVisibleItems = g_list_delete_link(self->priv->lVisibleItems, last_item); + self->priv->lHiddenItems = g_list_prepend(self->priv->lHiddenItems, last_menu_item); + last_item = NULL; + last_menu_item = NULL; + } + + while (g_menu_model_get_n_items(G_MENU_MODEL(self->priv->pNotificationsSection)) > self->priv->nMaxItems) + { + g_menu_remove(self->priv->pNotificationsSection, self->priv->nMaxItems); + } + + updateClearItem(self); + setUnread(self, TRUE); +} + +static GVariant *createHeaderState(IndicatorNotificationsService *self) +{ + GVariantBuilder b; + + g_variant_builder_init (&b, G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add (&b, "{sv}", "title", g_variant_new_string (_("Notifications"))); + g_variant_builder_add (&b, "{sv}", "visible", g_variant_new_boolean (TRUE)); + + gchar *sIcon = NULL; + + if (self->priv->bHasUnread) + { + if (self->priv->bHasDoNotDisturb && self->priv->bDoNotDisturb) + { + sIcon = "ayatana-indicator-notification-unread-dnd"; + } + else + { + sIcon = "ayatana-indicator-notification-unread"; + } + } + else + { + if (self->priv->bHasDoNotDisturb && self->priv->bDoNotDisturb) + { + sIcon = "ayatana-indicator-notification-read-dnd"; + } + else + { + sIcon = "ayatana-indicator-notification-read"; + } + } + + GIcon * icon = g_themed_icon_new_with_default_fallbacks(sIcon); + g_variant_builder_add (&b, "{sv}", "accessible-desc", g_variant_new_string (_("Notifications"))); + + if (icon) + { + GVariant * serialized_icon = g_icon_serialize (icon); + + 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); +} + +static GMenuModel *createDesktopNotificationsSection(IndicatorNotificationsService *self, int profile) +{ + self->priv->pNotificationsSection = g_menu_new(); + + return G_MENU_MODEL(self->priv->pNotificationsSection); +} + +static GMenuModel *createDesktopClearSection(IndicatorNotificationsService *self) +{ + GMenu * pMenu = g_menu_new (); + g_menu_append(pMenu, _("Clear"), "indicator.clear-notifications"); + + return G_MENU_MODEL(pMenu); +} + +static GMenuModel *createDesktopDoNotDisturbSection(IndicatorNotificationsService *self) +{ + GMenu *pMenu = g_menu_new(); + + if (self->priv->bHasDoNotDisturb) + { + GMenuItem *item = g_menu_item_new(_("Do not disturb"), NULL); + g_menu_item_set_attribute(item, "x-ayatana-type", "s", "org.ayatana.indicator.switch"); + g_menu_item_set_action_and_target(item, "indicator.do-not-disturb", NULL); + g_menu_append_item(pMenu, item); + g_object_unref(item); + } + + return G_MENU_MODEL(pMenu); +} + +static void rebuildSection(GMenu *parent, int pos, GMenuModel *new_section) +{ + g_menu_remove (parent, pos); + g_menu_insert_section (parent, pos, NULL, new_section); + g_object_unref (new_section); +} + +static void rebuildNow(IndicatorNotificationsService *self, guint sections) +{ + priv_t *p = self->priv; + struct ProfileMenuInfo *desktop = &p->lMenus[PROFILE_DESKTOP]; + + if (sections & SECTION_HEADER) + { + g_simple_action_set_state (p->pHeaderAction, createHeaderState (self)); + } + + if (!p->bMenusBuilt) + { + return; + } + + if (sections & SECTION_NOTIFICATIONS) + { + rebuildSection (desktop->pSubmenu, 0, createDesktopNotificationsSection (self, PROFILE_DESKTOP)); + } + + if (sections & SECTION_DO_NOT_DISTURB) + { + rebuildSection (desktop->pSubmenu, 1, createDesktopDoNotDisturbSection (self)); + } + + if (sections & SECTION_CLEAR) + { + rebuildSection (desktop->pSubmenu, 2, createDesktopClearSection (self)); + } +} + +static void createMenu(IndicatorNotificationsService *self, int profile) +{ + GMenu * pMenu; + GMenu * pSubmenu; + GMenuItem * header; + GMenuModel * sections[16]; + guint i; + guint n = 0; + + g_assert (0 <= profile && profile < N_PROFILES); + g_assert (self->priv->lMenus[profile].pMenu == NULL); + + // Build the sections + switch (profile) + { + case PROFILE_PHONE: + case PROFILE_DESKTOP: + { + sections[n++] = createDesktopNotificationsSection(self, PROFILE_DESKTOP); + sections[n++] = createDesktopDoNotDisturbSection(self); + sections[n++] = createDesktopClearSection(self); + + break; + } + + break; + } + + // Add sections to the submenu + pSubmenu = g_menu_new(); + + for (i=0; ipriv->lMenus[profile].pMenu = pMenu; + self->priv->lMenus[profile].pSubmenu = pSubmenu; +} + +static void clearMenuItems(IndicatorNotificationsService *self) +{ + // Remove each visible item from the menu + while (g_menu_model_get_n_items(G_MENU_MODEL(self->priv->pNotificationsSection)) > 0) + { + g_menu_remove(self->priv->pNotificationsSection, 0); + } + + // Clear the lists + g_list_free_full(self->priv->lVisibleItems, g_object_unref); + self->priv->lVisibleItems = NULL; + + g_list_free_full(self->priv->lHiddenItems, g_object_unref); + self->priv->lHiddenItems = NULL; + + updateClearItem(self); +} + +static void onRemoveNotification(GSimpleAction *a, GVariant *param, gpointer user_data) +{ + IndicatorNotificationsService *self = INDICATOR_NOTIFICATIONS_SERVICE(user_data); + guint nItems = g_menu_model_get_n_items(G_MENU_MODEL(self->priv->pNotificationsSection)); + gint64 nTimestampIn = g_variant_get_int64(param); + + for (guint nItem = 0; nItem < nItems; nItem++) + { + gint64 nTimestamp; + g_menu_model_get_item_attribute(G_MENU_MODEL(self->priv->pNotificationsSection), nItem, "x-ayatana-timestamp", "x", &nTimestamp); + + if (nTimestamp == nTimestampIn) + { + g_menu_remove(self->priv->pNotificationsSection, nItem); + + break; + } + } + + for (GList *pItem = self->priv->lVisibleItems; pItem; pItem = pItem->next) + { + gint64 nTimestamp; + g_menu_item_get_attribute(G_MENU_ITEM(pItem->data), "x-ayatana-timestamp", "x", &nTimestamp); + + if (nTimestamp == nTimestampIn) + { + // Remove the item + self->priv->lVisibleItems = g_list_delete_link(self->priv->lVisibleItems, pItem); + + // Add an item from the hidden list, if available + if (g_list_length(self->priv->lHiddenItems) > 0) + { + pItem = g_list_first(self->priv->lHiddenItems); + GMenuItem *pMenuItem = G_MENU_ITEM(pItem->data); + self->priv->lHiddenItems = g_list_delete_link(self->priv->lHiddenItems, pItem); + g_menu_append_item(self->priv->pNotificationsSection, pMenuItem); + + // Steal the ref back from the hidden list + self->priv->lVisibleItems = g_list_append(self->priv->lVisibleItems, pMenuItem); + } + + updateClearItem(self); + + break; + } + } +} + +static void onClear(GSimpleAction *a, GVariant *param, gpointer user_data) +{ + IndicatorNotificationsService *self = INDICATOR_NOTIFICATIONS_SERVICE(user_data); + + clearMenuItems(self); + setUnread(self, FALSE); +} + +static void onDoNotDisturb(GSimpleAction *a, GVariant *param, gpointer user_data) +{ + IndicatorNotificationsService *self = INDICATOR_NOTIFICATIONS_SERVICE(user_data); + + if (self->priv->bHasDoNotDisturb) + { + self->priv->bDoNotDisturb = g_variant_get_boolean(param); + GSettings *pSettings = g_settings_new("org.mate.NotificationDaemon"); + g_settings_set_boolean(pSettings, "do-not-disturb", self->priv->bDoNotDisturb); + g_object_unref(pSettings); + g_settings_set_boolean(self->priv->pSettings, "do-not-disturb", self->priv->bDoNotDisturb); + rebuildNow(self, SECTION_HEADER); + } +} + +static void onSettingsChanged(GSettings *pSettings, gchar *key, gpointer user_data) +{ + IndicatorNotificationsService *self = INDICATOR_NOTIFICATIONS_SERVICE(user_data); + + if (g_str_equal(key, "filter-list")) + { + updateFilters(self); + } + else if (g_str_equal(key, "do-not-disturb")) + { + if (self->priv->bHasDoNotDisturb) + { + self->priv->bDoNotDisturb = g_settings_get_boolean(self->priv->pSettings, key); + GSettings *pSettings = g_settings_new("org.mate.NotificationDaemon"); + g_settings_set_boolean(pSettings, key, self->priv->bDoNotDisturb); + g_object_unref(pSettings); + g_action_change_state(G_ACTION(self->priv->pDoNotDisturbAction), g_variant_new_boolean(self->priv->bDoNotDisturb)); + rebuildNow(self, SECTION_HEADER); + } + else if (g_settings_get_boolean(self->priv->pSettings, key)) + { + g_settings_set_boolean(self->priv->pSettings, key, FALSE); + } + } +} + +static void initActions(IndicatorNotificationsService *self) +{ + GSimpleAction * a; + GAction *max_items_action; + self->priv->pActionGroup = g_simple_action_group_new(); + + // Add the header action + a = g_simple_action_new_stateful ("_header", NULL, createHeaderState(self)); + g_action_map_add_action(G_ACTION_MAP(self->priv->pActionGroup), G_ACTION(a)); + self->priv->pHeaderAction = a; + + // Add the remove action + a = g_simple_action_new("remove-notification", G_VARIANT_TYPE_INT64); + g_action_map_add_action(G_ACTION_MAP(self->priv->pActionGroup), G_ACTION(a)); + self->priv->pRemoveAction = a; + g_signal_connect(a, "activate", G_CALLBACK(onRemoveNotification), self); + + a = g_simple_action_new("clear-notifications", NULL); + g_simple_action_set_enabled (a, FALSE); + g_action_map_add_action(G_ACTION_MAP(self->priv->pActionGroup), G_ACTION(a)); + self->priv->pClearAction = a; + g_signal_connect(a, "activate", G_CALLBACK(onClear), self); + + if (self->priv->bHasDoNotDisturb) + { + a = g_simple_action_new_stateful("do-not-disturb", G_VARIANT_TYPE_BOOLEAN, g_variant_new_boolean(self->priv->bDoNotDisturb)); + g_action_map_add_action(G_ACTION_MAP(self->priv->pActionGroup), G_ACTION(a)); + self->priv->pDoNotDisturbAction = a; + g_signal_connect(a, "activate", G_CALLBACK(onDoNotDisturb), self); + } + + // Add the max-items action + max_items_action = g_settings_create_action(self->priv->pSettings, "max-items"); + g_action_map_add_action(G_ACTION_MAP(self->priv->pActionGroup), max_items_action); + + g_signal_connect(self->priv->pSettings, "changed", G_CALLBACK(onSettingsChanged), self); + + rebuildNow(self, SECTION_HEADER); + + g_object_unref(max_items_action); +} + +static void onBusAcquired(GDBusConnection *connection, const gchar *name, gpointer gself) +{ + int i; + guint id; + GError * err = NULL; + IndicatorNotificationsService * self = INDICATOR_NOTIFICATIONS_SERVICE(gself); + priv_t * p = self->priv; + GString * path = g_string_new (NULL); + + g_debug ("bus acquired: %s", name); + + p->pConnection = (GDBusConnection*)g_object_ref(G_OBJECT (connection)); + + // Export the actions + if ((id = g_dbus_connection_export_action_group (connection, BUS_PATH, G_ACTION_GROUP (p->pActionGroup), &err))) + { + p->nActionsId = id; + } + else + { + g_warning ("cannot export action group: %s", err->message); + g_clear_error (&err); + } + + // Export the menus + for (i=0; ilMenus[i]; + + g_string_printf (path, "%s/%s", BUS_PATH, menu_names[i]); + + if ((id = g_dbus_connection_export_menu_model (connection, path->str, G_MENU_MODEL (menu->pMenu), &err))) + { + menu->nExportId = id; + } + else + { + g_warning ("cannot export %s menu: %s", path->str, err->message); + g_clear_error (&err); + } + } + + g_string_free (path, TRUE); +} + +static void unexport(IndicatorNotificationsService *self) +{ + int i; + priv_t * p = self->priv; + + // Unexport the menus + for (i=0; ipriv->lMenus[i].nExportId; + + if (*id) + { + g_dbus_connection_unexport_menu_model (p->pConnection, *id); + *id = 0; + } + } + + // Unexport the actions + if (p->nActionsId) + { + g_dbus_connection_unexport_action_group (p->pConnection, p->nActionsId); + p->nActionsId = 0; + } +} + +static void onNameLost(GDBusConnection *connection, const gchar *name, gpointer gself) +{ + IndicatorNotificationsService * self = INDICATOR_NOTIFICATIONS_SERVICE (gself); + + g_debug("%s %s name lost %s", G_STRLOC, G_STRFUNC, name); + + unexport(self); +} + +static void onDispose(GObject *o) +{ + IndicatorNotificationsService * self = INDICATOR_NOTIFICATIONS_SERVICE(o); + priv_t * p = self->priv; + + if (self->priv->lVisibleItems != NULL) + { + g_list_free_full(self->priv->lVisibleItems, g_object_unref); + self->priv->lVisibleItems = NULL; + } + + if (self->priv->lHiddenItems != NULL) + { + g_list_free_full(self->priv->lHiddenItems, g_object_unref); + self->priv->lHiddenItems = NULL; + } + + if (self->priv->pBusSpy != NULL) + { + g_object_unref(G_OBJECT(self->priv->pBusSpy)); + self->priv->pBusSpy = NULL; + } + + if(self->priv->lFilters != NULL) + { + g_hash_table_unref(self->priv->lFilters); + self->priv->lFilters = NULL; + } + + if(self->priv->lHints != NULL) + { + g_list_free_full(self->priv->lHints, g_free); + self->priv->lHints = NULL; + } + + if (p->nOwnId) + { + g_bus_unown_name (p->nOwnId); + p->nOwnId = 0; + } + + unexport (self); + + if (p->pCancellable != NULL) + { + g_cancellable_cancel (p->pCancellable); + g_clear_object (&p->pCancellable); + } + + if (p->pSettings != NULL) + { + g_signal_handlers_disconnect_by_data (p->pSettings, self); + g_clear_object (&p->pSettings); + } + + if (self->priv->bHasDoNotDisturb) + { + g_clear_object(&p->pDoNotDisturbAction); + } + + g_clear_object (&p->pClearAction); + g_clear_object (&p->pRemoveAction); + g_clear_object (&p->pHeaderAction); + g_clear_object (&p->pActionGroup); + g_clear_object (&p->pConnection); + + G_OBJECT_CLASS (indicator_notifications_service_parent_class)->dispose (o); +} + +static void updateFilters(IndicatorNotificationsService *self) +{ + g_return_if_fail(self->priv->lFilters != NULL); + + g_hash_table_remove_all(self->priv->lFilters); + gchar **items = g_settings_get_strv(self->priv->pSettings, "filter-list"); + int i; + + for(i = 0; items[i] != NULL; i++) + { + g_hash_table_insert(self->priv->lFilters, g_strdup(items[i]), NULL); + } + + g_strfreev(items); +} + +static void loadHints(IndicatorNotificationsService *self) +{ + g_return_if_fail(self->priv->lHints == NULL); + + gchar **items = g_settings_get_strv(self->priv->pSettings, "filter-list-hints"); + int i; + + for (i = 0; items[i] != NULL; i++) + { + self->priv->lHints = g_list_prepend(self->priv->lHints, items[i]); + } + + g_free(items); +} + +static gboolean getDoNotDisturb() +{ + // Check if we can access the schema + GSettingsSchemaSource *source = g_settings_schema_source_get_default(); + + if (source == NULL) + { + return FALSE; + } + + // Lookup the schema + GSettingsSchema *source_schema = g_settings_schema_source_lookup(source, "org.mate.NotificationDaemon", TRUE); + + // Couldn't find the schema + if (source_schema == NULL) + { + return FALSE; + } + + gboolean bResult = FALSE; + + // Found the schema, make sure we have the key + if (g_settings_schema_has_key(source_schema, "do-not-disturb")) + { + // Make sure the key is of boolean type + GSettingsSchemaKey *source_key = g_settings_schema_get_key(source_schema, "do-not-disturb"); + + if (g_variant_type_equal(g_settings_schema_key_get_value_type(source_key), G_VARIANT_TYPE_BOOLEAN)) + { + bResult = TRUE; + } + + g_settings_schema_key_unref(source_key); + } + + g_settings_schema_unref(source_schema); + + return bResult; +} + +static void indicator_notifications_service_init(IndicatorNotificationsService *self) +{ + int i; + self->priv = indicator_notifications_service_get_instance_private(self); + self->priv->pCancellable = g_cancellable_new(); + self->priv->bHasDoNotDisturb = getDoNotDisturb(); + self->priv->pSettings = g_settings_new("org.ayatana.indicator.notifications"); + self->priv->bHasUnread = FALSE; + self->priv->lVisibleItems = NULL; + self->priv->lHiddenItems = NULL; + + // Watch for notifications from dbus + self->priv->pBusSpy = dbus_spy_new(); + g_signal_connect(self->priv->pBusSpy, DBUS_SPY_SIGNAL_MESSAGE_RECEIVED, G_CALLBACK(onMessageReceived), self); + + // Initialize an empty filter list + self->priv->lFilters = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + self->priv->nMaxItems = g_settings_get_int(self->priv->pSettings, "max-items"); + + if (self->priv->bHasDoNotDisturb) + { + self->priv->bDoNotDisturb = g_settings_get_boolean(self->priv->pSettings, "do-not-disturb"); + } + + updateFilters(self); + + // Set up filter-list hints + self->priv->lHints = NULL; + loadHints(self); + + initActions(self); + + for (i=0; ipriv->bMenusBuilt = TRUE; + self->priv->nOwnId = g_bus_own_name(G_BUS_TYPE_SESSION, BUS_NAME, G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, onBusAcquired, NULL, onNameLost, self, NULL); +} + +static void indicator_notifications_service_class_init(IndicatorNotificationsServiceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + object_class->dispose = onDispose; + m_nSignal = g_signal_new(INDICATOR_NOTIFICATIONS_SERVICE_SIGNAL_NAME_LOST, G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (IndicatorNotificationsServiceClass, name_lost), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); +} + +IndicatorNotificationsService *indicator_notifications_service_new() +{ + GObject *o = g_object_new(INDICATOR_TYPE_NOTIFICATIONS_SERVICE, NULL); + + return INDICATOR_NOTIFICATIONS_SERVICE(o); +} diff --git a/src/service.h b/src/service.h new file mode 100644 index 0000000..45621ca --- /dev/null +++ b/src/service.h @@ -0,0 +1,65 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr + * + * 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 . + */ + +#ifndef __INDICATOR_NOTIFICATIONS_SERVICE_H__ +#define __INDICATOR_NOTIFICATIONS_SERVICE_H__ + +#include +#include + +G_BEGIN_DECLS + +/* standard GObject macros */ +#define INDICATOR_NOTIFICATIONS_SERVICE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), INDICATOR_TYPE_NOTIFICATIONS_SERVICE, IndicatorNotificationsService)) +#define INDICATOR_TYPE_NOTIFICATIONS_SERVICE (indicator_notifications_service_get_type()) +#define INDICATOR_IS_NOTIFICATIONS_SERVICE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), INDICATOR_TYPE_NOTIFICATIONS_SERVICE)) + +typedef struct _IndicatorNotificationsService IndicatorNotificationsService; +typedef struct _IndicatorNotificationsServiceClass IndicatorNotificationsServiceClass; +typedef struct _IndicatorNotificationsServicePrivate IndicatorNotificationsServicePrivate; + +/* signal keys */ +#define INDICATOR_NOTIFICATIONS_SERVICE_SIGNAL_NAME_LOST "name-lost" + +/** + * The Indicator Notifications Service. + */ +struct _IndicatorNotificationsService +{ + /*< private >*/ + GObject parent; + IndicatorNotificationsServicePrivate * priv; +}; + +struct _IndicatorNotificationsServiceClass +{ + GObjectClass parent_class; + + /* signals */ + + void (* name_lost)(IndicatorNotificationsService * self); +}; + +GType indicator_notifications_service_get_type (void); + +IndicatorNotificationsService *indicator_notifications_service_new(); + +G_END_DECLS + +#endif /* __INDICATOR_NOTIFICATIONS_SERVICE_H__ */ diff --git a/src/settings.h b/src/settings.h deleted file mode 100644 index 9663c8d..0000000 --- a/src/settings.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * settings.h - Definitions of settings keys and schema. - */ - -#ifndef __SETTINGS_H__ -#define __SETTINGS_H__ - -#define NOTIFICATIONS_SCHEMA "org.ayatana.indicator.notifications" -#define NOTIFICATIONS_KEY_FILTER_LIST "filter-list" -#define NOTIFICATIONS_KEY_FILTER_LIST_HINTS "filter-list-hints" -#define NOTIFICATIONS_KEY_CLEAR_MC "clear-on-middle-click" -#define NOTIFICATIONS_KEY_DND "do-not-disturb" -#define NOTIFICATIONS_KEY_HIDE_INDICATOR "hide-indicator" -#define NOTIFICATIONS_KEY_MAX_ITEMS "max-items" -#define NOTIFICATIONS_KEY_SWAP_CLEAR_SETTINGS "swap-clear-settings" - -#define MATE_SCHEMA "org.mate.NotificationDaemon" -#define MATE_KEY_DND "do-not-disturb" - -#endif diff --git a/tests/Makefile.am b/tests/Makefile.am deleted file mode 100644 index e69de29..0000000 -- cgit v1.2.3