From b0e30ceba2288eab10c6ff7be0ac0cb05a9ed0b7 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:51:51 +0200 Subject: Adding upstream version 43.0. Signed-off-by: Daniel Baumann --- plugins/a11y-settings/gsd-a11y-settings-manager.c | 165 + plugins/a11y-settings/gsd-a11y-settings-manager.h | 37 + plugins/a11y-settings/main.c | 7 + plugins/a11y-settings/meson.build | 20 + plugins/color/gcm-self-test.c | 359 ++ plugins/color/gnome-datetime-source.c | 286 ++ plugins/color/gnome-datetime-source.h | 38 + plugins/color/gsd-color-calibrate.c | 412 +++ plugins/color/gsd-color-calibrate.h | 38 + plugins/color/gsd-color-manager.c | 491 +++ plugins/color/gsd-color-manager.h | 46 + plugins/color/gsd-color-state.c | 82 + plugins/color/gsd-color-state.h | 45 + plugins/color/gsd-night-light-common.c | 137 + plugins/color/gsd-night-light-common.h | 39 + plugins/color/gsd-night-light.c | 807 +++++ plugins/color/gsd-night-light.h | 58 + plugins/color/main.c | 7 + plugins/color/meson.build | 50 + plugins/color/test-data/LG-L225W-External.bin | Bin 0 -> 128 bytes plugins/color/test-data/Lenovo-T61-Internal.bin | Bin 0 -> 128 bytes plugins/common/daemon-skeleton-gtk.h | 295 ++ plugins/common/daemon-skeleton.h | 264 ++ plugins/common/gsd-input-helper.c | 79 + plugins/common/gsd-input-helper.h | 33 + plugins/common/gsd-settings-migrate.c | 74 + plugins/common/gsd-settings-migrate.h | 43 + plugins/common/gsd-shell-helper.c | 66 + plugins/common/gsd-shell-helper.h | 42 + plugins/common/gsd.gresources.xml | 6 + plugins/common/gtk.css | 0 plugins/common/meson.build | 43 + plugins/datetime/backward | 118 + plugins/datetime/gsd-datetime-manager.c | 226 ++ plugins/datetime/gsd-datetime-manager.h | 37 + plugins/datetime/gsd-timezone-monitor.c | 472 +++ plugins/datetime/gsd-timezone-monitor.h | 55 + plugins/datetime/main.c | 7 + plugins/datetime/meson.build | 40 + plugins/datetime/timedated1-interface.xml | 28 + plugins/datetime/tz.c | 482 +++ plugins/datetime/tz.h | 89 + plugins/datetime/weather-tz.c | 118 + plugins/datetime/weather-tz.h | 27 + plugins/gsd.service.in | 26 + plugins/gsd.target.in | 12 + plugins/housekeeping/gsd-disk-space-helper.c | 159 + plugins/housekeeping/gsd-disk-space-helper.h | 38 + plugins/housekeeping/gsd-disk-space-test.c | 48 + plugins/housekeeping/gsd-disk-space.c | 1081 ++++++ plugins/housekeeping/gsd-disk-space.h | 64 + plugins/housekeeping/gsd-empty-trash-test.c | 44 + plugins/housekeeping/gsd-housekeeping-manager.c | 519 +++ plugins/housekeeping/gsd-housekeeping-manager.h | 38 + plugins/housekeeping/gsd-purge-temp-test.c | 62 + plugins/housekeeping/gsd-systemd-notify.c | 259 ++ plugins/housekeeping/gsd-systemd-notify.h | 32 + plugins/housekeeping/main.c | 7 + plugins/housekeeping/meson.build | 42 + plugins/keyboard/.indent.pro | 2 + plugins/keyboard/gsd-keyboard-manager.c | 609 ++++ plugins/keyboard/gsd-keyboard-manager.h | 38 + plugins/keyboard/main.c | 7 + plugins/keyboard/meson.build | 21 + plugins/media-keys/audio-selection-test.c | 263 ++ plugins/media-keys/bus-watch-namespace.c | 347 ++ plugins/media-keys/bus-watch-namespace.h | 34 + plugins/media-keys/gsd-marshal.list | 1 + plugins/media-keys/gsd-media-keys-manager.c | 3553 +++++++++++++++++++ plugins/media-keys/gsd-media-keys-manager.h | 47 + plugins/media-keys/main.c | 7 + plugins/media-keys/media-keys.h | 79 + plugins/media-keys/meson.build | 58 + plugins/media-keys/mpris-controller.c | 440 +++ plugins/media-keys/mpris-controller.h | 38 + plugins/media-keys/org.gnome.ShellKeyGrabber.xml | 27 + plugins/media-keys/shell-action-modes.h | 53 + plugins/media-keys/shortcuts-list.h | 103 + plugins/meson-add-wants.sh | 30 + plugins/meson.build | 185 + plugins/org.gnome.SettingsDaemon.Dummy.desktop.in | 7 + plugins/org.gnome.SettingsDaemon.Real.desktop.in | 10 + plugins/power/gpm-common.c | 456 +++ plugins/power/gpm-common.h | 59 + plugins/power/gsd-backlight-helper.c | 149 + plugins/power/gsd-backlight.c | 978 ++++++ plugins/power/gsd-backlight.h | 81 + plugins/power/gsd-power-constants-update.pl | 49 + plugins/power/gsd-power-constants.h | 42 + plugins/power/gsd-power-enums-update.c | 44 + plugins/power/gsd-power-enums.c.in | 40 + plugins/power/gsd-power-enums.h.in | 27 + plugins/power/gsd-power-manager.c | 3561 ++++++++++++++++++++ plugins/power/gsd-power-manager.h | 47 + plugins/power/gsdpowerconstants.py | 16 + plugins/power/gsm-inhibitor-flag.h | 36 + plugins/power/gsm-manager-logout-mode.h | 34 + plugins/power/gsm-presence-flag.h | 33 + plugins/power/main.c | 7 + plugins/power/meson.build | 149 + ...nome.settings-daemon.plugins.power.policy.in.in | 32 + plugins/power/test-backlight-helper | 5 + plugins/power/test.py | 1310 +++++++ .../gsd-print-notifications-manager.c | 1725 ++++++++++ .../gsd-print-notifications-manager.h | 38 + plugins/print-notifications/gsd-printer.c | 1403 ++++++++ plugins/print-notifications/main.c | 7 + plugins/print-notifications/meson.build | 37 + .../rfkill/61-gnome-settings-daemon-rfkill.rules | 8 + plugins/rfkill/gsd-rfkill-manager.c | 902 +++++ plugins/rfkill/gsd-rfkill-manager.h | 39 + plugins/rfkill/main.c | 7 + plugins/rfkill/meson.build | 28 + plugins/rfkill/rfkill-glib.c | 641 ++++ plugins/rfkill/rfkill-glib.h | 57 + plugins/rfkill/rfkill.h | 107 + .../gsd-screensaver-proxy-manager.c | 444 +++ .../gsd-screensaver-proxy-manager.h | 38 + plugins/screensaver-proxy/main.c | 7 + plugins/screensaver-proxy/meson.build | 17 + plugins/sharing/gsd-sharing-enums.h | 34 + plugins/sharing/gsd-sharing-manager.c | 828 +++++ plugins/sharing/gsd-sharing-manager.h | 38 + plugins/sharing/main.c | 7 + plugins/sharing/meson.build | 24 + plugins/smartcard/gsd-smartcard-enum-types.c.in | 42 + plugins/smartcard/gsd-smartcard-enum-types.h.in | 24 + plugins/smartcard/gsd-smartcard-manager.c | 993 ++++++ plugins/smartcard/gsd-smartcard-manager.h | 66 + plugins/smartcard/gsd-smartcard-service.c | 849 +++++ plugins/smartcard/gsd-smartcard-service.h | 60 + plugins/smartcard/gsd-smartcard-utils.c | 174 + plugins/smartcard/gsd-smartcard-utils.h | 33 + plugins/smartcard/main.c | 7 + plugins/smartcard/meson.build | 49 + .../org.gnome.SettingsDaemon.Smartcard.xml | 89 + plugins/sound/gsd-sound-manager.c | 362 ++ plugins/sound/gsd-sound-manager.h | 38 + plugins/sound/main.c | 7 + plugins/sound/meson.build | 20 + .../usb-protection/gsd-usb-protection-manager.c | 1199 +++++++ .../usb-protection/gsd-usb-protection-manager.h | 44 + plugins/usb-protection/main.c | 7 + plugins/usb-protection/meson.build | 21 + plugins/wacom/gsd-wacom-manager.c | 528 +++ plugins/wacom/gsd-wacom-manager.h | 39 + plugins/wacom/gsd-wacom-oled-constants.h | 41 + plugins/wacom/gsd-wacom-oled-helper.c | 422 +++ plugins/wacom/gsd-wacom-oled.c | 258 ++ plugins/wacom/gsd-wacom-oled.h | 34 + plugins/wacom/main.c | 7 + plugins/wacom/meson.build | 62 + ...nome.settings-daemon.plugins.wacom.policy.in.in | 46 + plugins/wwan/cc-wwan-device.c | 1343 ++++++++ plugins/wwan/cc-wwan-device.h | 152 + plugins/wwan/cc-wwan-errors-private.h | 104 + plugins/wwan/gsd-wwan-manager.c | 830 +++++ plugins/wwan/gsd-wwan-manager.h | 36 + plugins/wwan/main.c | 7 + plugins/wwan/meson.build | 21 + plugins/xsettings/00-xrdb | 9 + plugins/xsettings/README.xsettings | 35 + plugins/xsettings/fc-monitor.c | 306 ++ plugins/xsettings/fc-monitor.h | 36 + plugins/xsettings/fontconfig-test/fonts.conf | 8 + plugins/xsettings/gsd-remote-display-manager.h | 28 + plugins/xsettings/gsd-xsettings-gtk.c | 384 +++ plugins/xsettings/gsd-xsettings-gtk.h | 39 + plugins/xsettings/gsd-xsettings-manager.c | 1804 ++++++++++ plugins/xsettings/gsd-xsettings-manager.h | 38 + .../gtk-modules-test/canberra-gtk-module.desktop | 6 + .../gtk-modules-test/pk-gtk-module.desktop | 4 + plugins/xsettings/main.c | 8 + plugins/xsettings/meson.build | 69 + plugins/xsettings/test-gtk-modules.c | 31 + .../xsettings/test-wm-button-layout-translations.c | 54 + plugins/xsettings/test.py | 140 + plugins/xsettings/wm-button-layout-translation.c | 88 + plugins/xsettings/wm-button-layout-translation.h | 26 + plugins/xsettings/xsettings-common.c | 111 + plugins/xsettings/xsettings-common.h | 65 + plugins/xsettings/xsettings-manager.c | 396 +++ plugins/xsettings/xsettings-manager.h | 59 + 183 files changed, 39507 insertions(+) create mode 100644 plugins/a11y-settings/gsd-a11y-settings-manager.c create mode 100644 plugins/a11y-settings/gsd-a11y-settings-manager.h create mode 100644 plugins/a11y-settings/main.c create mode 100644 plugins/a11y-settings/meson.build create mode 100644 plugins/color/gcm-self-test.c create mode 100644 plugins/color/gnome-datetime-source.c create mode 100644 plugins/color/gnome-datetime-source.h create mode 100644 plugins/color/gsd-color-calibrate.c create mode 100644 plugins/color/gsd-color-calibrate.h create mode 100644 plugins/color/gsd-color-manager.c create mode 100644 plugins/color/gsd-color-manager.h create mode 100644 plugins/color/gsd-color-state.c create mode 100644 plugins/color/gsd-color-state.h create mode 100644 plugins/color/gsd-night-light-common.c create mode 100644 plugins/color/gsd-night-light-common.h create mode 100644 plugins/color/gsd-night-light.c create mode 100644 plugins/color/gsd-night-light.h create mode 100644 plugins/color/main.c create mode 100644 plugins/color/meson.build create mode 100644 plugins/color/test-data/LG-L225W-External.bin create mode 100644 plugins/color/test-data/Lenovo-T61-Internal.bin create mode 100644 plugins/common/daemon-skeleton-gtk.h create mode 100644 plugins/common/daemon-skeleton.h create mode 100644 plugins/common/gsd-input-helper.c create mode 100644 plugins/common/gsd-input-helper.h create mode 100644 plugins/common/gsd-settings-migrate.c create mode 100644 plugins/common/gsd-settings-migrate.h create mode 100644 plugins/common/gsd-shell-helper.c create mode 100644 plugins/common/gsd-shell-helper.h create mode 100644 plugins/common/gsd.gresources.xml create mode 100644 plugins/common/gtk.css create mode 100644 plugins/common/meson.build create mode 100644 plugins/datetime/backward create mode 100644 plugins/datetime/gsd-datetime-manager.c create mode 100644 plugins/datetime/gsd-datetime-manager.h create mode 100644 plugins/datetime/gsd-timezone-monitor.c create mode 100644 plugins/datetime/gsd-timezone-monitor.h create mode 100644 plugins/datetime/main.c create mode 100644 plugins/datetime/meson.build create mode 100644 plugins/datetime/timedated1-interface.xml create mode 100644 plugins/datetime/tz.c create mode 100644 plugins/datetime/tz.h create mode 100644 plugins/datetime/weather-tz.c create mode 100644 plugins/datetime/weather-tz.h create mode 100644 plugins/gsd.service.in create mode 100644 plugins/gsd.target.in create mode 100644 plugins/housekeeping/gsd-disk-space-helper.c create mode 100644 plugins/housekeeping/gsd-disk-space-helper.h create mode 100644 plugins/housekeeping/gsd-disk-space-test.c create mode 100644 plugins/housekeeping/gsd-disk-space.c create mode 100644 plugins/housekeeping/gsd-disk-space.h create mode 100644 plugins/housekeeping/gsd-empty-trash-test.c create mode 100644 plugins/housekeeping/gsd-housekeeping-manager.c create mode 100644 plugins/housekeeping/gsd-housekeeping-manager.h create mode 100644 plugins/housekeeping/gsd-purge-temp-test.c create mode 100644 plugins/housekeeping/gsd-systemd-notify.c create mode 100644 plugins/housekeeping/gsd-systemd-notify.h create mode 100644 plugins/housekeeping/main.c create mode 100644 plugins/housekeeping/meson.build create mode 100644 plugins/keyboard/.indent.pro create mode 100644 plugins/keyboard/gsd-keyboard-manager.c create mode 100644 plugins/keyboard/gsd-keyboard-manager.h create mode 100644 plugins/keyboard/main.c create mode 100644 plugins/keyboard/meson.build create mode 100644 plugins/media-keys/audio-selection-test.c create mode 100644 plugins/media-keys/bus-watch-namespace.c create mode 100644 plugins/media-keys/bus-watch-namespace.h create mode 100644 plugins/media-keys/gsd-marshal.list create mode 100644 plugins/media-keys/gsd-media-keys-manager.c create mode 100644 plugins/media-keys/gsd-media-keys-manager.h create mode 100644 plugins/media-keys/main.c create mode 100644 plugins/media-keys/media-keys.h create mode 100644 plugins/media-keys/meson.build create mode 100644 plugins/media-keys/mpris-controller.c create mode 100644 plugins/media-keys/mpris-controller.h create mode 100644 plugins/media-keys/org.gnome.ShellKeyGrabber.xml create mode 100644 plugins/media-keys/shell-action-modes.h create mode 100644 plugins/media-keys/shortcuts-list.h create mode 100755 plugins/meson-add-wants.sh create mode 100644 plugins/meson.build create mode 100644 plugins/org.gnome.SettingsDaemon.Dummy.desktop.in create mode 100644 plugins/org.gnome.SettingsDaemon.Real.desktop.in create mode 100644 plugins/power/gpm-common.c create mode 100644 plugins/power/gpm-common.h create mode 100644 plugins/power/gsd-backlight-helper.c create mode 100644 plugins/power/gsd-backlight.c create mode 100644 plugins/power/gsd-backlight.h create mode 100755 plugins/power/gsd-power-constants-update.pl create mode 100644 plugins/power/gsd-power-constants.h create mode 100644 plugins/power/gsd-power-enums-update.c create mode 100644 plugins/power/gsd-power-enums.c.in create mode 100644 plugins/power/gsd-power-enums.h.in create mode 100644 plugins/power/gsd-power-manager.c create mode 100644 plugins/power/gsd-power-manager.h create mode 100644 plugins/power/gsdpowerconstants.py create mode 100644 plugins/power/gsm-inhibitor-flag.h create mode 100644 plugins/power/gsm-manager-logout-mode.h create mode 100644 plugins/power/gsm-presence-flag.h create mode 100644 plugins/power/main.c create mode 100644 plugins/power/meson.build create mode 100644 plugins/power/org.gnome.settings-daemon.plugins.power.policy.in.in create mode 100755 plugins/power/test-backlight-helper create mode 100755 plugins/power/test.py create mode 100644 plugins/print-notifications/gsd-print-notifications-manager.c create mode 100644 plugins/print-notifications/gsd-print-notifications-manager.h create mode 100644 plugins/print-notifications/gsd-printer.c create mode 100644 plugins/print-notifications/main.c create mode 100644 plugins/print-notifications/meson.build create mode 100644 plugins/rfkill/61-gnome-settings-daemon-rfkill.rules create mode 100644 plugins/rfkill/gsd-rfkill-manager.c create mode 100644 plugins/rfkill/gsd-rfkill-manager.h create mode 100644 plugins/rfkill/main.c create mode 100644 plugins/rfkill/meson.build create mode 100644 plugins/rfkill/rfkill-glib.c create mode 100644 plugins/rfkill/rfkill-glib.h create mode 100644 plugins/rfkill/rfkill.h create mode 100644 plugins/screensaver-proxy/gsd-screensaver-proxy-manager.c create mode 100644 plugins/screensaver-proxy/gsd-screensaver-proxy-manager.h create mode 100644 plugins/screensaver-proxy/main.c create mode 100644 plugins/screensaver-proxy/meson.build create mode 100644 plugins/sharing/gsd-sharing-enums.h create mode 100644 plugins/sharing/gsd-sharing-manager.c create mode 100644 plugins/sharing/gsd-sharing-manager.h create mode 100644 plugins/sharing/main.c create mode 100644 plugins/sharing/meson.build create mode 100644 plugins/smartcard/gsd-smartcard-enum-types.c.in create mode 100644 plugins/smartcard/gsd-smartcard-enum-types.h.in create mode 100644 plugins/smartcard/gsd-smartcard-manager.c create mode 100644 plugins/smartcard/gsd-smartcard-manager.h create mode 100644 plugins/smartcard/gsd-smartcard-service.c create mode 100644 plugins/smartcard/gsd-smartcard-service.h create mode 100644 plugins/smartcard/gsd-smartcard-utils.c create mode 100644 plugins/smartcard/gsd-smartcard-utils.h create mode 100644 plugins/smartcard/main.c create mode 100644 plugins/smartcard/meson.build create mode 100644 plugins/smartcard/org.gnome.SettingsDaemon.Smartcard.xml create mode 100644 plugins/sound/gsd-sound-manager.c create mode 100644 plugins/sound/gsd-sound-manager.h create mode 100644 plugins/sound/main.c create mode 100644 plugins/sound/meson.build create mode 100644 plugins/usb-protection/gsd-usb-protection-manager.c create mode 100644 plugins/usb-protection/gsd-usb-protection-manager.h create mode 100644 plugins/usb-protection/main.c create mode 100644 plugins/usb-protection/meson.build create mode 100644 plugins/wacom/gsd-wacom-manager.c create mode 100644 plugins/wacom/gsd-wacom-manager.h create mode 100644 plugins/wacom/gsd-wacom-oled-constants.h create mode 100644 plugins/wacom/gsd-wacom-oled-helper.c create mode 100644 plugins/wacom/gsd-wacom-oled.c create mode 100644 plugins/wacom/gsd-wacom-oled.h create mode 100644 plugins/wacom/main.c create mode 100644 plugins/wacom/meson.build create mode 100644 plugins/wacom/org.gnome.settings-daemon.plugins.wacom.policy.in.in create mode 100644 plugins/wwan/cc-wwan-device.c create mode 100644 plugins/wwan/cc-wwan-device.h create mode 100644 plugins/wwan/cc-wwan-errors-private.h create mode 100644 plugins/wwan/gsd-wwan-manager.c create mode 100644 plugins/wwan/gsd-wwan-manager.h create mode 100644 plugins/wwan/main.c create mode 100644 plugins/wwan/meson.build create mode 100755 plugins/xsettings/00-xrdb create mode 100644 plugins/xsettings/README.xsettings create mode 100644 plugins/xsettings/fc-monitor.c create mode 100644 plugins/xsettings/fc-monitor.h create mode 100644 plugins/xsettings/fontconfig-test/fonts.conf create mode 100644 plugins/xsettings/gsd-remote-display-manager.h create mode 100644 plugins/xsettings/gsd-xsettings-gtk.c create mode 100644 plugins/xsettings/gsd-xsettings-gtk.h create mode 100644 plugins/xsettings/gsd-xsettings-manager.c create mode 100644 plugins/xsettings/gsd-xsettings-manager.h create mode 100644 plugins/xsettings/gtk-modules-test/canberra-gtk-module.desktop create mode 100644 plugins/xsettings/gtk-modules-test/pk-gtk-module.desktop create mode 100644 plugins/xsettings/main.c create mode 100644 plugins/xsettings/meson.build create mode 100644 plugins/xsettings/test-gtk-modules.c create mode 100644 plugins/xsettings/test-wm-button-layout-translations.c create mode 100755 plugins/xsettings/test.py create mode 100644 plugins/xsettings/wm-button-layout-translation.c create mode 100644 plugins/xsettings/wm-button-layout-translation.h create mode 100644 plugins/xsettings/xsettings-common.c create mode 100644 plugins/xsettings/xsettings-common.h create mode 100644 plugins/xsettings/xsettings-manager.c create mode 100644 plugins/xsettings/xsettings-manager.h (limited to 'plugins') diff --git a/plugins/a11y-settings/gsd-a11y-settings-manager.c b/plugins/a11y-settings/gsd-a11y-settings-manager.c new file mode 100644 index 0000000..48cb43e --- /dev/null +++ b/plugins/a11y-settings/gsd-a11y-settings-manager.c @@ -0,0 +1,165 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "gnome-settings-profile.h" +#include "gsd-a11y-settings-manager.h" + +struct _GsdA11ySettingsManager +{ + GObject parent; + + GSettings *interface_settings; + GSettings *a11y_apps_settings; +}; + +enum { + PROP_0, +}; + +static void gsd_a11y_settings_manager_class_init (GsdA11ySettingsManagerClass *klass); +static void gsd_a11y_settings_manager_init (GsdA11ySettingsManager *a11y_settings_manager); +static void gsd_a11y_settings_manager_finalize (GObject *object); + +G_DEFINE_TYPE (GsdA11ySettingsManager, gsd_a11y_settings_manager, G_TYPE_OBJECT) + +static gpointer manager_object = NULL; + +static void +apps_settings_changed (GSettings *settings, + const char *key, + GsdA11ySettingsManager *manager) +{ + gboolean screen_reader, keyboard, magnifier; + + if (g_str_equal (key, "screen-reader-enabled") == FALSE && + g_str_equal (key, "screen-keyboard-enabled") == FALSE && + g_str_equal (key, "screen-magnifier-enabled") == FALSE) + return; + + g_debug ("screen reader, OSK or magnifier enablement changed"); + + screen_reader = g_settings_get_boolean (manager->a11y_apps_settings, "screen-reader-enabled"); + keyboard = g_settings_get_boolean (manager->a11y_apps_settings, "screen-keyboard-enabled"); + magnifier = g_settings_get_boolean (manager->a11y_apps_settings, "screen-magnifier-enabled"); + + if (screen_reader || keyboard || magnifier) { + g_debug ("Enabling toolkit-accessibility, screen reader, OSK or magnifier enabled"); + g_settings_set_boolean (manager->interface_settings, "toolkit-accessibility", TRUE); + } else if (screen_reader == FALSE && keyboard == FALSE && magnifier == FALSE) { + g_debug ("Disabling toolkit-accessibility, screen reader, OSK and magnifier disabled"); + g_settings_set_boolean (manager->interface_settings, "toolkit-accessibility", FALSE); + } +} + +gboolean +gsd_a11y_settings_manager_start (GsdA11ySettingsManager *manager, + GError **error) +{ + g_debug ("Starting a11y_settings manager"); + gnome_settings_profile_start (NULL); + + manager->interface_settings = g_settings_new ("org.gnome.desktop.interface"); + manager->a11y_apps_settings = g_settings_new ("org.gnome.desktop.a11y.applications"); + + g_signal_connect (G_OBJECT (manager->a11y_apps_settings), "changed", + G_CALLBACK (apps_settings_changed), manager); + + /* If any of the screen reader, on-screen keyboard or magnifier are + * enabled, make sure a11y is enabled for the toolkits. + * We don't do the same thing for the reverse so it's possible to + * enable AT-SPI for the toolkits without using an a11y app */ + if (g_settings_get_boolean (manager->a11y_apps_settings, "screen-keyboard-enabled") || + g_settings_get_boolean (manager->a11y_apps_settings, "screen-reader-enabled") || + g_settings_get_boolean (manager->a11y_apps_settings, "screen-magnifier-enabled")) + g_settings_set_boolean (manager->interface_settings, "toolkit-accessibility", TRUE); + + gnome_settings_profile_end (NULL); + return TRUE; +} + +void +gsd_a11y_settings_manager_stop (GsdA11ySettingsManager *manager) +{ + if (manager->interface_settings) { + g_object_unref (manager->interface_settings); + manager->interface_settings = NULL; + } + if (manager->a11y_apps_settings) { + g_object_unref (manager->a11y_apps_settings); + manager->a11y_apps_settings = NULL; + } + g_debug ("Stopping a11y_settings manager"); +} + +static void +gsd_a11y_settings_manager_class_init (GsdA11ySettingsManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gsd_a11y_settings_manager_finalize; +} + +static void +gsd_a11y_settings_manager_init (GsdA11ySettingsManager *manager) +{ +} + +static void +gsd_a11y_settings_manager_finalize (GObject *object) +{ + GsdA11ySettingsManager *a11y_settings_manager; + + g_return_if_fail (object != NULL); + g_return_if_fail (GSD_IS_A11Y_SETTINGS_MANAGER (object)); + + a11y_settings_manager = GSD_A11Y_SETTINGS_MANAGER (object); + + gsd_a11y_settings_manager_stop (a11y_settings_manager); + + G_OBJECT_CLASS (gsd_a11y_settings_manager_parent_class)->finalize (object); +} + +GsdA11ySettingsManager * +gsd_a11y_settings_manager_new (void) +{ + if (manager_object != NULL) { + g_object_ref (manager_object); + } else { + manager_object = g_object_new (GSD_TYPE_A11Y_SETTINGS_MANAGER, NULL); + g_object_add_weak_pointer (manager_object, + (gpointer *) &manager_object); + } + + return GSD_A11Y_SETTINGS_MANAGER (manager_object); +} diff --git a/plugins/a11y-settings/gsd-a11y-settings-manager.h b/plugins/a11y-settings/gsd-a11y-settings-manager.h new file mode 100644 index 0000000..56a2bbb --- /dev/null +++ b/plugins/a11y-settings/gsd-a11y-settings-manager.h @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 __GSD_A11Y_SETTINGS_MANAGER_H +#define __GSD_A11Y_SETTINGS_MANAGER_H + +#include + +G_BEGIN_DECLS + +#define GSD_TYPE_A11Y_SETTINGS_MANAGER gsd_a11y_settings_manager_get_type () +G_DECLARE_FINAL_TYPE (GsdA11ySettingsManager, gsd_a11y_settings_manager, GSD, A11Y_SETTINGS_MANAGER, GObject) + +GsdA11ySettingsManager *gsd_a11y_settings_manager_new (void); +gboolean gsd_a11y_settings_manager_start (GsdA11ySettingsManager *manager, + GError **error); +void gsd_a11y_settings_manager_stop (GsdA11ySettingsManager *manager); + +G_END_DECLS + +#endif /* __GSD_A11Y_SETTINGS_MANAGER_H */ diff --git a/plugins/a11y-settings/main.c b/plugins/a11y-settings/main.c new file mode 100644 index 0000000..aff6cd5 --- /dev/null +++ b/plugins/a11y-settings/main.c @@ -0,0 +1,7 @@ +#define NEW gsd_a11y_settings_manager_new +#define START gsd_a11y_settings_manager_start +#define STOP gsd_a11y_settings_manager_stop +#define MANAGER GsdA11ySettingsManager +#include "gsd-a11y-settings-manager.h" + +#include "daemon-skeleton.h" diff --git a/plugins/a11y-settings/meson.build b/plugins/a11y-settings/meson.build new file mode 100644 index 0000000..b4a0fb6 --- /dev/null +++ b/plugins/a11y-settings/meson.build @@ -0,0 +1,20 @@ +sources = files( + 'gsd-a11y-settings-manager.c', + 'main.c' +) + +deps = plugins_deps + [ + gio_dep, + gsettings_desktop_dep +] + +executable( + 'gsd-' + plugin_name, + sources, + include_directories: [top_inc, common_inc], + dependencies: deps, + c_args: cflags, + install: true, + install_rpath: gsd_pkglibdir, + install_dir: gsd_libexecdir +) diff --git a/plugins/color/gcm-self-test.c b/plugins/color/gcm-self-test.c new file mode 100644 index 0000000..e293861 --- /dev/null +++ b/plugins/color/gcm-self-test.c @@ -0,0 +1,359 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2007-2011 Richard Hughes + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include +#include +#include + +#include "gsd-color-state.h" +#include "gsd-night-light.h" +#include "gsd-night-light-common.h" + +GMainLoop *mainloop; + +static void +on_notify (GsdNightLight *nlight, + GParamSpec *pspec, + gpointer user_data) +{ + guint *cnt = (guint *) user_data; + (*cnt)++; +} + +static gboolean +quit_mainloop (gpointer user_data) +{ + g_main_loop_quit (mainloop); + + return FALSE; +} + +static void +gcm_test_night_light (void) +{ + gboolean ret; + guint active_cnt = 0; + guint disabled_until_tmw_cnt = 0; + guint sunrise_cnt = 0; + guint sunset_cnt = 0; + guint temperature_cnt = 0; + g_autoptr(GDateTime) datetime_override = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GsdNightLight) nlight = NULL; + g_autoptr(GSettings) settings = NULL; + + nlight = gsd_night_light_new (); + g_assert (GSD_IS_NIGHT_LIGHT (nlight)); + g_signal_connect (nlight, "notify::active", + G_CALLBACK (on_notify), &active_cnt); + g_signal_connect (nlight, "notify::sunset", + G_CALLBACK (on_notify), &sunset_cnt); + g_signal_connect (nlight, "notify::sunrise", + G_CALLBACK (on_notify), &sunrise_cnt); + g_signal_connect (nlight, "notify::temperature", + G_CALLBACK (on_notify), &temperature_cnt); + g_signal_connect (nlight, "notify::disabled-until-tmw", + G_CALLBACK (on_notify), &disabled_until_tmw_cnt); + + /* hardcode a specific date and time */ + datetime_override = g_date_time_new_utc (2017, 2, 8, 20, 0, 0); + gsd_night_light_set_date_time_now (nlight, datetime_override); + + /* do not start geoclue */ + gsd_night_light_set_geoclue_enabled (nlight, FALSE); + + /* do not smooth the transition */ + gsd_night_light_set_smooth_enabled (nlight, FALSE); + + /* switch off */ + settings = g_settings_new ("org.gnome.settings-daemon.plugins.color"); + g_settings_set_boolean (settings, "night-light-enabled", FALSE); + g_settings_set_uint (settings, "night-light-temperature", 4000); + + /* check default values */ + g_assert (!gsd_night_light_get_active (nlight)); + g_assert_cmpint ((gint) gsd_night_light_get_sunrise (nlight), ==, -1); + g_assert_cmpint ((gint) gsd_night_light_get_sunset (nlight), ==, -1); + g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT); + g_assert (!gsd_night_light_get_disabled_until_tmw (nlight)); + + /* start module, disabled */ + ret = gsd_night_light_start (nlight, &error); + g_assert_no_error (error); + g_assert (ret); + g_assert (!gsd_night_light_get_active (nlight)); + g_assert_cmpint (active_cnt, ==, 0); + g_assert_cmpint (sunset_cnt, ==, 0); + g_assert_cmpint (sunrise_cnt, ==, 0); + g_assert_cmpint (temperature_cnt, ==, 0); + g_assert_cmpint (disabled_until_tmw_cnt, ==, 0); + + /* enable automatic mode */ + g_settings_set_value (settings, "night-light-last-coordinates", + g_variant_new ("(dd)", 51.5, -0.1278)); + g_settings_set_boolean (settings, "night-light-schedule-automatic", TRUE); + g_settings_set_boolean (settings, "night-light-enabled", TRUE); + g_assert (gsd_night_light_get_active (nlight)); + g_assert_cmpint (active_cnt, ==, 1); + g_assert_cmpint (sunset_cnt, ==, 1); + g_assert_cmpint (sunrise_cnt, ==, 1); + g_assert_cmpint (temperature_cnt, ==, 1); + g_assert_cmpint (disabled_until_tmw_cnt, ==, 0); + g_assert_cmpint ((gint) gsd_night_light_get_sunrise (nlight), ==, 7); + g_assert_cmpint ((gint) gsd_night_light_get_sunset (nlight), ==, 17); + g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, 4000); + g_assert (!gsd_night_light_get_disabled_until_tmw (nlight)); + + /* disable for one day */ + gsd_night_light_set_disabled_until_tmw (nlight, TRUE); + gsd_night_light_set_disabled_until_tmw (nlight, TRUE); + g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT); + g_assert (gsd_night_light_get_active (nlight)); + g_assert (gsd_night_light_get_disabled_until_tmw (nlight)); + g_assert_cmpint (temperature_cnt, ==, 2); + g_assert_cmpint (disabled_until_tmw_cnt, ==, 1); + + /* change our mind */ + gsd_night_light_set_disabled_until_tmw (nlight, FALSE); + g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, 4000); + g_assert (gsd_night_light_get_active (nlight)); + g_assert (!gsd_night_light_get_disabled_until_tmw (nlight)); + g_assert_cmpint (active_cnt, ==, 1); + g_assert_cmpint (temperature_cnt, ==, 3); + g_assert_cmpint (disabled_until_tmw_cnt, ==, 2); + + /* enabled manual mode (night shift) */ + g_settings_set_double (settings, "night-light-schedule-from", 4.0); + g_settings_set_double (settings, "night-light-schedule-to", 16.f); + g_settings_set_boolean (settings, "night-light-schedule-automatic", FALSE); + g_assert_cmpint (active_cnt, ==, 2); + g_assert_cmpint (sunset_cnt, ==, 1); + g_assert_cmpint (sunrise_cnt, ==, 1); + g_assert_cmpint (temperature_cnt, ==, 4); + g_assert_cmpint (disabled_until_tmw_cnt, ==, 2); + g_assert (!gsd_night_light_get_active (nlight)); + g_assert_cmpint ((gint) gsd_night_light_get_sunrise (nlight), ==, 7); + g_assert_cmpint ((gint) gsd_night_light_get_sunset (nlight), ==, 17); + g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT); + g_assert (!gsd_night_light_get_disabled_until_tmw (nlight)); + + /* disable, with no changes */ + g_settings_set_boolean (settings, "night-light-enabled", FALSE); + g_assert (!gsd_night_light_get_active (nlight)); + g_assert_cmpint (active_cnt, ==, 2); + g_assert_cmpint (sunset_cnt, ==, 1); + g_assert_cmpint (sunrise_cnt, ==, 1); + g_assert_cmpint (temperature_cnt, ==, 4); + g_assert_cmpint (disabled_until_tmw_cnt, ==, 2); + + + /* Finally, check that cancelling a smooth transition works */ + gsd_night_light_set_smooth_enabled (nlight, TRUE); + /* Enable night light and automatic scheduling */ + g_settings_set_boolean (settings, "night-light-schedule-automatic", TRUE); + g_settings_set_boolean (settings, "night-light-enabled", TRUE); + /* It should be active again, and a smooth transition is being done, + * so the color temperature is still the default at this point. */ + g_assert (gsd_night_light_get_active (nlight)); + g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT); + + /* Turn off immediately, before the first timeout event is fired. */ + g_settings_set_boolean (settings, "night-light-schedule-automatic", FALSE); + g_settings_set_boolean (settings, "night-light-enabled", FALSE); + g_assert (!gsd_night_light_get_active (nlight)); + + /* Now, sleep for a bit (the smooth transition time is 5 seconds) */ + g_timeout_add (5000, quit_mainloop, NULL); + g_main_loop_run (mainloop); + + /* Ensure that the color temperature is still the default one.*/ + g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT); + + + /* Check that disabled until tomorrow resets again correctly. */ + g_settings_set_double (settings, "night-light-schedule-from", 17.0); + g_settings_set_double (settings, "night-light-schedule-to", 7.f); + g_settings_set_boolean (settings, "night-light-enabled", TRUE); + gsd_night_light_set_disabled_until_tmw (nlight, TRUE); + + /* Move time past midnight */ + g_clear_pointer (&datetime_override, g_date_time_unref); + datetime_override = g_date_time_new_utc (2017, 2, 9, 1, 0, 0); + gsd_night_light_set_date_time_now (nlight, datetime_override); + g_assert_true (gsd_night_light_get_disabled_until_tmw (nlight)); + + /* Move past sunrise */ + g_clear_pointer (&datetime_override, g_date_time_unref); + datetime_override = g_date_time_new_utc (2017, 2, 9, 8, 0, 0); + gsd_night_light_set_date_time_now (nlight, datetime_override); + g_assert_false (gsd_night_light_get_disabled_until_tmw (nlight)); + + gsd_night_light_set_disabled_until_tmw (nlight, TRUE); + + /* Move into night more than 24h in the future */ + g_clear_pointer (&datetime_override, g_date_time_unref); + datetime_override = g_date_time_new_utc (2017, 2, 10, 20, 0, 0); + gsd_night_light_set_date_time_now (nlight, datetime_override); + g_assert_false (gsd_night_light_get_disabled_until_tmw (nlight)); + + + /* Check that we are always in night mode if from/to are equal. */ + gsd_night_light_set_smooth_enabled (nlight, FALSE); + g_settings_set_double (settings, "night-light-schedule-from", 6.0); + g_settings_set_double (settings, "night-light-schedule-to", 6.0); + g_settings_set_boolean (settings, "night-light-enabled", TRUE); + + datetime_override = g_date_time_new_utc (2017, 2, 10, 5, 50, 0); + gsd_night_light_set_date_time_now (nlight, datetime_override); + g_assert (gsd_night_light_get_active (nlight)); + g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, 4000); + + datetime_override = g_date_time_new_utc (2017, 2, 10, 6, 0, 0); + gsd_night_light_set_date_time_now (nlight, datetime_override); + g_assert (gsd_night_light_get_active (nlight)); + g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, 4000); + + datetime_override = g_date_time_new_utc (2017, 2, 10, 6, 10, 0); + gsd_night_light_set_date_time_now (nlight, datetime_override); + g_assert (gsd_night_light_get_active (nlight)); + g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, 4000); + + + /* Check that the smearing time is lowered correctly when the times are close. */ + g_settings_set_double (settings, "night-light-schedule-from", 6.0); + g_settings_set_double (settings, "night-light-schedule-to", 6.1); + + /* Not enabled 10 minutes before sunset */ + datetime_override = g_date_time_new_utc (2017, 2, 10, 5, 50, 0); + gsd_night_light_set_date_time_now (nlight, datetime_override); + g_assert_false (gsd_night_light_get_active (nlight)); + g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT); + + /* Not enabled >10 minutes after sunrise */ + datetime_override = g_date_time_new_utc (2017, 2, 10, 6, 20, 0); + gsd_night_light_set_date_time_now (nlight, datetime_override); + g_assert_false (gsd_night_light_get_active (nlight)); + g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT); + + /* ~50% smeared 3 min before sunrise (sunrise at 6 past) */ + datetime_override = g_date_time_new_utc (2017, 2, 10, 6, 3, 0); + gsd_night_light_set_date_time_now (nlight, datetime_override); + g_assert_true (gsd_night_light_get_active (nlight)); + g_assert_cmpint (gsd_night_light_get_temperature (nlight), <=, (GSD_COLOR_TEMPERATURE_DEFAULT + 4000) / 2 + 20); + g_assert_cmpint (gsd_night_light_get_temperature (nlight), >=, (GSD_COLOR_TEMPERATURE_DEFAULT + 4000) / 2 - 20); + + /* ~50% smeared 3 min before sunset (sunset at 6 past) */ + g_settings_set_double (settings, "night-light-schedule-from", 6.1); + g_settings_set_double (settings, "night-light-schedule-to", 6.0); + datetime_override = g_date_time_new_utc (2017, 2, 10, 6, 3, 0); + gsd_night_light_set_date_time_now (nlight, datetime_override); + g_assert_true (gsd_night_light_get_active (nlight)); + g_assert_cmpint (gsd_night_light_get_temperature (nlight), <=, (GSD_COLOR_TEMPERATURE_DEFAULT + 4000) / 2 + 20); + g_assert_cmpint (gsd_night_light_get_temperature (nlight), >=, (GSD_COLOR_TEMPERATURE_DEFAULT + 4000) / 2 - 20); +} + +static void +gcm_test_sunset_sunrise (void) +{ + gdouble sunrise; + gdouble sunrise_actual = 7.6; + gdouble sunset; + gdouble sunset_actual = 16.8; + g_autoptr(GDateTime) dt = g_date_time_new_utc (2007, 2, 1, 0, 0, 0); + + /* get for London, today */ + gsd_night_light_get_sunrise_sunset (dt, 51.5, -0.1278, &sunrise, &sunset); + g_assert_cmpfloat (sunrise, <, sunrise_actual + 0.1); + g_assert_cmpfloat (sunrise, >, sunrise_actual - 0.1); + g_assert_cmpfloat (sunset, <, sunset_actual + 0.1); + g_assert_cmpfloat (sunset, >, sunset_actual - 0.1); +} + +static void +gcm_test_sunset_sunrise_fractional_timezone (void) +{ + gdouble sunrise; + gdouble sunrise_actual = 7.6 + 1.5; + gdouble sunset; + gdouble sunset_actual = 16.8 + 1.5; + g_autoptr(GTimeZone) tz = NULL; + g_autoptr(GDateTime) dt = NULL; + + tz = g_time_zone_new ("+01:30"); + dt = g_date_time_new (tz, 2007, 2, 1, 0, 0, 0); + + /* get for our made up timezone, today */ + gsd_night_light_get_sunrise_sunset (dt, 51.5, -0.1278, &sunrise, &sunset); + g_assert_cmpfloat (sunrise, <, sunrise_actual + 0.1); + g_assert_cmpfloat (sunrise, >, sunrise_actual - 0.1); + g_assert_cmpfloat (sunset, <, sunset_actual + 0.1); + g_assert_cmpfloat (sunset, >, sunset_actual - 0.1); +} + +static void +gcm_test_frac_day (void) +{ + g_autoptr(GDateTime) dt = g_date_time_new_utc (2007, 2, 1, 12, 59, 59); + gdouble fd; + gdouble fd_actual = 12.99; + + /* test for 12:59:59 */ + fd = gsd_night_light_frac_day_from_dt (dt); + g_assert_cmpfloat (fd, >, fd_actual - 0.01); + g_assert_cmpfloat (fd, <, fd_actual + 0.01); + + /* test same day */ + g_assert_true (gsd_night_light_frac_day_is_between (12, 6, 20)); + g_assert_false (gsd_night_light_frac_day_is_between (5, 6, 20)); + g_assert_true (gsd_night_light_frac_day_is_between (12, 0, 24)); + g_assert_true (gsd_night_light_frac_day_is_between (12, -1, 25)); + + /* test rollover to next day */ + g_assert_true (gsd_night_light_frac_day_is_between (23, 20, 6)); + g_assert_false (gsd_night_light_frac_day_is_between (12, 20, 6)); + + /* test rollover to the previous day */ + g_assert_true (gsd_night_light_frac_day_is_between (5, 16, 8)); + + /* test equality */ + g_assert_true (gsd_night_light_frac_day_is_between (12, 0.5, 24.5)); + g_assert_true (gsd_night_light_frac_day_is_between (0.5, 0.5, 0.5)); +} + +int +main (int argc, char **argv) +{ + g_setenv ("GSETTINGS_BACKEND", "memory", TRUE); + + g_test_init (&argc, &argv, NULL); + + mainloop = g_main_loop_new (g_main_context_default (), FALSE); + + g_test_add_func ("/color/sunset-sunrise", gcm_test_sunset_sunrise); + g_test_add_func ("/color/sunset-sunrise/fractional-timezone", gcm_test_sunset_sunrise_fractional_timezone); + g_test_add_func ("/color/fractional-day", gcm_test_frac_day); + g_test_add_func ("/color/night-light", gcm_test_night_light); + + return g_test_run (); +} + diff --git a/plugins/color/gnome-datetime-source.c b/plugins/color/gnome-datetime-source.c new file mode 100644 index 0000000..287ba2d --- /dev/null +++ b/plugins/color/gnome-datetime-source.c @@ -0,0 +1,286 @@ +/* -*- mode: C; c-file-style: "linux"; indent-tabs-mode: t -*- + * gdatetime-source.c - copy&paste from https://bugzilla.gnome.org/show_bug.cgi?id=655129 + * + * Copyright (C) 2011 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Author: Colin Walters + */ + +#include "config.h" + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include "gnome-datetime-source.h" + +#if HAVE_TIMERFD +#include +#include +#include +#endif + +typedef struct _GDateTimeSource GDateTimeSource; +struct _GDateTimeSource +{ + GSource source; + + gint64 real_expiration; + gint64 wakeup_expiration; + + gboolean cancel_on_set : 1; + gboolean initially_expired : 1; + + GPollFD pollfd; +}; + +static inline void +g_datetime_source_reschedule (GDateTimeSource *datetime_source, + gint64 from_monotonic) +{ + datetime_source->wakeup_expiration = from_monotonic + G_TIME_SPAN_SECOND; +} + +static gboolean +g_datetime_source_is_expired (GDateTimeSource *datetime_source) +{ + gint64 real_now; + gint64 monotonic_now; + + real_now = g_get_real_time (); + monotonic_now = g_source_get_time ((GSource*)datetime_source); + + if (datetime_source->initially_expired) + return TRUE; + + if (datetime_source->real_expiration <= real_now) + return TRUE; + + /* We can't really detect without system support when things + * change; so just trigger every second (i.e. our wakeup + * expiration) + */ + if (datetime_source->cancel_on_set && monotonic_now >= datetime_source->wakeup_expiration) + return TRUE; + + return FALSE; +} + +/* In prepare, we're just checking the monotonic time against + * our projected wakeup. + */ +static gboolean +g_datetime_source_prepare (GSource *source, + gint *timeout) +{ + GDateTimeSource *datetime_source = (GDateTimeSource*)source; + gint64 monotonic_now; + +#if HAVE_TIMERFD + if (datetime_source->pollfd.fd != -1) { + *timeout = -1; + return datetime_source->initially_expired; /* Should be TRUE at most one time, FALSE forever after */ + } +#endif + + monotonic_now = g_source_get_time (source); + + if (monotonic_now < datetime_source->wakeup_expiration) { + /* Round up to ensure that we don't try again too early */ + *timeout = (datetime_source->wakeup_expiration - monotonic_now + 999) / 1000; + return FALSE; + } + + *timeout = 0; + return g_datetime_source_is_expired (datetime_source); +} + +/* In check, we're looking at the wall clock. + */ +static gboolean +g_datetime_source_check (GSource *source) +{ + GDateTimeSource *datetime_source = (GDateTimeSource*)source; + +#if HAVE_TIMERFD + if (datetime_source->pollfd.fd != -1) + return datetime_source->pollfd.revents != 0; +#endif + + if (g_datetime_source_is_expired (datetime_source)) + return TRUE; + + g_datetime_source_reschedule (datetime_source, g_source_get_time (source)); + + return FALSE; +} + +static gboolean +g_datetime_source_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + GDateTimeSource *datetime_source = (GDateTimeSource*)source; + + datetime_source->initially_expired = FALSE; + + if (!callback) { + g_warning ("Timeout source dispatched without callback\n" + "You must call g_source_set_callback()."); + return FALSE; + } + + (callback) (user_data); + + /* Always false as this source is documented to run once */ + return FALSE; +} + +static void +g_datetime_source_finalize (GSource *source) +{ +#if HAVE_TIMERFD + GDateTimeSource *datetime_source = (GDateTimeSource*)source; + if (datetime_source->pollfd.fd != -1) + close (datetime_source->pollfd.fd); +#endif +} + +static GSourceFuncs g_datetime_source_funcs = { + g_datetime_source_prepare, + g_datetime_source_check, + g_datetime_source_dispatch, + g_datetime_source_finalize +}; + +#if HAVE_TIMERFD +static gboolean +g_datetime_source_init_timerfd (GDateTimeSource *datetime_source, + gint64 expected_now_seconds, + gint64 unix_seconds) +{ + struct itimerspec its; + int settime_flags; + + datetime_source->pollfd.fd = timerfd_create (CLOCK_REALTIME, TFD_CLOEXEC); + if (datetime_source->pollfd.fd == -1) + return FALSE; + + memset (&its, 0, sizeof (its)); + its.it_value.tv_sec = (time_t) unix_seconds; + + /* http://article.gmane.org/gmane.linux.kernel/1132138 */ +#ifndef TFD_TIMER_CANCEL_ON_SET +#define TFD_TIMER_CANCEL_ON_SET (1 << 1) +#endif + + settime_flags = TFD_TIMER_ABSTIME; + if (datetime_source->cancel_on_set) + settime_flags |= TFD_TIMER_CANCEL_ON_SET; + + if (timerfd_settime (datetime_source->pollfd.fd, settime_flags, &its, NULL) < 0) { + close (datetime_source->pollfd.fd); + datetime_source->pollfd.fd = -1; + return FALSE; + } + + /* Now we need to check that the clock didn't go backwards before we + * had the timerfd set up. See + * https://bugzilla.gnome.org/show_bug.cgi?id=655129 + */ + clock_gettime (CLOCK_REALTIME, &its.it_value); + if (its.it_value.tv_sec < expected_now_seconds) + datetime_source->initially_expired = TRUE; + + datetime_source->pollfd.events = G_IO_IN; + + g_source_add_poll ((GSource*) datetime_source, &datetime_source->pollfd); + + return TRUE; +} +#endif + +/** + * _gnome_date_time_source_new: + * @now: The expected current time + * @expiry: Time to await + * @cancel_on_set: Also invoke callback if the system clock changes discontiguously + * + * This function is designed for programs that want to schedule an + * event based on real (wall clock) time, as returned by + * g_get_real_time(). For example, HOUR:MINUTE wall-clock displays + * and calendaring software. The callback will be invoked when the + * specified wall clock time @expiry is reached. This includes + * events such as the system clock being set past the given time. + * + * Compare versus g_timeout_source_new() which is defined to use + * monotonic time as returned by g_get_monotonic_time(). + * + * The parameter @now is necessary to avoid a race condition in + * between getting the current time and calling this function. + * + * If @cancel_on_set is given, the callback will also be invoked at + * most a second after the system clock is changed. This includes + * being set backwards or forwards, and system + * resume from suspend. Not all operating systems allow detecting all + * relevant events efficiently - this function may cause the process + * to wake up once a second in those cases. + * + * A wall clock display should use @cancel_on_set; a calendaring + * program shouldn't need to. + * + * Note that the return value from the associated callback will be + * ignored; this is a one time watch. + * + * This function currently does not detect time zone + * changes. On Linux, your program should also monitor the + * /etc/timezone file using + * #GFileMonitor. + * + * Clock exampleFIXME: MISSING XINCLUDE CONTENT + * + * Return value: A newly-constructed #GSource + * + * Since: 2.30 + **/ +GSource * +_gnome_datetime_source_new (GDateTime *now, + GDateTime *expiry, + gboolean cancel_on_set) +{ + GDateTimeSource *datetime_source; + gint64 unix_seconds; + + unix_seconds = g_date_time_to_unix (expiry); + + datetime_source = (GDateTimeSource*) g_source_new (&g_datetime_source_funcs, sizeof (GDateTimeSource)); + + datetime_source->cancel_on_set = cancel_on_set; + +#if HAVE_TIMERFD + { + gint64 expected_now_seconds = g_date_time_to_unix (now); + if (g_datetime_source_init_timerfd (datetime_source, expected_now_seconds, unix_seconds)) + return (GSource*)datetime_source; + /* Fall through to non-timerfd code */ + } +#endif + + datetime_source->real_expiration = unix_seconds * 1000000; + g_datetime_source_reschedule (datetime_source, g_get_monotonic_time ()); + + return (GSource*)datetime_source; +} + diff --git a/plugins/color/gnome-datetime-source.h b/plugins/color/gnome-datetime-source.h new file mode 100644 index 0000000..e9ecbf0 --- /dev/null +++ b/plugins/color/gnome-datetime-source.h @@ -0,0 +1,38 @@ +/* gnome-rr.h + * + * Copyright 2011, Red Hat, Inc. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Author: Colin Walters + */ + +#ifndef GNOME_DATETIME_SOURCE_H +#define GNOME_DATETIME_SOURCE_H + +#ifndef GNOME_DESKTOP_USE_UNSTABLE_API +#error This is unstable API. You must define GNOME_DESKTOP_USE_UNSTABLE_API +#endif + +#include + +GSource *_gnome_datetime_source_new (GDateTime *now, + GDateTime *expiry, + gboolean cancel_on_set); + +#endif /* GNOME_DATETIME_SOURCE_H */ diff --git a/plugins/color/gsd-color-calibrate.c b/plugins/color/gsd-color-calibrate.c new file mode 100644 index 0000000..f642e18 --- /dev/null +++ b/plugins/color/gsd-color-calibrate.c @@ -0,0 +1,412 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann + * Copyright (C) 2011-2013 Richard Hughes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 "config.h" + +#include +#include +#include +#include + +#include "gsd-color-calibrate.h" + +#define GCM_SESSION_NOTIFY_TIMEOUT 30000 /* ms */ +#define GCM_SETTINGS_RECALIBRATE_PRINTER_THRESHOLD "recalibrate-printer-threshold" +#define GCM_SETTINGS_RECALIBRATE_DISPLAY_THRESHOLD "recalibrate-display-threshold" + +struct _GsdColorCalibrate +{ + GObject parent; + + CdClient *client; + GSettings *settings; +}; + +static void gsd_color_calibrate_class_init (GsdColorCalibrateClass *klass); +static void gsd_color_calibrate_init (GsdColorCalibrate *color_calibrate); +static void gsd_color_calibrate_finalize (GObject *object); + +G_DEFINE_TYPE (GsdColorCalibrate, gsd_color_calibrate, G_TYPE_OBJECT) + +typedef struct { + GsdColorCalibrate *calibrate; + CdProfile *profile; + CdDevice *device; + guint32 output_id; +} GcmSessionAsyncHelper; + +static void +gcm_session_async_helper_free (GcmSessionAsyncHelper *helper) +{ + if (helper->calibrate != NULL) + g_object_unref (helper->calibrate); + if (helper->profile != NULL) + g_object_unref (helper->profile); + if (helper->device != NULL) + g_object_unref (helper->device); + g_free (helper); +} + +static void +gcm_session_exec_control_center (GsdColorCalibrate *calibrate) +{ + gboolean ret; + GError *error = NULL; + GAppInfo *app_info; + GdkAppLaunchContext *launch_context; + + /* setup the launch context so the startup notification is correct */ + launch_context = gdk_display_get_app_launch_context (gdk_display_get_default ()); + app_info = g_app_info_create_from_commandline (BINDIR "/gnome-control-center color", + "gnome-control-center", + G_APP_INFO_CREATE_SUPPORTS_STARTUP_NOTIFICATION, + &error); + if (app_info == NULL) { + g_warning ("failed to create application info: %s", + error->message); + g_error_free (error); + goto out; + } + + /* launch gnome-control-center */ + ret = g_app_info_launch (app_info, + NULL, + G_APP_LAUNCH_CONTEXT (launch_context), + &error); + if (!ret) { + g_warning ("failed to launch gnome-control-center: %s", + error->message); + g_error_free (error); + goto out; + } +out: + g_object_unref (launch_context); + if (app_info != NULL) + g_object_unref (app_info); +} + +static void +gcm_session_notify_cb (NotifyNotification *notification, + gchar *action, + gpointer user_data) +{ + GsdColorCalibrate *calibrate = GSD_COLOR_CALIBRATE (user_data); + + if (g_strcmp0 (action, "recalibrate") == 0) { + notify_notification_close (notification, NULL); + gcm_session_exec_control_center (calibrate); + } +} + +static void +closed_cb (NotifyNotification *notification, gpointer data) +{ + g_object_unref (notification); +} + +static gboolean +gcm_session_notify_recalibrate (GsdColorCalibrate *calibrate, + const gchar *title, + const gchar *message, + CdDeviceKind kind) +{ + gboolean ret; + GError *error = NULL; + NotifyNotification *notification; + + /* show a bubble */ + notification = notify_notification_new (title, message, "preferences-color"); + notify_notification_set_timeout (notification, GCM_SESSION_NOTIFY_TIMEOUT); + notify_notification_set_urgency (notification, NOTIFY_URGENCY_LOW); + notify_notification_set_app_name (notification, _("Color")); + notify_notification_set_hint_string (notification, "desktop-entry", "gnome-color-panel"); + + notify_notification_add_action (notification, + "recalibrate", + /* TRANSLATORS: button: this is to open GCM */ + _("Recalibrate now"), + gcm_session_notify_cb, + calibrate, NULL); + + g_signal_connect (notification, "closed", G_CALLBACK (closed_cb), NULL); + ret = notify_notification_show (notification, &error); + if (!ret) { + g_warning ("failed to show notification: %s", + error->message); + g_error_free (error); + } + return ret; +} + +static gchar * +gcm_session_device_get_title (CdDevice *device) +{ + const gchar *vendor; + const gchar *model; + + model = cd_device_get_model (device); + vendor = cd_device_get_vendor (device); + if (model != NULL && vendor != NULL) + return g_strdup_printf ("%s - %s", vendor, model); + if (vendor != NULL) + return g_strdup (vendor); + if (model != NULL) + return g_strdup (model); + return g_strdup (cd_device_get_id (device)); +} + +static void +gcm_session_notify_device (GsdColorCalibrate *calibrate, CdDevice *device) +{ + CdDeviceKind kind; + const gchar *title; + gchar *device_title = NULL; + gchar *message; + guint threshold; + glong since; + + /* TRANSLATORS: this is when the device has not been recalibrated in a while */ + title = _("Recalibration required"); + device_title = gcm_session_device_get_title (device); + + /* check we care */ + kind = cd_device_get_kind (device); + if (kind == CD_DEVICE_KIND_DISPLAY) { + + /* get from GSettings */ + threshold = g_settings_get_uint (calibrate->settings, + GCM_SETTINGS_RECALIBRATE_DISPLAY_THRESHOLD); + + /* TRANSLATORS: this is when the display has not been recalibrated in a while */ + message = g_strdup_printf (_("The display “%s” should be recalibrated soon."), + device_title); + } else { + + /* get from GSettings */ + threshold = g_settings_get_uint (calibrate->settings, + GCM_SETTINGS_RECALIBRATE_PRINTER_THRESHOLD); + + /* TRANSLATORS: this is when the printer has not been recalibrated in a while */ + message = g_strdup_printf (_("The printer “%s” should be recalibrated soon."), + device_title); + } + + /* check if we need to notify */ + since = (g_get_real_time () - cd_device_get_modified (device)) / G_USEC_PER_SEC; + if (threshold > since) + gcm_session_notify_recalibrate (calibrate, title, message, kind); + g_free (device_title); + g_free (message); +} + +static void +gcm_session_profile_connect_cb (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + const gchar *filename; + gboolean ret; + gchar *basename = NULL; + const gchar *data_source; + GError *error = NULL; + CdProfile *profile = CD_PROFILE (object); + GcmSessionAsyncHelper *helper = (GcmSessionAsyncHelper *) user_data; + GsdColorCalibrate *calibrate = GSD_COLOR_CALIBRATE (helper->calibrate); + + ret = cd_profile_connect_finish (profile, + res, + &error); + if (!ret) { + g_warning ("failed to connect to profile: %s", + error->message); + g_error_free (error); + goto out; + } + + /* ensure it's a profile generated by us */ + data_source = cd_profile_get_metadata_item (profile, + CD_PROFILE_METADATA_DATA_SOURCE); + if (data_source == NULL) { + + /* existing profiles from gnome-color-calibrate < 3.1 + * won't have the extra metadata values added */ + filename = cd_profile_get_filename (profile); + if (filename == NULL) + goto out; + basename = g_path_get_basename (filename); + if (!g_str_has_prefix (basename, "GCM")) { + g_debug ("not a GCM profile for %s: %s", + cd_device_get_id (helper->device), filename); + goto out; + } + + /* ensure it's been created from a calibration, rather than from + * auto-EDID */ + } else if (g_strcmp0 (data_source, + CD_PROFILE_METADATA_DATA_SOURCE_CALIB) != 0) { + g_debug ("not a calib profile for %s", + cd_device_get_id (helper->device)); + goto out; + } + + /* handle device */ + gcm_session_notify_device (calibrate, helper->device); +out: + gcm_session_async_helper_free (helper); + g_free (basename); +} + +static void +gcm_session_device_connect_cb (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + gboolean ret; + GError *error = NULL; + CdDeviceKind kind; + CdProfile *profile = NULL; + CdDevice *device = CD_DEVICE (object); + GsdColorCalibrate *calibrate = GSD_COLOR_CALIBRATE (user_data); + GcmSessionAsyncHelper *helper; + + ret = cd_device_connect_finish (device, + res, + &error); + if (!ret) { + g_warning ("failed to connect to device: %s", + error->message); + g_error_free (error); + goto out; + } + + /* check we care */ + kind = cd_device_get_kind (device); + if (kind != CD_DEVICE_KIND_DISPLAY && + kind != CD_DEVICE_KIND_PRINTER) + goto out; + + /* ensure we have a profile */ + profile = cd_device_get_default_profile (device); + if (profile == NULL) { + g_debug ("no profile set for %s", cd_device_get_id (device)); + goto out; + } + + /* connect to the profile */ + helper = g_new0 (GcmSessionAsyncHelper, 1); + helper->calibrate = g_object_ref (calibrate); + helper->device = g_object_ref (device); + cd_profile_connect (profile, + NULL, + gcm_session_profile_connect_cb, + helper); +out: + if (profile != NULL) + g_object_unref (profile); +} + +static void +gcm_session_device_added_notify_cb (CdClient *client, + CdDevice *device, + GsdColorCalibrate *calibrate) +{ + /* connect to the device to get properties */ + cd_device_connect (device, + NULL, + gcm_session_device_connect_cb, + calibrate); +} + +static void +gcm_session_sensor_added_cb (CdClient *client, + CdSensor *sensor, + GsdColorCalibrate *calibrate) +{ + ca_context_play (ca_gtk_context_get (), 0, + CA_PROP_EVENT_ID, "device-added", + /* TRANSLATORS: this is the application name */ + CA_PROP_APPLICATION_NAME, _("GNOME Settings Daemon Color Plugin"), + /* TRANSLATORS: this is a sound description */ + CA_PROP_EVENT_DESCRIPTION, _("Color calibration device added"), NULL); + + /* open up the color prefs window */ + gcm_session_exec_control_center (calibrate); +} + +static void +gcm_session_sensor_removed_cb (CdClient *client, + CdSensor *sensor, + GsdColorCalibrate *calibrate) +{ + ca_context_play (ca_gtk_context_get (), 0, + CA_PROP_EVENT_ID, "device-removed", + /* TRANSLATORS: this is the application name */ + CA_PROP_APPLICATION_NAME, _("GNOME Settings Daemon Color Plugin"), + /* TRANSLATORS: this is a sound description */ + CA_PROP_EVENT_DESCRIPTION, _("Color calibration device removed"), NULL); +} + +static void +gsd_color_calibrate_class_init (GsdColorCalibrateClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gsd_color_calibrate_finalize; +} + +static void +gsd_color_calibrate_init (GsdColorCalibrate *calibrate) +{ + calibrate->settings = g_settings_new ("org.gnome.settings-daemon.plugins.color"); + calibrate->client = cd_client_new (); + g_signal_connect (calibrate->client, "device-added", + G_CALLBACK (gcm_session_device_added_notify_cb), + calibrate); + g_signal_connect (calibrate->client, "sensor-added", + G_CALLBACK (gcm_session_sensor_added_cb), + calibrate); + g_signal_connect (calibrate->client, "sensor-removed", + G_CALLBACK (gcm_session_sensor_removed_cb), + calibrate); +} + +static void +gsd_color_calibrate_finalize (GObject *object) +{ + GsdColorCalibrate *calibrate; + + g_return_if_fail (object != NULL); + g_return_if_fail (GSD_IS_COLOR_CALIBRATE (object)); + + calibrate = GSD_COLOR_CALIBRATE (object); + + g_clear_object (&calibrate->settings); + g_clear_object (&calibrate->client); + + G_OBJECT_CLASS (gsd_color_calibrate_parent_class)->finalize (object); +} + +GsdColorCalibrate * +gsd_color_calibrate_new (void) +{ + GsdColorCalibrate *calibrate; + calibrate = g_object_new (GSD_TYPE_COLOR_CALIBRATE, NULL); + return GSD_COLOR_CALIBRATE (calibrate); +} diff --git a/plugins/color/gsd-color-calibrate.h b/plugins/color/gsd-color-calibrate.h new file mode 100644 index 0000000..8980098 --- /dev/null +++ b/plugins/color/gsd-color-calibrate.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann + * Copyright (C) 2011-2013 Richard Hughes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 __GSD_COLOR_CALIBRATE_H +#define __GSD_COLOR_CALIBRATE_H + +#include + +G_BEGIN_DECLS + +#define GSD_TYPE_COLOR_CALIBRATE (gsd_color_calibrate_get_type ()) +G_DECLARE_FINAL_TYPE (GsdColorCalibrate, gsd_color_calibrate, GSD, COLOR_CALIBRATE, GObject) + +GType gsd_color_calibrate_get_type (void); +GQuark gsd_color_calibrate_error_quark (void); + +GsdColorCalibrate * gsd_color_calibrate_new (void); + +G_END_DECLS + +#endif /* __GSD_COLOR_CALIBRATE_H */ diff --git a/plugins/color/gsd-color-manager.c b/plugins/color/gsd-color-manager.c new file mode 100644 index 0000000..a9d8f74 --- /dev/null +++ b/plugins/color/gsd-color-manager.c @@ -0,0 +1,491 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann + * Copyright (C) 2011-2013 Richard Hughes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 "config.h" + +#include +#include +#include +#include + +#include "gnome-settings-profile.h" +#include "gsd-color-calibrate.h" +#include "gsd-color-manager.h" +#include "gsd-color-state.h" +#include "gsd-night-light.h" + +#define GSD_DBUS_NAME "org.gnome.SettingsDaemon" +#define GSD_DBUS_PATH "/org/gnome/SettingsDaemon" +#define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon" + +#define GSD_COLOR_DBUS_NAME GSD_DBUS_NAME ".Color" +#define GSD_COLOR_DBUS_PATH GSD_DBUS_PATH "/Color" +#define GSD_COLOR_DBUS_INTERFACE GSD_DBUS_BASE_INTERFACE ".Color" + +static const gchar introspection_xml[] = +"" +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +""; + +struct _GsdColorManager +{ + GObject parent; + + /* D-Bus */ + guint name_id; + GDBusNodeInfo *introspection_data; + GDBusConnection *connection; + GCancellable *bus_cancellable; + + GsdColorCalibrate *calibrate; + GsdColorState *state; + GsdNightLight *nlight; + + guint nlight_forced_timeout_id; +}; + +enum { + PROP_0, +}; + +static void gsd_color_manager_class_init (GsdColorManagerClass *klass); +static void gsd_color_manager_init (GsdColorManager *color_manager); +static void gsd_color_manager_finalize (GObject *object); + +G_DEFINE_TYPE (GsdColorManager, gsd_color_manager, G_TYPE_OBJECT) + +static gpointer manager_object = NULL; + +GQuark +gsd_color_manager_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("gsd_color_manager_error"); + return quark; +} + +gboolean +gsd_color_manager_start (GsdColorManager *manager, + GError **error) +{ + g_debug ("Starting color manager"); + gnome_settings_profile_start (NULL); + + /* start the device probing */ + gsd_color_state_start (manager->state); + + gnome_settings_profile_end (NULL); + return TRUE; +} + +void +gsd_color_manager_stop (GsdColorManager *manager) +{ + g_debug ("Stopping color manager"); + gsd_color_state_stop (manager->state); +} + +static void +gsd_color_manager_class_init (GsdColorManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gsd_color_manager_finalize; +} + +static void +emit_property_changed (GsdColorManager *manager, + const gchar *property_name, + GVariant *property_value) +{ + GVariantBuilder builder; + GVariantBuilder invalidated_builder; + + /* not yet connected */ + if (manager->connection == NULL) + return; + + /* build the dict */ + g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as")); + g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); + g_variant_builder_add (&builder, + "{sv}", + property_name, + property_value); + g_dbus_connection_emit_signal (manager->connection, + NULL, + GSD_COLOR_DBUS_PATH, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new ("(sa{sv}as)", + GSD_COLOR_DBUS_INTERFACE, + &builder, + &invalidated_builder), + NULL); + g_variant_builder_clear (&builder); + g_variant_builder_clear (&invalidated_builder); +} + +static void +on_active_notify (GsdNightLight *nlight, + GParamSpec *pspec, + gpointer user_data) +{ + GsdColorManager *manager = GSD_COLOR_MANAGER (user_data); + emit_property_changed (manager, "NightLightActive", + g_variant_new_boolean (gsd_night_light_get_active (manager->nlight))); +} + +static void +on_sunset_notify (GsdNightLight *nlight, + GParamSpec *pspec, + gpointer user_data) +{ + GsdColorManager *manager = GSD_COLOR_MANAGER (user_data); + emit_property_changed (manager, "Sunset", + g_variant_new_double (gsd_night_light_get_sunset (manager->nlight))); +} + +static void +on_sunrise_notify (GsdNightLight *nlight, + GParamSpec *pspec, + gpointer user_data) +{ + GsdColorManager *manager = GSD_COLOR_MANAGER (user_data); + emit_property_changed (manager, "Sunrise", + g_variant_new_double (gsd_night_light_get_sunrise (manager->nlight))); +} + +static void +on_disabled_until_tmw_notify (GsdNightLight *nlight, + GParamSpec *pspec, + gpointer user_data) +{ + GsdColorManager *manager = GSD_COLOR_MANAGER (user_data); + emit_property_changed (manager, "DisabledUntilTomorrow", + g_variant_new_boolean (gsd_night_light_get_disabled_until_tmw (manager->nlight))); +} + +static void +on_temperature_notify (GsdNightLight *nlight, + GParamSpec *pspec, + gpointer user_data) +{ + GsdColorManager *manager = GSD_COLOR_MANAGER (user_data); + gdouble temperature = gsd_night_light_get_temperature (manager->nlight); + gsd_color_state_set_temperature (manager->state, temperature); + emit_property_changed (manager, "Temperature", + g_variant_new_uint32 (roundf (temperature))); +} + +static void +gsd_color_manager_init (GsdColorManager *manager) +{ + /* setup calibration features */ + manager->calibrate = gsd_color_calibrate_new (); + manager->state = gsd_color_state_new (); + + /* night light features */ + manager->nlight = gsd_night_light_new (); + g_signal_connect (manager->nlight, "notify::active", + G_CALLBACK (on_active_notify), manager); + g_signal_connect (manager->nlight, "notify::sunset", + G_CALLBACK (on_sunset_notify), manager); + g_signal_connect (manager->nlight, "notify::sunrise", + G_CALLBACK (on_sunrise_notify), manager); + g_signal_connect (manager->nlight, "notify::temperature", + G_CALLBACK (on_temperature_notify), manager); + g_signal_connect (manager->nlight, "notify::disabled-until-tmw", + G_CALLBACK (on_disabled_until_tmw_notify), manager); +} + +static void +gsd_color_manager_finalize (GObject *object) +{ + GsdColorManager *manager; + + g_return_if_fail (object != NULL); + g_return_if_fail (GSD_IS_COLOR_MANAGER (object)); + + manager = GSD_COLOR_MANAGER (object); + + gsd_color_manager_stop (manager); + + if (manager->bus_cancellable != NULL) { + g_cancellable_cancel (manager->bus_cancellable); + g_clear_object (&manager->bus_cancellable); + } + + g_clear_pointer (&manager->introspection_data, g_dbus_node_info_unref); + g_clear_object (&manager->connection); + + if (manager->name_id != 0) { + g_bus_unown_name (manager->name_id); + manager->name_id = 0; + } + + if (manager->nlight_forced_timeout_id) + g_source_remove (manager->nlight_forced_timeout_id); + + g_clear_object (&manager->calibrate); + g_clear_object (&manager->state); + g_clear_object (&manager->nlight); + + G_OBJECT_CLASS (gsd_color_manager_parent_class)->finalize (object); +} + +static gboolean +nlight_forced_timeout_cb (gpointer user_data) +{ + GsdColorManager *manager = GSD_COLOR_MANAGER (user_data); + + manager->nlight_forced_timeout_id = 0; + gsd_night_light_set_forced (manager->nlight, FALSE); + + return G_SOURCE_REMOVE; +} + +static void +handle_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GsdColorManager *manager = GSD_COLOR_MANAGER (user_data); + + if (g_strcmp0 (method_name, "NightLightPreview") == 0) { + guint32 duration = 0; + + if (!manager->nlight) { + g_dbus_method_invocation_return_error_literal (invocation, + G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, + "Night-light is currently unavailable"); + + return; + } + + g_variant_get (parameters, "(u)", &duration); + + if (duration == 0 || duration > 120) { + g_dbus_method_invocation_return_error_literal (invocation, + G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "Duration is out of the range (0-120]."); + + return; + } + + if (manager->nlight_forced_timeout_id) + g_source_remove (manager->nlight_forced_timeout_id); + manager->nlight_forced_timeout_id = g_timeout_add_seconds (duration, nlight_forced_timeout_cb, manager); + + gsd_night_light_set_forced (manager->nlight, TRUE); + + g_dbus_method_invocation_return_value (invocation, NULL); + } else { + g_assert_not_reached (); + } +} + +static GVariant * +handle_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, gpointer user_data) +{ + GsdColorManager *manager = GSD_COLOR_MANAGER (user_data); + + if (g_strcmp0 (interface_name, GSD_COLOR_DBUS_INTERFACE) != 0) { + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, + "No such interface: %s", interface_name); + return NULL; + } + + if (g_strcmp0 (property_name, "NightLightActive") == 0) + return g_variant_new_boolean (gsd_night_light_get_active (manager->nlight)); + + if (g_strcmp0 (property_name, "Temperature") == 0) { + guint temperature; + temperature = gsd_color_state_get_temperature (manager->state); + return g_variant_new_uint32 (temperature); + } + + if (g_strcmp0 (property_name, "DisabledUntilTomorrow") == 0) + return g_variant_new_boolean (gsd_night_light_get_disabled_until_tmw (manager->nlight)); + + if (g_strcmp0 (property_name, "Sunrise") == 0) + return g_variant_new_double (gsd_night_light_get_sunrise (manager->nlight)); + + if (g_strcmp0 (property_name, "Sunset") == 0) + return g_variant_new_double (gsd_night_light_get_sunset (manager->nlight)); + + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, + "Failed to get property: %s", property_name); + return NULL; +} + +static gboolean +handle_set_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GVariant *value, + GError **error, gpointer user_data) +{ + GsdColorManager *manager = GSD_COLOR_MANAGER (user_data); + + if (g_strcmp0 (interface_name, GSD_COLOR_DBUS_INTERFACE) != 0) { + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, + "No such interface: %s", interface_name); + return FALSE; + } + + if (g_strcmp0 (property_name, "Temperature") == 0) { + guint32 temperature; + g_variant_get (value, "u", &temperature); + if (temperature < GSD_COLOR_TEMPERATURE_MIN) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "%" G_GUINT32_FORMAT "K is < min %" G_GUINT32_FORMAT "K", + temperature, GSD_COLOR_TEMPERATURE_MIN); + return FALSE; + } + if (temperature > GSD_COLOR_TEMPERATURE_MAX) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "%" G_GUINT32_FORMAT "K is > max %" G_GUINT32_FORMAT "K", + temperature, GSD_COLOR_TEMPERATURE_MAX); + return FALSE; + } + gsd_color_state_set_temperature (manager->state, temperature); + return TRUE; + } + + if (g_strcmp0 (property_name, "DisabledUntilTomorrow") == 0) { + gsd_night_light_set_disabled_until_tmw (manager->nlight, + g_variant_get_boolean (value)); + return TRUE; + } + + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, + "No such property: %s", property_name); + return FALSE; +} + +static const GDBusInterfaceVTable interface_vtable = +{ + handle_method_call, + handle_get_property, + handle_set_property +}; + +static void +name_lost_handler_cb (GDBusConnection *connection, const gchar *name, gpointer user_data) +{ + g_debug ("lost name, so exiting"); + gtk_main_quit (); +} + +static void +on_bus_gotten (GObject *source_object, + GAsyncResult *res, + GsdColorManager *manager) +{ + GDBusConnection *connection; + GError *error = NULL; + + connection = g_bus_get_finish (res, &error); + if (connection == NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Could not get session bus: %s", error->message); + g_error_free (error); + return; + } + + manager->connection = connection; + + g_dbus_connection_register_object (connection, + GSD_COLOR_DBUS_PATH, + manager->introspection_data->interfaces[0], + &interface_vtable, + manager, + NULL, + NULL); + + manager->name_id = g_bus_own_name_on_connection (connection, + GSD_COLOR_DBUS_NAME, + G_BUS_NAME_OWNER_FLAGS_NONE, + NULL, + name_lost_handler_cb, + manager, + NULL); + + /* setup night light module */ + if (!gsd_night_light_start (manager->nlight, &error)) { + g_warning ("Could not start night light module: %s", error->message); + g_error_free (error); + } +} + +static void +register_manager_dbus (GsdColorManager *manager) +{ + manager->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); + g_assert (manager->introspection_data != NULL); + manager->bus_cancellable = g_cancellable_new (); + + g_bus_get (G_BUS_TYPE_SESSION, + manager->bus_cancellable, + (GAsyncReadyCallback) on_bus_gotten, + manager); +} + +GsdColorManager * +gsd_color_manager_new (void) +{ + if (manager_object != NULL) { + g_object_ref (manager_object); + } else { + manager_object = g_object_new (GSD_TYPE_COLOR_MANAGER, NULL); + g_object_add_weak_pointer (manager_object, + (gpointer *) &manager_object); + register_manager_dbus (manager_object); + } + + return GSD_COLOR_MANAGER (manager_object); +} diff --git a/plugins/color/gsd-color-manager.h b/plugins/color/gsd-color-manager.h new file mode 100644 index 0000000..ba71fca --- /dev/null +++ b/plugins/color/gsd-color-manager.h @@ -0,0 +1,46 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann + * Copyright (C) 2011 Richard Hughes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 __GSD_COLOR_MANAGER_H +#define __GSD_COLOR_MANAGER_H + +#include + +G_BEGIN_DECLS + +#define GSD_TYPE_COLOR_MANAGER (gsd_color_manager_get_type ()) +#define GSD_COLOR_MANAGER_ERROR (gsd_color_manager_error_quark ()) +G_DECLARE_FINAL_TYPE (GsdColorManager, gsd_color_manager, GSD, COLOR_MANAGER, GObject) + +enum +{ + GSD_COLOR_MANAGER_ERROR_FAILED +}; + +GQuark gsd_color_manager_error_quark (void); + +GsdColorManager * gsd_color_manager_new (void); +gboolean gsd_color_manager_start (GsdColorManager *manager, + GError **error); +void gsd_color_manager_stop (GsdColorManager *manager); + +G_END_DECLS + +#endif /* __GSD_COLOR_MANAGER_H */ diff --git a/plugins/color/gsd-color-state.c b/plugins/color/gsd-color-state.c new file mode 100644 index 0000000..eb48eb7 --- /dev/null +++ b/plugins/color/gsd-color-state.c @@ -0,0 +1,82 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann + * Copyright (C) 2011-2013 Richard Hughes + * Copyright (C) 2020 NVIDIA CORPORATION + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 "config.h" + +#include "gsd-color-manager.h" +#include "gsd-color-state.h" + +struct _GsdColorState +{ + GObject parent; + + guint color_temperature; +}; + +static void gsd_color_state_class_init (GsdColorStateClass *klass); +static void gsd_color_state_init (GsdColorState *color_state); + +G_DEFINE_TYPE (GsdColorState, gsd_color_state, G_TYPE_OBJECT) + +void +gsd_color_state_set_temperature (GsdColorState *state, guint temperature) +{ + g_return_if_fail (GSD_IS_COLOR_STATE (state)); + + state->color_temperature = temperature; +} + +guint +gsd_color_state_get_temperature (GsdColorState *state) +{ + g_return_val_if_fail (GSD_IS_COLOR_STATE (state), 0); + return state->color_temperature; +} + +void +gsd_color_state_start (GsdColorState *state) +{ +} + +void +gsd_color_state_stop (GsdColorState *state) +{ +} + +static void +gsd_color_state_class_init (GsdColorStateClass *klass) +{ +} + +static void +gsd_color_state_init (GsdColorState *state) +{ + /* default color temperature */ + state->color_temperature = GSD_COLOR_TEMPERATURE_DEFAULT; +} + +GsdColorState * +gsd_color_state_new (void) +{ + GsdColorState *state; + state = g_object_new (GSD_TYPE_COLOR_STATE, NULL); + return GSD_COLOR_STATE (state); +} diff --git a/plugins/color/gsd-color-state.h b/plugins/color/gsd-color-state.h new file mode 100644 index 0000000..c296e20 --- /dev/null +++ b/plugins/color/gsd-color-state.h @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann + * Copyright (C) 2011-2013 Richard Hughes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 __GSD_COLOR_STATE_H +#define __GSD_COLOR_STATE_H + +#include + +G_BEGIN_DECLS + +#define GSD_TYPE_COLOR_STATE (gsd_color_state_get_type ()) + +G_DECLARE_FINAL_TYPE (GsdColorState, gsd_color_state, GSD, COLOR_STATE, GObject) + +#define GSD_COLOR_TEMPERATURE_MIN 1000 /* Kelvin */ +#define GSD_COLOR_TEMPERATURE_DEFAULT 6500 /* Kelvin, is RGB [1.0,1.0,1.0] */ +#define GSD_COLOR_TEMPERATURE_MAX 10000 /* Kelvin */ + +GsdColorState * gsd_color_state_new (void); +void gsd_color_state_start (GsdColorState *state); +void gsd_color_state_stop (GsdColorState *state); +void gsd_color_state_set_temperature (GsdColorState *state, + guint temperature); +guint gsd_color_state_get_temperature (GsdColorState *state); + +G_END_DECLS + +#endif /* __GSD_COLOR_STATE_H */ diff --git a/plugins/color/gsd-night-light-common.c b/plugins/color/gsd-night-light-common.c new file mode 100644 index 0000000..5fe756e --- /dev/null +++ b/plugins/color/gsd-night-light-common.c @@ -0,0 +1,137 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2017 Richard Hughes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 "config.h" + +#include +#include + +#include "gsd-night-light-common.h" + +static gdouble +deg2rad (gdouble degrees) +{ + return (M_PI * degrees) / 180.f; +} + +static gdouble +rad2deg (gdouble radians) +{ + return radians * (180.f / M_PI); +} + +/* + * Formulas taken from https://www.esrl.noaa.gov/gmd/grad/solcalc/calcdetails.html + * + * The returned values are fractional hours, so 6am would be 6.0 and 4:30pm + * would be 16.5. + * + * The values returned by this function might not make sense for locations near + * the polar regions. For example, in the north of Lapland there might not be + * a sunrise at all. + */ +gboolean +gsd_night_light_get_sunrise_sunset (GDateTime *dt, + gdouble pos_lat, gdouble pos_long, + gdouble *sunrise, gdouble *sunset) +{ + g_autoptr(GDateTime) dt_zero = g_date_time_new_utc (1900, 1, 1, 0, 0, 0); + GTimeSpan ts = g_date_time_difference (dt, dt_zero); + + g_return_val_if_fail (pos_lat <= 90.f && pos_lat >= -90.f, FALSE); + g_return_val_if_fail (pos_long <= 180.f && pos_long >= -180.f, FALSE); + + gdouble tz_offset = (gdouble) g_date_time_get_utc_offset (dt) / G_USEC_PER_SEC / 60 / 60; // B5 + gdouble date_as_number = ts / G_USEC_PER_SEC / 24 / 60 / 60 + 2; // B7 + gdouble time_past_local_midnight = 0; // E2, unused in this calculation + gdouble julian_day = date_as_number + 2415018.5 + + time_past_local_midnight - tz_offset / 24; + gdouble julian_century = (julian_day - 2451545) / 36525; + gdouble geom_mean_long_sun = fmod (280.46646 + julian_century * + (36000.76983 + julian_century * 0.0003032), 360); // I2 + gdouble geom_mean_anom_sun = 357.52911 + julian_century * + (35999.05029 - 0.0001537 * julian_century); // J2 + gdouble eccent_earth_orbit = 0.016708634 - julian_century * + (0.000042037 + 0.0000001267 * julian_century); // K2 + gdouble sun_eq_of_ctr = sin (deg2rad (geom_mean_anom_sun)) * + (1.914602 - julian_century * (0.004817 + 0.000014 * julian_century)) + + sin (deg2rad (2 * geom_mean_anom_sun)) * (0.019993 - 0.000101 * julian_century) + + sin (deg2rad (3 * geom_mean_anom_sun)) * 0.000289; // L2 + gdouble sun_true_long = geom_mean_long_sun + sun_eq_of_ctr; // M2 + gdouble sun_app_long = sun_true_long - 0.00569 - 0.00478 * + sin (deg2rad (125.04 - 1934.136 * julian_century)); // P2 + gdouble mean_obliq_ecliptic = 23 + (26 + ((21.448 - julian_century * + (46.815 + julian_century * (0.00059 - julian_century * 0.001813)))) / 60) / 60; // Q2 + gdouble obliq_corr = mean_obliq_ecliptic + 0.00256 * + cos (deg2rad (125.04 - 1934.136 * julian_century)); // R2 + gdouble sun_declin = rad2deg (asin (sin (deg2rad (obliq_corr)) * + sin (deg2rad (sun_app_long)))); // T2 + gdouble var_y = tan (deg2rad (obliq_corr/2)) * tan (deg2rad (obliq_corr / 2)); // U2 + gdouble eq_of_time = 4 * rad2deg (var_y * sin (2 * deg2rad (geom_mean_long_sun)) - + 2 * eccent_earth_orbit * sin (deg2rad (geom_mean_anom_sun)) + + 4 * eccent_earth_orbit * var_y * + sin (deg2rad (geom_mean_anom_sun)) * + cos (2 * deg2rad (geom_mean_long_sun)) - + 0.5 * var_y * var_y * sin (4 * deg2rad (geom_mean_long_sun)) - + 1.25 * eccent_earth_orbit * eccent_earth_orbit * + sin (2 * deg2rad (geom_mean_anom_sun))); // V2 + gdouble ha_sunrise = rad2deg (acos (cos (deg2rad (90.833)) / (cos (deg2rad (pos_lat)) * + cos (deg2rad (sun_declin))) - tan (deg2rad (pos_lat)) * + tan (deg2rad (sun_declin)))); // W2 + gdouble solar_noon = (720 - 4 * pos_long - eq_of_time + tz_offset * 60) / 1440; // X2 + gdouble sunrise_time = solar_noon - ha_sunrise * 4 / 1440; // Y2 + gdouble sunset_time = solar_noon + ha_sunrise * 4 / 1440; // Z2 + + /* convert to hours */ + if (sunrise != NULL) + *sunrise = sunrise_time * 24; + if (sunset != NULL) + *sunset = sunset_time * 24; + return TRUE; +} + +gdouble +gsd_night_light_frac_day_from_dt (GDateTime *dt) +{ + return g_date_time_get_hour (dt) + + (gdouble) g_date_time_get_minute (dt) / 60.f + + (gdouble) g_date_time_get_second (dt) / 3600.f; +} + +gboolean +gsd_night_light_frac_day_is_between (gdouble value, + gdouble start, + gdouble end) +{ + /* wrap end to the next day if it is before start, + * considering equal values as a full 24h period + */ + if (end <= start) + end += 24; + + /* wrap value to the next day if it is before the range */ + if (value < start && value < end) + value += 24; + + /* Check whether value falls into range; together with the 24h + * wrap around above this means that TRUE is always returned when + * start == end. + */ + return value >= start && value < end; +} diff --git a/plugins/color/gsd-night-light-common.h b/plugins/color/gsd-night-light-common.h new file mode 100644 index 0000000..4995da5 --- /dev/null +++ b/plugins/color/gsd-night-light-common.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2017 Richard Hughes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 __GSD_NIGHT_LIGHT_COMMON_H +#define __GSD_NIGHT_LIGHT_COMMON_H + +#include + +G_BEGIN_DECLS + +gboolean gsd_night_light_get_sunrise_sunset (GDateTime *dt, + gdouble pos_lat, + gdouble pos_long, + gdouble *sunrise, + gdouble *sunset); +gdouble gsd_night_light_frac_day_from_dt (GDateTime *dt); +gboolean gsd_night_light_frac_day_is_between (gdouble value, + gdouble start, + gdouble end); + +G_END_DECLS + +#endif /* __GSD_NIGHT_LIGHT_COMMON_H */ diff --git a/plugins/color/gsd-night-light.c b/plugins/color/gsd-night-light.c new file mode 100644 index 0000000..b11f075 --- /dev/null +++ b/plugins/color/gsd-night-light.c @@ -0,0 +1,807 @@ +/* + * Copyright (C) 2017 Richard Hughes + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include "gnome-datetime-source.h" + +#include "gsd-color-state.h" + +#include "gsd-night-light.h" +#include "gsd-night-light-common.h" + +struct _GsdNightLight { + GObject parent; + GSettings *settings; + gboolean forced; + gboolean disabled_until_tmw; + GDateTime *disabled_until_tmw_dt; + gboolean geoclue_enabled; + GSource *source; + guint validate_id; + GClueClient *geoclue_client; + GClueSimple *geoclue_simple; + GSettings *location_settings; + gdouble cached_sunrise; + gdouble cached_sunset; + gdouble cached_temperature; + gboolean cached_active; + gboolean smooth_enabled; + GTimer *smooth_timer; + guint smooth_id; + gdouble smooth_target_temperature; + GCancellable *cancellable; + GDateTime *datetime_override; +}; + +enum { + PROP_0, + PROP_ACTIVE, + PROP_SUNRISE, + PROP_SUNSET, + PROP_TEMPERATURE, + PROP_DISABLED_UNTIL_TMW, + PROP_FORCED, + PROP_LAST +}; + +#define GSD_NIGHT_LIGHT_SCHEDULE_TIMEOUT 5 /* seconds */ +#define GSD_NIGHT_LIGHT_POLL_TIMEOUT 60 /* seconds */ +#define GSD_NIGHT_LIGHT_POLL_SMEAR 1 /* hours */ +#define GSD_NIGHT_LIGHT_SMOOTH_SMEAR 5.f /* seconds */ + +#define GSD_FRAC_DAY_MAX_DELTA (1.f/60.f) /* 1 minute */ +#define GSD_TEMPERATURE_MAX_DELTA (10.f) /* Kelvin */ + +#define DESKTOP_ID "gnome-color-panel" + +static void poll_timeout_destroy (GsdNightLight *self); +static void poll_timeout_create (GsdNightLight *self); +static void night_light_recheck (GsdNightLight *self); + +G_DEFINE_TYPE (GsdNightLight, gsd_night_light, G_TYPE_OBJECT); + +static GDateTime * +gsd_night_light_get_date_time_now (GsdNightLight *self) +{ + if (self->datetime_override != NULL) + return g_date_time_ref (self->datetime_override); + return g_date_time_new_now_local (); +} + +void +gsd_night_light_set_date_time_now (GsdNightLight *self, GDateTime *datetime) +{ + if (self->datetime_override != NULL) + g_date_time_unref (self->datetime_override); + self->datetime_override = g_date_time_ref (datetime); + + night_light_recheck (self); +} + +static void +poll_smooth_destroy (GsdNightLight *self) +{ + if (self->smooth_id != 0) { + g_source_remove (self->smooth_id); + self->smooth_id = 0; + } + if (self->smooth_timer != NULL) + g_clear_pointer (&self->smooth_timer, g_timer_destroy); +} + +void +gsd_night_light_set_smooth_enabled (GsdNightLight *self, + gboolean smooth_enabled) +{ + /* ensure the timeout is stopped if called at runtime */ + if (!smooth_enabled) + poll_smooth_destroy (self); + self->smooth_enabled = smooth_enabled; +} + +static gdouble +linear_interpolate (gdouble val1, gdouble val2, gdouble factor) +{ + g_return_val_if_fail (factor >= 0.f, -1.f); + g_return_val_if_fail (factor <= 1.f, -1.f); + return ((val1 - val2) * factor) + val2; +} + +static gboolean +update_cached_sunrise_sunset (GsdNightLight *self) +{ + gboolean ret = FALSE; + gdouble latitude; + gdouble longitude; + gdouble sunrise; + gdouble sunset; + g_autoptr(GVariant) tmp = NULL; + g_autoptr(GDateTime) dt_now = gsd_night_light_get_date_time_now (self); + + /* calculate the sunrise/sunset for the location */ + tmp = g_settings_get_value (self->settings, "night-light-last-coordinates"); + g_variant_get (tmp, "(dd)", &latitude, &longitude); + if (latitude > 90.f || latitude < -90.f) + return FALSE; + if (longitude > 180.f || longitude < -180.f) + return FALSE; + if (!gsd_night_light_get_sunrise_sunset (dt_now, latitude, longitude, + &sunrise, &sunset)) { + g_warning ("failed to get sunset/sunrise for %.3f,%.3f", + longitude, longitude); + return FALSE; + } + + /* anything changed */ + if (ABS (self->cached_sunrise - sunrise) > GSD_FRAC_DAY_MAX_DELTA) { + self->cached_sunrise = sunrise; + g_object_notify (G_OBJECT (self), "sunrise"); + ret = TRUE; + } + if (ABS (self->cached_sunset - sunset) > GSD_FRAC_DAY_MAX_DELTA) { + self->cached_sunset = sunset; + g_object_notify (G_OBJECT (self), "sunset"); + ret = TRUE; + } + return ret; +} + +static void +gsd_night_light_set_temperature_internal (GsdNightLight *self, gdouble temperature, gboolean force) +{ + if (!force && ABS (self->cached_temperature - temperature) <= GSD_TEMPERATURE_MAX_DELTA) + return; + if (self->cached_temperature == temperature) + return; + self->cached_temperature = temperature; + g_object_notify (G_OBJECT (self), "temperature"); +} + +static gboolean +gsd_night_light_smooth_cb (gpointer user_data) +{ + GsdNightLight *self = GSD_NIGHT_LIGHT (user_data); + gdouble tmp; + gdouble frac; + + /* find fraction */ + frac = g_timer_elapsed (self->smooth_timer, NULL) / GSD_NIGHT_LIGHT_SMOOTH_SMEAR; + if (frac >= 1.f) { + gsd_night_light_set_temperature_internal (self, + self->smooth_target_temperature, + TRUE); + self->smooth_id = 0; + return G_SOURCE_REMOVE; + } + + /* set new temperature step using log curve */ + tmp = self->smooth_target_temperature - self->cached_temperature; + tmp *= frac; + tmp += self->cached_temperature; + gsd_night_light_set_temperature_internal (self, tmp, FALSE); + + return G_SOURCE_CONTINUE; +} + +static void +poll_smooth_create (GsdNightLight *self, gdouble temperature) +{ + g_assert (self->smooth_id == 0); + self->smooth_target_temperature = temperature; + self->smooth_timer = g_timer_new (); + self->smooth_id = g_timeout_add (50, gsd_night_light_smooth_cb, self); +} + +static void +gsd_night_light_set_temperature (GsdNightLight *self, gdouble temperature) +{ + /* immediate */ + if (!self->smooth_enabled) { + gsd_night_light_set_temperature_internal (self, temperature, TRUE); + return; + } + + /* Destroy any smooth transition, it will be recreated if neccessary */ + poll_smooth_destroy (self); + + /* small jump */ + if (ABS (temperature - self->cached_temperature) < GSD_TEMPERATURE_MAX_DELTA) { + gsd_night_light_set_temperature_internal (self, temperature, TRUE); + return; + } + + /* smooth out the transition */ + poll_smooth_create (self, temperature); +} + +static void +gsd_night_light_set_active (GsdNightLight *self, gboolean active) +{ + if (self->cached_active == active) + return; + self->cached_active = active; + + /* ensure set to unity temperature */ + if (!active) + gsd_night_light_set_temperature (self, GSD_COLOR_TEMPERATURE_DEFAULT); + + g_object_notify (G_OBJECT (self), "active"); +} + +static void +night_light_recheck (GsdNightLight *self) +{ + gdouble frac_day; + gdouble schedule_from = -1.f; + gdouble schedule_to = -1.f; + gdouble smear = GSD_NIGHT_LIGHT_POLL_SMEAR; /* hours */ + guint temperature; + guint temp_smeared; + g_autoptr(GDateTime) dt_now = gsd_night_light_get_date_time_now (self); + + /* Forced mode, just set the temperature to night light. + * Proper rechecking will happen once forced mode is disabled again */ + if (self->forced) { + temperature = g_settings_get_uint (self->settings, "night-light-temperature"); + gsd_night_light_set_temperature (self, temperature); + return; + } + + /* enabled */ + if (!g_settings_get_boolean (self->settings, "night-light-enabled")) { + g_debug ("night light disabled, resetting"); + gsd_night_light_set_active (self, FALSE); + return; + } + + /* calculate the position of the sun */ + if (g_settings_get_boolean (self->settings, "night-light-schedule-automatic")) { + update_cached_sunrise_sunset (self); + if (self->cached_sunrise > 0.f && self->cached_sunset > 0.f) { + schedule_to = self->cached_sunrise; + schedule_from = self->cached_sunset; + } + } + + /* fall back to manual settings */ + if (schedule_to <= 0.f || schedule_from <= 0.f) { + schedule_from = g_settings_get_double (self->settings, + "night-light-schedule-from"); + schedule_to = g_settings_get_double (self->settings, + "night-light-schedule-to"); + } + + /* get the current hour of a day as a fraction */ + frac_day = gsd_night_light_frac_day_from_dt (dt_now); + g_debug ("fractional day = %.3f, limits = %.3f->%.3f", + frac_day, schedule_from, schedule_to); + + /* disabled until tomorrow */ + if (self->disabled_until_tmw) { + GTimeSpan time_span; + gboolean reset = FALSE; + + time_span = g_date_time_difference (dt_now, self->disabled_until_tmw_dt); + + /* Reset if disabled until tomorrow is more than 24h ago. */ + if (time_span > (GTimeSpan) 24 * 60 * 60 * 1000000) { + g_debug ("night light disabled until tomorrow is older than 24h, resetting disabled until tomorrow"); + reset = TRUE; + } else if (time_span > 0) { + /* Or if a sunrise lies between the time it was disabled and now. */ + gdouble frac_disabled; + frac_disabled = gsd_night_light_frac_day_from_dt (self->disabled_until_tmw_dt); + if (frac_disabled != frac_day && + gsd_night_light_frac_day_is_between (schedule_to, + frac_disabled, + frac_day)) { + g_debug ("night light sun rise happened, resetting disabled until tomorrow"); + reset = TRUE; + } + } + + if (reset) { + self->disabled_until_tmw = FALSE; + g_clear_pointer(&self->disabled_until_tmw_dt, g_date_time_unref); + g_object_notify (G_OBJECT (self), "disabled-until-tmw"); + } + } + + /* lower smearing period to be smaller than the time between start/stop */ + smear = MIN (smear, + MIN ( ABS (schedule_to - schedule_from), + 24 - ABS (schedule_to - schedule_from))); + + if (!gsd_night_light_frac_day_is_between (frac_day, + schedule_from - smear, + schedule_to)) { + g_debug ("not time for night-light"); + gsd_night_light_set_active (self, FALSE); + return; + } + + gsd_night_light_set_active (self, TRUE); + + if (self->disabled_until_tmw) { + g_debug ("night light still day-disabled"); + gsd_night_light_set_temperature (self, GSD_COLOR_TEMPERATURE_DEFAULT); + return; + } + + /* smear the temperature for a short duration before the set limits + * + * |----------------------| = from->to + * |-| = smear down + * |-| = smear up + * + * \ / + * \ / + * \--------------------/ + */ + temperature = g_settings_get_uint (self->settings, "night-light-temperature"); + if (smear < 0.01) { + /* Don't try to smear for extremely short or zero periods */ + temp_smeared = temperature; + } else if (gsd_night_light_frac_day_is_between (frac_day, + schedule_from - smear, + schedule_from)) { + gdouble factor = 1.f - ((frac_day - (schedule_from - smear)) / smear); + temp_smeared = linear_interpolate (GSD_COLOR_TEMPERATURE_DEFAULT, + temperature, factor); + } else if (gsd_night_light_frac_day_is_between (frac_day, + schedule_to - smear, + schedule_to)) { + gdouble factor = (frac_day - (schedule_to - smear)) / smear; + temp_smeared = linear_interpolate (GSD_COLOR_TEMPERATURE_DEFAULT, + temperature, factor); + } else { + temp_smeared = temperature; + } + + g_debug ("night light mode on, using temperature of %uK (aiming for %uK)", + temp_smeared, temperature); + gsd_night_light_set_temperature (self, temp_smeared); +} + +static gboolean +night_light_recheck_schedule_cb (gpointer user_data) +{ + GsdNightLight *self = GSD_NIGHT_LIGHT (user_data); + night_light_recheck (self); + self->validate_id = 0; + return G_SOURCE_REMOVE; +} + +/* called when something changed */ +static void +night_light_recheck_schedule (GsdNightLight *self) +{ + if (self->validate_id != 0) + g_source_remove (self->validate_id); + self->validate_id = + g_timeout_add_seconds (GSD_NIGHT_LIGHT_SCHEDULE_TIMEOUT, + night_light_recheck_schedule_cb, + self); +} + +/* called when the time may have changed */ +static gboolean +night_light_recheck_cb (gpointer user_data) +{ + GsdNightLight *self = GSD_NIGHT_LIGHT (user_data); + + /* recheck parameters, then reschedule a new timeout */ + night_light_recheck (self); + poll_timeout_destroy (self); + poll_timeout_create (self); + + /* return value ignored for a one-time watch */ + return G_SOURCE_REMOVE; +} + +static void +poll_timeout_create (GsdNightLight *self) +{ + g_autoptr(GDateTime) dt_now = NULL; + g_autoptr(GDateTime) dt_expiry = NULL; + + if (self->source != NULL) + return; + + /* It is not a good idea to make this overridable, it just creates + * an infinite loop as a fixed date for testing just doesn't work. */ + dt_now = g_date_time_new_now_local (); + dt_expiry = g_date_time_add_seconds (dt_now, GSD_NIGHT_LIGHT_POLL_TIMEOUT); + self->source = _gnome_datetime_source_new (dt_now, + dt_expiry, + TRUE); + g_source_set_callback (self->source, + night_light_recheck_cb, + self, NULL); + g_source_attach (self->source, NULL); +} + +static void +poll_timeout_destroy (GsdNightLight *self) +{ + + if (self->source == NULL) + return; + + g_source_destroy (self->source); + g_source_unref (self->source); + self->source = NULL; +} + +static void +settings_changed_cb (GSettings *settings, gchar *key, gpointer user_data) +{ + GsdNightLight *self = GSD_NIGHT_LIGHT (user_data); + g_debug ("settings changed"); + night_light_recheck (self); +} + +static void +on_location_notify (GClueSimple *simple, + GParamSpec *pspec, + gpointer user_data) +{ + GsdNightLight *self = GSD_NIGHT_LIGHT (user_data); + GClueLocation *location; + gdouble latitude, longitude; + + location = gclue_simple_get_location (simple); + latitude = gclue_location_get_latitude (location); + longitude = gclue_location_get_longitude (location); + + g_settings_set_value (self->settings, + "night-light-last-coordinates", + g_variant_new ("(dd)", latitude, longitude)); + + g_debug ("got geoclue latitude %f, longitude %f", latitude, longitude); + + /* recheck the levels if the location changed significantly */ + if (update_cached_sunrise_sunset (self)) + night_light_recheck_schedule (self); +} + +static void +on_geoclue_simple_ready (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GsdNightLight *self = GSD_NIGHT_LIGHT (user_data); + GClueSimple *geoclue_simple; + g_autoptr(GError) error = NULL; + + geoclue_simple = gclue_simple_new_finish (res, &error); + if (geoclue_simple == NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to connect to GeoClue2 service: %s", error->message); + return; + } + + self->geoclue_simple = geoclue_simple; + self->geoclue_client = gclue_simple_get_client (self->geoclue_simple); + g_object_set (G_OBJECT (self->geoclue_client), + "time-threshold", 60*60, NULL); /* 1 hour */ + + g_signal_connect (self->geoclue_simple, "notify::location", + G_CALLBACK (on_location_notify), user_data); + + on_location_notify (self->geoclue_simple, NULL, user_data); +} + +static void +start_geoclue (GsdNightLight *self) +{ + self->cancellable = g_cancellable_new (); + gclue_simple_new (DESKTOP_ID, + GCLUE_ACCURACY_LEVEL_CITY, + self->cancellable, + on_geoclue_simple_ready, + self); + +} + +static void +stop_geoclue (GsdNightLight *self) +{ + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + + if (self->geoclue_client != NULL) { + gclue_client_call_stop (self->geoclue_client, NULL, NULL, NULL); + self->geoclue_client = NULL; + } + g_clear_object (&self->geoclue_simple); +} + +static void +check_location_settings (GsdNightLight *self) +{ + if (g_settings_get_boolean (self->location_settings, "enabled") && self->geoclue_enabled) + start_geoclue (self); + else + stop_geoclue (self); +} + +void +gsd_night_light_set_disabled_until_tmw (GsdNightLight *self, gboolean value) +{ + g_autoptr(GDateTime) dt = gsd_night_light_get_date_time_now (self); + + if (self->disabled_until_tmw == value) + return; + + self->disabled_until_tmw = value; + g_clear_pointer (&self->disabled_until_tmw_dt, g_date_time_unref); + if (self->disabled_until_tmw) + self->disabled_until_tmw_dt = g_steal_pointer (&dt); + night_light_recheck (self); + g_object_notify (G_OBJECT (self), "disabled-until-tmw"); +} + +gboolean +gsd_night_light_get_disabled_until_tmw (GsdNightLight *self) +{ + return self->disabled_until_tmw; +} + +void +gsd_night_light_set_forced (GsdNightLight *self, gboolean value) +{ + if (self->forced == value) + return; + + self->forced = value; + g_object_notify (G_OBJECT (self), "forced"); + + /* A simple recheck might not reset the temperature if + * night light is currently disabled. */ + if (!self->forced && !self->cached_active) + gsd_night_light_set_temperature (self, GSD_COLOR_TEMPERATURE_DEFAULT); + + night_light_recheck (self); +} + +gboolean +gsd_night_light_get_forced (GsdNightLight *self) +{ + return self->forced; +} + +gboolean +gsd_night_light_get_active (GsdNightLight *self) +{ + return self->cached_active; +} + +gdouble +gsd_night_light_get_sunrise (GsdNightLight *self) +{ + return self->cached_sunrise; +} + +gdouble +gsd_night_light_get_sunset (GsdNightLight *self) +{ + return self->cached_sunset; +} + +gdouble +gsd_night_light_get_temperature (GsdNightLight *self) +{ + return self->cached_temperature; +} + +void +gsd_night_light_set_geoclue_enabled (GsdNightLight *self, gboolean enabled) +{ + self->geoclue_enabled = enabled; +} + +gboolean +gsd_night_light_start (GsdNightLight *self, GError **error) +{ + night_light_recheck (self); + poll_timeout_create (self); + + /* care about changes */ + g_signal_connect (self->settings, "changed", + G_CALLBACK (settings_changed_cb), self); + + g_signal_connect_swapped (self->location_settings, "changed::enabled", + G_CALLBACK (check_location_settings), self); + check_location_settings (self); + + return TRUE; +} + +static void +gsd_night_light_finalize (GObject *object) +{ + GsdNightLight *self = GSD_NIGHT_LIGHT (object); + + stop_geoclue (self); + + poll_timeout_destroy (self); + poll_smooth_destroy (self); + + g_clear_object (&self->settings); + g_clear_pointer (&self->datetime_override, g_date_time_unref); + g_clear_pointer (&self->disabled_until_tmw_dt, g_date_time_unref); + + if (self->validate_id > 0) { + g_source_remove (self->validate_id); + self->validate_id = 0; + } + + g_clear_object (&self->location_settings); + G_OBJECT_CLASS (gsd_night_light_parent_class)->finalize (object); +} + +static void +gsd_night_light_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsdNightLight *self = GSD_NIGHT_LIGHT (object); + + switch (prop_id) { + case PROP_SUNRISE: + self->cached_sunrise = g_value_get_double (value); + break; + case PROP_SUNSET: + self->cached_sunset = g_value_get_double (value); + break; + case PROP_TEMPERATURE: + self->cached_temperature = g_value_get_double (value); + break; + case PROP_DISABLED_UNTIL_TMW: + gsd_night_light_set_disabled_until_tmw (self, g_value_get_boolean (value)); + break; + case PROP_FORCED: + gsd_night_light_set_forced (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gsd_night_light_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsdNightLight *self = GSD_NIGHT_LIGHT (object); + + switch (prop_id) { + case PROP_ACTIVE: + g_value_set_boolean (value, self->cached_active); + break; + case PROP_SUNRISE: + g_value_set_double (value, self->cached_sunrise); + break; + case PROP_SUNSET: + g_value_set_double (value, self->cached_sunset); + break; + case PROP_TEMPERATURE: + g_value_set_double (value, self->cached_temperature); + break; + case PROP_DISABLED_UNTIL_TMW: + g_value_set_boolean (value, gsd_night_light_get_disabled_until_tmw (self)); + break; + case PROP_FORCED: + g_value_set_boolean (value, gsd_night_light_get_forced (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gsd_night_light_class_init (GsdNightLightClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->finalize = gsd_night_light_finalize; + + object_class->set_property = gsd_night_light_set_property; + object_class->get_property = gsd_night_light_get_property; + + g_object_class_install_property (object_class, + PROP_ACTIVE, + g_param_spec_boolean ("active", + "Active", + "If night light functionality is active right now", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_SUNRISE, + g_param_spec_double ("sunrise", + "Sunrise", + "Sunrise in fractional hours", + 0, + 24.f, + 12, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_SUNSET, + g_param_spec_double ("sunset", + "Sunset", + "Sunset in fractional hours", + 0, + 24.f, + 12, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_TEMPERATURE, + g_param_spec_double ("temperature", + "Temperature", + "Temperature in Kelvin", + GSD_COLOR_TEMPERATURE_MIN, + GSD_COLOR_TEMPERATURE_MAX, + GSD_COLOR_TEMPERATURE_DEFAULT, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_DISABLED_UNTIL_TMW, + g_param_spec_boolean ("disabled-until-tmw", + "Disabled until tomorrow", + "If the night light is disabled until the next day", + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_FORCED, + g_param_spec_boolean ("forced", + "Forced", + "Whether night light should be forced on, useful for previewing", + FALSE, + G_PARAM_READWRITE)); + +} + +static void +gsd_night_light_init (GsdNightLight *self) +{ + self->geoclue_enabled = TRUE; + self->smooth_enabled = TRUE; + self->cached_sunrise = -1.f; + self->cached_sunset = -1.f; + self->cached_temperature = GSD_COLOR_TEMPERATURE_DEFAULT; + self->settings = g_settings_new ("org.gnome.settings-daemon.plugins.color"); + self->location_settings = g_settings_new ("org.gnome.system.location"); +} + +GsdNightLight * +gsd_night_light_new (void) +{ + return g_object_new (GSD_TYPE_NIGHT_LIGHT, NULL); +} diff --git a/plugins/color/gsd-night-light.h b/plugins/color/gsd-night-light.h new file mode 100644 index 0000000..8803c33 --- /dev/null +++ b/plugins/color/gsd-night-light.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 Richard Hughes + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __GSD_NIGHT_LIGHT_H__ +#define __GSD_NIGHT_LIGHT_H__ + +#include + +G_BEGIN_DECLS + +#define GSD_TYPE_NIGHT_LIGHT (gsd_night_light_get_type ()) +G_DECLARE_FINAL_TYPE (GsdNightLight, gsd_night_light, GSD, NIGHT_LIGHT, GObject) + +GsdNightLight *gsd_night_light_new (void); +gboolean gsd_night_light_start (GsdNightLight *self, + GError **error); + +gboolean gsd_night_light_get_active (GsdNightLight *self); +gdouble gsd_night_light_get_sunrise (GsdNightLight *self); +gdouble gsd_night_light_get_sunset (GsdNightLight *self); +gdouble gsd_night_light_get_temperature (GsdNightLight *self); + +gboolean gsd_night_light_get_disabled_until_tmw (GsdNightLight *self); +void gsd_night_light_set_disabled_until_tmw (GsdNightLight *self, + gboolean value); + +gboolean gsd_night_light_get_forced (GsdNightLight *self); +void gsd_night_light_set_forced (GsdNightLight *self, + gboolean value); + +/* only for the self test program */ +void gsd_night_light_set_geoclue_enabled (GsdNightLight *self, + gboolean enabled); +void gsd_night_light_set_date_time_now (GsdNightLight *self, + GDateTime *datetime); +void gsd_night_light_set_smooth_enabled (GsdNightLight *self, + gboolean smooth_enabled); + +G_END_DECLS + +#endif diff --git a/plugins/color/main.c b/plugins/color/main.c new file mode 100644 index 0000000..5dda3e7 --- /dev/null +++ b/plugins/color/main.c @@ -0,0 +1,7 @@ +#define NEW gsd_color_manager_new +#define START gsd_color_manager_start +#define STOP gsd_color_manager_stop +#define MANAGER GsdColorManager +#include "gsd-color-manager.h" + +#include "daemon-skeleton-gtk.h" diff --git a/plugins/color/meson.build b/plugins/color/meson.build new file mode 100644 index 0000000..b454bbb --- /dev/null +++ b/plugins/color/meson.build @@ -0,0 +1,50 @@ +sources = files( + 'gnome-datetime-source.c', + 'gsd-color-calibrate.c', + 'gsd-color-manager.c', + 'gsd-color-state.c', + 'gsd-night-light.c', + 'gsd-night-light-common.c', + 'main.c' +) + +deps = plugins_deps + [ + colord_dep, + libcanberra_gtk_dep, + libgeoclue_dep, + libnotify_dep, + m_dep, +] + +cflags += ['-DBINDIR="@0@"'.format(gsd_bindir)] + +executable( + 'gsd-' + plugin_name, + sources, + include_directories: [top_inc, common_inc], + dependencies: deps, + c_args: cflags, + install: true, + install_rpath: gsd_pkglibdir, + install_dir: gsd_libexecdir +) + +sources = files( + 'gcm-self-test.c', + 'gnome-datetime-source.c', + 'gsd-night-light.c', + 'gsd-night-light-common.c' +) + +test_unit = 'gcm-self-test' + +exe = executable( + test_unit, + sources, + include_directories: top_inc, + dependencies: deps, + c_args: '-DTESTDATADIR="@0@"'.format(join_paths(meson.current_source_dir(), 'test-data')) +) + +envs = ['GSETTINGS_SCHEMA_DIR=@0@'.format(join_paths(meson.build_root(), 'data'))] +test(test_unit, exe, env: envs) diff --git a/plugins/color/test-data/LG-L225W-External.bin b/plugins/color/test-data/LG-L225W-External.bin new file mode 100644 index 0000000..f08310a Binary files /dev/null and b/plugins/color/test-data/LG-L225W-External.bin differ diff --git a/plugins/color/test-data/Lenovo-T61-Internal.bin b/plugins/color/test-data/Lenovo-T61-Internal.bin new file mode 100644 index 0000000..45aec9d Binary files /dev/null and b/plugins/color/test-data/Lenovo-T61-Internal.bin differ diff --git a/plugins/common/daemon-skeleton-gtk.h b/plugins/common/daemon-skeleton-gtk.h new file mode 100644 index 0000000..4e67706 --- /dev/null +++ b/plugins/common/daemon-skeleton-gtk.h @@ -0,0 +1,295 @@ +/** + * Create a gnome-settings-daemon helper easily + * + * #define NEW gsd_media_keys_manager_new + * #define START gsd_media_keys_manager_start + * #define MANAGER GsdMediaKeysManager + * #include "gsd-media-keys-manager.h" + * + * #include "daemon-skeleton-gtk.h" + */ + +#include "config.h" + +#include +#include +#include + +#include +#include +#include + +#include "gnome-settings-bus.h" + +#ifndef PLUGIN_NAME +#error Include PLUGIN_CFLAGS in the daemon s CFLAGS +#endif /* !PLUGIN_NAME */ + +#define GNOME_SESSION_DBUS_NAME "org.gnome.SessionManager" +#define GNOME_SESSION_CLIENT_PRIVATE_DBUS_INTERFACE "org.gnome.SessionManager.ClientPrivate" + +static MANAGER *manager = NULL; +static int timeout = -1; +static char *dummy_name = NULL; +static gboolean verbose = FALSE; + +static GOptionEntry entries[] = { + { "exit-time", 0, 0, G_OPTION_ARG_INT, &timeout, "Exit after n seconds time", NULL }, + { "dummy-name", 0, 0, G_OPTION_ARG_STRING, &dummy_name, "Name when using the dummy daemon", NULL }, + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Verbose", NULL }, + {NULL} +}; + +static void +respond_to_end_session (GDBusProxy *proxy) +{ + /* we must answer with "EndSessionResponse" */ + g_dbus_proxy_call (proxy, "EndSessionResponse", + g_variant_new ("(bs)", TRUE, ""), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL, NULL); +} + +static void +do_stop (void) +{ + gtk_main_quit (); +} + +static void +client_proxy_signal_cb (GDBusProxy *proxy, + gchar *sender_name, + gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + if (g_strcmp0 (signal_name, "QueryEndSession") == 0) { + g_debug ("Got QueryEndSession signal"); + respond_to_end_session (proxy); + } else if (g_strcmp0 (signal_name, "EndSession") == 0) { + g_debug ("Got EndSession signal"); + respond_to_end_session (proxy); + } else if (g_strcmp0 (signal_name, "Stop") == 0) { + g_debug ("Got Stop signal"); + do_stop (); + } +} + +static void +on_client_registered (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GVariant *variant; + GDBusProxy *client_proxy; + GError *error = NULL; + gchar *object_path = NULL; + + variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error); + if (!variant) { + g_warning ("Unable to register client: %s", error->message); + g_error_free (error); + return; + } + + g_variant_get (variant, "(o)", &object_path); + + g_debug ("Registered client at path %s", object_path); + + client_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, 0, NULL, + GNOME_SESSION_DBUS_NAME, + object_path, + GNOME_SESSION_CLIENT_PRIVATE_DBUS_INTERFACE, + NULL, + &error); + if (!client_proxy) { + g_warning ("Unable to get the session client proxy: %s", error->message); + g_error_free (error); + return; + } + + g_signal_connect (client_proxy, "g-signal", + G_CALLBACK (client_proxy_signal_cb), NULL); + + g_free (object_path); + g_variant_unref (variant); +} + +static void +register_with_gnome_session (void) +{ + GDBusProxy *proxy; + const char *startup_id; + + proxy = G_DBUS_PROXY (gnome_settings_bus_get_session_proxy ()); + startup_id = g_getenv ("DESKTOP_AUTOSTART_ID"); + g_dbus_proxy_call (proxy, + "RegisterClient", + g_variant_new ("(ss)", dummy_name ? dummy_name : PLUGIN_NAME, startup_id ? startup_id : ""), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) on_client_registered, + NULL); + + /* DESKTOP_AUTOSTART_ID must not leak into child processes, because + * it can't be reused. Child processes will not know whether this is + * a genuine value or erroneous already-used value. */ + g_unsetenv ("DESKTOP_AUTOSTART_ID"); +} + +static void +set_empty_gtk_theme (gboolean set) +{ + static char *old_gtk_theme = NULL; + + if (set) { + /* Override GTK_THEME to reduce overhead of CSS engine. By using + * GTK_THEME environment variable, GtkSettings is not allowed to + * initially parse the Adwaita theme. + * + * https://bugzilla.gnome.org/show_bug.cgi?id=780555 */ + old_gtk_theme = g_strdup (g_getenv ("GTK_THEME")); + g_setenv ("GTK_THEME", "Disabled", TRUE); + } else { + /* GtkSettings has loaded, so we can drop GTK_THEME used to initialize + * our internal theme. Only the main thread accesses the GTK_THEME + * environment variable, so this is safe to release. */ + if (old_gtk_theme != NULL) + g_setenv ("GTK_THEME", old_gtk_theme, TRUE); + else + g_unsetenv ("GTK_THEME"); + } +} + +static gboolean +handle_sigterm (gpointer user_data) +{ + g_debug ("Got SIGTERM; shutting down ..."); + + if (gtk_main_level () > 0) + gtk_main_quit (); + + return G_SOURCE_REMOVE; +} + +static void +install_signal_handler (void) +{ + g_autoptr(GSource) source = NULL; + + source = g_unix_signal_source_new (SIGTERM); + + g_source_set_callback (source, handle_sigterm, NULL, NULL); + g_source_attach (source, NULL); +} + +static void +bus_acquired_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data G_GNUC_UNUSED) +{ + g_debug ("%s: acquired bus %p for name %s", G_STRFUNC, connection, name); +} + +static void +name_acquired_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data G_GNUC_UNUSED) +{ + g_debug ("%s: acquired name %s on bus %p", G_STRFUNC, name, connection); +} + +static void +name_lost_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data G_GNUC_UNUSED) +{ + g_debug ("%s: lost name %s on bus %p", G_STRFUNC, name, connection); +} + +int +main (int argc, char **argv) +{ + GError *error = NULL; + guint name_own_id; + + bindtextdomain (GETTEXT_PACKAGE, GNOME_SETTINGS_LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + setlocale (LC_ALL, ""); + + set_empty_gtk_theme (TRUE); + +#ifdef GDK_BACKEND + { + const gchar *setup_display = getenv ("GNOME_SETUP_DISPLAY"); + if (setup_display && *setup_display != '\0') + g_setenv ("DISPLAY", setup_display, TRUE); + } + + gdk_set_allowed_backends (GDK_BACKEND); + + /* GDK would fail to initialize with e.g. GDK_BACKEND=wayland */ + g_unsetenv ("GDK_BACKEND"); +#endif + + error = NULL; + if (! gtk_init_with_args (&argc, &argv, PLUGIN_NAME, entries, NULL, &error)) { + if (error != NULL) { + fprintf (stderr, "%s\n", error->message); + g_error_free (error); + } + exit (1); + } + + set_empty_gtk_theme (FALSE); + + if (verbose) { + g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); + /* Work around GLib not flushing the output for us by explicitly + * setting buffering to a sane behaviour. This is important + * during testing when the output is not going to a TTY and + * we are reading messages from g_debug on stdout. + * + * See also + * https://bugzilla.gnome.org/show_bug.cgi?id=792432 + */ + setlinebuf (stdout); + } + + if (timeout > 0) { + guint id; + id = g_timeout_add_seconds (timeout, (GSourceFunc) gtk_main_quit, NULL); + g_source_set_name_by_id (id, "[gnome-settings-daemon] gtk_main_quit"); + } + + install_signal_handler (); + + manager = NEW (); + register_with_gnome_session (); + + if (!START (manager, &error)) { + fprintf (stderr, "Failed to start: %s\n", error->message); + g_error_free (error); + exit (1); + } + + name_own_id = g_bus_own_name (G_BUS_TYPE_SESSION, + PLUGIN_DBUS_NAME, + G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE, + bus_acquired_cb, + name_acquired_cb, + name_lost_cb, + NULL, /* user_data */ + NULL /* user_data_free_func */); + + gtk_main (); + + STOP (manager); + + g_object_unref (manager); + g_bus_unown_name (name_own_id); + + return 0; +} diff --git a/plugins/common/daemon-skeleton.h b/plugins/common/daemon-skeleton.h new file mode 100644 index 0000000..40b7f14 --- /dev/null +++ b/plugins/common/daemon-skeleton.h @@ -0,0 +1,264 @@ +/** + * Create a gnome-settings-daemon helper easily + * + * #define NEW gsd_media_keys_manager_new + * #define START gsd_media_keys_manager_start + * #define MANAGER GsdMediaKeysManager + * #include "gsd-media-keys-manager.h" + * + * #include "daemon-skeleton.h" + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include "gnome-settings-bus.h" + +#ifndef PLUGIN_NAME +#error Include PLUGIN_CFLAGS in the daemon s CFLAGS +#endif /* !PLUGIN_NAME */ + +#ifndef PLUGIN_DBUS_NAME +#error Include PLUGIN_DBUS_NAME in the daemon s CFLAGS +#endif /* !PLUGIN_DBUS_NAME */ + +#define GNOME_SESSION_DBUS_NAME "org.gnome.SessionManager" +#define GNOME_SESSION_CLIENT_PRIVATE_DBUS_INTERFACE "org.gnome.SessionManager.ClientPrivate" + +static MANAGER *manager = NULL; +static int timeout = -1; +static char *dummy_name = NULL; +static gboolean verbose = FALSE; + +static GOptionEntry entries[] = { + { "exit-time", 0, 0, G_OPTION_ARG_INT, &timeout, "Exit after n seconds time", NULL }, + { "dummy-name", 0, 0, G_OPTION_ARG_STRING, &dummy_name, "Name when using the dummy daemon", NULL }, + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Verbose", NULL }, + {NULL} +}; + +static void +respond_to_end_session (GDBusProxy *proxy) +{ + /* we must answer with "EndSessionResponse" */ + g_dbus_proxy_call (proxy, "EndSessionResponse", + g_variant_new ("(bs)", TRUE, ""), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL, NULL); +} + +static void +do_stop (GMainLoop *loop) +{ + g_main_loop_quit (loop); +} + +static void +client_proxy_signal_cb (GDBusProxy *proxy, + gchar *sender_name, + gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + if (g_strcmp0 (signal_name, "QueryEndSession") == 0) { + g_debug ("Got QueryEndSession signal"); + respond_to_end_session (proxy); + } else if (g_strcmp0 (signal_name, "EndSession") == 0) { + g_debug ("Got EndSession signal"); + respond_to_end_session (proxy); + } else if (g_strcmp0 (signal_name, "Stop") == 0) { + g_debug ("Got Stop signal"); + do_stop (user_data); + } +} + +static void +on_client_registered (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GVariant *variant; + GMainLoop *loop = user_data; + GDBusProxy *client_proxy; + GError *error = NULL; + gchar *object_path = NULL; + + variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error); + if (!variant) { + g_warning ("Unable to register client: %s", error->message); + g_error_free (error); + return; + } + + g_variant_get (variant, "(o)", &object_path); + + g_debug ("Registered client at path %s", object_path); + + client_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, 0, NULL, + GNOME_SESSION_DBUS_NAME, + object_path, + GNOME_SESSION_CLIENT_PRIVATE_DBUS_INTERFACE, + NULL, + &error); + if (!client_proxy) { + g_warning ("Unable to get the session client proxy: %s", error->message); + g_error_free (error); + return; + } + + g_signal_connect (client_proxy, "g-signal", + G_CALLBACK (client_proxy_signal_cb), loop); + + g_free (object_path); + g_variant_unref (variant); +} + +static void +register_with_gnome_session (GMainLoop *loop) +{ + GDBusProxy *proxy; + const char *startup_id; + + proxy = G_DBUS_PROXY (gnome_settings_bus_get_session_proxy ()); + startup_id = g_getenv ("DESKTOP_AUTOSTART_ID"); + g_dbus_proxy_call (proxy, + "RegisterClient", + g_variant_new ("(ss)", dummy_name ? dummy_name : PLUGIN_NAME, startup_id ? startup_id : ""), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) on_client_registered, + loop); + + /* DESKTOP_AUTOSTART_ID must not leak into child processes, because + * it can't be reused. Child processes will not know whether this is + * a genuine value or erroneous already-used value. */ + g_unsetenv ("DESKTOP_AUTOSTART_ID"); +} + +static gboolean +handle_sigterm (gpointer user_data) +{ + GMainLoop *main_loop = user_data; + + g_debug ("Got SIGTERM; shutting down ..."); + + if (g_main_loop_is_running (main_loop)) + g_main_loop_quit (main_loop); + + return G_SOURCE_REMOVE; +} + +static void +install_signal_handler (GMainLoop *loop) +{ + g_autoptr(GSource) source = NULL; + + source = g_unix_signal_source_new (SIGTERM); + + g_source_set_callback (source, handle_sigterm, loop, NULL); + g_source_attach (source, NULL); +} + +static void +bus_acquired_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data G_GNUC_UNUSED) +{ + g_debug ("%s: acquired bus %p for name %s", G_STRFUNC, connection, name); +} + +static void +name_acquired_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data G_GNUC_UNUSED) +{ + g_debug ("%s: acquired name %s on bus %p", G_STRFUNC, name, connection); +} + +static void +name_lost_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data G_GNUC_UNUSED) +{ + g_debug ("%s: lost name %s on bus %p", G_STRFUNC, name, connection); +} + +int +main (int argc, char **argv) +{ + GError *error = NULL; + GOptionContext *context; + GMainLoop *loop; + guint name_own_id; + + bindtextdomain (GETTEXT_PACKAGE, GNOME_SETTINGS_LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + setlocale (LC_ALL, ""); + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); + if (!g_option_context_parse (context, &argc, &argv, &error)) { + fprintf (stderr, "%s\n", error->message); + g_error_free (error); + exit (1); + } + g_option_context_free (context); + + loop = g_main_loop_new (NULL, FALSE); + + if (verbose) { + g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); + /* Work around GLib not flushing the output for us by explicitly + * setting buffering to a sane behaviour. This is important + * during testing when the output is not going to a TTY and + * we are reading messages from g_debug on stdout. + * + * See also + * https://bugzilla.gnome.org/show_bug.cgi?id=792432 + */ + setlinebuf (stdout); + } + + if (timeout > 0) { + guint id; + id = g_timeout_add_seconds (timeout, (GSourceFunc) g_main_loop_quit, loop); + g_source_set_name_by_id (id, "[gnome-settings-daemon] g_main_loop_quit"); + } + + install_signal_handler (loop); + + manager = NEW (); + register_with_gnome_session (loop); + + if (!START (manager, &error)) { + fprintf (stderr, "Failed to start: %s\n", error->message); + g_error_free (error); + exit (1); + } + + name_own_id = g_bus_own_name (G_BUS_TYPE_SESSION, + PLUGIN_DBUS_NAME, + G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE, + bus_acquired_cb, + name_acquired_cb, + name_lost_cb, + NULL, /* user_data */ + NULL /* user_data_free_func */); + + g_main_loop_run (loop); + + STOP (manager); + + g_object_unref (manager); + g_bus_unown_name (name_own_id); + + return 0; +} diff --git a/plugins/common/gsd-input-helper.c b/plugins/common/gsd-input-helper.c new file mode 100644 index 0000000..d806ccf --- /dev/null +++ b/plugins/common/gsd-input-helper.c @@ -0,0 +1,79 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 Bastien Nocera + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 "config.h" + +#include + +#include +#include + +#include +#include +#include + +#include "gsd-input-helper.h" + +char * +xdevice_get_device_node (int deviceid) +{ + Atom prop; + Atom act_type; + int act_format; + unsigned long nitems, bytes_after; + unsigned char *data; + char *ret; + + gdk_display_sync (gdk_display_get_default ()); + + prop = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "Device Node", False); + if (!prop) + return NULL; + + gdk_x11_display_error_trap_push (gdk_display_get_default ()); + + if (!XIGetProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + deviceid, prop, 0, 1000, False, + AnyPropertyType, &act_type, &act_format, + &nitems, &bytes_after, &data) == Success) { + gdk_x11_display_error_trap_pop_ignored (gdk_display_get_default ()); + return NULL; + } + if (gdk_x11_display_error_trap_pop (gdk_display_get_default ())) + goto out; + + if (nitems == 0) + goto out; + + if (act_type != XA_STRING) + goto out; + + /* Unknown string format */ + if (act_format != 8) + goto out; + + ret = g_strdup ((char *) data); + + XFree (data); + return ret; + +out: + XFree (data); + return NULL; +} diff --git a/plugins/common/gsd-input-helper.h b/plugins/common/gsd-input-helper.h new file mode 100644 index 0000000..ae0d3ab --- /dev/null +++ b/plugins/common/gsd-input-helper.h @@ -0,0 +1,33 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 Bastien Nocera + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 __GSD_INPUT_HELPER_H +#define __GSD_INPUT_HELPER_H + +G_BEGIN_DECLS + +#include + +#include +#include + +char * xdevice_get_device_node (int deviceid); + +G_END_DECLS + +#endif /* __GSD_INPUT_HELPER_H */ diff --git a/plugins/common/gsd-settings-migrate.c b/plugins/common/gsd-settings-migrate.c new file mode 100644 index 0000000..a0e5cb8 --- /dev/null +++ b/plugins/common/gsd-settings-migrate.c @@ -0,0 +1,74 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Red Hat + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Carlos Garnacho + */ + +#include "config.h" + +#include + +#include "gsd-settings-migrate.h" + +void +gsd_settings_migrate_check (const gchar *origin_schema, + const gchar *origin_path, + const gchar *dest_schema, + const gchar *dest_path, + GsdSettingsMigrateEntry entries[], + guint n_entries) +{ + GSettings *origin_settings, *dest_settings; + guint i; + + origin_settings = g_settings_new_with_path (origin_schema, origin_path); + dest_settings = g_settings_new_with_path (dest_schema, dest_path); + + for (i = 0; i < n_entries; i++) { + g_autoptr(GVariant) variant = NULL; + + variant = g_settings_get_user_value (origin_settings, entries[i].origin_key); + + if (!variant) + continue; + + g_settings_reset (origin_settings, entries[i].origin_key); + + if (entries[i].dest_key) { + if (entries[i].func) { + g_autoptr(GVariant) old_default = NULL; + g_autoptr(GVariant) new_default = NULL; + GVariant *modified; + + old_default = g_settings_get_default_value (origin_settings, entries[i].origin_key); + new_default = g_settings_get_default_value (dest_settings, entries[i].dest_key); + + modified = entries[i].func (variant, old_default, new_default); + g_clear_pointer (&variant, g_variant_unref); + if (modified) + variant = g_variant_ref_sink (modified); + } + + if (variant) + g_settings_set_value (dest_settings, entries[i].dest_key, variant); + } + } + + g_object_unref (origin_settings); + g_object_unref (dest_settings); +} diff --git a/plugins/common/gsd-settings-migrate.h b/plugins/common/gsd-settings-migrate.h new file mode 100644 index 0000000..b8d2259 --- /dev/null +++ b/plugins/common/gsd-settings-migrate.h @@ -0,0 +1,43 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Red Hat + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Carlos Garnacho + */ + +#ifndef __GSD_SETTINGS_MIGRATE_H__ +#define __GSD_SETTINGS_MIGRATE_H__ + +typedef struct _GsdSettingsMigrateEntry GsdSettingsMigrateEntry; + +typedef GVariant * (* GsdSettingsMigrateFunc) (GVariant *variant, GVariant *old_default, GVariant *new_default); + +struct _GsdSettingsMigrateEntry +{ + const gchar *origin_key; + const gchar *dest_key; + GsdSettingsMigrateFunc func; +}; + +void gsd_settings_migrate_check (const gchar *origin_schema, + const gchar *origin_path, + const gchar *dest_schema, + const gchar *dest_path, + GsdSettingsMigrateEntry entries[], + guint n_entries); + +#endif /* __GSD_SETTINGS_MIGRATE_H__ */ diff --git a/plugins/common/gsd-shell-helper.c b/plugins/common/gsd-shell-helper.c new file mode 100644 index 0000000..e4a2f23 --- /dev/null +++ b/plugins/common/gsd-shell-helper.c @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Carlos Garnacho + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 "config.h" +#include "gsd-shell-helper.h" + +void +shell_show_osd (GsdShell *shell, + const gchar *icon_name, + const gchar *label, + double level, + const gchar *connector) +{ + shell_show_osd_with_max_level (shell, icon_name, label, level, -1, connector); +} + +void +shell_show_osd_with_max_level (GsdShell *shell, + const gchar *icon_name, + const gchar *label, + double level, + double max_level, + const gchar *connector) +{ + GVariantBuilder builder; + + g_return_if_fail (GSD_IS_SHELL (shell)); + + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + + if (icon_name) + g_variant_builder_add (&builder, "{sv}", + "icon", g_variant_new_string (icon_name)); + if (label) + g_variant_builder_add (&builder, "{sv}", + "label", g_variant_new_string (label)); + if (level >= 0.0) + g_variant_builder_add (&builder, "{sv}", + "level", g_variant_new_double (level)); + if (max_level > 1.0) + g_variant_builder_add (&builder, "{sv}", + "max_level", g_variant_new_double (max_level)); + if (connector) + g_variant_builder_add (&builder, "{sv}", + "connector", g_variant_new_string (connector)); + + gsd_shell_call_show_osd (shell, + g_variant_builder_end (&builder), + NULL, NULL, NULL); +} diff --git a/plugins/common/gsd-shell-helper.h b/plugins/common/gsd-shell-helper.h new file mode 100644 index 0000000..16a694b --- /dev/null +++ b/plugins/common/gsd-shell-helper.h @@ -0,0 +1,42 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Carlos Garnacho + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 __GSD_SHELL_HELPER_H__ +#define __GSD_SHELL_HELPER_H__ + +#include "gsd-shell-glue.h" + +G_BEGIN_DECLS + +void shell_show_osd (GsdShell *shell, + const gchar *icon_name, + const gchar *label, + double level, + const gchar *connector); + +void shell_show_osd_with_max_level (GsdShell *shell, + const gchar *icon_name, + const gchar *label, + double level, + double max_level, + const gchar *connector); + +G_END_DECLS + +#endif /* __GSD_SHELL_HELPER_H__ */ diff --git a/plugins/common/gsd.gresources.xml b/plugins/common/gsd.gresources.xml new file mode 100644 index 0000000..e4ac1cd --- /dev/null +++ b/plugins/common/gsd.gresources.xml @@ -0,0 +1,6 @@ + + + + gtk.css + + diff --git a/plugins/common/gtk.css b/plugins/common/gtk.css new file mode 100644 index 0000000..e69de29 diff --git a/plugins/common/meson.build b/plugins/common/meson.build new file mode 100644 index 0000000..2f18c2c --- /dev/null +++ b/plugins/common/meson.build @@ -0,0 +1,43 @@ +common_inc = include_directories('.') + +sources = files( + 'gsd-input-helper.c', + 'gsd-settings-migrate.c', + 'gsd-shell-helper.c' +) + +resource_data = files('gtk.css') + +sources += gnome.compile_resources( + 'gsd-resources', + 'gsd.gresources.xml', + c_name: 'gsd', + dependencies: resource_data +) + +deps = plugins_deps + [ + gnome_desktop_dep, + gtk_x11_dep, + x11_dep, + dependency('kbproto'), + dependency('xi') +] + +ldflags = [] +if host_is_darwin + ldflags += ['-Wl,-bundle_loader,@0@'.format(join_paths(), meson.build_root(), meson.project_name(), meson.project_name())] +endif + +libcommon = static_library( + plugin_name, + sources: sources, + include_directories: [top_inc, data_inc], + dependencies: deps, + c_args: cflags, + link_args: ldflags +) + +libcommon_dep = declare_dependency( + include_directories: common_inc, + link_with: libcommon +) diff --git a/plugins/datetime/backward b/plugins/datetime/backward new file mode 100644 index 0000000..f1f95a8 --- /dev/null +++ b/plugins/datetime/backward @@ -0,0 +1,118 @@ +#
+# @(#)backward	8.9
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# This file provides links between current names for time zones
+# and their old names.  Many names changed in late 1993.
+
+Link	Africa/Asmara		Africa/Asmera
+Link	Africa/Bamako		Africa/Timbuktu
+Link	America/Argentina/Catamarca	America/Argentina/ComodRivadavia
+Link	America/Adak		America/Atka
+Link	America/Argentina/Buenos_Aires	America/Buenos_Aires
+Link	America/Argentina/Catamarca	America/Catamarca
+Link	America/Atikokan	America/Coral_Harbour
+Link	America/Argentina/Cordoba	America/Cordoba
+Link	America/Tijuana		America/Ensenada
+Link	America/Indiana/Indianapolis	America/Fort_Wayne
+Link	America/Indiana/Indianapolis	America/Indianapolis
+Link	America/Argentina/Jujuy	America/Jujuy
+Link	America/Indiana/Knox	America/Knox_IN
+Link	America/Kentucky/Louisville	America/Louisville
+Link	America/Argentina/Mendoza	America/Mendoza
+Link	America/Rio_Branco	America/Porto_Acre
+Link	America/Argentina/Cordoba	America/Rosario
+Link	America/St_Thomas	America/Virgin
+Link	Asia/Ashgabat		Asia/Ashkhabad
+Link	Asia/Chongqing		Asia/Chungking
+Link	Asia/Dhaka		Asia/Dacca
+Link	Asia/Kathmandu		Asia/Katmandu
+Link	Asia/Kolkata		Asia/Calcutta
+Link	Asia/Macau		Asia/Macao
+Link	Asia/Jerusalem		Asia/Tel_Aviv
+Link	Asia/Ho_Chi_Minh	Asia/Saigon
+Link	Asia/Thimphu		Asia/Thimbu
+Link	Asia/Makassar		Asia/Ujung_Pandang
+Link	Asia/Ulaanbaatar	Asia/Ulan_Bator
+Link	Atlantic/Faroe		Atlantic/Faeroe
+Link	Europe/Oslo		Atlantic/Jan_Mayen
+Link	Australia/Sydney	Australia/ACT
+Link	Australia/Sydney	Australia/Canberra
+Link	Australia/Lord_Howe	Australia/LHI
+Link	Australia/Sydney	Australia/NSW
+Link	Australia/Darwin	Australia/North
+Link	Australia/Brisbane	Australia/Queensland
+Link	Australia/Adelaide	Australia/South
+Link	Australia/Hobart	Australia/Tasmania
+Link	Australia/Melbourne	Australia/Victoria
+Link	Australia/Perth		Australia/West
+Link	Australia/Broken_Hill	Australia/Yancowinna
+Link	America/Rio_Branco	Brazil/Acre
+Link	America/Noronha		Brazil/DeNoronha
+Link	America/Sao_Paulo	Brazil/East
+Link	America/Manaus		Brazil/West
+Link	America/Halifax		Canada/Atlantic
+Link	America/Winnipeg	Canada/Central
+Link	America/Regina		Canada/East-Saskatchewan
+Link	America/Toronto		Canada/Eastern
+Link	America/Edmonton	Canada/Mountain
+Link	America/St_Johns	Canada/Newfoundland
+Link	America/Vancouver	Canada/Pacific
+Link	America/Regina		Canada/Saskatchewan
+Link	America/Whitehorse	Canada/Yukon
+Link	America/Santiago	Chile/Continental
+Link	Pacific/Easter		Chile/EasterIsland
+Link	America/Havana		Cuba
+Link	Africa/Cairo		Egypt
+Link	Europe/Dublin		Eire
+Link	Europe/London		Europe/Belfast
+Link	Europe/Chisinau		Europe/Tiraspol
+Link	Europe/London		GB
+Link	Europe/London		GB-Eire
+Link	Etc/GMT			GMT+0
+Link	Etc/GMT			GMT-0
+Link	Etc/GMT			GMT0
+Link	Etc/GMT			Greenwich
+Link	Asia/Hong_Kong		Hongkong
+Link	Atlantic/Reykjavik	Iceland
+Link	Asia/Tehran		Iran
+Link	Asia/Jerusalem		Israel
+Link	America/Jamaica		Jamaica
+Link	Asia/Tokyo		Japan
+Link	Pacific/Kwajalein	Kwajalein
+Link	Africa/Tripoli		Libya
+Link	America/Tijuana		Mexico/BajaNorte
+Link	America/Mazatlan	Mexico/BajaSur
+Link	America/Mexico_City	Mexico/General
+Link	Pacific/Auckland	NZ
+Link	Pacific/Chatham		NZ-CHAT
+Link	America/Denver		Navajo
+Link	Asia/Shanghai		PRC
+Link	Pacific/Pago_Pago	Pacific/Samoa
+Link	Pacific/Chuuk		Pacific/Yap
+Link	Pacific/Chuuk		Pacific/Truk
+Link	Pacific/Pohnpei		Pacific/Ponape
+Link	Europe/Warsaw		Poland
+Link	Europe/Lisbon		Portugal
+Link	Asia/Taipei		ROC
+Link	Asia/Seoul		ROK
+Link	Asia/Singapore		Singapore
+Link	Europe/Istanbul		Turkey
+Link	Etc/UCT			UCT
+Link	America/Anchorage	US/Alaska
+Link	America/Adak		US/Aleutian
+Link	America/Phoenix		US/Arizona
+Link	America/Chicago		US/Central
+Link	America/Indiana/Indianapolis	US/East-Indiana
+Link	America/New_York	US/Eastern
+Link	Pacific/Honolulu	US/Hawaii
+Link	America/Indiana/Knox	US/Indiana-Starke
+Link	America/Detroit		US/Michigan
+Link	America/Denver		US/Mountain
+Link	America/Los_Angeles	US/Pacific
+Link	Pacific/Pago_Pago	US/Samoa
+Link	Etc/UTC			UTC
+Link	Etc/UTC			Universal
+Link	Europe/Moscow		W-SU
+Link	Etc/UTC			Zulu
diff --git a/plugins/datetime/gsd-datetime-manager.c b/plugins/datetime/gsd-datetime-manager.c
new file mode 100644
index 0000000..dcd9f8c
--- /dev/null
+++ b/plugins/datetime/gsd-datetime-manager.c
@@ -0,0 +1,226 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Kalev Lember 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+#include 
+
+#include "gsd-datetime-manager.h"
+#include "gsd-timezone-monitor.h"
+#include "gnome-settings-profile.h"
+
+#define DATETIME_SCHEMA "org.gnome.desktop.datetime"
+#define AUTO_TIMEZONE_KEY "automatic-timezone"
+
+struct _GsdDatetimeManager
+{
+        GObject parent;
+
+        GSettings *settings;
+        GsdTimezoneMonitor *timezone_monitor;
+        NotifyNotification *notification;
+};
+
+static void gsd_datetime_manager_class_init (GsdDatetimeManagerClass *klass);
+static void gsd_datetime_manager_init (GsdDatetimeManager *manager);
+static void gsd_datetime_manager_finalize (GObject *object);
+
+G_DEFINE_TYPE (GsdDatetimeManager, gsd_datetime_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+static void
+notification_closed_cb (NotifyNotification *n,
+                        GsdDatetimeManager *self)
+{
+        g_clear_object (&self->notification);
+}
+
+static void
+open_settings_cb (NotifyNotification *n,
+                  const char         *action,
+                  const char         *path)
+{
+        const gchar *argv[] = { "gnome-control-center", "datetime", NULL };
+
+        g_debug ("Running gnome-control-center datetime");
+        g_spawn_async (NULL, (gchar **) argv, NULL, G_SPAWN_SEARCH_PATH,
+                       NULL, NULL, NULL, NULL);
+
+        notify_notification_close (n, NULL);
+}
+
+static void
+timezone_changed_cb (GsdTimezoneMonitor *timezone_monitor,
+                     const gchar        *timezone_id,
+                     GsdDatetimeManager *self)
+{
+        GDateTime *datetime;
+        GTimeZone *tz;
+        gchar *notification_summary;
+        gchar *timezone_name;
+        gchar *utc_offset;
+
+        tz = g_time_zone_new (timezone_id);
+        datetime = g_date_time_new_now (tz);
+        g_time_zone_unref (tz);
+
+        /* Translators: UTC here means the Coordinated Universal Time.
+         * %:::z will be replaced by the offset from UTC e.g. UTC+02 */
+        utc_offset = g_date_time_format (datetime, _("UTC%:::z"));
+        timezone_name = g_strdup (g_date_time_get_timezone_abbreviation (datetime));
+        g_date_time_unref (datetime);
+
+        notification_summary = g_strdup_printf (_("Time Zone Updated to %s (%s)"),
+                                                timezone_name,
+                                                utc_offset);
+        g_free (timezone_name);
+        g_free (utc_offset);
+
+        if (self->notification == NULL) {
+                self->notification = notify_notification_new (notification_summary, NULL,
+                                                                    "preferences-system-time-symbolic");
+                g_signal_connect (self->notification,
+                                  "closed",
+                                  G_CALLBACK (notification_closed_cb),
+                                  self);
+
+                notify_notification_add_action (self->notification,
+                                                "settings",
+                                                _("Settings"),
+                                                (NotifyActionCallback) open_settings_cb,
+                                                NULL, NULL);
+        } else {
+                notify_notification_update (self->notification,
+                                            notification_summary, NULL,
+                                            "preferences-system-time-symbolic");
+        }
+        g_free (notification_summary);
+
+        notify_notification_set_app_name (self->notification, _("Date & Time Settings"));
+        notify_notification_set_hint_string (self->notification, "desktop-entry", "gnome-datetime-panel");
+        notify_notification_set_urgency (self->notification, NOTIFY_URGENCY_NORMAL);
+        notify_notification_set_timeout (self->notification, NOTIFY_EXPIRES_NEVER);
+
+        if (!notify_notification_show (self->notification, NULL)) {
+                g_warning ("Failed to send timezone notification");
+        }
+}
+
+static void
+auto_timezone_settings_changed_cb (GSettings          *settings,
+                                   const char         *key,
+                                   GsdDatetimeManager *self)
+{
+        gboolean enabled;
+
+        enabled = g_settings_get_boolean (settings, key);
+        if (enabled && self->timezone_monitor == NULL) {
+                g_debug ("Automatic timezone enabled");
+                self->timezone_monitor = gsd_timezone_monitor_new ();
+
+                g_signal_connect (self->timezone_monitor, "timezone-changed",
+                                  G_CALLBACK (timezone_changed_cb), self);
+        } else {
+                g_debug ("Automatic timezone disabled");
+                g_clear_object (&self->timezone_monitor);
+        }
+}
+
+gboolean
+gsd_datetime_manager_start (GsdDatetimeManager *self,
+                            GError            **error)
+{
+        g_debug ("Starting datetime manager");
+        gnome_settings_profile_start (NULL);
+
+        self->settings = g_settings_new (DATETIME_SCHEMA);
+
+        g_signal_connect (self->settings, "changed::" AUTO_TIMEZONE_KEY,
+                          G_CALLBACK (auto_timezone_settings_changed_cb), self);
+        auto_timezone_settings_changed_cb (self->settings, AUTO_TIMEZONE_KEY, self);
+
+        gnome_settings_profile_end (NULL);
+
+        return TRUE;
+}
+
+void
+gsd_datetime_manager_stop (GsdDatetimeManager *self)
+{
+        g_debug ("Stopping datetime manager");
+
+        g_clear_object (&self->settings);
+        g_clear_object (&self->timezone_monitor);
+
+        if (self->notification != NULL) {
+                g_signal_handlers_disconnect_by_func (self->notification,
+                                                      G_CALLBACK (notification_closed_cb),
+                                                      self);
+                g_clear_object (&self->notification);
+        }
+}
+
+static void
+gsd_datetime_manager_class_init (GsdDatetimeManagerClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_datetime_manager_finalize;
+
+        notify_init ("gnome-settings-daemon");
+}
+
+static void
+gsd_datetime_manager_init (GsdDatetimeManager *manager)
+{
+}
+
+static void
+gsd_datetime_manager_finalize (GObject *object)
+{
+        GsdDatetimeManager *manager;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSD_IS_DATETIME_MANAGER (object));
+
+        manager = GSD_DATETIME_MANAGER (object);
+
+        g_return_if_fail (manager != NULL);
+
+        gsd_datetime_manager_stop (manager);
+
+        G_OBJECT_CLASS (gsd_datetime_manager_parent_class)->finalize (object);
+}
+
+GsdDatetimeManager *
+gsd_datetime_manager_new (void)
+{
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_DATETIME_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+        }
+
+        return GSD_DATETIME_MANAGER (manager_object);
+}
diff --git a/plugins/datetime/gsd-datetime-manager.h b/plugins/datetime/gsd-datetime-manager.h
new file mode 100644
index 0000000..5478145
--- /dev/null
+++ b/plugins/datetime/gsd-datetime-manager.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Kalev Lember 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_DATETIME_MANAGER_H
+#define __GSD_DATETIME_MANAGER_H
+
+#include 
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_DATETIME_MANAGER         (gsd_datetime_manager_get_type ())
+G_DECLARE_FINAL_TYPE (GsdDatetimeManager, gsd_datetime_manager, GSD, DATETIME_MANAGER, GObject)
+
+GsdDatetimeManager *gsd_datetime_manager_new (void);
+gboolean gsd_datetime_manager_start (GsdDatetimeManager *manager, GError **error);
+void gsd_datetime_manager_stop (GsdDatetimeManager *manager);
+
+G_END_DECLS
+
+#endif /* __GSD_DATETIME_MANAGER_H */
diff --git a/plugins/datetime/gsd-timezone-monitor.c b/plugins/datetime/gsd-timezone-monitor.c
new file mode 100644
index 0000000..a6e8a48
--- /dev/null
+++ b/plugins/datetime/gsd-timezone-monitor.c
@@ -0,0 +1,472 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Kalev Lember 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include "gsd-timezone-monitor.h"
+
+#include "timedated.h"
+#include "tz.h"
+#include "weather-tz.h"
+
+#include 
+#include 
+#include 
+
+#define DESKTOP_ID "gnome-datetime-panel"
+#define SET_TIMEZONE_PERMISSION "org.freedesktop.timedate1.set-timezone"
+
+enum {
+        TIMEZONE_CHANGED,
+        LAST_SIGNAL
+};
+
+static int signals[LAST_SIGNAL] = { 0 };
+
+typedef struct
+{
+        GCancellable *cancellable;
+        GPermission *permission;
+        Timedate1 *dtm;
+
+        GClueClient *geoclue_client;
+        GClueSimple *geoclue_simple;
+        GCancellable *geoclue_cancellable;
+
+        gchar *current_timezone;
+
+        GSettings *location_settings;
+} GsdTimezoneMonitorPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (GsdTimezoneMonitor, gsd_timezone_monitor, G_TYPE_OBJECT)
+
+static void
+set_timezone_cb (GObject      *source,
+                 GAsyncResult *res,
+                 gpointer      user_data)
+{
+        GsdTimezoneMonitorPrivate *priv;
+        GError *error = NULL;
+
+        if (!timedate1_call_set_timezone_finish (TIMEDATE1 (source),
+                                                 res,
+                                                 &error)) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Could not set system timezone: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        priv = gsd_timezone_monitor_get_instance_private (user_data);
+        g_signal_emit (G_OBJECT (user_data),
+                       signals[TIMEZONE_CHANGED],
+                       0, priv->current_timezone);
+
+        g_debug ("Successfully changed timezone to '%s'",
+                 priv->current_timezone);
+}
+
+static void
+queue_set_timezone (GsdTimezoneMonitor *self,
+                    const gchar        *new_timezone)
+{
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (self);
+
+        g_debug ("Changing timezone to '%s'", new_timezone);
+
+        timedate1_call_set_timezone (priv->dtm,
+                                     new_timezone,
+                                     TRUE,
+                                     priv->cancellable,
+                                     set_timezone_cb,
+                                     self);
+
+        g_free (priv->current_timezone);
+        priv->current_timezone = g_strdup (new_timezone);
+}
+
+static gint
+compare_locations (TzLocation *a,
+                   TzLocation *b)
+{
+        if (a->dist > b->dist)
+                return 1;
+
+        if (a->dist < b->dist)
+                return -1;
+
+        return 0;
+}
+
+static GList *
+sort_by_closest_to (GList           *locations,
+                    GeocodeLocation *location)
+{
+        GList *l;
+
+        for (l = locations; l; l = l->next) {
+                GeocodeLocation *loc;
+                TzLocation *tz_location = l->data;
+
+                loc = geocode_location_new (tz_location->latitude,
+                                            tz_location->longitude,
+                                            GEOCODE_LOCATION_ACCURACY_UNKNOWN);
+                tz_location->dist = geocode_location_get_distance_from (loc, location);
+                g_object_unref (loc);
+        }
+
+        return g_list_sort (locations, (GCompareFunc) compare_locations);
+}
+
+static GList *
+ptr_array_to_list (GPtrArray *array)
+{
+        GList *l = NULL;
+        gint i;
+
+        for (i = 0; i < array->len; i++)
+                l = g_list_prepend (l, g_ptr_array_index (array, i));
+
+        return l;
+}
+
+static GList *
+find_by_country (GList       *locations,
+                 const gchar *country_code)
+{
+        GList *l, *found = NULL;
+        gchar *c1;
+        gchar *c2;
+
+        c1 = g_ascii_strdown (country_code, -1);
+
+        for (l = locations; l; l = l->next) {
+                TzLocation *loc = l->data;
+
+                c2 = g_ascii_strdown (loc->country, -1);
+                if (g_strcmp0 (c1, c2) == 0)
+                        found = g_list_prepend (found, loc);
+                g_free (c2);
+        }
+        g_free (c1);
+
+        return found;
+}
+
+static gchar *
+find_timezone (GsdTimezoneMonitor *self,
+               GeocodeLocation    *location,
+               const gchar        *country_code)
+{
+        TzDB *tzdb;
+        gchar *res;
+        GList *filtered;
+        GList *weather_locations;
+        GList *locations;
+        TzLocation *closest_tz_location;
+
+        tzdb = tz_load_db ();
+
+        /* First load locations from Olson DB */
+        locations = ptr_array_to_list (tz_get_locations (tzdb));
+        g_return_val_if_fail (locations != NULL, NULL);
+
+        /* ... and then add libgweather's locations as well */
+        weather_locations = weather_tz_db_get_locations (country_code);
+        locations = g_list_concat (locations,
+                                   g_list_copy (weather_locations));
+
+        /* Filter tz locations by country */
+        filtered = find_by_country (locations, country_code);
+        if (filtered != NULL) {
+                g_list_free (locations);
+                locations = filtered;
+        } else {
+                g_debug ("No match for country code '%s' in tzdb", country_code);
+        }
+
+        /* Find the closest tz location */
+        locations = sort_by_closest_to (locations, location);
+        closest_tz_location = (TzLocation *) locations->data;
+
+        res = g_strdup (closest_tz_location->zone);
+
+        g_list_free (locations);
+        g_list_free_full (weather_locations, (GDestroyNotify) tz_location_free);
+        tz_db_free (tzdb);
+
+        return res;
+}
+
+static void
+process_location (GsdTimezoneMonitor *self,
+                  GeocodePlace       *place)
+{
+        GeocodeLocation *location;
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (self);
+        const gchar *country_code;
+        g_autofree gchar *new_timezone = NULL;
+
+        country_code = geocode_place_get_country_code (place);
+        location = geocode_place_get_location (place);
+
+        new_timezone = find_timezone (self, location, country_code);
+
+        if (g_strcmp0 (priv->current_timezone, new_timezone) != 0) {
+                g_debug ("Found updated timezone '%s' for country '%s'",
+                         new_timezone, country_code);
+                queue_set_timezone (self, new_timezone);
+        } else {
+                g_debug ("Timezone didn't change from '%s' for country '%s'",
+                         new_timezone, country_code);
+        }
+}
+
+static void
+on_reverse_geocoding_ready (GObject      *source_object,
+                            GAsyncResult *res,
+                            gpointer      user_data)
+{
+        GeocodePlace *place;
+        GError *error = NULL;
+
+        place = geocode_reverse_resolve_finish (GEOCODE_REVERSE (source_object),
+                                                res,
+                                                &error);
+        if (error != NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_debug ("Reverse geocoding failed: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+        g_debug ("Geocode lookup resolved country to '%s'",
+                 geocode_place_get_country (place));
+
+        process_location (user_data, place);
+        g_object_unref (place);
+}
+
+static void
+start_reverse_geocoding (GsdTimezoneMonitor *self,
+                         gdouble             latitude,
+                         gdouble             longitude)
+{
+        GeocodeLocation *location;
+        GeocodeReverse *reverse;
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (self);
+
+        location = geocode_location_new (latitude,
+                                         longitude,
+                                         GEOCODE_LOCATION_ACCURACY_CITY);
+
+        reverse = geocode_reverse_new_for_location (location);
+        geocode_reverse_resolve_async (reverse,
+                                       priv->geoclue_cancellable,
+                                       on_reverse_geocoding_ready,
+                                       self);
+
+        g_object_unref (location);
+        g_object_unref (reverse);
+}
+
+static void
+on_location_notify (GClueSimple *simple,
+                    GParamSpec  *pspec,
+                    gpointer     user_data)
+{
+        GsdTimezoneMonitor *self = user_data;
+        GClueLocation *location;
+        gdouble latitude, longitude;
+
+        location = gclue_simple_get_location (simple);
+
+        latitude = gclue_location_get_latitude (location);
+        longitude = gclue_location_get_longitude (location);
+
+        g_debug ("Got location %lf,%lf", latitude, longitude);
+
+        start_reverse_geocoding (self, latitude, longitude);
+}
+
+static void
+on_geoclue_simple_ready (GObject      *source_object,
+                         GAsyncResult *res,
+                         gpointer      user_data)
+{
+        GError *error = NULL;
+        GsdTimezoneMonitorPrivate *priv;
+        GClueSimple *geoclue_simple;
+
+        geoclue_simple = gclue_simple_new_finish (res, &error);
+        if (geoclue_simple == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to connect to GeoClue2 service: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        g_debug ("Geoclue now available");
+
+        priv = gsd_timezone_monitor_get_instance_private (user_data);
+        priv->geoclue_simple = geoclue_simple;
+        priv->geoclue_client = gclue_simple_get_client (priv->geoclue_simple);
+        gclue_client_set_distance_threshold (priv->geoclue_client,
+                                             GEOCODE_LOCATION_ACCURACY_CITY);
+
+        g_signal_connect (priv->geoclue_simple, "notify::location",
+                          G_CALLBACK (on_location_notify), user_data);
+
+        on_location_notify (priv->geoclue_simple, NULL, user_data);
+}
+
+static void
+start_geoclue (GsdTimezoneMonitor *self)
+{
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (self);
+
+        g_debug ("Timezone monitor enabled, starting geoclue");
+
+        priv->geoclue_cancellable = g_cancellable_new ();
+        gclue_simple_new (DESKTOP_ID,
+                          GCLUE_ACCURACY_LEVEL_CITY,
+                          priv->geoclue_cancellable,
+                          on_geoclue_simple_ready,
+                          self);
+
+}
+
+static void
+stop_geoclue (GsdTimezoneMonitor *self)
+{
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (self);
+
+        g_debug ("Timezone monitor disabled, stopping geoclue");
+
+        g_cancellable_cancel (priv->geoclue_cancellable);
+        g_clear_object (&priv->geoclue_cancellable);
+
+        if (priv->geoclue_client) {
+                gclue_client_call_stop (priv->geoclue_client, NULL, NULL, NULL);
+                priv->geoclue_client = NULL;
+        }
+
+        g_clear_object (&priv->geoclue_simple);
+}
+
+GsdTimezoneMonitor *
+gsd_timezone_monitor_new (void)
+{
+        return g_object_new (GSD_TYPE_TIMEZONE_MONITOR, NULL);
+}
+
+static void
+gsd_timezone_monitor_finalize (GObject *obj)
+{
+        GsdTimezoneMonitor *monitor = GSD_TIMEZONE_MONITOR (obj);
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (monitor);
+
+        g_debug ("Stopping timezone monitor");
+
+        stop_geoclue (monitor);
+
+        if (priv->cancellable) {
+                g_cancellable_cancel (priv->cancellable);
+                g_clear_object (&priv->cancellable);
+        }
+
+        g_clear_object (&priv->dtm);
+        g_clear_object (&priv->permission);
+        g_clear_pointer (&priv->current_timezone, g_free);
+
+        g_clear_object (&priv->location_settings);
+
+        G_OBJECT_CLASS (gsd_timezone_monitor_parent_class)->finalize (obj);
+}
+
+static void
+gsd_timezone_monitor_class_init (GsdTimezoneMonitorClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_timezone_monitor_finalize;
+
+        signals[TIMEZONE_CHANGED] =
+                g_signal_new ("timezone-changed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GsdTimezoneMonitorClass, timezone_changed),
+                              NULL, NULL,
+                              NULL,
+                              G_TYPE_NONE, 1, G_TYPE_POINTER);
+}
+
+static void
+check_location_settings (GsdTimezoneMonitor *self)
+{
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (self);
+        if (g_settings_get_boolean (priv->location_settings, "enabled"))
+                start_geoclue (self);
+        else
+                stop_geoclue (self);
+}
+
+static void
+gsd_timezone_monitor_init (GsdTimezoneMonitor *self)
+{
+        GError *error = NULL;
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (self);
+
+        g_debug ("Starting timezone monitor");
+
+        priv->permission = polkit_permission_new_sync (SET_TIMEZONE_PERMISSION,
+                                                       NULL, NULL,
+                                                       &error);
+        if (priv->permission == NULL) {
+                g_warning ("Could not get '%s' permission: %s",
+                           SET_TIMEZONE_PERMISSION,
+                           error->message);
+                g_error_free (error);
+                return;
+        }
+
+        if (!g_permission_get_allowed (priv->permission)) {
+                g_debug ("No permission to set timezone");
+                return;
+        }
+
+        priv->cancellable = g_cancellable_new ();
+        priv->dtm = timedate1_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+                                                      G_DBUS_PROXY_FLAGS_NONE,
+                                                      "org.freedesktop.timedate1",
+                                                      "/org/freedesktop/timedate1",
+                                                      priv->cancellable,
+                                                      &error);
+        if (priv->dtm == NULL) {
+                g_warning ("Could not get proxy for DateTimeMechanism: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        priv->current_timezone = timedate1_dup_timezone (priv->dtm);
+
+        priv->location_settings = g_settings_new ("org.gnome.system.location");
+        g_signal_connect_swapped (priv->location_settings, "changed::enabled",
+                                  G_CALLBACK (check_location_settings), self);
+        check_location_settings (self);
+}
diff --git a/plugins/datetime/gsd-timezone-monitor.h b/plugins/datetime/gsd-timezone-monitor.h
new file mode 100644
index 0000000..da2bcf8
--- /dev/null
+++ b/plugins/datetime/gsd-timezone-monitor.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Kalev Lember 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_TIMEZONE_MONITOR_H
+#define __GSD_TIMEZONE_MONITOR_H
+
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_TIMEZONE_MONITOR                  (gsd_timezone_monitor_get_type ())
+#define GSD_TIMEZONE_MONITOR(obj)                  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSD_TYPE_TIMEZONE_MONITOR, GsdTimezoneMonitor))
+#define GSD_IS_TIMEZONE_MONITOR(obj)               (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSD_TYPE_TIMEZONE_MONITOR))
+#define GSD_TIMEZONE_MONITOR_CLASS(klass)          (G_TYPE_CHECK_CLASS_CAST ((klass), GSD_TYPE_TIMEZONE_MONITOR, GsdTimezoneMonitorClass))
+#define GSD_IS_TIMEZONE_MONITOR_CLASS(klass)       (G_TYPE_CHECK_CLASS_TYPE ((klass), GSD_TYPE_TIMEZONE_MONITOR))
+#define GSD_TIMEZONE_MONITOR_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj), GSD_TYPE_TIMEZONE_MONITOR, GsdTimezoneMonitorClass))
+
+typedef struct _GsdTimezoneMonitor        GsdTimezoneMonitor;
+typedef struct _GsdTimezoneMonitorClass   GsdTimezoneMonitorClass;
+
+struct _GsdTimezoneMonitor
+{
+	GObject parent_instance;
+};
+
+struct _GsdTimezoneMonitorClass
+{
+	GObjectClass parent_class;
+
+	void (*timezone_changed) (GsdTimezoneMonitor *monitor, gchar *timezone_id);
+};
+
+GType gsd_timezone_monitor_get_type (void) G_GNUC_CONST;
+
+GsdTimezoneMonitor *gsd_timezone_monitor_new (void);
+
+G_END_DECLS
+
+#endif /* __GSD_TIMEZONE_MONITOR_H */
diff --git a/plugins/datetime/main.c b/plugins/datetime/main.c
new file mode 100644
index 0000000..8950ab0
--- /dev/null
+++ b/plugins/datetime/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_datetime_manager_new
+#define START gsd_datetime_manager_start
+#define STOP gsd_datetime_manager_stop
+#define MANAGER GsdDatetimeManager
+#include "gsd-datetime-manager.h"
+
+#include "daemon-skeleton.h"
diff --git a/plugins/datetime/meson.build b/plugins/datetime/meson.build
new file mode 100644
index 0000000..ed2d433
--- /dev/null
+++ b/plugins/datetime/meson.build
@@ -0,0 +1,40 @@
+install_data(
+  'backward',
+  install_dir: join_paths(gsd_pkgdatadir, 'datetime')
+)
+
+sources = files(
+  'gsd-datetime-manager.c',
+  'gsd-timezone-monitor.c',
+  'main.c',
+  'tz.c',
+  'weather-tz.c'
+)
+
+sources += gnome.gdbus_codegen(
+  'timedated',
+  'timedated1-interface.xml',
+  interface_prefix: 'org.freedesktop.'
+)
+
+deps = plugins_deps + [
+  geocode_glib_dep,
+  gweather_dep,
+  libgeoclue_dep,
+  libnotify_dep,
+  m_dep,
+  polkit_gobject_dep
+]
+
+cflags += ['-DGNOMECC_DATA_DIR="@0@"'.format(gsd_pkgdatadir)]
+
+executable(
+  'gsd-' + plugin_name,
+  sources,
+  include_directories: [top_inc, common_inc],
+  dependencies: deps,
+  c_args: cflags,
+  install: true,
+  install_rpath: gsd_pkglibdir,
+  install_dir: gsd_libexecdir
+)
diff --git a/plugins/datetime/timedated1-interface.xml b/plugins/datetime/timedated1-interface.xml
new file mode 100644
index 0000000..3370e0e
--- /dev/null
+++ b/plugins/datetime/timedated1-interface.xml
@@ -0,0 +1,28 @@
+
+
+ 
+  
+  
+  
+  
+  
+   
+   
+   
+  
+  
+   
+   
+  
+  
+   
+   
+   
+  
+  
+   
+   
+  
+ 
+
diff --git a/plugins/datetime/tz.c b/plugins/datetime/tz.c
new file mode 100644
index 0000000..034d63d
--- /dev/null
+++ b/plugins/datetime/tz.c
@@ -0,0 +1,482 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Generic timezone utilities.
+ *
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Authors: Hans Petter Jansson 
+ * 
+ * Largely based on Michael Fulbright's work on Anaconda.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 
+#include 
+#include 
+#include 
+#include 
+#include "tz.h"
+
+
+/* Forward declarations for private functions */
+
+static float convert_pos (gchar *pos, int digits);
+static int compare_country_names (const void *a, const void *b);
+static void sort_locations_by_country (GPtrArray *locations);
+static gchar * tz_data_file_get (void);
+static void load_backward_tz (TzDB *tz_db);
+
+/* ---------------- *
+ * Public interface *
+ * ---------------- */
+TzDB *
+tz_load_db (void)
+{
+	gchar *tz_data_file;
+	TzDB *tz_db;
+	FILE *tzfile;
+	char buf[4096];
+
+	tz_data_file = tz_data_file_get ();
+	if (!tz_data_file) {
+		g_warning ("Could not get the TimeZone data file name");
+		return NULL;
+	}
+	tzfile = fopen (tz_data_file, "r");
+	if (!tzfile) {
+		g_warning ("Could not open *%s*\n", tz_data_file);
+		g_free (tz_data_file);
+		return NULL;
+	}
+
+	tz_db = g_new0 (TzDB, 1);
+	tz_db->locations = g_ptr_array_new ();
+
+	while (fgets (buf, sizeof(buf), tzfile))
+	{
+		gchar **tmpstrarr;
+		gchar *latstr, *lngstr, *p;
+		TzLocation *loc;
+
+		if (*buf == '#') continue;
+
+		g_strchomp(buf);
+		tmpstrarr = g_strsplit(buf,"\t", 6);
+		
+		latstr = g_strdup (tmpstrarr[1]);
+		p = latstr + 1;
+		while (*p != '-' && *p != '+') p++;
+		lngstr = g_strdup (p);
+		*p = '\0';
+		
+		loc = g_new0 (TzLocation, 1);
+		loc->country = g_strdup (tmpstrarr[0]);
+		loc->zone = g_strdup (tmpstrarr[2]);
+		loc->latitude  = convert_pos (latstr, 2);
+		loc->longitude = convert_pos (lngstr, 3);
+		
+#ifdef __sun
+		if (tmpstrarr[3] && *tmpstrarr[3] == '-' && tmpstrarr[4])
+			loc->comment = g_strdup (tmpstrarr[4]);
+
+		if (tmpstrarr[3] && *tmpstrarr[3] != '-' && !islower(loc->zone)) {
+			TzLocation *locgrp;
+
+			/* duplicate entry */
+			locgrp = g_new0 (TzLocation, 1);
+			locgrp->country = g_strdup (tmpstrarr[0]);
+			locgrp->zone = g_strdup (tmpstrarr[3]);
+			locgrp->latitude  = convert_pos (latstr, 2);
+			locgrp->longitude = convert_pos (lngstr, 3);
+			locgrp->comment = (tmpstrarr[4]) ? g_strdup (tmpstrarr[4]) : NULL;
+
+			g_ptr_array_add (tz_db->locations, (gpointer) locgrp);
+		}
+#else
+		loc->comment = (tmpstrarr[3]) ? g_strdup(tmpstrarr[3]) : NULL;
+#endif
+
+		g_ptr_array_add (tz_db->locations, (gpointer) loc);
+
+		g_free (latstr);
+		g_free (lngstr);
+		g_strfreev (tmpstrarr);
+	}
+	
+	fclose (tzfile);
+	
+	/* now sort by country */
+	sort_locations_by_country (tz_db->locations);
+	
+	g_free (tz_data_file);
+
+	/* Load up the hashtable of backward links */
+	load_backward_tz (tz_db);
+
+	return tz_db;
+}
+
+void
+tz_location_free (TzLocation *loc)
+{
+	g_free (loc->country);
+	g_free (loc->zone);
+	g_free (loc->comment);
+
+	g_free (loc);
+}
+
+void
+tz_db_free (TzDB *db)
+{
+	g_ptr_array_foreach (db->locations, (GFunc) tz_location_free, NULL);
+	g_ptr_array_free (db->locations, TRUE);
+	g_hash_table_destroy (db->backward);
+	g_free (db);
+}
+
+GPtrArray *
+tz_get_locations (TzDB *db)
+{
+	return db->locations;
+}
+
+
+gchar *
+tz_location_get_country (TzLocation *loc)
+{
+	return loc->country;
+}
+
+
+gchar *
+tz_location_get_zone (TzLocation *loc)
+{
+	return loc->zone;
+}
+
+
+gchar *
+tz_location_get_comment (TzLocation *loc)
+{
+	return loc->comment;
+}
+
+
+void
+tz_location_get_position (TzLocation *loc, double *longitude, double *latitude)
+{
+	*longitude = loc->longitude;
+	*latitude = loc->latitude;
+}
+
+glong
+tz_location_get_utc_offset (TzLocation *loc)
+{
+	TzInfo *tz_info;
+	glong offset;
+
+	tz_info = tz_info_from_location (loc);
+	offset = tz_info->utc_offset;
+	tz_info_free (tz_info);
+	return offset;
+}
+
+TzInfo *
+tz_info_from_location (TzLocation *loc)
+{
+	TzInfo *tzinfo;
+	time_t curtime;
+	struct tm *curzone;
+	gchar *tz_env_value;
+	
+	g_return_val_if_fail (loc != NULL, NULL);
+	g_return_val_if_fail (loc->zone != NULL, NULL);
+	
+	tz_env_value = g_strdup (getenv ("TZ"));
+	setenv ("TZ", loc->zone, 1);
+	
+#if 0
+	tzset ();
+#endif
+	tzinfo = g_new0 (TzInfo, 1);
+
+	curtime = time (NULL);
+	curzone = localtime (&curtime);
+
+#ifndef __sun
+	/* Currently this solution doesnt seem to work - I get that */
+	/* America/Phoenix uses daylight savings, which is wrong    */
+	tzinfo->tzname_normal = g_strdup (curzone->tm_zone);
+	if (curzone->tm_isdst) 
+		tzinfo->tzname_daylight =
+			g_strdup (&curzone->tm_zone[curzone->tm_isdst]);
+	else
+		tzinfo->tzname_daylight = NULL;
+
+	tzinfo->utc_offset = curzone->tm_gmtoff;
+#else
+	tzinfo->tzname_normal = NULL;
+	tzinfo->tzname_daylight = NULL;
+	tzinfo->utc_offset = 0;
+#endif
+
+	tzinfo->daylight = curzone->tm_isdst;
+
+	if (tz_env_value)
+		setenv ("TZ", tz_env_value, 1);
+	else
+		unsetenv ("TZ");
+
+	g_free (tz_env_value);
+	
+	return tzinfo;
+}
+
+
+void
+tz_info_free (TzInfo *tzinfo)
+{
+	g_return_if_fail (tzinfo != NULL);
+	
+	if (tzinfo->tzname_normal) g_free (tzinfo->tzname_normal);
+	if (tzinfo->tzname_daylight) g_free (tzinfo->tzname_daylight);
+	g_free (tzinfo);
+}
+
+struct {
+	const char *orig;
+	const char *dest;
+} aliases[] = {
+	{ "Asia/Istanbul",  "Europe/Istanbul" },	/* Istanbul is in both Europe and Asia */
+	{ "Europe/Nicosia", "Asia/Nicosia" },		/* Ditto */
+	{ "EET",            "Europe/Istanbul" },	/* Same tz as the 2 above */
+	{ "HST",            "Pacific/Honolulu" },
+	{ "WET",            "Europe/Brussels" },	/* Other name for the mainland Europe tz */
+	{ "CET",            "Europe/Brussels" },	/* ditto */
+	{ "MET",            "Europe/Brussels" },
+	{ "Etc/Zulu",       "Etc/GMT" },
+	{ "Etc/UTC",        "Etc/GMT" },
+	{ "GMT",            "Etc/GMT" },
+	{ "Greenwich",      "Etc/GMT" },
+	{ "Etc/UCT",        "Etc/GMT" },
+	{ "Etc/GMT0",       "Etc/GMT" },
+	{ "Etc/GMT+0",      "Etc/GMT" },
+	{ "Etc/GMT-0",      "Etc/GMT" },
+	{ "Etc/Universal",  "Etc/GMT" },
+	{ "PST8PDT",        "America/Los_Angeles" },	/* Other name for the Atlantic tz */
+	{ "EST",            "America/New_York" },	/* Other name for the Eastern tz */
+	{ "EST5EDT",        "America/New_York" },	/* ditto */
+	{ "CST6CDT",        "America/Chicago" },	/* Other name for the Central tz */
+	{ "MST",            "America/Denver" },		/* Other name for the mountain tz */
+	{ "MST7MDT",        "America/Denver" },		/* ditto */
+};
+
+static gboolean
+compare_timezones (const char *a,
+		   const char *b)
+{
+	if (g_str_equal (a, b))
+		return TRUE;
+	if (strchr (b, '/') == NULL) {
+		char *prefixed;
+
+		prefixed = g_strdup_printf ("/%s", b);
+		if (g_str_has_suffix (a, prefixed)) {
+			g_free (prefixed);
+			return TRUE;
+		}
+		g_free (prefixed);
+	}
+
+	return FALSE;
+}
+
+char *
+tz_info_get_clean_name (TzDB *tz_db,
+			const char *tz)
+{
+	char *ret;
+	const char *timezone;
+	guint i;
+	gboolean replaced;
+
+	/* Remove useless prefixes */
+	if (g_str_has_prefix (tz, "right/"))
+		tz = tz + strlen ("right/");
+	else if (g_str_has_prefix (tz, "posix/"))
+		tz = tz + strlen ("posix/");
+
+	/* Here start the crazies */
+	replaced = FALSE;
+
+	for (i = 0; i < G_N_ELEMENTS (aliases); i++) {
+		if (compare_timezones (tz, aliases[i].orig)) {
+			replaced = TRUE;
+			timezone = aliases[i].dest;
+			break;
+		}
+	}
+
+	/* Try again! */
+	if (!replaced) {
+		/* Ignore crazy solar times from the '80s */
+		if (g_str_has_prefix (tz, "Asia/Riyadh") ||
+		    g_str_has_prefix (tz, "Mideast/Riyadh")) {
+			timezone = "Asia/Riyadh";
+			replaced = TRUE;
+		}
+	}
+
+	if (!replaced)
+		timezone = tz;
+
+	ret = g_hash_table_lookup (tz_db->backward, timezone);
+	if (ret == NULL)
+		return g_strdup (timezone);
+	return g_strdup (ret);
+}
+
+/* ----------------- *
+ * Private functions *
+ * ----------------- */
+
+static gchar *
+tz_data_file_get (void)
+{
+	gchar *file;
+
+	file = g_strdup (TZ_DATA_FILE);
+
+	return file;
+}
+
+static float
+convert_pos (gchar *pos, int digits)
+{
+	gchar whole[10];
+	gchar *fraction;
+	gint i;
+	float t1, t2;
+	
+	if (!pos || strlen(pos) < 4 || digits > 9) return 0.0;
+	
+	for (i = 0; i < digits + 1; i++) whole[i] = pos[i];
+	whole[i] = '\0';
+	fraction = pos + digits + 1;
+
+	t1 = g_strtod (whole, NULL);
+	t2 = g_strtod (fraction, NULL);
+
+	if (t1 >= 0.0) return t1 + t2/pow (10.0, strlen(fraction));
+	else return t1 - t2/pow (10.0, strlen(fraction));
+}
+
+
+#if 0
+
+/* Currently not working */
+static void
+free_tzdata (TzLocation *tz)
+{
+	
+	if (tz->country)
+	  g_free(tz->country);
+	if (tz->zone)
+	  g_free(tz->zone);
+	if (tz->comment)
+	  g_free(tz->comment);
+	
+	g_free(tz);
+}
+#endif
+
+
+static int
+compare_country_names (const void *a, const void *b)
+{
+	const TzLocation *tza = * (TzLocation **) a;
+	const TzLocation *tzb = * (TzLocation **) b;
+	
+	return strcmp (tza->zone, tzb->zone);
+}
+
+
+static void
+sort_locations_by_country (GPtrArray *locations)
+{
+	qsort (locations->pdata, locations->len, sizeof (gpointer),
+	       compare_country_names);
+}
+
+static void
+load_backward_tz (TzDB *tz_db)
+{
+  GError *error = NULL;
+  char **lines, *contents;
+  guint i;
+
+  tz_db->backward = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+  if (g_file_get_contents (GNOMECC_DATA_DIR "/datetime/backward", &contents, NULL, &error) == FALSE)
+    {
+      g_warning ("Failed to load 'backward' file: %s", error->message);
+      return;
+    }
+  lines = g_strsplit (contents, "\n", -1);
+  g_free (contents);
+  for (i = 0; lines[i] != NULL; i++)
+    {
+      char **items;
+      guint j;
+      char *real, *alias;
+
+      if (g_ascii_strncasecmp (lines[i], "Link\t", 5) != 0)
+        continue;
+
+      items = g_strsplit (lines[i], "\t", -1);
+      real = NULL;
+      alias = NULL;
+      /* Skip the "Link" part */
+      for (j = 1; items[j] != NULL; j++)
+        {
+          if (items[j][0] == '\0')
+            continue;
+          if (real == NULL)
+            {
+              real = items[j];
+              continue;
+            }
+          alias = items[j];
+          break;
+        }
+
+      if (real == NULL || alias == NULL)
+        g_warning ("Could not parse line: %s", lines[i]);
+
+      /* We don't need more than one name for it */
+      if (g_str_equal (real, "Etc/UTC") ||
+          g_str_equal (real, "Etc/UCT"))
+        real = "Etc/GMT";
+
+      g_hash_table_insert (tz_db->backward, g_strdup (alias), g_strdup (real));
+      g_strfreev (items);
+    }
+  g_strfreev (lines);
+}
+
diff --git a/plugins/datetime/tz.h b/plugins/datetime/tz.h
new file mode 100644
index 0000000..ab5535c
--- /dev/null
+++ b/plugins/datetime/tz.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Generic timezone utilities.
+ *
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Authors: Hans Petter Jansson 
+ * 
+ * Largely based on Michael Fulbright's work on Anaconda.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 _E_TZ_H
+#define _E_TZ_H
+
+#include 
+
+#ifndef __sun
+#  define TZ_DATA_FILE "/usr/share/zoneinfo/zone.tab"
+#else
+#  define TZ_DATA_FILE "/usr/share/lib/zoneinfo/tab/zone_sun.tab"
+#endif
+
+typedef struct _TzDB TzDB;
+typedef struct _TzLocation TzLocation;
+typedef struct _TzInfo TzInfo;
+
+
+struct _TzDB
+{
+	GPtrArray  *locations;
+	GHashTable *backward;
+};
+
+struct _TzLocation
+{
+	gchar *country;
+	gdouble latitude;
+	gdouble longitude;
+	gchar *zone;
+	gchar *comment;
+
+	gdouble dist; /* distance to clicked point for comparison */
+};
+
+/* see the glibc info page information on time zone information */
+/*  tzname_normal    is the default name for the timezone */
+/*  tzname_daylight  is the name of the zone when in daylight savings */
+/*  utc_offset       is offset in seconds from utc */
+/*  daylight         if non-zero then location obeys daylight savings */
+
+struct _TzInfo
+{
+	gchar *tzname_normal;
+	gchar *tzname_daylight;
+	glong utc_offset;
+	gint daylight;
+};
+
+
+TzDB      *tz_load_db                 (void);
+void       tz_db_free                 (TzDB *db);
+char *     tz_info_get_clean_name     (TzDB *tz_db,
+				       const char *tz);
+GPtrArray *tz_get_locations           (TzDB *db);
+void       tz_location_get_position   (TzLocation *loc,
+				       double *longitude, double *latitude);
+void       tz_location_free           (TzLocation *loc);
+char      *tz_location_get_country    (TzLocation *loc);
+gchar     *tz_location_get_zone       (TzLocation *loc);
+gchar     *tz_location_get_comment    (TzLocation *loc);
+glong      tz_location_get_utc_offset (TzLocation *loc);
+gint       tz_location_set_locally    (TzLocation *loc);
+TzInfo    *tz_info_from_location      (TzLocation *loc);
+void       tz_info_free               (TzInfo *tz_info);
+
+#endif
diff --git a/plugins/datetime/weather-tz.c b/plugins/datetime/weather-tz.c
new file mode 100644
index 0000000..f2d38d9
--- /dev/null
+++ b/plugins/datetime/weather-tz.c
@@ -0,0 +1,118 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Kalev Lember 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include "weather-tz.h"
+#include "tz.h"
+
+#include 
+
+static GList *
+location_get_cities (GWeatherLocation *parent_location)
+{
+        GList *cities = NULL;
+        GWeatherLocation *child = NULL;
+
+        while ((child = gweather_location_next_child (parent_location, child))) {
+                if (gweather_location_get_level (child) == GWEATHER_LOCATION_CITY) {
+                        cities = g_list_prepend (cities, g_object_ref (child));
+                } else {
+                        cities = g_list_concat (cities,
+                                                location_get_cities (child));
+                }
+        }
+
+        return cities;
+}
+
+static gboolean
+weather_location_has_timezone (GWeatherLocation *loc)
+{
+        return gweather_location_get_timezone (loc) != NULL;
+}
+
+/**
+ * load_timezones:
+ * @cities: a list of #GWeatherLocation
+ *
+ * Returns: a list of #TzLocation
+ */
+static GList *
+load_timezones (GList *cities)
+{
+        GList *l;
+        GList *tz_locations = NULL;
+
+        for (l = cities; l; l = l->next) {
+                TzLocation *loc;
+                const gchar *country;
+                const gchar *timezone_id;
+                GTimeZone *tz;
+                gdouble latitude;
+                gdouble longitude;
+
+                if (!gweather_location_has_coords (l->data) ||
+                    !weather_location_has_timezone (l->data)) {
+                        g_debug ("Incomplete GWeather location entry: (%s) %s",
+                                 gweather_location_get_country (l->data),
+                                 gweather_location_get_city_name (l->data));
+                        continue;
+                }
+
+                country = gweather_location_get_country (l->data);
+                tz = gweather_location_get_timezone (l->data);
+                timezone_id = g_time_zone_get_identifier (tz);
+                gweather_location_get_coords (l->data,
+                                              &latitude,
+                                              &longitude);
+
+                loc = g_new0 (TzLocation, 1);
+                loc->country = g_strdup (country);
+                loc->latitude = latitude;
+                loc->longitude = longitude;
+                loc->zone = g_strdup (timezone_id);
+                loc->comment = NULL;
+
+                tz_locations = g_list_prepend (tz_locations, loc);
+        }
+
+        return tz_locations;
+}
+
+GList *
+weather_tz_db_get_locations (const gchar *country_code)
+{
+        g_autoptr(GWeatherLocation) world = NULL;
+        g_autoptr(GWeatherLocation) country = NULL;
+        g_autolist(GWeatherLocation) cities = NULL;
+        GList *tz_locations;
+
+        world = gweather_location_get_world ();
+
+        country = gweather_location_find_by_country_code (world, country_code);
+
+        if (!country)
+                return NULL;
+
+        cities = location_get_cities (country);
+        tz_locations = load_timezones (cities);
+
+        return tz_locations;
+}
diff --git a/plugins/datetime/weather-tz.h b/plugins/datetime/weather-tz.h
new file mode 100644
index 0000000..15b1571
--- /dev/null
+++ b/plugins/datetime/weather-tz.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Kalev Lember 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __WEATHER_TZ_H
+#define __WEATHER_TZ_H
+
+#include 
+
+GList           *weather_tz_db_get_locations    (const char *country);
+
+#endif /* __WEATHER_TZ_H */
diff --git a/plugins/gsd.service.in b/plugins/gsd.service.in
new file mode 100644
index 0000000..c47a676
--- /dev/null
+++ b/plugins/gsd.service.in
@@ -0,0 +1,26 @@
+[Unit]
+Description=@description@ service
+CollectMode=inactive-or-failed
+RefuseManualStart=true
+RefuseManualStop=true
+
+After=gnome-session-initialized.target
+
+# Requisite/PartOf means the dependency is not loaded automatically.
+# The ordering here also implies Before=gnome-session.target
+Requisite=@plugin_dbus_name@.target
+PartOf=@plugin_dbus_name@.target
+Before=@plugin_dbus_name@.target
+
+@plugin_gate_units_section@
+
+[Service]
+Slice=session.slice
+Type=dbus
+ExecStart=@libexecdir@/gsd-@plugin_name@
+Restart=on-failure
+BusName=@plugin_dbus_name@
+TimeoutStopSec=5
+# We cannot use OnFailure as e.g. dependency failures are normal
+# https://github.com/systemd/systemd/issues/12352
+ExecStopPost=@libexecdir@/gnome-session-ctl --exec-stop-check
diff --git a/plugins/gsd.target.in b/plugins/gsd.target.in
new file mode 100644
index 0000000..137df20
--- /dev/null
+++ b/plugins/gsd.target.in
@@ -0,0 +1,12 @@
+[Unit]
+Description=@description@ target
+CollectMode=inactive-or-failed
+
+# Pull in the service
+Wants=@plugin_dbus_name@.service
+
+# Require GNOME session and specify startup ordering
+Requisite=gnome-session-initialized.target
+After=gnome-session-initialized.target
+PartOf=gnome-session-initialized.target
+Before=gnome-session.target
diff --git a/plugins/housekeeping/gsd-disk-space-helper.c b/plugins/housekeeping/gsd-disk-space-helper.c
new file mode 100644
index 0000000..56e054c
--- /dev/null
+++ b/plugins/housekeeping/gsd-disk-space-helper.c
@@ -0,0 +1,159 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ * vim: set et sw=8 ts=8:
+ *
+ * Copyright (c) 2008, Novell, Inc.
+ * Copyright (c) 2012, Red Hat, Inc.
+ *
+ * Authors: Vincent Untz 
+ * Bastien Nocera 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+
+#include "gsd-disk-space-helper.h"
+
+gboolean
+gsd_should_ignore_unix_mount (GUnixMountEntry *mount)
+{
+        const char *fs, *device;
+        g_autofree char *label = NULL;
+        guint i;
+
+        /* This is borrowed from GLib and used as a way to determine
+         * which mounts we should ignore by default. GLib doesn't
+         * expose this in a way that allows it to be used for this
+         * purpose
+         */
+
+         /* We also ignore network filesystems */
+#if !GLIB_CHECK_VERSION(2, 56, 0)
+        const gchar *ignore_fs[] = {
+                "adfs",
+                "afs",
+                "auto",
+                "autofs",
+                "autofs4",
+                "cgroup",
+                "configfs",
+                "cxfs",
+                "debugfs",
+                "devfs",
+                "devpts",
+                "devtmpfs",
+                "ecryptfs",
+                "fdescfs",
+                "fusectl",
+                "gfs",
+                "gfs2",
+                "gpfs",
+                "hugetlbfs",
+                "kernfs",
+                "linprocfs",
+                "linsysfs",
+                "lustre",
+                "lustre_lite",
+                "mfs",
+                "mqueue",
+                "ncpfs",
+                "nfsd",
+                "nullfs",
+                "ocfs2",
+                "overlay",
+                "proc",
+                "procfs",
+                "pstore",
+                "ptyfs",
+                "rootfs",
+                "rpc_pipefs",
+                "securityfs",
+                "selinuxfs",
+                "sysfs",
+                "tmpfs",
+                "usbfs",
+                "zfs",
+                NULL
+        };
+#endif  /* GLIB < 2.56.0 */
+        const gchar *ignore_network_fs[] = {
+                "cifs",
+                "nfs",
+                "nfs4",
+                "smbfs",
+                NULL
+        };
+        const gchar *ignore_devices[] = {
+                "none",
+                "sunrpc",
+                "devpts",
+                "nfsd",
+                "/dev/loop",
+                "/dev/vn",
+                NULL
+        };
+        const gchar *ignore_labels[] = {
+                "RETRODE",
+                NULL
+        };
+
+        fs = g_unix_mount_get_fs_type (mount);
+#if GLIB_CHECK_VERSION(2, 56, 0)
+        if (g_unix_is_system_fs_type (fs))
+                return TRUE;
+#else
+        for (i = 0; ignore_fs[i] != NULL; i++)
+                if (g_str_equal (ignore_fs[i], fs))
+                        return TRUE;
+#endif
+        for (i = 0; ignore_network_fs[i] != NULL; i++)
+                if (g_str_equal (ignore_network_fs[i], fs))
+                        return TRUE;
+
+        device = g_unix_mount_get_device_path (mount);
+        for (i = 0; ignore_devices[i] != NULL; i++)
+                if (g_str_equal (ignore_devices[i], device))
+                        return TRUE;
+
+        label = g_unix_mount_guess_name (mount);
+        for (i = 0; ignore_labels[i] != NULL; i++)
+                if (g_str_equal (ignore_labels[i], label))
+                        return TRUE;
+
+        return FALSE;
+}
+
+/* Used in gnome-control-center's info panel */
+gboolean
+gsd_is_removable_mount (GUnixMountEntry *mount)
+{
+        const char *mount_path;
+        char *path;
+
+        mount_path = g_unix_mount_get_mount_path (mount);
+        if (mount_path == NULL)
+                return FALSE;
+
+        path = g_strdup_printf ("/run/media/%s", g_get_user_name ());
+        if (g_str_has_prefix (mount_path, path)) {
+                g_free (path);
+                return TRUE;
+        }
+        g_free (path);
+        return FALSE;
+}
diff --git a/plugins/housekeeping/gsd-disk-space-helper.h b/plugins/housekeeping/gsd-disk-space-helper.h
new file mode 100644
index 0000000..3898359
--- /dev/null
+++ b/plugins/housekeeping/gsd-disk-space-helper.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ * vim: set et sw=8 ts=8:
+ *
+ * Copyright (c) 2008, Novell, Inc.
+ * Copyright (c) 2012, Red Hat, Inc.
+ *
+ * Authors: Vincent Untz 
+ * Bastien Nocera 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_DISK_SPACE_HELPER_H
+#define __GSD_DISK_SPACE_HELPER_H
+
+#include 
+#include 
+
+G_BEGIN_DECLS
+
+gboolean gsd_should_ignore_unix_mount (GUnixMountEntry *mount);
+gboolean gsd_is_removable_mount       (GUnixMountEntry *mount);
+
+G_END_DECLS
+
+#endif /* __GSD_DISK_SPACE_HELPER_H */
diff --git a/plugins/housekeeping/gsd-disk-space-test.c b/plugins/housekeeping/gsd-disk-space-test.c
new file mode 100644
index 0000000..c0410f8
--- /dev/null
+++ b/plugins/housekeeping/gsd-disk-space-test.c
@@ -0,0 +1,48 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ * vim: set et sw=8 ts=8:
+ *
+ * Copyright (c) 2008, Novell, Inc.
+ *
+ * Authors: Vincent Untz 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+#include 
+#include 
+#include "gsd-disk-space.h"
+
+int
+main (int    argc,
+      char **argv)
+{
+        GMainLoop *loop;
+
+        gtk_init (&argc, &argv);
+        notify_init ("gsd-disk-space-test");
+
+        loop = g_main_loop_new (NULL, FALSE);
+
+        gsd_ldsm_setup (TRUE);
+
+        g_main_loop_run (loop);
+
+        gsd_ldsm_clean ();
+        g_main_loop_unref (loop);
+
+        return 0;
+}
+
diff --git a/plugins/housekeeping/gsd-disk-space.c b/plugins/housekeeping/gsd-disk-space.c
new file mode 100644
index 0000000..c5ae8f6
--- /dev/null
+++ b/plugins/housekeeping/gsd-disk-space.c
@@ -0,0 +1,1081 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ * vim: set et sw=8 ts=8:
+ *
+ * Copyright (c) 2008, Novell, Inc.
+ *
+ * Authors: Vincent Untz 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "gsd-disk-space.h"
+#include "gsd-disk-space-helper.h"
+
+#define GIGABYTE                   1024 * 1024 * 1024
+
+#define CHECK_EVERY_X_SECONDS      60
+
+#define DISK_SPACE_ANALYZER        "baobab"
+
+#define SETTINGS_HOUSEKEEPING_DIR     "org.gnome.settings-daemon.plugins.housekeeping"
+#define SETTINGS_FREE_PC_NOTIFY_KEY   "free-percent-notify"
+#define SETTINGS_FREE_PC_NOTIFY_AGAIN_KEY "free-percent-notify-again"
+#define SETTINGS_FREE_SIZE_NO_NOTIFY  "free-size-gb-no-notify"
+#define SETTINGS_MIN_NOTIFY_PERIOD    "min-notify-period"
+#define SETTINGS_IGNORE_PATHS         "ignore-paths"
+
+#define PRIVACY_SETTINGS              "org.gnome.desktop.privacy"
+#define SETTINGS_PURGE_TRASH          "remove-old-trash-files"
+#define SETTINGS_PURGE_TEMP_FILES     "remove-old-temp-files"
+#define SETTINGS_PURGE_AFTER          "old-files-age"
+
+typedef struct
+{
+        GUnixMountEntry *mount;
+        struct statvfs buf;
+        time_t notify_time;
+} LdsmMountInfo;
+
+static GHashTable        *ldsm_notified_hash = NULL;
+static unsigned int       ldsm_timeout_id = 0;
+static GUnixMountMonitor *ldsm_monitor = NULL;
+static double             free_percent_notify = 0.05;
+static double             free_percent_notify_again = 0.01;
+static unsigned int       free_size_gb_no_notify = 2;
+static unsigned int       min_notify_period = 10;
+static GSList            *ignore_paths = NULL;
+static GSettings         *settings = NULL;
+static GSettings         *privacy_settings = NULL;
+static NotifyNotification *notification = NULL;
+
+static guint64           *time_read;
+
+static gboolean           purge_trash;
+static gboolean           purge_temp_files;
+static guint              purge_after;
+static guint              purge_trash_id = 0;
+static guint              purge_temp_id = 0;
+
+static gchar*
+ldsm_get_fs_id_for_path (const gchar *path)
+{
+        GFile *file;
+        GFileInfo *fileinfo;
+        gchar *attr_id_fs;
+
+        file = g_file_new_for_path (path);
+        fileinfo = g_file_query_info (file, G_FILE_ATTRIBUTE_ID_FILESYSTEM, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL);
+        if (fileinfo) {
+                attr_id_fs = g_strdup (g_file_info_get_attribute_string (fileinfo, G_FILE_ATTRIBUTE_ID_FILESYSTEM));
+                g_object_unref (fileinfo);
+        } else {
+                attr_id_fs = NULL;
+        }
+
+        g_object_unref (file);
+
+        return attr_id_fs;
+}
+
+static gboolean
+ldsm_mount_has_trash (const char *path)
+{
+        const gchar *user_data_dir;
+        gchar *user_data_attr_id_fs;
+        gchar *path_attr_id_fs;
+        gboolean mount_uses_user_trash = FALSE;
+        gchar *trash_files_dir;
+        gboolean has_trash = FALSE;
+        GDir *dir;
+
+        user_data_dir = g_get_user_data_dir ();
+        user_data_attr_id_fs = ldsm_get_fs_id_for_path (user_data_dir);
+
+        path_attr_id_fs = ldsm_get_fs_id_for_path (path);
+
+        if (g_strcmp0 (user_data_attr_id_fs, path_attr_id_fs) == 0) {
+                /* The volume that is low on space is on the same volume as our home
+                 * directory. This means the trash is at $XDG_DATA_HOME/Trash,
+                 * not at the root of the volume which is full.
+                 */
+                mount_uses_user_trash = TRUE;
+        }
+
+        g_free (user_data_attr_id_fs);
+        g_free (path_attr_id_fs);
+
+        /* I can't think of a better way to find out if a volume has any trash. Any suggestions? */
+        if (mount_uses_user_trash) {
+                trash_files_dir = g_build_filename (g_get_user_data_dir (), "Trash", "files", NULL);
+        } else {
+                gchar *uid;
+
+                uid = g_strdup_printf ("%d", getuid ());
+                trash_files_dir = g_build_filename (path, ".Trash", uid, "files", NULL);
+                if (!g_file_test (trash_files_dir, G_FILE_TEST_IS_DIR)) {
+                        gchar *trash_dir;
+
+                        g_free (trash_files_dir);
+                        trash_dir = g_strdup_printf (".Trash-%s", uid);
+                        trash_files_dir = g_build_filename (path, trash_dir, "files", NULL);
+                        g_free (trash_dir);
+                        if (!g_file_test (trash_files_dir, G_FILE_TEST_IS_DIR)) {
+                                g_free (trash_files_dir);
+                                g_free (uid);
+                                return has_trash;
+                        }
+                }
+                g_free (uid);
+        }
+
+        dir = g_dir_open (trash_files_dir, 0, NULL);
+        if (dir) {
+                if (g_dir_read_name (dir))
+                        has_trash = TRUE;
+                g_dir_close (dir);
+        }
+
+        g_free (trash_files_dir);
+
+        return has_trash;
+}
+
+static void
+ldsm_analyze_path (const gchar *path)
+{
+        const gchar *argv[] = { DISK_SPACE_ANALYZER, path, NULL };
+
+        g_spawn_async (NULL, (gchar **) argv, NULL, G_SPAWN_SEARCH_PATH,
+                        NULL, NULL, NULL, NULL);
+}
+
+static void
+ignore_callback (NotifyNotification *n,
+                 const char         *action)
+{
+        g_assert (action != NULL);
+        g_assert (strcmp (action, "ignore") == 0);
+
+        /* Do nothing */
+
+        notify_notification_close (n, NULL);
+}
+
+static void
+examine_callback (NotifyNotification *n,
+                  const char         *action,
+                  const char         *path)
+{
+        g_assert (action != NULL);
+        g_assert (strcmp (action, "examine") == 0);
+
+        ldsm_analyze_path (path);
+
+        notify_notification_close (n, NULL);
+}
+
+static gboolean
+should_purge_file (GFile        *file,
+                   GCancellable *cancellable,
+                   GDateTime    *old)
+{
+        GFileInfo *info;
+        GDateTime *date;
+        gboolean should_purge;
+
+        should_purge = FALSE;
+
+        info = g_file_query_info (file,
+                                  G_FILE_ATTRIBUTE_TRASH_DELETION_DATE ","
+                                  G_FILE_ATTRIBUTE_UNIX_UID ","
+                                  G_FILE_ATTRIBUTE_TIME_CHANGED,
+                                  G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                  cancellable,
+                                  NULL);
+        if (!info)
+                return FALSE;
+
+        date = g_file_info_get_deletion_date (info);
+        if (date == NULL) {
+                guint uid;
+                guint64 ctime;
+
+                uid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID);
+                if (uid != getuid ()) {
+                        should_purge = FALSE;
+                        goto out;
+                }
+
+                ctime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CHANGED);
+                date = g_date_time_new_from_unix_local ((gint64) ctime);
+        }
+
+        should_purge = g_date_time_difference (old, date) >= 0;
+        g_date_time_unref (date);
+
+out:
+        g_object_unref (info);
+
+        return should_purge;
+}
+
+DeleteData *
+delete_data_new (GFile        *file,
+                 GCancellable *cancellable,
+                 GDateTime    *old,
+                 gboolean      dry_run,
+                 gboolean      trash,
+                 gint          depth,
+                 const char   *filesystem)
+{
+        DeleteData *data;
+
+        data = g_new (DeleteData, 1);
+        g_ref_count_init (&data->ref_count);
+        data->file = g_object_ref (file);
+        data->filesystem = g_strdup (filesystem);
+        data->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+        data->old = g_date_time_ref (old);
+        data->dry_run = dry_run;
+        data->trash = trash;
+        data->depth = depth;
+        data->name = g_file_get_parse_name (data->file);
+
+        return data;
+}
+
+char*
+get_filesystem (GFile *file)
+{
+        g_autoptr(GFileInfo) info = NULL;
+
+        info = g_file_query_info (file,
+                                  G_FILE_ATTRIBUTE_ID_FILESYSTEM,
+                                  G_FILE_QUERY_INFO_NONE,
+                                  NULL,
+                                  NULL);
+
+        return g_file_info_get_attribute_as_string (info,
+                                                    G_FILE_ATTRIBUTE_ID_FILESYSTEM);
+}
+
+static DeleteData *
+delete_data_ref (DeleteData *data)
+{
+        g_ref_count_inc (&data->ref_count);
+
+        return data;
+}
+
+void
+delete_data_unref (DeleteData *data)
+{
+        if (g_ref_count_dec (&data->ref_count)) {
+                g_object_unref (data->file);
+                if (data->cancellable)
+                        g_object_unref (data->cancellable);
+                g_date_time_unref (data->old);
+                g_free (data->name);
+                g_free (data->filesystem);
+                g_free (data);
+        }
+}
+
+static void
+delete_batch (GObject      *source,
+              GAsyncResult *res,
+              gpointer      user_data)
+{
+        GFileEnumerator *enumerator = G_FILE_ENUMERATOR (source);
+        DeleteData *data = user_data;
+        const char *fs;
+        GList *files, *f;
+        GFile *child_file;
+        DeleteData *child;
+        GFileInfo *info;
+        GError *error = NULL;
+
+        files = g_file_enumerator_next_files_finish (enumerator, res, &error);
+
+        g_debug ("GsdHousekeeping: purging %d children of %s", g_list_length (files), data->name);
+
+        if (files) {
+                for (f = files; f; f = f->next) {
+                        if (g_cancellable_is_cancelled (data->cancellable))
+                                break;
+                        info = f->data;
+
+                        fs = g_file_info_get_attribute_string (info,
+                                                               G_FILE_ATTRIBUTE_ID_FILESYSTEM);
+
+                        /* Do not consider the file if it is on a different file system.
+                         * Ignore data->filesystem if it is NULL (this only happens if
+                         * it is the toplevel trash directory). */
+                        if (data->filesystem && g_strcmp0 (fs, data->filesystem) != 0) {
+                                g_debug ("GsdHousekeeping: skipping file \"%s\" as it is on a different file system",
+                                         g_file_info_get_name (info));
+                                continue;
+                        }
+
+                        child_file = g_file_get_child (data->file, g_file_info_get_name (info));
+                        child = delete_data_new (child_file,
+                                                 data->cancellable,
+                                                 data->old,
+                                                 data->dry_run,
+                                                 data->trash,
+                                                 data->depth + 1,
+                                                 fs);
+                        delete_recursively_by_age (child);
+                        delete_data_unref (child);
+                        g_object_unref (child_file);
+                }
+                g_list_free_full (files, g_object_unref);
+                if (!g_cancellable_is_cancelled (data->cancellable)) {
+                        g_file_enumerator_next_files_async (enumerator, 20,
+                                                            0,
+                                                            data->cancellable,
+                                                            delete_batch,
+                                                            data);
+                        return;
+                }
+        }
+
+        g_file_enumerator_close (enumerator, data->cancellable, NULL);
+        g_object_unref (enumerator);
+
+        if (data->depth > 0 && !g_cancellable_is_cancelled (data->cancellable)) {
+                if ((data->trash && data->depth > 1) ||
+                     should_purge_file (data->file, data->cancellable, data->old)) {
+                        g_debug ("GsdHousekeeping: purging %s\n", data->name);
+                        if (!data->dry_run) {
+                                g_file_delete (data->file, data->cancellable, NULL);
+                        }
+                }
+        }
+        delete_data_unref (data);
+}
+
+static void
+delete_subdir (GObject      *source,
+               GAsyncResult *res,
+               gpointer      user_data)
+{
+        GFile *file = G_FILE (source);
+        DeleteData *data = user_data;
+        GFileEnumerator *enumerator;
+        GError *error = NULL;
+
+        g_debug ("GsdHousekeeping: purging %s in %s\n",
+                 data->trash ? "trash" : "temporary files", data->name);
+
+        enumerator = g_file_enumerate_children_finish (file, res, &error);
+        if (error) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY) &&
+                    !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+                    !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
+                        g_warning ("Failed to enumerate children of %s: %s\n", data->name, error->message);
+        }
+        if (enumerator) {
+                g_file_enumerator_next_files_async (enumerator, 20,
+                                                    0,
+                                                    data->cancellable,
+                                                    delete_batch,
+                                                    delete_data_ref (data));
+        } else if (data->depth > 0 && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY)) {
+                if ((data->trash && data->depth > 1) ||
+                     should_purge_file (data->file, data->cancellable, data->old)) {
+                        g_debug ("Purging %s leaf node", data->name);
+                        if (!data->dry_run) {
+                                g_file_delete (data->file, data->cancellable, NULL);
+                        }
+                }
+        }
+
+        if (error)
+                g_error_free (error);
+        delete_data_unref (data);
+}
+
+static void
+delete_subdir_check_symlink (GObject      *source,
+                             GAsyncResult *res,
+                             gpointer      user_data)
+{
+        GFile *file = G_FILE (source);
+        DeleteData *data = user_data;
+        GFileInfo *info;
+
+        info = g_file_query_info_finish (file, res, NULL);
+        if (!info) {
+                delete_data_unref (data);
+                return;
+        }
+
+        if (g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK) {
+                if (should_purge_file (data->file, data->cancellable, data->old)) {
+                        g_debug ("Purging %s leaf node", data->name);
+                        if (!data->dry_run) {
+                                g_file_delete (data->file, data->cancellable, NULL);
+                        }
+                }
+        } else if (g_strcmp0 (g_file_info_get_name (info), ".X11-unix") == 0) {
+                g_debug ("Skipping X11 socket directory");
+        } else {
+                g_file_enumerate_children_async (data->file,
+                                                 G_FILE_ATTRIBUTE_STANDARD_NAME ","
+                                                 G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+                                                 G_FILE_ATTRIBUTE_ID_FILESYSTEM,
+                                                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                                 0,
+                                                 data->cancellable,
+                                                 delete_subdir,
+                                                 delete_data_ref (data));
+        }
+        g_object_unref (info);
+        delete_data_unref (data);
+}
+
+void
+delete_recursively_by_age (DeleteData *data)
+{
+        if (data->trash && (data->depth == 1) &&
+            !should_purge_file (data->file, data->cancellable, data->old)) {
+                /* no need to recurse into trashed directories */
+                return;
+        }
+
+        g_file_query_info_async (data->file,
+                                 G_FILE_ATTRIBUTE_STANDARD_NAME ","
+                                 G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                 0,
+                                 data->cancellable,
+                                 delete_subdir_check_symlink,
+                                 delete_data_ref (data));
+}
+
+void
+gsd_ldsm_purge_trash (GDateTime *old)
+{
+        GFile *file;
+        DeleteData *data;
+
+        file = g_file_new_for_uri ("trash:");
+        data = delete_data_new (file, NULL, old, FALSE, TRUE, 0, NULL);
+        delete_recursively_by_age (data);
+        delete_data_unref (data);
+        g_object_unref (file);
+}
+
+void
+gsd_ldsm_purge_temp_files (GDateTime *old)
+{
+        DeleteData *data;
+        GFile *file;
+        char *filesystem;
+
+        /* Never clean temporary files on a sane (i.e. systemd managed)
+         * system. In that case systemd already ships
+         *   /usr/lib/tmpfiles.d/tmp.conf
+         * which does the trick in a much safer way.
+         * Ideally we can just drop this feature, I am not sure why it was
+         * added in the first place though, it does not really seem like a
+         * privacy feature (also, it was late in the release cycle).
+         *   https://en.wikipedia.org/wiki/Wikipedia:Chesterton%27s_fence
+         *
+         * This does the same as sd_booted without needing libsystemd.
+         */
+        if (g_file_test ("/run/systemd/system/", G_FILE_TEST_IS_DIR))
+                return;
+
+        file = g_file_new_for_path (g_get_tmp_dir ());
+        filesystem = get_filesystem (file);
+        data = delete_data_new (file, NULL, old, FALSE, FALSE, 0, filesystem);
+        g_free (filesystem);
+        delete_recursively_by_age (data);
+        delete_data_unref (data);
+        g_object_unref (file);
+
+        if (g_strcmp0 (g_get_tmp_dir (), "/var/tmp") != 0) {
+                file = g_file_new_for_path ("/var/tmp");
+                filesystem = get_filesystem (file);
+                data = delete_data_new (file, NULL, old, FALSE, FALSE, 0, filesystem);
+                g_free (filesystem);
+                delete_recursively_by_age (data);
+                delete_data_unref (data);
+                g_object_unref (file);
+        }
+
+        if (g_strcmp0 (g_get_tmp_dir (), "/tmp") != 0) {
+                file = g_file_new_for_path ("/tmp");
+                filesystem = get_filesystem (file);
+                data = delete_data_new (file, NULL, old, FALSE, FALSE, 0, filesystem);
+                g_free (filesystem);
+                delete_recursively_by_age (data);
+                delete_data_unref (data);
+                g_object_unref (file);
+        }
+}
+
+void
+gsd_ldsm_show_empty_trash (void)
+{
+        GFile *file;
+        GDateTime *old;
+        DeleteData *data;
+
+        old = g_date_time_new_now_local ();
+        file = g_file_new_for_uri ("trash:");
+        data = delete_data_new (file, NULL, old, TRUE, TRUE, 0, NULL);
+        g_object_unref (file);
+        g_date_time_unref (old);
+
+        delete_recursively_by_age (data);
+        delete_data_unref (data);
+}
+
+static gboolean
+ldsm_purge_trash_and_temp (gpointer data)
+{
+        GDateTime *now, *old;
+
+        now = g_date_time_new_now_local ();
+        old = g_date_time_add_days (now, - purge_after);
+
+        if (purge_trash) {
+                g_debug ("housekeeping: purge trash older than %u days", purge_after);
+                gsd_ldsm_purge_trash (old);
+        }
+        if (purge_temp_files) {
+                g_debug ("housekeeping: purge temp files older than %u days", purge_after);
+                gsd_ldsm_purge_temp_files (old);
+        }
+
+        g_date_time_unref (old);
+        g_date_time_unref (now);
+
+        return G_SOURCE_CONTINUE;
+}
+
+static void
+empty_trash_callback (NotifyNotification *n,
+                      const char         *action)
+{
+        GDateTime *old;
+
+        g_assert (action != NULL);
+        g_assert (strcmp (action, "empty-trash") == 0);
+
+        old = g_date_time_new_now_local ();
+        gsd_ldsm_purge_trash (old);
+        g_date_time_unref (old);
+
+        notify_notification_close (n, NULL);
+}
+
+static void
+on_notification_closed (NotifyNotification *n)
+{
+        g_object_unref (notification);
+        notification = NULL;
+}
+
+static void
+ldsm_notify (const char *summary,
+             const char *body,
+             const char *mount_path)
+{
+        gchar *program;
+        gboolean has_disk_analyzer;
+        gboolean has_trash;
+
+        /* Don't show a notice if one is already displayed */
+        if (notification != NULL)
+                return;
+
+        notification = notify_notification_new (summary, body, "drive-harddisk-symbolic");
+        g_signal_connect (notification,
+                          "closed",
+                          G_CALLBACK (on_notification_closed),
+                          NULL);
+
+        notify_notification_set_app_name (notification, _("Disk Space"));
+        notify_notification_set_hint (notification, "transient", g_variant_new_boolean (TRUE));
+        notify_notification_set_urgency (notification, NOTIFY_URGENCY_CRITICAL);
+        notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
+        notify_notification_set_hint_string (notification, "desktop-entry", "org.gnome.baobab");
+
+        program = g_find_program_in_path (DISK_SPACE_ANALYZER);
+        has_disk_analyzer = (program != NULL);
+        g_free (program);
+
+        if (has_disk_analyzer) {
+                notify_notification_add_action (notification,
+                                                "examine",
+                                                _("Examine"),
+                                                (NotifyActionCallback) examine_callback,
+                                                g_strdup (mount_path),
+                                                g_free);
+        }
+
+        has_trash = ldsm_mount_has_trash (mount_path);
+
+        if (has_trash) {
+                notify_notification_add_action (notification,
+                                                "empty-trash",
+                                                _("Empty Trash"),
+                                                (NotifyActionCallback) empty_trash_callback,
+                                                NULL,
+                                                NULL);
+        }
+
+        notify_notification_add_action (notification,
+                                        "ignore",
+                                        _("Ignore"),
+                                        (NotifyActionCallback) ignore_callback,
+                                        NULL,
+                                        NULL);
+        notify_notification_set_category (notification, "device");
+
+        if (!notify_notification_show (notification, NULL)) {
+                g_warning ("failed to send disk space notification\n");
+        }
+}
+
+static void
+ldsm_notify_for_mount (LdsmMountInfo *mount,
+                       gboolean       multiple_volumes)
+{
+        gboolean has_trash;
+        gchar  *name;
+        gint64 free_space;
+        const gchar *path;
+        char *free_space_str;
+        char *summary;
+        char *body;
+
+        name = g_unix_mount_guess_name (mount->mount);
+        path = g_unix_mount_get_mount_path (mount->mount);
+        has_trash = ldsm_mount_has_trash (path);
+
+        free_space = (gint64) mount->buf.f_frsize * (gint64) mount->buf.f_bavail;
+        free_space_str = g_format_size (free_space);
+
+        if (multiple_volumes) {
+                summary = g_strdup_printf (_("Low Disk Space on “%s”"), name);
+                if (has_trash) {
+                        body = g_strdup_printf (_("The volume “%s” has only %s disk space remaining.  You may free up some space by emptying the trash."),
+                                                name,
+                                                free_space_str);
+                } else {
+                        body = g_strdup_printf (_("The volume “%s” has only %s disk space remaining."),
+                                                name,
+                                                free_space_str);
+                }
+        } else {
+                summary = g_strdup (_("Low Disk Space"));
+                if (has_trash) {
+                        body = g_strdup_printf (_("This computer has only %s disk space remaining.  You may free up some space by emptying the trash."),
+                                                free_space_str);
+                } else {
+                        body = g_strdup_printf (_("This computer has only %s disk space remaining."),
+                                                free_space_str);
+                }
+        }
+
+        ldsm_notify (summary, body, path);
+
+        g_free (free_space_str);
+        g_free (summary);
+        g_free (body);
+        g_free (name);
+}
+
+static gboolean
+ldsm_mount_has_space (LdsmMountInfo *mount)
+{
+        gdouble free_space;
+
+        free_space = (double) mount->buf.f_bavail / (double) mount->buf.f_blocks;
+        /* enough free space, nothing to do */
+        if (free_space > free_percent_notify)
+                return TRUE;
+
+        if (((gint64) mount->buf.f_frsize * (gint64) mount->buf.f_bavail) > ((gint64) free_size_gb_no_notify * GIGABYTE))
+                return TRUE;
+
+        /* If we got here, then this volume is low on space */
+        return FALSE;
+}
+
+static gboolean
+ldsm_mount_is_virtual (LdsmMountInfo *mount)
+{
+        if (mount->buf.f_blocks == 0) {
+                /* Filesystems with zero blocks are virtual */
+                return TRUE;
+        }
+
+        return FALSE;
+}
+
+static gint
+ldsm_ignore_path_compare (gconstpointer a,
+                          gconstpointer b)
+{
+        return g_strcmp0 ((const gchar *)a, (const gchar *)b);
+}
+
+static gboolean
+ldsm_mount_is_user_ignore (const gchar *path)
+{
+        if (g_slist_find_custom (ignore_paths, path, (GCompareFunc) ldsm_ignore_path_compare) != NULL)
+                return TRUE;
+        else
+                return FALSE;
+}
+
+
+static void
+ldsm_free_mount_info (gpointer data)
+{
+        LdsmMountInfo *mount = data;
+
+        g_return_if_fail (mount != NULL);
+
+        g_unix_mount_free (mount->mount);
+        g_free (mount);
+}
+
+static void
+ldsm_maybe_warn_mounts (GList *mounts,
+                        gboolean multiple_volumes)
+{
+        GList *l;
+        gboolean done = FALSE;
+
+        for (l = mounts; l != NULL; l = l->next) {
+                LdsmMountInfo *mount_info = l->data;
+                LdsmMountInfo *previous_mount_info;
+                gdouble free_space;
+                gdouble previous_free_space;
+                time_t curr_time;
+                const gchar *path;
+                gboolean show_notify;
+
+                if (done) {
+                        /* Don't show any more dialogs if the user took action with the last one. The user action
+                         * might free up space on multiple volumes, making the next dialog redundant.
+                         */
+                        ldsm_free_mount_info (mount_info);
+                        continue;
+                }
+
+                path = g_unix_mount_get_mount_path (mount_info->mount);
+
+                previous_mount_info = g_hash_table_lookup (ldsm_notified_hash, path);
+                if (previous_mount_info != NULL)
+                        previous_free_space = (gdouble) previous_mount_info->buf.f_bavail / (gdouble) previous_mount_info->buf.f_blocks;
+
+                free_space = (gdouble) mount_info->buf.f_bavail / (gdouble) mount_info->buf.f_blocks;
+
+                if (previous_mount_info == NULL) {
+                        /* We haven't notified for this mount yet */
+                        show_notify = TRUE;
+                        mount_info->notify_time = time (NULL);
+                        g_hash_table_replace (ldsm_notified_hash, g_strdup (path), mount_info);
+                } else if ((previous_free_space - free_space) > free_percent_notify_again) {
+                        /* We've notified for this mount before and free space has decreased sufficiently since last time to notify again */
+                        curr_time = time (NULL);
+                        if (difftime (curr_time, previous_mount_info->notify_time) > (gdouble)(min_notify_period * 60)) {
+                                show_notify = TRUE;
+                                mount_info->notify_time = curr_time;
+                        } else {
+                                /* It's too soon to show the dialog again. However, we still replace the LdsmMountInfo
+                                 * struct in the hash table, but give it the notfiy time from the previous dialog.
+                                 * This will stop the notification from reappearing unnecessarily as soon as the timeout expires.
+                                 */
+                                show_notify = FALSE;
+                                mount_info->notify_time = previous_mount_info->notify_time;
+                        }
+                        g_hash_table_replace (ldsm_notified_hash, g_strdup (path), mount_info);
+                } else {
+                        /* We've notified for this mount before, but the free space hasn't decreased sufficiently to notify again */
+                        ldsm_free_mount_info (mount_info);
+                        show_notify = FALSE;
+                }
+
+                if (show_notify) {
+                        ldsm_notify_for_mount (mount_info, multiple_volumes);
+                        done = TRUE;
+                }
+        }
+}
+
+static gboolean
+ldsm_check_all_mounts (gpointer data)
+{
+        GList *mounts;
+        GList *l;
+        GList *check_mounts = NULL;
+        GList *full_mounts = NULL;
+        guint number_of_mounts = 0;
+        gboolean multiple_volumes = FALSE;
+
+        /* We iterate through the static mounts in /etc/fstab first, seeing if
+         * they're mounted by checking if the GUnixMountPoint has a corresponding GUnixMountEntry.
+         * Iterating through the static mounts means we automatically ignore dynamically mounted media.
+         */
+        mounts = g_unix_mount_points_get (time_read);
+
+        for (l = mounts; l != NULL; l = l->next) {
+                GUnixMountPoint *mount_point = l->data;
+                GUnixMountEntry *mount;
+                LdsmMountInfo *mount_info;
+                const gchar *path;
+
+                path = g_unix_mount_point_get_mount_path (mount_point);
+                mount = g_unix_mount_at (path, time_read);
+                g_unix_mount_point_free (mount_point);
+                if (mount == NULL) {
+                        /* The GUnixMountPoint is not mounted */
+                        continue;
+                }
+
+                mount_info = g_new0 (LdsmMountInfo, 1);
+                mount_info->mount = mount;
+
+                path = g_unix_mount_get_mount_path (mount);
+
+                if (g_unix_mount_is_readonly (mount)) {
+                        ldsm_free_mount_info (mount_info);
+                        continue;
+                }
+
+                if (ldsm_mount_is_user_ignore (path)) {
+                        ldsm_free_mount_info (mount_info);
+                        continue;
+                }
+
+                if (gsd_should_ignore_unix_mount (mount)) {
+                        ldsm_free_mount_info (mount_info);
+                        continue;
+                }
+
+                if (statvfs (path, &mount_info->buf) != 0) {
+                        ldsm_free_mount_info (mount_info);
+                        continue;
+                }
+
+                if (ldsm_mount_is_virtual (mount_info)) {
+                        ldsm_free_mount_info (mount_info);
+                        continue;
+                }
+
+                check_mounts = g_list_prepend (check_mounts, mount_info);
+                number_of_mounts += 1;
+        }
+
+        g_list_free (mounts);
+
+        if (number_of_mounts > 1)
+                multiple_volumes = TRUE;
+
+        for (l = check_mounts; l != NULL; l = l->next) {
+                LdsmMountInfo *mount_info = l->data;
+
+                if (!ldsm_mount_has_space (mount_info)) {
+                        full_mounts = g_list_prepend (full_mounts, mount_info);
+                } else {
+                        g_hash_table_remove (ldsm_notified_hash, g_unix_mount_get_mount_path (mount_info->mount));
+                        ldsm_free_mount_info (mount_info);
+                }
+        }
+
+        ldsm_maybe_warn_mounts (full_mounts, multiple_volumes);
+
+        g_list_free (check_mounts);
+        g_list_free (full_mounts);
+
+        return TRUE;
+}
+
+static gboolean
+ldsm_is_hash_item_not_in_mounts (gpointer key,
+                                 gpointer value,
+                                 gpointer user_data)
+{
+        GList *l;
+
+        for (l = (GList *) user_data; l != NULL; l = l->next) {
+                GUnixMountEntry *mount = l->data;
+                const char *path;
+
+                path = g_unix_mount_get_mount_path (mount);
+
+                if (strcmp (path, key) == 0)
+                        return FALSE;
+        }
+
+        return TRUE;
+}
+
+static void
+ldsm_mounts_changed (GObject  *monitor,
+                     gpointer  data)
+{
+        GList *mounts;
+
+        /* remove the saved data for mounts that got removed */
+        mounts = g_unix_mounts_get (time_read);
+        g_hash_table_foreach_remove (ldsm_notified_hash,
+                                     ldsm_is_hash_item_not_in_mounts, mounts);
+        g_list_free_full (mounts, (GDestroyNotify) g_unix_mount_free);
+
+        /* check the status now, for the new mounts */
+        ldsm_check_all_mounts (NULL);
+
+        /* and reset the timeout */
+        if (ldsm_timeout_id)
+                g_source_remove (ldsm_timeout_id);
+        ldsm_timeout_id = g_timeout_add_seconds (CHECK_EVERY_X_SECONDS,
+                                                 ldsm_check_all_mounts, NULL);
+        g_source_set_name_by_id (ldsm_timeout_id, "[gnome-settings-daemon] ldsm_check_all_mounts");
+}
+
+static gboolean
+ldsm_is_hash_item_in_ignore_paths (gpointer key,
+                                   gpointer value,
+                                   gpointer user_data)
+{
+        return ldsm_mount_is_user_ignore (key);
+}
+
+static void
+gsd_ldsm_get_config (void)
+{
+        gchar **settings_list;
+
+        free_percent_notify = g_settings_get_double (settings, SETTINGS_FREE_PC_NOTIFY_KEY);
+        free_percent_notify_again = g_settings_get_double (settings, SETTINGS_FREE_PC_NOTIFY_AGAIN_KEY);
+
+        free_size_gb_no_notify = g_settings_get_int (settings, SETTINGS_FREE_SIZE_NO_NOTIFY);
+        min_notify_period = g_settings_get_int (settings, SETTINGS_MIN_NOTIFY_PERIOD);
+
+        if (ignore_paths != NULL) {
+                g_slist_foreach (ignore_paths, (GFunc) g_free, NULL);
+                g_clear_pointer (&ignore_paths, g_slist_free);
+        }
+
+        settings_list = g_settings_get_strv (settings, SETTINGS_IGNORE_PATHS);
+        if (settings_list != NULL) {
+                guint i;
+
+                for (i = 0; settings_list[i] != NULL; i++)
+                        ignore_paths = g_slist_prepend (ignore_paths, g_strdup (settings_list[i]));
+
+                /* Make sure we dont leave stale entries in ldsm_notified_hash */
+                g_hash_table_foreach_remove (ldsm_notified_hash,
+                                             ldsm_is_hash_item_in_ignore_paths, NULL);
+
+                g_strfreev (settings_list);
+        }
+
+        purge_trash = g_settings_get_boolean (privacy_settings, SETTINGS_PURGE_TRASH);
+        purge_temp_files = g_settings_get_boolean (privacy_settings, SETTINGS_PURGE_TEMP_FILES);
+        purge_after = g_settings_get_uint (privacy_settings, SETTINGS_PURGE_AFTER);
+}
+
+static void
+gsd_ldsm_update_config (GSettings *settings,
+                        const gchar *key,
+                        gpointer user_data)
+{
+        gsd_ldsm_get_config ();
+}
+
+void
+gsd_ldsm_setup (gboolean check_now)
+{
+        if (ldsm_notified_hash || ldsm_timeout_id || ldsm_monitor) {
+                g_warning ("Low disk space monitor already initialized.");
+                return;
+        }
+
+        ldsm_notified_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                    g_free,
+                                                    ldsm_free_mount_info);
+
+        settings = g_settings_new (SETTINGS_HOUSEKEEPING_DIR);
+        privacy_settings = g_settings_new (PRIVACY_SETTINGS);
+        gsd_ldsm_get_config ();
+        g_signal_connect (G_OBJECT (settings), "changed",
+                          G_CALLBACK (gsd_ldsm_update_config), NULL);
+
+        ldsm_monitor = g_unix_mount_monitor_get ();
+        g_signal_connect (ldsm_monitor, "mounts-changed",
+                          G_CALLBACK (ldsm_mounts_changed), NULL);
+
+        if (check_now)
+                ldsm_check_all_mounts (NULL);
+
+        ldsm_timeout_id = g_timeout_add_seconds (CHECK_EVERY_X_SECONDS,
+                                                 ldsm_check_all_mounts, NULL);
+        g_source_set_name_by_id (ldsm_timeout_id, "[gnome-settings-daemon] ldsm_check_all_mounts");
+
+        purge_trash_id = g_timeout_add_seconds (3600, ldsm_purge_trash_and_temp, NULL);
+        g_source_set_name_by_id (purge_trash_id, "[gnome-settings-daemon] ldsm_purge_trash_and_temp");
+}
+
+void
+gsd_ldsm_clean (void)
+{
+        if (purge_trash_id)
+                g_source_remove (purge_trash_id);
+        purge_trash_id = 0;
+
+        if (purge_temp_id)
+                g_source_remove (purge_temp_id);
+        purge_temp_id = 0;
+
+        if (ldsm_timeout_id)
+                g_source_remove (ldsm_timeout_id);
+        ldsm_timeout_id = 0;
+
+        g_clear_pointer (&ldsm_notified_hash, g_hash_table_destroy);
+        g_clear_object (&ldsm_monitor);
+        g_clear_object (&settings);
+        g_clear_object (&privacy_settings);
+        /* NotifyNotification::closed callback will drop reference */
+        if (notification != NULL)
+                notify_notification_close (notification, NULL);
+        g_slist_free_full (ignore_paths, g_free);
+        ignore_paths = NULL;
+}
+
diff --git a/plugins/housekeeping/gsd-disk-space.h b/plugins/housekeeping/gsd-disk-space.h
new file mode 100644
index 0000000..d90d36e
--- /dev/null
+++ b/plugins/housekeeping/gsd-disk-space.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ * vim: set et sw=8 ts=8:
+ *
+ * Copyright (c) 2008, Novell, Inc.
+ *
+ * Authors: Vincent Untz 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_DISK_SPACE_H
+#define __GSD_DISK_SPACE_H
+
+#include 
+
+G_BEGIN_DECLS
+
+typedef struct {
+        grefcount  ref_count;
+        GFile           *file;
+        GCancellable    *cancellable;
+        GDateTime       *old;
+        gboolean         dry_run;
+        gboolean         trash;
+        gchar           *name;
+        gchar           *filesystem;
+        gint             depth;
+} DeleteData;
+
+void delete_data_unref (DeleteData *data);
+DeleteData *delete_data_new (GFile        *file,
+                             GCancellable *cancellable,
+                             GDateTime    *old,
+                             gboolean      dry_run,
+                             gboolean      trash,
+                             gint          depth,
+                             const char   *filesystem);
+char* get_filesystem (GFile *file);
+
+void delete_recursively_by_age (DeleteData *data);
+
+void gsd_ldsm_setup (gboolean check_now);
+void gsd_ldsm_clean (void);
+
+/* for the test */
+void gsd_ldsm_show_empty_trash (void);
+void gsd_ldsm_purge_trash      (GDateTime *old);
+void gsd_ldsm_purge_temp_files (GDateTime *old);
+
+G_END_DECLS
+
+#endif /* __GSD_DISK_SPACE_H */
diff --git a/plugins/housekeeping/gsd-empty-trash-test.c b/plugins/housekeeping/gsd-empty-trash-test.c
new file mode 100644
index 0000000..6a2c37f
--- /dev/null
+++ b/plugins/housekeeping/gsd-empty-trash-test.c
@@ -0,0 +1,44 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ * vim: set et sw=8 ts=8:
+ *
+ * Copyright (c) 2011, Red Hat, Inc.
+ *
+ * Authors: Cosimo Cecchi 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+#include 
+#include "gsd-disk-space.h"
+
+int
+main (int    argc,
+      char **argv)
+{
+        GMainLoop *loop;
+
+        gtk_init (&argc, &argv);
+
+        loop = g_main_loop_new (NULL, FALSE);
+
+        gsd_ldsm_show_empty_trash ();
+        g_main_loop_run (loop);
+
+        g_main_loop_unref (loop);
+
+        return 0;
+}
+
diff --git a/plugins/housekeeping/gsd-housekeeping-manager.c b/plugins/housekeeping/gsd-housekeeping-manager.c
new file mode 100644
index 0000000..b291667
--- /dev/null
+++ b/plugins/housekeeping/gsd-housekeeping-manager.c
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2008 Michael J. Chudobiak 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+#include 
+#include 
+
+#include "gnome-settings-profile.h"
+#include "gsd-housekeeping-manager.h"
+#include "gsd-disk-space.h"
+#include "gsd-systemd-notify.h"
+
+
+/* General */
+#define INTERVAL_ONCE_A_DAY 24*60*60
+#define INTERVAL_TWO_MINUTES 2*60
+
+/* Thumbnail cleaner */
+#define THUMB_PREFIX "org.gnome.desktop.thumbnail-cache"
+
+#define THUMB_AGE_KEY "maximum-age"
+#define THUMB_SIZE_KEY "maximum-size"
+
+#define GSD_HOUSEKEEPING_DBUS_PATH "/org/gnome/SettingsDaemon/Housekeeping"
+
+static const gchar introspection_xml[] =
+""
+"  "
+"    "
+"    "
+"  "
+"";
+
+struct _GsdHousekeepingManager {
+        GObject          parent;
+
+        GSettings *settings;
+        guint long_term_cb;
+        guint short_term_cb;
+
+        GDBusNodeInfo   *introspection_data;
+        GDBusConnection *connection;
+        GCancellable    *bus_cancellable;
+        guint            name_id;
+
+        GsdSystemdNotify *systemd_notify;
+};
+
+static void     gsd_housekeeping_manager_class_init  (GsdHousekeepingManagerClass *klass);
+static void     gsd_housekeeping_manager_init        (GsdHousekeepingManager      *housekeeping_manager);
+
+G_DEFINE_TYPE (GsdHousekeepingManager, gsd_housekeeping_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+
+typedef struct {
+        glong now;
+        glong max_age;
+        goffset total_size;
+        goffset max_size;
+} PurgeData;
+
+
+typedef struct {
+        gint64  time;
+        char   *path;
+        glong   size;
+} ThumbData;
+
+
+static void
+thumb_data_free (gpointer data)
+{
+        ThumbData *info = data;
+
+        if (info) {
+                g_free (info->path);
+                g_free (info);
+        }
+}
+
+static GList *
+read_dir_for_purge (const char *path, GList *files)
+{
+        GFile           *read_path;
+        GFileEnumerator *enum_dir;
+        int              cannot_get_time = 0;
+
+        read_path = g_file_new_for_path (path);
+        enum_dir = g_file_enumerate_children (read_path,
+                                              G_FILE_ATTRIBUTE_STANDARD_NAME ","
+                                              G_FILE_ATTRIBUTE_TIME_ACCESS ","
+                                              G_FILE_ATTRIBUTE_TIME_MODIFIED ","
+                                              G_FILE_ATTRIBUTE_STANDARD_SIZE,
+                                              G_FILE_QUERY_INFO_NONE,
+                                              NULL,
+                                              NULL);
+
+        if (enum_dir != NULL) {
+                GFileInfo *info;
+                while ((info = g_file_enumerator_next_file (enum_dir, NULL, NULL)) != NULL) {
+                        const char *name;
+                        name = g_file_info_get_name (info);
+
+                        if (strlen (name) == 36 && strcmp (name + 32, ".png") == 0) {
+                                ThumbData *td;
+                                GFile     *entry;
+                                char      *entry_path;
+                                gint64     time;
+
+                                entry = g_file_get_child (read_path, name);
+                                entry_path = g_file_get_path (entry);
+                                g_object_unref (entry);
+
+                                // If atime is available, it should be no worse than mtime.
+                                // - Even if the file system is mounted with noatime, the atime and
+                                //   mtime will be set to the same value on file creation.
+                                // - Since the thumbnailer never edits thumbnails, and instead swaps
+                                //   in newly created temp files, atime will always be >= mtime.
+
+                                if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_ACCESS)) {
+                                    time = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS);
+                                } else if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED)) {
+                                    time = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+                                } else {
+                                    // Unlikely to get here, but be defensive
+                                    cannot_get_time += 1;
+                                    time = G_MAXINT64;
+                                }
+
+                                td = g_new0 (ThumbData, 1);
+                                td->path = entry_path;
+                                td->time = time;
+                                td->size = g_file_info_get_size (info);
+
+                                files = g_list_prepend (files, td);
+                        }
+                        g_object_unref (info);
+                }
+                g_object_unref (enum_dir);
+
+                if (cannot_get_time > 0) {
+                    g_warning ("Could not read atime or mtime on %d files in %s", cannot_get_time, path);
+                }
+        }
+        g_object_unref (read_path);
+
+        return files;
+}
+
+static void
+purge_old_thumbnails (ThumbData *info, PurgeData *purge_data)
+{
+        if ((purge_data->now - info->time) > purge_data->max_age) {
+                g_unlink (info->path);
+                info->size = 0;
+        } else {
+                purge_data->total_size += info->size;
+        }
+}
+
+static int
+sort_file_time (ThumbData *file1, ThumbData *file2)
+{
+        return file1->time - file2->time;
+}
+
+static char **
+get_thumbnail_dirs (void)
+{
+        GPtrArray *array;
+        char *path;
+
+        array = g_ptr_array_new ();
+
+        /* check new XDG cache */
+        path = g_build_filename (g_get_user_cache_dir (),
+                                 "thumbnails",
+                                 "normal",
+                                 NULL);
+        g_ptr_array_add (array, path);
+
+        path = g_build_filename (g_get_user_cache_dir (),
+                                 "thumbnails",
+                                 "large",
+                                 NULL);
+        g_ptr_array_add (array, path);
+
+        path = g_build_filename (g_get_user_cache_dir (),
+                                 "thumbnails",
+                                 "fail",
+                                 "gnome-thumbnail-factory",
+                                 NULL);
+        g_ptr_array_add (array, path);
+
+        /* cleanup obsolete locations too */
+        path = g_build_filename (g_get_home_dir (),
+                                 ".thumbnails",
+                                 "normal",
+                                 NULL);
+        g_ptr_array_add (array, path);
+
+        path = g_build_filename (g_get_home_dir (),
+                                 ".thumbnails",
+                                 "large",
+                                 NULL);
+        g_ptr_array_add (array, path);
+
+        path = g_build_filename (g_get_home_dir (),
+                                 ".thumbnails",
+                                 "fail",
+                                 "gnome-thumbnail-factory",
+                                 NULL);
+        g_ptr_array_add (array, path);
+
+        g_ptr_array_add (array, NULL);
+
+        return (char **) g_ptr_array_free (array, FALSE);
+}
+
+static void
+purge_thumbnail_cache (GsdHousekeepingManager *manager)
+{
+
+        char     **paths;
+        GList     *files;
+        PurgeData  purge_data;
+        GTimeVal   current_time;
+        guint      i;
+
+        g_debug ("housekeeping: checking thumbnail cache size and freshness");
+
+        purge_data.max_age = (glong) g_settings_get_int (manager->settings, THUMB_AGE_KEY) * 24 * 60 * 60;
+        purge_data.max_size = (goffset) g_settings_get_int (manager->settings, THUMB_SIZE_KEY) * 1024 * 1024;
+
+        /* if both are set to -1, we don't need to read anything */
+        if ((purge_data.max_age < 0) && (purge_data.max_size < 0))
+                return;
+
+        paths = get_thumbnail_dirs ();
+        files = NULL;
+        for (i = 0; paths[i] != NULL; i++)
+                files = read_dir_for_purge (paths[i], files);
+        g_strfreev (paths);
+
+        g_get_current_time (¤t_time);
+
+        purge_data.now = current_time.tv_sec;
+        purge_data.total_size = 0;
+
+        if (purge_data.max_age >= 0)
+                g_list_foreach (files, (GFunc) purge_old_thumbnails, &purge_data);
+
+        if ((purge_data.total_size > purge_data.max_size) && (purge_data.max_size >= 0)) {
+                GList *scan;
+                files = g_list_sort (files, (GCompareFunc) sort_file_time);
+                for (scan = files; scan && (purge_data.total_size > purge_data.max_size); scan = scan->next) {
+                        ThumbData *info = scan->data;
+                        g_unlink (info->path);
+                        purge_data.total_size -= info->size;
+                }
+        }
+
+        g_list_foreach (files, (GFunc) thumb_data_free, NULL);
+        g_list_free (files);
+}
+
+static gboolean
+do_cleanup (GsdHousekeepingManager *manager)
+{
+        purge_thumbnail_cache (manager);
+        return TRUE;
+}
+
+static gboolean
+do_cleanup_once (GsdHousekeepingManager *manager)
+{
+        do_cleanup (manager);
+        manager->short_term_cb = 0;
+        return FALSE;
+}
+
+static void
+do_cleanup_soon (GsdHousekeepingManager *manager)
+{
+        if (manager->short_term_cb == 0) {
+                g_debug ("housekeeping: will tidy up in 2 minutes");
+                manager->short_term_cb = g_timeout_add_seconds (INTERVAL_TWO_MINUTES,
+                                               (GSourceFunc) do_cleanup_once,
+                                               manager);
+                g_source_set_name_by_id (manager->short_term_cb, "[gnome-settings-daemon] do_cleanup_once");
+        }
+}
+
+static void
+settings_changed_callback (GSettings              *settings,
+                           const char             *key,
+                           GsdHousekeepingManager *manager)
+{
+        do_cleanup_soon (manager);
+}
+
+static void
+handle_method_call (GDBusConnection       *connection,
+                    const gchar           *sender,
+                    const gchar           *object_path,
+                    const gchar           *interface_name,
+                    const gchar           *method_name,
+                    GVariant              *parameters,
+                    GDBusMethodInvocation *invocation,
+                    gpointer               user_data)
+{
+        GDateTime *now;
+        now = g_date_time_new_now_local ();
+        if (g_strcmp0 (method_name, "EmptyTrash") == 0) {
+                gsd_ldsm_purge_trash (now);
+                g_dbus_method_invocation_return_value (invocation, NULL);
+        }
+        else if (g_strcmp0 (method_name, "RemoveTempFiles") == 0) {
+                gsd_ldsm_purge_temp_files (now);
+                g_dbus_method_invocation_return_value (invocation, NULL);
+        }
+        g_date_time_unref (now);
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+        handle_method_call,
+        NULL, /* Get Property */
+        NULL, /* Set Property */
+};
+
+static void
+on_bus_gotten (GObject                *source_object,
+               GAsyncResult           *res,
+               GsdHousekeepingManager *manager)
+{
+        GDBusConnection *connection;
+        GError *error = NULL;
+        GDBusInterfaceInfo **infos;
+        int i;
+
+        connection = g_bus_get_finish (res, &error);
+        if (connection == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Could not get session bus: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+        manager->connection = connection;
+
+        infos = manager->introspection_data->interfaces;
+        for (i = 0; infos[i] != NULL; i++) {
+                g_dbus_connection_register_object (connection,
+                                                   GSD_HOUSEKEEPING_DBUS_PATH,
+                                                   infos[i],
+                                                   &interface_vtable,
+                                                   manager,
+                                                   NULL,
+                                                   NULL);
+        }
+
+        manager->name_id = g_bus_own_name_on_connection (connection,
+                                                               "org.gnome.SettingsDaemon.Housekeeping",
+                                                               G_BUS_NAME_OWNER_FLAGS_NONE,
+                                                               NULL,
+                                                               NULL,
+                                                               NULL,
+                                                               NULL);
+}
+
+static void
+register_manager_dbus (GsdHousekeepingManager *manager)
+{
+        manager->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+        g_assert (manager->introspection_data != NULL);
+        manager->bus_cancellable = g_cancellable_new ();
+
+        g_bus_get (G_BUS_TYPE_SESSION,
+                   manager->bus_cancellable,
+                   (GAsyncReadyCallback) on_bus_gotten,
+                   manager);
+}
+
+gboolean
+gsd_housekeeping_manager_start (GsdHousekeepingManager *manager,
+                                GError                **error)
+{
+        gchar *dir;
+
+        g_debug ("Starting housekeeping manager");
+        gnome_settings_profile_start (NULL);
+
+        /* Create ~/.local/ as early as possible */
+        (void) g_mkdir_with_parents(g_get_user_data_dir (), 0700);
+
+        /* Create ~/.local/share/applications/, see
+         * https://bugzilla.gnome.org/show_bug.cgi?id=703048 */
+        dir = g_build_filename (g_get_user_data_dir (), "applications", NULL);
+        (void) g_mkdir (dir, 0700);
+        g_free (dir);
+
+        gsd_ldsm_setup (FALSE);
+
+        manager->settings = g_settings_new (THUMB_PREFIX);
+        g_signal_connect (G_OBJECT (manager->settings), "changed",
+                          G_CALLBACK (settings_changed_callback), manager);
+
+        /* Clean once, a few minutes after start-up */
+        do_cleanup_soon (manager);
+
+        /* Clean periodically, on a daily basis. */
+        manager->long_term_cb = g_timeout_add_seconds (INTERVAL_ONCE_A_DAY,
+                                      (GSourceFunc) do_cleanup,
+                                      manager);
+        g_source_set_name_by_id (manager->long_term_cb, "[gnome-settings-daemon] do_cleanup");
+
+        manager->systemd_notify = g_object_new (GSD_TYPE_SYSTEMD_NOTIFY, NULL);
+
+        gnome_settings_profile_end (NULL);
+
+        return TRUE;
+}
+
+void
+gsd_housekeeping_manager_stop (GsdHousekeepingManager *manager)
+{
+        g_debug ("Stopping housekeeping manager");
+
+        if (manager->name_id != 0) {
+                g_bus_unown_name (manager->name_id);
+                manager->name_id = 0;
+        }
+
+        g_clear_object (&manager->bus_cancellable);
+        g_clear_pointer (&manager->introspection_data, g_dbus_node_info_unref);
+        g_clear_object (&manager->connection);
+
+        g_clear_object (&manager->systemd_notify);
+
+        if (manager->short_term_cb) {
+                g_source_remove (manager->short_term_cb);
+                manager->short_term_cb = 0;
+        }
+
+        if (manager->long_term_cb) {
+                g_source_remove (manager->long_term_cb);
+                manager->long_term_cb = 0;
+
+                /* Do a clean-up on shutdown if and only if the size or age
+                   limits have been set to paranoid levels (zero) */
+                if ((g_settings_get_int (manager->settings, THUMB_AGE_KEY) == 0) ||
+                    (g_settings_get_int (manager->settings, THUMB_SIZE_KEY) == 0)) {
+                        do_cleanup (manager);
+                }
+
+        }
+
+        g_clear_object (&manager->settings);
+        gsd_ldsm_clean ();
+}
+
+static void
+gsd_housekeeping_manager_finalize (GObject *object)
+{
+        gsd_housekeeping_manager_stop (GSD_HOUSEKEEPING_MANAGER (object));
+
+        G_OBJECT_CLASS (gsd_housekeeping_manager_parent_class)->finalize (object);
+}
+
+static void
+gsd_housekeeping_manager_class_init (GsdHousekeepingManagerClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_housekeeping_manager_finalize;
+
+        notify_init ("gnome-settings-daemon");
+}
+
+static void
+gsd_housekeeping_manager_init (GsdHousekeepingManager *manager)
+{
+}
+
+GsdHousekeepingManager *
+gsd_housekeeping_manager_new (void)
+{
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_HOUSEKEEPING_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+
+                register_manager_dbus (manager_object);
+        }
+
+        return GSD_HOUSEKEEPING_MANAGER (manager_object);
+}
diff --git a/plugins/housekeeping/gsd-housekeeping-manager.h b/plugins/housekeeping/gsd-housekeeping-manager.h
new file mode 100644
index 0000000..8b5f840
--- /dev/null
+++ b/plugins/housekeeping/gsd-housekeeping-manager.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Michael J. Chudobiak 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_HOUSEKEEPING_MANAGER_H
+#define __GSD_HOUSEKEEPING_MANAGER_H
+
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_HOUSEKEEPING_MANAGER         (gsd_housekeeping_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdHousekeepingManager, gsd_housekeeping_manager, GSD, HOUSEKEEPING_MANAGER, GObject)
+
+GsdHousekeepingManager * gsd_housekeeping_manager_new           (void);
+gboolean                 gsd_housekeeping_manager_start         (GsdHousekeepingManager  *manager,
+                                                                 GError                 **error);
+void                     gsd_housekeeping_manager_stop          (GsdHousekeepingManager  *manager);
+
+G_END_DECLS
+
+#endif /* __GSD_HOUSEKEEPING_MANAGER_H */
diff --git a/plugins/housekeeping/gsd-purge-temp-test.c b/plugins/housekeeping/gsd-purge-temp-test.c
new file mode 100644
index 0000000..6ff2d5e
--- /dev/null
+++ b/plugins/housekeeping/gsd-purge-temp-test.c
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ * vim: set et sw=8 ts=8:
+ *
+ * Copyright (c) 2008, Novell, Inc.
+ *
+ * Authors: Vincent Untz 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+#include 
+#include 
+#include "gsd-disk-space.h"
+
+int
+main (int    argc,
+      char **argv)
+{
+        GFile *file;
+        DeleteData *data;
+        GDateTime *old;
+        GMainLoop *loop;
+        g_autofree char *filesystem = NULL;
+
+        g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
+
+        gtk_init (&argc, &argv);
+        notify_init ("gsd-purge-temp-test");
+        loop = g_main_loop_new (NULL, FALSE);
+
+        file = g_file_new_for_path ("/tmp/gsd-purge-temp-test");
+        if (!g_file_query_exists (file, NULL)) {
+                g_warning ("Create /tmp/gsd-purge-temp-test and add some files to it to test deletion by date");
+                g_object_unref (file);
+                return 1;
+        }
+
+        old = g_date_time_new_now_local ();
+        filesystem = get_filesystem (file);
+        data = delete_data_new (file, NULL, old, FALSE, FALSE, 0, filesystem);
+        delete_recursively_by_age (data);
+        delete_data_unref (data);
+        g_object_unref (file);
+
+        g_main_loop_run (loop);
+
+        return 0;
+}
+
diff --git a/plugins/housekeeping/gsd-systemd-notify.c b/plugins/housekeeping/gsd-systemd-notify.c
new file mode 100644
index 0000000..9ead905
--- /dev/null
+++ b/plugins/housekeeping/gsd-systemd-notify.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 Benjamin Berg 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "gsd-systemd-notify.h"
+#include 
+#include 
+#include 
+#include 
+
+struct _GsdSystemdNotify {
+        GObject parent;
+
+        GDBusConnection *session;
+        guint sub_service;
+        guint sub_scope;
+};
+
+G_DEFINE_TYPE (GsdSystemdNotify, gsd_systemd_notify, G_TYPE_OBJECT)
+
+static void
+notify_oom_kill (char *unit)
+{
+        g_autoptr(GDesktopAppInfo) app = NULL;
+        g_autofree char *unit_copy = NULL;
+        g_autofree char *app_id = NULL;
+        g_autofree char *desktop_id = NULL;
+        g_autofree char *summary = NULL;
+        g_autofree char *message = NULL;
+        NotifyNotification *notification = NULL;
+        char *pos;
+
+        unit_copy = g_strdup (unit);
+
+        if (g_str_has_suffix (unit_copy, ".service")) {
+                /* Find (first) @ character */
+                pos = strchr (unit_copy, '@');
+                if (pos)
+                        *pos = '\0';
+        } else if (g_str_has_suffix (unit_copy, ".scope")) {
+                /* Find last - character */
+                pos = strrchr (unit_copy, '-');
+                if (pos)
+                        *pos = '\0';
+        } else {
+                /* This cannot happen, because we only subscribe to the Scope
+                 * and Service DBus interfaces.
+                 */
+                g_assert_not_reached ();
+                return;
+        }
+
+
+        pos = strrchr (unit_copy, '-');
+        if (pos) {
+                pos += 1;
+
+                app_id = g_strcompress (pos);
+                desktop_id = g_strjoin (NULL, app_id, ".desktop", NULL);
+
+                app = g_desktop_app_info_new (desktop_id);
+        }
+
+        if (app) {
+                /* TRANSLATORS: %s is the application name. */
+                summary = g_strdup_printf (_("%s Stopped"),
+                                           g_app_info_get_name (G_APP_INFO (app)));
+                /* TRANSLATORS: %s is the application name. */
+                message = g_strdup_printf (_("Device memory is nearly full. %s was using a lot of memory and was forced to stop."),
+                                           g_app_info_get_name (G_APP_INFO (app)));
+        } else if (g_str_has_prefix (unit, "vte-spawn-")) {
+                /* TRANSLATORS: A terminal tab/window was killed. */
+                summary = g_strdup_printf (_("Virtual Terminal Stopped"));
+                /* TRANSLATORS: A terminal tab/window was killed. */
+                message = g_strdup_printf (_("Device memory is nearly full. Virtual Terminal processes were using a lot of memory and were forced to stop."));
+        } else {
+                /* TRANSLATORS: We don't have a good description of what was killed. */
+                summary = g_strdup_printf (_("Application Stopped"));
+                /* TRANSLATORS: We don't have a good description of what was killed. */
+                message = g_strdup_printf (_("Device memory is nearly full. An Application that was using a lot of memory and was forced to stop."));
+        }
+
+        notification = notify_notification_new (summary, message, "dialog-warning-symbolic");
+
+        if (app) {
+                notify_notification_set_hint_string (notification, "desktop-entry", desktop_id);
+                notify_notification_set_app_name (notification, g_app_info_get_name (G_APP_INFO (app)));
+        }
+        notify_notification_set_hint (notification, "transient", g_variant_new_boolean (TRUE));
+        notify_notification_set_urgency (notification, NOTIFY_URGENCY_CRITICAL);
+        notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
+
+        notify_notification_show (notification, NULL);
+        g_object_unref (notification);
+}
+
+/* Taken from hexdecoct.c in systemd, LGPL-2.1-or-later */
+static int
+unhexchar (char c)
+{
+        if (c >= '0' && c <= '9')
+                return c - '0';
+
+        if (c >= 'a' && c <= 'f')
+                return c - 'a' + 10;
+
+        if (c >= 'A' && c <= 'F')
+                return c - 'A' + 10;
+
+        return -EINVAL;
+}
+
+
+static char*
+unescape_dbus_path (const char *path)
+{
+        g_autofree char *res = g_malloc (strlen (path) + 1);
+        char *r;
+
+        for (r = res; *path; path += 1, r += 1) {
+                int c1, c2;
+                if (*path != '_') {
+                        *r = *path;
+                        continue;
+                }
+                /* Read next two hex characters */
+                path += 1;
+                c1 = unhexchar (*path);
+                if (c1 < 0)
+                        return NULL;
+                path += 1;
+                c2 = unhexchar (*path);
+                if (c2 < 0)
+                        return NULL;
+
+                *r = (c1 << 4) | c2;
+        }
+        *r = '\0';
+
+        return g_steal_pointer (&res);
+}
+
+static void
+on_unit_properties_changed (GDBusConnection *connection,
+                            const char *sender_name,
+                            const char *object_path,
+                            const char *interface_name,
+                            const char *signal_name,
+                            GVariant *parameters,
+                            gpointer user_data)
+{
+        g_autoptr(GVariant) dict = NULL;
+        const char *result = NULL;
+        const char *unit_escaped = NULL;
+        g_autofree char *unit = NULL;
+
+        g_assert (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sa{sv}as)")));
+
+        dict = g_variant_get_child_value (parameters, 1);
+        g_assert (dict);
+
+        unit_escaped = strrchr (object_path, '/');
+        g_assert (unit_escaped);
+        unit_escaped += 1;
+
+        unit = unescape_dbus_path (unit_escaped);
+        g_assert (unit);
+
+        if (g_variant_lookup (dict, "Result", "&s", &result)) {
+                if (g_strcmp0 (result, "oom-kill") == 0)
+                        notify_oom_kill (unit);
+        }
+}
+
+static void
+on_bus_gotten (GDBusConnection  *obj,
+               GAsyncResult     *res,
+               GsdSystemdNotify *self)
+{
+        g_autoptr(GError) error = NULL;
+        GDBusConnection *con;
+
+        con = g_bus_get_finish (res, &error);
+        if (!con) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to get session bus: %s", error->message);
+                return;
+        }
+
+        self->session = con;
+        self->sub_service = g_dbus_connection_signal_subscribe (self->session,
+                                                                "org.freedesktop.systemd1",
+                                                                "org.freedesktop.DBus.Properties",
+                                                                "PropertiesChanged",
+                                                                NULL,
+                                                                "org.freedesktop.systemd1.Service",
+                                                                G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE,
+                                                                on_unit_properties_changed,
+                                                                self,
+                                                                NULL);
+
+        self->sub_scope = g_dbus_connection_signal_subscribe (self->session,
+                                                              "org.freedesktop.systemd1",
+                                                              "org.freedesktop.DBus.Properties",
+                                                              "PropertiesChanged",
+                                                              NULL,
+                                                              "org.freedesktop.systemd1.Scope",
+                                                              G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE,
+                                                              on_unit_properties_changed,
+                                                              self,
+                                                              NULL);
+}
+
+static void
+gsd_systemd_notify_init (GsdSystemdNotify *self)
+{
+        g_bus_get (G_BUS_TYPE_SESSION, NULL, (GAsyncReadyCallback) on_bus_gotten, self);
+}
+
+static void
+gsd_systemd_notify_dispose (GObject *obj)
+{
+        GsdSystemdNotify *self = GSD_SYSTEMD_NOTIFY (obj);
+
+        if (self->sub_service) {
+                g_dbus_connection_signal_unsubscribe (self->session, self->sub_service);
+                g_dbus_connection_signal_unsubscribe (self->session, self->sub_scope);
+        }
+        self->sub_service = 0;
+        self->sub_scope = 0;
+        g_clear_object (&self->session);
+
+        G_OBJECT_CLASS (gsd_systemd_notify_parent_class)->dispose (obj);
+}
+
+static void
+gsd_systemd_notify_class_init (GsdSystemdNotifyClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->dispose = gsd_systemd_notify_dispose;
+}
+
diff --git a/plugins/housekeeping/gsd-systemd-notify.h b/plugins/housekeeping/gsd-systemd-notify.h
new file mode 100644
index 0000000..51560d1
--- /dev/null
+++ b/plugins/housekeeping/gsd-systemd-notify.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 Benjamin Berg 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_SYSTEMD_NOTIFY_H
+#define __GSD_SYSTEMD_NOTIFY_H
+
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_SYSTEMD_NOTIFY         (gsd_systemd_notify_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdSystemdNotify, gsd_systemd_notify, GSD, SYSTEMD_NOTIFY, GObject)
+
+G_END_DECLS
+
+#endif /* __GSD_SYSTEMD_NOTIFY_H */
diff --git a/plugins/housekeeping/main.c b/plugins/housekeeping/main.c
new file mode 100644
index 0000000..89c12f7
--- /dev/null
+++ b/plugins/housekeeping/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_housekeeping_manager_new
+#define START gsd_housekeeping_manager_start
+#define STOP gsd_housekeeping_manager_stop
+#define MANAGER GsdHousekeepingManager
+#include "gsd-housekeeping-manager.h"
+
+#include "daemon-skeleton.h"
diff --git a/plugins/housekeeping/meson.build b/plugins/housekeeping/meson.build
new file mode 100644
index 0000000..ce7ba81
--- /dev/null
+++ b/plugins/housekeeping/meson.build
@@ -0,0 +1,42 @@
+common_files = files(
+  'gsd-disk-space.c',
+  'gsd-disk-space-helper.c'
+)
+
+sources = common_files + files(
+  'gsd-housekeeping-manager.c',
+  'gsd-systemd-notify.c',
+  'main.c'
+)
+
+deps = plugins_deps + [
+  gio_unix_dep,
+  gtk_dep,
+  libnotify_dep
+]
+
+executable(
+  'gsd-' + plugin_name,
+  sources,
+  include_directories: [top_inc, common_inc],
+  dependencies: deps,
+  c_args: cflags,
+  install: true,
+  install_rpath: gsd_pkglibdir,
+  install_dir: gsd_libexecdir
+)
+
+programs = [
+  'gsd-disk-space-test',
+  'gsd-empty-trash-test',
+  'gsd-purge-temp-test'
+]
+
+foreach program: programs
+  executable(
+    program,
+    common_files + [program + '.c'],
+    include_directories: top_inc,
+    dependencies: deps
+  )
+endforeach
diff --git a/plugins/keyboard/.indent.pro b/plugins/keyboard/.indent.pro
new file mode 100644
index 0000000..bdff074
--- /dev/null
+++ b/plugins/keyboard/.indent.pro
@@ -0,0 +1,2 @@
+-kr -i8 -pcs -lps -psl
+
diff --git a/plugins/keyboard/gsd-keyboard-manager.c b/plugins/keyboard/gsd-keyboard-manager.c
new file mode 100644
index 0000000..15247c7
--- /dev/null
+++ b/plugins/keyboard/gsd-keyboard-manager.c
@@ -0,0 +1,609 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright © 2001 Ximian, Inc.
+ * Copyright (C) 2007 William Jon McCann 
+ * Written by Sergey V. Oudaltsov 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "gnome-settings-bus.h"
+#include "gnome-settings-profile.h"
+#include "gsd-keyboard-manager.h"
+#include "gsd-input-helper.h"
+#include "gsd-enums.h"
+#include "gsd-settings-migrate.h"
+
+#define GSD_KEYBOARD_DIR "org.gnome.settings-daemon.peripherals.keyboard"
+
+#define KEY_CLICK          "click"
+#define KEY_CLICK_VOLUME   "click-volume"
+
+#define KEY_BELL_VOLUME    "bell-volume"
+#define KEY_BELL_PITCH     "bell-pitch"
+#define KEY_BELL_DURATION  "bell-duration"
+#define KEY_BELL_MODE      "bell-mode"
+#define KEY_BELL_CUSTOM_FILE "bell-custom-file"
+
+#define GNOME_DESKTOP_INTERFACE_DIR "org.gnome.desktop.interface"
+
+#define GNOME_DESKTOP_INPUT_SOURCES_DIR "org.gnome.desktop.input-sources"
+
+#define KEY_INPUT_SOURCES        "sources"
+#define KEY_KEYBOARD_OPTIONS     "xkb-options"
+
+#define INPUT_SOURCE_TYPE_XKB  "xkb"
+#define INPUT_SOURCE_TYPE_IBUS "ibus"
+
+#define DEFAULT_LAYOUT "us"
+
+#define SETTINGS_PORTED_FILE ".gsd-keyboard.settings-ported"
+
+struct _GsdKeyboardManager
+{
+        GObject    parent;
+
+        guint      start_idle_id;
+        GSettings *settings;
+        GSettings *input_sources_settings;
+        GDBusProxy *localed;
+        GCancellable *cancellable;
+};
+
+static void     gsd_keyboard_manager_class_init  (GsdKeyboardManagerClass *klass);
+static void     gsd_keyboard_manager_init        (GsdKeyboardManager      *keyboard_manager);
+static void     gsd_keyboard_manager_finalize    (GObject                 *object);
+
+G_DEFINE_TYPE (GsdKeyboardManager, gsd_keyboard_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+static void
+init_builder_with_sources (GVariantBuilder *builder,
+                           GSettings       *settings)
+{
+        const gchar *type;
+        const gchar *id;
+        GVariantIter iter;
+        GVariant *sources;
+
+        sources = g_settings_get_value (settings, KEY_INPUT_SOURCES);
+
+        g_variant_builder_init (builder, G_VARIANT_TYPE ("a(ss)"));
+
+        g_variant_iter_init (&iter, sources);
+        while (g_variant_iter_next (&iter, "(&s&s)", &type, &id))
+                g_variant_builder_add (builder, "(ss)", type, id);
+
+        g_variant_unref (sources);
+}
+
+static gboolean
+schema_is_installed (const char *schema)
+{
+        GSettingsSchemaSource *source = NULL;
+        gchar **non_relocatable = NULL;
+        gchar **relocatable = NULL;
+        gboolean installed = FALSE;
+
+        source = g_settings_schema_source_get_default ();
+        if (!source)
+                return FALSE;
+
+        g_settings_schema_source_list_schemas (source, TRUE, &non_relocatable, &relocatable);
+
+        if (g_strv_contains ((const gchar * const *)non_relocatable, schema) ||
+            g_strv_contains ((const gchar * const *)relocatable, schema))
+                installed = TRUE;
+
+        g_strfreev (non_relocatable);
+        g_strfreev (relocatable);
+        return installed;
+}
+
+static void
+apply_bell (GsdKeyboardManager *manager)
+{
+        GdkDisplay      *gdisplay;
+	GSettings       *settings;
+        XKeyboardControl kbdcontrol;
+        gboolean         click;
+        int              bell_volume;
+        int              bell_pitch;
+        int              bell_duration;
+        GsdBellMode      bell_mode;
+        int              click_volume;
+
+        if (gnome_settings_is_wayland ())
+                return;
+
+        gdisplay = gdk_display_get_default ();
+
+        g_debug ("Applying the bell settings");
+        settings      = manager->settings;
+        click         = g_settings_get_boolean  (settings, KEY_CLICK);
+        click_volume  = g_settings_get_int   (settings, KEY_CLICK_VOLUME);
+
+        bell_pitch    = g_settings_get_int   (settings, KEY_BELL_PITCH);
+        bell_duration = g_settings_get_int   (settings, KEY_BELL_DURATION);
+
+        bell_mode = g_settings_get_enum (settings, KEY_BELL_MODE);
+        bell_volume   = (bell_mode == GSD_BELL_MODE_ON) ? 50 : 0;
+
+        /* as percentage from 0..100 inclusive */
+        if (click_volume < 0) {
+                click_volume = 0;
+        } else if (click_volume > 100) {
+                click_volume = 100;
+        }
+        kbdcontrol.key_click_percent = click ? click_volume : 0;
+        kbdcontrol.bell_percent = bell_volume;
+        kbdcontrol.bell_pitch = bell_pitch;
+        kbdcontrol.bell_duration = bell_duration;
+
+        gdk_x11_display_error_trap_push (gdisplay);
+        XChangeKeyboardControl (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+                                KBKeyClickPercent | KBBellPercent | KBBellPitch | KBBellDuration,
+                                &kbdcontrol);
+
+        XSync (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), FALSE);
+        gdk_x11_display_error_trap_pop_ignored (gdisplay);
+}
+
+static void
+apply_all_settings (GsdKeyboardManager *manager)
+{
+	apply_bell (manager);
+}
+
+static void
+settings_changed (GSettings          *settings,
+                  const char         *key,
+                  GsdKeyboardManager *manager)
+{
+	if (g_strcmp0 (key, KEY_CLICK) == 0||
+	    g_strcmp0 (key, KEY_CLICK_VOLUME) == 0 ||
+	    g_strcmp0 (key, KEY_BELL_PITCH) == 0 ||
+	    g_strcmp0 (key, KEY_BELL_DURATION) == 0 ||
+	    g_strcmp0 (key, KEY_BELL_MODE) == 0) {
+		g_debug ("Bell setting '%s' changed, applying bell settings", key);
+		apply_bell (manager);
+	} else if (g_strcmp0 (key, KEY_BELL_CUSTOM_FILE) == 0){
+		g_debug ("Ignoring '%s' setting change", KEY_BELL_CUSTOM_FILE);
+	} else {
+		g_warning ("Unhandled settings change, key '%s'", key);
+	}
+
+}
+
+static void
+get_sources_from_xkb_config (GsdKeyboardManager *manager)
+{
+        GVariantBuilder builder;
+        GVariant *v;
+        gint i, n;
+        gchar **layouts = NULL;
+        gchar **variants = NULL;
+
+        v = g_dbus_proxy_get_cached_property (manager->localed, "X11Layout");
+        if (v) {
+                const gchar *s = g_variant_get_string (v, NULL);
+                if (*s)
+                        layouts = g_strsplit (s, ",", -1);
+                g_variant_unref (v);
+        }
+
+        init_builder_with_sources (&builder, manager->input_sources_settings);
+
+        if (!layouts) {
+                g_variant_builder_add (&builder, "(ss)", INPUT_SOURCE_TYPE_XKB, DEFAULT_LAYOUT);
+                goto out;
+	}
+
+        v = g_dbus_proxy_get_cached_property (manager->localed, "X11Variant");
+        if (v) {
+                const gchar *s = g_variant_get_string (v, NULL);
+                if (*s)
+                        variants = g_strsplit (s, ",", -1);
+                g_variant_unref (v);
+        }
+
+        if (variants && variants[0])
+                n = MIN (g_strv_length (layouts), g_strv_length (variants));
+        else
+                n = g_strv_length (layouts);
+
+        for (i = 0; i < n && layouts[i][0]; ++i) {
+                gchar *id;
+
+                if (variants && variants[i] && variants[i][0])
+                        id = g_strdup_printf ("%s+%s", layouts[i], variants[i]);
+                else
+                        id = g_strdup (layouts[i]);
+
+                g_variant_builder_add (&builder, "(ss)", INPUT_SOURCE_TYPE_XKB, id);
+                g_free (id);
+        }
+
+out:
+        g_settings_set_value (manager->input_sources_settings, KEY_INPUT_SOURCES, g_variant_builder_end (&builder));
+
+        g_strfreev (layouts);
+        g_strfreev (variants);
+}
+
+static void
+get_options_from_xkb_config (GsdKeyboardManager *manager)
+{
+        GVariant *v;
+        gchar **options = NULL;
+
+        v = g_dbus_proxy_get_cached_property (manager->localed, "X11Options");
+        if (v) {
+                const gchar *s = g_variant_get_string (v, NULL);
+                if (*s)
+                        options = g_strsplit (s, ",", -1);
+                g_variant_unref (v);
+        }
+
+        if (!options)
+                return;
+
+        g_settings_set_strv (manager->input_sources_settings, KEY_KEYBOARD_OPTIONS, (const gchar * const*) options);
+
+        g_strfreev (options);
+}
+
+static void
+convert_libgnomekbd_options (GSettings *settings)
+{
+        GPtrArray *opt_array;
+        GSettings *libgnomekbd_settings;
+        gchar **options, **o;
+
+        if (!schema_is_installed ("org.gnome.libgnomekbd.keyboard"))
+                return;
+
+        opt_array = g_ptr_array_new_with_free_func (g_free);
+
+        libgnomekbd_settings = g_settings_new ("org.gnome.libgnomekbd.keyboard");
+        options = g_settings_get_strv (libgnomekbd_settings, "options");
+
+        for (o = options; *o; ++o) {
+                gchar **strv;
+
+                strv = g_strsplit (*o, "\t", 2);
+                if (strv[0] && strv[1])
+                        g_ptr_array_add (opt_array, g_strdup (strv[1]));
+                g_strfreev (strv);
+        }
+        g_ptr_array_add (opt_array, NULL);
+
+        g_settings_set_strv (settings, KEY_KEYBOARD_OPTIONS, (const gchar * const*) opt_array->pdata);
+
+        g_strfreev (options);
+        g_object_unref (libgnomekbd_settings);
+        g_ptr_array_free (opt_array, TRUE);
+}
+
+static void
+convert_libgnomekbd_layouts (GSettings *settings)
+{
+        GVariantBuilder builder;
+        GSettings *libgnomekbd_settings;
+        gchar **layouts, **l;
+
+        if (!schema_is_installed ("org.gnome.libgnomekbd.keyboard"))
+                return;
+
+        init_builder_with_sources (&builder, settings);
+
+        libgnomekbd_settings = g_settings_new ("org.gnome.libgnomekbd.keyboard");
+        layouts = g_settings_get_strv (libgnomekbd_settings, "layouts");
+
+        for (l = layouts; *l; ++l) {
+                gchar *id;
+                gchar **strv;
+
+                strv = g_strsplit (*l, "\t", 2);
+                if (strv[0] && !strv[1])
+                        id = g_strdup (strv[0]);
+                else if (strv[0] && strv[1])
+                        id = g_strdup_printf ("%s+%s", strv[0], strv[1]);
+                else
+                        id = NULL;
+
+                if (id)
+                        g_variant_builder_add (&builder, "(ss)", INPUT_SOURCE_TYPE_XKB, id);
+
+                g_free (id);
+                g_strfreev (strv);
+        }
+
+        g_settings_set_value (settings, KEY_INPUT_SOURCES, g_variant_builder_end (&builder));
+
+        g_strfreev (layouts);
+        g_object_unref (libgnomekbd_settings);
+}
+
+static void
+maybe_convert_old_settings (GSettings *settings)
+{
+        GVariant *sources;
+        gchar **options;
+        gchar *stamp_dir_path = NULL;
+        gchar *stamp_file_path = NULL;
+        GError *error = NULL;
+
+        stamp_dir_path = g_build_filename (g_get_user_data_dir (), PACKAGE_NAME, NULL);
+        if (g_mkdir_with_parents (stamp_dir_path, 0755)) {
+                g_warning ("Failed to create directory %s: %s", stamp_dir_path, g_strerror (errno));
+                goto out;
+        }
+
+        stamp_file_path = g_build_filename (stamp_dir_path, "input-sources-converted", NULL);
+        if (g_file_test (stamp_file_path, G_FILE_TEST_EXISTS))
+                goto out;
+
+        sources = g_settings_get_value (settings, KEY_INPUT_SOURCES);
+        if (g_variant_n_children (sources) < 1) {
+                convert_libgnomekbd_layouts (settings);
+        }
+        g_variant_unref (sources);
+
+        options = g_settings_get_strv (settings, KEY_KEYBOARD_OPTIONS);
+        if (g_strv_length (options) < 1)
+                convert_libgnomekbd_options (settings);
+        g_strfreev (options);
+
+        if (!g_file_set_contents (stamp_file_path, "", 0, &error)) {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+        }
+out:
+        g_free (stamp_file_path);
+        g_free (stamp_dir_path);
+}
+
+static void
+maybe_create_initial_settings (GsdKeyboardManager *manager)
+{
+        GSettings *settings;
+        GVariant *sources;
+        gchar **options;
+
+        settings = manager->input_sources_settings;
+
+        if (g_getenv ("RUNNING_UNDER_GDM"))
+                return;
+
+        maybe_convert_old_settings (settings);
+
+        /* if we still don't have anything do some educated guesses */
+        sources = g_settings_get_value (settings, KEY_INPUT_SOURCES);
+        if (g_variant_n_children (sources) < 1)
+                get_sources_from_xkb_config (manager);
+        g_variant_unref (sources);
+
+        options = g_settings_get_strv (settings, KEY_KEYBOARD_OPTIONS);
+        if (g_strv_length (options) < 1)
+                get_options_from_xkb_config (manager);
+        g_strfreev (options);
+}
+
+static void
+localed_proxy_ready (GObject      *source,
+                     GAsyncResult *res,
+                     gpointer      data)
+{
+        GsdKeyboardManager *manager = data;
+        GDBusProxy *proxy;
+        GError *error = NULL;
+
+        proxy = g_dbus_proxy_new_finish (res, &error);
+        if (!proxy) {
+                if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                        g_error_free (error);
+                        return;
+                }
+                g_warning ("Failed to contact localed: %s", error->message);
+                g_error_free (error);
+        }
+
+        manager->localed = proxy;
+        maybe_create_initial_settings (manager);
+}
+
+static gboolean
+start_keyboard_idle_cb (GsdKeyboardManager *manager)
+{
+        gnome_settings_profile_start (NULL);
+
+        g_debug ("Starting keyboard manager");
+
+        manager->settings = g_settings_new (GSD_KEYBOARD_DIR);
+
+        manager->input_sources_settings = g_settings_new (GNOME_DESKTOP_INPUT_SOURCES_DIR);
+
+        manager->cancellable = g_cancellable_new ();
+
+        g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+                                  G_DBUS_PROXY_FLAGS_NONE,
+                                  NULL,
+                                  "org.freedesktop.locale1",
+                                  "/org/freedesktop/locale1",
+                                  "org.freedesktop.locale1",
+                                  manager->cancellable,
+                                  localed_proxy_ready,
+                                  manager);
+
+        if (!gnome_settings_is_wayland ()) {
+                /* apply current settings before we install the callback */
+                g_debug ("Started the keyboard plugin, applying all settings");
+                apply_all_settings (manager);
+
+                g_signal_connect (G_OBJECT (manager->settings), "changed",
+                                  G_CALLBACK (settings_changed), manager);
+        }
+
+        gnome_settings_profile_end (NULL);
+
+        manager->start_idle_id = 0;
+
+        return FALSE;
+}
+
+gboolean
+gsd_keyboard_manager_start (GsdKeyboardManager *manager,
+                            GError            **error)
+{
+        gnome_settings_profile_start (NULL);
+
+        manager->start_idle_id = g_idle_add ((GSourceFunc) start_keyboard_idle_cb, manager);
+        g_source_set_name_by_id (manager->start_idle_id, "[gnome-settings-daemon] start_keyboard_idle_cb");
+
+        gnome_settings_profile_end (NULL);
+
+        return TRUE;
+}
+
+void
+gsd_keyboard_manager_stop (GsdKeyboardManager *manager)
+{
+        g_debug ("Stopping keyboard manager");
+
+        g_cancellable_cancel (manager->cancellable);
+        g_clear_object (&manager->cancellable);
+
+        g_clear_object (&manager->settings);
+        g_clear_object (&manager->input_sources_settings);
+        g_clear_object (&manager->localed);
+}
+
+static void
+gsd_keyboard_manager_class_init (GsdKeyboardManagerClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_keyboard_manager_finalize;
+}
+
+static void
+gsd_keyboard_manager_init (GsdKeyboardManager *manager)
+{
+}
+
+static void
+gsd_keyboard_manager_finalize (GObject *object)
+{
+        GsdKeyboardManager *keyboard_manager;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSD_IS_KEYBOARD_MANAGER (object));
+
+        keyboard_manager = GSD_KEYBOARD_MANAGER (object);
+
+        g_return_if_fail (keyboard_manager != NULL);
+
+        gsd_keyboard_manager_stop (keyboard_manager);
+
+        if (keyboard_manager->start_idle_id != 0)
+                g_source_remove (keyboard_manager->start_idle_id);
+
+        G_OBJECT_CLASS (gsd_keyboard_manager_parent_class)->finalize (object);
+}
+
+static GVariant *
+reset_gtk_im_module (GVariant *variant,
+                     GVariant *old_default,
+                     GVariant *new_default)
+{
+        return NULL;
+}
+
+static void
+migrate_keyboard_settings (void)
+{
+        GsdSettingsMigrateEntry entries[] = {
+                { "repeat",                 "repeat",                 NULL },
+                { "repeat-interval",        "repeat-interval",        NULL },
+                { "delay",                  "delay",                  NULL },
+                { "remember-numlock-state", "remember-numlock-state", NULL },
+        };
+        g_autofree char *filename = NULL;
+
+        gsd_settings_migrate_check ("org.gnome.settings-daemon.peripherals.keyboard.deprecated",
+                                    "/org/gnome/settings-daemon/peripherals/keyboard/",
+                                    "org.gnome.desktop.peripherals.keyboard",
+                                    "/org/gnome/desktop/peripherals/keyboard/",
+                                    entries, G_N_ELEMENTS (entries));
+
+        /* In prior versions to GNOME 42, the gtk-im-module setting was
+         * owned by gsd-keyboard. Reset it once before giving it back
+         * to the user.
+         */
+        filename = g_build_filename (g_get_user_config_dir (),
+                                     SETTINGS_PORTED_FILE,
+                                     NULL);
+
+        if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
+                GsdSettingsMigrateEntry im_entry[] = {
+                        { "gtk-im-module", "gtk-im-module", reset_gtk_im_module },
+                };
+                g_autoptr(GError) error = NULL;
+
+                gsd_settings_migrate_check ("org.gnome.desktop.interface",
+                                            "/org/gnome/desktop/interface/",
+                                            "org.gnome.desktop.interface",
+                                            "/org/gnome/desktop/interface/",
+                                            im_entry, G_N_ELEMENTS (im_entry));
+
+                if (!g_file_set_contents (filename, "", -1, &error))
+                        g_warning ("Error migrating gtk-im-module: %s", error->message);
+        }
+}
+
+GsdKeyboardManager *
+gsd_keyboard_manager_new (void)
+{
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                migrate_keyboard_settings ();
+                manager_object = g_object_new (GSD_TYPE_KEYBOARD_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+        }
+
+        return GSD_KEYBOARD_MANAGER (manager_object);
+}
diff --git a/plugins/keyboard/gsd-keyboard-manager.h b/plugins/keyboard/gsd-keyboard-manager.h
new file mode 100644
index 0000000..e498f31
--- /dev/null
+++ b/plugins/keyboard/gsd-keyboard-manager.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_KEYBOARD_MANAGER_H
+#define __GSD_KEYBOARD_MANAGER_H
+
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_KEYBOARD_MANAGER         (gsd_keyboard_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdKeyboardManager, gsd_keyboard_manager, GSD, KEYBOARD_MANAGER, GObject)
+
+GsdKeyboardManager *       gsd_keyboard_manager_new                 (void);
+gboolean                gsd_keyboard_manager_start               (GsdKeyboardManager *manager,
+                                                               GError         **error);
+void                    gsd_keyboard_manager_stop                (GsdKeyboardManager *manager);
+
+G_END_DECLS
+
+#endif /* __GSD_KEYBOARD_MANAGER_H */
diff --git a/plugins/keyboard/main.c b/plugins/keyboard/main.c
new file mode 100644
index 0000000..48fce06
--- /dev/null
+++ b/plugins/keyboard/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_keyboard_manager_new
+#define START gsd_keyboard_manager_start
+#define STOP gsd_keyboard_manager_stop
+#define MANAGER GsdKeyboardManager
+#include "gsd-keyboard-manager.h"
+
+#include "daemon-skeleton-gtk.h"
diff --git a/plugins/keyboard/meson.build b/plugins/keyboard/meson.build
new file mode 100644
index 0000000..172193f
--- /dev/null
+++ b/plugins/keyboard/meson.build
@@ -0,0 +1,21 @@
+sources = files(
+  'gsd-keyboard-manager.c',
+  'main.c'
+)
+
+deps = plugins_deps + [
+  gtk_dep,
+  libcommon_dep,
+  x11_dep
+]
+
+executable(
+  'gsd-' + plugin_name,
+  sources,
+  include_directories: [top_inc, data_inc],
+  dependencies: deps,
+  c_args: cflags,
+  install: true,
+  install_rpath: gsd_pkglibdir,
+  install_dir: gsd_libexecdir
+)
diff --git a/plugins/media-keys/audio-selection-test.c b/plugins/media-keys/audio-selection-test.c
new file mode 100644
index 0000000..d06759f
--- /dev/null
+++ b/plugins/media-keys/audio-selection-test.c
@@ -0,0 +1,263 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2017 Bastien Nocera 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+
+#define AUDIO_SELECTION_DBUS_NAME               "org.gnome.Shell.AudioDeviceSelection"
+#define AUDIO_SELECTION_DBUS_PATH               "/org/gnome/Shell/AudioDeviceSelection"
+#define AUDIO_SELECTION_DBUS_INTERFACE          "org.gnome.Shell.AudioDeviceSelection"
+
+static guint audio_selection_watch_id;
+static guint audio_selection_signal_id;
+static GDBusConnection *audio_selection_conn;
+static gboolean audio_selection_requested;
+static GtkWidget *check_headphones, *check_headset, *check_micro;
+static GtkWidget *button, *label;
+
+/* Copy-paste from gvc-mixer-control.h */
+typedef enum
+{
+	GVC_HEADSET_PORT_CHOICE_NONE        = 0,
+	GVC_HEADSET_PORT_CHOICE_HEADPHONES  = 1 << 0,
+	GVC_HEADSET_PORT_CHOICE_HEADSET     = 1 << 1,
+	GVC_HEADSET_PORT_CHOICE_MIC         = 1 << 2
+} GvcHeadsetPortChoice;
+
+typedef struct {
+	GvcHeadsetPortChoice choice;
+	gchar *name;
+} AudioSelectionChoice;
+
+static AudioSelectionChoice audio_selection_choices[] = {
+	{ GVC_HEADSET_PORT_CHOICE_HEADPHONES,   "headphones" },
+	{ GVC_HEADSET_PORT_CHOICE_HEADSET,      "headset" },
+	{ GVC_HEADSET_PORT_CHOICE_MIC,          "microphone" },
+};
+
+static void
+audio_selection_done (GDBusConnection *connection,
+		      const gchar     *sender_name,
+		      const gchar     *object_path,
+		      const gchar     *interface_name,
+		      const gchar     *signal_name,
+		      GVariant        *parameters,
+		      gpointer         data)
+{
+	const gchar *choice;
+
+	if (!audio_selection_requested)
+		return;
+
+	choice = NULL;
+	g_variant_get_child (parameters, 0, "&s", &choice);
+	if (!choice)
+		return;
+
+	gtk_label_set_text (GTK_LABEL (label), choice);
+
+	audio_selection_requested = FALSE;
+}
+
+static void
+audio_selection_needed (GvcHeadsetPortChoice  choices)
+{
+	gchar *args[G_N_ELEMENTS (audio_selection_choices) + 1];
+	guint i, n;
+
+	if (!audio_selection_conn)
+		return;
+
+	n = 0;
+	for (i = 0; i < G_N_ELEMENTS (audio_selection_choices); ++i) {
+		if (choices & audio_selection_choices[i].choice)
+			args[n++] = audio_selection_choices[i].name;
+	}
+	args[n] = NULL;
+
+	audio_selection_requested = TRUE;
+	g_dbus_connection_call (audio_selection_conn,
+				AUDIO_SELECTION_DBUS_NAME,
+				AUDIO_SELECTION_DBUS_PATH,
+				AUDIO_SELECTION_DBUS_INTERFACE,
+				"Open",
+				g_variant_new ("(^as)", args),
+				NULL,
+				G_DBUS_CALL_FLAGS_NONE,
+				-1, NULL, NULL, NULL);
+}
+
+static void
+update_ask_button (void)
+{
+	guint num_buttons = 0;
+	gboolean active = FALSE;
+
+	/* Need gnome-shell running */
+	if (audio_selection_conn == NULL)
+		goto end;
+
+	/* Need at least 2 choices */
+	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_headphones)))
+		num_buttons++;
+	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_headset)))
+		num_buttons++;
+	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_micro)))
+		num_buttons++;
+
+	if (num_buttons < 2)
+		goto end;
+
+	/* And no questions in flight */
+	if (audio_selection_requested)
+		goto end;
+
+	active = TRUE;
+
+end:
+	gtk_widget_set_sensitive (GTK_WIDGET (button), active);
+}
+
+static void
+audio_selection_appeared (GDBusConnection *connection,
+			  const gchar     *name,
+			  const gchar     *name_owner,
+			  gpointer         data)
+{
+	audio_selection_conn = connection;
+	audio_selection_signal_id =
+		g_dbus_connection_signal_subscribe (connection,
+						    AUDIO_SELECTION_DBUS_NAME,
+						    AUDIO_SELECTION_DBUS_INTERFACE,
+						    "DeviceSelected",
+						    AUDIO_SELECTION_DBUS_PATH,
+						    NULL,
+						    G_DBUS_SIGNAL_FLAGS_NONE,
+						    audio_selection_done,
+						    NULL,
+						    NULL);
+	update_ask_button ();
+}
+
+static void
+audio_selection_vanished (GDBusConnection *connection,
+			  const gchar     *name,
+			  gpointer         data)
+{
+	if (audio_selection_signal_id)
+		g_dbus_connection_signal_unsubscribe (audio_selection_conn,
+						      audio_selection_signal_id);
+	audio_selection_signal_id = 0;
+	audio_selection_conn = NULL;
+	update_ask_button ();
+}
+
+static void
+watch_gnome_shell (void)
+{
+	audio_selection_watch_id =
+		g_bus_watch_name (G_BUS_TYPE_SESSION,
+				  AUDIO_SELECTION_DBUS_NAME,
+				  G_BUS_NAME_WATCHER_FLAGS_NONE,
+				  audio_selection_appeared,
+				  audio_selection_vanished,
+				  NULL,
+				  NULL);
+}
+
+static void
+check_buttons_changed (GtkToggleButton *button,
+		       gpointer         user_data)
+{
+	update_ask_button ();
+}
+
+static void
+button_clicked (GtkButton *button,
+		gpointer   user_data)
+{
+	guint choices = 0;
+
+	gtk_label_set_text (GTK_LABEL (label), "");
+
+	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_headphones)))
+		choices |= GVC_HEADSET_PORT_CHOICE_HEADPHONES;
+	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_headset)))
+		choices |= GVC_HEADSET_PORT_CHOICE_HEADSET;
+	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_micro)))
+		choices |= GVC_HEADSET_PORT_CHOICE_MIC;
+
+	audio_selection_needed (choices);
+}
+
+static void
+setup_ui (void)
+{
+	GtkWidget *window;
+	GtkWidget *box;
+
+	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	g_signal_connect (GTK_WINDOW (window), "delete-event",
+			  G_CALLBACK (gtk_main_quit), NULL);
+	box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
+	gtk_container_add (GTK_CONTAINER (window), box);
+
+	check_headphones = gtk_check_button_new_with_label ("Headphones");
+	g_signal_connect (check_headphones, "toggled",
+			  G_CALLBACK (check_buttons_changed), NULL);
+	gtk_container_add (GTK_CONTAINER (box), check_headphones);
+
+	check_headset = gtk_check_button_new_with_label ("Headset");
+	g_signal_connect (check_headset, "toggled",
+			  G_CALLBACK (check_buttons_changed), NULL);
+	gtk_container_add (GTK_CONTAINER (box), check_headset);
+
+	check_micro = gtk_check_button_new_with_label ("Microphone");
+	g_signal_connect (check_micro, "toggled",
+			  G_CALLBACK (check_buttons_changed), NULL);
+	gtk_container_add (GTK_CONTAINER (box), check_micro);
+
+	button = gtk_button_new_with_label ("Ask!");
+	g_signal_connect (button, "clicked",
+			  G_CALLBACK (button_clicked), NULL);
+	gtk_container_add (GTK_CONTAINER (box), button);
+	gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE);
+
+	label = gtk_label_new ("");
+	gtk_container_add (GTK_CONTAINER (box), label);
+
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_headphones), TRUE);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_headset), TRUE);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_micro), TRUE);
+
+	gtk_widget_show_all (window);
+}
+
+int main (int argc, char **argv)
+{
+	gtk_init (&argc, &argv);
+
+	setup_ui ();
+	watch_gnome_shell ();
+
+	gtk_main ();
+
+	return 0;
+}
diff --git a/plugins/media-keys/bus-watch-namespace.c b/plugins/media-keys/bus-watch-namespace.c
new file mode 100644
index 0000000..a9349b4
--- /dev/null
+++ b/plugins/media-keys/bus-watch-namespace.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see .
+ *
+ * Author: Lars Uebernickel 
+ */
+
+#include 
+#include 
+#include "bus-watch-namespace.h"
+
+typedef struct
+{
+  guint                     id;
+  gchar                    *name_space;
+  GBusNameAppearedCallback  appeared_handler;
+  GBusNameVanishedCallback  vanished_handler;
+  gpointer                  user_data;
+  GDestroyNotify            user_data_destroy;
+
+  GDBusConnection          *connection;
+  GCancellable             *cancellable;
+  GHashTable               *names;
+  guint                     subscription_id;
+} NamespaceWatcher;
+
+typedef struct
+{
+  NamespaceWatcher *watcher;
+  gchar            *name;
+} GetNameOwnerData;
+
+static guint namespace_watcher_next_id;
+static GHashTable *namespace_watcher_watchers;
+
+static void
+namespace_watcher_stop (gpointer data)
+{
+  NamespaceWatcher *watcher = data;
+
+  g_cancellable_cancel (watcher->cancellable);
+  g_object_unref (watcher->cancellable);
+
+  if (watcher->subscription_id)
+    g_dbus_connection_signal_unsubscribe (watcher->connection, watcher->subscription_id);
+
+  if (watcher->vanished_handler)
+    {
+      GHashTableIter it;
+      const gchar *name;
+
+      g_hash_table_iter_init (&it, watcher->names);
+      while (g_hash_table_iter_next (&it, (gpointer *) &name, NULL))
+        watcher->vanished_handler (watcher->connection, name, watcher->user_data);
+    }
+
+  if (watcher->user_data_destroy)
+    watcher->user_data_destroy (watcher->user_data);
+
+  if (watcher->connection)
+    {
+      g_signal_handlers_disconnect_by_func (watcher->connection, namespace_watcher_stop, watcher);
+      g_object_unref (watcher->connection);
+    }
+
+  g_hash_table_unref (watcher->names);
+
+  g_hash_table_remove (namespace_watcher_watchers, GUINT_TO_POINTER (watcher->id));
+  if (g_hash_table_size (namespace_watcher_watchers) == 0)
+    g_clear_pointer (&namespace_watcher_watchers, g_hash_table_destroy);
+
+  g_free (watcher);
+}
+
+static void
+namespace_watcher_name_appeared (NamespaceWatcher *watcher,
+                                 const gchar      *name,
+                                 const gchar      *owner)
+{
+  /* There's a race between NameOwnerChanged signals arriving and the
+   * ListNames/GetNameOwner sequence returning, so this function might
+   * be called more than once for the same name. To ensure that
+   * appeared_handler is only called once for each name, it is only
+   * called when inserting the name into watcher->names (each name is
+   * only inserted once there).
+   */
+  if (g_hash_table_contains (watcher->names, name))
+    return;
+
+  g_hash_table_add (watcher->names, g_strdup (name));
+
+  if (watcher->appeared_handler)
+    watcher->appeared_handler (watcher->connection, name, owner, watcher->user_data);
+}
+
+static void
+namespace_watcher_name_vanished (NamespaceWatcher *watcher,
+                                 const gchar      *name)
+{
+  if (g_hash_table_remove (watcher->names, name) && watcher->vanished_handler)
+    watcher->vanished_handler (watcher->connection, name, watcher->user_data);
+}
+
+static gboolean
+dbus_name_has_namespace (const gchar *name,
+                         const gchar *name_space)
+{
+  gint len_name;
+  gint len_namespace;
+
+  len_name = strlen (name);
+  len_namespace = strlen (name_space);
+
+  if (len_name < len_namespace)
+    return FALSE;
+
+  if (memcmp (name_space, name, len_namespace) != 0)
+    return FALSE;
+
+  return len_namespace == len_name || name[len_namespace] == '.';
+}
+
+static void
+name_owner_changed (GDBusConnection *connection,
+                    const gchar     *sender_name,
+                    const gchar     *object_path,
+                    const gchar     *interface_name,
+                    const gchar     *signal_name,
+                    GVariant        *parameters,
+                    gpointer         user_data)
+{
+  NamespaceWatcher *watcher = user_data;
+  const gchar *name;
+  const gchar *old_owner;
+  const gchar *new_owner;
+
+  g_variant_get (parameters, "(&s&s&s)", &name, &old_owner, &new_owner);
+
+  if (old_owner[0] != '\0')
+    namespace_watcher_name_vanished (watcher, name);
+
+  if (new_owner[0] != '\0')
+    namespace_watcher_name_appeared (watcher, name, new_owner);
+}
+
+static void
+got_name_owner (GObject      *object,
+                GAsyncResult *result,
+                gpointer      user_data)
+{
+  GetNameOwnerData *data = user_data;
+  GError *error = NULL;
+  GVariant *reply;
+  const gchar *owner;
+
+  reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      g_error_free (error);
+      goto out;
+    }
+
+  if (reply == NULL)
+    {
+      if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER))
+        g_warning ("bus_watch_namespace: error calling org.freedesktop.DBus.GetNameOwner: %s", error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  g_variant_get (reply, "(&s)", &owner);
+  namespace_watcher_name_appeared (data->watcher, data->name, owner);
+
+  g_variant_unref (reply);
+
+out:
+  g_free (data->name);
+  g_free (data);
+}
+
+static void
+names_listed (GObject      *object,
+              GAsyncResult *result,
+              gpointer      user_data)
+{
+  NamespaceWatcher *watcher;
+  GError *error = NULL;
+  GVariant *reply;
+  GVariantIter *iter;
+  const gchar *name;
+
+  reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      g_error_free (error);
+      return;
+    }
+
+  watcher = user_data;
+
+  if (reply == NULL)
+    {
+      g_warning ("bus_watch_namespace: error calling org.freedesktop.DBus.ListNames: %s", error->message);
+      g_error_free (error);
+      return;
+    }
+
+  g_variant_get (reply, "(as)", &iter);
+  while (g_variant_iter_next (iter, "&s", &name))
+    {
+      if (dbus_name_has_namespace (name, watcher->name_space))
+        {
+          GetNameOwnerData *data = g_new (GetNameOwnerData, 1);
+          data->watcher = watcher;
+          data->name = g_strdup (name);
+          g_dbus_connection_call (watcher->connection, "org.freedesktop.DBus", "/",
+                                  "org.freedesktop.DBus", "GetNameOwner",
+                                  g_variant_new ("(s)", name), G_VARIANT_TYPE ("(s)"),
+                                  G_DBUS_CALL_FLAGS_NONE, -1, watcher->cancellable,
+                                  got_name_owner, data);
+        }
+    }
+
+  g_variant_iter_free (iter);
+  g_variant_unref (reply);
+}
+
+static void
+connection_closed (GDBusConnection *connection,
+                   gboolean         remote_peer_vanished,
+                   GError          *error,
+                   gpointer         user_data)
+{
+  NamespaceWatcher *watcher = user_data;
+
+  namespace_watcher_stop (watcher);
+}
+
+static void
+got_bus (GObject      *object,
+         GAsyncResult *result,
+         gpointer      user_data)
+{
+  GDBusConnection *connection;
+  NamespaceWatcher *watcher;
+  GError *error = NULL;
+
+  connection = g_bus_get_finish (result, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      g_error_free (error);
+      return;
+    }
+
+  watcher = user_data;
+
+  if (connection == NULL)
+    {
+      namespace_watcher_stop (watcher);
+      return;
+    }
+
+  watcher->connection = connection;
+  g_signal_connect (watcher->connection, "closed", G_CALLBACK (connection_closed), watcher);
+
+  watcher->subscription_id =
+    g_dbus_connection_signal_subscribe (watcher->connection, "org.freedesktop.DBus",
+                                        "org.freedesktop.DBus", "NameOwnerChanged", "/org/freedesktop/DBus",
+                                        watcher->name_space, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE,
+                                        name_owner_changed, watcher, NULL);
+
+  g_dbus_connection_call (watcher->connection, "org.freedesktop.DBus", "/",
+                          "org.freedesktop.DBus", "ListNames", NULL, G_VARIANT_TYPE ("(as)"),
+                          G_DBUS_CALL_FLAGS_NONE, -1, watcher->cancellable,
+                          names_listed, watcher);
+}
+
+guint
+bus_watch_namespace (GBusType                  bus_type,
+                     const gchar              *name_space,
+                     GBusNameAppearedCallback  appeared_handler,
+                     GBusNameVanishedCallback  vanished_handler,
+                     gpointer                  user_data,
+                     GDestroyNotify            user_data_destroy)
+{
+  NamespaceWatcher *watcher;
+
+  /* same rules for interfaces and well-known names */
+  g_return_val_if_fail (name_space != NULL && g_dbus_is_interface_name (name_space), 0);
+  g_return_val_if_fail (appeared_handler || vanished_handler, 0);
+
+  watcher = g_new0 (NamespaceWatcher, 1);
+  watcher->id = namespace_watcher_next_id++;
+  watcher->name_space = g_strdup (name_space);
+  watcher->appeared_handler = appeared_handler;
+  watcher->vanished_handler = vanished_handler;
+  watcher->user_data = user_data;
+  watcher->user_data_destroy = user_data_destroy;
+  watcher->cancellable = g_cancellable_new ();;
+  watcher->names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+  if (namespace_watcher_watchers == NULL)
+    namespace_watcher_watchers = g_hash_table_new (g_direct_hash, g_direct_equal);
+  g_hash_table_insert (namespace_watcher_watchers, GUINT_TO_POINTER (watcher->id), watcher);
+
+  g_bus_get (bus_type, watcher->cancellable, got_bus, watcher);
+
+  return watcher->id;
+}
+
+void
+bus_unwatch_namespace (guint id)
+{
+  /* namespace_watcher_stop() might have already removed the watcher
+   * with @id in the case of a connection error. Thus, this function
+   * doesn't warn when @id is absent from the hash table.
+   */
+
+  if (namespace_watcher_watchers)
+    {
+      NamespaceWatcher *watcher;
+
+      watcher = g_hash_table_lookup (namespace_watcher_watchers, GUINT_TO_POINTER (id));
+      if (watcher)
+        {
+          /* make sure vanished() is not called as a result of this function */
+          g_hash_table_remove_all (watcher->names);
+
+          namespace_watcher_stop (watcher);
+        }
+    }
+}
diff --git a/plugins/media-keys/bus-watch-namespace.h b/plugins/media-keys/bus-watch-namespace.h
new file mode 100644
index 0000000..215f6be
--- /dev/null
+++ b/plugins/media-keys/bus-watch-namespace.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see .
+ *
+ * Author: Lars Uebernickel 
+ */
+
+#ifndef __BUS_WATCH_NAMESPACE_H__
+#define __BUS_WATCH_NAMESPACE_H__
+
+#include 
+
+guint       bus_watch_namespace         (GBusType                  bus_type,
+                                         const gchar              *name_space,
+                                         GBusNameAppearedCallback  appeared_handler,
+                                         GBusNameVanishedCallback  vanished_handler,
+                                         gpointer                  user_data,
+                                         GDestroyNotify            user_data_destroy);
+
+void        bus_unwatch_namespace       (guint id);
+
+#endif
diff --git a/plugins/media-keys/gsd-marshal.list b/plugins/media-keys/gsd-marshal.list
new file mode 100644
index 0000000..72f9937
--- /dev/null
+++ b/plugins/media-keys/gsd-marshal.list
@@ -0,0 +1 @@
+VOID:STRING,STRING
diff --git a/plugins/media-keys/gsd-media-keys-manager.c b/plugins/media-keys/gsd-media-keys-manager.c
new file mode 100644
index 0000000..15e96e0
--- /dev/null
+++ b/plugins/media-keys/gsd-media-keys-manager.c
@@ -0,0 +1,3553 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2001-2003 Bastien Nocera 
+ * Copyright (C) 2006-2007 William Jon McCann 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include 
+
+#if HAVE_GUDEV
+#include 
+#endif
+
+#include "gsd-settings-migrate.h"
+
+#include "mpris-controller.h"
+#include "gnome-settings-bus.h"
+#include "gnome-settings-profile.h"
+#include "gsd-marshal.h"
+#include "gsd-media-keys-manager.h"
+
+#include "shortcuts-list.h"
+#include "shell-key-grabber.h"
+#include "gsd-input-helper.h"
+#include "gsd-enums.h"
+#include "gsd-shell-helper.h"
+
+#include 
+#include 
+#include "gvc-mixer-control.h"
+#include "gvc-mixer-sink.h"
+
+#define GSD_DBUS_PATH "/org/gnome/SettingsDaemon"
+#define GSD_DBUS_NAME "org.gnome.SettingsDaemon"
+#define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon"
+
+#define GSD_MEDIA_KEYS_DBUS_PATH GSD_DBUS_PATH "/MediaKeys"
+#define GSD_MEDIA_KEYS_DBUS_NAME GSD_DBUS_NAME ".MediaKeys"
+
+#define GNOME_KEYRING_DBUS_NAME "org.gnome.keyring"
+#define GNOME_KEYRING_DBUS_PATH "/org/gnome/keyring/daemon"
+#define GNOME_KEYRING_DBUS_INTERFACE "org.gnome.keyring.Daemon"
+
+#define SHELL_DBUS_NAME "org.gnome.Shell"
+#define SHELL_DBUS_PATH "/org/gnome/Shell"
+
+#define CUSTOM_BINDING_SCHEMA SETTINGS_BINDING_DIR ".custom-keybinding"
+
+#define SETTINGS_SOUND_DIR "org.gnome.desktop.sound"
+#define ALLOW_VOLUME_ABOVE_100_PERCENT_KEY "allow-volume-above-100-percent"
+
+#define SHELL_GRABBER_CALL_TIMEOUT G_MAXINT
+#define SHELL_GRABBER_RETRY_INTERVAL_MS 1000
+
+/* How long to suppress power-button presses after resume,
+ * 3 seconds is the minimum necessary to make resume reliable */
+#define GSD_REENABLE_POWER_BUTTON_DELAY                 3000 /* ms */
+
+#define SETTINGS_INTERFACE_DIR "org.gnome.desktop.interface"
+#define SETTINGS_POWER_DIR "org.gnome.settings-daemon.plugins.power"
+#define SETTINGS_XSETTINGS_DIR "org.gnome.settings-daemon.plugins.xsettings"
+#define SETTINGS_TOUCHPAD_DIR "org.gnome.desktop.peripherals.touchpad"
+#define TOUCHPAD_ENABLED_KEY "send-events"
+#define HIGH_CONTRAST "HighContrast"
+
+#define REWIND_USEC (-10 * G_USEC_PER_SEC)
+#define FASTFORWARD_USEC (45 * G_USEC_PER_SEC)
+
+#define VOLUME_STEP "volume-step"
+#define VOLUME_STEP_PRECISE 2
+#define MAX_VOLUME 65536.0
+
+#define SYSTEMD_DBUS_NAME                       "org.freedesktop.login1"
+#define SYSTEMD_DBUS_PATH                       "/org/freedesktop/login1"
+#define SYSTEMD_DBUS_INTERFACE                  "org.freedesktop.login1.Manager"
+
+#define AUDIO_SELECTION_DBUS_NAME               "org.gnome.Shell.AudioDeviceSelection"
+#define AUDIO_SELECTION_DBUS_PATH               "/org/gnome/Shell/AudioDeviceSelection"
+#define AUDIO_SELECTION_DBUS_INTERFACE          "org.gnome.Shell.AudioDeviceSelection"
+
+#define GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE(o) (gsd_media_keys_manager_get_instance_private (o))
+
+typedef struct {
+        char   *application;
+        char   *dbus_name;
+        guint32 time;
+        guint   watch_id;
+} MediaPlayer;
+
+typedef struct {
+        gint ref_count;
+
+        MediaKeyType key_type;
+        ShellActionMode modes;
+        MetaKeyBindingFlags grab_flags;
+        const char *settings_key;
+        gboolean static_setting;
+        char *custom_path;
+        char *custom_command;
+        GArray *accel_ids;
+} MediaKey;
+
+typedef struct {
+        GsdMediaKeysManager *manager;
+        GPtrArray *keys;
+
+        /* NOTE: This is to implement a custom cancellation handling where
+         *       we immediately emit an ungrab call if grabbing was cancelled.
+         */
+        gboolean cancelled;
+} GrabUngrabData;
+
+typedef struct
+{
+        /* Volume bits */
+        GvcMixerControl *volume;
+        GvcMixerStream  *sink;
+        GvcMixerStream  *source;
+        ca_context      *ca;
+        GSettings       *sound_settings;
+        pa_volume_t      max_volume;
+        GtkSettings     *gtksettings;
+#if HAVE_GUDEV
+        GHashTable      *streams; /* key = X device ID, value = stream id */
+        GUdevClient     *udev_client;
+#endif /* HAVE_GUDEV */
+        guint            audio_selection_watch_id;
+        guint            audio_selection_signal_id;
+        GDBusConnection *audio_selection_conn;
+        gboolean         audio_selection_requested;
+        guint            audio_selection_device_id;
+
+        GSettings       *settings;
+        GHashTable      *custom_settings;
+
+        GPtrArray       *keys;
+
+        /* HighContrast theme settings */
+        GSettings       *interface_settings;
+        char            *icon_theme;
+        char            *gtk_theme;
+
+        /* Power stuff */
+        GSettings       *power_settings;
+        GDBusProxy      *power_proxy;
+        GDBusProxy      *power_screen_proxy;
+        GDBusProxy      *power_keyboard_proxy;
+        UpDevice        *composite_device;
+        char            *chassis_type;
+        gboolean         power_button_disabled;
+        guint            reenable_power_button_timer_id;
+
+        /* Shell stuff */
+        GsdShell        *shell_proxy;
+        ShellKeyGrabber *key_grabber;
+        GCancellable    *grab_cancellable;
+        GHashTable      *keys_to_sync;
+        guint            keys_sync_source_id;
+        GrabUngrabData  *keys_sync_data;
+
+        /* ScreenSaver stuff */
+        GsdScreenSaver  *screen_saver_proxy;
+
+        /* Rotation */
+        guint            iio_sensor_watch_id;
+        gboolean         has_accel;
+        GDBusProxy      *iio_sensor_proxy;
+
+        /* RFKill stuff */
+        guint            rfkill_watch_id;
+        guint64          rfkill_last_time;
+        GDBusProxy      *rfkill_proxy;
+        GCancellable    *rfkill_cancellable;
+
+        /* systemd stuff */
+        GDBusProxy      *logind_proxy;
+        gint             inhibit_keys_fd;
+        gint             inhibit_suspend_fd;
+        gboolean         inhibit_suspend_taken;
+
+        GDBusConnection *connection;
+        GCancellable    *bus_cancellable;
+
+        guint            start_idle_id;
+
+        /* Multimedia keys */
+        MprisController *mpris_controller;
+} GsdMediaKeysManagerPrivate;
+
+static void     gsd_media_keys_manager_class_init  (GsdMediaKeysManagerClass *klass);
+static void     gsd_media_keys_manager_init        (GsdMediaKeysManager      *media_keys_manager);
+static void     gsd_media_keys_manager_finalize    (GObject                  *object);
+static void     register_manager                   (GsdMediaKeysManager      *manager);
+static void     custom_binding_changed             (GSettings           *settings,
+                                                    const char          *settings_key,
+                                                    GsdMediaKeysManager *manager);
+static void     keys_sync_queue                    (GsdMediaKeysManager *manager,
+                                                    gboolean             immediate,
+                                                    gboolean             retry);
+static void     keys_sync_continue                 (GsdMediaKeysManager *manager);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GsdMediaKeysManager, gsd_media_keys_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+static void
+media_key_unref (MediaKey *key)
+{
+        if (key == NULL)
+                return;
+        if (!g_atomic_int_dec_and_test (&key->ref_count))
+                return;
+        g_clear_pointer (&key->accel_ids, g_array_unref);
+        g_free (key->custom_path);
+        g_free (key->custom_command);
+        g_free (key);
+}
+
+static MediaKey *
+media_key_ref (MediaKey *key)
+{
+        g_atomic_int_inc (&key->ref_count);
+        return key;
+}
+
+static MediaKey *
+media_key_new (void)
+{
+        MediaKey *key = g_new0 (MediaKey, 1);
+
+        key->accel_ids = g_array_new (FALSE, TRUE, sizeof(guint));
+
+        return media_key_ref (key);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (MediaKey, media_key_unref)
+
+static void
+grab_ungrab_data_free (GrabUngrabData *data)
+{
+        /* NOTE: The manager pointer is not owned and is invalid if the
+         *       operation was cancelled.
+         */
+
+        if (!data->cancelled) {
+                GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (data->manager);
+
+                if (priv->keys_sync_data == data)
+                        priv->keys_sync_data = NULL;
+        }
+
+        data->manager = NULL;
+        g_clear_pointer (&data->keys, g_ptr_array_unref);
+        g_free (data);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GrabUngrabData, grab_ungrab_data_free)
+
+static void
+set_launch_context_env (GsdMediaKeysManager *manager,
+			GAppLaunchContext   *launch_context)
+{
+	GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+	GError *error = NULL;
+	GVariant *variant, *item;
+	GVariantIter *iter;
+
+	variant = g_dbus_connection_call_sync (priv->connection,
+					       GNOME_KEYRING_DBUS_NAME,
+					       GNOME_KEYRING_DBUS_PATH,
+					       GNOME_KEYRING_DBUS_INTERFACE,
+					       "GetEnvironment",
+					       NULL,
+					       NULL,
+					       G_DBUS_CALL_FLAGS_NONE,
+					       -1,
+					       NULL,
+					       &error);
+	if (variant == NULL) {
+		g_warning ("Failed to call GetEnvironment on keyring daemon: %s", error->message);
+		g_error_free (error);
+		return;
+	}
+
+	g_variant_get (variant, "(a{ss})", &iter);
+
+	while ((item = g_variant_iter_next_value (iter))) {
+		char *key;
+		char *value;
+
+		g_variant_get (item,
+			       "{ss}",
+			       &key,
+			       &value);
+
+		g_app_launch_context_setenv (launch_context, key, value);
+
+		g_variant_unref (item);
+		g_free (key);
+		g_free (value);
+	}
+
+	g_variant_iter_free (iter);
+	g_variant_unref (variant);
+}
+
+static char *
+get_key_string (MediaKey *key)
+{
+	if (key->settings_key != NULL)
+		return g_strdup_printf ("settings:%s", key->settings_key);
+	else if (key->custom_path != NULL)
+		return g_strdup_printf ("custom:%s", key->custom_path);
+	else
+		g_assert_not_reached ();
+}
+
+static GStrv
+get_bindings (GsdMediaKeysManager *manager,
+	      MediaKey            *key)
+{
+	GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+	GPtrArray *array;
+	gchar *binding;
+
+	if (key->settings_key != NULL) {
+		g_autofree gchar *static_settings_key = NULL;
+		g_autofree GStrv keys = NULL;
+		g_autofree GStrv static_keys = NULL;
+		gchar **item;
+
+		if (!key->static_setting)
+			return g_settings_get_strv (priv->settings, key->settings_key);
+
+		static_settings_key = g_strconcat (key->settings_key, "-static", NULL);
+		keys = g_settings_get_strv (priv->settings, key->settings_key);
+		static_keys = g_settings_get_strv (priv->settings, static_settings_key);
+
+		array = g_ptr_array_new ();
+		/* Steals all strings from the settings */
+		for (item = keys; *item; item++)
+			g_ptr_array_add (array, *item);
+		for (item = static_keys; *item; item++)
+			g_ptr_array_add (array, *item);
+		g_ptr_array_add (array, NULL);
+
+		return (GStrv) g_ptr_array_free (array, FALSE);
+	}
+
+	else if (key->custom_path != NULL) {
+                GSettings *settings;
+
+                settings = g_hash_table_lookup (priv->custom_settings,
+                                                key->custom_path);
+		binding = g_settings_get_string (settings, "binding");
+	} else
+		g_assert_not_reached ();
+
+        array = g_ptr_array_new ();
+        g_ptr_array_add (array, binding);
+        g_ptr_array_add (array, NULL);
+
+        return (GStrv) g_ptr_array_free (array, FALSE);
+}
+
+static void
+show_osd_with_max_level (GsdMediaKeysManager *manager,
+                         const char          *icon,
+                         const char          *label,
+                         double               level,
+                         double               max_level,
+                         const gchar         *connector)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        if (priv->shell_proxy == NULL)
+                return;
+
+        shell_show_osd_with_max_level (priv->shell_proxy,
+                                       icon, label, level, max_level, connector);
+}
+
+static void
+show_osd (GsdMediaKeysManager *manager,
+          const char          *icon,
+          const char          *label,
+          double               level,
+          const char          *connector)
+{
+        show_osd_with_max_level(manager,
+                                icon, label, level, -1, connector);
+}
+
+static const char *
+get_icon_name_for_volume (gboolean is_mic,
+                          gboolean muted,
+                          double volume)
+{
+        static const char *icon_names[] = {
+                "audio-volume-muted-symbolic",
+                "audio-volume-low-symbolic",
+                "audio-volume-medium-symbolic",
+                "audio-volume-high-symbolic",
+                "audio-volume-overamplified-symbolic",
+                NULL
+        };
+        static const char *mic_icon_names[] = {
+                "microphone-sensitivity-muted-symbolic",
+                "microphone-sensitivity-low-symbolic",
+                "microphone-sensitivity-medium-symbolic",
+                "microphone-sensitivity-high-symbolic",
+                NULL
+        };
+        int n;
+
+        if (muted) {
+                n = 0;
+        } else {
+                /* select image */
+                n = ceill (3.0 * volume);
+                if (n < 1)
+                        n = 1;
+                /* output volume above 100% */
+                else if (n > 3 && !is_mic)
+                        n = 4;
+                else if (n > 3)
+                        n = 3;
+        }
+
+	if (is_mic)
+		return mic_icon_names[n];
+	else
+		return icon_names[n];
+}
+
+static void
+ungrab_accelerators_complete (GObject      *object,
+                              GAsyncResult *result,
+                              gpointer      user_data)
+{
+        g_autoptr(GrabUngrabData) data = user_data;
+        gboolean success = FALSE;
+        g_autoptr(GError) error = NULL;
+        gint i;
+
+        g_debug ("Ungrab call completed!");
+
+        if (!shell_key_grabber_call_ungrab_accelerators_finish (SHELL_KEY_GRABBER (object),
+                                                                &success, result, &error)) {
+                g_warning ("Failed to ungrab accelerators: %s", error->message);
+
+                if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
+                        keys_sync_queue (data->manager, FALSE, TRUE);
+                        return;
+                }
+
+                /* We are screwed at this point; we'll still keep going assuming that we don't
+                 * have the bindings registered anymore.
+                 * The only alternative would be to die and force cleanup of all registered
+                 * grabs that way.
+                 */
+        } else if (!success) {
+                g_warning ("Failed to ungrab some accelerators, they were probably not registered!");
+        }
+
+        /* Clear the accelerator IDs. */
+        for (i = 0; i < data->keys->len; i++) {
+                MediaKey *key;
+
+                key = g_ptr_array_index (data->keys, i);
+
+                /* Always clear, as it would just fail again the next time. */
+                g_array_set_size (key->accel_ids, 0);
+        }
+
+        /* Nothing left to do if the operation was cancelled */
+        if (data->cancelled)
+                return;
+
+        keys_sync_continue (data->manager);
+}
+
+static void
+grab_accelerators_complete (GObject      *object,
+                            GAsyncResult *result,
+                            gpointer      user_data)
+{
+        g_autoptr(GrabUngrabData) data = user_data;
+        g_autoptr(GVariant) actions = NULL;
+        g_autoptr(GError) error = NULL;
+        gint i;
+
+        g_debug ("Grab call completed!");
+
+        if (!shell_key_grabber_call_grab_accelerators_finish (SHELL_KEY_GRABBER (object),
+                                                              &actions, result, &error)) {
+                g_warning ("Failed to grab accelerators: %s", error->message);
+
+                if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
+                        keys_sync_queue (data->manager, FALSE, TRUE);
+                        return;
+                }
+
+                /* We are screwed at this point as we can't grab the keys. Most likely
+                 * this means we are not running on GNOME, or ran into some other weird
+                 * error.
+                 * Either way, finish the operation as there is no way we can recover
+                 * from this.
+                 */
+                keys_sync_continue (data->manager);
+                return;
+        }
+
+        /* Do an immediate ungrab if the operation was cancelled.
+         * This may happen on daemon shutdown for example. */
+        if (data->cancelled) {
+                g_debug ("Doing an immediate ungrab on the grabbed accelerators!");
+
+                shell_key_grabber_call_ungrab_accelerators (SHELL_KEY_GRABBER (object),
+                                                            actions,
+                                                            NULL,
+                                                            ungrab_accelerators_complete,
+                                                            g_steal_pointer (&data));
+
+                return;
+        }
+
+        /* We need to stow away the accel_ids that have been registered successfully. */
+        for (i = 0; i < data->keys->len; i++) {
+                MediaKey *key;
+
+                key = g_ptr_array_index (data->keys, i);
+                g_assert (key->accel_ids->len == 0);
+        }
+        for (i = 0; i < data->keys->len; i++) {
+                MediaKey *key;
+                guint accel_id;
+
+                key = g_ptr_array_index (data->keys, i);
+
+                g_variant_get_child (actions, i, "u", &accel_id);
+                if (accel_id == 0) {
+                        g_autofree gchar *tmp = NULL;
+                        tmp = get_key_string (key);
+                        g_warning ("Failed to grab accelerator for keybinding %s", tmp);
+                } else {
+                        g_array_append_val (key->accel_ids, accel_id);
+                }
+        }
+
+        keys_sync_continue (data->manager);
+}
+
+static void
+keys_sync_continue (GsdMediaKeysManager *manager)
+{
+	GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        g_auto(GVariantBuilder) ungrab_builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("au"));
+        g_auto(GVariantBuilder) grab_builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(suu)"));
+        g_autoptr(GPtrArray) keys_being_ungrabbed = NULL;
+        g_autoptr(GPtrArray) keys_being_grabbed = NULL;
+        g_autoptr(GrabUngrabData) data = NULL;
+        GHashTableIter iter;
+        MediaKey *key;
+        gboolean need_ungrab = FALSE;
+
+        /* Syncing keys is a two step process in principle, i.e. we first ungrab all keys
+         * and then grab the new ones.
+         * To make this work, this function will be called multiple times and it will
+         * either emit an ungrab or grab call or do nothing when done.
+         */
+
+        /* If the keys_to_sync hash table is empty at this point, then we are done.
+         * priv->keys_sync_data will be cleared automatically when it is unref'ed.
+         */
+        if (g_hash_table_size (priv->keys_to_sync) == 0)
+                return;
+
+        keys_being_ungrabbed = g_ptr_array_new_with_free_func ((GDestroyNotify) media_key_unref);
+        keys_being_grabbed = g_ptr_array_new_with_free_func ((GDestroyNotify) media_key_unref);
+
+        g_hash_table_iter_init (&iter, priv->keys_to_sync);
+        while (g_hash_table_iter_next (&iter, (gpointer*) &key, NULL)) {
+                g_auto(GStrv) bindings = NULL;
+                gchar **pos = NULL;
+                gint i;
+
+                for (i = 0; i < key->accel_ids->len; i++) {
+                        g_variant_builder_add (&ungrab_builder, "u", g_array_index (key->accel_ids, guint, i));
+                        g_ptr_array_add (keys_being_ungrabbed, media_key_ref (key));
+
+                        need_ungrab = TRUE;
+                }
+
+                /* Keys that are synced but aren't in the internal list are being removed. */
+                if (!g_ptr_array_find (priv->keys, key, NULL))
+                        continue;
+
+                bindings = get_bindings (manager, key);
+                pos = bindings;
+                while (*pos) {
+                        /* Do not try to register empty keybindings. */
+                        if (strlen (*pos) > 0) {
+                                g_variant_builder_add (&grab_builder, "(suu)", *pos, key->modes, key->grab_flags);
+                                g_ptr_array_add (keys_being_grabbed, media_key_ref (key));
+                        }
+                        pos++;
+                }
+        }
+
+        data = g_new0 (GrabUngrabData, 1);
+        data->manager = manager;
+
+        /* These calls intentionally do not get a cancellable. See comment in
+         * GrabUngrabData.
+         */
+        priv->keys_sync_data = data;
+
+        if (need_ungrab) {
+                data->keys = g_steal_pointer (&keys_being_ungrabbed);
+
+                shell_key_grabber_call_ungrab_accelerators (priv->key_grabber,
+                                                            g_variant_builder_end (&ungrab_builder),
+                                                            NULL,
+                                                            ungrab_accelerators_complete,
+                                                            g_steal_pointer (&data));
+        } else {
+                data->keys = g_steal_pointer (&keys_being_grabbed);
+
+                g_hash_table_remove_all (priv->keys_to_sync);
+
+                shell_key_grabber_call_grab_accelerators (priv->key_grabber,
+                                                          g_variant_builder_end (&grab_builder),
+                                                          NULL,
+                                                          grab_accelerators_complete,
+                                                          g_steal_pointer (&data));
+        }
+}
+
+static gboolean
+keys_sync_start (gpointer user_data)
+{
+        GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        priv->keys_sync_source_id = 0;
+        g_assert (priv->keys_sync_data == NULL);
+        keys_sync_continue (manager);
+
+        return G_SOURCE_REMOVE;
+}
+
+void
+keys_sync_queue (GsdMediaKeysManager *manager, gboolean immediate, gboolean retry)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        guint i;
+
+        if (priv->keys_sync_source_id)
+                g_source_remove (priv->keys_sync_source_id);
+
+        if (retry) {
+                /* Abort the currently running operation, and don't retry
+                 * immediately to avoid race condition if an operation was
+                 * already active. */
+                if (priv->keys_sync_data) {
+                        priv->keys_sync_data->cancelled = TRUE;
+                        priv->keys_sync_data = NULL;
+
+                        immediate = FALSE;
+                }
+
+                /* Mark all existing keys for sync. */
+                for (i = 0; i < priv->keys->len; i++) {
+                        MediaKey *key = g_ptr_array_index (priv->keys, i);
+                        g_hash_table_add (priv->keys_to_sync, media_key_ref (key));
+                }
+        } else if (priv->keys_sync_data) {
+                /* We are already actively syncing, no need to do anything. */
+                return;
+        }
+
+        priv->keys_sync_source_id =
+                g_timeout_add (immediate ? 0 : (retry ? SHELL_GRABBER_RETRY_INTERVAL_MS : 50),
+                               keys_sync_start,
+                               manager);
+}
+
+static void
+gsettings_changed_cb (GSettings           *settings,
+                      const gchar         *settings_key,
+                      GsdMediaKeysManager *manager)
+{
+	GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        int      i;
+
+        /* Give up if we don't have proxy to the shell */
+        if (!priv->key_grabber)
+                return;
+
+	/* handled in gsettings_custom_changed_cb() */
+        if (g_str_equal (settings_key, "custom-keybindings"))
+		return;
+
+        /* Find the key that was modified */
+        if (priv->keys == NULL)
+                return;
+
+        for (i = 0; i < priv->keys->len; i++) {
+                MediaKey *key;
+
+                key = g_ptr_array_index (priv->keys, i);
+
+                /* Skip over hard-coded and GConf keys */
+                if (key->settings_key == NULL)
+                        continue;
+                if (strcmp (settings_key, key->settings_key) == 0) {
+                        g_hash_table_add (priv->keys_to_sync, media_key_ref (key));
+                        keys_sync_queue (manager, FALSE, FALSE);
+                        break;
+                }
+        }
+}
+
+static MediaKey *
+media_key_new_for_path (GsdMediaKeysManager *manager,
+			char                *path)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GSettings *settings;
+        char *command, *binding;
+        MediaKey *key;
+
+        g_debug ("media_key_new_for_path: %s", path);
+
+	settings = g_hash_table_lookup (priv->custom_settings, path);
+	if (settings == NULL) {
+		settings = g_settings_new_with_path (CUSTOM_BINDING_SCHEMA, path);
+
+		g_signal_connect (settings, "changed",
+				  G_CALLBACK (custom_binding_changed), manager);
+		g_hash_table_insert (priv->custom_settings,
+				     g_strdup (path), settings);
+	}
+
+        command = g_settings_get_string (settings, "command");
+        binding = g_settings_get_string (settings, "binding");
+
+        if (*command == '\0' && *binding == '\0') {
+                g_debug ("Key binding (%s) is incomplete", path);
+                g_free (command);
+                g_free (binding);
+                return NULL;
+        }
+        g_free (binding);
+
+        key = media_key_new ();
+        key->key_type = CUSTOM_KEY;
+        key->modes = GSD_ACTION_MODE_LAUNCHER;
+        key->custom_path = g_strdup (path);
+        key->custom_command = command;
+        key->grab_flags = META_KEY_BINDING_NONE;
+
+        return key;
+}
+
+static void
+update_custom_binding (GsdMediaKeysManager *manager,
+                       char                *path)
+{
+	GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        MediaKey *key;
+        int i;
+
+        /* Remove the existing key */
+        for (i = 0; i < priv->keys->len; i++) {
+                key = g_ptr_array_index (priv->keys, i);
+
+                if (key->custom_path == NULL)
+                        continue;
+                if (strcmp (key->custom_path, path) == 0) {
+                        g_debug ("Removing custom key binding %s", path);
+                        g_hash_table_add (priv->keys_to_sync, media_key_ref (key));
+                        g_ptr_array_remove_index_fast (priv->keys, i);
+                        break;
+                }
+        }
+
+        /* And create a new one! */
+        key = media_key_new_for_path (manager, path);
+        if (key) {
+                g_debug ("Adding new custom key binding %s", path);
+                g_ptr_array_add (priv->keys, key);
+
+                g_hash_table_add (priv->keys_to_sync, media_key_ref (key));
+        }
+
+        keys_sync_queue (manager, FALSE, FALSE);
+}
+
+static void
+update_custom_binding_command (GsdMediaKeysManager *manager,
+                               GSettings           *settings,
+                               char                *path)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        MediaKey *key;
+        int i;
+
+        for (i = 0; i < priv->keys->len; i++) {
+                key = g_ptr_array_index (priv->keys, i);
+
+                if (key->custom_path == NULL)
+                        continue;
+                if (strcmp (key->custom_path, path) == 0) {
+                        g_free (key->custom_command);
+                        key->custom_command = g_settings_get_string (settings, "command");
+                        break;
+                }
+        }
+}
+
+static void
+custom_binding_changed (GSettings           *settings,
+                        const char          *settings_key,
+                        GsdMediaKeysManager *manager)
+{
+        char *path;
+
+        g_object_get (settings, "path", &path, NULL);
+
+        if (strcmp (settings_key, "binding") == 0)
+                update_custom_binding (manager, path);
+        else if (strcmp (settings_key, "command") == 0)
+                update_custom_binding_command (manager, settings, path);
+
+        g_free (path);
+}
+
+static void
+gsettings_custom_changed_cb (GSettings           *settings,
+                             const char          *settings_key,
+                             GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        char **bindings;
+        int i, j, n_bindings;
+
+        bindings = g_settings_get_strv (settings, settings_key);
+        n_bindings = g_strv_length (bindings);
+
+        /* Handle additions */
+        for (i = 0; i < n_bindings; i++) {
+                if (g_hash_table_lookup (priv->custom_settings,
+                                         bindings[i]))
+                        continue;
+                update_custom_binding (manager, bindings[i]);
+        }
+
+        /* Handle removals */
+        for (i = 0; i < priv->keys->len; i++) {
+                gboolean found = FALSE;
+                MediaKey *key = g_ptr_array_index (priv->keys, i);
+                if (key->key_type != CUSTOM_KEY)
+                        continue;
+
+                for (j = 0; j < n_bindings && !found; j++)
+                        found = strcmp (bindings[j], key->custom_path) == 0;
+
+                if (found)
+                        continue;
+
+                g_hash_table_add (priv->keys_to_sync, media_key_ref (key));
+                g_hash_table_remove (priv->custom_settings,
+                                     key->custom_path);
+                g_ptr_array_remove_index_fast (priv->keys, i);
+                --i; /* make up for the removed key */
+        }
+        keys_sync_queue (manager, FALSE, FALSE);
+        g_strfreev (bindings);
+}
+
+static void
+add_key (GsdMediaKeysManager *manager, guint i)
+{
+	GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+	MediaKey *key;
+
+	key = media_key_new ();
+	key->key_type = media_keys[i].key_type;
+	key->settings_key = media_keys[i].settings_key;
+	key->static_setting = media_keys[i].static_setting;
+	key->modes = media_keys[i].modes;
+	key->grab_flags = media_keys[i].grab_flags;
+
+	g_ptr_array_add (priv->keys, key);
+}
+
+static void
+init_kbd (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        char **custom_paths;
+        int i;
+
+        gnome_settings_profile_start (NULL);
+
+        for (i = 0; i < G_N_ELEMENTS (media_keys); i++)
+                add_key (manager, i);
+
+        /* Custom shortcuts */
+        custom_paths = g_settings_get_strv (priv->settings,
+                                            "custom-keybindings");
+
+        for (i = 0; i < g_strv_length (custom_paths); i++) {
+                MediaKey *key;
+
+                g_debug ("Setting up custom keybinding %s", custom_paths[i]);
+
+                key = media_key_new_for_path (manager, custom_paths[i]);
+                if (!key) {
+                        continue;
+                }
+                g_ptr_array_add (priv->keys, key);
+        }
+        g_strfreev (custom_paths);
+
+        keys_sync_queue (manager, TRUE, TRUE);
+
+        gnome_settings_profile_end (NULL);
+}
+
+static void
+app_launched_cb (GAppLaunchContext *context,
+                 GAppInfo          *info,
+                 GVariant          *platform_data,
+                 gpointer           user_data)
+{
+        GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        gint32 pid;
+        const gchar *app_name;
+
+        if (!g_variant_lookup (platform_data, "pid", "i", &pid))
+                return;
+
+        app_name = g_app_info_get_id (info);
+        if (app_name == NULL)
+                app_name = g_app_info_get_executable (info);
+
+        /* Start async request; we don't care about the result */
+        gnome_start_systemd_scope (app_name,
+                                   pid,
+                                   NULL,
+                                   priv->connection,
+                                   NULL, NULL, NULL);
+}
+
+static void
+launch_app (GsdMediaKeysManager *manager,
+	    GAppInfo            *app_info,
+	    gint64               timestamp)
+{
+	GError *error = NULL;
+        GdkAppLaunchContext *launch_context;
+
+        /* setup the launch context so the startup notification is correct */
+        launch_context = gdk_display_get_app_launch_context (gdk_display_get_default ());
+        gdk_app_launch_context_set_timestamp (launch_context, timestamp);
+        set_launch_context_env (manager, G_APP_LAUNCH_CONTEXT (launch_context));
+
+        g_signal_connect_object (launch_context,
+                                 "launched",
+                                 G_CALLBACK (app_launched_cb),
+                                 manager,
+                                 0);
+
+	if (!g_app_info_launch (app_info, NULL, G_APP_LAUNCH_CONTEXT (launch_context), &error)) {
+		g_warning ("Could not launch '%s': %s",
+			   g_app_info_get_commandline (app_info),
+			   error->message);
+		g_error_free (error);
+	}
+        g_object_unref (launch_context);
+}
+
+static void
+execute (GsdMediaKeysManager *manager,
+         char                *cmd,
+         gint64               timestamp)
+{
+	GAppInfo *app_info;
+	g_autofree gchar *escaped = NULL;
+	gchar *p;
+
+	/* Escape all % characters as g_app_info_create_from_commandline will
+	 * try to interpret them otherwise. */
+	escaped = g_malloc (strlen (cmd) * 2 + 1);
+	p = escaped;
+	while (*cmd) {
+		*p = *cmd;
+		p++;
+		if (*cmd == '%') {
+			*p = '%';
+			p++;
+		}
+		cmd++;
+	}
+	*p = '\0';
+
+	app_info = g_app_info_create_from_commandline (escaped, NULL, G_APP_INFO_CREATE_NONE, NULL);
+	launch_app (manager, app_info, timestamp);
+	g_object_unref (app_info);
+}
+
+static void
+do_url_action (GsdMediaKeysManager *manager,
+               const char          *scheme,
+               gint64               timestamp)
+{
+        GAppInfo *app_info;
+
+        app_info = g_app_info_get_default_for_uri_scheme (scheme);
+        if (app_info != NULL) {
+                launch_app (manager, app_info, timestamp);
+                g_object_unref (app_info);
+        } else {
+                g_warning ("Could not find default application for '%s' scheme", scheme);
+	}
+}
+
+static void
+do_media_action (GsdMediaKeysManager *manager,
+		 gint64               timestamp)
+{
+        GAppInfo *app_info;
+
+        app_info = g_app_info_get_default_for_type ("audio/x-vorbis+ogg", FALSE);
+        if (app_info != NULL) {
+                launch_app (manager, app_info, timestamp);
+                g_object_unref (app_info);
+        } else {
+                g_warning ("Could not find default application for '%s' mime-type", "audio/x-vorbis+ogg");
+        }
+}
+
+static void
+gnome_session_shutdown_cb (GObject *source_object,
+                           GAsyncResult *res,
+                           gpointer user_data)
+{
+        GVariant *result;
+        GError *error = NULL;
+
+        result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+                                           res,
+                                           &error);
+        if (result == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to call Shutdown on session manager: %s",
+                                   error->message);
+                g_error_free (error);
+        } else {
+                g_variant_unref (result);
+        }
+}
+
+static void
+gnome_session_shutdown (GsdMediaKeysManager *manager)
+{
+	GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GDBusProxy *proxy;
+
+        proxy = G_DBUS_PROXY (gnome_settings_bus_get_session_proxy ());
+
+        g_dbus_proxy_call (proxy,
+                           "Shutdown",
+                           NULL,
+                           G_DBUS_CALL_FLAGS_NONE,
+                           -1,
+                           priv->bus_cancellable,
+                           gnome_session_shutdown_cb,
+                           NULL);
+
+        g_object_unref (proxy);
+}
+
+static void
+do_eject_action_cb (GDrive              *drive,
+                    GAsyncResult        *res,
+                    GsdMediaKeysManager *manager)
+{
+        g_drive_eject_with_operation_finish (drive, res, NULL);
+}
+
+#define NO_SCORE 0
+#define SCORE_CAN_EJECT 50
+#define SCORE_HAS_MEDIA 100
+static void
+do_eject_action (GsdMediaKeysManager *manager)
+{
+        GList *drives, *l;
+        GDrive *fav_drive;
+        guint score;
+        GVolumeMonitor *volume_monitor;
+
+        volume_monitor = g_volume_monitor_get ();
+
+
+        /* Find the best drive to eject */
+        fav_drive = NULL;
+        score = NO_SCORE;
+        drives = g_volume_monitor_get_connected_drives (volume_monitor);
+        for (l = drives; l != NULL; l = l->next) {
+                GDrive *drive = l->data;
+
+                if (g_drive_can_eject (drive) == FALSE)
+                        continue;
+                if (g_drive_is_media_removable (drive) == FALSE)
+                        continue;
+                if (score < SCORE_CAN_EJECT) {
+                        fav_drive = drive;
+                        score = SCORE_CAN_EJECT;
+                }
+                if (g_drive_has_media (drive) == FALSE)
+                        continue;
+                if (score < SCORE_HAS_MEDIA) {
+                        fav_drive = drive;
+                        score = SCORE_HAS_MEDIA;
+                        break;
+                }
+        }
+
+        /* Show OSD */
+        show_osd (manager, "media-eject-symbolic", NULL, -1, NULL);
+
+        /* Clean up the drive selection and exit if no suitable
+         * drives are found */
+        if (fav_drive != NULL)
+                fav_drive = g_object_ref (fav_drive);
+
+        g_list_foreach (drives, (GFunc) g_object_unref, NULL);
+        if (fav_drive == NULL)
+                return;
+
+        /* Eject! */
+        g_drive_eject_with_operation (fav_drive, G_MOUNT_UNMOUNT_FORCE,
+                                      NULL, NULL,
+                                      (GAsyncReadyCallback) do_eject_action_cb,
+                                      manager);
+        g_object_unref (fav_drive);
+        g_object_unref (volume_monitor);
+}
+
+static void
+do_home_key_action (GsdMediaKeysManager *manager,
+		    gint64               timestamp)
+{
+	GFile *file;
+	GError *error = NULL;
+	char *uri;
+
+	file = g_file_new_for_path (g_get_home_dir ());
+	uri = g_file_get_uri (file);
+	g_object_unref (file);
+
+	if (gtk_show_uri_on_window (NULL, uri, timestamp, &error) == FALSE) {
+		g_warning ("Failed to launch '%s': %s", uri, error->message);
+		g_error_free (error);
+	}
+	g_free (uri);
+}
+
+static void
+do_search_action (GsdMediaKeysManager *manager,
+		  gint64               timestamp)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        if (priv->shell_proxy == NULL)
+                return;
+
+        gsd_shell_call_focus_search (priv->shell_proxy,
+                                     NULL, NULL, NULL);
+}
+
+static void
+do_execute_desktop_or_desktop (GsdMediaKeysManager *manager,
+			       const char          *desktop,
+			       const char          *alt_desktop,
+			       gint64               timestamp)
+{
+        GDesktopAppInfo *app_info;
+
+        app_info = g_desktop_app_info_new (desktop);
+        if (app_info == NULL && alt_desktop != NULL)
+                app_info = g_desktop_app_info_new (alt_desktop);
+
+        if (app_info != NULL) {
+                launch_app (manager, G_APP_INFO (app_info), timestamp);
+                g_object_unref (app_info);
+                return;
+        }
+
+        g_warning ("Could not find application '%s' or '%s'", desktop, alt_desktop);
+}
+
+static void
+do_touchpad_osd_action (GsdMediaKeysManager *manager, gboolean state)
+{
+        show_osd (manager, state ? "input-touchpad-symbolic"
+                                 : "touchpad-disabled-symbolic", NULL, -1, NULL);
+}
+
+static void
+do_touchpad_action (GsdMediaKeysManager *manager)
+{
+        GSettings *settings;
+        gboolean state;
+
+        settings = g_settings_new (SETTINGS_TOUCHPAD_DIR);
+        state = (g_settings_get_enum (settings, TOUCHPAD_ENABLED_KEY) ==
+                 G_DESKTOP_DEVICE_SEND_EVENTS_ENABLED);
+
+        do_touchpad_osd_action (manager, !state);
+
+        g_settings_set_enum (settings, TOUCHPAD_ENABLED_KEY,
+                             !state ?
+                             G_DESKTOP_DEVICE_SEND_EVENTS_ENABLED :
+                             G_DESKTOP_DEVICE_SEND_EVENTS_DISABLED);
+        g_object_unref (settings);
+}
+
+static void
+on_screen_locked (GsdScreenSaver      *screen_saver,
+                  GAsyncResult        *result,
+                  GsdMediaKeysManager *manager)
+{
+        gboolean is_locked;
+        GError *error = NULL;
+
+        is_locked = gsd_screen_saver_call_lock_finish (screen_saver, result, &error);
+
+        if (!is_locked) {
+                g_warning ("Couldn't lock screen: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+}
+
+static void
+do_lock_screensaver (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        if (priv->screen_saver_proxy == NULL)
+                priv->screen_saver_proxy = gnome_settings_bus_get_screen_saver_proxy ();
+
+        gsd_screen_saver_call_lock (priv->screen_saver_proxy,
+                                    priv->bus_cancellable,
+                                    (GAsyncReadyCallback) on_screen_locked,
+                                    manager);
+}
+
+static void
+sound_theme_changed (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        char *theme_name;
+
+        g_object_get (G_OBJECT (priv->gtksettings), "gtk-sound-theme-name", &theme_name, NULL);
+        if (theme_name)
+                ca_context_change_props (priv->ca, CA_PROP_CANBERRA_XDG_THEME_NAME, theme_name, NULL);
+        g_free (theme_name);
+}
+
+static void
+allow_volume_above_100_percent_changed_cb (GSettings           *settings,
+                                           const char          *settings_key,
+                                           GsdMediaKeysManager *manager)
+{
+	GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        gboolean allow_volume_above_100_percent;
+
+        g_assert (g_str_equal (settings_key, ALLOW_VOLUME_ABOVE_100_PERCENT_KEY));
+
+        allow_volume_above_100_percent = g_settings_get_boolean (settings, settings_key);
+        priv->max_volume = allow_volume_above_100_percent ? PA_VOLUME_UI_MAX : PA_VOLUME_NORM;
+}
+
+static void
+play_volume_changed_audio (GsdMediaKeysManager *manager,
+                           GvcMixerStream      *stream)
+{
+	GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+	if (priv->ca == NULL) {
+                ca_context_create (&priv->ca);
+                ca_context_set_driver (priv->ca, "pulse");
+                ca_context_change_props (priv->ca, 0,
+                                         CA_PROP_APPLICATION_ID,
+                                         "org.gnome.VolumeControl",
+                                         NULL);
+
+                priv->gtksettings =
+                        gtk_settings_get_for_screen (gdk_screen_get_default ());
+
+                g_signal_connect_swapped (priv->gtksettings,
+                                          "notify::gtk-sound-theme-name",
+                                          G_CALLBACK (sound_theme_changed),
+                                          manager);
+                sound_theme_changed (manager);
+        }
+
+        ca_context_change_device (priv->ca,
+                                  gvc_mixer_stream_get_name (stream));
+        ca_context_play (priv->ca, 1,
+                         CA_PROP_EVENT_ID, "audio-volume-change",
+                         CA_PROP_EVENT_DESCRIPTION, "volume changed through key press",
+                         CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
+                         NULL);
+}
+
+static void
+show_volume_osd (GsdMediaKeysManager *manager,
+                 GvcMixerStream      *stream,
+                 guint                vol,
+                 gboolean             muted,
+                 gboolean             sound_changed,
+                 gboolean             quiet)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GvcMixerUIDevice *device;
+        const GvcMixerStreamPort *port;
+        const char *icon;
+        gboolean playing = FALSE;
+        double new_vol;
+        double max_volume;
+
+        max_volume = (double) priv->max_volume / PA_VOLUME_NORM;
+        if (!muted) {
+                new_vol = (double) vol / PA_VOLUME_NORM;
+                new_vol = CLAMP (new_vol, 0, max_volume);
+        } else {
+                new_vol = 0.0;
+        }
+        icon = get_icon_name_for_volume (!GVC_IS_MIXER_SINK (stream), muted, new_vol);
+        port = gvc_mixer_stream_get_port (stream);
+        if (g_strcmp0 (gvc_mixer_stream_get_form_factor (stream), "internal") != 0 ||
+            (port != NULL &&
+             g_strcmp0 (port->port, "[OUT] Speaker") != 0 &&
+             g_strcmp0 (port->port, "[OUT] Handset") != 0 &&
+             g_strcmp0 (port->port, "analog-output-speaker") != 0 &&
+             g_strcmp0 (port->port, "analog-output") != 0)) {
+                device = gvc_mixer_control_lookup_device_from_stream (priv->volume, stream);
+                show_osd_with_max_level (manager, icon,
+                                         gvc_mixer_ui_device_get_description (device),
+                                         new_vol, max_volume, NULL);
+        } else {
+                show_osd_with_max_level (manager, icon, NULL, new_vol, max_volume, NULL);
+        }
+
+        if (priv->ca)
+                ca_context_playing (priv->ca, 1, &playing);
+        playing = !playing && gvc_mixer_stream_get_state (stream) == GVC_STREAM_STATE_RUNNING;
+
+        if (quiet == FALSE && sound_changed != FALSE && muted == FALSE && playing == FALSE)
+                play_volume_changed_audio (manager, stream);
+}
+
+#if HAVE_GUDEV
+/* PulseAudio gives us /devices/... paths, when udev
+ * expects /sys/devices/... paths. */
+static GUdevDevice *
+get_udev_device_for_sysfs_path (GsdMediaKeysManager *manager,
+				const char *sysfs_path)
+{
+	GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+	char *path;
+	GUdevDevice *dev;
+
+	path = g_strdup_printf ("/sys%s", sysfs_path);
+	dev = g_udev_client_query_by_sysfs_path (priv->udev_client, path);
+	g_free (path);
+
+	return dev;
+}
+
+static GvcMixerStream *
+get_stream_for_device_node (GsdMediaKeysManager *manager,
+                            gboolean             is_output,
+                            const gchar         *devnode)
+{
+	GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+	gpointer id_ptr;
+	GvcMixerStream *res;
+	GUdevDevice *dev, *parent;
+	GSList *streams, *l;
+
+	id_ptr = g_hash_table_lookup (priv->streams, devnode);
+	if (id_ptr != NULL) {
+		if (GPOINTER_TO_UINT (id_ptr) == (guint) -1)
+			return NULL;
+		else
+			return gvc_mixer_control_lookup_stream_id (priv->volume, GPOINTER_TO_UINT (id_ptr));
+	}
+
+	dev = g_udev_client_query_by_device_file (priv->udev_client, devnode);
+	if (dev == NULL) {
+		g_debug ("Could not find udev device for device path '%s'", devnode);
+		return NULL;
+	}
+
+	if (g_strcmp0 (g_udev_device_get_property (dev, "ID_BUS"), "usb") != 0) {
+		g_debug ("Not handling XInput device %s, not USB", devnode);
+		g_hash_table_insert (priv->streams,
+				     g_strdup (devnode),
+				     GUINT_TO_POINTER ((guint) -1));
+		g_object_unref (dev);
+		return NULL;
+	}
+
+	parent = g_udev_device_get_parent_with_subsystem (dev, "usb", "usb_device");
+	if (parent == NULL) {
+		g_warning ("No USB device parent for XInput device %s even though it's USB", devnode);
+		g_object_unref (dev);
+		return NULL;
+	}
+
+	res = NULL;
+	if (is_output)
+		streams = gvc_mixer_control_get_sinks (priv->volume);
+	else
+		streams = gvc_mixer_control_get_sources (priv->volume);
+	for (l = streams; l; l = l->next) {
+		GvcMixerStream *stream = l->data;
+		const char *sysfs_path;
+		GUdevDevice *stream_dev, *stream_parent;
+
+		sysfs_path = gvc_mixer_stream_get_sysfs_path (stream);
+		stream_dev = get_udev_device_for_sysfs_path (manager, sysfs_path);
+		if (stream_dev == NULL)
+			continue;
+		stream_parent = g_udev_device_get_parent_with_subsystem (stream_dev, "usb", "usb_device");
+		g_object_unref (stream_dev);
+		if (stream_parent == NULL)
+			continue;
+
+		if (g_strcmp0 (g_udev_device_get_sysfs_path (stream_parent),
+			       g_udev_device_get_sysfs_path (parent)) == 0) {
+			res = stream;
+		}
+		g_object_unref (stream_parent);
+		if (res != NULL)
+			break;
+	}
+
+	g_slist_free (streams);
+
+	if (res)
+		g_hash_table_insert (priv->streams,
+				     g_strdup (devnode),
+				     GUINT_TO_POINTER (gvc_mixer_stream_get_id (res)));
+	else
+		g_hash_table_insert (priv->streams,
+				     g_strdup (devnode),
+				     GUINT_TO_POINTER ((guint) -1));
+
+	return res;
+}
+#endif /* HAVE_GUDEV */
+
+typedef enum {
+	SOUND_ACTION_FLAG_IS_OUTPUT  = 1 << 0,
+	SOUND_ACTION_FLAG_IS_QUIET   = 1 << 1,
+	SOUND_ACTION_FLAG_IS_PRECISE = 1 << 2,
+} SoundActionFlags;
+
+static void
+do_sound_action (GsdMediaKeysManager *manager,
+                 const gchar         *device_node,
+                 int                  type,
+                 SoundActionFlags     flags)
+{
+	GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GvcMixerStream *stream = NULL;
+        gboolean old_muted, new_muted;
+        guint old_vol, new_vol, norm_vol_step, vol_step;
+        gboolean sound_changed;
+
+        /* Find the stream that corresponds to the device, if any */
+        stream = NULL;
+#if HAVE_GUDEV
+        if (device_node) {
+                stream = get_stream_for_device_node (manager,
+                                                     flags & SOUND_ACTION_FLAG_IS_OUTPUT,
+                                                     device_node);
+        }
+#endif /* HAVE_GUDEV */
+
+        if (stream == NULL) {
+                if (flags & SOUND_ACTION_FLAG_IS_OUTPUT)
+                        stream = priv->sink;
+                else
+                        stream = priv->source;
+        }
+
+        if (stream == NULL)
+                return;
+
+        if (flags & SOUND_ACTION_FLAG_IS_PRECISE) {
+                norm_vol_step = PA_VOLUME_NORM * VOLUME_STEP_PRECISE / 100;
+        }
+        else {
+                vol_step = g_settings_get_int (priv->settings, VOLUME_STEP);
+                norm_vol_step = PA_VOLUME_NORM * vol_step / 100;
+        }
+        /* FIXME: this is racy */
+        new_vol = old_vol = gvc_mixer_stream_get_volume (stream);
+        new_muted = old_muted = gvc_mixer_stream_get_is_muted (stream);
+        sound_changed = FALSE;
+
+        switch (type) {
+        case MUTE_KEY:
+                new_muted = !old_muted;
+                break;
+        case VOLUME_DOWN_KEY:
+                if (old_vol <= norm_vol_step) {
+                        new_vol = 0;
+                        new_muted = TRUE;
+                } else {
+                        new_vol = old_vol - norm_vol_step;
+                }
+                break;
+        case VOLUME_UP_KEY:
+                new_muted = FALSE;
+                /* When coming out of mute only increase the volume if it was 0 */
+                if (!old_muted || old_vol == 0)
+                        new_vol = MIN (old_vol + norm_vol_step, priv->max_volume);
+                break;
+        }
+
+        if (old_muted != new_muted) {
+                gvc_mixer_stream_change_is_muted (stream, new_muted);
+                sound_changed = TRUE;
+        }
+
+        if (old_vol != new_vol) {
+                if (gvc_mixer_stream_set_volume (stream, new_vol) != FALSE) {
+                        gvc_mixer_stream_push_volume (stream);
+                        sound_changed = TRUE;
+                }
+        }
+
+        show_volume_osd (manager, stream, new_vol, new_muted, sound_changed,
+                         flags & SOUND_ACTION_FLAG_IS_QUIET);
+}
+
+static void
+update_default_sink (GsdMediaKeysManager *manager,
+                     gboolean             warn)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GvcMixerStream *stream;
+
+        stream = gvc_mixer_control_get_default_sink (priv->volume);
+        if (stream == priv->sink)
+                return;
+
+        g_clear_object (&priv->sink);
+
+        if (stream != NULL) {
+                priv->sink = g_object_ref (stream);
+        } else {
+                if (warn)
+                        g_warning ("Unable to get default sink");
+                else
+                        g_debug ("Unable to get default sink");
+        }
+}
+
+static void
+update_default_source (GsdMediaKeysManager *manager,
+                       gboolean             warn)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GvcMixerStream *stream;
+
+        stream = gvc_mixer_control_get_default_source (priv->volume);
+        if (stream == priv->source)
+                return;
+
+        g_clear_object (&priv->source);
+
+        if (stream != NULL) {
+                priv->source = g_object_ref (stream);
+        } else {
+                if (warn)
+                        g_warning ("Unable to get default source");
+                else
+                        g_debug ("Unable to get default source");
+        }
+}
+
+static void
+on_control_state_changed (GvcMixerControl     *control,
+                          GvcMixerControlState new_state,
+                          GsdMediaKeysManager *manager)
+{
+        update_default_sink (manager, new_state == GVC_STATE_READY);
+        update_default_source (manager, new_state == GVC_STATE_READY);
+}
+
+static void
+on_control_default_sink_changed (GvcMixerControl     *control,
+                                 guint                id,
+                                 GsdMediaKeysManager *manager)
+{
+        update_default_sink (manager, TRUE);
+}
+
+static void
+on_control_default_source_changed (GvcMixerControl     *control,
+                                   guint                id,
+                                   GsdMediaKeysManager *manager)
+{
+        update_default_source (manager, TRUE);
+}
+
+#if HAVE_GUDEV
+static gboolean
+remove_stream (gpointer key,
+	       gpointer value,
+	       gpointer id)
+{
+	if (GPOINTER_TO_UINT (value) == GPOINTER_TO_UINT (id))
+		return TRUE;
+	return FALSE;
+}
+#endif /* HAVE_GUDEV */
+
+static void
+on_control_stream_removed (GvcMixerControl     *control,
+                           guint                id,
+                           GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        if (priv->sink != NULL) {
+		if (gvc_mixer_stream_get_id (priv->sink) == id)
+			g_clear_object (&priv->sink);
+        }
+        if (priv->source != NULL) {
+		if (gvc_mixer_stream_get_id (priv->source) == id)
+			g_clear_object (&priv->source);
+        }
+
+#if HAVE_GUDEV
+	g_hash_table_foreach_remove (priv->streams, (GHRFunc) remove_stream, GUINT_TO_POINTER (id));
+#endif
+}
+
+static gboolean
+do_multimedia_player_action (GsdMediaKeysManager *manager,
+                             const char          *key)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        g_return_val_if_fail (key != NULL, FALSE);
+
+        g_debug ("Media key '%s' pressed", key);
+
+        if (mpris_controller_get_has_active_player (priv->mpris_controller)) {
+                if (g_str_equal (key, "Rewind")) {
+                        if (mpris_controller_seek (priv->mpris_controller, REWIND_USEC))
+                                return TRUE;
+                } else if (g_str_equal (key, "FastForward")) {
+                        if (mpris_controller_seek (priv->mpris_controller, FASTFORWARD_USEC))
+                                return TRUE;
+                } else if (g_str_equal (key, "Repeat")) {
+                        if (mpris_controller_toggle (priv->mpris_controller, "LoopStatus"))
+                                return TRUE;
+                } else if (g_str_equal (key, "Shuffle")) {
+                        if (mpris_controller_toggle (priv->mpris_controller, "Shuffle"))
+                                return TRUE;
+                } else if (mpris_controller_key (priv->mpris_controller, key)) {
+                        return TRUE;
+                }
+        }
+
+	/* Popup a dialog with an (/) icon */
+	show_osd (manager, "action-unavailable-symbolic", NULL, -1, NULL);
+	return TRUE;
+}
+
+static void
+sensor_properties_changed (GDBusProxy *proxy,
+                           GVariant   *changed_properties,
+                           GStrv       invalidated_properties,
+                           gpointer    user_data)
+{
+        GsdMediaKeysManager *manager = user_data;
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GVariant *v;
+        GVariantDict dict;
+
+        if (priv->iio_sensor_proxy == NULL)
+                return;
+
+        if (changed_properties)
+                g_variant_dict_init (&dict, changed_properties);
+
+        if (changed_properties == NULL ||
+            g_variant_dict_contains (&dict, "HasAccelerometer")) {
+                v = g_dbus_proxy_get_cached_property (priv->iio_sensor_proxy,
+                                                      "HasAccelerometer");
+                if (v == NULL) {
+                        g_debug ("Couldn't fetch HasAccelerometer property");
+                        return;
+                }
+                priv->has_accel = g_variant_get_boolean (v);
+                g_variant_unref (v);
+        }
+}
+
+static void
+iio_sensor_appeared_cb (GDBusConnection *connection,
+                        const gchar     *name,
+                        const gchar     *name_owner,
+                        gpointer         user_data)
+{
+        GsdMediaKeysManager *manager = user_data;
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GError *error = NULL;
+
+        priv->iio_sensor_proxy = g_dbus_proxy_new_sync (connection,
+                                                        G_DBUS_PROXY_FLAGS_NONE,
+                                                        NULL,
+                                                        "net.hadess.SensorProxy",
+                                                        "/net/hadess/SensorProxy",
+                                                        "net.hadess.SensorProxy",
+                                                        NULL,
+                                                        &error);
+
+        if (priv->iio_sensor_proxy == NULL) {
+                g_warning ("Failed to access net.hadess.SensorProxy after it appeared");
+                return;
+        }
+        g_signal_connect (G_OBJECT (priv->iio_sensor_proxy),
+                          "g-properties-changed",
+                          G_CALLBACK (sensor_properties_changed), manager);
+
+        sensor_properties_changed (priv->iio_sensor_proxy,
+                                   NULL, NULL, manager);
+}
+
+static void
+iio_sensor_disappeared_cb (GDBusConnection *connection,
+                           const gchar     *name,
+                           gpointer         user_data)
+{
+        GsdMediaKeysManager *manager = user_data;
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        g_clear_object (&priv->iio_sensor_proxy);
+        priv->has_accel = FALSE;
+}
+
+static void
+do_video_rotate_lock_action (GsdMediaKeysManager *manager,
+                             gint64               timestamp)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GSettings *settings;
+        gboolean locked;
+
+        if (!priv->has_accel) {
+                g_debug ("Ignoring attempt to set orientation lock: no accelerometer");
+                return;
+        }
+
+        settings = g_settings_new ("org.gnome.settings-daemon.peripherals.touchscreen");
+        locked = !g_settings_get_boolean (settings, "orientation-lock");
+        g_settings_set_boolean (settings, "orientation-lock", locked);
+        g_object_unref (settings);
+
+        show_osd (manager, locked ? "rotation-locked-symbolic"
+                                  : "rotation-allowed-symbolic", NULL, -1, NULL);
+}
+
+static void
+do_toggle_accessibility_key (const char *key)
+{
+        GSettings *settings;
+        gboolean state;
+
+        settings = g_settings_new ("org.gnome.desktop.a11y.applications");
+        state = g_settings_get_boolean (settings, key);
+        g_settings_set_boolean (settings, key, !state);
+        g_object_unref (settings);
+}
+
+static void
+do_magnifier_action (GsdMediaKeysManager *manager)
+{
+        do_toggle_accessibility_key ("screen-magnifier-enabled");
+}
+
+static void
+do_screenreader_action (GsdMediaKeysManager *manager)
+{
+        do_toggle_accessibility_key ("screen-reader-enabled");
+}
+
+static void
+do_on_screen_keyboard_action (GsdMediaKeysManager *manager)
+{
+        do_toggle_accessibility_key ("screen-keyboard-enabled");
+}
+
+static void
+do_text_size_action (GsdMediaKeysManager *manager,
+		     MediaKeyType         type)
+{
+	GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+	gdouble factor, best, distance;
+	guint i;
+
+	/* Same values used in the Seeing tab of the Universal Access panel */
+	static gdouble factors[] = {
+		0.75,
+		1.0,
+		1.25,
+		1.5
+	};
+
+	/* Figure out the current DPI scaling factor */
+	factor = g_settings_get_double (priv->interface_settings, "text-scaling-factor");
+	factor += (type == INCREASE_TEXT_KEY ? 0.25 : -0.25);
+
+	/* Try to find a matching value */
+	distance = 1e6;
+	best = 1.0;
+	for (i = 0; i < G_N_ELEMENTS(factors); i++) {
+		gdouble d;
+		d = fabs (factor - factors[i]);
+		if (d < distance) {
+			best = factors[i];
+			distance = d;
+		}
+	}
+
+	if (best == 1.0)
+		g_settings_reset (priv->interface_settings, "text-scaling-factor");
+	else
+		g_settings_set_double (priv->interface_settings, "text-scaling-factor", best);
+}
+
+static void
+do_magnifier_zoom_action (GsdMediaKeysManager *manager,
+			  MediaKeyType         type)
+{
+	GSettings *settings;
+	gdouble offset, value;
+
+	if (type == MAGNIFIER_ZOOM_IN_KEY)
+		offset = 1.0;
+	else
+		offset = -1.0;
+
+	settings = g_settings_new ("org.gnome.desktop.a11y.magnifier");
+	value = g_settings_get_double (settings, "mag-factor");
+	value += offset;
+	value = roundl (value);
+	g_settings_set_double (settings, "mag-factor", value);
+	g_object_unref (settings);
+}
+
+static void
+do_toggle_contrast_action (GsdMediaKeysManager *manager)
+{
+	GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+	gboolean high_contrast;
+	char *theme;
+
+	/* Are we using HighContrast now? */
+	theme = g_settings_get_string (priv->interface_settings, "gtk-theme");
+	high_contrast = g_str_equal (theme, HIGH_CONTRAST);
+	g_free (theme);
+
+	if (high_contrast != FALSE) {
+		if (priv->gtk_theme == NULL)
+			g_settings_reset (priv->interface_settings, "gtk-theme");
+		else
+			g_settings_set (priv->interface_settings, "gtk-theme", priv->gtk_theme);
+		g_settings_set (priv->interface_settings, "icon-theme", priv->icon_theme);
+	} else {
+		g_settings_set (priv->interface_settings, "gtk-theme", HIGH_CONTRAST);
+		g_settings_set (priv->interface_settings, "icon-theme", HIGH_CONTRAST);
+	}
+}
+
+static void
+power_action (GsdMediaKeysManager *manager,
+              const char          *action,
+              gboolean             allow_interaction)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        g_dbus_proxy_call (priv->logind_proxy,
+                           action,
+                           g_variant_new ("(b)", allow_interaction),
+                           G_DBUS_CALL_FLAGS_NONE,
+                           G_MAXINT,
+                           priv->bus_cancellable,
+                           NULL, NULL);
+}
+
+static void
+do_config_power_action (GsdMediaKeysManager *manager,
+                        GsdPowerActionType   action_type,
+                        gboolean             in_lock_screen)
+{
+        switch (action_type) {
+        case GSD_POWER_ACTION_SUSPEND:
+                power_action (manager, "Suspend", !in_lock_screen);
+                break;
+        case GSD_POWER_ACTION_INTERACTIVE:
+                if (!in_lock_screen)
+                        gnome_session_shutdown (manager);
+                break;
+        case GSD_POWER_ACTION_SHUTDOWN:
+                power_action (manager, "PowerOff", !in_lock_screen);
+                break;
+        case GSD_POWER_ACTION_HIBERNATE:
+                power_action (manager, "Hibernate", !in_lock_screen);
+                break;
+        case GSD_POWER_ACTION_BLANK:
+        case GSD_POWER_ACTION_LOGOUT:
+        case GSD_POWER_ACTION_NOTHING:
+                /* these actions cannot be handled by media-keys and
+                 * are not used in this context */
+                break;
+        }
+}
+
+static gboolean
+supports_power_action (GsdMediaKeysManager *manager,
+                       GsdPowerActionType   action_type)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        const char *method_name = NULL;
+        g_autoptr(GVariant) variant = NULL;
+        const char *reply;
+        gboolean result = FALSE;
+
+        switch (action_type) {
+        case GSD_POWER_ACTION_SUSPEND:
+                method_name = "CanSuspend";
+                break;
+        case GSD_POWER_ACTION_SHUTDOWN:
+                method_name = "CanPowerOff";
+                break;
+        case GSD_POWER_ACTION_HIBERNATE:
+                method_name = "CanHibernate";
+                break;
+        case GSD_POWER_ACTION_INTERACTIVE:
+        case GSD_POWER_ACTION_BLANK:
+        case GSD_POWER_ACTION_LOGOUT:
+        case GSD_POWER_ACTION_NOTHING:
+                break;
+        }
+
+        if (method_name == NULL)
+                return FALSE;
+
+        variant = g_dbus_proxy_call_sync (priv->logind_proxy,
+                                          method_name,
+                                          NULL,
+                                          G_DBUS_CALL_FLAGS_NONE,
+                                          -1,
+                                          priv->bus_cancellable,
+                                          NULL);
+
+        if (variant == NULL)
+                return FALSE;
+
+        g_variant_get (variant, "(&s)", &reply);
+        if (g_strcmp0 (reply, "yes") == 0)
+                result = TRUE;
+
+        return result;
+}
+
+static void
+do_config_power_button_action (GsdMediaKeysManager *manager,
+                               gboolean             in_lock_screen)
+{
+	GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GsdPowerButtonActionType action_type;
+        GsdPowerActionType action;
+
+        if (priv->power_button_disabled)
+                return;
+
+        /* Always power off VMs when power off is pressed in the menus */
+        if (g_strcmp0 (priv->chassis_type, "vm") == 0) {
+                power_action (manager, "PowerOff", !in_lock_screen);
+                return;
+        }
+
+        action_type = g_settings_get_enum (priv->power_settings, "power-button-action");
+        switch (action_type) {
+        case GSD_POWER_BUTTON_ACTION_SUSPEND:
+                action = GSD_POWER_ACTION_SUSPEND;
+                break;
+        case GSD_POWER_BUTTON_ACTION_HIBERNATE:
+                action = GSD_POWER_ACTION_HIBERNATE;
+                break;
+        case GSD_POWER_BUTTON_ACTION_INTERACTIVE:
+                action = GSD_POWER_ACTION_INTERACTIVE;
+                break;
+        default:
+                g_warn_if_reached ();
+                G_GNUC_FALLTHROUGH;
+        case GSD_POWER_BUTTON_ACTION_NOTHING:
+                /* do nothing */
+                return;
+        }
+
+        if (action != GSD_POWER_ACTION_INTERACTIVE && !supports_power_action (manager, action))
+                action = GSD_POWER_ACTION_INTERACTIVE;
+
+        do_config_power_action (manager, action, in_lock_screen);
+}
+
+static void
+update_brightness_cb (GObject             *source_object,
+                      GAsyncResult        *res,
+                      gpointer             user_data)
+{
+        GError *error = NULL;
+        int percentage;
+        GVariant *variant;
+        GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        const char *icon, *debug;
+        char *connector = NULL;
+
+        /* update the dialog with the new value */
+        if (G_DBUS_PROXY (source_object) == priv->power_keyboard_proxy) {
+                debug = "keyboard";
+        } else {
+                debug = "screen";
+        }
+
+        variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+                                        res, &error);
+        if (variant == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to set new %s percentage: %s",
+                                   debug, error->message);
+                g_error_free (error);
+                return;
+        }
+
+        /* update the dialog with the new value */
+        if (G_DBUS_PROXY (source_object) == priv->power_keyboard_proxy) {
+                icon = "keyboard-brightness-symbolic";
+                g_variant_get (variant, "(i)", &percentage);
+        } else {
+                icon = "display-brightness-symbolic";
+                g_variant_get (variant, "(i&s)", &percentage, &connector);
+        }
+
+        show_osd (manager, icon, NULL, (double) percentage / 100.0, connector);
+        g_variant_unref (variant);
+}
+
+static void
+do_brightness_action (GsdMediaKeysManager *manager,
+                      MediaKeyType type)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        const char *cmd;
+        GDBusProxy *proxy;
+
+        switch (type) {
+        case KEYBOARD_BRIGHTNESS_UP_KEY:
+        case KEYBOARD_BRIGHTNESS_DOWN_KEY:
+        case KEYBOARD_BRIGHTNESS_TOGGLE_KEY:
+                proxy = priv->power_keyboard_proxy;
+                break;
+        case SCREEN_BRIGHTNESS_UP_KEY:
+        case SCREEN_BRIGHTNESS_DOWN_KEY:
+        case SCREEN_BRIGHTNESS_CYCLE_KEY:
+                proxy = priv->power_screen_proxy;
+                break;
+        default:
+                g_assert_not_reached ();
+        }
+
+        if (priv->connection == NULL ||
+            proxy == NULL) {
+                g_warning ("No existing D-Bus connection trying to handle power keys");
+                return;
+        }
+
+        switch (type) {
+        case KEYBOARD_BRIGHTNESS_UP_KEY:
+        case SCREEN_BRIGHTNESS_UP_KEY:
+                cmd = "StepUp";
+                break;
+        case KEYBOARD_BRIGHTNESS_DOWN_KEY:
+        case SCREEN_BRIGHTNESS_DOWN_KEY:
+                cmd = "StepDown";
+                break;
+        case KEYBOARD_BRIGHTNESS_TOGGLE_KEY:
+                cmd = "Toggle";
+                break;
+        case SCREEN_BRIGHTNESS_CYCLE_KEY:
+                cmd = "Cycle";
+                break;
+        default:
+                g_assert_not_reached ();
+        }
+
+        /* call into the power plugin */
+        g_dbus_proxy_call (proxy,
+                           cmd,
+                           NULL,
+                           G_DBUS_CALL_FLAGS_NONE,
+                           -1,
+                           NULL,
+                           update_brightness_cb,
+                           manager);
+}
+
+static void
+do_battery_action (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        gdouble percentage;
+        UpDeviceKind kind;
+        gchar *icon_name;
+
+        g_return_if_fail (priv->composite_device != NULL);
+
+        g_object_get (priv->composite_device,
+                      "kind", &kind,
+                      "icon-name", &icon_name,
+                      "percentage", &percentage,
+                      NULL);
+
+        if (kind == UP_DEVICE_KIND_UPS || kind == UP_DEVICE_KIND_BATTERY) {
+                g_debug ("showing battery level OSD");
+                show_osd (manager, icon_name, NULL, (double) percentage / 100.0, NULL);
+        }
+
+        g_free (icon_name);
+}
+
+static gboolean
+get_rfkill_property (GsdMediaKeysManager *manager,
+                     const char          *property)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GVariant *v;
+        gboolean ret;
+
+        v = g_dbus_proxy_get_cached_property (priv->rfkill_proxy, property);
+        if (!v)
+                return FALSE;
+        ret = g_variant_get_boolean (v);
+        g_variant_unref (v);
+
+        return ret;
+}
+
+typedef struct {
+        GsdMediaKeysManager *manager;
+        char *property;
+        gboolean bluetooth;
+        gboolean target_state;
+} RfkillData;
+
+static void
+set_rfkill_complete (GObject      *object,
+                     GAsyncResult *result,
+                     gpointer      user_data)
+{
+        GError *error = NULL;
+        GVariant *variant;
+        RfkillData *data = user_data;
+
+        variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), result, &error);
+
+        if (variant == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to set '%s' property: %s", data->property, error->message);
+                g_error_free (error);
+                goto out;
+        }
+        g_variant_unref (variant);
+
+        g_debug ("Finished changing rfkill, property %s is now %s",
+                 data->property, data->target_state ? "true" : "false");
+
+        if (data->bluetooth) {
+                if (data->target_state)
+                        show_osd (data->manager, "bluetooth-disabled-symbolic",
+                                  _("Bluetooth disabled"), -1, NULL);
+                else
+                        show_osd (data->manager, "bluetooth-active-symbolic",
+                                  _("Bluetooth enabled"), -1, NULL);
+        } else {
+                if (data->target_state)
+                        show_osd (data->manager, "airplane-mode-symbolic",
+                                  _("Airplane mode enabled"), -1, NULL);
+                else
+                        show_osd (data->manager, "network-wireless-signal-excellent-symbolic",
+                                  _("Airplane mode disabled"), -1, NULL);
+        }
+
+out:
+        g_free (data->property);
+        g_free (data);
+}
+
+static void
+do_rfkill_action (GsdMediaKeysManager *manager,
+                  gboolean             bluetooth)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        const char *has_mode, *hw_mode, *mode;
+        gboolean new_state;
+        guint64 current_time;
+        RfkillData *data;
+
+        has_mode = bluetooth ? "BluetoothHasAirplaneMode" : "HasAirplaneMode";
+        hw_mode = bluetooth ? "BluetoothHardwareAirplaneMode" : "HardwareAirplaneMode";
+        mode = bluetooth ? "BluetoothAirplaneMode" : "AirplaneMode";
+
+        if (priv->rfkill_proxy == NULL)
+                return;
+
+        /* Some hardwares can generate multiple rfkill events from different
+         * drivers, on a single hotkey press. Only process the first event and
+         * debounce the others */
+        current_time = g_get_monotonic_time ();
+        if (current_time - priv->rfkill_last_time < G_USEC_PER_SEC)
+                return;
+
+        priv->rfkill_last_time = current_time;
+
+        if (get_rfkill_property (manager, has_mode) == FALSE)
+                return;
+
+        if (get_rfkill_property (manager, hw_mode)) {
+                show_osd (manager, "airplane-mode-symbolic",
+                          _("Hardware Airplane Mode"), -1, NULL);
+                return;
+        }
+
+        new_state = !get_rfkill_property (manager, mode);
+        data = g_new0 (RfkillData, 1);
+        data->manager = manager;
+        data->property = g_strdup (mode);
+        data->bluetooth = bluetooth;
+        data->target_state = new_state;
+        g_dbus_proxy_call (priv->rfkill_proxy,
+                           "org.freedesktop.DBus.Properties.Set",
+                           g_variant_new ("(ssv)",
+                                          "org.gnome.SettingsDaemon.Rfkill",
+                                          data->property,
+                                          g_variant_new_boolean (new_state)),
+                           G_DBUS_CALL_FLAGS_NONE, -1,
+                           priv->rfkill_cancellable,
+                           set_rfkill_complete, data);
+
+        g_debug ("Setting rfkill property %s to %s",
+                 data->property, new_state ? "true" : "false");
+}
+
+static void
+do_custom_action (GsdMediaKeysManager *manager,
+                  const gchar         *device_node,
+                  MediaKey            *key,
+                  gint64               timestamp)
+{
+        g_debug ("Launching custom action for key (on device node %s)", device_node);
+
+	execute (manager, key->custom_command, timestamp);
+}
+
+static gboolean
+do_action (GsdMediaKeysManager *manager,
+           const gchar         *device_node,
+           guint                mode,
+           MediaKeyType         type,
+           gint64               timestamp)
+{
+        g_debug ("Launching action for key type '%d' (on device node %s)", type, device_node);
+
+        gboolean power_action_noninteractive = (POWER_KEYS_MODE_NO_DIALOG & mode);
+
+        switch (type) {
+        case TOUCHPAD_KEY:
+                do_touchpad_action (manager);
+                break;
+        case TOUCHPAD_ON_KEY:
+                do_touchpad_osd_action (manager, TRUE);
+                break;
+        case TOUCHPAD_OFF_KEY:
+                do_touchpad_osd_action (manager, FALSE);
+                break;
+        case MUTE_KEY:
+        case VOLUME_DOWN_KEY:
+        case VOLUME_UP_KEY:
+                do_sound_action (manager, device_node, type, SOUND_ACTION_FLAG_IS_OUTPUT);
+                break;
+        case MIC_MUTE_KEY:
+                do_sound_action (manager, device_node, MUTE_KEY, SOUND_ACTION_FLAG_IS_QUIET);
+                break;
+        case MUTE_QUIET_KEY:
+                do_sound_action (manager, device_node, MUTE_KEY,
+                                 SOUND_ACTION_FLAG_IS_OUTPUT | SOUND_ACTION_FLAG_IS_QUIET);
+                break;
+        case VOLUME_DOWN_QUIET_KEY:
+                do_sound_action (manager, device_node, VOLUME_DOWN_KEY,
+                                 SOUND_ACTION_FLAG_IS_OUTPUT | SOUND_ACTION_FLAG_IS_QUIET);
+                break;
+        case VOLUME_UP_QUIET_KEY:
+                do_sound_action (manager, device_node, VOLUME_UP_KEY,
+                                 SOUND_ACTION_FLAG_IS_OUTPUT | SOUND_ACTION_FLAG_IS_QUIET);
+                break;
+        case VOLUME_DOWN_PRECISE_KEY:
+                do_sound_action (manager, device_node, VOLUME_DOWN_KEY,
+                                 SOUND_ACTION_FLAG_IS_OUTPUT | SOUND_ACTION_FLAG_IS_PRECISE);
+                break;
+        case VOLUME_UP_PRECISE_KEY:
+                do_sound_action (manager, device_node, VOLUME_UP_KEY,
+                                 SOUND_ACTION_FLAG_IS_OUTPUT | SOUND_ACTION_FLAG_IS_PRECISE);
+                break;
+        case LOGOUT_KEY:
+                gnome_session_shutdown (manager);
+                break;
+        case EJECT_KEY:
+                do_eject_action (manager);
+                break;
+        case HOME_KEY:
+                do_home_key_action (manager, timestamp);
+                break;
+        case SEARCH_KEY:
+                do_search_action (manager, timestamp);
+                break;
+        case EMAIL_KEY:
+                do_url_action (manager, "mailto", timestamp);
+                break;
+        case SCREENSAVER_KEY:
+                do_lock_screensaver (manager);
+                break;
+        case HELP_KEY:
+                do_url_action (manager, "ghelp", timestamp);
+                break;
+        case WWW_KEY:
+                do_url_action (manager, "http", timestamp);
+                break;
+        case MEDIA_KEY:
+                do_media_action (manager, timestamp);
+                break;
+        case CALCULATOR_KEY:
+                do_execute_desktop_or_desktop (manager, "org.gnome.Calculator.desktop", "gnome-calculator.desktop", timestamp);
+                break;
+        case CONTROL_CENTER_KEY:
+                do_execute_desktop_or_desktop (manager, "org.gnome.Settings.desktop", NULL, timestamp);
+                break;
+        case PLAY_KEY:
+                return do_multimedia_player_action (manager, "Play");
+        case PAUSE_KEY:
+                return do_multimedia_player_action (manager, "Pause");
+        case STOP_KEY:
+                return do_multimedia_player_action (manager, "Stop");
+        case PREVIOUS_KEY:
+                return do_multimedia_player_action (manager, "Previous");
+        case NEXT_KEY:
+                return do_multimedia_player_action (manager, "Next");
+        case REWIND_KEY:
+                return do_multimedia_player_action (manager, "Rewind");
+        case FORWARD_KEY:
+                return do_multimedia_player_action (manager, "FastForward");
+        case REPEAT_KEY:
+                return do_multimedia_player_action (manager, "Repeat");
+        case RANDOM_KEY:
+                return do_multimedia_player_action (manager, "Shuffle");
+        case ROTATE_VIDEO_LOCK_KEY:
+                do_video_rotate_lock_action (manager, timestamp);
+                break;
+        case MAGNIFIER_KEY:
+                do_magnifier_action (manager);
+                break;
+        case SCREENREADER_KEY:
+                do_screenreader_action (manager);
+                break;
+        case ON_SCREEN_KEYBOARD_KEY:
+                do_on_screen_keyboard_action (manager);
+                break;
+	case INCREASE_TEXT_KEY:
+	case DECREASE_TEXT_KEY:
+		do_text_size_action (manager, type);
+		break;
+	case MAGNIFIER_ZOOM_IN_KEY:
+	case MAGNIFIER_ZOOM_OUT_KEY:
+		do_magnifier_zoom_action (manager, type);
+		break;
+	case TOGGLE_CONTRAST_KEY:
+		do_toggle_contrast_action (manager);
+		break;
+        case POWER_KEY:
+                do_config_power_button_action (manager, power_action_noninteractive);
+                break;
+        case SUSPEND_KEY:
+                do_config_power_action (manager, GSD_POWER_ACTION_SUSPEND, power_action_noninteractive);
+                break;
+        case HIBERNATE_KEY:
+                do_config_power_action (manager, GSD_POWER_ACTION_HIBERNATE, power_action_noninteractive);
+                break;
+        case SCREEN_BRIGHTNESS_UP_KEY:
+        case SCREEN_BRIGHTNESS_DOWN_KEY:
+        case SCREEN_BRIGHTNESS_CYCLE_KEY:
+        case KEYBOARD_BRIGHTNESS_UP_KEY:
+        case KEYBOARD_BRIGHTNESS_DOWN_KEY:
+        case KEYBOARD_BRIGHTNESS_TOGGLE_KEY:
+                do_brightness_action (manager, type);
+                break;
+        case BATTERY_KEY:
+                do_battery_action (manager);
+                break;
+        case RFKILL_KEY:
+                do_rfkill_action (manager, FALSE);
+                break;
+        case BLUETOOTH_RFKILL_KEY:
+                do_rfkill_action (manager, TRUE);
+                break;
+        /* Note, no default so compiler catches missing keys */
+        case CUSTOM_KEY:
+                g_assert_not_reached ();
+        }
+
+        return FALSE;
+}
+
+static void
+on_accelerator_activated (ShellKeyGrabber     *grabber,
+                          guint                accel_id,
+                          GVariant            *parameters,
+                          GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GVariantDict dict;
+        guint i;
+        guint deviceid;
+        gchar *device_node;
+        guint timestamp;
+        guint mode;
+
+        g_variant_dict_init (&dict, parameters);
+
+        if (!g_variant_dict_lookup (&dict, "device-id", "u", &deviceid))
+              deviceid = 0;
+        if (!g_variant_dict_lookup (&dict, "device-node", "s", &device_node))
+              device_node = NULL;
+        if (!g_variant_dict_lookup (&dict, "timestamp", "u", ×tamp))
+              timestamp = GDK_CURRENT_TIME;
+        if (!g_variant_dict_lookup (&dict, "action-mode", "u", &mode))
+              mode = 0;
+
+	if (!device_node && !gnome_settings_is_wayland ())
+              device_node = xdevice_get_device_node (deviceid);
+
+        g_debug ("Received accel id %u (device-id: %u, timestamp: %u, mode: 0x%X)",
+                 accel_id, deviceid, timestamp, mode);
+
+        for (i = 0; i < priv->keys->len; i++) {
+                MediaKey *key;
+                guint j;
+
+                key = g_ptr_array_index (priv->keys, i);
+
+                for (j = 0; j < key->accel_ids->len; j++) {
+                        if (g_array_index (key->accel_ids, guint, j) == accel_id)
+                                break;
+                }
+                if (j >= key->accel_ids->len)
+                        continue;
+
+                if (key->key_type == CUSTOM_KEY)
+                        do_custom_action (manager, device_node, key, timestamp);
+                else
+                        do_action (manager, device_node, mode, key->key_type, timestamp);
+
+                g_free (device_node);
+                return;
+        }
+
+        g_warning ("Could not find accelerator for accel id %u", accel_id);
+        g_free (device_node);
+}
+
+static void
+update_theme_settings (GSettings           *settings,
+		       const char          *key,
+		       GsdMediaKeysManager *manager)
+{
+	GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+	char *theme;
+
+	theme = g_settings_get_string (priv->interface_settings, key);
+	if (g_str_equal (theme, HIGH_CONTRAST)) {
+		g_free (theme);
+	} else {
+		if (g_str_equal (key, "gtk-theme")) {
+			g_free (priv->gtk_theme);
+			priv->gtk_theme = theme;
+		} else {
+			g_free (priv->icon_theme);
+			priv->icon_theme = theme;
+		}
+	}
+}
+
+typedef struct {
+        GvcHeadsetPortChoice choice;
+        gchar *name;
+} AudioSelectionChoice;
+
+static AudioSelectionChoice audio_selection_choices[] = {
+        { GVC_HEADSET_PORT_CHOICE_HEADPHONES,   "headphones" },
+        { GVC_HEADSET_PORT_CHOICE_HEADSET,      "headset" },
+        { GVC_HEADSET_PORT_CHOICE_MIC,          "microphone" },
+};
+
+static void
+audio_selection_done (GDBusConnection *connection,
+                      const gchar     *sender_name,
+                      const gchar     *object_path,
+                      const gchar     *interface_name,
+                      const gchar     *signal_name,
+                      GVariant        *parameters,
+                      gpointer         data)
+{
+        GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (data);
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        const gchar *choice;
+        guint i;
+
+        if (!priv->audio_selection_requested)
+                return;
+
+        choice = NULL;
+        g_variant_get_child (parameters, 0, "&s", &choice);
+        if (!choice)
+                return;
+
+        for (i = 0; i < G_N_ELEMENTS (audio_selection_choices); ++i) {
+                if (g_str_equal (choice, audio_selection_choices[i].name)) {
+                        gvc_mixer_control_set_headset_port (priv->volume,
+                                                            priv->audio_selection_device_id,
+                                                            audio_selection_choices[i].choice);
+                        break;
+                }
+        }
+
+        priv->audio_selection_requested = FALSE;
+}
+
+static void
+audio_selection_needed (GvcMixerControl      *control,
+                        guint                 id,
+                        gboolean              show_dialog,
+                        GvcHeadsetPortChoice  choices,
+                        GsdMediaKeysManager  *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        gchar *args[G_N_ELEMENTS (audio_selection_choices) + 1];
+        guint i, n;
+
+        if (!priv->audio_selection_conn)
+                return;
+
+        if (priv->audio_selection_requested) {
+                g_dbus_connection_call (priv->audio_selection_conn,
+                                        AUDIO_SELECTION_DBUS_NAME,
+                                        AUDIO_SELECTION_DBUS_PATH,
+                                        AUDIO_SELECTION_DBUS_INTERFACE,
+                                        "Close", NULL, NULL,
+                                        G_DBUS_CALL_FLAGS_NONE,
+                                        -1, NULL, NULL, NULL);
+                priv->audio_selection_requested = FALSE;
+        }
+
+        if (!show_dialog)
+                return;
+
+        n = 0;
+        for (i = 0; i < G_N_ELEMENTS (audio_selection_choices); ++i) {
+                if (choices & audio_selection_choices[i].choice)
+                        args[n++] = audio_selection_choices[i].name;
+        }
+        args[n] = NULL;
+
+        priv->audio_selection_requested = TRUE;
+        priv->audio_selection_device_id = id;
+        g_dbus_connection_call (priv->audio_selection_conn,
+                                AUDIO_SELECTION_DBUS_NAME,
+                                AUDIO_SELECTION_DBUS_PATH,
+                                AUDIO_SELECTION_DBUS_INTERFACE,
+                                "Open",
+                                g_variant_new ("(^as)", args),
+                                NULL,
+                                G_DBUS_CALL_FLAGS_NONE,
+                                -1, NULL, NULL, NULL);
+}
+
+static void
+audio_selection_appeared (GDBusConnection *connection,
+                          const gchar     *name,
+                          const gchar     *name_owner,
+                          gpointer         data)
+{
+        GsdMediaKeysManager *manager = data;
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        priv->audio_selection_conn = connection;
+        priv->audio_selection_signal_id =
+                g_dbus_connection_signal_subscribe (connection,
+                                                    AUDIO_SELECTION_DBUS_NAME,
+                                                    AUDIO_SELECTION_DBUS_INTERFACE,
+                                                    "DeviceSelected",
+                                                    AUDIO_SELECTION_DBUS_PATH,
+                                                    NULL,
+                                                    G_DBUS_SIGNAL_FLAGS_NONE,
+                                                    audio_selection_done,
+                                                    manager,
+                                                    NULL);
+}
+
+static void
+clear_audio_selection (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        if (priv->audio_selection_signal_id)
+                g_dbus_connection_signal_unsubscribe (priv->audio_selection_conn,
+                                                      priv->audio_selection_signal_id);
+        priv->audio_selection_signal_id = 0;
+        priv->audio_selection_conn = NULL;
+}
+
+static void
+audio_selection_vanished (GDBusConnection *connection,
+                          const gchar     *name,
+                          gpointer         data)
+{
+        if (connection)
+                clear_audio_selection (data);
+}
+
+static void
+initialize_volume_handler (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        /* initialise Volume handler
+         *
+         * We do this one here to force checking gstreamer cache, etc.
+         * The rest (grabbing and setting the keys) can happen in an
+         * idle.
+         */
+        gnome_settings_profile_start ("gvc_mixer_control_new");
+
+        priv->volume = gvc_mixer_control_new ("GNOME Volume Control Media Keys");
+
+        g_signal_connect (priv->volume,
+                          "state-changed",
+                          G_CALLBACK (on_control_state_changed),
+                          manager);
+        g_signal_connect (priv->volume,
+                          "default-sink-changed",
+                          G_CALLBACK (on_control_default_sink_changed),
+                          manager);
+        g_signal_connect (priv->volume,
+                          "default-source-changed",
+                          G_CALLBACK (on_control_default_source_changed),
+                          manager);
+        g_signal_connect (priv->volume,
+                          "stream-removed",
+                          G_CALLBACK (on_control_stream_removed),
+                          manager);
+        g_signal_connect (priv->volume,
+                          "audio-device-selection-needed",
+                          G_CALLBACK (audio_selection_needed),
+                          manager);
+
+        gvc_mixer_control_open (priv->volume);
+
+        priv->audio_selection_watch_id =
+                g_bus_watch_name (G_BUS_TYPE_SESSION,
+                                  AUDIO_SELECTION_DBUS_NAME,
+                                  G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                  audio_selection_appeared,
+                                  audio_selection_vanished,
+                                  manager,
+                                  NULL);
+
+        gnome_settings_profile_end ("gvc_mixer_control_new");
+}
+
+static void
+on_key_grabber_ready (GObject      *source,
+                      GAsyncResult *result,
+                      gpointer      data)
+{
+        GsdMediaKeysManager *manager = data;
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GError *error = NULL;
+
+        priv->key_grabber = shell_key_grabber_proxy_new_for_bus_finish (result, &error);
+
+        if (!priv->key_grabber) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to create proxy for key grabber: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (priv->key_grabber),
+                                          SHELL_GRABBER_CALL_TIMEOUT);
+
+        g_signal_connect (priv->key_grabber, "accelerator-activated",
+                          G_CALLBACK (on_accelerator_activated), manager);
+
+        init_kbd (manager);
+}
+
+static void
+shell_presence_changed (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        gchar *name_owner;
+
+        name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (priv->shell_proxy));
+
+        g_ptr_array_set_size (priv->keys, 0);
+        g_clear_object (&priv->key_grabber);
+
+        if (name_owner) {
+                shell_key_grabber_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+                                                     0,
+                                                     name_owner,
+                                                     SHELL_DBUS_PATH,
+                                                     priv->grab_cancellable,
+                                                     on_key_grabber_ready, manager);
+                g_free (name_owner);
+        }
+}
+
+static void
+on_rfkill_proxy_ready (GObject      *source,
+                       GAsyncResult *result,
+                       gpointer      data)
+{
+        GsdMediaKeysManager *manager = data;
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        priv->rfkill_proxy =
+                g_dbus_proxy_new_for_bus_finish (result, NULL);
+}
+
+static void
+rfkill_appeared_cb (GDBusConnection *connection,
+                    const gchar     *name,
+                    const gchar     *name_owner,
+                    gpointer         user_data)
+{
+        GsdMediaKeysManager *manager = user_data;
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+                                  0, NULL,
+                                  "org.gnome.SettingsDaemon.Rfkill",
+                                  "/org/gnome/SettingsDaemon/Rfkill",
+                                  "org.gnome.SettingsDaemon.Rfkill",
+                                  priv->rfkill_cancellable,
+                                  on_rfkill_proxy_ready, manager);
+}
+
+static gboolean
+start_media_keys_idle_cb (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        g_debug ("Starting media_keys manager");
+        gnome_settings_profile_start (NULL);
+
+        priv->keys = g_ptr_array_new_with_free_func ((GDestroyNotify) media_key_unref);
+        priv->keys_to_sync = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) media_key_unref, NULL);
+
+        initialize_volume_handler (manager);
+
+        priv->settings = g_settings_new (SETTINGS_BINDING_DIR);
+        g_signal_connect (G_OBJECT (priv->settings), "changed",
+                          G_CALLBACK (gsettings_changed_cb), manager);
+        g_signal_connect (G_OBJECT (priv->settings), "changed::custom-keybindings",
+                          G_CALLBACK (gsettings_custom_changed_cb), manager);
+
+        priv->custom_settings =
+          g_hash_table_new_full (g_str_hash, g_str_equal,
+                                 g_free, g_object_unref);
+
+        priv->sound_settings = g_settings_new (SETTINGS_SOUND_DIR);
+        g_signal_connect (G_OBJECT (priv->sound_settings),
+			  "changed::" ALLOW_VOLUME_ABOVE_100_PERCENT_KEY,
+                          G_CALLBACK (allow_volume_above_100_percent_changed_cb), manager);
+        allow_volume_above_100_percent_changed_cb (priv->sound_settings,
+                                                   ALLOW_VOLUME_ABOVE_100_PERCENT_KEY, manager);
+
+        /* for the power plugin interface code */
+        priv->power_settings = g_settings_new (SETTINGS_POWER_DIR);
+        priv->chassis_type = gnome_settings_get_chassis_type ();
+
+        /* Logic from http://git.gnome.org/browse/gnome-shell/tree/js/ui/status/accessibility.js#n163 */
+        priv->interface_settings = g_settings_new (SETTINGS_INTERFACE_DIR);
+        g_signal_connect (G_OBJECT (priv->interface_settings), "changed::gtk-theme",
+			  G_CALLBACK (update_theme_settings), manager);
+        g_signal_connect (G_OBJECT (priv->interface_settings), "changed::icon-theme",
+			  G_CALLBACK (update_theme_settings), manager);
+	priv->gtk_theme = g_settings_get_string (priv->interface_settings, "gtk-theme");
+	if (g_str_equal (priv->gtk_theme, HIGH_CONTRAST)) {
+		g_free (priv->gtk_theme);
+		priv->gtk_theme = NULL;
+	}
+	priv->icon_theme = g_settings_get_string (priv->interface_settings, "icon-theme");
+
+        priv->grab_cancellable = g_cancellable_new ();
+        priv->rfkill_cancellable = g_cancellable_new ();
+
+        priv->shell_proxy = gnome_settings_bus_get_shell_proxy ();
+        g_signal_connect_swapped (priv->shell_proxy, "notify::g-name-owner",
+                                  G_CALLBACK (shell_presence_changed), manager);
+        shell_presence_changed (manager);
+
+        priv->rfkill_watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+                                                  "org.gnome.SettingsDaemon.Rfkill",
+                                                  G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                                  rfkill_appeared_cb,
+                                                  NULL,
+                                                  manager, NULL);
+
+        g_debug ("Starting mpris controller");
+        priv->mpris_controller = mpris_controller_new ();
+
+        /* Rotation */
+        priv->iio_sensor_watch_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM,
+                                                      "net.hadess.SensorProxy",
+                                                      G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                                      iio_sensor_appeared_cb,
+                                                      iio_sensor_disappeared_cb,
+                                                      manager, NULL);
+
+        gnome_settings_profile_end (NULL);
+
+        priv->start_idle_id = 0;
+
+        return FALSE;
+}
+
+static GVariant *
+map_keybinding (GVariant *variant, GVariant *old_default, GVariant *new_default)
+{
+        g_autoptr(GPtrArray) array = g_ptr_array_new ();
+        g_autofree const gchar **defaults = NULL;
+        const gchar *old_default_value;
+        const gchar **pos;
+        const gchar *value;
+
+        defaults = g_variant_get_strv (new_default, NULL);
+        g_return_val_if_fail (defaults != NULL, NULL);
+        pos = defaults;
+
+        value = g_variant_get_string (variant, NULL);
+        old_default_value = g_variant_get_string (old_default, NULL);
+
+        /* Reset the keybinding configuration even if the user has the default
+         * configured explicitly (as the key will be bound by the corresponding
+         * static binding now). */
+        if (g_strcmp0 (value, old_default_value) == 0)
+                return NULL;
+
+        /* If the user has a custom value that is not in the list, then
+         * insert it instead of the first default entry. */
+        if (!g_strv_contains (defaults, value)) {
+                g_ptr_array_add (array, (gpointer) value);
+                if (*pos)
+                        pos++;
+        }
+
+        /* Add all remaining default values */
+        for (; *pos; pos++)
+              g_ptr_array_add (array, (gpointer) *pos);
+
+        g_ptr_array_add (array, NULL);
+
+        return g_variant_new_strv ((const gchar * const *) array->pdata, -1);
+}
+
+static void
+migrate_keybinding_settings (void)
+{
+        GsdSettingsMigrateEntry binding_entries[] = {
+                { "calculator",                 "calculator",                   map_keybinding },
+                { "control-center",             "control-center",               map_keybinding },
+                { "email",                      "email",                        map_keybinding },
+                { "eject",                      "eject",                        map_keybinding },
+                { "help",                       "help",                         map_keybinding },
+                { "home",                       "home",                         map_keybinding },
+                { "media",                      "media",                        map_keybinding },
+                { "next",                       "next",                         map_keybinding },
+                { "pause",                      "pause",                        map_keybinding },
+                { "play",                       "play",                         map_keybinding },
+                { "logout",                     "logout",                       map_keybinding },
+                { "previous",                   "previous",                     map_keybinding },
+                { "screensaver",                "screensaver",                  map_keybinding },
+                { "search",                     "search",                       map_keybinding },
+                { "stop",                       "stop",                         map_keybinding },
+                { "volume-down",                "volume-down",                  map_keybinding },
+                { "volume-mute",                "volume-mute",                  map_keybinding },
+                { "volume-up",                  "volume-up",                    map_keybinding },
+                { "mic-mute",                   "mic-mute",                     map_keybinding },
+                { "www",                        "www",                          map_keybinding },
+                { "magnifier",                  "magnifier",                    map_keybinding },
+                { "screenreader",               "screenreader",                 map_keybinding },
+                { "on-screen-keyboard",         "on-screen-keyboard",           map_keybinding },
+                { "increase-text-size",         "increase-text-size",           map_keybinding },
+                { "decrease-text-size",         "decrease-text-size",           map_keybinding },
+                { "toggle-contrast",            "toggle-contrast",              map_keybinding },
+                { "magnifier-zoom-in",          "magnifier-zoom-in",            map_keybinding },
+                { "magnifier-zoom-out",         "magnifier-zoom-out",           map_keybinding },
+        };
+
+        gsd_settings_migrate_check ("org.gnome.settings-daemon.plugins.media-keys.deprecated",
+                                    "/org/gnome/settings-daemon/plugins/media-keys/",
+                                    "org.gnome.settings-daemon.plugins.media-keys",
+                                    "/org/gnome/settings-daemon/plugins/media-keys/",
+                                    binding_entries, G_N_ELEMENTS (binding_entries));
+}
+
+gboolean
+gsd_media_keys_manager_start (GsdMediaKeysManager *manager,
+                              GError             **error)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        const char * const subsystems[] = { "input", "usb", "sound", NULL };
+
+        gnome_settings_profile_start (NULL);
+
+        migrate_keybinding_settings ();
+
+#if HAVE_GUDEV
+        priv->streams = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+        priv->udev_client = g_udev_client_new (subsystems);
+#endif
+
+        priv->start_idle_id = g_idle_add ((GSourceFunc) start_media_keys_idle_cb, manager);
+        g_source_set_name_by_id (priv->start_idle_id, "[gnome-settings-daemon] start_media_keys_idle_cb");
+
+        register_manager (manager_object);
+
+        gnome_settings_profile_end (NULL);
+
+        return TRUE;
+}
+
+void
+gsd_media_keys_manager_stop (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        g_debug ("Stopping media_keys manager");
+
+        if (priv->start_idle_id != 0) {
+                g_source_remove (priv->start_idle_id);
+                priv->start_idle_id = 0;
+        }
+
+        if (priv->bus_cancellable != NULL) {
+                g_cancellable_cancel (priv->bus_cancellable);
+                g_object_unref (priv->bus_cancellable);
+                priv->bus_cancellable = NULL;
+        }
+
+        if (priv->gtksettings != NULL) {
+                g_signal_handlers_disconnect_by_func (priv->gtksettings, sound_theme_changed, manager);
+                priv->gtksettings = NULL;
+        }
+
+        if (priv->rfkill_watch_id > 0) {
+                g_bus_unwatch_name (priv->rfkill_watch_id);
+                priv->rfkill_watch_id = 0;
+        }
+
+        if (priv->iio_sensor_watch_id > 0) {
+                g_bus_unwatch_name (priv->iio_sensor_watch_id);
+                priv->iio_sensor_watch_id = 0;
+        }
+
+        if (priv->inhibit_suspend_fd != -1) {
+                close (priv->inhibit_suspend_fd);
+                priv->inhibit_suspend_fd = -1;
+                priv->inhibit_suspend_taken = FALSE;
+        }
+
+        if (priv->reenable_power_button_timer_id) {
+                g_source_remove (priv->reenable_power_button_timer_id);
+                priv->reenable_power_button_timer_id = 0;
+        }
+
+        g_clear_pointer (&priv->ca, ca_context_destroy);
+
+#if HAVE_GUDEV
+        g_clear_pointer (&priv->streams, g_hash_table_destroy);
+        g_clear_object (&priv->udev_client);
+#endif /* HAVE_GUDEV */
+
+        g_clear_object (&priv->logind_proxy);
+        g_clear_object (&priv->settings);
+        g_clear_object (&priv->sound_settings);
+        g_clear_object (&priv->power_settings);
+        g_clear_object (&priv->power_proxy);
+        g_clear_object (&priv->power_screen_proxy);
+        g_clear_object (&priv->power_keyboard_proxy);
+        g_clear_object (&priv->composite_device);
+        g_clear_object (&priv->mpris_controller);
+        g_clear_object (&priv->iio_sensor_proxy);
+        g_clear_pointer (&priv->chassis_type, g_free);
+        g_clear_object (&priv->connection);
+
+        if (priv->keys_sync_data) {
+                /* Cancel ongoing sync. */
+                priv->keys_sync_data->cancelled = TRUE;
+                priv->keys_sync_data = NULL;
+        }
+        if (priv->keys_sync_source_id)
+                g_source_remove (priv->keys_sync_source_id);
+        priv->keys_sync_source_id = 0;
+
+        /* Remove all grabs; i.e.:
+         *  - add all keys to the sync queue
+         *  - remove all keys from the internal keys list
+         *  - call the function to start a sync
+         *  - "cancel" the sync operation as the manager will be gone
+         */
+        if (priv->keys != NULL) {
+                while (priv->keys->len) {
+                        MediaKey *key = g_ptr_array_index (priv->keys, 0);
+                        g_hash_table_add (priv->keys_to_sync, media_key_ref (key));
+                        g_ptr_array_remove_index_fast (priv->keys, 0);
+                }
+
+                keys_sync_start (manager);
+
+                g_clear_pointer (&priv->keys, g_ptr_array_unref);
+        }
+
+        g_clear_pointer (&priv->keys_to_sync, g_hash_table_destroy);
+
+        g_clear_object (&priv->key_grabber);
+
+        if (priv->grab_cancellable != NULL) {
+                g_cancellable_cancel (priv->grab_cancellable);
+                g_clear_object (&priv->grab_cancellable);
+        }
+
+        if (priv->rfkill_cancellable != NULL) {
+                g_cancellable_cancel (priv->rfkill_cancellable);
+                g_clear_object (&priv->rfkill_cancellable);
+        }
+
+        g_clear_object (&priv->sink);
+        g_clear_object (&priv->source);
+        g_clear_object (&priv->volume);
+        g_clear_object (&priv->shell_proxy);
+
+        if (priv->audio_selection_watch_id)
+                g_bus_unwatch_name (priv->audio_selection_watch_id);
+        priv->audio_selection_watch_id = 0;
+        clear_audio_selection (manager);
+}
+
+static void
+inhibit_suspend_done (GObject      *source,
+                      GAsyncResult *result,
+                      gpointer      user_data)
+{
+        GDBusProxy *proxy = G_DBUS_PROXY (source);
+        GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GError *error = NULL;
+        GVariant *res;
+        GUnixFDList *fd_list = NULL;
+        gint idx;
+
+        res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error);
+        if (res == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Unable to inhibit suspend: %s", error->message);
+                g_error_free (error);
+        } else {
+                g_variant_get (res, "(h)", &idx);
+                priv->inhibit_suspend_fd = g_unix_fd_list_get (fd_list, idx, &error);
+                if (priv->inhibit_suspend_fd == -1) {
+                        g_warning ("Failed to receive system suspend inhibitor fd: %s", error->message);
+                        g_error_free (error);
+                }
+                g_debug ("System suspend inhibitor fd is %d", priv->inhibit_suspend_fd);
+                g_object_unref (fd_list);
+                g_variant_unref (res);
+        }
+}
+
+/* We take a delay inhibitor here, which causes logind to send a PrepareForSleep
+ * signal, so that we can set power_button_disabled on suspend.
+ */
+static void
+inhibit_suspend (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        if (priv->inhibit_suspend_taken) {
+                g_debug ("already inhibited suspend");
+                return;
+        }
+        g_debug ("Adding suspend delay inhibitor");
+        priv->inhibit_suspend_taken = TRUE;
+        g_dbus_proxy_call_with_unix_fd_list (priv->logind_proxy,
+                                             "Inhibit",
+                                             g_variant_new ("(ssss)",
+                                                            "sleep",
+                                                            g_get_user_name (),
+                                                            "GNOME handling keypresses",
+                                                            "delay"),
+                                             0,
+                                             G_MAXINT,
+                                             NULL,
+                                             NULL,
+                                             inhibit_suspend_done,
+                                             manager);
+}
+
+static void
+uninhibit_suspend (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        if (priv->inhibit_suspend_fd == -1) {
+                g_debug ("no suspend delay inhibitor");
+                return;
+        }
+        g_debug ("Removing suspend delay inhibitor");
+        close (priv->inhibit_suspend_fd);
+        priv->inhibit_suspend_fd = -1;
+        priv->inhibit_suspend_taken = FALSE;
+}
+
+static gboolean
+reenable_power_button_timer_cb (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        priv->power_button_disabled = FALSE;
+        /* This is a one shot timer. */
+        priv->reenable_power_button_timer_id = 0;
+        return G_SOURCE_REMOVE;
+}
+
+static void
+setup_reenable_power_button_timer (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        if (priv->reenable_power_button_timer_id != 0)
+                return;
+
+        priv->reenable_power_button_timer_id =
+                g_timeout_add (GSD_REENABLE_POWER_BUTTON_DELAY,
+                               (GSourceFunc) reenable_power_button_timer_cb,
+                               manager);
+        g_source_set_name_by_id (priv->reenable_power_button_timer_id,
+                                 "[GsdMediaKeysManager] Reenable power button timer");
+}
+
+static void
+stop_reenable_power_button_timer (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        if (priv->reenable_power_button_timer_id == 0)
+                return;
+
+        g_source_remove (priv->reenable_power_button_timer_id);
+        priv->reenable_power_button_timer_id = 0;
+}
+
+static void
+logind_proxy_signal_cb (GDBusProxy  *proxy,
+                        const gchar *sender_name,
+                        const gchar *signal_name,
+                        GVariant    *parameters,
+                        gpointer     user_data)
+{
+        GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        gboolean is_about_to_suspend;
+
+        if (g_strcmp0 (signal_name, "PrepareForSleep") != 0)
+                return;
+        g_variant_get (parameters, "(b)", &is_about_to_suspend);
+        if (is_about_to_suspend) {
+                /* Some devices send a power-button press on resume when woken
+                 * up with the power-button, suppress this, to avoid immediate
+                 * re-suspend. */
+                stop_reenable_power_button_timer (manager);
+                priv->power_button_disabled = TRUE;
+                uninhibit_suspend (manager);
+        } else {
+                inhibit_suspend (manager);
+                /* Re-enable power-button handling (after a small delay) */
+                setup_reenable_power_button_timer (manager);
+        }
+}
+
+static void
+gsd_media_keys_manager_class_init (GsdMediaKeysManagerClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_media_keys_manager_finalize;
+}
+
+static void
+inhibit_done (GObject      *source,
+              GAsyncResult *result,
+              gpointer      user_data)
+{
+        GDBusProxy *proxy = G_DBUS_PROXY (source);
+        GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GError *error = NULL;
+        GVariant *res;
+        GUnixFDList *fd_list = NULL;
+        gint idx;
+
+        res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error);
+        if (res == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Unable to inhibit keypresses: %s", error->message);
+                g_error_free (error);
+        } else {
+                g_variant_get (res, "(h)", &idx);
+                priv->inhibit_keys_fd = g_unix_fd_list_get (fd_list, idx, &error);
+                if (priv->inhibit_keys_fd == -1) {
+                        g_warning ("Failed to receive system inhibitor fd: %s", error->message);
+                        g_error_free (error);
+                }
+                g_debug ("System inhibitor fd is %d", priv->inhibit_keys_fd);
+                g_object_unref (fd_list);
+                g_variant_unref (res);
+        }
+}
+
+static void
+gsd_media_keys_manager_init (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GError *error;
+        GDBusConnection *bus;
+
+        error = NULL;
+        priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+        if (bus == NULL) {
+                g_warning ("Failed to connect to system bus: %s",
+                           error->message);
+                g_error_free (error);
+                return;
+        }
+
+        priv->logind_proxy =
+                g_dbus_proxy_new_sync (bus,
+                                       0,
+                                       NULL,
+                                       SYSTEMD_DBUS_NAME,
+                                       SYSTEMD_DBUS_PATH,
+                                       SYSTEMD_DBUS_INTERFACE,
+                                       NULL,
+                                       &error);
+
+        if (priv->logind_proxy == NULL) {
+                g_warning ("Failed to connect to systemd: %s",
+                           error->message);
+                g_error_free (error);
+        }
+
+        g_object_unref (bus);
+
+        g_debug ("Adding system inhibitors for power keys");
+        priv->inhibit_keys_fd = -1;
+        g_dbus_proxy_call_with_unix_fd_list (priv->logind_proxy,
+                                             "Inhibit",
+                                             g_variant_new ("(ssss)",
+                                                            "handle-power-key:handle-suspend-key:handle-hibernate-key",
+                                                            g_get_user_name (),
+                                                            "GNOME handling keypresses",
+                                                            "block"),
+                                             0,
+                                             G_MAXINT,
+                                             NULL,
+                                             NULL,
+                                             inhibit_done,
+                                             manager);
+
+        g_debug ("Adding delay inhibitor for suspend");
+        priv->inhibit_suspend_fd = -1;
+        g_signal_connect (priv->logind_proxy, "g-signal",
+                          G_CALLBACK (logind_proxy_signal_cb),
+                          manager);
+        inhibit_suspend (manager);
+}
+
+static void
+gsd_media_keys_manager_finalize (GObject *object)
+{
+        GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (object);
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        gsd_media_keys_manager_stop (manager);
+
+        if (priv->inhibit_keys_fd != -1)
+                close (priv->inhibit_keys_fd);
+
+        g_clear_object (&priv->logind_proxy);
+        g_clear_object (&priv->screen_saver_proxy);
+
+        G_OBJECT_CLASS (gsd_media_keys_manager_parent_class)->finalize (object);
+}
+
+static void
+power_keyboard_proxy_signal_cb (GDBusProxy  *proxy,
+                       const gchar *sender_name,
+                       const gchar *signal_name,
+                       GVariant    *parameters,
+                       gpointer     user_data)
+{
+        GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+        gint brightness;
+        const gchar *source;
+
+        if (g_strcmp0 (signal_name, "BrightnessChanged") != 0)
+                return;
+
+        g_variant_get (parameters, "(i&s)", &brightness, &source);
+
+        /* For non "internal" changes we already show the osd when handling
+         * the hotkey causing the change. */
+        if (g_strcmp0 (source, "internal") != 0)
+                return;
+
+        show_osd (manager, "keyboard-brightness-symbolic", NULL, (double) brightness / 100.0, NULL);
+}
+
+static void
+power_ready_cb (GObject             *source_object,
+                GAsyncResult        *res,
+                GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GError *error = NULL;
+
+        priv->power_proxy = g_dbus_proxy_new_finish (res, &error);
+        if (priv->power_proxy == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to get proxy for power: %s",
+                                   error->message);
+                g_error_free (error);
+        }
+}
+
+static void
+power_screen_ready_cb (GObject             *source_object,
+                       GAsyncResult        *res,
+                       GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GError *error = NULL;
+
+        priv->power_screen_proxy = g_dbus_proxy_new_finish (res, &error);
+        if (priv->power_screen_proxy == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to get proxy for power (screen): %s",
+                                   error->message);
+                g_error_free (error);
+        }
+}
+
+static void
+power_keyboard_ready_cb (GObject             *source_object,
+                         GAsyncResult        *res,
+                         GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GError *error = NULL;
+
+        priv->power_keyboard_proxy = g_dbus_proxy_new_finish (res, &error);
+        if (priv->power_keyboard_proxy == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to get proxy for power (keyboard): %s",
+                                   error->message);
+                g_error_free (error);
+        }
+
+        g_signal_connect (priv->power_keyboard_proxy, "g-signal",
+                          G_CALLBACK (power_keyboard_proxy_signal_cb),
+                          manager);
+}
+
+static void
+on_bus_gotten (GObject             *source_object,
+               GAsyncResult        *res,
+               GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GDBusConnection *connection;
+        GError *error = NULL;
+        UpClient *up_client;
+
+        connection = g_bus_get_finish (res, &error);
+        if (connection == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Could not get session bus: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+        priv->connection = connection;
+
+        g_dbus_proxy_new (priv->connection,
+                          G_DBUS_PROXY_FLAGS_NONE,
+                          NULL,
+                          GSD_DBUS_NAME ".Power",
+                          GSD_DBUS_PATH "/Power",
+                          GSD_DBUS_BASE_INTERFACE ".Power",
+                          NULL,
+                          (GAsyncReadyCallback) power_ready_cb,
+                          manager);
+
+        g_dbus_proxy_new (priv->connection,
+                          G_DBUS_PROXY_FLAGS_NONE,
+                          NULL,
+                          GSD_DBUS_NAME ".Power",
+                          GSD_DBUS_PATH "/Power",
+                          GSD_DBUS_BASE_INTERFACE ".Power.Screen",
+                          NULL,
+                          (GAsyncReadyCallback) power_screen_ready_cb,
+                          manager);
+
+        g_dbus_proxy_new (priv->connection,
+                          G_DBUS_PROXY_FLAGS_NONE,
+                          NULL,
+                          GSD_DBUS_NAME ".Power",
+                          GSD_DBUS_PATH "/Power",
+                          GSD_DBUS_BASE_INTERFACE ".Power.Keyboard",
+                          NULL,
+                          (GAsyncReadyCallback) power_keyboard_ready_cb,
+                          manager);
+
+        up_client = up_client_new ();
+        priv->composite_device = up_client_get_display_device (up_client);
+        g_object_unref (up_client);
+}
+
+static void
+register_manager (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        priv->bus_cancellable = g_cancellable_new ();
+        g_bus_get (G_BUS_TYPE_SESSION,
+                   priv->bus_cancellable,
+                   (GAsyncReadyCallback) on_bus_gotten,
+                   manager);
+}
+
+GsdMediaKeysManager *
+gsd_media_keys_manager_new (void)
+{
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_MEDIA_KEYS_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+        }
+
+        return GSD_MEDIA_KEYS_MANAGER (manager_object);
+}
diff --git a/plugins/media-keys/gsd-media-keys-manager.h b/plugins/media-keys/gsd-media-keys-manager.h
new file mode 100644
index 0000000..d7042b8
--- /dev/null
+++ b/plugins/media-keys/gsd-media-keys-manager.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_MEDIA_KEYS_MANAGER_H
+#define __GSD_MEDIA_KEYS_MANAGER_H
+
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_MEDIA_KEYS_MANAGER         (gsd_media_keys_manager_get_type ())
+
+G_DECLARE_DERIVABLE_TYPE (GsdMediaKeysManager, gsd_media_keys_manager, GSD, MEDIA_KEYS_MANAGER, GObject)
+
+struct _GsdMediaKeysManagerClass
+{
+        GObjectClass   parent_class;
+        void          (* media_player_key_pressed) (GsdMediaKeysManager *manager,
+                                                    const char          *application,
+                                                    const char          *key);
+};
+
+GsdMediaKeysManager * gsd_media_keys_manager_new                       (void);
+gboolean              gsd_media_keys_manager_start                     (GsdMediaKeysManager *manager,
+                                                                        GError             **error);
+void                  gsd_media_keys_manager_stop                      (GsdMediaKeysManager *manager);
+
+
+G_END_DECLS
+
+#endif /* __GSD_MEDIA_KEYS_MANAGER_H */
diff --git a/plugins/media-keys/main.c b/plugins/media-keys/main.c
new file mode 100644
index 0000000..fab28f6
--- /dev/null
+++ b/plugins/media-keys/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_media_keys_manager_new
+#define START gsd_media_keys_manager_start
+#define STOP gsd_media_keys_manager_stop
+#define MANAGER GsdMediaKeysManager
+#include "gsd-media-keys-manager.h"
+
+#include "daemon-skeleton-gtk.h"
diff --git a/plugins/media-keys/media-keys.h b/plugins/media-keys/media-keys.h
new file mode 100644
index 0000000..801a13b
--- /dev/null
+++ b/plugins/media-keys/media-keys.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2001 Bastien Nocera 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __MEDIA_KEYS_H__
+#define __MEDIA_KEYS_H__
+
+typedef enum {
+        TOUCHPAD_KEY,
+        TOUCHPAD_ON_KEY,
+        TOUCHPAD_OFF_KEY,
+        MUTE_KEY,
+        VOLUME_DOWN_KEY,
+        VOLUME_UP_KEY,
+        MIC_MUTE_KEY,
+        MUTE_QUIET_KEY,
+        VOLUME_DOWN_QUIET_KEY,
+        VOLUME_UP_QUIET_KEY,
+        VOLUME_DOWN_PRECISE_KEY,
+        VOLUME_UP_PRECISE_KEY,
+        LOGOUT_KEY,
+        EJECT_KEY,
+        HOME_KEY,
+        MEDIA_KEY,
+        CALCULATOR_KEY,
+        SEARCH_KEY,
+        EMAIL_KEY,
+        CONTROL_CENTER_KEY,
+        SCREENSAVER_KEY,
+        HELP_KEY,
+        WWW_KEY,
+        PLAY_KEY,
+        PAUSE_KEY,
+        STOP_KEY,
+        PREVIOUS_KEY,
+        NEXT_KEY,
+        REWIND_KEY,
+        FORWARD_KEY,
+        REPEAT_KEY,
+        RANDOM_KEY,
+        ROTATE_VIDEO_LOCK_KEY,
+        MAGNIFIER_KEY,
+        SCREENREADER_KEY,
+        ON_SCREEN_KEYBOARD_KEY,
+        INCREASE_TEXT_KEY,
+        DECREASE_TEXT_KEY,
+        TOGGLE_CONTRAST_KEY,
+        MAGNIFIER_ZOOM_IN_KEY,
+        MAGNIFIER_ZOOM_OUT_KEY,
+        POWER_KEY,
+        SUSPEND_KEY,
+        HIBERNATE_KEY,
+        SCREEN_BRIGHTNESS_UP_KEY,
+        SCREEN_BRIGHTNESS_DOWN_KEY,
+        SCREEN_BRIGHTNESS_CYCLE_KEY,
+        KEYBOARD_BRIGHTNESS_UP_KEY,
+        KEYBOARD_BRIGHTNESS_DOWN_KEY,
+        KEYBOARD_BRIGHTNESS_TOGGLE_KEY,
+        BATTERY_KEY,
+        RFKILL_KEY,
+        BLUETOOTH_RFKILL_KEY,
+        CUSTOM_KEY
+} MediaKeyType;
+
+#endif  /* __MEDIA_KEYS_H__ */
diff --git a/plugins/media-keys/meson.build b/plugins/media-keys/meson.build
new file mode 100644
index 0000000..92d471a
--- /dev/null
+++ b/plugins/media-keys/meson.build
@@ -0,0 +1,58 @@
+sources = files(
+  'bus-watch-namespace.c',
+  'gsd-media-keys-manager.c',
+  'main.c',
+  'mpris-controller.c'
+)
+
+marshal = 'gsd-marshal'
+
+sources += gnome.genmarshal(
+  marshal,
+  sources: marshal + '.list',
+  prefix: marshal.underscorify(),
+  internal: true
+)
+
+sources += gnome.gdbus_codegen(
+  'shell-key-grabber',
+  'org.gnome.ShellKeyGrabber.xml',
+  interface_prefix: 'org.gnome.',
+  namespace: 'Shell'
+)
+
+deps = plugins_deps + [
+  gio_unix_dep,
+  gsettings_desktop_dep,
+  libcanberra_gtk_dep,
+  libcommon_dep,
+  gnome_desktop_dep,
+  libgvc_dep,
+  libpulse_mainloop_glib_dep,
+  m_dep,
+  upower_glib_dep
+]
+
+if enable_gudev
+  deps += gudev_dep
+endif
+
+executable(
+  'gsd-' + plugin_name,
+  sources,
+  include_directories: [top_inc, data_inc],
+  dependencies: deps,
+  c_args: cflags,
+  install: true,
+  install_rpath: gsd_pkglibdir,
+  install_dir: gsd_libexecdir
+)
+
+program = 'audio-selection-test'
+
+executable(
+  program,
+  program + '.c',
+  include_directories: top_inc,
+  dependencies: deps
+)
diff --git a/plugins/media-keys/mpris-controller.c b/plugins/media-keys/mpris-controller.c
new file mode 100644
index 0000000..d60d362
--- /dev/null
+++ b/plugins/media-keys/mpris-controller.c
@@ -0,0 +1,440 @@
+/*
+ * Copyright © 2013 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see 
+ *
+ * Author: Michael Wood 
+ */
+
+#include "mpris-controller.h"
+#include "bus-watch-namespace.h"
+#include 
+
+enum {
+  PROP_0,
+  PROP_HAS_ACTIVE_PLAYER
+};
+
+struct _MprisController
+{
+  GObject parent;
+
+  GCancellable *cancellable;
+  GDBusProxy *mpris_client_proxy;
+  guint namespace_watcher_id;
+  GSList *other_proxies;
+};
+
+G_DEFINE_TYPE (MprisController, mpris_controller, G_TYPE_OBJECT)
+
+static void
+mpris_controller_dispose (GObject *object)
+{
+  MprisController *self = MPRIS_CONTROLLER (object);
+
+  g_clear_object (&self->cancellable);
+  g_clear_object (&self->mpris_client_proxy);
+
+  if (self->namespace_watcher_id)
+    {
+      bus_unwatch_namespace (self->namespace_watcher_id);
+      self->namespace_watcher_id = 0;
+    }
+
+  g_slist_free_full (g_steal_pointer (&self->other_proxies), g_object_unref);
+
+  G_OBJECT_CLASS (mpris_controller_parent_class)->dispose (object);
+}
+
+static void
+mpris_proxy_call_done (GObject      *object,
+                       GAsyncResult *res,
+                       gpointer      user_data)
+{
+  GError *error = NULL;
+  GVariant *ret;
+
+  if (!(ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), res, &error)))
+    {
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        g_warning ("Error calling method %s", error->message);
+      g_clear_error (&error);
+      return;
+    }
+  g_variant_unref (ret);
+}
+
+gboolean
+mpris_controller_key (MprisController *self, const gchar *key)
+{
+  g_return_val_if_fail (MPRIS_IS_CONTROLLER (self), FALSE);
+  g_return_val_if_fail (key != NULL, FALSE);
+
+  if (!self->mpris_client_proxy)
+    return FALSE;
+
+  if (g_strcmp0 (key, "Play") == 0)
+    key = "PlayPause";
+
+  g_debug ("calling %s over dbus to mpris client %s",
+           key, g_dbus_proxy_get_name (self->mpris_client_proxy));
+  g_dbus_proxy_call (self->mpris_client_proxy,
+                     key, NULL, 0, -1, self->cancellable,
+                     mpris_proxy_call_done,
+                     NULL);
+  return TRUE;
+}
+
+gboolean
+mpris_controller_seek (MprisController *self, gint64 offset)
+{
+  g_return_val_if_fail (MPRIS_IS_CONTROLLER (self), FALSE);
+
+  if (!self->mpris_client_proxy)
+    return FALSE;
+
+  g_debug ("calling Seek over dbus to mpris client %s",
+           g_dbus_proxy_get_name (self->mpris_client_proxy));
+  g_dbus_proxy_call (self->mpris_client_proxy,
+                     "Seek", g_variant_new ("(x)", offset, NULL),
+                     G_DBUS_CALL_FLAGS_NONE, -1, self->cancellable,
+                     mpris_proxy_call_done,
+                     NULL);
+  return TRUE;
+}
+
+static GDBusProxy *
+get_props_proxy (GDBusProxy   *proxy,
+                 GCancellable *cancellable)
+{
+  GDBusProxy *props = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_return_val_if_fail (proxy != NULL, NULL);
+
+  props = g_dbus_proxy_new_sync (g_dbus_proxy_get_connection (proxy),
+                                 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+                                 NULL,
+                                 g_dbus_proxy_get_name (proxy),
+                                 g_dbus_proxy_get_object_path (proxy),
+                                 "org.freedesktop.DBus.Properties",
+                                 cancellable,
+                                 &error);
+  if (!props) {
+    g_debug ("Could not get properties proxy for %s: %s",
+             g_dbus_proxy_get_interface_name (proxy),
+             error->message);
+    return NULL;
+  }
+
+  return props;
+}
+
+gboolean
+mpris_controller_toggle (MprisController *self, const gchar *property)
+{
+  g_return_val_if_fail (MPRIS_IS_CONTROLLER (self), FALSE);
+  g_return_val_if_fail (property != NULL, FALSE);
+
+  if (!self->mpris_client_proxy)
+    return FALSE;
+
+  if (g_str_equal (property, "LoopStatus")) {
+    g_autoptr(GDBusProxy) props = NULL;
+    g_autoptr(GVariant) loop_status;
+    const gchar *status_str, *new_status;
+
+    loop_status = g_dbus_proxy_get_cached_property (self->mpris_client_proxy, "LoopStatus");
+    if (!loop_status)
+      return FALSE;
+    if (!g_variant_is_of_type (loop_status, G_VARIANT_TYPE_STRING))
+      return FALSE;
+    status_str = g_variant_get_string (loop_status, NULL);
+    if (g_str_equal (status_str, "Playlist"))
+      new_status = "None";
+    else
+      new_status = "Playlist";
+
+    props = get_props_proxy (self->mpris_client_proxy, self->cancellable);
+    if (!props)
+      return FALSE;
+    g_dbus_proxy_call (props,
+                       "Set",
+                       g_variant_new_parsed ("('org.mpris.MediaPlayer2.Player', 'LoopStatus', %v)",
+                                             g_variant_new_string (new_status)),
+                       G_DBUS_CALL_FLAGS_NONE,
+                       -1,
+                       self->cancellable,
+                       mpris_proxy_call_done, NULL);
+  } else if (g_str_equal (property, "Shuffle")) {
+    g_autoptr(GDBusProxy) props = NULL;
+    g_autoptr(GVariant) shuffle_status;
+    gboolean status;
+
+    shuffle_status = g_dbus_proxy_get_cached_property (self->mpris_client_proxy, "Shuffle");
+    if (!shuffle_status)
+      return FALSE;
+    if (!g_variant_is_of_type (shuffle_status, G_VARIANT_TYPE_BOOLEAN))
+      return FALSE;
+    status = g_variant_get_boolean (shuffle_status);
+
+    props = get_props_proxy (self->mpris_client_proxy, self->cancellable);
+    if (!props)
+      return FALSE;
+    g_dbus_proxy_call (props,
+                       "Set",
+                       g_variant_new_parsed ("('org.mpris.MediaPlayer2.Player', 'Shuffle', %v)",
+                                             g_variant_new_boolean (!status)),
+                       G_DBUS_CALL_FLAGS_NONE,
+                       -1,
+                       self->cancellable,
+                       mpris_proxy_call_done, NULL);
+  }
+
+  g_debug ("Unhandled toggle property '%s'", property);
+
+  return TRUE;
+}
+
+static gboolean
+mpris_client_is_playing (GDBusProxy *proxy)
+{
+  g_autoptr(GVariant) playback_status;
+  const gchar *status_str;
+
+  playback_status = g_dbus_proxy_get_cached_property (proxy, "PlaybackStatus");
+  if (!playback_status)
+    return FALSE;
+
+  if (!g_variant_is_of_type (playback_status, G_VARIANT_TYPE_STRING))
+    return FALSE;
+
+  status_str = g_variant_get_string (playback_status, NULL);
+  return g_strcmp0 (status_str, "Playing") == 0;
+}
+
+static void
+mpris_client_notify_name_owner_cb (GDBusProxy      *proxy,
+                                   GParamSpec      *pspec,
+                                   MprisController *self)
+{
+  g_autofree gchar *name_owner = NULL;
+  GSList *first;
+
+  /* Owner changed, but the proxy is still valid. */
+  name_owner = g_dbus_proxy_get_name_owner (proxy);
+  if (name_owner)
+    return;
+
+  if (proxy == self->mpris_client_proxy)
+    {
+      g_debug ("Clearing the current MPRIS client proxy");
+      g_clear_object (&self->mpris_client_proxy);
+
+      if ((first = self->other_proxies))
+        {
+          self->mpris_client_proxy = first->data;
+          self->other_proxies = first->next;
+          g_slist_free_1 (first);
+
+          g_debug ("Falling back to MPRIS client %s",
+                   g_dbus_proxy_get_name (self->mpris_client_proxy));
+        }
+      else
+        {
+          g_object_notify (G_OBJECT (self), "has-active-player");
+        }
+    }
+  else
+    {
+      g_debug ("Forgetting MPRIS client %s", g_dbus_proxy_get_name (proxy));
+      self->other_proxies = g_slist_remove (self->other_proxies, proxy);
+      g_object_unref (proxy);
+    }
+}
+
+static void
+mpris_client_properties_changed_cb (GDBusProxy *proxy,
+                                    GVariant   *changed_properties,
+                                    GStrv       invalidated_properties,
+                                    gpointer    user_data)
+{
+  MprisController *self = MPRIS_CONTROLLER (user_data);
+  GDBusProxy *current_proxy;
+
+  current_proxy = self->mpris_client_proxy;
+  if (current_proxy == proxy)
+    return;
+
+  if (current_proxy && mpris_client_is_playing (current_proxy))
+    return;
+
+  if (mpris_client_is_playing (proxy))
+    {
+      g_debug ("Switching to MPRIS client %s because it is playing",
+               g_dbus_proxy_get_name (proxy));
+
+      self->other_proxies = g_slist_remove (self->other_proxies, proxy);
+
+      if (current_proxy)
+        self->other_proxies = g_slist_prepend (self->other_proxies, current_proxy);
+
+      self->mpris_client_proxy = proxy;
+
+      if (!current_proxy)
+        g_object_notify (user_data, "has-active-player");
+    }
+}
+
+static void
+mpris_proxy_ready_cb (GObject      *object,
+                      GAsyncResult *res,
+                      gpointer      user_data)
+{
+  MprisController *self = MPRIS_CONTROLLER (user_data);
+  GError *error = NULL;
+  GDBusProxy *proxy;
+  const gchar *name;
+
+  proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+
+  if (!proxy)
+    {
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        g_warning ("Error connecting to MPRIS interface: %s", error->message);
+      g_clear_error (&error);
+      return;
+    }
+
+  g_signal_connect (proxy, "notify::g-name-owner",
+                    G_CALLBACK (mpris_client_notify_name_owner_cb), user_data);
+
+  g_signal_connect (proxy, "g-properties-changed",
+                    G_CALLBACK (mpris_client_properties_changed_cb), user_data);
+
+  name = g_dbus_proxy_get_name (proxy);
+
+  if (self->mpris_client_proxy)
+    {
+      if (mpris_client_is_playing (self->mpris_client_proxy))
+        {
+          g_debug ("Remembering %s for later because the current MPRIS client is playing",
+                   name);
+          self->other_proxies = g_slist_prepend (self->other_proxies, proxy);
+          return;
+        }
+
+      g_debug ("Remembering the current MPRIS client for later");
+      self->other_proxies =
+        g_slist_prepend (self->other_proxies, self->mpris_client_proxy);
+    }
+
+  g_debug ("Switching to MPRIS client %s because it just appeared", name);
+
+  self->mpris_client_proxy = proxy;
+
+  g_object_notify (user_data, "has-active-player");
+}
+
+static void
+start_mpris_proxy (MprisController *self, const gchar *name)
+{
+  g_debug ("Creating proxy for %s", name);
+  g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+                            0,
+                            NULL,
+                            name,
+                            "/org/mpris/MediaPlayer2",
+                            "org.mpris.MediaPlayer2.Player",
+                            self->cancellable,
+                            mpris_proxy_ready_cb,
+                            self);
+}
+
+static void
+mpris_player_appeared (GDBusConnection *connection,
+                       const gchar     *name,
+                       const gchar     *name_owner,
+                       gpointer         user_data)
+{
+  start_mpris_proxy (MPRIS_CONTROLLER (user_data), name);
+}
+
+static void
+mpris_controller_constructed (GObject *object)
+{
+  MprisController *self = MPRIS_CONTROLLER (object);
+
+  self->namespace_watcher_id = bus_watch_namespace (G_BUS_TYPE_SESSION,
+                                                    "org.mpris.MediaPlayer2",
+                                                    mpris_player_appeared,
+                                                    NULL,
+                                                    MPRIS_CONTROLLER (object),
+                                                    NULL);
+}
+
+static void
+mpris_controller_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  MprisController *self = MPRIS_CONTROLLER (object);
+
+  switch (prop_id) {
+  case PROP_HAS_ACTIVE_PLAYER:
+    g_value_set_boolean (value,
+                         mpris_controller_get_has_active_player (self));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    break;
+  }
+}
+
+static void
+mpris_controller_class_init (MprisControllerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = mpris_controller_constructed;
+  object_class->dispose = mpris_controller_dispose;
+  object_class->get_property = mpris_controller_get_property;
+
+  g_object_class_install_property (object_class,
+                                   PROP_HAS_ACTIVE_PLAYER,
+                                   g_param_spec_boolean ("has-active-player",
+                                                         NULL,
+                                                         NULL,
+                                                         FALSE,
+                                                         G_PARAM_READABLE));
+}
+
+static void
+mpris_controller_init (MprisController *self)
+{
+}
+
+gboolean
+mpris_controller_get_has_active_player (MprisController *controller)
+{
+  g_return_val_if_fail (MPRIS_IS_CONTROLLER (controller), FALSE);
+
+  return (controller->mpris_client_proxy != NULL);
+}
+
+MprisController *
+mpris_controller_new (void)
+{
+  return g_object_new (MPRIS_TYPE_CONTROLLER, NULL);
+}
diff --git a/plugins/media-keys/mpris-controller.h b/plugins/media-keys/mpris-controller.h
new file mode 100644
index 0000000..e44bc90
--- /dev/null
+++ b/plugins/media-keys/mpris-controller.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2013 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see 
+ *
+ * Author: Michael Wood 
+ */
+
+#ifndef __MPRIS_CONTROLLER_H__
+#define __MPRIS_CONTROLLER_H__
+
+#include 
+
+G_BEGIN_DECLS
+
+#define MPRIS_TYPE_CONTROLLER mpris_controller_get_type()
+
+G_DECLARE_FINAL_TYPE (MprisController, mpris_controller, MPRIS, CONTROLLER, GObject)
+
+MprisController *mpris_controller_new (void);
+gboolean         mpris_controller_key (MprisController *self, const gchar *key);
+gboolean         mpris_controller_seek (MprisController *self, gint64 offset);
+gboolean         mpris_controller_toggle (MprisController *self, const gchar *property);
+gboolean         mpris_controller_get_has_active_player (MprisController *controller);
+
+G_END_DECLS
+
+#endif /* __MPRIS_CONTROLLER_H__ */
diff --git a/plugins/media-keys/org.gnome.ShellKeyGrabber.xml b/plugins/media-keys/org.gnome.ShellKeyGrabber.xml
new file mode 100644
index 0000000..df7b8d5
--- /dev/null
+++ b/plugins/media-keys/org.gnome.ShellKeyGrabber.xml
@@ -0,0 +1,27 @@
+
+  
+    
+    
+      
+      
+      
+      
+    
+    
+      
+      
+    
+    
+      
+      
+    
+    
+      
+      
+    
+    
+      
+      
+    
+  
+
diff --git a/plugins/media-keys/shell-action-modes.h b/plugins/media-keys/shell-action-modes.h
new file mode 100644
index 0000000..f17a7a0
--- /dev/null
+++ b/plugins/media-keys/shell-action-modes.h
@@ -0,0 +1,53 @@
+/**
+ * ShellActionMode:
+ * @SHELL_ACTION_MODE_NONE: block action
+ * @SHELL_ACTION_MODE_NORMAL: allow action when in window mode,
+ *     e.g. when the focus is in an application window
+ * @SHELL_ACTION_MODE_OVERVIEW: allow action while the overview
+ *     is active
+ * @SHELL_ACTION_MODE_LOCK_SCREEN: allow action when the screen
+ *     is locked, e.g. when the screen shield is shown
+ * @SHELL_ACTION_MODE_UNLOCK_SCREEN: allow action in the unlock
+ *     dialog
+ * @SHELL_ACTION_MODE_LOGIN_SCREEN: allow action in the login screen
+ * @SHELL_ACTION_MODE_SYSTEM_MODAL: allow action when a system modal
+ *     dialog (e.g. authentification or session dialogs) is open
+ * @SHELL_ACTION_MODE_LOOKING_GLASS: allow action in looking glass
+ * @SHELL_ACTION_MODE_POPUP: allow action while a shell menu is open
+ * @SHELL_ACTION_MODE_ALL: always allow action
+ *
+ * Controls in which GNOME Shell states an action (like keybindings and gestures)
+ * should be handled.
+*/
+typedef enum {
+  SHELL_ACTION_MODE_NONE          = 0,
+  SHELL_ACTION_MODE_NORMAL        = 1 << 0,
+  SHELL_ACTION_MODE_OVERVIEW      = 1 << 1,
+  SHELL_ACTION_MODE_LOCK_SCREEN   = 1 << 2,
+  SHELL_ACTION_MODE_UNLOCK_SCREEN = 1 << 3,
+  SHELL_ACTION_MODE_LOGIN_SCREEN  = 1 << 4,
+  SHELL_ACTION_MODE_SYSTEM_MODAL  = 1 << 5,
+  SHELL_ACTION_MODE_LOOKING_GLASS = 1 << 6,
+  SHELL_ACTION_MODE_POPUP         = 1 << 7,
+
+  SHELL_ACTION_MODE_ALL = ~0,
+} ShellActionMode;
+
+/**
+ * MetaKeyBindingFlags:
+ * @META_KEY_BINDING_NONE: none
+ * @META_KEY_BINDING_PER_WINDOW: per-window
+ * @META_KEY_BINDING_BUILTIN: built-in
+ * @META_KEY_BINDING_IS_REVERSED: is reversed
+ * @META_KEY_BINDING_NON_MASKABLE: always active
+ * @META_KEY_BINDING_IGNORE_AUTOREPEAT: ignore key autorepeat
+ */
+typedef enum
+{
+  META_KEY_BINDING_NONE,
+  META_KEY_BINDING_PER_WINDOW   = 1 << 0,
+  META_KEY_BINDING_BUILTIN      = 1 << 1,
+  META_KEY_BINDING_IS_REVERSED  = 1 << 2,
+  META_KEY_BINDING_NON_MASKABLE = 1 << 3,
+  META_KEY_BINDING_IGNORE_AUTOREPEAT = 1 << 4,
+} MetaKeyBindingFlags;
\ No newline at end of file
diff --git a/plugins/media-keys/shortcuts-list.h b/plugins/media-keys/shortcuts-list.h
new file mode 100644
index 0000000..c8f084c
--- /dev/null
+++ b/plugins/media-keys/shortcuts-list.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2001 Bastien Nocera 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __SHORTCUTS_LIST_H__
+#define __SHORTCUTS_LIST_H__
+
+#include "shell-action-modes.h"
+#include "media-keys.h"
+
+#define SETTINGS_BINDING_DIR "org.gnome.settings-daemon.plugins.media-keys"
+
+#define GSD_ACTION_MODE_LAUNCHER (SHELL_ACTION_MODE_NORMAL | \
+                                  SHELL_ACTION_MODE_OVERVIEW)
+#define SCREENSAVER_MODE SHELL_ACTION_MODE_ALL & ~SHELL_ACTION_MODE_UNLOCK_SCREEN
+#define NO_LOCK_MODE SCREENSAVER_MODE & ~SHELL_ACTION_MODE_LOCK_SCREEN
+#define POWER_KEYS_MODE_NO_DIALOG (SHELL_ACTION_MODE_LOCK_SCREEN | \
+				   SHELL_ACTION_MODE_UNLOCK_SCREEN)
+#define POWER_KEYS_MODE (SHELL_ACTION_MODE_NORMAL | \
+			 SHELL_ACTION_MODE_OVERVIEW | \
+			 SHELL_ACTION_MODE_LOGIN_SCREEN |\
+                         POWER_KEYS_MODE_NO_DIALOG)
+
+static struct {
+        MediaKeyType key_type;
+        const char *settings_key;
+        gboolean static_setting;
+        ShellActionMode modes;
+        MetaKeyBindingFlags grab_flags;
+} media_keys[] = {
+        { TOUCHPAD_KEY, "touchpad-toggle", TRUE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { TOUCHPAD_ON_KEY, "touchpad-on", TRUE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { TOUCHPAD_OFF_KEY, "touchpad-off", TRUE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { MUTE_KEY, "volume-mute", TRUE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { VOLUME_DOWN_KEY, "volume-down", TRUE, SHELL_ACTION_MODE_ALL },
+        { VOLUME_UP_KEY, "volume-up", TRUE, SHELL_ACTION_MODE_ALL },
+        { MIC_MUTE_KEY, "mic-mute", TRUE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { MUTE_QUIET_KEY, "volume-mute-quiet", TRUE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { VOLUME_DOWN_QUIET_KEY, "volume-down-quiet", TRUE, SHELL_ACTION_MODE_ALL },
+        { VOLUME_UP_QUIET_KEY, "volume-up-quiet", TRUE, SHELL_ACTION_MODE_ALL },
+        { VOLUME_DOWN_PRECISE_KEY, "volume-down-precise", TRUE, SHELL_ACTION_MODE_ALL },
+        { VOLUME_UP_PRECISE_KEY, "volume-up-precise", TRUE, SHELL_ACTION_MODE_ALL },
+        { LOGOUT_KEY, "logout", FALSE, GSD_ACTION_MODE_LAUNCHER, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { EJECT_KEY, "eject", TRUE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { HOME_KEY, "home", TRUE, GSD_ACTION_MODE_LAUNCHER, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { MEDIA_KEY, "media", TRUE, GSD_ACTION_MODE_LAUNCHER, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { CALCULATOR_KEY, "calculator", TRUE, GSD_ACTION_MODE_LAUNCHER, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { SEARCH_KEY, "search", TRUE, GSD_ACTION_MODE_LAUNCHER, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { EMAIL_KEY, "email", TRUE, GSD_ACTION_MODE_LAUNCHER, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { CONTROL_CENTER_KEY, "control-center", TRUE, GSD_ACTION_MODE_LAUNCHER, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { SCREENSAVER_KEY, "screensaver", TRUE, SCREENSAVER_MODE, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { HELP_KEY, "help", FALSE, GSD_ACTION_MODE_LAUNCHER, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { WWW_KEY, "www", TRUE, GSD_ACTION_MODE_LAUNCHER, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { PLAY_KEY, "play", TRUE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { PAUSE_KEY, "pause", TRUE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { STOP_KEY, "stop", TRUE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { PREVIOUS_KEY, "previous", TRUE, SHELL_ACTION_MODE_ALL },
+        { NEXT_KEY, "next", TRUE, SHELL_ACTION_MODE_ALL },
+        { REWIND_KEY, "playback-rewind", TRUE, SHELL_ACTION_MODE_ALL },
+        { FORWARD_KEY, "playback-forward", TRUE, SHELL_ACTION_MODE_ALL },
+        { REPEAT_KEY, "playback-repeat", TRUE, SHELL_ACTION_MODE_ALL },
+        { RANDOM_KEY, "playback-random", TRUE, SHELL_ACTION_MODE_ALL },
+        { ROTATE_VIDEO_LOCK_KEY, "rotate-video-lock", TRUE, SHELL_ACTION_MODE_ALL },
+        { MAGNIFIER_KEY, "magnifier", FALSE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { SCREENREADER_KEY, "screenreader", FALSE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { ON_SCREEN_KEYBOARD_KEY, "on-screen-keyboard", FALSE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { INCREASE_TEXT_KEY, "increase-text-size", FALSE, SHELL_ACTION_MODE_ALL },
+        { DECREASE_TEXT_KEY, "decrease-text-size", FALSE, SHELL_ACTION_MODE_ALL },
+        { TOGGLE_CONTRAST_KEY, "toggle-contrast", FALSE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { MAGNIFIER_ZOOM_IN_KEY, "magnifier-zoom-in", FALSE, SHELL_ACTION_MODE_ALL },
+        { MAGNIFIER_ZOOM_OUT_KEY, "magnifier-zoom-out", FALSE, SHELL_ACTION_MODE_ALL },
+        { POWER_KEY, "power", TRUE, POWER_KEYS_MODE, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        /* the kernel / Xorg names really are like this... */
+        { SUSPEND_KEY, "suspend", TRUE, POWER_KEYS_MODE, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { HIBERNATE_KEY, "hibernate", TRUE, POWER_KEYS_MODE, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { SCREEN_BRIGHTNESS_UP_KEY, "screen-brightness-up", TRUE, SHELL_ACTION_MODE_ALL },
+        { SCREEN_BRIGHTNESS_DOWN_KEY, "screen-brightness-down", TRUE, SHELL_ACTION_MODE_ALL },
+        { SCREEN_BRIGHTNESS_CYCLE_KEY, "screen-brightness-cycle", TRUE, SHELL_ACTION_MODE_ALL },
+        { KEYBOARD_BRIGHTNESS_UP_KEY, "keyboard-brightness-up", TRUE, SHELL_ACTION_MODE_ALL },
+        { KEYBOARD_BRIGHTNESS_DOWN_KEY, "keyboard-brightness-down", TRUE, SHELL_ACTION_MODE_ALL },
+        { KEYBOARD_BRIGHTNESS_TOGGLE_KEY, "keyboard-brightness-toggle", TRUE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { BATTERY_KEY, "battery-status", TRUE, GSD_ACTION_MODE_LAUNCHER, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { RFKILL_KEY, "rfkill", TRUE, GSD_ACTION_MODE_LAUNCHER, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+        { BLUETOOTH_RFKILL_KEY, "rfkill-bluetooth", TRUE, GSD_ACTION_MODE_LAUNCHER, META_KEY_BINDING_IGNORE_AUTOREPEAT}
+};
+
+#undef SCREENSAVER_MODE
+
+#endif /* __SHORTCUTS_LIST_H__ */
diff --git a/plugins/meson-add-wants.sh b/plugins/meson-add-wants.sh
new file mode 100755
index 0000000..c33d1b4
--- /dev/null
+++ b/plugins/meson-add-wants.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+set -eu
+
+# Script copied from systemd
+
+unitdir="$1"
+target="$2"
+unit="$3"
+
+case "$target" in
+    */?*) # a path, but not just a slash at the end
+        dir="${DESTDIR:-}${target}"
+        ;;
+    *)
+        dir="${DESTDIR:-}${unitdir}/${target}"
+        ;;
+esac
+
+unitpath="${DESTDIR:-}${unitdir}/${unit}"
+
+case "$target" in
+    */)
+        mkdir -vp -m 0755 "$dir"
+        ;;
+    *)
+        mkdir -vp -m 0755 "$(dirname "$dir")"
+        ;;
+esac
+
+ln -vfs --relative "$unitpath" "$dir"
diff --git a/plugins/meson.build b/plugins/meson.build
new file mode 100644
index 0000000..16397dc
--- /dev/null
+++ b/plugins/meson.build
@@ -0,0 +1,185 @@
+all_plugins = [
+    ['a11y-settings', 'A11ySettings', 'GNOME accessibility'],
+    ['color', 'Color', 'GNOME color management'],
+    ['datetime', 'Datetime', 'GNOME date & time'],
+    ['power', 'Power', 'GNOME power management'],
+    ['housekeeping', 'Housekeeping', 'GNOME maintenance of expirable data'],
+    ['keyboard', 'Keyboard', 'GNOME keyboard configuration'],
+    ['media-keys', 'MediaKeys', 'GNOME keyboard shortcuts'],
+    ['screensaver-proxy', 'ScreensaverProxy', 'GNOME FreeDesktop screensaver'],
+    ['sharing', 'Sharing', 'GNOME file sharing'],
+    ['sound', 'Sound', 'GNOME sound sample caching'],
+    ['usb-protection', 'UsbProtection', 'GNOME USB protection'],
+    ['xsettings', 'XSettings', 'GNOME XSettings'],
+    ['smartcard', 'Smartcard', 'GNOME smartcard'],
+    ['wacom', 'Wacom', 'GNOME Wacom tablet support'],
+    ['print-notifications', 'PrintNotifications', 'GNOME printer notifications'],
+    ['rfkill', 'Rfkill', 'GNOME RFKill support'],
+    ['wwan', 'Wwan', 'GNOME WWan support'],
+]
+
+disabled_plugins = []
+
+if not enable_smartcard
+    disabled_plugins += ['smartcard']
+endif
+
+if not enable_usb_protection
+    disabled_plugins += ['usb-protection']
+endif
+
+if not enable_wacom
+    disabled_plugins += ['wacom']
+endif
+
+if not enable_cups
+    disabled_plugins += ['cups']
+endif
+
+if not enable_rfkill
+    disabled_plugins += ['rfkill']
+endif
+
+if not enable_wwan
+    disabled_plugins += ['wwan']
+endif
+
+if not enable_colord
+  disabled_plugins += ['color']
+endif
+
+if not enable_cups
+  disabled_plugins += ['print-notifications']
+endif
+
+# Specify futher required units, 'before' or 'after' may be specified if ordering is needed
+plugin_gate_units = {
+    'xsettings': [
+      # Both after/before. after for stopping reliably, before for synchronisation
+      ['gnome-session-x11-services.target', 'after'],
+      ['gnome-session-x11-services-ready.target', 'before'],
+    ],
+#    'wacom': [['wacom.target']],
+#    'smartcard': [['smartcard.target']],
+}
+
+plugins_conf = configuration_data()
+plugins_conf.set('libexecdir', gsd_libexecdir)
+
+plugins_deps = [libgsd_dep]
+
+plugins_cflags = ['-DGNOME_SETTINGS_LOCALEDIR="@0@"'.format(gsd_localedir)]
+
+all_plugins_file = []
+
+cflags = [
+    '-DG_LOG_DOMAIN="common"'
+] + plugins_cflags
+plugin_name = 'common'
+subdir('common')
+
+foreach plugin: all_plugins
+    plugin_name = plugin[0]
+    plugin_name_case = plugin[1]
+    plugin_description = plugin[2]
+    plugin_dbus_name='org.gnome.SettingsDaemon.@0@'.format(plugin_name_case)
+
+    desktop = 'org.gnome.SettingsDaemon.@0@.desktop'.format(plugin[1])
+
+    if disabled_plugins.contains(plugin_name)
+        desktop_in_file = files('org.gnome.SettingsDaemon.Dummy.desktop.in')
+    else
+        desktop_in_file = files('org.gnome.SettingsDaemon.Real.desktop.in')
+    endif
+
+    cflags = [
+        '-DG_LOG_DOMAIN="@0@-plugin"'.format(plugin_name),
+        '-DPLUGIN_NAME="@0@"'.format(plugin_name),
+        '-DPLUGIN_DBUS_NAME="@0@"'.format(plugin_dbus_name),
+    ] + plugins_cflags
+
+    desktop = 'org.gnome.SettingsDaemon.@0@.desktop'.format(plugin[1])
+    desktop_conf = configuration_data()
+    desktop_conf.set('libexecdir', gsd_libexecdir)
+    desktop_conf.set('systemd_hidden', enable_systemd ? 'true' : 'false')
+    desktop_conf.set('pluginname', plugin_name)
+    desktop_conf.set('description', plugin_description)
+    configure_file(
+        input: desktop_in_file,
+        output: desktop,
+        configuration: desktop_conf,
+        install_dir: gsd_xdg_autostart
+    )
+
+    if not disabled_plugins.contains(plugin_name)
+        user_target = 'org.gnome.SettingsDaemon.@0@.target'.format(plugin[1])
+        user_service = 'org.gnome.SettingsDaemon.@0@.service'.format(plugin[1])
+
+        unit_conf = configuration_data()
+        unit_conf.set('plugin_name', plugin_name)
+        unit_conf.set('description', plugin_description)
+        unit_conf.set('libexecdir', gsd_libexecdir)
+        unit_conf.set('plugin_dbus_name', plugin_dbus_name)
+
+        gates_all = []
+        gates_after = []
+        gates_before = []
+        foreach gate: plugin_gate_units.get(plugin_name, [])
+            gates_all += [gate[0]]
+            if gate.length() > 1
+                if gate[1] == 'before'
+                    gates_before += [gate[0]]
+                elif gate[1] == 'after'
+                    gates_after += [gate[0]]
+                else
+                    error('Ordering key must be either "before" or "after"')
+                endif
+            endif
+        endforeach
+        gate_unit_section = []
+        if gates_all.length() > 0
+            gate_unit_section += ['Requisite=' + ' '.join(gates_all)]
+            gate_unit_section += ['PartOf=' + ' '.join(gates_all)]
+
+            if gates_after.length() > 0
+                gate_unit_section += ['After=' + ' '.join(gates_after)]
+            endif
+            if gates_before.length() > 0
+                gate_unit_section += ['Before=' + ' '.join(gates_before)]
+            endif
+        endif
+        unit_conf.set('plugin_gate_units_section', '\n'.join(gate_unit_section))
+
+        if enable_systemd
+            configure_file(
+                input: 'gsd.service.in',
+                output: user_service,
+                configuration: unit_conf,
+                install_dir: systemd_userunitdir
+            )
+            configure_file(
+                input: 'gsd.target.in',
+                output: user_target,
+                configuration: unit_conf,
+                install_dir: systemd_userunitdir
+            )
+
+            # Wipe out old target names if our prefix differes from the
+            # systemd one, i.e. we are probably in a virtual environment and
+            # may be picking up old units from a system installation.
+            # This saves a lot of pain when running a new g-s-d inside
+            # jhbuild on an old host.
+            # TODO: Should be deleted once we can assume developers have 3.38
+            #       installed on their machines.
+            if gsd_prefix != systemd_dep.get_pkgconfig_variable('prefix')
+                meson.add_install_script('sh', '-c', 'ln -vfs /dev/null "${DESTDIR:-}$1"', 'sh', systemd_userunitdir / 'gsd-@0@.target'.format(plugin_name))
+            endif
+
+            foreach target: gates_all
+                meson.add_install_script('meson-add-wants.sh', systemd_userunitdir, target + '.wants/', user_service)
+            endforeach
+        endif
+
+        subdir(plugin_name)
+    endif
+endforeach
diff --git a/plugins/org.gnome.SettingsDaemon.Dummy.desktop.in b/plugins/org.gnome.SettingsDaemon.Dummy.desktop.in
new file mode 100644
index 0000000..f4cc7a2
--- /dev/null
+++ b/plugins/org.gnome.SettingsDaemon.Dummy.desktop.in
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Type=Application
+Name=GNOME Settings Daemon's @pluginname@ dummy autostart file
+Exec=false
+OnlyShowIn=GNOME;
+NoDisplay=true
+Hidden=true
diff --git a/plugins/org.gnome.SettingsDaemon.Real.desktop.in b/plugins/org.gnome.SettingsDaemon.Real.desktop.in
new file mode 100644
index 0000000..3034e71
--- /dev/null
+++ b/plugins/org.gnome.SettingsDaemon.Real.desktop.in
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Type=Application
+Name=@description@
+Exec=@libexecdir@/gsd-@pluginname@
+OnlyShowIn=GNOME;
+NoDisplay=true
+X-GNOME-Autostart-Phase=Initialization
+X-GNOME-Autostart-Notify=true
+X-GNOME-AutoRestart=true
+X-GNOME-HiddenUnderSystemd=@systemd_hidden@
diff --git a/plugins/power/gpm-common.c b/plugins/power/gpm-common.c
new file mode 100644
index 0000000..a7ca87f
--- /dev/null
+++ b/plugins/power/gpm-common.c
@@ -0,0 +1,456 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2005-2011 Richard Hughes 
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include 
+
+#include "gnome-settings-bus.h"
+#include "gpm-common.h"
+#include "gsd-power-constants.h"
+#include "gsd-power-manager.h"
+
+#define XSCREENSAVER_WATCHDOG_TIMEOUT           120 /* seconds */
+#define UPS_SOUND_LOOP_ID                        99
+#define GSD_POWER_MANAGER_CRITICAL_ALERT_TIMEOUT  5 /* seconds */
+
+static int
+gsd_power_backlight_convert_safe (int value, int from_range, int to_range)
+{
+        /* round (value / from_range) * to_range */
+        return (value * to_range + from_range / 2) / from_range;
+}
+
+/* take a discrete value with offset and convert to percentage */
+int
+gsd_power_backlight_abs_to_percentage (int min, int max, int value)
+{
+        g_return_val_if_fail (max > min, -1);
+        g_return_val_if_fail (value >= min, -1);
+        g_return_val_if_fail (value <= max, -1);
+        return gsd_power_backlight_convert_safe (value - min, max - min, 100);
+}
+
+/* take a percentage and convert to a discrete value with offset */
+int
+gsd_power_backlight_percentage_to_abs (int min, int max, int value)
+{
+        g_return_val_if_fail (max > min, -1);
+        g_return_val_if_fail (value >= 0, -1);
+        g_return_val_if_fail (value <= 100, -1);
+
+        return min + gsd_power_backlight_convert_safe (value, 100, max - min);
+}
+
+#define GPM_UP_TIME_PRECISION                   5*60
+#define GPM_UP_TEXT_MIN_TIME                    120
+
+/**
+ * Return value: The time string, e.g. "2 hours 3 minutes"
+ **/
+gchar *
+gpm_get_timestring (guint time_secs)
+{
+        char* timestring = NULL;
+        gint  hours;
+        gint  minutes;
+
+        /* Add 0.5 to do rounding */
+        minutes = (int) ( ( time_secs / 60.0 ) + 0.5 );
+
+        if (minutes == 0) {
+                timestring = g_strdup (_("Unknown time"));
+                return timestring;
+        }
+
+        if (minutes < 60) {
+                timestring = g_strdup_printf (ngettext ("%i minute",
+                                                        "%i minutes",
+                                                        minutes), minutes);
+                return timestring;
+        }
+
+        hours = minutes / 60;
+        minutes = minutes % 60;
+        if (minutes == 0)
+                timestring = g_strdup_printf (ngettext (
+                                "%i hour",
+                                "%i hours",
+                                hours), hours);
+        else
+                /* TRANSLATOR: "%i %s %i %s" are "%i hours %i minutes"
+                 * Swap order with "%2$s %2$i %1$s %1$i if needed */
+                timestring = g_strdup_printf (_("%i %s %i %s"),
+                                hours, ngettext ("hour", "hours", hours),
+                                minutes, ngettext ("minute", "minutes", minutes));
+        return timestring;
+}
+
+static gboolean
+parse_vm_kernel_cmdline (gboolean *is_virtual_machine)
+{
+        gboolean ret = FALSE;
+        GRegex *regex;
+        GMatchInfo *match;
+        char *contents;
+        char *word;
+        const char *arg;
+
+        if (!g_file_get_contents ("/proc/cmdline", &contents, NULL, NULL))
+                return ret;
+
+        regex = g_regex_new ("gnome.is_vm=(\\S+)", 0, G_REGEX_MATCH_NOTEMPTY, NULL);
+        if (!g_regex_match (regex, contents, G_REGEX_MATCH_NOTEMPTY, &match))
+                goto out;
+
+        word = g_match_info_fetch (match, 0);
+        g_debug ("Found command-line match '%s'", word);
+        arg = word + strlen ("gnome.is_vm=");
+        if (*arg != '0' && *arg != '1') {
+                g_warning ("Invalid value '%s' for gnome.is_vm passed in kernel command line.\n", arg);
+        } else {
+                *is_virtual_machine = atoi (arg);
+                ret = TRUE;
+        }
+        g_free (word);
+
+out:
+        g_match_info_free (match);
+        g_regex_unref (regex);
+        g_free (contents);
+
+        if (ret)
+                g_debug ("Kernel command-line parsed to %d", *is_virtual_machine);
+
+        return ret;
+}
+
+gboolean
+gsd_power_is_hardware_a_vm (void)
+{
+        const gchar *str;
+        gboolean ret = FALSE;
+        GError *error = NULL;
+        GVariant *inner;
+        GVariant *variant = NULL;
+        GDBusConnection *connection;
+
+        if (parse_vm_kernel_cmdline (&ret))
+                return ret;
+
+        connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM,
+                                     NULL,
+                                     &error);
+        if (connection == NULL) {
+                g_warning ("system bus not available: %s", error->message);
+                g_error_free (error);
+                goto out;
+        }
+        variant = g_dbus_connection_call_sync (connection,
+                                               "org.freedesktop.systemd1",
+                                               "/org/freedesktop/systemd1",
+                                               "org.freedesktop.DBus.Properties",
+                                               "Get",
+                                               g_variant_new ("(ss)",
+                                                              "org.freedesktop.systemd1.Manager",
+                                                              "Virtualization"),
+                                               NULL,
+                                               G_DBUS_CALL_FLAGS_NONE,
+                                               -1,
+                                               NULL,
+                                               &error);
+        if (variant == NULL) {
+                g_debug ("Failed to get property '%s': %s", "Virtualization", error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        /* on bare-metal hardware this is the empty string,
+         * otherwise an identifier such as "kvm", "vmware", etc. */
+        g_variant_get (variant, "(v)", &inner);
+        str = g_variant_get_string (inner, NULL);
+        if (str != NULL && str[0] != '\0')
+                ret = TRUE;
+        g_variant_unref (inner);
+out:
+        if (connection != NULL)
+                g_object_unref (connection);
+        if (variant != NULL)
+                g_variant_unref (variant);
+        return ret;
+}
+
+/* This timer goes off every few minutes, whether the user is idle or not,
+   to try and clean up anything that has gone wrong.
+
+   It calls disable_builtin_screensaver() so that if xset has been used,
+   or some other program (like xlock) has messed with the XSetScreenSaver()
+   settings, they will be set back to sensible values (if a server extension
+   is in use, messing with xlock can cause the screensaver to never get a wakeup
+   event, and could cause monitor power-saving to occur, and all manner of
+   heinousness.)
+
+   This code was originally part of gnome-screensaver, see
+   http://git.gnome.org/browse/gnome-screensaver/tree/src/gs-watcher-x11.c?id=fec00b12ec46c86334cfd36b37771cc4632f0d4d#n530
+ */
+static gboolean
+disable_builtin_screensaver (gpointer unused)
+{
+        int current_server_timeout, current_server_interval;
+        int current_prefer_blank,   current_allow_exp;
+        int desired_server_timeout, desired_server_interval;
+        int desired_prefer_blank,   desired_allow_exp;
+
+        XGetScreenSaver (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+                         ¤t_server_timeout,
+                         ¤t_server_interval,
+                         ¤t_prefer_blank,
+                         ¤t_allow_exp);
+
+        desired_server_timeout  = current_server_timeout;
+        desired_server_interval = current_server_interval;
+        desired_prefer_blank    = current_prefer_blank;
+        desired_allow_exp       = current_allow_exp;
+
+        desired_server_interval = 0;
+
+        /* I suspect (but am not sure) that DontAllowExposures might have
+           something to do with powering off the monitor as well, at least
+           on some systems that don't support XDPMS?  Who know... */
+        desired_allow_exp = AllowExposures;
+
+        /* When we're not using an extension, set the server-side timeout to 0,
+           so that the server never gets involved with screen blanking, and we
+           do it all ourselves.  (However, when we *are* using an extension,
+           we tell the server when to notify us, and rather than blanking the
+           screen, the server will send us an X event telling us to blank.)
+        */
+        desired_server_timeout = 0;
+
+        if (desired_server_timeout     != current_server_timeout
+            || desired_server_interval != current_server_interval
+            || desired_prefer_blank    != current_prefer_blank
+            || desired_allow_exp       != current_allow_exp) {
+
+                g_debug ("disabling server builtin screensaver:"
+                         " (xset s %d %d; xset s %s; xset s %s)",
+                         desired_server_timeout,
+                         desired_server_interval,
+                         (desired_prefer_blank ? "blank" : "noblank"),
+                         (desired_allow_exp ? "expose" : "noexpose"));
+
+                XSetScreenSaver (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+                                 desired_server_timeout,
+                                 desired_server_interval,
+                                 desired_prefer_blank,
+                                 desired_allow_exp);
+
+                XSync (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), FALSE);
+        }
+
+        return TRUE;
+}
+
+guint
+gsd_power_enable_screensaver_watchdog (void)
+{
+        int dummy;
+        guint id;
+
+        /* Make sure that Xorg's DPMS extension never gets in our
+         * way. The defaults are now applied in Fedora 20 from
+         * being "0" by default to being "600" by default */
+        gdk_x11_display_error_trap_push (gdk_display_get_default ());
+        if (DPMSQueryExtension(GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), &dummy, &dummy))
+                DPMSSetTimeouts (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), 0, 0, 0);
+        gdk_x11_display_error_trap_pop_ignored (gdk_display_get_default ());
+        id = g_timeout_add_seconds (XSCREENSAVER_WATCHDOG_TIMEOUT,
+                                    disable_builtin_screensaver,
+                                    NULL);
+        g_source_set_name_by_id (id, "[gnome-settings-daemon] disable_builtin_screensaver");
+        return id;
+}
+
+static gpointer
+parse_mock_mock_external_monitor (gpointer data)
+{
+	const char *mocked_file;
+	mocked_file = g_getenv ("GSD_MOCK_EXTERNAL_MONITOR_FILE");
+
+	return g_strdup (mocked_file);
+}
+
+static const gchar *
+get_mock_external_monitor_file (void)
+{
+	  static GOnce mocked_once = G_ONCE_INIT;
+	  g_once (&mocked_once, parse_mock_mock_external_monitor, NULL);
+	  return mocked_once.retval;
+}
+
+static gboolean
+randr_output_is_on (GnomeRROutput *output)
+{
+        GnomeRRCrtc *crtc;
+
+        crtc = gnome_rr_output_get_crtc (output);
+        if (!crtc)
+                return FALSE;
+        return gnome_rr_crtc_get_current_mode (crtc) != NULL;
+}
+
+static void
+mock_monitor_changed (GFileMonitor     *monitor,
+		      GFile            *file,
+		      GFile            *other_file,
+		      GFileMonitorEvent event_type,
+		      gpointer          user_data)
+{
+	GnomeRRScreen *screen = (GnomeRRScreen *) user_data;
+
+	g_debug ("Mock screen configuration changed");
+	g_signal_emit_by_name (G_OBJECT (screen), "changed");
+}
+
+static void
+screen_destroyed (gpointer  user_data,
+		  GObject  *where_the_object_was)
+{
+	g_object_unref (G_OBJECT (user_data));
+}
+
+void
+watch_external_monitor (GnomeRRScreen *screen)
+{
+	const gchar *filename;
+	GFile *file;
+	GFileMonitor *monitor;
+
+	filename = get_mock_external_monitor_file ();
+	if (!filename)
+		return;
+
+	file = g_file_new_for_commandline_arg (filename);
+	monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, NULL, NULL);
+	g_object_unref (file);
+	g_signal_connect (monitor, "changed",
+			  G_CALLBACK (mock_monitor_changed), screen);
+	g_object_weak_ref (G_OBJECT (screen), screen_destroyed, monitor);
+}
+
+static gboolean
+mock_external_monitor_is_connected (GnomeRRScreen *screen)
+{
+	char *mock_external_monitor_contents;
+	const gchar *filename;
+
+	filename = get_mock_external_monitor_file ();
+	g_assert (filename);
+
+	if (g_file_get_contents (filename, &mock_external_monitor_contents, NULL, NULL)) {
+		if (mock_external_monitor_contents[0] == '1') {
+			g_free (mock_external_monitor_contents);
+			g_debug ("Mock external monitor is on");
+			return TRUE;
+		} else if (mock_external_monitor_contents[0] == '0') {
+			g_free (mock_external_monitor_contents);
+			g_debug ("Mock external monitor is off");
+			return FALSE;
+		}
+
+		g_error ("Unhandled value for GSD_MOCK_EXTERNAL_MONITOR contents: %s", mock_external_monitor_contents);
+		g_free (mock_external_monitor_contents);
+	}
+
+	return FALSE;
+}
+
+gboolean
+external_monitor_is_connected (GnomeRRScreen *screen)
+{
+        GnomeRROutput **outputs;
+        guint i;
+
+        if (get_mock_external_monitor_file ())
+                return mock_external_monitor_is_connected (screen);
+
+	g_assert (screen != NULL);
+
+        /* see if we have more than one screen plugged in */
+        outputs = gnome_rr_screen_list_outputs (screen);
+        for (i = 0; outputs[i] != NULL; i++) {
+                if (randr_output_is_on (outputs[i]) &&
+                    !gnome_rr_output_is_builtin_display (outputs[i]))
+                        return TRUE;
+        }
+
+        return FALSE;
+}
+
+static void
+play_sound (void)
+{
+        ca_context_play (ca_gtk_context_get (), UPS_SOUND_LOOP_ID,
+                         CA_PROP_EVENT_ID, "battery-caution",
+                         CA_PROP_EVENT_DESCRIPTION, _("Battery is critically low"), NULL);
+}
+
+static gboolean
+play_loop_timeout_cb (gpointer user_data)
+{
+        play_sound ();
+        return TRUE;
+}
+
+void
+play_loop_start (guint *id)
+{
+        if (*id != 0)
+                return;
+
+        *id = g_timeout_add_seconds (GSD_POWER_MANAGER_CRITICAL_ALERT_TIMEOUT,
+                                     (GSourceFunc) play_loop_timeout_cb,
+                                     NULL);
+        g_source_set_name_by_id (*id, "[gnome-settings-daemon] play_loop_timeout_cb");
+        play_sound ();
+}
+
+void
+play_loop_stop (guint *id)
+{
+        if (*id == 0)
+                return;
+
+        ca_context_cancel (ca_gtk_context_get (), UPS_SOUND_LOOP_ID);
+        g_source_remove (*id);
+        *id = 0;
+}
diff --git a/plugins/power/gpm-common.h b/plugins/power/gpm-common.h
new file mode 100644
index 0000000..88a8e00
--- /dev/null
+++ b/plugins/power/gpm-common.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2005-2011 Richard Hughes 
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __GPMCOMMON_H
+#define __GPMCOMMON_H
+
+#include 
+#include 
+
+G_BEGIN_DECLS
+
+/* UPower helpers */
+gchar           *gpm_get_timestring                     (guint           time);
+
+/* Power helpers */
+gboolean         gsd_power_is_hardware_a_vm             (void);
+guint            gsd_power_enable_screensaver_watchdog  (void);
+void             reset_idletime                         (void);
+
+/* Backlight helpers */
+
+/* on ACPI machines we have 4-16 levels, on others it's ~150 */
+#define BRIGHTNESS_STEP_AMOUNT(max) ((max) < 20 ? 1 : (max) / 20)
+
+#define ABS_TO_PERCENTAGE(min, max, value) gsd_power_backlight_abs_to_percentage(min, max, value)
+#define PERCENTAGE_TO_ABS(min, max, value) gsd_power_backlight_percentage_to_abs(min, max, value)
+
+int              gsd_power_backlight_abs_to_percentage  (int min, int max, int value);
+int              gsd_power_backlight_percentage_to_abs  (int min, int max, int value);
+
+/* RandR helpers */
+void             watch_external_monitor                 (GnomeRRScreen *screen);
+gboolean         external_monitor_is_connected          (GnomeRRScreen *screen);
+
+/* Sound helpers */
+void             play_loop_start                        (guint *id);
+void             play_loop_stop                         (guint *id);
+
+G_END_DECLS
+
+#endif  /* __GPMCOMMON_H */
diff --git a/plugins/power/gsd-backlight-helper.c b/plugins/power/gsd-backlight-helper.c
new file mode 100644
index 0000000..f0fbbb5
--- /dev/null
+++ b/plugins/power/gsd-backlight-helper.c
@@ -0,0 +1,149 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2018 Benjamin Berg 
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#define GSD_BACKLIGHT_HELPER_EXIT_CODE_SUCCESS			0
+#define GSD_BACKLIGHT_HELPER_EXIT_CODE_FAILED			1
+#define GSD_BACKLIGHT_HELPER_EXIT_CODE_ARGUMENTS_INVALID	3
+#define GSD_BACKLIGHT_HELPER_EXIT_CODE_INVALID_USER		4
+
+#ifndef __linux__
+#error "gsd-backlight-helper does not work on non-Linux"
+#endif
+
+static void
+usage(int argc, char *argv[])
+{
+	fprintf (stderr, "Usage: %s device brightness\n", argv[0]);
+	fprintf (stderr, "  device:      The backlight directory starting with \"/sys/class/backlight/\"\n");
+	fprintf (stderr, "  brightness:  The new brightness to write\n");
+}
+
+int
+main (int argc, char *argv[])
+{
+	char tmp[512];
+	char *device = NULL;
+	int fd, len, res;
+	int uid, euid;
+	int brightness;
+	int result = GSD_BACKLIGHT_HELPER_EXIT_CODE_FAILED;
+	DIR *dp = NULL;
+	struct dirent *ep;
+
+	/* check calling UID */
+	uid = getuid ();
+	euid = geteuid ();
+	if (uid != 0 || euid != 0) {
+		fprintf (stderr, "This program can only be used by the root user\n");
+		result = GSD_BACKLIGHT_HELPER_EXIT_CODE_INVALID_USER;
+		goto done;
+	}
+
+	if (argc != 3) {
+		fprintf (stderr, "Error: Need to be called with exactly two arguments\n");
+		usage (argc, argv);
+		result = GSD_BACKLIGHT_HELPER_EXIT_CODE_ARGUMENTS_INVALID;
+		goto done;
+	}
+
+	errno = 0;
+	brightness = strtol (argv[2], NULL, 0);
+	if (errno) {
+		fprintf (stderr, "Error: Invalid brightness argument (%d: %s)\n", errno, strerror (errno));
+		usage (argc, argv);
+		goto done;
+	}
+
+	dp = opendir ("/sys/class/backlight");
+	if (dp == NULL) {
+		fprintf (stderr, "Error: Could not open /sys/class/backlight (%d: %s)\n", errno, strerror (errno));
+		result = GSD_BACKLIGHT_HELPER_EXIT_CODE_FAILED;
+		goto done;
+	}
+
+	/* May be NULL if the path cannot be resolved */
+	device = realpath (argv[1], NULL);
+
+	while ((ep = readdir (dp))) {
+		char *path;
+
+		if (ep->d_name[0] == '.')
+			continue;
+
+		/* Leave room for "/brightness" */
+		snprintf (tmp, sizeof(tmp) - 11, "/sys/class/backlight/%s", ep->d_name);
+		path = realpath (tmp, NULL);
+		if (path && device && strcmp (path, device) == 0) {
+			free (path);
+			strcat (tmp, "/brightness");
+
+			fd = open (tmp, O_WRONLY);
+			if (fd < 0) {
+				fprintf (stderr, "Error: Could not open brightness sysfs file (%d: %s)\n", errno, strerror(errno));
+				result = GSD_BACKLIGHT_HELPER_EXIT_CODE_FAILED;
+				goto done;
+			}
+
+			len = snprintf (tmp, sizeof(tmp), "%d", brightness);
+			if ((res = write (fd, tmp, len)) != len) {
+				if (res == -1)
+					fprintf (stderr, "Error: Writing to file (%d: %s)\n", errno, strerror(errno));
+				else
+					fprintf (stderr, "Error: Wrote the wrong length (%d of %d bytes)!\n", res, len);
+
+				close (fd);
+				result = GSD_BACKLIGHT_HELPER_EXIT_CODE_FAILED;
+				goto done;
+			}
+			close (fd);
+
+			result = GSD_BACKLIGHT_HELPER_EXIT_CODE_SUCCESS;
+			goto done;
+		} else {
+			free (path);
+		}
+	}
+
+	result = GSD_BACKLIGHT_HELPER_EXIT_CODE_FAILED;
+	fprintf (stderr, "Error: Could not find the specified backlight \"%s\"\n", argv[1]);
+
+done:
+	if (device)
+		free (device);
+	if (dp)
+		closedir (dp);
+
+	return result;
+}
+
diff --git a/plugins/power/gsd-backlight.c b/plugins/power/gsd-backlight.c
new file mode 100644
index 0000000..ca5f272
--- /dev/null
+++ b/plugins/power/gsd-backlight.c
@@ -0,0 +1,978 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2018 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+#include 
+
+#include "gsd-backlight.h"
+#include "gpm-common.h"
+#include "gsd-power-constants.h"
+#include "gsd-power-manager.h"
+
+#ifdef __linux__
+#include 
+#endif /* __linux__ */
+
+struct _GsdBacklight
+{
+        GObject object;
+
+        gint brightness_min;
+        gint brightness_max;
+        gint brightness_val;
+        gint brightness_target;
+        gint brightness_step;
+
+#ifdef __linux__
+        GDBusProxy *logind_proxy;
+
+        GUdevClient *udev;
+        GUdevDevice *udev_device;
+
+        GTask *active_task;
+        GQueue tasks;
+
+        gint idle_update;
+#endif /* __linux__ */
+
+        GnomeRRScreen *rr_screen;
+        gboolean builtin_display_disabled;
+};
+
+enum {
+        PROP_RR_SCREEN = 1,
+        PROP_BRIGHTNESS,
+        PROP_LAST,
+};
+
+#define SYSTEMD_DBUS_NAME                       "org.freedesktop.login1"
+#define SYSTEMD_DBUS_PATH                       "/org/freedesktop/login1/session/auto"
+#define SYSTEMD_DBUS_INTERFACE                  "org.freedesktop.login1.Session"
+
+static GParamSpec *props[PROP_LAST];
+
+static void     gsd_backlight_initable_iface_init (GInitableIface  *iface);
+static gboolean gsd_backlight_initable_init       (GInitable       *initable,
+                                                   GCancellable    *cancellable,
+                                                   GError         **error);
+
+
+G_DEFINE_TYPE_EXTENDED (GsdBacklight, gsd_backlight, G_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                               gsd_backlight_initable_iface_init);)
+
+#ifdef __linux__
+static GUdevDevice*
+gsd_backlight_udev_get_type (GList *devices, const gchar *type)
+{
+        const gchar *type_tmp;
+        GList *d;
+
+        for (d = devices; d != NULL; d = d->next) {
+                type_tmp = g_udev_device_get_sysfs_attr (d->data, "type");
+                if (g_strcmp0 (type_tmp, type) == 0)
+                        return G_UDEV_DEVICE (g_object_ref (d->data));
+        }
+        return NULL;
+}
+
+/*
+ * Search for a raw backlight interface, raw backlight interfaces registered
+ * by the drm driver will have the drm-connector as their parent, check the
+ * drm-connector's enabled sysfs attribute so that we pick the right LCD-panel
+ * connector on laptops with hybrid-gfx. Fall back to just picking the first
+ * raw backlight interface if no enabled interface is found.
+ */
+static GUdevDevice*
+gsd_backlight_udev_get_raw (GList *devices)
+{
+        GUdevDevice *parent;
+        const gchar *attr;
+        GList *d;
+
+        for (d = devices; d != NULL; d = d->next) {
+                attr = g_udev_device_get_sysfs_attr (d->data, "type");
+                if (g_strcmp0 (attr, "raw") != 0)
+                        continue;
+
+                parent = g_udev_device_get_parent (d->data);
+                if (!parent)
+                        continue;
+
+                attr = g_udev_device_get_sysfs_attr (parent, "enabled");
+                if (!attr || g_strcmp0 (attr, "enabled") != 0)
+                        continue;
+
+                return G_UDEV_DEVICE (g_object_ref (d->data));
+        }
+
+        return gsd_backlight_udev_get_type (devices, "raw");
+}
+
+static void
+gsd_backlight_udev_resolve (GsdBacklight *backlight)
+{
+        g_autolist(GUdevDevice) devices = NULL;
+
+        g_assert (backlight->udev != NULL);
+
+        devices = g_udev_client_query_by_subsystem (backlight->udev, "backlight");
+        if (devices == NULL)
+                return;
+
+        /* Search the backlight devices and prefer the types:
+         * firmware -> platform -> raw */
+        backlight->udev_device = gsd_backlight_udev_get_type (devices, "firmware");
+        if (backlight->udev_device != NULL)
+                return;
+
+        backlight->udev_device = gsd_backlight_udev_get_type (devices, "platform");
+        if (backlight->udev_device != NULL)
+                return;
+
+        backlight->udev_device = gsd_backlight_udev_get_raw (devices);
+        if (backlight->udev_device != NULL)
+                return;
+}
+
+static gboolean
+gsd_backlight_udev_idle_update_cb (GsdBacklight *backlight)
+{
+        g_autoptr(GError) error = NULL;
+        gint brightness;
+        g_autofree gchar *path = NULL;
+        g_autofree gchar *contents = NULL;
+        backlight->idle_update = 0;
+
+        /* If we are active again now, just stop. */
+        if (backlight->active_task)
+                return FALSE;
+
+        path = g_build_filename (g_udev_device_get_sysfs_path (backlight->udev_device), "brightness", NULL);
+        if (!g_file_get_contents (path, &contents, NULL, &error)) {
+                g_warning ("Could not get brightness from sysfs: %s", error->message);
+                return FALSE;
+        }
+        brightness = g_ascii_strtoll (contents, NULL, 0);
+
+        /* e.g. brightness lower than our minimum. */
+        brightness = CLAMP (brightness, backlight->brightness_min, backlight->brightness_max);
+
+        /* Only notify if brightness has changed. */
+        if (brightness == backlight->brightness_val)
+                return FALSE;
+
+        backlight->brightness_val = brightness;
+        backlight->brightness_target = brightness;
+        g_object_notify_by_pspec (G_OBJECT (backlight), props[PROP_BRIGHTNESS]);
+
+        return FALSE;
+}
+
+static void
+gsd_backlight_udev_idle_update (GsdBacklight *backlight)
+{
+        if (backlight->idle_update)
+                return;
+
+        backlight->idle_update = g_idle_add ((GSourceFunc) gsd_backlight_udev_idle_update_cb, backlight);
+}
+
+
+static void
+gsd_backlight_udev_uevent (GUdevClient *client, const gchar *action, GUdevDevice *device, gpointer user_data)
+{
+        GsdBacklight *backlight = GSD_BACKLIGHT (user_data);
+
+        if (g_strcmp0 (action, "change") != 0)
+                return;
+
+        /* We are going to update our state after processing the tasks anyway. */
+        if (!g_queue_is_empty (&backlight->tasks))
+                return;
+
+        if (g_strcmp0 (g_udev_device_get_sysfs_path (device),
+                       g_udev_device_get_sysfs_path (backlight->udev_device)) != 0)
+                return;
+
+        g_debug ("GsdBacklight: Got uevent");
+
+        gsd_backlight_udev_idle_update (backlight);
+}
+
+
+static gboolean
+gsd_backlight_udev_init (GsdBacklight *backlight)
+{
+        const gchar* const subsystems[] = {"backlight", NULL};
+        gint brightness_val;
+
+        backlight->udev = g_udev_client_new (subsystems);
+        gsd_backlight_udev_resolve (backlight);
+        if (backlight->udev_device == NULL)
+                return FALSE;
+
+        backlight->brightness_max = g_udev_device_get_sysfs_attr_as_int (backlight->udev_device,
+                                                                         "max_brightness");
+        backlight->brightness_min = MAX (1, backlight->brightness_max * 0.01);
+
+        /* If the interface has less than 100 possible values, and it is of type
+         * raw, then assume that 0 does not turn off the backlight completely. */
+        if (backlight->brightness_max < 99 &&
+            g_strcmp0 (g_udev_device_get_sysfs_attr (backlight->udev_device, "type"), "raw") == 0)
+                backlight->brightness_min = 0;
+
+        /* Ignore a backlight which has no steps. */
+        if (backlight->brightness_min >= backlight->brightness_max) {
+                g_warning ("Resolved kernel backlight has an unusable maximum brightness (%d)", backlight->brightness_max);
+                g_clear_object (&backlight->udev_device);
+                return FALSE;
+        }
+
+        brightness_val = g_udev_device_get_sysfs_attr_as_int (backlight->udev_device,
+                                                              "brightness");
+        backlight->brightness_val = CLAMP (brightness_val,
+                                           backlight->brightness_min,
+                                           backlight->brightness_max);
+        g_debug ("Using udev device with brightness from %i to %i. Current brightness is %i.",
+                 backlight->brightness_min, backlight->brightness_max, backlight->brightness_val);
+
+        g_signal_connect_object (backlight->udev, "uevent",
+                                 G_CALLBACK (gsd_backlight_udev_uevent),
+                                 backlight, 0);
+
+        return TRUE;
+}
+
+
+typedef struct {
+        int value;
+        char *value_str;
+} BacklightHelperData;
+
+static void gsd_backlight_process_taskqueue (GsdBacklight *backlight);
+
+static void
+backlight_task_data_destroy (gpointer data)
+{
+        BacklightHelperData *task_data = (BacklightHelperData*) data;
+
+        g_free (task_data->value_str);
+        g_free (task_data);
+}
+
+static void
+gsd_backlight_set_helper_return (GsdBacklight *backlight, GTask *task, gint result, const GError *error)
+{
+        GTask *finished_task;
+        gint percent = ABS_TO_PERCENTAGE (backlight->brightness_min, backlight->brightness_max, result);
+
+        if (error)
+                g_warning ("Error executing backlight helper: %s", error->message);
+
+        /* If the queue will be empty then update the current value. */
+        if (task == g_queue_peek_tail (&backlight->tasks)) {
+                if (error == NULL) {
+                        g_assert (backlight->brightness_target == result);
+
+                        backlight->brightness_val = backlight->brightness_target;
+                        g_debug ("New brightness value is in effect %i (%i..%i)",
+                                 backlight->brightness_val, backlight->brightness_min, backlight->brightness_max);
+                        g_object_notify_by_pspec (G_OBJECT (backlight), props[PROP_BRIGHTNESS]);
+                }
+
+                /* The udev handler won't read while a write is pending, so queue an
+                 * update in case we have missed some events. */
+                gsd_backlight_udev_idle_update (backlight);
+        }
+
+        /* Return all the pending tasks up and including the one we actually
+         * processed. */
+        do {
+                finished_task = g_queue_pop_head (&backlight->tasks);
+
+                if (error)
+                        g_task_return_error (finished_task, g_error_copy (error));
+                else
+                        g_task_return_int (finished_task, percent);
+
+                g_object_unref (finished_task);
+        } while (finished_task != task);
+}
+
+static void
+gsd_backlight_set_helper_finish (GObject *obj, GAsyncResult *res, gpointer user_data)
+{
+        g_autoptr(GSubprocess) proc = G_SUBPROCESS (obj);
+        GTask *task = G_TASK (user_data);
+        BacklightHelperData *data = g_task_get_task_data (task);
+        GsdBacklight *backlight = g_task_get_source_object (task);
+        g_autoptr(GError) error = NULL;
+
+        g_assert (task == backlight->active_task);
+        backlight->active_task = NULL;
+
+        g_subprocess_wait_finish (proc, res, &error);
+
+        if (error)
+                goto done;
+
+        g_spawn_check_exit_status (g_subprocess_get_exit_status (proc), &error);
+        if (error)
+                goto done;
+
+done:
+        gsd_backlight_set_helper_return (backlight, task, data->value, error);
+        /* Start processing any tasks that were added in the meantime. */
+        gsd_backlight_process_taskqueue (backlight);
+}
+
+static void
+gsd_backlight_run_set_helper (GsdBacklight *backlight, GTask *task)
+{
+        GSubprocess *proc = NULL;
+        BacklightHelperData *data = g_task_get_task_data (task);
+        const gchar *gsd_backlight_helper = NULL;
+        GError *error = NULL;
+
+        g_assert (backlight->active_task == NULL);
+        backlight->active_task = task;
+
+        if (data->value_str == NULL)
+                data->value_str = g_strdup_printf ("%d", data->value);
+
+        /* This is solely for use by the test environment. If given, execute
+         * this helper instead of the internal helper using pkexec */
+        gsd_backlight_helper = g_getenv ("GSD_BACKLIGHT_HELPER");
+        if (!gsd_backlight_helper) {
+                proc = g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_SILENCE,
+                                         &error,
+                                         "pkexec",
+                                         LIBEXECDIR "/gsd-backlight-helper",
+                                         g_udev_device_get_sysfs_path (backlight->udev_device),
+                                         data->value_str, NULL);
+        } else {
+                proc = g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_SILENCE,
+                                         &error,
+                                         gsd_backlight_helper,
+                                         g_udev_device_get_sysfs_path (backlight->udev_device),
+                                         data->value_str, NULL);
+        }
+
+        if (proc == NULL) {
+                gsd_backlight_set_helper_return (backlight, task, -1, error);
+                return;
+        }
+
+        g_subprocess_wait_async (proc, g_task_get_cancellable (task),
+                                 gsd_backlight_set_helper_finish,
+                                 task);
+}
+
+static void
+gsd_backlight_process_taskqueue (GsdBacklight *backlight)
+{
+        GTask *to_run;
+
+        /* There is already a task active, nothing to do. */
+        if (backlight->active_task)
+                return;
+
+        /* Get the last added task, thereby compressing the updates into one. */
+        to_run = G_TASK (g_queue_peek_tail (&backlight->tasks));
+        if (to_run == NULL)
+                return;
+
+        /* And run it! */
+        gsd_backlight_run_set_helper (backlight, to_run);
+}
+#endif /* __linux__ */
+
+static GnomeRROutput*
+gsd_backlight_rr_find_output (GsdBacklight *backlight, gboolean controllable)
+{
+        GnomeRROutput *output = NULL;
+        GnomeRROutput **outputs;
+        guint i;
+
+        /* search all X11 outputs for the device id */
+        outputs = gnome_rr_screen_list_outputs (backlight->rr_screen);
+        if (outputs == NULL)
+                goto out;
+
+        for (i = 0; outputs[i] != NULL; i++) {
+                gboolean builtin = gnome_rr_output_is_builtin_display (outputs[i]);
+                gint backlight = gnome_rr_output_get_backlight (outputs[i]);
+
+                g_debug("Output %d: %s, backlight %d", i, builtin ? "builtin" : "external", backlight);
+                if (builtin && (!controllable || backlight >= 0)) {
+                        output = outputs[i];
+                        break;
+                }
+        }
+out:
+        return output;
+}
+
+/**
+ * gsd_backlight_get_brightness
+ * @backlight: a #GsdBacklight
+ * @target: Output parameter for the value the target value of pending set operations.
+ *
+ * The backlight value returns the last known stable value. This value will
+ * only update once all pending operations to set a new value have finished.
+ *
+ * As such, this function may return a different value from the return value
+ * of the async brightness setter. This happens when another set operation was
+ * queued after it was already running.
+ *
+ * If the internal display is detected as disabled, then the function will
+ * instead return -1.
+ *
+ * Returns: The last stable backlight value or -1 if the internal display is disabled.
+ **/
+gint
+gsd_backlight_get_brightness (GsdBacklight *backlight, gint *target)
+{
+        if (backlight->builtin_display_disabled)
+                return -1;
+
+        if (target)
+                *target = ABS_TO_PERCENTAGE (backlight->brightness_min, backlight->brightness_max, backlight->brightness_target);
+
+        return ABS_TO_PERCENTAGE (backlight->brightness_min, backlight->brightness_max, backlight->brightness_val);
+}
+
+static void
+gsd_backlight_set_brightness_val_async (GsdBacklight *backlight,
+                                        int value,
+                                        GCancellable *cancellable,
+                                        GAsyncReadyCallback callback,
+                                        gpointer user_data)
+{
+        GError *error = NULL;
+        GTask *task = NULL;
+        GnomeRROutput *output;
+        gint percent;
+
+        value = MIN(backlight->brightness_max, value);
+        value = MAX(backlight->brightness_min, value);
+
+        backlight->brightness_target = value;
+
+        task = g_task_new (backlight, cancellable, callback, user_data);
+
+#ifdef __linux__
+        if (backlight->udev_device != NULL) {
+                BacklightHelperData *task_data;
+
+                if (backlight->logind_proxy) {
+                        g_dbus_proxy_call (backlight->logind_proxy,
+                                           "SetBrightness",
+                                           g_variant_new ("(ssu)",
+                                                          "backlight",
+                                                          g_udev_device_get_name (backlight->udev_device),
+                                                          backlight->brightness_target),
+                                           G_DBUS_CALL_FLAGS_NONE,
+                                           -1, NULL,
+                                           NULL, NULL);
+
+                        percent = ABS_TO_PERCENTAGE (backlight->brightness_min,
+                                                     backlight->brightness_max,
+                                                     backlight->brightness_target);
+                        g_task_return_int (task, percent);
+                } else {
+                        task_data = g_new0 (BacklightHelperData, 1);
+                        task_data->value = backlight->brightness_target;
+                        g_task_set_task_data (task, task_data, backlight_task_data_destroy);
+
+                        /* Task is set up now. Queue it and ensure we are working something. */
+                        g_queue_push_tail (&backlight->tasks, task);
+                        gsd_backlight_process_taskqueue (backlight);
+                }
+
+                return;
+        }
+#endif /* __linux__ */
+
+        /* Fallback to setting via GNOME RR/X11 */
+        output = gsd_backlight_rr_find_output (backlight, TRUE);
+        if (output) {
+                if (!gnome_rr_output_set_backlight (output, value, &error)) {
+                        g_task_return_error (task, error);
+                        g_object_unref (task);
+                        return;
+                }
+                backlight->brightness_val = gnome_rr_output_get_backlight (output);
+                g_object_notify_by_pspec (G_OBJECT (backlight), props[PROP_BRIGHTNESS]);
+                g_task_return_int (task, gsd_backlight_get_brightness (backlight, NULL));
+                g_object_unref (task);
+
+                return;
+        }
+
+        g_assert_not_reached ();
+
+        g_task_return_new_error (task, GSD_POWER_MANAGER_ERROR,
+                                 GSD_POWER_MANAGER_ERROR_FAILED,
+                                 "No method to set brightness!");
+        g_object_unref (task);
+}
+
+void
+gsd_backlight_set_brightness_async (GsdBacklight *backlight,
+                                    gint percent,
+                                    GCancellable *cancellable,
+                                    GAsyncReadyCallback callback,
+                                    gpointer user_data)
+{
+        /* Overflow/underflow is handled by gsd_backlight_set_brightness_val_async. */
+        gsd_backlight_set_brightness_val_async (backlight,
+                                                PERCENTAGE_TO_ABS (backlight->brightness_min, backlight->brightness_max, percent),
+                                                cancellable,
+                                                callback,
+                                                user_data);
+}
+
+/**
+ * gsd_backlight_set_brightness_finish
+ * @backlight: a #GsdBacklight
+ * @res: the #GAsyncResult passed to the callback
+ * @error: #GError return address
+ *
+ * Finish an operation started by gsd_backlight_set_brightness_async(). Will
+ * return the value that was actually set (which may be different because of
+ * rounding or as multiple set actions were queued up).
+ *
+ * Please note that a call to gsd_backlight_get_brightness() may not in fact
+ * return the same value if further operations to set the value are pending.
+ *
+ * Returns: The brightness in percent that was set.
+ **/
+gint
+gsd_backlight_set_brightness_finish (GsdBacklight *backlight,
+                                     GAsyncResult *res,
+                                     GError **error)
+{
+        return g_task_propagate_int (G_TASK (res), error);
+}
+
+void
+gsd_backlight_step_up_async (GsdBacklight *backlight,
+                             GCancellable *cancellable,
+                             GAsyncReadyCallback callback,
+                             gpointer user_data)
+{
+        gint value;
+
+        /* Overflows are handled by gsd_backlight_set_brightness_val_async. */
+        value = backlight->brightness_target + backlight->brightness_step;
+
+        gsd_backlight_set_brightness_val_async (backlight,
+                                                value,
+                                                cancellable,
+                                                callback,
+                                                user_data);
+}
+
+/**
+ * gsd_backlight_step_up_finish
+ * @backlight: a #GsdBacklight
+ * @res: the #GAsyncResult passed to the callback
+ * @error: #GError return address
+ *
+ * Finish an operation started by gsd_backlight_step_up_async(). Will return
+ * the value that was actually set (which may be different because of rounding
+ * or as multiple set actions were queued up).
+ *
+ * Please note that a call to gsd_backlight_get_brightness() may not in fact
+ * return the same value if further operations to set the value are pending.
+ *
+ * For simplicity it is also valid to call gsd_backlight_set_brightness_finish()
+ * allowing sharing the callback routine for calls to
+ * gsd_backlight_set_brightness_async(), gsd_backlight_step_up_async(),
+ * gsd_backlight_step_down_async() and gsd_backlight_cycle_up_async().
+ *
+ * Returns: The brightness in percent that was set.
+ **/
+gint
+gsd_backlight_step_up_finish (GsdBacklight *backlight,
+                              GAsyncResult *res,
+                              GError **error)
+{
+        return g_task_propagate_int (G_TASK (res), error);
+}
+
+void
+gsd_backlight_step_down_async (GsdBacklight *backlight,
+                               GCancellable *cancellable,
+                               GAsyncReadyCallback callback,
+                               gpointer user_data)
+{
+        gint value;
+
+        /* Underflows are handled by gsd_backlight_set_brightness_val_async. */
+        value = backlight->brightness_target - backlight->brightness_step;
+
+        gsd_backlight_set_brightness_val_async (backlight,
+                                                value,
+                                                cancellable,
+                                                callback,
+                                                user_data);
+}
+
+/**
+ * gsd_backlight_step_down_finish
+ * @backlight: a #GsdBacklight
+ * @res: the #GAsyncResult passed to the callback
+ * @error: #GError return address
+ *
+ * Finish an operation started by gsd_backlight_step_down_async(). Will return
+ * the value that was actually set (which may be different because of rounding
+ * or as multiple set actions were queued up).
+ *
+ * Please note that a call to gsd_backlight_get_brightness() may not in fact
+ * return the same value if further operations to set the value are pending.
+ *
+ * For simplicity it is also valid to call gsd_backlight_set_brightness_finish()
+ * allowing sharing the callback routine for calls to
+ * gsd_backlight_set_brightness_async(), gsd_backlight_step_up_async(),
+ * gsd_backlight_step_down_async() and gsd_backlight_cycle_up_async().
+ *
+ * Returns: The brightness in percent that was set.
+ **/
+gint
+gsd_backlight_step_down_finish (GsdBacklight *backlight,
+                                GAsyncResult *res,
+                                GError **error)
+{
+        return g_task_propagate_int (G_TASK (res), error);
+}
+
+/**
+ * gsd_backlight_cycle_up_async
+ * @backlight: a #GsdBacklight
+ * @cancellable: an optional #GCancellable, NULL to ignore
+ * @callback: the #GAsyncReadyCallback invoked for cycle up to be finished
+ * @user_data: the #gpointer passed to the callback
+ *
+ * Start a brightness cycle up operation by gsd_backlight_cycle_up_async().
+ * The brightness will be stepped up if it is not already at the maximum.
+ * If it is already at the maximum, it will be set to the minimum brightness.
+ **/
+void
+gsd_backlight_cycle_up_async (GsdBacklight *backlight,
+                              GCancellable *cancellable,
+                              GAsyncReadyCallback callback,
+                              gpointer user_data)
+{
+        if (backlight->brightness_target < backlight->brightness_max)
+                gsd_backlight_step_up_async (backlight,
+                                             cancellable,
+                                             callback,
+                                             user_data);
+        else
+                gsd_backlight_set_brightness_val_async (backlight,
+                                                        backlight->brightness_min,
+                                                        cancellable,
+                                                        callback,
+                                                        user_data);
+}
+
+/**
+ * gsd_backlight_cycle_up_finish
+ * @backlight: a #GsdBacklight
+ * @res: the #GAsyncResult passed to the callback
+ * @error: #GError return address
+ *
+ * Finish an operation started by gsd_backlight_cycle_up_async(). Will return
+ * the value that was actually set (which may be different because of rounding
+ * or as multiple set actions were queued up).
+ *
+ * Please note that a call to gsd_backlight_get_brightness() may not in fact
+ * return the same value if further operations to set the value are pending.
+ *
+ * For simplicity it is also valid to call gsd_backlight_set_brightness_finish()
+ * allowing sharing the callback routine for calls to
+ * gsd_backlight_set_brightness_async(), gsd_backlight_step_up_async(),
+ * gsd_backlight_step_down_async() and gsd_backlight_cycle_up_async().
+ *
+ * Returns: The brightness in percent that was set.
+ **/
+gint
+gsd_backlight_cycle_up_finish (GsdBacklight *backlight,
+                               GAsyncResult *res,
+                               GError **error)
+{
+        return g_task_propagate_int (G_TASK (res), error);
+}
+
+/**
+ * gsd_backlight_get_connector
+ * @backlight: a #GsdBacklight
+ *
+ * Return the connector for the display that is being controlled by the
+ * #GsdBacklight object. This connector can be passed to gnome-shell to show
+ * the on screen display only on the affected screen.
+ *
+ * Returns: The connector of the controlled output or NULL if unknown.
+ **/
+const char*
+gsd_backlight_get_connector (GsdBacklight *backlight)
+{
+        GnomeRROutput *output;
+
+        output = gsd_backlight_rr_find_output (backlight, FALSE);
+        if (output == NULL)
+                return NULL;
+
+        return gnome_rr_output_get_name (output);
+}
+
+static void
+gsd_backlight_rr_screen_changed_cb (GnomeRRScreen *screen,
+                                    gpointer data)
+{
+        GsdBacklight *backlight = GSD_BACKLIGHT (data);
+        GnomeRROutput *output;
+        gboolean builtin_display_disabled = FALSE;
+
+        /* NOTE: Err on the side of assuming the backlight controlls something
+         *       even if we cannot find the output that belongs to it.
+         *       This might backfire on us obviously if the hardware claims it
+         *       can control a non-existing screen.
+         */
+        output = gsd_backlight_rr_find_output (backlight, FALSE);
+        if (output)
+                builtin_display_disabled = !gnome_rr_output_get_crtc (output);
+
+        if (builtin_display_disabled != backlight->builtin_display_disabled) {
+                backlight->builtin_display_disabled = builtin_display_disabled;
+                g_object_notify_by_pspec (G_OBJECT (backlight), props[PROP_BRIGHTNESS]);
+        }
+}
+
+static void
+gsd_backlight_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+        GsdBacklight *backlight = GSD_BACKLIGHT (object);
+
+        switch (prop_id) {
+        case PROP_RR_SCREEN:
+                g_value_set_object (value, backlight->rr_screen);
+                break;
+
+        case PROP_BRIGHTNESS:
+                g_value_set_int (value, gsd_backlight_get_brightness (backlight, NULL));
+                break;
+
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gsd_backlight_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+        GsdBacklight *backlight = GSD_BACKLIGHT (object);
+
+        switch (prop_id) {
+        case PROP_RR_SCREEN:
+                backlight->rr_screen = g_value_dup_object (value);
+
+                g_signal_connect_object (backlight->rr_screen, "changed",
+                                         G_CALLBACK (gsd_backlight_rr_screen_changed_cb),
+                                         object, 0);
+                gsd_backlight_rr_screen_changed_cb (backlight->rr_screen, object);
+
+                break;
+
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static gboolean
+gsd_backlight_initable_init (GInitable       *initable,
+                             GCancellable    *cancellable,
+                             GError         **error)
+{
+        GsdBacklight *backlight = GSD_BACKLIGHT (initable);
+        GnomeRROutput* output = NULL;
+        GError *logind_error = NULL;
+
+        if (cancellable != NULL) {
+                g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                                     "GsdBacklight does not support cancelling initialization.");
+                return FALSE;
+        }
+
+#ifdef __linux__
+        backlight->logind_proxy =
+                g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+                                               0,
+                                               NULL,
+                                               SYSTEMD_DBUS_NAME,
+                                               SYSTEMD_DBUS_PATH,
+                                               SYSTEMD_DBUS_INTERFACE,
+                                               NULL, &logind_error);
+        if (backlight->logind_proxy) {
+                /* Check that the SetBrightness method does exist */
+                g_dbus_proxy_call_sync (backlight->logind_proxy,
+                                        "SetBrightness", NULL,
+                                        G_DBUS_CALL_FLAGS_NONE, -1,
+                                        NULL, &logind_error);
+
+                if (g_error_matches (logind_error, G_DBUS_ERROR,
+                                     G_DBUS_ERROR_INVALID_ARGS)) {
+                        /* We are calling the method with no arguments, so
+                         * this is expected.
+                         */
+                        g_clear_error (&logind_error);
+                } else if (g_error_matches (logind_error, G_DBUS_ERROR,
+                                            G_DBUS_ERROR_UNKNOWN_METHOD)) {
+                        /* systemd version is too old, so ignore.
+                         */
+                        g_clear_error (&logind_error);
+                        g_clear_object (&backlight->logind_proxy);
+                } else {
+                        /* Fail on anything else */
+                        g_clear_object (&backlight->logind_proxy);
+                }
+        }
+
+        if (logind_error) {
+                g_warning ("No logind found: %s", logind_error->message);
+                g_error_free (logind_error);
+        }
+
+        /* Try finding a udev device. */
+        if (gsd_backlight_udev_init (backlight))
+                goto found;
+#endif /* __linux__ */
+
+        /* Try GNOME RR as a fallback. */
+        output = gsd_backlight_rr_find_output (backlight, TRUE);
+        if (output) {
+                g_debug ("Using GNOME RR (mutter) for backlight.");
+                backlight->brightness_min = 1;
+                backlight->brightness_max = 100;
+                backlight->brightness_val = gnome_rr_output_get_backlight (output);
+                backlight->brightness_step = gnome_rr_output_get_min_backlight_step (output);
+
+                goto found;
+        }
+
+        g_debug ("No usable backlight found.");
+
+        g_set_error_literal (error, GSD_POWER_MANAGER_ERROR, GSD_POWER_MANAGER_ERROR_NO_BACKLIGHT,
+                             "No usable backlight could be found!");
+
+        return FALSE;
+
+found:
+        backlight->brightness_target = backlight->brightness_val;
+        backlight->brightness_step = MAX(backlight->brightness_step, BRIGHTNESS_STEP_AMOUNT(backlight->brightness_max - backlight->brightness_min + 1));
+
+        g_debug ("Step size for backlight is %i.", backlight->brightness_step);
+
+        return TRUE;
+}
+
+static void
+gsd_backlight_finalize (GObject *object)
+{
+        GsdBacklight *backlight = GSD_BACKLIGHT (object);
+
+#ifdef __linux__
+        g_assert (backlight->active_task == NULL);
+        g_assert (g_queue_is_empty (&backlight->tasks));
+        g_clear_object (&backlight->logind_proxy);
+        g_clear_object (&backlight->udev);
+        g_clear_object (&backlight->udev_device);
+        if (backlight->idle_update) {
+                g_source_remove (backlight->idle_update);
+                backlight->idle_update = 0;
+        }
+#endif /* __linux__ */
+
+        g_clear_object (&backlight->rr_screen);
+}
+
+static void
+gsd_backlight_initable_iface_init (GInitableIface *iface)
+{
+  iface->init = gsd_backlight_initable_init;
+}
+
+static void
+gsd_backlight_class_init (GsdBacklightClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_backlight_finalize;
+        object_class->get_property = gsd_backlight_get_property;
+        object_class->set_property = gsd_backlight_set_property;
+
+        props[PROP_RR_SCREEN] = g_param_spec_object ("rr-screen", "GnomeRRScreen",
+                                                     "GnomeRRScreen usable for backlight control.",
+                                                     GNOME_TYPE_RR_SCREEN,
+                                                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+        props[PROP_BRIGHTNESS] = g_param_spec_int ("brightness", "The display brightness",
+                                                   "The brightness of the internal display in percent.",
+                                                   0, 100, 100,
+                                                   G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+        g_object_class_install_properties (object_class, PROP_LAST, props);
+}
+
+
+static void
+gsd_backlight_init (GsdBacklight *backlight)
+{
+        backlight->brightness_target = -1;
+        backlight->brightness_min = -1;
+        backlight->brightness_max = -1;
+        backlight->brightness_val = -1;
+        backlight->brightness_step = 1;
+
+#ifdef __linux__
+        backlight->active_task = NULL;
+        g_queue_init (&backlight->tasks);
+#endif /* __linux__ */
+}
+
+GsdBacklight *
+gsd_backlight_new (GnomeRRScreen  *rr_screen,
+                   GError        **error)
+{
+        return GSD_BACKLIGHT (g_initable_new (GSD_TYPE_BACKLIGHT, NULL, error,
+                                              "rr-screen", rr_screen,
+                                              NULL));
+}
+
diff --git a/plugins/power/gsd-backlight.h b/plugins/power/gsd-backlight.h
new file mode 100644
index 0000000..e4fac6b
--- /dev/null
+++ b/plugins/power/gsd-backlight.h
@@ -0,0 +1,81 @@
+/* -*- mode: c; style: linux -*-
+ * 
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * Written by: Benjamin Berg 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 _GSD_BACKLIGHT_H
+#define _GSD_BACKLIGHT_H
+
+#include 
+#include 
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include 
+
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_BACKLIGHT gsd_backlight_get_type ()
+G_DECLARE_FINAL_TYPE (GsdBacklight, gsd_backlight, GSD, BACKLIGHT, GObject);
+
+gint gsd_backlight_get_brightness        (GsdBacklight         *backlight,
+                                          gint                 *target);
+
+void gsd_backlight_set_brightness_async  (GsdBacklight         *backlight,
+                                          gint                  percentage,
+                                          GCancellable         *cancellable,
+                                          GAsyncReadyCallback   callback,
+                                          gpointer              user_data);
+void gsd_backlight_step_up_async         (GsdBacklight         *backlight,
+                                          GCancellable         *cancellable,
+                                          GAsyncReadyCallback   callback,
+                                          gpointer              user_data);
+void gsd_backlight_step_down_async       (GsdBacklight         *backlight,
+                                          GCancellable         *cancellable,
+                                          GAsyncReadyCallback   callback,
+                                          gpointer              user_data);
+void gsd_backlight_cycle_up_async        (GsdBacklight         *backlight,
+                                          GCancellable         *cancellable,
+                                          GAsyncReadyCallback   callback,
+                                          gpointer              user_data);
+
+gint gsd_backlight_set_brightness_finish (GsdBacklight         *backlight,
+                                          GAsyncResult         *res,
+                                          GError              **error);
+
+gint gsd_backlight_step_up_finish        (GsdBacklight         *backlight,
+                                          GAsyncResult         *res,
+                                          GError              **error);
+
+gint gsd_backlight_step_down_finish      (GsdBacklight         *backlight,
+                                          GAsyncResult         *res,
+                                          GError              **error);
+
+gint gsd_backlight_cycle_up_finish       (GsdBacklight         *backlight,
+                                          GAsyncResult         *res,
+                                          GError              **error);
+
+const char*  gsd_backlight_get_connector (GsdBacklight         *backlight);
+
+GsdBacklight* gsd_backlight_new          (GnomeRRScreen        *screen,
+                                          GError              **error);
+
+
+G_END_DECLS
+
+#endif /* _GSD_BACKLIGHT_H */
diff --git a/plugins/power/gsd-power-constants-update.pl b/plugins/power/gsd-power-constants-update.pl
new file mode 100755
index 0000000..1615131
--- /dev/null
+++ b/plugins/power/gsd-power-constants-update.pl
@@ -0,0 +1,49 @@
+#!/usr/bin/env perl
+
+# Author  : Simos Xenitellis .
+# Author  : Bastien Nocera 
+# Version : 1.2
+#
+# Input   : gsd-power-constants.h
+# Output  : gsdpowerconstants.py
+#
+use strict;
+
+# Used for reading the keysymdef symbols.
+my @constantselements;
+
+(scalar @ARGV >= 2) or die "Usage: $0  \n";
+my ($input, $output) = @ARGV;
+
+die "Could not open file gsd-power-constants.h: $!\n" unless open(IN_CONSTANTS, "<:utf8", $input);
+
+# Output: gtk+/gdk/gdkkeysyms.h
+die "Could not open file gsdpowerconstants.py: $!\n" unless open(OUT_CONSTANTS, ">:utf8", $output);
+
+print OUT_CONSTANTS<)
+{
+	next if ( ! /^#define / );
+
+	@constantselements = split(/\s+/);
+	die "Internal error, no \@constantselements: $_\n" unless @constantselements;
+
+	my $constant = $constantselements[1];
+	my $value = $constantselements[2];
+
+	printf OUT_CONSTANTS "%s = %s;\n", $constant, $value;
+}
+
+close IN_CONSTANTS;
+
+printf "We just finished converting $input to $output\nThank you\n";
diff --git a/plugins/power/gsd-power-constants.h b/plugins/power/gsd-power-constants.h
new file mode 100644
index 0000000..8bf9c64
--- /dev/null
+++ b/plugins/power/gsd-power-constants.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 .
+ *
+ */
+
+/* The blank delay when the screensaver is active */
+#define SCREENSAVER_TIMEOUT_BLANK                       30 /* seconds */
+
+/* The dim delay when dimming on idle is requested but idle-delay
+ * is set to "Never" */
+#define IDLE_DIM_BLANK_DISABLED_MIN                     60 /* seconds */
+
+/* Which fraction of the idle-delay is the idle-dim delay */
+#define IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER                1.0/2.0
+
+/* The dim delay under which we do not bother dimming */
+#define MINIMUM_IDLE_DIM_DELAY                          10 /* seconds */
+
+/* The amount of time we'll undim if the machine is idle when plugged in */
+#define POWER_UP_TIME_ON_AC                             15 /* seconds */
+
+/* Default brightness values for the mock backlight used in the test suite */
+#define GSD_MOCK_DEFAULT_BRIGHTNESS                     50
+#define GSD_MOCK_MAX_BRIGHTNESS                        100
+
+/* When unplugging the external monitor, give a certain amount
+ * of time before suspending the laptop */
+#define LID_CLOSE_SAFETY_TIMEOUT                        8 /* seconds */
diff --git a/plugins/power/gsd-power-enums-update.c b/plugins/power/gsd-power-enums-update.c
new file mode 100644
index 0000000..546e494
--- /dev/null
+++ b/plugins/power/gsd-power-enums-update.c
@@ -0,0 +1,44 @@
+#include 
+#include 
+#include 
+
+/* XXX: The following functions use printf, because otherwise there were
+ *      build failures when the building with the memory sanitizer enabled.
+ *      These may be false positives though.
+ */
+
+static void
+output_enum_values (GType class_type)
+{
+	GEnumClass *eclass;
+	guint i;
+
+	eclass = G_ENUM_CLASS (g_type_class_peek (class_type));
+	for (i = 0; i < eclass->n_values; i++) {
+		GEnumValue *value = &(eclass->values[i]);
+		printf ("%s = %d;\n", value->value_name, value->value);
+	}
+}
+
+static void
+output_flags_values (GType class_type)
+{
+	GFlagsClass *fclass;
+	guint i;
+
+	fclass = G_FLAGS_CLASS (g_type_class_peek (class_type));
+	for (i = 0; i < fclass->n_values; i++) {
+		GFlagsValue *value = &(fclass->values[i]);
+		printf ("%s = %d;\n", value->value_name, value->value);
+	}
+}
+
+int
+main (int argc, char **argv)
+{
+	g_type_class_ref (GSD_POWER_TYPE_INHIBITOR_FLAG);
+	g_type_class_ref (GSD_POWER_TYPE_PRESENCE_STATUS);
+	output_flags_values (GSD_POWER_TYPE_INHIBITOR_FLAG);
+	output_enum_values (GSD_POWER_TYPE_PRESENCE_STATUS);
+	return 0;
+}
diff --git a/plugins/power/gsd-power-enums.c.in b/plugins/power/gsd-power-enums.c.in
new file mode 100644
index 0000000..1b994bb
--- /dev/null
+++ b/plugins/power/gsd-power-enums.c.in
@@ -0,0 +1,40 @@
+/*** BEGIN file-header ***/
+#include "gsd-power-enums.h"
+#include 
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@basename@" */
+#include "@basename@"
+
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void)
+{
+	static GType the_type = 0;
+
+	if (the_type == 0)
+	{
+		static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+			{ @VALUENAME@,
+			  "@VALUENAME@",
+			  "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+			{ 0, NULL, NULL }
+		};
+		the_type = g_@type@_register_static (
+				g_intern_static_string ("@EnumName@"),
+				values);
+	}
+	return the_type;
+}
+
+/*** END value-tail ***/
diff --git a/plugins/power/gsd-power-enums.h.in b/plugins/power/gsd-power-enums.h.in
new file mode 100644
index 0000000..53e37c0
--- /dev/null
+++ b/plugins/power/gsd-power-enums.h.in
@@ -0,0 +1,27 @@
+/*** BEGIN file-header ***/
+#ifndef GSD_POWER_ENUMS_H
+#define GSD_POWER_ENUMS_H
+
+#include 
+
+G_BEGIN_DECLS
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* Enumerations from "@basename@" */
+
+/*** END file-production ***/
+
+/*** BEGIN enumeration-production ***/
+#define GSD_POWER_TYPE_@ENUMSHORT@ (@enum_name@_get_type())
+GType @enum_name@_get_type (void) G_GNUC_CONST;
+
+/*** END enumeration-production ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* !GSD_POWER_ENUMS_H */
+/*** END file-tail ***/
+
diff --git a/plugins/power/gsd-power-manager.c b/plugins/power/gsd-power-manager.c
new file mode 100644
index 0000000..757986e
--- /dev/null
+++ b/plugins/power/gsd-power-manager.c
@@ -0,0 +1,3561 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann 
+ * Copyright (C) 2011-2012, 2015 Richard Hughes 
+ * Copyright (C) 2011 Ritesh Khadgaray 
+ * Copyright (C) 2012-2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include 
+#include 
+
+#include 
+
+#include "gsd-power-constants.h"
+#include "gsm-inhibitor-flag.h"
+#include "gsm-presence-flag.h"
+#include "gsm-manager-logout-mode.h"
+#include "gpm-common.h"
+#include "gsd-backlight.h"
+#include "gnome-settings-profile.h"
+#include "gnome-settings-bus.h"
+#include "gsd-enums.h"
+#include "gsd-power-manager.h"
+
+#define GSD_DBUS_NAME "org.gnome.SettingsDaemon"
+#define GSD_DBUS_PATH "/org/gnome/SettingsDaemon"
+#define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon"
+
+#define UPOWER_DBUS_NAME                        "org.freedesktop.UPower"
+#define UPOWER_DBUS_PATH                        "/org/freedesktop/UPower"
+#define UPOWER_DBUS_PATH_KBDBACKLIGHT           "/org/freedesktop/UPower/KbdBacklight"
+#define UPOWER_DBUS_INTERFACE                   "org.freedesktop.UPower"
+#define UPOWER_DBUS_INTERFACE_KBDBACKLIGHT      "org.freedesktop.UPower.KbdBacklight"
+
+#define PPD_DBUS_NAME                           "net.hadess.PowerProfiles"
+#define PPD_DBUS_PATH                           "/net/hadess/PowerProfiles"
+#define PPD_DBUS_INTERFACE                      "net.hadess.PowerProfiles"
+
+#define GSD_POWER_SETTINGS_SCHEMA               "org.gnome.settings-daemon.plugins.power"
+
+#define GSD_POWER_DBUS_NAME                     GSD_DBUS_NAME ".Power"
+#define GSD_POWER_DBUS_PATH                     GSD_DBUS_PATH "/Power"
+#define GSD_POWER_DBUS_INTERFACE                GSD_DBUS_BASE_INTERFACE ".Power"
+#define GSD_POWER_DBUS_INTERFACE_SCREEN         GSD_POWER_DBUS_INTERFACE ".Screen"
+#define GSD_POWER_DBUS_INTERFACE_KEYBOARD       GSD_POWER_DBUS_INTERFACE ".Keyboard"
+
+#define GSD_POWER_MANAGER_NOTIFY_TIMEOUT_SHORT          10 * 1000 /* ms */
+#define GSD_POWER_MANAGER_NOTIFY_TIMEOUT_LONG           30 * 1000 /* ms */
+
+#define SYSTEMD_DBUS_NAME                       "org.freedesktop.login1"
+#define SYSTEMD_DBUS_PATH                       "/org/freedesktop/login1"
+#define SYSTEMD_DBUS_INTERFACE                  "org.freedesktop.login1.Manager"
+
+/* Time between notifying the user about a critical action and the action itself in UPower. */
+#define GSD_ACTION_DELAY 20
+/* And the time before we stop the warning sound */
+#define GSD_STOP_SOUND_DELAY GSD_ACTION_DELAY - 2
+
+/* The bandwidth of the low-pass filter used to smooth ambient light readings,
+ * measured in Hz.  Smaller numbers result in smoother backlight changes.
+ * Larger numbers are more responsive to abrupt changes in ambient light. */
+#define GSD_AMBIENT_BANDWIDTH_HZ       0.1f
+
+/* Convert bandwidth to time constant.  Units of constant are microseconds. */
+#define GSD_AMBIENT_TIME_CONSTANT       (G_USEC_PER_SEC * 1.0f / (2.0f * G_PI * GSD_AMBIENT_BANDWIDTH_HZ))
+
+static const gchar introspection_xml[] =
+""
+"  "
+"    "
+"    "
+"      "
+"      "
+"    "
+"    "
+"      "
+"      "
+"    "
+"    "
+"      "
+"      "
+"    "
+"  "
+"  "
+"    "
+"    "
+"      "
+"    "
+"    "
+"      "
+"    "
+"    "
+"      "
+"    "
+"    "
+"      "
+"      "
+"    "
+"  "
+"";
+
+typedef enum {
+        GSD_POWER_IDLE_MODE_NORMAL,
+        GSD_POWER_IDLE_MODE_DIM,
+        GSD_POWER_IDLE_MODE_BLANK,
+        GSD_POWER_IDLE_MODE_SLEEP
+} GsdPowerIdleMode;
+
+struct _GsdPowerManager
+{
+        GObject                  parent;
+
+        /* D-Bus */
+        GsdSessionManager       *session;
+        guint                    name_id;
+        GDBusNodeInfo           *introspection_data;
+        GDBusConnection         *connection;
+        GCancellable            *cancellable;
+
+        /* Settings */
+        GSettings               *settings;
+        GSettings               *settings_bus;
+        GSettings               *settings_screensaver;
+
+        /* Screensaver */
+        GsdScreenSaver          *screensaver_proxy;
+        gboolean                 screensaver_active;
+
+        /* State */
+        gboolean                 lid_is_present;
+        gboolean                 lid_is_closed;
+        gboolean                 session_is_active;
+        UpClient                *up_client;
+        GPtrArray               *devices_array;
+        UpDevice                *device_composite;
+        GnomeRRScreen           *rr_screen;
+        NotifyNotification      *notification_ups_discharging;
+        NotifyNotification      *notification_low;
+        NotifyNotification      *notification_sleep_warning;
+        GsdPowerActionType       sleep_action_type;
+        GHashTable              *devices_notified_ht; /* key = serial str, value = UpDeviceLevel */
+        gboolean                 battery_is_low; /* battery low, or UPS discharging */
+
+        /* Brightness */
+        GsdBacklight            *backlight;
+        gint                     pre_dim_brightness; /* level, not percentage */
+
+        /* Keyboard */
+        GDBusProxy              *upower_kbd_proxy;
+        gint                     kbd_brightness_now;
+        gint                     kbd_brightness_max;
+        gint                     kbd_brightness_old;
+        gint                     kbd_brightness_pre_dim;
+
+        /* Ambient */
+        GDBusProxy              *iio_proxy;
+        guint                    iio_proxy_watch_id;
+        gboolean                 ambient_norm_required;
+        gdouble                  ambient_accumulator;
+        gdouble                  ambient_norm_value;
+        gdouble                  ambient_percentage_old;
+        gdouble                  ambient_last_absolute;
+        gint64                   ambient_last_time;
+
+        /* Power Profiles */
+        GDBusProxy              *power_profiles_proxy;
+        guint32                  power_saver_cookie;
+        gboolean                 power_saver_enabled;
+
+        /* Sound */
+        guint32                  critical_alert_timeout_id;
+
+        /* systemd stuff */
+        GDBusProxy              *logind_proxy;
+        gint                     inhibit_lid_switch_fd;
+        gboolean                 inhibit_lid_switch_taken;
+        gint                     inhibit_suspend_fd;
+        gboolean                 inhibit_suspend_taken;
+        guint                    inhibit_lid_switch_timer_id;
+        gboolean                 is_virtual_machine;
+
+        /* Idles */
+        GnomeIdleMonitor        *idle_monitor;
+        guint                    idle_dim_id;
+        guint                    idle_blank_id;
+        guint                    idle_sleep_warning_id;
+        guint                    idle_sleep_id;
+        guint                    user_active_id;
+        GsdPowerIdleMode         current_idle_mode;
+
+        guint                    temporary_unidle_on_ac_id;
+        GsdPowerIdleMode         previous_idle_mode;
+
+        guint                    xscreensaver_watchdog_timer_id;
+};
+
+enum {
+        PROP_0,
+};
+
+static void     gsd_power_manager_class_init  (GsdPowerManagerClass *klass);
+static void     gsd_power_manager_init        (GsdPowerManager      *power_manager);
+
+static void      engine_device_warning_changed_cb (UpDevice *device, GParamSpec *pspec, GsdPowerManager *manager);
+static void      do_power_action_type (GsdPowerManager *manager, GsdPowerActionType action_type);
+static void      uninhibit_lid_switch (GsdPowerManager *manager);
+static void      stop_inhibit_lid_switch_timer (GsdPowerManager *manager);
+static void      sync_lid_inhibitor (GsdPowerManager *manager);
+static void      main_battery_or_ups_low_changed (GsdPowerManager *manager, gboolean is_low);
+static gboolean  idle_is_session_inhibited (GsdPowerManager *manager, guint mask, gboolean *is_inhibited);
+static void      idle_triggered_idle_cb (GnomeIdleMonitor *monitor, guint watch_id, gpointer user_data);
+static void      idle_became_active_cb (GnomeIdleMonitor *monitor, guint watch_id, gpointer user_data);
+static void      iio_proxy_changed (GsdPowerManager *manager);
+static void      iio_proxy_changed_cb (GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidated_properties, gpointer user_data);
+
+G_DEFINE_TYPE (GsdPowerManager, gsd_power_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+GQuark
+gsd_power_manager_error_quark (void)
+{
+        static GQuark quark = 0;
+        if (!quark)
+                quark = g_quark_from_static_string ("gsd_power_manager_error");
+        return quark;
+}
+
+static void
+notify_close_if_showing (NotifyNotification **notification)
+{
+        if (*notification == NULL)
+                return;
+        notify_notification_close (*notification, NULL);
+        g_clear_object (notification);
+}
+
+static void
+engine_device_add (GsdPowerManager *manager, UpDevice *device)
+{
+        UpDeviceKind kind;
+
+        /* Batteries and UPSes are already handled through
+         * the composite battery */
+        g_object_get (device, "kind", &kind, NULL);
+        if (kind == UP_DEVICE_KIND_BATTERY ||
+            kind == UP_DEVICE_KIND_UPS ||
+            kind == UP_DEVICE_KIND_LINE_POWER)
+                return;
+        g_ptr_array_add (manager->devices_array, g_object_ref (device));
+
+        g_signal_connect (device, "notify::warning-level",
+                          G_CALLBACK (engine_device_warning_changed_cb), manager);
+
+        engine_device_warning_changed_cb (device, NULL, manager);
+}
+
+static gboolean
+engine_coldplug (GsdPowerManager *manager)
+{
+        guint i;
+        GPtrArray *array = NULL;
+        UpDevice *device;
+
+        /* add to database */
+        array = up_client_get_devices2 (manager->up_client);
+
+        for (i = 0 ; array != NULL && i < array->len ; i++) {
+                device = g_ptr_array_index (array, i);
+                engine_device_add (manager, device);
+        }
+
+        g_clear_pointer (&array, g_ptr_array_unref);
+
+        /* never repeat */
+        return FALSE;
+}
+
+static void
+engine_device_added_cb (UpClient *client, UpDevice *device, GsdPowerManager *manager)
+{
+        engine_device_add (manager, device);
+}
+
+static void
+engine_device_removed_cb (UpClient *client, const char *object_path, GsdPowerManager *manager)
+{
+        guint i;
+
+        for (i = 0; i < manager->devices_array->len; i++) {
+                UpDevice *device = g_ptr_array_index (manager->devices_array, i);
+
+                if (g_strcmp0 (object_path, up_device_get_object_path (device)) == 0) {
+                        g_ptr_array_remove_index (manager->devices_array, i);
+                        break;
+                }
+        }
+}
+
+static void
+on_notification_closed (NotifyNotification *notification, gpointer data)
+{
+    g_object_unref (notification);
+}
+
+/* See PrivacyScope in messageTray.js in gnome-shell. A notification with
+ * ‘system’ scope has its detailed description shown in the lock screen. ‘user’
+ * scope notifications don’t (because they could contain private information). */
+typedef enum
+{
+        NOTIFICATION_PRIVACY_USER,
+        NOTIFICATION_PRIVACY_SYSTEM,
+} NotificationPrivacyScope;
+
+static const gchar *
+notification_privacy_scope_to_string (NotificationPrivacyScope scope)
+{
+        switch (scope) {
+        case NOTIFICATION_PRIVACY_USER:
+                return "user";
+        case NOTIFICATION_PRIVACY_SYSTEM:
+                return "system";
+        default:
+                g_assert_not_reached ();
+        }
+}
+
+static void
+create_notification (const char *summary,
+                     const char *body,
+                     const char *icon_name,
+                     NotificationPrivacyScope privacy_scope,
+                     NotifyNotification **weak_pointer_location)
+{
+        NotifyNotification *notification;
+
+        notification = notify_notification_new (summary, body, icon_name);
+        /* TRANSLATORS: this is the notification application name */
+        notify_notification_set_app_name (notification, _("Power"));
+        notify_notification_set_hint_string (notification, "desktop-entry", "gnome-power-panel");
+        notify_notification_set_hint_string (notification, "x-gnome-privacy-scope",
+                                             notification_privacy_scope_to_string (privacy_scope));
+        notify_notification_set_urgency (notification,
+                                         NOTIFY_URGENCY_CRITICAL);
+        *weak_pointer_location = notification;
+        g_object_add_weak_pointer (G_OBJECT (notification),
+                                   (gpointer *) weak_pointer_location);
+        g_signal_connect (notification, "closed",
+                          G_CALLBACK (on_notification_closed), NULL);
+}
+
+static void
+engine_ups_discharging (GsdPowerManager *manager, UpDevice *device)
+{
+        const gchar *title;
+        gchar *remaining_text = NULL;
+        gdouble percentage;
+        char *icon_name;
+        gint64 time_to_empty;
+        GString *message;
+        UpDeviceKind kind;
+
+        /* get device properties */
+        g_object_get (device,
+                      "kind", &kind,
+                      "percentage", &percentage,
+                      "time-to-empty", &time_to_empty,
+                      "icon-name", &icon_name,
+                      NULL);
+
+        if (kind != UP_DEVICE_KIND_UPS)
+                return;
+
+        /* only show text if there is a valid time */
+        if (time_to_empty > 0)
+                remaining_text = gpm_get_timestring (time_to_empty);
+
+        /* TRANSLATORS: UPS is now discharging */
+        title = _("UPS Discharging");
+
+        message = g_string_new ("");
+        if (remaining_text != NULL) {
+                /* TRANSLATORS: tell the user how much time they have got */
+                g_string_append_printf (message, _("%s of UPS backup power remaining"),
+                                        remaining_text);
+        } else {
+                g_string_append (message, _("Unknown amount of UPS backup power remaining"));
+        }
+        g_string_append_printf (message, " (%.0f%%)", percentage);
+
+        /* close any existing notification of this class */
+        notify_close_if_showing (&manager->notification_ups_discharging);
+
+        /* create a new notification */
+        create_notification (title, message->str,
+                             icon_name, NOTIFICATION_PRIVACY_SYSTEM,
+                             &manager->notification_ups_discharging);
+        notify_notification_set_timeout (manager->notification_ups_discharging,
+                                         GSD_POWER_MANAGER_NOTIFY_TIMEOUT_LONG);
+        notify_notification_set_hint (manager->notification_ups_discharging,
+                                      "transient", g_variant_new_boolean (TRUE));
+
+        notify_notification_show (manager->notification_ups_discharging, NULL);
+
+        g_string_free (message, TRUE);
+        g_free (icon_name);
+        g_free (remaining_text);
+}
+
+static GsdPowerActionType
+manager_critical_action_get (GsdPowerManager *manager)
+{
+        GsdPowerActionType policy;
+        char *action;
+
+        action = up_client_get_critical_action (manager->up_client);
+        /* We don't make the difference between HybridSleep and Hibernate */
+        if (g_strcmp0 (action, "PowerOff") == 0)
+                policy = GSD_POWER_ACTION_SHUTDOWN;
+        else
+                policy = GSD_POWER_ACTION_HIBERNATE;
+        g_free (action);
+        return policy;
+}
+
+static gboolean
+manager_critical_action_stop_sound_cb (GsdPowerManager *manager)
+{
+        /* stop playing the alert as it's too late to do anything now */
+        play_loop_stop (&manager->critical_alert_timeout_id);
+
+        return FALSE;
+}
+
+static gboolean
+engine_device_debounce_warn (GsdPowerManager *manager,
+                             UpDeviceKind kind,
+                             UpDeviceLevel warning,
+                             const char *serial)
+{
+        gpointer last_warning_ptr;
+        UpDeviceLevel last_warning;
+        gboolean ret = TRUE;
+
+        if (!serial)
+                return TRUE;
+
+        if (kind == UP_DEVICE_KIND_BATTERY ||
+            kind == UP_DEVICE_KIND_UPS)
+                return TRUE;
+
+        if (g_hash_table_lookup_extended (manager->devices_notified_ht, serial,
+                                          NULL, &last_warning_ptr)) {
+                last_warning = GPOINTER_TO_INT (last_warning_ptr);
+
+                if (last_warning >= warning)
+                        ret = FALSE;
+        }
+
+        if (warning != UP_DEVICE_LEVEL_UNKNOWN && warning != UP_DEVICE_LEVEL_NONE)
+                g_hash_table_insert (manager->devices_notified_ht,
+                                     g_strdup (serial),
+                                     GINT_TO_POINTER (warning));
+
+        return ret;
+}
+
+static const struct {
+        UpDeviceKind kind;
+        const char *title;
+        const char *low_body_remain;
+        const char *low_body;
+        const char *low_body_unk;
+        const char *crit_body;
+        const char *crit_body_unk;
+} peripheral_battery_notifications[] = {
+        /* Intentionally skipped types (too uncommon, and name too imprecise):
+         * UP_DEVICE_KIND_MODEM
+         * UP_DEVICE_KIND_NETWORK
+         * UP_DEVICE_KIND_VIDEO
+         * UP_DEVICE_KIND_WEARABLE
+         * UP_DEVICE_KIND_TOY
+         */
+        {
+                .kind = UP_DEVICE_KIND_MOUSE,
+                /* TRANSLATORS: notification title, a wireless mouse is low or very low on power */
+                .title         = N_("Mouse battery low"),
+
+                /* TRANSLATORS: notification body, a wireless mouse is low on power */
+                .low_body      = N_("Wireless mouse is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Wireless mouse is low on power"),
+                /* TRANSLATORS: notification body, a wireless mouse is very low on power */
+                .crit_body     = N_("Wireless mouse is very low on power (%.0f%%). "
+                                    "This device will soon stop functioning if not charged."),
+                .crit_body_unk = N_("Wireless mouse is very low on power. "
+                                    "This device will soon stop functioning if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_KEYBOARD,
+                /* TRANSLATORS: notification title, a wireless keyboard is low or very low on power */
+                .title         = N_("Keyboard battery low"),
+
+                /* TRANSLATORS: notification body, a wireless keyboard is low on power */
+                .low_body      = N_("Wireless keyboard is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Wireless keyboard is low on power"),
+                /* TRANSLATORS: notification body, a wireless keyboard is very low on power */
+                .crit_body     = N_("Wireless keyboard is very low on power (%.0f%%). "
+                                    "This device will soon stop functioning if not charged."),
+                .crit_body_unk = N_("Wireless keyboard is very low on power. "
+                                    "This device will soon stop functioning if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_PDA,
+                /* TRANSLATORS: notification title, a PDA (Personal Digital Assistance device) is low or very on power */
+                .title         = N_("PDA battery low"),
+
+                /* TRANSLATORS: notification body, a PDA (Personal Digital Assistance device) is low on power */
+                .low_body      = N_("PDA is low on power (%.0f%%)"),
+                .low_body_unk  = N_("PDA is low on power"),
+                /* TRANSLATORS: notification body, a PDA (Personal Digital Assistance device) is very low on power */
+                .crit_body     = N_("PDA is very low on power (%.0f%%). "
+                                    "This device will soon stop functioning if not charged."),
+                .crit_body_unk = N_("PDA is very low on power. "
+                                    "This device will soon stop functioning if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_PHONE,
+                /* TRANSLATORS: notification title, a cell phone (mobile phone) is low or very low on power */
+                .title         = N_("Cell phone battery low"),
+
+                /* TRANSLATORS: notification body, a cell phone (mobile phone) is low on power */
+                .low_body      = N_("Cell phone is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Cell phone is low on power"),
+                /* TRANSLATORS: notification body, a cell phone (mobile phone) is very low on power */
+                .crit_body     = N_("Cell phone is very low on power (%.0f%%). "
+                                    "This device will soon stop functioning if not charged."),
+                .crit_body_unk = N_("Cell phone is very low on power. "
+                                    "This device will soon stop functioning if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_MEDIA_PLAYER,
+                /* TRANSLATORS: notification title, a media player (e.g. mp3 player) is low or very low on power */
+                .title         = N_("Media player battery low"),
+
+                /* TRANSLATORS: notification body, a media player (e.g. mp3 player) is low on power */
+                .low_body      = N_("Media player is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Media player is low on power"),
+                /* TRANSLATORS: notification body, a media player (e.g. mp3 player) is very low on power */
+                .crit_body     = N_("Media player is very low on power (%.0f%%). "
+                                    "This device will soon stop functioning if not charged."),
+                .crit_body_unk = N_("Media player is very low on power. "
+                                    "This device will soon stop functioning if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_TABLET,
+                /* TRANSLATORS: notification title, a graphics tablet (e.g. wacom) is low or very low on power */
+                .title         = N_("Tablet battery low"),
+
+                /* TRANSLATORS: notification body, a graphics tablet (e.g. wacom) is low on power */
+                .low_body      = N_("Tablet is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Tablet is low on power"),
+                /* TRANSLATORS: notification body, a graphics tablet (e.g. wacom) is very low on power */
+                .crit_body     = N_("Tablet is very low on power (%.0f%%). "
+                                    "This device will soon stop functioning if not charged."),
+                .crit_body_unk = N_("Tablet is very low on power. "
+                                    "This device will soon stop functioning if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_COMPUTER,
+                /* TRANSLATORS: notification title, an attached computer (e.g. ipad) is low or very low on power */
+                .title         = N_("Attached computer battery low"),
+
+                /* TRANSLATORS: notification body, an attached computer (e.g. ipad) is low on power */
+                .low_body      = N_("Attached computer is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Attached computer is low on power"),
+                /* TRANSLATORS: notification body, an attached computer (e.g. ipad) is very low on power */
+                .crit_body     = N_("Attached computer is very low on power (%.0f%%). "
+                                    "The device will soon shutdown if not charged."),
+                .crit_body_unk = N_("Attached computer is very low on power. "
+                                    "The device will soon shutdown if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_GAMING_INPUT,
+                /* TRANSLATORS: notification title, a game controller (e.g. joystick or joypad) is low or very low on power */
+                .title         = N_("Game controller battery low"),
+
+                /* TRANSLATORS: notification body, a game controller (e.g. joystick or joypad) is low on power */
+                .low_body      = N_("Game controller is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Game controller is low on power"),
+                /* TRANSLATORS: notification body, an attached game controller (e.g. joystick or joypad) is very low on power */
+                .crit_body     = N_("Game controller is very low on power (%.0f%%). "
+                                    "The device will soon shutdown if not charged."),
+                .crit_body_unk = N_("Game controller is very low on power. "
+                                    "The device will soon shutdown if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_PEN,
+                /* TRANSLATORS: notification title, a pen is low or very low on power */
+                .title         = N_("Pen battery low"),
+
+                /* TRANSLATORS: notification body, a pen is low on power */
+                .low_body      = N_("Pen is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Pen is low on power"),
+                /* TRANSLATORS: notification body, a pen is very low on power */
+                .crit_body     = N_("Pen is very low on power (%.0f%%). "
+                                    "The device will soon shutdown if not charged."),
+                .crit_body_unk = N_("Pen is very low on power. "
+                                    "The device will soon shutdown if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_TOUCHPAD,
+                /* TRANSLATORS: notification title, an external touchpad is low or very low on power */
+                .title         = N_("Touchpad battery low"),
+
+                /* TRANSLATORS: notification body, an external touchpad is low on power */
+                .low_body      = N_("Touchpad is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Touchpad is low on power"),
+                /* TRANSLATORS: notification body, an external touchpad is very low on power */
+                .crit_body     = N_("Touchpad is very low on power (%.0f%%). "
+                                    "The device will soon shutdown if not charged."),
+                .crit_body_unk = N_("Touchpad is very low on power. "
+                                    "The device will soon shutdown if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_HEADSET,
+                /* TRANSLATORS: notification title, a headset (headphones + microphone) is low or very low on power */
+                .title         = N_("Headset battery low"),
+
+                /* TRANSLATORS: notification body, a headset (headphones + microphone) is low on power */
+                .low_body      = N_("Headset is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Headset is low on power"),
+                /* TRANSLATORS: notification body, a headset (headphones + microphone) is very low on power */
+                .crit_body     = N_("Headset is very low on power (%.0f%%). "
+                                    "The device will soon shutdown if not charged."),
+                .crit_body_unk = N_("Headset is very low on power. "
+                                    "The device will soon shutdown if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_SPEAKERS,
+                /* TRANSLATORS: notification title, speaker is low or very low on power */
+                .title         = N_("Speaker battery low"),
+
+                /* TRANSLATORS: notification body, a speaker is low on power */
+                .low_body      = N_("Speaker is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Speaker is low on power"),
+                /* TRANSLATORS: notification body, a speaker is very low on power */
+                .crit_body     = N_("Speaker is very low on power (%.0f%%). "
+                                    "The device will soon shutdown if not charged."),
+                .crit_body_unk = N_("Speaker is very low on power. "
+                                    "The device will soon shutdown if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_HEADPHONES,
+                /* TRANSLATORS: notification title, headphones (no microphone) are low or very low on power */
+                .title         = N_("Headphones battery low"),
+
+                /* TRANSLATORS: notification body, headphones (no microphone) are low on power */
+                .low_body      = N_("Headphones are low on power (%.0f%%)"),
+                .low_body_unk  = N_("Headphones are low on power"),
+                /* TRANSLATORS: notification body, headphones (no microphone) are very low on power */
+                .crit_body     = N_("Headphones are very low on power (%.0f%%). "
+                                    "The device will soon shutdown if not charged."),
+                .crit_body_unk = N_("Headphones are very low on power. "
+                                    "The device will soon shutdown if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_OTHER_AUDIO,
+                /* TRANSLATORS: notification title, an audio device is low or very low on power */
+                .title         = N_("Audio device battery low"),
+
+                /* TRANSLATORS: notification body, an audio device is low on power */
+                .low_body      = N_("Audio device is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Audio device is low on power"),
+                /* TRANSLATORS: notification body, an audio device is very low on power */
+                .crit_body     = N_("Audio device is very low on power (%.0f%%). "
+                                    "The device will soon shutdown if not charged."),
+                .crit_body_unk = N_("Audio device is very low on power. "
+                                    "The device will soon shutdown if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_REMOTE_CONTROL,
+                /* TRANSLATORS: notification title, a remote control is low or very low on power */
+                .title         = N_("Remote battery low"),
+
+                /* TRANSLATORS: notification body, an remote control is low on power */
+                .low_body      = N_("Remote is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Remote is low on power"),
+                /* TRANSLATORS: notification body, a remote control is very low on power */
+                .crit_body     = N_("Remote is very low on power (%.0f%%). "
+                                    "The device will soon shutdown if not charged."),
+                .crit_body_unk = N_("Remote is very low on power. "
+                                    "The device will soon shutdown if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_PRINTER,
+                /* TRANSLATORS: notification title, a printer is low or very low on power */
+                .title         = N_("Printer battery low"),
+
+                /* TRANSLATORS: notification body, a printer is low on power */
+                .low_body      = N_("Printer is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Printer is low on power"),
+                /* TRANSLATORS: notification body, a printer is very low on power */
+                .crit_body     = N_("Printer is very low on power (%.0f%%). "
+                                    "The device will soon shutdown if not charged."),
+                .crit_body_unk = N_("Printer is very low on power. "
+                                    "The device will soon shutdown if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_SCANNER,
+                /* TRANSLATORS: notification title, a scanner is low or very low on power */
+                .title         = N_("Scanner battery low"),
+
+                /* TRANSLATORS: notification body, a scanner is low on power */
+                .low_body      = N_("Scanner is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Scanner is low on power"),
+                /* TRANSLATORS: notification body, a scanner is very low on power */
+                .crit_body     = N_("Scanner is very low on power (%.0f%%). "
+                                    "The device will soon shutdown if not charged."),
+                .crit_body_unk = N_("Scanner is very low on power. "
+                                    "The device will soon shutdown if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_CAMERA,
+                /* TRANSLATORS: notification title, a camera is low or very low on power */
+                .title         = N_("Camera battery low"),
+
+                /* TRANSLATORS: notification body, a camera is low on power */
+                .low_body      = N_("Camera is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Camera is low on power"),
+                /* TRANSLATORS: notification body, a camera is very low on power */
+                .crit_body     = N_("Camera is very low on power (%.0f%%). "
+                                    "The device will soon shutdown if not charged."),
+                .crit_body_unk = N_("Camera is very low on power. "
+                                    "The device will soon shutdown if not charged."),
+        }, {
+                .kind = UP_DEVICE_KIND_BLUETOOTH_GENERIC,
+                /* TRANSLATORS: notification title, a Bluetooth device is low or very low on power */
+                .title         = N_("Bluetooth device battery low"),
+
+                /* TRANSLATORS: notification body, a Bluetooth device is low on power */
+                .low_body      = N_("Bluetooth device is low on power (%.0f%%)"),
+                .low_body_unk  = N_("Bluetooth device is low on power"),
+                /* TRANSLATORS: notification body, a Bluetooth device is very low on power */
+                .crit_body     = N_("Bluetooth device is very low on power (%.0f%%). "
+                                    "The device will soon shutdown if not charged."),
+                .crit_body_unk = N_("Bluetooth device is very low on power. "
+                                    "The device will soon shutdown if not charged."),
+        }, {
+                /* Last entry is the fallback (kind is actually unused)! */
+                .kind = UP_DEVICE_KIND_UNKNOWN,
+                /* TRANSLATORS: notification title, a connected (wireless) device or peripheral of unhandled type is low or very on power */
+                .title         = N_("Connected device battery is low"),
+
+                /* TRANSLATORS: notification body, a connected (wireless) device or peripheral of unhandled type is low on power */
+                .low_body      = N_("A connected device is low on power (%.0f%%)"),
+                .low_body_unk  = N_("A connected device is low on power"),
+                /* TRANSLATORS: notification body, a connected (wireless) device or peripheral of unhandled type is very low on power */
+                .crit_body     = N_("A connected device is very low on power (%.0f%%). "
+                                    "The device will soon shutdown if not charged."),
+                .crit_body_unk = N_("A connected device is very low on power. "
+                                    "The device will soon shutdown if not charged."),
+        }
+};
+
+static void
+engine_charge_low (GsdPowerManager *manager, UpDevice *device)
+{
+        const gchar *title = NULL;
+        gchar *message = NULL;
+        gchar *tmp;
+        gchar *remaining_text;
+        gdouble percentage;
+        guint battery_level;
+        char *icon_name;
+        gint64 time_to_empty;
+        UpDeviceKind kind;
+
+        /* get device properties */
+        g_object_get (device,
+                      "kind", &kind,
+                      "percentage", &percentage,
+                      "time-to-empty", &time_to_empty,
+                      "battery-level", &battery_level,
+                      "icon-name", &icon_name,
+                      NULL);
+
+        if (battery_level == UP_DEVICE_LEVEL_UNKNOWN)
+                battery_level = UP_DEVICE_LEVEL_NONE;
+
+        if (kind == UP_DEVICE_KIND_BATTERY) {
+                /* TRANSLATORS: notification title, the battery of this laptop/tablet/phone is running low, shows time remaining */
+                title = _("Battery low");
+                tmp = gpm_get_timestring (time_to_empty);
+                remaining_text = g_strconcat ("", tmp, "", NULL);
+                g_free (tmp);
+
+                /* TRANSLATORS: notification body, the battery of this laptop/tablet/phone is running low, shows time remaining */
+                message = g_strdup_printf (_("Approximately %s remaining (%.0f%%)"), remaining_text, percentage);
+                g_free (remaining_text);
+
+        } else if (kind == UP_DEVICE_KIND_UPS) {
+                /* TRANSLATORS: notification title, an Uninterruptible Power Supply (UPS) is running low, shows time remaining */
+                title = _("UPS low");
+                tmp = gpm_get_timestring (time_to_empty);
+                remaining_text = g_strconcat ("", tmp, "", NULL);
+                g_free (tmp);
+
+                /* TRANSLATORS: notification body, an Uninterruptible Power Supply (UPS) is running low, shows time remaining */
+                message = g_strdup_printf (_("Approximately %s of remaining UPS backup power (%.0f%%)"),
+                                           remaining_text, percentage);
+                g_free (remaining_text);
+        } else {
+                guint i;
+
+                for (i = 0; i < G_N_ELEMENTS (peripheral_battery_notifications); i++) {
+                        if (peripheral_battery_notifications[i].kind == kind)
+                                break;
+                }
+                /* Use the last element if nothing was found*/
+                i = MIN (i, G_N_ELEMENTS (peripheral_battery_notifications) - 1);
+
+                title = gettext (peripheral_battery_notifications[i].title);
+
+                if (battery_level == UP_DEVICE_LEVEL_NONE)
+                        message = g_strdup_printf (gettext (peripheral_battery_notifications[i].low_body), percentage);
+                else
+                        message = g_strdup (gettext (peripheral_battery_notifications[i].low_body_unk));
+        }
+
+        /* close any existing notification of this class */
+        notify_close_if_showing (&manager->notification_low);
+
+        /* create a new notification */
+        create_notification (title, message,
+                             icon_name, NOTIFICATION_PRIVACY_SYSTEM,
+                             &manager->notification_low);
+        notify_notification_set_timeout (manager->notification_low,
+                                         GSD_POWER_MANAGER_NOTIFY_TIMEOUT_LONG);
+        notify_notification_set_hint (manager->notification_low,
+                                      "transient", g_variant_new_boolean (TRUE));
+
+        notify_notification_show (manager->notification_low, NULL);
+
+        /* play the sound, using sounds from the naming spec */
+        ca_context_play (ca_gtk_context_get (), 0,
+                         CA_PROP_EVENT_ID, "battery-low",
+                         /* TRANSLATORS: this is the sound description */
+                         CA_PROP_EVENT_DESCRIPTION, _("Battery is low"), NULL);
+
+        g_free (icon_name);
+        g_free (message);
+}
+
+static void
+engine_charge_critical (GsdPowerManager *manager, UpDevice *device)
+{
+        const gchar *title = NULL;
+        gchar *message = NULL;
+        gdouble percentage;
+        guint battery_level;
+        char *icon_name;
+        gint64 time_to_empty;
+        GsdPowerActionType policy;
+        UpDeviceKind kind;
+
+        /* get device properties */
+        g_object_get (device,
+                      "kind", &kind,
+                      "percentage", &percentage,
+                      "battery-level", &battery_level,
+                      "time-to-empty", &time_to_empty,
+                      "icon-name", &icon_name,
+                      NULL);
+
+        if (battery_level == UP_DEVICE_LEVEL_UNKNOWN)
+                battery_level = UP_DEVICE_LEVEL_NONE;
+
+        if (kind == UP_DEVICE_KIND_BATTERY) {
+                /* TRANSLATORS: notification title, the battery of this laptop/tablet/phone is critically low, warning about action happening soon */
+                title = _("Battery critically low");
+
+                /* we have to do different warnings depending on the policy */
+                policy = manager_critical_action_get (manager);
+
+                if (policy == GSD_POWER_ACTION_HIBERNATE) {
+                        /* TRANSLATORS: notification body, the battery of this laptop/tablet/phone is critically low, warning about action happening soon */
+                        message = g_strdup_printf (_("Hibernating soon unless plugged in."));
+                } else if (policy == GSD_POWER_ACTION_SHUTDOWN) {
+                        message = g_strdup_printf (_("Shutting down soon unless plugged in."));
+                }
+
+        } else if (kind == UP_DEVICE_KIND_UPS) {
+                gchar *remaining_text;
+                gchar *tmp;
+
+                /* TRANSLATORS: notification title, an Uninterruptible Power Supply (UPS) is running low, warning about action happening soon */
+                title = _("UPS critically low");
+                tmp = gpm_get_timestring (time_to_empty);
+                remaining_text = g_strconcat ("", tmp, "", NULL);
+                g_free (tmp);
+
+                /* TRANSLATORS: notification body, an Uninterruptible Power Supply (UPS) is running low, warning about action happening soon */
+                message = g_strdup_printf (_("Approximately %s of remaining UPS power (%.0f%%). "
+                                             "Restore AC power to your computer to avoid losing data."),
+                                           remaining_text, percentage);
+                g_free (remaining_text);
+        } else {
+                guint i;
+
+                for (i = 0; i < G_N_ELEMENTS (peripheral_battery_notifications); i++) {
+                        if (peripheral_battery_notifications[i].kind == kind)
+                                break;
+                }
+                /* Use the last element if nothing was found*/
+                i = MIN (i, G_N_ELEMENTS (peripheral_battery_notifications) - 1);
+
+                title = gettext (peripheral_battery_notifications[i].title);
+
+                if (battery_level == UP_DEVICE_LEVEL_NONE)
+                        message = g_strdup_printf (gettext (peripheral_battery_notifications[i].crit_body), percentage);
+                else
+                        message = g_strdup (gettext (peripheral_battery_notifications[i].crit_body_unk));
+        }
+
+        /* close any existing notification of this class */
+        notify_close_if_showing (&manager->notification_low);
+
+        /* create a new notification */
+        create_notification (title, message,
+                             icon_name, NOTIFICATION_PRIVACY_SYSTEM,
+                             &manager->notification_low);
+        notify_notification_set_timeout (manager->notification_low,
+                                         NOTIFY_EXPIRES_NEVER);
+
+        notify_notification_show (manager->notification_low, NULL);
+
+        switch (kind) {
+
+        case UP_DEVICE_KIND_BATTERY:
+        case UP_DEVICE_KIND_UPS:
+                g_debug ("critical charge level reached, starting sound loop");
+                play_loop_start (&manager->critical_alert_timeout_id);
+                break;
+
+        default:
+                /* play the sound, using sounds from the naming spec */
+                ca_context_play (ca_gtk_context_get (), 0,
+                                 CA_PROP_EVENT_ID, "battery-caution",
+                                 /* TRANSLATORS: this is the sound description */
+                                 CA_PROP_EVENT_DESCRIPTION, _("Battery is critically low"), NULL);
+                break;
+        }
+
+        g_free (icon_name);
+        g_free (message);
+}
+
+static void
+engine_charge_action (GsdPowerManager *manager, UpDevice *device)
+{
+        const gchar *title = NULL;
+        gchar *message = NULL;
+        char *icon_name;
+        GsdPowerActionType policy;
+        guint timer_id;
+        UpDeviceKind kind;
+
+        /* get device properties */
+        g_object_get (device,
+                      "kind", &kind,
+                      "icon-name", &icon_name,
+                      NULL);
+
+        if (kind == UP_DEVICE_KIND_BATTERY) {
+                /* TRANSLATORS: notification title, the battery of this laptop/tablet/phone is critically low, warning about action happening now */
+                title = _("Battery critically low");
+
+                /* we have to do different warnings depending on the policy */
+                policy = manager_critical_action_get (manager);
+
+                if (policy == GSD_POWER_ACTION_HIBERNATE) {
+                        /* TRANSLATORS: notification body, the battery of this laptop/tablet/phone is critically low, warning about action happening now */
+                        message = g_strdup (_("The battery is below the critical level and "
+                                              "this computer is about to hibernate."));
+
+                } else if (policy == GSD_POWER_ACTION_SHUTDOWN) {
+                        message = g_strdup (_("The battery is below the critical level and "
+                                              "this computer is about to shutdown."));
+                }
+
+                /* wait 20 seconds for user-panic */
+                timer_id = g_timeout_add_seconds (GSD_STOP_SOUND_DELAY,
+                                                  (GSourceFunc) manager_critical_action_stop_sound_cb,
+                                                  manager);
+                g_source_set_name_by_id (timer_id, "[GsdPowerManager] battery critical-action");
+
+        } else if (kind == UP_DEVICE_KIND_UPS) {
+                /* TRANSLATORS: notification title, an Uninterruptible Power Supply (UPS) is running low, warning about action happening now */
+                title = _("UPS critically low");
+
+                /* we have to do different warnings depending on the policy */
+                policy = manager_critical_action_get (manager);
+
+                if (policy == GSD_POWER_ACTION_HIBERNATE) {
+                        /* TRANSLATORS: notification body, an Uninterruptible Power Supply (UPS) is running low, warning about action happening now */
+                        message = g_strdup (_("UPS is below the critical level and "
+                                              "this computer is about to hibernate."));
+
+                } else if (policy == GSD_POWER_ACTION_SHUTDOWN) {
+                        message = g_strdup (_("UPS is below the critical level and "
+                                              "this computer is about to shutdown."));
+                }
+
+                /* wait 20 seconds for user-panic */
+                timer_id = g_timeout_add_seconds (GSD_STOP_SOUND_DELAY,
+                                                  (GSourceFunc) manager_critical_action_stop_sound_cb,
+                                                  manager);
+                g_source_set_name_by_id (timer_id, "[GsdPowerManager] ups critical-action");
+        }
+
+        /* not all types have actions */
+        if (title == NULL)
+                return;
+
+        /* close any existing notification of this class */
+        notify_close_if_showing (&manager->notification_low);
+
+        /* create a new notification */
+        create_notification (title, message,
+                             icon_name, NOTIFICATION_PRIVACY_SYSTEM,
+                             &manager->notification_low);
+        notify_notification_set_timeout (manager->notification_low,
+                                         NOTIFY_EXPIRES_NEVER);
+
+        /* try to show */
+        notify_notification_show (manager->notification_low, NULL);
+
+        /* play the sound, using sounds from the naming spec */
+        ca_context_play (ca_gtk_context_get (), 0,
+                         CA_PROP_EVENT_ID, "battery-caution",
+                         /* TRANSLATORS: this is the sound description */
+                         CA_PROP_EVENT_DESCRIPTION, _("Battery is critically low"), NULL);
+
+        g_free (icon_name);
+        g_free (message);
+}
+
+static void
+engine_device_warning_changed_cb (UpDevice *device, GParamSpec *pspec, GsdPowerManager *manager)
+{
+        g_autofree char *serial = NULL;
+        UpDeviceLevel warning;
+        UpDeviceKind kind;
+
+        g_object_get (device,
+                      "serial", &serial,
+                      "warning-level", &warning,
+                      "kind", &kind,
+                      NULL);
+
+        if (!engine_device_debounce_warn (manager, kind, warning, serial))
+                return;
+
+        if (warning == UP_DEVICE_LEVEL_DISCHARGING) {
+                g_debug ("** EMIT: discharging");
+                engine_ups_discharging (manager, device);
+        } else if (warning == UP_DEVICE_LEVEL_LOW) {
+                g_debug ("** EMIT: charge-low");
+                engine_charge_low (manager, device);
+        } else if (warning == UP_DEVICE_LEVEL_CRITICAL) {
+                g_debug ("** EMIT: charge-critical");
+                engine_charge_critical (manager, device);
+        } else if (warning == UP_DEVICE_LEVEL_ACTION) {
+                g_debug ("** EMIT: charge-action");
+                engine_charge_action (manager, device);
+        } else if (warning == UP_DEVICE_LEVEL_NONE) {
+                /* FIXME: this only handles one notification
+                 * for the whole system, instead of one per device */
+                g_debug ("fully charged or charging, hiding notifications if any");
+                play_loop_stop (&manager->critical_alert_timeout_id);
+                if (kind != UP_DEVICE_KIND_UPS)
+                        notify_close_if_showing (&manager->notification_low);
+                else
+                        notify_close_if_showing (&manager->notification_ups_discharging);
+        }
+
+        if (kind == UP_DEVICE_KIND_BATTERY ||
+            kind == UP_DEVICE_KIND_UPS)
+                main_battery_or_ups_low_changed (manager, (warning != UP_DEVICE_LEVEL_NONE));
+}
+
+static void
+gnome_session_shutdown_cb (GObject *source_object,
+                           GAsyncResult *res,
+                           gpointer user_data)
+{
+        GVariant *result;
+        GError *error = NULL;
+
+        result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+                                           res,
+                                           &error);
+        if (result == NULL) {
+                g_warning ("couldn't shutdown using gnome-session: %s",
+                           error->message);
+                g_error_free (error);
+        } else {
+                g_variant_unref (result);
+        }
+}
+
+static void
+gnome_session_shutdown (GsdPowerManager *manager)
+{
+        g_dbus_proxy_call (G_DBUS_PROXY (manager->session),
+                           "Shutdown",
+                           NULL,
+                           G_DBUS_CALL_FLAGS_NONE,
+                           -1, NULL,
+                           gnome_session_shutdown_cb, NULL);
+}
+
+static void
+gnome_session_logout_cb (GObject *source_object,
+                         GAsyncResult *res,
+                         gpointer user_data)
+{
+        GVariant *result;
+        GError *error = NULL;
+
+        result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+                                           res,
+                                           &error);
+        if (result == NULL) {
+                g_warning ("couldn't log out using gnome-session: %s",
+                           error->message);
+                g_error_free (error);
+        } else {
+                g_variant_unref (result);
+        }
+}
+
+static void
+gnome_session_logout (GsdPowerManager *manager,
+                      guint            logout_mode)
+{
+        if (g_getenv ("RUNNING_UNDER_GDM")) {
+                g_warning ("Prevented logout from GDM session! This indicates an issue in gsd-power.");
+                return;
+        }
+
+        g_dbus_proxy_call (G_DBUS_PROXY (manager->session),
+                           "Logout",
+                           g_variant_new ("(u)", logout_mode),
+                           G_DBUS_CALL_FLAGS_NONE,
+                           -1, NULL,
+                           gnome_session_logout_cb, NULL);
+}
+
+static void
+dbus_call_log_error (GObject *source_object,
+                     GAsyncResult *res,
+                     gpointer user_data)
+{
+        g_autoptr(GVariant) result = NULL;
+        g_autoptr(GError) error = NULL;
+        const gchar *msg = user_data;
+
+        result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+                                           res,
+                                           &error);
+        if (result == NULL)
+                g_warning ("%s: %s", msg, error->message);
+}
+
+static void
+action_poweroff (GsdPowerManager *manager)
+{
+        if (manager->logind_proxy == NULL) {
+                g_warning ("no systemd support");
+                return;
+        }
+        g_dbus_proxy_call (manager->logind_proxy,
+                           "PowerOff",
+                           g_variant_new ("(b)", FALSE),
+                           G_DBUS_CALL_FLAGS_NONE,
+                           G_MAXINT,
+                           NULL,
+                           dbus_call_log_error,
+                           "Error calling PowerOff");
+}
+
+static void
+action_suspend (GsdPowerManager *manager)
+{
+        if (manager->logind_proxy == NULL) {
+                g_warning ("no systemd support");
+                return;
+        }
+        g_dbus_proxy_call (manager->logind_proxy,
+                           "Suspend",
+                           g_variant_new ("(b)", FALSE),
+                           G_DBUS_CALL_FLAGS_NONE,
+                           G_MAXINT,
+                           NULL,
+                           dbus_call_log_error,
+                           "Error calling suspend action");
+}
+
+static void
+action_hibernate (GsdPowerManager *manager)
+{
+        if (manager->logind_proxy == NULL) {
+                g_warning ("no systemd support");
+                return;
+        }
+        g_dbus_proxy_call (manager->logind_proxy,
+                           "Hibernate",
+                           g_variant_new ("(b)", FALSE),
+                           G_DBUS_CALL_FLAGS_NONE,
+                           G_MAXINT,
+                           NULL,
+                           dbus_call_log_error,
+                           "Error calling Hibernate");
+}
+
+static void
+iio_proxy_claim_light (GsdPowerManager *manager, gboolean active)
+{
+        GError *error = NULL;
+        if (manager->iio_proxy == NULL)
+                return;
+        if (!manager->backlight)
+                return;
+	if (active && !manager->session_is_active)
+		return;
+
+        /* FIXME:
+         * Remove when iio-sensor-proxy sends events only to clients instead
+         * of all listeners:
+         * https://github.com/hadess/iio-sensor-proxy/issues/210 */
+
+        /* disconnect, otherwise callback can be added multiple times */
+        g_signal_handlers_disconnect_by_func (manager->iio_proxy,
+                                              G_CALLBACK (iio_proxy_changed_cb),
+                                              manager);
+
+        if (active)
+                g_signal_connect (manager->iio_proxy, "g-properties-changed",
+                                  G_CALLBACK (iio_proxy_changed_cb), manager);
+
+        if (!g_dbus_proxy_call_sync (manager->iio_proxy,
+                                     active ? "ClaimLight" : "ReleaseLight",
+                                     NULL,
+                                     G_DBUS_CALL_FLAGS_NONE,
+                                     -1,
+                                     NULL,
+                                     &error)) {
+                g_warning ("Call to iio-proxy failed: %s", error->message);
+                g_error_free (error);
+        }
+
+        if (active)
+                iio_proxy_changed (manager);
+}
+
+static void
+backlight_enable (GsdPowerManager *manager)
+{
+        gboolean ret;
+        GError *error = NULL;
+
+        iio_proxy_claim_light (manager, TRUE);
+        ret = gnome_rr_screen_set_dpms_mode (manager->rr_screen,
+                                             GNOME_RR_DPMS_ON,
+                                             &error);
+        if (!ret) {
+                g_warning ("failed to turn the panel on: %s",
+                           error->message);
+                g_error_free (error);
+        }
+
+        g_debug ("TESTSUITE: Unblanked screen");
+}
+
+static void
+backlight_disable (GsdPowerManager *manager)
+{
+        gboolean ret;
+        GError *error = NULL;
+
+        iio_proxy_claim_light (manager, FALSE);
+        ret = gnome_rr_screen_set_dpms_mode (manager->rr_screen,
+                                             GNOME_RR_DPMS_OFF,
+                                             &error);
+        if (!ret) {
+                g_warning ("failed to turn the panel off: %s",
+                           error->message);
+                g_error_free (error);
+        }
+
+        g_debug ("TESTSUITE: Blanked screen");
+}
+
+static void
+do_power_action_type (GsdPowerManager *manager,
+                      GsdPowerActionType action_type)
+{
+        switch (action_type) {
+        case GSD_POWER_ACTION_SUSPEND:
+                action_suspend (manager);
+                break;
+        case GSD_POWER_ACTION_INTERACTIVE:
+                gnome_session_shutdown (manager);
+                break;
+        case GSD_POWER_ACTION_HIBERNATE:
+                action_hibernate (manager);
+                break;
+        case GSD_POWER_ACTION_SHUTDOWN:
+                /* this is only used on critically low battery where
+                 * hibernate is not available and is marginally better
+                 * than just powering down the computer mid-write */
+                action_poweroff (manager);
+                break;
+        case GSD_POWER_ACTION_BLANK:
+                backlight_disable (manager);
+                break;
+        case GSD_POWER_ACTION_NOTHING:
+                break;
+        case GSD_POWER_ACTION_LOGOUT:
+                gnome_session_logout (manager, GSM_MANAGER_LOGOUT_MODE_FORCE);
+                break;
+        }
+}
+
+static GsmInhibitorFlag
+get_idle_inhibitors_for_action (GsdPowerActionType action_type)
+{
+        switch (action_type) {
+        case GSD_POWER_ACTION_BLANK:
+        case GSD_POWER_ACTION_SHUTDOWN:
+        case GSD_POWER_ACTION_INTERACTIVE:
+                return GSM_INHIBITOR_FLAG_IDLE;
+        case GSD_POWER_ACTION_HIBERNATE:
+        case GSD_POWER_ACTION_SUSPEND:
+                return GSM_INHIBITOR_FLAG_SUSPEND; /* in addition to idle */
+        case GSD_POWER_ACTION_NOTHING:
+                return 0;
+        case GSD_POWER_ACTION_LOGOUT:
+                return GSM_INHIBITOR_FLAG_LOGOUT; /* in addition to idle */
+        }
+        return 0;
+}
+
+static gboolean
+is_action_inhibited (GsdPowerManager *manager, GsdPowerActionType action_type)
+{
+        GsmInhibitorFlag flag;
+        gboolean is_inhibited;
+
+        flag = get_idle_inhibitors_for_action (action_type);
+        if (!flag)
+                return FALSE;
+        idle_is_session_inhibited (manager,
+                                   flag,
+                                   &is_inhibited);
+        return is_inhibited;
+}
+
+static gboolean
+upower_kbd_set_brightness (GsdPowerManager *manager, guint value, GError **error)
+{
+        GVariant *retval;
+
+        /* same as before */
+        if (manager->kbd_brightness_now == value)
+                return TRUE;
+        if (manager->upower_kbd_proxy == NULL)
+                return TRUE;
+
+        /* update h/w value */
+        retval = g_dbus_proxy_call_sync (manager->upower_kbd_proxy,
+                                         "SetBrightness",
+                                         g_variant_new ("(i)", (gint) value),
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1,
+                                         manager->cancellable,
+                                         error);
+        if (retval == NULL)
+                return FALSE;
+
+        /* save new value */
+        manager->kbd_brightness_now = value;
+        g_variant_unref (retval);
+        return TRUE;
+}
+
+static int
+upower_kbd_toggle (GsdPowerManager *manager,
+                   GError **error)
+{
+        gboolean ret;
+        int value = -1;
+
+        if (manager->kbd_brightness_old >= 0) {
+                g_debug ("keyboard toggle off");
+                ret = upower_kbd_set_brightness (manager,
+                                                 manager->kbd_brightness_old,
+                                                 error);
+                if (ret) {
+                        /* succeeded, set to -1 since now no old value */
+                        manager->kbd_brightness_old = -1;
+                        value = 0;
+                }
+        } else {
+                g_debug ("keyboard toggle on");
+                /* save the current value to restore later when untoggling */
+                manager->kbd_brightness_old = manager->kbd_brightness_now;
+                ret = upower_kbd_set_brightness (manager, 0, error);
+                if (!ret) {
+                        /* failed, reset back to -1 */
+                        manager->kbd_brightness_old = -1;
+                } else {
+                        value = 0;
+                }
+        }
+
+        if (ret)
+                return value;
+        return -1;
+}
+
+static gboolean
+suspend_on_lid_close (GsdPowerManager *manager)
+{
+        return !external_monitor_is_connected (manager->rr_screen) || !manager->session_is_active;
+}
+
+static gboolean
+inhibit_lid_switch_timer_cb (GsdPowerManager *manager)
+{
+        stop_inhibit_lid_switch_timer (manager);
+
+        if (suspend_on_lid_close (manager)) {
+                g_debug ("no external monitors or session inactive for a while; uninhibiting lid close");
+                uninhibit_lid_switch (manager);
+        }
+
+        /* This is a one shot timer. */
+        return G_SOURCE_REMOVE;
+}
+
+/* Sets up a timer to be triggered some seconds after closing the laptop lid
+ * when the laptop is *not* suspended for some reason.  We'll check conditions
+ * again in the timeout handler to see if we can suspend then.
+ */
+static void
+setup_inhibit_lid_switch_timer (GsdPowerManager *manager)
+{
+        if (manager->inhibit_lid_switch_timer_id != 0) {
+                g_debug ("lid close safety timer already set up");
+                return;
+        }
+
+        g_debug ("setting up lid close safety timer");
+
+        manager->inhibit_lid_switch_timer_id = g_timeout_add_seconds (LID_CLOSE_SAFETY_TIMEOUT,
+                                                                            (GSourceFunc) inhibit_lid_switch_timer_cb,
+                                                                            manager);
+        g_source_set_name_by_id (manager->inhibit_lid_switch_timer_id, "[GsdPowerManager] lid close safety timer");
+}
+
+static void
+stop_inhibit_lid_switch_timer (GsdPowerManager *manager) {
+        if (manager->inhibit_lid_switch_timer_id != 0) {
+                g_debug ("stopping lid close safety timer");
+                g_source_remove (manager->inhibit_lid_switch_timer_id);
+                manager->inhibit_lid_switch_timer_id = 0;
+        }
+}
+
+static void
+restart_inhibit_lid_switch_timer (GsdPowerManager *manager)
+{
+        stop_inhibit_lid_switch_timer (manager);
+        g_debug ("restarting lid close safety timer");
+        setup_inhibit_lid_switch_timer (manager);
+}
+
+static void
+do_lid_open_action (GsdPowerManager *manager)
+{
+        /* play a sound, using sounds from the naming spec */
+        ca_context_play (ca_gtk_context_get (), 0,
+                         CA_PROP_EVENT_ID, "lid-open",
+                         /* TRANSLATORS: this is the sound description */
+                         CA_PROP_EVENT_DESCRIPTION, _("Lid has been opened"),
+                         NULL);
+}
+
+static void
+lock_screensaver (GsdPowerManager *manager)
+{
+        gboolean do_lock;
+
+        do_lock = g_settings_get_boolean (manager->settings_screensaver,
+                                          "lock-enabled");
+        if (!do_lock) {
+                g_dbus_proxy_call_sync (G_DBUS_PROXY (manager->screensaver_proxy),
+                                        "SetActive",
+                                        g_variant_new ("(b)", TRUE),
+                                        G_DBUS_CALL_FLAGS_NONE,
+                                        -1, NULL, NULL);
+                return;
+        }
+
+        g_dbus_proxy_call_sync (G_DBUS_PROXY (manager->screensaver_proxy),
+                                "Lock",
+                                NULL,
+                                G_DBUS_CALL_FLAGS_NONE,
+                                -1, NULL, NULL);
+}
+
+static void
+do_lid_closed_action (GsdPowerManager *manager)
+{
+        /* play a sound, using sounds from the naming spec */
+        ca_context_play (ca_gtk_context_get (), 0,
+                         CA_PROP_EVENT_ID, "lid-close",
+                         /* TRANSLATORS: this is the sound description */
+                         CA_PROP_EVENT_DESCRIPTION, _("Lid has been closed"),
+                         NULL);
+
+        /* refresh RANDR so we get an accurate view of what monitors are plugged in when the lid is closed */
+        gnome_rr_screen_refresh (manager->rr_screen, NULL); /* NULL-GError */
+
+        if (suspend_on_lid_close (manager)) {
+                gboolean is_inhibited;
+
+                idle_is_session_inhibited (manager,
+                                           GSM_INHIBITOR_FLAG_SUSPEND,
+                                           &is_inhibited);
+                if (is_inhibited) {
+                        g_debug ("Suspend is inhibited but lid is closed, locking the screen");
+                        /* We put the screensaver on * as we're not suspending,
+                         * but the lid is closed */
+                        lock_screensaver (manager);
+                }
+        }
+}
+
+static void
+lid_state_changed_cb (UpClient *client, GParamSpec *pspec, GsdPowerManager *manager)
+{
+        gboolean tmp;
+
+        if (!manager->lid_is_present)
+                return;
+
+        /* same lid state */
+        tmp = up_client_get_lid_is_closed (manager->up_client);
+        if (manager->lid_is_closed == tmp)
+                return;
+        manager->lid_is_closed = tmp;
+        g_debug ("up changed: lid is now %s", tmp ? "closed" : "open");
+
+        if (manager->lid_is_closed)
+                do_lid_closed_action (manager);
+        else
+                do_lid_open_action (manager);
+}
+
+static const gchar *
+idle_mode_to_string (GsdPowerIdleMode mode)
+{
+        if (mode == GSD_POWER_IDLE_MODE_NORMAL)
+                return "normal";
+        if (mode == GSD_POWER_IDLE_MODE_DIM)
+                return "dim";
+        if (mode == GSD_POWER_IDLE_MODE_BLANK)
+                return "blank";
+        if (mode == GSD_POWER_IDLE_MODE_SLEEP)
+                return "sleep";
+        return "unknown";
+}
+
+static const char *
+idle_watch_id_to_string (GsdPowerManager *manager, guint id)
+{
+        if (id == manager->idle_dim_id)
+                return "dim";
+        if (id == manager->idle_blank_id)
+                return "blank";
+        if (id == manager->idle_sleep_id)
+                return "sleep";
+        if (id == manager->idle_sleep_warning_id)
+                return "sleep-warning";
+        return NULL;
+}
+
+static void
+backlight_iface_emit_changed (GsdPowerManager *manager,
+                              const char      *interface_name,
+                              gint32           value,
+                              const char      *source)
+{
+        GVariant *params;
+
+        /* not yet connected to the bus */
+        if (manager->connection == NULL)
+                return;
+
+        params = g_variant_new_parsed ("(%s, [{'Brightness', <%i>}], @as [])", interface_name,
+                                       value);
+        g_dbus_connection_emit_signal (manager->connection,
+                                       NULL,
+                                       GSD_POWER_DBUS_PATH,
+                                       "org.freedesktop.DBus.Properties",
+                                       "PropertiesChanged",
+                                       params, NULL);
+
+        if (!source)
+                return;
+
+        g_dbus_connection_emit_signal (manager->connection,
+                                       NULL,
+                                       GSD_POWER_DBUS_PATH,
+                                       GSD_POWER_DBUS_INTERFACE_KEYBOARD,
+                                       "BrightnessChanged",
+                                       g_variant_new ("(is)", value, source),
+                                       NULL);
+}
+
+static void
+backlight_notify_brightness_cb (GsdPowerManager *manager, GParamSpec *pspec, GsdBacklight *backlight)
+{
+        backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_SCREEN,
+                                      gsd_backlight_get_brightness (backlight, NULL), NULL);
+}
+
+static void
+display_backlight_dim (GsdPowerManager *manager,
+                       gint idle_percentage)
+{
+        gint brightness;
+
+        if (!manager->backlight)
+                return;
+
+        /* Fetch the current target brightness (not the actual display brightness)
+         * and return if it is already lower than the idle percentage. */
+        gsd_backlight_get_brightness (manager->backlight, &brightness);
+        if (brightness < idle_percentage)
+                return;
+
+        manager->pre_dim_brightness = brightness;
+        gsd_backlight_set_brightness_async (manager->backlight, idle_percentage, NULL, NULL, NULL);
+}
+
+static gboolean
+kbd_backlight_dim (GsdPowerManager *manager,
+                   gint idle_percentage,
+                   GError **error)
+{
+        gboolean ret;
+        gint idle;
+        gint max;
+        gint now;
+
+        if (manager->upower_kbd_proxy == NULL)
+                return TRUE;
+
+        now = manager->kbd_brightness_now;
+        max = manager->kbd_brightness_max;
+        idle = PERCENTAGE_TO_ABS (0, max, idle_percentage);
+        if (idle > now) {
+                g_debug ("kbd brightness already now %i/%i, so "
+                         "ignoring dim to %i/%i",
+                         now, max, idle, max);
+                return TRUE;
+        }
+        ret = upower_kbd_set_brightness (manager, idle, error);
+        if (!ret)
+                return FALSE;
+
+        /* save for undim */
+        manager->kbd_brightness_pre_dim = now;
+        return TRUE;
+}
+
+static void
+upower_kbd_proxy_signal_cb (GDBusProxy  *proxy,
+                            const gchar *sender_name,
+                            const gchar *signal_name,
+                            GVariant    *parameters,
+                            gpointer     user_data)
+{
+        GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+        gint brightness, percentage;
+        const gchar *source;
+
+        if (g_strcmp0 (signal_name, "BrightnessChangedWithSource") != 0)
+                return;
+
+        g_variant_get (parameters, "(i&s)", &brightness, &source);
+
+        /* Ignore changes caused by us calling UPower's SetBrightness method,
+         * we already call backlight_iface_emit_changed for these after the
+         * SetBrightness method call completes. */
+        if (g_strcmp0 (source, "external") == 0)
+                return;
+
+        manager->kbd_brightness_now = brightness;
+        percentage = ABS_TO_PERCENTAGE (0,
+                                        manager->kbd_brightness_max,
+                                        manager->kbd_brightness_now);
+        backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_KEYBOARD, percentage, source);
+}
+
+static gboolean
+is_session_active (GsdPowerManager *manager)
+{
+        GVariant *variant;
+        gboolean is_session_active = FALSE;
+
+        variant = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (manager->session),
+                                                    "SessionIsActive");
+        if (variant) {
+                is_session_active = g_variant_get_boolean (variant);
+                g_variant_unref (variant);
+        }
+
+        return is_session_active;
+}
+
+static void
+idle_set_mode (GsdPowerManager *manager, GsdPowerIdleMode mode)
+{
+        gboolean ret = FALSE;
+        GError *error = NULL;
+        gint idle_percentage;
+        GsdPowerActionType action_type;
+
+        /* Ignore attempts to set "less idle" modes */
+        if (mode <= manager->current_idle_mode &&
+            mode != GSD_POWER_IDLE_MODE_NORMAL) {
+                g_debug ("Not going to 'less idle' mode %s (current: %s)",
+                         idle_mode_to_string (mode),
+                         idle_mode_to_string (manager->current_idle_mode));
+                return;
+        }
+
+        /* ensure we're still on an active console */
+        if (!manager->session_is_active) {
+                g_debug ("ignoring state transition to %s as inactive",
+                         idle_mode_to_string (mode));
+                return;
+        }
+
+        manager->current_idle_mode = mode;
+        g_debug ("Doing a state transition: %s", idle_mode_to_string (mode));
+
+        /* if we're moving to an idle mode, make sure
+         * we add a watch to take us back to normal */
+        if (mode != GSD_POWER_IDLE_MODE_NORMAL) {
+                if (manager->user_active_id < 1) {
+                    manager->user_active_id = gnome_idle_monitor_add_user_active_watch (manager->idle_monitor,
+                                                                                        idle_became_active_cb,
+                                                                                        manager,
+                                                                                        NULL);
+                    g_debug ("installing idle_became_active_cb to clear sleep warning when transitioning away from normal (%i)",
+                             manager->user_active_id);
+                }
+        }
+
+        /* save current brightness, and set dim level */
+        if (mode == GSD_POWER_IDLE_MODE_DIM) {
+                /* display backlight */
+                idle_percentage = g_settings_get_int (manager->settings,
+                                                      "idle-brightness");
+                display_backlight_dim (manager, idle_percentage);
+
+                /* keyboard backlight */
+                ret = kbd_backlight_dim (manager, idle_percentage, &error);
+                if (!ret) {
+                        g_warning ("failed to set dim kbd backlight to %i%%: %s",
+                                   idle_percentage,
+                                   error->message);
+                        g_clear_error (&error);
+                }
+
+        /* turn off screen and kbd */
+        } else if (mode == GSD_POWER_IDLE_MODE_BLANK) {
+
+                backlight_disable (manager);
+
+                /* only toggle keyboard if present and not already toggled */
+                if (manager->upower_kbd_proxy &&
+                    manager->kbd_brightness_old == -1) {
+                        if (upower_kbd_toggle (manager, &error) < 0) {
+                                g_warning ("failed to turn the kbd backlight off: %s",
+                                           error->message);
+                                g_error_free (error);
+                        }
+                }
+
+        /* sleep */
+        } else if (mode == GSD_POWER_IDLE_MODE_SLEEP) {
+
+                if (up_client_get_on_battery (manager->up_client)) {
+                        action_type = g_settings_get_enum (manager->settings,
+                                                           "sleep-inactive-battery-type");
+                } else {
+                        action_type = g_settings_get_enum (manager->settings,
+                                                           "sleep-inactive-ac-type");
+                }
+                do_power_action_type (manager, action_type);
+
+        /* turn on screen and restore user-selected brightness level */
+        } else if (mode == GSD_POWER_IDLE_MODE_NORMAL) {
+
+                backlight_enable (manager);
+
+                /* reset brightness if we dimmed */
+                if (manager->backlight && manager->pre_dim_brightness >= 0) {
+                        gsd_backlight_set_brightness_async (manager->backlight,
+                                                            manager->pre_dim_brightness,
+                                                            NULL, NULL, NULL);
+                        /* XXX: Ideally we would do this from the async callback. */
+                        manager->pre_dim_brightness = -1;
+                }
+
+                /* only toggle keyboard if present and already toggled off */
+                if (manager->upower_kbd_proxy &&
+                    manager->kbd_brightness_old != -1) {
+                        if (upower_kbd_toggle (manager, &error) < 0) {
+                                g_warning ("failed to turn the kbd backlight on: %s",
+                                           error->message);
+                                g_clear_error (&error);
+                        }
+                }
+
+                /* reset kbd brightness if we dimmed */
+                if (manager->kbd_brightness_pre_dim >= 0) {
+                        ret = upower_kbd_set_brightness (manager,
+                                                         manager->kbd_brightness_pre_dim,
+                                                         &error);
+                        if (!ret) {
+                                g_warning ("failed to restore kbd backlight to %i: %s",
+                                           manager->kbd_brightness_pre_dim,
+                                           error->message);
+                                g_error_free (error);
+                        }
+                        manager->kbd_brightness_pre_dim = -1;
+                }
+
+        }
+}
+
+static gboolean
+idle_is_session_inhibited (GsdPowerManager  *manager,
+                           GsmInhibitorFlag  mask,
+                           gboolean         *is_inhibited)
+{
+        GVariant *variant;
+        GsmInhibitorFlag inhibited_actions;
+
+        /* not yet connected to gnome-session */
+        if (manager->session == NULL)
+                return FALSE;
+
+        variant = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (manager->session),
+                                                    "InhibitedActions");
+        if (!variant)
+                return FALSE;
+
+        inhibited_actions = g_variant_get_uint32 (variant);
+        g_variant_unref (variant);
+
+        *is_inhibited = (inhibited_actions & mask);
+
+        return TRUE;
+}
+
+static void
+clear_idle_watch (GnomeIdleMonitor *monitor,
+                  guint            *id)
+{
+        if (*id == 0)
+                return;
+        gnome_idle_monitor_remove_watch (monitor, *id);
+        *id = 0;
+}
+
+static gboolean
+is_power_save_active (GsdPowerManager *manager)
+{
+        /*
+         * If we have power-profiles-daemon, then we follow its setting,
+         * otherwise we go into power-save mode when the battery is low.
+         */
+        if (manager->power_profiles_proxy &&
+            g_dbus_proxy_get_name_owner (manager->power_profiles_proxy))
+                return manager->power_saver_enabled;
+        else
+                return manager->battery_is_low;
+}
+
+static void
+idle_configure (GsdPowerManager *manager)
+{
+        gboolean is_idle_inhibited;
+        GsdPowerActionType action_type;
+        guint timeout_sleep;
+        guint timeout_dim;
+        gboolean on_battery;
+
+        if (!idle_is_session_inhibited (manager,
+                                        GSM_INHIBITOR_FLAG_IDLE,
+                                        &is_idle_inhibited)) {
+                /* Session isn't available yet, postpone */
+                return;
+        }
+
+        /* set up blank callback only when the screensaver is on,
+         * as it's what will drive the blank */
+        clear_idle_watch (manager->idle_monitor,
+                          &manager->idle_blank_id);
+        if (manager->screensaver_active) {
+                /* The tail is wagging the dog.
+                 * The screensaver coming on will blank the screen.
+                 * If an event occurs while the screensaver is on,
+                 * the aggressive idle watch will handle it */
+                guint timeout_blank = SCREENSAVER_TIMEOUT_BLANK;
+                g_debug ("setting up blank callback for %is", timeout_blank);
+                manager->idle_blank_id = gnome_idle_monitor_add_idle_watch (manager->idle_monitor,
+                                                                                  timeout_blank * 1000,
+                                                                                  idle_triggered_idle_cb, manager, NULL);
+        }
+
+        /* are we inhibited from going idle */
+        if (!manager->session_is_active ||
+            (is_idle_inhibited && !manager->screensaver_active)) {
+                if (is_idle_inhibited && !manager->screensaver_active)
+                        g_debug ("inhibited and screensaver not active, so using normal state");
+                else
+                        g_debug ("inactive, so using normal state");
+                idle_set_mode (manager, GSD_POWER_IDLE_MODE_NORMAL);
+
+                clear_idle_watch (manager->idle_monitor,
+                                  &manager->idle_sleep_id);
+                clear_idle_watch (manager->idle_monitor,
+                                  &manager->idle_dim_id);
+                clear_idle_watch (manager->idle_monitor,
+                                  &manager->idle_sleep_warning_id);
+                notify_close_if_showing (&manager->notification_sleep_warning);
+                return;
+        }
+
+        /* only do the sleep timeout when the session is idle
+         * and we aren't inhibited from sleeping (or logging out, etc.) */
+        on_battery = up_client_get_on_battery (manager->up_client);
+        action_type = g_settings_get_enum (manager->settings, on_battery ?
+                                           "sleep-inactive-battery-type" : "sleep-inactive-ac-type");
+        timeout_sleep = 0;
+        if (!is_action_inhibited (manager, action_type)) {
+                gint timeout_sleep_;
+                timeout_sleep_ = g_settings_get_int (manager->settings, on_battery ?
+                                                     "sleep-inactive-battery-timeout" : "sleep-inactive-ac-timeout");
+                timeout_sleep = CLAMP (timeout_sleep_, 0, G_MAXINT);
+        }
+
+        clear_idle_watch (manager->idle_monitor,
+                          &manager->idle_sleep_id);
+        clear_idle_watch (manager->idle_monitor,
+                          &manager->idle_sleep_warning_id);
+
+        /* don't do any power saving if we're a VM */
+        if (manager->is_virtual_machine &&
+            (action_type == GSD_POWER_ACTION_SUSPEND ||
+             action_type == GSD_POWER_ACTION_HIBERNATE)) {
+                g_debug ("Ignoring sleep timeout with suspend action inside VM");
+                timeout_sleep = 0;
+        }
+
+        /* don't do any automatic logout if we are in GDM */
+        if (g_getenv ("RUNNING_UNDER_GDM") &&
+            (action_type == GSD_POWER_ACTION_LOGOUT)) {
+                g_debug ("Ignoring sleep timeout with logout action inside GDM");
+                timeout_sleep = 0;
+        }
+
+        if (timeout_sleep != 0) {
+                g_debug ("setting up sleep callback %is", timeout_sleep);
+
+                if (action_type != GSD_POWER_ACTION_NOTHING) {
+                        manager->idle_sleep_id = gnome_idle_monitor_add_idle_watch (manager->idle_monitor,
+                                                                                          timeout_sleep * 1000,
+                                                                                          idle_triggered_idle_cb, manager, NULL);
+                }
+
+                if (action_type == GSD_POWER_ACTION_LOGOUT ||
+                    action_type == GSD_POWER_ACTION_SUSPEND ||
+                    action_type == GSD_POWER_ACTION_HIBERNATE) {
+                        guint timeout_sleep_warning_msec;
+
+                        manager->sleep_action_type = action_type;
+                        timeout_sleep_warning_msec = timeout_sleep * IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER * 1000;
+                        if (timeout_sleep_warning_msec * 1000 < MINIMUM_IDLE_DIM_DELAY) {
+                                /* 0 is not a valid idle timeout */
+                                timeout_sleep_warning_msec = 1;
+                        }
+
+                        g_debug ("setting up sleep warning callback %i msec", timeout_sleep_warning_msec);
+
+                        manager->idle_sleep_warning_id = gnome_idle_monitor_add_idle_watch (manager->idle_monitor,
+                                                                                                  timeout_sleep_warning_msec,
+                                                                                                  idle_triggered_idle_cb, manager, NULL);
+                }
+        }
+
+        if (manager->idle_sleep_warning_id == 0)
+                notify_close_if_showing (&manager->notification_sleep_warning);
+
+        /* set up dim callback for when the screen lock is not active,
+         * but only if we actually want to dim. */
+        timeout_dim = 0;
+        if (manager->screensaver_active) {
+                /* Don't dim when the screen lock is active */
+        } else if (is_power_save_active (manager)) {
+                /* Try to save power by dimming agressively */
+                timeout_dim = SCREENSAVER_TIMEOUT_BLANK;
+        } else {
+                if (g_settings_get_boolean (manager->settings, "idle-dim")) {
+                        timeout_dim = g_settings_get_uint (manager->settings_bus,
+                                                           "idle-delay");
+                        if (timeout_dim == 0) {
+                                timeout_dim = IDLE_DIM_BLANK_DISABLED_MIN;
+                        } else {
+                                timeout_dim *= IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER;
+                                /* Don't bother dimming if the idle-delay is
+                                 * too low, we'll do that when we bring down the
+                                 * screen lock */
+                                if (timeout_dim < MINIMUM_IDLE_DIM_DELAY)
+                                        timeout_dim = 0;
+                        }
+                }
+        }
+
+        clear_idle_watch (manager->idle_monitor,
+                          &manager->idle_dim_id);
+
+        if (timeout_dim != 0) {
+                g_debug ("setting up dim callback for %is", timeout_dim);
+
+                manager->idle_dim_id = gnome_idle_monitor_add_idle_watch (manager->idle_monitor,
+                                                                                timeout_dim * 1000,
+                                                                                idle_triggered_idle_cb, manager, NULL);
+        }
+}
+
+static void
+hold_profile_cb (GObject      *source_object,
+                 GAsyncResult *res,
+                 gpointer      user_data)
+{
+        GsdPowerManager *manager = user_data;
+        g_autoptr(GError) error = NULL;
+        g_autoptr(GVariant) result = NULL;
+
+        result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+                                           res,
+                                           &error);
+        if (result == NULL) {
+                g_warning ("Couldn't hold power-saver profile: %s", error->message);
+                return;
+        }
+
+        if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(u)"))) {
+                g_variant_get (result, "(u)", &manager->power_saver_cookie);
+                g_debug ("Holding power-saver profile with cookie %u", manager->power_saver_cookie);
+        } else {
+                g_warning ("Calling HoldProfile() did not return a uint32");
+        }
+}
+
+static void
+enable_power_saver (GsdPowerManager *manager)
+{
+        if (!manager->power_profiles_proxy)
+                return;
+        if (!g_settings_get_boolean (manager->settings, "power-saver-profile-on-low-battery"))
+                return;
+
+        g_debug ("Starting hold of power-saver profile");
+
+        g_dbus_proxy_call (manager->power_profiles_proxy,
+                           "HoldProfile",
+                           g_variant_new("(sss)",
+                                         "power-saver",
+                                         "Power saver profile when low on battery",
+                                         GSD_POWER_DBUS_NAME),
+                           G_DBUS_CALL_FLAGS_NONE,
+                           -1, manager->cancellable, hold_profile_cb, manager);
+}
+
+static void
+disable_power_saver (GsdPowerManager *manager)
+{
+        if (!manager->power_profiles_proxy || manager->power_saver_cookie == 0)
+                return;
+
+        g_debug ("Releasing power-saver profile");
+
+        g_dbus_proxy_call (manager->power_profiles_proxy,
+                           "ReleaseProfile",
+                           g_variant_new ("(u)", manager->power_saver_cookie),
+                           G_DBUS_CALL_FLAGS_NONE,
+                           -1, NULL, dbus_call_log_error, "ReleaseProfile failed");
+        manager->power_saver_cookie = 0;
+}
+
+static void
+main_battery_or_ups_low_changed (GsdPowerManager *manager,
+                                 gboolean         is_low)
+{
+        if (is_low == manager->battery_is_low)
+                return;
+        manager->battery_is_low = is_low;
+        idle_configure (manager);
+        if (is_low)
+                enable_power_saver (manager);
+        else
+                disable_power_saver (manager);
+}
+
+static gboolean
+temporary_unidle_done_cb (GsdPowerManager *manager)
+{
+        idle_set_mode (manager, manager->previous_idle_mode);
+        manager->temporary_unidle_on_ac_id = 0;
+        return FALSE;
+}
+
+static void
+set_temporary_unidle_on_ac (GsdPowerManager *manager,
+                            gboolean         enable)
+{
+        if (!enable) {
+                /* Don't automatically go back to the previous idle
+                   mode. The caller probably has a better idea of
+                   which state to move to when disabling us. */
+                if (manager->temporary_unidle_on_ac_id != 0) {
+                        g_source_remove (manager->temporary_unidle_on_ac_id);
+                        manager->temporary_unidle_on_ac_id = 0;
+                }
+        } else {
+                /* Don't overwrite the previous idle mode when an unidle is
+                 * already on-going */
+                if (manager->temporary_unidle_on_ac_id != 0) {
+                        g_source_remove (manager->temporary_unidle_on_ac_id);
+                } else {
+                        manager->previous_idle_mode = manager->current_idle_mode;
+                        idle_set_mode (manager, GSD_POWER_IDLE_MODE_NORMAL);
+                }
+                manager->temporary_unidle_on_ac_id = g_timeout_add_seconds (POWER_UP_TIME_ON_AC,
+                                                                                  (GSourceFunc) temporary_unidle_done_cb,
+                                                                                  manager);
+                g_source_set_name_by_id (manager->temporary_unidle_on_ac_id, "[gnome-settings-daemon] temporary_unidle_done_cb");
+        }
+}
+
+static void
+up_client_on_battery_cb (UpClient *client,
+                         GParamSpec *pspec,
+                         GsdPowerManager *manager)
+{
+        if (up_client_get_on_battery (manager->up_client)) {
+                ca_context_play (ca_gtk_context_get (), 0,
+                                 CA_PROP_EVENT_ID, "power-unplug",
+                                 /* TRANSLATORS: this is the sound description */
+                                 CA_PROP_EVENT_DESCRIPTION, _("On battery power"), NULL);
+        } else {
+                ca_context_play (ca_gtk_context_get (), 0,
+                                 CA_PROP_EVENT_ID, "power-plug",
+                                 /* TRANSLATORS: this is the sound description */
+                                 CA_PROP_EVENT_DESCRIPTION, _("On AC power"), NULL);
+
+        }
+
+        idle_configure (manager);
+
+        if (manager->lid_is_closed)
+                return;
+
+        if (manager->current_idle_mode == GSD_POWER_IDLE_MODE_BLANK ||
+            manager->current_idle_mode == GSD_POWER_IDLE_MODE_DIM ||
+            manager->temporary_unidle_on_ac_id != 0)
+                set_temporary_unidle_on_ac (manager, TRUE);
+}
+
+static void
+gsd_power_manager_finalize (GObject *object)
+{
+        GsdPowerManager *manager;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSD_IS_POWER_MANAGER (object));
+
+        manager = GSD_POWER_MANAGER (object);
+
+        g_return_if_fail (manager != NULL);
+
+        gsd_power_manager_stop (manager);
+
+        g_clear_object (&manager->connection);
+
+        if (manager->name_id != 0)
+                g_bus_unown_name (manager->name_id);
+
+        if (manager->iio_proxy_watch_id != 0)
+                g_bus_unwatch_name (manager->iio_proxy_watch_id);
+        manager->iio_proxy_watch_id = 0;
+
+        G_OBJECT_CLASS (gsd_power_manager_parent_class)->finalize (object);
+}
+
+static void
+gsd_power_manager_class_init (GsdPowerManagerClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_power_manager_finalize;
+
+        notify_init ("gnome-settings-daemon");
+}
+
+static void
+handle_screensaver_active (GsdPowerManager *manager,
+                           GVariant        *parameters)
+{
+        gboolean active;
+
+        g_variant_get (parameters, "(b)", &active);
+        g_debug ("Received screensaver ActiveChanged signal: %d (old: %d)", active, manager->screensaver_active);
+        if (manager->screensaver_active != active) {
+                manager->screensaver_active = active;
+                idle_configure (manager);
+
+                /* Setup blank as soon as the screensaver comes on,
+                 * and its fade has finished.
+                 *
+                 * See also idle_configure() */
+                if (active)
+                        idle_set_mode (manager, GSD_POWER_IDLE_MODE_BLANK);
+        }
+}
+
+static void
+handle_wake_up_screen (GsdPowerManager *manager)
+{
+        set_temporary_unidle_on_ac (manager, TRUE);
+}
+
+static void
+screensaver_signal_cb (GDBusProxy *proxy,
+                       const gchar *sender_name,
+                       const gchar *signal_name,
+                       GVariant *parameters,
+                       gpointer user_data)
+{
+        if (g_strcmp0 (signal_name, "ActiveChanged") == 0)
+                handle_screensaver_active (GSD_POWER_MANAGER (user_data), parameters);
+        else if (g_strcmp0 (signal_name, "WakeUpScreen") == 0)
+                handle_wake_up_screen (GSD_POWER_MANAGER (user_data));
+}
+
+static void
+power_profiles_proxy_signal_cb (GDBusProxy  *proxy,
+                               const gchar *sender_name,
+                               const gchar *signal_name,
+                               GVariant    *parameters,
+                               gpointer     user_data)
+{
+        GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+
+        if (g_strcmp0 (signal_name, "ProfileReleased") != 0)
+                return;
+        manager->power_saver_cookie = 0;
+}
+
+static void
+update_active_power_profile (GsdPowerManager *manager)
+{
+        g_autoptr(GVariant) v = NULL;
+        const char *active_profile;
+        gboolean power_saver_enabled;
+
+        v = g_dbus_proxy_get_cached_property (manager->power_profiles_proxy, "ActiveProfile");
+        if (v) {
+                active_profile = g_variant_get_string (v, NULL);
+                power_saver_enabled = g_strcmp0 (active_profile, "power-saver") == 0;
+                if (power_saver_enabled != manager->power_saver_enabled) {
+                        manager->power_saver_enabled = power_saver_enabled;
+                        idle_configure (manager);
+                }
+        } else {
+                /* p-p-d might have disappeared from the bus */
+                idle_configure (manager);
+        }
+}
+
+static void
+power_profiles_proxy_ready_cb (GObject             *source_object,
+                              GAsyncResult        *res,
+                              gpointer             user_data)
+{
+        g_autoptr(GError) error = NULL;
+        GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+
+        manager->power_profiles_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+        if (manager->power_profiles_proxy == NULL) {
+                g_debug ("Could not connect to power-profiles-daemon: %s", error->message);
+                return;
+        }
+
+        g_signal_connect_swapped (manager->power_profiles_proxy,
+                                  "g-properties-changed",
+                                  G_CALLBACK (update_active_power_profile),
+                                  manager);
+        g_signal_connect (manager->power_profiles_proxy, "g-signal",
+                          G_CALLBACK (power_profiles_proxy_signal_cb),
+                          manager);
+
+        update_active_power_profile (manager);
+}
+
+static void
+power_keyboard_proxy_ready_cb (GObject             *source_object,
+                               GAsyncResult        *res,
+                               gpointer             user_data)
+{
+        GVariant *k_now = NULL;
+        GVariant *k_max = NULL;
+        GError *error = NULL;
+        GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+        gint percentage;
+
+        manager->upower_kbd_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+        if (manager->upower_kbd_proxy == NULL) {
+                g_warning ("Could not connect to UPower: %s",
+                           error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        g_signal_connect (manager->upower_kbd_proxy, "g-signal",
+                          G_CALLBACK (upower_kbd_proxy_signal_cb),
+                          manager);
+
+        k_now = g_dbus_proxy_call_sync (manager->upower_kbd_proxy,
+                                        "GetBrightness",
+                                        NULL,
+                                        G_DBUS_CALL_FLAGS_NONE,
+                                        -1,
+                                        manager->cancellable,
+                                        &error);
+        if (k_now == NULL) {
+                if (error->domain != G_DBUS_ERROR ||
+                    error->code != G_DBUS_ERROR_UNKNOWN_METHOD) {
+                        g_warning ("Failed to get brightness: %s",
+                                   error->message);
+                } else {
+                        /* Keyboard brightness is not available */
+                        g_clear_object (&manager->upower_kbd_proxy);
+                }
+                g_error_free (error);
+                goto out;
+        }
+
+        k_max = g_dbus_proxy_call_sync (manager->upower_kbd_proxy,
+                                        "GetMaxBrightness",
+                                        NULL,
+                                        G_DBUS_CALL_FLAGS_NONE,
+                                        -1,
+                                        manager->cancellable,
+                                        &error);
+        if (k_max == NULL) {
+                g_warning ("Failed to get max brightness: %s", error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        g_variant_get (k_now, "(i)", &manager->kbd_brightness_now);
+        g_variant_get (k_max, "(i)", &manager->kbd_brightness_max);
+
+        /* set brightness to max if not currently set so is something
+         * sensible */
+        if (manager->kbd_brightness_now < 0) {
+                gboolean ret;
+                ret = upower_kbd_set_brightness (manager,
+                                                 manager->kbd_brightness_max,
+                                                 &error);
+                if (!ret) {
+                        g_warning ("failed to initialize kbd backlight to %i: %s",
+                                   manager->kbd_brightness_max,
+                                   error->message);
+                        g_error_free (error);
+                }
+        }
+
+        /* Tell the front-end that the brightness changed from
+         * its default "-1/no keyboard backlight available" default */
+        percentage = ABS_TO_PERCENTAGE (0,
+                                        manager->kbd_brightness_max,
+                                        manager->kbd_brightness_now);
+        backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_KEYBOARD, percentage, "initial value");
+
+out:
+        if (k_now != NULL)
+                g_variant_unref (k_now);
+        if (k_max != NULL)
+                g_variant_unref (k_max);
+}
+
+static void
+show_sleep_warning (GsdPowerManager *manager)
+{
+        /* close any existing notification of this class */
+        notify_close_if_showing (&manager->notification_sleep_warning);
+
+        /* create a new notification */
+        switch (manager->sleep_action_type) {
+        case GSD_POWER_ACTION_LOGOUT:
+                create_notification (_("Automatic logout"), _("You will soon log out because of inactivity."),
+                                     NULL, NOTIFICATION_PRIVACY_USER,
+                                     &manager->notification_sleep_warning);
+                break;
+        case GSD_POWER_ACTION_SUSPEND:
+                create_notification (_("Automatic suspend"), _("Suspending soon because of inactivity."),
+                                     NULL, NOTIFICATION_PRIVACY_SYSTEM,
+                                     &manager->notification_sleep_warning);
+                break;
+        case GSD_POWER_ACTION_HIBERNATE:
+                create_notification (_("Automatic hibernation"), _("Suspending soon because of inactivity."),
+                                     NULL, NOTIFICATION_PRIVACY_SYSTEM,
+                                     &manager->notification_sleep_warning);
+                break;
+        default:
+                g_assert_not_reached ();
+                break;
+        }
+        notify_notification_set_timeout (manager->notification_sleep_warning,
+                                         NOTIFY_EXPIRES_NEVER);
+        notify_notification_set_urgency (manager->notification_sleep_warning,
+                                         NOTIFY_URGENCY_CRITICAL);
+
+        notify_notification_show (manager->notification_sleep_warning, NULL);
+}
+
+static void
+idle_set_mode_no_temp (GsdPowerManager  *manager,
+                       GsdPowerIdleMode  mode)
+{
+        if (manager->temporary_unidle_on_ac_id != 0) {
+                manager->previous_idle_mode = mode;
+                return;
+        }
+
+        idle_set_mode (manager, mode);
+}
+
+static void
+idle_triggered_idle_cb (GnomeIdleMonitor *monitor,
+                        guint             watch_id,
+                        gpointer          user_data)
+{
+        GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+        const char *id_name;
+
+        id_name = idle_watch_id_to_string (manager, watch_id);
+        if (id_name == NULL)
+                g_debug ("idletime watch: %i", watch_id);
+        else
+                g_debug ("idletime watch: %s (%i)", id_name, watch_id);
+
+        if (watch_id == manager->idle_dim_id) {
+                idle_set_mode_no_temp (manager, GSD_POWER_IDLE_MODE_DIM);
+        } else if (watch_id == manager->idle_blank_id) {
+                idle_set_mode_no_temp (manager, GSD_POWER_IDLE_MODE_BLANK);
+        } else if (watch_id == manager->idle_sleep_id) {
+                idle_set_mode_no_temp (manager, GSD_POWER_IDLE_MODE_SLEEP);
+        } else if (watch_id == manager->idle_sleep_warning_id) {
+                show_sleep_warning (manager);
+
+                if (manager->user_active_id < 1) {
+                    manager->user_active_id = gnome_idle_monitor_add_user_active_watch (manager->idle_monitor,
+                                                                                        idle_became_active_cb,
+                                                                                        manager,
+                                                                                        NULL);
+                    g_debug ("installing idle_became_active_cb to clear sleep warning on activity (%i)",
+                             manager->user_active_id);
+                }
+        }
+}
+
+static void
+idle_became_active_cb (GnomeIdleMonitor *monitor,
+                       guint             watch_id,
+                       gpointer          user_data)
+{
+        GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+
+        g_debug ("idletime reset (%i)", watch_id);
+
+        set_temporary_unidle_on_ac (manager, FALSE);
+
+        /* close any existing notification about idleness */
+        notify_close_if_showing (&manager->notification_sleep_warning);
+
+        idle_set_mode (manager, GSD_POWER_IDLE_MODE_NORMAL);
+        manager->user_active_id = 0;
+}
+
+static void
+ch_backlight_renormalize (GsdPowerManager *manager)
+{
+        if (manager->ambient_percentage_old < 0)
+                return;
+        if (manager->ambient_last_absolute < 0)
+                return;
+        manager->ambient_norm_value = manager->ambient_last_absolute /
+                                        (gdouble) manager->ambient_percentage_old;
+        manager->ambient_norm_value *= 100.f;
+        manager->ambient_norm_required = FALSE;
+}
+
+static void
+engine_settings_key_changed_cb (GSettings *settings,
+                                const gchar *key,
+                                GsdPowerManager *manager)
+{
+        if (g_str_has_prefix (key, "sleep-inactive") ||
+            g_str_equal (key, "idle-delay") ||
+            g_str_equal (key, "idle-dim")) {
+                idle_configure (manager);
+                return;
+        }
+        if (g_str_equal (key, "power-saver-profile-on-low-battery")) {
+                if (manager->battery_is_low &&
+                    g_settings_get_boolean (settings, key))
+                        enable_power_saver (manager);
+                else
+                        disable_power_saver (manager);
+                return;
+        }
+}
+
+static void
+engine_session_properties_changed_cb (GDBusProxy      *session,
+                                      GVariant        *changed,
+                                      char           **invalidated,
+                                      GsdPowerManager *manager)
+{
+        GVariant *v;
+
+        v = g_variant_lookup_value (changed, "SessionIsActive", G_VARIANT_TYPE_BOOLEAN);
+        if (v) {
+                gboolean active;
+
+                active = g_variant_get_boolean (v);
+                g_debug ("Received session is active change: now %s", active ? "active" : "inactive");
+                manager->session_is_active = active;
+                /* when doing the fast-user-switch into a new account,
+                 * ensure the new account is undimmed and with the backlight on */
+                if (active) {
+                        idle_set_mode (manager, GSD_POWER_IDLE_MODE_NORMAL);
+                        iio_proxy_claim_light (manager, TRUE);
+                } else {
+                        iio_proxy_claim_light (manager, FALSE);
+                }
+                g_variant_unref (v);
+
+                sync_lid_inhibitor (manager);
+        }
+
+        v = g_variant_lookup_value (changed, "InhibitedActions", G_VARIANT_TYPE_UINT32);
+        if (v) {
+                g_variant_unref (v);
+                g_debug ("Received gnome session inhibitor change");
+                idle_configure (manager);
+        }
+}
+
+static void
+inhibit_lid_switch_done (GObject      *source,
+                         GAsyncResult *result,
+                         gpointer      user_data)
+{
+        GDBusProxy *proxy = G_DBUS_PROXY (source);
+        GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+        GError *error = NULL;
+        GVariant *res;
+        GUnixFDList *fd_list = NULL;
+        gint idx;
+
+        res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error);
+        if (res == NULL) {
+                g_warning ("Unable to inhibit lid switch: %s", error->message);
+                g_error_free (error);
+        } else {
+                g_variant_get (res, "(h)", &idx);
+                manager->inhibit_lid_switch_fd = g_unix_fd_list_get (fd_list, idx, &error);
+                if (manager->inhibit_lid_switch_fd == -1) {
+                        g_warning ("Failed to receive system inhibitor fd: %s", error->message);
+                        g_error_free (error);
+                }
+                g_debug ("System inhibitor fd is %d", manager->inhibit_lid_switch_fd);
+                g_object_unref (fd_list);
+                g_variant_unref (res);
+        }
+}
+
+static void
+inhibit_lid_switch (GsdPowerManager *manager)
+{
+        GVariant *params;
+
+        if (manager->inhibit_lid_switch_taken) {
+                g_debug ("already inhibited lid-switch");
+                return;
+        }
+        g_debug ("Adding lid switch system inhibitor");
+        manager->inhibit_lid_switch_taken = TRUE;
+
+        params = g_variant_new ("(ssss)",
+                                "handle-lid-switch",
+                                g_get_user_name (),
+                                "External monitor attached or configuration changed recently",
+                                "block");
+        g_dbus_proxy_call_with_unix_fd_list (manager->logind_proxy,
+                                             "Inhibit",
+                                             params,
+                                             0,
+                                             G_MAXINT,
+                                             NULL,
+                                             NULL,
+                                             inhibit_lid_switch_done,
+                                             manager);
+}
+
+static void
+uninhibit_lid_switch (GsdPowerManager *manager)
+{
+        if (manager->inhibit_lid_switch_fd == -1) {
+                g_debug ("no lid-switch inhibitor");
+                return;
+        }
+        g_debug ("Removing lid switch system inhibitor");
+        close (manager->inhibit_lid_switch_fd);
+        manager->inhibit_lid_switch_fd = -1;
+        manager->inhibit_lid_switch_taken = FALSE;
+}
+
+static void
+inhibit_suspend_done (GObject      *source,
+                      GAsyncResult *result,
+                      gpointer      user_data)
+{
+        GDBusProxy *proxy = G_DBUS_PROXY (source);
+        GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+        GError *error = NULL;
+        GVariant *res;
+        GUnixFDList *fd_list = NULL;
+        gint idx;
+
+        res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error);
+        if (res == NULL) {
+                g_warning ("Unable to inhibit suspend: %s", error->message);
+                g_error_free (error);
+        } else {
+                g_variant_get (res, "(h)", &idx);
+                manager->inhibit_suspend_fd = g_unix_fd_list_get (fd_list, idx, &error);
+                if (manager->inhibit_suspend_fd == -1) {
+                        g_warning ("Failed to receive system inhibitor fd: %s", error->message);
+                        g_error_free (error);
+                }
+                g_debug ("System inhibitor fd is %d", manager->inhibit_suspend_fd);
+                g_object_unref (fd_list);
+                g_variant_unref (res);
+        }
+}
+
+/* We take a delay inhibitor here, which causes logind to send a
+ * PrepareForSleep signal, which gives us a chance to lock the screen
+ * and do some other preparations.
+ */
+static void
+inhibit_suspend (GsdPowerManager *manager)
+{
+        if (manager->inhibit_suspend_taken) {
+                g_debug ("already inhibited lid-switch");
+                return;
+        }
+        g_debug ("Adding suspend delay inhibitor");
+        manager->inhibit_suspend_taken = TRUE;
+        g_dbus_proxy_call_with_unix_fd_list (manager->logind_proxy,
+                                             "Inhibit",
+                                             g_variant_new ("(ssss)",
+                                                            "sleep",
+                                                            g_get_user_name (),
+                                                            "GNOME needs to lock the screen",
+                                                            "delay"),
+                                             0,
+                                             G_MAXINT,
+                                             NULL,
+                                             NULL,
+                                             inhibit_suspend_done,
+                                             manager);
+}
+
+static void
+uninhibit_suspend (GsdPowerManager *manager)
+{
+        if (manager->inhibit_suspend_fd == -1) {
+                g_debug ("no suspend delay inhibitor");
+                return;
+        }
+        g_debug ("Removing suspend delay inhibitor");
+        close (manager->inhibit_suspend_fd);
+        manager->inhibit_suspend_fd = -1;
+        manager->inhibit_suspend_taken = FALSE;
+}
+
+static void
+sync_lid_inhibitor (GsdPowerManager *manager)
+{
+        g_debug ("Syncing lid inhibitor and grabbing it temporarily");
+
+        /* Uninhibiting is done in inhibit_lid_switch_timer_cb,
+         * since we want to give users a few seconds when unplugging
+         * and replugging an external monitor, not suspend right away.
+         */
+        inhibit_lid_switch (manager);
+        restart_inhibit_lid_switch_timer (manager);
+}
+
+static void
+on_randr_event (GnomeRRScreen *screen, gpointer user_data)
+{
+        GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+
+        g_debug ("Screen configuration changed");
+
+        sync_lid_inhibitor (manager);
+}
+
+static void
+handle_suspend_actions (GsdPowerManager *manager)
+{
+        /* close any existing notification about idleness */
+        notify_close_if_showing (&manager->notification_sleep_warning);
+        backlight_disable (manager);
+        uninhibit_suspend (manager);
+}
+
+static void
+handle_resume_actions (GsdPowerManager *manager)
+{
+        /* ensure we turn the panel back on after resume */
+        backlight_enable (manager);
+
+        /* set up the delay again */
+        inhibit_suspend (manager);
+}
+
+static void
+logind_proxy_signal_cb (GDBusProxy  *proxy,
+                        const gchar *sender_name,
+                        const gchar *signal_name,
+                        GVariant    *parameters,
+                        gpointer     user_data)
+{
+        GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+        gboolean is_about_to_suspend;
+
+        if (g_strcmp0 (signal_name, "PrepareForSleep") != 0)
+                return;
+        g_variant_get (parameters, "(b)", &is_about_to_suspend);
+        if (is_about_to_suspend) {
+                handle_suspend_actions (manager);
+        } else {
+                handle_resume_actions (manager);
+        }
+}
+
+static void
+on_rr_screen_acquired (GObject      *object,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+        GsdPowerManager *manager = user_data;
+        GError *error = NULL;
+
+        gnome_settings_profile_start (NULL);
+
+        manager->rr_screen = gnome_rr_screen_new_finish (result, &error);
+
+        if (error) {
+                g_warning ("Could not create GnomeRRScreen: %s\n", error->message);
+                g_error_free (error);
+                gnome_settings_profile_end (NULL);
+
+                return;
+        }
+
+        /* Resolve screen backlight */
+        manager->backlight = gsd_backlight_new (manager->rr_screen, NULL);
+
+        if (manager->backlight)
+                g_signal_connect_object (manager->backlight,
+                                         "notify::brightness",
+                                         G_CALLBACK (backlight_notify_brightness_cb),
+                                         manager, G_CONNECT_SWAPPED);
+
+        /* Set up a delay inhibitor to be informed about suspend attempts */
+        g_signal_connect (manager->logind_proxy, "g-signal",
+                          G_CALLBACK (logind_proxy_signal_cb),
+                          manager);
+        inhibit_suspend (manager);
+
+        /* track the active session */
+        manager->session = gnome_settings_bus_get_session_proxy ();
+        g_signal_connect_object (manager->session, "g-properties-changed",
+                                 G_CALLBACK (engine_session_properties_changed_cb),
+                                 manager, 0);
+        manager->session_is_active = is_session_active (manager);
+
+        /* set up the screens */
+        if (manager->lid_is_present) {
+                g_signal_connect (manager->rr_screen, "changed", G_CALLBACK (on_randr_event), manager);
+                watch_external_monitor (manager->rr_screen);
+                on_randr_event (manager->rr_screen, manager);
+        }
+
+        manager->screensaver_proxy = gnome_settings_bus_get_screen_saver_proxy ();
+
+        g_signal_connect (manager->screensaver_proxy, "g-signal",
+                          G_CALLBACK (screensaver_signal_cb), manager);
+
+        manager->kbd_brightness_old = -1;
+        manager->kbd_brightness_pre_dim = -1;
+        manager->pre_dim_brightness = -1;
+        g_signal_connect (manager->settings, "changed",
+                          G_CALLBACK (engine_settings_key_changed_cb), manager);
+        g_signal_connect (manager->settings_bus, "changed",
+                          G_CALLBACK (engine_settings_key_changed_cb), manager);
+        g_signal_connect (manager->up_client, "device-added",
+                          G_CALLBACK (engine_device_added_cb), manager);
+        g_signal_connect (manager->up_client, "device-removed",
+                          G_CALLBACK (engine_device_removed_cb), manager);
+        g_signal_connect_after (manager->up_client, "notify::lid-is-closed",
+                                G_CALLBACK (lid_state_changed_cb), manager);
+        g_signal_connect (manager->up_client, "notify::on-battery",
+                          G_CALLBACK (up_client_on_battery_cb), manager);
+
+        /* connect to power-profiles-daemon */
+        g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+                                  G_DBUS_PROXY_FLAGS_NONE,
+                                  NULL,
+                                  PPD_DBUS_NAME,
+                                  PPD_DBUS_PATH,
+                                  PPD_DBUS_INTERFACE,
+                                  manager->cancellable,
+                                  power_profiles_proxy_ready_cb,
+                                  manager);
+
+        /* connect to UPower for keyboard backlight control */
+        manager->kbd_brightness_now = -1;
+        g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+                                  G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+                                  NULL,
+                                  UPOWER_DBUS_NAME,
+                                  UPOWER_DBUS_PATH_KBDBACKLIGHT,
+                                  UPOWER_DBUS_INTERFACE_KBDBACKLIGHT,
+                                  NULL,
+                                  power_keyboard_proxy_ready_cb,
+                                  manager);
+
+        manager->devices_array = g_ptr_array_new_with_free_func (g_object_unref);
+        manager->devices_notified_ht = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                              g_free, NULL);
+
+        /* create a fake virtual composite battery */
+        manager->device_composite = up_client_get_display_device (manager->up_client);
+        g_signal_connect (manager->device_composite, "notify::warning-level",
+                          G_CALLBACK (engine_device_warning_changed_cb), manager);
+
+        /* create IDLETIME watcher */
+        manager->idle_monitor = gnome_idle_monitor_new ();
+
+        /* coldplug the engine */
+        engine_coldplug (manager);
+        idle_configure (manager);
+
+        /* ensure the default dpms timeouts are cleared */
+        backlight_enable (manager);
+
+        if (!gnome_settings_is_wayland ())
+                manager->xscreensaver_watchdog_timer_id = gsd_power_enable_screensaver_watchdog ();
+
+        /* don't blank inside a VM */
+        manager->is_virtual_machine = gsd_power_is_hardware_a_vm ();
+
+        /* queue a signal in case the proxy from gnome-shell was created before we got here
+           (likely, considering that to get here we need a reply from gnome-shell)
+        */
+        if (manager->backlight) {
+                manager->ambient_percentage_old = gsd_backlight_get_brightness (manager->backlight, NULL);
+                backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_SCREEN,
+                                              manager->ambient_percentage_old, NULL);
+        } else {
+                backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_SCREEN, -1, NULL);
+        }
+
+        gnome_settings_profile_end (NULL);
+}
+
+static void
+iio_proxy_changed (GsdPowerManager *manager)
+{
+        GVariant *val_has = NULL;
+        GVariant *val_als = NULL;
+        gdouble brightness;
+        gdouble alpha;
+        gint64 current_time;
+        gint pc;
+
+        /* no display hardware */
+        if (!manager->backlight)
+                return;
+
+        /* disabled */
+        if (!g_settings_get_boolean (manager->settings, "ambient-enabled"))
+                return;
+
+        /* get latest results, which do not have to be Lux */
+        val_has = g_dbus_proxy_get_cached_property (manager->iio_proxy, "HasAmbientLight");
+        if (val_has == NULL || !g_variant_get_boolean (val_has))
+                goto out;
+        val_als = g_dbus_proxy_get_cached_property (manager->iio_proxy, "LightLevel");
+        if (val_als == NULL || g_variant_get_double (val_als) == 0.0)
+                goto out;
+        manager->ambient_last_absolute = g_variant_get_double (val_als);
+        g_debug ("Read last absolute light level: %f", manager->ambient_last_absolute);
+
+        /* the user has asked to renormalize */
+        if (manager->ambient_norm_required) {
+                g_debug ("Renormalizing light level from old light percentage: %.1f%%",
+                         manager->ambient_percentage_old);
+                manager->ambient_accumulator = manager->ambient_percentage_old;
+                ch_backlight_renormalize (manager);
+        }
+
+        /* time-weighted constant for moving average */
+        current_time = g_get_monotonic_time();
+        if (manager->ambient_last_time)
+                alpha = 1.0f / (1.0f + (GSD_AMBIENT_TIME_CONSTANT / (current_time - manager->ambient_last_time)));
+        else
+                alpha = 0.0f;
+        manager->ambient_last_time = current_time;
+
+        /* calculate exponential weighted moving average */
+        brightness = manager->ambient_last_absolute * 100.f / manager->ambient_norm_value;
+        brightness = MIN (brightness, 100.f);
+        brightness = MAX (brightness, 0.f);
+
+        manager->ambient_accumulator = (alpha * brightness) +
+                (1.0 - alpha) * manager->ambient_accumulator;
+
+        /* no valid readings yet */
+        if (manager->ambient_accumulator < 0.f)
+                goto out;
+
+        /* set new value */
+        g_debug ("Setting brightness from ambient %.1f%%",
+                 manager->ambient_accumulator);
+        pc = manager->ambient_accumulator;
+
+        if (manager->backlight)
+                gsd_backlight_set_brightness_async (manager->backlight, pc, NULL, NULL, NULL);
+
+        /* Assume setting worked. */
+        manager->ambient_percentage_old = pc;
+out:
+        g_clear_pointer (&val_has, g_variant_unref);
+        g_clear_pointer (&val_als, g_variant_unref);
+}
+
+static void
+iio_proxy_changed_cb (GDBusProxy *proxy,
+                      GVariant   *changed_properties,
+                      GStrv       invalidated_properties,
+                      gpointer    user_data)
+{
+        iio_proxy_changed ((GsdPowerManager *) user_data);
+}
+
+static void
+iio_proxy_appeared_cb (GDBusConnection *connection,
+                       const gchar *name,
+                       const gchar *name_owner,
+                       gpointer user_data)
+{
+        GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+        manager->iio_proxy =
+                g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+                                               0,
+                                               NULL,
+                                               "net.hadess.SensorProxy",
+                                               "/net/hadess/SensorProxy",
+                                               "net.hadess.SensorProxy",
+                                               NULL,
+                                               NULL);
+        iio_proxy_claim_light (manager, TRUE);
+}
+
+static void
+iio_proxy_vanished_cb (GDBusConnection *connection,
+                       const gchar *name,
+                       gpointer user_data)
+{
+        GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+        g_clear_object (&manager->iio_proxy);
+}
+
+gboolean
+gsd_power_manager_start (GsdPowerManager *manager,
+                         GError **error)
+{
+        g_debug ("Starting power manager");
+        gnome_settings_profile_start (NULL);
+
+        /* Check whether we have a lid first */
+        manager->up_client = up_client_new ();
+        manager->lid_is_present = up_client_get_lid_is_present (manager->up_client);
+        if (manager->lid_is_present)
+                manager->lid_is_closed = up_client_get_lid_is_closed (manager->up_client);
+
+        /* Set up the logind proxy */
+        manager->logind_proxy =
+                g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+                                               0,
+                                               NULL,
+                                               SYSTEMD_DBUS_NAME,
+                                               SYSTEMD_DBUS_PATH,
+                                               SYSTEMD_DBUS_INTERFACE,
+                                               NULL,
+                                               error);
+        if (manager->logind_proxy == NULL) {
+                g_debug ("No systemd (logind) support, disabling plugin");
+                return FALSE;
+        }
+
+        /* coldplug the list of screens */
+        gnome_rr_screen_new_async (gdk_screen_get_default (),
+                                   on_rr_screen_acquired, manager);
+
+        manager->settings = g_settings_new (GSD_POWER_SETTINGS_SCHEMA);
+        manager->settings_screensaver = g_settings_new ("org.gnome.desktop.screensaver");
+        manager->settings_bus = g_settings_new ("org.gnome.desktop.session");
+
+        /* setup ambient light support */
+        manager->iio_proxy_watch_id =
+                g_bus_watch_name (G_BUS_TYPE_SYSTEM,
+                                  "net.hadess.SensorProxy",
+                                  G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                  iio_proxy_appeared_cb,
+                                  iio_proxy_vanished_cb,
+                                  manager, NULL);
+        manager->ambient_norm_required = TRUE;
+        manager->ambient_accumulator = -1.f;
+        manager->ambient_norm_value = -1.f;
+        manager->ambient_percentage_old = -1.f;
+        manager->ambient_last_absolute = -1.f;
+        manager->ambient_last_time = 0;
+
+        gnome_settings_profile_end (NULL);
+        return TRUE;
+}
+
+void
+gsd_power_manager_stop (GsdPowerManager *manager)
+{
+        g_debug ("Stopping power manager");
+
+        if (manager->inhibit_lid_switch_timer_id != 0) {
+                g_source_remove (manager->inhibit_lid_switch_timer_id);
+                manager->inhibit_lid_switch_timer_id = 0;
+        }
+
+        if (manager->cancellable != NULL) {
+                g_cancellable_cancel (manager->cancellable);
+                g_clear_object (&manager->cancellable);
+        }
+
+        g_clear_pointer (&manager->introspection_data, g_dbus_node_info_unref);
+
+        if (manager->up_client)
+                g_signal_handlers_disconnect_by_data (manager->up_client, manager);
+
+        g_clear_object (&manager->session);
+        g_clear_object (&manager->settings);
+        g_clear_object (&manager->settings_screensaver);
+        g_clear_object (&manager->settings_bus);
+        g_clear_object (&manager->up_client);
+
+        iio_proxy_claim_light (manager, FALSE);
+        g_clear_object (&manager->iio_proxy);
+
+        if (manager->inhibit_lid_switch_fd != -1) {
+                close (manager->inhibit_lid_switch_fd);
+                manager->inhibit_lid_switch_fd = -1;
+                manager->inhibit_lid_switch_taken = FALSE;
+        }
+        if (manager->inhibit_suspend_fd != -1) {
+                close (manager->inhibit_suspend_fd);
+                manager->inhibit_suspend_fd = -1;
+                manager->inhibit_suspend_taken = FALSE;
+        }
+
+        g_clear_object (&manager->logind_proxy);
+        g_clear_object (&manager->rr_screen);
+
+        g_clear_pointer (&manager->devices_array, g_ptr_array_unref);
+        g_clear_object (&manager->device_composite);
+        g_clear_pointer (&manager->devices_notified_ht, g_hash_table_destroy);
+
+        g_clear_object (&manager->screensaver_proxy);
+
+        disable_power_saver (manager);
+        g_clear_object (&manager->power_profiles_proxy);
+
+        play_loop_stop (&manager->critical_alert_timeout_id);
+
+        g_clear_object (&manager->idle_monitor);
+        g_clear_object (&manager->upower_kbd_proxy);
+
+        if (manager->xscreensaver_watchdog_timer_id > 0) {
+                g_source_remove (manager->xscreensaver_watchdog_timer_id);
+                manager->xscreensaver_watchdog_timer_id = 0;
+        }
+}
+
+static void
+gsd_power_manager_init (GsdPowerManager *manager)
+{
+        manager->inhibit_lid_switch_fd = -1;
+        manager->inhibit_suspend_fd = -1;
+        manager->cancellable = g_cancellable_new ();
+}
+
+/* returns new level */
+static void
+handle_method_call_keyboard (GsdPowerManager *manager,
+                             const gchar *method_name,
+                             GVariant *parameters,
+                             GDBusMethodInvocation *invocation)
+{
+        gint step;
+        gint value = -1;
+        gboolean ret;
+        guint percentage;
+        GError *error = NULL;
+
+        if (g_strcmp0 (method_name, "StepUp") == 0) {
+                g_debug ("keyboard step up");
+                step = BRIGHTNESS_STEP_AMOUNT (manager->kbd_brightness_max);
+                value = MIN (manager->kbd_brightness_now + step,
+                             manager->kbd_brightness_max);
+                ret = upower_kbd_set_brightness (manager, value, &error);
+
+        } else if (g_strcmp0 (method_name, "StepDown") == 0) {
+                g_debug ("keyboard step down");
+                step = BRIGHTNESS_STEP_AMOUNT (manager->kbd_brightness_max);
+                value = MAX (manager->kbd_brightness_now - step, 0);
+                ret = upower_kbd_set_brightness (manager, value, &error);
+
+        } else if (g_strcmp0 (method_name, "Toggle") == 0) {
+                value = upower_kbd_toggle (manager, &error);
+                ret = (value >= 0);
+
+        } else {
+                g_assert_not_reached ();
+        }
+
+        /* return value */
+        if (!ret) {
+                g_dbus_method_invocation_take_error (invocation,
+                                                     error);
+                backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_KEYBOARD, -1, method_name);
+        } else {
+                percentage = ABS_TO_PERCENTAGE (0,
+                                                manager->kbd_brightness_max,
+                                                value);
+                g_dbus_method_invocation_return_value (invocation,
+                                                       g_variant_new ("(i)",
+                                                                      percentage));
+                backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_KEYBOARD, percentage, method_name);
+        }
+}
+
+static void
+backlight_brightness_step_cb (GObject *object,
+                              GAsyncResult *res,
+                              gpointer user_data)
+{
+        GsdBacklight *backlight = GSD_BACKLIGHT (object);
+        GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (user_data);
+        GsdPowerManager *manager;
+        GError *error = NULL;
+        const char *connector;
+        gint brightness;
+
+        manager = g_object_get_data (G_OBJECT (invocation), "gsd-power-manager");
+        brightness = gsd_backlight_set_brightness_finish (backlight, res, &error);
+
+        /* ambient brightness no longer valid */
+        manager->ambient_percentage_old = brightness;
+        manager->ambient_norm_required = TRUE;
+
+        if (error) {
+                g_dbus_method_invocation_take_error (invocation,
+                                                     error);
+        } else {
+                connector = gsd_backlight_get_connector (backlight);
+
+                g_dbus_method_invocation_return_value (invocation,
+                                                       g_variant_new ("(is)",
+                                                                      brightness,
+                                                                      connector ? connector : ""));
+        }
+}
+
+/* Callback */
+static void
+backlight_brightness_set_cb (GObject *object,
+                             GAsyncResult *res,
+                             gpointer user_data)
+{
+        GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+        GsdBacklight *backlight = GSD_BACKLIGHT (object);
+        gint brightness;
+
+        /* Return the invocation. */
+        brightness = gsd_backlight_set_brightness_finish (backlight, res, NULL);
+
+        if (brightness >= 0) {
+                manager->ambient_percentage_old = brightness;
+                manager->ambient_norm_required = TRUE;
+        }
+
+        g_object_unref (manager);
+}
+
+static void
+handle_method_call_screen (GsdPowerManager *manager,
+                           const gchar *method_name,
+                           GVariant *parameters,
+                           GDBusMethodInvocation *invocation)
+{
+        if (!manager->backlight) {
+                g_dbus_method_invocation_return_error_literal (invocation,
+                                                               GSD_POWER_MANAGER_ERROR, GSD_POWER_MANAGER_ERROR_NO_BACKLIGHT,
+                                                               "No usable backlight could be found!");
+                return;
+        }
+
+        g_object_set_data_full (G_OBJECT (invocation), "gsd-power-manager", g_object_ref (manager), g_object_unref);
+
+        if (g_strcmp0 (method_name, "StepUp") == 0) {
+                g_debug ("screen step up");
+                gsd_backlight_step_up_async (manager->backlight, NULL, backlight_brightness_step_cb, invocation);
+
+        } else if (g_strcmp0 (method_name, "StepDown") == 0) {
+                g_debug ("screen step down");
+                gsd_backlight_step_down_async (manager->backlight, NULL, backlight_brightness_step_cb, invocation);
+
+        } else if (g_strcmp0 (method_name, "Cycle") == 0) {
+                g_debug ("screen cycle up");
+                gsd_backlight_cycle_up_async (manager->backlight, NULL, backlight_brightness_step_cb, invocation);
+
+        } else {
+                g_assert_not_reached ();
+        }
+}
+
+static void
+handle_method_call (GDBusConnection       *connection,
+                    const gchar           *sender,
+                    const gchar           *object_path,
+                    const gchar           *interface_name,
+                    const gchar           *method_name,
+                    GVariant              *parameters,
+                    GDBusMethodInvocation *invocation,
+                    gpointer               user_data)
+{
+        GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+
+        /* Check session pointer as a proxy for whether the manager is in the
+           start or stop state */
+        if (manager->session == NULL) {
+                return;
+        }
+
+        g_debug ("Calling method '%s.%s' for Power",
+                 interface_name, method_name);
+
+        if (g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_SCREEN) == 0) {
+                handle_method_call_screen (manager,
+                                           method_name,
+                                           parameters,
+                                           invocation);
+        } else if (g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_KEYBOARD) == 0) {
+                handle_method_call_keyboard (manager,
+                                             method_name,
+                                             parameters,
+                                             invocation);
+        } else {
+                g_warning ("not recognised interface: %s", interface_name);
+        }
+}
+
+static GVariant *
+handle_get_property_other (GsdPowerManager *manager,
+                           const gchar *interface_name,
+                           const gchar *property_name,
+                           GError **error)
+{
+        GVariant *retval = NULL;
+        gint32 value;
+
+        if (g_strcmp0 (property_name, "Brightness") != 0) {
+                g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+                             "No such property: %s", property_name);
+                return NULL;
+        }
+
+        if (g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_SCREEN) == 0) {
+                if (manager->backlight)
+                        value = gsd_backlight_get_brightness (manager->backlight, NULL);
+                else
+                        value = -1;
+
+                retval = g_variant_new_int32 (value);
+        } else if (manager->upower_kbd_proxy &&
+                   g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_KEYBOARD) == 0) {
+                value = ABS_TO_PERCENTAGE (0,
+                                           manager->kbd_brightness_max,
+                                           manager->kbd_brightness_now);
+                retval =  g_variant_new_int32 (value);
+        }
+
+        if (retval == NULL) {
+                g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+                             "Failed to get property %s on interface %s",
+                             property_name, interface_name);
+        }
+        return retval;
+}
+
+static GVariant *
+handle_get_property (GDBusConnection *connection,
+                     const gchar *sender,
+                     const gchar *object_path,
+                     const gchar *interface_name,
+                     const gchar *property_name,
+                     GError **error, gpointer user_data)
+{
+        GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+
+        /* Check session pointer as a proxy for whether the manager is in the
+           start or stop state */
+        if (manager->session == NULL) {
+                g_set_error_literal (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+                                     "No session");
+                return NULL;
+        }
+
+        if (g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_SCREEN) == 0 ||
+                   g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_KEYBOARD) == 0) {
+                return handle_get_property_other (manager, interface_name, property_name, error);
+        } else {
+                g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+                             "No such interface: %s", interface_name);
+                return NULL;
+        }
+}
+
+static gboolean
+handle_set_property_other (GsdPowerManager *manager,
+                           const gchar *interface_name,
+                           const gchar *property_name,
+                           GVariant *value,
+                           GError **error)
+{
+        gint32 brightness_value;
+
+        if (g_strcmp0 (property_name, "Brightness") != 0) {
+                g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+                             "No such property: %s", property_name);
+                return FALSE;
+        }
+
+        if (g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_SCREEN) == 0) {
+                /* To do error reporting we would need to handle the Set call
+                 * instead of doing it through set_property.
+                 * But none of our DBus API users actually read the result. */
+                g_variant_get (value, "i", &brightness_value);
+                if (manager->backlight) {
+                        gsd_backlight_set_brightness_async (manager->backlight, brightness_value,
+                                                            NULL,
+                                                            backlight_brightness_set_cb, g_object_ref (manager));
+                        return TRUE;
+                } else {
+                        g_set_error_literal (error, GSD_POWER_MANAGER_ERROR, GSD_POWER_MANAGER_ERROR_NO_BACKLIGHT,
+                                             "No usable backlight could be found!");
+                        return FALSE;
+                }
+
+        } else if (g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_KEYBOARD) == 0) {
+                g_variant_get (value, "i", &brightness_value);
+                brightness_value = PERCENTAGE_TO_ABS (0, manager->kbd_brightness_max,
+                                                      brightness_value);
+                if (upower_kbd_set_brightness (manager, brightness_value, error)) {
+                        brightness_value = ABS_TO_PERCENTAGE (0,
+                                                              manager->kbd_brightness_max,
+                                                              manager->kbd_brightness_now);
+                        backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_KEYBOARD, brightness_value, "set property");
+                        return TRUE;
+                } else {
+                        return FALSE;
+                }
+        }
+
+        g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+                     "No such interface: %s", interface_name);
+        return FALSE;
+}
+
+static gboolean
+handle_set_property (GDBusConnection *connection,
+                     const gchar *sender,
+                     const gchar *object_path,
+                     const gchar *interface_name,
+                     const gchar *property_name,
+                     GVariant *value,
+                     GError **error, gpointer user_data)
+{
+        GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+
+        /* Check session pointer as a proxy for whether the manager is in the
+           start or stop state */
+        if (manager->session == NULL) {
+                g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+                             "Manager is starting or stopping");
+                return FALSE;
+        }
+
+        if (g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_SCREEN) == 0 ||
+            g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_KEYBOARD) == 0) {
+                return handle_set_property_other (manager, interface_name, property_name, value, error);
+        } else {
+                g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+                             "No such interface: %s", interface_name);
+                return FALSE;
+        }
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+        handle_method_call,
+        handle_get_property,
+        handle_set_property
+};
+
+static void
+on_bus_gotten (GObject             *source_object,
+               GAsyncResult        *res,
+               GsdPowerManager     *manager)
+{
+        GDBusConnection *connection;
+        GDBusInterfaceInfo **infos;
+        GError *error = NULL;
+        guint i;
+
+        connection = g_bus_get_finish (res, &error);
+        if (connection == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Could not get session bus: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        manager->connection = connection;
+
+        infos = manager->introspection_data->interfaces;
+        for (i = 0; infos[i] != NULL; i++) {
+                g_dbus_connection_register_object (connection,
+                                                   GSD_POWER_DBUS_PATH,
+                                                   infos[i],
+                                                   &interface_vtable,
+                                                   manager,
+                                                   NULL,
+                                                   NULL);
+        }
+
+        manager->name_id = g_bus_own_name_on_connection (connection,
+                                                               GSD_POWER_DBUS_NAME,
+                                                               G_BUS_NAME_OWNER_FLAGS_NONE,
+                                                               NULL,
+                                                               NULL,
+                                                               NULL,
+                                                               NULL);
+}
+
+static void
+register_manager_dbus (GsdPowerManager *manager)
+{
+        manager->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+        g_assert (manager->introspection_data != NULL);
+
+        g_bus_get (G_BUS_TYPE_SESSION,
+                   manager->cancellable,
+                   (GAsyncReadyCallback) on_bus_gotten,
+                   manager);
+}
+
+GsdPowerManager *
+gsd_power_manager_new (void)
+{
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_POWER_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+                register_manager_dbus (manager_object);
+        }
+        return GSD_POWER_MANAGER (manager_object);
+}
diff --git a/plugins/power/gsd-power-manager.h b/plugins/power/gsd-power-manager.h
new file mode 100644
index 0000000..75618de
--- /dev/null
+++ b/plugins/power/gsd-power-manager.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_POWER_MANAGER_H
+#define __GSD_POWER_MANAGER_H
+
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_POWER_MANAGER         (gsd_power_manager_get_type ())
+#define GSD_POWER_MANAGER_ERROR        (gsd_power_manager_error_quark ())
+
+G_DECLARE_FINAL_TYPE (GsdPowerManager, gsd_power_manager, GSD, POWER_MANAGER, GObject)
+
+enum
+{
+        GSD_POWER_MANAGER_ERROR_FAILED,
+        GSD_POWER_MANAGER_ERROR_NO_BACKLIGHT,
+};
+
+GQuark                  gsd_power_manager_error_quark         (void);
+
+GsdPowerManager *       gsd_power_manager_new                 (void);
+gboolean                gsd_power_manager_start               (GsdPowerManager *manager,
+                                                               GError         **error);
+void                    gsd_power_manager_stop                (GsdPowerManager *manager);
+
+G_END_DECLS
+
+#endif /* __GSD_POWER_MANAGER_H */
diff --git a/plugins/power/gsdpowerconstants.py b/plugins/power/gsdpowerconstants.py
new file mode 100644
index 0000000..26fa5bf
--- /dev/null
+++ b/plugins/power/gsdpowerconstants.py
@@ -0,0 +1,16 @@
+
+# File auto-generated from script http://git.gnome.org/browse/gnome-settings-daemon/tree/plugins/power/gsd-power-constants-update.pl
+
+# Modified by the GTK+ Team and others 1997-2012.  See the AUTHORS
+# file for a list of people on the GTK+ Team.  See the ChangeLog
+# files for a list of changes.  These files are distributed with
+# GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+
+SCREENSAVER_TIMEOUT_BLANK = 15;
+IDLE_DIM_BLANK_DISABLED_MIN = 60;
+IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER = 1.0/2.0;
+MINIMUM_IDLE_DIM_DELAY = 10;
+POWER_UP_TIME_ON_AC = 15;
+GSD_MOCK_DEFAULT_BRIGHTNESS = 50;
+GSD_MOCK_MAX_BRIGHTNESS = 100;
+LID_CLOSE_SAFETY_TIMEOUT = 8;
diff --git a/plugins/power/gsm-inhibitor-flag.h b/plugins/power/gsm-inhibitor-flag.h
new file mode 100644
index 0000000..40698f9
--- /dev/null
+++ b/plugins/power/gsm-inhibitor-flag.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ */
+
+#ifndef __GSM_INHIBITOR_FLAG_H__
+#define __GSM_INHIBITOR_FLAG_H__
+
+#include 
+
+G_BEGIN_DECLS
+
+typedef enum {
+        GSM_INHIBITOR_FLAG_LOGOUT      = 1 << 0,
+        GSM_INHIBITOR_FLAG_SWITCH_USER = 1 << 1,
+        GSM_INHIBITOR_FLAG_SUSPEND     = 1 << 2,
+        GSM_INHIBITOR_FLAG_IDLE        = 1 << 3,
+        GSM_INHIBITOR_FLAG_AUTOMOUNT   = 1 << 4
+} GsmInhibitorFlag;
+
+G_END_DECLS
+
+#endif /* __GSM_INHIBITOR_FLAG_H__ */
diff --git a/plugins/power/gsm-manager-logout-mode.h b/plugins/power/gsm-manager-logout-mode.h
new file mode 100644
index 0000000..7b51751
--- /dev/null
+++ b/plugins/power/gsm-manager-logout-mode.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSM_MANAGER_LOGOUT_MODE_H
+#define __GSM_MANAGER_LOGOUT_MODE_H
+
+G_BEGIN_DECLS
+
+typedef enum {
+        GSM_MANAGER_LOGOUT_MODE_NORMAL = 0,
+        GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION,
+        GSM_MANAGER_LOGOUT_MODE_FORCE
+} GsmManagerLogoutMode;
+
+G_END_DECLS
+
+#endif /* __GSM_MANAGER_LOGOUT_MODE_H */
diff --git a/plugins/power/gsm-presence-flag.h b/plugins/power/gsm-presence-flag.h
new file mode 100644
index 0000000..f3bbca4
--- /dev/null
+++ b/plugins/power/gsm-presence-flag.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ */
+
+#ifndef __GSM_PRESENCE_FLAG_H__
+#define __GSM_PRESENCE_FLAG_H__
+
+G_BEGIN_DECLS
+
+typedef enum {
+        GSM_PRESENCE_STATUS_AVAILABLE = 0,
+        GSM_PRESENCE_STATUS_INVISIBLE,
+        GSM_PRESENCE_STATUS_BUSY,
+        GSM_PRESENCE_STATUS_IDLE,
+} GsmPresenceStatus;
+
+G_END_DECLS
+
+#endif /* __GSM_PRESENCE_FLAG_H__ */
diff --git a/plugins/power/main.c b/plugins/power/main.c
new file mode 100644
index 0000000..4b851ed
--- /dev/null
+++ b/plugins/power/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_power_manager_new
+#define START gsd_power_manager_start
+#define STOP gsd_power_manager_stop
+#define MANAGER GsdPowerManager
+#include "gsd-power-manager.h"
+
+#include "daemon-skeleton-gtk.h"
diff --git a/plugins/power/meson.build b/plugins/power/meson.build
new file mode 100644
index 0000000..014bb6d
--- /dev/null
+++ b/plugins/power/meson.build
@@ -0,0 +1,149 @@
+sources = files(
+  'gpm-common.c',
+  'gsd-backlight.c',
+  'gsd-power-manager.c',
+  'main.c'
+)
+
+deps = plugins_deps + [
+  libcanberra_gtk_dep,
+  libcommon_dep,
+  libnotify_dep,
+  gio_unix_dep,
+  gnome_desktop_dep,
+  m_dep,
+  upower_glib_dep,
+  x11_dep,
+  dependency('xext')
+]
+
+# required for gsd-power-enums-update, which we have
+# to compile natively
+native_deps = [
+  dependency('glib-2.0', native: true),
+  dependency('gio-2.0', native:true)
+]
+
+if host_is_linux
+  deps += gudev_dep
+endif
+
+cflags += ['-DLIBEXECDIR="@0@"'.format(gsd_libexecdir)]
+
+gsd_power = executable(
+  'gsd-' + plugin_name,
+  sources,
+  include_directories: [top_inc, data_inc],
+  dependencies: deps,
+  c_args: cflags,
+  install: true,
+  install_rpath: gsd_pkglibdir,
+  install_dir: gsd_libexecdir
+)
+
+sources = files('gsd-power-enums-update.c')
+
+enums_headers = files(
+  'gsm-inhibitor-flag.h',
+  'gsm-presence-flag.h'
+)
+
+enums = 'gsd-power-enums'
+
+sources += gnome.mkenums(
+  enums,
+  sources: enums_headers,
+  c_template: enums + '.c.in',
+  h_template: enums + '.h.in'
+)
+
+gsd_power_enums_update = executable(
+  'gsd-power-enums-update',
+  sources,
+  include_directories: top_inc,
+  dependencies: native_deps,
+  c_args: cflags,
+  native: true
+)
+
+if host_is_linux
+  policy = 'org.gnome.settings-daemon.plugins.power.policy'
+
+  policy_in = configure_file(
+    input: policy + '.in.in',
+    output: policy + '.in',
+    configuration: plugins_conf
+  )
+
+  i18n.merge_file(
+    input: policy_in,
+    output: policy,
+    po_dir: po_dir,
+    install: true,
+    install_dir: join_paths(gsd_datadir, 'polkit-1', 'actions')
+  )
+
+  sources = files(
+    'gsd-backlight-helper.c',
+  )
+
+  deps = [
+  ]
+
+  executable(
+    'gsd-backlight-helper',
+    sources,
+    include_directories: top_inc,
+    dependencies: deps,
+    install: true,
+    install_rpath: gsd_pkglibdir,
+    install_dir: gsd_libexecdir
+  )
+endif
+
+output = 'gsdpowerconstants.py'
+
+gsdpowerconstants_py = custom_target(
+  output,
+  input: 'gsd-power-constants.h',
+  output: output,
+  build_by_default: true,
+  command: [join_paths(meson.current_source_dir(), 'gsd-power-constants-update.pl'), '@INPUT@', '@OUTPUT@']
+)
+
+output = 'gsdpowerenums.py'
+
+gsdpowerenums_py = custom_target(
+  output,
+  output: output,
+  capture: true,
+  build_by_default: true,
+  command: [gsd_power_enums_update]
+)
+
+test_py = find_program('test.py')
+
+envs = environment()
+#envs.prepend('G_DEBUG', 'fatal-warnings')
+envs.set('BUILDDIR', meson.current_build_dir())
+envs.set('TOP_BUILDDIR', meson.build_root())
+envs.set('LD_PRELOAD', 'libumockdev-preload.so.0')
+envs.set('NO_AT_BRIDGE', '1')
+envs.set('HAVE_SYSFS_BACKLIGHT', host_is_linux ? '1' : '0')
+
+if get_option('b_sanitize').split(',').contains('address')
+  # libasan needs to be loaded first; so we need to explicitly preload it
+  envs.set('POWER_LD_PRELOAD', 'libasan.so.5')
+endif
+
+foreach i : [ 1, 2, 3, 4, 5, 6, 7, 8 ]
+  test(
+    'test-power @0@/8'.format(i),
+    test_py,
+    args: [ 'PowerPluginTest@0@'.format(i) ],
+    env: envs,
+    # The first set of tests takes very long because of SCREENSAVER_TIMEOUT_BLANK
+    timeout: i == 1 ? 180 : 120
+  )
+endforeach
+
diff --git a/plugins/power/org.gnome.settings-daemon.plugins.power.policy.in.in b/plugins/power/org.gnome.settings-daemon.plugins.power.policy.in.in
new file mode 100644
index 0000000..f16300f
--- /dev/null
+++ b/plugins/power/org.gnome.settings-daemon.plugins.power.policy.in.in
@@ -0,0 +1,32 @@
+
+
+
+
+  
+
+  GNOME Settings Daemon
+  http://git.gnome.org/browse/gnome-settings-daemon
+  battery
+
+  
+    
+    Modify the laptop brightness
+    Authentication is required to modify the laptop brightness
+    
+      no
+      no
+      yes
+    
+    @libexecdir@/gsd-backlight-helper
+  
+
+
+
diff --git a/plugins/power/test-backlight-helper b/plugins/power/test-backlight-helper
new file mode 100755
index 0000000..2dceacd
--- /dev/null
+++ b/plugins/power/test-backlight-helper
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+# Simulate a slow call and just write the given brightness value to the device
+sleep 0.2
+echo "$2" >"$1/brightness"
diff --git a/plugins/power/test.py b/plugins/power/test.py
new file mode 100755
index 0000000..beb88b4
--- /dev/null
+++ b/plugins/power/test.py
@@ -0,0 +1,1310 @@
+#!/usr/bin/python3 -u
+'''GNOME settings daemon tests for power plugin.'''
+
+__author__ = 'Martin Pitt '
+__copyright__ = '(C) 2013 Canonical Ltd.'
+__license__ = 'GPL v2 or later'
+
+import unittest
+import subprocess
+import sys
+import time
+import math
+import os
+import os.path
+import signal
+
+project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+builddir = os.environ.get('BUILDDIR', os.path.dirname(__file__))
+
+sys.path.insert(0, os.path.join(project_root, 'tests'))
+sys.path.insert(0, builddir)
+import gsdtestcase
+import gsdpowerconstants
+import gsdpowerenums
+from output_checker import OutputChecker
+
+import dbus
+from dbus.mainloop.glib import DBusGMainLoop
+
+DBusGMainLoop(set_as_default=True)
+
+import gi
+gi.require_version('UPowerGlib', '1.0')
+gi.require_version('UMockdev', '1.0')
+
+from gi.repository import Gio
+from gi.repository import GLib
+from gi.repository import UPowerGlib
+from gi.repository import UMockdev
+
+# There must be a better way to do a version comparison ... but this works
+mutter_version = subprocess.run(['mutter', '--version'], stdout=subprocess.PIPE).stdout.decode().strip()
+assert mutter_version.startswith('mutter ')
+mutter_version = mutter_version[7:].split('.')
+
+def mutter_at_least(version):
+    global mutter_version
+    version = version.split('.')
+
+    for i in range(max(len(mutter_version), len(version))):
+        m = mutter_version[i]
+        try:
+            m = int(m)
+        except:
+            pass
+
+        v = version[i]
+        try:
+            v = int(v)
+        except:
+            pass
+
+        try:
+            if m > v:
+                return True
+            elif m < v:
+                return False
+        except TypeError:
+            # String is smaller than integer
+            if isinstance(m, str):
+                return False
+            else:
+                return True
+
+    # assume equal
+    return True
+
+class PowerPluginBase(gsdtestcase.GSDTestCase):
+    '''Test the power plugin'''
+
+    gsd_plugin = 'power'
+    gsd_plugin_case = 'Power'
+
+    COMMON_SUSPEND_METHODS=['Suspend', 'Hibernate', 'SuspendThenHibernate']
+
+    def setUp(self):
+        self.mock_external_monitor_file = os.path.join(self.workdir, 'GSD_MOCK_EXTERNAL_MONITOR')
+        os.environ['GSD_MOCK_EXTERNAL_MONITOR_FILE'] = self.mock_external_monitor_file
+        self.addCleanup(self.delete_external_monitor_file)
+
+        self.check_logind_gnome_session()
+        self.start_logind()
+        self.addCleanup(self.stop_logind)
+
+        # Setup umockdev testbed
+        self.testbed = UMockdev.Testbed.new()
+        self.addCleanup(self.cleanup_testbed)
+        os.environ['UMOCKDEV_DIR'] = self.testbed.get_root_dir()
+
+        # Create a mock backlight device
+        # Note that this function creates a different or even no backlight
+        # device based on the name of the test.
+        self.add_backlight()
+
+        if 'HAVE_SYSFS_BACKLIGHT' in os.environ and os.environ['HAVE_SYSFS_BACKLIGHT'] == '1':
+            self.skip_sysfs_backlight = False
+        else:
+            self.skip_sysfs_backlight = True
+
+        # start mock upowerd
+        (self.upowerd, self.obj_upower) = self.spawn_server_template(
+            'upower', {'DaemonVersion': '0.99', 'OnBattery': True, 'LidIsClosed': False})
+        self.addCleanup(self.stop_process, self.upowerd)
+
+        # start mock gnome-shell screensaver
+        (self.screensaver, self.obj_screensaver) = self.spawn_server_template(
+            'gnome_screensaver')
+        self.addCleanup(self.stop_process, self.screensaver)
+
+        # start mock power-profiles-daemon
+        try:
+            (self.ppd, self.obj_ppd) = self.spawn_server_template('power_profiles_daemon')
+            self.addCleanup(self.stop_process, self.ppd)
+        except ModuleNotFoundError:
+            self.ppd = None
+
+        self.start_session()
+        self.addCleanup(self.stop_session)
+
+        self.obj_session_mgr = self.session_bus_con.get_object(
+            'org.gnome.SessionManager', '/org/gnome/SessionManager')
+
+        self.start_mutter()
+        self.addCleanup(self.stop_mutter)
+
+        # Set up the gnome-session presence
+        obj_session_presence = self.session_bus_con.get_object(
+            'org.gnome.SessionManager', '/org/gnome/SessionManager/Presence')
+        self.obj_session_presence_props = dbus.Interface(obj_session_presence, dbus.PROPERTIES_IFACE)
+
+        # ensure that our tests don't lock the screen when the screensaver
+        # gets active
+        self.settings_screensaver = Gio.Settings(schema_id='org.gnome.desktop.screensaver')
+        self.addCleanup(self.reset_settings, self.settings_screensaver)
+        self.settings_screensaver['lock-enabled'] = False
+
+        # Ensure we set up the external monitor state
+        self.set_has_external_monitor(False)
+
+        self.settings_gsd_power = Gio.Settings(schema_id='org.gnome.settings-daemon.plugins.power')
+        self.addCleanup(self.reset_settings, self.settings_gsd_power)
+
+        Gio.Settings.sync()
+        # avoid painfully long delays of actions for tests
+        env = os.environ.copy()
+        # Disable PulseAudio output from libcanberra
+        env['CANBERRA_DRIVER'] = 'null'
+
+        # Use dummy script as testing backlight helper
+        env['GSD_BACKLIGHT_HELPER'] = os.path.join (project_root, 'plugins', 'power', 'test-backlight-helper')
+        if 'POWER_LD_PRELOAD' in env:
+            if 'LD_PRELOAD' in env and env['LD_PRELOAD']:
+                env['LD_PRELOAD'] = ':'.join((env['POWER_LD_PRELOAD'], env['LD_PRELOAD']))
+            else:
+                env['LD_PRELOAD'] = env['POWER_LD_PRELOAD']
+
+        self.start_plugin(env)
+        self.addCleanup(self.stop_plugin)
+
+        # Store the early-init messages, some tests need them.
+        self.plugin_startup_msgs = self.plugin_log.check_line(b'System inhibitor fd is', timeout=10)
+
+        # always start with zero idle time
+        self.reset_idle_timer()
+
+        self.p_notify_log.clear()
+
+    def cleanup_testbed(self):
+        del self.testbed
+
+    def delete_external_monitor_file(self):
+        try:
+            os.unlink(self.mock_external_monitor_file)
+        except OSError:
+            pass
+
+    def check_logind_gnome_session(self):
+        '''Check that gnome-session is built with logind support'''
+
+        path = GLib.find_program_in_path ('gnome-session')
+        assert(path)
+        (success, data) = GLib.file_get_contents (path)
+        lines = data.split(b'\n')
+        new_path = None
+        for line in lines:
+            items = line.split()
+            if items and items[0] == b'exec':
+                new_path = items[1]
+        if not new_path:
+            self.fail("could not get gnome-session's real path from %s" % path)
+        path = new_path
+        ldd = subprocess.Popen(['ldd', path], stdout=subprocess.PIPE)
+        out = ldd.communicate()[0]
+        if not b'libsystemd.so.0' in out:
+            self.fail('gnome-session is not built with logind support')
+
+    def get_status(self):
+        return self.obj_session_presence_props.Get('org.gnome.SessionManager.Presence', 'status')
+
+    def backlight_defaults(self):
+        # Hack to modify the brightness defaults before starting gsd-power.
+        # The alternative would be to create two separate test files.
+        if 'no_backlight' in self.id():
+            return None, None
+        elif 'legacy_brightness' in self.id():
+            return 15, 15
+        else:
+            return 100, 50
+
+    def add_backlight(self, _type="raw"):
+        max_brightness, brightness = self.backlight_defaults()
+
+        if max_brightness is None:
+            self.backlight = None
+            return
+
+        # Undo mangling done in GSD
+        if max_brightness >= 99:
+            max_brightness += 1
+            brightness += 1
+
+        # This needs to be done before starting gsd-power!
+        self.backlight = self.testbed.add_device('backlight', 'mock_backlight', None,
+                                                 ['type', _type,
+                                                  'max_brightness', str(max_brightness),
+                                                  'brightness', str(brightness)],
+                                                 [])
+
+    def get_brightness(self):
+        max_brightness = int(open(os.path.join(self.testbed.get_root_dir() + self.backlight, 'max_brightness')).read())
+
+        # self.backlight contains the leading slash, so os.path.join doesn't quite work
+        res = int(open(os.path.join(self.testbed.get_root_dir() + self.backlight, 'brightness')).read())
+        # Undo mangling done in GSD
+        if max_brightness >= 99:
+            res -= 1
+        return res
+
+    def set_has_external_monitor(self, external):
+        if external:
+            val = b'1'
+        else:
+            val = b'0'
+        GLib.file_set_contents (self.mock_external_monitor_file, val)
+
+    def set_composite_battery_discharging(self, icon='battery-good-symbolic'):
+        self.obj_upower.SetupDisplayDevice(
+            UPowerGlib.DeviceKind.BATTERY,
+            UPowerGlib.DeviceState.DISCHARGING,
+            50., 50., 100., # 50%, charge 50 of 100
+            0.01, 600, 0, # Discharge rate 0.01 with 600 seconds remaining, 0 time to full
+            True, # present
+            icon, UPowerGlib.DeviceLevel.NONE
+        )
+
+    def set_composite_battery_critical(self, icon='battery-caution-symbolic'):
+        self.obj_upower.SetupDisplayDevice(
+            UPowerGlib.DeviceKind.BATTERY,
+            UPowerGlib.DeviceState.DISCHARGING,
+            2., 2., 100., # 2%, charge 2 of 100
+            0.01, 60, 0, # Discharge rate 0.01 with 60 seconds remaining, 0 time to full
+            True, # present
+            icon, UPowerGlib.DeviceLevel.CRITICAL
+        )
+
+    def check_for_logout(self, timeout):
+        '''Check that logout is requested.
+
+        Fail after the given timeout.
+        '''
+        self.session_log.check_line(b'GsmManager: requesting logout', timeout)
+
+    def check_no_logout(self, seconds):
+        '''Check that no logout is requested in the given time'''
+
+        # wait for specified time to ensure it didn't do anything
+        self.session_log.check_no_line(b'GsmManager: requesting logout', seconds)
+
+    def check_for_suspend(self, timeout, methods=COMMON_SUSPEND_METHODS):
+        '''Check that one of the given suspend methods are requested. Default
+        methods are Suspend() or Hibernate() but also HibernateThenSuspend()
+        is valid.
+
+        Fail after the given timeout.
+        '''
+
+        needle = r'|'.join(' {} '.format(m) for m in methods)
+
+        self.logind_log.check_line_re(needle, timeout,
+                                      failmsg='timed out waiting for logind suspend call, methods: %s' % ', '.join(methods))
+
+    def check_for_lid_inhibited(self, timeout=0):
+        '''Check that the lid inhibitor has been added.
+
+        Fail after the given timeout.
+        '''
+        self.check_plugin_log('Adding lid switch system inhibitor', timeout,
+                              'Timed out waiting for lid inhibitor')
+
+    def check_for_lid_uninhibited(self, timeout=0):
+        '''Check that the lid inhibitor has been dropped.
+
+        Fail after the given timeout.
+        '''
+        self.check_plugin_log('uninhibiting lid close', timeout,
+                              'Timed out waiting for lid uninhibition')
+
+    def check_no_lid_uninhibited(self, timeout=0):
+        '''Check that the lid inhibitor has been dropped.
+
+        Fail after the given timeout.
+        '''
+        self.plugin_log.check_no_line(b'uninhibiting lid close', wait=timeout)
+
+    def check_no_suspend(self, seconds, methods=COMMON_SUSPEND_METHODS):
+        '''Check that no Suspend or Hibernate is requested in the given time'''
+
+        needle = r'|'.join(' {} '.format(m) for m in methods)
+
+        self.logind_log.check_no_line_re(needle, wait=seconds)
+
+    def check_suspend_no_hibernate(self, seconds):
+        '''Check that Suspend was requested and not Hibernate, in the given time'''
+
+        lines = self.logind_log.check_no_line(b' Hibernate', wait=seconds)
+        # Check that we did suspend
+        for l in lines:
+            if b' Suspend' in l:
+                 break
+        else:
+            self.fail('Missing Suspend request')
+
+    def check_plugin_log(self, needle, timeout=0, failmsg=None):
+        '''Check that needle is found in the log within the given timeout.
+        Returns immediately when found.
+
+        Fail after the given timeout.
+        '''
+        self.plugin_log.check_line(needle, timeout=timeout, failmsg=failmsg)
+
+    def check_no_dim(self, seconds):
+        '''Check that mode is not set to dim in the given time'''
+
+        # wait for specified time to ensure it didn't do anything
+        self.plugin_log.check_no_line(b'Doing a state transition: dim', wait=seconds)
+
+    def check_dim(self, timeout):
+        '''Check that mode is set to dim in the given time'''
+
+        self.check_plugin_log('Doing a state transition: dim', timeout,
+                              'timed out waiting for dim')
+
+    def check_undim(self, timeout):
+        '''Check that mode is set to normal in the given time'''
+
+        self.check_plugin_log('Doing a state transition: normal', timeout,
+                              'timed out waiting for normal mode')
+
+    def check_blank(self, timeout):
+        '''Check that blank is requested.
+
+        Fail after the given timeout.
+        '''
+
+        self.check_plugin_log('TESTSUITE: Blanked screen', timeout,
+                              'timed out waiting for blank')
+
+    def check_unblank(self, timeout):
+        '''Check that unblank is requested.
+
+        Fail after the given timeout.
+        '''
+
+        self.check_plugin_log('TESTSUITE: Unblanked screen', timeout,
+                              'timed out waiting for unblank')
+
+    def check_no_blank(self, seconds):
+        '''Check that no blank is requested in the given time'''
+
+        self.plugin_log.check_no_line(b'TESTSUITE: Blanked screen', wait=seconds)
+
+    def check_no_unblank(self, seconds):
+        '''Check that no unblank is requested in the given time'''
+
+        self.plugin_log.check_no_line(b'TESTSUITE: Unblanked screen', wait=seconds)
+
+class PowerPluginTest1(PowerPluginBase):
+    def test_screensaver(self):
+        # Note that the screensaver mock object
+        # doesn't know how to get out of being active,
+        # be it if the lock is disabled, or not.
+
+        self.obj_screensaver.Lock()
+        # 0.3 second animation
+        time.sleep(1)
+        self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
+
+        # blank is supposed to happen straight away
+        self.check_blank(2)
+
+        # Wait a bit for the active watch to be registered through dbus, then
+        # fake user activity and check that the screen is unblanked.
+        time.sleep(0.5)
+        self.reset_idle_timer()
+        self.check_unblank(2)
+
+        # Check for no blank before the normal blank timeout
+        self.check_no_blank(gsdpowerconstants.SCREENSAVER_TIMEOUT_BLANK - 4)
+        self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
+
+        # and check for blank after the blank timeout
+        self.check_blank(10)
+
+        # Wait a bit for the active watch to be registered through dbus, then
+        # fake user activity and check that the screen is unblanked.
+        time.sleep(0.5)
+        self.reset_idle_timer()
+        self.check_unblank(2)
+
+        # check no blank and then blank
+        self.check_no_blank(gsdpowerconstants.SCREENSAVER_TIMEOUT_BLANK - 4)
+        self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
+        self.check_blank(10)
+
+    def test_sleep_inactive_blank(self):
+        '''screensaver/blank interaction'''
+
+        # create suspend inhibitor which should have no effect on the idle
+        inhibit_id = self.obj_session_mgr.Inhibit(
+            'testsuite', dbus.UInt32(0), 'for testing',
+            dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND),
+            dbus_interface='org.gnome.SessionManager')
+
+        self.obj_screensaver.SetActive(True)
+        self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
+
+        # blank is supposed to happen straight away
+        self.check_blank(2)
+
+        # Wait a bit for the active watch to be registered through dbus, then
+        # fake user activity and check that the screen is unblanked.
+        time.sleep(0.5)
+        self.reset_idle_timer()
+        self.check_unblank(2)
+        if not self.skip_sysfs_backlight:
+            self.assertTrue(self.get_brightness() == gsdpowerconstants.GSD_MOCK_DEFAULT_BRIGHTNESS , 'incorrect unblanked brightness (%d != %d)' % (self.get_brightness(), gsdpowerconstants.GSD_MOCK_DEFAULT_BRIGHTNESS))
+
+        # Check for no blank before the normal blank timeout
+        self.check_no_blank(gsdpowerconstants.SCREENSAVER_TIMEOUT_BLANK - 4)
+        self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
+
+        # and check for blank after the blank timeout
+        self.check_blank(10)
+
+        # Drop inhibitor
+        self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
+                dbus_interface='org.gnome.SessionManager')
+
+class PowerPluginTest2(PowerPluginBase):
+    def test_screensaver_no_unblank(self):
+        '''Ensure the screensaver is not unblanked for new inhibitors.'''
+
+        # Lower idle delay a lot
+        self.settings_session['idle-delay'] = 1
+        Gio.Settings.sync()
+
+        # Bring down the screensaver
+        self.obj_screensaver.SetActive(True)
+        self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
+
+        # Check that we blank
+        self.check_blank(2)
+
+        # Create the different possible inhibitors
+        inhibit_id = self.obj_session_mgr.Inhibit(
+            'testsuite', dbus.UInt32(0), 'for testing',
+            dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_IDLE | gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND | gsdpowerenums.GSM_INHIBITOR_FLAG_LOGOUT),
+            dbus_interface='org.gnome.SessionManager')
+
+        self.check_no_unblank(2)
+
+        # Drop inhibitor
+        self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
+                dbus_interface='org.gnome.SessionManager')
+
+        self.check_no_unblank(2)
+
+    def test_session_idle_delay(self):
+        '''verify that session idle delay works as expected when changed'''
+
+        # Verify that idle is set after 5 seconds
+        self.settings_session['idle-delay'] = 5
+        Gio.Settings.sync()
+        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+        time.sleep(7)
+        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_IDLE)
+
+        # Raise the idle delay, and see that we stop being idle
+        # and get idle again after the timeout
+        self.settings_session['idle-delay'] = 10
+        Gio.Settings.sync()
+        # Resolve possible race condition, see also https://gitlab.gnome.org/GNOME/mutter/issues/113
+        time.sleep(0.2)
+        self.reset_idle_timer()
+        time.sleep(5)
+        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+        time.sleep(10)
+        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_IDLE)
+
+        # Lower the delay again, and see that we get idle as we should
+        self.settings_session['idle-delay'] = 5
+        Gio.Settings.sync()
+        # Resolve possible race condition, see also https://gitlab.gnome.org/GNOME/mutter/issues/113
+        time.sleep(0.2)
+        self.reset_idle_timer()
+        time.sleep(2)
+        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+        time.sleep(5)
+        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_IDLE)
+
+    def test_idle_time_reset_on_resume(self):
+        '''Check that the IDLETIME is reset when resuming'''
+
+        self.settings_screensaver['lock-enabled'] = False
+
+        # Go idle
+        self.settings_session['idle-delay'] = 5
+        Gio.Settings.sync()
+        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+        time.sleep(7)
+        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_IDLE)
+
+        # Go to sleep
+        self.logind_obj.EmitSignal('', 'PrepareForSleep', 'b', [True], dbus_interface='org.freedesktop.DBus.Mock')
+        time.sleep(1)
+
+        # Wake up
+        self.logind_obj.EmitSignal('', 'PrepareForSleep', 'b', [False], dbus_interface='org.freedesktop.DBus.Mock')
+        time.sleep(1)
+
+        # And check we're not idle
+        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+
+class PowerPluginTest3(PowerPluginBase):
+    def test_sleep_inactive_battery(self):
+        '''sleep-inactive-battery-timeout'''
+
+        self.settings_session['idle-delay'] = 2
+        self.settings_gsd_power['sleep-inactive-battery-timeout'] = 5
+        self.settings_gsd_power['sleep-inactive-battery-type'] = 'suspend'
+        Gio.Settings.sync()
+
+        # wait for idle delay; should not yet suspend
+        self.check_no_suspend(2)
+
+        # suspend should happen after inactive sleep timeout + 1 s notification
+        # delay + 1 s error margin
+        self.check_for_suspend(7)
+
+    def _test_suspend_no_hibernate(self):
+        '''suspend-no-hibernate'''
+
+        self.settings_session['idle-delay'] = 2
+        self.settings_gsd_power['sleep-inactive-battery-timeout'] = 5
+        # Hibernate isn't possible, so it should end up suspending
+        # FIXME
+        self.settings_gsd_power['critical-battery-action'] = 'hibernate'
+        Gio.Settings.sync()
+
+        # wait for idle delay; should not yet hibernate
+        self.check_no_suspend(2)
+
+        # suspend should happen after inactive sleep timeout + 1 s notification
+        # delay + 1 s error margin
+        self.check_suspend_no_hibernate(7)
+
+    def test_sleep_inhibition(self):
+        '''Does not sleep under idle inhibition'''
+
+        idle_delay = round(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
+
+        self.settings_session['idle-delay'] = idle_delay
+        self.settings_gsd_power['sleep-inactive-battery-timeout'] = 5
+        self.settings_gsd_power['sleep-inactive-battery-type'] = 'suspend'
+        Gio.Settings.sync()
+
+        # create inhibitor
+        inhibit_id = self.obj_session_mgr.Inhibit(
+            'testsuite', dbus.UInt32(0), 'for testing',
+            dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_IDLE | gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND),
+            dbus_interface='org.gnome.SessionManager')
+        self.check_no_suspend(idle_delay + 2)
+        self.check_no_dim(0)
+
+        # Check that we didn't go to idle either
+        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+
+        self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
+                dbus_interface='org.gnome.SessionManager')
+
+class PowerPluginTest4(PowerPluginBase):
+    def test_lock_on_lid_close(self):
+        '''Check that we do lock on lid closing, if the machine will not suspend'''
+
+        self.settings_screensaver['lock-enabled'] = True
+        Gio.Settings.sync()
+
+        # create inhibitor
+        inhibit_id = self.obj_session_mgr.Inhibit(
+            'testsuite', dbus.UInt32(0), 'for testing',
+            dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND),
+            dbus_interface='org.gnome.SessionManager')
+
+        # Wait for startup inhibition to be gone
+        self.check_for_lid_uninhibited(gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 2)
+
+        # Close the lid
+        self.obj_upower.Set('org.freedesktop.UPower', 'LidIsClosed', True)
+        self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+
+        # Check that we've blanked
+        time.sleep(2)
+        self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
+        self.check_blank(2)
+
+        # Drop the inhibit and see whether we suspend
+        self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
+                dbus_interface='org.gnome.SessionManager')
+        # At this point logind should suspend for us
+        self.settings_screensaver['lock-enabled'] = False
+        Gio.Settings.sync()
+
+    def test_blank_on_lid_close(self):
+        '''Check that we do blank on lid closing, if the machine will not suspend'''
+
+        # create inhibitor
+        inhibit_id = self.obj_session_mgr.Inhibit(
+            'testsuite', dbus.UInt32(0), 'for testing',
+            dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND),
+            dbus_interface='org.gnome.SessionManager')
+
+        # Wait for startup inhibition to be gone
+        self.check_for_lid_uninhibited(gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 2)
+
+        # Close the lid
+        self.obj_upower.Set('org.freedesktop.UPower', 'LidIsClosed', True)
+        self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+
+        # Check that we've blanked
+        self.check_blank(4)
+
+        # Drop the inhibit and see whether we suspend
+        self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
+                dbus_interface='org.gnome.SessionManager')
+        # At this point logind should suspend for us
+
+    @unittest.skipIf(not mutter_at_least('42.0'), reason="mutter is too old and may be buggy")
+    def test_unblank_on_lid_open(self):
+        '''Check that we do unblank on lid opening, if the machine will not suspend'''
+
+        # create inhibitor
+        inhibit_id = self.obj_session_mgr.Inhibit(
+            'testsuite', dbus.UInt32(0), 'for testing',
+            dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND),
+            dbus_interface='org.gnome.SessionManager')
+
+        # Wait for startup inhibition to be gone
+        self.check_for_lid_uninhibited(gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 2)
+
+        # Close the lid
+        self.obj_upower.Set('org.freedesktop.UPower', 'LidIsClosed', True)
+        self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+
+        # Check that we've blanked
+        self.check_blank(2)
+
+        # Reopen the lid
+        self.obj_upower.Set('org.freedesktop.UPower', 'LidIsClosed', False)
+        self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+
+        # Check for unblanking
+        self.check_unblank(2)
+
+        # Drop the inhibit
+        self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
+                dbus_interface='org.gnome.SessionManager')
+
+class PowerPluginTest5(PowerPluginBase):
+    def test_dim(self):
+        '''Check that we do go to dim'''
+
+        # Wait for startup inhibition to be gone
+        self.check_for_lid_uninhibited(gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 2)
+
+        idle_delay = math.ceil(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
+        self.reset_idle_timer()
+
+        self.settings_session['idle-delay'] = idle_delay
+        self.settings_gsd_power['sleep-inactive-battery-timeout'] = idle_delay + 1
+        self.settings_gsd_power['sleep-inactive-battery-type'] = 'suspend'
+        Gio.Settings.sync()
+        # This is an absolute percentage, and our brightness is 0..100
+        dim_level = self.settings_gsd_power['idle-brightness'];
+
+        # Check that we're not idle
+        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+
+        # Wait and check we're not idle, but dimmed
+        self.check_dim(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY + 1)
+        # Give time for the brightness to change
+        time.sleep(2)
+        if not self.skip_sysfs_backlight:
+            level = self.get_brightness();
+            self.assertTrue(level == dim_level, 'incorrect dim brightness (%d != %d)' % (level, dim_level))
+
+        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+
+        # Bring down the screensaver
+        self.obj_screensaver.SetActive(True)
+        self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
+
+        # Check that we blank
+        self.check_blank(2)
+
+        # Go to sleep
+        self.logind_obj.EmitSignal('', 'PrepareForSleep', 'b', [True], dbus_interface='org.freedesktop.DBus.Mock')
+        time.sleep(1)
+
+        # Wake up
+        self.logind_obj.EmitSignal('', 'PrepareForSleep', 'b', [False], dbus_interface='org.freedesktop.DBus.Mock')
+        time.sleep(1)
+
+        # And check that we have the pre-dim brightness
+        if not self.skip_sysfs_backlight:
+            self.assertTrue(self.get_brightness() == gsdpowerconstants.GSD_MOCK_DEFAULT_BRIGHTNESS , 'incorrect unblanked brightness (%d != %d)' % (self.get_brightness(), gsdpowerconstants.GSD_MOCK_DEFAULT_BRIGHTNESS))
+
+    def test_lid_close_inhibition(self):
+        '''Check that we correctly inhibit suspend with an external monitor'''
+
+        # Wait for startup inhibition to be gone
+        self.check_for_lid_uninhibited(gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 2)
+
+        # Add an external monitor
+        self.set_has_external_monitor(True)
+        self.check_for_lid_inhibited(1)
+
+        # Check that we do not uninhibit with the external monitor attached
+        self.check_no_lid_uninhibited(gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 1)
+
+        # Close the lid
+        self.obj_upower.Set('org.freedesktop.UPower', 'LidIsClosed', True)
+        self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+        time.sleep(0.5)
+
+        # Unplug the external monitor
+        self.set_has_external_monitor(False)
+
+        # Check that no action happens during the safety time minus 1 second
+        self.check_no_lid_uninhibited(gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT - 1)
+        # Check that we're uninhibited after the safety time
+        self.check_for_lid_uninhibited(4)
+
+class PowerPluginTest6(PowerPluginBase):
+    def test_notify_critical_battery(self):
+        '''action on critical battery'''
+
+        self.set_composite_battery_discharging()
+
+        time.sleep(2)
+
+        self.set_composite_battery_critical()
+
+        # Check that it was picked up
+        self.check_plugin_log('EMIT: charge-critical', 2)
+
+        self.p_notify_log.check_line_re(b'[0-9.]+ Notify "Power" .* "battery-caution-symbolic" ".*[Bb]attery critical.*"', timeout=0.5)
+
+    def test_notify_critical_battery_on_start(self):
+        '''action on critical battery on startup'''
+
+        self.set_composite_battery_critical()
+
+        # Check that it was picked up
+        self.check_plugin_log('EMIT: charge-critical', 2)
+
+        self.p_notify_log.check_line_re(b'[0-9.]+ Notify "Power" .* "battery-caution-symbolic" ".*[Bb]attery critical.*"', timeout=0.5)
+
+    def test_notify_device_battery(self):
+        '''critical power level notification for device batteries'''
+
+        # Set internal battery to discharging
+        self.set_composite_battery_discharging()
+
+        # Add a device battery
+        bat2_path = '/org/freedesktop/UPower/devices/' + 'mock_MOUSE_BAT1'
+        self.obj_upower.AddObject(bat2_path,
+                                  'org.freedesktop.UPower.Device',
+                                  {
+                                      'PowerSupply': dbus.Boolean(False, variant_level=1),
+                                      'IsPresent': dbus.Boolean(True, variant_level=1),
+                                      'Model': dbus.String('Bat1', variant_level=1),
+                                      'Percentage': dbus.Double(40.0, variant_level=1),
+                                      'TimeToEmpty': dbus.Int64(1600, variant_level=1),
+                                      'EnergyFull': dbus.Double(100.0, variant_level=1),
+                                      'Energy': dbus.Double(40.0, variant_level=1),
+                                      'State': dbus.UInt32(UPowerGlib.DeviceState.DISCHARGING, variant_level=1),
+                                      'Type': dbus.UInt32(UPowerGlib.DeviceKind.MOUSE, variant_level=1),
+                                      'WarningLevel': dbus.UInt32(UPowerGlib.DeviceLevel.NONE, variant_level=1),
+                                   }, dbus.Array([], signature='(ssss)'))
+
+        obj_bat2 = self.system_bus_con.get_object('org.freedesktop.UPower', bat2_path)
+        self.obj_upower.EmitSignal('', 'DeviceAdded', 'o', [bat2_path],
+                                   dbus_interface='org.freedesktop.DBus.Mock')
+        time.sleep(1)
+
+        # now change the mouse battery to critical charge
+        obj_bat2.Set('org.freedesktop.UPower.Device', 'TimeToEmpty',
+                     dbus.Int64(30, variant_level=1),
+                     dbus_interface=dbus.PROPERTIES_IFACE)
+        obj_bat2.Set('org.freedesktop.UPower.Device', 'Energy',
+                     dbus.Double(0.5, variant_level=1),
+                     dbus_interface=dbus.PROPERTIES_IFACE)
+        obj_bat2.Set('org.freedesktop.UPower.Device', 'WarningLevel',
+                     dbus.UInt32(UPowerGlib.DeviceLevel.CRITICAL, variant_level=1),
+                     dbus_interface=dbus.PROPERTIES_IFACE)
+        obj_bat2.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+        self.obj_upower.EmitSignal('', 'DeviceChanged', 'o', [bat2_path],
+                                   dbus_interface='org.freedesktop.DBus.Mock')
+
+        self.check_plugin_log('EMIT: charge-critical', 2)
+
+        self.p_notify_log.check_line_re(b'[0-9.]+ Notify "Power" .* ".*" ".*Wireless mouse .*low.* power.*\([0-9.]+%\).*"', timeout=0.5)
+
+    def test_notify_device_spam(self):
+        '''no repeat notifications for device batteries'''
+
+        # Set internal battery to discharging
+        self.set_composite_battery_discharging()
+
+        # Add a device battery
+        bat2_path = '/org/freedesktop/UPower/devices/' + 'mock_MOUSE_BAT1'
+        self.obj_upower.AddObject(bat2_path,
+                                  'org.freedesktop.UPower.Device',
+                                  {
+                                      'PowerSupply': dbus.Boolean(False, variant_level=1),
+                                      'IsPresent': dbus.Boolean(True, variant_level=1),
+                                      'Model': dbus.String('Bat1', variant_level=1),
+                                      'Serial': dbus.String('12345678', variant_level=1),
+                                      'Percentage': dbus.Double(10.0, variant_level=1),
+                                      'State': dbus.UInt32(UPowerGlib.DeviceState.DISCHARGING, variant_level=1),
+                                      'Type': dbus.UInt32(UPowerGlib.DeviceKind.MOUSE, variant_level=1),
+                                      'WarningLevel': dbus.UInt32(UPowerGlib.DeviceLevel.LOW, variant_level=1),
+                                   }, dbus.Array([], signature='(ssss)'))
+
+        obj_bat2 = self.system_bus_con.get_object('org.freedesktop.UPower', bat2_path)
+        self.obj_upower.EmitSignal('', 'DeviceAdded', 'o', [bat2_path],
+                                   dbus_interface='org.freedesktop.DBus.Mock')
+        time.sleep(1)
+
+        self.check_plugin_log('EMIT: charge-low', 2)
+
+        self.p_notify_log.check_line_re(b'[0-9.]+ Notify "Power" .* ".*" ".*Wireless mouse .*low.* power.*\([0-9.]+%\).*"', timeout=0.5)
+
+        # Disconnect mouse
+        self.obj_upower.RemoveObject(bat2_path)
+        time.sleep(0.5)
+
+        # Reconnect mouse
+        self.obj_upower.AddObject(bat2_path,
+                                  'org.freedesktop.UPower.Device',
+                                  {
+                                      'PowerSupply': dbus.Boolean(False, variant_level=1),
+                                      'IsPresent': dbus.Boolean(True, variant_level=1),
+                                      'Model': dbus.String('Bat1', variant_level=1),
+                                      'Serial': dbus.String('12345678', variant_level=1),
+                                      'Percentage': dbus.Double(10.0, variant_level=1),
+                                      'State': dbus.UInt32(UPowerGlib.DeviceState.DISCHARGING, variant_level=1),
+                                      'Type': dbus.UInt32(UPowerGlib.DeviceKind.MOUSE, variant_level=1),
+                                      'WarningLevel': dbus.UInt32(UPowerGlib.DeviceLevel.LOW, variant_level=1),
+                                   }, dbus.Array([], signature='(ssss)'))
+
+        obj_bat2 = self.system_bus_con.get_object('org.freedesktop.UPower', bat2_path)
+        self.obj_upower.EmitSignal('', 'DeviceAdded', 'o', [bat2_path],
+                                   dbus_interface='org.freedesktop.DBus.Mock')
+
+        self.p_notify_log.check_no_line(b'', wait=1)
+
+        # Disconnect mouse
+        self.obj_upower.RemoveObject(bat2_path)
+        time.sleep(0.5)
+
+        # Reconnect mouse with critical battery level
+        self.obj_upower.AddObject(bat2_path,
+                                  'org.freedesktop.UPower.Device',
+                                  {
+                                      'PowerSupply': dbus.Boolean(False, variant_level=1),
+                                      'IsPresent': dbus.Boolean(True, variant_level=1),
+                                      'Model': dbus.String('Bat1', variant_level=1),
+                                      'Serial': dbus.String('12345678', variant_level=1),
+                                      'Percentage': dbus.Double(5.0, variant_level=1),
+                                      'State': dbus.UInt32(UPowerGlib.DeviceState.DISCHARGING, variant_level=1),
+                                      'Type': dbus.UInt32(UPowerGlib.DeviceKind.MOUSE, variant_level=1),
+                                      'WarningLevel': dbus.UInt32(UPowerGlib.DeviceLevel.CRITICAL, variant_level=1),
+                                   }, dbus.Array([], signature='(ssss)'))
+
+        obj_bat2 = self.system_bus_con.get_object('org.freedesktop.UPower', bat2_path)
+        self.obj_upower.EmitSignal('', 'DeviceAdded', 'o', [bat2_path],
+                                   dbus_interface='org.freedesktop.DBus.Mock')
+        time.sleep(1)
+
+        # Verify new warning
+        self.check_plugin_log('EMIT: charge-critical', 2)
+
+        self.p_notify_log.check_line_re(b'[0-9.]+ Notify "Power" .* ".*" ".*Wireless mouse .*very low.* power.*\([0-9.]+%\).*"', timeout=0.5)
+
+    def test_notify_device_battery_coarse_level(self):
+        '''critical power level notification for device batteries with coarse level'''
+
+        # Set internal battery to discharging
+        self.set_composite_battery_discharging()
+
+        # Add a device battery
+        bat2_path = '/org/freedesktop/UPower/devices/' + 'mock_MOUSE_BAT1'
+        self.obj_upower.AddObject(bat2_path,
+                                  'org.freedesktop.UPower.Device',
+                                  {
+                                      'PowerSupply': dbus.Boolean(False, variant_level=1),
+                                      'IsPresent': dbus.Boolean(True, variant_level=1),
+                                      'Model': dbus.String('Bat1', variant_level=1),
+                                      'Percentage': dbus.Double(40.0, variant_level=1),
+                                      'BatteryLevel': dbus.UInt32(UPowerGlib.DeviceLevel.LOW, variant_level=1),
+                                      'TimeToEmpty': dbus.Int64(1600, variant_level=1),
+                                      'EnergyFull': dbus.Double(100.0, variant_level=1),
+                                      'Energy': dbus.Double(40.0, variant_level=1),
+                                      'State': dbus.UInt32(UPowerGlib.DeviceState.DISCHARGING, variant_level=1),
+                                      'Type': dbus.UInt32(UPowerGlib.DeviceKind.MOUSE, variant_level=1),
+                                      'WarningLevel': dbus.UInt32(UPowerGlib.DeviceLevel.NONE, variant_level=1),
+                                   }, dbus.Array([], signature='(ssss)'))
+
+        obj_bat2 = self.system_bus_con.get_object('org.freedesktop.UPower', bat2_path)
+        self.obj_upower.EmitSignal('', 'DeviceAdded', 'o', [bat2_path],
+                                   dbus_interface='org.freedesktop.DBus.Mock')
+        time.sleep(1)
+
+        # now change the mouse battery to critical charge
+        obj_bat2.Set('org.freedesktop.UPower.Device', 'TimeToEmpty',
+                     dbus.Int64(30, variant_level=1),
+                     dbus_interface=dbus.PROPERTIES_IFACE)
+        obj_bat2.Set('org.freedesktop.UPower.Device', 'Energy',
+                     dbus.Double(0.5, variant_level=1),
+                     dbus_interface=dbus.PROPERTIES_IFACE)
+        obj_bat2.Set('org.freedesktop.UPower.Device', 'WarningLevel',
+                     dbus.UInt32(UPowerGlib.DeviceLevel.CRITICAL, variant_level=1),
+                     dbus_interface=dbus.PROPERTIES_IFACE)
+        obj_bat2.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+        self.obj_upower.EmitSignal('', 'DeviceChanged', 'o', [bat2_path],
+                                   dbus_interface='org.freedesktop.DBus.Mock')
+
+        self.check_plugin_log('EMIT: charge-critical', 2)
+
+        time.sleep(0.5)
+        lines = self.p_notify_log.check_line_re(b'[0-9.]+ Notify "Power" .* ".*" ".*Wireless mouse .*low.* power.*"')
+        lines += self.p_notify_log.clear()
+        for l in lines:
+            self.assertNotRegex(l, b'[0-9.]+ Notify "Power" .* ".*" ".*\([0-9.]+%\).*"')
+
+    def test_forced_logout(self):
+        '''Test forced logout'''
+
+        self.plugin_death_expected = True
+        idle_delay = round(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
+
+        self.settings_session['idle-delay'] = idle_delay
+        self.settings_gsd_power['sleep-inactive-battery-timeout'] = idle_delay + 1
+        self.settings_gsd_power['sleep-inactive-battery-type'] = 'logout'
+        Gio.Settings.sync()
+
+        self.check_for_logout(idle_delay + 2)
+
+        self.p_notify_log.check_line(b'You will soon log out because of inactivity.')
+
+    def test_forced_logout_inhibition(self):
+        '''Test we don't force logout when inhibited'''
+
+        idle_delay = round(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
+
+        self.settings_session['idle-delay'] = idle_delay
+        self.settings_gsd_power['sleep-inactive-battery-timeout'] = idle_delay + 1
+        self.settings_gsd_power['sleep-inactive-battery-type'] = 'logout'
+        Gio.Settings.sync()
+
+        # create suspend inhibitor which should stop us logging out
+        inhibit_id = self.obj_session_mgr.Inhibit(
+            'testsuite', dbus.UInt32(0), 'for testing',
+            dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_LOGOUT),
+            dbus_interface='org.gnome.SessionManager')
+
+        self.check_no_logout(idle_delay + 3)
+
+        # Drop inhibitor
+        self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
+                dbus_interface='org.gnome.SessionManager')
+
+class PowerPluginTest7(PowerPluginBase):
+    def test_check_missing_kbd_brightness(self):
+        ''' https://bugzilla.gnome.org/show_bug.cgi?id=793512 '''
+
+        obj_gsd_power_kbd = self.session_bus_con.get_object(
+            'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
+        obj_gsd_power_kbd_props = dbus.Interface(obj_gsd_power_kbd, dbus.PROPERTIES_IFACE)
+
+        # Will return -1 if gsd-power crashed, and an exception if the code caught the problem
+        with self.assertRaises(dbus.DBusException) as exc:
+            kbd_brightness = obj_gsd_power_kbd_props.Get('org.gnome.SettingsDaemon.Power.Keyboard', 'Brightness')
+
+            # We should not have arrived here, if we did then the test failed, let's print this to help debugging
+            print('Got keyboard brightness: {}'.format(kbd_brightness))
+
+        self.assertEqual(exc.exception.get_dbus_message(), 'Failed to get property Brightness on interface org.gnome.SettingsDaemon.Power.Keyboard')
+
+    def test_inhibitor_idletime(self):
+        ''' https://bugzilla.gnome.org/show_bug.cgi?id=705942 '''
+
+        idle_delay = round(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
+
+        self.settings_session['idle-delay'] = idle_delay
+        self.settings_gsd_power['sleep-inactive-battery-timeout'] = 5
+        self.settings_gsd_power['sleep-inactive-battery-type'] = 'suspend'
+        Gio.Settings.sync()
+
+        # create inhibitor
+        inhibit_id = self.obj_session_mgr.Inhibit(
+            'testsuite', dbus.UInt32(0), 'for testing',
+            dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_IDLE),
+            dbus_interface='org.gnome.SessionManager')
+        self.check_no_suspend(idle_delay + 2)
+        self.check_no_dim(0)
+
+        # Check that we didn't go to idle either
+        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+
+        self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
+                dbus_interface='org.gnome.SessionManager')
+
+        self.check_no_suspend(2)
+        self.check_no_dim(0)
+
+        time.sleep(5)
+
+        self.check_suspend_no_hibernate(7)
+
+    def disabled_test_unindle_on_ac_plug(self):
+        idle_delay = round(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
+        self.settings_session['idle-delay'] = idle_delay
+        Gio.Settings.sync()
+
+        # Wait for idle
+        self.check_dim(idle_delay + 2)
+
+        # Plug in the AC
+        self.obj_upower.Set('org.freedesktop.UPower', 'OnBattery', False)
+        self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+
+        # Check that we undim
+        self.check_undim(gsdpowerconstants.POWER_UP_TIME_ON_AC / 2)
+
+        # And wait a little more to see us dim again
+        self.check_dim(idle_delay + 2)
+
+        # Unplug the AC
+        self.obj_upower.Set('org.freedesktop.UPower', 'OnBattery', True)
+        self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+
+        # Check that we undim
+        self.check_undim(gsdpowerconstants.POWER_UP_TIME_ON_AC / 2)
+
+        # And wait a little more to see us dim again
+        self.check_dim(idle_delay + 2)
+
+class PowerPluginTest8(PowerPluginBase):
+    def test_brightness_stepping(self):
+        '''Check that stepping the backlight works as expected'''
+
+        if self.skip_sysfs_backlight:
+            self.skipTest("sysfs backlight support required for test")
+
+        obj_gsd_power = self.session_bus_con.get_object(
+            'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
+        obj_gsd_power_screen_iface = dbus.Interface(obj_gsd_power, 'org.gnome.SettingsDaemon.Power.Screen')
+
+        # Each of the step calls will only return when the value was written
+        start = time.time()
+        # We start at 50% and step by 5% each time
+        obj_gsd_power_screen_iface.StepUp()
+        self.assertEqual(self.get_brightness(), 55)
+        obj_gsd_power_screen_iface.StepUp()
+        self.assertEqual(self.get_brightness(), 60)
+        obj_gsd_power_screen_iface.StepUp()
+        self.assertEqual(self.get_brightness(), 65)
+        obj_gsd_power_screen_iface.StepUp()
+        self.assertEqual(self.get_brightness(), 70)
+        stop = time.time()
+        # This needs to take more than 0.8 seconds as each write is delayed by
+        # 0.2 seconds by the test backlight helper
+        self.assertGreater(stop - start, 0.8)
+
+        # Now, the same thing should work fine if we step multiple times,
+        # even if we are so quick that compression will happen.
+        # Use a list to keep rack of replies (as integer is immutable and would
+        # not be modified in the outer scope)
+        replies = [0]
+
+        def handle_reply(*args):
+            replies[0] += 1
+
+        def last_reply(*args):
+            replies[0] += 1
+            loop.quit()
+
+        def error_handler(*args):
+            loop.quit()
+
+        start = time.time()
+        obj_gsd_power_screen_iface.StepDown(reply_handler=handle_reply, error_handler=error_handler)
+        obj_gsd_power_screen_iface.StepDown(reply_handler=handle_reply, error_handler=error_handler)
+        obj_gsd_power_screen_iface.StepDown(reply_handler=handle_reply, error_handler=error_handler)
+        obj_gsd_power_screen_iface.StepDown(reply_handler=last_reply, error_handler=error_handler)
+        loop = GLib.MainLoop()
+        loop.run()
+        stop = time.time()
+
+        # The calls need to be returned in order. As we got the last reply, all
+        # others must have been received too.
+        self.assertEqual(replies[0], 4)
+        # Four steps down, so back at 50%
+        self.assertEqual(self.get_brightness(), 50)
+        # And compression must have happened, so it should take less than 0.8s
+        self.assertLess(stop - start, 0.8)
+
+    def test_brightness_compression(self):
+        '''Check that compression also happens when setting the property'''
+
+        if self.skip_sysfs_backlight:
+            self.skipTest("sysfs backlight support required for test")
+
+        # Now test that the compression works correctly.
+        # NOTE: Relies on the implementation detail, that the property setter
+        #       returns immediately rather than waiting for the brightness to
+        #       be updated.
+        # Should this ever be fixed, then this will need to be changed to use
+        # async dbus calls similar to the stepping code
+
+        obj_gsd_power = self.session_bus_con.get_object(
+            'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
+        obj_gsd_power_prop_iface = dbus.Interface(obj_gsd_power, dbus.PROPERTIES_IFACE)
+
+        # Quickly ramp the brightness up
+        for brightness in range(70, 91):
+            obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', brightness)
+
+        # The brightness of 80 should be in effect after slightly more than
+        # 0.4 seconds. If compression does not work as expected, this would take
+        # more than 5 seconds for the 20 steps.
+        time.sleep(2.0)
+        self.assertEqual(self.get_brightness(), 90)
+
+    def test_brightness_uevent(self):
+        if self.skip_sysfs_backlight:
+            self.skipTest("sysfs backlight support required for test")
+
+        obj_gsd_power = self.session_bus_con.get_object(
+            'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
+        obj_gsd_power_prop_iface = dbus.Interface(obj_gsd_power, dbus.PROPERTIES_IFACE)
+
+        brightness = obj_gsd_power_prop_iface.Get('org.gnome.SettingsDaemon.Power.Screen', 'Brightness')
+        self.assertEqual(50, brightness)
+
+        # Check that the brightness is updated if it was changed through some
+        # other mechanism (e.g. firmware).
+        # Set to 80+1 because of the GSD offset (see add_backlight).
+        self.testbed.set_attribute(self.backlight, 'brightness', '81')
+        self.testbed.uevent(self.backlight, 'change')
+
+        self.check_plugin_log('GsdBacklight: Got uevent', 1, 'gsd-power did not process uevent')
+        time.sleep(0.2)
+
+        brightness = obj_gsd_power_prop_iface.Get('org.gnome.SettingsDaemon.Power.Screen', 'Brightness')
+        self.assertEqual(80, brightness)
+
+    def test_brightness_step(self):
+        if self.skip_sysfs_backlight:
+            self.skipTest("sysfs backlight support required for test")
+
+        for l in self.plugin_startup_msgs:
+            if b'Step size for backlight is 5.' in l:
+                break
+        else:
+            self.fail('Step size is not 5')
+
+    def test_legacy_brightness_step(self):
+        if self.skip_sysfs_backlight:
+            self.skipTest("sysfs backlight support required for test")
+
+        for l in self.plugin_startup_msgs:
+            if b'Step size for backlight is 1.' in l:
+                break
+        else:
+            self.fail('Step size is not 1')
+
+    def test_legacy_brightness_rounding(self):
+        if self.skip_sysfs_backlight:
+            self.skipTest("sysfs backlight support required for test")
+
+        obj_gsd_power = self.session_bus_con.get_object(
+            'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
+        obj_gsd_power_prop_iface = dbus.Interface(obj_gsd_power, dbus.PROPERTIES_IFACE)
+
+        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 0)
+        time.sleep(0.6)
+        self.assertEqual(self.get_brightness(), 0)
+        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 10)
+        time.sleep(0.6)
+        self.assertEqual(self.get_brightness(), 2)
+        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 20)
+        time.sleep(0.6)
+        self.assertEqual(self.get_brightness(), 3)
+        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 25)
+        time.sleep(0.6)
+        self.assertEqual(self.get_brightness(), 4)
+        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 49)
+        time.sleep(0.6)
+        self.assertEqual(self.get_brightness(), 7)
+        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 50)
+        time.sleep(0.6)
+        self.assertEqual(self.get_brightness(), 8)
+        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 56)
+        time.sleep(0.6)
+        self.assertEqual(self.get_brightness(), 8)
+        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 57)
+        time.sleep(0.6)
+        self.assertEqual(self.get_brightness(), 9)
+        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 98)
+        time.sleep(0.6)
+        self.assertEqual(self.get_brightness(), 15)
+
+    def test_no_backlight(self):
+        '''Check that backlight brightness DBus api without a backlight'''
+
+        obj_gsd_power = self.session_bus_con.get_object(
+            'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
+        obj_gsd_power_props = dbus.Interface(obj_gsd_power, dbus.PROPERTIES_IFACE)
+        obj_gsd_power_screen = dbus.Interface(obj_gsd_power, 'org.gnome.SettingsDaemon.Power.Screen')
+
+        # We expect -1 to be returned
+        brightness = obj_gsd_power_props.Get('org.gnome.SettingsDaemon.Power.Screen', 'Brightness')
+        self.assertEqual(brightness, -1)
+
+        # Trying to set the brightness
+        with self.assertRaises(dbus.DBusException) as exc:
+            obj_gsd_power_props.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 1)
+
+        self.assertEqual(exc.exception.get_dbus_message(), 'No usable backlight could be found!')
+
+        with self.assertRaises(dbus.DBusException) as exc:
+            obj_gsd_power_screen.StepUp()
+
+        self.assertEqual(exc.exception.get_dbus_message(), 'No usable backlight could be found!')
+
+        with self.assertRaises(dbus.DBusException) as exc:
+            obj_gsd_power_screen.StepDown()
+
+        self.assertEqual(exc.exception.get_dbus_message(), 'No usable backlight could be found!')
+
+    def test_power_saver_on_low_battery(self):
+        '''Check that the power-saver profile gets held when low on battery'''
+
+        if not self.ppd:
+            self.skipTest("power-profiles-daemon dbusmock support is not available")
+
+        obj_props = dbus.Interface(self.obj_ppd, dbus.PROPERTIES_IFACE)
+
+        self.set_composite_battery_discharging()
+        time.sleep(0.5)
+        holds = obj_props.Get('net.hadess.PowerProfiles', 'ActiveProfileHolds')
+        self.assertEqual(len(holds), 0)
+
+        self.set_composite_battery_critical()
+        time.sleep(0.5)
+        holds = obj_props.Get('net.hadess.PowerProfiles', 'ActiveProfileHolds')
+        self.assertEqual(len(holds), 1)
+        self.assertEqual(holds[0]['Profile'], 'power-saver')
+        self.assertEqual(holds[0]['ApplicationId'], 'org.gnome.SettingsDaemon.Power')
+
+        self.set_composite_battery_discharging()
+        time.sleep(0.5)
+        holds = obj_props.Get('net.hadess.PowerProfiles', 'ActiveProfileHolds')
+        self.assertEqual(len(holds), 0)
+
+# avoid writing to stderr
+unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))
diff --git a/plugins/print-notifications/gsd-print-notifications-manager.c b/plugins/print-notifications/gsd-print-notifications-manager.c
new file mode 100644
index 0000000..4e0b3ab
--- /dev/null
+++ b/plugins/print-notifications/gsd-print-notifications-manager.c
@@ -0,0 +1,1725 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include "gnome-settings-profile.h"
+#include "gsd-print-notifications-manager.h"
+
+#define CUPS_DBUS_NAME      "org.cups.cupsd.Notifier"
+#define CUPS_DBUS_PATH      "/org/cups/cupsd/Notifier"
+#define CUPS_DBUS_INTERFACE "org.cups.cupsd.Notifier"
+
+#define RENEW_INTERVAL                   3500
+#define SUBSCRIPTION_DURATION            3600
+#define CONNECTING_TIMEOUT               60
+#define REASON_TIMEOUT                   15000
+#define CUPS_CONNECTION_TEST_INTERVAL    300
+#define CHECK_INTERVAL                   60 /* secs */
+#define AUTHENTICATION_CHECK_TIMEOUT     3
+
+#if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5)
+#define HAVE_CUPS_1_6 1
+#endif
+
+#ifndef HAVE_CUPS_1_6
+#define ippGetStatusCode(ipp) ipp->request.status.status_code
+#define ippGetInteger(attr, element) attr->values[element].integer
+#define ippGetString(attr, element, language) attr->values[element].string.text
+#define ippGetName(attr) attr->name
+#define ippGetCount(attr) attr->num_values
+#define ippGetBoolean(attr, index) attr->values[index].boolean
+
+static ipp_attribute_t *
+ippNextAttribute (ipp_t *ipp)
+{
+  if (!ipp || !ipp->current)
+    return (NULL);
+  return (ipp->current = ipp->current->next);
+}
+#endif
+
+struct _GsdPrintNotificationsManager
+{
+        GObject                       parent;
+
+        GDBusConnection              *cups_bus_connection;
+        gint                          subscription_id;
+        cups_dest_t                  *dests;
+        gint                          num_dests;
+        gboolean                      scp_handler_spawned;
+        GPid                          scp_handler_pid;
+        GList                        *timeouts;
+        GHashTable                   *printing_printers;
+        GList                        *active_notifications;
+        guint                         cups_connection_timeout_id;
+        guint                         check_source_id;
+        guint                         cups_dbus_subscription_id;
+        guint                         renew_source_id;
+        gint                          last_notify_sequence_number;
+        guint                         start_idle_id;
+        GList                        *held_jobs;
+};
+
+static void     gsd_print_notifications_manager_class_init  (GsdPrintNotificationsManagerClass *klass);
+static void     gsd_print_notifications_manager_init        (GsdPrintNotificationsManager      *print_notifications_manager);
+static void     gsd_print_notifications_manager_finalize    (GObject                           *object);
+static gboolean cups_connection_test                        (gpointer                           user_data);
+static gboolean process_new_notifications                   (gpointer                           user_data);
+
+G_DEFINE_TYPE (GsdPrintNotificationsManager, gsd_print_notifications_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+static const char *
+password_cb (const char *prompt,
+             http_t     *http,
+             const char *method,
+             const char *resource,
+             void       *user_data)
+{
+  return NULL;
+}
+
+static char *
+get_dest_attr (const char *dest_name,
+               const char *attr,
+               cups_dest_t *dests,
+               int          num_dests)
+{
+        cups_dest_t *dest;
+        const char  *value;
+        char        *ret;
+
+        if (dest_name == NULL)
+                return NULL;
+
+        ret = NULL;
+
+        dest = cupsGetDest (dest_name, NULL, num_dests, dests);
+        if (dest == NULL) {
+                g_debug ("Unable to find a printer named '%s'", dest_name);
+                goto out;
+        }
+
+        value = cupsGetOption (attr, dest->num_options, dest->options);
+        if (value == NULL) {
+                g_debug ("Unable to get %s for '%s'", attr, dest_name);
+                goto out;
+        }
+        ret = g_strdup (value);
+ out:
+        return ret;
+}
+
+static gboolean
+is_cupsbrowsed_dest (const char *name)
+{
+        const char  *val = NULL;
+        gboolean     is_cupsbrowsed = FALSE;
+        cups_dest_t *found_dest = NULL;
+
+        found_dest = cupsGetNamedDest (CUPS_HTTP_DEFAULT, name, NULL);
+        if (found_dest == NULL) {
+                goto out;
+        }
+
+        val = cupsGetOption ("cups-browsed", found_dest->num_options, found_dest->options);
+        if (val == NULL) {
+                goto out;
+        }
+
+        if (g_str_equal (val, "yes") || g_str_equal (val, "on") || g_str_equal (val, "true")) {
+                is_cupsbrowsed = TRUE;
+        }
+out:
+        if (found_dest != NULL) {
+                cupsFreeDests (1, found_dest);
+        }
+
+        return is_cupsbrowsed;
+}
+
+static gboolean
+is_local_dest (const char  *name,
+               cups_dest_t *dests,
+               int          num_dests)
+{
+        char        *type_str;
+        cups_ptype_t type;
+        gboolean     is_remote;
+
+        is_remote = TRUE;
+
+        type_str = get_dest_attr (name, "printer-type", dests, num_dests);
+        if (type_str == NULL) {
+                goto out;
+        }
+
+        type = atoi (type_str);
+        is_remote = type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT);
+        g_free (type_str);
+ out:
+        return !is_remote;
+}
+
+static gboolean
+server_is_local (const gchar *server_name)
+{
+        if (server_name != NULL &&
+            (g_ascii_strncasecmp (server_name, "localhost", 9) == 0 ||
+             g_ascii_strncasecmp (server_name, "127.0.0.1", 9) == 0 ||
+             g_ascii_strncasecmp (server_name, "::1", 3) == 0 ||
+             server_name[0] == '/')) {
+                return TRUE;
+        } else {
+                return FALSE;
+        }
+}
+
+static int
+strcmp0(const void *a, const void *b)
+{
+        return g_strcmp0 (*((gchar **) a), *((gchar **) b));
+}
+
+struct
+{
+        gchar *printer_name;
+        gchar *primary_text;
+        gchar *secondary_text;
+        guint  timeout_id;
+        GsdPrintNotificationsManager *manager;
+} typedef TimeoutData;
+
+struct
+{
+        gchar *printer_name;
+        gchar *reason;
+        NotifyNotification *notification;
+        gulong notification_close_id;
+        GsdPrintNotificationsManager *manager;
+} typedef ReasonData;
+
+struct
+{
+        guint  job_id;
+        gchar *printer_name;
+        guint  timeout_id;
+} typedef HeldJob;
+
+static void
+free_timeout_data (gpointer user_data)
+{
+        TimeoutData *data = (TimeoutData *) user_data;
+
+        if (data) {
+                g_free (data->printer_name);
+                g_free (data->primary_text);
+                g_free (data->secondary_text);
+                g_free (data);
+        }
+}
+
+static void
+free_reason_data (gpointer user_data)
+{
+        ReasonData *data = (ReasonData *) user_data;
+
+        if (data) {
+                if (data->notification_close_id > 0 &&
+                    g_signal_handler_is_connected (data->notification,
+                                                   data->notification_close_id))
+                        g_signal_handler_disconnect (data->notification, data->notification_close_id);
+
+                g_object_unref (data->notification);
+
+                g_free (data->printer_name);
+                g_free (data->reason);
+
+                g_free (data);
+        }
+}
+
+static void
+free_held_job (gpointer user_data)
+{
+        HeldJob *job = (HeldJob *) user_data;
+
+        if (job != NULL) {
+                g_free (job->printer_name);
+                g_free (job);
+        }
+}
+
+static void
+notification_closed_cb (NotifyNotification *notification,
+                        gpointer            user_data)
+{
+        ReasonData *data = (ReasonData *) user_data;
+
+        if (data) {
+                data->manager->active_notifications =
+                        g_list_remove (data->manager->active_notifications, data);
+
+                free_reason_data (data);
+        }
+}
+
+static gboolean
+show_notification (gpointer user_data)
+{
+        NotifyNotification *notification;
+        TimeoutData        *data = (TimeoutData *) user_data;
+        ReasonData         *reason_data;
+        GList              *tmp;
+
+        if (!data)
+                return FALSE;
+
+        notification = notify_notification_new (data->primary_text,
+                                                data->secondary_text,
+                                                "printer-symbolic");
+
+        notify_notification_set_app_name (notification, _("Printers"));
+        notify_notification_set_hint (notification,
+                                      "resident",
+                                      g_variant_new_boolean (TRUE));
+        notify_notification_set_timeout (notification, REASON_TIMEOUT);
+        notify_notification_set_hint_string (notification, "desktop-entry", "gnome-printers-panel");
+
+        reason_data = g_new0 (ReasonData, 1);
+        reason_data->printer_name = g_strdup (data->printer_name);
+        reason_data->reason = g_strdup ("connecting-to-device");
+        reason_data->notification = notification;
+        reason_data->manager = data->manager;
+
+        reason_data->notification_close_id =
+                g_signal_connect (notification,
+                                  "closed",
+                                  G_CALLBACK (notification_closed_cb),
+                                  reason_data);
+
+        reason_data->manager->active_notifications =
+                g_list_append (reason_data->manager->active_notifications, reason_data);
+
+        notify_notification_show (notification, NULL);
+
+        tmp = g_list_find (data->manager->timeouts, data);
+        if (tmp) {
+                data->manager->timeouts = g_list_remove_link (data->manager->timeouts, tmp);
+                g_list_free_full (tmp, free_timeout_data);
+        }
+
+        return FALSE;
+}
+
+static gboolean
+reason_is_blacklisted (const gchar *reason)
+{
+        if (g_str_equal (reason, "none"))
+                return TRUE;
+
+        if (g_str_equal (reason, "other"))
+                return TRUE;
+
+        if (g_str_equal (reason, "com.apple.print.recoverable"))
+                return TRUE;
+
+        /* https://bugzilla.redhat.com/show_bug.cgi?id=883401 */
+        if (g_str_has_prefix (reason, "cups-remote-"))
+                return TRUE;
+
+        /* https://bugzilla.redhat.com/show_bug.cgi?id=1207154 */
+        if (g_str_equal (reason, "cups-waiting-for-job-completed"))
+                return TRUE;
+
+        return FALSE;
+}
+
+static void
+on_cups_notification (GDBusConnection *connection,
+                      const char      *sender_name,
+                      const char      *object_path,
+                      const char      *interface_name,
+                      const char      *signal_name,
+                      GVariant        *parameters,
+                      gpointer         user_data)
+{
+        /* Ignore any signal starting with Server*. This has caused a message
+         * storm through ServerAudit messages in the past, see
+         *  https://gitlab.gnome.org/GNOME/gnome-settings-daemon/issues/62
+         */
+        if (!signal_name || (strncmp (signal_name, "Server", 6) == 0))
+                return;
+
+        process_new_notifications (user_data);
+}
+
+static gchar *
+get_statuses_second (guint i,
+                     const gchar *printer_name)
+{
+        gchar *status;
+
+        switch (i) {
+                case 0:
+                        /* Translators: The printer is low on toner (same as in system-config-printer) */
+                        status = g_strdup_printf (_("Printer “%s” is low on toner."), printer_name);
+                        break;
+                case 1:
+                        /* Translators: The printer has no toner left (same as in system-config-printer) */
+                        status = g_strdup_printf (_("Printer “%s” has no toner left."), printer_name);
+                        break;
+                case 2:
+                        /* Translators: The printer is in the process of connecting to a shared network output device (same as in system-config-printer) */
+                        status = g_strdup_printf (_("Printer “%s” may not be connected."), printer_name);
+                        break;
+                case 3:
+                        /* Translators: One or more covers on the printer are open (same as in system-config-printer) */
+                        status = g_strdup_printf (_("The cover is open on printer “%s”."), printer_name);
+                        break;
+                case 4:
+                        /* Translators: A filter or backend is not installed (same as in system-config-printer) */
+                        status = g_strdup_printf (_("There is a missing print filter for "
+                                                    "printer “%s”."), printer_name);
+                        break;
+                case 5:
+                        /* Translators: One or more doors on the printer are open (same as in system-config-printer) */
+                        status = g_strdup_printf (_("The door is open on printer “%s”."), printer_name);
+                        break;
+                case 6:
+                        /* Translators: "marker" is one color bin of the printer */
+                        status = g_strdup_printf (_("Printer “%s” is low on a marker supply."), printer_name);
+                        break;
+                case 7:
+                        /* Translators: "marker" is one color bin of the printer */
+                        status = g_strdup_printf (_("Printer “%s” is out of a marker supply."), printer_name);
+                        break;
+                case 8:
+                        /* Translators: At least one input tray is low on media (same as in system-config-printer) */
+                        status = g_strdup_printf (_("Printer “%s” is low on paper."), printer_name);
+                        break;
+                case 9:
+                        /* Translators: At least one input tray is empty (same as in system-config-printer) */
+                        status = g_strdup_printf (_("Printer “%s” is out of paper."), printer_name);
+                        break;
+                case 10:
+                        /* Translators: The printer is offline (same as in system-config-printer) */
+                        status = g_strdup_printf (_("Printer “%s” is currently off-line."), printer_name);
+                        break;
+                case 11:
+                        /* Translators: The printer has detected an error (same as in system-config-printer) */
+                        status = g_strdup_printf (_("There is a problem on printer “%s”."), printer_name);
+                        break;
+                default:
+                        g_assert_not_reached ();
+        }
+
+        return status;
+}
+
+static void
+authenticate_cb (NotifyNotification *notification,
+                 gchar              *action,
+                 gpointer            user_data)
+{
+        GAppInfo *app_info;
+        gboolean  ret;
+        GError   *error = NULL;
+        gchar    *commandline;
+        gchar    *printer_name = user_data;
+
+        if (g_strcmp0 (action, "default") == 0) {
+                notify_notification_close (notification, NULL);
+
+                commandline = g_strdup_printf (BINDIR "/gnome-control-center printers show-jobs %s", printer_name);
+                app_info = g_app_info_create_from_commandline (commandline,
+                                                               "gnome-control-center",
+                                                               G_APP_INFO_CREATE_SUPPORTS_STARTUP_NOTIFICATION,
+                                                               &error);
+                g_free (commandline);
+
+                if (app_info != NULL) {
+                        ret = g_app_info_launch (app_info,
+                                                 NULL,
+                                                 NULL,
+                                                 &error);
+
+                        if (!ret) {
+                                g_warning ("failed to launch gnome-control-center: %s", error->message);
+                                g_error_free (error);
+                        }
+
+                        g_object_unref (app_info);
+                } else {
+                        g_warning ("failed to create application info: %s", error->message);
+                        g_error_free (error);
+                }
+        }
+}
+
+static void
+unref_notification (NotifyNotification *notification,
+                    gpointer            data)
+{
+        g_object_unref (notification);
+}
+
+static gint
+check_job_for_authentication (gpointer userdata)
+{
+        GsdPrintNotificationsManager *manager = userdata;
+        ipp_attribute_t              *attr;
+        static gchar                 *requested_attributes[] = { "job-state-reasons", "job-hold-until", NULL };
+        gboolean                      needs_authentication = FALSE;
+        HeldJob                      *job;
+        gchar                        *primary_text;
+        gchar                        *secondary_text;
+        gchar                        *job_uri;
+        ipp_t                        *request, *response;
+        gint                          i;
+
+        if (manager->held_jobs != NULL) {
+                job = (HeldJob *) manager->held_jobs->data;
+
+                manager->held_jobs = g_list_delete_link (manager->held_jobs,
+                                                               manager->held_jobs);
+
+                request = ippNewRequest (IPP_GET_JOB_ATTRIBUTES);
+
+                job_uri = g_strdup_printf ("ipp://localhost/jobs/%u", job->job_id);
+                ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+                              "job-uri", NULL, job_uri);
+                g_free (job_uri);
+                ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+                              "requesting-user-name", NULL, cupsUser ());
+                ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+                               "requested-attributes", 2, NULL, (const char **) requested_attributes);
+
+                response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/");
+                if (response != NULL) {
+                        if (ippGetStatusCode (response) <= IPP_OK_CONFLICT) {
+                                if ((attr = ippFindAttribute (response, "job-state-reasons", IPP_TAG_ZERO)) != NULL) {
+                                        for (i = 0; i < ippGetCount (attr); i++) {
+                                                if (g_strcmp0 (ippGetString (attr, i, NULL), "cups-held-for-authentication") == 0) {
+                                                        needs_authentication = TRUE;
+                                                        break;
+                                                }
+                                        }
+                                }
+
+                                if (!needs_authentication && (attr = ippFindAttribute (response, "job-hold-until", IPP_TAG_ZERO)) != NULL) {
+                                        if (g_strcmp0 (ippGetString (attr, 0, NULL), "auth-info-required") == 0)
+                                                needs_authentication = TRUE;
+                                }
+                        }
+
+                        ippDelete (response);
+                }
+
+                if (needs_authentication) {
+                        NotifyNotification *notification;
+
+                        /* Translators: The printer has a job to print but the printer needs authentication to continue with the print */
+                        primary_text = g_strdup_printf (_("%s Requires Authentication"), job->printer_name);
+                        /* Translators: A printer needs credentials to continue printing a job */
+                        secondary_text = g_strdup_printf (_("Credentials required in order to print"));
+
+                        notification = notify_notification_new (primary_text,
+                                                                secondary_text,
+                                                                "printer-symbolic");
+                        notify_notification_set_app_name (notification, _("Printers"));
+                        notify_notification_set_hint_string (notification, "desktop-entry", "gnome-printers-panel");
+                        notify_notification_add_action (notification,
+                                                        "default",
+                                                        /* This is a default action so the label won't be shown */
+                                                        "Authenticate",
+                                                        authenticate_cb,
+                                                        g_strdup (job->printer_name), g_free);
+                        g_signal_connect (notification, "closed", G_CALLBACK (unref_notification), NULL);
+
+                        notify_notification_show (notification, NULL);
+
+                        g_free (primary_text);
+                        g_free (secondary_text);
+                }
+
+                free_held_job (job);
+        }
+
+        return G_SOURCE_REMOVE;
+}
+
+static void
+process_cups_notification (GsdPrintNotificationsManager *manager,
+                           const char                   *notify_subscribed_event,
+                           const char                   *notify_text,
+                           const char                   *notify_printer_uri,
+                           const char                   *printer_name,
+                           gint                          printer_state,
+                           const char                   *printer_state_reasons,
+                           gboolean                      printer_is_accepting_jobs,
+                           guint                         notify_job_id,
+                           gint                          job_state,
+                           const char                   *job_state_reasons,
+                           const char                   *job_name,
+                           gint                          job_impressions_completed)
+{
+        ipp_attribute_t *attr;
+        gboolean         my_job = FALSE;
+        gboolean         known_reason;
+        HeldJob         *held_job;
+        http_t          *http;
+        gchar           *primary_text = NULL;
+        gchar           *secondary_text = NULL;
+        gchar           *job_uri = NULL;
+        ipp_t           *request, *response;
+        static const char * const reasons[] = {
+                "toner-low",
+                "toner-empty",
+                "connecting-to-device",
+                "cover-open",
+                "cups-missing-filter",
+                "door-open",
+                "marker-supply-low",
+                "marker-supply-empty",
+                "media-low",
+                "media-empty",
+                "offline",
+                "other"};
+
+        static const char * statuses_first[] = {
+                /* Translators: The printer is low on toner (same as in system-config-printer) */
+                N_("Toner low"),
+                /* Translators: The printer has no toner left (same as in system-config-printer) */
+                N_("Toner empty"),
+                /* Translators: The printer is in the process of connecting to a shared network output device (same as in system-config-printer) */
+                N_("Not connected?"),
+                /* Translators: One or more covers on the printer are open (same as in system-config-printer) */
+                N_("Cover open"),
+                /* Translators: A filter or backend is not installed (same as in system-config-printer) */
+                N_("Printer configuration error"),
+                /* Translators: One or more doors on the printer are open (same as in system-config-printer) */
+                N_("Door open"),
+                /* Translators: "marker" is one color bin of the printer */
+                N_("Marker supply low"),
+                /* Translators: "marker" is one color bin of the printer */
+                N_("Out of a marker supply"),
+                /* Translators: At least one input tray is low on media (same as in system-config-printer) */
+                N_("Paper low"),
+                /* Translators: At least one input tray is empty (same as in system-config-printer) */
+                N_("Out of paper"),
+                /* Translators: The printer is offline (same as in system-config-printer) */
+                N_("Printer off-line"),
+                /* Translators: The printer has detected an error (same as in system-config-printer) */
+                N_("Printer error") };
+
+        if (g_strcmp0 (notify_subscribed_event, "printer-added") != 0 &&
+            g_strcmp0 (notify_subscribed_event, "printer-deleted") != 0 &&
+            g_strcmp0 (notify_subscribed_event, "printer-state-changed") != 0 &&
+            g_strcmp0 (notify_subscribed_event, "job-completed") != 0 &&
+            g_strcmp0 (notify_subscribed_event, "job-state-changed") != 0 &&
+            g_strcmp0 (notify_subscribed_event, "job-created") != 0)
+                return;
+
+        if (notify_job_id > 0) {
+                if ((http = httpConnectEncrypt (cupsServer (), ippPort (),
+                                                cupsEncryption ())) == NULL) {
+                        g_debug ("Connection to CUPS server \'%s\' failed.", cupsServer ());
+                } else {
+                        job_uri = g_strdup_printf ("ipp://localhost/jobs/%d", notify_job_id);
+
+                        request = ippNewRequest (IPP_GET_JOB_ATTRIBUTES);
+                        ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+                                      "job-uri", NULL, job_uri);
+                        ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+                                     "requesting-user-name", NULL, cupsUser ());
+                        ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+                                     "requested-attributes", NULL, "job-originating-user-name");
+                        response = cupsDoRequest (http, request, "/");
+
+                        if (response) {
+                                if (ippGetStatusCode (response) <= IPP_OK_CONFLICT &&
+                                    (attr = ippFindAttribute(response, "job-originating-user-name",
+                                                             IPP_TAG_NAME))) {
+                                        if (g_strcmp0 (ippGetString (attr, 0, NULL), cupsUser ()) == 0)
+                                                my_job = TRUE;
+                                }
+                                ippDelete(response);
+                        }
+                        g_free (job_uri);
+                        httpClose (http);
+                }
+        }
+
+        if (g_strcmp0 (notify_subscribed_event, "printer-added") == 0) {
+                cupsFreeDests (manager->num_dests, manager->dests);
+                manager->num_dests = cupsGetDests (&manager->dests);
+
+                if (is_local_dest (printer_name,
+                                   manager->dests,
+                                   manager->num_dests) &&
+                    !is_cupsbrowsed_dest (printer_name)) {
+                        /* Translators: New printer has been added */
+                        primary_text = g_strdup (_("Printer added"));
+                        secondary_text = g_strdup (printer_name);
+                }
+        } else if (g_strcmp0 (notify_subscribed_event, "printer-deleted") == 0) {
+                cupsFreeDests (manager->num_dests, manager->dests);
+                manager->num_dests = cupsGetDests (&manager->dests);
+        } else if (g_strcmp0 (notify_subscribed_event, "job-completed") == 0 && my_job) {
+                g_hash_table_remove (manager->printing_printers,
+                                     printer_name);
+
+                switch (job_state) {
+                        case IPP_JOB_PENDING:
+                        case IPP_JOB_HELD:
+                        case IPP_JOB_PROCESSING:
+                                break;
+                        case IPP_JOB_STOPPED:
+                                /* Translators: A print job has been stopped */
+                                primary_text = g_strdup (C_("print job state", "Printing stopped"));
+                                /* Translators: "print-job xy" on a printer */
+                                secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name);
+                                break;
+                        case IPP_JOB_CANCELED:
+                                /* Translators: A print job has been canceled */
+                                primary_text = g_strdup (C_("print job state", "Printing canceled"));
+                                /* Translators: "print-job xy" on a printer */
+                                secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name);
+                                break;
+                        case IPP_JOB_ABORTED:
+                                /* Translators: A print job has been aborted */
+                                primary_text = g_strdup (C_("print job state", "Printing aborted"));
+                                /* Translators: "print-job xy" on a printer */
+                                secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name);
+                                break;
+                        case IPP_JOB_COMPLETED:
+                                /* Translators: A print job has been completed */
+                                primary_text = g_strdup (C_("print job state", "Printing completed"));
+                                /* Translators: "print-job xy" on a printer */
+                                secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name);
+                                break;
+                }
+        } else if (g_strcmp0 (notify_subscribed_event, "job-state-changed") == 0 && my_job) {
+                switch (job_state) {
+                        case IPP_JOB_PROCESSING:
+                                g_hash_table_insert (manager->printing_printers,
+                                                     g_strdup (printer_name), NULL);
+
+                                /* Translators: A job is printing */
+                                primary_text = g_strdup (C_("print job state", "Printing"));
+                                /* Translators: "print-job xy" on a printer */
+                                secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name);
+                                break;
+                        case IPP_JOB_STOPPED:
+                                g_hash_table_remove (manager->printing_printers,
+                                                     printer_name);
+                                /* Translators: A print job has been stopped */
+                                primary_text = g_strdup (C_("print job state", "Printing stopped"));
+                                /* Translators: "print-job xy" on a printer */
+                                secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name);
+                                break;
+                        case IPP_JOB_CANCELED:
+                                g_hash_table_remove (manager->printing_printers,
+                                                     printer_name);
+                                /* Translators: A print job has been canceled */
+                                primary_text = g_strdup (C_("print job state", "Printing canceled"));
+                                /* Translators: "print-job xy" on a printer */
+                                secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name);
+                                break;
+                        case IPP_JOB_ABORTED:
+                                g_hash_table_remove (manager->printing_printers,
+                                                     printer_name);
+                                /* Translators: A print job has been aborted */
+                                primary_text = g_strdup (C_("print job state", "Printing aborted"));
+                                /* Translators: "print-job xy" on a printer */
+                                secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name);
+                                break;
+                        case IPP_JOB_COMPLETED:
+                                g_hash_table_remove (manager->printing_printers,
+                                                     printer_name);
+                                /* Translators: A print job has been completed */
+                                primary_text = g_strdup (C_("print job state", "Printing completed"));
+                                /* Translators: "print-job xy" on a printer */
+                                secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name);
+                                break;
+                        case IPP_JOB_HELD:
+                                held_job = g_new (HeldJob, 1);
+                                held_job->job_id = notify_job_id;
+                                held_job->printer_name = g_strdup (printer_name);
+                                /* CUPS takes sometime to change the "job-state-reasons" to "cups-held-for-authentication"
+                                   after the job changes job-state to "held" state but this change is not signalized
+                                   by any event so we just check the job-state-reason (or job-hold-until) after some timeout */
+                                held_job->timeout_id = g_timeout_add_seconds (AUTHENTICATION_CHECK_TIMEOUT, check_job_for_authentication, manager);
+
+                                manager->held_jobs = g_list_append (manager->held_jobs, held_job);
+                                break;
+                        default:
+                                break;
+                }
+        } else if (g_strcmp0 (notify_subscribed_event, "job-created") == 0 && my_job) {
+                if (job_state == IPP_JOB_PROCESSING) {
+                        g_hash_table_insert (manager->printing_printers,
+                                             g_strdup (printer_name), NULL);
+
+                        /* Translators: A job is printing */
+                        primary_text = g_strdup (C_("print job state", "Printing"));
+                        /* Translators: "print-job xy" on a printer */
+                        secondary_text = g_strdup_printf (C_("print job", "“%s” on %s"), job_name, printer_name);
+                }
+        } else if (g_strcmp0 (notify_subscribed_event, "printer-state-changed") == 0) {
+                cups_dest_t  *dest = NULL;
+                const gchar  *tmp_printer_state_reasons = NULL;
+                GSList       *added_reasons = NULL;
+                GSList       *tmp_list = NULL;
+                GList        *tmp;
+                gchar       **old_state_reasons = NULL;
+                gchar       **new_state_reasons = NULL;
+                gint          i, j;
+
+                /* Remove timeout which shows notification about possible disconnection of printer
+                 * if "connecting-to-device" has vanished.
+                 */
+                if (printer_state_reasons == NULL ||
+                    g_strrstr (printer_state_reasons, "connecting-to-device") == NULL) {
+                        TimeoutData *data;
+
+                        for (tmp = manager->timeouts; tmp; tmp = g_list_next (tmp)) {
+                                data = (TimeoutData *) tmp->data;
+                                if (g_strcmp0 (printer_name, data->printer_name) == 0) {
+                                        g_source_remove (data->timeout_id);
+                                        manager->timeouts = g_list_remove_link (manager->timeouts, tmp);
+                                        g_list_free_full (tmp, free_timeout_data);
+                                        break;
+                                }
+                        }
+                }
+
+                for (tmp = manager->active_notifications; tmp; tmp = g_list_next (tmp)) {
+                        ReasonData *reason_data = (ReasonData *) tmp->data;
+                        GList      *remove_list;
+
+                        if (printer_state_reasons == NULL ||
+                            (g_strcmp0 (printer_name, reason_data->printer_name) == 0 &&
+                             g_strrstr (printer_state_reasons, reason_data->reason) == NULL)) {
+
+                                if (reason_data->notification_close_id > 0 &&
+                                    g_signal_handler_is_connected (reason_data->notification,
+                                                                   reason_data->notification_close_id)) {
+                                        g_signal_handler_disconnect (reason_data->notification,
+                                                                     reason_data->notification_close_id);
+                                        reason_data->notification_close_id = 0;
+                                }
+
+                                notify_notification_close (reason_data->notification, NULL);
+
+                                remove_list = tmp;
+                                tmp = g_list_next (tmp);
+                                manager->active_notifications =
+                                        g_list_remove_link (manager->active_notifications, remove_list);
+
+                                g_list_free_full (remove_list, free_reason_data);
+                        }
+                }
+
+                /* Check whether we are printing on this printer right now. */
+                if (g_hash_table_lookup_extended (manager->printing_printers, printer_name, NULL, NULL)) {
+                        dest = cupsGetDest (printer_name,
+                                            NULL,
+                                            manager->num_dests,
+                                            manager->dests);
+                        if (dest)
+                                tmp_printer_state_reasons = cupsGetOption ("printer-state-reasons",
+                                                                           dest->num_options,
+                                                                           dest->options);
+
+                        if (tmp_printer_state_reasons)
+                                old_state_reasons = g_strsplit (tmp_printer_state_reasons, ",", -1);
+
+                        cupsFreeDests (manager->num_dests, manager->dests);
+                        manager->num_dests = cupsGetDests (&manager->dests);
+
+                        dest = cupsGetDest (printer_name,
+                                            NULL,
+                                            manager->num_dests,
+                                            manager->dests);
+                        if (dest)
+                                tmp_printer_state_reasons = cupsGetOption ("printer-state-reasons",
+                                                                           dest->num_options,
+                                                                           dest->options);
+
+                        if (tmp_printer_state_reasons)
+                                new_state_reasons = g_strsplit (tmp_printer_state_reasons, ",", -1);
+
+                        if (new_state_reasons)
+                                qsort (new_state_reasons,
+                                       g_strv_length (new_state_reasons),
+                                       sizeof (gchar *),
+                                       strcmp0);
+
+                        if (old_state_reasons) {
+                                qsort (old_state_reasons,
+                                       g_strv_length (old_state_reasons),
+                                       sizeof (gchar *),
+                                       strcmp0);
+
+                                j = 0;
+                                for (i = 0; new_state_reasons && i < g_strv_length (new_state_reasons); i++) {
+                                        while (old_state_reasons[j] &&
+                                               g_strcmp0 (old_state_reasons[j], new_state_reasons[i]) < 0)
+                                                j++;
+
+                                        if (old_state_reasons[j] == NULL ||
+                                            g_strcmp0 (old_state_reasons[j], new_state_reasons[i]) != 0)
+                                                added_reasons = g_slist_append (added_reasons,
+                                                                                new_state_reasons[i]);
+                                }
+                        } else {
+                                for (i = 0; new_state_reasons && i < g_strv_length (new_state_reasons); i++) {
+                                        added_reasons = g_slist_append (added_reasons,
+                                                                        new_state_reasons[i]);
+                                }
+                        }
+
+                        for (tmp_list = added_reasons; tmp_list; tmp_list = tmp_list->next) {
+                                gchar *data = (gchar *) tmp_list->data;
+                                known_reason = FALSE;
+                                for (j = 0; j < G_N_ELEMENTS (reasons); j++) {
+                                        if (strncmp (data,
+                                                     reasons[j],
+                                                     strlen (reasons[j])) == 0) {
+                                                NotifyNotification *notification;
+                                                known_reason = TRUE;
+
+                                                if (g_strcmp0 (reasons[j], "connecting-to-device") == 0) {
+                                                        TimeoutData *data;
+
+                                                        data = g_new0 (TimeoutData, 1);
+                                                        data->printer_name = g_strdup (printer_name);
+                                                        data->primary_text = g_strdup ( _(statuses_first[j]));
+                                                        data->secondary_text = get_statuses_second (j, printer_name);
+                                                        data->manager = manager;
+
+                                                        data->timeout_id = g_timeout_add_seconds (CONNECTING_TIMEOUT, show_notification, data);
+                                                        g_source_set_name_by_id (data->timeout_id, "[gnome-settings-daemon] show_notification");
+                                                        manager->timeouts = g_list_append (manager->timeouts, data);
+                                                } else {
+                                                        ReasonData *reason_data;
+                                                        gchar *second_row = get_statuses_second (j, printer_name);
+
+                                                        notification = notify_notification_new ( _(statuses_first[j]),
+                                                                                                second_row,
+                                                                                                "printer-symbolic");
+                                                        notify_notification_set_app_name (notification, _("Printers"));
+                                                        notify_notification_set_hint_string (notification, "desktop-entry", "gnome-printers-panel");
+                                                        notify_notification_set_hint (notification,
+                                                                                      "resident",
+                                                                                      g_variant_new_boolean (TRUE));
+                                                        notify_notification_set_timeout (notification, REASON_TIMEOUT);
+
+                                                        reason_data = g_new0 (ReasonData, 1);
+                                                        reason_data->printer_name = g_strdup (printer_name);
+                                                        reason_data->reason = g_strdup (reasons[j]);
+                                                        reason_data->notification = notification;
+                                                        reason_data->manager = manager;
+
+                                                        reason_data->notification_close_id =
+                                                                g_signal_connect (notification,
+                                                                                  "closed",
+                                                                                  G_CALLBACK (notification_closed_cb),
+                                                                                  reason_data);
+
+                                                        manager->active_notifications =
+                                                                g_list_append (manager->active_notifications, reason_data);
+
+                                                        notify_notification_show (notification, NULL);
+
+                                                        g_free (second_row);
+                                                }
+                                        }
+                                }
+
+                                if (!known_reason &&
+                                    !reason_is_blacklisted (data)) {
+                                        NotifyNotification *notification;
+                                        ReasonData         *reason_data;
+                                        gchar              *first_row;
+                                        gchar              *second_row;
+                                        gchar              *text = NULL;
+                                        gchar              *ppd_file_name;
+                                        ppd_file_t         *ppd_file;
+                                        char                buffer[8192];
+
+                                        ppd_file_name = g_strdup (cupsGetPPD (printer_name));
+                                        if (ppd_file_name) {
+                                                ppd_file = ppdOpenFile (ppd_file_name);
+                                                if (ppd_file) {
+                                                        gchar **tmpv;
+                                                        static const char * const schemes[] = {
+                                                                "text", "http", "help", "file"
+                                                        };
+
+                                                        tmpv = g_new0 (gchar *, G_N_ELEMENTS (schemes) + 1);
+                                                        i = 0;
+                                                        for (j = 0; j < G_N_ELEMENTS (schemes); j++) {
+                                                                if (ppdLocalizeIPPReason (ppd_file, data, schemes[j], buffer, sizeof (buffer))) {
+                                                                        tmpv[i++] = g_strdup (buffer);
+                                                                }
+                                                        }
+
+                                                        if (i > 0)
+                                                                text = g_strjoinv (", ", tmpv);
+                                                        g_strfreev (tmpv);
+
+                                                        ppdClose (ppd_file);
+                                                }
+
+                                                g_unlink (ppd_file_name);
+                                                g_free (ppd_file_name);
+                                        }
+
+
+                                        if (g_str_has_suffix (data, "-report"))
+                                                /* Translators: This is a title of a report notification for a printer */
+                                                first_row = g_strdup (_("Printer report"));
+                                        else if (g_str_has_suffix (data, "-warning"))
+                                                /* Translators: This is a title of a warning notification for a printer */
+                                                first_row = g_strdup (_("Printer warning"));
+                                        else
+                                                /* Translators: This is a title of an error notification for a printer */
+                                                first_row = g_strdup (_("Printer error"));
+
+
+                                        if (text == NULL)
+                                                text = g_strdup (data);
+
+                                        /* Translators: "Printer 'MyPrinterName': 'Description of the report/warning/error from a PPD file'." */
+                                        second_row = g_strdup_printf (_("Printer “%s”: “%s”."), printer_name, text);
+                                        g_free (text);
+
+
+                                        notification = notify_notification_new (first_row,
+                                                                                second_row,
+                                                                                "printer-symbolic");
+                                        notify_notification_set_app_name (notification, _("Printers"));
+                                        notify_notification_set_hint_string (notification, "desktop-entry", "gnome-printers-panel");
+                                        notify_notification_set_hint (notification,
+                                                                      "resident",
+                                                                      g_variant_new_boolean (TRUE));
+                                        notify_notification_set_timeout (notification, REASON_TIMEOUT);
+
+                                        reason_data = g_new0 (ReasonData, 1);
+                                        reason_data->printer_name = g_strdup (printer_name);
+                                        reason_data->reason = g_strdup (data);
+                                        reason_data->notification = notification;
+                                        reason_data->manager = manager;
+
+                                        reason_data->notification_close_id =
+                                                g_signal_connect (notification,
+                                                                  "closed",
+                                                                  G_CALLBACK (notification_closed_cb),
+                                                                  reason_data);
+
+                                        manager->active_notifications =
+                                                g_list_append (manager->active_notifications, reason_data);
+
+                                        notify_notification_show (notification, NULL);
+
+                                        g_free (first_row);
+                                        g_free (second_row);
+                                }
+                        }
+                        g_slist_free (added_reasons);
+                }
+
+                if (new_state_reasons)
+                        g_strfreev (new_state_reasons);
+
+                if (old_state_reasons)
+                        g_strfreev (old_state_reasons);
+        }
+
+
+        if (primary_text) {
+                NotifyNotification *notification;
+                notification = notify_notification_new (primary_text,
+                                                        secondary_text,
+                                                        "printer-symbolic");
+                notify_notification_set_app_name (notification, _("Printers"));
+                notify_notification_set_hint_string (notification, "desktop-entry", "gnome-printers-panel");
+                notify_notification_set_hint (notification, "transient", g_variant_new_boolean (TRUE));
+                notify_notification_show (notification, NULL);
+                g_object_unref (notification);
+                g_free (primary_text);
+                g_free (secondary_text);
+        }
+}
+
+static gboolean
+process_new_notifications (gpointer user_data)
+{
+        GsdPrintNotificationsManager  *manager = (GsdPrintNotificationsManager *) user_data;
+        ipp_attribute_t               *attr;
+        const gchar                   *notify_subscribed_event = NULL;
+        const gchar                   *printer_name = NULL;
+        const gchar                   *notify_text = NULL;
+        const gchar                   *notify_printer_uri = NULL;
+        gchar                         *job_state_reasons = NULL;
+        const gchar                   *job_name = NULL;
+        const char                    *attr_name;
+        gboolean                       printer_is_accepting_jobs = FALSE;
+        gchar                         *printer_state_reasons = NULL;
+        gchar                        **reasons;
+        guint                          notify_job_id = 0;
+        ipp_t                         *request;
+        ipp_t                         *response;
+        gint                           printer_state = -1;
+        gint                           job_state = -1;
+        gint                           job_impressions_completed = -1;
+        gint                           notify_sequence_number = -1;
+        gint                           i;
+
+        request = ippNewRequest (IPP_GET_NOTIFICATIONS);
+
+        ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+                      "requesting-user-name", NULL, cupsUser ());
+
+        ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
+                       "notify-subscription-ids", manager->subscription_id);
+
+        ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
+                      "/printers/");
+
+        ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL,
+                      "/jobs/");
+
+        ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
+                       "notify-sequence-numbers",
+                       manager->last_notify_sequence_number + 1);
+
+
+        response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/");
+
+
+        for (attr = ippFindAttribute (response, "notify-sequence-number", IPP_TAG_INTEGER);
+             attr != NULL;
+             attr = ippNextAttribute (response)) {
+
+                attr_name = ippGetName (attr);
+                if (g_strcmp0 (attr_name, "notify-sequence-number") == 0) {
+                        notify_sequence_number = ippGetInteger (attr, 0);
+
+                        if (notify_sequence_number > manager->last_notify_sequence_number)
+                                manager->last_notify_sequence_number = notify_sequence_number;
+
+                        if (notify_subscribed_event != NULL) {
+                                process_cups_notification (manager,
+                                                           notify_subscribed_event,
+                                                           notify_text,
+                                                           notify_printer_uri,
+                                                           printer_name,
+                                                           printer_state,
+                                                           printer_state_reasons,
+                                                           printer_is_accepting_jobs,
+                                                           notify_job_id,
+                                                           job_state,
+                                                           job_state_reasons,
+                                                           job_name,
+                                                           job_impressions_completed);
+
+                                g_clear_pointer (&printer_state_reasons, g_free);
+                                g_clear_pointer (&job_state_reasons, g_free);
+                        }
+
+                        notify_subscribed_event = NULL;
+                        notify_text = NULL;
+                        notify_printer_uri = NULL;
+                        printer_name = NULL;
+                        printer_state = -1;
+                        printer_state_reasons = NULL;
+                        printer_is_accepting_jobs = FALSE;
+                        notify_job_id = 0;
+                        job_state = -1;
+                        job_state_reasons = NULL;
+                        job_name = NULL;
+                        job_impressions_completed = -1;
+                } else if (g_strcmp0 (attr_name, "notify-subscribed-event") == 0) {
+                        notify_subscribed_event = ippGetString (attr, 0, NULL);
+                } else if (g_strcmp0 (attr_name, "notify-text") == 0) {
+                        notify_text = ippGetString (attr, 0, NULL);
+                } else if (g_strcmp0 (attr_name, "notify-printer-uri") == 0) {
+                        notify_printer_uri = ippGetString (attr, 0, NULL);
+                } else if (g_strcmp0 (attr_name, "printer-name") == 0) {
+                        printer_name = ippGetString (attr, 0, NULL);
+                } else if (g_strcmp0 (attr_name, "printer-state") == 0) {
+                        printer_state = ippGetInteger (attr, 0);
+                } else if (g_strcmp0 (attr_name, "printer-state-reasons") == 0) {
+                        reasons = g_new0 (gchar *, ippGetCount (attr) + 1);
+                        for (i = 0; i < ippGetCount (attr); i++)
+                                reasons[i] = g_strdup (ippGetString (attr, i, NULL));
+                        printer_state_reasons = g_strjoinv (",", reasons);
+                        g_strfreev (reasons);
+                } else if (g_strcmp0 (attr_name, "printer-is-accepting-jobs") == 0) {
+                        printer_is_accepting_jobs = ippGetBoolean (attr, 0);
+                } else if (g_strcmp0 (attr_name, "notify-job-id") == 0) {
+                        notify_job_id = ippGetInteger (attr, 0);
+                } else if (g_strcmp0 (attr_name, "job-state") == 0) {
+                        job_state = ippGetInteger (attr, 0);
+                } else if (g_strcmp0 (attr_name, "job-state-reasons") == 0) {
+                        reasons = g_new0 (gchar *, ippGetCount (attr) + 1);
+                        for (i = 0; i < ippGetCount (attr); i++)
+                                reasons[i] = g_strdup (ippGetString (attr, i, NULL));
+                        job_state_reasons = g_strjoinv (",", reasons);
+                        g_strfreev (reasons);
+                } else if (g_strcmp0 (attr_name, "job-name") == 0) {
+                        job_name = ippGetString (attr, 0, NULL);
+                } else if (g_strcmp0 (attr_name, "job-impressions-completed") == 0) {
+                        job_impressions_completed = ippGetInteger (attr, 0);
+                }
+        }
+
+        if (notify_subscribed_event != NULL) {
+                process_cups_notification (manager,
+                                           notify_subscribed_event,
+                                           notify_text,
+                                           notify_printer_uri,
+                                           printer_name,
+                                           printer_state,
+                                           printer_state_reasons,
+                                           printer_is_accepting_jobs,
+                                           notify_job_id,
+                                           job_state,
+                                           job_state_reasons,
+                                           job_name,
+                                           job_impressions_completed);
+
+                g_clear_pointer (&printer_state_reasons, g_free);
+                g_clear_pointer (&job_state_reasons, g_free);
+        }
+
+        if (response != NULL)
+                ippDelete (response);
+
+        return TRUE;
+}
+
+static void
+scp_handler (GsdPrintNotificationsManager *manager,
+             gboolean                      start)
+{
+        if (start) {
+                GError *error = NULL;
+                char *args[2];
+
+                if (manager->scp_handler_spawned)
+                        return;
+
+                args[0] = LIBEXECDIR "/gsd-printer";
+                args[1] = NULL;
+
+                g_spawn_async (NULL, args, NULL,
+                               0, NULL, NULL,
+                               &manager->scp_handler_pid, &error);
+
+                manager->scp_handler_spawned = (error == NULL);
+
+                if (error) {
+                        g_warning ("Could not execute system-config-printer-udev handler: %s",
+                                   error->message);
+                        g_error_free (error);
+                }
+        } else if (manager->scp_handler_spawned) {
+                kill (manager->scp_handler_pid, SIGHUP);
+                g_spawn_close_pid (manager->scp_handler_pid);
+                manager->scp_handler_spawned = FALSE;
+        }
+}
+
+static void
+cancel_subscription (gint id)
+{
+        http_t *http;
+        ipp_t  *request;
+
+        if (id >= 0 &&
+            ((http = httpConnectEncrypt (cupsServer (), ippPort (),
+                                        cupsEncryption ())) != NULL)) {
+                request = ippNewRequest (IPP_CANCEL_SUBSCRIPTION);
+                ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+                             "printer-uri", NULL, "/");
+                ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+                             "requesting-user-name", NULL, cupsUser ());
+                ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
+                              "notify-subscription-id", id);
+                ippDelete (cupsDoRequest (http, request, "/"));
+                httpClose (http);
+        }
+}
+
+static gboolean
+renew_subscription (gpointer data)
+{
+        GsdPrintNotificationsManager *manager = (GsdPrintNotificationsManager *) data;
+        ipp_attribute_t              *attr = NULL;
+        http_t                       *http;
+        ipp_t                        *request;
+        ipp_t                        *response;
+        gint                          num_events = 7;
+        static const char * const events[] = {
+                "job-created",
+                "job-completed",
+                "job-state-changed",
+                "job-state",
+                "printer-added",
+                "printer-deleted",
+                "printer-state-changed"};
+
+        if ((http = httpConnectEncrypt (cupsServer (), ippPort (),
+                                        cupsEncryption ())) == NULL) {
+                g_debug ("Connection to CUPS server \'%s\' failed.", cupsServer ());
+        } else {
+                if (manager->subscription_id >= 0) {
+                        request = ippNewRequest (IPP_RENEW_SUBSCRIPTION);
+                        ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+                                     "printer-uri", NULL, "/");
+                        ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+                                     "requesting-user-name", NULL, cupsUser ());
+                        ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
+                                      "notify-subscription-id", manager->subscription_id);
+                        ippAddInteger (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER,
+                                      "notify-lease-duration", SUBSCRIPTION_DURATION);
+                        ippDelete (cupsDoRequest (http, request, "/"));
+                } else {
+                        request = ippNewRequest (IPP_CREATE_PRINTER_SUBSCRIPTION);
+                        ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+                                      "printer-uri", NULL,
+                                      "/");
+                        ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+                                      "requesting-user-name", NULL, cupsUser ());
+                        ippAddStrings (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD,
+                                       "notify-events", num_events, NULL, events);
+                        ippAddString (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD,
+                                      "notify-pull-method", NULL, "ippget");
+                        if (server_is_local (cupsServer ())) {
+                                ippAddString (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI,
+                                              "notify-recipient-uri", NULL, "dbus://");
+                        }
+                        ippAddInteger (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER,
+                                       "notify-lease-duration", SUBSCRIPTION_DURATION);
+                        response = cupsDoRequest (http, request, "/");
+
+                        if (response != NULL && ippGetStatusCode (response) <= IPP_OK_CONFLICT) {
+                                if ((attr = ippFindAttribute (response, "notify-subscription-id",
+                                                              IPP_TAG_INTEGER)) == NULL)
+                                        g_debug ("No notify-subscription-id in response!\n");
+                                else
+                                        manager->subscription_id = ippGetInteger (attr, 0);
+                        }
+
+                        if (response)
+                                ippDelete (response);
+                }
+                httpClose (http);
+        }
+        return TRUE;
+}
+
+static void
+renew_subscription_with_connection_test_cb (GObject      *source_object,
+                                            GAsyncResult *res,
+                                            gpointer      user_data)
+{
+        GSocketConnection *connection;
+        GError            *error = NULL;
+
+        connection = g_socket_client_connect_to_host_finish (G_SOCKET_CLIENT (source_object),
+                                                             res,
+                                                             &error);
+
+        if (connection) {
+                g_debug ("Test connection to CUPS server \'%s:%d\' succeeded.", cupsServer (), ippPort ());
+
+                g_io_stream_close (G_IO_STREAM (connection), NULL, NULL);
+                g_object_unref (connection);
+
+                renew_subscription (user_data);
+        } else {
+                g_debug ("Test connection to CUPS server \'%s:%d\' failed.", cupsServer (), ippPort ());
+        }
+}
+
+static gboolean
+renew_subscription_with_connection_test (gpointer user_data)
+{
+        GSocketClient *client;
+        gchar         *address;
+        int            port;
+
+        port = ippPort ();
+
+        address = g_strdup_printf ("%s:%d", cupsServer (), port);
+
+        if (address && address[0] != '/') {
+                client = g_socket_client_new ();
+
+                g_debug ("Initiating test connection to CUPS server \'%s:%d\'.", cupsServer (), port);
+
+                g_socket_client_connect_to_host_async (client,
+                                                       address,
+                                                       port,
+                                                       NULL,
+                                                       renew_subscription_with_connection_test_cb,
+                                                       user_data);
+
+                g_object_unref (client);
+        } else {
+                renew_subscription (user_data);
+        }
+
+        g_free (address);
+
+        return TRUE;
+}
+
+static void
+renew_subscription_timeout_enable (GsdPrintNotificationsManager *manager,
+                                   gboolean                      enable,
+                                   gboolean                      with_connection_test)
+{
+        if (manager->renew_source_id > 0)
+                g_source_remove (manager->renew_source_id);
+
+        if (enable) {
+                renew_subscription (manager);
+                if (with_connection_test) {
+                        manager->renew_source_id =
+                                g_timeout_add_seconds (RENEW_INTERVAL,
+                                                       renew_subscription_with_connection_test,
+                                                       manager);
+                        g_source_set_name_by_id (manager->renew_source_id, "[gnome-settings-daemon] renew_subscription_with_connection_test");
+                } else {
+                        manager->renew_source_id =
+                                g_timeout_add_seconds (RENEW_INTERVAL,
+                                                       renew_subscription,
+                                                       manager);
+                        g_source_set_name_by_id (manager->renew_source_id, "[gnome-settings-daemon] renew_subscription");
+                }
+        } else {
+                manager->renew_source_id = 0;
+        }
+}
+
+static void
+cups_connection_test_cb (GObject      *source_object,
+                         GAsyncResult *res,
+                         gpointer      user_data)
+{
+        GsdPrintNotificationsManager *manager = (GsdPrintNotificationsManager *) user_data;
+        GSocketConnection            *connection;
+        GError                       *error = NULL;
+
+        connection = g_socket_client_connect_to_host_finish (G_SOCKET_CLIENT (source_object),
+                                                             res,
+                                                             &error);
+
+        if (connection) {
+                g_debug ("Test connection to CUPS server \'%s:%d\' succeeded.", cupsServer (), ippPort ());
+
+                g_io_stream_close (G_IO_STREAM (connection), NULL, NULL);
+                g_object_unref (connection);
+
+                manager->num_dests = cupsGetDests (&manager->dests);
+                g_debug ("Got dests from remote CUPS server.");
+
+                renew_subscription_timeout_enable (manager, TRUE, TRUE);
+                manager->check_source_id = g_timeout_add_seconds (CHECK_INTERVAL, process_new_notifications, manager);
+                g_source_set_name_by_id (manager->check_source_id, "[gnome-settings-daemon] process_new_notifications");
+        } else {
+                g_debug ("Test connection to CUPS server \'%s:%d\' failed.", cupsServer (), ippPort ());
+                if (manager->cups_connection_timeout_id == 0) {
+                        manager->cups_connection_timeout_id =
+                                g_timeout_add_seconds (CUPS_CONNECTION_TEST_INTERVAL, cups_connection_test, manager);
+                        g_source_set_name_by_id (manager->cups_connection_timeout_id, "[gnome-settings-daemon] cups_connection_test");
+                }
+        }
+}
+
+static gboolean
+cups_connection_test (gpointer user_data)
+{
+        GsdPrintNotificationsManager *manager = (GsdPrintNotificationsManager *) user_data;
+        GSocketClient                *client;
+        gchar                        *address;
+        int                           port = ippPort ();
+
+        if (!manager->dests) {
+                address = g_strdup_printf ("%s:%d", cupsServer (), port);
+
+                client = g_socket_client_new ();
+
+                g_debug ("Initiating test connection to CUPS server \'%s:%d\'.", cupsServer (), port);
+
+                g_socket_client_connect_to_host_async (client,
+                                                       address,
+                                                       port,
+                                                       NULL,
+                                                       cups_connection_test_cb,
+                                                       manager);
+
+                g_object_unref (client);
+                g_free (address);
+        }
+
+        if (manager->dests) {
+                manager->cups_connection_timeout_id = 0;
+
+                return FALSE;
+        } else {
+                return TRUE;
+        }
+}
+
+static void
+gsd_print_notifications_manager_got_dbus_connection (GObject      *source_object,
+                                                     GAsyncResult *res,
+                                                     gpointer      user_data)
+{
+        GsdPrintNotificationsManager *manager = (GsdPrintNotificationsManager *) user_data;
+        GError                       *error = NULL;
+
+        manager->cups_bus_connection = g_bus_get_finish (res, &error);
+
+        if (manager->cups_bus_connection != NULL) {
+                manager->cups_dbus_subscription_id =
+                        g_dbus_connection_signal_subscribe (manager->cups_bus_connection,
+                                                            NULL,
+                                                            CUPS_DBUS_INTERFACE,
+                                                            NULL,
+                                                            CUPS_DBUS_PATH,
+                                                            NULL,
+                                                            0,
+                                                            on_cups_notification,
+                                                            manager,
+                                                            NULL);
+        } else {
+                g_warning ("Connection to message bus failed: %s", error->message);
+                g_error_free (error);
+        }
+}
+
+static gboolean
+gsd_print_notifications_manager_start_idle (gpointer data)
+{
+        GsdPrintNotificationsManager *manager = data;
+
+        gnome_settings_profile_start (NULL);
+
+        manager->printing_printers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+        /*
+         * Set a password callback which cancels authentication
+         * before we prepare a correct solution (see bug #725440).
+         */
+        cupsSetPasswordCB2 (password_cb, NULL);
+
+        if (server_is_local (cupsServer ())) {
+                manager->num_dests = cupsGetDests (&manager->dests);
+                g_debug ("Got dests from local CUPS server.");
+
+                renew_subscription_timeout_enable (manager, TRUE, FALSE);
+
+                g_bus_get (G_BUS_TYPE_SYSTEM,
+                           NULL,
+                           gsd_print_notifications_manager_got_dbus_connection,
+                           data);
+        } else {
+                cups_connection_test (manager);
+        }
+
+        scp_handler (manager, TRUE);
+
+        gnome_settings_profile_end (NULL);
+
+        manager->start_idle_id = 0;
+        return G_SOURCE_REMOVE;
+}
+
+gboolean
+gsd_print_notifications_manager_start (GsdPrintNotificationsManager *manager,
+                                       GError                      **error)
+{
+        g_debug ("Starting print-notifications manager");
+
+        gnome_settings_profile_start (NULL);
+
+        manager->subscription_id = -1;
+        manager->dests = NULL;
+        manager->num_dests = 0;
+        manager->scp_handler_spawned = FALSE;
+        manager->timeouts = NULL;
+        manager->printing_printers = NULL;
+        manager->active_notifications = NULL;
+        manager->cups_bus_connection = NULL;
+        manager->cups_connection_timeout_id = 0;
+        manager->last_notify_sequence_number = -1;
+        manager->held_jobs = NULL;
+
+        manager->start_idle_id = g_idle_add (gsd_print_notifications_manager_start_idle, manager);
+        g_source_set_name_by_id (manager->start_idle_id, "[gnome-settings-daemon] gsd_print_notifications_manager_start_idle");
+
+        gnome_settings_profile_end (NULL);
+
+        return TRUE;
+}
+
+void
+gsd_print_notifications_manager_stop (GsdPrintNotificationsManager *manager)
+{
+        TimeoutData *data;
+        ReasonData  *reason_data;
+        HeldJob     *job;
+        GList       *tmp;
+
+        g_debug ("Stopping print-notifications manager");
+
+        cupsFreeDests (manager->num_dests, manager->dests);
+        manager->num_dests = 0;
+        manager->dests = NULL;
+
+        if (manager->cups_dbus_subscription_id > 0 &&
+            manager->cups_bus_connection != NULL) {
+                g_dbus_connection_signal_unsubscribe (manager->cups_bus_connection,
+                                                      manager->cups_dbus_subscription_id);
+                manager->cups_dbus_subscription_id = 0;
+        }
+
+        renew_subscription_timeout_enable (manager, FALSE, FALSE);
+
+        if (manager->check_source_id > 0) {
+                g_source_remove (manager->check_source_id);
+                manager->check_source_id = 0;
+        }
+
+        if (manager->subscription_id >= 0)
+                cancel_subscription (manager->subscription_id);
+
+        g_clear_pointer (&manager->printing_printers, g_hash_table_destroy);
+
+        g_clear_object (&manager->cups_bus_connection);
+
+        for (tmp = manager->timeouts; tmp; tmp = g_list_next (tmp)) {
+                data = (TimeoutData *) tmp->data;
+                if (data)
+                        g_source_remove (data->timeout_id);
+        }
+        g_list_free_full (manager->timeouts, free_timeout_data);
+
+        for (tmp = manager->active_notifications; tmp; tmp = g_list_next (tmp)) {
+                reason_data = (ReasonData *) tmp->data;
+                if (reason_data) {
+                        if (reason_data->notification_close_id > 0 &&
+                            g_signal_handler_is_connected (reason_data->notification,
+                                                           reason_data->notification_close_id)) {
+                                g_signal_handler_disconnect (reason_data->notification,
+                                                             reason_data->notification_close_id);
+                                reason_data->notification_close_id = 0;
+                        }
+
+                        notify_notification_close (reason_data->notification, NULL);
+                }
+        }
+        g_list_free_full (manager->active_notifications, free_reason_data);
+
+        for (tmp = manager->held_jobs; tmp; tmp = g_list_next (tmp)) {
+                job = (HeldJob *) tmp->data;
+                g_source_remove (job->timeout_id);
+        }
+        g_list_free_full (manager->held_jobs, free_held_job);
+
+        scp_handler (manager, FALSE);
+}
+
+static void
+gsd_print_notifications_manager_class_init (GsdPrintNotificationsManagerClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_print_notifications_manager_finalize;
+
+        notify_init ("gnome-settings-daemon");
+}
+
+static void
+gsd_print_notifications_manager_init (GsdPrintNotificationsManager *manager)
+{
+}
+
+static void
+gsd_print_notifications_manager_finalize (GObject *object)
+{
+        GsdPrintNotificationsManager *manager;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSD_IS_PRINT_NOTIFICATIONS_MANAGER (object));
+
+        manager = GSD_PRINT_NOTIFICATIONS_MANAGER (object);
+
+        g_return_if_fail (manager != NULL);
+
+        gsd_print_notifications_manager_stop (manager);
+
+        if (manager->start_idle_id != 0)
+                g_source_remove (manager->start_idle_id);
+
+        G_OBJECT_CLASS (gsd_print_notifications_manager_parent_class)->finalize (object);
+}
+
+GsdPrintNotificationsManager *
+gsd_print_notifications_manager_new (void)
+{
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_PRINT_NOTIFICATIONS_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+        }
+
+        return GSD_PRINT_NOTIFICATIONS_MANAGER (manager_object);
+}
diff --git a/plugins/print-notifications/gsd-print-notifications-manager.h b/plugins/print-notifications/gsd-print-notifications-manager.h
new file mode 100644
index 0000000..ec1dc72
--- /dev/null
+++ b/plugins/print-notifications/gsd-print-notifications-manager.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_PRINT_NOTIFICATIONS_MANAGER_H
+#define __GSD_PRINT_NOTIFICATIONS_MANAGER_H
+
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_PRINT_NOTIFICATIONS_MANAGER         (gsd_print_notifications_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdPrintNotificationsManager, gsd_print_notifications_manager, GSD, PRINT_NOTIFICATIONS_MANAGER, GObject)
+
+GsdPrintNotificationsManager *gsd_print_notifications_manager_new      (void);
+gboolean                      gsd_print_notifications_manager_start    (GsdPrintNotificationsManager *manager,
+                                                                        GError                      **error);
+void                          gsd_print_notifications_manager_stop     (GsdPrintNotificationsManager *manager);
+
+G_END_DECLS
+
+#endif /* __GSD_PRINT_NOTIFICATIONS_MANAGER_H */
diff --git a/plugins/print-notifications/gsd-printer.c b/plugins/print-notifications/gsd-printer.c
new file mode 100644
index 0000000..573129b
--- /dev/null
+++ b/plugins/print-notifications/gsd-printer.c
@@ -0,0 +1,1403 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+static GDBusNodeInfo *npn_introspection_data = NULL;
+static GDBusNodeInfo *pdi_introspection_data = NULL;
+
+#define SCP_DBUS_NPN_NAME      "com.redhat.NewPrinterNotification"
+#define SCP_DBUS_NPN_PATH      "/com/redhat/NewPrinterNotification"
+#define SCP_DBUS_NPN_INTERFACE "com.redhat.NewPrinterNotification"
+
+#define SCP_DBUS_PDI_NAME      "com.redhat.PrinterDriversInstaller"
+#define SCP_DBUS_PDI_PATH      "/com/redhat/PrinterDriversInstaller"
+#define SCP_DBUS_PDI_INTERFACE "com.redhat.PrinterDriversInstaller"
+
+#define PACKAGE_KIT_BUS "org.freedesktop.PackageKit"
+#define PACKAGE_KIT_PATH "/org/freedesktop/PackageKit"
+#define PACKAGE_KIT_MODIFY_IFACE  "org.freedesktop.PackageKit.Modify"
+#define PACKAGE_KIT_QUERY_IFACE  "org.freedesktop.PackageKit.Query"
+
+#define SCP_BUS   "org.fedoraproject.Config.Printing"
+#define SCP_PATH  "/org/fedoraproject/Config/Printing"
+#define SCP_IFACE "org.fedoraproject.Config.Printing"
+
+#define MECHANISM_BUS "org.opensuse.CupsPkHelper.Mechanism"
+
+#define ALLOWED_CHARACTERS "abcdefghijklmnopqrtsuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
+
+#define DBUS_TIMEOUT           60000
+#define DBUS_INSTALL_TIMEOUT 3600000
+
+#define GNOME_SESSION_DBUS_NAME                 "org.gnome.SessionManager"
+#define GNOME_SESSION_DBUS_PATH                 "/org/gnome/SessionManager"
+#define GNOME_SESSION_DBUS_IFACE                "org.gnome.SessionManager"
+#define GNOME_SESSION_CLIENT_PRIVATE_DBUS_IFACE "org.gnome.SessionManager.ClientPrivate"
+
+#define GNOME_SESSION_PRESENCE_DBUS_PATH  "/org/gnome/SessionManager/Presence"
+#define GNOME_SESSION_PRESENCE_DBUS_IFACE "org.gnome.SessionManager.Presence"
+
+#if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5)
+#define HAVE_CUPS_1_6 1
+#endif
+
+#ifndef HAVE_CUPS_1_6
+#define ippGetState(ipp) ipp->state
+#endif
+
+enum {
+  PRESENCE_STATUS_AVAILABLE = 0,
+  PRESENCE_STATUS_INVISIBLE,
+  PRESENCE_STATUS_BUSY,
+  PRESENCE_STATUS_IDLE,
+  PRESENCE_STATUS_UNKNOWN
+};
+
+static const gchar npn_introspection_xml[] =
+  ""
+  "  "
+  "    "
+  "    "
+  "    "
+  "      "
+  "      "
+  "      "
+  "      "
+  "      "
+  "      "
+  "    "
+  "  "
+  "";
+
+static const gchar pdi_introspection_xml[] =
+  ""
+  "  "
+  "    "
+  "      "
+  "      "
+  "      "
+  "    "
+  "  "
+  "";
+
+static GMainLoop *main_loop;
+static guint      npn_registration_id;
+static guint      pdi_registration_id;
+static guint      npn_owner_id;
+static guint      pdi_owner_id;
+
+static GHashTable *
+get_missing_executables (const gchar *ppd_file_name)
+{
+        GHashTable *executables = NULL;
+        GDBusProxy *proxy;
+        GVariant   *output;
+        GVariant   *array;
+        GError     *error = NULL;
+        gint        i;
+
+        if (!ppd_file_name)
+                return NULL;
+
+        proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+                                               G_DBUS_PROXY_FLAGS_NONE,
+                                               NULL,
+                                               SCP_BUS,
+                                               SCP_PATH,
+                                               SCP_IFACE,
+                                               NULL,
+                                               &error);
+
+        if (!proxy) {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+                return NULL;
+        }
+
+        output = g_dbus_proxy_call_sync (proxy,
+                                         "MissingExecutables",
+                                         g_variant_new ("(s)",
+                                                        ppd_file_name),
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         DBUS_TIMEOUT,
+                                         NULL,
+                                         &error);
+
+        if (output && g_variant_n_children (output) == 1) {
+                array = g_variant_get_child_value (output, 0);
+                if (array) {
+                        executables = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                             g_free, NULL);
+                        for (i = 0; i < g_variant_n_children (array); i++) {
+                                g_hash_table_insert (executables,
+                                                     g_strdup (g_variant_get_string (
+                                                       g_variant_get_child_value (array, i),
+                                                       NULL)),
+                                                     NULL);
+                        }
+                }
+        }
+
+        if (output) {
+                g_variant_unref (output);
+        } else {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+        }
+
+        g_object_unref (proxy);
+
+        return executables;
+}
+
+static GHashTable *
+find_packages_for_executables (GHashTable *executables)
+{
+        GHashTableIter  exec_iter;
+        GHashTable     *packages = NULL;
+        GDBusProxy     *proxy;
+        GVariant       *output;
+        gpointer        key, value;
+        GError         *error = NULL;
+
+        if (!executables || g_hash_table_size (executables) <= 0)
+                return NULL;
+
+        proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+                                               G_DBUS_PROXY_FLAGS_NONE,
+                                               NULL,
+                                               PACKAGE_KIT_BUS,
+                                               PACKAGE_KIT_PATH,
+                                               PACKAGE_KIT_QUERY_IFACE,
+                                               NULL,
+                                               &error);
+
+        if (!proxy) {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+                return NULL;
+        }
+
+        packages = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                          g_free, NULL);
+
+        g_hash_table_iter_init (&exec_iter, executables);
+        while (g_hash_table_iter_next (&exec_iter, &key, &value)) {
+                output = g_dbus_proxy_call_sync (proxy,
+                                                 "SearchFile",
+                                                 g_variant_new ("(ss)",
+                                                                (gchar *) key,
+                                                                ""),
+                                                 G_DBUS_CALL_FLAGS_NONE,
+                                                 DBUS_TIMEOUT,
+                                                 NULL,
+                                                 &error);
+
+                if (output) {
+                        gboolean  installed;
+                        gchar    *package;
+
+                        g_variant_get (output,
+                                       "(bs)",
+                                       &installed,
+                                       &package);
+                        if (!installed)
+                                g_hash_table_insert (packages, g_strdup (package), NULL);
+
+                        g_variant_unref (output);
+                } else {
+                        g_warning ("%s", error->message);
+                        g_error_free (error);
+                }
+        }
+
+        g_object_unref (proxy);
+
+        return packages;
+}
+
+static void
+install_packages (GHashTable *packages)
+{
+        GVariantBuilder  array_builder;
+        GHashTableIter   pkg_iter;
+        GDBusProxy      *proxy;
+        GVariant        *output;
+        gpointer         key, value;
+        GError          *error = NULL;
+
+        if (!packages || g_hash_table_size (packages) <= 0)
+                return;
+
+        proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+                                               G_DBUS_PROXY_FLAGS_NONE,
+                                               NULL,
+                                               PACKAGE_KIT_BUS,
+                                               PACKAGE_KIT_PATH,
+                                               PACKAGE_KIT_MODIFY_IFACE,
+                                               NULL,
+                                               &error);
+
+        if (!proxy) {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("as"));
+
+        g_hash_table_iter_init (&pkg_iter, packages);
+        while (g_hash_table_iter_next (&pkg_iter, &key, &value)) {
+                g_variant_builder_add (&array_builder,
+                                       "s",
+                                       (gchar *) key);
+        }
+
+        output = g_dbus_proxy_call_sync (proxy,
+                                         "InstallPackageNames",
+                                         g_variant_new ("(uass)",
+                                                        0,
+                                                        &array_builder,
+                                                        "hide-finished"),
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         DBUS_INSTALL_TIMEOUT,
+                                         NULL,
+                                         &error);
+
+        if (output) {
+                g_variant_unref (output);
+        } else {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+        }
+
+        g_object_unref (proxy);
+}
+
+static gchar *
+get_best_ppd (gchar *device_id,
+              gchar *device_make_and_model,
+              gchar *device_uri)
+{
+        GDBusProxy  *proxy;
+        GVariant    *output;
+        GVariant    *array;
+        GVariant    *tuple;
+        GError      *error = NULL;
+        gchar       *ppd_name = NULL;
+        gint         i, j;
+        static const char * const match_levels[] = {
+                   "exact-cmd",
+                   "exact",
+                   "close",
+                   "generic",
+                   "none"};
+
+        proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+                                               G_DBUS_PROXY_FLAGS_NONE,
+                                               NULL,
+                                               SCP_BUS,
+                                               SCP_PATH,
+                                               SCP_IFACE,
+                                               NULL,
+                                               &error);
+
+        if (!proxy) {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+                return NULL;
+        }
+
+        output = g_dbus_proxy_call_sync (proxy,
+                                         "GetBestDrivers",
+                                         g_variant_new ("(sss)",
+                                                 device_id ? device_id : "",
+                                                 device_make_and_model ? device_make_and_model : "",
+                                                 device_uri ? device_uri : ""),
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         DBUS_TIMEOUT,
+                                         NULL,
+                                         &error);
+
+        if (output && g_variant_n_children (output) >= 1) {
+                array = g_variant_get_child_value (output, 0);
+                if (array)
+                        for (j = 0; j < G_N_ELEMENTS (match_levels) && ppd_name == NULL; j++)
+                                for (i = 0; i < g_variant_n_children (array) && ppd_name == NULL; i++) {
+                                        tuple = g_variant_get_child_value (array, i);
+                                        if (tuple && g_variant_n_children (tuple) == 2) {
+                                                if (g_strcmp0 (g_variant_get_string (
+                                                                   g_variant_get_child_value (tuple, 1),
+                                                                   NULL), match_levels[j]) == 0)
+                                                        ppd_name = g_strdup (g_variant_get_string (
+                                                                                 g_variant_get_child_value (tuple, 0),
+                                                                                 NULL));
+                                        }
+                                }
+        }
+
+        if (output) {
+                g_variant_unref (output);
+        } else {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+        }
+
+        g_object_unref (proxy);
+
+        return ppd_name;
+}
+
+static gchar *
+get_tag_value (const gchar *tag_string,
+               const gchar *tag_name)
+{
+        gchar **tag_string_splitted;
+        gchar  *tag_value = NULL;
+        gint    tag_name_length;
+        gint    i;
+
+        if (!tag_string ||
+            !tag_name)
+                return NULL;
+
+        tag_name_length = strlen (tag_name);
+        tag_string_splitted = g_strsplit (tag_string, ";", 0);
+        if (tag_string_splitted) {
+                for (i = 0; i < g_strv_length (tag_string_splitted); i++)
+                        if (g_ascii_strncasecmp (tag_string_splitted[i], tag_name, tag_name_length) == 0)
+                                if (strlen (tag_string_splitted[i]) > tag_name_length + 1)
+                                        tag_value = g_strdup (tag_string_splitted[i] + tag_name_length + 1);
+
+                g_strfreev (tag_string_splitted);
+        }
+
+        return tag_value;
+}
+
+static gchar *
+create_name (gchar *device_id)
+{
+        cups_dest_t *dests;
+        gboolean     already_present = FALSE;
+        gchar       *name = NULL;
+        gchar       *new_name = NULL;
+        gint         num_dests;
+        gint         name_index = 2;
+        gint         j;
+
+        g_return_val_if_fail (device_id != NULL, NULL);
+
+        name = get_tag_value (device_id, "mdl");
+        if (!name)
+                name = get_tag_value (device_id, "model");
+
+        if (name)
+                name = g_strcanon (name, ALLOWED_CHARACTERS, '-');
+
+        num_dests = cupsGetDests (&dests);
+        do {
+                if (already_present) {
+                        new_name = g_strdup_printf ("%s-%d", name, name_index);
+                        name_index++;
+                } else {
+                        new_name = g_strdup (name);
+                }
+
+                already_present = FALSE;
+                for (j = 0; j < num_dests; j++)
+                        if (g_strcmp0 (dests[j].name, new_name) == 0)
+                                already_present = TRUE;
+
+                if (already_present) {
+                        g_free (new_name);
+                } else {
+                        g_free (name);
+                        name = new_name;
+                }
+        } while (already_present);
+        cupsFreeDests (num_dests, dests);
+
+        return name;
+}
+
+static gboolean
+add_printer (gchar *printer_name,
+             gchar *device_uri,
+             gchar *ppd_name,
+             gchar *info,
+             gchar *location)
+{
+        cups_dest_t *dests;
+        GDBusProxy  *proxy;
+        gboolean     success = FALSE;
+        GVariant    *output;
+        GError      *error = NULL;
+        gint         num_dests;
+        gint         i;
+
+        if (!printer_name || !device_uri || !ppd_name)
+                return FALSE;
+
+        proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+                                               G_DBUS_PROXY_FLAGS_NONE,
+                                               NULL,
+                                               MECHANISM_BUS,
+                                               "/",
+                                               MECHANISM_BUS,
+                                               NULL,
+                                               &error);
+
+        if (!proxy) {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+                return FALSE;
+        }
+
+        output = g_dbus_proxy_call_sync (proxy,
+                                         "PrinterAdd",
+                                         g_variant_new ("(sssss)",
+                                                        printer_name,
+                                                        device_uri,
+                                                        ppd_name,
+                                                        info ? info : "",
+                                                        location ? location : ""),
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         DBUS_TIMEOUT,
+                                         NULL,
+                                         &error);
+
+        if (output) {
+                g_variant_unref (output);
+        } else {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+        }
+
+        g_object_unref (proxy);
+
+        num_dests = cupsGetDests (&dests);
+        for (i = 0; i < num_dests; i++)
+                if (g_strcmp0 (dests[i].name, printer_name) == 0)
+                        success = TRUE;
+        cupsFreeDests (num_dests, dests);
+
+        return success;
+}
+
+static gboolean
+printer_set_enabled (const gchar *printer_name,
+                     gboolean     enabled)
+{
+        GDBusProxy *proxy;
+        gboolean    result = TRUE;
+        GVariant   *output;
+        GError     *error = NULL;
+
+        if (!printer_name)
+                return FALSE;
+
+        proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+                                               G_DBUS_PROXY_FLAGS_NONE,
+                                               NULL,
+                                               MECHANISM_BUS,
+                                               "/",
+                                               MECHANISM_BUS,
+                                               NULL,
+                                               &error);
+
+        if (!proxy) {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+                return FALSE;
+        }
+
+        output = g_dbus_proxy_call_sync (proxy,
+                                         "PrinterSetEnabled",
+                                         g_variant_new ("(sb)",
+                                                        printer_name,
+                                                        enabled),
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         DBUS_TIMEOUT,
+                                         NULL,
+                                         &error);
+
+        if (output) {
+                g_variant_unref (output);
+        } else {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+                result = FALSE;
+        }
+
+        g_object_unref (proxy);
+
+        return result;
+}
+
+static gboolean
+printer_set_accepting_jobs (const gchar *printer_name,
+                            gboolean     accepting_jobs,
+                            const gchar *reason)
+{
+        GDBusProxy *proxy;
+        gboolean    result = TRUE;
+        GVariant   *output;
+        GError     *error = NULL;
+
+        if (!printer_name)
+                return FALSE;
+
+        proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+                                               G_DBUS_PROXY_FLAGS_NONE,
+                                               NULL,
+                                               MECHANISM_BUS,
+                                               "/",
+                                               MECHANISM_BUS,
+                                               NULL,
+                                               &error);
+
+        if (!proxy) {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+                return FALSE;
+        }
+
+        output = g_dbus_proxy_call_sync (proxy,
+                                         "PrinterSetAcceptJobs",
+                                         g_variant_new ("(sbs)",
+                                                        printer_name,
+                                                        accepting_jobs,
+                                                        reason ? reason : ""),
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         DBUS_TIMEOUT,
+                                         NULL,
+                                         &error);
+
+        if (output) {
+                g_variant_unref (output);
+        } else {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+                result = FALSE;
+        }
+
+        g_object_unref (proxy);
+
+        return result;
+}
+
+static ipp_t *
+execute_maintenance_command (const char *printer_name,
+                             const char *command,
+                             const char *title)
+{
+        http_t *http;
+        GError *error = NULL;
+        ipp_t  *request = NULL;
+        ipp_t  *response = NULL;
+        gchar  *file_name = NULL;
+        char   *uri;
+        int     fd = -1;
+
+        http = httpConnectEncrypt (cupsServer (),
+                                   ippPort (),
+                                   cupsEncryption ());
+
+        if (!http)
+                return NULL;
+
+        request = ippNewRequest (IPP_PRINT_JOB);
+
+        uri = g_strdup_printf ("ipp://localhost/printers/%s",
+                               printer_name);
+
+        ippAddString (request,
+                      IPP_TAG_OPERATION,
+                      IPP_TAG_URI,
+                      "printer-uri",
+                      NULL,
+                      uri);
+
+        g_free (uri);
+
+        ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name",
+                      NULL, title);
+
+        ippAddString (request, IPP_TAG_JOB, IPP_TAG_MIMETYPE, "document-format",
+                      NULL, "application/vnd.cups-command");
+
+        fd = g_file_open_tmp ("ccXXXXXX", &file_name, &error);
+
+        if (fd != -1) {
+                FILE *file;
+
+                file = fdopen (fd, "w");
+                fprintf (file, "#CUPS-COMMAND\n");
+                fprintf (file, "%s\n", command);
+                fclose (file);
+
+                response = cupsDoFileRequest (http, request, "/", file_name);
+                g_unlink (file_name);
+        } else {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+        }
+
+        g_free (file_name);
+        httpClose (http);
+
+        return response;
+}
+
+static char *
+get_dest_attr (const char *dest_name,
+               const char *attr)
+{
+        cups_dest_t *dests;
+        int          num_dests;
+        cups_dest_t *dest;
+        const char  *value;
+        char        *ret;
+
+        if (dest_name == NULL)
+                return NULL;
+
+        ret = NULL;
+
+        num_dests = cupsGetDests (&dests);
+        if (num_dests < 1) {
+                g_debug ("Unable to get printer destinations");
+                return NULL;
+        }
+
+        dest = cupsGetDest (dest_name, NULL, num_dests, dests);
+        if (dest == NULL) {
+                g_debug ("Unable to find a printer named '%s'", dest_name);
+                goto out;
+        }
+
+        value = cupsGetOption (attr, dest->num_options, dest->options);
+        if (value == NULL) {
+                g_debug ("Unable to get %s for '%s'", attr, dest_name);
+                goto out;
+        }
+        ret = g_strdup (value);
+out:
+        cupsFreeDests (num_dests, dests);
+
+        return ret;
+}
+
+static void
+printer_autoconfigure (gchar *printer_name)
+{
+        gchar *commands;
+        gchar *commands_lowercase;
+        ipp_t *response = NULL;
+
+        if (!printer_name)
+                return;
+
+        commands = get_dest_attr (printer_name, "printer-commands");
+        commands_lowercase = g_ascii_strdown (commands, -1);
+
+        if (g_strrstr (commands_lowercase, "autoconfigure")) {
+                response = execute_maintenance_command (printer_name,
+                                                        "AutoConfigure",
+                                                        ("Automatic configuration"));
+                if (response) {
+                        if (ippGetState (response) == IPP_ERROR)
+                                g_warning ("An error has occured during automatic configuration of new printer.");
+                        ippDelete (response);
+                }
+        }
+        g_free (commands);
+        g_free (commands_lowercase);
+}
+
+/* Returns default page size for current locale */
+static const gchar *
+get_page_size_from_locale (void)
+{
+  if (g_str_equal (gtk_paper_size_get_default (), GTK_PAPER_NAME_LETTER))
+    return "Letter";
+  else
+    return "A4";
+}
+
+static void
+set_default_paper_size (const gchar *printer_name,
+                        const gchar *ppd_file_name)
+{
+        GDBusProxy      *proxy;
+        GVariant        *output;
+        GError          *error = NULL;
+        GVariantBuilder *builder;
+
+        proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+                                               G_DBUS_PROXY_FLAGS_NONE,
+                                               NULL,
+                                               MECHANISM_BUS,
+                                               "/",
+                                               MECHANISM_BUS,
+                                               NULL,
+                                               &error);
+
+        if (!proxy) {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        /* Set default media size according to the locale
+         * FIXME: Handle more than A4 and Letter:
+         * https://bugzilla.gnome.org/show_bug.cgi?id=660769 */
+        builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
+        g_variant_builder_add (builder, "s", get_page_size_from_locale ());
+
+        output = g_dbus_proxy_call_sync (proxy,
+                                         "PrinterAddOption",
+                                         g_variant_new ("(ssas)",
+                                                        printer_name ? printer_name : "",
+                                                        "PageSize",
+                                                        builder),
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         DBUS_TIMEOUT,
+                                         NULL,
+                                         &error);
+
+        if (output) {
+                g_variant_unref (output);
+        } else {
+                if (!(error->domain == G_DBUS_ERROR &&
+                      (error->code == G_DBUS_ERROR_SERVICE_UNKNOWN ||
+                       error->code == G_DBUS_ERROR_UNKNOWN_METHOD)))
+                        g_warning ("%s", error->message);
+                g_error_free (error);
+        }
+
+        g_object_unref (proxy);
+}
+
+/*
+ * Setup new printer and returns TRUE if successful.
+ */
+static gboolean
+setup_printer (gchar *device_id,
+               gchar *device_make_and_model,
+               gchar *device_uri)
+{
+        gboolean  success = FALSE;
+        gchar    *ppd_name;
+        gchar    *printer_name;
+
+        ppd_name = get_best_ppd (device_id, device_make_and_model, device_uri);
+        printer_name = create_name (device_id);
+
+        if (!ppd_name || !printer_name || !device_uri) {
+                g_free (ppd_name);
+                g_free (printer_name);
+                return FALSE;
+        }
+
+        success = add_printer (printer_name, device_uri,
+                               ppd_name, NULL, NULL);
+
+        /* Set some options of the new printer */
+        if (success) {
+                const char *ppd_file_name;
+
+                printer_set_accepting_jobs (printer_name, TRUE, NULL);
+                printer_set_enabled (printer_name, TRUE);
+                printer_autoconfigure (printer_name);
+
+                ppd_file_name = cupsGetPPD (printer_name);
+
+                if (ppd_file_name) {
+                        GHashTable *executables;
+                        GHashTable *packages;
+
+                        set_default_paper_size (printer_name, ppd_file_name);
+
+                        executables = get_missing_executables (ppd_file_name);
+                        packages = find_packages_for_executables (executables);
+                        install_packages (packages);
+
+                        if (executables)
+                                g_hash_table_destroy (executables);
+                        if (packages)
+                                g_hash_table_destroy (packages);
+                        g_unlink (ppd_file_name);
+                }
+        }
+
+        g_free (printer_name);
+        g_free (ppd_name);
+
+        return success;
+}
+
+static void
+handle_method_call (GDBusConnection       *connection,
+                    const gchar           *sender,
+                    const gchar           *object_path,
+                    const gchar           *interface_name,
+                    const gchar           *method_name,
+                    GVariant              *parameters,
+                    GDBusMethodInvocation *invocation,
+                    gpointer               user_data)
+{
+        gchar *primary_text = NULL;
+        gchar *secondary_text = NULL;
+        gchar *name = NULL;
+        gchar *mfg = NULL;
+        gchar *mdl = NULL;
+        gchar *des = NULL;
+        gchar *cmd = NULL;
+        gchar *device = NULL;
+        gchar *device_id;
+        gchar *make_and_model;
+        gint   status = 0;
+
+        if (g_strcmp0 (method_name, "GetReady") == 0) {
+                /* Translators: We are configuring new printer */
+                primary_text = g_strdup (_("Configuring new printer"));
+                /* Translators: Just wait */
+                secondary_text = g_strdup (_("Please wait…"));
+
+                g_dbus_method_invocation_return_value (invocation,
+                                                       NULL);
+        }
+        else if (g_strcmp0 (method_name, "NewPrinter") == 0) {
+                if (g_variant_n_children (parameters) == 6) {
+                        g_variant_get (parameters, "(i&s&s&s&s&s)",
+                               &status,
+                               &name,
+                               &mfg,
+                               &mdl,
+                               &des,
+                               &cmd);
+                }
+
+                if (g_strrstr (name, "/")) {
+                        /* name is a URI, no queue was generated, because no suitable
+                         * driver was found
+                         */
+
+                        device_id = g_strdup_printf ("MFG:%s;MDL:%s;DES:%s;CMD:%s;", mfg, mdl, des, cmd);
+                        make_and_model = g_strdup_printf ("%s %s", mfg, mdl);
+
+                        if (!setup_printer (device_id, make_and_model, name)) {
+
+                                /* Translators: We have no driver installed for this printer */
+                                primary_text = g_strdup (_("Missing printer driver"));
+
+                                if ((mfg && mdl) || des) {
+                                        if (mfg && mdl)
+                                                device = g_strdup_printf ("%s %s", mfg, mdl);
+                                        else
+                                                device = g_strdup (des);
+
+                                        /* Translators: We have no driver installed for the device */
+                                        secondary_text = g_strdup_printf (_("No printer driver for %s."), device);
+                                        g_free (device);
+                                }
+                                else
+                                        /* Translators: We have no driver installed for this printer */
+                                        secondary_text = g_strdup (_("No driver for this printer."));
+                        }
+
+                        g_free (make_and_model);
+                        g_free (device_id);
+                }
+                else {
+                        /* name is the name of the queue which hal_lpadmin has set up
+                         * automatically.
+                         */
+
+                        const char *ppd_file_name;
+
+                        ppd_file_name = cupsGetPPD (name);
+                        if (ppd_file_name) {
+                                GHashTable *executables;
+                                GHashTable *packages;
+
+                                executables = get_missing_executables (ppd_file_name);
+                                packages = find_packages_for_executables (executables);
+                                install_packages (packages);
+
+                                if (executables)
+                                        g_hash_table_destroy (executables);
+                                if (packages)
+                                        g_hash_table_destroy (packages);
+                                g_unlink (ppd_file_name);
+                        }
+                }
+
+                g_dbus_method_invocation_return_value (invocation,
+                                                       NULL);
+        }
+        else if (g_strcmp0 (method_name, "InstallDrivers") == 0) {
+                GDBusProxy *proxy;
+                GError     *error = NULL;
+
+                if (g_variant_n_children (parameters) == 3) {
+                        g_variant_get (parameters, "(&s&s&s)",
+                               &mfg,
+                               &mdl,
+                               &cmd);
+                }
+
+                if (mfg && mdl)
+                        device = g_strdup_printf ("MFG:%s;MDL:%s;", mfg, mdl);
+
+                proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+                                                       G_DBUS_PROXY_FLAGS_NONE,
+                                                       NULL,
+                                                       PACKAGE_KIT_BUS,
+                                                       PACKAGE_KIT_PATH,
+                                                       PACKAGE_KIT_MODIFY_IFACE,
+                                                       NULL,
+                                                       &error);
+
+                if (!proxy) {
+                        g_warning ("%s", error->message);
+                        g_error_free (error);
+                }
+
+                if (proxy && device) {
+                        GVariantBuilder *builder;
+                        GVariant        *output;
+
+                        builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
+                        g_variant_builder_add (builder, "s", device);
+
+                        output = g_dbus_proxy_call_sync (proxy,
+                                                         "InstallPrinterDrivers",
+                                                         g_variant_new ("(uass)",
+                                                                        0,
+                                                                        builder,
+                                                                        "hide-finished"),
+                                                         G_DBUS_CALL_FLAGS_NONE,
+                                                         DBUS_INSTALL_TIMEOUT,
+                                                         NULL,
+                                                         &error);
+
+                        if (output) {
+                                g_variant_unref (output);
+                        } else {
+                                g_warning ("%s", error->message);
+                                g_error_free (error);
+                        }
+
+                        g_object_unref (proxy);
+                }
+
+                g_dbus_method_invocation_return_value (invocation,
+                                                       NULL);
+        }
+
+        if (primary_text) {
+                NotifyNotification *notification;
+                notification = notify_notification_new (primary_text,
+                                                        secondary_text,
+                                                        "printer-symbolic");
+                notify_notification_set_app_name (notification, _("Printers"));
+                notify_notification_set_hint_string (notification, "desktop-entry", "gnome-printers-panel");
+                notify_notification_set_hint (notification, "transient", g_variant_new_boolean (TRUE));
+
+                notify_notification_show (notification, NULL);
+                g_object_unref (notification);
+                g_free (primary_text);
+                g_free (secondary_text);
+        }
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+  handle_method_call,
+  NULL,
+  NULL
+};
+
+static void
+unregister_objects ()
+{
+        GDBusConnection *system_connection;
+        GError          *error = NULL;
+
+        system_connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+
+        if (npn_registration_id > 0) {
+                g_dbus_connection_unregister_object (system_connection, npn_registration_id);
+                npn_registration_id = 0;
+        }
+
+        if (pdi_registration_id > 0) {
+                g_dbus_connection_unregister_object (system_connection, pdi_registration_id);
+                pdi_registration_id = 0;
+        }
+}
+
+static void
+unown_names ()
+{
+        if (npn_owner_id > 0) {
+                g_bus_unown_name (npn_owner_id);
+                npn_owner_id = 0;
+        }
+
+        if (pdi_owner_id > 0) {
+                g_bus_unown_name (pdi_owner_id);
+                pdi_owner_id = 0;
+        }
+}
+
+static void
+on_npn_bus_acquired (GDBusConnection *connection,
+                     const gchar     *name,
+                     gpointer         user_data)
+{
+        GError *error = NULL;
+
+        npn_registration_id = g_dbus_connection_register_object (connection,
+                                                                 SCP_DBUS_NPN_PATH,
+                                                                 npn_introspection_data->interfaces[0],
+                                                                 &interface_vtable,
+                                                                 NULL,
+                                                                 NULL,
+                                                                 &error);
+
+        if (npn_registration_id == 0) {
+                g_warning ("Failed to register object: %s\n", error->message);
+                g_error_free (error);
+        }
+}
+
+static void
+on_pdi_bus_acquired (GDBusConnection *connection,
+                     const gchar     *name,
+                     gpointer         user_data)
+{
+        GError *error = NULL;
+
+        pdi_registration_id = g_dbus_connection_register_object (connection,
+                                                                 SCP_DBUS_PDI_PATH,
+                                                                 pdi_introspection_data->interfaces[0],
+                                                                 &interface_vtable,
+                                                                 NULL,
+                                                                 NULL,
+                                                                 &error);
+
+        if (pdi_registration_id == 0) {
+                g_warning ("Failed to register object: %s\n", error->message);
+                g_error_free (error);
+        }
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+                  const gchar     *name,
+                  gpointer         user_data)
+{
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+              const gchar     *name,
+              gpointer         user_data)
+{
+        unregister_objects ();
+}
+
+static void
+session_signal_handler (GDBusConnection  *connection,
+                        const gchar      *sender_name,
+                        const gchar      *object_path,
+                        const gchar      *interface_name,
+                        const gchar      *signal_name,
+                        GVariant         *parameters,
+                        gpointer          user_data)
+{
+        guint            new_status;
+
+        g_variant_get (parameters, "(u)", &new_status);
+
+        if (new_status == PRESENCE_STATUS_IDLE ||
+            new_status == PRESENCE_STATUS_AVAILABLE) {
+                unregister_objects ();
+                unown_names ();
+
+                if (new_status == PRESENCE_STATUS_AVAILABLE) {
+                        npn_owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM,
+                                                       SCP_DBUS_NPN_NAME,
+                                                       G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
+                                                       G_BUS_NAME_OWNER_FLAGS_REPLACE,
+                                                       on_npn_bus_acquired,
+                                                       on_name_acquired,
+                                                       on_name_lost,
+                                                       NULL,
+                                                       NULL);
+
+                        pdi_owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM,
+                                                       SCP_DBUS_PDI_NAME,
+                                                       G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
+                                                       G_BUS_NAME_OWNER_FLAGS_REPLACE,
+                                                       on_pdi_bus_acquired,
+                                                       on_name_acquired,
+                                                       on_name_lost,
+                                                       NULL,
+                                                       NULL);
+                }
+        }
+}
+
+static void
+client_signal_handler (GDBusConnection  *connection,
+                       const gchar      *sender_name,
+                       const gchar      *object_path,
+                       const gchar      *interface_name,
+                       const gchar      *signal_name,
+                       GVariant         *parameters,
+                       gpointer          user_data)
+{
+        GDBusProxy *proxy;
+        GError     *error = NULL;
+        GVariant   *output;
+
+        if (g_strcmp0 (signal_name, "QueryEndSession") == 0 ||
+            g_strcmp0 (signal_name, "EndSession") == 0) {
+                proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+                                                       G_DBUS_PROXY_FLAGS_NONE,
+                                                       NULL,
+                                                       sender_name,
+                                                       object_path,
+                                                       interface_name,
+                                                       NULL,
+                                                       &error);
+
+                if (proxy) {
+                        output = g_dbus_proxy_call_sync (proxy,
+                                                         "EndSessionResponse",
+                                                         g_variant_new ("(bs)", TRUE, ""),
+                                                         G_DBUS_CALL_FLAGS_NONE,
+                                                         -1,
+                                                         NULL,
+                                                         &error);
+
+                        if (output) {
+                                g_variant_unref (output);
+                        }
+                        else {
+                                g_warning ("%s", error->message);
+                                g_error_free (error);
+                        }
+
+                        g_object_unref (proxy);
+                }
+                else {
+                        g_warning ("%s", error->message);
+                        g_error_free (error);
+                }
+
+                if (g_strcmp0 (signal_name, "EndSession") == 0) {
+                        g_main_loop_quit (main_loop);
+                        g_debug ("Exiting gsd-printer");
+                }
+        }
+}
+
+static gchar *
+register_gnome_session_client (const gchar *app_id,
+                               const gchar *client_startup_id)
+{
+        GDBusProxy  *proxy;
+        GVariant    *output = NULL;
+        GError      *error = NULL;
+        const gchar *client_id = NULL;
+        gchar       *result = NULL;
+
+        proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+                                               G_DBUS_PROXY_FLAGS_NONE,
+                                               NULL,
+                                               GNOME_SESSION_DBUS_NAME,
+                                               GNOME_SESSION_DBUS_PATH,
+                                               GNOME_SESSION_DBUS_IFACE,
+                                               NULL,
+                                               &error);
+
+        if (proxy) {
+                output = g_dbus_proxy_call_sync (proxy,
+                                                 "RegisterClient",
+                                                 g_variant_new ("(ss)", app_id, client_startup_id),
+                                                 G_DBUS_CALL_FLAGS_NONE,
+                                                 -1,
+                                                 NULL,
+                                                 &error);
+
+                if (output) {
+                        g_variant_get (output, "(o)", &client_id);
+                        if (client_id)
+                                result = g_strdup (client_id);
+                        g_variant_unref (output);
+                }
+                else {
+                        g_warning ("%s", error->message);
+                        g_error_free (error);
+                }
+
+                g_object_unref (proxy);
+        }
+        else {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+        }
+
+        return result;
+}
+
+int
+main (int argc, char *argv[])
+{
+  GDBusConnection *connection;
+  gboolean         client_signal_subscription_set = FALSE;
+  GError          *error = NULL;
+  guint            client_signal_subscription_id;
+  guint            session_signal_subscription_id;
+  gchar           *object_path;
+
+  bindtextdomain (GETTEXT_PACKAGE, GNOME_SETTINGS_LOCALEDIR);
+  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+  textdomain (GETTEXT_PACKAGE);
+  setlocale (LC_ALL, "");
+
+  npn_registration_id = 0;
+  pdi_registration_id = 0;
+  npn_owner_id = 0;
+  pdi_owner_id = 0;
+
+  notify_init ("gnome-settings-daemon-printer");
+
+  npn_introspection_data =
+          g_dbus_node_info_new_for_xml (npn_introspection_xml, &error);
+
+  if (npn_introspection_data == NULL) {
+          g_warning ("Error parsing introspection XML: %s\n", error->message);
+          g_error_free (error);
+          goto error;
+  }
+
+  pdi_introspection_data =
+          g_dbus_node_info_new_for_xml (pdi_introspection_xml, &error);
+
+  if (pdi_introspection_data == NULL) {
+          g_warning ("Error parsing introspection XML: %s\n", error->message);
+          g_error_free (error);
+          goto error;
+  }
+
+  connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+
+  session_signal_subscription_id =
+    g_dbus_connection_signal_subscribe (connection,
+                                        NULL,
+                                        GNOME_SESSION_PRESENCE_DBUS_IFACE,
+                                        "StatusChanged",
+                                        GNOME_SESSION_PRESENCE_DBUS_PATH,
+                                        NULL,
+                                        G_DBUS_SIGNAL_FLAGS_NONE,
+                                        session_signal_handler,
+                                        NULL,
+                                        NULL);
+
+  object_path = register_gnome_session_client ("gsd-printer", "");
+  if (object_path) {
+          client_signal_subscription_id =
+                  g_dbus_connection_signal_subscribe (connection,
+                                                      NULL,
+                                                      GNOME_SESSION_CLIENT_PRIVATE_DBUS_IFACE,
+                                                      NULL,
+                                                      object_path,
+                                                      NULL,
+                                                      G_DBUS_SIGNAL_FLAGS_NONE,
+                                                      client_signal_handler,
+                                                      NULL,
+                                                      NULL);
+          client_signal_subscription_set = TRUE;
+  }
+
+  if (npn_owner_id == 0)
+          npn_owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM,
+                                         SCP_DBUS_NPN_NAME,
+                                         G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
+                                         G_BUS_NAME_OWNER_FLAGS_REPLACE,
+                                         on_npn_bus_acquired,
+                                         on_name_acquired,
+                                         on_name_lost,
+                                         NULL,
+                                         NULL);
+
+  if (pdi_owner_id == 0)
+          pdi_owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM,
+                                         SCP_DBUS_PDI_NAME,
+                                         G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
+                                         G_BUS_NAME_OWNER_FLAGS_REPLACE,
+                                         on_pdi_bus_acquired,
+                                         on_name_acquired,
+                                         on_name_lost,
+                                         NULL,
+                                         NULL);
+
+  main_loop = g_main_loop_new (NULL, FALSE);
+  g_main_loop_run (main_loop);
+
+  unregister_objects ();
+  unown_names ();
+
+  if (client_signal_subscription_set)
+          g_dbus_connection_signal_unsubscribe (connection, client_signal_subscription_id);
+  g_dbus_connection_signal_unsubscribe (connection, session_signal_subscription_id);
+
+  g_free (object_path);
+
+  g_dbus_node_info_unref (npn_introspection_data);
+  g_dbus_node_info_unref (pdi_introspection_data);
+
+  return 0;
+
+error:
+
+  if (npn_introspection_data)
+          g_dbus_node_info_unref (npn_introspection_data);
+
+  if (pdi_introspection_data)
+          g_dbus_node_info_unref (pdi_introspection_data);
+
+  return 1;
+}
diff --git a/plugins/print-notifications/main.c b/plugins/print-notifications/main.c
new file mode 100644
index 0000000..a0dd406
--- /dev/null
+++ b/plugins/print-notifications/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_print_notifications_manager_new
+#define START gsd_print_notifications_manager_start
+#define STOP gsd_print_notifications_manager_stop
+#define MANAGER GsdPrintNotificationsManager
+#include "gsd-print-notifications-manager.h"
+
+#include "daemon-skeleton.h"
diff --git a/plugins/print-notifications/meson.build b/plugins/print-notifications/meson.build
new file mode 100644
index 0000000..1e1c614
--- /dev/null
+++ b/plugins/print-notifications/meson.build
@@ -0,0 +1,37 @@
+sources = files(
+  'gsd-print-notifications-manager.c',
+  'main.c'
+)
+
+deps = plugins_deps + [
+  cups_dep,
+  gtk_dep,
+  libnotify_dep
+]
+
+cflags += ['-DLIBEXECDIR="@0@"'.format(gsd_libexecdir)]
+cflags += ['-DBINDIR="@0@"'.format(gsd_bindir)]
+
+executable(
+  'gsd-' + plugin_name,
+  sources,
+  include_directories: [top_inc, common_inc],
+  dependencies: deps,
+  c_args: cflags,
+  install: true,
+  install_rpath: gsd_pkglibdir,
+  install_dir: gsd_libexecdir
+)
+
+program = 'gsd-printer'
+
+executable(
+  program,
+  program + '.c',
+  include_directories: top_inc,
+  dependencies: deps,
+  c_args: '-DGNOME_SETTINGS_LOCALEDIR="@0@"'.format(gsd_localedir),
+  install: true,
+  install_rpath: gsd_pkglibdir,
+  install_dir: gsd_libexecdir
+)
diff --git a/plugins/rfkill/61-gnome-settings-daemon-rfkill.rules b/plugins/rfkill/61-gnome-settings-daemon-rfkill.rules
new file mode 100644
index 0000000..87eabff
--- /dev/null
+++ b/plugins/rfkill/61-gnome-settings-daemon-rfkill.rules
@@ -0,0 +1,8 @@
+# Get access to /dev/rfkill for users
+# See https://bugzilla.redhat.com/show_bug.cgi?id=514798
+#
+# Simplified by Kay Sievers
+# https://bugzilla.redhat.com/show_bug.cgi?id=733326
+# See also https://bugzilla.gnome.org/show_bug.cgi?id=711373
+
+KERNEL=="rfkill", SUBSYSTEM=="misc", TAG+="uaccess"
diff --git a/plugins/rfkill/gsd-rfkill-manager.c b/plugins/rfkill/gsd-rfkill-manager.c
new file mode 100644
index 0000000..5c8b690
--- /dev/null
+++ b/plugins/rfkill/gsd-rfkill-manager.c
@@ -0,0 +1,902 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann 
+ * Copyright (C) 2010,2011 Red Hat, Inc.
+ *
+ * Author: Bastien Nocera 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 .
+ *
+ */
+
+/* Test with:
+ *    gdbus call \
+ *        --session \
+ *        --dest org.gnome.SettingsDaemon.Rfkill \
+ *        --object-path /org/gnome/SettingsDaemon/Rfkill \
+ *        --method org.freedesktop.DBus.Properties.Set \
+ *        "org.gnome.SettingsDaemon.Rfkill" \
+ *        "AirplaneMode" \
+ *        ""
+ * and
+ *    gdbus call \
+ *        --session \
+ *        --dest org.gnome.SettingsDaemon.Rfkill \
+ *        --object-path /org/gnome/SettingsDaemon/Rfkill \
+ *        --method org.freedesktop.DBus.Properties.Set \
+ *        "org.gnome.SettingsDaemon.Rfkill" \
+ *        "BluetoothAirplaneMode" \
+ *        ""
+ *
+ * and
+ *    gdbus call \
+ *        --session \
+ *        --dest org.gnome.SettingsDaemon.Rfkill \
+ *        --object-path /org/gnome/SettingsDaemon/Rfkill \
+ *        --method org.freedesktop.DBus.Properties.Set \
+ *        "org.gnome.SettingsDaemon.Rfkill" \
+ *        "WwanAirplaneMode" \
+ *        ""
+ */
+
+#include "config.h"
+
+#include 
+#include 
+
+#include "gnome-settings-profile.h"
+#include "gsd-rfkill-manager.h"
+#include "rfkill-glib.h"
+#include "gnome-settings-bus.h"
+
+struct _GsdRfkillManager
+{
+        GObject                  parent;
+
+        GDBusNodeInfo           *introspection_data;
+        guint                    name_id;
+        GDBusConnection         *connection;
+        GCancellable            *cancellable;
+
+        CcRfkillGlib            *rfkill;
+        GHashTable              *killswitches;
+        GHashTable              *bt_killswitches;
+        GHashTable              *wwan_killswitches;
+
+        /* In addition to using the rfkill kernel subsystem
+           (which is exposed by wlan, wimax, bluetooth, nfc,
+           some platform drivers and some usb modems), we
+           need to go through NetworkManager, which in turn
+           will tell ModemManager to write the right commands
+           in the USB bus to take external modems down, all
+           from userspace.
+        */
+        GDBusProxy              *nm_client;
+        gboolean                 wwan_enabled;
+        GDBusObjectManager      *mm_client;
+        gboolean                 wwan_interesting;
+
+        GsdSessionManager       *session;
+        GBinding                *rfkill_input_inhibit_binding;
+
+        gchar                   *chassis_type;
+};
+
+#define GSD_DBUS_NAME "org.gnome.SettingsDaemon"
+#define GSD_DBUS_PATH "/org/gnome/SettingsDaemon"
+#define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon"
+
+#define GSD_RFKILL_DBUS_NAME GSD_DBUS_NAME ".Rfkill"
+#define GSD_RFKILL_DBUS_PATH GSD_DBUS_PATH "/Rfkill"
+
+static const gchar introspection_xml[] =
+""
+"  "
+"    "
+"    "
+"    "
+"    "
+"    "
+"    "
+"    "
+"    "
+"    "
+"    "
+"    "
+"  "
+"";
+
+static void     gsd_rfkill_manager_class_init  (GsdRfkillManagerClass *klass);
+static void     gsd_rfkill_manager_init        (GsdRfkillManager      *rfkill_manager);
+static void     gsd_rfkill_manager_finalize    (GObject                    *object);
+
+G_DEFINE_TYPE (GsdRfkillManager, gsd_rfkill_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+static void
+gsd_rfkill_manager_class_init (GsdRfkillManagerClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_rfkill_manager_finalize;
+}
+
+static void
+gsd_rfkill_manager_init (GsdRfkillManager *manager)
+{
+}
+
+static gboolean
+engine_get_airplane_mode_helper (GHashTable *killswitches)
+{
+	GHashTableIter iter;
+	gpointer key, value;
+
+	if (g_hash_table_size (killswitches) == 0)
+		return FALSE;
+
+	g_hash_table_iter_init (&iter, killswitches);
+	while (g_hash_table_iter_next (&iter, &key, &value)) {
+		int state;
+
+		state = GPOINTER_TO_INT (value);
+
+		/* A single rfkill switch that's unblocked? Airplane mode is off */
+		if (state == RFKILL_STATE_UNBLOCKED)
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+engine_get_bluetooth_airplane_mode (GsdRfkillManager *manager)
+{
+	return engine_get_airplane_mode_helper (manager->bt_killswitches);
+}
+
+static gboolean
+engine_get_bluetooth_hardware_airplane_mode (GsdRfkillManager *manager)
+{
+	GHashTableIter iter;
+	gpointer key, value;
+
+	/* If we have no killswitches, hw airplane mode is off. */
+	if (g_hash_table_size (manager->bt_killswitches) == 0)
+		return FALSE;
+
+	g_hash_table_iter_init (&iter, manager->bt_killswitches);
+	while (g_hash_table_iter_next (&iter, &key, &value)) {
+		int state;
+
+		state = GPOINTER_TO_INT (value);
+
+		/* A single rfkill switch that's not hw blocked? Hw airplane mode is off */
+		if (state != RFKILL_STATE_HARD_BLOCKED) {
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean
+engine_get_has_bluetooth_airplane_mode (GsdRfkillManager *manager)
+{
+	return (g_hash_table_size (manager->bt_killswitches) > 0);
+}
+
+static gboolean
+engine_get_wwan_airplane_mode (GsdRfkillManager *manager)
+{
+        gboolean is_airplane;
+
+        is_airplane = engine_get_airplane_mode_helper (manager->wwan_killswitches);
+
+        /* Try our luck with Modem Manager too.  Check only if no rfkill
+         * devices found, or if rfkill reports all devices to be down.
+         * (Airplane mode will be disabled if at least one device is up,
+         * so if rfkill says no device is up, check any device is up via
+         * Network Manager (which in turn, is handled via Modem Manager))
+         */
+        if (g_hash_table_size (manager->wwan_killswitches) == 0 || is_airplane)
+                if (manager->wwan_interesting)
+                        is_airplane = !manager->wwan_enabled;
+
+        return is_airplane;
+}
+
+static gboolean
+engine_get_wwan_hardware_airplane_mode (GsdRfkillManager *manager)
+{
+        GHashTableIter iter;
+        gpointer key, value;
+
+        /* If we have no killswitches, hw airplane mode is off. */
+        if (g_hash_table_size (manager->wwan_killswitches) == 0)
+                return FALSE;
+
+        g_hash_table_iter_init (&iter, manager->wwan_killswitches);
+        while (g_hash_table_iter_next (&iter, &key, &value)) {
+                int state;
+
+                state = GPOINTER_TO_INT (value);
+
+                /* A single rfkill switch that's not hw blocked? Hw airplane mode is off */
+                if (state != RFKILL_STATE_HARD_BLOCKED) {
+                        return FALSE;
+                }
+        }
+
+        return TRUE;
+}
+
+static gboolean
+engine_get_has_wwan_airplane_mode (GsdRfkillManager *manager)
+{
+        return (g_hash_table_size (manager->wwan_killswitches) > 0 ||
+                manager->wwan_interesting);
+}
+
+static gboolean
+engine_get_airplane_mode (GsdRfkillManager *manager)
+{
+	if (!manager->wwan_interesting)
+		return engine_get_airplane_mode_helper (manager->killswitches);
+        /* wwan enabled? then airplane mode is off (because an USB modem
+           could be on in this state) */
+	return engine_get_airplane_mode_helper (manager->killswitches) && !manager->wwan_enabled;
+}
+
+static gboolean
+engine_get_hardware_airplane_mode (GsdRfkillManager *manager)
+{
+	GHashTableIter iter;
+	gpointer key, value;
+
+        /* If we have no killswitches, hw airplane mode is off. */
+        if (g_hash_table_size (manager->killswitches) == 0)
+                return FALSE;
+
+	g_hash_table_iter_init (&iter, manager->killswitches);
+	while (g_hash_table_iter_next (&iter, &key, &value)) {
+		int state;
+
+		state = GPOINTER_TO_INT (value);
+
+		/* A single rfkill switch that's not hw blocked? Hw airplane mode is off */
+		if (state != RFKILL_STATE_HARD_BLOCKED) {
+                        return FALSE;
+                }
+	}
+
+        return TRUE;
+}
+
+static gboolean
+engine_get_has_airplane_mode (GsdRfkillManager *manager)
+{
+        return (g_hash_table_size (manager->killswitches) > 0) ||
+                manager->wwan_interesting;
+}
+
+static gboolean
+engine_get_should_show_airplane_mode (GsdRfkillManager *manager)
+{
+        return (g_strcmp0 (manager->chassis_type, "desktop") != 0) &&
+                (g_strcmp0 (manager->chassis_type, "server") != 0) &&
+                (g_strcmp0 (manager->chassis_type, "vm") != 0) &&
+                (g_strcmp0 (manager->chassis_type, "container") != 0);
+}
+
+static void
+engine_properties_changed (GsdRfkillManager *manager)
+{
+        GVariantBuilder props_builder;
+        GVariant *props_changed = NULL;
+
+        /* not yet connected to the session bus */
+        if (manager->connection == NULL)
+                return;
+
+        g_variant_builder_init (&props_builder, G_VARIANT_TYPE ("a{sv}"));
+
+        g_variant_builder_add (&props_builder, "{sv}", "AirplaneMode",
+                               g_variant_new_boolean (engine_get_airplane_mode (manager)));
+        g_variant_builder_add (&props_builder, "{sv}", "HardwareAirplaneMode",
+                               g_variant_new_boolean (engine_get_hardware_airplane_mode (manager)));
+        g_variant_builder_add (&props_builder, "{sv}", "HasAirplaneMode",
+                               g_variant_new_boolean (engine_get_has_airplane_mode (manager)));
+        g_variant_builder_add (&props_builder, "{sv}", "ShouldShowAirplaneMode",
+                               g_variant_new_boolean (engine_get_should_show_airplane_mode (manager)));
+        g_variant_builder_add (&props_builder, "{sv}", "BluetoothAirplaneMode",
+                               g_variant_new_boolean (engine_get_bluetooth_airplane_mode (manager)));
+        g_variant_builder_add (&props_builder, "{sv}", "BluetoothHardwareAirplaneMode",
+                               g_variant_new_boolean (engine_get_bluetooth_hardware_airplane_mode (manager)));
+        g_variant_builder_add (&props_builder, "{sv}", "BluetoothHasAirplaneMode",
+                               g_variant_new_boolean (engine_get_has_bluetooth_airplane_mode (manager)));
+        g_variant_builder_add (&props_builder, "{sv}", "WwanAirplaneMode",
+                               g_variant_new_boolean (engine_get_wwan_airplane_mode (manager)));
+        g_variant_builder_add (&props_builder, "{sv}", "WwanHardwareAirplaneMode",
+                               g_variant_new_boolean (engine_get_wwan_hardware_airplane_mode (manager)));
+        g_variant_builder_add (&props_builder, "{sv}", "WwanHasAirplaneMode",
+                               g_variant_new_boolean (engine_get_has_wwan_airplane_mode (manager)));
+
+        props_changed = g_variant_new ("(s@a{sv}@as)", GSD_RFKILL_DBUS_NAME,
+                                       g_variant_builder_end (&props_builder),
+                                       g_variant_new_strv (NULL, 0));
+
+        g_dbus_connection_emit_signal (manager->connection,
+                                       NULL,
+                                       GSD_RFKILL_DBUS_PATH,
+                                       "org.freedesktop.DBus.Properties",
+                                       "PropertiesChanged",
+                                       props_changed, NULL);
+}
+
+static void
+rfkill_changed (CcRfkillGlib     *rfkill,
+		GList            *events,
+		GsdRfkillManager  *manager)
+{
+	GList *l;
+        int value;
+
+	for (l = events; l != NULL; l = l->next) {
+		struct rfkill_event *event = l->data;
+                const gchar *type = "";
+
+                if (event->type == RFKILL_TYPE_BLUETOOTH)
+                        type = "Bluetooth ";
+                else if (event->type == RFKILL_TYPE_WWAN)
+                        type = "WWAN ";
+
+                switch (event->op) {
+                case RFKILL_OP_ADD:
+                case RFKILL_OP_CHANGE:
+                        if (event->hard)
+                                value = RFKILL_STATE_HARD_BLOCKED;
+                        else if (event->soft)
+                                value = RFKILL_STATE_SOFT_BLOCKED;
+                        else
+                                value = RFKILL_STATE_UNBLOCKED;
+
+                        g_hash_table_insert (manager->killswitches,
+                                             GINT_TO_POINTER (event->idx),
+                                             GINT_TO_POINTER (value));
+                        if (event->type == RFKILL_TYPE_BLUETOOTH)
+				g_hash_table_insert (manager->bt_killswitches,
+						     GINT_TO_POINTER (event->idx),
+						     GINT_TO_POINTER (value));
+                        else if (event->type == RFKILL_TYPE_WWAN)
+                                g_hash_table_insert (manager->wwan_killswitches,
+                                                     GINT_TO_POINTER (event->idx),
+                                                     GINT_TO_POINTER (value));
+                        g_debug ("%s %srfkill with ID %d",
+                                 event->op == RFKILL_OP_ADD ? "Added" : "Changed",
+                                 type, event->idx);
+                        break;
+                case RFKILL_OP_DEL:
+			g_hash_table_remove (manager->killswitches,
+					     GINT_TO_POINTER (event->idx));
+			if (event->type == RFKILL_TYPE_BLUETOOTH)
+				g_hash_table_remove (manager->bt_killswitches,
+						     GINT_TO_POINTER (event->idx));
+                        else if (event->type == RFKILL_TYPE_WWAN)
+                                g_hash_table_remove (manager->wwan_killswitches,
+                                                     GINT_TO_POINTER (event->idx));
+                        g_debug ("Removed %srfkill with ID %d", type, event->idx);
+                        break;
+                }
+	}
+
+        engine_properties_changed (manager);
+}
+
+static void
+rfkill_set_cb (GObject      *source_object,
+	       GAsyncResult *res,
+	       gpointer      user_data)
+{
+	gboolean ret;
+	GError *error = NULL;
+
+	ret = cc_rfkill_glib_send_change_all_event_finish (CC_RFKILL_GLIB (source_object), res, &error);
+	if (!ret) {
+		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
+			g_debug ("Timed out waiting for blocked rfkills");
+		else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			g_warning ("Failed to set RFKill: %s", error->message);
+		g_error_free (error);
+	}
+}
+
+static void
+set_wwan_complete (GObject      *object,
+                   GAsyncResult *result,
+                   gpointer      user_data)
+{
+        GError *error;
+        GVariant *variant;
+
+        error = NULL;
+        variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), result, &error);
+
+        if (variant == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to set WWAN power status: %s", error->message);
+
+                g_error_free (error);
+        } else {
+                g_variant_unref (variant);
+        }
+}
+
+static gboolean
+engine_set_bluetooth_airplane_mode (GsdRfkillManager *manager,
+                                    gboolean          enable)
+{
+        cc_rfkill_glib_send_change_all_event (manager->rfkill, RFKILL_TYPE_BLUETOOTH,
+                                              enable, manager->cancellable, rfkill_set_cb, manager);
+
+        return TRUE;
+}
+
+static gboolean
+engine_set_wwan_airplane_mode (GsdRfkillManager *manager,
+                               gboolean          enable)
+{
+        cc_rfkill_glib_send_change_all_event (manager->rfkill, RFKILL_TYPE_WWAN,
+                                              enable, manager->cancellable, rfkill_set_cb, manager);
+
+        if (manager->nm_client) {
+                g_dbus_proxy_call (manager->nm_client,
+                                   "org.freedesktop.DBus.Properties.Set",
+                                   g_variant_new ("(ssv)",
+                                                  "org.freedesktop.NetworkManager",
+                                                  "WwanEnabled",
+                                                  g_variant_new_boolean (!enable)),
+                                   G_DBUS_CALL_FLAGS_NONE,
+                                   -1, /* timeout */
+                                   manager->cancellable,
+                                   set_wwan_complete, NULL);
+        }
+
+        return TRUE;
+}
+
+static gboolean
+engine_set_airplane_mode (GsdRfkillManager *manager,
+                          gboolean          enable)
+{
+        cc_rfkill_glib_send_change_all_event (manager->rfkill, RFKILL_TYPE_ALL,
+                                              enable, manager->cancellable, rfkill_set_cb, manager);
+
+        /* Note: we set the the NM property even if there are no modems, so we don't
+           need to resync when one is plugged in */
+        if (manager->nm_client) {
+                g_dbus_proxy_call (manager->nm_client,
+                                   "org.freedesktop.DBus.Properties.Set",
+                                   g_variant_new ("(ssv)",
+                                                  "org.freedesktop.NetworkManager",
+                                                  "WwanEnabled",
+                                                  g_variant_new_boolean (!enable)),
+                                   G_DBUS_CALL_FLAGS_NONE,
+                                   -1, /* timeout */
+                                   manager->cancellable,
+                                   set_wwan_complete, NULL);
+        }
+
+	return TRUE;
+}
+
+static gboolean
+handle_set_property (GDBusConnection *connection,
+                     const gchar     *sender,
+                     const gchar     *object_path,
+                     const gchar     *interface_name,
+                     const gchar     *property_name,
+                     GVariant        *value,
+                     GError         **error,
+                     gpointer         user_data)
+{
+        GsdRfkillManager *manager = GSD_RFKILL_MANAGER (user_data);
+
+        if (g_strcmp0 (property_name, "AirplaneMode") == 0) {
+                gboolean airplane_mode;
+                g_variant_get (value, "b", &airplane_mode);
+                return engine_set_airplane_mode (manager, airplane_mode);
+        } else if (g_strcmp0 (property_name, "BluetoothAirplaneMode") == 0) {
+                gboolean airplane_mode;
+                g_variant_get (value, "b", &airplane_mode);
+                return engine_set_bluetooth_airplane_mode (manager, airplane_mode);
+        }
+
+        if (g_strcmp0 (property_name, "WwanAirplaneMode") == 0) {
+                gboolean airplane_mode;
+                g_variant_get (value, "b", &airplane_mode);
+                return engine_set_wwan_airplane_mode (manager, airplane_mode);
+        }
+
+        return FALSE;
+}
+
+static GVariant *
+handle_get_property (GDBusConnection *connection,
+                     const gchar     *sender,
+                     const gchar     *object_path,
+                     const gchar     *interface_name,
+                     const gchar     *property_name,
+                     GError         **error,
+                     gpointer         user_data)
+{
+        GsdRfkillManager *manager = GSD_RFKILL_MANAGER (user_data);
+
+        /* Check session pointer as a proxy for whether the manager is in the
+           start or stop state */
+        if (manager->connection == NULL) {
+                return NULL;
+        }
+
+        if (g_strcmp0 (property_name, "AirplaneMode") == 0) {
+                gboolean airplane_mode;
+                airplane_mode = engine_get_airplane_mode (manager);
+                return g_variant_new_boolean (airplane_mode);
+        }
+
+        if (g_strcmp0 (property_name, "HardwareAirplaneMode") == 0) {
+                gboolean hw_airplane_mode;
+                hw_airplane_mode = engine_get_hardware_airplane_mode (manager);
+                return g_variant_new_boolean (hw_airplane_mode);
+        }
+
+        if (g_strcmp0 (property_name, "ShouldShowAirplaneMode") == 0) {
+                gboolean should_show_airplane_mode;
+                should_show_airplane_mode = engine_get_should_show_airplane_mode (manager);
+                return g_variant_new_boolean (should_show_airplane_mode);
+        }
+
+        if (g_strcmp0 (property_name, "HasAirplaneMode") == 0) {
+                gboolean has_airplane_mode;
+                has_airplane_mode = engine_get_has_airplane_mode (manager);
+                return g_variant_new_boolean (has_airplane_mode);
+        }
+
+        if (g_strcmp0 (property_name, "BluetoothAirplaneMode") == 0) {
+                gboolean airplane_mode;
+                airplane_mode = engine_get_bluetooth_airplane_mode (manager);
+                return g_variant_new_boolean (airplane_mode);
+        }
+
+        if (g_strcmp0 (property_name, "BluetoothHardwareAirplaneMode") == 0) {
+                gboolean hw_airplane_mode;
+                hw_airplane_mode = engine_get_bluetooth_hardware_airplane_mode (manager);
+                return g_variant_new_boolean (hw_airplane_mode);
+        }
+
+        if (g_strcmp0 (property_name, "BluetoothHasAirplaneMode") == 0) {
+                gboolean has_airplane_mode;
+                has_airplane_mode = engine_get_has_bluetooth_airplane_mode (manager);
+                return g_variant_new_boolean (has_airplane_mode);
+        }
+
+        if (g_strcmp0 (property_name, "WwanAirplaneMode") == 0) {
+                gboolean airplane_mode;
+                airplane_mode = engine_get_wwan_airplane_mode (manager);
+                return g_variant_new_boolean (airplane_mode);
+        }
+
+        if (g_strcmp0 (property_name, "WwanHardwareAirplaneMode") == 0) {
+                gboolean hw_airplane_mode;
+                hw_airplane_mode = engine_get_wwan_hardware_airplane_mode (manager);
+                return g_variant_new_boolean (hw_airplane_mode);
+        }
+
+        if (g_strcmp0 (property_name, "WwanHasAirplaneMode") == 0) {
+                gboolean has_airplane_mode;
+                has_airplane_mode = engine_get_has_wwan_airplane_mode (manager);
+                return g_variant_new_boolean (has_airplane_mode);
+        }
+
+        return NULL;
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+        NULL,
+        handle_get_property,
+        handle_set_property
+};
+
+static void
+on_bus_gotten (GObject               *source_object,
+               GAsyncResult          *res,
+               GsdRfkillManager *manager)
+{
+        GDBusConnection *connection;
+        GError *error = NULL;
+
+        connection = g_bus_get_finish (res, &error);
+        if (connection == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Could not get session bus: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+        manager->connection = connection;
+
+        g_dbus_connection_register_object (connection,
+                                           GSD_RFKILL_DBUS_PATH,
+                                           manager->introspection_data->interfaces[0],
+                                           &interface_vtable,
+                                           manager,
+                                           NULL,
+                                           NULL);
+
+        manager->name_id = g_bus_own_name_on_connection (connection,
+                                                               GSD_RFKILL_DBUS_NAME,
+                                                               G_BUS_NAME_OWNER_FLAGS_NONE,
+                                                               NULL,
+                                                               NULL,
+                                                               NULL,
+                                                               NULL);
+
+        manager->session = gnome_settings_bus_get_session_proxy ();
+        manager->rfkill_input_inhibit_binding = g_object_bind_property (manager->session, "session-is-active",
+                                                                              manager->rfkill, "rfkill-input-inhibited",
+                                                                              G_BINDING_SYNC_CREATE);
+}
+
+static void
+sync_wwan_enabled (GsdRfkillManager *manager)
+{
+        GVariant *property;
+
+        property = g_dbus_proxy_get_cached_property (manager->nm_client,
+                                                     "WwanEnabled");
+
+        if (property == NULL) {
+                /* GDBus telling us NM went down */
+                return;
+        }
+
+        manager->wwan_enabled = g_variant_get_boolean (property);
+        engine_properties_changed (manager);
+
+        g_variant_unref (property);
+}
+
+static void
+nm_signal (GDBusProxy *proxy,
+           char       *sender_name,
+           char       *signal_name,
+           GVariant   *parameters,
+           gpointer    user_data)
+{
+        GsdRfkillManager *manager = user_data;
+        GVariant *changed;
+        GVariant *property;
+
+        if (g_strcmp0 (signal_name, "PropertiesChanged") == 0) {
+                changed = g_variant_get_child_value (parameters, 0);
+                property = g_variant_lookup_value (changed, "WwanEnabled", G_VARIANT_TYPE ("b"));
+                g_dbus_proxy_set_cached_property (proxy, "WwanEnabled", property);
+
+                if (property != NULL) {
+                        sync_wwan_enabled (manager);
+                        g_variant_unref (property);
+                }
+
+                g_variant_unref (changed);
+        }
+}
+
+static void
+on_nm_proxy_gotten (GObject      *source,
+                    GAsyncResult *result,
+                    gpointer      user_data)
+{
+        GsdRfkillManager *manager = user_data;
+        GDBusProxy *proxy;
+        GError *error;
+
+        error = NULL;
+        proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+
+        if (proxy == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+                    !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))
+                        g_warning ("Failed to acquire NetworkManager proxy: %s", error->message);
+
+                g_error_free (error);
+                goto out;
+        }
+
+        manager->nm_client = proxy;
+
+        g_signal_connect (manager->nm_client, "g-signal",
+                          G_CALLBACK (nm_signal), manager);
+        sync_wwan_enabled (manager);
+
+ out:
+        g_object_unref (manager);
+}
+
+static void
+sync_wwan_interesting (GDBusObjectManager *object_manager,
+                       GDBusObject        *object,
+                       GDBusInterface     *interface,
+                       gpointer            user_data)
+{
+        GsdRfkillManager *manager = user_data;
+        GList *objects;
+
+        objects = g_dbus_object_manager_get_objects (object_manager);
+        manager->wwan_interesting = (objects != NULL);
+        engine_properties_changed (manager);
+
+        g_list_free_full (objects, g_object_unref);
+}
+
+static void
+on_mm_proxy_gotten (GObject      *source,
+                    GAsyncResult *result,
+                    gpointer      user_data)
+{
+        GsdRfkillManager *manager = user_data;
+        GDBusObjectManager *proxy;
+        GError *error;
+
+        error = NULL;
+        proxy = g_dbus_object_manager_client_new_for_bus_finish (result, &error);
+
+        if (proxy == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+                    !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))
+                        g_warning ("Failed to acquire ModemManager proxy: %s", error->message);
+
+                g_error_free (error);
+                goto out;
+        }
+
+        manager->mm_client = proxy;
+
+        g_signal_connect (manager->mm_client, "interface-added",
+                          G_CALLBACK (sync_wwan_interesting), manager);
+        g_signal_connect (manager->mm_client, "interface-removed",
+                          G_CALLBACK (sync_wwan_interesting), manager);
+        sync_wwan_interesting (manager->mm_client, NULL, NULL, manager);
+
+ out:
+        g_object_unref (manager);
+}
+
+gboolean
+gsd_rfkill_manager_start (GsdRfkillManager *manager,
+                         GError         **error)
+{
+        g_autoptr(GError) local_error = NULL;
+
+        gnome_settings_profile_start (NULL);
+
+        manager->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+        g_assert (manager->introspection_data != NULL);
+
+        manager->killswitches = g_hash_table_new (g_direct_hash, g_direct_equal);
+        manager->bt_killswitches = g_hash_table_new (g_direct_hash, g_direct_equal);
+        manager->wwan_killswitches = g_hash_table_new (g_direct_hash, g_direct_equal);
+        manager->rfkill = cc_rfkill_glib_new ();
+        g_signal_connect (G_OBJECT (manager->rfkill), "changed",
+                          G_CALLBACK (rfkill_changed), manager);
+
+        if (!cc_rfkill_glib_open (manager->rfkill, &local_error)) {
+                g_warning ("Error setting up rfkill: %s", local_error->message);
+                g_clear_error (&local_error);
+        }
+
+        manager->cancellable = g_cancellable_new ();
+
+        manager->chassis_type = gnome_settings_get_chassis_type ();
+
+        g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+                                  G_DBUS_PROXY_FLAGS_NONE,
+                                  NULL, /* g-interface-info */
+                                  "org.freedesktop.NetworkManager",
+                                  "/org/freedesktop/NetworkManager",
+                                  "org.freedesktop.NetworkManager",
+                                  manager->cancellable,
+                                  on_nm_proxy_gotten, g_object_ref (manager));
+
+        g_dbus_object_manager_client_new_for_bus (G_BUS_TYPE_SYSTEM,
+                                                  G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
+                                                  "org.freedesktop.ModemManager1",
+                                                  "/org/freedesktop/ModemManager1",
+                                                  NULL, NULL, NULL, /* get_proxy_type and closure */
+                                                  manager->cancellable,
+                                                  on_mm_proxy_gotten, g_object_ref (manager));
+
+        /* Start process of owning a D-Bus name */
+        g_bus_get (G_BUS_TYPE_SESSION,
+                   manager->cancellable,
+                   (GAsyncReadyCallback) on_bus_gotten,
+                   manager);
+
+        gnome_settings_profile_end (NULL);
+
+        return TRUE;
+}
+
+void
+gsd_rfkill_manager_stop (GsdRfkillManager *manager)
+{
+        g_debug ("Stopping rfkill manager");
+
+        if (manager->name_id != 0) {
+                g_bus_unown_name (manager->name_id);
+                manager->name_id = 0;
+        }
+
+        g_clear_pointer (&manager->introspection_data, g_dbus_node_info_unref);
+        g_clear_object (&manager->connection);
+        g_clear_object (&manager->rfkill_input_inhibit_binding);
+        g_clear_object (&manager->session);
+        g_clear_object (&manager->rfkill);
+        g_clear_pointer (&manager->killswitches, g_hash_table_destroy);
+        g_clear_pointer (&manager->bt_killswitches, g_hash_table_destroy);
+        g_clear_pointer (&manager->wwan_killswitches, g_hash_table_destroy);
+
+        if (manager->cancellable) {
+                g_cancellable_cancel (manager->cancellable);
+                g_clear_object (&manager->cancellable);
+        }
+
+        g_clear_object (&manager->nm_client);
+        g_clear_object (&manager->mm_client);
+        manager->wwan_enabled = FALSE;
+        manager->wwan_interesting = FALSE;
+
+        g_clear_pointer (&manager->chassis_type, g_free);
+}
+
+static void
+gsd_rfkill_manager_finalize (GObject *object)
+{
+        GsdRfkillManager *manager;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSD_IS_RFKILL_MANAGER (object));
+
+        manager = GSD_RFKILL_MANAGER (object);
+
+        g_return_if_fail (manager != NULL);
+
+        gsd_rfkill_manager_stop (manager);
+
+        G_OBJECT_CLASS (gsd_rfkill_manager_parent_class)->finalize (object);
+}
+
+GsdRfkillManager *
+gsd_rfkill_manager_new (void)
+{
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_RFKILL_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+        }
+
+        return GSD_RFKILL_MANAGER (manager_object);
+}
diff --git a/plugins/rfkill/gsd-rfkill-manager.h b/plugins/rfkill/gsd-rfkill-manager.h
new file mode 100644
index 0000000..025a7d8
--- /dev/null
+++ b/plugins/rfkill/gsd-rfkill-manager.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann 
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_RFKILL_MANAGER_H
+#define __GSD_RFKILL_MANAGER_H
+
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_RFKILL_MANAGER         (gsd_rfkill_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdRfkillManager, gsd_rfkill_manager, GSD, RFKILL_MANAGER, GObject)
+
+GsdRfkillManager *       gsd_rfkill_manager_new                 (void);
+gboolean                gsd_rfkill_manager_start               (GsdRfkillManager *manager,
+                                                               GError         **error);
+void                    gsd_rfkill_manager_stop                (GsdRfkillManager *manager);
+
+G_END_DECLS
+
+#endif /* __GSD_RFKILL_MANAGER_H */
diff --git a/plugins/rfkill/main.c b/plugins/rfkill/main.c
new file mode 100644
index 0000000..4f19f5d
--- /dev/null
+++ b/plugins/rfkill/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_rfkill_manager_new
+#define START gsd_rfkill_manager_start
+#define STOP gsd_rfkill_manager_stop
+#define MANAGER GsdRfkillManager
+#include "gsd-rfkill-manager.h"
+
+#include "daemon-skeleton.h"
diff --git a/plugins/rfkill/meson.build b/plugins/rfkill/meson.build
new file mode 100644
index 0000000..4d70352
--- /dev/null
+++ b/plugins/rfkill/meson.build
@@ -0,0 +1,28 @@
+install_data(
+  '61-gnome-settings-daemon-rfkill.rules',
+  install_dir: join_paths(udev_dir, 'rules.d')
+)
+
+sources = files(
+  'gsd-rfkill-manager.c',
+  'rfkill-glib.c',
+  'main.c'
+)
+
+deps = plugins_deps
+deps += [
+  gio_unix_dep,
+  gudev_dep,
+  m_dep
+]
+
+executable(
+  'gsd-' + plugin_name,
+  sources,
+  include_directories: [top_inc, common_inc],
+  dependencies: deps,
+  c_args: cflags,
+  install: true,
+  install_rpath: gsd_pkglibdir,
+  install_dir: gsd_libexecdir
+)
diff --git a/plugins/rfkill/rfkill-glib.c b/plugins/rfkill/rfkill-glib.c
new file mode 100644
index 0000000..b919ca8
--- /dev/null
+++ b/plugins/rfkill/rfkill-glib.c
@@ -0,0 +1,641 @@
+/*
+ *
+ *  gnome-bluetooth - Bluetooth integration for GNOME
+ *
+ *  Copyright (C) 2012  Bastien Nocera 
+ *  Copyright © 2017 Endless Mobile, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include 
+#endif
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include "rfkill-glib.h"
+#include 
+
+enum {
+	CHANGED,
+	LAST_SIGNAL
+};
+
+enum {
+	PROP_RFKILL_INPUT_INHIBITED = 1
+};
+
+static int signals[LAST_SIGNAL] = { 0 };
+
+struct _CcRfkillGlib {
+	GObject parent;
+
+	GUdevClient *udev;
+	gchar *device_file;
+
+	GOutputStream *stream;
+	GIOChannel *channel;
+	guint watch_id;
+
+	/* rfkill-input inhibitor */
+	gboolean noinput;
+	int noinput_fd;
+
+	/* Pending Bluetooth enablement.
+	 * If (@change_all_timeout_id != 0), then (task != NULL). The converse
+	 * does not necessarily hold. */
+	guint change_all_timeout_id;
+	GTask *task;
+};
+
+G_DEFINE_TYPE (CcRfkillGlib, cc_rfkill_glib, G_TYPE_OBJECT)
+
+#define CHANGE_ALL_TIMEOUT 500
+
+static const char *type_to_string (unsigned int type);
+
+static void
+cancel_current_task (CcRfkillGlib *rfkill)
+{
+	if (rfkill->task != NULL) {
+		g_cancellable_cancel (g_task_get_cancellable (rfkill->task));
+		g_clear_object (&rfkill->task);
+	}
+
+	if (rfkill->change_all_timeout_id != 0) {
+		g_source_remove (rfkill->change_all_timeout_id);
+		rfkill->change_all_timeout_id = 0;
+	}
+}
+
+/* Note that this can return %FALSE without setting @error. */
+gboolean
+cc_rfkill_glib_send_change_all_event_finish (CcRfkillGlib        *rfkill,
+					     GAsyncResult        *res,
+					     GError             **error)
+{
+	g_return_val_if_fail (CC_RFKILL_IS_GLIB (rfkill), FALSE);
+	g_return_val_if_fail (g_task_is_valid (res, rfkill), FALSE);
+	g_return_val_if_fail (g_async_result_is_tagged (res, cc_rfkill_glib_send_change_all_event), FALSE);
+
+	return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+write_change_all_again_done_cb (GObject      *source_object,
+				GAsyncResult *res,
+				gpointer      user_data)
+{
+	g_autoptr(GTask) task = G_TASK (user_data);
+	CcRfkillGlib *rfkill = g_task_get_source_object (task);
+	g_autoptr(GError) error = NULL;
+	gssize ret;
+
+	g_debug ("Finished writing second RFKILL_OP_CHANGE_ALL event");
+
+	ret = g_output_stream_write_finish (G_OUTPUT_STREAM (source_object), res, &error);
+	if (ret < 0)
+		g_task_return_error (task, g_steal_pointer (&error));
+	else
+		g_task_return_boolean (task, ret >= 0);
+
+	/* If this @task has been cancelled, it may have been superceded. */
+	if (rfkill->task == task)
+		g_clear_object (&rfkill->task);
+}
+
+static gboolean
+write_change_all_timeout_cb (CcRfkillGlib *rfkill)
+{
+	struct rfkill_event *event;
+
+	g_assert (rfkill->task != NULL);
+
+	g_debug ("Sending second RFKILL_OP_CHANGE_ALL timed out");
+
+	event = g_task_get_task_data (rfkill->task);
+	g_task_return_new_error (rfkill->task,
+				 G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+				 "Enabling rfkill for %s timed out",
+				 type_to_string (event->type));
+
+	g_clear_object (&rfkill->task);
+	rfkill->change_all_timeout_id = 0;
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
+write_change_all_done_cb (GObject      *source_object,
+			  GAsyncResult *res,
+			  gpointer      user_data)
+{
+	g_autoptr(GTask) task = G_TASK (user_data);
+	CcRfkillGlib *rfkill = g_task_get_source_object (task);
+	g_autoptr(GError) error = NULL;
+	gssize ret;
+	struct rfkill_event *event;
+
+	g_debug ("Sending original RFKILL_OP_CHANGE_ALL event done");
+
+	event = g_task_get_task_data (task);
+	ret = g_output_stream_write_finish (G_OUTPUT_STREAM (source_object), res, &error);
+	if (ret < 0) {
+		g_task_return_error (task, g_steal_pointer (&error));
+		goto bail;
+	} else if (event->soft == 1 ||
+		   event->type != RFKILL_TYPE_BLUETOOTH) {
+		g_task_return_boolean (task, ret >= 0);
+		goto bail;
+	}
+
+	g_assert (rfkill->change_all_timeout_id == 0);
+	rfkill->change_all_timeout_id = g_timeout_add (CHANGE_ALL_TIMEOUT,
+						       (GSourceFunc) write_change_all_timeout_cb,
+						       rfkill);
+
+	return;
+
+bail:
+	/* If this @task has been cancelled, it may have been superceded. */
+	if (rfkill->task == task)
+		g_clear_object (&rfkill->task);
+}
+
+void
+cc_rfkill_glib_send_change_all_event (CcRfkillGlib        *rfkill,
+				      guint                rfkill_type,
+				      gboolean             enable,
+				      GCancellable        *cancellable,
+				      GAsyncReadyCallback  callback,
+				      gpointer             user_data)
+{
+	g_autoptr(GTask) task = NULL;
+	struct rfkill_event *event;
+	g_autoptr(GCancellable) task_cancellable = NULL;
+
+	g_return_if_fail (CC_RFKILL_IS_GLIB (rfkill));
+	g_return_if_fail (rfkill->stream);
+
+	task_cancellable = g_cancellable_new ();
+	g_signal_connect_object (cancellable, "cancelled",
+				 (GCallback) g_cancellable_cancel,
+				 task_cancellable,
+				 G_CONNECT_SWAPPED);
+	/* Now check if it is cancelled already */
+	if (g_cancellable_is_cancelled (cancellable))
+		g_cancellable_cancel (task_cancellable);
+
+	task = g_task_new (rfkill, task_cancellable, callback, user_data);
+	g_task_set_source_tag (task, cc_rfkill_glib_send_change_all_event);
+
+	/* Clear any previous task. */
+	cancel_current_task (rfkill);
+	g_assert (rfkill->task == NULL);
+
+	/* Start writing out a new event. */
+	event = g_new0 (struct rfkill_event, 1);
+	event->op = RFKILL_OP_CHANGE_ALL;
+	event->type = rfkill_type;
+	event->soft = enable ? 1 : 0;
+
+	g_task_set_task_data (task, event, g_free);
+	rfkill->task = g_object_ref (task);
+	rfkill->change_all_timeout_id = 0;
+
+	g_output_stream_write_async (rfkill->stream,
+				     event, sizeof(struct rfkill_event),
+				     G_PRIORITY_DEFAULT,
+				     task_cancellable, write_change_all_done_cb,
+				     g_object_ref (task));
+}
+
+static const char *
+type_to_string (unsigned int type)
+{
+	switch (type) {
+	case RFKILL_TYPE_ALL:
+		return "ALL";
+	case RFKILL_TYPE_WLAN:
+		return "WLAN";
+	case RFKILL_TYPE_BLUETOOTH:
+		return "BLUETOOTH";
+	case RFKILL_TYPE_UWB:
+		return "UWB";
+	case RFKILL_TYPE_WIMAX:
+		return "WIMAX";
+	case RFKILL_TYPE_WWAN:
+		return "WWAN";
+	default:
+		return "UNKNOWN";
+	}
+}
+
+static const char *
+op_to_string (unsigned int op)
+{
+	switch (op) {
+	case RFKILL_OP_ADD:
+		return "ADD";
+	case RFKILL_OP_DEL:
+		return "DEL";
+	case RFKILL_OP_CHANGE:
+		return "CHANGE";
+	case RFKILL_OP_CHANGE_ALL:
+		return "CHANGE_ALL";
+	default:
+		g_assert_not_reached ();
+	}
+}
+
+static void
+print_event (struct rfkill_event *event)
+{
+	g_debug ("RFKILL event: idx %u type %u (%s) op %u (%s) soft %u hard %u",
+		 event->idx,
+		 event->type, type_to_string (event->type),
+		 event->op, op_to_string (event->op),
+		 event->soft, event->hard);
+}
+
+static gboolean
+got_change_event (GList *events)
+{
+	GList *l;
+
+	g_assert (events != NULL);
+
+	for (l = events ; l != NULL; l = l->next) {
+		struct rfkill_event *event = l->data;
+
+		if (event->op == RFKILL_OP_CHANGE)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void
+emit_changed_signal_and_free (CcRfkillGlib *rfkill,
+			      GList        *events)
+{
+	if (events == NULL)
+		return;
+
+	g_signal_emit (G_OBJECT (rfkill),
+		       signals[CHANGED],
+		       0, events);
+
+	if (rfkill->change_all_timeout_id > 0 &&
+	    got_change_event (events)) {
+		struct rfkill_event *event;
+
+		g_debug ("Received a change event after a RFKILL_OP_CHANGE_ALL event, re-sending RFKILL_OP_CHANGE_ALL");
+
+		event = g_task_get_task_data (rfkill->task);
+		g_output_stream_write_async (rfkill->stream,
+					     event, sizeof(struct rfkill_event),
+					     G_PRIORITY_DEFAULT,
+					     g_task_get_cancellable (rfkill->task),
+					     write_change_all_again_done_cb,
+					     g_object_ref (rfkill->task));
+
+		g_source_remove (rfkill->change_all_timeout_id);
+		rfkill->change_all_timeout_id = 0;
+	}
+
+	g_list_free_full (events, g_free);
+}
+
+static gboolean
+event_cb (GIOChannel   *source,
+	  GIOCondition  condition,
+	  CcRfkillGlib   *rfkill)
+{
+	GList *events;
+
+	events = NULL;
+
+	if (condition & G_IO_IN) {
+		GIOStatus status;
+		struct rfkill_event event = { 0 };
+		gsize read;
+
+		status = g_io_channel_read_chars (source,
+						  (char *) &event,
+						  sizeof(event),
+						  &read,
+						  NULL);
+
+		while (status == G_IO_STATUS_NORMAL && read >= RFKILL_EVENT_SIZE_V1) {
+			struct rfkill_event *event_ptr;
+
+			print_event (&event);
+
+			event_ptr = g_memdup (&event, sizeof(event));
+			events = g_list_prepend (events, event_ptr);
+
+			status = g_io_channel_read_chars (source,
+							  (char *) &event,
+							  sizeof(event),
+							  &read,
+							  NULL);
+		}
+		events = g_list_reverse (events);
+	} else {
+		g_debug ("Something unexpected happened on rfkill fd");
+		return FALSE;
+	}
+
+	emit_changed_signal_and_free (rfkill, events);
+
+	return TRUE;
+}
+
+static void
+cc_rfkill_glib_init (CcRfkillGlib *rfkill)
+{
+	rfkill->device_file = NULL;
+	rfkill->noinput_fd = -1;
+}
+
+static gboolean
+_cc_rfkill_glib_open (CcRfkillGlib  *rfkill,
+                      GError       **error)
+{
+	int fd;
+	int ret;
+
+	g_return_val_if_fail (CC_RFKILL_IS_GLIB (rfkill), FALSE);
+	g_return_val_if_fail (rfkill->stream == NULL, FALSE);
+	g_assert (rfkill->device_file);
+
+	fd = open (rfkill->device_file, O_RDWR);
+
+	if (fd < 0) {
+		g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno),
+		                     "Could not open RFKILL control device, please verify your installation");
+		return FALSE;
+	}
+
+	ret = fcntl(fd, F_SETFL, O_NONBLOCK);
+	if (ret < 0) {
+		g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno),
+		                     "Can't set RFKILL control device to non-blocking");
+		close(fd);
+		return FALSE;
+	}
+
+	/* Setup monitoring */
+	rfkill->channel = g_io_channel_unix_new (fd);
+	g_io_channel_set_encoding (rfkill->channel, NULL, NULL);
+	g_io_channel_set_buffered (rfkill->channel, FALSE);
+	rfkill->watch_id = g_io_add_watch (rfkill->channel,
+					   G_IO_IN | G_IO_HUP | G_IO_ERR,
+					   (GIOFunc) event_cb,
+					   rfkill);
+
+	/* Setup write stream */
+	rfkill->stream = g_unix_output_stream_new (fd, TRUE);
+
+	return TRUE;
+}
+
+static void
+uevent_cb (GUdevClient *client,
+           gchar       *action,
+           GUdevDevice *device,
+           gpointer     user_data)
+{
+	CcRfkillGlib  *rfkill = CC_RFKILL_GLIB (user_data);
+
+	if (g_strcmp0 (action, "add") != 0)
+		return;
+
+	if (g_strcmp0 (g_udev_device_get_name (device), "rfkill") == 0) {
+		g_autoptr(GError) error = NULL;
+
+		g_debug ("Rfkill device has been created");
+
+		if (g_udev_device_get_device_file (device)) {
+			g_clear_pointer (&rfkill->device_file, g_free);
+			rfkill->device_file = g_strdup (g_udev_device_get_device_file (device));
+		} else {
+			g_warning ("rfkill udev device does not have a device file!");
+		}
+
+		if (!_cc_rfkill_glib_open (rfkill, &error))
+			g_warning ("Could not open rfkill device: %s", error->message);
+		else
+			g_debug ("Opened rfkill device after uevent");
+
+		g_clear_object (&rfkill->udev);
+
+		/* Sync rfkill input inhibition state*/
+		cc_rfkill_glib_set_rfkill_input_inhibited (rfkill, rfkill->noinput);
+	}
+}
+
+gboolean
+cc_rfkill_glib_open (CcRfkillGlib  *rfkill,
+                     GError       **error)
+{
+	const char * const subsystems[] = { "misc", NULL };
+	GUdevDevice *device;
+
+	rfkill->udev = g_udev_client_new (subsystems);
+	g_debug ("Setting up uevent listener");
+	g_signal_connect (rfkill->udev, "uevent", G_CALLBACK (uevent_cb), rfkill);
+
+	/* Simulate uevent if device already exists. */
+	device = g_udev_client_query_by_subsystem_and_name (rfkill->udev, "misc", "rfkill");
+	if (device)
+		uevent_cb (rfkill->udev, "add", device, rfkill);
+
+	return TRUE;
+}
+
+#define RFKILL_INPUT_INHIBITED(rfkill) (rfkill->noinput_fd >= 0)
+
+gboolean
+cc_rfkill_glib_get_rfkill_input_inhibited (CcRfkillGlib        *rfkill)
+{
+	g_return_val_if_fail (CC_RFKILL_IS_GLIB (rfkill), FALSE);
+
+	return rfkill->noinput;
+}
+
+void
+cc_rfkill_glib_set_rfkill_input_inhibited (CcRfkillGlib *rfkill,
+					   gboolean      inhibit)
+{
+	g_return_if_fail (CC_RFKILL_IS_GLIB (rfkill));
+
+	/* Shortcut in case we don't have an rfkill device */
+	if (!rfkill->stream) {
+		if (rfkill->noinput == inhibit)
+			return;
+
+		rfkill->noinput = inhibit;
+		g_object_notify (G_OBJECT (rfkill), "rfkill-input-inhibited");
+
+		return;
+	}
+
+	if (!inhibit && RFKILL_INPUT_INHIBITED(rfkill)) {
+		close (rfkill->noinput_fd);
+		g_debug ("Closed rfkill noinput FD.");
+
+		rfkill->noinput_fd = -1;
+	}
+
+	if (inhibit && !RFKILL_INPUT_INHIBITED(rfkill)) {
+		int fd, res;
+		/* Open write only as we don't want to do any IO to it ever. */
+		fd = open (rfkill->device_file, O_WRONLY);
+		if (fd < 0) {
+			if (errno == EACCES)
+				g_warning ("Could not open RFKILL control device, please verify your installation");
+			else
+				g_debug ("Could not open RFKILL control device: %s", g_strerror (errno));
+			return;
+		}
+
+		res = ioctl (fd, RFKILL_IOCTL_NOINPUT, (long) 0);
+		if (res != 0) {
+			g_warning ("Could not disable kernel handling of RFKILL related keys: %s", g_strerror (errno));
+			close (fd);
+			return;
+		}
+
+		g_debug ("Opened rfkill-input inhibitor.");
+
+		rfkill->noinput_fd = fd;
+	}
+
+	if (rfkill->noinput != RFKILL_INPUT_INHIBITED(rfkill)) {
+		rfkill->noinput = RFKILL_INPUT_INHIBITED(rfkill);
+		g_object_notify (G_OBJECT (rfkill), "rfkill-input-inhibited");
+	}
+}
+
+static void
+cc_rfkill_glib_set_property (GObject      *object,
+			 guint	       prop_id,
+			 const GValue *value,
+			 GParamSpec   *pspec)
+{
+	CcRfkillGlib *rfkill = CC_RFKILL_GLIB (object);
+
+	switch (prop_id) {
+	case PROP_RFKILL_INPUT_INHIBITED:
+		cc_rfkill_glib_set_rfkill_input_inhibited (rfkill, g_value_get_boolean (value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+cc_rfkill_glib_get_property (GObject    *object,
+			 guint	     prop_id,
+			 GValue	    *value,
+			 GParamSpec *pspec)
+{
+	CcRfkillGlib *rfkill = CC_RFKILL_GLIB (object);
+
+	switch (prop_id) {
+	case PROP_RFKILL_INPUT_INHIBITED:
+		g_value_set_boolean (value, rfkill->noinput);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+cc_rfkill_glib_finalize (GObject *object)
+{
+	CcRfkillGlib *rfkill = CC_RFKILL_GLIB (object);
+
+	cancel_current_task (rfkill);
+
+	/* cleanup monitoring */
+	if (rfkill->watch_id > 0) {
+		g_source_remove (rfkill->watch_id);
+		rfkill->watch_id = 0;
+		g_io_channel_shutdown (rfkill->channel, FALSE, NULL);
+		g_io_channel_unref (rfkill->channel);
+	}
+	g_clear_object (&rfkill->stream);
+
+	if (RFKILL_INPUT_INHIBITED(rfkill)) {
+		close (rfkill->noinput_fd);
+		rfkill->noinput_fd = -1;
+	}
+
+	g_clear_pointer (&rfkill->device_file, g_free);
+	g_clear_object (&rfkill->udev);
+
+	G_OBJECT_CLASS(cc_rfkill_glib_parent_class)->finalize(object);
+}
+
+static void
+cc_rfkill_glib_class_init(CcRfkillGlibClass *klass)
+{
+	GObjectClass *object_class = (GObjectClass *) klass;
+
+	object_class->set_property = cc_rfkill_glib_set_property;
+	object_class->get_property = cc_rfkill_glib_get_property;
+	object_class->finalize = cc_rfkill_glib_finalize;
+
+	g_object_class_install_property (object_class,
+					 PROP_RFKILL_INPUT_INHIBITED,
+					 g_param_spec_boolean ("rfkill-input-inhibited",
+							       "Rfkill input inhibited",
+							       "Whether to prevent the kernel from handling RFKILL related key events.",
+							       FALSE,
+							       G_PARAM_READWRITE));
+
+	signals[CHANGED] =
+		g_signal_new ("changed",
+			      G_TYPE_FROM_CLASS (klass),
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      NULL,
+			      G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+}
+
+CcRfkillGlib *
+cc_rfkill_glib_new (void)
+{
+	return CC_RFKILL_GLIB (g_object_new (CC_RFKILL_TYPE_GLIB, NULL));
+}
diff --git a/plugins/rfkill/rfkill-glib.h b/plugins/rfkill/rfkill-glib.h
new file mode 100644
index 0000000..0655eb4
--- /dev/null
+++ b/plugins/rfkill/rfkill-glib.h
@@ -0,0 +1,57 @@
+/*
+ *
+ *  gnome-bluetooth - Bluetooth integration for GNOME
+ *
+ *  Copyright (C) 2012  Bastien Nocera 
+ *  Copyright © 2017  Endless Mobile, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef __CC_RFKILL_GLIB_H
+#define __CC_RFKILL_GLIB_H
+
+#include 
+#include 
+#include "rfkill.h"
+
+G_BEGIN_DECLS
+
+#define CC_RFKILL_TYPE_GLIB cc_rfkill_glib_get_type ()
+G_DECLARE_FINAL_TYPE (CcRfkillGlib, cc_rfkill_glib, CC_RFKILL, GLIB, GObject)
+
+CcRfkillGlib *cc_rfkill_glib_new               (void);
+gboolean      cc_rfkill_glib_open              (CcRfkillGlib  *rfkill,
+                                                GError       **error);
+
+gboolean      cc_rfkill_glib_get_rfkill_input_inhibited (CcRfkillGlib        *rfkill);
+void          cc_rfkill_glib_set_rfkill_input_inhibited (CcRfkillGlib        *rfkill,
+							 gboolean             noinput);
+
+void          cc_rfkill_glib_send_change_all_event        (CcRfkillGlib        *rfkill,
+							   guint                rfkill_type,
+							   gboolean             enable,
+							   GCancellable        *cancellable,
+							   GAsyncReadyCallback  callback,
+							   gpointer             user_data);
+
+gboolean      cc_rfkill_glib_send_change_all_event_finish (CcRfkillGlib        *rfkill,
+							   GAsyncResult        *res,
+							   GError             **error);
+
+G_END_DECLS
+
+#endif /* __CC_RFKILL_GLIB_H */
diff --git a/plugins/rfkill/rfkill.h b/plugins/rfkill/rfkill.h
new file mode 100644
index 0000000..abb2c66
--- /dev/null
+++ b/plugins/rfkill/rfkill.h
@@ -0,0 +1,107 @@
+#ifndef __RFKILL_H
+#define __RFKILL_H
+
+/*
+ * Copyright (C) 2006 - 2007 Ivo van Doorn
+ * Copyright (C) 2007 Dmitry Torokhov
+ * Copyright 2009 Johannes Berg 
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include 
+
+/* define userspace visible states */
+#define RFKILL_STATE_SOFT_BLOCKED	0
+#define RFKILL_STATE_UNBLOCKED		1
+#define RFKILL_STATE_HARD_BLOCKED	2
+
+/**
+ * enum rfkill_type - type of rfkill switch.
+ *
+ * @RFKILL_TYPE_ALL: toggles all switches (requests only - not a switch type)
+ * @RFKILL_TYPE_WLAN: switch is on a 802.11 wireless network device.
+ * @RFKILL_TYPE_BLUETOOTH: switch is on a bluetooth device.
+ * @RFKILL_TYPE_UWB: switch is on a ultra wideband device.
+ * @RFKILL_TYPE_WIMAX: switch is on a WiMAX device.
+ * @RFKILL_TYPE_WWAN: switch is on a wireless WAN device.
+ * @RFKILL_TYPE_GPS: switch is on a GPS device.
+ * @RFKILL_TYPE_FM: switch is on a FM radio device.
+ * @NUM_RFKILL_TYPES: number of defined rfkill types
+ */
+enum rfkill_type {
+	RFKILL_TYPE_ALL = 0,
+	RFKILL_TYPE_WLAN,
+	RFKILL_TYPE_BLUETOOTH,
+	RFKILL_TYPE_UWB,
+	RFKILL_TYPE_WIMAX,
+	RFKILL_TYPE_WWAN,
+	RFKILL_TYPE_GPS,
+	RFKILL_TYPE_FM,
+	NUM_RFKILL_TYPES,
+};
+
+/**
+ * enum rfkill_operation - operation types
+ * @RFKILL_OP_ADD: a device was added
+ * @RFKILL_OP_DEL: a device was removed
+ * @RFKILL_OP_CHANGE: a device's state changed -- userspace changes one device
+ * @RFKILL_OP_CHANGE_ALL: userspace changes all devices (of a type, or all)
+ */
+enum rfkill_operation {
+	RFKILL_OP_ADD = 0,
+	RFKILL_OP_DEL,
+	RFKILL_OP_CHANGE,
+	RFKILL_OP_CHANGE_ALL,
+};
+
+/**
+ * struct rfkill_event - events for userspace on /dev/rfkill
+ * @idx: index of dev rfkill
+ * @type: type of the rfkill struct
+ * @op: operation code
+ * @hard: hard state (0/1)
+ * @soft: soft state (0/1)
+ *
+ * Structure used for userspace communication on /dev/rfkill,
+ * used for events from the kernel and control to the kernel.
+ */
+struct rfkill_event {
+	__u32 idx;
+	__u8  type;
+	__u8  op;
+	__u8  soft, hard;
+} __attribute__((packed));
+
+/*
+ * We are planning to be backward and forward compatible with changes
+ * to the event struct, by adding new, optional, members at the end.
+ * When reading an event (whether the kernel from userspace or vice
+ * versa) we need to accept anything that's at least as large as the
+ * version 1 event size, but might be able to accept other sizes in
+ * the future.
+ *
+ * One exception is the kernel -- we already have two event sizes in
+ * that we've made the 'hard' member optional since our only option
+ * is to ignore it anyway.
+ */
+#define RFKILL_EVENT_SIZE_V1	8
+
+/* ioctl for turning off rfkill-input (if present) */
+#define RFKILL_IOC_MAGIC	'R'
+#define RFKILL_IOC_NOINPUT	1
+#define RFKILL_IOCTL_NOINPUT	_IO(RFKILL_IOC_MAGIC, RFKILL_IOC_NOINPUT)
+
+/* and that's all userspace gets */
+
+#endif /* RFKILL_H */
diff --git a/plugins/screensaver-proxy/gsd-screensaver-proxy-manager.c b/plugins/screensaver-proxy/gsd-screensaver-proxy-manager.c
new file mode 100644
index 0000000..e434eb9
--- /dev/null
+++ b/plugins/screensaver-proxy/gsd-screensaver-proxy-manager.c
@@ -0,0 +1,444 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Bastien Nocera 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+#include "gnome-settings-bus.h"
+#include "gnome-settings-profile.h"
+#include "gsd-screensaver-proxy-manager.h"
+
+/* As available in:
+ * https://projects.kde.org/projects/kde/kde-workspace/repository/revisions/master/entry/ksmserver/screenlocker/dbus/org.freedesktop.ScreenSaver.xml
+ * and documented in:
+ * https://projects.kde.org/projects/kde/kde-workspace/repository/revisions/master/entry/ksmserver/screenlocker/interface.h */
+static const gchar introspection_xml[] =
+""
+    ""
+    ""
+    ""
+    ""
+      ""
+    ""
+    ""
+      ""
+    ""
+    ""
+      ""
+    ""
+    ""
+      ""
+      ""
+    ""
+    ""
+      ""
+      ""
+      ""
+    ""
+    ""
+      ""
+    ""
+    ""
+      ""
+      ""
+      ""
+    ""
+    ""
+      ""
+    ""
+
+    ""
+      ""
+    ""
+  ""
+"";
+static const gchar introspection_xml2[] =
+""
+    ""
+    ""
+    ""
+    ""
+      ""
+    ""
+    ""
+      ""
+    ""
+    ""
+      ""
+    ""
+    ""
+      ""
+      ""
+    ""
+    ""
+      ""
+      ""
+      ""
+    ""
+    ""
+      ""
+    ""
+    ""
+      ""
+      ""
+      ""
+    ""
+    ""
+      ""
+    ""
+
+    ""
+      ""
+    ""
+  ""
+"";
+
+#define GSD_SCREENSAVER_PROXY_DBUS_SERVICE      "org.freedesktop.ScreenSaver"
+#define GSD_SCREENSAVER_PROXY_DBUS_PATH         "/org/freedesktop/ScreenSaver"
+#define GSD_SCREENSAVER_PROXY_DBUS_PATH2        "/ScreenSaver"
+#define GSD_SCREENSAVER_PROXY_DBUS_INTERFACE    "org.freedesktop.ScreenSaver"
+
+#define GSM_INHIBITOR_FLAG_IDLE 1 << 3
+
+struct _GsdScreensaverProxyManager
+{
+        GObject                  parent;
+
+        GsdSessionManager       *session;
+        GDBusConnection         *connection;
+        GCancellable            *bus_cancellable;
+        GDBusNodeInfo           *introspection_data;
+        GDBusNodeInfo           *introspection_data2;
+        guint                    name_id;
+
+        GHashTable              *watch_ht;  /* key = sender, value = name watch id */
+        GHashTable              *cookie_ht; /* key = cookie, value = sender */
+};
+
+static void     gsd_screensaver_proxy_manager_class_init  (GsdScreensaverProxyManagerClass *klass);
+static void     gsd_screensaver_proxy_manager_init        (GsdScreensaverProxyManager      *screensaver_proxy_manager);
+static void     gsd_screensaver_proxy_manager_finalize    (GObject             *object);
+
+G_DEFINE_TYPE (GsdScreensaverProxyManager, gsd_screensaver_proxy_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+static void
+name_vanished_cb (GDBusConnection            *connection,
+                  const gchar                *name,
+                  GsdScreensaverProxyManager *manager)
+{
+        GHashTableIter iter;
+        gpointer cookie_ptr;
+        const char *sender;
+
+        /* Look for all the cookies under that name,
+         * and call uninhibit for them */
+        g_hash_table_iter_init (&iter, manager->cookie_ht);
+        while (g_hash_table_iter_next (&iter, &cookie_ptr, (gpointer *) &sender)) {
+                if (g_strcmp0 (sender, name) == 0) {
+                        guint cookie = GPOINTER_TO_UINT (cookie_ptr);
+
+                        g_dbus_proxy_call_sync (G_DBUS_PROXY (manager->session),
+                                                "Uninhibit",
+                                                g_variant_new ("(u)", cookie),
+                                                G_DBUS_CALL_FLAGS_NONE,
+                                                -1, NULL, NULL);
+                        g_debug ("Removing cookie %u for sender %s",
+                                 cookie, sender);
+                        g_hash_table_iter_remove (&iter);
+                }
+        }
+
+        g_hash_table_remove (manager->watch_ht, name);
+}
+
+static void
+handle_method_call (GDBusConnection       *connection,
+                    const gchar           *sender,
+                    const gchar           *object_path,
+                    const gchar           *interface_name,
+                    const gchar           *method_name,
+                    GVariant              *parameters,
+                    GDBusMethodInvocation *invocation,
+                    gpointer               user_data)
+{
+        GsdScreensaverProxyManager *manager = GSD_SCREENSAVER_PROXY_MANAGER (user_data);
+        g_autoptr(GError) error = NULL;
+
+        /* Check session pointer as a proxy for whether the manager is in the
+           start or stop state */
+        if (manager->session == NULL) {
+                g_dbus_method_invocation_return_dbus_error (invocation,
+                                                            "org.freedesktop.DBus.Error.NotSupported",
+                                                            "Session is unavailable");
+                return;
+        }
+
+        g_debug ("Calling method '%s.%s' for ScreenSaver Proxy",
+                 interface_name, method_name);
+
+        if (g_strcmp0 (method_name, "Inhibit") == 0) {
+                GVariant *ret;
+                const char *app_id;
+                const char *reason;
+                guint cookie;
+
+                g_variant_get (parameters,
+                               "(ss)", &app_id, &reason);
+
+                ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (G_DBUS_PROXY (manager->session)),
+                                              "Inhibit",
+                                              g_variant_new ("(susu)",
+                                                             app_id, 0, reason, GSM_INHIBITOR_FLAG_IDLE),
+                                              G_DBUS_CALL_FLAGS_NONE,
+                                              -1, NULL, &error);
+
+                if (!ret)
+                        goto error;
+
+                g_variant_get (ret, "(u)", &cookie);
+                g_hash_table_insert (manager->cookie_ht,
+                                     GUINT_TO_POINTER (cookie),
+                                     g_strdup (sender));
+                if (g_hash_table_lookup (manager->watch_ht, sender) == NULL) {
+                        guint watch_id;
+
+                        watch_id = g_bus_watch_name_on_connection (manager->connection,
+                                                                   sender,
+                                                                   G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                                                   NULL,
+                                                                   (GBusNameVanishedCallback) name_vanished_cb,
+                                                                   manager,
+                                                                   NULL);
+                        g_hash_table_insert (manager->watch_ht,
+                                             g_strdup (sender),
+                                             GUINT_TO_POINTER (watch_id));
+                }
+                g_dbus_method_invocation_return_value (invocation, ret);
+        } else if (g_strcmp0 (method_name, "UnInhibit") == 0) {
+                GVariant *ret;
+                guint cookie;
+
+                g_variant_get (parameters, "(u)", &cookie);
+                ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (manager->session),
+                                              "Uninhibit",
+                                              parameters,
+                                              G_DBUS_CALL_FLAGS_NONE,
+                                              -1, NULL, &error);
+
+                if (!ret)
+                        goto error;
+
+                g_debug ("Removing cookie %u from the list for %s", cookie, sender);
+                g_hash_table_remove (manager->cookie_ht, GUINT_TO_POINTER (cookie));
+                g_dbus_method_invocation_return_value (invocation, NULL);
+        } else if (g_strcmp0 (method_name, "Throttle") == 0) {
+                g_dbus_method_invocation_return_value (invocation, NULL);
+        } else if (g_strcmp0 (method_name, "UnThrottle") == 0) {
+                g_dbus_method_invocation_return_value (invocation, NULL);
+        } else if (g_strcmp0 (method_name, "Lock") == 0) {
+                goto unimplemented;
+        } else if (g_strcmp0 (method_name, "SimulateUserActivity") == 0) {
+                goto unimplemented;
+        } else if (g_strcmp0 (method_name, "GetActive") == 0) {
+                goto unimplemented;
+        } else if (g_strcmp0 (method_name, "GetActiveTime") == 0) {
+                goto unimplemented;
+        } else if (g_strcmp0 (method_name, "GetSessionIdleTime") == 0) {
+                goto unimplemented;
+        } else if (g_strcmp0 (method_name, "SetActive") == 0) {
+                goto unimplemented;
+        }
+
+        return;
+
+unimplemented:
+        g_dbus_method_invocation_return_dbus_error (invocation,
+                                                    "org.freedesktop.DBus.Error.NotSupported",
+                                                    "This method is not implemented");
+        return;
+error:
+        g_dbus_method_invocation_return_gerror (invocation, error);
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+        handle_method_call,
+        NULL, /* GetProperty */
+        NULL, /* SetProperty */
+};
+
+static void
+on_bus_gotten (GObject                    *source_object,
+               GAsyncResult               *res,
+               GsdScreensaverProxyManager *manager)
+{
+        GDBusConnection *connection;
+        GDBusInterfaceInfo **infos;
+        GError *error = NULL;
+
+        if (manager->bus_cancellable == NULL ||
+            g_cancellable_is_cancelled (manager->bus_cancellable)) {
+                g_warning ("Operation has been cancelled, so not retrieving session bus");
+                return;
+        }
+
+        connection = g_bus_get_finish (res, &error);
+        if (connection == NULL) {
+                g_warning ("Could not get session bus: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+        manager->connection = connection;
+        infos = manager->introspection_data->interfaces;
+        g_dbus_connection_register_object (connection,
+                                           GSD_SCREENSAVER_PROXY_DBUS_PATH,
+                                           infos[0],
+                                           &interface_vtable,
+                                           manager,
+                                           NULL,
+                                           NULL);
+        infos = manager->introspection_data2->interfaces;
+        g_dbus_connection_register_object (connection,
+                                           GSD_SCREENSAVER_PROXY_DBUS_PATH2,
+                                           infos[0],
+                                           &interface_vtable,
+                                           manager,
+                                           NULL,
+                                           NULL);
+
+        manager->name_id = g_bus_own_name_on_connection (manager->connection,
+                                                               GSD_SCREENSAVER_PROXY_DBUS_SERVICE,
+                                                               G_BUS_NAME_OWNER_FLAGS_NONE,
+                                                               NULL,
+                                                               NULL,
+                                                               NULL,
+                                                               NULL);
+}
+
+static void
+register_manager_dbus (GsdScreensaverProxyManager *manager)
+{
+        manager->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+        manager->introspection_data2 = g_dbus_node_info_new_for_xml (introspection_xml2, NULL);
+        manager->bus_cancellable = g_cancellable_new ();
+        g_assert (manager->introspection_data != NULL);
+        g_assert (manager->introspection_data2 != NULL);
+
+        g_bus_get (G_BUS_TYPE_SESSION,
+                   manager->bus_cancellable,
+                   (GAsyncReadyCallback) on_bus_gotten,
+                   manager);
+}
+
+gboolean
+gsd_screensaver_proxy_manager_start (GsdScreensaverProxyManager *manager,
+                                     GError               **error)
+{
+        g_debug ("Starting screensaver-proxy manager");
+        gnome_settings_profile_start (NULL);
+        manager->session =
+                gnome_settings_bus_get_session_proxy ();
+        manager->watch_ht = g_hash_table_new_full (g_str_hash,
+                                                         g_str_equal,
+                                                         (GDestroyNotify) g_free,
+                                                         (GDestroyNotify) g_bus_unwatch_name);
+        manager->cookie_ht = g_hash_table_new_full (g_direct_hash,
+                                                          g_direct_equal,
+                                                          NULL,
+                                                          (GDestroyNotify) g_free);
+        gnome_settings_profile_end (NULL);
+        return TRUE;
+}
+
+void
+gsd_screensaver_proxy_manager_stop (GsdScreensaverProxyManager *manager)
+{
+        g_debug ("Stopping screensaver_proxy manager");
+        g_clear_object (&manager->session);
+        g_clear_pointer (&manager->watch_ht, g_hash_table_destroy);
+        g_clear_pointer (&manager->cookie_ht, g_hash_table_destroy);
+}
+
+static void
+gsd_screensaver_proxy_manager_class_init (GsdScreensaverProxyManagerClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_screensaver_proxy_manager_finalize;
+}
+
+static void
+gsd_screensaver_proxy_manager_init (GsdScreensaverProxyManager *manager)
+{
+}
+
+static void
+gsd_screensaver_proxy_manager_finalize (GObject *object)
+{
+        GsdScreensaverProxyManager *manager;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSD_IS_SCREENSAVER_PROXY_MANAGER (object));
+
+        manager = GSD_SCREENSAVER_PROXY_MANAGER (object);
+
+        g_return_if_fail (manager != NULL);
+
+        gsd_screensaver_proxy_manager_stop (manager);
+
+        if (manager->name_id != 0) {
+                g_bus_unown_name (manager->name_id);
+                manager->name_id = 0;
+        }
+        g_clear_object (&manager->connection);
+        g_clear_object (&manager->bus_cancellable);
+        g_clear_pointer (&manager->introspection_data, g_dbus_node_info_unref);
+        g_clear_pointer (&manager->introspection_data2, g_dbus_node_info_unref);
+
+        G_OBJECT_CLASS (gsd_screensaver_proxy_manager_parent_class)->finalize (object);
+}
+
+GsdScreensaverProxyManager *
+gsd_screensaver_proxy_manager_new (void)
+{
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_SCREENSAVER_PROXY_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+                register_manager_dbus (manager_object);
+        }
+
+        return GSD_SCREENSAVER_PROXY_MANAGER (manager_object);
+}
diff --git a/plugins/screensaver-proxy/gsd-screensaver-proxy-manager.h b/plugins/screensaver-proxy/gsd-screensaver-proxy-manager.h
new file mode 100644
index 0000000..c2f15ad
--- /dev/null
+++ b/plugins/screensaver-proxy/gsd-screensaver-proxy-manager.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Bastien Nocera 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_SCREENSAVER_PROXY_MANAGER_H
+#define __GSD_SCREENSAVER_PROXY_MANAGER_H
+
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_SCREENSAVER_PROXY_MANAGER         (gsd_screensaver_proxy_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdScreensaverProxyManager, gsd_screensaver_proxy_manager, GSD, SCREENSAVER_PROXY_MANAGER, GObject)
+
+GsdScreensaverProxyManager *gsd_screensaver_proxy_manager_new                 (void);
+gboolean                    gsd_screensaver_proxy_manager_start               (GsdScreensaverProxyManager  *manager,
+                                                                               GError                     **error);
+void                        gsd_screensaver_proxy_manager_stop                (GsdScreensaverProxyManager  *manager);
+
+G_END_DECLS
+
+#endif /* __GSD_SCREENSAVER_PROXY_MANAGER_H */
diff --git a/plugins/screensaver-proxy/main.c b/plugins/screensaver-proxy/main.c
new file mode 100644
index 0000000..3e3af27
--- /dev/null
+++ b/plugins/screensaver-proxy/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_screensaver_proxy_manager_new
+#define START gsd_screensaver_proxy_manager_start
+#define STOP gsd_screensaver_proxy_manager_stop
+#define MANAGER GsdScreensaverProxyManager
+#include "gsd-screensaver-proxy-manager.h"
+
+#include "daemon-skeleton.h"
diff --git a/plugins/screensaver-proxy/meson.build b/plugins/screensaver-proxy/meson.build
new file mode 100644
index 0000000..5430eb8
--- /dev/null
+++ b/plugins/screensaver-proxy/meson.build
@@ -0,0 +1,17 @@
+sources = files(
+  'gsd-screensaver-proxy-manager.c',
+  'main.c'
+)
+
+deps = plugins_deps + [gio_dep]
+
+executable(
+  'gsd-' + plugin_name,
+  sources,
+  include_directories: [top_inc, common_inc],
+  dependencies: deps,
+  c_args: cflags,
+  install: true,
+  install_rpath: gsd_pkglibdir,
+  install_dir: gsd_libexecdir
+)
diff --git a/plugins/sharing/gsd-sharing-enums.h b/plugins/sharing/gsd-sharing-enums.h
new file mode 100644
index 0000000..e9b2fc7
--- /dev/null
+++ b/plugins/sharing/gsd-sharing-enums.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2014 Bastien Nocera 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_SHARING_ENUMS_H
+#define __GSD_SHARING_ENUMS_H
+
+G_BEGIN_DECLS
+
+typedef enum {
+       GSD_SHARING_STATUS_OFFLINE,
+       GSD_SHARING_STATUS_DISABLED_MOBILE_BROADBAND,
+       GSD_SHARING_STATUS_DISABLED_LOW_SECURITY,
+       GSD_SHARING_STATUS_AVAILABLE
+} GsdSharingStatus;
+
+G_END_DECLS
+
+#endif /* __GSD_SHARING_ENUMS_H */
diff --git a/plugins/sharing/gsd-sharing-manager.c b/plugins/sharing/gsd-sharing-manager.c
new file mode 100644
index 0000000..aca78b3
--- /dev/null
+++ b/plugins/sharing/gsd-sharing-manager.c
@@ -0,0 +1,828 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2014 Bastien Nocera 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#if HAVE_NETWORK_MANAGER
+#include 
+#endif /* HAVE_NETWORK_MANAGER */
+
+#include "gnome-settings-profile.h"
+#include "gsd-sharing-manager.h"
+#include "gsd-sharing-enums.h"
+
+typedef struct {
+        const char  *name;
+        GSettings   *settings;
+} ServiceInfo;
+
+struct _GsdSharingManager
+{
+        GObject                  parent;
+
+        GDBusNodeInfo           *introspection_data;
+        guint                    name_id;
+        GDBusConnection         *connection;
+
+        GCancellable            *cancellable;
+#if HAVE_NETWORK_MANAGER
+        NMClient                *client;
+#endif /* HAVE_NETWORK_MANAGER */
+
+        GHashTable              *services;
+
+        char                    *current_network;
+        char                    *current_network_name;
+        char                    *carrier_type;
+        GsdSharingStatus         sharing_status;
+};
+
+#define GSD_DBUS_NAME "org.gnome.SettingsDaemon"
+#define GSD_DBUS_PATH "/org/gnome/SettingsDaemon"
+#define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon"
+
+#define GSD_SHARING_DBUS_NAME GSD_DBUS_NAME ".Sharing"
+#define GSD_SHARING_DBUS_PATH GSD_DBUS_PATH "/Sharing"
+
+static const gchar introspection_xml[] =
+""
+"  "
+"    "
+"    "
+"    "
+"    "
+"    "
+"    "
+"      "
+"    "
+"    "
+"      "
+"      "
+"    "
+"    "
+"      "
+"      "
+"    "
+"  "
+"";
+
+static void     gsd_sharing_manager_class_init  (GsdSharingManagerClass *klass);
+static void     gsd_sharing_manager_init        (GsdSharingManager      *manager);
+static void     gsd_sharing_manager_finalize    (GObject                *object);
+
+G_DEFINE_TYPE (GsdSharingManager, gsd_sharing_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+static const char * const services[] = {
+        "rygel",
+        "gnome-user-share-webdav"
+};
+
+static void
+handle_unit_cb (GObject      *source_object,
+                GAsyncResult *res,
+                gpointer      user_data)
+{
+        GError *error = NULL;
+        GVariant *ret;
+        const char *operation = user_data;
+
+        ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+                                             res, &error);
+        if (!ret) {
+                g_autofree gchar *remote_error = g_dbus_error_get_remote_error (error);
+
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+                    g_strcmp0 (remote_error, "org.freedesktop.systemd1.NoSuchUnit") != 0)
+                        g_warning ("Failed to %s service: %s", operation, error->message);
+                g_error_free (error);
+                return;
+        }
+
+        g_variant_unref (ret);
+
+}
+
+static void
+gsd_sharing_manager_handle_service (GsdSharingManager   *manager,
+                                    const char          *method,
+                                    ServiceInfo         *service)
+{
+        char *service_file;
+
+        service_file = g_strdup_printf ("%s.service", service->name);
+        g_dbus_connection_call (manager->connection,
+                                "org.freedesktop.systemd1",
+                                "/org/freedesktop/systemd1",
+                                "org.freedesktop.systemd1.Manager",
+                                method,
+                                g_variant_new ("(ss)", service_file, "replace"),
+                                NULL,
+                                G_DBUS_CALL_FLAGS_NONE,
+                                -1,
+                                manager->cancellable,
+                                handle_unit_cb,
+                                (gpointer) method);
+        g_free (service_file);
+}
+
+static void
+gsd_sharing_manager_start_service (GsdSharingManager *manager,
+                                   ServiceInfo       *service)
+{
+        g_debug ("About to start %s", service->name);
+
+        /* We use StartUnit, not StartUnitReplace, since the latter would
+         * cancel any pending start we already have going from an
+         * earlier _start_service() call */
+        gsd_sharing_manager_handle_service (manager, "StartUnit", service);
+}
+
+static void
+gsd_sharing_manager_stop_service (GsdSharingManager *manager,
+                                  ServiceInfo       *service)
+{
+        g_debug ("About to stop %s", service->name);
+
+        gsd_sharing_manager_handle_service (manager, "StopUnit", service);
+}
+
+#if HAVE_NETWORK_MANAGER
+static gboolean
+service_is_enabled_on_current_connection (GsdSharingManager *manager,
+                                          ServiceInfo       *service)
+{
+        char **connections;
+        int j;
+        gboolean ret;
+        connections = g_settings_get_strv (service->settings, "enabled-connections");
+        ret = FALSE;
+        for (j = 0; connections[j] != NULL; j++) {
+                if (g_strcmp0 (connections[j], manager->current_network) == 0) {
+                        ret = TRUE;
+                        break;
+                }
+        }
+
+        g_strfreev (connections);
+        return ret;
+}
+#else
+static gboolean
+service_is_enabled_on_current_connection (GsdSharingManager *manager,
+                                          ServiceInfo       *service)
+{
+        return FALSE;
+}
+#endif /* HAVE_NETWORK_MANAGER */
+
+static void
+gsd_sharing_manager_sync_services (GsdSharingManager *manager)
+{
+        GList *services, *l;
+
+        services = g_hash_table_get_values (manager->services);
+
+        for (l = services; l != NULL; l = l->next) {
+                ServiceInfo *service = l->data;
+                gboolean should_be_started = FALSE;
+
+                if (manager->sharing_status == GSD_SHARING_STATUS_AVAILABLE &&
+                    service_is_enabled_on_current_connection (manager, service))
+                        should_be_started = TRUE;
+
+                if (should_be_started)
+                        gsd_sharing_manager_start_service (manager, service);
+                else
+                        gsd_sharing_manager_stop_service (manager, service);
+        }
+        g_list_free (services);
+}
+
+#if HAVE_NETWORK_MANAGER
+static void
+properties_changed (GsdSharingManager *manager)
+{
+        GVariantBuilder props_builder;
+        GVariant *props_changed = NULL;
+
+        /* not yet connected to the session bus */
+        if (manager->connection == NULL)
+                return;
+
+        g_variant_builder_init (&props_builder, G_VARIANT_TYPE ("a{sv}"));
+
+        g_variant_builder_add (&props_builder, "{sv}", "CurrentNetwork",
+                               g_variant_new_string (manager->current_network));
+        g_variant_builder_add (&props_builder, "{sv}", "CurrentNetworkName",
+                               g_variant_new_string (manager->current_network_name));
+        g_variant_builder_add (&props_builder, "{sv}", "CarrierType",
+                               g_variant_new_string (manager->carrier_type));
+        g_variant_builder_add (&props_builder, "{sv}", "SharingStatus",
+                               g_variant_new_uint32 (manager->sharing_status));
+
+        props_changed = g_variant_new ("(s@a{sv}@as)", GSD_SHARING_DBUS_NAME,
+                                       g_variant_builder_end (&props_builder),
+                                       g_variant_new_strv (NULL, 0));
+
+        g_dbus_connection_emit_signal (manager->connection,
+                                       NULL,
+                                       GSD_SHARING_DBUS_PATH,
+                                       "org.freedesktop.DBus.Properties",
+                                       "PropertiesChanged",
+                                       props_changed, NULL);
+}
+
+static char **
+get_connections_for_service (GsdSharingManager *manager,
+                             const char        *service_name)
+{
+        ServiceInfo *service;
+
+        service = g_hash_table_lookup (manager->services, service_name);
+        return g_settings_get_strv (service->settings, "enabled-connections");
+}
+#else
+static char **
+get_connections_for_service (GsdSharingManager *manager,
+                             const char        *service_name)
+{
+        const char * const * connections [] = { NULL };
+        return g_strdupv ((char **) connections);
+}
+#endif /* HAVE_NETWORK_MANAGER */
+
+static gboolean
+check_service (GsdSharingManager  *manager,
+               const char         *service_name,
+               GError            **error)
+{
+        if (g_hash_table_lookup (manager->services, service_name))
+                return TRUE;
+
+        g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+                     "Invalid service name '%s'", service_name);
+        return FALSE;
+}
+
+static gboolean
+gsd_sharing_manager_enable_service (GsdSharingManager  *manager,
+                                    const char         *service_name,
+                                    GError            **error)
+{
+        ServiceInfo *service;
+        char **connections;
+        GPtrArray *array;
+        guint i;
+
+        if (!check_service (manager, service_name, error))
+                return FALSE;
+
+        if (manager->sharing_status != GSD_SHARING_STATUS_AVAILABLE) {
+                g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+                             "Sharing cannot be enabled on this network, status is '%d'", manager->sharing_status);
+                return FALSE;
+        }
+
+        service = g_hash_table_lookup (manager->services, service_name);
+        connections = g_settings_get_strv (service->settings, "enabled-connections");
+        array = g_ptr_array_new ();
+        for (i = 0; connections[i] != NULL; i++) {
+                if (g_strcmp0 (connections[i], manager->current_network) == 0)
+                        goto bail;
+                g_ptr_array_add (array, connections[i]);
+        }
+        g_ptr_array_add (array, manager->current_network);
+        g_ptr_array_add (array, NULL);
+
+        g_settings_set_strv (service->settings, "enabled-connections", (const gchar *const *) array->pdata);
+
+bail:
+
+        gsd_sharing_manager_start_service (manager, service);
+
+        g_ptr_array_unref (array);
+        g_strfreev (connections);
+
+        return TRUE;
+}
+
+static gboolean
+gsd_sharing_manager_disable_service (GsdSharingManager  *manager,
+                                     const char         *service_name,
+                                     const char         *network_name,
+                                     GError            **error)
+{
+        ServiceInfo *service;
+        char **connections;
+        GPtrArray *array;
+        guint i;
+
+        if (!check_service (manager, service_name, error))
+                return FALSE;
+
+        service = g_hash_table_lookup (manager->services, service_name);
+        connections = g_settings_get_strv (service->settings, "enabled-connections");
+        array = g_ptr_array_new ();
+        for (i = 0; connections[i] != NULL; i++) {
+                if (g_strcmp0 (connections[i], network_name) != 0)
+                        g_ptr_array_add (array, connections[i]);
+        }
+        g_ptr_array_add (array, NULL);
+
+        g_settings_set_strv (service->settings, "enabled-connections", (const gchar *const *) array->pdata);
+        g_ptr_array_unref (array);
+        g_strfreev (connections);
+
+        if (g_str_equal (network_name, manager->current_network))
+                gsd_sharing_manager_stop_service (manager, service);
+
+        return TRUE;
+}
+
+#if HAVE_NETWORK_MANAGER
+static const char *
+get_type_and_name_for_connection_uuid (GsdSharingManager *manager,
+                                       const char        *uuid,
+                                       const char       **name)
+{
+        NMRemoteConnection *conn;
+        const char *type;
+
+        if (!manager->client)
+                return NULL;
+
+        conn = nm_client_get_connection_by_uuid (manager->client, uuid);
+        if (!conn)
+                return NULL;
+        type = nm_connection_get_connection_type (NM_CONNECTION (conn));
+        *name = nm_connection_get_id (NM_CONNECTION (conn));
+
+        return type;
+}
+#else
+static const char *
+get_type_and_name_for_connection_uuid (GsdSharingManager *manager,
+                                       const char        *id,
+                                       const char       **name)
+{
+        return NULL;
+}
+#endif /* HAVE_NETWORK_MANAGER */
+
+#if HAVE_NETWORK_MANAGER
+static gboolean
+connection_is_low_security (GsdSharingManager *manager,
+                            const char        *uuid)
+{
+        NMRemoteConnection *conn;
+
+        if (!manager->client)
+                return TRUE;
+
+        conn = nm_client_get_connection_by_uuid (manager->client, uuid);
+        if (!conn)
+                return TRUE;
+
+        /* Disable sharing on open Wi-Fi
+         * XXX: Also do this for WEP networks? */
+        return (nm_connection_get_setting_wireless_security (NM_CONNECTION (conn)) == NULL);
+}
+#endif /* HAVE_NETWORK_MANAGER */
+
+static GVariant *
+gsd_sharing_manager_list_networks (GsdSharingManager  *manager,
+                                   const char         *service_name,
+                                   GError            **error)
+{
+        char **connections;
+        GVariantBuilder builder;
+        guint i;
+
+        if (!check_service (manager, service_name, error))
+                return NULL;
+
+#if HAVE_NETWORK_MANAGER
+        if (!manager->client) {
+                g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Not ready yet");
+                return NULL;
+        }
+#endif /* HAVE_NETWORK_MANAGER */
+
+        connections = get_connections_for_service (manager, service_name);
+
+        g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a(sss))"));
+        g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(sss)"));
+
+        for (i = 0; connections[i] != NULL; i++) {
+                const char *type, *name;
+
+                type = get_type_and_name_for_connection_uuid (manager, connections[i], &name);
+                if (!type)
+                        continue;
+
+                g_variant_builder_add (&builder, "(sss)", connections[i], name, type);
+        }
+        g_strfreev (connections);
+
+        g_variant_builder_close (&builder);
+
+        return g_variant_builder_end (&builder);
+}
+
+static GVariant *
+handle_get_property (GDBusConnection *connection,
+                     const gchar     *sender,
+                     const gchar     *object_path,
+                     const gchar     *interface_name,
+                     const gchar     *property_name,
+                     GError         **error,
+                     gpointer         user_data)
+{
+        GsdSharingManager *manager = GSD_SHARING_MANAGER (user_data);
+
+        /* Check session pointer as a proxy for whether the manager is in the
+           start or stop state */
+        if (manager->connection == NULL)
+                return NULL;
+
+        if (g_strcmp0 (property_name, "CurrentNetwork") == 0) {
+                return g_variant_new_string (manager->current_network);
+        }
+
+        if (g_strcmp0 (property_name, "CurrentNetworkName") == 0) {
+                return g_variant_new_string (manager->current_network_name);
+        }
+
+        if (g_strcmp0 (property_name, "CarrierType") == 0) {
+                return g_variant_new_string (manager->carrier_type);
+        }
+
+        if (g_strcmp0 (property_name, "SharingStatus") == 0) {
+                return g_variant_new_uint32 (manager->sharing_status);
+        }
+
+        return NULL;
+}
+
+static void
+handle_method_call (GDBusConnection       *connection,
+                    const gchar           *sender,
+                    const gchar           *object_path,
+                    const gchar           *interface_name,
+                    const gchar           *method_name,
+                    GVariant              *parameters,
+                    GDBusMethodInvocation *invocation,
+                    gpointer               user_data)
+{
+        GsdSharingManager *manager = (GsdSharingManager *) user_data;
+
+        g_debug ("Calling method '%s' for sharing", method_name);
+
+        /* Check session pointer as a proxy for whether the manager is in the
+           start or stop state */
+        if (manager->connection == NULL)
+                return;
+
+        if (g_strcmp0 (method_name, "EnableService") == 0) {
+                const char *service;
+                GError *error = NULL;
+
+                g_variant_get (parameters, "(&s)", &service);
+                if (!gsd_sharing_manager_enable_service (manager, service, &error))
+                        g_dbus_method_invocation_take_error (invocation, error);
+                else
+                        g_dbus_method_invocation_return_value (invocation, NULL);
+        } else if (g_strcmp0 (method_name, "DisableService") == 0) {
+                const char *service;
+                const char *network_name;
+                GError *error = NULL;
+
+                g_variant_get (parameters, "(&s&s)", &service, &network_name);
+                if (!gsd_sharing_manager_disable_service (manager, service, network_name, &error))
+                        g_dbus_method_invocation_take_error (invocation, error);
+                else
+                        g_dbus_method_invocation_return_value (invocation, NULL);
+        } else if (g_strcmp0 (method_name, "ListNetworks") == 0) {
+                const char *service;
+                GError *error = NULL;
+                GVariant *variant;
+
+                g_variant_get (parameters, "(&s)", &service);
+                variant = gsd_sharing_manager_list_networks (manager, service, &error);
+                if (!variant)
+                        g_dbus_method_invocation_take_error (invocation, error);
+                else
+                        g_dbus_method_invocation_return_value (invocation, variant);
+        }
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+        handle_method_call,
+        handle_get_property,
+        NULL
+};
+
+static void
+on_bus_gotten (GObject               *source_object,
+               GAsyncResult          *res,
+               GsdSharingManager     *manager)
+{
+        GDBusConnection *connection;
+        GError *error = NULL;
+
+        connection = g_bus_get_finish (res, &error);
+        if (connection == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Could not get session bus: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+        manager->connection = connection;
+
+        g_dbus_connection_register_object (connection,
+                                           GSD_SHARING_DBUS_PATH,
+                                           manager->introspection_data->interfaces[0],
+                                           &interface_vtable,
+                                           manager,
+                                           NULL,
+                                           NULL);
+
+        manager->name_id = g_bus_own_name_on_connection (connection,
+                                                               GSD_SHARING_DBUS_NAME,
+                                                               G_BUS_NAME_OWNER_FLAGS_NONE,
+                                                               NULL,
+                                                               NULL,
+                                                               NULL,
+                                                               NULL);
+}
+
+#if HAVE_NETWORK_MANAGER
+static void
+primary_connection_changed (GObject    *gobject,
+                            GParamSpec *pspec,
+                            gpointer    user_data)
+{
+        GsdSharingManager *manager = user_data;
+        NMActiveConnection *a_con;
+
+        a_con = nm_client_get_primary_connection (manager->client);
+
+        g_clear_pointer (&manager->current_network, g_free);
+        g_clear_pointer (&manager->current_network_name, g_free);
+        g_clear_pointer (&manager->carrier_type, g_free);
+
+        if (a_con) {
+                manager->current_network = g_strdup (nm_active_connection_get_uuid (a_con));
+                manager->current_network_name = g_strdup (nm_active_connection_get_id (a_con));
+                manager->carrier_type = g_strdup (nm_active_connection_get_connection_type (a_con));
+                if (manager->carrier_type == NULL)
+                        manager->carrier_type = g_strdup ("");
+        } else {
+                manager->current_network = g_strdup ("");
+                manager->current_network_name = g_strdup ("");
+                manager->carrier_type = g_strdup ("");
+        }
+
+        if (!a_con) {
+                manager->sharing_status = GSD_SHARING_STATUS_OFFLINE;
+        } else if (*(manager->carrier_type) == '\0') {
+                /* Missing carrier type information? */
+                manager->sharing_status = GSD_SHARING_STATUS_OFFLINE;
+        } else if (g_str_equal (manager->carrier_type, "bluetooth") ||
+                   g_str_equal (manager->carrier_type, "gsm") ||
+                   g_str_equal (manager->carrier_type, "cdma")) {
+                manager->sharing_status = GSD_SHARING_STATUS_DISABLED_MOBILE_BROADBAND;
+        } else if (g_str_equal (manager->carrier_type, "802-11-wireless")) {
+                if (connection_is_low_security (manager, manager->current_network))
+                        manager->sharing_status = GSD_SHARING_STATUS_DISABLED_LOW_SECURITY;
+                else
+                        manager->sharing_status = GSD_SHARING_STATUS_AVAILABLE;
+        } else {
+                manager->sharing_status = GSD_SHARING_STATUS_AVAILABLE;
+        }
+
+        g_debug ("current network: %s", manager->current_network);
+        g_debug ("current network name: %s", manager->current_network_name);
+        g_debug ("conn type: %s", manager->carrier_type);
+        g_debug ("status: %d", manager->sharing_status);
+
+        properties_changed (manager);
+        gsd_sharing_manager_sync_services (manager);
+}
+
+static void
+nm_client_ready (GObject      *source_object,
+                 GAsyncResult *res,
+                 gpointer      user_data)
+{
+        GsdSharingManager *manager = user_data;
+        GError *error = NULL;
+        NMClient *client;
+
+        client = nm_client_new_finish (res, &error);
+        if (!client) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Couldn't get NMClient: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+        manager->client = client;
+
+        g_signal_connect (G_OBJECT (client), "notify::primary-connection",
+                          G_CALLBACK (primary_connection_changed), manager);
+
+        primary_connection_changed (NULL, NULL, manager);
+}
+
+#endif /* HAVE_NETWORK_MANAGER */
+
+#define RYGEL_BUS_NAME "org.gnome.Rygel1"
+#define RYGEL_OBJECT_PATH "/org/gnome/Rygel1"
+#define RYGEL_INTERFACE_NAME "org.gnome.Rygel1"
+
+static void
+gsd_sharing_manager_disable_rygel (void)
+{
+	GDBusConnection *connection;
+	gchar *path;
+
+	path = g_build_filename (g_get_user_config_dir (), "autostart",
+				 "rygel.desktop", NULL);
+	if (!g_file_test (path, G_FILE_TEST_IS_SYMLINK | G_FILE_TEST_IS_REGULAR))
+                goto out;
+
+        g_unlink (path);
+
+	connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+	if (connection) {
+		g_dbus_connection_call (connection, RYGEL_BUS_NAME, RYGEL_OBJECT_PATH, RYGEL_INTERFACE_NAME,
+					"Shutdown", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1,
+					NULL, NULL, NULL);
+	}
+	g_object_unref (connection);
+
+ out:
+        g_free (path);
+}
+
+gboolean
+gsd_sharing_manager_start (GsdSharingManager *manager,
+                           GError           **error)
+{
+        g_debug ("Starting sharing manager");
+        gnome_settings_profile_start (NULL);
+
+        manager->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+        g_assert (manager->introspection_data != NULL);
+
+        gsd_sharing_manager_disable_rygel ();
+
+        manager->cancellable = g_cancellable_new ();
+#if HAVE_NETWORK_MANAGER
+        nm_client_new_async (manager->cancellable, nm_client_ready, manager);
+#endif /* HAVE_NETWORK_MANAGER */
+
+        /* Start process of owning a D-Bus name */
+        g_bus_get (G_BUS_TYPE_SESSION,
+                   manager->cancellable,
+                   (GAsyncReadyCallback) on_bus_gotten,
+                   manager);
+
+        gnome_settings_profile_end (NULL);
+        return TRUE;
+}
+
+void
+gsd_sharing_manager_stop (GsdSharingManager *manager)
+{
+        g_debug ("Stopping sharing manager");
+
+        if (manager->sharing_status == GSD_SHARING_STATUS_AVAILABLE &&
+            manager->connection != NULL) {
+                manager->sharing_status = GSD_SHARING_STATUS_OFFLINE;
+                gsd_sharing_manager_sync_services (manager);
+        }
+
+        if (manager->cancellable) {
+                g_cancellable_cancel (manager->cancellable);
+                g_clear_object (&manager->cancellable);
+        }
+
+#if HAVE_NETWORK_MANAGER
+        g_clear_object (&manager->client);
+#endif /* HAVE_NETWORK_MANAGER */
+
+        if (manager->name_id != 0) {
+                g_bus_unown_name (manager->name_id);
+                manager->name_id = 0;
+        }
+
+        g_clear_pointer (&manager->introspection_data, g_dbus_node_info_unref);
+        g_clear_object (&manager->connection);
+
+        g_clear_pointer (&manager->current_network, g_free);
+        g_clear_pointer (&manager->current_network_name, g_free);
+        g_clear_pointer (&manager->carrier_type, g_free);
+}
+
+static void
+gsd_sharing_manager_class_init (GsdSharingManagerClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_sharing_manager_finalize;
+}
+
+static void
+service_free (gpointer pointer)
+{
+        ServiceInfo *service = pointer;
+
+        g_clear_object (&service->settings);
+        g_free (service);
+}
+
+static void
+gsd_sharing_manager_init (GsdSharingManager *manager)
+{
+        guint i;
+
+        manager->services = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, service_free);
+
+        /* Default state */
+        manager->current_network = g_strdup ("");
+        manager->current_network_name = g_strdup ("");
+        manager->carrier_type = g_strdup ("");
+        manager->sharing_status = GSD_SHARING_STATUS_OFFLINE;
+
+        for (i = 0; i < G_N_ELEMENTS (services); i++) {
+                ServiceInfo *service;
+                char *path;
+
+                service = g_new0 (ServiceInfo, 1);
+                service->name = services[i];
+                path = g_strdup_printf ("/org/gnome/settings-daemon/plugins/sharing/%s/", services[i]);
+                service->settings = g_settings_new_with_path ("org.gnome.settings-daemon.plugins.sharing.service", path);
+                g_free (path);
+
+                g_hash_table_insert (manager->services, (gpointer) services[i], service);
+        }
+}
+
+static void
+gsd_sharing_manager_finalize (GObject *object)
+{
+        GsdSharingManager *manager;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSD_IS_SHARING_MANAGER (object));
+
+        manager = GSD_SHARING_MANAGER (object);
+
+        g_return_if_fail (manager != NULL);
+
+        gsd_sharing_manager_stop (manager);
+
+        g_hash_table_unref (manager->services);
+
+        G_OBJECT_CLASS (gsd_sharing_manager_parent_class)->finalize (object);
+}
+
+GsdSharingManager *
+gsd_sharing_manager_new (void)
+{
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_SHARING_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+        }
+
+        return GSD_SHARING_MANAGER (manager_object);
+}
diff --git a/plugins/sharing/gsd-sharing-manager.h b/plugins/sharing/gsd-sharing-manager.h
new file mode 100644
index 0000000..0087abc
--- /dev/null
+++ b/plugins/sharing/gsd-sharing-manager.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_SHARING_MANAGER_H
+#define __GSD_SHARING_MANAGER_H
+
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_SHARING_MANAGER         (gsd_sharing_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdSharingManager, gsd_sharing_manager, GSD, SHARING_MANAGER, GObject)
+
+GsdSharingManager *     gsd_sharing_manager_new                 (void);
+gboolean                gsd_sharing_manager_start               (GsdSharingManager *manager,
+                                                               GError         **error);
+void                    gsd_sharing_manager_stop                (GsdSharingManager *manager);
+
+G_END_DECLS
+
+#endif /* __GSD_SHARING_MANAGER_H */
diff --git a/plugins/sharing/main.c b/plugins/sharing/main.c
new file mode 100644
index 0000000..656e27d
--- /dev/null
+++ b/plugins/sharing/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_sharing_manager_new
+#define START gsd_sharing_manager_start
+#define STOP gsd_sharing_manager_stop
+#define MANAGER GsdSharingManager
+#include "gsd-sharing-manager.h"
+
+#include "daemon-skeleton.h"
diff --git a/plugins/sharing/meson.build b/plugins/sharing/meson.build
new file mode 100644
index 0000000..9484312
--- /dev/null
+++ b/plugins/sharing/meson.build
@@ -0,0 +1,24 @@
+sources = files(
+  'gsd-sharing-manager.c',
+  'main.c'
+)
+
+deps = plugins_deps + [
+  gio_unix_dep,
+  libnotify_dep
+]
+
+if enable_network_manager
+  deps += libnm_dep
+endif
+
+executable(
+  'gsd-' + plugin_name,
+  sources,
+  include_directories: [top_inc, common_inc],
+  dependencies: deps,
+  c_args: cflags,
+  install: true,
+  install_rpath: gsd_pkglibdir,
+  install_dir: gsd_libexecdir
+)
diff --git a/plugins/smartcard/gsd-smartcard-enum-types.c.in b/plugins/smartcard/gsd-smartcard-enum-types.c.in
new file mode 100644
index 0000000..f281cf4
--- /dev/null
+++ b/plugins/smartcard/gsd-smartcard-enum-types.c.in
@@ -0,0 +1,42 @@
+/*** BEGIN file-header ***/
+
+#include 
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+#include "@filename@"
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType @enum_name@_get_type (void) G_GNUC_CONST;
+
+GType
+@enum_name@_get_type (void)
+{
+ static GType etype = 0;
+
+ if (G_UNLIKELY(etype == 0)) {
+ static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+ { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+ { 0, NULL, NULL }
+ };
+
+ etype = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values);
+ }
+
+ return etype;
+}
+
+/*** END value-tail ***/
+
+/*** BEGIN file-tail ***/
+ /**/
+/*** END file-tail ***/
diff --git a/plugins/smartcard/gsd-smartcard-enum-types.h.in b/plugins/smartcard/gsd-smartcard-enum-types.h.in
new file mode 100644
index 0000000..79dcc3d
--- /dev/null
+++ b/plugins/smartcard/gsd-smartcard-enum-types.h.in
@@ -0,0 +1,24 @@
+/*** BEGIN file-header ***/
+#ifndef GSD_IDENTITY_ENUM_TYPES_H
+#define GSD_IDENTITY_ENUM_TYPES_H
+
+#include 
+
+G_BEGIN_DECLS
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType @enum_name@_get_type (void) G_GNUC_CONST;
+#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ())
+/*** END value-header ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* GSD_IDENTITY_ENUM_TYPES_H */
+/*** END file-tail ***/
diff --git a/plugins/smartcard/gsd-smartcard-manager.c b/plugins/smartcard/gsd-smartcard-manager.c
new file mode 100644
index 0000000..fc6ae0b
--- /dev/null
+++ b/plugins/smartcard/gsd-smartcard-manager.c
@@ -0,0 +1,993 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann 
+ * Copyright (C) 2010,2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+
+#include "gnome-settings-profile.h"
+#include "gnome-settings-bus.h"
+#include "gsd-smartcard-manager.h"
+#include "gsd-smartcard-service.h"
+#include "gsd-smartcard-enum-types.h"
+#include "gsd-smartcard-utils.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#define GSD_SESSION_MANAGER_LOGOUT_MODE_FORCE 2
+
+struct _GsdSmartcardManager
+{
+        GObject parent;
+
+        guint start_idle_id;
+        GsdSmartcardService *service;
+        GList *smartcards_watch_tasks;
+        GCancellable *cancellable;
+
+        GsdSessionManager *session_manager;
+        GsdScreenSaver *screen_saver;
+
+        GSettings *settings;
+
+        NSSInitContext *nss_context;
+};
+
+#define CONF_SCHEMA "org.gnome.settings-daemon.peripherals.smartcard"
+#define KEY_REMOVE_ACTION "removal-action"
+
+static void     gsd_smartcard_manager_class_init  (GsdSmartcardManagerClass *klass);
+static void     gsd_smartcard_manager_init        (GsdSmartcardManager      *self);
+static void     gsd_smartcard_manager_finalize    (GObject                  *object);
+static void     lock_screen                       (GsdSmartcardManager *self);
+static void     log_out                           (GsdSmartcardManager *self);
+static void     on_smartcards_from_driver_watched (GsdSmartcardManager *self,
+                                                   GAsyncResult        *result,
+                                                   GTask               *task);
+G_DEFINE_TYPE (GsdSmartcardManager, gsd_smartcard_manager, G_TYPE_OBJECT)
+G_DEFINE_QUARK (gsd-smartcard-manager-error, gsd_smartcard_manager_error)
+G_LOCK_DEFINE_STATIC (gsd_smartcards_watch_tasks);
+
+typedef struct {
+        SECMODModule *driver;
+        guint         idle_id;
+        GError       *error;
+} DriverRegistrationOperation;
+
+static gpointer manager_object = NULL;
+
+static void
+gsd_smartcard_manager_class_init (GsdSmartcardManagerClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_smartcard_manager_finalize;
+
+        gsd_smartcard_utils_register_error_domain (GSD_SMARTCARD_MANAGER_ERROR,
+                                                   GSD_TYPE_SMARTCARD_MANAGER_ERROR);
+}
+
+static void
+gsd_smartcard_manager_init (GsdSmartcardManager *self)
+{
+}
+
+static void
+load_nss (GsdSmartcardManager *self)
+{
+        NSSInitContext *context = NULL;
+
+        /* The first field in the NSSInitParameters structure
+         * is the size of the structure. NSS requires this, so
+         * that it can change the size of the structure in future
+         * versions of NSS in a detectable way
+         */
+        NSSInitParameters parameters = { sizeof (parameters), };
+        static const guint32 flags = NSS_INIT_READONLY
+                                   | NSS_INIT_FORCEOPEN
+                                   | NSS_INIT_NOROOTINIT
+                                   | NSS_INIT_OPTIMIZESPACE
+                                   | NSS_INIT_PK11RELOAD;
+
+        g_debug ("attempting to load NSS database '%s'",
+                 GSD_SMARTCARD_MANAGER_NSS_DB);
+
+        PR_Init (PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+        context = NSS_InitContext (GSD_SMARTCARD_MANAGER_NSS_DB,
+                                   "", "", SECMOD_DB, ¶meters, flags);
+
+        if (context == NULL) {
+                gsize error_message_size;
+                char *error_message;
+
+                error_message_size = PR_GetErrorTextLength ();
+
+                if (error_message_size == 0) {
+                        g_debug ("NSS security system could not be initialized");
+                } else {
+                        error_message = g_alloca (error_message_size);
+                        PR_GetErrorText (error_message);
+
+                        g_debug ("NSS security system could not be initialized - %s",
+                                 error_message);
+                }
+
+                self->nss_context = NULL;
+                return;
+
+        }
+
+        g_debug ("NSS database '%s' loaded", GSD_SMARTCARD_MANAGER_NSS_DB);
+        self->nss_context = context;
+}
+
+static void
+unload_nss (GsdSmartcardManager *self)
+{
+        g_debug ("attempting to unload NSS security system with database '%s'",
+                 GSD_SMARTCARD_MANAGER_NSS_DB);
+
+        if (self->nss_context != NULL) {
+                g_clear_pointer (&self->nss_context,
+                                 NSS_ShutdownContext);
+                g_debug ("NSS database '%s' unloaded", GSD_SMARTCARD_MANAGER_NSS_DB);
+        } else {
+                g_debug ("NSS database '%s' already not loaded", GSD_SMARTCARD_MANAGER_NSS_DB);
+        }
+}
+
+typedef struct
+{
+        SECMODModule *driver;
+        GHashTable *smartcards;
+        int number_of_consecutive_errors;
+} WatchSmartcardsOperation;
+
+static void
+on_watch_cancelled (GCancellable             *cancellable,
+                    WatchSmartcardsOperation *operation)
+{
+        SECMOD_CancelWait (operation->driver);
+}
+
+static gboolean
+watch_one_event_from_driver (GsdSmartcardManager       *self,
+                             WatchSmartcardsOperation  *operation,
+                             GCancellable              *cancellable,
+                             GError                   **error)
+{
+        PK11SlotInfo *card = NULL, *old_card;
+        CK_SLOT_ID slot_id;
+        gulong handler_id;
+        int old_slot_series = -1, slot_series;
+
+        handler_id = g_cancellable_connect (cancellable,
+                                            G_CALLBACK (on_watch_cancelled),
+                                            operation,
+                                            NULL);
+
+        if (handler_id != 0) {
+                /* Use the non-blocking version of the call as p11-kit, which
+                 * is used on both Fedora and Ubuntu, doesn't support the
+                 * blocking version of the call.
+                 */
+                card = SECMOD_WaitForAnyTokenEvent (operation->driver, CKF_DONT_BLOCK, PR_SecondsToInterval (1));
+        }
+
+        g_cancellable_disconnect (cancellable, handler_id);
+
+        if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+                g_warning ("smartcard event function cancelled");
+                return FALSE;
+        }
+
+        if (card == NULL) {
+                int error_code;
+
+                error_code = PORT_GetError ();
+
+                if (error_code == SEC_ERROR_NO_EVENT) {
+                    g_usleep (1 * G_USEC_PER_SEC);
+
+                    return TRUE;
+                }
+
+                operation->number_of_consecutive_errors++;
+                if (operation->number_of_consecutive_errors > 10) {
+                     g_warning ("Got %d consecutive smartcard errors, so giving up.",
+                                operation->number_of_consecutive_errors);
+
+                     g_set_error (error,
+                                  GSD_SMARTCARD_MANAGER_ERROR,
+                                  GSD_SMARTCARD_MANAGER_ERROR_WITH_NSS,
+                                  "encountered unexpected error while "
+                                  "waiting for smartcard events (error %x)",
+                                  error_code);
+                     return FALSE;
+                }
+
+                g_warning ("Got potentially spurious smartcard event error: %x.", error_code);
+
+                g_usleep (1 * G_USEC_PER_SEC);
+                return TRUE;
+        }
+        operation->number_of_consecutive_errors = 0;
+
+        slot_id = PK11_GetSlotID (card);
+        slot_series = PK11_GetSlotSeries (card);
+
+        old_card = g_hash_table_lookup (operation->smartcards, GINT_TO_POINTER ((int) slot_id));
+
+        /* If there is a different card in the slot now than
+         * there was before, then we need to emit a removed signal
+         * for the old card
+         */
+        if (old_card != NULL) {
+                old_slot_series = PK11_GetSlotSeries (old_card);
+
+                if (old_slot_series != slot_series) {
+                        /* Card registered with slot previously is
+                         * different than this card, so update its
+                         * exported state to track the implicit missed
+                         * removal
+                         */
+                        gsd_smartcard_service_sync_token (self->service, old_card, cancellable);
+                }
+
+                g_hash_table_remove (operation->smartcards, GINT_TO_POINTER ((int) slot_id));
+        }
+
+        if (PK11_IsPresent (card)) {
+                g_debug ("Detected smartcard insertion event in slot %d", (int) slot_id);
+
+                g_hash_table_replace (operation->smartcards,
+                                      GINT_TO_POINTER ((int) slot_id),
+                                      PK11_ReferenceSlot (card));
+
+                gsd_smartcard_service_sync_token (self->service, card, cancellable);
+        } else if (old_card == NULL) {
+                /* If the just removed smartcard is not known to us then
+                 * ignore the removal event. NSS sends a synthentic removal
+                 * event for slots that are empty at startup
+                 */
+                g_debug ("Detected slot %d is empty in reader", (int) slot_id);
+        } else {
+                g_debug ("Detected smartcard removal event in slot %d", (int) slot_id);
+
+                /* If the just removed smartcard is known to us then
+                 * we need to update its exported state to reflect the
+                 * removal
+                 */
+                if (old_slot_series == slot_series)
+                        gsd_smartcard_service_sync_token (self->service, card, cancellable);
+        }
+
+        PK11_FreeSlot (card);
+
+        return TRUE;
+}
+
+static void
+watch_smartcards_from_driver (GTask                    *task,
+                              GsdSmartcardManager      *self,
+                              WatchSmartcardsOperation *operation,
+                              GCancellable             *cancellable)
+{
+        g_debug ("watching for smartcard events");
+        while (!g_cancellable_is_cancelled (cancellable)) {
+                gboolean watch_succeeded;
+                GError *error = NULL;
+
+                watch_succeeded = watch_one_event_from_driver (self, operation, cancellable, &error);
+
+                if (g_task_return_error_if_cancelled (task)) {
+                        break;
+                }
+
+                if (!watch_succeeded) {
+                        g_task_return_error (task, error);
+                        break;
+                }
+        }
+}
+
+static void
+destroy_watch_smartcards_operation (WatchSmartcardsOperation *operation)
+{
+        SECMOD_DestroyModule (operation->driver);
+        g_hash_table_unref (operation->smartcards);
+        g_free (operation);
+}
+
+static void
+on_smartcards_watch_task_destroyed (GsdSmartcardManager *self,
+                                    GTask               *freed_task)
+{
+        G_LOCK (gsd_smartcards_watch_tasks);
+        self->smartcards_watch_tasks = g_list_remove (self->smartcards_watch_tasks,
+                                                      freed_task);
+        G_UNLOCK (gsd_smartcards_watch_tasks);
+}
+
+static void
+sync_initial_tokens_from_driver (GsdSmartcardManager *self,
+                                 SECMODModule        *driver,
+                                 GHashTable          *smartcards,
+                                 GCancellable        *cancellable)
+{
+        int i;
+
+        for (i = 0; i < driver->slotCount; i++) {
+                PK11SlotInfo *card;
+
+                card = driver->slots[i];
+
+                if (PK11_IsPresent (card)) {
+                        CK_SLOT_ID slot_id;
+                        slot_id = PK11_GetSlotID (card);
+
+                        g_debug ("Detected smartcard in slot %d at start up", (int) slot_id);
+
+                        g_hash_table_replace (smartcards,
+                                              GINT_TO_POINTER ((int) slot_id),
+                                              PK11_ReferenceSlot (card));
+                        gsd_smartcard_service_sync_token (self->service, card, cancellable);
+                }
+        }
+}
+
+static void
+watch_smartcards_from_driver_async (GsdSmartcardManager *self,
+                                    SECMODModule        *driver,
+                                    GCancellable        *cancellable,
+                                    GAsyncReadyCallback  callback,
+                                    gpointer             user_data)
+{
+        GTask *task;
+        WatchSmartcardsOperation *operation;
+
+        operation = g_new0 (WatchSmartcardsOperation, 1);
+        operation->driver = SECMOD_ReferenceModule (driver);
+        operation->smartcards = g_hash_table_new_full (g_direct_hash,
+                                                       g_direct_equal,
+                                                       NULL,
+                                                       (GDestroyNotify) PK11_FreeSlot);
+
+        task = g_task_new (self, cancellable, callback, user_data);
+
+        g_task_set_task_data (task,
+                              operation,
+                              (GDestroyNotify) destroy_watch_smartcards_operation);
+
+        G_LOCK (gsd_smartcards_watch_tasks);
+        self->smartcards_watch_tasks = g_list_prepend (self->smartcards_watch_tasks,
+                                                       task);
+        g_object_weak_ref (G_OBJECT (task),
+                           (GWeakNotify) on_smartcards_watch_task_destroyed,
+                           self);
+        G_UNLOCK (gsd_smartcards_watch_tasks);
+
+        sync_initial_tokens_from_driver (self, driver, operation->smartcards, cancellable);
+
+        g_task_run_in_thread (task, (GTaskThreadFunc) watch_smartcards_from_driver);
+}
+
+static gboolean
+register_driver_finish (GsdSmartcardManager  *self,
+                        GAsyncResult         *result,
+                        GError              **error)
+{
+        return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+on_driver_registered (GsdSmartcardManager *self,
+                      GAsyncResult        *result,
+                      GTask               *task)
+{
+        GError *error = NULL;
+        DriverRegistrationOperation *operation;
+
+        operation = g_task_get_task_data (G_TASK (result));
+
+        if (!register_driver_finish (self, result, &error)) {
+                g_task_return_error (task, error);
+                g_object_unref (task);
+                return;
+        }
+
+        watch_smartcards_from_driver_async (self,
+                                            operation->driver,
+                                            self->cancellable,
+                                            (GAsyncReadyCallback) on_smartcards_from_driver_watched,
+                                            task);
+
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+}
+
+static void
+on_smartcards_from_driver_watched (GsdSmartcardManager *self,
+                                   GAsyncResult        *result,
+                                   GTask               *task)
+{
+        g_debug ("Done watching smartcards from driver");
+}
+
+static void
+destroy_driver_registration_operation (DriverRegistrationOperation *operation)
+{
+        SECMOD_DestroyModule (operation->driver);
+        g_free (operation);
+}
+
+static gboolean
+on_task_thread_to_complete_driver_registration (GTask *task)
+{
+        DriverRegistrationOperation *operation;
+        operation = g_task_get_task_data (task);
+
+        if (operation->error != NULL)
+                g_task_return_error (task, operation->error);
+        else
+                g_task_return_boolean (task, TRUE);
+
+        return G_SOURCE_REMOVE;
+}
+
+static gboolean
+on_main_thread_to_register_driver (GTask *task)
+{
+        GsdSmartcardManager *self;
+        DriverRegistrationOperation *operation;
+        GSource *source;
+
+        self = g_task_get_source_object (task);
+        operation = g_task_get_task_data (task);
+
+        gsd_smartcard_service_register_driver (self->service,
+                                               operation->driver);
+
+        source = g_idle_source_new ();
+        g_task_attach_source (task,
+                              source,
+                              (GSourceFunc) on_task_thread_to_complete_driver_registration);
+        g_source_unref (source);
+
+        return G_SOURCE_REMOVE;
+}
+
+static void
+register_driver (GsdSmartcardManager *self,
+                 SECMODModule         *driver,
+                 GCancellable         *cancellable,
+                 GAsyncReadyCallback   callback,
+                 gpointer              user_data)
+{
+        GTask *task;
+        DriverRegistrationOperation *operation;
+
+        task = g_task_new (self, cancellable, callback, user_data);
+        operation = g_new0 (DriverRegistrationOperation, 1);
+        operation->driver = SECMOD_ReferenceModule (driver);
+        g_task_set_task_data (task,
+                              operation,
+                              (GDestroyNotify) destroy_driver_registration_operation);
+
+        operation->idle_id = g_idle_add ((GSourceFunc) on_main_thread_to_register_driver, task);
+        g_source_set_name_by_id (operation->idle_id, "[gnome-settings-daemon] on_main_thread_to_register_driver");
+}
+
+static void
+activate_driver (GsdSmartcardManager *self,
+                 SECMODModule        *driver,
+                 GCancellable        *cancellable,
+                 GAsyncReadyCallback  callback,
+                 gpointer             user_data)
+{
+        GTask *task;
+
+        g_debug ("Activating driver '%s'", driver->commonName);
+
+        task = g_task_new (self, cancellable, callback, user_data);
+
+        register_driver (self,
+                         driver,
+                         cancellable,
+                         (GAsyncReadyCallback) on_driver_registered,
+                         task);
+}
+
+typedef struct
+{
+  int pending_drivers_count;
+  int activated_drivers_count;
+} ActivateAllDriversOperation;
+
+static gboolean
+activate_driver_async_finish (GsdSmartcardManager  *self,
+                              GAsyncResult         *result,
+                              GError              **error)
+{
+        return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+try_to_complete_all_drivers_activation (GTask *task)
+{
+        ActivateAllDriversOperation *operation;
+
+        operation = g_task_get_task_data (task);
+
+        if (operation->pending_drivers_count > 0)
+                return;
+
+        if (operation->activated_drivers_count > 0)
+                g_task_return_boolean (task, TRUE);
+        else
+                g_task_return_new_error (task, GSD_SMARTCARD_MANAGER_ERROR,
+                                         GSD_SMARTCARD_MANAGER_ERROR_NO_DRIVERS,
+                                         "No smartcards exist to be activated.");
+
+        g_object_unref (task);
+}
+
+static void
+on_driver_activated (GsdSmartcardManager *self,
+                     GAsyncResult        *result,
+                     GTask               *task)
+{
+        GError *error = NULL;
+        gboolean driver_activated;
+        ActivateAllDriversOperation *operation;
+
+        driver_activated = activate_driver_async_finish (self, result, &error);
+
+        operation = g_task_get_task_data (task);
+
+        if (driver_activated)
+                operation->activated_drivers_count++;
+
+        operation->pending_drivers_count--;
+
+        try_to_complete_all_drivers_activation (task);
+}
+
+static void
+activate_all_drivers_async (GsdSmartcardManager *self,
+                            GCancellable        *cancellable,
+                            GAsyncReadyCallback  callback,
+                            gpointer             user_data)
+{
+        GTask *task;
+        SECMODListLock *lock;
+        SECMODModuleList *driver_list, *node;
+        ActivateAllDriversOperation *operation;
+
+        task = g_task_new (self, cancellable, callback, user_data);
+        operation = g_new0 (ActivateAllDriversOperation, 1);
+        g_task_set_task_data (task, operation, (GDestroyNotify) g_free);
+
+        lock = SECMOD_GetDefaultModuleListLock ();
+
+        g_assert (lock != NULL);
+
+        SECMOD_GetReadLock (lock);
+        driver_list = SECMOD_GetDefaultModuleList ();
+        for (node = driver_list; node != NULL; node = node->next) {
+                if (!node->module->loaded)
+                        continue;
+
+                if (!SECMOD_HasRemovableSlots (node->module))
+                        continue;
+
+                if (node->module->dllName == NULL)
+                        continue;
+
+                operation->pending_drivers_count++;
+
+                activate_driver (self, node->module,
+                                 cancellable,
+                                 (GAsyncReadyCallback) on_driver_activated,
+                                 task);
+
+        }
+        SECMOD_ReleaseReadLock (lock);
+
+        try_to_complete_all_drivers_activation (task);
+}
+
+/* Will error with %GSD_SMARTCARD_MANAGER_ERROR_NO_DRIVERS if there were no
+ * drivers to activate.. */
+static gboolean
+activate_all_drivers_async_finish (GsdSmartcardManager  *self,
+                                   GAsyncResult         *result,
+                                   GError              **error)
+{
+        return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+on_all_drivers_activated (GsdSmartcardManager *self,
+                          GAsyncResult        *result,
+                          GTask               *task)
+{
+        GError *error = NULL;
+        gboolean driver_activated;
+        PK11SlotInfo *login_token;
+
+        driver_activated = activate_all_drivers_async_finish (self, result, &error);
+
+        if (!driver_activated) {
+                g_task_return_error (task, error);
+                return;
+        }
+
+        login_token = gsd_smartcard_manager_get_login_token (self);
+
+        if (login_token || g_getenv ("PKCS11_LOGIN_TOKEN_NAME") != NULL) {
+                /* The card used to log in was removed before login completed.
+                 * Do removal action immediately
+                 */
+                if (!login_token || !PK11_IsPresent (login_token))
+                        gsd_smartcard_manager_do_remove_action (self);
+        }
+
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+}
+
+static void
+watch_smartcards (GTask               *task,
+                  GsdSmartcardManager *self,
+                  gpointer             data,
+                  GCancellable        *cancellable)
+{
+        GMainContext *context;
+        GMainLoop *loop;
+
+        g_debug ("Getting list of suitable drivers");
+        context = g_main_context_new ();
+        g_main_context_push_thread_default (context);
+
+        activate_all_drivers_async (self,
+                                    cancellable,
+                                    (GAsyncReadyCallback) on_all_drivers_activated,
+                                    task);
+
+        loop = g_main_loop_new (context, FALSE);
+        g_main_loop_run (loop);
+        g_main_loop_unref (loop);
+
+        g_main_context_pop_thread_default (context);
+        g_main_context_unref (context);
+}
+
+static void
+watch_smartcards_async (GsdSmartcardManager *self,
+                        GCancellable        *cancellable,
+                        GAsyncReadyCallback  callback,
+                        gpointer             user_data)
+{
+        GTask *task;
+
+        task = g_task_new (self, cancellable, callback, user_data);
+
+        g_task_run_in_thread (task, (GTaskThreadFunc) watch_smartcards);
+}
+
+static gboolean
+watch_smartcards_async_finish (GsdSmartcardManager  *self,
+                               GAsyncResult         *result,
+                               GError              **error)
+{
+        return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+on_smartcards_watched (GsdSmartcardManager *self,
+                       GAsyncResult        *result)
+{
+        GError *error = NULL;
+
+        if (!watch_smartcards_async_finish (self, result, &error)) {
+                g_debug ("Error watching smartcards: %s", error->message);
+                g_error_free (error);
+        }
+}
+
+static void
+on_service_created (GObject             *source_object,
+                    GAsyncResult        *result,
+                    GsdSmartcardManager *self)
+{
+        GsdSmartcardService *service;
+        GError *error = NULL;
+
+        service = gsd_smartcard_service_new_finish (result, &error);
+
+        if (service == NULL) {
+                g_warning("Couldn't create session bus service: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        self->service = service;
+
+        watch_smartcards_async (self,
+                                self->cancellable,
+                                (GAsyncReadyCallback) on_smartcards_watched,
+                                NULL);
+
+}
+
+static gboolean
+gsd_smartcard_manager_idle_cb (GsdSmartcardManager *self)
+{
+        gnome_settings_profile_start (NULL);
+
+        self->cancellable = g_cancellable_new();
+        self->settings = g_settings_new (CONF_SCHEMA);
+
+        load_nss (self);
+
+        gsd_smartcard_service_new_async (self,
+                                         self->cancellable,
+                                         (GAsyncReadyCallback) on_service_created,
+                                         self);
+
+        gnome_settings_profile_end (NULL);
+
+        self->start_idle_id = 0;
+        return FALSE;
+}
+
+gboolean
+gsd_smartcard_manager_start (GsdSmartcardManager  *self,
+                             GError              **error)
+{
+        gnome_settings_profile_start (NULL);
+
+        self->start_idle_id = g_idle_add ((GSourceFunc) gsd_smartcard_manager_idle_cb, self);
+        g_source_set_name_by_id (self->start_idle_id, "[gnome-settings-daemon] gsd_smartcard_manager_idle_cb");
+
+        gnome_settings_profile_end (NULL);
+
+        return TRUE;
+}
+
+void
+gsd_smartcard_manager_stop (GsdSmartcardManager *self)
+{
+        g_debug ("Stopping smartcard manager");
+
+        g_cancellable_cancel (self->cancellable);
+
+        unload_nss (self);
+
+        g_clear_object (&self->settings);
+        g_clear_object (&self->cancellable);
+        g_clear_object (&self->session_manager);
+        g_clear_object (&self->screen_saver);
+}
+
+static void
+on_screen_locked (GsdScreenSaver      *screen_saver,
+                  GAsyncResult        *result,
+                  GsdSmartcardManager *self)
+{
+        gboolean is_locked;
+        GError *error = NULL;
+
+        is_locked = gsd_screen_saver_call_lock_finish (screen_saver, result, &error);
+
+        if (!is_locked) {
+                g_warning ("Couldn't lock screen: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+}
+
+static void
+lock_screen (GsdSmartcardManager *self)
+{
+        if (self->screen_saver == NULL)
+                self->screen_saver = gnome_settings_bus_get_screen_saver_proxy ();
+
+        gsd_screen_saver_call_lock (self->screen_saver,
+                                    self->cancellable,
+                                    (GAsyncReadyCallback) on_screen_locked,
+                                    self);
+}
+
+static void
+on_logged_out (GsdSessionManager   *session_manager,
+               GAsyncResult        *result,
+               GsdSmartcardManager *self)
+{
+        gboolean is_logged_out;
+        GError *error = NULL;
+
+        is_logged_out = gsd_session_manager_call_logout_finish (session_manager, result, &error);
+
+        if (!is_logged_out) {
+                g_warning ("Couldn't log out: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+}
+
+static void
+log_out (GsdSmartcardManager *self)
+{
+        if (self->session_manager == NULL)
+                self->session_manager = gnome_settings_bus_get_session_proxy ();
+
+        gsd_session_manager_call_logout (self->session_manager,
+                                         GSD_SESSION_MANAGER_LOGOUT_MODE_FORCE,
+                                         self->cancellable,
+                                         (GAsyncReadyCallback) on_logged_out,
+                                         self);
+}
+
+void
+gsd_smartcard_manager_do_remove_action (GsdSmartcardManager *self)
+{
+        char *remove_action;
+
+        remove_action = g_settings_get_string (self->settings, KEY_REMOVE_ACTION);
+
+        if (strcmp (remove_action, "lock-screen") == 0)
+                lock_screen (self);
+        else if (strcmp (remove_action, "force-logout") == 0)
+                log_out (self);
+}
+
+static PK11SlotInfo *
+get_login_token_for_operation (GsdSmartcardManager      *self,
+                               WatchSmartcardsOperation *operation)
+{
+        GHashTableIter iter;
+        gpointer key, value;
+
+        g_hash_table_iter_init (&iter, operation->smartcards);
+        while (g_hash_table_iter_next (&iter, &key, &value)) {
+                PK11SlotInfo *card_slot;
+                const char *token_name;
+
+                card_slot = (PK11SlotInfo *) value;
+                token_name = PK11_GetTokenName (card_slot);
+
+                if (g_strcmp0 (g_getenv ("PKCS11_LOGIN_TOKEN_NAME"), token_name) == 0)
+                        return card_slot;
+        }
+
+        return NULL;
+}
+
+PK11SlotInfo *
+gsd_smartcard_manager_get_login_token (GsdSmartcardManager *self)
+{
+        PK11SlotInfo *card_slot = NULL;
+        GList *node;
+
+        G_LOCK (gsd_smartcards_watch_tasks);
+        node = self->smartcards_watch_tasks;
+        while (node != NULL) {
+                GTask *task = node->data;
+                WatchSmartcardsOperation *operation = g_task_get_task_data (task);
+
+                card_slot = get_login_token_for_operation (self, operation);
+
+                if (card_slot != NULL)
+                        break;
+
+                node = node->next;
+        }
+        G_UNLOCK (gsd_smartcards_watch_tasks);
+
+        return card_slot;
+}
+
+static GList *
+get_inserted_tokens_for_operation (GsdSmartcardManager      *self,
+                                   WatchSmartcardsOperation *operation)
+{
+        GList *inserted_tokens = NULL;
+        GHashTableIter iter;
+        gpointer key, value;
+
+        g_hash_table_iter_init (&iter, operation->smartcards);
+        while (g_hash_table_iter_next (&iter, &key, &value)) {
+                PK11SlotInfo *card_slot;
+
+                card_slot = (PK11SlotInfo *) value;
+
+                if (PK11_IsPresent (card_slot))
+                        inserted_tokens = g_list_prepend (inserted_tokens, card_slot);
+        }
+
+        return inserted_tokens;
+}
+
+GList *
+gsd_smartcard_manager_get_inserted_tokens (GsdSmartcardManager *self,
+                                           gsize               *num_tokens)
+{
+        GList *inserted_tokens = NULL, *node;
+
+        G_LOCK (gsd_smartcards_watch_tasks);
+        for (node = self->smartcards_watch_tasks; node != NULL; node = node->next) {
+                GTask *task = node->data;
+                WatchSmartcardsOperation *operation = g_task_get_task_data (task);
+                GList *operation_inserted_tokens;
+
+                operation_inserted_tokens = get_inserted_tokens_for_operation (self, operation);
+
+                inserted_tokens = g_list_concat (inserted_tokens, operation_inserted_tokens);
+        }
+        G_UNLOCK (gsd_smartcards_watch_tasks);
+
+        if (num_tokens != NULL)
+                *num_tokens = g_list_length (inserted_tokens);
+
+        return inserted_tokens;
+}
+
+static void
+gsd_smartcard_manager_finalize (GObject *object)
+{
+        GsdSmartcardManager *self;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSD_IS_SMARTCARD_MANAGER (object));
+
+        self = GSD_SMARTCARD_MANAGER (object);
+
+        g_return_if_fail (self != NULL);
+
+        if (self->start_idle_id != 0)
+                g_source_remove (self->start_idle_id);
+
+        gsd_smartcard_manager_stop (self);
+
+        G_OBJECT_CLASS (gsd_smartcard_manager_parent_class)->finalize (object);
+}
+
+GsdSmartcardManager *
+gsd_smartcard_manager_new (void)
+{
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_SMARTCARD_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+        }
+
+        return GSD_SMARTCARD_MANAGER (manager_object);
+}
diff --git a/plugins/smartcard/gsd-smartcard-manager.h b/plugins/smartcard/gsd-smartcard-manager.h
new file mode 100644
index 0000000..c2a9eb3
--- /dev/null
+++ b/plugins/smartcard/gsd-smartcard-manager.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann 
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_SMARTCARD_MANAGER_H
+#define __GSD_SMARTCARD_MANAGER_H
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_SMARTCARD_MANAGER         (gsd_smartcard_manager_get_type ())
+#define GSD_SMARTCARD_MANAGER_ERROR        (gsd_smartcard_manager_error_quark ())
+
+G_DECLARE_FINAL_TYPE (GsdSmartcardManager, gsd_smartcard_manager, GSD, SMARTCARD_MANAGER, GObject)
+
+typedef enum
+{
+         GSD_SMARTCARD_MANAGER_ERROR_GENERIC = 0,
+         GSD_SMARTCARD_MANAGER_ERROR_WITH_NSS,
+         GSD_SMARTCARD_MANAGER_ERROR_LOADING_DRIVER,
+         GSD_SMARTCARD_MANAGER_ERROR_WATCHING_FOR_EVENTS,
+         GSD_SMARTCARD_MANAGER_ERROR_REPORTING_EVENTS,
+         GSD_SMARTCARD_MANAGER_ERROR_FINDING_SMARTCARD,
+         GSD_SMARTCARD_MANAGER_ERROR_NO_DRIVERS,
+} GsdSmartcardManagerError;
+
+GQuark                  gsd_smartcard_manager_error_quark (void);
+
+
+GsdSmartcardManager *   gsd_smartcard_manager_new         (void);
+gboolean                gsd_smartcard_manager_start       (GsdSmartcardManager  *manager,
+                                                           GError              **error);
+void                    gsd_smartcard_manager_stop        (GsdSmartcardManager  *manager);
+
+PK11SlotInfo *          gsd_smartcard_manager_get_login_token (GsdSmartcardManager *manager);
+GList *                 gsd_smartcard_manager_get_inserted_tokens (GsdSmartcardManager *manager,
+                                                                   gsize               *num_tokens);
+void                    gsd_smartcard_manager_do_remove_action (GsdSmartcardManager *manager);
+
+G_END_DECLS
+
+#endif /* __GSD_SMARTCARD_MANAGER_H */
diff --git a/plugins/smartcard/gsd-smartcard-service.c b/plugins/smartcard/gsd-smartcard-service.c
new file mode 100644
index 0000000..4d529c3
--- /dev/null
+++ b/plugins/smartcard/gsd-smartcard-service.c
@@ -0,0 +1,849 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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 .
+ *
+ * Authors: Ray Strode
+ */
+
+#include "config.h"
+
+#include "gsd-smartcard-service.h"
+#include "org.gnome.SettingsDaemon.Smartcard.h"
+#include "gsd-smartcard-manager.h"
+#include "gsd-smartcard-enum-types.h"
+#include "gsd-smartcard-utils.h"
+
+#include 
+#include 
+#include 
+
+struct _GsdSmartcardService
+{
+        GsdSmartcardServiceManagerSkeleton parent;
+
+        GDBusConnection            *bus_connection;
+        GDBusObjectManagerServer   *object_manager_server;
+        GsdSmartcardManager        *smartcard_manager;
+        GCancellable               *cancellable;
+        GHashTable                 *tokens;
+
+        gboolean                    login_token_bound;
+        guint name_id;
+};
+
+#define GSD_DBUS_NAME "org.gnome.SettingsDaemon"
+#define GSD_DBUS_PATH "/org/gnome/SettingsDaemon"
+#define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon"
+
+#define GSD_SMARTCARD_DBUS_NAME GSD_DBUS_NAME ".Smartcard"
+#define GSD_SMARTCARD_DBUS_PATH GSD_DBUS_PATH "/Smartcard"
+#define GSD_SMARTCARD_MANAGER_DBUS_PATH GSD_SMARTCARD_DBUS_PATH "/Manager"
+#define GSD_SMARTCARD_MANAGER_DRIVERS_DBUS_PATH GSD_SMARTCARD_MANAGER_DBUS_PATH "/Drivers"
+#define GSD_SMARTCARD_MANAGER_TOKENS_DBUS_PATH  GSD_SMARTCARD_MANAGER_DBUS_PATH "/Tokens"
+
+enum {
+        PROP_0,
+        PROP_MANAGER,
+        PROP_BUS_CONNECTION
+};
+
+static void gsd_smartcard_service_set_property (GObject *object,
+                                                guint property_id,
+                                                const GValue *value,
+                                                GParamSpec *param_spec);
+static void gsd_smartcard_service_get_property (GObject *object,
+                                                guint property_id,
+                                                GValue *value,
+                                                GParamSpec *param_spec);
+static void async_initable_interface_init (GAsyncInitableIface *interface);
+static void smartcard_service_manager_interface_init (GsdSmartcardServiceManagerIface *interface);
+
+G_LOCK_DEFINE_STATIC (gsd_smartcard_tokens);
+
+G_DEFINE_TYPE_WITH_CODE (GsdSmartcardService,
+                         gsd_smartcard_service,
+                         GSD_SMARTCARD_SERVICE_TYPE_MANAGER_SKELETON,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
+                                                async_initable_interface_init)
+                         G_IMPLEMENT_INTERFACE (GSD_SMARTCARD_SERVICE_TYPE_MANAGER,
+                                                smartcard_service_manager_interface_init));
+
+static void
+set_bus_connection (GsdSmartcardService  *self,
+                    GDBusConnection      *connection)
+{
+        if (self->bus_connection != connection) {
+                g_clear_object (&self->bus_connection);
+                self->bus_connection = g_object_ref (connection);
+                g_object_notify (G_OBJECT (self), "bus-connection");
+        }
+}
+
+static void
+register_object_manager (GsdSmartcardService *self)
+{
+        GsdSmartcardServiceObjectSkeleton *object;
+
+        self->object_manager_server = g_dbus_object_manager_server_new (GSD_SMARTCARD_DBUS_PATH);
+
+        object = gsd_smartcard_service_object_skeleton_new (GSD_SMARTCARD_MANAGER_DBUS_PATH);
+        gsd_smartcard_service_object_skeleton_set_manager (object,
+                                                           GSD_SMARTCARD_SERVICE_MANAGER (self));
+
+        g_dbus_object_manager_server_export (self->object_manager_server,
+                                             G_DBUS_OBJECT_SKELETON (object));
+        g_object_unref (object);
+
+        g_dbus_object_manager_server_set_connection (self->object_manager_server,
+                                                     self->bus_connection);
+}
+
+static const char *
+get_login_token_object_path (GsdSmartcardService *self)
+{
+        return GSD_SMARTCARD_MANAGER_TOKENS_DBUS_PATH "/login_token";
+}
+
+static void
+register_login_token_alias (GsdSmartcardService *self)
+{
+        GDBusObjectSkeleton *object;
+        GDBusInterfaceSkeleton *interface;
+        const char *object_path;
+        const char *token_name;
+
+        token_name = g_getenv ("PKCS11_LOGIN_TOKEN_NAME");
+
+        if (token_name == NULL)
+                return;
+
+        object_path = get_login_token_object_path (self);
+        object = G_DBUS_OBJECT_SKELETON (gsd_smartcard_service_object_skeleton_new (object_path));
+        interface = G_DBUS_INTERFACE_SKELETON (gsd_smartcard_service_token_skeleton_new ());
+
+        g_dbus_object_skeleton_add_interface (object, interface);
+        g_object_unref (interface);
+
+        g_object_set (G_OBJECT (interface),
+                      "name", token_name,
+                      "used-to-login", TRUE,
+                      "is-inserted", FALSE,
+                      NULL);
+
+        g_dbus_object_manager_server_export (self->object_manager_server,
+                                             object);
+
+        G_LOCK (gsd_smartcard_tokens);
+        g_hash_table_insert (self->tokens, g_strdup (object_path), interface);
+        G_UNLOCK (gsd_smartcard_tokens);
+}
+
+static void
+on_bus_gotten (GObject      *source_object,
+               GAsyncResult *result,
+               GTask        *task)
+{
+        GsdSmartcardService *self;
+        GDBusConnection *connection;
+        GError *error = NULL;
+
+        connection = g_bus_get_finish (result, &error);
+        if (connection == NULL) {
+                g_task_return_error (task, error);
+                goto out;
+        }
+
+        g_debug ("taking name %s on session bus", GSD_SMARTCARD_DBUS_NAME);
+
+        self = g_task_get_source_object (task);
+
+        set_bus_connection (self, connection);
+
+        register_object_manager (self);
+        self->name_id = g_bus_own_name_on_connection (connection,
+                                                      GSD_SMARTCARD_DBUS_NAME,
+                                                      G_BUS_NAME_OWNER_FLAGS_NONE,
+                                                      NULL,
+                                                      NULL,
+                                                      NULL,
+                                                      NULL);
+
+        /* In case the login token is removed at start up, register an
+         * an alias interface that's always around
+         */
+        register_login_token_alias (self);
+        g_task_return_boolean (task, TRUE);
+
+out:
+        g_object_unref (task);
+        return;
+}
+
+static gboolean
+gsd_smartcard_service_initable_init_finish (GAsyncInitable  *initable,
+                                            GAsyncResult    *result,
+                                            GError         **error)
+{
+        GTask *task;
+
+        task = G_TASK (result);
+
+        return g_task_propagate_boolean (task, error);
+}
+
+static void
+gsd_smartcard_service_initable_init_async (GAsyncInitable      *initable,
+                                           int                  io_priority,
+                                           GCancellable        *cancellable,
+                                           GAsyncReadyCallback  callback,
+                                           gpointer             user_data)
+{
+        GsdSmartcardService *self = GSD_SMARTCARD_SERVICE (initable);
+        GTask *task;
+
+        task = g_task_new (G_OBJECT (self), cancellable, callback, user_data);
+        g_task_set_priority (task, io_priority);
+
+        g_bus_get (G_BUS_TYPE_SESSION, cancellable, (GAsyncReadyCallback) on_bus_gotten, task);
+}
+
+static void
+async_initable_interface_init (GAsyncInitableIface *interface)
+{
+        interface->init_async = gsd_smartcard_service_initable_init_async;
+        interface->init_finish = gsd_smartcard_service_initable_init_finish;
+}
+
+static char *
+get_object_path_for_token (GsdSmartcardService *self,
+                           PK11SlotInfo        *card_slot)
+{
+        char *object_path;
+        char *escaped_library_path;
+        SECMODModule *driver;
+        CK_SLOT_ID slot_id;
+
+        driver = PK11_GetModule (card_slot);
+        slot_id = PK11_GetSlotID (card_slot);
+
+        escaped_library_path = gsd_smartcard_utils_escape_object_path (driver->dllName);
+
+        object_path = g_strdup_printf ("%s/token_from_%s_slot_%lu",
+                                       GSD_SMARTCARD_MANAGER_TOKENS_DBUS_PATH,
+                                       escaped_library_path,
+                                       (gulong) slot_id);
+        g_free (escaped_library_path);
+
+        return object_path;
+}
+
+static gboolean
+gsd_smartcard_service_handle_get_login_token (GsdSmartcardServiceManager *manager,
+                                              GDBusMethodInvocation      *invocation)
+{
+        GsdSmartcardService *self = GSD_SMARTCARD_SERVICE (manager);
+        PK11SlotInfo *card_slot;
+        char *object_path;
+
+        card_slot = gsd_smartcard_manager_get_login_token (self->smartcard_manager);
+
+        if (card_slot == NULL) {
+                const char *login_token_object_path;
+
+                /* If we know there's a login token but it was removed before the
+                 * smartcard manager could examine it, just return the generic login
+                 * token object path
+                 */
+                login_token_object_path = get_login_token_object_path (self);
+
+                if (g_hash_table_contains (self->tokens, login_token_object_path)) {
+                        gsd_smartcard_service_manager_complete_get_login_token (manager,
+                                                                                invocation,
+                                                                                login_token_object_path);
+                        return TRUE;
+                }
+
+                g_dbus_method_invocation_return_error (invocation,
+                                                       GSD_SMARTCARD_MANAGER_ERROR,
+                                                       GSD_SMARTCARD_MANAGER_ERROR_FINDING_SMARTCARD,
+                                                       _("User was not logged in with smartcard."));
+
+                return TRUE;
+        }
+
+        object_path = get_object_path_for_token (self, card_slot);
+        gsd_smartcard_service_manager_complete_get_login_token (manager,
+                                                                invocation,
+                                                                object_path);
+        g_free (object_path);
+
+        return TRUE;
+}
+
+static gboolean
+gsd_smartcard_service_handle_get_inserted_tokens (GsdSmartcardServiceManager *manager,
+                                                  GDBusMethodInvocation      *invocation)
+{
+        GsdSmartcardService *self = GSD_SMARTCARD_SERVICE (manager);
+        GList *inserted_tokens, *node;
+        GPtrArray *object_paths;
+
+        inserted_tokens = gsd_smartcard_manager_get_inserted_tokens (self->smartcard_manager,
+                                                                     NULL);
+
+        object_paths = g_ptr_array_new ();
+        for (node = inserted_tokens; node != NULL; node = node->next) {
+                PK11SlotInfo *card_slot = node->data;
+                char *object_path;
+
+                object_path = get_object_path_for_token (self, card_slot);
+                g_ptr_array_add (object_paths, object_path);
+        }
+        g_ptr_array_add (object_paths, NULL);
+        g_list_free (inserted_tokens);
+
+        gsd_smartcard_service_manager_complete_get_inserted_tokens (manager,
+                                                                    invocation,
+                                                                    (const char * const *) object_paths->pdata);
+
+        g_ptr_array_free (object_paths, TRUE);
+
+        return TRUE;
+}
+
+static void
+smartcard_service_manager_interface_init (GsdSmartcardServiceManagerIface *interface)
+{
+        interface->handle_get_login_token = gsd_smartcard_service_handle_get_login_token;
+        interface->handle_get_inserted_tokens = gsd_smartcard_service_handle_get_inserted_tokens;
+}
+
+static void
+gsd_smartcard_service_init (GsdSmartcardService *self)
+{
+        self->tokens = g_hash_table_new_full (g_str_hash,
+                                              g_str_equal,
+                                              (GDestroyNotify) g_free,
+                                              NULL);
+}
+
+static void
+gsd_smartcard_service_dispose (GObject *object)
+{
+        GsdSmartcardService *self = GSD_SMARTCARD_SERVICE (object);
+
+        g_clear_object (&self->bus_connection);
+        g_clear_object (&self->object_manager_server);
+        g_clear_object (&self->smartcard_manager);
+
+        g_cancellable_cancel (self->cancellable);
+        g_clear_object (&self->cancellable);
+        g_clear_pointer (&self->tokens, g_hash_table_unref);
+
+        G_OBJECT_CLASS (gsd_smartcard_service_parent_class)->dispose (object);
+}
+
+static void
+gsd_smartcard_service_set_property (GObject      *object,
+                                    guint         property_id,
+                                    const GValue *value,
+                                    GParamSpec   *param_spec)
+{
+        GsdSmartcardService *self = GSD_SMARTCARD_SERVICE (object);
+
+        switch (property_id) {
+                case PROP_MANAGER:
+                        self->smartcard_manager = g_value_dup_object (value);
+                        break;
+                default:
+                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, param_spec);
+                        break;
+        }
+}
+
+static void
+gsd_smartcard_service_get_property (GObject    *object,
+                                    guint       property_id,
+                                    GValue     *value,
+                                    GParamSpec *param_spec)
+{
+        GsdSmartcardService *self = GSD_SMARTCARD_SERVICE (object);
+
+        switch (property_id) {
+                case PROP_MANAGER:
+                        g_value_set_object (value, self->smartcard_manager);
+                        break;
+                case PROP_BUS_CONNECTION:
+                        g_value_set_object (value, self->bus_connection);
+                        break;
+                default:
+                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, param_spec);
+                        break;
+        }
+}
+
+static void
+gsd_smartcard_service_class_init (GsdSmartcardServiceClass *service_class)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (service_class);
+        GParamSpec *param_spec;
+
+        object_class->dispose = gsd_smartcard_service_dispose;
+        object_class->set_property = gsd_smartcard_service_set_property;
+        object_class->get_property = gsd_smartcard_service_get_property;
+
+        param_spec = g_param_spec_object ("manager",
+                                          "Smartcard Manager",
+                                          "Smartcard Manager",
+                                          GSD_TYPE_SMARTCARD_MANAGER,
+                                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+        g_object_class_install_property (object_class, PROP_MANAGER, param_spec);
+        param_spec = g_param_spec_object ("bus-connection",
+                                          "Bus Connection",
+                                          "bus connection",
+                                          G_TYPE_DBUS_CONNECTION,
+                                          G_PARAM_READABLE);
+        g_object_class_install_property (object_class, PROP_BUS_CONNECTION, param_spec);
+}
+
+static void
+on_new_async_finished (GObject      *source_object,
+                       GAsyncResult *result,
+                       GTask        *task)
+{
+        GError *error = NULL;
+        GObject *object;
+
+        object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
+                                              result,
+                                              &error);
+
+        if (object == NULL) {
+                g_task_return_error (task, error);
+                goto out;
+        }
+
+        g_assert (GSD_IS_SMARTCARD_SERVICE (object));
+
+        g_task_return_pointer (task, object, g_object_unref);
+out:
+        g_object_unref (task);
+        return;
+}
+
+void
+gsd_smartcard_service_new_async (GsdSmartcardManager *manager,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+        GTask *task;
+
+        task = g_task_new (NULL, cancellable, callback, user_data);
+
+        g_async_initable_new_async (GSD_TYPE_SMARTCARD_SERVICE,
+                                    G_PRIORITY_DEFAULT,
+                                    cancellable,
+                                    (GAsyncReadyCallback) on_new_async_finished,
+                                    task,
+                                    "manager", manager,
+                                    NULL);
+}
+
+GsdSmartcardService *
+gsd_smartcard_service_new_finish (GAsyncResult  *result,
+                                  GError       **error)
+{
+        GTask *task;
+        GsdSmartcardService *self = NULL;
+
+        task = G_TASK (result);
+
+        self = g_task_propagate_pointer (task, error);
+
+        if (self == NULL)
+                return self;
+
+        return g_object_ref (self);
+}
+
+static char *
+get_object_path_for_driver (GsdSmartcardService *self,
+                            SECMODModule        *driver)
+{
+        char *object_path;
+        char *escaped_library_path;
+
+        escaped_library_path = gsd_smartcard_utils_escape_object_path (driver->dllName);
+
+        object_path = g_build_path ("/",
+                                    GSD_SMARTCARD_MANAGER_DRIVERS_DBUS_PATH,
+                                    escaped_library_path, NULL);
+        g_free (escaped_library_path);
+
+        return object_path;
+}
+
+void
+gsd_smartcard_service_register_driver (GsdSmartcardService  *self,
+                                       SECMODModule         *driver)
+{
+        char *object_path;
+        GDBusObjectSkeleton *object;
+        GDBusInterfaceSkeleton *interface;
+
+        object_path = get_object_path_for_driver (self, driver);
+        object = G_DBUS_OBJECT_SKELETON (gsd_smartcard_service_object_skeleton_new (object_path));
+        g_free (object_path);
+
+        interface = G_DBUS_INTERFACE_SKELETON (gsd_smartcard_service_driver_skeleton_new ());
+        g_dbus_object_skeleton_add_interface (object, interface);
+        g_object_unref (interface);
+
+        g_object_set (G_OBJECT (interface),
+                      "library", driver->dllName,
+                      "description", driver->commonName,
+                      NULL);
+        g_dbus_object_manager_server_export (self->object_manager_server,
+                                             object);
+        g_object_unref (object);
+}
+
+static void
+synchronize_token_now (GsdSmartcardService *self,
+                       PK11SlotInfo        *card_slot)
+{
+        GDBusInterfaceSkeleton *interface;
+        char *object_path;
+        const char *token_name;
+        gboolean is_present, is_login_card;
+
+        object_path = get_object_path_for_token (self, card_slot);
+
+        G_LOCK (gsd_smartcard_tokens);
+        interface = g_hash_table_lookup (self->tokens, object_path);
+        g_free (object_path);
+
+        if (interface == NULL)
+                goto out;
+
+        token_name = PK11_GetTokenName (card_slot);
+        is_present = PK11_IsPresent (card_slot);
+
+        if (g_strcmp0 (g_getenv ("PKCS11_LOGIN_TOKEN_NAME"), token_name) == 0)
+                is_login_card = TRUE;
+        else
+                is_login_card = FALSE;
+
+        g_debug ("===============================");
+        g_debug (" Token '%s'", token_name);
+        g_debug (" Inserted: %s", is_present? "yes" : "no");
+        g_debug (" Previously used to login: %s", is_login_card? "yes" : "no");
+        g_debug ("===============================\n");
+
+        if (!is_present && is_login_card) {
+                gboolean was_present;
+
+                g_object_get (G_OBJECT (interface),
+                              "is-inserted", &was_present,
+                              NULL);
+
+                if (was_present)
+                        gsd_smartcard_manager_do_remove_action (self->smartcard_manager);
+        }
+
+        g_object_set (G_OBJECT (interface),
+                      "used-to-login", is_login_card,
+                      "is-inserted", is_present,
+                      NULL);
+        g_object_get (G_OBJECT (interface),
+                      "used-to-login", &is_login_card,
+                      "is-inserted", &is_present,
+                      NULL);
+
+        if (is_login_card && !self->login_token_bound) {
+                const char *login_token_path;
+                GDBusInterfaceSkeleton *login_token_interface;
+
+                login_token_path = get_login_token_object_path (self);
+                login_token_interface = g_hash_table_lookup (self->tokens, login_token_path);
+
+                if (login_token_interface != NULL) {
+                        g_object_bind_property (interface, "driver",
+                                                login_token_interface, "driver",
+                                                G_BINDING_SYNC_CREATE);
+                        g_object_bind_property (interface, "is-inserted",
+                                                login_token_interface, "is-inserted",
+                                                G_BINDING_SYNC_CREATE);
+                        self->login_token_bound = TRUE;
+                }
+        }
+
+out:
+        G_UNLOCK (gsd_smartcard_tokens);
+}
+
+typedef struct
+{
+        PK11SlotInfo *card_slot;
+        char         *object_path;
+        GSource      *main_thread_source;
+} RegisterNewTokenOperation;
+
+static void
+destroy_register_new_token_operation (RegisterNewTokenOperation *operation)
+{
+        g_clear_pointer (&operation->main_thread_source,
+                         g_source_destroy);
+        PK11_FreeSlot (operation->card_slot);
+        g_free (operation->object_path);
+        g_free (operation);
+}
+
+static gboolean
+on_main_thread_to_register_new_token (GTask *task)
+{
+        GsdSmartcardService *self;
+        GDBusObjectSkeleton *object;
+        GDBusInterfaceSkeleton *interface;
+        RegisterNewTokenOperation *operation;
+        SECMODModule *driver;
+        char *driver_object_path;
+        const char *token_name;
+
+        self = g_task_get_source_object (task);
+
+        operation = g_task_get_task_data (task);
+        operation->main_thread_source = NULL;
+
+        object = G_DBUS_OBJECT_SKELETON (gsd_smartcard_service_object_skeleton_new (operation->object_path));
+        interface = G_DBUS_INTERFACE_SKELETON (gsd_smartcard_service_token_skeleton_new ());
+
+        g_dbus_object_skeleton_add_interface (object, interface);
+        g_object_unref (interface);
+
+        driver = PK11_GetModule (operation->card_slot);
+        driver_object_path = get_object_path_for_driver (self, driver);
+
+        token_name = PK11_GetTokenName (operation->card_slot);
+
+        g_object_set (G_OBJECT (interface),
+                      "driver", driver_object_path,
+                      "name", token_name,
+                      NULL);
+        g_free (driver_object_path);
+
+        g_dbus_object_manager_server_export (self->object_manager_server,
+                                             object);
+
+        G_LOCK (gsd_smartcard_tokens);
+        g_hash_table_insert (self->tokens, g_strdup (operation->object_path), interface);
+        G_UNLOCK (gsd_smartcard_tokens);
+
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+
+        return G_SOURCE_REMOVE;
+}
+
+static void
+create_main_thread_source (GSourceFunc   callback,
+                           gpointer      user_data,
+                           GSource     **source_out)
+{
+        GSource *source;
+
+        source = g_idle_source_new ();
+        g_source_set_callback (source, callback, user_data, NULL);
+
+        *source_out = source;
+        g_source_attach (source, NULL);
+        g_source_unref (source);
+}
+
+static void
+register_new_token_in_main_thread (GsdSmartcardService *self,
+                                   PK11SlotInfo        *card_slot,
+                                   char                *object_path,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
+{
+        RegisterNewTokenOperation *operation;
+        GTask *task;
+
+        operation = g_new0 (RegisterNewTokenOperation, 1);
+        operation->card_slot = PK11_ReferenceSlot (card_slot);
+        operation->object_path = g_strdup (object_path);
+
+        task = g_task_new (self, cancellable, callback, user_data);
+
+        g_task_set_task_data (task,
+                              operation,
+                              (GDestroyNotify) destroy_register_new_token_operation);
+
+        create_main_thread_source ((GSourceFunc) on_main_thread_to_register_new_token,
+                                   task,
+                                   &operation->main_thread_source);
+}
+
+static gboolean
+register_new_token_in_main_thread_finish (GsdSmartcardService  *self,
+                                          GAsyncResult         *result,
+                                          GError              **error)
+{
+        return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+on_token_registered (GsdSmartcardService *self,
+                     GAsyncResult        *result,
+                     PK11SlotInfo        *card_slot)
+{
+        gboolean registered;
+        GError *error = NULL;
+
+        registered = register_new_token_in_main_thread_finish (self, result, &error);
+
+        if (!registered) {
+                g_debug ("Couldn't register token: %s",
+                         error->message);
+                goto out;
+        }
+
+        synchronize_token_now (self, card_slot);
+
+out:
+        PK11_FreeSlot (card_slot);
+}
+
+typedef struct
+{
+        PK11SlotInfo *card_slot;
+        GSource      *main_thread_source;
+} SynchronizeTokenOperation;
+
+static void
+destroy_synchronize_token_operation (SynchronizeTokenOperation *operation)
+{
+        g_clear_pointer (&operation->main_thread_source,
+                         g_source_destroy);
+        PK11_FreeSlot (operation->card_slot);
+        g_free (operation);
+}
+
+static gboolean
+on_main_thread_to_synchronize_token (GTask *task)
+{
+        GsdSmartcardService *self;
+        SynchronizeTokenOperation *operation;
+
+        self = g_task_get_source_object (task);
+
+        operation = g_task_get_task_data (task);
+        operation->main_thread_source = NULL;
+
+        synchronize_token_now (self, operation->card_slot);
+
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+
+        return G_SOURCE_REMOVE;
+}
+
+static gboolean
+synchronize_token_in_main_thread_finish (GsdSmartcardService  *self,
+                                         GAsyncResult         *result,
+                                         GError              **error)
+{
+        return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+synchronize_token_in_main_thread (GsdSmartcardService *self,
+                                  PK11SlotInfo        *card_slot,
+                                  GCancellable        *cancellable,
+                                  GAsyncReadyCallback  callback,
+                                  gpointer             user_data)
+{
+        SynchronizeTokenOperation *operation;
+        GTask *task;
+
+        operation = g_new0 (SynchronizeTokenOperation, 1);
+        operation->card_slot = PK11_ReferenceSlot (card_slot);
+
+        task = g_task_new (self, cancellable, callback, user_data);
+
+        g_task_set_task_data (task,
+                              operation,
+                              (GDestroyNotify)
+                              destroy_synchronize_token_operation);
+
+        create_main_thread_source ((GSourceFunc)
+                                   on_main_thread_to_synchronize_token,
+                                   task,
+                                   &operation->main_thread_source);
+}
+
+static void
+on_token_synchronized (GsdSmartcardService *self,
+                       GAsyncResult        *result,
+                       PK11SlotInfo        *card_slot)
+{
+        gboolean synchronized;
+        GError *error = NULL;
+
+        synchronized = synchronize_token_in_main_thread_finish (self, result, &error);
+
+        if (!synchronized)
+                g_debug ("Couldn't synchronize token: %s", error->message);
+
+        PK11_FreeSlot (card_slot);
+}
+
+void
+gsd_smartcard_service_sync_token (GsdSmartcardService *self,
+                                  PK11SlotInfo        *card_slot,
+                                  GCancellable        *cancellable)
+{
+        char *object_path;
+        GDBusInterfaceSkeleton *interface;
+
+        object_path = get_object_path_for_token (self, card_slot);
+
+        G_LOCK (gsd_smartcard_tokens);
+        interface = g_hash_table_lookup (self->tokens, object_path);
+        G_UNLOCK (gsd_smartcard_tokens);
+
+        if (interface == NULL)
+                register_new_token_in_main_thread (self,
+                                                   card_slot,
+                                                   object_path,
+                                                   cancellable,
+                                                   (GAsyncReadyCallback)
+                                                   on_token_registered,
+                                                   PK11_ReferenceSlot (card_slot));
+
+        else
+                synchronize_token_in_main_thread (self,
+                                                  card_slot,
+                                                  cancellable,
+                                                  (GAsyncReadyCallback)
+                                                  on_token_synchronized,
+                                                  PK11_ReferenceSlot (card_slot));
+
+        g_free (object_path);
+}
diff --git a/plugins/smartcard/gsd-smartcard-service.h b/plugins/smartcard/gsd-smartcard-service.h
new file mode 100644
index 0000000..11b3e22
--- /dev/null
+++ b/plugins/smartcard/gsd-smartcard-service.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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 .
+ *
+ * Authors: Ray Strode
+ */
+
+#ifndef __GSD_SMARTCARD_SERVICE_H__
+#define __GSD_SMARTCARD_SERVICE_H__
+
+#include 
+#include 
+#include 
+#include "gsd-smartcard-manager.h"
+
+#include "org.gnome.SettingsDaemon.Smartcard.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_SMARTCARD_SERVICE (gsd_smartcard_service_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdSmartcardService, gsd_smartcard_service, GSD, SMARTCARD_SERVICE, GsdSmartcardServiceManagerSkeleton)
+
+void  gsd_smartcard_service_new_async (GsdSmartcardManager  *manager,
+                                       GCancellable         *cancellable,
+                                       GAsyncReadyCallback   callback,
+                                       gpointer              user_data);
+GsdSmartcardService *gsd_smartcard_service_new_finish (GAsyncResult         *result,
+                                                       GError              **error);
+
+void  gsd_smartcard_service_register_driver (GsdSmartcardService  *service,
+                                             SECMODModule         *driver);
+void  gsd_smartcard_service_sync_token (GsdSmartcardService  *service,
+                                        PK11SlotInfo         *slot_info,
+                                        GCancellable         *cancellable);
+
+
+G_END_DECLS
+
+#endif /* __GSD_SMARTCARD_SERVICE_H__ */
diff --git a/plugins/smartcard/gsd-smartcard-utils.c b/plugins/smartcard/gsd-smartcard-utils.c
new file mode 100644
index 0000000..6b9461b
--- /dev/null
+++ b/plugins/smartcard/gsd-smartcard-utils.c
@@ -0,0 +1,174 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+#include "gsd-smartcard-utils.h"
+
+#include 
+
+#include 
+#include 
+
+static char *
+dashed_string_to_studly_caps (const char *dashed_string)
+{
+        char *studly_string;
+        size_t studly_string_length;
+        size_t i;
+
+        i = 0;
+
+        studly_string = g_strdup (dashed_string);
+        studly_string_length = strlen (studly_string);
+
+        studly_string[i] = g_ascii_toupper (studly_string[i]);
+        i++;
+
+        while (i < studly_string_length) {
+                if (studly_string[i] == '-' || studly_string[i] == '_') {
+                        memmove (studly_string + i,
+                                 studly_string + i + 1,
+                                 studly_string_length - i - 1);
+                        studly_string_length--;
+                        if (g_ascii_isalpha (studly_string[i])) {
+                                studly_string[i] = g_ascii_toupper (studly_string[i]);
+                        }
+                }
+                i++;
+        }
+        studly_string[studly_string_length] = '\0';
+
+        return studly_string;
+}
+
+static char *
+dashed_string_to_dbus_error_string (const char *dashed_string,
+                                    const char *old_prefix,
+                                    const char *new_prefix,
+                                    const char *suffix)
+{
+        char *studly_suffix;
+        char *dbus_error_string;
+        size_t dbus_error_string_length;
+        size_t i;
+
+        i = 0;
+
+        if (g_str_has_prefix (dashed_string, old_prefix) &&
+            (dashed_string[strlen(old_prefix)] == '-' ||
+             dashed_string[strlen(old_prefix)] == '_')) {
+                dashed_string += strlen (old_prefix) + 1;
+        }
+
+        studly_suffix = dashed_string_to_studly_caps (suffix);
+        dbus_error_string = g_strdup_printf ("%s.%s.%s", new_prefix, dashed_string, studly_suffix);
+        g_free (studly_suffix);
+        i += strlen (new_prefix) + 1;
+
+        dbus_error_string_length = strlen (dbus_error_string);
+
+        dbus_error_string[i] = g_ascii_toupper (dbus_error_string[i]);
+        i++;
+
+        while (i < dbus_error_string_length) {
+                if (dbus_error_string[i] == '_' || dbus_error_string[i] == '-') {
+                        dbus_error_string[i] = '.';
+
+                        if (g_ascii_isalpha (dbus_error_string[i + 1])) {
+                                dbus_error_string[i + 1] = g_ascii_toupper (dbus_error_string[i + 1]);
+                        }
+                }
+
+                i++;
+        }
+
+        return dbus_error_string;
+}
+
+void
+gsd_smartcard_utils_register_error_domain (GQuark error_domain,
+                                           GType  error_enum)
+{
+        const char *error_domain_string;
+        char *type_name;
+        GType type;
+        GTypeClass *type_class;
+        GEnumClass *enum_class;
+        guint i;
+
+        error_domain_string = g_quark_to_string (error_domain);
+        type_name = dashed_string_to_studly_caps (error_domain_string);
+        type = g_type_from_name (type_name);
+        type_class = g_type_class_ref (type);
+        enum_class = G_ENUM_CLASS (type_class);
+
+        for (i = 0; i < enum_class->n_values; i++) {
+                char *dbus_error_string;
+
+                dbus_error_string = dashed_string_to_dbus_error_string (error_domain_string,
+                                                                        "gsd",
+                                                                        "org.gnome.SettingsDaemon",
+                                                                        enum_class->values[i].value_nick);
+
+                g_debug ("%s: Registering dbus error %s", type_name, dbus_error_string);
+                g_dbus_error_register_error (error_domain,
+                                             enum_class->values[i].value,
+                                             dbus_error_string);
+                g_free (dbus_error_string);
+        }
+
+        g_type_class_unref (type_class);
+}
+
+char *
+gsd_smartcard_utils_escape_object_path (const char *unescaped_string)
+{
+  const char *p;
+  char *object_path;
+  GString *string;
+
+  g_return_val_if_fail (unescaped_string != NULL, NULL);
+
+  string = g_string_new ("");
+
+  for (p = unescaped_string; *p != '\0'; p++)
+    {
+      guchar character;
+
+      character = (guchar) * p;
+
+      if (((character >= ((guchar) 'a')) &&
+           (character <= ((guchar) 'z'))) ||
+          ((character >= ((guchar) 'A')) &&
+           (character <= ((guchar) 'Z'))) ||
+          ((character >= ((guchar) '0')) && (character <= ((guchar) '9'))))
+        {
+          g_string_append_c (string, (char) character);
+          continue;
+        }
+
+      g_string_append_printf (string, "_%x_", character);
+    }
+
+  object_path = string->str;
+
+  g_string_free (string, FALSE);
+
+  return object_path;
+}
diff --git a/plugins/smartcard/gsd-smartcard-utils.h b/plugins/smartcard/gsd-smartcard-utils.h
new file mode 100644
index 0000000..c7822bf
--- /dev/null
+++ b/plugins/smartcard/gsd-smartcard-utils.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_SMARTCARD_UTILS_H
+#define __GSD_SMARTCARD_UTILS_H
+
+#include 
+#include 
+
+G_BEGIN_DECLS
+void             gsd_smartcard_utils_register_error_domain             (GQuark error_domain,
+                                                                        GType error_enum);
+char *           gsd_smartcard_utils_escape_object_path                (const char *unescaped_string);
+
+G_END_DECLS
+
+#endif /* __GSD_SMARTCARD_MANAGER_H */
diff --git a/plugins/smartcard/main.c b/plugins/smartcard/main.c
new file mode 100644
index 0000000..3552e5e
--- /dev/null
+++ b/plugins/smartcard/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_smartcard_manager_new
+#define START gsd_smartcard_manager_start
+#define STOP gsd_smartcard_manager_stop
+#define MANAGER GsdSmartcardManager
+#include "gsd-smartcard-manager.h"
+
+#include "daemon-skeleton.h"
diff --git a/plugins/smartcard/meson.build b/plugins/smartcard/meson.build
new file mode 100644
index 0000000..916a0fc
--- /dev/null
+++ b/plugins/smartcard/meson.build
@@ -0,0 +1,49 @@
+sources = files(
+  'gsd-smartcard-manager.c',
+  'gsd-smartcard-service.c',
+  'gsd-smartcard-utils.c',
+  'main.c'
+)
+
+enum_headers = files(
+  'gsd-smartcard-manager.h',
+  'gsd-smartcard-utils.h'
+)
+
+enum_types = 'gsd-smartcard-enum-types'
+
+sources += gnome.mkenums(
+  enum_types,
+  sources: enum_headers,
+  c_template: enum_types + '.c.in',
+  h_template: enum_types + '.h.in'
+)
+
+gdbus = 'org.gnome.SettingsDaemon.Smartcard'
+
+sources += gnome.gdbus_codegen(
+  gdbus,
+  gdbus + '.xml',
+  interface_prefix: gdbus + '.',
+  namespace: 'GsdSmartcardService',
+  object_manager: true
+)
+
+deps = plugins_deps + [
+  gio_unix_dep,
+  libnotify_dep,
+  nss_dep
+]
+
+cflags += ['-DGSD_SMARTCARD_MANAGER_NSS_DB="@0@"'.format(system_nssdb_dir)]
+
+executable(
+  'gsd-' + plugin_name,
+  sources,
+  include_directories: [top_inc, common_inc],
+  dependencies: deps,
+  c_args: cflags,
+  install: true,
+  install_rpath: gsd_pkglibdir,
+  install_dir: gsd_libexecdir
+)
diff --git a/plugins/smartcard/org.gnome.SettingsDaemon.Smartcard.xml b/plugins/smartcard/org.gnome.SettingsDaemon.Smartcard.xml
new file mode 100644
index 0000000..53d56a0
--- /dev/null
+++ b/plugins/smartcard/org.gnome.SettingsDaemon.Smartcard.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+        
+        
+                
+                        
+                
+
+                
+                        
+                
+        
+
+        
+        
+                
+                
+
+                
+                
+        
+
+        
+        
+                
+                
+
+                
+                
+
+                
+                
+
+                
+                
+        
+
diff --git a/plugins/sound/gsd-sound-manager.c b/plugins/sound/gsd-sound-manager.c
new file mode 100644
index 0000000..5fdb062
--- /dev/null
+++ b/plugins/sound/gsd-sound-manager.c
@@ -0,0 +1,362 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Lennart Poettering 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+
+#include "gsd-sound-manager.h"
+#include "gnome-settings-profile.h"
+
+struct _GsdSoundManager
+{
+        GObject    parent;
+
+        GSettings *settings;
+        GList     *monitors;
+        guint      timeout;
+};
+
+static void gsd_sound_manager_class_init (GsdSoundManagerClass *klass);
+static void gsd_sound_manager_init (GsdSoundManager *sound_manager);
+static void gsd_sound_manager_finalize (GObject *object);
+
+G_DEFINE_TYPE (GsdSoundManager, gsd_sound_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+static void
+sample_info_cb (pa_context *c, const pa_sample_info *i, int eol, void *userdata)
+{
+        pa_operation *o;
+
+        if (!i)
+                return;
+
+        g_debug ("Found sample %s", i->name);
+
+        /* We only flush those samples which have an XDG sound name
+         * attached, because only those originate from themeing  */
+        if (!(pa_proplist_gets (i->proplist, PA_PROP_EVENT_ID)))
+                return;
+
+        g_debug ("Dropping sample %s from cache", i->name);
+
+        if (!(o = pa_context_remove_sample (c, i->name, NULL, NULL))) {
+                g_debug ("pa_context_remove_sample (): %s", pa_strerror (pa_context_errno (c)));
+                return;
+        }
+
+        pa_operation_unref (o);
+
+        /* We won't wait until the operation is actually executed to
+         * speed things up a bit.*/
+}
+
+static void
+flush_cache (void)
+{
+        pa_mainloop *ml = NULL;
+        pa_context *c = NULL;
+        pa_proplist *pl = NULL;
+        pa_operation *o = NULL;
+
+        g_debug ("Flushing sample cache");
+
+        if (!(ml = pa_mainloop_new ())) {
+                g_debug ("Failed to allocate pa_mainloop");
+                goto fail;
+        }
+
+        if (!(pl = pa_proplist_new ())) {
+                g_debug ("Failed to allocate pa_proplist");
+                goto fail;
+        }
+
+        pa_proplist_sets (pl, PA_PROP_APPLICATION_NAME, PACKAGE_NAME);
+        pa_proplist_sets (pl, PA_PROP_APPLICATION_VERSION, PACKAGE_VERSION);
+        pa_proplist_sets (pl, PA_PROP_APPLICATION_ID, "org.gnome.SettingsDaemon.Sound");
+
+        if (!(c = pa_context_new_with_proplist (pa_mainloop_get_api (ml), PACKAGE_NAME, pl))) {
+                g_debug ("Failed to allocate pa_context");
+                goto fail;
+        }
+
+        pa_proplist_free (pl);
+        pl = NULL;
+
+        if (pa_context_connect (c, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0) {
+                g_debug ("pa_context_connect(): %s", pa_strerror (pa_context_errno (c)));
+                goto fail;
+        }
+
+        /* Wait until the connection is established */
+        while (pa_context_get_state (c) != PA_CONTEXT_READY) {
+
+                if (!PA_CONTEXT_IS_GOOD (pa_context_get_state (c))) {
+                        g_debug ("Connection failed: %s", pa_strerror (pa_context_errno (c)));
+                        goto fail;
+                }
+
+                if (pa_mainloop_iterate (ml, TRUE, NULL) < 0) {
+                        g_debug ("pa_mainloop_iterate() failed");
+                        goto fail;
+                }
+        }
+
+        /* Enumerate all cached samples */
+        if (!(o = pa_context_get_sample_info_list (c, sample_info_cb, NULL))) {
+                g_debug ("pa_context_get_sample_info_list(): %s", pa_strerror (pa_context_errno (c)));
+                goto fail;
+        }
+
+        /* Wait until our operation is finished and there's nothing
+         * more queued to send to the server */
+        while (pa_operation_get_state (o) == PA_OPERATION_RUNNING || pa_context_is_pending (c)) {
+
+                if (!PA_CONTEXT_IS_GOOD (pa_context_get_state (c))) {
+                        g_debug ("Connection failed: %s", pa_strerror (pa_context_errno (c)));
+                        goto fail;
+                }
+
+                if (pa_mainloop_iterate (ml, TRUE, NULL) < 0) {
+                        g_debug ("pa_mainloop_iterate() failed");
+                        goto fail;
+                }
+        }
+
+        g_debug ("Sample cache flushed");
+
+fail:
+        if (o) {
+                pa_operation_cancel (o);
+                pa_operation_unref (o);
+        }
+
+        if (c) {
+                pa_context_disconnect (c);
+                pa_context_unref (c);
+        }
+
+        if (pl)
+                pa_proplist_free (pl);
+
+        if (ml)
+                pa_mainloop_free (ml);
+}
+
+static gboolean
+flush_cb (GsdSoundManager *manager)
+{
+        flush_cache ();
+        manager->timeout = 0;
+        return FALSE;
+}
+
+static void
+trigger_flush (GsdSoundManager *manager)
+{
+
+        if (manager->timeout)
+                g_source_remove (manager->timeout);
+
+        /* We delay the flushing a bit so that we can coalesce
+         * multiple changes into a single cache flush */
+        manager->timeout = g_timeout_add (500, (GSourceFunc) flush_cb, manager);
+        g_source_set_name_by_id (manager->timeout, "[gnome-settings-daemon] flush_cb");
+}
+
+static void
+settings_changed_cb (GSettings       *settings,
+		     const char      *key,
+		     GsdSoundManager *manager)
+{
+        trigger_flush (manager);
+}
+
+static void
+register_config_callback (GsdSoundManager *manager)
+{
+	manager->settings = g_settings_new ("org.gnome.desktop.sound");
+	g_signal_connect (G_OBJECT (manager->settings), "changed",
+			  G_CALLBACK (settings_changed_cb), manager);
+}
+
+static void
+file_monitor_changed_cb (GFileMonitor *monitor,
+                         GFile *file,
+                         GFile *other_file,
+                         GFileMonitorEvent event,
+                         GsdSoundManager *manager)
+{
+        g_debug ("Theme dir changed");
+        trigger_flush (manager);
+}
+
+static gboolean
+register_directory_callback (GsdSoundManager *manager,
+                             const char *path,
+                             GError **error)
+{
+        GFile *f;
+        GFileMonitor *m;
+        gboolean succ = FALSE;
+
+        g_debug ("Registering directory monitor for %s", path);
+
+        f = g_file_new_for_path (path);
+
+        m = g_file_monitor_directory (f, 0, NULL, error);
+
+        if (m != NULL) {
+                g_signal_connect (m, "changed", G_CALLBACK (file_monitor_changed_cb), manager);
+
+                manager->monitors = g_list_prepend (manager->monitors, m);
+
+                succ = TRUE;
+        }
+
+        g_object_unref (f);
+
+        return succ;
+}
+
+gboolean
+gsd_sound_manager_start (GsdSoundManager *manager,
+                         GError **error)
+{
+        guint i;
+        const gchar * const * dirs;
+        char *p;
+
+        g_debug ("Starting sound manager");
+        gnome_settings_profile_start (NULL);
+
+        /* We listen for change of the selected theme ... */
+        register_config_callback (manager);
+
+        /* ... and we listen to changes of the theme base directories
+         * in $HOME ...*/
+        p = g_build_filename (g_get_user_data_dir (), "sounds", NULL);
+        if (g_mkdir_with_parents(p, 0700) == 0)
+                register_directory_callback (manager, p, NULL);
+        g_free (p);
+
+        /* ... and globally. */
+        dirs = g_get_system_data_dirs ();
+        for (i = 0; dirs[i] != NULL; i++) {
+                p = g_build_filename (dirs[i], "sounds", NULL);
+                if (g_file_test (p, G_FILE_TEST_IS_DIR))
+                        register_directory_callback (manager, p, NULL);
+                g_free (p);
+        }
+
+        gnome_settings_profile_end (NULL);
+
+        return TRUE;
+}
+
+void
+gsd_sound_manager_stop (GsdSoundManager *manager)
+{
+        g_debug ("Stopping sound manager");
+
+        if (manager->settings != NULL) {
+                g_object_unref (manager->settings);
+                manager->settings = NULL;
+        }
+
+        if (manager->timeout) {
+                g_source_remove (manager->timeout);
+                manager->timeout = 0;
+        }
+
+        while (manager->monitors) {
+                g_file_monitor_cancel (G_FILE_MONITOR (manager->monitors->data));
+                g_object_unref (manager->monitors->data);
+                manager->monitors = g_list_delete_link (manager->monitors, manager->monitors);
+        }
+}
+
+static void
+gsd_sound_manager_dispose (GObject *object)
+{
+        GsdSoundManager *manager;
+
+        manager = GSD_SOUND_MANAGER (object);
+
+        gsd_sound_manager_stop (manager);
+
+        G_OBJECT_CLASS (gsd_sound_manager_parent_class)->dispose (object);
+}
+
+static void
+gsd_sound_manager_class_init (GsdSoundManagerClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->dispose = gsd_sound_manager_dispose;
+        object_class->finalize = gsd_sound_manager_finalize;
+}
+
+static void
+gsd_sound_manager_init (GsdSoundManager *manager)
+{
+}
+
+static void
+gsd_sound_manager_finalize (GObject *object)
+{
+        GsdSoundManager *sound_manager;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSD_IS_SOUND_MANAGER (object));
+
+        sound_manager = GSD_SOUND_MANAGER (object);
+
+        g_return_if_fail (sound_manager);
+
+        G_OBJECT_CLASS (gsd_sound_manager_parent_class)->finalize (object);
+}
+
+GsdSoundManager *
+gsd_sound_manager_new (void)
+{
+        if (manager_object) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_SOUND_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object, (gpointer *) &manager_object);
+        }
+
+        return GSD_SOUND_MANAGER (manager_object);
+}
diff --git a/plugins/sound/gsd-sound-manager.h b/plugins/sound/gsd-sound-manager.h
new file mode 100644
index 0000000..de25e04
--- /dev/null
+++ b/plugins/sound/gsd-sound-manager.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Lennart Poettering 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_SOUND_MANAGER_H
+#define __GSD_SOUND_MANAGER_H
+
+#include 
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_SOUND_MANAGER         (gsd_sound_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdSoundManager, gsd_sound_manager, GSD, SOUND_MANAGER, GObject)
+
+GsdSoundManager *gsd_sound_manager_new (void);
+gboolean gsd_sound_manager_start (GsdSoundManager *manager, GError **error);
+void gsd_sound_manager_stop (GsdSoundManager *manager);
+
+G_END_DECLS
+
+#endif /* __GSD_SOUND_MANAGER_H */
diff --git a/plugins/sound/main.c b/plugins/sound/main.c
new file mode 100644
index 0000000..a170d48
--- /dev/null
+++ b/plugins/sound/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_sound_manager_new
+#define START gsd_sound_manager_start
+#define STOP gsd_sound_manager_stop
+#define MANAGER GsdSoundManager
+#include "gsd-sound-manager.h"
+
+#include "daemon-skeleton.h"
diff --git a/plugins/sound/meson.build b/plugins/sound/meson.build
new file mode 100644
index 0000000..300397f
--- /dev/null
+++ b/plugins/sound/meson.build
@@ -0,0 +1,20 @@
+sources = files(
+  'gsd-sound-manager.c',
+  'main.c'
+)
+
+deps = plugins_deps + [
+  gio_dep,
+  libpulse_mainloop_glib_dep
+]
+
+executable(
+  'gsd-' + plugin_name,
+  sources,
+  include_directories: [top_inc, common_inc],
+  dependencies: deps,
+  c_args: cflags,
+  install: true,
+  install_rpath: gsd_pkglibdir,
+  install_dir: gsd_libexecdir
+)
diff --git a/plugins/usb-protection/gsd-usb-protection-manager.c b/plugins/usb-protection/gsd-usb-protection-manager.c
new file mode 100644
index 0000000..1139696
--- /dev/null
+++ b/plugins/usb-protection/gsd-usb-protection-manager.c
@@ -0,0 +1,1199 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2018 Ludovico de Nittis 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include "gnome-settings-bus.h"
+#include "gnome-settings-profile.h"
+#include "gsd-enums.h"
+#include "gsd-usb-protection-manager.h"
+
+#define PRIVACY_SETTINGS "org.gnome.desktop.privacy"
+#define USB_PROTECTION "usb-protection"
+#define USB_PROTECTION_LEVEL "usb-protection-level"
+
+#define DBUS_VERSION "1"
+
+#define USBGUARD_DBUS_NAME "org.usbguard" DBUS_VERSION
+#define USBGUARD_DBUS_PATH "/org/usbguard" DBUS_VERSION
+#define USBGUARD_DBUS_INTERFACE "org.usbguard"
+#define USBGUARD_DBUS_INTERFACE_VERSIONED USBGUARD_DBUS_INTERFACE DBUS_VERSION
+
+#define USBGUARD_DBUS_PATH_POLICY USBGUARD_DBUS_PATH "/Policy"
+#define USBGUARD_DBUS_INTERFACE_POLICY USBGUARD_DBUS_INTERFACE ".Policy" DBUS_VERSION
+
+#define USBGUARD_DBUS_PATH_DEVICES USBGUARD_DBUS_PATH "/Devices"
+#define USBGUARD_DBUS_INTERFACE_DEVICES USBGUARD_DBUS_INTERFACE ".Devices" DBUS_VERSION
+
+#define APPLY_POLICY "apply-policy"
+#define BLOCK "block"
+#define REJECT "reject"
+
+#define APPLY_DEVICE_POLICY "applyDevicePolicy"
+#define LIST_DEVICES "listDevices"
+#define LIST_RULES "listRules"
+#define ALLOW "allow"
+#define DEVICE_POLICY_CHANGED "DevicePolicyChanged"
+#define DEVICE_PRESENCE_CHANGED "DevicePresenceChanged"
+#define INSERTED_DEVICE_POLICY "InsertedDevicePolicy"
+#define APPEND_RULE "appendRule"
+#define ALLOW_ALL "allow id *:* label \"GNOME_SETTINGS_DAEMON_RULE\""
+#define WITH_CONNECT_TYPE "with-connect-type"
+#define WITH_INTERFACE "with-interface"
+#define NAME "name"
+
+struct _GsdUsbProtectionManager
+{
+        GObject             parent;
+        guint               start_idle_id;
+        GDBusNodeInfo      *introspection_data;
+        GSettings          *settings;
+        guint               name_id;
+        GDBusConnection    *connection;
+        gboolean            available;
+        GDBusProxy         *usb_protection;
+        GDBusProxy         *usb_protection_devices;
+        GDBusProxy         *usb_protection_policy;
+        GCancellable       *cancellable;
+        GsdScreenSaver     *screensaver_proxy;
+        gboolean            screensaver_active;
+        guint               last_device_id;
+        NotifyNotification *notification;
+};
+
+typedef enum {
+        EVENT_PRESENT,
+        EVENT_INSERT,
+        EVENT_UPDATE,
+        EVENT_REMOVE
+} UsbGuardEvent;
+
+
+typedef enum {
+        TARGET_ALLOW,
+        TARGET_BLOCK,
+        TARGET_REJECT
+} UsbGuardTarget;
+
+typedef enum {
+        POLICY_DEVICE_ID,
+        POLICY_TARGET_OLD,
+        /* This is the rule that has been applied */
+        POLICY_TARGET_NEW,
+        POLICY_DEV_RULE,
+        /* The ID of the rule that has been applied.
+         * uint32 - 1 is one of the implicit rules,
+         * e.g. ImplicitPolicyTarget or InsertedDevicePolicy.
+         */
+        POLICY_RULE_ID,
+        POLICY_ATTRIBUTES
+} UsbGuardPolicyChanged;
+
+typedef enum {
+        PRESENCE_DEVICE_ID,
+        PRESENCE_EVENT,
+        /* That does not reflect what USBGuard intends to do with the device :( */
+        PRESENCE_TARGET,
+        PRESENCE_DEV_RULE,
+        PRESENCE_ATTRIBUTES
+} UsbGuardPresenceChanged;
+
+static void gsd_usb_protection_manager_finalize (GObject *object);
+
+G_DEFINE_TYPE (GsdUsbProtectionManager, gsd_usb_protection_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+#define GSD_DBUS_NAME "org.gnome.SettingsDaemon"
+#define GSD_DBUS_PATH "/org/gnome/SettingsDaemon"
+#define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon"
+
+#define GSD_USB_PROTECTION_DBUS_NAME GSD_DBUS_NAME ".UsbProtection"
+#define GSD_USB_PROTECTION_DBUS_PATH GSD_DBUS_PATH "/UsbProtection"
+
+static const gchar introspection_xml[] =
+""
+"  "
+"    "
+"  "
+"";
+
+static void
+dbus_call_log_error (GObject      *source_object,
+                     GAsyncResult *res,
+                     gpointer      user_data)
+{
+        g_autoptr(GVariant) result = NULL;
+        g_autoptr(GError) error = NULL;
+        const gchar *msg = user_data;
+
+        result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+                                           res,
+                                           &error);
+        if (result == NULL &&
+            !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))
+                g_warning ("%s: %s", msg, error->message);
+}
+
+static void
+add_usbguard_allow_rule (GsdUsbProtectionManager *manager)
+{
+        /* This appends a "allow all" rule.
+         * It has the purpose of ensuring the authorization of new devices when
+         * the lockscreen is off while respecting existing rules.
+         * We make it temporary, so that we are stateless and don't alter the
+         * existing (persistent) configuration.
+         */
+
+        GVariant *params;
+        GDBusProxy *policy_proxy = manager->usb_protection_policy;
+
+        if (policy_proxy == NULL) {
+            g_warning ("Cannot add allow rule, because dbus proxy is missing");
+        } else {
+                gboolean temporary = TRUE;
+                /* This is USBGuard's Rule::LastID */
+                const guint32 last_rule_id = G_MAXUINT32 - 2;
+                g_debug ("Adding rule %u", last_rule_id);
+                params = g_variant_new ("(sub)", ALLOW_ALL, last_rule_id, temporary);
+                g_dbus_proxy_call (policy_proxy,
+                                   APPEND_RULE,
+                                   params,
+                                   G_DBUS_CALL_FLAGS_NONE,
+                                   -1,
+                                   manager->cancellable,
+                                   dbus_call_log_error,
+                                   "Error appending USBGuard rule");
+        }
+}
+
+static gboolean
+is_usbguard_allow_rule_present (GVariant *rules)
+{
+        g_autoptr(GVariantIter) iter = NULL;
+        g_autofree gchar *value = NULL;
+        guint number = 0;
+
+        g_debug ("Detecting rule...");
+
+        g_variant_get (rules, "a(us)", &iter);
+        g_return_val_if_fail (iter != NULL, FALSE);
+        while (g_variant_iter_loop (iter, "(us)", &number, &value)) {
+                if (g_strcmp0 (value, ALLOW_ALL) == 0) {
+                        g_debug ("Detected rule!");
+                        return TRUE;
+                    }
+        }
+        g_debug ("Rule not present");
+        return FALSE;
+}
+
+static void
+usbguard_listrules_cb (GObject      *source_object,
+                       GAsyncResult *res,
+                       gpointer      user_data)
+{
+        GVariant *result, *rules;
+        g_autoptr(GError) error = NULL;
+
+        result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+                                           res,
+                                           &error);
+
+        if (!result) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                        g_warning ("Failed to fetch USBGuard rules list: %s", error->message);
+                }
+                return;
+        }
+
+        rules = g_variant_get_child_value (result, 0);
+        g_variant_unref (result);
+        g_return_if_fail (rules != NULL);
+        if (!is_usbguard_allow_rule_present (rules))
+                add_usbguard_allow_rule (user_data);
+
+}
+
+static void
+usbguard_ensure_allow_rule (GsdUsbProtectionManager *manager)
+{
+        GVariant *params;
+        GDBusProxy *policy_proxy = manager->usb_protection_policy;
+
+        if (policy_proxy == NULL) {
+            g_warning ("Cannot list rules, because dbus proxy is missing");
+        } else {
+                /* listRules parameter is a label for matching rules.
+                 * Currently we are using an empty label to get all the
+                 * rules instead of just using "GNOME_SETTINGS_DAEMON_RULE"
+                 * until this bug gets solved:
+                 * https://github.com/USBGuard/usbguard/issues/328 */
+                params = g_variant_new ("(s)", "");
+                g_dbus_proxy_call (policy_proxy,
+                                   LIST_RULES,
+                                   params,
+                                   G_DBUS_CALL_FLAGS_NONE,
+                                   -1,
+                                   manager->cancellable,
+                                   usbguard_listrules_cb,
+                                   manager);
+        }
+}
+
+static void
+settings_changed_callback (GSettings               *settings,
+                           const char              *key,
+                           GsdUsbProtectionManager *manager)
+{
+        gchar *value_usbguard;
+        gboolean usbguard_controlled;
+        GVariant *params;
+        GDesktopUsbProtection protection_level;
+
+        /* We react only if one of the two USB related properties has been changed */
+        if (g_strcmp0 (key, USB_PROTECTION) != 0 && g_strcmp0 (key, USB_PROTECTION_LEVEL) != 0)
+                return;
+
+        usbguard_controlled = g_settings_get_boolean (settings, USB_PROTECTION);
+        protection_level = g_settings_get_enum (settings, USB_PROTECTION_LEVEL);
+        g_debug ("USBGuard control is currently %i with a protection level of %i",
+                 usbguard_controlled, protection_level);
+
+        /* If previously we were controlling USBGuard and now we are not,
+         * we leave the USBGuard configuration in a clean state. I.e. we set
+         * "InsertedDevicePolicy" to "apply-policy" and we ensure that
+         * there is an always allow rule. In this way even if USBGuard daemon
+         * is running every USB devices will be automatically authorized. */
+        if (g_strcmp0 (key, USB_PROTECTION) == 0 && !usbguard_controlled) {
+                g_debug ("let's clean usbguard config state");
+                params = g_variant_new ("(ss)",
+                                        INSERTED_DEVICE_POLICY,
+                                        APPLY_POLICY);
+
+                if (manager->usb_protection != NULL) {
+                        g_dbus_proxy_call (manager->usb_protection,
+                                           "setParameter",
+                                           params,
+                                           G_DBUS_CALL_FLAGS_NONE,
+                                           -1,
+                                           manager->cancellable,
+                                           dbus_call_log_error,
+                                           "Error calling USBGuard DBus to set a clean configuration state");
+                }
+
+                usbguard_ensure_allow_rule (manager);
+        }
+
+        /* Only if we are entitled to handle USBGuard */
+        if (usbguard_controlled && manager->usb_protection != NULL) {
+                value_usbguard = (protection_level == G_DESKTOP_USB_PROTECTION_ALWAYS) ? BLOCK : APPLY_POLICY;
+                params = g_variant_new ("(ss)",
+                                        INSERTED_DEVICE_POLICY,
+                                        value_usbguard);
+
+                g_dbus_proxy_call (manager->usb_protection,
+                                   "setParameter",
+                                   params,
+                                   G_DBUS_CALL_FLAGS_NONE,
+                                   -1,
+                                   manager->cancellable,
+                                   dbus_call_log_error,
+                                   "Error calling USBGuard DBus to set the desidered protection level");
+
+                /* If we are in "When lockscreen is active" we also check if the
+                 * always allow rule is present. */
+                if (protection_level == G_DESKTOP_USB_PROTECTION_LOCKSCREEN)
+                        usbguard_ensure_allow_rule (manager);
+        }
+}
+
+static void update_usb_protection_store (GsdUsbProtectionManager *manager,
+                                         GVariant                *parameter)
+{
+        const gchar *key;
+        gboolean usbguard_controlled;
+        GDesktopUsbProtection protection_level;
+        GSettings *settings = manager->settings;
+
+        usbguard_controlled = g_settings_get_boolean (settings, USB_PROTECTION);
+        /* If we are not handling USBGuard configuration (e.g. the user is using
+         * a third party program) we do nothing when the config changes. */
+        if (usbguard_controlled) {
+                key = g_variant_get_string (parameter, NULL);
+                protection_level = g_settings_get_enum (settings, USB_PROTECTION_LEVEL);
+                /* If the USBGuard configuration has been changed and doesn't match
+                 * our internal state, most likely means that the user externally
+                 * changed it. When this happens we set to false the control value. */
+                if ((g_strcmp0 (key, APPLY_POLICY) == 0 && protection_level == G_DESKTOP_USB_PROTECTION_ALWAYS)) {
+                        g_settings_set (settings, USB_PROTECTION, "b", FALSE);
+                        g_warning ("We don't control anymore USBGuard because the configuration changed externally.");
+                }
+        }
+}
+
+static gboolean
+is_protection_active (GsdUsbProtectionManager *manager)
+{
+        GSettings *settings = manager->settings;
+
+        return g_settings_get_boolean (settings, USB_PROTECTION);
+}
+
+static void
+on_notification_closed (NotifyNotification      *n,
+                        GsdUsbProtectionManager *manager)
+{
+        g_debug ("Clearing notification");
+        g_clear_object (&manager->notification);
+}
+
+static void
+show_notification (GsdUsbProtectionManager *manager,
+                   const char              *summary,
+                   const char              *body)
+{
+        /* Don't show a notice if one is already displayed */
+        if (manager->notification != NULL) {
+                g_debug ("A notification already exists, we do not show a new one");
+                return;
+        }
+
+        manager->notification = notify_notification_new (summary, body, "drive-removable-media-symbolic");
+        notify_notification_set_app_name (manager->notification, _("USB Protection"));
+        notify_notification_set_hint (manager->notification, "transient", g_variant_new_boolean (TRUE));
+        notify_notification_set_hint_string (manager->notification, "x-gnome-privacy-scope", "system");
+        notify_notification_set_timeout (manager->notification, NOTIFY_EXPIRES_DEFAULT);
+        notify_notification_set_urgency (manager->notification, NOTIFY_URGENCY_CRITICAL);
+        g_signal_connect_object (manager->notification,
+                                 "closed",
+                                 G_CALLBACK (on_notification_closed),
+                                 manager,
+                                 0);
+        g_debug ("Showing notification for %s: %s", summary, body);
+        if (!notify_notification_show (manager->notification, NULL)) {
+                g_warning ("Failed to send USB protection notification");
+                g_clear_object (&manager->notification);
+        }
+}
+
+static void call_usbguard_dbus (GDBusProxy              *proxy,
+                                GsdUsbProtectionManager *manager,
+                                guint                    device_id,
+                                guint                    target,
+                                gboolean                 permanent)
+{
+        if (manager->usb_protection_devices == NULL) {
+            g_warning("Could not call USBGuard, because DBus is missing");
+        } else {
+            g_debug ("Calling applyDevicePolicy with device_id %u, target %u and permanent: %i", device_id, target, permanent);
+            GVariant *params = g_variant_new ("(uub)", device_id, target, permanent);
+            g_dbus_proxy_call (manager->usb_protection_devices,
+                               APPLY_DEVICE_POLICY,
+                               params,
+                               G_DBUS_CALL_FLAGS_NONE,
+                               -1,
+                               manager->cancellable,
+                               dbus_call_log_error,
+                               "Error calling USBGuard DBus to authorize a device");
+        }
+}
+
+static gboolean
+is_hid_or_hub (GVariant *device,
+               gboolean *has_other_classes)
+{
+        g_autoptr(GVariantIter) iter = NULL;
+        g_autofree gchar *name = NULL;
+        g_autofree gchar *value = NULL;
+        guint i;
+        gboolean is_hid_or_hub = FALSE;
+
+        if (has_other_classes != NULL) {
+                *has_other_classes = FALSE;
+        }
+
+        g_variant_get_child (device, PRESENCE_ATTRIBUTES, "a{ss}", &iter);
+        g_return_val_if_fail (iter != NULL, FALSE);
+        while (g_variant_iter_loop (iter, "{ss}", &name, &value)) {
+                if (g_strcmp0 (name, WITH_INTERFACE) == 0) {
+                        g_auto(GStrv) interfaces_splitted = NULL;
+                        interfaces_splitted = g_strsplit (value, " ", -1);
+                        for (i = 0; i < g_strv_length (interfaces_splitted); i++) {
+                                if (g_str_has_prefix (interfaces_splitted[i], "03:")
+                                    || g_str_has_prefix (interfaces_splitted[i], "09:")) {
+                                        is_hid_or_hub = TRUE;
+                                    }
+                                else if (has_other_classes != NULL) {
+                                        *has_other_classes = TRUE;
+                                }
+                        }
+                }
+        }
+        return is_hid_or_hub;
+}
+
+static gboolean
+is_hardwired (GVariant *device)
+{
+        g_autoptr(GVariantIter) iter = NULL;
+        g_autofree gchar *name = NULL;
+        g_autofree gchar *value = NULL;
+
+        g_variant_get_child (device, PRESENCE_ATTRIBUTES, "a{ss}", &iter);
+        g_return_val_if_fail (iter != NULL, FALSE);
+        while (g_variant_iter_loop (iter, "{ss}", &name, &value)) {
+                if (g_strcmp0 (name, WITH_CONNECT_TYPE) == 0) {
+                        return g_strcmp0 (value, "hardwired") == 0;
+                }
+        }
+        return FALSE;
+}
+
+static void
+authorize_device (GsdUsbProtectionManager *manager,
+                  guint device_id)
+{
+        g_return_if_fail (manager->usb_protection_devices != NULL);
+
+        g_debug ("Authorizing device %u", device_id);
+        call_usbguard_dbus(manager->usb_protection_devices,
+                         manager,
+                         device_id,
+                         TARGET_ALLOW,
+                         FALSE);
+}
+
+static void
+on_screen_locked (GsdScreenSaver          *screen_saver,
+                  GAsyncResult            *result,
+                  GsdUsbProtectionManager *manager)
+{
+        guint device_id;
+        g_autoptr(GError) error = NULL;
+
+        gsd_screen_saver_call_lock_finish (screen_saver, result, &error);
+
+        if (error) {
+                if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        return;
+                g_warning ("Couldn't lock screen: %s", error->message);
+        }
+
+        device_id = manager->last_device_id;
+        authorize_device (manager, device_id);
+        manager->last_device_id = G_MAXUINT;
+        show_notification (manager,
+                           _("New USB device"),
+                           _("New device has been detected while the session was not locked. "
+                             "If you did not plug anything, check your system for any suspicious device."));
+
+}
+
+static void
+on_usbguard_signal (GDBusProxy *proxy,
+                           gchar      *sender_name,
+                           gchar      *signal_name,
+                           GVariant   *parameters,
+                           gpointer    user_data)
+{
+        UsbGuardTarget target = TARGET_BLOCK;
+        UsbGuardEvent device_event;
+        GDesktopUsbProtection protection_level;
+        GsdUsbProtectionManager *manager = user_data;
+        g_autoptr(GVariantIter) iter = NULL;
+        g_autofree gchar *name = NULL;
+        g_autofree gchar *device_name = NULL;
+        gboolean hid_or_hub = FALSE;
+        gboolean has_other_classes = FALSE;
+
+        g_debug ("USBGuard signal: %s", signal_name);
+
+        /* We act only if we receive a signal indicating that a device has been inserted */
+        if (g_strcmp0 (signal_name, DEVICE_PRESENCE_CHANGED) != 0) {
+                return;
+        }
+
+        g_return_if_fail (g_variant_n_children (parameters) >= PRESENCE_EVENT);
+        g_variant_get_child (parameters, PRESENCE_EVENT, "u", &device_event);
+        if (device_event != EVENT_INSERT) {
+            g_debug ("Device hat not been inserted (%d); ignoring", device_event);
+            return;
+        }
+
+        /* We would like to show a notification for an inserted device that
+         * *has not been blocked*.  But USBGuard is not providing that information.
+         * So we have to work around that limitation and assume that any device plugged in
+         * during screensaver shall be blocked.
+         * https://github.com/USBGuard/usbguard/issues/353
+         
+           g_variant_get_child (parameters, POLICY_TARGET_NEW, "u", &target);
+        */
+
+        /* If the device is already authorized we do nothing */
+        if (target == TARGET_ALLOW) {
+                g_debug ("Device will be allowed, we return");
+                return;
+        }
+
+        /* If the USB protection is disabled we do nothing */
+        if (!is_protection_active (manager)) {
+                g_debug ("Protection is not active. Not acting on the device");
+                return;
+        }
+
+        g_variant_get_child (parameters, PRESENCE_ATTRIBUTES, "a{ss}", &iter);
+        g_return_if_fail (iter != NULL);
+        while (g_variant_iter_loop (iter, "{ss}", &name, &device_name)) {
+                if (g_strcmp0 (name, NAME) == 0)
+                        g_debug ("A new USB device has been connected: %s", device_name);
+        }
+
+        if (is_hardwired (parameters)) {
+            guint device_id;
+            g_debug ("Device is hardwired, allowing it to be connected");
+            g_variant_get_child (parameters, POLICY_DEVICE_ID, "u", &device_id);
+            authorize_device (manager, device_id);
+            return;
+        }
+
+        protection_level = g_settings_get_enum (manager->settings, USB_PROTECTION_LEVEL);
+
+        g_debug ("Screensaver active: %d", manager->screensaver_active);
+        hid_or_hub = is_hid_or_hub (parameters, &has_other_classes);
+        if (manager->screensaver_active) {
+                /* If the session is locked we check if the inserted device is a HID,
+                 * e.g. a keyboard or a mouse, or an HUB.
+                 * If that is the case we authorize the newly inserted device as an
+                 * antilockout policy.
+                 *
+                 * If this device advertises also interfaces outside the HID class, or the
+                 * HUB class, it is suspect. It could be a false positive because this could
+                 * be a "smart" keyboard for example, but at this stage is better be safe. */
+                if (hid_or_hub && !has_other_classes) {
+                        guint device_id;
+                        show_notification (manager,
+                                           _("New device detected"),
+                                           _("Either one of your existing devices has been reconnected or a new one has been plugged in. "
+                                             "If you did not do it, check your system for any suspicious device."));
+                        g_variant_get_child (parameters, POLICY_DEVICE_ID, "u", &device_id);
+                        authorize_device (manager, device_id);
+                } else {
+                    if (protection_level == G_DESKTOP_USB_PROTECTION_LOCKSCREEN) {
+                            show_notification (manager,
+                                               _("Reconnect USB device"),
+                                               _("New device has been detected while you were away. "
+                                                 "Please disconnect and reconnect the device to start using it."));
+                    } else {
+                            const char* name_for_notification = device_name ? device_name : "unknown name";
+                            g_debug ("Showing notification for %s", name_for_notification);
+                            show_notification (manager,
+                                               _("USB device blocked"),
+                                               _("New device has been detected while you were away. "
+                                                 "It has been blocked because the USB protection is active."));
+                    }
+                }
+        } else {
+                /* If the protection level is "lockscreen" the device will be automatically
+                 * authorized by usbguard. */
+                if (protection_level == G_DESKTOP_USB_PROTECTION_ALWAYS) {
+                        /* We authorize the device if this is a HID,
+                         * e.g. a keyboard or a mouse, or an HUB.
+                         * We also lock the screen to prevent an attacker to plug malicious
+                         * devices if the legitimate user forgot to lock his session.
+                         *
+                         * If this device advertises also interfaces outside the HID class, or the
+                         * HUB class, it is suspect. It could be a false positive because this could
+                         * be a "smart" keyboard for example, but at this stage is better be safe. */
+                        if (hid_or_hub && !has_other_classes) {
+                                g_variant_get_child (parameters, POLICY_DEVICE_ID, "u", &(manager->last_device_id));
+                                gsd_screen_saver_call_lock (manager->screensaver_proxy,
+                                                            manager->cancellable,
+                                                            (GAsyncReadyCallback) on_screen_locked,
+                                                            manager);
+                        } else {
+                                show_notification (manager,
+                                                   _("USB device blocked"),
+                                                   _("The new inserted device has been blocked because the USB protection is active."));
+                        }
+                }
+            }
+}
+
+static void
+on_usb_protection_signal (GDBusProxy *proxy,
+                          gchar      *sender_name,
+                          gchar      *signal_name,
+                          GVariant   *parameters,
+                          gpointer    user_data)
+{
+        g_autoptr(GVariant) parameter = NULL;
+        g_autofree gchar *policy_name = NULL;
+
+        if (g_strcmp0 (signal_name, "PropertyParameterChanged") != 0)
+                return;
+
+        g_variant_get_child (parameters, 0, "s", &policy_name);
+
+        /* Right now we just care about the InsertedDevicePolicy value */
+        if (g_strcmp0 (policy_name, INSERTED_DEVICE_POLICY) != 0)
+                return;
+
+        parameter = g_variant_get_child_value (parameters, 2);
+        g_return_if_fail (parameter != NULL);
+        update_usb_protection_store (user_data, parameter);
+
+}
+
+static void
+get_parameter_cb (GObject      *source_object,
+                  GAsyncResult *res,
+                  gpointer      user_data)
+{
+        GVariant *result;
+        GVariant *params = NULL;
+        g_autofree gchar *key = NULL;
+        GDesktopUsbProtection protection_level;
+        GsdUsbProtectionManager *manager;
+        GSettings *settings;
+        g_autoptr(GError) error = NULL;
+
+        result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+                                           res,
+                                           &error);
+        if (result == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                        g_warning ("Failed to fetch USBGuard parameters: %s", error->message);
+                }
+                return;
+        }
+
+        manager = GSD_USB_PROTECTION_MANAGER (user_data);
+        settings = manager->settings;
+
+        g_variant_get_child (result, 0, "s", &key);
+        g_variant_unref (result);
+        protection_level = g_settings_get_enum (settings, USB_PROTECTION_LEVEL);
+
+        g_debug ("InsertedDevicePolicy is: %s", key);
+
+        if (protection_level == G_DESKTOP_USB_PROTECTION_LOCKSCREEN) {
+                if (g_strcmp0 (key, APPLY_POLICY) != 0) {
+                        /* We are out of sync. */
+                        params = g_variant_new ("(ss)",
+                                                INSERTED_DEVICE_POLICY,
+                                                APPLY_POLICY);
+                }
+        } else if (protection_level == G_DESKTOP_USB_PROTECTION_ALWAYS) {
+                if (g_strcmp0 (key, BLOCK) != 0) {
+                        /* We are out of sync. */
+                        params = g_variant_new ("(ss)",
+                                                INSERTED_DEVICE_POLICY,
+                                                BLOCK);
+                }
+        }
+
+        if (params != NULL) {
+                /* We are out of sync. We need to call setParameter to update USBGuard state */
+                if (manager->usb_protection != NULL) {
+                        g_debug ("Setting InsertedDevicePolicy");
+                        g_dbus_proxy_call (manager->usb_protection,
+                                           "setParameter",
+                                           params,
+                                           G_DBUS_CALL_FLAGS_NONE,
+                                           -1,
+                                           manager->cancellable,
+                                           dbus_call_log_error,
+                                           "Error calling USBGuard DBus while we were out of sync");
+                }
+
+        }
+
+        /* If we are in "When lockscreen is active" we also check
+         * if the "always allow" rule is present. */
+        if (protection_level == G_DESKTOP_USB_PROTECTION_LOCKSCREEN) {
+                g_debug ("Ensuring allow all");
+                usbguard_ensure_allow_rule (manager);
+        }
+}
+
+static void
+sync_usb_protection (GDBusProxy              *proxy,
+                     GsdUsbProtectionManager *manager)
+{
+        GVariant *params;
+        gboolean usbguard_controlled;
+        GSettings *settings = manager->settings;
+
+        usbguard_controlled = g_settings_get_boolean (settings, USB_PROTECTION);
+
+        g_debug ("Attempting to sync USB parameters: %d %p %p",
+            usbguard_controlled, proxy, manager->usb_protection);
+
+        if (!usbguard_controlled || manager->usb_protection == NULL)
+                return;
+
+        params = g_variant_new ("(s)", INSERTED_DEVICE_POLICY);
+        g_dbus_proxy_call (manager->usb_protection,
+                           "getParameter",
+                           params,
+                           G_DBUS_CALL_FLAGS_NONE,
+                           -1,
+                           manager->cancellable,
+                           get_parameter_cb,
+                           manager);
+}
+
+static void
+usb_protection_properties_changed (GsdUsbProtectionManager *manager)
+{
+        GVariantBuilder props_builder;
+        GVariant *props_changed = NULL;
+
+        /* not yet connected to the session bus */
+        if (manager->connection == NULL)
+                return;
+
+        g_variant_builder_init (&props_builder, G_VARIANT_TYPE ("a{sv}"));
+
+        g_variant_builder_add (&props_builder, "{sv}", "Available",
+                               g_variant_new_boolean (manager->available));
+
+        props_changed = g_variant_new ("(s@a{sv}@as)", GSD_USB_PROTECTION_DBUS_NAME,
+                                       g_variant_builder_end (&props_builder),
+                                       g_variant_new_strv (NULL, 0));
+
+        g_dbus_connection_emit_signal (manager->connection,
+                                       NULL,
+                                       GSD_USB_PROTECTION_DBUS_PATH,
+                                       "org.freedesktop.DBus.Properties",
+                                       "PropertiesChanged",
+                                       props_changed, NULL);
+}
+
+static void
+on_usb_protection_owner_changed_cb (GObject    *object,
+                                    GParamSpec *pspec,
+                                    gpointer    user_data)
+{
+        GsdUsbProtectionManager *manager = user_data;
+        GDBusProxy *proxy = G_DBUS_PROXY(object);
+        g_autofree gchar *name_owner = NULL;
+
+        name_owner = g_dbus_proxy_get_name_owner (proxy);
+        g_debug ("Got owner change: %s", name_owner);
+
+        if (name_owner) {
+                manager->available = TRUE;
+        } else {
+                manager->available = FALSE;
+        }
+
+        usb_protection_properties_changed (manager);
+}
+
+static void
+handle_screensaver_active (GsdUsbProtectionManager *manager,
+                           GVariant                *parameters)
+{
+        gboolean active;
+        gchar *value_usbguard;
+        gboolean usbguard_controlled;
+        GVariant *params;
+        GDesktopUsbProtection protection_level;
+        GSettings *settings = manager->settings;
+
+        usbguard_controlled = g_settings_get_boolean (settings, USB_PROTECTION);
+        protection_level = g_settings_get_enum (settings, USB_PROTECTION_LEVEL);
+
+        g_variant_get (parameters, "(b)", &active);
+        g_debug ("Received screensaver ActiveChanged signal: %d (old: %d)", active, manager->screensaver_active);
+        if (manager->screensaver_active != active) {
+                manager->screensaver_active = active;
+                if (usbguard_controlled && protection_level == G_DESKTOP_USB_PROTECTION_LOCKSCREEN) {
+                        /* If we are in the "lockscreen protection" level we change
+                         * the usbguard config with apply-policy or block if the session
+                         * is unlocked or locked, respectively. */
+                        value_usbguard = active ? BLOCK : APPLY_POLICY;
+                        params = g_variant_new ("(ss)",
+                                                INSERTED_DEVICE_POLICY,
+                                                value_usbguard);
+                        if (manager->usb_protection != NULL) {
+                                g_dbus_proxy_call (manager->usb_protection,
+                                                   "setParameter",
+                                                   params,
+                                                   G_DBUS_CALL_FLAGS_NONE,
+                                                   -1,
+                                                   manager->cancellable,
+                                                   dbus_call_log_error,
+                                                   "Error calling USBGuard DBus to change the protection after a screensaver event");
+                        }
+                }
+        }
+}
+
+static void
+screensaver_signal_cb (GDBusProxy  *proxy,
+                       const gchar *sender_name,
+                       const gchar *signal_name,
+                       GVariant    *parameters,
+                       gpointer     user_data)
+{
+        g_debug ("ScreenSaver Signal: %s", signal_name);
+        if (g_strcmp0 (signal_name, "ActiveChanged") == 0)
+                handle_screensaver_active (GSD_USB_PROTECTION_MANAGER (user_data), parameters);
+}
+
+static void
+usb_protection_policy_proxy_ready (GObject      *source_object,
+                                   GAsyncResult *res,
+                                   gpointer      user_data)
+{
+        GsdUsbProtectionManager *manager;
+        GDBusProxy *proxy;
+        g_autoptr(GError) error = NULL;
+        g_debug ("usb_protection_policy_proxy_ready");
+
+        proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+        if (!proxy) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to contact USBGuard: %s", error->message);
+                return;
+        } else {
+            manager = GSD_USB_PROTECTION_MANAGER (user_data);
+            manager->usb_protection_policy = proxy;
+            g_debug ("Set protection policy proxy to %p", proxy);
+            sync_usb_protection (proxy, manager);
+        }
+}
+
+static void
+usb_protection_devices_proxy_ready (GObject      *source_object,
+                                    GAsyncResult *res,
+                                    gpointer      user_data)
+{
+        GsdUsbProtectionManager *manager;
+        GDBusProxy *proxy;
+        g_autoptr(GError) error = NULL;
+
+        proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+        if (!proxy) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to contact USBGuard: %s", error->message);
+                return;
+        }
+        manager = GSD_USB_PROTECTION_MANAGER (user_data);
+        manager->usb_protection_devices = proxy;
+
+        /* We don't care about already plugged in devices because they'll be
+         * already autorized by the "allow all" rule in USBGuard. */
+        g_debug ("Listening to signals");
+        g_signal_connect_object (source_object,
+                                 "g-signal",
+                                 G_CALLBACK (on_usbguard_signal),
+                                 user_data,
+                                 0);
+}
+
+static void
+get_current_screen_saver_status (GsdUsbProtectionManager *manager)
+{
+        g_autoptr(GVariant) ret = NULL;
+        g_autoptr(GError) error = NULL;
+
+        ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (manager->screensaver_proxy),
+                                      "GetActive",
+                                      NULL,
+                                      G_DBUS_CALL_FLAGS_NONE,
+                                      -1,
+                                      manager->cancellable,
+                                      &error);
+        if (ret == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to get screen saver status: %s", error->message);
+                return;
+        }
+        handle_screensaver_active (manager, ret);
+}
+
+static void
+usb_protection_proxy_ready (GObject      *source_object,
+                            GAsyncResult *res,
+                            gpointer      user_data)
+{
+        GsdUsbProtectionManager *manager;
+        GDBusProxy *proxy;
+        g_autofree gchar *name_owner = NULL;
+        g_autoptr(GError) error = NULL;
+
+        proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+        if (!proxy) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to contact USBGuard: %s", error->message);
+                return;
+        }
+        manager = GSD_USB_PROTECTION_MANAGER (user_data);
+        manager->usb_protection = proxy;
+
+        g_signal_connect (G_OBJECT (manager->settings), "changed",
+                          G_CALLBACK (settings_changed_callback), manager);
+
+        manager->screensaver_proxy = gnome_settings_bus_get_screen_saver_proxy ();
+        if (!manager->screensaver_proxy) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to connect to screensaver service: %s", error->message);
+                g_clear_object (&manager->usb_protection);
+                return;
+        }
+
+        get_current_screen_saver_status (manager);
+
+        g_signal_connect (manager->screensaver_proxy, "g-signal",
+                          G_CALLBACK (screensaver_signal_cb), manager);
+
+        name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy));
+
+        if (name_owner == NULL) {
+                g_debug("Probably USBGuard >= 0.7.5 is not currently installed.");
+                manager->available = FALSE;
+        } else {
+                manager->available = TRUE;
+        }
+
+        g_signal_connect_object (source_object,
+                                 "notify::g-name-owner",
+                                 G_CALLBACK (on_usb_protection_owner_changed_cb),
+                                 user_data,
+                                 0);
+
+        g_signal_connect_object (source_object,
+                                 "g-signal",
+                                 G_CALLBACK (on_usb_protection_signal),
+                                 user_data,
+                                 0);
+
+        g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+                                  G_DBUS_PROXY_FLAGS_NONE,
+                                  NULL,
+                                  USBGUARD_DBUS_NAME,
+                                  USBGUARD_DBUS_PATH_DEVICES,
+                                  USBGUARD_DBUS_INTERFACE_DEVICES,
+                                  manager->cancellable,
+                                  usb_protection_devices_proxy_ready,
+                                  manager);
+
+        g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+                                  G_DBUS_PROXY_FLAGS_NONE,
+                                  NULL,
+                                  USBGUARD_DBUS_NAME,
+                                  USBGUARD_DBUS_PATH_POLICY,
+                                  USBGUARD_DBUS_INTERFACE_POLICY,
+                                  manager->cancellable,
+                                  usb_protection_policy_proxy_ready,
+                                  manager);
+}
+
+static GVariant *
+handle_get_property (GDBusConnection *connection,
+                     const gchar     *sender,
+                     const gchar     *object_path,
+                     const gchar     *interface_name,
+                     const gchar     *property_name,
+                     GError         **error,
+                     gpointer         user_data)
+{
+        GsdUsbProtectionManager *manager = GSD_USB_PROTECTION_MANAGER (user_data);
+
+        /* Check session pointer as a proxy for whether the manager is in the
+           start or stop state */
+        if (manager->connection == NULL)
+                return NULL;
+
+        if (g_strcmp0 (property_name, "Available") == 0)
+                return g_variant_new_boolean (manager->available);
+
+        return NULL;
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+        NULL,
+        handle_get_property,
+        NULL
+};
+
+static void
+on_bus_gotten (GObject                 *source_object,
+               GAsyncResult            *res,
+               GsdUsbProtectionManager *manager)
+{
+        GDBusConnection *connection;
+        GError *error = NULL;
+
+        connection = g_bus_get_finish (res, &error);
+        if (connection == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Could not get session bus: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+        manager->connection = connection;
+
+        g_dbus_connection_register_object (connection,
+                                           GSD_USB_PROTECTION_DBUS_PATH,
+                                           manager->introspection_data->interfaces[0],
+                                           &interface_vtable,
+                                           manager,
+                                           NULL,
+                                           NULL);
+
+        manager->name_id = g_bus_own_name_on_connection (connection,
+                                                         GSD_USB_PROTECTION_DBUS_NAME,
+                                                         G_BUS_NAME_OWNER_FLAGS_NONE,
+                                                         NULL,
+                                                         NULL,
+                                                         NULL,
+                                                         NULL);
+}
+
+static gboolean
+start_usb_protection_idle_cb (GsdUsbProtectionManager *manager)
+{
+        g_debug ("Starting USB protection manager");
+
+        manager->settings = g_settings_new (PRIVACY_SETTINGS);
+        manager->cancellable = g_cancellable_new ();
+
+        g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+                                  G_DBUS_PROXY_FLAGS_NONE,
+                                  NULL,
+                                  USBGUARD_DBUS_NAME,
+                                  USBGUARD_DBUS_PATH,
+                                  USBGUARD_DBUS_INTERFACE_VERSIONED,
+                                  manager->cancellable,
+                                  usb_protection_proxy_ready,
+                                  manager);
+
+        notify_init ("gnome-settings-daemon");
+
+        manager->start_idle_id = 0;
+
+        return FALSE;
+}
+
+gboolean
+gsd_usb_protection_manager_start (GsdUsbProtectionManager *manager,
+                                  GError                 **error)
+{
+        gnome_settings_profile_start (NULL);
+
+        manager->start_idle_id = g_idle_add ((GSourceFunc) start_usb_protection_idle_cb, manager);
+        g_source_set_name_by_id (manager->start_idle_id, "[gnome-settings-daemon] start_usbguard_idle_cb");
+
+        manager->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+        g_assert (manager->introspection_data != NULL);
+
+        /* Start process of owning a D-Bus name */
+        g_bus_get (G_BUS_TYPE_SESSION,
+                   manager->cancellable,
+                   (GAsyncReadyCallback) on_bus_gotten,
+                   manager);
+
+        gnome_settings_profile_end (NULL);
+        return TRUE;
+}
+
+void
+gsd_usb_protection_manager_stop (GsdUsbProtectionManager *manager)
+{
+        g_debug ("Stopping USB protection manager");
+
+        if (manager->cancellable != NULL) {
+                g_cancellable_cancel (manager->cancellable);
+                g_clear_object (&manager->cancellable);
+        }
+
+        g_clear_object (&manager->notification);
+
+        if (manager->start_idle_id != 0) {
+                g_source_remove (manager->start_idle_id);
+                manager->start_idle_id = 0;
+        }
+
+        if (manager->name_id != 0) {
+                g_bus_unown_name (manager->name_id);
+                manager->name_id = 0;
+        }
+
+        g_clear_pointer (&manager->introspection_data, g_dbus_node_info_unref);
+        g_clear_object (&manager->connection);
+        g_clear_object (&manager->settings);
+        g_clear_object (&manager->usb_protection);
+        g_clear_object (&manager->usb_protection_devices);
+        g_clear_object (&manager->usb_protection_policy);
+        g_clear_object (&manager->screensaver_proxy);
+}
+
+static void
+gsd_usb_protection_manager_class_init (GsdUsbProtectionManagerClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_usb_protection_manager_finalize;
+}
+
+static void
+gsd_usb_protection_manager_init (GsdUsbProtectionManager *manager)
+{
+}
+
+static void
+gsd_usb_protection_manager_finalize (GObject *object)
+{
+        GsdUsbProtectionManager *usb_protection_manager;
+
+        usb_protection_manager = GSD_USB_PROTECTION_MANAGER (object);
+        gsd_usb_protection_manager_stop (usb_protection_manager);
+
+        G_OBJECT_CLASS (gsd_usb_protection_manager_parent_class)->finalize (object);
+}
+
+GsdUsbProtectionManager *
+gsd_usb_protection_manager_new (void)
+{
+        g_debug ("Starting USB Protection");
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_USB_PROTECTION_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+        }
+
+        return GSD_USB_PROTECTION_MANAGER (manager_object);
+}
diff --git a/plugins/usb-protection/gsd-usb-protection-manager.h b/plugins/usb-protection/gsd-usb-protection-manager.h
new file mode 100644
index 0000000..8b2bbd7
--- /dev/null
+++ b/plugins/usb-protection/gsd-usb-protection-manager.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2018 Ludovico de Nittis 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_USB_PROTECTION_MANAGER_H
+#define __GSD_USB_PROTECTION_MANAGER_H
+
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_USB_PROTECTION_MANAGER         (gsd_usb_protection_manager_get_type ())
+G_DECLARE_FINAL_TYPE (GsdUsbProtectionManager, gsd_usb_protection_manager, GSD, USB_PROTECTION_MANAGER, GObject);
+
+typedef struct
+{
+        GObjectClass   parent_class;
+} _GsdUsbProtectionManagerClass;
+
+GType                       gsd_usb_protection_manager_get_type        (void);
+
+GsdUsbProtectionManager *   gsd_usb_protection_manager_new             (void);
+gboolean                    gsd_usb_protection_manager_start           (GsdUsbProtectionManager *manager,
+                                                                        GError                 **error);
+void                        gsd_usb_protection_manager_stop            (GsdUsbProtectionManager *manager);
+
+G_END_DECLS
+
+#endif /* __GSD_USB_PROTECTION_MANAGER_H */
diff --git a/plugins/usb-protection/main.c b/plugins/usb-protection/main.c
new file mode 100644
index 0000000..fd453bc
--- /dev/null
+++ b/plugins/usb-protection/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_usb_protection_manager_new
+#define START gsd_usb_protection_manager_start
+#define STOP gsd_usb_protection_manager_stop
+#define MANAGER GsdUsbProtectionManager
+#include "gsd-usb-protection-manager.h"
+
+#include "daemon-skeleton.h"
diff --git a/plugins/usb-protection/meson.build b/plugins/usb-protection/meson.build
new file mode 100644
index 0000000..1eed2fb
--- /dev/null
+++ b/plugins/usb-protection/meson.build
@@ -0,0 +1,21 @@
+sources = files(
+  'gsd-usb-protection-manager.c',
+  'main.c'
+)
+
+deps = plugins_deps + [
+  gsettings_desktop_dep,
+  libcommon_dep,
+  libnotify_dep,
+]
+
+executable(
+  'gsd-' + plugin_name,
+  sources,
+  include_directories: [top_inc, common_inc, data_inc],
+  dependencies: deps,
+  c_args: cflags,
+  install: true,
+  install_rpath: gsd_pkglibdir,
+  install_dir: gsd_libexecdir
+)
diff --git a/plugins/wacom/gsd-wacom-manager.c b/plugins/wacom/gsd-wacom-manager.c
new file mode 100644
index 0000000..0364063
--- /dev/null
+++ b/plugins/wacom/gsd-wacom-manager.c
@@ -0,0 +1,528 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann 
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+
+#ifdef GDK_WINDOWING_WAYLAND
+#include 
+#endif
+#ifdef GDK_WINDOWING_X11
+#include 
+#endif
+
+#if HAVE_WACOM
+#include 
+#endif
+
+#include "gsd-enums.h"
+#include "gnome-settings-profile.h"
+#include "gnome-settings-bus.h"
+#include "gsd-wacom-manager.h"
+#include "gsd-wacom-oled.h"
+#include "gsd-settings-migrate.h"
+#include "gsd-input-helper.h"
+
+
+#define UNKNOWN_DEVICE_NOTIFICATION_TIMEOUT 15000
+
+#define GSD_DBUS_NAME "org.gnome.SettingsDaemon"
+#define GSD_DBUS_PATH "/org/gnome/SettingsDaemon"
+#define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon"
+
+#define GSD_WACOM_DBUS_PATH GSD_DBUS_PATH "/Wacom"
+#define GSD_WACOM_DBUS_NAME GSD_DBUS_NAME ".Wacom"
+
+#define LEFT_HANDED_KEY		"left-handed"
+
+static const gchar introspection_xml[] =
+""
+"  "
+"    "
+"      "
+"      "
+"    "
+"  "
+"";
+
+struct _GsdWacomManager
+{
+        GObject parent;
+
+        guint start_idle_id;
+        GdkSeat *seat;
+        guint device_added_id;
+
+        GsdShell *shell_proxy;
+
+        gchar *machine_id;
+
+#if HAVE_WACOM
+        WacomDeviceDatabase *wacom_db;
+#endif
+
+        /* DBus */
+        GDBusNodeInfo   *introspection_data;
+        GDBusConnection *dbus_connection;
+        GCancellable    *dbus_cancellable;
+        guint            dbus_register_object_id;
+        guint            name_id;
+};
+
+static void     gsd_wacom_manager_class_init  (GsdWacomManagerClass *klass);
+static void     gsd_wacom_manager_init        (GsdWacomManager      *wacom_manager);
+static void     gsd_wacom_manager_finalize    (GObject              *object);
+
+static gboolean is_opaque_tablet (GsdWacomManager *manager,
+                                  GdkDevice       *device);
+
+G_DEFINE_TYPE (GsdWacomManager, gsd_wacom_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+static GVariant *
+map_tablet_mapping (GVariant *value, GVariant *old_default, GVariant *new_default)
+{
+        const gchar *mapping;
+
+        mapping = g_variant_get_boolean (value) ? "absolute" : "relative";
+        return g_variant_new_string (mapping);
+}
+
+static GVariant *
+map_tablet_left_handed (GVariant *value, GVariant *old_default, GVariant *new_default)
+{
+        const gchar *rotation = g_variant_get_string (value, NULL);
+        return g_variant_new_boolean (g_strcmp0 (rotation, "half") == 0 ||
+                                      g_strcmp0 (rotation, "ccw") == 0);
+}
+
+static void
+migrate_tablet_settings (GsdWacomManager *manager,
+                         GdkDevice       *device)
+{
+        GsdSettingsMigrateEntry tablet_settings[] = {
+                { "is-absolute", "mapping", map_tablet_mapping },
+                { "keep-aspect", "keep-aspect", NULL },
+                { "rotation", "left-handed", map_tablet_left_handed },
+        };
+        gchar *old_path, *new_path;
+        const gchar *vendor, *product;
+
+        vendor = gdk_device_get_vendor_id (device);
+        product = gdk_device_get_product_id (device);
+
+        old_path = g_strdup_printf ("/org/gnome/settings-daemon/peripherals/wacom/%s-usb:%s:%s/",
+                                    manager->machine_id, vendor, product);
+        new_path = g_strdup_printf ("/org/gnome/desktop/peripherals/tablets/%s:%s/",
+                                    vendor, product);
+
+        gsd_settings_migrate_check ("org.gnome.settings-daemon.peripherals.wacom.deprecated",
+                                    old_path,
+                                    "org.gnome.desktop.peripherals.tablet",
+                                    new_path,
+                                    tablet_settings, G_N_ELEMENTS (tablet_settings));
+
+        /* Opaque tablets' mapping may be modified by users, so only these
+         * need migration of settings.
+         */
+        if (is_opaque_tablet (manager, device)) {
+                GsdSettingsMigrateEntry display_setting[] = {
+                        { "display", "output", NULL },
+                };
+
+                gsd_settings_migrate_check ("org.gnome.desktop.peripherals.tablet.deprecated",
+                                            new_path,
+                                            "org.gnome.desktop.peripherals.tablet",
+                                            new_path,
+                                            display_setting, G_N_ELEMENTS (display_setting));
+        }
+
+        g_free (old_path);
+        g_free (new_path);
+}
+
+static void
+gsd_wacom_manager_class_init (GsdWacomManagerClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_wacom_manager_finalize;
+}
+
+static gchar *
+get_device_path (GdkDevice *device)
+{
+#if HAVE_WAYLAND
+        if (gnome_settings_is_wayland ())
+                return g_strdup (gdk_wayland_device_get_node_path (device));
+        else
+#endif
+                return xdevice_get_device_node (gdk_x11_device_get_id (device));
+}
+
+static gboolean
+is_opaque_tablet (GsdWacomManager *manager,
+                  GdkDevice       *device)
+{
+        gboolean is_opaque = FALSE;
+#if HAVE_WACOM
+        WacomDevice *wacom_device;
+        gchar *devpath;
+
+        devpath = get_device_path (device);
+        wacom_device = libwacom_new_from_path (manager->wacom_db, devpath,
+                                               WFALLBACK_GENERIC, NULL);
+        if (wacom_device) {
+                WacomIntegrationFlags integration_flags;
+
+                integration_flags = libwacom_get_integration_flags (wacom_device);
+                is_opaque = (integration_flags &
+                             (WACOM_DEVICE_INTEGRATED_DISPLAY | WACOM_DEVICE_INTEGRATED_SYSTEM)) == 0;
+                libwacom_destroy (wacom_device);
+        }
+
+#endif
+        return is_opaque;
+}
+
+static GdkDevice *
+lookup_device_by_path (GsdWacomManager *manager,
+                       const gchar     *path)
+{
+        GList *devices, *l;
+
+        devices = gdk_seat_get_slaves (manager->seat,
+                                       GDK_SEAT_CAPABILITY_ALL);
+
+        for (l = devices; l; l = l->next) {
+                GdkDevice *device = l->data;
+                gchar *dev_path = get_device_path (device);
+
+                if (g_strcmp0 (dev_path, path) == 0) {
+                        g_free (dev_path);
+                        return device;
+                }
+
+                g_free (dev_path);
+        }
+
+        g_list_free (devices);
+
+        return NULL;
+}
+
+static GSettings *
+device_get_settings (GdkDevice *device)
+{
+        GSettings *settings;
+        gchar *path;
+
+        path = g_strdup_printf ("/org/gnome/desktop/peripherals/tablets/%s:%s/",
+                                gdk_device_get_vendor_id (device),
+                                gdk_device_get_product_id (device));
+        settings = g_settings_new_with_path ("org.gnome.desktop.peripherals.tablet",
+                                             path);
+        g_free (path);
+
+        return settings;
+}
+
+static void
+handle_method_call (GDBusConnection       *connection,
+                    const gchar           *sender,
+                    const gchar           *object_path,
+                    const gchar           *interface_name,
+                    const gchar           *method_name,
+                    GVariant              *parameters,
+                    GDBusMethodInvocation *invocation,
+                    gpointer               data)
+{
+	GsdWacomManager *self = GSD_WACOM_MANAGER (data);
+        GError *error = NULL;
+        GdkDevice *device;
+
+        if (g_strcmp0 (method_name, "SetOLEDLabels") == 0) {
+                gchar *device_path, *label;
+                gboolean left_handed;
+                GSettings *settings;
+                GVariantIter *iter;
+                gint i = 0;
+
+		g_variant_get (parameters, "(sas)", &device_path, &iter);
+                device = lookup_device_by_path (self, device_path);
+                if (!device) {
+                        g_dbus_method_invocation_return_value (invocation, NULL);
+                        return;
+                }
+
+                settings = device_get_settings (device);
+                left_handed = g_settings_get_boolean (settings, LEFT_HANDED_KEY);
+                g_object_unref (settings);
+
+                while (g_variant_iter_loop (iter, "s", &label)) {
+                        if (!set_oled (device_path, left_handed, i, label, &error)) {
+                                g_free (label);
+                                break;
+                        }
+                        i++;
+                }
+
+                g_variant_iter_free (iter);
+
+                if (error)
+                        g_dbus_method_invocation_return_gerror (invocation, error);
+                else
+                        g_dbus_method_invocation_return_value (invocation, NULL);
+        }
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+	handle_method_call,
+	NULL, /* Get Property */
+	NULL, /* Set Property */
+};
+
+static void
+device_added_cb (GdkSeat         *seat,
+                 GdkDevice       *device,
+                 GsdWacomManager *manager)
+{
+        if (gdk_device_get_source (device) == GDK_SOURCE_PEN &&
+            gdk_device_get_device_type (device) == GDK_DEVICE_TYPE_SLAVE) {
+                migrate_tablet_settings (manager, device);
+        }
+}
+
+static void
+add_devices (GsdWacomManager     *manager,
+             GdkSeatCapabilities  capabilities)
+{
+        GList *devices, *l;
+
+        devices = gdk_seat_get_slaves (manager->seat, capabilities);
+        for (l = devices; l ; l = l->next)
+		device_added_cb (manager->seat, l->data, manager);
+        g_list_free (devices);
+}
+
+static void
+set_devicepresence_handler (GsdWacomManager *manager)
+{
+        GdkSeat *seat;
+
+        seat = gdk_display_get_default_seat (gdk_display_get_default ());
+        manager->device_added_id = g_signal_connect (seat, "device-added",
+                                                           G_CALLBACK (device_added_cb), manager);
+        manager->seat = seat;
+}
+
+static void
+gsd_wacom_manager_init (GsdWacomManager *manager)
+{
+#if HAVE_WACOM
+        manager->wacom_db = libwacom_database_new ();
+#endif
+}
+
+static gboolean
+gsd_wacom_manager_idle_cb (GsdWacomManager *manager)
+{
+        gnome_settings_profile_start (NULL);
+
+        set_devicepresence_handler (manager);
+
+        add_devices (manager, GDK_SEAT_CAPABILITY_TABLET_STYLUS);
+
+        gnome_settings_profile_end (NULL);
+
+        manager->start_idle_id = 0;
+
+        return FALSE;
+}
+
+static void
+on_bus_gotten (GObject		   *source_object,
+	       GAsyncResult	   *res,
+	       GsdWacomManager	   *manager)
+{
+	GDBusConnection	       *connection;
+	GError		       *error = NULL;
+
+	connection = g_bus_get_finish (res, &error);
+
+	if (connection == NULL) {
+		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			g_warning ("Couldn't get session bus: %s", error->message);
+		g_error_free (error);
+		return;
+	}
+
+	manager->dbus_connection = connection;
+	manager->dbus_register_object_id = g_dbus_connection_register_object (connection,
+									      GSD_WACOM_DBUS_PATH,
+									      manager->introspection_data->interfaces[0],
+									      &interface_vtable,
+									      manager,
+									      NULL,
+									      &error);
+
+	if (manager->dbus_register_object_id == 0) {
+		g_warning ("Error registering object: %s", error->message);
+		g_error_free (error);
+		return;
+	}
+
+        manager->name_id = g_bus_own_name_on_connection (connection,
+                                                         GSD_WACOM_DBUS_NAME,
+                                                         G_BUS_NAME_OWNER_FLAGS_NONE,
+                                                         NULL,
+                                                         NULL,
+                                                         NULL,
+                                                         NULL);
+}
+
+static void
+register_manager (GsdWacomManager *manager)
+{
+        manager->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+        manager->dbus_cancellable = g_cancellable_new ();
+        g_assert (manager->introspection_data != NULL);
+
+        g_bus_get (G_BUS_TYPE_SESSION,
+                   manager->dbus_cancellable,
+                   (GAsyncReadyCallback) on_bus_gotten,
+                   manager);
+}
+
+static gchar *
+get_machine_id (void)
+{
+        gchar *no_per_machine_file, *machine_id = NULL;
+        gboolean per_machine;
+        gsize len;
+
+        no_per_machine_file = g_build_filename (g_get_user_config_dir (), "gnome-settings-daemon", "no-per-machine-config", NULL);
+        per_machine = !g_file_test (no_per_machine_file, G_FILE_TEST_EXISTS);
+        g_free (no_per_machine_file);
+
+        if (!per_machine ||
+            (!g_file_get_contents ("/etc/machine-id", &machine_id, &len, NULL) &&
+             !g_file_get_contents ("/var/lib/dbus/machine-id", &machine_id, &len, NULL))) {
+                return g_strdup ("00000000000000000000000000000000");
+        }
+
+        machine_id[len - 1] = '\0';
+        return machine_id;
+}
+
+gboolean
+gsd_wacom_manager_start (GsdWacomManager *manager,
+                         GError         **error)
+{
+        gnome_settings_profile_start (NULL);
+
+        register_manager (manager_object);
+
+        manager->machine_id = get_machine_id ();
+
+        manager->start_idle_id = g_idle_add ((GSourceFunc) gsd_wacom_manager_idle_cb, manager);
+        g_source_set_name_by_id (manager->start_idle_id, "[gnome-settings-daemon] gsd_wacom_manager_idle_cb");
+
+        gnome_settings_profile_end (NULL);
+
+        return TRUE;
+}
+
+void
+gsd_wacom_manager_stop (GsdWacomManager *manager)
+{
+        g_debug ("Stopping wacom manager");
+
+        g_clear_pointer (&manager->machine_id, g_free);
+
+        if (manager->name_id != 0) {
+                g_bus_unown_name (manager->name_id);
+                manager->name_id = 0;
+        }
+
+        if (manager->dbus_register_object_id) {
+                g_dbus_connection_unregister_object (manager->dbus_connection,
+                                                     manager->dbus_register_object_id);
+                manager->dbus_register_object_id = 0;
+        }
+
+        if (manager->seat != NULL) {
+                g_signal_handler_disconnect (manager->seat, manager->device_added_id);
+                manager->seat = NULL;
+        }
+}
+
+static void
+gsd_wacom_manager_finalize (GObject *object)
+{
+        GsdWacomManager *wacom_manager;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSD_IS_WACOM_MANAGER (object));
+
+        wacom_manager = GSD_WACOM_MANAGER (object);
+
+        g_return_if_fail (wacom_manager != NULL);
+
+        gsd_wacom_manager_stop (wacom_manager);
+
+        if (wacom_manager->start_idle_id != 0)
+                g_source_remove (wacom_manager->start_idle_id);
+
+        g_clear_object (&wacom_manager->shell_proxy);
+
+#if HAVE_WACOM
+        libwacom_database_destroy (wacom_manager->wacom_db);
+#endif
+
+        G_OBJECT_CLASS (gsd_wacom_manager_parent_class)->finalize (object);
+}
+
+GsdWacomManager *
+gsd_wacom_manager_new (void)
+{
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_WACOM_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+        }
+
+        return GSD_WACOM_MANAGER (manager_object);
+}
diff --git a/plugins/wacom/gsd-wacom-manager.h b/plugins/wacom/gsd-wacom-manager.h
new file mode 100644
index 0000000..faef607
--- /dev/null
+++ b/plugins/wacom/gsd-wacom-manager.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann 
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_WACOM_MANAGER_H
+#define __GSD_WACOM_MANAGER_H
+
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_WACOM_MANAGER         (gsd_wacom_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdWacomManager, gsd_wacom_manager, GSD, WACOM_MANAGER, GObject)
+
+GsdWacomManager *       gsd_wacom_manager_new                 (void);
+gboolean                gsd_wacom_manager_start               (GsdWacomManager *manager,
+                                                               GError         **error);
+void                    gsd_wacom_manager_stop                (GsdWacomManager *manager);
+
+G_END_DECLS
+
+#endif /* __GSD_WACOM_MANAGER_H */
diff --git a/plugins/wacom/gsd-wacom-oled-constants.h b/plugins/wacom/gsd-wacom-oled-constants.h
new file mode 100644
index 0000000..e93f744
--- /dev/null
+++ b/plugins/wacom/gsd-wacom-oled-constants.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Przemo Firszt 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_WACOM_OLED_CONSTANTS_H
+#define __GSD_WACOM_OLED_CONSTANTS_H
+
+G_BEGIN_DECLS
+
+typedef enum {
+	GSD_WACOM_OLED_TYPE_USB,
+	GSD_WACOM_OLED_TYPE_BLUETOOTH,
+	GSD_WACOM_OLED_TYPE_RAW_BLUETOOTH
+} GsdWacomOledType;
+
+/* OLED parameters */
+#define OLED_WIDTH		64			/*Width of OLED icon - hardware dependent*/
+#define OLED_HEIGHT		32			/*Height of OLED icon - hardware dependent*/
+#define LABEL_SIZE		30			/*Maximum length of text for OLED icon*/
+#define MAX_TOKEN		(LABEL_SIZE >> 1)	/*Maximum number of tokens equals half of maximum number of characters*/
+#define MAX_IMAGE_SIZE		1024			/*Size of buffer for storing OLED image*/
+#define MAX_1ST_LINE_LEN	10			/*Maximum number of characters in 1st line of OLED icon*/
+
+G_END_DECLS
+
+#endif /* __GSD_WACOM_OLED_CONSTANTS_H */
diff --git a/plugins/wacom/gsd-wacom-oled-helper.c b/plugins/wacom/gsd-wacom-oled-helper.c
new file mode 100644
index 0000000..86f2891
--- /dev/null
+++ b/plugins/wacom/gsd-wacom-oled-helper.c
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2012      Przemo Firszt 
+ *
+ * The code is derived from gsd-wacom-led-helper.c
+ * written by:
+ * Copyright (C) 2010-2011 Richard Hughes 
+ * Copyright (C) 2012      Bastien Nocera 
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include 
+
+#include 
+#include "config.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "gsd-wacom-oled-constants.h"
+
+#define USB_PIXELS_PER_BYTE 2
+#define BT_PIXELS_PER_BYTE 8
+#define USB_BUF_LEN OLED_HEIGHT * OLED_WIDTH / USB_PIXELS_PER_BYTE
+#define BT_BUF_LEN OLED_WIDTH * OLED_HEIGHT / BT_PIXELS_PER_BYTE
+
+static void
+oled_scramble_icon (guchar *image)
+{
+	guchar buf[USB_BUF_LEN];
+	int x, y, i;
+	guchar l1, l2, h1, h2;
+
+	for (i = 0; i < USB_BUF_LEN; i++)
+		buf[i] = image[i];
+
+	for (y = 0; y < (OLED_HEIGHT / 2); y++) {
+		for (x = 0; x < (OLED_WIDTH / 2); x++) {
+			l1 = (0x0F & (buf[OLED_HEIGHT - 1 - x + OLED_WIDTH * y]));
+			l2 = (0x0F & (buf[OLED_HEIGHT - 1 - x + OLED_WIDTH * y] >> 4));
+			h1 = (0xF0 & (buf[OLED_WIDTH - 1 - x + OLED_WIDTH * y] << 4));
+			h2 = (0xF0 & (buf[OLED_WIDTH - 1 - x + OLED_WIDTH * y]));
+
+			image[2 * x + OLED_WIDTH * y] = h1 | l1;
+			image[2 * x + 1 + OLED_WIDTH * y] = h2 | l2;
+		}
+	}
+}
+
+static void
+oled_bt_scramble_icon (guchar *input_image)
+{
+	unsigned char image[BT_BUF_LEN];
+	unsigned mask;
+	unsigned s1;
+	unsigned s2;
+	unsigned r1 ;
+	unsigned r2 ;
+	unsigned r;
+	unsigned char buf[256];
+	int i, w, x, y, z;
+
+	for (i = 0; i < BT_BUF_LEN; i++)
+		image[i] = input_image[i];
+
+	for (x = 0; x < 32; x++) {
+		for (y = 0; y < 8; y++)
+			buf[(8 * x) + (7 - y)] = image[(8 * x) + y];
+	}
+
+	/* Change 76543210 into GECA6420 as required by Intuos4 WL
+	 *        HGFEDCBA      HFDB7531
+	 */
+	for (x = 0; x < 4; x++) {
+		for (y = 0; y < 4; y++) {
+			for (z = 0; z < 8; z++) {
+				mask = 0x0001;
+				r1 = 0;
+				r2 = 0;
+				i = (x << 6) + (y << 4) + z;
+				s1 = buf[i];
+				s2 = buf[i+8];
+				for (w = 0; w < 8; w++) {
+					r1 |= (s1 & mask);
+					r2 |= (s2 & mask);
+					s1 <<= 1;
+					s2 <<= 1;
+					mask <<= 2;
+				}
+				r = r1 | (r2 << 1);
+				i = (x << 6) + (y << 4) + (z << 1);
+				image[i] = 0xFF & r;
+				image[i+1] = (0xFF00 & r) >> 8;
+			}
+		}
+	}
+	for (i = 0; i < BT_BUF_LEN; i++)
+		input_image[i] = image[i];
+}
+
+static void
+gsd_wacom_oled_convert_1_bit (guchar *image)
+{
+	guchar buf[BT_BUF_LEN];
+	guchar b0, b1, b2, b3, b4, b5, b6, b7;
+	int i;
+
+	for (i = 0; i < BT_BUF_LEN; i++) {
+		b0 = 0b10000000 & (image[(4 * i) + 0] >> 0);
+		b1 = 0b01000000 & (image[(4 * i) + 0] << 3);
+		b2 = 0b00100000 & (image[(4 * i) + 1] >> 2);
+		b3 = 0b00010000 & (image[(4 * i) + 1] << 1);
+		b4 = 0b00001000 & (image[(4 * i) + 2] >> 4);
+		b5 = 0b00000100 & (image[(4 * i) + 2] >> 1);
+		b6 = 0b00000010 & (image[(4 * i) + 3] >> 6);
+		b7 = 0b00000001 & (image[(4 * i) + 3] >> 3);
+		buf[i] = b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7;
+	}
+	for (i = 0; i < BT_BUF_LEN; i++)
+		image[i] = buf[i];
+}
+
+static int
+gsd_wacom_oled_prepare_buf (guchar *image, GsdWacomOledType type)
+{
+	int len = 0;
+
+	switch (type) {
+	case GSD_WACOM_OLED_TYPE_USB:
+		/* Image has to be scrambled for devices connected over USB ... */
+		oled_scramble_icon (image);
+		len = USB_BUF_LEN;
+		break;
+	case GSD_WACOM_OLED_TYPE_BLUETOOTH:
+		/* ... but for bluetooth it has to be converted to 1 bit colour instead of scrambling */
+		gsd_wacom_oled_convert_1_bit (image);
+		len = BT_BUF_LEN;
+		break;
+	case GSD_WACOM_OLED_TYPE_RAW_BLUETOOTH:
+		/* Image has also to be scrambled for devices connected over BT using the raw API ... */
+		gsd_wacom_oled_convert_1_bit (image);
+		len = BT_BUF_LEN;
+		oled_bt_scramble_icon (image);
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+
+	return len;
+}
+
+static gboolean
+gsd_wacom_oled_helper_write (const gchar *filename, gchar *buffer, GsdWacomOledType type, GError **error)
+{
+	guchar *image;
+	gint retval;
+	gsize length;
+	gint fd = -1;
+	gboolean ret = TRUE;
+
+	fd = open (filename, O_WRONLY);
+	if (fd < 0) {
+		ret = FALSE;
+		g_set_error (error, 1, 0, "Failed to open filename: %s", filename);
+		goto out;
+	}
+
+	image = g_base64_decode (buffer, &length);
+	if (length != USB_BUF_LEN) {
+		ret = FALSE;
+		g_set_error (error, 1, 0, "Base64 buffer has length of %" G_GSIZE_FORMAT " (expected %i)", length, USB_BUF_LEN);
+		goto out;
+	}
+	if (!image) {
+		ret = FALSE;
+		g_set_error (error, 1, 0, "Decoding base64 buffer failed");
+		goto out;
+	}
+
+	length = gsd_wacom_oled_prepare_buf (image, type);
+	if (!length) {
+		ret = FALSE;
+		g_set_error (error, 1, 0, "Invalid image buffer length");
+		goto out;
+	}
+
+	retval = write (fd, image, length);
+	if (retval != length) {
+		ret = FALSE;
+		g_set_error (error, 1, 0, "Writing to %s failed", filename);
+	}
+
+	g_free (image);
+out:
+	if (fd >= 0)
+		close (fd);
+	return ret;
+}
+
+static char *
+get_oled_sysfs_path (GUdevDevice *device,
+		    int          button_num)
+{
+	char *status;
+	char *filename;
+
+	status = g_strdup_printf ("button%d_rawimg", button_num);
+	filename = g_build_filename (g_udev_device_get_sysfs_path (device), "wacom_led", status, NULL);
+	g_free (status);
+
+	return filename;
+}
+
+static char *
+get_bt_oled_sysfs_path (GUdevDevice *device, int button_num)
+{
+	char *status;
+	char *filename;
+
+	status = g_strdup_printf ("/oled%i_img", button_num);
+	filename = g_build_filename (g_udev_device_get_sysfs_path (device), status, NULL);
+	g_free (status);
+	return filename;
+}
+
+static char *
+get_bt_oled_filename (GUdevClient *client, GUdevDevice *device, int button_num)
+{
+	GUdevDevice *hid_dev;
+	const char *dev_uniq;
+	GList *hid_list;
+	GList *element;
+	const char *dev_hid_uniq;
+	char *filename = NULL;
+
+	dev_uniq = g_udev_device_get_property (device, "UNIQ");
+
+	hid_list =  g_udev_client_query_by_subsystem (client, "hid");
+	element = g_list_first(hid_list);
+	while (element) {
+		hid_dev = (GUdevDevice*)element->data;
+		dev_hid_uniq = g_udev_device_get_property (hid_dev, "HID_UNIQ");
+		if (g_strrstr (dev_uniq, dev_hid_uniq)){
+			filename = get_bt_oled_sysfs_path (hid_dev, button_num);
+			g_object_unref (hid_dev);
+			break;
+		}
+		g_object_unref (hid_dev);
+		element = g_list_next(element);
+	}
+	g_list_free(hid_list);
+	return filename;
+}
+
+static char *
+get_oled_sys_path (GUdevClient      *client,
+		   GUdevDevice      *device,
+		   int               button_num,
+		   gboolean          usb,
+		   GsdWacomOledType *type)
+{
+	GUdevDevice *parent;
+	char *filename = NULL;
+
+	/* check for new unified hid implementation first */
+	parent = g_udev_device_get_parent_with_subsystem (device, "hid", NULL);
+	if (parent) {
+		filename = get_oled_sysfs_path (parent, button_num);
+		g_object_unref (parent);
+		if(g_file_test (filename, G_FILE_TEST_EXISTS)) {
+			*type = usb ? GSD_WACOM_OLED_TYPE_USB : GSD_WACOM_OLED_TYPE_RAW_BLUETOOTH;
+			return filename;
+		}
+		g_clear_pointer (&filename, g_free);
+	}
+
+	/* old kernel */
+	if (usb) {
+		parent = g_udev_device_get_parent_with_subsystem (device, "usb", "usb_interface");
+		if (!parent)
+			goto no_parent;
+
+		filename = get_oled_sysfs_path (parent, button_num);
+		*type = GSD_WACOM_OLED_TYPE_USB;
+	 } else if (g_strrstr (g_udev_device_get_property (device, "DEVPATH"), "bluetooth")) {
+		parent = g_udev_device_get_parent_with_subsystem (device, "input", NULL);
+		if (!parent)
+			goto no_parent;
+
+		filename = get_bt_oled_filename (client, parent, button_num);
+		*type = GSD_WACOM_OLED_TYPE_BLUETOOTH;
+	} else {
+		g_critical ("Not an expected device: '%s'",
+			    g_udev_device_get_device_file (device));
+		goto out_err;
+	}
+
+	g_object_unref (parent);
+
+	return filename;
+
+no_parent:
+	g_debug ("Could not find proper parent device for '%s'",
+		 g_udev_device_get_device_file (device));
+
+out_err:
+	return NULL;
+}
+
+
+int main (int argc, char **argv)
+{
+	GOptionContext *context;
+	GUdevClient *client;
+	GUdevDevice *device;
+	int uid, euid;
+	char *filename;
+	GError *error = NULL;
+	const char * const subsystems[] = { "input", NULL };
+	int ret = 1;
+	gboolean usb;
+	GsdWacomOledType type;
+
+	char *path = NULL;
+	char *buffer = NULL;
+	int button_num = -1;
+
+	const GOptionEntry options[] = {
+		{ "path", '\0', 0, G_OPTION_ARG_FILENAME, &path, "Device path for the Wacom device", NULL },
+		{ "buffer", '\0', 0, G_OPTION_ARG_STRING, &buffer, "Image to set base64 encoded", NULL },
+		{ "button", '\0', 0, G_OPTION_ARG_INT, &button_num, "Which button icon to set", NULL },
+		{ NULL}
+	};
+
+	/* get calling process */
+	uid = getuid ();
+	euid = geteuid ();
+	if (uid != 0 || euid != 0) {
+		g_print ("This program can only be used by the root user\n");
+		return 1;
+	}
+
+	context = g_option_context_new (NULL);
+	g_option_context_set_summary (context, "GNOME Settings Daemon Wacom OLED Icon Helper");
+	g_option_context_add_main_entries (context, options, NULL);
+	g_option_context_parse (context, &argc, &argv, NULL);
+
+	if (path == NULL ||
+	    button_num < 0 ||
+	    buffer == NULL) {
+		char *txt;
+
+		txt = g_option_context_get_help (context, FALSE, NULL);
+		g_print ("%s", txt);
+		g_free (txt);
+
+		g_option_context_free (context);
+
+		return 1;
+	}
+	g_option_context_free (context);
+
+	client = g_udev_client_new (subsystems);
+	device = g_udev_client_query_by_device_file (client, path);
+	if (device == NULL) {
+		g_critical ("Could not find device '%s' in udev database", path);
+		goto out;
+	}
+
+	if (g_udev_device_get_property_as_boolean (device, "ID_INPUT_TABLET") == FALSE &&
+	    g_udev_device_get_property_as_boolean (device, "ID_INPUT_TOUCHPAD") == FALSE) {
+		g_critical ("Device '%s' is not a Wacom tablet", path);
+		goto out;
+	}
+
+	if (g_strcmp0 (g_udev_device_get_property (device, "ID_BUS"), "usb") != 0)
+		usb = FALSE;
+	else
+		usb = TRUE;
+
+	filename = get_oled_sys_path (client, device, button_num, usb, &type);
+	if (!filename)
+		goto out;
+
+	if (gsd_wacom_oled_helper_write (filename, buffer, type, &error) == FALSE) {
+		g_critical ("Could not set OLED icon for '%s': %s", path, error->message);
+		g_error_free (error);
+		g_free (filename);
+		goto out;
+	}
+	g_free (filename);
+
+	g_debug ("Successfully set OLED icon for '%s', button %d", path, button_num);
+
+	ret = 0;
+
+out:
+	g_free (path);
+	g_free (buffer);
+
+	g_clear_object (&device);
+	g_clear_object (&client);
+
+	return ret;
+}
diff --git a/plugins/wacom/gsd-wacom-oled.c b/plugins/wacom/gsd-wacom-oled.c
new file mode 100644
index 0000000..4c2daf1
--- /dev/null
+++ b/plugins/wacom/gsd-wacom-oled.c
@@ -0,0 +1,258 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Przemo Firszt 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "gsd-wacom-oled.h"
+
+#define MAGIC_BASE64		"base64:"		/*Label starting with base64: is treated as already encoded*/
+#define MAGIC_BASE64_LEN	strlen(MAGIC_BASE64)
+
+static void
+oled_surface_to_image (guchar          *image,
+		       cairo_surface_t *surface)
+{
+	unsigned char *csurf;
+	int i, x, y;
+	unsigned char lo, hi;
+
+	cairo_surface_flush (surface);
+	csurf = cairo_image_surface_get_data (surface);
+	i = 0;
+	for (y = 0; y < OLED_HEIGHT; y++) {
+		for (x = 0; x < (OLED_WIDTH / 2); x++) {
+			hi = 0xf0 & csurf[4 * OLED_WIDTH * y + 8 * x + 1];
+			lo = 0x0f & (csurf[4 * OLED_WIDTH * y + 8 * x + 5] >> 4);
+			image[i] = hi | lo;
+			i++;
+		}
+	}
+}
+
+static void
+oled_split_text (char *label,
+		 char *line1,
+		 char *line2)
+{
+	char delimiters[5] = "+-_ ";
+	char **token;
+	int token_len[MAX_TOKEN];
+	gsize length;
+	int i;
+
+	if (g_utf8_strlen (label, LABEL_SIZE) <= MAX_1ST_LINE_LEN) {
+		g_utf8_strncpy (line1, label, MAX_1ST_LINE_LEN);
+		return;
+	}
+
+	token = g_strsplit_set (label, delimiters, -1);
+
+	if (g_utf8_strlen (token[0], LABEL_SIZE) > MAX_1ST_LINE_LEN) {
+		g_utf8_strncpy (line1, label, MAX_1ST_LINE_LEN);
+		g_utf8_strncpy (line2, label + MAX_1ST_LINE_LEN, LABEL_SIZE - MAX_1ST_LINE_LEN);
+		return;
+	}
+
+	for (i = 0; token[i] != NULL; i++)
+		token_len[i] = g_utf8_strlen (token[i], LABEL_SIZE);
+
+	length = token_len[0];
+	i = 0;
+	while ((length + token_len[i + 1] + 1) <= MAX_1ST_LINE_LEN) {
+		i++;
+		length = length + token_len[i] + 1;
+	}
+
+	g_utf8_strncpy (line1, label, length);
+	g_utf8_strncpy (line2, label + length + 1, LABEL_SIZE - length);
+
+	return;
+}
+
+static void
+oled_render_text (char             *label,
+		  guchar	   *image,
+                  gboolean          left_handed)
+{
+	cairo_t *cr;
+	cairo_surface_t *surface;
+	PangoFontDescription *desc;
+	PangoLayout *layout;
+	int width, height;
+	double dx, dy;
+	char line1[LABEL_SIZE + 1] = "";
+	char line2[LABEL_SIZE + 1] = "";
+	char *buf;
+
+	oled_split_text (label ,line1, line2);
+
+	buf = g_strdup_printf ("%s\n%s", line1, line2);
+
+	surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, OLED_WIDTH, OLED_HEIGHT);
+	cr = cairo_create (surface);
+
+        /* Rotate text so it's seen correctly on the device, or at
+         * least from top to bottom for LTR text in vertical modes.
+         */
+        if (left_handed) {
+		cairo_translate (cr, OLED_WIDTH, OLED_HEIGHT);
+		cairo_scale (cr, -1, -1);
+        }
+
+	cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.99);
+	cairo_paint (cr);
+
+	layout = pango_cairo_create_layout (cr);
+	pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
+	pango_layout_set_text (layout, buf, - 1);
+	g_free (buf);
+	desc = pango_font_description_new ();
+
+	pango_font_description_set_family (desc, "Terminal");
+	pango_font_description_set_absolute_size (desc, PANGO_SCALE * 11);
+	pango_layout_set_font_description (layout, desc);
+	pango_font_description_free (desc);
+	pango_layout_get_size (layout, &width, &height);
+	width = width/PANGO_SCALE;
+	cairo_new_path (cr);
+
+	dx = trunc (((double)OLED_WIDTH - width) / 2);
+
+	if (*line2 == '\0')
+		dy = 10;
+	else
+		dy = 4;
+
+	cairo_move_to (cr, dx, dy);
+	cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
+	pango_cairo_update_layout (cr, layout);
+
+	pango_cairo_layout_path (cr, layout);
+	cairo_fill (cr);
+
+	oled_surface_to_image (image, surface);
+
+	g_object_unref (layout);
+	cairo_destroy (cr);
+
+	cairo_surface_destroy (surface);
+}
+
+char *
+gsd_wacom_oled_gdkpixbuf_to_base64 (GdkPixbuf *pixbuf)
+{
+	int i, x, y, ch, rs;
+	guchar *pix, *p;
+	guchar *image;
+	guchar lo, hi;
+	char *base_string, *base64;
+
+	if (OLED_WIDTH != gdk_pixbuf_get_width (pixbuf))
+		return NULL;
+
+	if (OLED_HEIGHT != gdk_pixbuf_get_height (pixbuf))
+		return NULL;
+
+	ch = gdk_pixbuf_get_n_channels (pixbuf);
+	rs = gdk_pixbuf_get_rowstride (pixbuf);
+	pix = gdk_pixbuf_get_pixels (pixbuf);
+
+	image = g_malloc (MAX_IMAGE_SIZE);
+	i = 0;
+	for (y = 0; y < OLED_HEIGHT; y++) {
+		for (x = 0; x < (OLED_WIDTH / 2); x++) {
+			p = pix + y * rs + 2 * x * ch;
+			hi = 0xf0 & ((p[0] + p[1] + p[2])/ 3 * p[3] / 0xff);
+			p = pix + y * rs + ((2 * x) + 1) * ch;
+			lo = 0x0f & (((p[0] + p[1] + p[2])/ 3 * p[3] / 0xff) >> 4);
+			image[i] = hi | lo;
+			i++;
+		}
+	}
+
+	base_string = g_base64_encode (image, MAX_IMAGE_SIZE);
+	base64 = g_strconcat (MAGIC_BASE64, base_string, NULL);
+	g_free (base_string);
+	g_free (image);
+
+	return base64;
+}
+
+static char *
+oled_encode_image (char             *label,
+                   gboolean          left_handed)
+{
+	unsigned char *image;
+
+	image = g_malloc (MAX_IMAGE_SIZE);
+
+	/* convert label to image */
+	oled_render_text (label, image, left_handed);
+
+	return (g_base64_encode (image, MAX_IMAGE_SIZE));
+}
+
+gboolean
+set_oled (const gchar  *device_path,
+          gboolean      left_handed,
+          guint         button,
+          char	       *label,
+          GError      **error)
+{
+	char *command;
+	gboolean ret;
+	char *buffer;
+        gint status;
+
+#ifndef HAVE_GUDEV
+	/* Not implemented on non-Linux systems */
+	return TRUE;
+#endif
+
+	if (g_str_has_prefix (label, MAGIC_BASE64)) {
+		buffer = g_strdup (label + MAGIC_BASE64_LEN);
+        } else {
+		buffer = oled_encode_image (label, left_handed);
+	}
+
+	g_debug ("Setting OLED label '%s' on button %d (device %s)", label, button, device_path);
+
+	command = g_strdup_printf ("pkexec " LIBEXECDIR "/gsd-wacom-oled-helper --path %s --button %d --buffer %s",
+				   device_path, button, buffer);
+	ret = g_spawn_command_line_sync (command,
+					 NULL,
+					 NULL,
+					 &status,
+					 error);
+
+        if (ret == TRUE && status != 0)
+                ret = FALSE;
+
+	g_free (command);
+
+        return ret;
+}
diff --git a/plugins/wacom/gsd-wacom-oled.h b/plugins/wacom/gsd-wacom-oled.h
new file mode 100644
index 0000000..dc5d39c
--- /dev/null
+++ b/plugins/wacom/gsd-wacom-oled.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Przemo Firszt 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "gsd-wacom-oled-constants.h"
+
+#include 
+
+#ifndef __GSD_WACOM_OLED_H
+#define __GSD_WACOM_OLED_H
+
+G_BEGIN_DECLS
+
+gboolean set_oled (const gchar *device_path, gboolean left_handed, guint button, char *label, GError **error);
+char *gsd_wacom_oled_gdkpixbuf_to_base64 (GdkPixbuf *pixbuf);
+
+G_END_DECLS
+
+#endif /* __GSD_WACOM_OLED_H */
diff --git a/plugins/wacom/main.c b/plugins/wacom/main.c
new file mode 100644
index 0000000..a19a71b
--- /dev/null
+++ b/plugins/wacom/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_wacom_manager_new
+#define START gsd_wacom_manager_start
+#define STOP gsd_wacom_manager_stop
+#define MANAGER GsdWacomManager
+#include "gsd-wacom-manager.h"
+
+#include "daemon-skeleton-gtk.h"
diff --git a/plugins/wacom/meson.build b/plugins/wacom/meson.build
new file mode 100644
index 0000000..c00323d
--- /dev/null
+++ b/plugins/wacom/meson.build
@@ -0,0 +1,62 @@
+policy = 'org.gnome.settings-daemon.plugins.wacom.policy'
+
+policy_in = configure_file(
+  input: policy + '.in.in',
+  output: policy + '.in',
+  configuration: plugins_conf
+)
+
+i18n.merge_file(
+  input: policy_in,
+  output: policy,
+  po_dir: po_dir,
+  install: true,
+  install_dir: join_paths(gsd_datadir, 'polkit-1', 'actions')
+)
+
+sources = files(
+  'gsd-wacom-manager.c',
+  'gsd-wacom-oled.c',
+  'main.c'
+)
+
+deps = plugins_deps + [
+  gtk_dep,
+  libcommon_dep,
+  m_dep,
+  pango_dep
+]
+
+if enable_wacom
+  deps += libwacom_dep
+endif
+
+cflags += ['-DLIBEXECDIR="@0@"'.format(gsd_libexecdir)]
+
+executable(
+  'gsd-' + plugin_name,
+  sources,
+  include_directories: [top_inc, data_inc],
+  dependencies: deps,
+  c_args: cflags,
+  install: true,
+  install_rpath: gsd_pkglibdir,
+  install_dir: gsd_libexecdir
+)
+
+if enable_gudev
+  deps = [
+    gudev_dep,
+    m_dep
+  ]
+
+  executable(
+    'gsd-wacom-oled-helper',
+    'gsd-wacom-oled-helper.c',
+    include_directories: top_inc,
+    dependencies: deps,
+    install: true,
+    install_rpath: gsd_pkglibdir,
+    install_dir: gsd_libexecdir
+  )
+endif
diff --git a/plugins/wacom/org.gnome.settings-daemon.plugins.wacom.policy.in.in b/plugins/wacom/org.gnome.settings-daemon.plugins.wacom.policy.in.in
new file mode 100644
index 0000000..fe0df93
--- /dev/null
+++ b/plugins/wacom/org.gnome.settings-daemon.plugins.wacom.policy.in.in
@@ -0,0 +1,46 @@
+
+
+
+
+  
+
+  GNOME Settings Daemon
+  http://git.gnome.org/browse/gnome-settings-daemon
+  input-tablet
+
+  
+    
+    Modify the lit LED for a Wacom tablet
+    Authentication is required to modify the lit LED for a Wacom tablet
+    
+      no
+      no
+      yes
+    
+    @libexecdir@/gsd-wacom-led-helper
+  
+
+  
+    
+    Modify the OLED image for a Wacom tablet
+    Authentication is required to modify the OLED image for a Wacom tablet
+    
+      no
+      no
+      yes
+    
+    @libexecdir@/gsd-wacom-oled-helper
+  
+
+
+
diff --git a/plugins/wwan/cc-wwan-device.c b/plugins/wwan/cc-wwan-device.c
new file mode 100644
index 0000000..57b869b
--- /dev/null
+++ b/plugins/wwan/cc-wwan-device.c
@@ -0,0 +1,1343 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-device.c
+ *
+ * Copyright 2019-2020 Purism SPC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 .
+ *
+ * Author(s):
+ *   Mohammed Sadiq 
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cc-wwan-device"
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include 
+#include 
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+# include 
+# include 
+#endif
+
+#include "cc-wwan-errors-private.h"
+#include "cc-wwan-device.h"
+
+/**
+ * @short_description: Device Object
+ * @include: "cc-wwan-device.h"
+ */
+
+struct _CcWwanDevice
+{
+  GObject      parent_instance;
+
+  MMObject    *mm_object;
+  MMModem     *modem;
+  MMSim       *sim;
+  MMModem3gpp *modem_3gpp;
+
+  const char  *operator_code; /* MCCMNC */
+  GError      *error;
+
+  /* Building with NetworkManager is optional,
+   * so #NMclient type can’t be used here.
+   */
+  GObject      *nm_client; /* An #NMClient */
+  CcWwanData   *wwan_data;
+
+  gulong      modem_3gpp_id;
+  gulong      modem_3gpp_locks_id;
+
+  /* Enabled locks like PIN, PIN2, PUK, etc. */
+  MMModem3gppFacility locks;
+
+  CcWwanState  registration_state;
+  gboolean     network_is_manual;
+};
+
+G_DEFINE_TYPE (CcWwanDevice, cc_wwan_device, G_TYPE_OBJECT)
+
+
+enum {
+  PROP_0,
+  PROP_OPERATOR_NAME,
+  PROP_ENABLED_LOCKS,
+  PROP_ERROR,
+  PROP_HAS_DATA,
+  PROP_NETWORK_MODE,
+  PROP_REGISTRATION_STATE,
+  PROP_SIGNAL,
+  PROP_UNLOCK_REQUIRED,
+  N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static void
+cc_wwan_device_state_changed_cb (CcWwanDevice *self)
+{
+  MMModem3gppRegistrationState state;
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_OPERATOR_NAME]);
+
+  state = mm_modem_3gpp_get_registration_state (self->modem_3gpp);
+
+  switch (state)
+    {
+    case MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN:
+      self->registration_state = CC_WWAN_REGISTRATION_STATE_UNKNOWN;
+      break;
+
+    case MM_MODEM_3GPP_REGISTRATION_STATE_DENIED:
+      self->registration_state = CC_WWAN_REGISTRATION_STATE_DENIED;
+      break;
+
+    case MM_MODEM_3GPP_REGISTRATION_STATE_IDLE:
+      self->registration_state = CC_WWAN_REGISTRATION_STATE_IDLE;
+      break;
+
+    case MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING:
+      self->registration_state = CC_WWAN_REGISTRATION_STATE_SEARCHING;
+      break;
+
+    case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING:
+      self->registration_state = CC_WWAN_REGISTRATION_STATE_ROAMING;
+      break;
+
+    default:
+      self->registration_state = CC_WWAN_REGISTRATION_STATE_REGISTERED;
+      break;
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_REGISTRATION_STATE]);
+}
+
+static void
+cc_wwan_device_locks_changed_cb (CcWwanDevice *self)
+{
+  self->locks = mm_modem_3gpp_get_enabled_facility_locks (self->modem_3gpp);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLED_LOCKS]);
+}
+
+static void
+cc_wwan_device_3gpp_changed_cb (CcWwanDevice *self)
+{
+  gulong handler_id = 0;
+
+  if (self->modem_3gpp_id)
+    g_signal_handler_disconnect (self->modem_3gpp, self->modem_3gpp_id);
+  self->modem_3gpp_id = 0;
+
+  if (self->modem_3gpp_locks_id)
+    g_signal_handler_disconnect (self->modem_3gpp, self->modem_3gpp_locks_id);
+  self->modem_3gpp_locks_id = 0;
+
+  g_clear_object (&self->modem_3gpp);
+  self->modem_3gpp = mm_object_get_modem_3gpp (self->mm_object);
+
+  if (self->modem_3gpp)
+    {
+      handler_id = g_signal_connect_object (self->modem_3gpp, "notify::registration-state",
+                                            G_CALLBACK (cc_wwan_device_state_changed_cb),
+                                            self, G_CONNECT_SWAPPED);
+      self->modem_3gpp_id = handler_id;
+
+      handler_id = g_signal_connect_object (self->modem_3gpp, "notify::enabled-facility-locks",
+                                            G_CALLBACK (cc_wwan_device_locks_changed_cb),
+                                            self, G_CONNECT_SWAPPED);
+      self->modem_3gpp_locks_id = handler_id;
+      cc_wwan_device_locks_changed_cb (self);
+      cc_wwan_device_state_changed_cb (self);
+    }
+}
+
+static void
+cc_wwan_device_signal_quality_changed_cb (CcWwanDevice *self)
+{
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SIGNAL]);
+}
+
+static void
+cc_wwan_device_mode_changed_cb (CcWwanDevice *self)
+{
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NETWORK_MODE]);
+}
+
+static void
+cc_wwan_device_emit_data_changed (CcWwanDevice *self)
+{
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_DATA]);
+}
+
+static void
+cc_wwan_device_unlock_required_cb (CcWwanDevice *self)
+{
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_UNLOCK_REQUIRED]);
+}
+
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+static void
+cc_wwan_device_nm_changed_cb (CcWwanDevice *self,
+                              GParamSpec   *pspec,
+                              NMClient     *client)
+{
+  gboolean nm_is_running;
+
+  nm_is_running = nm_client_get_nm_running (client);
+
+  if (!nm_is_running && self->wwan_data != NULL)
+    {
+      g_clear_object (&self->wwan_data);
+      cc_wwan_device_emit_data_changed (self);
+    }
+}
+
+static void
+cc_wwan_device_nm_device_added_cb (CcWwanDevice *self,
+                                   NMDevice     *nm_device)
+{
+  if (!NM_IS_DEVICE_MODEM (nm_device))
+    return;
+
+  if(!self->sim || !cc_wwan_device_is_nm_device (self, G_OBJECT (nm_device)))
+    return;
+
+  self->wwan_data = cc_wwan_data_new (self->mm_object,
+                                      NM_CLIENT (self->nm_client));
+
+  if (self->wwan_data)
+    {
+      g_signal_connect_object (self->wwan_data, "notify::enabled",
+                               G_CALLBACK (cc_wwan_device_emit_data_changed),
+                               self, G_CONNECT_SWAPPED);
+      cc_wwan_device_emit_data_changed (self);
+    }
+}
+#endif
+
+static void
+cc_wwan_device_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  CcWwanDevice *self = (CcWwanDevice *)object;
+  MMModemMode allowed, preferred;
+
+  switch (prop_id)
+    {
+    case PROP_OPERATOR_NAME:
+      g_value_set_string (value, cc_wwan_device_get_operator_name (self));
+      break;
+
+    case PROP_ERROR:
+      g_value_set_boolean (value, self->error != NULL);
+      break;
+
+    case PROP_HAS_DATA:
+      g_value_set_boolean (value, self->wwan_data != NULL);
+      break;
+
+    case PROP_ENABLED_LOCKS:
+      g_value_set_int (value, self->locks);
+      break;
+
+    case PROP_NETWORK_MODE:
+      if (cc_wwan_device_get_current_mode (self, &allowed, &preferred))
+        g_value_take_string (value, cc_wwan_device_get_string_from_mode (self, allowed, preferred));
+      break;
+
+    case PROP_REGISTRATION_STATE:
+      g_value_set_int (value, self->registration_state);
+      break;
+
+    case PROP_UNLOCK_REQUIRED:
+      g_value_set_int (value, cc_wwan_device_get_lock (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+cc_wwan_device_dispose (GObject *object)
+{
+  CcWwanDevice *self = (CcWwanDevice *)object;
+
+  g_clear_error (&self->error);
+  g_clear_object (&self->modem);
+  g_clear_object (&self->mm_object);
+  g_clear_object (&self->sim);
+  g_clear_object (&self->modem_3gpp);
+
+  g_clear_object (&self->nm_client);
+  g_clear_object (&self->wwan_data);
+
+  G_OBJECT_CLASS (cc_wwan_device_parent_class)->dispose (object);
+}
+
+static void
+cc_wwan_device_class_init (CcWwanDeviceClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->get_property = cc_wwan_device_get_property;
+  object_class->dispose = cc_wwan_device_dispose;
+
+  properties[PROP_OPERATOR_NAME] =
+    g_param_spec_string ("operator-name",
+                         "Operator Name",
+                         "Operator Name the device is connected to",
+                         NULL,
+                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_ENABLED_LOCKS] =
+    g_param_spec_int ("enabled-locks",
+                      "Enabled Locks",
+                      "Locks Enabled in Modem",
+                      MM_MODEM_3GPP_FACILITY_NONE,
+                      MM_MODEM_3GPP_FACILITY_CORP_PERS,
+                      MM_MODEM_3GPP_FACILITY_NONE,
+                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_ERROR] =
+    g_param_spec_boolean ("error",
+                          "Error",
+                          "Set if some Error occurs",
+                          FALSE,
+                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_HAS_DATA] =
+    g_param_spec_boolean ("has-data",
+                          "has-data",
+                          "Data for the device",
+                          FALSE,
+                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_NETWORK_MODE] =
+    g_param_spec_string ("network-mode",
+                         "Network Mode",
+                         "A String representing preferred network mode",
+                         NULL,
+                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_REGISTRATION_STATE] =
+    g_param_spec_int ("registration-state",
+                      "Registration State",
+                      "The current network registration state",
+                      CC_WWAN_REGISTRATION_STATE_UNKNOWN,
+                      CC_WWAN_REGISTRATION_STATE_DENIED,
+                      CC_WWAN_REGISTRATION_STATE_UNKNOWN,
+                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_UNLOCK_REQUIRED] =
+    g_param_spec_int ("unlock-required",
+                      "Unlock Required",
+                      "The Modem lock status changed",
+                      MM_MODEM_LOCK_UNKNOWN,
+                      MM_MODEM_LOCK_PH_NETSUB_PUK,
+                      MM_MODEM_LOCK_UNKNOWN,
+                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_SIGNAL] =
+    g_param_spec_int ("signal",
+                      "Signal",
+                      "Get Device Signal",
+                      0, 100, 0,
+                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+cc_wwan_device_init (CcWwanDevice *self)
+{
+}
+
+/**
+ * cc_wwan_device_new:
+ * @mm_object: (transfer full): An #MMObject
+ *
+ * Create a new device representing the given
+ * @mm_object.
+ *
+ * Returns: A #CcWwanDevice
+ */
+CcWwanDevice *
+cc_wwan_device_new (MMObject *mm_object,
+                    GObject  *nm_client)
+{
+  CcWwanDevice *self;
+
+  g_return_val_if_fail (MM_IS_OBJECT (mm_object), NULL);
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+  g_return_val_if_fail (NM_IS_CLIENT (nm_client), NULL);
+#else
+  g_return_val_if_fail (!nm_client, NULL);
+#endif
+
+  self = g_object_new (CC_TYPE_WWAN_DEVICE, NULL);
+
+  self->mm_object = g_object_ref (mm_object);
+  self->modem = mm_object_get_modem (mm_object);
+  self->sim = mm_modem_get_sim_sync (self->modem, NULL, NULL);
+  g_set_object (&self->nm_client, nm_client);
+  if (self->sim)
+    {
+      self->operator_code = mm_sim_get_operator_identifier (self->sim);
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+      self->wwan_data = cc_wwan_data_new (mm_object,
+                                          NM_CLIENT (self->nm_client));
+#endif
+    }
+
+  g_signal_connect_object (self->mm_object, "notify::unlock-required",
+                           G_CALLBACK (cc_wwan_device_unlock_required_cb),
+                           self, G_CONNECT_SWAPPED);
+  if (self->wwan_data)
+    g_signal_connect_object (self->wwan_data, "notify::enabled",
+                             G_CALLBACK (cc_wwan_device_emit_data_changed),
+                             self, G_CONNECT_SWAPPED);
+
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+  g_signal_connect_object (self->nm_client, "notify::nm-running" ,
+                           G_CALLBACK (cc_wwan_device_nm_changed_cb), self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->nm_client, "device-added",
+                           G_CALLBACK (cc_wwan_device_nm_device_added_cb),
+                           self, G_CONNECT_SWAPPED);
+#endif
+
+  g_signal_connect_object (self->mm_object, "notify::modem3gpp",
+                           G_CALLBACK (cc_wwan_device_3gpp_changed_cb),
+                           self, G_CONNECT_SWAPPED);
+  g_signal_connect_object (self->modem, "notify::signal-quality",
+                           G_CALLBACK (cc_wwan_device_signal_quality_changed_cb),
+                           self, G_CONNECT_SWAPPED);
+
+  cc_wwan_device_3gpp_changed_cb (self);
+  g_signal_connect_object (self->modem, "notify::current-modes",
+                           G_CALLBACK (cc_wwan_device_mode_changed_cb),
+                           self, G_CONNECT_SWAPPED);
+
+  return self;
+}
+
+gboolean
+cc_wwan_device_has_sim (CcWwanDevice *self)
+{
+  MMModemStateFailedReason state_reason;
+
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+
+  state_reason = mm_modem_get_state_failed_reason (self->modem);
+
+  if (state_reason == MM_MODEM_STATE_FAILED_REASON_SIM_MISSING)
+    return FALSE;
+
+  return TRUE;
+}
+
+/**
+ * cc_wwan_device_get_lock:
+ * @self: a #CcWwanDevice
+ *
+ * Get the active device lock that is required to
+ * be unlocked for accessing device features.
+ *
+ * Returns: %TRUE if PIN enabled, %FALSE otherwise.
+ */
+MMModemLock
+cc_wwan_device_get_lock (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), MM_MODEM_LOCK_UNKNOWN);
+
+  return mm_modem_get_unlock_required (self->modem);
+}
+
+
+/**
+ * cc_wwan_device_get_sim_lock:
+ * @self: a #CcWwanDevice
+ *
+ * Get if SIM lock with PIN is enabled.  SIM PIN
+ * enabled doesn’t mean that SIM is locked.
+ * See cc_wwan_device_get_lock().
+ *
+ * Returns: %TRUE if PIN enabled, %FALSE otherwise.
+ */
+gboolean
+cc_wwan_device_get_sim_lock (CcWwanDevice *self)
+{
+  gboolean sim_lock;
+
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+
+  sim_lock = self->locks & MM_MODEM_3GPP_FACILITY_SIM;
+
+  return !!sim_lock;
+}
+
+guint
+cc_wwan_device_get_unlock_retries (CcWwanDevice *self,
+                                   MMModemLock   lock)
+{
+  MMUnlockRetries *retries;
+
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), 0);
+
+  retries = mm_modem_peek_unlock_retries (self->modem);
+
+  return mm_unlock_retries_get (retries, lock);
+}
+
+static void
+cc_wwan_device_pin_sent_cb (GObject      *object,
+                            GAsyncResult *result,
+                            gpointer      user_data)
+{
+  CcWwanDevice *self;
+  MMSim *sim = (MMSim *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  if (!mm_sim_send_pin_finish (sim, result, &error))
+    {
+      self = g_task_get_source_object (G_TASK (task));
+
+      g_clear_error (&self->error);
+      self->error = g_error_copy (error);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+void
+cc_wwan_device_send_pin (CcWwanDevice        *self,
+                         const gchar         *pin,
+                         GCancellable        *cancellable,
+                         GAsyncReadyCallback  callback,
+                         gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+  g_return_if_fail (MM_IS_SIM (self->sim));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (pin && *pin);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  mm_sim_send_pin (self->sim, pin, cancellable,
+                   cc_wwan_device_pin_sent_cb,
+                   g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_send_pin_finish (CcWwanDevice  *self,
+                                GAsyncResult  *result,
+                                GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_puk_sent_cb (GObject      *object,
+                            GAsyncResult *result,
+                            gpointer      user_data)
+{
+  CcWwanDevice *self;
+  MMSim *sim = (MMSim *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  if (!mm_sim_send_puk_finish (sim, result, &error))
+    {
+      self = g_task_get_source_object (G_TASK (task));
+
+      g_clear_error (&self->error);
+      self->error = g_error_copy (error);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+void
+cc_wwan_device_send_puk (CcWwanDevice        *self,
+                         const gchar         *puk,
+                         const gchar         *pin,
+                         GCancellable        *cancellable,
+                         GAsyncReadyCallback  callback,
+                         gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+  g_return_if_fail (MM_IS_SIM (self->sim));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (puk && *puk);
+  g_return_if_fail (pin && *pin);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  mm_sim_send_puk (self->sim, puk, pin, cancellable,
+                   cc_wwan_device_puk_sent_cb,
+                   g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_send_puk_finish (CcWwanDevice  *self,
+                                GAsyncResult  *result,
+                                GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_enable_pin_cb (GObject      *object,
+                              GAsyncResult *result,
+                              gpointer      user_data)
+{
+  CcWwanDevice *self;
+  MMSim *sim = (MMSim *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  if (!mm_sim_enable_pin_finish (sim, result, &error))
+    {
+      self = g_task_get_source_object (G_TASK (task));
+
+      g_clear_error (&self->error);
+      self->error = g_error_copy (error);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+void
+cc_wwan_device_enable_pin (CcWwanDevice        *self,
+                           const gchar         *pin,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (pin && *pin);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  mm_sim_enable_pin (self->sim, pin, cancellable,
+                     cc_wwan_device_enable_pin_cb,
+                     g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_enable_pin_finish (CcWwanDevice  *self,
+                                  GAsyncResult  *result,
+                                  GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_disable_pin_cb (GObject      *object,
+                               GAsyncResult *result,
+                               gpointer      user_data)
+{
+  CcWwanDevice *self;
+  MMSim *sim = (MMSim *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  if (!mm_sim_disable_pin_finish (sim, result, &error))
+    {
+      self = g_task_get_source_object (G_TASK (task));
+
+      g_clear_error (&self->error);
+      self->error = g_error_copy (error);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+void
+cc_wwan_device_disable_pin (CcWwanDevice        *self,
+                            const gchar         *pin,
+                            GCancellable        *cancellable,
+                            GAsyncReadyCallback  callback,
+                            gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (pin && *pin);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  mm_sim_disable_pin (self->sim, pin, cancellable,
+                      cc_wwan_device_disable_pin_cb,
+                      g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_disable_pin_finish (CcWwanDevice *self,
+                                   GAsyncResult *result,
+                                   GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_change_pin_cb (GObject      *object,
+                              GAsyncResult *result,
+                              gpointer      user_data)
+{
+  CcWwanDevice *self;
+  MMSim *sim = (MMSim *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  if (!mm_sim_change_pin_finish (sim, result, &error))
+    {
+      self = g_task_get_source_object (G_TASK (task));
+
+      g_clear_error (&self->error);
+      self->error = g_error_copy (error);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+void
+cc_wwan_device_change_pin (CcWwanDevice        *self,
+                           const gchar         *old_pin,
+                           const gchar         *new_pin,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (old_pin && *old_pin);
+  g_return_if_fail (new_pin && *new_pin);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  mm_sim_change_pin (self->sim, old_pin, new_pin, cancellable,
+                     cc_wwan_device_change_pin_cb,
+                     g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_change_pin_finish (CcWwanDevice  *self,
+                                  GAsyncResult  *result,
+                                  GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_network_mode_set_cb (GObject      *object,
+                                    GAsyncResult *result,
+                                    gpointer      user_data)
+{
+  CcWwanDevice *self;
+  MMModem *modem = (MMModem *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  if (!mm_modem_set_current_modes_finish (modem, result, &error))
+    {
+      self = g_task_get_source_object (G_TASK (task));
+
+      g_clear_error (&self->error);
+      self->error = g_error_copy (error);
+      g_warning ("Error: %s", error->message);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * cc_wwan_device_set_network_mode:
+ * @self: a #CcWwanDevice
+ * @allowed: The allowed #MMModemModes
+ * @preferred: The preferred #MMModemMode
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: (nullable): a #GAsyncReadyCallback or %NULL
+ * @user_data: (nullable): closure data for @callback
+ *
+ * Asynchronously set preferred network mode.
+ *
+ * Call @cc_wwan_device_set_current_mode_finish()
+ * in @callback to get the result of operation.
+ */
+void
+cc_wwan_device_set_current_mode (CcWwanDevice        *self,
+                                 MMModemMode          allowed,
+                                 MMModemMode          preferred,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  GPermission *permission;
+  g_autoptr(GError) error = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  permission = polkit_permission_new_sync ("org.freedesktop.ModemManager1.Device.Control",
+                                           NULL, cancellable, &error);
+  g_task_set_task_data (task, permission, g_object_unref);
+
+  if (error)
+    g_warning ("error: %s", error->message);
+
+  if (error)
+    g_task_return_error (task, g_steal_pointer (&error));
+  else if (!g_permission_get_allowed (permission))
+    {
+      error = g_error_new (G_IO_ERROR,
+                           G_IO_ERROR_PERMISSION_DENIED,
+                           "Access Denied");
+      g_clear_error (&self->error);
+      self->error = g_error_copy (error);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    mm_modem_set_current_modes (self->modem, allowed, preferred,
+                                cancellable, cc_wwan_device_network_mode_set_cb,
+                                g_steal_pointer (&task));
+}
+
+/**
+ * cc_wwan_device_set_current_mode_finish:
+ * @self: a #CcWwanDevice
+ * @result: a #GAsyncResult
+ * @error: a location for #GError or %NULL
+ *
+ * Get the status whether setting network mode
+ * succeeded
+ *
+ * Returns: %TRUE if network mode was successfully set,
+ * %FALSE otherwise.
+ */
+gboolean
+cc_wwan_device_set_current_mode_finish (CcWwanDevice  *self,
+                                        GAsyncResult  *result,
+                                        GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+gboolean
+cc_wwan_device_get_current_mode (CcWwanDevice *self,
+                                 MMModemMode  *allowed,
+                                 MMModemMode  *preferred)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+
+  return mm_modem_get_current_modes (self->modem, allowed, preferred);
+}
+
+gboolean
+cc_wwan_device_is_auto_network (CcWwanDevice *self)
+{
+  /*
+   * XXX: ModemManager Doesn’t have a true API to check
+   * if registration is automatic or manual.  So Let’s
+   * do some guess work.
+   */
+  if (self->registration_state == CC_WWAN_REGISTRATION_STATE_DENIED)
+    return FALSE;
+
+  return !self->network_is_manual;
+}
+
+CcWwanState
+cc_wwan_device_get_network_state (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), 0);
+
+  return self->registration_state;
+}
+
+gboolean
+cc_wwan_device_get_supported_modes (CcWwanDevice *self,
+                                    MMModemMode  *allowed,
+                                    MMModemMode  *preferred)
+{
+  g_autofree MMModemModeCombination *modes = NULL;
+  guint n_modes, i;
+
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+
+  if (!mm_modem_get_supported_modes (self->modem, &modes, &n_modes))
+    return FALSE;
+
+  if (allowed)
+    *allowed = 0;
+  if (preferred)
+    *preferred = 0;
+
+  for (i = 0; i < n_modes; i++)
+    {
+      if (allowed)
+        *allowed = *allowed | modes[i].allowed;
+      if (preferred)
+        *preferred = *preferred | modes[i].preferred;
+    }
+
+  return TRUE;
+}
+
+#define APPEND_MODE_TO_STRING(_str, _now, _preferred, _mode_str) do { \
+    if (_str->len > 0)                                                \
+      g_string_append (_str, ", ");                                   \
+    g_string_append (_str, _mode_str);                                \
+    if (_preferred == _now)                                           \
+      g_string_append (_str, _(" (Preferred)"));                      \
+  } while (0)
+
+gchar *
+cc_wwan_device_get_string_from_mode (CcWwanDevice *self,
+                                     MMModemMode   allowed,
+                                     MMModemMode   preferred)
+{
+  GString *str;
+
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+  g_return_val_if_fail (allowed != 0, NULL);
+
+  if (allowed == MM_MODEM_MODE_2G)
+    return g_strdup (_("2G Only"));
+  if (allowed == MM_MODEM_MODE_3G)
+    return g_strdup (_("3G Only"));
+  if (allowed == MM_MODEM_MODE_4G)
+    return g_strdup (_("4G Only"));
+
+  str = g_string_sized_new (10);
+
+  if (allowed & MM_MODEM_MODE_2G)
+    APPEND_MODE_TO_STRING (str, MM_MODEM_MODE_2G, preferred, "2G");
+  if (allowed & MM_MODEM_MODE_3G)
+    APPEND_MODE_TO_STRING (str, MM_MODEM_MODE_3G, preferred, "3G");
+  if (allowed & MM_MODEM_MODE_4G)
+    APPEND_MODE_TO_STRING (str, MM_MODEM_MODE_4G, preferred, "4G");
+
+  if (str->len == 0)
+    return g_string_free (str, TRUE);
+  else
+    return g_string_free (str, FALSE);
+}
+#undef APPEND_MODE_TO_STRING
+
+static void
+wwan_network_list_free (GList *network_list)
+{
+  g_list_free_full (network_list, (GDestroyNotify)mm_modem_3gpp_network_free);
+}
+
+static void
+cc_wwan_device_scan_complete_cb (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
+{
+  MMModem3gpp *modem_3gpp = (MMModem3gpp *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  GList *network_list;
+
+  network_list = mm_modem_3gpp_scan_finish (modem_3gpp, result, &error);
+
+  if (error)
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    g_task_return_pointer (task, network_list, (GDestroyNotify)wwan_network_list_free);
+}
+
+void
+cc_wwan_device_scan_networks (CcWwanDevice        *self,
+                              GCancellable        *cancellable,
+                              GAsyncReadyCallback  callback,
+                              gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  mm_modem_3gpp_scan (self->modem_3gpp, cancellable,
+                      cc_wwan_device_scan_complete_cb,
+                      g_steal_pointer (&task));
+}
+
+GList *
+cc_wwan_device_scan_networks_finish (CcWwanDevice  *self,
+                                     GAsyncResult  *result,
+                                     GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+cc_wwan_device_register_network_complete_cb (GObject      *object,
+                                             GAsyncResult *result,
+                                             gpointer      user_data)
+{
+  CcWwanDevice *self;
+  MMModem3gpp *modem_3gpp = (MMModem3gpp *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  if (!mm_modem_3gpp_register_finish (modem_3gpp, result, &error))
+    {
+      self = g_task_get_source_object (G_TASK (task));
+
+      g_clear_error (&self->error);
+      self->error = g_error_copy (error);
+      g_warning ("Error: %s", error->message);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
+
+      g_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+void
+cc_wwan_device_register_network (CcWwanDevice        *self,
+                                 const gchar         *network_id,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (CC_IS_WWAN_DEVICE (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  if (network_id && *network_id)
+    self->network_is_manual = TRUE;
+  else
+    self->network_is_manual = FALSE;
+
+  mm_modem_3gpp_register (self->modem_3gpp, network_id, cancellable,
+                          cc_wwan_device_register_network_complete_cb,
+                          g_steal_pointer (&task));
+}
+
+gboolean
+cc_wwan_device_register_network_finish (CcWwanDevice *self,
+                                        GAsyncResult *result,
+                                        GError       **error)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * cc_wwan_device_get_operator_name:
+ * @self: a #CcWwanDevice
+ *
+ * Get the human readable network operator name
+ * currently the device is connected to.
+ *
+ * Returns: (nullable): The operator name or %NULL
+ */
+const gchar *
+cc_wwan_device_get_operator_name (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  if (!self->modem_3gpp)
+    return NULL;
+
+  return mm_modem_3gpp_get_operator_name (self->modem_3gpp);
+}
+
+gchar *
+cc_wwan_device_dup_sim_identifier (CcWwanDevice *self)
+{
+  char *identifier;
+
+  identifier = mm_sim_dup_operator_name (self->sim);
+  if (identifier)
+    return identifier;
+
+  identifier = mm_sim_dup_operator_identifier (self->sim);
+  if (identifier)
+    return identifier;
+
+  identifier = mm_sim_dup_identifier (self->sim);
+  if (identifier)
+    return identifier;
+
+  return g_strdup ("");
+}
+
+gchar *
+cc_wwan_device_dup_network_type_string (CcWwanDevice *self)
+{
+  MMModemAccessTechnology type;
+
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  type = mm_modem_get_access_technologies (self->modem);
+
+  return mm_modem_access_technology_build_string_from_mask (type);
+}
+
+gchar *
+cc_wwan_device_dup_signal_string (CcWwanDevice *self)
+{
+  MMModemSignal *modem_signal;
+  MMSignal *signal;
+  GString *str;
+  gdouble value;
+  gboolean recent;
+
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  modem_signal = mm_object_peek_modem_signal (self->mm_object);
+
+  if (!modem_signal)
+    return g_strdup_printf ("%d%%", mm_modem_get_signal_quality (self->modem, &recent));
+
+  str = g_string_new ("");
+
+  /* Adapted from ModemManager mmcli-modem-signal.c */
+  signal = mm_modem_signal_peek_cdma (modem_signal);
+  if (signal)
+    {
+      if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "rssi: %.2g dBm ", value);
+      if ((value = mm_signal_get_ecio (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "ecio: %.2g dBm ", value);
+    }
+
+  signal = mm_modem_signal_peek_evdo (modem_signal);
+  if (signal)
+    {
+      if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "rssi: %.2g dBm ", value);
+      if ((value = mm_signal_get_ecio (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "ecio: %.2g dBm ", value);
+      if ((value = mm_signal_get_sinr (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "sinr: %.2g dB ", value);
+      if ((value = mm_signal_get_io (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "io: %.2g dBm ", value);
+    }
+
+  signal = mm_modem_signal_peek_gsm (modem_signal);
+  if (signal)
+    if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN)
+      g_string_append_printf (str, "rssi: %.2g dBm ", value);
+
+  signal = mm_modem_signal_peek_umts (modem_signal);
+  if (signal)
+    {
+      if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "rssi: %.2g dBm ", value);
+      if ((value = mm_signal_get_rscp (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "rscp: %.2g dBm ", value);
+      if ((value = mm_signal_get_ecio (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "ecio: %.2g dBm ", value);
+    }
+
+  signal = mm_modem_signal_peek_lte (modem_signal);
+  if (signal)
+    {
+      if ((value = mm_signal_get_rssi (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "rssi: %.2g dBm ", value);
+      if ((value = mm_signal_get_rsrq (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "rsrq: %.2g dB ", value);
+      if ((value = mm_signal_get_rsrp (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "rsrp: %.2g dBm ", value);
+      if ((value = mm_signal_get_snr (signal)) != MM_SIGNAL_UNKNOWN)
+        g_string_append_printf (str, "snr: %.2g dB ", value);
+    }
+
+  return g_string_free (str, FALSE);
+}
+
+const gchar *
+cc_wwan_device_get_manufacturer (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  return mm_modem_get_manufacturer (self->modem);
+}
+
+const gchar *
+cc_wwan_device_get_model (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  return mm_modem_get_model (self->modem);
+}
+
+const gchar *
+cc_wwan_device_get_firmware_version (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  return mm_modem_get_revision (self->modem);
+}
+
+const gchar *
+cc_wwan_device_get_identifier (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  return mm_modem_get_equipment_identifier (self->modem);
+}
+
+const gchar *
+cc_wwan_device_get_simple_error (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  if (!self->error)
+    return NULL;
+
+  return cc_wwan_error_get_message (self->error);
+}
+
+gboolean
+cc_wwan_device_is_nm_device (CcWwanDevice *self,
+                             GObject      *nm_device)
+{
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+  g_return_val_if_fail (NM_IS_DEVICE (nm_device), FALSE);
+
+  return g_str_equal (mm_modem_get_primary_port (self->modem),
+                      nm_device_get_iface (NM_DEVICE (nm_device)));
+#else
+  return FALSE;
+#endif
+}
+
+const gchar *
+cc_wwan_device_get_path (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), "");
+
+  return mm_object_get_path (self->mm_object);
+}
+
+CcWwanData *
+cc_wwan_device_get_data (CcWwanDevice *self)
+{
+  g_return_val_if_fail (CC_IS_WWAN_DEVICE (self), NULL);
+
+  return self->wwan_data;
+}
+
+gboolean
+cc_wwan_device_pin_valid (const gchar *password,
+                          MMModemLock  lock)
+{
+  size_t len;
+
+  g_return_val_if_fail (lock == MM_MODEM_LOCK_SIM_PIN  ||
+                        lock == MM_MODEM_LOCK_SIM_PIN2 ||
+                        lock == MM_MODEM_LOCK_SIM_PUK  ||
+                        lock == MM_MODEM_LOCK_SIM_PUK2, FALSE);
+  if (!password)
+    return FALSE;
+
+  len = strlen (password);
+
+  if (len < 4 || len > 8)
+    return FALSE;
+
+  if (strspn (password, "0123456789") != len)
+    return FALSE;
+
+  /*
+   * XXX: Can PUK code be something other than 8 digits?
+   * 3GPP standard seems mum on this
+   */
+  if (lock == MM_MODEM_LOCK_SIM_PUK ||
+      lock == MM_MODEM_LOCK_SIM_PUK2)
+    if (len != 8)
+      return FALSE;
+
+  return TRUE;
+}
diff --git a/plugins/wwan/cc-wwan-device.h b/plugins/wwan/cc-wwan-device.h
new file mode 100644
index 0000000..add27d3
--- /dev/null
+++ b/plugins/wwan/cc-wwan-device.h
@@ -0,0 +1,152 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-device.h
+ *
+ * Copyright 2019-2020 Purism SPC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 .
+ *
+ * Author(s):
+ *   Mohammed Sadiq 
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include 
+#include 
+
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+# include "cc-wwan-data.h"
+#endif
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+  CC_WWAN_REGISTRATION_STATE_UNKNOWN,
+  CC_WWAN_REGISTRATION_STATE_IDLE,
+  CC_WWAN_REGISTRATION_STATE_REGISTERED,
+  CC_WWAN_REGISTRATION_STATE_ROAMING,
+  CC_WWAN_REGISTRATION_STATE_SEARCHING,
+  CC_WWAN_REGISTRATION_STATE_DENIED
+} CcWwanState;
+
+typedef struct _CcWwanData CcWwanData;
+
+#define CC_TYPE_WWAN_DEVICE (cc_wwan_device_get_type())
+G_DECLARE_FINAL_TYPE (CcWwanDevice, cc_wwan_device, CC, WWAN_DEVICE, GObject)
+
+CcWwanDevice  *cc_wwan_device_new                (MMObject            *mm_object,
+                                                  GObject             *nm_client);
+gboolean       cc_wwan_device_has_sim            (CcWwanDevice        *self);
+MMModemLock    cc_wwan_device_get_lock           (CcWwanDevice        *self);
+gboolean       cc_wwan_device_get_sim_lock       (CcWwanDevice        *self);
+guint          cc_wwan_device_get_unlock_retries (CcWwanDevice        *self,
+                                                  MMModemLock          lock);
+void           cc_wwan_device_enable_pin         (CcWwanDevice        *self,
+                                                  const gchar         *pin,
+                                                  GCancellable        *cancellable,
+                                                  GAsyncReadyCallback  callback,
+                                                  gpointer             user_data);
+gboolean       cc_wwan_device_enable_pin_finish  (CcWwanDevice        *self,
+                                                  GAsyncResult        *result,
+                                                  GError             **error);
+void           cc_wwan_device_disable_pin        (CcWwanDevice        *self,
+                                                  const gchar         *pin,
+                                                  GCancellable        *cancellable,
+                                                  GAsyncReadyCallback  callback,
+                                                  gpointer             user_data);
+gboolean       cc_wwan_device_disable_pin_finish (CcWwanDevice        *self,
+                                                  GAsyncResult        *result,
+                                                  GError             **error);
+void           cc_wwan_device_send_pin           (CcWwanDevice        *self,
+                                                  const gchar         *pin,
+                                                  GCancellable        *cancellable,
+                                                  GAsyncReadyCallback  callback,
+                                                  gpointer             user_data);
+gboolean       cc_wwan_device_send_pin_finish    (CcWwanDevice        *self,
+                                                  GAsyncResult        *result,
+                                                  GError             **error);
+void          cc_wwan_device_send_puk            (CcWwanDevice        *self,
+                                                  const gchar         *puk,
+                                                  const gchar         *pin,
+                                                  GCancellable        *cancellable,
+                                                  GAsyncReadyCallback  callback,
+                                                  gpointer             user_data);
+gboolean      cc_wwan_device_send_puk_finish     (CcWwanDevice        *self,
+                                                  GAsyncResult        *result,
+                                                  GError             **error);
+void           cc_wwan_device_change_pin         (CcWwanDevice        *self,
+                                                  const gchar         *old_pin,
+                                                  const gchar         *new_pin,
+                                                  GCancellable        *cancellable,
+                                                  GAsyncReadyCallback  callback,
+                                                  gpointer             user_data);
+gboolean       cc_wwan_device_change_pin_finish  (CcWwanDevice        *self,
+                                                  GAsyncResult        *result,
+                                                  GError             **error);
+const gchar   *cc_wwan_device_get_operator_name  (CcWwanDevice        *self);
+gchar         *cc_wwan_device_dup_sim_identifier (CcWwanDevice        *self);
+gchar         *cc_wwan_device_dup_network_type_string (CcWwanDevice   *self);
+gchar         *cc_wwan_device_dup_signal_string  (CcWwanDevice        *self);
+const gchar   *cc_wwan_device_get_manufacturer   (CcWwanDevice        *self);
+const gchar   *cc_wwan_device_get_model          (CcWwanDevice        *self);
+const gchar   *cc_wwan_device_get_firmware_version (CcWwanDevice      *self);
+const gchar   *cc_wwan_device_get_identifier     (CcWwanDevice        *self);
+gboolean       cc_wwan_device_get_current_mode   (CcWwanDevice        *self,
+                                                  MMModemMode         *allowed,
+                                                  MMModemMode         *preferred);
+gboolean       cc_wwan_device_is_auto_network    (CcWwanDevice        *self);
+CcWwanState    cc_wwan_device_get_network_state  (CcWwanDevice        *self);
+gboolean       cc_wwan_device_get_supported_modes (CcWwanDevice       *self,
+                                                   MMModemMode        *allowed,
+                                                   MMModemMode        *preferred);
+void           cc_wwan_device_set_current_mode   (CcWwanDevice        *self,
+                                                  MMModemMode          allowed,
+                                                  MMModemMode          preferred,
+                                                  GCancellable        *cancellable,
+                                                  GAsyncReadyCallback  callback,
+                                                  gpointer             user_data);
+gboolean       cc_wwan_device_set_current_mode_finish (CcWwanDevice        *self,
+                                                       GAsyncResult        *result,
+                                                       GError             **error);
+gchar         *cc_wwan_device_get_string_from_mode    (CcWwanDevice        *self,
+                                                       MMModemMode          allowed,
+                                                       MMModemMode          preferred);
+void           cc_wwan_device_scan_networks           (CcWwanDevice        *self,
+                                                       GCancellable        *cancellable,
+                                                       GAsyncReadyCallback  callback,
+                                                       gpointer             user_data);
+GList         *cc_wwan_device_scan_networks_finish    (CcWwanDevice        *self,
+                                                       GAsyncResult        *result,
+                                                       GError             **error);
+void           cc_wwan_device_register_network        (CcWwanDevice        *self,
+                                                       const gchar         *network_id,
+                                                       GCancellable        *cancellable,
+                                                       GAsyncReadyCallback  callback,
+                                                       gpointer             user_data);
+gboolean       cc_wwan_device_register_network_finish (CcWwanDevice        *self,
+                                                       GAsyncResult        *result,
+                                                       GError             **error);
+const gchar   *cc_wwan_device_get_simple_error        (CcWwanDevice        *self);
+GSList        *cc_wwan_device_get_apn_list            (CcWwanDevice        *self);
+gboolean       cc_wwan_device_is_nm_device            (CcWwanDevice        *self,
+                                                       GObject             *nm_device);
+const gchar   *cc_wwan_device_get_path                (CcWwanDevice        *self);
+CcWwanData    *cc_wwan_device_get_data                (CcWwanDevice        *self);
+gboolean       cc_wwan_device_pin_valid               (const gchar         *password,
+                                                       MMModemLock          lock);
+
+G_END_DECLS
diff --git a/plugins/wwan/cc-wwan-errors-private.h b/plugins/wwan/cc-wwan-errors-private.h
new file mode 100644
index 0000000..955d6ee
--- /dev/null
+++ b/plugins/wwan/cc-wwan-errors-private.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-wwan-errors-private.h
+ *
+ * Copyright 2019 Purism SPC
+ *
+ * Modified from mm-error-helpers.c from ModemManager
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 .
+ *
+ * Author(s):
+ *   Mohammed Sadiq 
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include 
+#include 
+#include 
+
+typedef struct {
+  guint code;
+  const gchar *message;
+} ErrorTable;
+
+
+static ErrorTable me_errors[] = {
+  { MM_MOBILE_EQUIPMENT_ERROR_PHONE_FAILURE,                      N_("Phone failure") },
+  { MM_MOBILE_EQUIPMENT_ERROR_NO_CONNECTION,                      N_("No connection to phone") },
+  { MM_MOBILE_EQUIPMENT_ERROR_LINK_RESERVED,                      "Phone-adaptor link reserved" },
+  { MM_MOBILE_EQUIPMENT_ERROR_NOT_ALLOWED,                        N_("Operation not allowed") },
+  { MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED,                      N_("Operation not supported") },
+  { MM_MOBILE_EQUIPMENT_ERROR_PH_SIM_PIN,                         "PH-SIM PIN required" },
+  { MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PIN,                        "PH-FSIM PIN required" },
+  { MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PUK,                        "PH-FSIM PUK required" },
+  { MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED,                   N_("SIM not inserted") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN,                            N_("SIM PIN required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK,                            N_("SIM PUK required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SIM_FAILURE,                        N_("SIM failure") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SIM_BUSY,                           N_("SIM busy") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG,                          N_("SIM wrong") },
+  { MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD,                 N_("Incorrect password") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN2,                           N_("SIM PIN2 required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK2,                           N_("SIM PUK2 required") },
+  { MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FULL,                        "Memory full" },
+  { MM_MOBILE_EQUIPMENT_ERROR_INVALID_INDEX,                      "Invalid index" },
+  { MM_MOBILE_EQUIPMENT_ERROR_NOT_FOUND,                          "Not found" },
+  { MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FAILURE,                     "Memory failure" },
+  { MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK,                         "No network service" },
+  { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT,                    "Network timeout" },
+  { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_NOT_ALLOWED,                "Network not allowed - emergency calls only" },
+  { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PIN,                        "Network personalization PIN required" },
+  { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PUK,                        "Network personalization PUK required" },
+  { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PIN,                 "Network subset personalization PIN required" },
+  { MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PUK,                 "Network subset personalization PUK required" },
+  { MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PIN,                        "Service provider personalization PIN required" },
+  { MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PUK,                        "Service provider personalization PUK required" },
+  { MM_MOBILE_EQUIPMENT_ERROR_CORP_PIN,                           "Corporate personalization PIN required" },
+  { MM_MOBILE_EQUIPMENT_ERROR_CORP_PUK,                           "Corporate personalization PUK required" },
+  { MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN,                            N_("Unknown error") },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ILLEGAL_MS,                    "Illegal MS" },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ILLEGAL_ME,                    "Illegal ME" },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_NOT_ALLOWED,           "GPRS services not allowed" },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_PLMN_NOT_ALLOWED,              "PLMN not allowed" },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_LOCATION_NOT_ALLOWED,          "Location area not allowed" },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_ROAMING_NOT_ALLOWED,           "Roaming not allowed in this location area" },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_NOT_SUPPORTED,  "Service option not supported" },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_NOT_SUBSCRIBED, "Requested service option not subscribed" },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_SERVICE_OPTION_OUT_OF_ORDER,   "Service option temporarily out of order" },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_UNKNOWN,                       "Unspecified GPRS error" },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_PDP_AUTH_FAILURE,              "PDP authentication failure" },
+  { MM_MOBILE_EQUIPMENT_ERROR_GPRS_INVALID_MOBILE_CLASS,          "Invalid mobile class" },
+};
+
+static inline const gchar *
+cc_wwan_error_get_message (GError *error)
+{
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    return _("Action Cancelled");
+
+ if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED))
+   return _("Access denied");
+
+  if (error->domain != MM_MOBILE_EQUIPMENT_ERROR)
+    return error->message;
+
+  for (guint i = 0; i < G_N_ELEMENTS (me_errors); i++)
+    if (me_errors[i].code == error->code)
+      return _(me_errors[i].message);
+
+  return _("Unknown Error");
+}
diff --git a/plugins/wwan/gsd-wwan-manager.c b/plugins/wwan/gsd-wwan-manager.c
new file mode 100644
index 0000000..c228837
--- /dev/null
+++ b/plugins/wwan/gsd-wwan-manager.c
@@ -0,0 +1,830 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2019 Purism SPC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 .
+ *
+ * Author: Guido Günther 
+ *
+ */
+
+#include "config.h"
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+
+#define GCR_API_SUBJECT_TO_CHANGE
+#ifdef HAVE_GCR3
+#include 
+#else
+#include 
+#endif
+
+#include "gnome-settings-profile.h"
+#include "cc-wwan-device.h"
+#include "cc-wwan-errors-private.h"
+#include "gsd-wwan-manager.h"
+
+
+struct _GsdWwanManager
+{
+        GObject parent;
+
+        guint      start_idle_id;
+        gboolean   unlock;
+        GSettings *settings;
+
+        /* List of all devices not in ‘devices_to_unlock’ */
+        GPtrArray *devices;
+        GPtrArray *devices_to_unlock;
+
+        /* Currently shown prompt and device being unlocked */
+        GcrPrompt    *prompt;
+        CcWwanDevice *unlocking_device;
+        GCancellable *cancellable;
+        char         *puk_code;      /* Used only for PUK unlock */
+        guint         prompt_timeout_id;
+
+        MMManager *mm1;
+        gboolean  mm1_running;
+};
+
+enum {
+        PROP_0,
+        PROP_UNLOCK_SIM,
+        PROP_LAST_PROP,
+};
+static GParamSpec *props[PROP_LAST_PROP];
+
+#define GSD_WWAN_SCHEMA_DIR "org.gnome.settings-daemon.plugins.wwan"
+#define GSD_WWAN_SCHEMA_UNLOCK_SIM "unlock-sim"
+
+G_DEFINE_TYPE (GsdWwanManager, gsd_wwan_manager, G_TYPE_OBJECT)
+
+/* The plugin's manager object */
+static gpointer manager_object = NULL;
+
+static void wwan_manager_ensure_unlocking   (GsdWwanManager *self);
+static void wwan_manager_unlock_device      (CcWwanDevice   *device,
+                                             gpointer        user_data);
+static void wwan_manager_unlock_required_cb (GsdWwanManager *self,
+                                             GParamSpec     *pspec,
+                                             CcWwanDevice   *device);
+
+static void
+manager_unlock_prompt_new (GsdWwanManager *self,
+                           CcWwanDevice   *device,
+                           MMModemLock     lock,
+                           const char     *msg,
+                           gboolean        new_password)
+{
+        g_autoptr(GError) error = NULL;
+        g_autofree gchar *identifier = NULL;
+        g_autofree gchar *description = NULL;
+        g_autofree gchar *warning = NULL;
+        const gchar *message = NULL;
+        guint retries;
+
+        identifier = cc_wwan_device_dup_sim_identifier (device);
+        g_debug ("Creating new PIN/PUK dialog for SIM %s", identifier);
+
+        if (!self->prompt)
+                self->prompt = gcr_system_prompt_open (-1, self->cancellable, &error);
+
+        if (!self->prompt) {
+                if (error->code == GCR_SYSTEM_PROMPT_IN_PROGRESS)
+                        g_warning ("Another Gcr system prompt is already in progress.");
+                else
+                        g_warning ("Couldn't create prompt for SIM Code entry: %s", error->message);
+                return;
+        }
+
+        /* Set up the dialog  */
+        if (new_password) {
+                gcr_prompt_set_title (self->prompt, _("New PIN for SIM"));
+                gcr_prompt_set_continue_label (self->prompt, _("Set"));
+        } else {
+                gcr_prompt_set_title (self->prompt, _("Unlock SIM card"));
+                gcr_prompt_set_continue_label (self->prompt, _("Unlock"));
+        }
+
+        gcr_prompt_set_cancel_label (self->prompt, _("Cancel"));
+        gcr_prompt_set_password_new (self->prompt, new_password);
+
+        if (lock == MM_MODEM_LOCK_SIM_PIN) {
+                if (new_password) {
+                        description = g_strdup_printf (_("Please provide a new PIN for SIM card %s"),
+                                                       identifier);
+                        message = _("Enter a New PIN to unlock your SIM card");
+                } else {
+                        description = g_strdup_printf (_("Please provide the PIN for SIM card %s"),
+                                                       identifier);
+                        message = _("Enter PIN to unlock your SIM card");
+                }
+        } else if (lock == MM_MODEM_LOCK_SIM_PUK) {
+                description = g_strdup_printf (_("Please provide the PUK for SIM card %s"),
+                                               identifier);
+                message = _("Enter PUK to unlock your SIM card");
+        } else {
+                g_warning ("Unsupported lock type: %u", lock);
+                g_clear_object (&self->prompt);
+                return;
+        }
+
+        gcr_prompt_set_description (self->prompt, description);
+        gcr_prompt_set_message (self->prompt, message);
+
+        if (!new_password)
+                retries = cc_wwan_device_get_unlock_retries (device, lock);
+
+        if (!new_password && retries != MM_UNLOCK_RETRIES_UNKNOWN) {
+                if (msg) {
+                        /* msg is already localised */
+                        warning = g_strdup_printf (ngettext ("%2$s. You have %1$u try left",
+                                                             "%2$s. You have %1$u tries left", retries),
+                                                   retries, msg);
+                } else {
+                        warning = g_strdup_printf (ngettext ("You have %u try left",
+                                                             "You have %u tries left", retries),
+                                                   retries);
+                }
+        } else if (msg) {
+                warning = g_strdup (msg);
+        }
+
+        gcr_prompt_set_warning (self->prompt, warning);
+
+        /* TODO */
+        /* if (lock == MM_MODEM_LOCK_SIM_PIN) */
+        /*         gcr_prompt_set_choice_label (prompt, _("Automatically unlock this SIM card")); */
+}
+
+static gboolean
+unlock_device (gpointer user_data)
+{
+        GsdWwanManager *self;
+        CcWwanDevice *device;
+        g_autoptr(GTask) task = user_data;
+        MMModemLock lock;
+
+        g_assert (G_IS_TASK (task));
+
+        self = g_task_get_task_data (task);
+        device = g_task_get_source_object (task);
+
+        g_assert (GSD_IS_WWAN_MANAGER (self));
+        g_assert (CC_IS_WWAN_DEVICE (device));
+
+        self->prompt_timeout_id = 0;
+
+        if (g_task_return_error_if_cancelled (task))
+                return G_SOURCE_REMOVE;
+
+        lock = cc_wwan_device_get_lock (device);
+
+        if (lock != MM_MODEM_LOCK_SIM_PIN &&
+            lock != MM_MODEM_LOCK_SIM_PUK) {
+                g_cancellable_cancel (g_task_get_cancellable (task));
+                g_task_return_error_if_cancelled (task);
+                return G_SOURCE_REMOVE;
+        }
+
+        wwan_manager_unlock_device (device, g_steal_pointer (&task));
+
+        return G_SOURCE_REMOVE;
+}
+
+static void
+wwan_manager_password_sent_cb (GObject      *object,
+                               GAsyncResult *result,
+                               gpointer      user_data)
+{
+        GsdWwanManager *self;
+        CcWwanDevice *device = (CcWwanDevice *)object;
+        g_autoptr(GTask) task = user_data;
+        g_autoptr(GError) error = NULL;
+        gboolean ret;
+
+        g_assert (CC_IS_WWAN_DEVICE (device));
+        g_assert (G_IS_TASK (task));
+
+        self = g_task_get_task_data (task);
+        g_assert (GSD_IS_WWAN_MANAGER (self));
+
+        if (self->puk_code)
+                ret = cc_wwan_device_send_puk_finish (device, result, &error);
+        else
+                ret = cc_wwan_device_send_pin_finish (device, result, &error);
+
+        g_clear_pointer (&self->puk_code, gcr_secure_memory_free);
+
+        /* Ask again if a failable error occured */
+        if (error &&
+            (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD) ||
+             g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK) ||
+             g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN))) {
+                g_object_set_data (G_OBJECT (task), "error", (gpointer)cc_wwan_error_get_message (error));
+                /* ModemManager updates the lock status after some delay.  Wait around 250 milliseconds
+                 * so that the values are updated.
+                 */
+                self->prompt_timeout_id = g_timeout_add (250, unlock_device, g_steal_pointer (&task));
+
+                return;
+        }
+
+        if (ret)
+                g_task_return_boolean (task, TRUE);
+        else
+                g_task_return_error (task, error);
+
+}
+
+static gboolean
+wwan_manager_unlock_device_finish (CcWwanDevice  *self,
+                                   GAsyncResult  *result,
+                                   GError       **error)
+{
+        return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static const char *
+wwan_manager_show_prompt (GsdWwanManager *self,
+                          CcWwanDevice   *device,
+                          GTask          *task)
+{
+        g_autoptr(GError) error = NULL;
+        const char *code;
+
+        g_assert (GSD_IS_WWAN_MANAGER (self));
+        g_assert (CC_IS_WWAN_DEVICE (device));
+        g_assert (G_IS_TASK (task));
+
+        if (!self->prompt) {
+                g_task_return_new_error (task,
+                                         G_IO_ERROR,
+                                         G_IO_ERROR_FAILED,
+                                         "Failed to create a new prompt");
+                return NULL;
+        }
+
+        g_set_object (&self->unlocking_device, device);
+
+        /* Irritate user if an empty password is provided */
+        do {
+                code = gcr_prompt_password_run (self->prompt, self->cancellable, &error);
+        } while (code && !*code);
+
+        if (error) {
+                g_task_return_error (task, g_steal_pointer (&error));
+                return NULL;
+        }
+
+        /* User cancelled the dialog */
+        if (!code) {
+                g_cancellable_cancel (g_task_get_cancellable (task));
+                g_task_return_error_if_cancelled (task);
+                return NULL;
+        }
+
+        return code;
+}
+
+static void
+wwan_manager_unlock_device (CcWwanDevice *device,
+                            gpointer      user_data)
+{
+        GsdWwanManager *self;
+        g_autoptr(GTask) task = user_data;
+        GCancellable *cancellable;
+        const char *code, *error_msg;
+        MMModemLock lock;
+
+        g_assert (CC_IS_WWAN_DEVICE (device));
+        g_assert (G_IS_TASK (task));
+
+        self = g_task_get_task_data (task);
+        g_assert (GSD_IS_WWAN_MANAGER (self));
+
+        error_msg = g_object_get_data (G_OBJECT (task), "error");
+        lock = cc_wwan_device_get_lock (device);
+        manager_unlock_prompt_new (self, device, lock, error_msg, FALSE);
+        g_object_set_data (G_OBJECT (task), "error", NULL);
+
+        code = wwan_manager_show_prompt (self, device, task);
+        if (!code)
+                return;
+
+        if (lock == MM_MODEM_LOCK_SIM_PUK) {
+                gcr_secure_memory_free (self->puk_code);
+                self->puk_code = gcr_secure_memory_strdup (code);
+
+                manager_unlock_prompt_new (self, device, MM_MODEM_LOCK_SIM_PIN, NULL, TRUE);
+                code = wwan_manager_show_prompt (self, device, task);
+                if (!code)
+                        return;
+        }
+
+        cancellable = g_task_get_cancellable (task);
+
+        if (lock == MM_MODEM_LOCK_SIM_PIN)
+                cc_wwan_device_send_pin (device, code, cancellable,
+                                         wwan_manager_password_sent_cb,
+                                         g_steal_pointer (&task));
+        else if (lock == MM_MODEM_LOCK_SIM_PUK)
+                cc_wwan_device_send_puk (device, self->puk_code, code, cancellable,
+                                         wwan_manager_password_sent_cb,
+                                         g_steal_pointer (&task));
+}
+
+static void
+wwan_manager_unlock_device_cb (GObject      *object,
+                               GAsyncResult *result,
+                               gpointer      user_data)
+{
+        g_autoptr(GsdWwanManager) self = user_data;
+        CcWwanDevice *device = (CcWwanDevice *)object;
+        g_autoptr(GError) error = NULL;
+
+        g_assert (GSD_IS_WWAN_MANAGER (self));
+        g_assert (CC_IS_WWAN_DEVICE (device));
+        g_assert (G_IS_TASK (result));
+
+        wwan_manager_unlock_device_finish (device, result, &error);
+
+        /* Move the device from devices to unlock to the list of devices */
+        if (g_ptr_array_remove (self->devices_to_unlock, device))
+                g_ptr_array_add (self->devices, g_object_ref (device));
+
+        g_clear_pointer (&self->puk_code, gcr_secure_memory_free);
+        g_clear_object (&self->prompt);
+        g_clear_object (&self->cancellable);
+        g_clear_object (&self->unlocking_device);
+        g_clear_handle_id (&self->prompt_timeout_id, g_source_remove);
+
+        /* Unlock the next device */
+        if (self->devices_to_unlock->len)
+                wwan_manager_unlock_required_cb (self, NULL, self->devices_to_unlock->pdata[0]);
+
+        if (error)
+                g_debug ("Error unlocking device: %s", error->message);
+
+        if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                g_warning ("Error unlocking device: %s", error->message);
+}
+
+static void
+wwan_manager_unlock_required_cb (GsdWwanManager *self,
+                                 GParamSpec     *pspec,
+                                 CcWwanDevice   *device)
+{
+        MMModemLock lock;
+
+        g_assert (GSD_IS_WWAN_MANAGER (self));
+        g_assert (CC_IS_WWAN_DEVICE (device));
+
+        lock = cc_wwan_device_get_lock (device);
+
+        if (lock != MM_MODEM_LOCK_SIM_PIN &&
+            lock != MM_MODEM_LOCK_SIM_PUK) {
+                g_object_ref (device);
+
+                /* Move the device from devices to unlock to the list of devices */
+                if (g_ptr_array_remove (self->devices_to_unlock, device))
+                        g_ptr_array_add (self->devices, device);
+
+                /* If the device is the device being unlocked, cancel the process */
+                if (device == self->unlocking_device)
+                        g_cancellable_cancel (self->cancellable);
+        } else if (lock == MM_MODEM_LOCK_SIM_PIN ||
+                   lock == MM_MODEM_LOCK_SIM_PUK) {
+                g_object_ref (device);
+
+                /* Move the device to devices to unlock from the list of devices */
+                if (g_ptr_array_remove (self->devices, device)) {
+                        g_ptr_array_add (self->devices_to_unlock, device);
+                        wwan_manager_ensure_unlocking (self);
+                }
+        }
+}
+
+
+static gboolean
+device_match_by_object (CcWwanDevice *device, GDBusObject *object)
+{
+        const char *device_path, *object_path;
+
+        g_return_val_if_fail (G_IS_DBUS_OBJECT (object), FALSE);
+        g_return_val_if_fail (CC_IS_WWAN_DEVICE (device), FALSE);
+
+        device_path = cc_wwan_device_get_path (device);
+        object_path = mm_object_get_path (MM_OBJECT (object));
+
+        return g_strcmp0 (device_path, object_path) == 0;
+}
+
+/*
+ * @array: (out) (nullable):
+ * @index: (out) (nullable):
+ *
+ * Returns: %TRUE if found.  %FALSE otherwise
+ */
+static gboolean
+wwan_manager_find_match (GsdWwanManager  *self,
+                         GDBusObject     *object,
+                         GPtrArray      **array,
+                         guint           *index)
+{
+        GPtrArray *devices = NULL;
+        guint i = 0;
+
+        g_return_val_if_fail (G_IS_DBUS_OBJECT (object), FALSE);
+
+        if (g_ptr_array_find_with_equal_func (self->devices,
+                                              object,
+                                              (GEqualFunc) device_match_by_object,
+                                              &i))
+                devices = self->devices;
+        else if (g_ptr_array_find_with_equal_func (self->devices_to_unlock,
+                                                     object,
+                                                     (GEqualFunc) device_match_by_object,
+                                                     &i))
+                devices = self->devices_to_unlock;
+
+        if (index && i >= 0)
+                *index = i;
+        if (array)
+                *array = devices;
+
+        if (devices)
+                return TRUE;
+
+        return FALSE;
+}
+
+
+static void
+wwan_manager_ensure_unlocking (GsdWwanManager *self)
+{
+        CcWwanDevice *device;
+        GTask *task;
+
+        g_assert (GSD_WWAN_MANAGER (self));
+
+        if (!self->unlock || self->unlocking_device)
+                return;
+
+        if (self->devices_to_unlock->len == 0)
+                return;
+
+        g_warn_if_fail (!self->cancellable);
+        g_clear_object (&self->cancellable);
+
+        device = self->devices_to_unlock->pdata[0];
+        self->cancellable = g_cancellable_new ();
+        task = g_task_new (device, self->cancellable,
+                           wwan_manager_unlock_device_cb,
+                           g_object_ref (self));
+        g_task_set_task_data (task, g_object_ref (self), g_object_unref);
+
+        wwan_manager_unlock_device (device, task);
+}
+
+static void
+gsd_wwan_manager_cache_mm_object (GsdWwanManager *self, MMObject *obj)
+{
+        const gchar *modem_object_path;
+        CcWwanDevice *wwan_device;
+        MMModemLock lock;
+
+        modem_object_path = g_dbus_object_get_object_path (G_DBUS_OBJECT (obj));
+        g_return_if_fail (modem_object_path);
+
+        /* This shouldn’t happen, so warn and return if this happen. */
+        if (wwan_manager_find_match (self, G_DBUS_OBJECT (obj), NULL, NULL)) {
+                g_warning("Device %s already tracked", modem_object_path);
+                return;
+        }
+
+        g_debug ("Tracking device at: %s", modem_object_path);
+        wwan_device = cc_wwan_device_new (MM_OBJECT (obj), NULL);
+        lock = cc_wwan_device_get_lock (wwan_device);
+        if (lock == MM_MODEM_LOCK_SIM_PIN ||
+            lock == MM_MODEM_LOCK_SIM_PUK)
+                g_ptr_array_add (self->devices_to_unlock, wwan_device);
+        else
+                g_ptr_array_add (self->devices, wwan_device);
+
+        g_signal_connect_object (wwan_device, "notify::unlock-required",
+                                 G_CALLBACK (wwan_manager_unlock_required_cb),
+                                 self, G_CONNECT_SWAPPED);
+        wwan_manager_ensure_unlocking (self);
+}
+
+
+static void
+object_added_cb (GsdWwanManager *self, GDBusObject *object, GDBusObjectManager *obj_manager)
+{
+        g_return_if_fail (GSD_IS_WWAN_MANAGER (self));
+        g_return_if_fail (G_IS_DBUS_OBJECT_MANAGER (obj_manager));
+
+        gsd_wwan_manager_cache_mm_object (self, MM_OBJECT(object));
+}
+
+
+static void
+object_removed_cb (GsdWwanManager     *self,
+                   GDBusObject        *object,
+                   GDBusObjectManager *obj_manager)
+{
+        CcWwanDevice *device;
+        GPtrArray *devices;
+        guint index;
+
+        g_return_if_fail (GSD_IS_WWAN_MANAGER (self));
+        g_return_if_fail (G_IS_DBUS_OBJECT_MANAGER (obj_manager));
+
+        if (!wwan_manager_find_match (self, object, &devices, &index))
+                g_return_if_reached ();
+
+        device = g_ptr_array_index (devices, index);
+
+        g_ptr_array_remove_index (devices, index);
+
+        if (device == self->unlocking_device)
+                g_cancellable_cancel (self->cancellable);
+}
+
+
+static void
+mm1_name_owner_changed_cb (GDBusObjectManagerClient *client, GParamSpec *pspec, GsdWwanManager *self)
+{
+        g_autofree gchar *name_owner = NULL;
+
+        name_owner = g_dbus_object_manager_client_get_name_owner (client);
+        self->mm1_running = !!name_owner;
+        g_debug ("mm name owned: %d", self->mm1_running);
+
+        if (!self->mm1_running) {
+                /* Drop all devices when MM goes away */
+                g_ptr_array_set_size (self->devices, 0);
+                g_ptr_array_set_size (self->devices_to_unlock, 0);
+
+                g_clear_object (&self->prompt);
+                g_clear_pointer (&self->puk_code, gcr_secure_memory_free);
+                g_clear_object (&self->unlocking_device);
+
+                return;
+        }
+}
+
+
+static void
+get_all_modems (GsdWwanManager *self)
+{
+        GList *list, *l;
+
+        g_return_if_fail (MM_IS_MANAGER (self->mm1));
+
+        list = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self->mm1));
+        for (l = list; l != NULL; l = l->next)
+                gsd_wwan_manager_cache_mm_object (self, MM_OBJECT(l->data));
+        g_list_free_full (list, g_object_unref);
+}
+
+
+static void
+mm1_manager_new_cb (GDBusConnection *connection, GAsyncResult *res, GsdWwanManager *self)
+{
+        g_autoptr(GError) error = NULL;
+
+        self->mm1 = mm_manager_new_finish (res, &error);
+        if (self->mm1) {
+                /* Listen for added/removed modems */
+                g_signal_connect_object (self->mm1,
+                                         "object-added",
+                                         G_CALLBACK (object_added_cb),
+                                         self,
+                                         G_CONNECT_SWAPPED);
+
+                g_signal_connect_object (self->mm1,
+                                         "object-removed",
+                                         G_CALLBACK (object_removed_cb),
+                                         self,
+                                         G_CONNECT_SWAPPED);
+
+                /* Listen for name owner changes */
+                g_signal_connect (self->mm1,
+                                  "notify::name-owner",
+                                  G_CALLBACK (mm1_name_owner_changed_cb),
+                                  self);
+
+                /* Handle all modems already known to MM */
+                get_all_modems (self);
+        } else {
+                g_warning ("Error connecting to D-Bus: %s", error->message);
+        }
+}
+
+
+static void
+set_modem_manager (GsdWwanManager *self)
+{
+        GDBusConnection *system_bus;
+        g_autoptr(GError) error = NULL;
+
+        system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+        if (system_bus) {
+                mm_manager_new (system_bus,
+                                G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
+                                NULL,
+                                (GAsyncReadyCallback) mm1_manager_new_cb,
+                                self);
+                g_object_unref (system_bus);
+        } else {
+                g_warning ("Error connecting to system D-Bus: %s", error->message);
+        }
+}
+
+
+static gboolean
+start_wwan_idle_cb (GsdWwanManager *self)
+{
+        g_debug ("Idle starting wwan manager");
+        gnome_settings_profile_start (NULL);
+
+        g_return_val_if_fail(GSD_IS_WWAN_MANAGER (self), FALSE);
+        self->settings = g_settings_new (GSD_WWAN_SCHEMA_DIR);
+        g_settings_bind (self->settings, "unlock-sim", self, "unlock-sim", G_SETTINGS_BIND_GET);
+
+        set_modem_manager (self);
+        gnome_settings_profile_end (NULL);
+        self->start_idle_id = 0;
+
+        return FALSE;
+}
+
+gboolean
+gsd_wwan_manager_start (GsdWwanManager *self,
+                        GError        **error)
+{
+        g_debug ("Starting wwan manager");
+        g_return_val_if_fail(GSD_IS_WWAN_MANAGER (self), FALSE);
+
+        gnome_settings_profile_start (NULL);
+        self->start_idle_id = g_idle_add ((GSourceFunc) start_wwan_idle_cb, self);
+        g_source_set_name_by_id (self->start_idle_id, "[gnome-settings-daemon] start_wwan_idle_cb");
+
+        gnome_settings_profile_end (NULL);
+        return TRUE;
+}
+
+void
+gsd_wwan_manager_stop (GsdWwanManager *self)
+{
+        g_debug ("Stopping wwan manager");
+}
+
+
+static void
+gsd_wwan_manager_set_unlock_sim (GsdWwanManager *self, gboolean unlock)
+{
+        if (self->unlock == unlock)
+                return;
+
+        self->unlock = unlock;
+
+        /*
+         * XXX: Should the devices in ‘self->devices’ be moved to
+         * ‘self->devices_to_unlock’ if required?  Otherwise, no prompt
+         * will be shown for devices the user explicitly cancelled
+         * unlock prompt.
+         */
+        /* Unlock the first device if no device is being unlocked.  Unlocking
+         * the rest will be handled appropriately after this is finished. */
+        if (self->unlock && self->devices_to_unlock->len > 0 && !self->unlocking_device)
+                wwan_manager_unlock_required_cb (self, NULL,
+                                                 self->devices_to_unlock->pdata[0]);
+
+        g_object_notify_by_pspec (G_OBJECT (self), props[PROP_UNLOCK_SIM]);
+}
+
+
+static void
+gsd_wwan_manager_set_property (GObject        *object,
+                               guint           prop_id,
+                               const GValue   *value,
+                               GParamSpec     *pspec)
+{
+        GsdWwanManager *self = GSD_WWAN_MANAGER (object);
+
+        switch (prop_id) {
+        case PROP_UNLOCK_SIM:
+                gsd_wwan_manager_set_unlock_sim (self, g_value_get_boolean (value));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gsd_wwan_manager_get_property (GObject        *object,
+                               guint           prop_id,
+                               GValue         *value,
+                               GParamSpec     *pspec)
+{
+        GsdWwanManager *self = GSD_WWAN_MANAGER (object);
+
+        switch (prop_id) {
+        case PROP_UNLOCK_SIM:
+                g_value_set_boolean (value, self->unlock);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gsd_wwan_manager_dispose (GObject *object)
+{
+        GsdWwanManager *self = GSD_WWAN_MANAGER (object);
+
+        if (self->mm1) {
+                self->mm1_running = FALSE;
+                g_clear_object (&self->mm1);
+        }
+
+        if (self->cancellable)
+                g_cancellable_cancel (self->cancellable);
+        g_clear_object (&self->cancellable);
+        g_clear_handle_id (&self->prompt_timeout_id, g_source_remove);
+        g_clear_object (&self->unlocking_device);
+        g_clear_pointer (&self->puk_code, gcr_secure_memory_free);
+        g_clear_object (&self->prompt);
+
+        g_clear_pointer (&self->devices, g_ptr_array_unref);
+        g_clear_pointer (&self->devices_to_unlock, g_ptr_array_unref);
+        g_clear_object (&self->settings);
+
+        G_OBJECT_CLASS (gsd_wwan_manager_parent_class)->dispose (object);
+}
+
+static void
+gsd_wwan_manager_class_init (GsdWwanManagerClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->get_property = gsd_wwan_manager_get_property;
+        object_class->set_property = gsd_wwan_manager_set_property;
+        object_class->dispose = gsd_wwan_manager_dispose;
+
+        props[PROP_UNLOCK_SIM] =
+                g_param_spec_boolean ("unlock-sim",
+                                      "unlock-sim",
+                                      "Whether to unlock new sims right away",
+                                      FALSE,
+                                      G_PARAM_READWRITE |
+                                      G_PARAM_EXPLICIT_NOTIFY |
+                                      G_PARAM_STATIC_STRINGS);
+        g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
+}
+
+static void
+gsd_wwan_manager_init (GsdWwanManager *self)
+{
+        self->devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+        self->devices_to_unlock = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+}
+
+
+GsdWwanManager *
+gsd_wwan_manager_new (void)
+{
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_WWAN_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+        }
+
+        return GSD_WWAN_MANAGER (manager_object);
+}
diff --git a/plugins/wwan/gsd-wwan-manager.h b/plugins/wwan/gsd-wwan-manager.h
new file mode 100644
index 0000000..127d3d2
--- /dev/null
+++ b/plugins/wwan/gsd-wwan-manager.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2019 Purism SPC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 .
+ *
+ * Author: Guido Günther 
+ *
+ */
+
+# pragma once
+
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_WWAN_MANAGER (gsd_wwan_manager_get_type())
+G_DECLARE_FINAL_TYPE (GsdWwanManager, gsd_wwan_manager, GSD, WWAN_MANAGER, GObject)
+
+GsdWwanManager *        gsd_wwan_manager_new                 (void);
+gboolean                gsd_wwan_manager_start               (GsdWwanManager *manager,
+                                                              GError        **error);
+void                    gsd_wwan_manager_stop                (GsdWwanManager *manager);
+
+G_END_DECLS
diff --git a/plugins/wwan/main.c b/plugins/wwan/main.c
new file mode 100644
index 0000000..c6adebd
--- /dev/null
+++ b/plugins/wwan/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_wwan_manager_new
+#define START gsd_wwan_manager_start
+#define STOP gsd_wwan_manager_stop
+#define MANAGER GsdWwanManager
+#include "gsd-wwan-manager.h"
+
+#include "daemon-skeleton.h"
diff --git a/plugins/wwan/meson.build b/plugins/wwan/meson.build
new file mode 100644
index 0000000..238288c
--- /dev/null
+++ b/plugins/wwan/meson.build
@@ -0,0 +1,21 @@
+sources = files(
+  'cc-wwan-device.c',
+  'gsd-wwan-manager.c',
+  'main.c'
+)
+
+deps = plugins_deps + [gio_dep, gcr_dep, mm_glib_dep, polkit_gobject_dep]
+
+cflags += ['-DGNOMECC_DATA_DIR="@0@"'.format(gsd_pkgdatadir)]
+
+executable(
+  'gsd-' + plugin_name,
+  sources,
+  include_directories: [top_inc, common_inc],
+  dependencies: deps,
+  c_args: cflags,
+  install: true,
+  install_rpath: gsd_pkglibdir,
+  install_dir: gsd_libexecdir
+)
+
diff --git a/plugins/xsettings/00-xrdb b/plugins/xsettings/00-xrdb
new file mode 100755
index 0000000..a047e8b
--- /dev/null
+++ b/plugins/xsettings/00-xrdb
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+userresources=$HOME/.Xresources
+sysresources=/etc/X11/Xresources
+
+# merge in defaults
+[ -r "$sysresources" ] && xrdb -nocpp -merge "$sysresources"
+[ -r "$userresources" ] && xrdb -merge "$userresources"
+
diff --git a/plugins/xsettings/README.xsettings b/plugins/xsettings/README.xsettings
new file mode 100644
index 0000000..624ccab
--- /dev/null
+++ b/plugins/xsettings/README.xsettings
@@ -0,0 +1,35 @@
+This is very simple documentation for the 'override' GSettings key for
+gnome-setting-daemon's xsettings plugin.
+
+The override is given as a dictionary of overrides to be applied on top
+of the usual values that are exported to the X server as XSETTINGS.  The
+intent of this is to allow users to override values of programmatically
+determined settings (such as 'Gtk/ShellShowsAppMenu') and to allow
+developers to introduce new XSETTINGS for testing (without having to kill the
+gnome-settings-daemon running in the session and run their own patched
+version).
+
+The type of the overrides is 'a{sv}'.
+
+The key gives the full XSETTINGS setting name to override (for example,
+'Gtk/ShellShowsAppMenu').  The value is one of the following:
+
+ - a string ('s') for the case of a string XSETTING
+
+ - an int32 ('i') for the case of an integer XSETTING
+
+ - a 4-tuple of uint16s ('(qqqq)') for the case of a color XSETTING
+
+Dictionary items with a value that is not one of the above types will be
+ignored.  Specifically note that XSETTINGS does not have a concept of
+booleans -- you must use an integer that is either 0 or 1.
+
+An example setting for this key (as expressed in GVariant text format)
+might be:
+
+ { 'Gtk/ShellShowsAppMenu': < 0 >, 'Xft/DPI': < 98304 > }
+
+Noting that variants must be specified in the usual way (wrapped in <>).
+
+Note also that DPI in the above example is expressed in 1024ths of an
+inch.
diff --git a/plugins/xsettings/fc-monitor.c b/plugins/xsettings/fc-monitor.c
new file mode 100644
index 0000000..cafc8bf
--- /dev/null
+++ b/plugins/xsettings/fc-monitor.c
@@ -0,0 +1,306 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ * Copyright (C) 2017 Jan Alexander Steffens (heftig) 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 .
+ *
+ * Author:  Behdad Esfahbod, Red Hat, Inc.
+ */
+
+#include "fc-monitor.h"
+
+#include 
+#include 
+
+#define TIMEOUT_MILLISECONDS 1000
+
+static void
+fontconfig_cache_update_thread (GTask *task,
+                                gpointer source_object G_GNUC_UNUSED,
+                                gpointer task_data G_GNUC_UNUSED,
+                                GCancellable *cancellable G_GNUC_UNUSED)
+{
+        if (FcConfigUptoDate (NULL)) {
+                g_task_return_boolean (task, FALSE);
+                return;
+        }
+
+        if (!FcInitReinitialize ()) {
+                g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
+                                         "FcInitReinitialize failed");
+                return;
+        }
+
+        g_task_return_boolean (task, TRUE);
+}
+
+static void
+fontconfig_cache_update_async (GAsyncReadyCallback callback,
+                               gpointer user_data)
+{
+        GTask *task = g_task_new (NULL, NULL, callback, user_data);
+        g_task_run_in_thread (task, fontconfig_cache_update_thread);
+        g_object_unref (task);
+}
+
+static gboolean
+fontconfig_cache_update_finish (GAsyncResult *result,
+                                GError **error)
+{
+        return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+typedef enum {
+        UPDATE_IDLE,
+        UPDATE_PENDING,
+        UPDATE_RUNNING,
+        UPDATE_RESTART,
+} UpdateState;
+
+struct _FcMonitor {
+        GObject parent_instance;
+
+        GPtrArray *monitors;
+
+        guint timeout;
+        UpdateState state;
+        gboolean notify;
+};
+
+enum {
+        SIGNAL_UPDATED,
+
+        N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0, };
+
+static void fc_monitor_finalize (GObject *object);
+static void monitor_files (FcMonitor *self, FcStrList *list);
+static void stuff_changed (GFileMonitor *monitor, GFile *file, GFile *other_file,
+                           GFileMonitorEvent event_type, gpointer data);
+static void start_timeout (FcMonitor *self);
+static gboolean start_update (gpointer data);
+static void update_done (GObject *source_object, GAsyncResult *result, gpointer user_data);
+
+G_DEFINE_TYPE (FcMonitor, fc_monitor, G_TYPE_OBJECT);
+
+static void
+fc_monitor_class_init (FcMonitorClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = fc_monitor_finalize;
+
+        signals[SIGNAL_UPDATED] = g_signal_new ("updated",
+                                                G_TYPE_FROM_CLASS (klass),
+                                                G_SIGNAL_RUN_LAST,
+                                                0,
+                                                NULL,
+                                                NULL,
+                                                NULL,
+                                                G_TYPE_NONE,
+                                                0);
+}
+
+FcMonitor *
+fc_monitor_new (void)
+{
+        return g_object_new (FC_TYPE_MONITOR, NULL);
+}
+
+static void
+fc_monitor_init (FcMonitor *self G_GNUC_UNUSED)
+{
+        FcInit ();
+}
+
+static void
+fc_monitor_finalize (GObject *object)
+{
+        FcMonitor *self = FC_MONITOR (object);
+
+        if (self->timeout)
+                g_source_remove (self->timeout);
+        self->timeout = 0;
+
+        g_clear_pointer (&self->monitors, g_ptr_array_unref);
+
+        G_OBJECT_CLASS (fc_monitor_parent_class)->finalize (object);
+}
+
+void
+fc_monitor_start (FcMonitor *self)
+{
+        g_return_if_fail (FC_IS_MONITOR (self));
+        g_return_if_fail (self->monitors == NULL);
+
+        self->monitors = g_ptr_array_new_with_free_func (g_object_unref);
+
+        monitor_files (self, FcConfigGetConfigFiles (NULL));
+        monitor_files (self, FcConfigGetFontDirs (NULL));
+}
+
+void
+fc_monitor_stop (FcMonitor *self)
+{
+        g_return_if_fail (FC_IS_MONITOR (self));
+        g_clear_pointer (&self->monitors, g_ptr_array_unref);
+}
+
+static void
+monitor_files (FcMonitor *self,
+               FcStrList *list)
+{
+        const char *str;
+
+        while ((str = (const char *) FcStrListNext (list))) {
+                GFile *file;
+                GFileMonitor *monitor;
+
+                file = g_file_new_for_path (str);
+
+                monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, NULL, NULL);
+
+                g_object_unref (file);
+
+                if (!monitor)
+                        continue;
+
+                g_signal_connect (monitor, "changed", G_CALLBACK (stuff_changed), self);
+
+                g_ptr_array_add (self->monitors, monitor);
+        }
+
+        FcStrListDone (list);
+}
+
+static void
+stuff_changed (GFileMonitor *monitor G_GNUC_UNUSED,
+               GFile *file G_GNUC_UNUSED,
+               GFile *other_file G_GNUC_UNUSED,
+               GFileMonitorEvent event_type,
+               gpointer data)
+{
+        FcMonitor *self = FC_MONITOR (data);
+        const gchar *event_name = g_enum_to_string (G_TYPE_FILE_MONITOR_EVENT, event_type);
+
+        switch (self->state) {
+        case UPDATE_IDLE:
+                g_debug ("Got %-38s: starting fontconfig update timeout", event_name);
+                start_timeout (self);
+                break;
+
+        case UPDATE_PENDING:
+                /* wait for quiescence */
+                g_debug ("Got %-38s: restarting fontconfig update timeout", event_name);
+                g_source_remove (self->timeout);
+                start_timeout (self);
+                break;
+
+        case UPDATE_RUNNING:
+                g_debug ("Got %-38s: restarting fontconfig update", event_name);
+                self->state = UPDATE_RESTART;
+                break;
+
+        case UPDATE_RESTART:
+                g_debug ("Got %-38s: waiting on fontconfig update", event_name);
+                break;
+        }
+}
+
+static void
+start_timeout (FcMonitor *self)
+{
+        self->state = UPDATE_PENDING;
+        self->timeout = g_timeout_add (TIMEOUT_MILLISECONDS, start_update, self);
+        g_source_set_name_by_id (self->timeout, "[gnome-settings-daemon] update");
+}
+
+static gboolean
+start_update (gpointer data)
+{
+        FcMonitor *self = FC_MONITOR (data);
+
+        self->state = UPDATE_RUNNING;
+        self->timeout = 0;
+
+        g_debug ("Timeout completed: starting fontconfig update");
+        fontconfig_cache_update_async (update_done, g_object_ref (self));
+
+        return G_SOURCE_REMOVE;
+}
+
+static void
+update_done (GObject *source_object G_GNUC_UNUSED,
+             GAsyncResult *result,
+             gpointer data)
+{
+        FcMonitor *self = FC_MONITOR (data);
+        gboolean restart = self->state == UPDATE_RESTART;
+        GError *error = NULL;
+
+        self->state = UPDATE_IDLE;
+
+        if (fontconfig_cache_update_finish (result, &error)) {
+                g_debug ("Fontconfig update successful");
+                /* Remember we had a successful update even if we have to restart it */
+                self->notify = TRUE;
+        } else if (error) {
+                g_warning ("Fontconfig update failed: %s", error->message);
+                g_error_free (error);
+        } else
+                g_debug ("Fontconfig update was unnecessary");
+
+        if (restart) {
+                g_debug ("Concurrent change: restarting fontconfig update timeout");
+                start_timeout (self);
+        } else if (self->notify) {
+                self->notify = FALSE;
+
+                if (self->monitors) {
+                        fc_monitor_stop (self);
+                        fc_monitor_start (self);
+                }
+
+                /* we finish modifying self before emitting the signal,
+                 * allowing the callback to stop us if it decides to. */
+                g_signal_emit (self, signals[SIGNAL_UPDATED], 0);
+        }
+
+        /* release ref taken in start_update */
+        g_object_unref (self);
+}
+
+#ifdef FONTCONFIG_MONITOR_TEST
+static void
+yay (void)
+{
+        g_message ("yay");
+}
+
+int
+main (void)
+{
+        GMainLoop *loop = g_main_loop_new (NULL, TRUE);
+        FcMonitor *monitor = fc_monitor_new ();
+
+        fc_monitor_start (monitor);
+        g_signal_connect (monitor, "updated", G_CALLBACK (yay), NULL);
+
+        g_main_loop_run (loop);
+        return 0;
+}
+#endif
diff --git a/plugins/xsettings/fc-monitor.h b/plugins/xsettings/fc-monitor.h
new file mode 100644
index 0000000..4b564f8
--- /dev/null
+++ b/plugins/xsettings/fc-monitor.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2017 Jan Alexander Steffens (heftig) 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 FC_MONITOR_H
+#define FC_MONITOR_H
+
+#include 
+
+G_BEGIN_DECLS
+
+#define FC_TYPE_MONITOR (fc_monitor_get_type ())
+G_DECLARE_FINAL_TYPE (FcMonitor, fc_monitor, FC, MONITOR, GObject)
+
+FcMonitor *fc_monitor_new (void);
+
+void fc_monitor_start (FcMonitor *monitor);
+void fc_monitor_stop  (FcMonitor *monitor);
+
+G_END_DECLS
+
+#endif /* FC_MONITOR_H */
diff --git a/plugins/xsettings/fontconfig-test/fonts.conf b/plugins/xsettings/fontconfig-test/fonts.conf
new file mode 100644
index 0000000..f9236ea
--- /dev/null
+++ b/plugins/xsettings/fontconfig-test/fonts.conf
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+	/usr/share/fonts
+
diff --git a/plugins/xsettings/gsd-remote-display-manager.h b/plugins/xsettings/gsd-remote-display-manager.h
new file mode 100644
index 0000000..3c73ab6
--- /dev/null
+++ b/plugins/xsettings/gsd-remote-display-manager.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Bastien Nocera 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+
+#define GSD_TYPE_REMOTE_DISPLAY_MANAGER            (gsd_remote_display_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdRemoteDisplayManager, gsd_remote_display_manager, GSD, REMOTE_DISPLAY_MANAGER, GObject)
+
+GsdRemoteDisplayManager * gsd_remote_display_manager_new (void);
diff --git a/plugins/xsettings/gsd-xsettings-gtk.c b/plugins/xsettings/gsd-xsettings-gtk.c
new file mode 100644
index 0000000..40baf41
--- /dev/null
+++ b/plugins/xsettings/gsd-xsettings-gtk.c
@@ -0,0 +1,384 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+
+#include "gsd-xsettings-gtk.h"
+
+#define XSETTINGS_PLUGIN_SCHEMA "org.gnome.settings-daemon.plugins.xsettings"
+
+#define GTK_MODULES_DISABLED_KEY "disabled-gtk-modules"
+#define GTK_MODULES_ENABLED_KEY  "enabled-gtk-modules"
+
+static const char *modules_path = NULL;
+
+enum {
+        PROP_0,
+        PROP_GTK_MODULES
+};
+
+struct _GsdXSettingsGtk {
+        GObject            parent;
+
+        char              *modules;
+        GHashTable        *dir_modules;
+
+        GSettings         *settings;
+
+        guint64            dir_mtime;
+        GFileMonitor      *monitor;
+        GList             *cond_settings;
+};
+
+G_DEFINE_TYPE(GsdXSettingsGtk, gsd_xsettings_gtk, G_TYPE_OBJECT)
+
+static void update_gtk_modules (GsdXSettingsGtk *gtk);
+
+static void
+empty_cond_settings_list (GsdXSettingsGtk *gtk)
+{
+        if (gtk->cond_settings == NULL)
+                return;
+
+        /* Empty the list of settings */
+        g_list_foreach (gtk->cond_settings, (GFunc) g_object_unref, NULL);
+        g_list_free (gtk->cond_settings);
+        gtk->cond_settings = NULL;
+}
+
+static void
+cond_setting_changed (GSettings       *settings,
+                      const char      *key,
+                      GsdXSettingsGtk *gtk)
+{
+        gboolean enabled;
+        const char *module_name;
+
+        module_name = g_object_get_data (G_OBJECT (settings), "module-name");
+
+        enabled = g_settings_get_boolean (settings, key);
+        if (enabled != FALSE) {
+                if (gtk->dir_modules == NULL)
+                        gtk->dir_modules = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+                g_hash_table_insert (gtk->dir_modules, g_strdup (module_name), NULL);
+        } else if (gtk->dir_modules != NULL) {
+                g_hash_table_remove (gtk->dir_modules, module_name);
+        }
+
+        update_gtk_modules (gtk);
+}
+
+static char *
+process_desktop_file (const char      *path,
+                      GsdXSettingsGtk *gtk)
+{
+        GKeyFile *keyfile;
+        char *retval;
+        char *module_name;
+
+        retval = NULL;
+
+        if (g_str_has_suffix (path, ".desktop") == FALSE &&
+            g_str_has_suffix (path, ".gtk-module") == FALSE)
+                return retval;
+
+        keyfile = g_key_file_new ();
+        if (g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL) == FALSE)
+                goto bail;
+
+        if (g_key_file_has_group (keyfile, "GTK Module") == FALSE)
+                goto bail;
+
+        module_name = g_key_file_get_string (keyfile, "GTK Module", "X-GTK-Module-Name", NULL);
+        if (module_name == NULL)
+                goto bail;
+
+        if (g_key_file_has_key (keyfile, "GTK Module", "X-GTK-Module-Enabled-Schema", NULL) != FALSE) {
+                char *schema;
+                char *key;
+                gboolean enabled;
+                GSettings *settings;
+                char *signal;
+
+                schema = g_key_file_get_string (keyfile, "GTK Module", "X-GTK-Module-Enabled-Schema", NULL);
+                key = g_key_file_get_string (keyfile, "GTK Module", "X-GTK-Module-Enabled-Key", NULL);
+
+                settings = g_settings_new (schema);
+
+                gtk->cond_settings = g_list_prepend (gtk->cond_settings, settings);
+
+                g_object_set_data_full (G_OBJECT (settings), "module-name", g_strdup (module_name), (GDestroyNotify) g_free);
+
+                signal = g_strdup_printf ("changed::%s", key);
+                g_signal_connect_object (G_OBJECT (settings), signal, G_CALLBACK (cond_setting_changed), gtk, 0);
+                enabled = g_settings_get_boolean (settings, key);
+                g_free (signal);
+                g_free (schema);
+                g_free (key);
+
+                if (enabled != FALSE)
+                        retval = g_strdup (module_name);
+        } else {
+                retval = g_strdup (module_name);
+        }
+
+	g_free (module_name);
+
+bail:
+        g_key_file_free (keyfile);
+        return retval;
+}
+
+static void
+get_gtk_modules_from_dir (GsdXSettingsGtk *gtk)
+{
+        GFile *file;
+        GFileInfo *info;
+        GHashTable *ht;
+
+        file = g_file_new_for_path (modules_path);
+        info = g_file_query_info (file,
+                                  G_FILE_ATTRIBUTE_TIME_MODIFIED,
+                                  G_FILE_QUERY_INFO_NONE,
+                                  NULL,
+                                  NULL);
+        if (info != NULL) {
+                guint64 dir_mtime;
+
+                dir_mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+                if (gtk->dir_mtime == 0 ||
+                    dir_mtime > gtk->dir_mtime) {
+                        GDir *dir;
+                        const char *name;
+
+                        empty_cond_settings_list (gtk);
+
+                        gtk->dir_mtime = dir_mtime;
+
+                        if (gtk->dir_modules != NULL) {
+                                g_hash_table_destroy (gtk->dir_modules);
+                                gtk->dir_modules = NULL;
+                        }
+
+                        dir = g_dir_open (modules_path, 0, NULL);
+                        if (dir == NULL)
+                                goto bail;
+
+                        ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+                        while ((name = g_dir_read_name (dir)) != NULL) {
+                                char *path;
+                                char *module;
+
+                                path = g_build_filename (modules_path, name, NULL);
+                                module = process_desktop_file (path, gtk);
+                                if (module != NULL)
+                                        g_hash_table_insert (ht, module, NULL);
+                                g_free (path);
+                        }
+                        g_dir_close (dir);
+
+                        gtk->dir_modules = ht;
+                }
+                g_object_unref (info);
+        } else {
+                empty_cond_settings_list (gtk);
+        }
+
+bail:
+        g_object_unref (file);
+}
+
+static void
+stringify_gtk_modules (gpointer key,
+                       gpointer value,
+                       GString *str)
+{
+        if (str->len != 0)
+                g_string_append_c (str, ':');
+        g_string_append (str, key);
+}
+
+static void
+update_gtk_modules (GsdXSettingsGtk *gtk)
+{
+        char **enabled, **disabled;
+        GHashTable *ht;
+        guint i;
+        GString *str;
+        char *modules;
+
+        enabled = g_settings_get_strv (gtk->settings, GTK_MODULES_ENABLED_KEY);
+        disabled = g_settings_get_strv (gtk->settings, GTK_MODULES_DISABLED_KEY);
+
+        ht = g_hash_table_new (g_str_hash, g_str_equal);
+
+        if (gtk->dir_modules != NULL) {
+                GList *list, *l;
+
+                list = g_hash_table_get_keys (gtk->dir_modules);
+                for (l = list; l != NULL; l = l->next) {
+                        g_hash_table_insert (ht, l->data, NULL);
+                }
+                g_list_free (list);
+        }
+
+        for (i = 0; enabled[i] != NULL; i++)
+                g_hash_table_insert (ht, enabled[i], NULL);
+
+        for (i = 0; disabled[i] != NULL; i++)
+                g_hash_table_remove (ht, disabled[i]);
+
+        str = g_string_new (NULL);
+        g_hash_table_foreach (ht, (GHFunc) stringify_gtk_modules, str);
+        g_hash_table_destroy (ht);
+
+        modules = g_string_free (str, FALSE);
+
+        if (modules == NULL ||
+            gtk->modules == NULL ||
+            g_str_equal (modules, gtk->modules) == FALSE) {
+                g_free (gtk->modules);
+                gtk->modules = modules;
+                g_object_notify (G_OBJECT (gtk), "gtk-modules");
+        } else {
+                g_free (modules);
+        }
+
+	g_strfreev (enabled);
+	g_strfreev (disabled);
+}
+
+static void
+gtk_modules_dir_changed_cb (GFileMonitor     *monitor,
+                            GFile            *file,
+                            GFile            *other_file,
+                            GFileMonitorEvent event_type,
+                            GsdXSettingsGtk  *gtk)
+{
+        get_gtk_modules_from_dir (gtk);
+        update_gtk_modules (gtk);
+}
+
+static void
+gsd_xsettings_gtk_init (GsdXSettingsGtk *gtk)
+{
+        GFile *file;
+
+        g_debug ("GsdXSettingsGtk initializing");
+
+        gtk->settings = g_settings_new (XSETTINGS_PLUGIN_SCHEMA);
+
+        modules_path = g_getenv ("GSD_gtk_modules_dir");
+        if (modules_path == NULL)
+                modules_path = GTK_MODULES_DIRECTORY;
+
+        get_gtk_modules_from_dir (gtk);
+
+        file = g_file_new_for_path (modules_path);
+        gtk->monitor = g_file_monitor (file,
+                                             G_FILE_MONITOR_NONE,
+                                             NULL,
+                                             NULL);
+        g_signal_connect (G_OBJECT (gtk->monitor), "changed",
+                          G_CALLBACK (gtk_modules_dir_changed_cb), gtk);
+        g_object_unref (file);
+
+        update_gtk_modules (gtk);
+}
+
+static void
+gsd_xsettings_gtk_finalize (GObject *object)
+{
+        GsdXSettingsGtk *gtk;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSD_IS_XSETTINGS_GTK (object));
+
+        g_debug ("GsdXSettingsGtk finalizing");
+
+        gtk = GSD_XSETTINGS_GTK (object);
+
+        g_return_if_fail (gtk != NULL);
+
+        g_free (gtk->modules);
+        gtk->modules = NULL;
+
+        if (gtk->dir_modules != NULL) {
+                g_hash_table_destroy (gtk->dir_modules);
+                gtk->dir_modules = NULL;
+        }
+
+        g_object_unref (gtk->settings);
+
+        if (gtk->monitor != NULL)
+                g_object_unref (gtk->monitor);
+
+        empty_cond_settings_list (gtk);
+
+        G_OBJECT_CLASS (gsd_xsettings_gtk_parent_class)->finalize (object);
+}
+
+static void
+gsd_xsettings_gtk_get_property (GObject        *object,
+                                guint           prop_id,
+                                GValue         *value,
+                                GParamSpec     *pspec)
+{
+        GsdXSettingsGtk *self;
+
+        self = GSD_XSETTINGS_GTK (object);
+
+        switch (prop_id) {
+        case PROP_GTK_MODULES:
+                g_value_set_string (value, self->modules);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gsd_xsettings_gtk_class_init (GsdXSettingsGtkClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->get_property = gsd_xsettings_gtk_get_property;
+        object_class->finalize = gsd_xsettings_gtk_finalize;
+
+        g_object_class_install_property (object_class, PROP_GTK_MODULES,
+                                         g_param_spec_string ("gtk-modules", NULL, NULL,
+                                                              NULL, G_PARAM_READABLE));
+}
+
+GsdXSettingsGtk *
+gsd_xsettings_gtk_new (void)
+{
+        return GSD_XSETTINGS_GTK (g_object_new (GSD_TYPE_XSETTINGS_GTK, NULL));
+}
+
+const char *
+gsd_xsettings_gtk_get_modules (GsdXSettingsGtk *gtk)
+{
+        return gtk->modules;
+}
diff --git a/plugins/xsettings/gsd-xsettings-gtk.h b/plugins/xsettings/gsd-xsettings-gtk.h
new file mode 100644
index 0000000..13f6b88
--- /dev/null
+++ b/plugins/xsettings/gsd-xsettings-gtk.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Bastien Nocera 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_XSETTINGS_GTK_H__
+#define __GSD_XSETTINGS_GTK_H__
+
+#include 
+#include 
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_XSETTINGS_GTK                (gsd_xsettings_gtk_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdXSettingsGtk, gsd_xsettings_gtk, GSD, XSETTINGS_GTK, GObject)
+
+GsdXSettingsGtk *gsd_xsettings_gtk_new        (void);
+
+const char * gsd_xsettings_gtk_get_modules (GsdXSettingsGtk *gtk);
+
+G_END_DECLS
+
+#endif /* __GSD_XSETTINGS_GTK_H__ */
diff --git a/plugins/xsettings/gsd-xsettings-manager.c b/plugins/xsettings/gsd-xsettings-manager.c
new file mode 100644
index 0000000..68b6ed7
--- /dev/null
+++ b/plugins/xsettings/gsd-xsettings-manager.c
@@ -0,0 +1,1804 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 Rodrigo Moya
+ * Copyright (C) 2007 William Jon McCann 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 "config.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "gnome-settings-profile.h"
+#include "gsd-enums.h"
+#include "gsd-xsettings-manager.h"
+#include "gsd-xsettings-gtk.h"
+#include "gnome-settings-bus.h"
+#include "gsd-settings-migrate.h"
+#include "xsettings-manager.h"
+#include "fc-monitor.h"
+#include "gsd-remote-display-manager.h"
+#include "wm-button-layout-translation.h"
+
+#define MOUSE_SETTINGS_SCHEMA     "org.gnome.desktop.peripherals.mouse"
+#define BACKGROUND_SETTINGS_SCHEMA "org.gnome.desktop.background"
+#define INTERFACE_SETTINGS_SCHEMA "org.gnome.desktop.interface"
+#define SOUND_SETTINGS_SCHEMA     "org.gnome.desktop.sound"
+#define PRIVACY_SETTINGS_SCHEMA     "org.gnome.desktop.privacy"
+#define WM_SETTINGS_SCHEMA        "org.gnome.desktop.wm.preferences"
+#define A11Y_SCHEMA               "org.gnome.desktop.a11y"
+#define A11Y_INTERFACE_SCHEMA     "org.gnome.desktop.a11y.interface"
+#define A11Y_APPLICATIONS_SCHEMA   "org.gnome.desktop.a11y.applications"
+#define INPUT_SOURCES_SCHEMA       "org.gnome.desktop.input-sources"
+#define CLASSIC_WM_SETTINGS_SCHEMA "org.gnome.shell.extensions.classic-overrides"
+
+#define XSETTINGS_PLUGIN_SCHEMA "org.gnome.settings-daemon.plugins.xsettings"
+#define XSETTINGS_OVERRIDE_KEY  "overrides"
+
+#define GTK_MODULES_DISABLED_KEY "disabled-gtk-modules"
+#define GTK_MODULES_ENABLED_KEY  "enabled-gtk-modules"
+
+#define TEXT_SCALING_FACTOR_KEY "text-scaling-factor"
+#define CURSOR_SIZE_KEY "cursor-size"
+#define CURSOR_THEME_KEY "cursor-theme"
+
+#define FONT_ANTIALIASING_KEY "font-antialiasing"
+#define FONT_HINTING_KEY      "font-hinting"
+#define FONT_RGBA_ORDER_KEY   "font-rgba-order"
+
+#define HIGH_CONTRAST_KEY "high-contrast"
+
+#define INPUT_SOURCES_KEY      "sources"
+#define OSK_ENABLED_KEY        "screen-keyboard-enabled"
+#define GTK_IM_MODULE_KEY      "gtk-im-module"
+
+#define GTK_SETTINGS_DBUS_PATH "/org/gtk/Settings"
+#define GTK_SETTINGS_DBUS_NAME "org.gtk.Settings"
+
+#define INPUT_SOURCE_TYPE_IBUS "ibus"
+
+#define GTK_IM_MODULE_SIMPLE "gtk-im-context-simple"
+#define GTK_IM_MODULE_IBUS   "ibus"
+
+static const gchar introspection_xml[] =
+""
+"  "
+"    "
+"    "
+"    "
+"  "
+"";
+
+/* As we cannot rely on the X server giving us good DPI information, and
+ * that we don't want multi-monitor screens to have different DPIs (thus
+ * different text sizes), we'll hard-code the value of the DPI
+ *
+ * See also:
+ * https://bugzilla.novell.com/show_bug.cgi?id=217790•
+ * https://bugzilla.gnome.org/show_bug.cgi?id=643704
+ *
+ * http://lists.fedoraproject.org/pipermail/devel/2011-October/157671.html
+ * Why EDID is not trustworthy for DPI
+ * Adam Jackson ajax at redhat.com
+ * Tue Oct 4 17:54:57 UTC 2011
+ * 
+ *     Previous message: GNOME 3 - font point sizes now scaled?
+ *     Next message: Why EDID is not trustworthy for DPI
+ *     Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
+ * 
+ * On Tue, 2011-10-04 at 11:46 -0400, Kaleb S. KEITHLEY wrote:
+ * 
+ * > Grovelling around in the F15 xorg-server sources and reviewing the Xorg 
+ * > log file on my F15 box, I see, with _modern hardware_ at least, that we 
+ * > do have the monitor geometry available from DDC or EDIC, and obviously 
+ * > it is trivial to compute the actual, correct DPI for each screen.
+ * 
+ * I am clearly going to have to explain this one more time, forever.
+ * Let's see if I can't write it authoritatively once and simply answer
+ * with a URL from here out.  (As always, use of the second person "you"
+ * herein is plural, not singular.)
+ * 
+ * EDID does not reliably give you the size of the display.
+ * 
+ * Base EDID has at least two different places where you can give a
+ * physical size (before considering extensions that aren't widely deployed
+ * so whatever).  The first is a global property, measured in centimeters,
+ * of the physical size of the glass.  The second is attached to your (zero
+ * or more) detailed timing specifications, and reflects the size of the
+ * mode, in millimeters.
+ * 
+ * So, how does this screw you?
+ * 
+ * a) Glass size is too coarse.  On a large display that cm roundoff isn't
+ * a big deal, but on subnotebooks it's a different game.  The 11" MBA is
+ * 25.68x14.44 cm, so that gives you a range of 52.54-54.64 dpcm horizontal
+ * and 51.20-54.86 dpcm vertical (133.4-138.8 dpi h and 130.0-139.3 dpi v).
+ * Which is optimistic, because that's doing the math forward from knowing
+ * the actual size, and you as the EDID parser can't know which way the
+ * manufacturer rounded.
+ * 
+ * b) Glass size need not be non-zero.  This is in fact the usual case for
+ * projectors, which don't have a fixed display size since it's a function
+ * of how far away the wall is from the lens.
+ * 
+ * c) Glass size could be partially non-zero.  Yes, really.  EDID 1.4
+ * defines a method of using these two bytes to encode aspect ratio, where
+ * if vertical size is 0 then the aspect ratio is computed as (horizontal
+ * value + 99) / 100 in portrait mode (and the obvious reverse thing if
+ * horizontal is zero).  Admittedly, unlike every other item in this list,
+ * I've never seen this in the wild.  But it's legal.
+ * 
+ * d) Glass size could be a direct encoding of the aspect ratio.  Base EDID
+ * doesn't condone this behaviour, but the CEA spec (to which all HDMI
+ * monitors must conform) does allow-but-not-require it, which means your
+ * 1920x1080 TV could claim to be 16 "cm" by 9 "cm".  So of course that's
+ * what TV manufacturers do because that way they don't have to modify the
+ * EDID info when physical construction changes, and that's cheaper.
+ * 
+ * e) You could use mode size to get size in millimeters, but you might not
+ * have any detailed timings.
+ * 
+ * f) You could use mode size, but mode size is explicitly _not_ glass
+ * size.  It's the size that the display chooses to present that mode.
+ * Sometimes those are the same, and sometimes they're not.  You could be
+ * scaled or {letter,pillar}boxed, and that's not necessarily something you
+ * can control from the host side.
+ * 
+ * g) You could use mode size, but it could be an encoded aspect ratio, as
+ * in case d above, because CEA says that's okay.
+ * 
+ * h) You could use mode size, but it could be the aspect ratio from case d
+ * multiplied by 10 in each direction (because, of course, you gave size in
+ * centimeters and so your authoring tool just multiplied it up).
+ * 
+ * i) Any or all of the above could be complete and utter garbage, because
+ * - and I really, really need you to understand this - there is no
+ * requirements program for any commercial OS or industry standard that
+ * requires honesty here, as far as I'm aware.  There is every incentive
+ * for there to _never_ be one, because it would make the manufacturing
+ * process more expensive.
+ * 
+ * So from this point the suggestion is usually "well come up with some
+ * heuristic to make a good guess assuming there's some correlation between
+ * the various numbers you're given".  I have in fact written heuristics
+ * for this, and they're in your kernel and your X server, and they still
+ * encounter a huge number of cases where we simply _cannot_ know from EDID
+ * anything like a physical size, because - to pick only one example - the
+ * consumer electronics industry are cheap bastards, because you the
+ * consumer demanded that they be cheap.
+ * 
+ * And then your only recourse is to an external database, and now you're
+ * up the creek again because the identifying information here is a
+ * vendor/model/serial tuple, and the vendor can and does change physical
+ * construction without changing model number.  Now you get to play the
+ * guessing game of how big the serial number range is for each subvariant,
+ * assuming they bothered to encode a serial number - and they didn't.  Or,
+ * if they bothered to encode week/year of manufacturer correctly - and
+ * they didn't - which weeks meant which models.  And then you still have
+ * to go out and buy one of every TV at Fry's, and that covers you for one
+ * market, for three months.
+ * 
+ * If someone wants to write something better, please, by all means.  If
+ * it's kernel code, send it to dri-devel at lists.freedesktop.org and cc me
+ * and I will happily review it.  Likewise xorg-devel@ for X server
+ * changes.
+ * 
+ * I gently suggest that doing so is a waste of time.
+ * 
+ * But if there's one thing free software has taught me, it's that you can
+ * not tell people something is a bad idea and have any expectation they
+ * will believe you.
+ * 
+ * > Obviously in a multi-screen set-up using Xinerama this has the potential 
+ * > to be a Hard Problem if the monitors differ greatly in their DPI.
+ * > 
+ * > If the major resistance is over what to do with older hardware that 
+ * > doesn't have this data available, then yes, punt; use a hard-coded 
+ * > default. Likewise, if the two monitors really differ greatly, then punt.
+ * 
+ * I'm going to limit myself to observing that "greatly" is a matter of
+ * opinion, and that in order to be really useful you'd need some way of
+ * communicating "I punted" to the desktop.
+ * 
+ * Beyond that, sure, pick a heuristic, accept that it's going to be
+ * insufficient for someone, and then sit back and wait to get
+ * second-guessed on it over and over.
+ * 
+ * > And it wouldn't be so hard to to add something like -dpi:0, -dpi:1, 
+ * > -dpi:2 command line options to specify per-screen dpi. I kinda thought I 
+ * > did that a long, long time ago, but maybe I only thought about doing it 
+ * > and never actually got around to it.
+ * 
+ * The RANDR extension as of version 1.2 does allow you to override
+ * physical size on a per-output basis at runtime.  We even try pretty hard
+ * to set them as honestly as we can up front.  The 96dpi thing people
+ * complain about is from the per-screen info, which is simply a default
+ * because of all the tl;dr above; because you have N outputs per screen
+ * which means a single number is in general useless; and because there is
+ * no way to refresh the per-screen info at runtime, as it's only ever sent
+ * in the initial connection handshake.
+ * 
+ * - ajax
+ * 
+ */
+#define DPI_FALLBACK 96
+
+typedef struct _TranslationEntry TranslationEntry;
+typedef void (* TranslationFunc) (GsdXSettingsManager *manager,
+                                  TranslationEntry    *trans,
+                                  GVariant            *value);
+
+struct _TranslationEntry {
+        const char     *gsettings_schema;
+        const char     *gsettings_key;
+        const char     *xsetting_name;
+
+        TranslationFunc translate;
+};
+
+typedef struct _FixedEntry FixedEntry;
+typedef void (* FixedFunc) (GsdXSettingsManager *manager,
+                            FixedEntry            *fixed);
+typedef union {
+        const char *str;
+        int num;
+} FixedEntryValue;
+
+struct _FixedEntry {
+        const char     *xsetting_name;
+        FixedFunc       func;
+        FixedEntryValue val;
+};
+
+struct _GsdXSettingsManager
+{
+        GObject            parent;
+
+        guint              start_idle_id;
+        XSettingsManager  *manager;
+        GHashTable        *settings;
+
+        GSettings         *plugin_settings;
+        FcMonitor         *fontconfig_monitor;
+        gint64             fontconfig_timestamp;
+
+        GSettings         *interface_settings;
+        GSettings         *input_sources_settings;
+        GSettings         *a11y_settings;
+        GdkSeat           *user_seat;
+
+        GsdXSettingsGtk   *gtk;
+
+        guint              introspect_properties_changed_id;
+        guint              shell_introspect_watch_id;
+        gboolean           enable_animations;
+
+        guint              display_config_watch_id;
+        guint              monitors_changed_id;
+
+        guint              device_added_id;
+        guint              device_removed_id;
+
+        guint              shell_name_watch_id;
+        gboolean           have_shell;
+
+        guint              notify_idle_id;
+
+        GDBusNodeInfo     *introspection_data;
+        GDBusConnection   *dbus_connection;
+        guint              gtk_settings_name_id;
+};
+
+#define GSD_XSETTINGS_ERROR gsd_xsettings_error_quark ()
+
+enum {
+        GSD_XSETTINGS_ERROR_INIT
+};
+
+static void     gsd_xsettings_manager_class_init  (GsdXSettingsManagerClass *klass);
+static void     gsd_xsettings_manager_init        (GsdXSettingsManager      *xsettings_manager);
+static void     gsd_xsettings_manager_finalize    (GObject                  *object);
+
+static void     register_manager_dbus             (GsdXSettingsManager *manager);
+
+G_DEFINE_TYPE (GsdXSettingsManager, gsd_xsettings_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+static GQuark
+gsd_xsettings_error_quark (void)
+{
+        return g_quark_from_static_string ("gsd-xsettings-error-quark");
+}
+
+static void
+translate_bool_int (GsdXSettingsManager *manager,
+                    TranslationEntry    *trans,
+                    GVariant            *value)
+{
+        xsettings_manager_set_int (manager->manager, trans->xsetting_name,
+                                   g_variant_get_boolean (value));
+}
+
+static void
+translate_int_int (GsdXSettingsManager *manager,
+                   TranslationEntry    *trans,
+                   GVariant            *value)
+{
+        xsettings_manager_set_int (manager->manager, trans->xsetting_name,
+                                   g_variant_get_int32 (value));
+}
+
+static void
+translate_string_string (GsdXSettingsManager *manager,
+                         TranslationEntry    *trans,
+                         GVariant            *value)
+{
+        xsettings_manager_set_string (manager->manager,
+                                      trans->xsetting_name,
+                                      g_variant_get_string (value, NULL));
+}
+
+static void
+translate_button_layout (GsdXSettingsManager *manager,
+                         TranslationEntry    *trans,
+                         GVariant            *value)
+{
+        GSettings *classic_settings;
+        GVariant *classic_value = NULL;
+        char *layout;
+
+        /* Hack: until we get session-dependent defaults in GSettings,
+         *       swap out the usual schema for the "classic" one when
+         *       running in classic mode
+         */
+        classic_settings = g_hash_table_lookup (manager->settings,
+                                                CLASSIC_WM_SETTINGS_SCHEMA);
+        if (classic_settings) {
+                classic_value = g_settings_get_value (classic_settings, "button-layout");
+                layout = g_variant_dup_string (classic_value, NULL);
+        } else {
+                layout = g_variant_dup_string (value, NULL);
+        }
+
+        translate_wm_button_layout_to_gtk (layout);
+
+        xsettings_manager_set_string (manager->manager,
+                                      trans->xsetting_name,
+                                      layout);
+
+        if (classic_value)
+                g_variant_unref (classic_value);
+        g_free (layout);
+}
+
+static void
+translate_theme_name (GsdXSettingsManager *manager,
+                      TranslationEntry    *trans,
+                      GVariant            *value)
+{
+        GSettings *settings;
+        gboolean hc = FALSE;
+
+        settings = g_hash_table_lookup (manager->settings, A11Y_INTERFACE_SCHEMA);
+
+        if (settings)
+                hc = g_settings_get_boolean (settings, HIGH_CONTRAST_KEY);
+
+        xsettings_manager_set_string (manager->manager,
+                                      trans->xsetting_name,
+                                      hc ? "HighContrast"
+                                         : g_variant_get_string (value, NULL));
+}
+
+static void
+fixed_false_int (GsdXSettingsManager *manager,
+                 FixedEntry          *fixed)
+{
+        xsettings_manager_set_int (manager->manager, fixed->xsetting_name, FALSE);
+}
+
+static void
+fixed_true_int (GsdXSettingsManager *manager,
+                FixedEntry          *fixed)
+{
+        xsettings_manager_set_int (manager->manager, fixed->xsetting_name, TRUE);
+}
+
+static void
+fixed_bus_id (GsdXSettingsManager *manager,
+              FixedEntry          *fixed)
+{
+        const gchar *id;
+        GDBusConnection *bus;
+        GVariant *res;
+
+        bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+        res = g_dbus_connection_call_sync (bus,
+                                           "org.freedesktop.DBus",
+                                           "/org/freedesktop/DBus",
+                                           "org.freedesktop.DBus",
+                                           "GetId",
+                                           NULL,
+                                           NULL,
+                                           G_DBUS_CALL_FLAGS_NONE,
+                                           -1,
+                                           NULL,
+                                           NULL);
+
+        if (res) {
+                g_variant_get (res, "(&s)", &id);
+
+                xsettings_manager_set_string (manager->manager, fixed->xsetting_name, id);
+                g_variant_unref (res);
+        }
+
+        g_object_unref (bus);
+}
+
+static void
+fixed_string (GsdXSettingsManager *manager,
+              FixedEntry          *fixed)
+{
+        xsettings_manager_set_string (manager->manager,
+                                      fixed->xsetting_name,
+                                      fixed->val.str);
+}
+
+static void
+fixed_int (GsdXSettingsManager *manager,
+           FixedEntry            *fixed)
+{
+        xsettings_manager_set_int (manager->manager,
+                                   fixed->xsetting_name,
+                                   fixed->val.num);
+}
+
+#define DEFAULT_COLOR_PALETTE "black:white:gray50:red:purple:blue:light blue:green:yellow:orange:lavender:brown:goldenrod4:dodger blue:pink:light green:gray10:gray30:gray75:gray90"
+
+static FixedEntry fixed_entries [] = {
+        { "Gtk/MenuImages",          fixed_false_int },
+        { "Gtk/ButtonImages",        fixed_false_int },
+        { "Gtk/ShowInputMethodMenu", fixed_false_int },
+        { "Gtk/ShowUnicodeMenu",     fixed_false_int },
+        { "Gtk/AutoMnemonics",       fixed_true_int },
+        { "Gtk/DialogsUseHeader",    fixed_true_int },
+        { "Gtk/SessionBusId",        fixed_bus_id },
+        { "Gtk/ShellShowsAppMenu",   fixed_false_int },
+        { "Gtk/ColorPalette",        fixed_string,      { .str = DEFAULT_COLOR_PALETTE } },
+        { "Net/FallbackIconTheme",   fixed_string,      { .str = "gnome" } },
+        { "Gtk/ToolbarStyle",        fixed_string,      { .str =  "both-horiz" } },
+        { "Gtk/ToolbarIconSize",     fixed_string,      { .str = "large" } },
+        { "Gtk/CanChangeAccels",     fixed_false_int },
+        { "Gtk/TimeoutInitial",      fixed_int,         { .num = 200 } },
+        { "Gtk/TimeoutRepeat",       fixed_int,         { .num = 20 } },
+        { "Gtk/ColorScheme",         fixed_string,      { .str = "" } },
+        { "Gtk/IMPreeditStyle",      fixed_string,      { .str = "callback" } },
+        { "Gtk/IMStatusStyle",       fixed_string,      { .str = "callback" } },
+        { "Gtk/MenuBarAccel",        fixed_string,      { .str = "F10" } }
+};
+
+static TranslationEntry translations [] = {
+        { "org.gnome.desktop.peripherals.mouse", "double-click",   "Net/DoubleClickTime",  translate_int_int },
+        { "org.gnome.desktop.peripherals.mouse", "drag-threshold", "Net/DndDragThreshold", translate_int_int },
+
+        { "org.gnome.desktop.background", "show-desktop-icons",    "Gtk/ShellShowsDesktop",   translate_bool_int },
+
+        { "org.gnome.desktop.interface", "font-name",              "Gtk/FontName",            translate_string_string },
+        { "org.gnome.desktop.interface", "gtk-key-theme",          "Gtk/KeyThemeName",        translate_string_string },
+        { "org.gnome.desktop.interface", "cursor-blink",           "Net/CursorBlink",         translate_bool_int },
+        { "org.gnome.desktop.interface", "cursor-blink-time",      "Net/CursorBlinkTime",     translate_int_int },
+        { "org.gnome.desktop.interface", "cursor-blink-timeout",   "Gtk/CursorBlinkTimeout",  translate_int_int },
+        { "org.gnome.desktop.interface", "gtk-theme",              "Net/ThemeName",           translate_theme_name },
+        { "org.gnome.desktop.interface", "icon-theme",             "Net/IconThemeName",       translate_theme_name },
+        { "org.gnome.desktop.interface", "cursor-theme",           "Gtk/CursorThemeName",     translate_string_string },
+        { "org.gnome.desktop.interface", "gtk-enable-primary-paste", "Gtk/EnablePrimaryPaste", translate_bool_int },
+        { "org.gnome.desktop.interface", "overlay-scrolling",      "Gtk/OverlayScrolling",    translate_bool_int },
+        /* cursor-size is handled via the Xft side as it needs the scaling factor */
+
+        { "org.gnome.desktop.sound", "theme-name",                 "Net/SoundThemeName",            translate_string_string },
+        { "org.gnome.desktop.sound", "event-sounds",               "Net/EnableEventSounds" ,        translate_bool_int },
+        { "org.gnome.desktop.sound", "input-feedback-sounds",      "Net/EnableInputFeedbackSounds", translate_bool_int },
+
+        { "org.gnome.desktop.privacy", "recent-files-max-age",      "Gtk/RecentFilesMaxAge", translate_int_int },
+        { "org.gnome.desktop.privacy", "remember-recent-files",    "Gtk/RecentFilesEnabled", translate_bool_int },
+        { "org.gnome.desktop.wm.preferences", "button-layout",     "Gtk/DecorationLayout", translate_button_layout },
+        { "org.gnome.desktop.wm.preferences", "action-double-click-titlebar",  "Gtk/TitlebarDoubleClick",     translate_string_string },
+        { "org.gnome.desktop.wm.preferences", "action-middle-click-titlebar",  "Gtk/TitlebarMiddleClick",     translate_string_string },
+        { "org.gnome.desktop.wm.preferences", "action-right-click-titlebar",  "Gtk/TitlebarRightClick",     translate_string_string },
+        { "org.gnome.desktop.a11y", "always-show-text-caret",       "Gtk/KeynavUseCaret",         translate_bool_int }
+};
+
+static gboolean
+notify_idle (gpointer data)
+{
+        GsdXSettingsManager *manager = data;
+
+        xsettings_manager_notify (manager->manager);
+
+        manager->notify_idle_id = 0;
+        return G_SOURCE_REMOVE;
+}
+
+static void
+queue_notify (GsdXSettingsManager *manager)
+{
+        if (manager->notify_idle_id != 0)
+                return;
+
+        manager->notify_idle_id = g_idle_add (notify_idle, manager);
+        g_source_set_name_by_id (manager->notify_idle_id, "[gnome-settings-daemon] notify_idle");
+}
+
+typedef enum {
+        GTK_SETTINGS_FONTCONFIG_TIMESTAMP = 1 << 0,
+        GTK_SETTINGS_MODULES              = 1 << 1,
+        GTK_SETTINGS_ENABLE_ANIMATIONS    = 1 << 2
+} GtkSettingsMask;
+
+static void
+send_dbus_event (GsdXSettingsManager *manager,
+                 GtkSettingsMask      mask)
+{
+        GVariantBuilder props_builder;
+        GVariant *props_changed = NULL;
+
+        g_variant_builder_init (&props_builder, G_VARIANT_TYPE ("a{sv}"));
+
+        if (mask & GTK_SETTINGS_FONTCONFIG_TIMESTAMP) {
+                g_variant_builder_add (&props_builder, "{sv}", "FontconfigTimestamp",
+                                       g_variant_new_int64 (manager->fontconfig_timestamp));
+        }
+
+        if (mask & GTK_SETTINGS_MODULES) {
+                const char *modules = gsd_xsettings_gtk_get_modules (manager->gtk);
+                g_variant_builder_add (&props_builder, "{sv}", "Modules",
+                                       g_variant_new_string (modules ? modules : ""));
+        }
+
+        if (mask & GTK_SETTINGS_ENABLE_ANIMATIONS) {
+                g_variant_builder_add (&props_builder, "{sv}", "EnableAnimations",
+                                       g_variant_new_boolean (manager->enable_animations));
+        }
+
+        props_changed = g_variant_new ("(s@a{sv}@as)", GTK_SETTINGS_DBUS_NAME,
+                                       g_variant_builder_end (&props_builder),
+                                       g_variant_new_strv (NULL, 0));
+
+        g_dbus_connection_emit_signal (manager->dbus_connection,
+                                       NULL,
+                                       GTK_SETTINGS_DBUS_PATH,
+                                       "org.freedesktop.DBus.Properties",
+                                       "PropertiesChanged",
+                                       props_changed, NULL);
+}
+
+static double
+get_dpi_from_gsettings (GsdXSettingsManager *manager)
+{
+	GSettings  *interface_settings;
+        double      dpi;
+        double      factor;
+
+	interface_settings = g_hash_table_lookup (manager->settings, INTERFACE_SETTINGS_SCHEMA);
+        factor = g_settings_get_double (interface_settings, TEXT_SCALING_FACTOR_KEY);
+
+	dpi = DPI_FALLBACK;
+
+        return dpi * factor;
+}
+
+static gboolean
+get_legacy_ui_scale (GVariantIter *properties,
+                     int          *scale)
+{
+        const char *key;
+        GVariant *value;
+
+        *scale = 0;
+
+        while (g_variant_iter_loop (properties, "{&sv}", &key, &value)) {
+                if (!g_str_equal (key, "legacy-ui-scaling-factor"))
+                        continue;
+
+                *scale = g_variant_get_int32 (value);
+                break;
+        }
+
+        if (*scale < 1) {
+                g_warning ("Failed to get current UI legacy scaling factor");
+                *scale = 1;
+                return FALSE;
+        }
+
+        return TRUE;
+}
+
+#define MODE_FORMAT "(siiddada{sv})"
+#define MODES_FORMAT "a" MODE_FORMAT
+
+#define MONITOR_SPEC_FORMAT "(ssss)"
+#define MONITOR_FORMAT "(" MONITOR_SPEC_FORMAT MODES_FORMAT "a{sv})"
+#define MONITORS_FORMAT "a" MONITOR_FORMAT
+
+#define LOGICAL_MONITOR_FORMAT "(iiduba" MONITOR_SPEC_FORMAT "a{sv})"
+#define LOGICAL_MONITORS_FORMAT "a" LOGICAL_MONITOR_FORMAT
+
+#define CURRENT_STATE_FORMAT "(u" MONITORS_FORMAT LOGICAL_MONITORS_FORMAT "a{sv})"
+
+static int
+get_window_scale (GsdXSettingsManager *manager)
+{
+        g_autoptr(GError) error = NULL;
+        g_autoptr(GVariant) current_state = NULL;
+        g_autoptr(GVariantIter) properties = NULL;
+        int scale = 1;
+
+        current_state =
+                g_dbus_connection_call_sync (manager->dbus_connection,
+                                             "org.gnome.Mutter.DisplayConfig",
+                                             "/org/gnome/Mutter/DisplayConfig",
+                                             "org.gnome.Mutter.DisplayConfig",
+                                             "GetCurrentState",
+                                             NULL,
+                                             NULL,
+                                             G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                                             -1,
+                                             NULL,
+                                             &error);
+        if (!current_state) {
+                g_warning ("Failed to get current display configuration state: %s",
+                           error->message);
+                return 1;
+        }
+
+        g_variant_get (current_state,
+                       CURRENT_STATE_FORMAT,
+                       NULL,
+                       NULL,
+                       NULL,
+                       &properties);
+
+        if (!get_legacy_ui_scale (properties, &scale))
+                g_warning ("Failed to get current UI legacy scaling factor");
+
+        return scale;
+}
+
+typedef struct {
+        gboolean    antialias;
+        gboolean    hinting;
+        int         scaled_dpi;
+        int         dpi;
+        int         window_scale;
+        int         cursor_size;
+        char       *cursor_theme;
+        const char *rgba;
+        const char *hintstyle;
+} GsdXftSettings;
+
+/* Read GSettings and determine the appropriate Xft settings based on them. */
+static void
+xft_settings_get (GsdXSettingsManager *manager,
+                  GsdXftSettings    *settings)
+{
+	GSettings  *interface_settings;
+        GDesktopFontAntialiasingMode antialiasing;
+        GDesktopFontHinting hinting;
+        GDesktopFontRgbaOrder order;
+        gboolean use_rgba = FALSE;
+        double dpi;
+        int cursor_size;
+
+	interface_settings = g_hash_table_lookup (manager->settings, INTERFACE_SETTINGS_SCHEMA);
+
+        antialiasing = g_settings_get_enum (interface_settings, FONT_ANTIALIASING_KEY);
+        hinting = g_settings_get_enum (interface_settings, FONT_HINTING_KEY);
+        order = g_settings_get_enum (interface_settings, FONT_RGBA_ORDER_KEY);
+
+        settings->antialias = (antialiasing != G_DESKTOP_FONT_ANTIALIASING_MODE_NONE);
+        settings->hinting = (hinting != G_DESKTOP_FONT_HINTING_NONE);
+        settings->window_scale = get_window_scale (manager);
+        dpi = get_dpi_from_gsettings (manager);
+        settings->dpi = dpi * 1024; /* Xft wants 1/1024ths of an inch */
+        settings->scaled_dpi = dpi * settings->window_scale * 1024;
+        cursor_size = g_settings_get_int (interface_settings, CURSOR_SIZE_KEY);
+        settings->cursor_size = cursor_size * settings->window_scale;
+        settings->cursor_theme = g_settings_get_string (interface_settings, CURSOR_THEME_KEY);
+        settings->rgba = "rgb";
+        settings->hintstyle = "hintfull";
+
+        switch (hinting) {
+        case G_DESKTOP_FONT_HINTING_NONE:
+                settings->hintstyle = "hintnone";
+                break;
+        case G_DESKTOP_FONT_HINTING_SLIGHT:
+                settings->hintstyle = "hintslight";
+                break;
+        case G_DESKTOP_FONT_HINTING_MEDIUM:
+                settings->hintstyle = "hintmedium";
+                break;
+        case G_DESKTOP_FONT_HINTING_FULL:
+                settings->hintstyle = "hintfull";
+                break;
+        }
+
+        switch (order) {
+        case G_DESKTOP_FONT_RGBA_ORDER_RGBA:
+                settings->rgba = "rgba";
+                break;
+        case G_DESKTOP_FONT_RGBA_ORDER_RGB:
+                settings->rgba = "rgb";
+                break;
+        case G_DESKTOP_FONT_RGBA_ORDER_BGR:
+                settings->rgba = "bgr";
+                break;
+        case G_DESKTOP_FONT_RGBA_ORDER_VRGB:
+                settings->rgba = "vrgb";
+                break;
+        case G_DESKTOP_FONT_RGBA_ORDER_VBGR:
+                settings->rgba = "vbgr";
+                break;
+        }
+
+        switch (antialiasing) {
+        case G_DESKTOP_FONT_ANTIALIASING_MODE_NONE:
+                settings->antialias = 0;
+                break;
+        case G_DESKTOP_FONT_ANTIALIASING_MODE_GRAYSCALE:
+                settings->antialias = 1;
+                break;
+        case G_DESKTOP_FONT_ANTIALIASING_MODE_RGBA:
+                settings->antialias = 1;
+                use_rgba = TRUE;
+        }
+
+        if (!use_rgba) {
+                settings->rgba = "none";
+        }
+}
+
+static void
+xft_settings_clear (GsdXftSettings *settings)
+{
+        g_free (settings->cursor_theme);
+}
+
+static void
+xft_settings_set_xsettings (GsdXSettingsManager *manager,
+                            GsdXftSettings      *settings)
+{
+        gnome_settings_profile_start (NULL);
+
+        xsettings_manager_set_int (manager->manager, "Xft/Antialias", settings->antialias);
+        xsettings_manager_set_int (manager->manager, "Xft/Hinting", settings->hinting);
+        xsettings_manager_set_string (manager->manager, "Xft/HintStyle", settings->hintstyle);
+        xsettings_manager_set_int (manager->manager, "Gdk/WindowScalingFactor", settings->window_scale);
+        xsettings_manager_set_int (manager->manager, "Gdk/UnscaledDPI", settings->dpi);
+        xsettings_manager_set_int (manager->manager, "Xft/DPI", settings->scaled_dpi);
+        xsettings_manager_set_string (manager->manager, "Xft/RGBA", settings->rgba);
+        xsettings_manager_set_int (manager->manager, "Gtk/CursorThemeSize", settings->cursor_size);
+        xsettings_manager_set_string (manager->manager, "Gtk/CursorThemeName", settings->cursor_theme);
+
+        gnome_settings_profile_end (NULL);
+}
+
+static void
+update_property (GString *props, const gchar* key, const gchar* value)
+{
+        gchar* needle;
+        size_t needle_len;
+        gchar* found = NULL;
+
+        /* update an existing property */
+        needle = g_strconcat (key, ":", NULL);
+        needle_len = strlen (needle);
+        if (g_str_has_prefix (props->str, needle))
+                found = props->str;
+        else 
+            found = strstr (props->str, needle);
+
+        if (found) {
+                size_t value_index;
+                gchar* end;
+
+                end = strchr (found, '\n');
+                value_index = (found - props->str) + needle_len + 1;
+                g_string_erase (props, value_index, end ? (end - found - needle_len) : -1);
+                g_string_insert (props, value_index, "\n");
+                g_string_insert (props, value_index, value);
+        } else {
+                g_string_append_printf (props, "%s:\t%s\n", key, value);
+        }
+
+	g_free (needle);
+}
+
+static void
+xft_settings_set_xresources (GsdXftSettings *settings)
+{
+        GString    *add_string;
+        char        dpibuf[G_ASCII_DTOSTR_BUF_SIZE];
+        Display    *dpy;
+
+        gnome_settings_profile_start (NULL);
+
+        /* get existing properties */
+        dpy = XOpenDisplay (NULL);
+        g_return_if_fail (dpy != NULL);
+        add_string = g_string_new (XResourceManagerString (dpy));
+
+        g_debug("xft_settings_set_xresources: orig res '%s'", add_string->str);
+
+        g_snprintf (dpibuf, sizeof (dpibuf), "%d", (int) (settings->scaled_dpi / 1024.0 + 0.5));
+        update_property (add_string, "Xft.dpi", dpibuf);
+        update_property (add_string, "Xft.antialias",
+                                settings->antialias ? "1" : "0");
+        update_property (add_string, "Xft.hinting",
+                                settings->hinting ? "1" : "0");
+        update_property (add_string, "Xft.hintstyle",
+                                settings->hintstyle);
+        update_property (add_string, "Xft.rgba",
+                                settings->rgba);
+        update_property (add_string, "Xcursor.size",
+                                g_ascii_dtostr (dpibuf, sizeof (dpibuf), (double) settings->cursor_size));
+        update_property (add_string, "Xcursor.theme",
+                                settings->cursor_theme);
+
+        g_debug("xft_settings_set_xresources: new res '%s'", add_string->str);
+
+        /* Set the new X property */
+        XChangeProperty(dpy, RootWindow (dpy, 0),
+                        XA_RESOURCE_MANAGER, XA_STRING, 8, PropModeReplace, (const unsigned char *) add_string->str, add_string->len);
+        XCloseDisplay (dpy);
+
+        g_string_free (add_string, TRUE);
+
+        gnome_settings_profile_end (NULL);
+}
+
+/* We mirror the Xft properties both through XSETTINGS and through
+ * X resources
+ */
+static void
+update_xft_settings (GsdXSettingsManager *manager)
+{
+        GsdXftSettings settings;
+
+        gnome_settings_profile_start (NULL);
+
+        xft_settings_get (manager, &settings);
+        xft_settings_set_xsettings (manager, &settings);
+        xft_settings_set_xresources (&settings);
+        xft_settings_clear (&settings);
+
+        gnome_settings_profile_end (NULL);
+}
+
+static void
+xft_callback (GSettings           *settings,
+              const gchar         *key,
+              GsdXSettingsManager *manager)
+{
+        update_xft_settings (manager);
+        queue_notify (manager);
+}
+
+static void
+override_callback (GSettings           *settings,
+                   const gchar         *key,
+                   GsdXSettingsManager *manager)
+{
+        GVariant *value;
+
+        value = g_settings_get_value (settings, XSETTINGS_OVERRIDE_KEY);
+
+        xsettings_manager_set_overrides (manager->manager, value);
+        queue_notify (manager);
+
+        g_variant_unref (value);
+}
+
+static void
+plugin_callback (GSettings           *settings,
+                 const char          *key,
+                 GsdXSettingsManager *manager)
+{
+        if (g_str_equal (key, GTK_MODULES_DISABLED_KEY) ||
+            g_str_equal (key, GTK_MODULES_ENABLED_KEY)) {
+                /* Do nothing, as GsdXsettingsGtk will handle it */
+        } else if (g_str_equal (key, XSETTINGS_OVERRIDE_KEY)) {
+                override_callback (settings, key, manager);
+        }
+}
+
+static void
+gtk_modules_callback (GsdXSettingsGtk     *gtk,
+                      GParamSpec          *spec,
+                      GsdXSettingsManager *manager)
+{
+        const char *modules = gsd_xsettings_gtk_get_modules (manager->gtk);
+
+        if (modules == NULL) {
+                xsettings_manager_delete_setting (manager->manager, "Gtk/Modules");
+        } else {
+                g_debug ("Setting GTK modules '%s'", modules);
+                xsettings_manager_set_string (manager->manager,
+                                              "Gtk/Modules",
+                                              modules);
+        }
+
+        queue_notify (manager);
+        send_dbus_event (manager, GTK_SETTINGS_MODULES);
+}
+
+static void
+fontconfig_callback (FcMonitor            *monitor,
+                     GsdXSettingsManager  *manager)
+{
+        gint64 timestamp = g_get_real_time ();
+        gint timestamp_sec = (int)(timestamp / G_TIME_SPAN_SECOND);
+
+        gnome_settings_profile_start (NULL);
+
+        xsettings_manager_set_int (manager->manager, "Fontconfig/Timestamp", timestamp_sec);
+
+        manager->fontconfig_timestamp = timestamp;
+
+        queue_notify (manager);
+        send_dbus_event (manager, GTK_SETTINGS_FONTCONFIG_TIMESTAMP);
+        gnome_settings_profile_end (NULL);
+}
+
+static gboolean
+start_fontconfig_monitor_idle_cb (GsdXSettingsManager *manager)
+{
+        gnome_settings_profile_start (NULL);
+
+        fc_monitor_start (manager->fontconfig_monitor);
+
+        gnome_settings_profile_end (NULL);
+
+        manager->start_idle_id = 0;
+
+        return FALSE;
+}
+
+static void
+start_fontconfig_monitor (GsdXSettingsManager  *manager)
+{
+        gnome_settings_profile_start (NULL);
+
+        manager->fontconfig_monitor = fc_monitor_new ();
+        g_signal_connect (manager->fontconfig_monitor, "updated", G_CALLBACK (fontconfig_callback), manager);
+
+        manager->start_idle_id = g_idle_add ((GSourceFunc) start_fontconfig_monitor_idle_cb, manager);
+        g_source_set_name_by_id (manager->start_idle_id, "[gnome-settings-daemon] start_fontconfig_monitor_idle_cb");
+
+        gnome_settings_profile_end (NULL);
+}
+
+static void
+process_value (GsdXSettingsManager *manager,
+               TranslationEntry    *trans,
+               GVariant            *value)
+{
+        (* trans->translate) (manager, trans, value);
+}
+
+static TranslationEntry *
+find_translation_entry (GSettings *settings, const char *key)
+{
+        guint i;
+        char *schema;
+
+        g_object_get (settings, "schema-id", &schema, NULL);
+
+        if (g_str_equal (schema, CLASSIC_WM_SETTINGS_SCHEMA)) {
+              g_free (schema);
+              schema = g_strdup (WM_SETTINGS_SCHEMA);
+        }
+
+        for (i = 0; i < G_N_ELEMENTS (translations); i++) {
+                if (g_str_equal (schema, translations[i].gsettings_schema) &&
+                    g_str_equal (key, translations[i].gsettings_key)) {
+                            g_free (schema);
+                        return &translations[i];
+                }
+        }
+
+        g_free (schema);
+
+        return NULL;
+}
+
+static void
+xsettings_callback (GSettings           *settings,
+                    const char          *key,
+                    GsdXSettingsManager *manager)
+{
+        TranslationEntry *trans;
+        GVariant         *value;
+
+        if (g_str_equal (key, TEXT_SCALING_FACTOR_KEY) ||
+            g_str_equal (key, FONT_ANTIALIASING_KEY) ||
+            g_str_equal (key, FONT_HINTING_KEY) ||
+            g_str_equal (key, FONT_RGBA_ORDER_KEY) ||
+            g_str_equal (key, CURSOR_SIZE_KEY) ||
+            g_str_equal (key, CURSOR_THEME_KEY)) {
+        	xft_callback (NULL, key, manager);
+        	return;
+	}
+
+        if (g_str_equal (key, HIGH_CONTRAST_KEY)) {
+                GSettings *iface_settings;
+
+                iface_settings = g_hash_table_lookup (manager->settings,
+                                                      INTERFACE_SETTINGS_SCHEMA);
+                xsettings_callback (iface_settings, "gtk-theme", manager);
+                xsettings_callback (iface_settings, "icon-theme", manager);
+                return;
+        }
+
+        trans = find_translation_entry (settings, key);
+        if (trans == NULL) {
+                return;
+        }
+
+        value = g_settings_get_value (settings, key);
+
+        process_value (manager, trans, value);
+
+        g_variant_unref (value);
+
+        queue_notify (manager);
+}
+
+static void
+terminate_cb (void *data)
+{
+        gboolean *terminated = data;
+
+        if (*terminated) {
+                return;
+        }
+
+        *terminated = TRUE;
+        g_warning ("X Settings Manager is terminating");
+        gtk_main_quit ();
+}
+
+static gboolean
+setup_xsettings_managers (GsdXSettingsManager *manager)
+{
+        GdkDisplay *display;
+        gboolean    res;
+        gboolean    terminated;
+
+        display = gdk_display_get_default ();
+
+        res = xsettings_manager_check_running (gdk_x11_display_get_xdisplay (display),
+                                               gdk_x11_screen_get_screen_number (gdk_screen_get_default ()));
+
+        if (res) {
+                g_warning ("You can only run one xsettings manager at a time; exiting");
+                return FALSE;
+        }
+
+        terminated = FALSE;
+        manager->manager = xsettings_manager_new (gdk_x11_display_get_xdisplay (display),
+                                                  gdk_x11_screen_get_screen_number (gdk_screen_get_default ()),
+                                                  terminate_cb,
+                                                  &terminated);
+        if (! manager->manager) {
+                g_warning ("Could not create xsettings manager!");
+                return FALSE;
+        }
+
+        return TRUE;
+}
+
+static void
+monitors_changed (GsdXSettingsManager *manager)
+{
+        update_xft_settings (manager);
+        queue_notify (manager);
+}
+
+static void
+on_monitors_changed (GDBusConnection *connection,
+                     const gchar     *sender_name,
+                     const gchar     *object_path,
+                     const gchar     *interface_name,
+                     const gchar     *signal_name,
+                     GVariant        *parameters,
+                     gpointer         data)
+{
+        GsdXSettingsManager *manager = data;
+        monitors_changed (manager);
+}
+
+static void
+on_display_config_name_appeared_handler (GDBusConnection *connection,
+                                         const gchar     *name,
+                                         const gchar     *name_owner,
+                                         gpointer         data)
+{
+        GsdXSettingsManager *manager = data;
+        monitors_changed (manager);
+}
+
+static void
+animations_enabled_changed (GsdXSettingsManager *manager)
+{
+        g_autoptr(GError) error = NULL;
+        g_autoptr(GVariant) res = NULL;
+        g_autoptr(GVariant) animations_enabled_variant = NULL;
+        gboolean animations_enabled;
+
+        res = g_dbus_connection_call_sync (manager->dbus_connection,
+                                           "org.gnome.Shell.Introspect",
+                                           "/org/gnome/Shell/Introspect",
+                                           "org.freedesktop.DBus.Properties",
+                                           "Get",
+                                           g_variant_new ("(ss)",
+                                                          "org.gnome.Shell.Introspect",
+                                                          "AnimationsEnabled"),
+                                           NULL,
+                                           G_DBUS_CALL_FLAGS_NONE,
+                                           -1,
+                                           NULL,
+                                           &error);
+        if (!res) {
+                g_warning ("Failed to get animations-enabled state: %s",
+                           error->message);
+                return;
+        }
+
+        g_variant_get (res, "(v)", &animations_enabled_variant);
+        g_variant_get (animations_enabled_variant, "b", &animations_enabled);
+
+        if (manager->enable_animations == animations_enabled)
+                return;
+
+        manager->enable_animations = animations_enabled;
+        xsettings_manager_set_int (manager->manager, "Gtk/EnableAnimations",
+                                   animations_enabled);
+        queue_notify (manager);
+        send_dbus_event (manager, GTK_SETTINGS_ENABLE_ANIMATIONS);
+}
+
+static void
+on_introspect_properties_changed (GDBusConnection *connection,
+                                  const gchar     *sender_name,
+                                  const gchar     *object_path,
+                                  const gchar     *interface_name,
+                                  const gchar     *signal_name,
+                                  GVariant        *parameters,
+                                  gpointer         data)
+{
+        GsdXSettingsManager *manager = data;
+        animations_enabled_changed (manager);
+}
+
+static void
+on_shell_introspect_name_appeared_handler (GDBusConnection *connection,
+                                           const gchar     *name,
+                                           const gchar     *name_owner,
+                                           gpointer         data)
+{
+        GsdXSettingsManager *manager = data;
+        animations_enabled_changed (manager);
+}
+
+static void
+launch_xwayland_services_on_dir (const gchar *path)
+{
+        GFileEnumerator *enumerator;
+        GError *error = NULL;
+        GList *l, *scripts = NULL;
+        GFile *dir;
+
+        g_debug ("launch_xwayland_services_on_dir: %s", path);
+
+        dir = g_file_new_for_path (path);
+        enumerator = g_file_enumerate_children (dir,
+                                                G_FILE_ATTRIBUTE_STANDARD_NAME ","
+                                                G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE ","
+                                                G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                                G_FILE_QUERY_INFO_NONE,
+                                                NULL, &error);
+        g_object_unref (dir);
+
+        if (!enumerator) {
+                if (!g_error_matches (error,
+                                      G_IO_ERROR,
+                                      G_IO_ERROR_NOT_FOUND)) {
+                        g_warning ("Error opening '%s': %s",
+                                   path, error->message);
+                }
+
+                g_error_free (error);
+                return;
+        }
+
+        while (TRUE) {
+                GFileInfo *info;
+                GFile *child;
+
+                if (!g_file_enumerator_iterate (enumerator,
+                                                &info, &child,
+                                                NULL, &error)) {
+                        g_warning ("Error iterating on '%s': %s",
+                                   path, error->message);
+                        g_error_free (error);
+                        break;
+                }
+
+                if (!info)
+                        break;
+
+                if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR ||
+                    !g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
+                        continue;
+
+                scripts = g_list_prepend (scripts, g_file_get_path (child));
+        }
+
+        scripts = g_list_sort (scripts, (GCompareFunc) strcmp);
+
+        for (l = scripts; l; l = l->next) {
+                gchar *args[2] = { l->data, NULL };
+
+                g_debug ("launch_xwayland_services_on_dir: Spawning '%s'", args[0]);
+                if (!g_spawn_sync (NULL, args, NULL,
+                                   G_SPAWN_DEFAULT,
+                                   NULL, NULL,
+                                   NULL, NULL, NULL,
+                                   &error)) {
+                        g_warning ("Error when spawning '%s': %s",
+                                   args[0], error->message);
+                        g_clear_error (&error);
+                }
+        }
+
+        g_object_unref (enumerator);
+        g_list_free_full (scripts, g_free);
+}
+
+static void
+launch_xwayland_services (void)
+{
+        const gchar * const * config_dirs;
+        gint i;
+
+        config_dirs = g_get_system_config_dirs ();
+
+        for (i = 0; config_dirs[i] != NULL; i++) {
+                gchar *config_dir;
+
+                config_dir = g_build_filename (config_dirs[i],
+                                               "Xwayland-session.d",
+                                               NULL);
+
+                launch_xwayland_services_on_dir (config_dir);
+                g_free (config_dir);
+        }
+}
+
+static void
+migrate_settings (void)
+{
+        GsdSettingsMigrateEntry xsettings_entries[] = {
+                { "antialiasing", "font-antialiasing", NULL },
+                { "hinting", "font-hinting", NULL },
+                { "rgba-order", "font-rgba-order", NULL },
+        };
+        GsdSettingsMigrateEntry mouse_entries[] = {
+                { "double-click", "double-click", NULL },
+                { "drag-threshold", "drag-threshold", NULL },
+        };
+
+        gsd_settings_migrate_check ("org.gnome.settings-daemon.plugins.xsettings.deprecated",
+                                    "/org/gnome/settings-daemon/plugins/xsettings/",
+                                    "org.gnome.desktop.interface",
+                                    "/org/gnome/desktop/interface/",
+                                    xsettings_entries, G_N_ELEMENTS (xsettings_entries));
+
+        gsd_settings_migrate_check ("org.gnome.settings-daemon.peripherals.mouse.deprecated",
+                                    "/org/gnome/settings-daemon/peripherals/mouse/",
+                                    "org.gnome.desktop.peripherals.mouse",
+                                    "/org/gnome/desktop/peripherals/mouse/",
+                                    mouse_entries, G_N_ELEMENTS (mouse_entries));
+}
+
+static gboolean
+need_ibus (GsdXSettingsManager *manager)
+{
+        GVariant *sources;
+        GVariantIter iter;
+        const gchar *type;
+        gboolean needs_ibus = FALSE;
+
+        sources = g_settings_get_value (manager->input_sources_settings,
+                                        INPUT_SOURCES_KEY);
+
+        g_variant_iter_init (&iter, sources);
+        while (g_variant_iter_next (&iter, "(&s&s)", &type, NULL)) {
+                if (g_str_equal (type, INPUT_SOURCE_TYPE_IBUS)) {
+                        needs_ibus = TRUE;
+                        break;
+                }
+        }
+
+        g_variant_unref (sources);
+
+        return needs_ibus;
+}
+
+static gboolean
+need_osk (GsdXSettingsManager *manager)
+{
+        gboolean has_touchscreen = FALSE;
+        GList *devices;
+        GdkSeat *seat;
+
+        if (g_settings_get_boolean (manager->a11y_settings,
+                                    OSK_ENABLED_KEY))
+                return TRUE;
+
+        seat = gdk_display_get_default_seat (gdk_display_get_default ());
+        devices = gdk_seat_get_slaves (seat, GDK_SEAT_CAPABILITY_TOUCH);
+
+        has_touchscreen = devices != NULL;
+
+        g_list_free (devices);
+
+        return has_touchscreen;
+}
+
+static void
+update_gtk_im_module (GsdXSettingsManager *manager)
+{
+        const gchar *module;
+        gchar *setting;
+
+        setting = g_settings_get_string (manager->interface_settings,
+                                         GTK_IM_MODULE_KEY);
+        if (setting && *setting)
+                module = setting;
+        else if (need_ibus (manager) || need_osk (manager))
+                module = GTK_IM_MODULE_IBUS;
+        else
+                module = GTK_IM_MODULE_SIMPLE;
+
+        xsettings_manager_set_string (manager->manager, "Gtk/IMModule", module);
+        g_free (setting);
+}
+
+static void
+device_added_cb (GdkSeat             *user_seat,
+                 GdkDevice           *device,
+                 GsdXSettingsManager *manager)
+{
+        GdkInputSource source;
+
+        source = gdk_device_get_source (device);
+        if (source == GDK_SOURCE_TOUCHSCREEN) {
+                update_gtk_im_module (manager);
+        }
+}
+
+static void
+device_removed_cb (GdkSeat             *user_seat,
+                   GdkDevice           *device,
+                   GsdXSettingsManager *manager)
+{
+        GdkInputSource source;
+
+        source = gdk_device_get_source (device);
+        if (source == GDK_SOURCE_TOUCHSCREEN)
+                update_gtk_im_module (manager);
+}
+
+static void
+set_devicepresence_handler (GsdXSettingsManager *manager)
+{
+        GdkSeat *user_seat;
+
+        if (gnome_settings_is_wayland ())
+                return;
+
+        user_seat = gdk_display_get_default_seat (gdk_display_get_default ());
+
+        manager->device_added_id = g_signal_connect (G_OBJECT (user_seat), "device-added",
+                                                     G_CALLBACK (device_added_cb), manager);
+        manager->device_removed_id = g_signal_connect (G_OBJECT (user_seat), "device-removed",
+                                                       G_CALLBACK (device_removed_cb), manager);
+        manager->user_seat = user_seat;
+}
+
+gboolean
+gsd_xsettings_manager_start (GsdXSettingsManager *manager,
+                             GError             **error)
+{
+        GVariant    *overrides;
+        guint        i;
+        GList       *list, *l;
+        const char  *session;
+
+        g_debug ("Starting xsettings manager");
+        gnome_settings_profile_start (NULL);
+
+        migrate_settings ();
+
+        if (!setup_xsettings_managers (manager)) {
+                g_set_error (error, GSD_XSETTINGS_ERROR,
+                             GSD_XSETTINGS_ERROR_INIT,
+                             "Could not initialize xsettings manager.");
+                return FALSE;
+        }
+
+	set_devicepresence_handler (manager);
+        manager->interface_settings = g_settings_new (INTERFACE_SETTINGS_SCHEMA);
+        g_signal_connect_swapped (manager->interface_settings,
+                                  "changed::" GTK_IM_MODULE_KEY,
+                                  G_CALLBACK (update_gtk_im_module), manager);
+
+        manager->input_sources_settings = g_settings_new (INPUT_SOURCES_SCHEMA);
+        g_signal_connect_swapped (manager->input_sources_settings,
+                                  "changed::" INPUT_SOURCES_KEY,
+                                  G_CALLBACK (update_gtk_im_module), manager);
+
+        manager->a11y_settings = g_settings_new (A11Y_APPLICATIONS_SCHEMA);
+        g_signal_connect_swapped (manager->a11y_settings,
+                                  "changed::" OSK_ENABLED_KEY,
+                                  G_CALLBACK (update_gtk_im_module), manager);
+        update_gtk_im_module (manager);
+
+        manager->monitors_changed_id =
+                g_dbus_connection_signal_subscribe (manager->dbus_connection,
+                                                    "org.gnome.Mutter.DisplayConfig",
+                                                    "org.gnome.Mutter.DisplayConfig",
+                                                    "MonitorsChanged",
+                                                    "/org/gnome/Mutter/DisplayConfig",
+                                                    NULL,
+                                                    G_DBUS_SIGNAL_FLAGS_NONE,
+                                                    on_monitors_changed,
+                                                    manager,
+                                                    NULL);
+        manager->display_config_watch_id =
+                g_bus_watch_name_on_connection (manager->dbus_connection,
+                                                "org.gnome.Mutter.DisplayConfig",
+                                                G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                                on_display_config_name_appeared_handler,
+                                                NULL,
+                                                manager,
+                                                NULL);
+
+        manager->introspect_properties_changed_id =
+                g_dbus_connection_signal_subscribe (manager->dbus_connection,
+                                                    "org.gnome.Shell.Introspect",
+                                                    "org.freedesktop.DBus.Properties",
+                                                    "PropertiesChanged",
+                                                    "/org/gnome/Shell/Introspect",
+                                                    NULL,
+                                                    G_DBUS_SIGNAL_FLAGS_NONE,
+                                                    on_introspect_properties_changed,
+                                                    manager,
+                                                    NULL);
+        manager->shell_introspect_watch_id =
+                g_bus_watch_name_on_connection (manager->dbus_connection,
+                                                "org.gnome.Shell.Introspect",
+                                                G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                                on_shell_introspect_name_appeared_handler,
+                                                NULL,
+                                                manager,
+                                                NULL);
+
+        manager->settings = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                         NULL, (GDestroyNotify) g_object_unref);
+
+        g_hash_table_insert (manager->settings,
+                             MOUSE_SETTINGS_SCHEMA, g_settings_new (MOUSE_SETTINGS_SCHEMA));
+        g_hash_table_insert (manager->settings,
+                             BACKGROUND_SETTINGS_SCHEMA, g_settings_new (BACKGROUND_SETTINGS_SCHEMA));
+        g_hash_table_insert (manager->settings,
+                             INTERFACE_SETTINGS_SCHEMA, g_settings_new (INTERFACE_SETTINGS_SCHEMA));
+        g_hash_table_insert (manager->settings,
+                             SOUND_SETTINGS_SCHEMA, g_settings_new (SOUND_SETTINGS_SCHEMA));
+        g_hash_table_insert (manager->settings,
+                             PRIVACY_SETTINGS_SCHEMA, g_settings_new (PRIVACY_SETTINGS_SCHEMA));
+        g_hash_table_insert (manager->settings,
+                             WM_SETTINGS_SCHEMA, g_settings_new (WM_SETTINGS_SCHEMA));
+        g_hash_table_insert (manager->settings,
+                             A11Y_SCHEMA, g_settings_new (A11Y_SCHEMA));
+        g_hash_table_insert (manager->settings,
+                             A11Y_INTERFACE_SCHEMA, g_settings_new (A11Y_INTERFACE_SCHEMA));
+
+        session = g_getenv ("XDG_CURRENT_DESKTOP");
+        if (session && strstr (session, "GNOME-Classic")) {
+                GSettingsSchema *schema;
+
+                schema = g_settings_schema_source_lookup (g_settings_schema_source_get_default (),
+                                                  CLASSIC_WM_SETTINGS_SCHEMA, FALSE);
+                if (schema) {
+                        g_hash_table_insert (manager->settings,
+                                             CLASSIC_WM_SETTINGS_SCHEMA,
+                                             g_settings_new_full (schema, NULL, NULL));
+                        g_settings_schema_unref (schema);
+                }
+        }
+
+        for (i = 0; i < G_N_ELEMENTS (fixed_entries); i++) {
+                FixedEntry *fixed = &fixed_entries[i];
+                (* fixed->func) (manager, fixed);
+        }
+
+        list = g_hash_table_get_values (manager->settings);
+        for (l = list; l != NULL; l = l->next) {
+                g_signal_connect_object (G_OBJECT (l->data), "changed", G_CALLBACK (xsettings_callback), manager, 0);
+        }
+        g_list_free (list);
+
+        for (i = 0; i < G_N_ELEMENTS (translations); i++) {
+                GVariant *val;
+                GSettings *settings;
+
+                settings = g_hash_table_lookup (manager->settings,
+                                                translations[i].gsettings_schema);
+                if (settings == NULL) {
+                        g_warning ("Schemas '%s' has not been setup", translations[i].gsettings_schema);
+                        continue;
+                }
+
+                val = g_settings_get_value (settings, translations[i].gsettings_key);
+
+                process_value (manager, &translations[i], val);
+                g_variant_unref (val);
+        }
+
+        /* Plugin settings (GTK modules and Xft) */
+        manager->plugin_settings = g_settings_new (XSETTINGS_PLUGIN_SCHEMA);
+        g_signal_connect_object (manager->plugin_settings, "changed", G_CALLBACK (plugin_callback), manager, 0);
+
+        manager->gtk = gsd_xsettings_gtk_new ();
+        g_signal_connect (G_OBJECT (manager->gtk), "notify::gtk-modules",
+                          G_CALLBACK (gtk_modules_callback), manager);
+        gtk_modules_callback (manager->gtk, NULL, manager);
+
+        /* Xft settings */
+        update_xft_settings (manager);
+
+        /* Launch Xwayland services */
+        if (gnome_settings_is_wayland ())
+                launch_xwayland_services ();
+
+        register_manager_dbus (manager);
+
+        start_fontconfig_monitor (manager);
+
+        overrides = g_settings_get_value (manager->plugin_settings, XSETTINGS_OVERRIDE_KEY);
+        xsettings_manager_set_overrides (manager->manager, overrides);
+        queue_notify (manager);
+        g_variant_unref (overrides);
+
+
+        gnome_settings_profile_end (NULL);
+
+        return TRUE;
+}
+
+void
+gsd_xsettings_manager_stop (GsdXSettingsManager *manager)
+{
+        g_debug ("Stopping xsettings manager");
+
+        if (manager->introspect_properties_changed_id) {
+                g_dbus_connection_signal_unsubscribe (manager->dbus_connection,
+                                                      manager->introspect_properties_changed_id);
+                manager->introspect_properties_changed_id = 0;
+        }
+
+        if (manager->shell_introspect_watch_id) {
+                g_bus_unwatch_name (manager->shell_introspect_watch_id);
+                manager->shell_introspect_watch_id = 0;
+        }
+
+        if (manager->monitors_changed_id) {
+                g_dbus_connection_signal_unsubscribe (manager->dbus_connection,
+                                                      manager->monitors_changed_id);
+                manager->monitors_changed_id = 0;
+        }
+
+        if (manager->display_config_watch_id) {
+                g_bus_unwatch_name (manager->display_config_watch_id);
+                manager->display_config_watch_id = 0;
+        }
+
+        if (manager->shell_name_watch_id > 0) {
+                g_bus_unwatch_name (manager->shell_name_watch_id);
+                manager->shell_name_watch_id = 0;
+        }
+
+        if (manager->manager != NULL) {
+                xsettings_manager_destroy (manager->manager);
+                manager->manager = NULL;
+        }
+
+        if (manager->plugin_settings != NULL) {
+                g_signal_handlers_disconnect_by_data (manager->plugin_settings, manager);
+                g_object_unref (manager->plugin_settings);
+                manager->plugin_settings = NULL;
+        }
+
+        if (manager->gtk_settings_name_id > 0) {
+                g_bus_unown_name (manager->gtk_settings_name_id);
+                manager->gtk_settings_name_id = 0;
+        }
+
+        if (manager->fontconfig_monitor != NULL) {
+                g_signal_handlers_disconnect_by_data (manager->fontconfig_monitor, manager);
+                fc_monitor_stop (manager->fontconfig_monitor);
+                g_object_unref (manager->fontconfig_monitor);
+                manager->fontconfig_monitor = NULL;
+        }
+
+        if (manager->settings != NULL) {
+                g_hash_table_destroy (manager->settings);
+                manager->settings = NULL;
+        }
+
+        if (manager->gtk != NULL) {
+                g_object_unref (manager->gtk);
+                manager->gtk = NULL;
+        }
+
+        if (manager->user_seat != NULL) {
+                g_signal_handler_disconnect (manager->user_seat, manager->device_added_id);
+                g_signal_handler_disconnect (manager->user_seat, manager->device_removed_id);
+                manager->user_seat = NULL;
+        }
+
+        g_clear_object (&manager->a11y_settings);
+        g_clear_object (&manager->input_sources_settings);
+        g_clear_object (&manager->interface_settings);
+}
+
+static void
+gsd_xsettings_manager_class_init (GsdXSettingsManagerClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_xsettings_manager_finalize;
+}
+
+static void
+gsd_xsettings_manager_init (GsdXSettingsManager *manager)
+{
+        GError *error = NULL;
+
+        manager->dbus_connection = g_bus_get_sync (G_BUS_TYPE_SESSION,
+                                                         NULL, &error);
+        if (!manager->dbus_connection)
+                g_error ("Failed to get session bus: %s", error->message);
+}
+
+static void
+gsd_xsettings_manager_finalize (GObject *object)
+{
+        GsdXSettingsManager *xsettings_manager;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSD_IS_XSETTINGS_MANAGER (object));
+
+        xsettings_manager = GSD_XSETTINGS_MANAGER (object);
+
+        g_return_if_fail (xsettings_manager != NULL);
+
+        gsd_xsettings_manager_stop (xsettings_manager);
+
+        if (xsettings_manager->start_idle_id != 0)
+                g_source_remove (xsettings_manager->start_idle_id);
+
+        g_clear_object (&xsettings_manager->dbus_connection);
+
+        G_OBJECT_CLASS (gsd_xsettings_manager_parent_class)->finalize (object);
+}
+
+static GVariant *
+handle_get_property (GDBusConnection *connection,
+                     const gchar *sender,
+                     const gchar *object_path,
+                     const gchar *interface_name,
+                     const gchar *property_name,
+                     GError **error,
+                     gpointer user_data)
+{
+        GsdXSettingsManager *manager = user_data;
+
+        if (g_strcmp0 (property_name, "FontconfigTimestamp") == 0) {
+                return g_variant_new_int64 (manager->fontconfig_timestamp);
+        } else if (g_strcmp0 (property_name, "Modules") == 0) {
+                const char *modules = gsd_xsettings_gtk_get_modules (manager->gtk);
+                return g_variant_new_string (modules ? modules : "");
+        } else if (g_strcmp0 (property_name, "EnableAnimations") == 0) {
+                return g_variant_new_boolean (manager->enable_animations);
+        } else {
+                g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+                             "No such interface: %s", interface_name);
+                return NULL;
+        }
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+        NULL,
+        handle_get_property,
+        NULL
+};
+
+static void
+register_manager_dbus (GsdXSettingsManager *manager)
+{
+        g_assert (manager->dbus_connection != NULL);
+
+        manager->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+        g_assert (manager->introspection_data != NULL);
+
+        g_dbus_connection_register_object (manager->dbus_connection,
+                                           GTK_SETTINGS_DBUS_PATH,
+                                           manager->introspection_data->interfaces[0],
+                                           &interface_vtable,
+                                           manager,
+                                           NULL,
+                                           NULL);
+
+        manager->gtk_settings_name_id = g_bus_own_name_on_connection (manager->dbus_connection,
+                                                                      GTK_SETTINGS_DBUS_NAME,
+                                                                      G_BUS_NAME_OWNER_FLAGS_NONE,
+                                                                      NULL, NULL, NULL, NULL);
+}
+
+GsdXSettingsManager *
+gsd_xsettings_manager_new (void)
+{
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_XSETTINGS_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+        }
+
+        return GSD_XSETTINGS_MANAGER (manager_object);
+}
diff --git a/plugins/xsettings/gsd-xsettings-manager.h b/plugins/xsettings/gsd-xsettings-manager.h
new file mode 100644
index 0000000..96fff13
--- /dev/null
+++ b/plugins/xsettings/gsd-xsettings-manager.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 __GSD_XSETTINGS_MANAGER_H
+#define __GSD_XSETTINGS_MANAGER_H
+
+#include 
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_XSETTINGS_MANAGER         (gsd_xsettings_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdXSettingsManager, gsd_xsettings_manager, GSD, XSETTINGS_MANAGER, GObject)
+
+GsdXSettingsManager   * gsd_xsettings_manager_new                 (void);
+gboolean                gsd_xsettings_manager_start               (GsdXSettingsManager *manager,
+                                                                   GError             **error);
+void                    gsd_xsettings_manager_stop                (GsdXSettingsManager *manager);
+
+G_END_DECLS
+
+#endif /* __GNOME_XSETTINGS_MANAGER_H */
diff --git a/plugins/xsettings/gtk-modules-test/canberra-gtk-module.desktop b/plugins/xsettings/gtk-modules-test/canberra-gtk-module.desktop
new file mode 100644
index 0000000..80a087b
--- /dev/null
+++ b/plugins/xsettings/gtk-modules-test/canberra-gtk-module.desktop
@@ -0,0 +1,6 @@
+[GTK Module]
+Name=canberra-gtk-module
+Description=Event Sound Module
+X-GTK-Module-Name=canberra-gtk-module
+X-GTK-Module-Enabled-Schema=org.gnome.desktop.sound
+X-GTK-Module-Enabled-Key=event-sounds
diff --git a/plugins/xsettings/gtk-modules-test/pk-gtk-module.desktop b/plugins/xsettings/gtk-modules-test/pk-gtk-module.desktop
new file mode 100644
index 0000000..f13cf0f
--- /dev/null
+++ b/plugins/xsettings/gtk-modules-test/pk-gtk-module.desktop
@@ -0,0 +1,4 @@
+[GTK Module]
+Name=PackageKit
+Description=PackageKit Font Installer
+X-GTK-Module-Name=pk-gtk-module
diff --git a/plugins/xsettings/main.c b/plugins/xsettings/main.c
new file mode 100644
index 0000000..65d9a20
--- /dev/null
+++ b/plugins/xsettings/main.c
@@ -0,0 +1,8 @@
+#define NEW gsd_xsettings_manager_new
+#define START gsd_xsettings_manager_start
+#define STOP gsd_xsettings_manager_stop
+#define MANAGER GsdXSettingsManager
+#define GDK_BACKEND "x11"
+#include "gsd-xsettings-manager.h"
+
+#include "daemon-skeleton-gtk.h"
diff --git a/plugins/xsettings/meson.build b/plugins/xsettings/meson.build
new file mode 100644
index 0000000..49ed70f
--- /dev/null
+++ b/plugins/xsettings/meson.build
@@ -0,0 +1,69 @@
+gsd_xsettings_gtk = files('gsd-xsettings-gtk.c')
+
+fc_monitor = files('fc-monitor.c')
+
+wm_button_layout_translation = files('wm-button-layout-translation.c')
+
+sources = gsd_xsettings_gtk + fc_monitor + wm_button_layout_translation + files(
+  'gsd-xsettings-manager.c',
+  'xsettings-common.c',
+  'xsettings-manager.c',
+  'main.c'
+)
+
+deps = plugins_deps + [
+  gtk_dep,
+  x11_dep,
+  xfixes_dep,
+  libcommon_dep,
+  gsettings_desktop_dep,
+  dependency('fontconfig')
+]
+
+cflags += ['-DGTK_MODULES_DIRECTORY="@0@"'.format(gsd_gtk_modules_directory)]
+
+executable(
+  'gsd-' + plugin_name,
+  sources,
+  include_directories: [top_inc, common_inc, data_inc],
+  dependencies: deps,
+  c_args: cflags,
+  install: true,
+  install_rpath: gsd_pkglibdir,
+  install_dir: gsd_libexecdir
+)
+
+programs = [
+  ['test-gtk-modules', gsd_xsettings_gtk + ['test-gtk-modules.c'], cflags],
+  ['test-fontconfig-monitor', fc_monitor, cflags + ['-DFONTCONFIG_MONITOR_TEST']],
+  ['test-wm-button-layout-translations', wm_button_layout_translation + ['test-wm-button-layout-translations.c'], []]
+]
+
+foreach program: programs
+  executable(
+    program[0],
+    program[1],
+    include_directories: top_inc,
+    dependencies: deps,
+    c_args: program[2]
+  )
+endforeach
+
+install_data(
+  files('00-xrdb'),
+  install_dir: join_paths(gsd_sysconfdir, 'xdg/Xwayland-session.d')
+)
+
+test_py = find_program('test.py')
+
+envs = [
+  'BUILDDIR=' + meson.current_build_dir(),
+  'TOP_BUILDDIR=' + meson.build_root()
+]
+
+test(
+  'test-xsettings',
+  test_py,
+  env: envs,
+  timeout: 300
+)
diff --git a/plugins/xsettings/test-gtk-modules.c b/plugins/xsettings/test-gtk-modules.c
new file mode 100644
index 0000000..ef83fc3
--- /dev/null
+++ b/plugins/xsettings/test-gtk-modules.c
@@ -0,0 +1,31 @@
+
+
+#include "gsd-xsettings-gtk.h"
+
+static void
+gtk_modules_callback (GsdXSettingsGtk       *gtk,
+		      GParamSpec            *spec,
+		      gpointer               user_data)
+{
+	const char *modules;
+
+	modules = gsd_xsettings_gtk_get_modules (gtk);
+	g_message ("GTK+ modules list changed to: %s", modules ? modules : "(empty)");
+}
+
+int main (int argc, char **argv)
+{
+	GMainLoop *loop;
+	GsdXSettingsGtk *gtk;
+
+	gtk = gsd_xsettings_gtk_new ();
+        g_signal_connect (G_OBJECT (gtk), "notify::gtk-modules",
+			  G_CALLBACK (gtk_modules_callback), NULL);
+
+	gtk_modules_callback (gtk, NULL, NULL);
+
+	loop = g_main_loop_new (NULL, TRUE);
+	g_main_loop_run (loop);
+
+	return 0;
+}
diff --git a/plugins/xsettings/test-wm-button-layout-translations.c b/plugins/xsettings/test-wm-button-layout-translations.c
new file mode 100644
index 0000000..5ab140a
--- /dev/null
+++ b/plugins/xsettings/test-wm-button-layout-translations.c
@@ -0,0 +1,54 @@
+#include 
+
+#include "wm-button-layout-translation.h"
+
+static void
+test_button_layout_translations (void)
+{
+  static struct {
+    char *layout;
+    char *expected;
+  } tests[] = {
+    { "", "" },
+    { "invalid", "" },
+
+    { ":", ":" },
+    { ":invalid", ":" },
+    { "invalid:", ":" },
+    { "invalid:invalid", ":" },
+
+    { "appmenu", "menu" },
+    { "appmenu:", "menu:" },
+    { ":menu", ":icon" },
+    { "appmenu:close", "menu:close" },
+    { "appmenu:minimize,maximize,close", "menu:minimize,maximize,close" },
+    { "menu,appmenu:minimize,maximize,close", "icon,menu:minimize,maximize,close" },
+
+    { "close,close,close:close,close,close", "close,close,close:close,close,close" },
+
+    { "invalid,appmenu:invalid,minimize", "menu:minimize" },
+    { "appmenu,invalid:minimize,invalid", "menu:minimize" },
+    { "invalidmenu:invalidclose", ":" },
+    { "invalid,invalid,invalid:invalid,minimize,maximize,close", ":minimize,maximize,close" },
+  };
+  int i;
+
+  for (i = 0; i < G_N_ELEMENTS (tests); i++)
+    {
+      char *layout = g_strdup (tests[i].layout);
+
+      translate_wm_button_layout_to_gtk (layout);
+      g_assert_cmpstr (layout, ==, tests[i].expected);
+      g_free (layout);
+    }
+}
+
+int
+main (int argc, char *argv[])
+{
+  g_test_init (&argc, &argv, NULL);
+
+  g_test_add_func ("/layout-translations", test_button_layout_translations);
+
+  return g_test_run ();
+}
diff --git a/plugins/xsettings/test.py b/plugins/xsettings/test.py
new file mode 100755
index 0000000..0909752
--- /dev/null
+++ b/plugins/xsettings/test.py
@@ -0,0 +1,140 @@
+#!/usr/bin/python3 -u
+'''GNOME settings daemon tests for xsettings plugin.'''
+
+__author__ = 'Bastien Nocera '
+__copyright__ = '(C) 2018 Red Hat, Inc.'
+__license__ = 'GPL v2 or later'
+
+import unittest
+import subprocess
+import sys
+import time
+import os
+import os.path
+import signal
+import shutil
+
+project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+builddir = os.environ.get('BUILDDIR', os.path.dirname(__file__))
+
+sys.path.insert(0, os.path.join(project_root, 'tests'))
+sys.path.insert(0, builddir)
+import gsdtestcase
+import dbus
+import dbusmock
+from output_checker import OutputChecker
+
+from gi.repository import Gio
+from gi.repository import GLib
+
+class XsettingsPluginTest(gsdtestcase.GSDTestCase):
+    '''Test the xsettings plugin'''
+
+    gsd_plugin = 'xsettings'
+    gsd_plugin_case = 'XSettings'
+
+    def setUp(self):
+        self.start_logind()
+        self.addCleanup(self.stop_logind)
+
+        self.start_session()
+        self.addCleanup(self.stop_session)
+
+        self.obj_session_mgr = self.session_bus_con.get_object(
+            'org.gnome.SessionManager', '/org/gnome/SessionManager')
+
+        self.start_mutter()
+        self.addCleanup(self.stop_mutter)
+
+        Gio.Settings.sync()
+        os.environ['GSD_ignore_llvmpipe'] = '1'
+
+        # Setup fontconfig config path before starting the daemon
+        self.fc_dir = os.path.join(self.workdir, 'fontconfig')
+        os.environ['FONTCONFIG_PATH'] = self.fc_dir
+        try:
+            os.makedirs(self.fc_dir)
+        except:
+            pass
+        shutil.copy(os.path.join(os.path.dirname(__file__), 'fontconfig-test/fonts.conf'),
+                os.path.join(self.fc_dir, 'fonts.conf'))
+
+        # Setup GTK+ modules before starting the daemon
+        modules_dir = os.path.join(self.workdir, 'gtk-modules')
+        os.environ['GSD_gtk_modules_dir'] = modules_dir
+        try:
+            os.makedirs(modules_dir)
+        except:
+            pass
+        shutil.copy(os.path.join(os.path.dirname(__file__), 'gtk-modules-test/canberra-gtk-module.desktop'),
+                os.path.join(modules_dir, 'canberra-gtk-module.desktop'))
+        shutil.copy(os.path.join(os.path.dirname(__file__), 'gtk-modules-test/pk-gtk-module.desktop'),
+                os.path.join(modules_dir, 'pk-gtk-module.desktop'))
+
+        self.settings_sound = Gio.Settings.new('org.gnome.desktop.sound')
+        self.addCleanup(self.reset_settings, self.settings_sound)
+        Gio.Settings.sync()
+
+        env = os.environ.copy()
+        self.start_plugin(os.environ.copy())
+
+        # flush notification log
+        self.p_notify_log.clear()
+
+        self.plugin_log.check_line(b'GsdXSettingsGtk initializing', timeout=10)
+
+        obj_xsettings = self.session_bus_con.get_object(
+            'org.gtk.Settings', '/org/gtk/Settings')
+        self.obj_xsettings_props = dbus.Interface(obj_xsettings, dbus.PROPERTIES_IFACE)
+
+    def check_plugin_log(self, needle, timeout=0, failmsg=None):
+        '''Check that needle is found in the log within the given timeout.
+        Returns immediately when found.
+
+        Fail after the given timeout.
+        '''
+        self.plugin_log.check_line(needle, timeout=timeout, failmsg=failmsg)
+
+    def test_gtk_modules(self):
+        # Turn off event sounds
+        self.settings_sound['event-sounds'] = False
+        Gio.Settings.sync()
+        time.sleep(2)
+
+        # Verify that only the PackageKit plugin is enabled
+        self.assertEqual(self.obj_xsettings_props.Get('org.gtk.Settings', 'Modules'),
+                dbus.String('pk-gtk-module', variant_level=1))
+
+        # Turn on sounds
+        self.settings_sound['event-sounds'] = True
+        Gio.Settings.sync()
+        time.sleep(2)
+
+        # Check that both PK and canberra plugin are enabled
+        retval = self.obj_xsettings_props.Get('org.gtk.Settings', 'Modules')
+        values = sorted(str(retval).split(':'))
+        self.assertEqual(values, ['canberra-gtk-module', 'pk-gtk-module'])
+
+    def test_fontconfig_timestamp(self):
+        # Initially, the value is zero
+        before = self.obj_xsettings_props.Get('org.gtk.Settings', 'FontconfigTimestamp')
+        self.assertEqual(before, 0)
+
+        # Make sure the seconds changed
+        time.sleep(1)
+
+        # Copy the fonts.conf again
+        shutil.copy(os.path.join(os.path.dirname(__file__), 'fontconfig-test/fonts.conf'),
+                os.path.join(self.fc_dir, 'fonts.conf'))
+
+        # Wait for gsd-xsettings to pick up the change (and process it)
+        self.check_plugin_log("Fontconfig update successful", timeout=5, failmsg="Fontconfig was not updated!")
+
+        # Sleep a bit to ensure that the setting is updated
+        time.sleep(1)
+
+        after = self.obj_xsettings_props.Get('org.gtk.Settings', 'FontconfigTimestamp')
+        self.assertTrue(after > before)
+
+# avoid writing to stderr
+unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))
diff --git a/plugins/xsettings/wm-button-layout-translation.c b/plugins/xsettings/wm-button-layout-translation.c
new file mode 100644
index 0000000..0fa4d2c
--- /dev/null
+++ b/plugins/xsettings/wm-button-layout-translation.c
@@ -0,0 +1,88 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2014 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 .
+ *
+ * Author:  Florian Müllner 
+ */
+
+#include 
+#include 
+#include 
+
+#include "wm-button-layout-translation.h"
+
+static void
+translate_buttons (char *layout, int *len_p)
+{
+  char *strp = layout, *button;
+  int len = 0;
+
+  if (!layout || !*layout)
+    goto out;
+
+  while ((button = strsep (&strp, ",")))
+    {
+      char *gtkbutton;
+
+      if (strcmp (button, "menu") == 0)
+        gtkbutton = "icon";
+      else if (strcmp (button, "appmenu") == 0)
+        gtkbutton = "menu";
+      else if (strcmp (button, "minimize") == 0)
+        gtkbutton = "minimize";
+      else if (strcmp (button, "maximize") == 0)
+        gtkbutton = "maximize";
+      else if  (strcmp (button, "close") == 0)
+        gtkbutton = "close";
+      else
+        continue;
+
+      if (len)
+        layout[len++] = ',';
+
+      strcpy (layout + len, gtkbutton);
+      len += strlen (gtkbutton);
+    }
+  layout[len] = '\0';
+
+out:
+  if (len_p)
+    *len_p = len;
+}
+
+void
+translate_wm_button_layout_to_gtk (char *layout)
+{
+  char *strp = layout, *left_buttons, *right_buttons;
+  int left_len, right_len = 0;
+
+  left_buttons = strsep (&strp, ":");
+  right_buttons = strp;
+
+  translate_buttons (left_buttons, &left_len);
+  memmove (layout, left_buttons, left_len);
+
+  if (strp == NULL)
+    goto out; /* no ":" in layout */
+
+  layout[left_len++] = ':';
+
+  translate_buttons (right_buttons, &right_len);
+  memmove (layout + left_len, right_buttons, right_len);
+
+out:
+  layout[left_len + right_len] = '\0';
+}
diff --git a/plugins/xsettings/wm-button-layout-translation.h b/plugins/xsettings/wm-button-layout-translation.h
new file mode 100644
index 0000000..87210b6
--- /dev/null
+++ b/plugins/xsettings/wm-button-layout-translation.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2014 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU 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 .
+ *
+ * Author:  Florian Müllner 
+ */
+
+#ifndef __WM_BUTTON_LAYOUT_TRANSLATION__
+#define __WM_BUTTON_LAYOUT_TRANSLATION__
+
+void translate_wm_button_layout_to_gtk (char *layout);
+
+#endif
diff --git a/plugins/xsettings/xsettings-common.c b/plugins/xsettings/xsettings-common.c
new file mode 100644
index 0000000..53c421a
--- /dev/null
+++ b/plugins/xsettings/xsettings-common.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright © 2001 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of Red Hat not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  Red Hat makes no representations about the
+ * suitability of this software for any purpose.  It is provided "as is"
+ * without express or implied warranty.
+ *
+ * RED HAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL RED HAT
+ * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Author:  Owen Taylor, Red Hat, Inc.
+ */
+
+#include 
+
+#include "string.h"
+#include "stdlib.h"
+
+#include 
+#include 		/* For CARD32 */
+
+#include "xsettings-common.h"
+
+XSettingsSetting *
+xsettings_setting_new (const gchar *name)
+{
+  XSettingsSetting *result;
+
+  result = g_new0 (XSettingsSetting, 1);
+  result->name = g_strdup (name);
+
+  return result;
+}
+
+static gboolean
+xsettings_variant_equal0 (GVariant *a,
+                          GVariant *b)
+{
+  if (a == b)
+    return TRUE;
+
+  if (!a || !b)
+    return FALSE;
+
+  return g_variant_equal (a, b);
+}
+
+GVariant *
+xsettings_setting_get (XSettingsSetting *setting)
+{
+  gint i;
+
+  for (i = G_N_ELEMENTS (setting->value) - 1; 0 <= i; i--)
+    if (setting->value[i])
+      return setting->value[i];
+
+  return NULL;
+}
+
+void
+xsettings_setting_set (XSettingsSetting *setting,
+                       gint              tier,
+                       GVariant         *value,
+                       guint32           serial)
+{
+  GVariant *old_value;
+
+  old_value = xsettings_setting_get (setting);
+  if (old_value)
+    g_variant_ref (old_value);
+
+  if (setting->value[tier])
+    g_variant_unref (setting->value[tier]);
+  setting->value[tier] = value ? g_variant_ref_sink (value) : NULL;
+
+  if (!xsettings_variant_equal0 (old_value, xsettings_setting_get (setting)))
+    setting->last_change_serial = serial;
+
+  if (old_value)
+    g_variant_unref (old_value);
+}
+
+void
+xsettings_setting_free (XSettingsSetting *setting)
+{
+  gint i;
+
+  for (i = 0; i < G_N_ELEMENTS (setting->value); i++)
+    if (setting->value[i])
+      g_variant_unref (setting->value[i]);
+
+  g_free (setting->name);
+  g_free (setting);
+}
+
+char
+xsettings_byte_order (void)
+{
+  CARD32 myint = 0x01020304;
+  return (*(char *)&myint == 1) ? MSBFirst : LSBFirst;
+}
diff --git a/plugins/xsettings/xsettings-common.h b/plugins/xsettings/xsettings-common.h
new file mode 100644
index 0000000..2062f47
--- /dev/null
+++ b/plugins/xsettings/xsettings-common.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright © 2001 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of Red Hat not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  Red Hat makes no representations about the
+ * suitability of this software for any purpose.  It is provided "as is"
+ * without express or implied warranty.
+ *
+ * RED HAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL RED HAT
+ * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Author:  Owen Taylor, Red Hat, Inc.
+ */
+#ifndef XSETTINGS_COMMON_H
+#define XSETTINGS_COMMON_H
+
+#include 
+
+#define XSETTINGS_N_TIERS 2
+
+typedef struct _XSettingsColor   XSettingsColor;
+typedef struct _XSettingsSetting XSettingsSetting;
+
+/* Types of settings possible. Enum values correspond to
+ * protocol values.
+ */
+typedef enum 
+{
+  XSETTINGS_TYPE_INT     = 0,
+  XSETTINGS_TYPE_STRING  = 1,
+  XSETTINGS_TYPE_COLOR   = 2
+} XSettingsType;
+
+struct _XSettingsColor
+{
+  unsigned short red, green, blue, alpha;
+};
+
+struct _XSettingsSetting
+{
+  char *name;
+  GVariant *value[XSETTINGS_N_TIERS];
+  unsigned long last_change_serial;
+};
+
+XSettingsSetting *xsettings_setting_new   (const gchar      *name);
+GVariant *        xsettings_setting_get   (XSettingsSetting *setting);
+void              xsettings_setting_set   (XSettingsSetting *setting,
+                                           gint              tier,
+                                           GVariant         *value,
+                                           guint32           serial);
+void              xsettings_setting_free  (XSettingsSetting *setting);
+
+char xsettings_byte_order (void);
+
+#endif /* XSETTINGS_COMMON_H */
diff --git a/plugins/xsettings/xsettings-manager.c b/plugins/xsettings/xsettings-manager.c
new file mode 100644
index 0000000..89edef2
--- /dev/null
+++ b/plugins/xsettings/xsettings-manager.c
@@ -0,0 +1,396 @@
+/*
+ * Copyright © 2001 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of Red Hat not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  Red Hat makes no representations about the
+ * suitability of this software for any purpose.  It is provided "as is"
+ * without express or implied warranty.
+ *
+ * RED HAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL RED HAT
+ * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Author:  Owen Taylor, Red Hat, Inc.
+ */
+#include 
+#include 
+#include 
+
+#include 
+#include 		/* For CARD16 */
+
+#include "xsettings-manager.h"
+
+#define XSETTINGS_VARIANT_TYPE_COLOR  (G_VARIANT_TYPE ("(qqqq)"))
+
+struct _XSettingsManager
+{
+  Display *display;
+  int screen;
+
+  Window window;
+  Atom manager_atom;
+  Atom selection_atom;
+  Atom xsettings_atom;
+
+  XSettingsTerminateFunc terminate;
+  void *cb_data;
+
+  GHashTable *settings;
+  unsigned long serial;
+
+  GVariant *overrides;
+};
+
+typedef struct 
+{
+  Window window;
+  Atom timestamp_prop_atom;
+} TimeStampInfo;
+
+static Bool
+timestamp_predicate (Display *display,
+		     XEvent  *xevent,
+		     XPointer arg)
+{
+  TimeStampInfo *info = (TimeStampInfo *)arg;
+
+  if (xevent->type == PropertyNotify &&
+      xevent->xproperty.window == info->window &&
+      xevent->xproperty.atom == info->timestamp_prop_atom)
+    return True;
+
+  return False;
+}
+
+/**
+ * get_server_time:
+ * @display: display from which to get the time
+ * @window: a #Window, used for communication with the server.
+ *          The window must have PropertyChangeMask in its
+ *          events mask or a hang will result.
+ * 
+ * Routine to get the current X server time stamp. 
+ * 
+ * Return value: the time stamp.
+ **/
+static Time
+get_server_time (Display *display,
+		 Window   window)
+{
+  unsigned char c = 'a';
+  XEvent xevent;
+  TimeStampInfo info;
+
+  info.timestamp_prop_atom = XInternAtom  (display, "_TIMESTAMP_PROP", False);
+  info.window = window;
+
+  XChangeProperty (display, window,
+		   info.timestamp_prop_atom, info.timestamp_prop_atom,
+		   8, PropModeReplace, &c, 1);
+
+  XIfEvent (display, &xevent,
+	    timestamp_predicate, (XPointer)&info);
+
+  return xevent.xproperty.time;
+}
+
+Bool
+xsettings_manager_check_running (Display *display,
+				 int      screen)
+{
+  char buffer[256];
+  Atom selection_atom;
+  
+  sprintf(buffer, "_XSETTINGS_S%d", screen);
+  selection_atom = XInternAtom (display, buffer, False);
+
+  if (XGetSelectionOwner (display, selection_atom))
+    return True;
+  else
+    return False;
+}
+
+XSettingsManager *
+xsettings_manager_new (Display                *display,
+		       int                     screen,
+		       XSettingsTerminateFunc  terminate,
+		       void                   *cb_data)
+{
+  XSettingsManager *manager;
+  Time timestamp;
+  XClientMessageEvent xev;
+
+  char buffer[256];
+
+
+  XFixesSetClientDisconnectMode (display, XFixesClientDisconnectFlagTerminate);
+
+  manager = g_new (XSettingsManager, 1);
+
+  manager->display = display;
+  manager->screen = screen;
+
+  sprintf(buffer, "_XSETTINGS_S%d", screen);
+  manager->selection_atom = XInternAtom (display, buffer, False);
+  manager->xsettings_atom = XInternAtom (display, "_XSETTINGS_SETTINGS", False);
+  manager->manager_atom = XInternAtom (display, "MANAGER", False);
+
+  manager->terminate = terminate;
+  manager->cb_data = cb_data;
+
+  manager->settings = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) xsettings_setting_free);
+  manager->serial = 0;
+  manager->overrides = NULL;
+
+  manager->window = XCreateSimpleWindow (display,
+					 RootWindow (display, screen),
+					 0, 0, 10, 10, 0,
+					 WhitePixel (display, screen),
+					 WhitePixel (display, screen));
+
+  XSelectInput (display, manager->window, PropertyChangeMask);
+  timestamp = get_server_time (display, manager->window);
+
+  XSetSelectionOwner (display, manager->selection_atom,
+		      manager->window, timestamp);
+
+  /* Check to see if we managed to claim the selection. If not,
+   * we treat it as if we got it then immediately lost it
+   */
+
+  if (XGetSelectionOwner (display, manager->selection_atom) ==
+      manager->window)
+    {
+      xev.type = ClientMessage;
+      xev.window = RootWindow (display, screen);
+      xev.message_type = manager->manager_atom;
+      xev.format = 32;
+      xev.data.l[0] = timestamp;
+      xev.data.l[1] = manager->selection_atom;
+      xev.data.l[2] = manager->window;
+      xev.data.l[3] = 0;	/* manager specific data */
+      xev.data.l[4] = 0;	/* manager specific data */
+      
+      XSendEvent (display, RootWindow (display, screen),
+		  False, StructureNotifyMask, (XEvent *)&xev);
+    }
+  else
+    {
+      manager->terminate (manager->cb_data);
+    }
+  
+  return manager;
+}
+
+void
+xsettings_manager_destroy (XSettingsManager *manager)
+{
+  XDestroyWindow (manager->display, manager->window);
+
+  g_hash_table_unref (manager->settings);
+
+  g_free (manager);
+}
+
+static void
+xsettings_manager_set_setting (XSettingsManager *manager,
+                               const gchar      *name,
+                               gint              tier,
+                               GVariant         *value)
+{
+  XSettingsSetting *setting;
+
+  setting = g_hash_table_lookup (manager->settings, name);
+
+  if (setting == NULL)
+    {
+      setting = xsettings_setting_new (name);
+      setting->last_change_serial = manager->serial;
+      g_hash_table_insert (manager->settings, setting->name, setting);
+    }
+
+  xsettings_setting_set (setting, tier, value, manager->serial);
+
+  if (xsettings_setting_get (setting) == NULL)
+    g_hash_table_remove (manager->settings, name);
+}
+
+void
+xsettings_manager_set_int (XSettingsManager *manager,
+			   const char       *name,
+			   int               value)
+{
+  xsettings_manager_set_setting (manager, name, 0, g_variant_new_int32 (value));
+}
+
+void
+xsettings_manager_set_string (XSettingsManager *manager,
+			      const char       *name,
+			      const char       *value)
+{
+  xsettings_manager_set_setting (manager, name, 0, g_variant_new_string (value));
+}
+
+void
+xsettings_manager_set_color (XSettingsManager *manager,
+			     const char       *name,
+			     XSettingsColor   *value)
+{
+  GVariant *tmp;
+
+  tmp = g_variant_new ("(qqqq)", value->red, value->green, value->blue, value->alpha);
+  g_assert (g_variant_is_of_type (tmp, XSETTINGS_VARIANT_TYPE_COLOR)); /* paranoia... */
+  xsettings_manager_set_setting (manager, name, 0, tmp);
+}
+
+void
+xsettings_manager_delete_setting (XSettingsManager *manager,
+                                  const char       *name)
+{
+  xsettings_manager_set_setting (manager, name, 0, NULL);
+}
+
+static gchar
+xsettings_get_typecode (GVariant *value)
+{
+  switch (g_variant_classify (value))
+    {
+    case G_VARIANT_CLASS_INT32:
+      return XSETTINGS_TYPE_INT;
+    case G_VARIANT_CLASS_STRING:
+      return XSETTINGS_TYPE_STRING;
+    case G_VARIANT_CLASS_TUPLE:
+      return XSETTINGS_TYPE_COLOR;
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+align_string (GString *string,
+              gint     alignment)
+{
+  /* Adds nul-bytes to the string until its length is an even multiple
+   * of the specified alignment requirement.
+   */
+  while ((string->len % alignment) != 0)
+    g_string_append_c (string, '\0');
+}
+
+static void
+setting_store (XSettingsSetting *setting,
+               GString          *buffer)
+{
+  XSettingsType type;
+  GVariant *value;
+  guint16 len16;
+
+  value = xsettings_setting_get (setting);
+
+  type = xsettings_get_typecode (value);
+
+  g_string_append_c (buffer, type);
+  g_string_append_c (buffer, 0);
+
+  len16 = strlen (setting->name);
+  g_string_append_len (buffer, (gchar *) &len16, 2);
+  g_string_append (buffer, setting->name);
+  align_string (buffer, 4);
+
+  g_string_append_len (buffer, (gchar *) &setting->last_change_serial, 4);
+
+  if (type == XSETTINGS_TYPE_STRING)
+    {
+      const gchar *string;
+      gsize stringlen;
+      guint32 len32;
+
+      string = g_variant_get_string (value, &stringlen);
+      len32 = stringlen;
+      g_string_append_len (buffer, (gchar *) &len32, 4);
+      g_string_append (buffer, string);
+      align_string (buffer, 4);
+    }
+  else
+    /* GVariant format is the same as XSETTINGS format for the non-string types */
+    g_string_append_len (buffer, g_variant_get_data (value), g_variant_get_size (value));
+}
+
+void
+xsettings_manager_notify (XSettingsManager *manager)
+{
+  GString *buffer;
+  GHashTableIter iter;
+  int n_settings;
+  gpointer value;
+
+  n_settings = g_hash_table_size (manager->settings);
+
+  buffer = g_string_new (NULL);
+  g_string_append_c (buffer, xsettings_byte_order ());
+  g_string_append_c (buffer, '\0');
+  g_string_append_c (buffer, '\0');
+  g_string_append_c (buffer, '\0');
+
+  g_string_append_len (buffer, (gchar *) &manager->serial, 4);
+  g_string_append_len (buffer, (gchar *) &n_settings, 4);
+
+  g_hash_table_iter_init (&iter, manager->settings);
+  while (g_hash_table_iter_next (&iter, NULL, &value))
+    setting_store (value, buffer);
+
+  XChangeProperty (manager->display, manager->window,
+                   manager->xsettings_atom, manager->xsettings_atom,
+                   8, PropModeReplace, (guchar *) buffer->str, buffer->len);
+
+  g_string_free (buffer, TRUE);
+  manager->serial++;
+}
+
+void
+xsettings_manager_set_overrides (XSettingsManager *manager,
+                                 GVariant         *overrides)
+{
+  GVariantIter iter;
+  const gchar *key;
+  GVariant *value;
+
+  g_return_if_fail (overrides != NULL && g_variant_is_of_type (overrides, G_VARIANT_TYPE_VARDICT));
+
+  if (manager->overrides)
+    {
+      /* unset the existing overrides */
+
+      g_variant_iter_init (&iter, manager->overrides);
+      while (g_variant_iter_next (&iter, "{&sv}", &key, NULL))
+        /* only unset it at this point if it's not in the new list */
+        if (!g_variant_lookup (overrides, key, "*", NULL))
+          xsettings_manager_set_setting (manager, key, 1, NULL);
+      g_variant_unref (manager->overrides);
+    }
+
+  /* save this so we can do the unsets next time */
+  manager->overrides = g_variant_ref_sink (overrides);
+
+  /* set the new values */
+  g_variant_iter_init (&iter, overrides);
+  while (g_variant_iter_loop (&iter, "{&sv}", &key, &value))
+    {
+      /* only accept recognised types... */
+      if (!g_variant_is_of_type (value, G_VARIANT_TYPE_STRING) &&
+          !g_variant_is_of_type (value, G_VARIANT_TYPE_INT32) &&
+          !g_variant_is_of_type (value, XSETTINGS_VARIANT_TYPE_COLOR))
+        continue;
+
+      xsettings_manager_set_setting (manager, key, 1, value);
+    }
+}
diff --git a/plugins/xsettings/xsettings-manager.h b/plugins/xsettings/xsettings-manager.h
new file mode 100644
index 0000000..6346b09
--- /dev/null
+++ b/plugins/xsettings/xsettings-manager.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright © 2001 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of Red Hat not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  Red Hat makes no representations about the
+ * suitability of this software for any purpose.  It is provided "as is"
+ * without express or implied warranty.
+ *
+ * RED HAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL RED HAT
+ * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Author:  Owen Taylor, Red Hat, Inc.
+ */
+#ifndef XSETTINGS_MANAGER_H
+#define XSETTINGS_MANAGER_H
+
+#include 
+#include 
+#include "xsettings-common.h"
+
+typedef struct _XSettingsManager XSettingsManager;
+
+typedef void (*XSettingsTerminateFunc)  (void *cb_data);
+
+Bool xsettings_manager_check_running (Display *display,
+				      int      screen);
+
+XSettingsManager *xsettings_manager_new (Display                *display,
+					 int                     screen,
+					 XSettingsTerminateFunc  terminate,
+					 void                   *cb_data);
+
+void   xsettings_manager_destroy       (XSettingsManager *manager);
+
+void   xsettings_manager_delete_setting (XSettingsManager *manager,
+                                         const char       *name);
+void   xsettings_manager_set_int        (XSettingsManager *manager,
+                                         const char       *name,
+                                         int               value);
+void   xsettings_manager_set_string     (XSettingsManager *manager,
+                                         const char       *name,
+                                         const char       *value);
+void   xsettings_manager_set_color      (XSettingsManager *manager,
+                                         const char       *name,
+                                         XSettingsColor   *value);
+void   xsettings_manager_notify         (XSettingsManager *manager);
+void   xsettings_manager_set_overrides  (XSettingsManager *manager,
+                                         GVariant         *overrides);
+
+#endif /* XSETTINGS_MANAGER_H */
-- 
cgit v1.2.3