summaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 14:51:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 14:51:55 +0000
commit86b7f1a83d7db9c912f32b29c32e1124c0a6454d (patch)
tree42a7ff7c6885e99e0669d07b104df11b2bf387b6 /plugins
parentInitial commit. (diff)
downloadgnome-settings-daemon-86b7f1a83d7db9c912f32b29c32e1124c0a6454d.tar.xz
gnome-settings-daemon-86b7f1a83d7db9c912f32b29c32e1124c0a6454d.zip
Adding upstream version 3.38.2.upstream/3.38.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plugins')
-rw-r--r--plugins/a11y-settings/gsd-a11y-settings-manager.c162
-rw-r--r--plugins/a11y-settings/gsd-a11y-settings-manager.h37
-rw-r--r--plugins/a11y-settings/main.c7
-rw-r--r--plugins/a11y-settings/meson.build20
-rw-r--r--plugins/color/gcm-edid.c450
-rw-r--r--plugins/color/gcm-edid.h63
-rw-r--r--plugins/color/gcm-self-test.c430
-rw-r--r--plugins/color/gnome-datetime-source.c286
-rw-r--r--plugins/color/gnome-datetime-source.h38
-rw-r--r--plugins/color/gsd-color-calibrate.c412
-rw-r--r--plugins/color/gsd-color-calibrate.h38
-rw-r--r--plugins/color/gsd-color-manager.c502
-rw-r--r--plugins/color/gsd-color-manager.h46
-rw-r--r--plugins/color/gsd-color-profiles.c246
-rw-r--r--plugins/color/gsd-color-profiles.h40
-rw-r--r--plugins/color/gsd-color-state.c1592
-rw-r--r--plugins/color/gsd-color-state.h47
-rw-r--r--plugins/color/gsd-night-light-common.c137
-rw-r--r--plugins/color/gsd-night-light-common.h39
-rw-r--r--plugins/color/gsd-night-light.c801
-rw-r--r--plugins/color/gsd-night-light.h58
-rw-r--r--plugins/color/main.c7
-rw-r--r--plugins/color/meson.build55
-rw-r--r--plugins/color/test-data/LG-L225W-External.binbin0 -> 128 bytes
-rw-r--r--plugins/color/test-data/Lenovo-T61-Internal.binbin0 -> 128 bytes
-rw-r--r--plugins/common/daemon-skeleton-gtk.h292
-rw-r--r--plugins/common/daemon-skeleton.h264
-rw-r--r--plugins/common/gsd-input-helper.c79
-rw-r--r--plugins/common/gsd-input-helper.h33
-rw-r--r--plugins/common/gsd-settings-migrate.c74
-rw-r--r--plugins/common/gsd-settings-migrate.h43
-rw-r--r--plugins/common/gsd-shell-helper.c66
-rw-r--r--plugins/common/gsd-shell-helper.h42
-rw-r--r--plugins/common/gsd.gresources.xml6
-rw-r--r--plugins/common/gtk.css0
-rw-r--r--plugins/common/meson.build43
-rw-r--r--plugins/datetime/backward118
-rw-r--r--plugins/datetime/gsd-datetime-manager.c226
-rw-r--r--plugins/datetime/gsd-datetime-manager.h37
-rw-r--r--plugins/datetime/gsd-timezone-monitor.c472
-rw-r--r--plugins/datetime/gsd-timezone-monitor.h55
-rw-r--r--plugins/datetime/main.c7
-rw-r--r--plugins/datetime/meson.build40
-rw-r--r--plugins/datetime/timedated1-interface.xml28
-rw-r--r--plugins/datetime/tz.c482
-rw-r--r--plugins/datetime/tz.h89
-rw-r--r--plugins/datetime/weather-tz.c122
-rw-r--r--plugins/datetime/weather-tz.h27
-rw-r--r--plugins/gsd.service.in26
-rw-r--r--plugins/gsd.target.in12
-rw-r--r--plugins/housekeeping/gsd-disk-space-helper.c159
-rw-r--r--plugins/housekeeping/gsd-disk-space-helper.h38
-rw-r--r--plugins/housekeeping/gsd-disk-space-test.c48
-rw-r--r--plugins/housekeeping/gsd-disk-space.c1082
-rw-r--r--plugins/housekeeping/gsd-disk-space.h64
-rw-r--r--plugins/housekeeping/gsd-empty-trash-test.c44
-rw-r--r--plugins/housekeeping/gsd-housekeeping-manager.c512
-rw-r--r--plugins/housekeeping/gsd-housekeeping-manager.h38
-rw-r--r--plugins/housekeeping/gsd-purge-temp-test.c62
-rw-r--r--plugins/housekeeping/main.c7
-rw-r--r--plugins/housekeeping/meson.build41
-rw-r--r--plugins/keyboard/.indent.pro2
-rw-r--r--plugins/keyboard/gsd-keyboard-manager.c718
-rw-r--r--plugins/keyboard/gsd-keyboard-manager.h38
-rw-r--r--plugins/keyboard/main.c7
-rw-r--r--plugins/keyboard/meson.build21
-rw-r--r--plugins/media-keys/README.media-keys-API51
-rw-r--r--plugins/media-keys/audio-selection-test.c263
-rw-r--r--plugins/media-keys/bus-watch-namespace.c347
-rw-r--r--plugins/media-keys/bus-watch-namespace.h34
-rw-r--r--plugins/media-keys/gsd-marshal.list1
-rw-r--r--plugins/media-keys/gsd-media-keys-manager.c3920
-rw-r--r--plugins/media-keys/gsd-media-keys-manager.h47
-rw-r--r--plugins/media-keys/gsd-screenshot-utils.c310
-rw-r--r--plugins/media-keys/gsd-screenshot-utils.h36
-rw-r--r--plugins/media-keys/main.c7
-rw-r--r--plugins/media-keys/media-keys.h86
-rw-r--r--plugins/media-keys/meson.build59
-rw-r--r--plugins/media-keys/mpris-controller.c326
-rw-r--r--plugins/media-keys/mpris-controller.h36
-rw-r--r--plugins/media-keys/org.gnome.ShellKeyGrabber.xml27
-rw-r--r--plugins/media-keys/shell-action-modes.h53
-rw-r--r--plugins/media-keys/shortcuts-list.h110
-rwxr-xr-xplugins/meson-add-wants.sh30
-rw-r--r--plugins/meson.build194
-rw-r--r--plugins/org.gnome.SettingsDaemon.Dummy.desktop.in7
-rw-r--r--plugins/org.gnome.SettingsDaemon.Real.desktop.in10
-rw-r--r--plugins/power/gpm-common.c456
-rw-r--r--plugins/power/gpm-common.h59
-rw-r--r--plugins/power/gsd-backlight-helper.c153
-rw-r--r--plugins/power/gsd-backlight.c978
-rw-r--r--plugins/power/gsd-backlight.h81
-rwxr-xr-xplugins/power/gsd-power-constants-update.pl49
-rw-r--r--plugins/power/gsd-power-constants.h42
-rw-r--r--plugins/power/gsd-power-enums-update.c44
-rw-r--r--plugins/power/gsd-power-enums.c.in40
-rw-r--r--plugins/power/gsd-power-enums.h.in27
-rw-r--r--plugins/power/gsd-power-manager.c3270
-rw-r--r--plugins/power/gsd-power-manager.h47
-rw-r--r--plugins/power/gsdpowerconstants.py16
-rw-r--r--plugins/power/gsm-inhibitor-flag.h36
-rw-r--r--plugins/power/gsm-manager-logout-mode.h34
-rw-r--r--plugins/power/gsm-presence-flag.h33
-rw-r--r--plugins/power/main.c7
-rw-r--r--plugins/power/meson.build150
-rw-r--r--plugins/power/org.gnome.settings-daemon.plugins.power.policy.in.in32
-rwxr-xr-xplugins/power/test-backlight-helper5
-rwxr-xr-xplugins/power/test.py1397
-rw-r--r--plugins/print-notifications/gsd-print-notifications-manager.c1725
-rw-r--r--plugins/print-notifications/gsd-print-notifications-manager.h38
-rw-r--r--plugins/print-notifications/gsd-printer.c1403
-rw-r--r--plugins/print-notifications/main.c7
-rw-r--r--plugins/print-notifications/meson.build37
-rw-r--r--plugins/rfkill/61-gnome-settings-daemon-rfkill.rules8
-rw-r--r--plugins/rfkill/gsd-rfkill-manager.c902
-rw-r--r--plugins/rfkill/gsd-rfkill-manager.h39
-rw-r--r--plugins/rfkill/main.c7
-rw-r--r--plugins/rfkill/meson.build28
-rw-r--r--plugins/rfkill/rfkill-glib.c680
-rw-r--r--plugins/rfkill/rfkill-glib.h57
-rw-r--r--plugins/rfkill/rfkill.h107
-rw-r--r--plugins/screensaver-proxy/gsd-screensaver-proxy-manager.c444
-rw-r--r--plugins/screensaver-proxy/gsd-screensaver-proxy-manager.h38
-rw-r--r--plugins/screensaver-proxy/main.c7
-rw-r--r--plugins/screensaver-proxy/meson.build17
-rw-r--r--plugins/sharing/gsd-sharing-enums.h34
-rw-r--r--plugins/sharing/gsd-sharing-manager.c829
-rw-r--r--plugins/sharing/gsd-sharing-manager.h38
-rw-r--r--plugins/sharing/main.c7
-rw-r--r--plugins/sharing/meson.build24
-rw-r--r--plugins/smartcard/gsd-smartcard-enum-types.c.in42
-rw-r--r--plugins/smartcard/gsd-smartcard-enum-types.h.in24
-rw-r--r--plugins/smartcard/gsd-smartcard-manager.c993
-rw-r--r--plugins/smartcard/gsd-smartcard-manager.h66
-rw-r--r--plugins/smartcard/gsd-smartcard-service.c849
-rw-r--r--plugins/smartcard/gsd-smartcard-service.h60
-rw-r--r--plugins/smartcard/gsd-smartcard-utils.c174
-rw-r--r--plugins/smartcard/gsd-smartcard-utils.h33
-rw-r--r--plugins/smartcard/main.c7
-rw-r--r--plugins/smartcard/meson.build49
-rw-r--r--plugins/smartcard/org.gnome.SettingsDaemon.Smartcard.xml89
-rw-r--r--plugins/sound/gsd-sound-manager.c362
-rw-r--r--plugins/sound/gsd-sound-manager.h38
-rw-r--r--plugins/sound/main.c7
-rw-r--r--plugins/sound/meson.build20
-rw-r--r--plugins/usb-protection/gsd-usb-protection-manager.c1179
-rw-r--r--plugins/usb-protection/gsd-usb-protection-manager.h44
-rw-r--r--plugins/usb-protection/main.c7
-rw-r--r--plugins/usb-protection/meson.build21
-rw-r--r--plugins/wacom/gsd-wacom-manager.c528
-rw-r--r--plugins/wacom/gsd-wacom-manager.h39
-rw-r--r--plugins/wacom/gsd-wacom-oled-constants.h41
-rw-r--r--plugins/wacom/gsd-wacom-oled-helper.c422
-rw-r--r--plugins/wacom/gsd-wacom-oled.c258
-rw-r--r--plugins/wacom/gsd-wacom-oled.h34
-rw-r--r--plugins/wacom/main.c7
-rw-r--r--plugins/wacom/meson.build63
-rw-r--r--plugins/wacom/org.gnome.settings-daemon.plugins.wacom.policy.in.in46
-rw-r--r--plugins/wwan/cc-wwan-device.c1343
-rw-r--r--plugins/wwan/cc-wwan-device.h152
-rw-r--r--plugins/wwan/cc-wwan-errors-private.h104
-rw-r--r--plugins/wwan/gsd-wwan-manager.c826
-rw-r--r--plugins/wwan/gsd-wwan-manager.h36
-rw-r--r--plugins/wwan/main.c7
-rw-r--r--plugins/wwan/meson.build21
-rwxr-xr-xplugins/xsettings/00-xrdb9
-rw-r--r--plugins/xsettings/README.xsettings35
-rw-r--r--plugins/xsettings/fc-monitor.c317
-rw-r--r--plugins/xsettings/fc-monitor.h36
-rw-r--r--plugins/xsettings/fontconfig-test/fonts.conf8
-rw-r--r--plugins/xsettings/gsd-remote-display-manager.h28
-rw-r--r--plugins/xsettings/gsd-xsettings-gtk.c384
-rw-r--r--plugins/xsettings/gsd-xsettings-gtk.h39
-rw-r--r--plugins/xsettings/gsd-xsettings-manager.c1588
-rw-r--r--plugins/xsettings/gsd-xsettings-manager.h38
-rw-r--r--plugins/xsettings/gtk-modules-test/canberra-gtk-module.desktop6
-rw-r--r--plugins/xsettings/gtk-modules-test/pk-gtk-module.desktop4
-rw-r--r--plugins/xsettings/main.c8
-rw-r--r--plugins/xsettings/meson.build66
-rw-r--r--plugins/xsettings/test-gtk-modules.c31
-rw-r--r--plugins/xsettings/test-wm-button-layout-translations.c54
-rwxr-xr-xplugins/xsettings/test.py209
-rw-r--r--plugins/xsettings/wm-button-layout-translation.c88
-rw-r--r--plugins/xsettings/wm-button-layout-translation.h26
-rw-r--r--plugins/xsettings/xsettings-common.c112
-rw-r--r--plugins/xsettings/xsettings-common.h65
-rw-r--r--plugins/xsettings/xsettings-manager.c393
-rw-r--r--plugins/xsettings/xsettings-manager.h58
188 files changed, 42056 insertions, 0 deletions
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..77bb117
--- /dev/null
+++ b/plugins/a11y-settings/gsd-a11y-settings-manager.c
@@ -0,0 +1,162 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <locale.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#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;
+
+ if (g_str_equal (key, "screen-reader-enabled") == FALSE &&
+ g_str_equal (key, "screen-keyboard-enabled") == FALSE)
+ return;
+
+ g_debug ("screen reader or OSK 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");
+
+ if (screen_reader || keyboard) {
+ g_debug ("Enabling toolkit-accessibility, screen reader or OSK enabled");
+ g_settings_set_boolean (manager->interface_settings, "toolkit-accessibility", TRUE);
+ } else if (screen_reader == FALSE && keyboard == FALSE) {
+ g_debug ("Disabling toolkit-accessibility, screen reader and OSK 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 or on-screen keyboard 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_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 <mccann@jhu.edu>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_A11Y_SETTINGS_MANAGER_H
+#define __GSD_A11Y_SETTINGS_MANAGER_H
+
+#include <glib-object.h>
+
+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-edid.c b/plugins/color/gcm-edid.c
new file mode 100644
index 0000000..a00d2ce
--- /dev/null
+++ b/plugins/color/gcm-edid.c
@@ -0,0 +1,450 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Soren Sandmann <sandmann@redhat.com>
+ * Copyright (C) 2009-2011 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <glib-object.h>
+#include <math.h>
+#include <string.h>
+#include <gio/gio.h>
+#include <stdlib.h>
+#include <libgnome-desktop/gnome-pnp-ids.h>
+
+#include "gcm-edid.h"
+
+static void gcm_edid_finalize (GObject *object);
+
+struct _GcmEdid
+{
+ GObject parent;
+
+ gchar *monitor_name;
+ gchar *vendor_name;
+ gchar *serial_number;
+ gchar *eisa_id;
+ gchar *checksum;
+ gchar *pnp_id;
+ guint width;
+ guint height;
+ gfloat gamma;
+ CdColorYxy *red;
+ CdColorYxy *green;
+ CdColorYxy *blue;
+ CdColorYxy *white;
+ GnomePnpIds *pnp_ids;
+};
+
+G_DEFINE_TYPE (GcmEdid, gcm_edid, G_TYPE_OBJECT)
+
+#define GCM_EDID_OFFSET_PNPID 0x08
+#define GCM_EDID_OFFSET_SERIAL 0x0c
+#define GCM_EDID_OFFSET_SIZE 0x15
+#define GCM_EDID_OFFSET_GAMMA 0x17
+#define GCM_EDID_OFFSET_DATA_BLOCKS 0x36
+#define GCM_EDID_OFFSET_LAST_BLOCK 0x6c
+#define GCM_EDID_OFFSET_EXTENSION_BLOCK_COUNT 0x7e
+
+#define GCM_DESCRIPTOR_DISPLAY_PRODUCT_NAME 0xfc
+#define GCM_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER 0xff
+#define GCM_DESCRIPTOR_COLOR_MANAGEMENT_DATA 0xf9
+#define GCM_DESCRIPTOR_ALPHANUMERIC_DATA_STRING 0xfe
+#define GCM_DESCRIPTOR_COLOR_POINT 0xfb
+
+GQuark
+gcm_edid_error_quark (void)
+{
+ static GQuark quark = 0;
+ if (!quark)
+ quark = g_quark_from_static_string ("gcm_edid_error");
+ return quark;
+}
+
+const gchar *
+gcm_edid_get_monitor_name (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->monitor_name;
+}
+
+const gchar *
+gcm_edid_get_vendor_name (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+
+ if (edid->vendor_name == NULL)
+ edid->vendor_name = gnome_pnp_ids_get_pnp_id (edid->pnp_ids, edid->pnp_id);
+ return edid->vendor_name;
+}
+
+const gchar *
+gcm_edid_get_serial_number (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->serial_number;
+}
+
+const gchar *
+gcm_edid_get_eisa_id (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->eisa_id;
+}
+
+const gchar *
+gcm_edid_get_checksum (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->checksum;
+}
+
+const gchar *
+gcm_edid_get_pnp_id (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->pnp_id;
+}
+
+guint
+gcm_edid_get_width (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), 0);
+ return edid->width;
+}
+
+guint
+gcm_edid_get_height (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), 0);
+ return edid->height;
+}
+
+gfloat
+gcm_edid_get_gamma (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), 0.0f);
+ return edid->gamma;
+}
+
+const CdColorYxy *
+gcm_edid_get_red (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->red;
+}
+
+const CdColorYxy *
+gcm_edid_get_green (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->green;
+}
+
+const CdColorYxy *
+gcm_edid_get_blue (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->blue;
+}
+
+const CdColorYxy *
+gcm_edid_get_white (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->white;
+}
+
+void
+gcm_edid_reset (GcmEdid *edid)
+{
+ g_return_if_fail (GCM_IS_EDID (edid));
+
+ /* free old data */
+ g_free (edid->monitor_name);
+ g_free (edid->vendor_name);
+ g_free (edid->serial_number);
+ g_free (edid->eisa_id);
+ g_free (edid->checksum);
+
+ /* do not deallocate, just blank */
+ edid->pnp_id[0] = '\0';
+
+ /* set to default values */
+ edid->monitor_name = NULL;
+ edid->vendor_name = NULL;
+ edid->serial_number = NULL;
+ edid->eisa_id = NULL;
+ edid->checksum = NULL;
+ edid->width = 0;
+ edid->height = 0;
+ edid->gamma = 0.0f;
+}
+
+static gint
+gcm_edid_get_bit (gint in, gint bit)
+{
+ return (in & (1 << bit)) >> bit;
+}
+
+/**
+ * gcm_edid_get_bits:
+ **/
+static gint
+gcm_edid_get_bits (gint in, gint begin, gint end)
+{
+ gint mask = (1 << (end - begin + 1)) - 1;
+
+ return (in >> begin) & mask;
+}
+
+/**
+ * gcm_edid_decode_fraction:
+ **/
+static gdouble
+gcm_edid_decode_fraction (gint high, gint low)
+{
+ gdouble result = 0.0;
+ gint i;
+
+ high = (high << 2) | low;
+ for (i = 0; i < 10; ++i)
+ result += gcm_edid_get_bit (high, i) * pow (2, i - 10);
+ return result;
+}
+
+static gchar *
+gcm_edid_parse_string (const guint8 *data)
+{
+ gchar *text;
+ guint i;
+ guint replaced = 0;
+
+ /* this is always 13 bytes, but we can't guarantee it's null
+ * terminated or not junk. */
+ text = g_strndup ((const gchar *) data, 13);
+
+ /* remove insane newline chars */
+ g_strdelimit (text, "\n\r", '\0');
+
+ /* remove spaces */
+ g_strchomp (text);
+
+ /* nothing left? */
+ if (text[0] == '\0') {
+ g_free (text);
+ text = NULL;
+ goto out;
+ }
+
+ /* ensure string is printable */
+ for (i = 0; text[i] != '\0'; i++) {
+ if (!g_ascii_isprint (text[i])) {
+ text[i] = '-';
+ replaced++;
+ }
+ }
+
+ /* if the string is junk, ignore the string */
+ if (replaced > 4) {
+ g_free (text);
+ text = NULL;
+ goto out;
+ }
+out:
+ return text;
+}
+
+gboolean
+gcm_edid_parse (GcmEdid *edid, const guint8 *data, gsize length, GError **error)
+{
+ gboolean ret = TRUE;
+ guint i;
+ guint32 serial;
+ gchar *tmp;
+
+ /* check header */
+ if (length < 128) {
+ g_set_error_literal (error,
+ GCM_EDID_ERROR,
+ GCM_EDID_ERROR_FAILED_TO_PARSE,
+ "EDID length is too small");
+ ret = FALSE;
+ goto out;
+ }
+ if (data[0] != 0x00 || data[1] != 0xff) {
+ g_set_error_literal (error,
+ GCM_EDID_ERROR,
+ GCM_EDID_ERROR_FAILED_TO_PARSE,
+ "Failed to parse EDID header");
+ ret = FALSE;
+ goto out;
+ }
+
+ /* free old data */
+ gcm_edid_reset (edid);
+
+ /* decode the PNP ID from three 5 bit words packed into 2 bytes
+ * /--08--\/--09--\
+ * 7654321076543210
+ * |\---/\---/\---/
+ * R C1 C2 C3 */
+ edid->pnp_id[0] = 'A' + ((data[GCM_EDID_OFFSET_PNPID+0] & 0x7c) / 4) - 1;
+ edid->pnp_id[1] = 'A' + ((data[GCM_EDID_OFFSET_PNPID+0] & 0x3) * 8) + ((data[GCM_EDID_OFFSET_PNPID+1] & 0xe0) / 32) - 1;
+ edid->pnp_id[2] = 'A' + (data[GCM_EDID_OFFSET_PNPID+1] & 0x1f) - 1;
+
+ /* maybe there isn't a ASCII serial number descriptor, so use this instead */
+ serial = (guint32) data[GCM_EDID_OFFSET_SERIAL+0];
+ serial += (guint32) data[GCM_EDID_OFFSET_SERIAL+1] * 0x100;
+ serial += (guint32) data[GCM_EDID_OFFSET_SERIAL+2] * 0x10000;
+ serial += (guint32) data[GCM_EDID_OFFSET_SERIAL+3] * 0x1000000;
+ if (serial > 0)
+ edid->serial_number = g_strdup_printf ("%" G_GUINT32_FORMAT, serial);
+
+ /* get the size */
+ edid->width = data[GCM_EDID_OFFSET_SIZE+0];
+ edid->height = data[GCM_EDID_OFFSET_SIZE+1];
+
+ /* we don't care about aspect */
+ if (edid->width == 0 || edid->height == 0) {
+ edid->width = 0;
+ edid->height = 0;
+ }
+
+ /* get gamma */
+ if (data[GCM_EDID_OFFSET_GAMMA] == 0xff) {
+ edid->gamma = 1.0f;
+ } else {
+ edid->gamma = ((gfloat) data[GCM_EDID_OFFSET_GAMMA] / 100) + 1;
+ }
+
+ /* get color red */
+ edid->red->x = gcm_edid_decode_fraction (data[0x1b], gcm_edid_get_bits (data[0x19], 6, 7));
+ edid->red->y = gcm_edid_decode_fraction (data[0x1c], gcm_edid_get_bits (data[0x19], 4, 5));
+
+ /* get color green */
+ edid->green->x = gcm_edid_decode_fraction (data[0x1d], gcm_edid_get_bits (data[0x19], 2, 3));
+ edid->green->y = gcm_edid_decode_fraction (data[0x1e], gcm_edid_get_bits (data[0x19], 0, 1));
+
+ /* get color blue */
+ edid->blue->x = gcm_edid_decode_fraction (data[0x1f], gcm_edid_get_bits (data[0x1a], 6, 7));
+ edid->blue->y = gcm_edid_decode_fraction (data[0x20], gcm_edid_get_bits (data[0x1a], 4, 5));
+
+ /* get color white */
+ edid->white->x = gcm_edid_decode_fraction (data[0x21], gcm_edid_get_bits (data[0x1a], 2, 3));
+ edid->white->y = gcm_edid_decode_fraction (data[0x22], gcm_edid_get_bits (data[0x1a], 0, 1));
+
+ /* parse EDID data */
+ for (i = GCM_EDID_OFFSET_DATA_BLOCKS;
+ i <= GCM_EDID_OFFSET_LAST_BLOCK;
+ i += 18) {
+ /* ignore pixel clock data */
+ if (data[i] != 0)
+ continue;
+ if (data[i+2] != 0)
+ continue;
+
+ /* any useful blocks? */
+ if (data[i+3] == GCM_DESCRIPTOR_DISPLAY_PRODUCT_NAME) {
+ tmp = gcm_edid_parse_string (&data[i+5]);
+ if (tmp != NULL) {
+ g_free (edid->monitor_name);
+ edid->monitor_name = tmp;
+ }
+ } else if (data[i+3] == GCM_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER) {
+ tmp = gcm_edid_parse_string (&data[i+5]);
+ if (tmp != NULL) {
+ g_free (edid->serial_number);
+ edid->serial_number = tmp;
+ }
+ } else if (data[i+3] == GCM_DESCRIPTOR_COLOR_MANAGEMENT_DATA) {
+ g_warning ("failing to parse color management data");
+ } else if (data[i+3] == GCM_DESCRIPTOR_ALPHANUMERIC_DATA_STRING) {
+ tmp = gcm_edid_parse_string (&data[i+5]);
+ if (tmp != NULL) {
+ g_free (edid->eisa_id);
+ edid->eisa_id = tmp;
+ }
+ } else if (data[i+3] == GCM_DESCRIPTOR_COLOR_POINT) {
+ if (data[i+3+9] != 0xff) {
+ /* extended EDID block(1) which contains
+ * a better gamma value */
+ edid->gamma = ((gfloat) data[i+3+9] / 100) + 1;
+ }
+ if (data[i+3+14] != 0xff) {
+ /* extended EDID block(2) which contains
+ * a better gamma value */
+ edid->gamma = ((gfloat) data[i+3+9] / 100) + 1;
+ }
+ }
+ }
+
+ /* calculate checksum */
+ edid->checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5, data, length);
+out:
+ return ret;
+}
+
+static void
+gcm_edid_class_init (GcmEdidClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = gcm_edid_finalize;
+}
+
+static void
+gcm_edid_init (GcmEdid *edid)
+{
+ edid->pnp_ids = gnome_pnp_ids_new ();
+ edid->pnp_id = g_new0 (gchar, 4);
+ edid->red = cd_color_yxy_new ();
+ edid->green = cd_color_yxy_new ();
+ edid->blue = cd_color_yxy_new ();
+ edid->white = cd_color_yxy_new ();
+}
+
+static void
+gcm_edid_finalize (GObject *object)
+{
+ GcmEdid *edid = GCM_EDID (object);
+
+ g_free (edid->monitor_name);
+ g_free (edid->vendor_name);
+ g_free (edid->serial_number);
+ g_free (edid->eisa_id);
+ g_free (edid->checksum);
+ g_free (edid->pnp_id);
+ cd_color_yxy_free (edid->white);
+ cd_color_yxy_free (edid->red);
+ cd_color_yxy_free (edid->green);
+ cd_color_yxy_free (edid->blue);
+ g_object_unref (edid->pnp_ids);
+
+ G_OBJECT_CLASS (gcm_edid_parent_class)->finalize (object);
+}
+
+GcmEdid *
+gcm_edid_new (void)
+{
+ GcmEdid *edid;
+ edid = g_object_new (GCM_TYPE_EDID, NULL);
+ return GCM_EDID (edid);
+}
+
diff --git a/plugins/color/gcm-edid.h b/plugins/color/gcm-edid.h
new file mode 100644
index 0000000..d50a32e
--- /dev/null
+++ b/plugins/color/gcm-edid.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009-2010 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 __GCM_EDID_H
+#define __GCM_EDID_H
+
+#include <glib-object.h>
+#include <colord.h>
+
+G_BEGIN_DECLS
+
+#define GCM_TYPE_EDID (gcm_edid_get_type ())
+G_DECLARE_FINAL_TYPE (GcmEdid, gcm_edid, GCM, EDID, GObject)
+
+#define GCM_EDID_ERROR (gcm_edid_error_quark ())
+enum
+{
+ GCM_EDID_ERROR_FAILED_TO_PARSE
+};
+
+GQuark gcm_edid_error_quark (void);
+GcmEdid *gcm_edid_new (void);
+void gcm_edid_reset (GcmEdid *edid);
+gboolean gcm_edid_parse (GcmEdid *edid,
+ const guint8 *data,
+ gsize length,
+ GError **error);
+const gchar *gcm_edid_get_monitor_name (GcmEdid *edid);
+const gchar *gcm_edid_get_vendor_name (GcmEdid *edid);
+const gchar *gcm_edid_get_serial_number (GcmEdid *edid);
+const gchar *gcm_edid_get_eisa_id (GcmEdid *edid);
+const gchar *gcm_edid_get_checksum (GcmEdid *edid);
+const gchar *gcm_edid_get_pnp_id (GcmEdid *edid);
+guint gcm_edid_get_width (GcmEdid *edid);
+guint gcm_edid_get_height (GcmEdid *edid);
+gfloat gcm_edid_get_gamma (GcmEdid *edid);
+const CdColorYxy *gcm_edid_get_red (GcmEdid *edid);
+const CdColorYxy *gcm_edid_get_green (GcmEdid *edid);
+const CdColorYxy *gcm_edid_get_blue (GcmEdid *edid);
+const CdColorYxy *gcm_edid_get_white (GcmEdid *edid);
+
+G_END_DECLS
+
+#endif /* __GCM_EDID_H */
+
diff --git a/plugins/color/gcm-self-test.c b/plugins/color/gcm-self-test.c
new file mode 100644
index 0000000..7ff01f6
--- /dev/null
+++ b/plugins/color/gcm-self-test.c
@@ -0,0 +1,430 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007-2011 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <glib.h>
+#include <glib-object.h>
+#include <stdlib.h>
+
+#include "gcm-edid.h"
+#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 const gboolean
+gcm_vendor_is_goldstar (const char * const vendor) {
+ if (g_strcmp0 (vendor, "Goldstar Company Ltd") == 0)
+ return TRUE;
+ /* Goldstar was changed to LG in hwdb (systemd) 240.
+ * https://github.com/systemd/systemd/commit/c6d7a5e9a3836f8
+ */
+ if (g_strcmp0 (vendor, "LG Electronics") == 0)
+ return TRUE;
+ return FALSE;
+}
+
+static void
+gcm_test_edid_func (void)
+{
+ GcmEdid *edid;
+ gchar *data;
+ gboolean ret;
+ GError *error = NULL;
+ gsize length = 0;
+
+ edid = gcm_edid_new ();
+ g_assert (edid != NULL);
+
+ /* LG 21" LCD panel */
+ ret = g_file_get_contents (TESTDATADIR "/LG-L225W-External.bin",
+ &data, &length, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+ ret = gcm_edid_parse (edid, (const guint8 *) data, length, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+
+ g_assert_cmpstr (gcm_edid_get_monitor_name (edid), ==, "L225W");
+ g_assert_true (gcm_vendor_is_goldstar (gcm_edid_get_vendor_name (edid)));
+ g_assert_cmpstr (gcm_edid_get_serial_number (edid), ==, "34398");
+ g_assert_cmpstr (gcm_edid_get_eisa_id (edid), ==, NULL);
+ g_assert_cmpstr (gcm_edid_get_checksum (edid), ==, "0bb44865bb29984a4bae620656c31368");
+ g_assert_cmpstr (gcm_edid_get_pnp_id (edid), ==, "GSM");
+ g_assert_cmpint (gcm_edid_get_height (edid), ==, 30);
+ g_assert_cmpint (gcm_edid_get_width (edid), ==, 47);
+ g_assert_cmpfloat (gcm_edid_get_gamma (edid), >=, 2.2f - 0.01);
+ g_assert_cmpfloat (gcm_edid_get_gamma (edid), <, 2.2f + 0.01);
+ g_free (data);
+
+ /* Lenovo T61 internal Panel */
+ ret = g_file_get_contents (TESTDATADIR "/Lenovo-T61-Internal.bin",
+ &data, &length, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+ ret = gcm_edid_parse (edid, (const guint8 *) data, length, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+
+ g_assert_cmpstr (gcm_edid_get_monitor_name (edid), ==, NULL);
+ g_assert_cmpstr (gcm_edid_get_vendor_name (edid), ==, "IBM Brasil");
+ g_assert_cmpstr (gcm_edid_get_serial_number (edid), ==, NULL);
+ g_assert_cmpstr (gcm_edid_get_eisa_id (edid), ==, "LTN154P2-L05");
+ g_assert_cmpstr (gcm_edid_get_checksum (edid), ==, "e1865128c7cd5e5ed49ecfc8102f6f9c");
+ g_assert_cmpstr (gcm_edid_get_pnp_id (edid), ==, "IBM");
+ g_assert_cmpint (gcm_edid_get_height (edid), ==, 21);
+ g_assert_cmpint (gcm_edid_get_width (edid), ==, 33);
+ g_assert_cmpfloat (gcm_edid_get_gamma (edid), >=, 2.2f - 0.01);
+ g_assert_cmpfloat (gcm_edid_get_gamma (edid), <, 2.2f + 0.01);
+ g_free (data);
+
+ g_object_unref (edid);
+}
+
+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/edid", gcm_test_edid_func);
+ 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 <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include "gnome-datetime-source.h"
+
+#if HAVE_TIMERFD
+#include <sys/timerfd.h>
+#include <unistd.h>
+#include <string.h>
+#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.
+ *
+ * <note><para>This function currently does not detect time zone
+ * changes. On Linux, your program should also monitor the
+ * <literal>/etc/timezone</literal> file using
+ * #GFileMonitor.</para></note>
+ *
+ * <example id="gdatetime-example-watch"><title>Clock example</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../glib/tests/glib-clock.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ *
+ * 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 <walters@verbum.org>
+ */
+
+#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 <glib.h>
+
+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 <mccann@jhu.edu>
+ * Copyright (C) 2011-2013 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <colord.h>
+#include <libnotify/notify.h>
+#include <canberra-gtk.h>
+
+#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 <mccann@jhu.edu>
+ * Copyright (C) 2011-2013 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_COLOR_CALIBRATE_H
+#define __GSD_COLOR_CALIBRATE_H
+
+#include <glib-object.h>
+
+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..62a417e
--- /dev/null
+++ b/plugins/color/gsd-color-manager.c
@@ -0,0 +1,502 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ * Copyright (C) 2011-2013 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include "gnome-settings-profile.h"
+#include "gsd-color-calibrate.h"
+#include "gsd-color-manager.h"
+#include "gsd-color-profiles.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[] =
+"<node>"
+" <interface name='org.gnome.SettingsDaemon.Color'>"
+" <method name='NightLightPreview'>"
+" <arg type='u' name='duration' direction='in'/>"
+" </method>"
+" <property name='NightLightActive' type='b' access='read'/>"
+" <property name='Temperature' type='u' access='readwrite'/>"
+" <property name='DisabledUntilTomorrow' type='b' access='readwrite'/>"
+" <property name='Sunrise' type='d' access='read'/>"
+" <property name='Sunset' type='d' access='read'/>"
+" </interface>"
+"</node>";
+
+struct _GsdColorManager
+{
+ GObject parent;
+
+ /* D-Bus */
+ guint name_id;
+ GDBusNodeInfo *introspection_data;
+ GDBusConnection *connection;
+ GCancellable *bus_cancellable;
+
+ GsdColorCalibrate *calibrate;
+ GsdColorProfiles *profiles;
+ 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)
+{
+ gboolean ret;
+
+ g_debug ("Starting color manager");
+ gnome_settings_profile_start (NULL);
+
+ /* start the device probing */
+ gsd_color_state_start (manager->state);
+
+ /* start the profiles collection */
+ ret = gsd_color_profiles_start (manager->profiles, error);
+ if (!ret)
+ goto out;
+out:
+ gnome_settings_profile_end (NULL);
+ return ret;
+}
+
+void
+gsd_color_manager_stop (GsdColorManager *manager)
+{
+ g_debug ("Stopping color manager");
+ gsd_color_state_stop (manager->state);
+ gsd_color_profiles_stop (manager->profiles);
+}
+
+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_double (temperature));
+}
+
+static void
+gsd_color_manager_init (GsdColorManager *manager)
+{
+ /* setup calibration features */
+ manager->calibrate = gsd_color_calibrate_new ();
+ manager->profiles = gsd_color_profiles_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->profiles);
+ 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 <mccann@jhu.edu>
+ * Copyright (C) 2011 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_COLOR_MANAGER_H
+#define __GSD_COLOR_MANAGER_H
+
+#include <glib-object.h>
+
+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-profiles.c b/plugins/color/gsd-color-profiles.c
new file mode 100644
index 0000000..c73abd7
--- /dev/null
+++ b/plugins/color/gsd-color-profiles.c
@@ -0,0 +1,246 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ * Copyright (C) 2011-2013 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <colord.h>
+
+#include "gsd-color-profiles.h"
+
+struct _GsdColorProfiles
+{
+ GObject parent;
+
+ GCancellable *cancellable;
+ CdClient *client;
+ CdIccStore *icc_store;
+};
+
+static void gsd_color_profiles_class_init (GsdColorProfilesClass *klass);
+static void gsd_color_profiles_init (GsdColorProfiles *color_profiles);
+static void gsd_color_profiles_finalize (GObject *object);
+
+G_DEFINE_TYPE (GsdColorProfiles, gsd_color_profiles, G_TYPE_OBJECT)
+
+static void
+gsd_color_profiles_class_init (GsdColorProfilesClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gsd_color_profiles_finalize;
+}
+
+static void
+gcm_session_client_connect_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ gboolean ret;
+ GError *error = NULL;
+ CdClient *client = CD_CLIENT (source_object);
+ GsdColorProfiles *profiles;
+
+ /* connected */
+ ret = cd_client_connect_finish (client, res, &error);
+ if (!ret) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("failed to connect to colord: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ /* is there an available colord instance? */
+ profiles = GSD_COLOR_PROFILES (user_data);
+ ret = cd_client_get_has_server (profiles->client);
+ if (!ret) {
+ g_warning ("There is no colord server available");
+ return;
+ }
+
+ /* add profiles */
+ ret = cd_icc_store_search_kind (profiles->icc_store,
+ CD_ICC_STORE_SEARCH_KIND_USER,
+ CD_ICC_STORE_SEARCH_FLAGS_CREATE_LOCATION,
+ profiles->cancellable,
+ &error);
+ if (!ret) {
+ g_warning ("failed to add user icc: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+gboolean
+gsd_color_profiles_start (GsdColorProfiles *profiles,
+ GError **error)
+{
+ /* use a fresh cancellable for each start->stop operation */
+ g_cancellable_cancel (profiles->cancellable);
+ g_clear_object (&profiles->cancellable);
+ profiles->cancellable = g_cancellable_new ();
+
+ cd_client_connect (profiles->client,
+ profiles->cancellable,
+ gcm_session_client_connect_cb,
+ profiles);
+
+ return TRUE;
+}
+
+void
+gsd_color_profiles_stop (GsdColorProfiles *profiles)
+{
+ g_cancellable_cancel (profiles->cancellable);
+}
+
+static void
+gcm_session_create_profile_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CdProfile *profile;
+ GError *error = NULL;
+ CdClient *client = CD_CLIENT (object);
+
+ profile = cd_client_create_profile_finish (client, res, &error);
+ if (profile == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+ !g_error_matches (error, CD_CLIENT_ERROR, CD_CLIENT_ERROR_ALREADY_EXISTS))
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return;
+ }
+ g_object_unref (profile);
+}
+
+static void
+gcm_session_icc_store_added_cb (CdIccStore *icc_store,
+ CdIcc *icc,
+ GsdColorProfiles *profiles)
+{
+ cd_client_create_profile_for_icc (profiles->client,
+ icc,
+ CD_OBJECT_SCOPE_TEMP,
+ profiles->cancellable,
+ gcm_session_create_profile_cb,
+ profiles);
+}
+
+static void
+gcm_session_delete_profile_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ gboolean ret;
+ GError *error = NULL;
+ CdClient *client = CD_CLIENT (object);
+
+ ret = cd_client_delete_profile_finish (client, res, &error);
+ if (!ret) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+gcm_session_find_profile_by_filename_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ CdProfile *profile;
+ CdClient *client = CD_CLIENT (object);
+ GsdColorProfiles *profiles = GSD_COLOR_PROFILES (user_data);
+
+ profile = cd_client_find_profile_by_filename_finish (client, res, &error);
+ if (profile == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* remove it from colord */
+ cd_client_delete_profile (profiles->client,
+ profile,
+ profiles->cancellable,
+ gcm_session_delete_profile_cb,
+ profiles);
+out:
+ if (profile != NULL)
+ g_object_unref (profile);
+}
+
+static void
+gcm_session_icc_store_removed_cb (CdIccStore *icc_store,
+ CdIcc *icc,
+ GsdColorProfiles *profiles)
+{
+ /* find the ID for the filename */
+ g_debug ("filename %s removed", cd_icc_get_filename (icc));
+ cd_client_find_profile_by_filename (profiles->client,
+ cd_icc_get_filename (icc),
+ profiles->cancellable,
+ gcm_session_find_profile_by_filename_cb,
+ profiles);
+}
+
+static void
+gsd_color_profiles_init (GsdColorProfiles *profiles)
+{
+ /* have access to all user profiles */
+ profiles->client = cd_client_new ();
+ profiles->icc_store = cd_icc_store_new ();
+ cd_icc_store_set_load_flags (profiles->icc_store,
+ CD_ICC_LOAD_FLAGS_FALLBACK_MD5);
+ g_signal_connect (profiles->icc_store, "added",
+ G_CALLBACK (gcm_session_icc_store_added_cb),
+ profiles);
+ g_signal_connect (profiles->icc_store, "removed",
+ G_CALLBACK (gcm_session_icc_store_removed_cb),
+ profiles);
+}
+
+static void
+gsd_color_profiles_finalize (GObject *object)
+{
+ GsdColorProfiles *profiles;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSD_IS_COLOR_PROFILES (object));
+
+ profiles = GSD_COLOR_PROFILES (object);
+
+ g_cancellable_cancel (profiles->cancellable);
+ g_clear_object (&profiles->cancellable);
+ g_clear_object (&profiles->icc_store);
+ g_clear_object (&profiles->client);
+
+ G_OBJECT_CLASS (gsd_color_profiles_parent_class)->finalize (object);
+}
+
+GsdColorProfiles *
+gsd_color_profiles_new (void)
+{
+ GsdColorProfiles *profiles;
+ profiles = g_object_new (GSD_TYPE_COLOR_PROFILES, NULL);
+ return GSD_COLOR_PROFILES (profiles);
+}
diff --git a/plugins/color/gsd-color-profiles.h b/plugins/color/gsd-color-profiles.h
new file mode 100644
index 0000000..24aed34
--- /dev/null
+++ b/plugins/color/gsd-color-profiles.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ * Copyright (C) 2011-2013 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_COLOR_PROFILES_H
+#define __GSD_COLOR_PROFILES_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_COLOR_PROFILES (gsd_color_profiles_get_type ())
+G_DECLARE_FINAL_TYPE (GsdColorProfiles, gsd_color_profiles, GSD, COLOR_PROFILES, GObject)
+
+GQuark gsd_color_profiles_error_quark (void);
+
+GsdColorProfiles * gsd_color_profiles_new (void);
+gboolean gsd_color_profiles_start (GsdColorProfiles *profiles,
+ GError **error);
+void gsd_color_profiles_stop (GsdColorProfiles *profiles);
+
+G_END_DECLS
+
+#endif /* __GSD_COLOR_PROFILES_H */
diff --git a/plugins/color/gsd-color-state.c b/plugins/color/gsd-color-state.c
new file mode 100644
index 0000000..746cf23
--- /dev/null
+++ b/plugins/color/gsd-color-state.c
@@ -0,0 +1,1592 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ * Copyright (C) 2011-2013 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <colord.h>
+#include <gdk/gdk.h>
+#include <stdlib.h>
+#include <lcms2.h>
+#include <canberra-gtk.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-rr.h>
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#include "gnome-settings-bus.h"
+
+#include "gsd-color-manager.h"
+#include "gsd-color-state.h"
+#include "gcm-edid.h"
+
+#define GSD_DBUS_NAME "org.gnome.SettingsDaemon"
+#define GSD_DBUS_PATH "/org/gnome/SettingsDaemon"
+#define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon"
+
+static void gcm_session_set_gamma_for_all_devices (GsdColorState *state);
+
+struct _GsdColorState
+{
+ GObject parent;
+
+ GCancellable *cancellable;
+ GsdSessionManager *session;
+ CdClient *client;
+ GnomeRRScreen *state_screen;
+ GHashTable *edid_cache;
+ GdkWindow *gdk_window;
+ gboolean session_is_active;
+ GHashTable *device_assign_hash;
+ guint color_temperature;
+};
+
+static void gsd_color_state_class_init (GsdColorStateClass *klass);
+static void gsd_color_state_init (GsdColorState *color_state);
+static void gsd_color_state_finalize (GObject *object);
+
+G_DEFINE_TYPE (GsdColorState, gsd_color_state, G_TYPE_OBJECT)
+
+/* see http://www.oyranos.org/wiki/index.php?title=ICC_Profiles_in_X_Specification_0.3 */
+#define GCM_ICC_PROFILE_IN_X_VERSION_MAJOR 0
+#define GCM_ICC_PROFILE_IN_X_VERSION_MINOR 3
+
+typedef struct {
+ guint32 red;
+ guint32 green;
+ guint32 blue;
+} GnomeRROutputClutItem;
+
+GQuark
+gsd_color_state_error_quark (void)
+{
+ static GQuark quark = 0;
+ if (!quark)
+ quark = g_quark_from_static_string ("gsd_color_state_error");
+ return quark;
+}
+
+static GcmEdid *
+gcm_session_get_output_edid (GsdColorState *state, GnomeRROutput *output, GError **error)
+{
+ const guint8 *data;
+ gsize size;
+ GcmEdid *edid = NULL;
+ gboolean ret;
+
+ /* can we find it in the cache */
+ edid = g_hash_table_lookup (state->edid_cache,
+ gnome_rr_output_get_name (output));
+ if (edid != NULL) {
+ g_object_ref (edid);
+ return edid;
+ }
+
+ /* parse edid */
+ data = gnome_rr_output_get_edid_data (output, &size);
+ if (data == NULL || size == 0) {
+ g_set_error_literal (error,
+ GNOME_RR_ERROR,
+ GNOME_RR_ERROR_UNKNOWN,
+ "unable to get EDID for output");
+ return NULL;
+ }
+ edid = gcm_edid_new ();
+ ret = gcm_edid_parse (edid, data, size, error);
+ if (!ret) {
+ g_object_unref (edid);
+ return NULL;
+ }
+
+ /* add to cache */
+ g_hash_table_insert (state->edid_cache,
+ g_strdup (gnome_rr_output_get_name (output)),
+ g_object_ref (edid));
+
+ return edid;
+}
+
+static gboolean
+gcm_session_screen_set_icc_profile (GsdColorState *state,
+ const gchar *filename,
+ GError **error)
+{
+ gchar *data = NULL;
+ gsize length;
+ guint version_data;
+
+ g_return_val_if_fail (filename != NULL, FALSE);
+
+ /* wayland */
+ if (state->gdk_window == NULL) {
+ g_debug ("not setting atom as running under wayland");
+ return TRUE;
+ }
+
+ g_debug ("setting root window ICC profile atom from %s", filename);
+
+ /* get contents of file */
+ if (!g_file_get_contents (filename, &data, &length, error))
+ return FALSE;
+
+ /* set profile property */
+ gdk_property_change (state->gdk_window,
+ gdk_atom_intern_static_string ("_ICC_PROFILE"),
+ gdk_atom_intern_static_string ("CARDINAL"),
+ 8,
+ GDK_PROP_MODE_REPLACE,
+ (const guchar *) data, length);
+
+ /* set version property */
+ version_data = GCM_ICC_PROFILE_IN_X_VERSION_MAJOR * 100 +
+ GCM_ICC_PROFILE_IN_X_VERSION_MINOR * 1;
+ gdk_property_change (state->gdk_window,
+ gdk_atom_intern_static_string ("_ICC_PROFILE_IN_X_VERSION"),
+ gdk_atom_intern_static_string ("CARDINAL"),
+ 8,
+ GDK_PROP_MODE_REPLACE,
+ (const guchar *) &version_data, 1);
+
+ g_free (data);
+ return TRUE;
+}
+
+void
+gsd_color_state_set_temperature (GsdColorState *state, guint temperature)
+{
+ g_return_if_fail (GSD_IS_COLOR_STATE (state));
+
+ if (state->color_temperature == temperature)
+ return;
+
+ state->color_temperature = temperature;
+ gcm_session_set_gamma_for_all_devices (state);
+}
+
+guint
+gsd_color_state_get_temperature (GsdColorState *state)
+{
+ g_return_val_if_fail (GSD_IS_COLOR_STATE (state), 0);
+ return state->color_temperature;
+}
+
+static gchar *
+gcm_session_get_output_id (GsdColorState *state, GnomeRROutput *output)
+{
+ const gchar *name;
+ const gchar *serial;
+ const gchar *vendor;
+ GcmEdid *edid = NULL;
+ GString *device_id;
+ GError *error = NULL;
+
+ /* all output devices are prefixed with this */
+ device_id = g_string_new ("xrandr");
+
+ /* get the output EDID if possible */
+ edid = gcm_session_get_output_edid (state, output, &error);
+ if (edid == NULL) {
+ g_debug ("no edid for %s [%s], falling back to connection name",
+ gnome_rr_output_get_name (output),
+ error->message);
+ g_error_free (error);
+ g_string_append_printf (device_id,
+ "-%s",
+ gnome_rr_output_get_name (output));
+ goto out;
+ }
+
+ /* check EDID data is okay to use */
+ vendor = gcm_edid_get_vendor_name (edid);
+ name = gcm_edid_get_monitor_name (edid);
+ serial = gcm_edid_get_serial_number (edid);
+ if (vendor == NULL && name == NULL && serial == NULL) {
+ g_debug ("edid invalid for %s, falling back to connection name",
+ gnome_rr_output_get_name (output));
+ g_string_append_printf (device_id,
+ "-%s",
+ gnome_rr_output_get_name (output));
+ goto out;
+ }
+
+ /* use EDID data */
+ if (vendor != NULL)
+ g_string_append_printf (device_id, "-%s", vendor);
+ if (name != NULL)
+ g_string_append_printf (device_id, "-%s", name);
+ if (serial != NULL)
+ g_string_append_printf (device_id, "-%s", serial);
+out:
+ if (edid != NULL)
+ g_object_unref (edid);
+ return g_string_free (device_id, FALSE);
+}
+
+typedef struct {
+ GsdColorState *state;
+ CdProfile *profile;
+ CdDevice *device;
+ guint32 output_id;
+} GcmSessionAsyncHelper;
+
+static void
+gcm_session_async_helper_free (GcmSessionAsyncHelper *helper)
+{
+ if (helper->state != NULL)
+ g_object_unref (helper->state);
+ if (helper->profile != NULL)
+ g_object_unref (helper->profile);
+ if (helper->device != NULL)
+ g_object_unref (helper->device);
+ g_free (helper);
+}
+
+static gboolean
+gcm_utils_mkdir_for_filename (GFile *file, GError **error)
+{
+ gboolean ret = FALSE;
+ GFile *parent_dir = NULL;
+
+ /* get parent directory */
+ parent_dir = g_file_get_parent (file);
+ if (parent_dir == NULL) {
+ g_set_error_literal (error,
+ GSD_COLOR_MANAGER_ERROR,
+ GSD_COLOR_MANAGER_ERROR_FAILED,
+ "could not get parent dir");
+ goto out;
+ }
+
+ /* ensure desination does not already exist */
+ ret = g_file_query_exists (parent_dir, NULL);
+ if (ret)
+ goto out;
+ ret = g_file_make_directory_with_parents (parent_dir, NULL, error);
+ if (!ret)
+ goto out;
+out:
+ if (parent_dir != NULL)
+ g_object_unref (parent_dir);
+ return ret;
+}
+
+static gboolean
+gcm_get_system_icc_profile (GsdColorState *state,
+ GFile *file)
+{
+ const char efi_path[] = "/sys/firmware/efi/efivars/INTERNAL_PANEL_COLOR_INFO-01e1ada1-79f2-46b3-8d3e-71fc0996ca6b";
+ /* efi variables have a 4-byte header */
+ const int efi_var_header_length = 4;
+ g_autoptr(GFile) efi_file = g_file_new_for_path (efi_path);
+ gboolean ret;
+ g_autofree char *data = NULL;
+ gsize length;
+ g_autoptr(GError) error = NULL;
+
+ ret = g_file_query_exists (efi_file, NULL);
+ if (!ret)
+ return FALSE;
+
+ ret = g_file_load_contents (efi_file,
+ NULL /* cancellable */,
+ &data,
+ &length,
+ NULL /* etag_out */,
+ &error);
+
+ if (!ret) {
+ g_warning ("failed to read EFI system color profile: %s",
+ error->message);
+ return FALSE;
+ }
+
+ if (length <= efi_var_header_length) {
+ g_warning ("EFI system color profile was too short");
+ return FALSE;
+ }
+
+ ret = g_file_replace_contents (file,
+ data + efi_var_header_length,
+ length - efi_var_header_length,
+ NULL /* etag */,
+ FALSE /* make_backup */,
+ G_FILE_CREATE_NONE,
+ NULL /* new_etag */,
+ NULL /* cancellable */,
+ &error);
+ if (!ret) {
+ g_warning ("failed to write system color profile: %s",
+ error->message);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gcm_apply_create_icc_profile_for_edid (GsdColorState *state,
+ CdDevice *device,
+ GcmEdid *edid,
+ GFile *file,
+ GError **error)
+{
+ CdIcc *icc = NULL;
+ const gchar *data;
+ gboolean ret = FALSE;
+
+ /* ensure the per-user directory exists */
+ ret = gcm_utils_mkdir_for_filename (file, error);
+ if (!ret)
+ goto out;
+
+ /* create our generated profile */
+ icc = cd_icc_new ();
+ ret = cd_icc_create_from_edid (icc,
+ gcm_edid_get_gamma (edid),
+ gcm_edid_get_red (edid),
+ gcm_edid_get_green (edid),
+ gcm_edid_get_blue (edid),
+ gcm_edid_get_white (edid),
+ error);
+ if (!ret)
+ goto out;
+
+ /* set copyright */
+ cd_icc_set_copyright (icc, NULL,
+ /* deliberately not translated */
+ "This profile is free of known copyright restrictions.");
+
+ /* set model and title */
+ data = gcm_edid_get_monitor_name (edid);
+ if (data == NULL)
+ data = cd_client_get_system_model (state->client);
+ if (data == NULL)
+ data = "Unknown monitor";
+ cd_icc_set_model (icc, NULL, data);
+ cd_icc_set_description (icc, NULL, data);
+
+ /* get manufacturer */
+ data = gcm_edid_get_vendor_name (edid);
+ if (data == NULL)
+ data = cd_client_get_system_vendor (state->client);
+ if (data == NULL)
+ data = "Unknown vendor";
+ cd_icc_set_manufacturer (icc, NULL, data);
+
+ /* set the framework creator metadata */
+ cd_icc_add_metadata (icc,
+ CD_PROFILE_METADATA_CMF_PRODUCT,
+ PACKAGE_NAME);
+ cd_icc_add_metadata (icc,
+ CD_PROFILE_METADATA_CMF_BINARY,
+ PACKAGE_NAME);
+ cd_icc_add_metadata (icc,
+ CD_PROFILE_METADATA_CMF_VERSION,
+ PACKAGE_VERSION);
+ cd_icc_add_metadata (icc,
+ CD_PROFILE_METADATA_MAPPING_DEVICE_ID,
+ cd_device_get_id (device));
+
+ /* set 'ICC meta Tag for Monitor Profiles' data */
+ cd_icc_add_metadata (icc, CD_PROFILE_METADATA_EDID_MD5, gcm_edid_get_checksum (edid));
+ data = gcm_edid_get_monitor_name (edid);
+ if (data != NULL)
+ cd_icc_add_metadata (icc, CD_PROFILE_METADATA_EDID_MODEL, data);
+ data = gcm_edid_get_serial_number (edid);
+ if (data != NULL)
+ cd_icc_add_metadata (icc, CD_PROFILE_METADATA_EDID_SERIAL, data);
+ data = gcm_edid_get_pnp_id (edid);
+ if (data != NULL)
+ cd_icc_add_metadata (icc, CD_PROFILE_METADATA_EDID_MNFT, data);
+ data = gcm_edid_get_vendor_name (edid);
+ if (data != NULL)
+ cd_icc_add_metadata (icc, CD_PROFILE_METADATA_EDID_VENDOR, data);
+
+ /* save */
+ ret = cd_icc_save_file (icc, file, CD_ICC_SAVE_FLAGS_NONE, NULL, error);
+ if (!ret)
+ goto out;
+out:
+ if (icc != NULL)
+ g_object_unref (icc);
+ return ret;
+}
+
+static GPtrArray *
+gcm_session_generate_vcgt (CdProfile *profile, guint color_temperature, guint size)
+{
+ GnomeRROutputClutItem *tmp;
+ GPtrArray *array = NULL;
+ const cmsToneCurve **vcgt;
+ cmsFloat32Number in;
+ guint i;
+ cmsHPROFILE lcms_profile;
+ CdIcc *icc = NULL;
+ CdColorRGB temp;
+
+ /* invalid size */
+ if (size == 0)
+ goto out;
+
+ /* open file */
+ icc = cd_profile_load_icc (profile, CD_ICC_LOAD_FLAGS_NONE, NULL, NULL);
+ if (icc == NULL)
+ goto out;
+
+ /* get tone curves from profile */
+ lcms_profile = cd_icc_get_handle (icc);
+ vcgt = cmsReadTag (lcms_profile, cmsSigVcgtTag);
+ if (vcgt == NULL || vcgt[0] == NULL) {
+ g_debug ("profile does not have any VCGT data");
+ goto out;
+ }
+
+ /* get the color temperature */
+ if (!cd_color_get_blackbody_rgb_full (color_temperature,
+ &temp,
+ CD_COLOR_BLACKBODY_FLAG_USE_PLANCKIAN)) {
+ g_warning ("failed to get blackbody for %uK", color_temperature);
+ cd_color_rgb_set (&temp, 1.0, 1.0, 1.0);
+ } else {
+ g_debug ("using VCGT gamma of %uK = %.1f,%.1f,%.1f",
+ color_temperature, temp.R, temp.G, temp.B);
+ }
+
+ /* create array */
+ array = g_ptr_array_new_with_free_func (g_free);
+ for (i = 0; i < size; i++) {
+ in = (gdouble) i / (gdouble) (size - 1);
+ tmp = g_new0 (GnomeRROutputClutItem, 1);
+ tmp->red = cmsEvalToneCurveFloat(vcgt[0], in) * temp.R * (gdouble) 0xffff;
+ tmp->green = cmsEvalToneCurveFloat(vcgt[1], in) * temp.G * (gdouble) 0xffff;
+ tmp->blue = cmsEvalToneCurveFloat(vcgt[2], in) * temp.B * (gdouble) 0xffff;
+ g_ptr_array_add (array, tmp);
+ }
+out:
+ if (icc != NULL)
+ g_object_unref (icc);
+ return array;
+}
+
+static guint
+gnome_rr_output_get_gamma_size (GnomeRROutput *output)
+{
+ GnomeRRCrtc *crtc;
+ gint len = 0;
+
+ crtc = gnome_rr_output_get_crtc (output);
+ if (crtc == NULL)
+ return 0;
+ gnome_rr_crtc_get_gamma (crtc,
+ &len,
+ NULL, NULL, NULL);
+ return (guint) len;
+}
+
+static gboolean
+gcm_session_output_set_gamma (GnomeRROutput *output,
+ GPtrArray *array,
+ GError **error)
+{
+ gboolean ret = TRUE;
+ guint16 *red = NULL;
+ guint16 *green = NULL;
+ guint16 *blue = NULL;
+ guint i;
+ GnomeRROutputClutItem *data;
+ GnomeRRCrtc *crtc;
+
+ /* no length? */
+ if (array->len == 0) {
+ ret = FALSE;
+ g_set_error_literal (error,
+ GSD_COLOR_MANAGER_ERROR,
+ GSD_COLOR_MANAGER_ERROR_FAILED,
+ "no data in the CLUT array");
+ goto out;
+ }
+
+ /* convert to a type X understands */
+ red = g_new (guint16, array->len);
+ green = g_new (guint16, array->len);
+ blue = g_new (guint16, array->len);
+ for (i = 0; i < array->len; i++) {
+ data = g_ptr_array_index (array, i);
+ red[i] = data->red;
+ green[i] = data->green;
+ blue[i] = data->blue;
+ }
+
+ /* send to LUT */
+ crtc = gnome_rr_output_get_crtc (output);
+ if (crtc == NULL) {
+ ret = FALSE;
+ g_set_error (error,
+ GSD_COLOR_MANAGER_ERROR,
+ GSD_COLOR_MANAGER_ERROR_FAILED,
+ "failed to get ctrc for %s",
+ gnome_rr_output_get_name (output));
+ goto out;
+ }
+ gnome_rr_crtc_set_gamma (crtc, array->len,
+ red, green, blue);
+out:
+ g_free (red);
+ g_free (green);
+ g_free (blue);
+ return ret;
+}
+
+static gboolean
+gcm_session_device_set_gamma (GnomeRROutput *output,
+ CdProfile *profile,
+ guint color_temperature,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ guint size;
+ GPtrArray *clut = NULL;
+
+ /* create a lookup table */
+ size = gnome_rr_output_get_gamma_size (output);
+ if (size == 0) {
+ ret = TRUE;
+ goto out;
+ }
+ clut = gcm_session_generate_vcgt (profile, color_temperature, size);
+ if (clut == NULL) {
+ g_set_error_literal (error,
+ GSD_COLOR_MANAGER_ERROR,
+ GSD_COLOR_MANAGER_ERROR_FAILED,
+ "failed to generate vcgt");
+ goto out;
+ }
+
+ /* apply the vcgt to this output */
+ ret = gcm_session_output_set_gamma (output, clut, error);
+ if (!ret)
+ goto out;
+out:
+ if (clut != NULL)
+ g_ptr_array_unref (clut);
+ return ret;
+}
+
+static gboolean
+gcm_session_device_reset_gamma (GnomeRROutput *output,
+ guint color_temperature,
+ GError **error)
+{
+ gboolean ret;
+ guint i;
+ guint size;
+ guint32 value;
+ GPtrArray *clut;
+ GnomeRROutputClutItem *data;
+ CdColorRGB temp;
+
+ /* create a linear ramp */
+ g_debug ("falling back to dummy ramp");
+ clut = g_ptr_array_new_with_free_func (g_free);
+ size = gnome_rr_output_get_gamma_size (output);
+ if (size == 0) {
+ ret = TRUE;
+ goto out;
+ }
+
+ /* get the color temperature */
+ if (!cd_color_get_blackbody_rgb_full (color_temperature,
+ &temp,
+ CD_COLOR_BLACKBODY_FLAG_USE_PLANCKIAN)) {
+ g_warning ("failed to get blackbody for %uK", color_temperature);
+ cd_color_rgb_set (&temp, 1.0, 1.0, 1.0);
+ } else {
+ g_debug ("using reset gamma of %uK = %.1f,%.1f,%.1f",
+ color_temperature, temp.R, temp.G, temp.B);
+ }
+
+ for (i = 0; i < size; i++) {
+ value = (i * 0xffff) / (size - 1);
+ data = g_new0 (GnomeRROutputClutItem, 1);
+ data->red = value * temp.R;
+ data->green = value * temp.G;
+ data->blue = value * temp.B;
+ g_ptr_array_add (clut, data);
+ }
+
+ /* apply the vcgt to this output */
+ ret = gcm_session_output_set_gamma (output, clut, error);
+ if (!ret)
+ goto out;
+out:
+ g_ptr_array_unref (clut);
+ return ret;
+}
+
+static GnomeRROutput *
+gcm_session_get_state_output_by_id (GsdColorState *state,
+ const gchar *device_id,
+ GError **error)
+{
+ gchar *output_id;
+ GnomeRROutput *output = NULL;
+ GnomeRROutput **outputs = NULL;
+ guint i;
+
+ /* search all STATE outputs for the device id */
+ outputs = gnome_rr_screen_list_outputs (state->state_screen);
+ if (outputs == NULL) {
+ g_set_error_literal (error,
+ GSD_COLOR_MANAGER_ERROR,
+ GSD_COLOR_MANAGER_ERROR_FAILED,
+ "Failed to get outputs");
+ goto out;
+ }
+ for (i = 0; outputs[i] != NULL && output == NULL; i++) {
+ output_id = gcm_session_get_output_id (state, outputs[i]);
+ if (g_strcmp0 (output_id, device_id) == 0)
+ output = outputs[i];
+ g_free (output_id);
+ }
+ if (output == NULL) {
+ g_set_error (error,
+ GSD_COLOR_MANAGER_ERROR,
+ GSD_COLOR_MANAGER_ERROR_FAILED,
+ "Failed to find output %s",
+ device_id);
+ }
+out:
+ return output;
+}
+
+/* this function is more complicated than it should be, due to the
+ * fact that XOrg sometimes assigns no primary devices when using
+ * "xrandr --auto" or when the version of RANDR is < 1.3 */
+static gboolean
+gcm_session_use_output_profile_for_screen (GsdColorState *state,
+ GnomeRROutput *output)
+{
+ gboolean has_laptop = FALSE;
+ gboolean has_primary = FALSE;
+ GnomeRROutput **outputs;
+ GnomeRROutput *connected = NULL;
+ guint i;
+
+ /* do we have any screens marked as primary */
+ outputs = gnome_rr_screen_list_outputs (state->state_screen);
+ if (outputs == NULL || outputs[0] == NULL) {
+ g_warning ("failed to get outputs");
+ return FALSE;
+ }
+ for (i = 0; outputs[i] != NULL; i++) {
+ if (connected == NULL)
+ connected = outputs[i];
+ if (gnome_rr_output_get_is_primary (outputs[i]))
+ has_primary = TRUE;
+ if (gnome_rr_output_is_builtin_display (outputs[i]))
+ has_laptop = TRUE;
+ }
+
+ /* we have an assigned primary device, are we that? */
+ if (has_primary)
+ return gnome_rr_output_get_is_primary (output);
+
+ /* choosing the internal panel is probably sane */
+ if (has_laptop)
+ return gnome_rr_output_is_builtin_display (output);
+
+ /* we have to choose one, so go for the first connected device */
+ if (connected != NULL)
+ return gnome_rr_output_get_id (connected) == gnome_rr_output_get_id (output);
+
+ return FALSE;
+}
+
+/* TODO: remove when we can dep on a released version of colord */
+#ifndef CD_PROFILE_METADATA_SCREEN_BRIGHTNESS
+#define CD_PROFILE_METADATA_SCREEN_BRIGHTNESS "SCREEN_brightness"
+#endif
+
+#define GSD_DBUS_NAME_POWER GSD_DBUS_NAME ".Power"
+#define GSD_DBUS_INTERFACE_POWER_SCREEN GSD_DBUS_BASE_INTERFACE ".Power.Screen"
+#define GSD_DBUS_PATH_POWER GSD_DBUS_PATH "/Power"
+
+static void
+gcm_session_set_output_percentage (guint percentage)
+{
+ GDBusConnection *connection;
+
+ /* get a ref to the existing bus connection */
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+ if (connection == NULL)
+ return;
+ g_dbus_connection_call (connection,
+ GSD_DBUS_NAME_POWER,
+ GSD_DBUS_PATH_POWER,
+ "org.freedesktop.DBus.Properties",
+ "Set",
+ g_variant_new_parsed ("('" GSD_DBUS_INTERFACE_POWER_SCREEN "',"
+ "'Brightness', %v)",
+ g_variant_new_int32 (percentage)),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, NULL, NULL);
+ g_object_unref (connection);
+}
+
+static void
+gcm_session_device_assign_profile_connect_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CdProfile *profile = CD_PROFILE (object);
+ const gchar *brightness_profile;
+ const gchar *filename;
+ gboolean ret;
+ GError *error = NULL;
+ GnomeRROutput *output;
+ guint brightness_percentage;
+ GcmSessionAsyncHelper *helper = (GcmSessionAsyncHelper *) user_data;
+ GsdColorState *state = GSD_COLOR_STATE (helper->state);
+
+ /* get properties */
+ ret = cd_profile_connect_finish (profile, res, &error);
+ if (!ret) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("failed to connect to profile: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* get the filename */
+ filename = cd_profile_get_filename (profile);
+ g_assert (filename != NULL);
+
+ /* get the output (can't save in helper as GnomeRROutput isn't
+ * a GObject, just a pointer */
+ output = gnome_rr_screen_get_output_by_id (state->state_screen,
+ helper->output_id);
+ if (output == NULL)
+ goto out;
+
+ /* if output is a laptop screen and the profile has a
+ * calibration brightness then set this new brightness */
+ brightness_profile = cd_profile_get_metadata_item (profile,
+ CD_PROFILE_METADATA_SCREEN_BRIGHTNESS);
+ if (gnome_rr_output_is_builtin_display (output) &&
+ brightness_profile != NULL) {
+ /* the percentage is stored in the profile metadata as
+ * a string, not ideal, but it's all we have... */
+ brightness_percentage = atoi (brightness_profile);
+ gcm_session_set_output_percentage (brightness_percentage);
+ }
+
+ /* set the _ICC_PROFILE atom */
+ ret = gcm_session_use_output_profile_for_screen (state, output);
+ if (ret) {
+ ret = gcm_session_screen_set_icc_profile (state,
+ filename,
+ &error);
+ if (!ret) {
+ g_warning ("failed to set screen _ICC_PROFILE: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+ }
+
+ /* create a vcgt for this icc file */
+ ret = cd_profile_get_has_vcgt (profile);
+ if (ret) {
+ ret = gcm_session_device_set_gamma (output,
+ profile,
+ state->color_temperature,
+ &error);
+ if (!ret) {
+ g_warning ("failed to set %s gamma tables: %s",
+ cd_device_get_id (helper->device),
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+ } else {
+ ret = gcm_session_device_reset_gamma (output,
+ state->color_temperature,
+ &error);
+ if (!ret) {
+ g_warning ("failed to reset %s gamma tables: %s",
+ cd_device_get_id (helper->device),
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+ }
+out:
+ gcm_session_async_helper_free (helper);
+}
+
+/*
+ * Check to see if the on-disk profile has the MAPPING_device_id
+ * metadata, and if not, we should delete the profile and re-create it
+ * so that it gets mapped by the daemon.
+ */
+static gboolean
+gcm_session_check_profile_device_md (GFile *file)
+{
+ const gchar *key_we_need = CD_PROFILE_METADATA_MAPPING_DEVICE_ID;
+ CdIcc *icc;
+ gboolean ret;
+
+ icc = cd_icc_new ();
+ ret = cd_icc_load_file (icc, file, CD_ICC_LOAD_FLAGS_METADATA, NULL, NULL);
+ if (!ret)
+ goto out;
+ ret = cd_icc_get_metadata_item (icc, key_we_need) != NULL;
+ if (!ret) {
+ g_debug ("auto-edid profile is old, and contains no %s data",
+ key_we_need);
+ }
+out:
+ g_object_unref (icc);
+ return ret;
+}
+
+static void
+gcm_session_device_assign_connect_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CdDeviceKind kind;
+ CdProfile *profile = NULL;
+ gboolean ret;
+ gchar *autogen_filename = NULL;
+ gchar *autogen_path = NULL;
+ GcmEdid *edid = NULL;
+ GnomeRROutput *output = NULL;
+ GError *error = NULL;
+ GFile *file = NULL;
+ const gchar *xrandr_id;
+ GcmSessionAsyncHelper *helper;
+ CdDevice *device = CD_DEVICE (object);
+ GsdColorState *state = GSD_COLOR_STATE (user_data);
+
+ /* remove from assign array */
+ g_hash_table_remove (state->device_assign_hash,
+ cd_device_get_object_path (device));
+
+ /* get properties */
+ ret = cd_device_connect_finish (device, res, &error);
+ if (!ret) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ 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)
+ goto out;
+
+ g_debug ("need to assign display device %s",
+ cd_device_get_id (device));
+
+ /* get the GnomeRROutput for the device id */
+ xrandr_id = cd_device_get_id (device);
+ output = gcm_session_get_state_output_by_id (state,
+ xrandr_id,
+ &error);
+ if (output == NULL) {
+ g_warning ("no %s device found: %s",
+ cd_device_get_id (device),
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* create profile from device edid if it exists */
+ edid = gcm_session_get_output_edid (state, output, &error);
+ if (edid == NULL) {
+ g_warning ("unable to get EDID for %s: %s",
+ cd_device_get_id (device),
+ error->message);
+ g_clear_error (&error);
+
+ } else {
+ autogen_filename = g_strdup_printf ("edid-%s.icc",
+ gcm_edid_get_checksum (edid));
+ autogen_path = g_build_filename (g_get_user_data_dir (),
+ "icc", autogen_filename, NULL);
+
+ /* check if auto-profile has up-to-date metadata */
+ file = g_file_new_for_path (autogen_path);
+ if (gcm_session_check_profile_device_md (file)) {
+ g_debug ("auto-profile edid %s exists with md", autogen_path);
+ } else {
+ g_debug ("auto-profile edid does not exist, creating as %s",
+ autogen_path);
+
+ /* check if the system has a built-in profile */
+ ret = gnome_rr_output_is_builtin_display (output) &&
+ gcm_get_system_icc_profile (state, file);
+
+ /* try creating one from the EDID */
+ if (!ret) {
+ ret = gcm_apply_create_icc_profile_for_edid (state,
+ device,
+ edid,
+ file,
+ &error);
+ }
+
+ if (!ret) {
+ g_warning ("failed to create profile from EDID data: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+ }
+ }
+
+ /* get the default profile for the device */
+ profile = cd_device_get_default_profile (device);
+ if (profile == NULL) {
+ g_debug ("%s has no default profile to set",
+ cd_device_get_id (device));
+
+ /* the default output? */
+ if (gnome_rr_output_get_is_primary (output) &&
+ state->gdk_window != NULL) {
+ gdk_property_delete (state->gdk_window,
+ gdk_atom_intern_static_string ("_ICC_PROFILE"));
+ gdk_property_delete (state->gdk_window,
+ gdk_atom_intern_static_string ("_ICC_PROFILE_IN_X_VERSION"));
+ }
+
+ /* reset, as we want linear profiles for profiling */
+ ret = gcm_session_device_reset_gamma (output,
+ state->color_temperature,
+ &error);
+ if (!ret) {
+ g_warning ("failed to reset %s gamma tables: %s",
+ cd_device_get_id (device),
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+ goto out;
+ }
+
+ /* get properties */
+ helper = g_new0 (GcmSessionAsyncHelper, 1);
+ helper->output_id = gnome_rr_output_get_id (output);
+ helper->state = g_object_ref (state);
+ helper->device = g_object_ref (device);
+ cd_profile_connect (profile,
+ state->cancellable,
+ gcm_session_device_assign_profile_connect_cb,
+ helper);
+out:
+ g_free (autogen_filename);
+ g_free (autogen_path);
+ if (file != NULL)
+ g_object_unref (file);
+ if (edid != NULL)
+ g_object_unref (edid);
+ if (profile != NULL)
+ g_object_unref (profile);
+}
+
+static void
+gcm_session_device_assign (GsdColorState *state, CdDevice *device)
+{
+ const gchar *key;
+ gpointer found;
+
+ /* are we already assigning this device */
+ key = cd_device_get_object_path (device);
+ found = g_hash_table_lookup (state->device_assign_hash, key);
+ if (found != NULL) {
+ g_debug ("assign for %s already in progress", key);
+ return;
+ }
+ g_hash_table_insert (state->device_assign_hash,
+ g_strdup (key),
+ GINT_TO_POINTER (TRUE));
+ cd_device_connect (device,
+ state->cancellable,
+ gcm_session_device_assign_connect_cb,
+ state);
+}
+
+static void
+gcm_session_device_added_assign_cb (CdClient *client,
+ CdDevice *device,
+ GsdColorState *state)
+{
+ gcm_session_device_assign (state, device);
+}
+
+static void
+gcm_session_device_changed_assign_cb (CdClient *client,
+ CdDevice *device,
+ GsdColorState *state)
+{
+ g_debug ("%s changed", cd_device_get_object_path (device));
+ gcm_session_device_assign (state, device);
+}
+
+static void
+gcm_session_create_device_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CdDevice *device;
+ GError *error = NULL;
+
+ device = cd_client_create_device_finish (CD_CLIENT (object),
+ res,
+ &error);
+ if (device == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+ !g_error_matches (error, CD_CLIENT_ERROR, CD_CLIENT_ERROR_ALREADY_EXISTS))
+ g_warning ("failed to create device: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+ g_object_unref (device);
+}
+
+static void
+gcm_session_add_state_output (GsdColorState *state, GnomeRROutput *output)
+{
+ const gchar *edid_checksum = NULL;
+ const gchar *model = NULL;
+ const gchar *output_name = NULL;
+ const gchar *serial = NULL;
+ const gchar *vendor = NULL;
+ gboolean ret;
+ gchar *device_id = NULL;
+ GcmEdid *edid;
+ GError *error = NULL;
+ GHashTable *device_props = NULL;
+
+ /* VNC creates a fake device that cannot be color managed */
+ output_name = gnome_rr_output_get_name (output);
+ if (output_name != NULL && g_str_has_prefix (output_name, "VNC-")) {
+ g_debug ("ignoring %s as fake VNC device detected", output_name);
+ return;
+ }
+
+ /* try to get edid */
+ edid = gcm_session_get_output_edid (state, output, &error);
+ if (edid == NULL) {
+ g_warning ("failed to get edid: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+
+ /* prefer DMI data for the internal output */
+ ret = gnome_rr_output_is_builtin_display (output);
+ if (ret) {
+ model = cd_client_get_system_model (state->client);
+ vendor = cd_client_get_system_vendor (state->client);
+ }
+
+ /* use EDID data if we have it */
+ if (edid != NULL) {
+ edid_checksum = gcm_edid_get_checksum (edid);
+ if (model == NULL)
+ model = gcm_edid_get_monitor_name (edid);
+ if (vendor == NULL)
+ vendor = gcm_edid_get_vendor_name (edid);
+ if (serial == NULL)
+ serial = gcm_edid_get_serial_number (edid);
+ }
+
+ /* ensure mandatory fields are set */
+ if (model == NULL)
+ model = gnome_rr_output_get_name (output);
+ if (vendor == NULL)
+ vendor = "unknown";
+ if (serial == NULL)
+ serial = "unknown";
+
+ device_id = gcm_session_get_output_id (state, output);
+ g_debug ("output %s added", device_id);
+ device_props = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, NULL);
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_PROPERTY_KIND,
+ (gpointer) cd_device_kind_to_string (CD_DEVICE_KIND_DISPLAY));
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_PROPERTY_MODE,
+ (gpointer) cd_device_mode_to_string (CD_DEVICE_MODE_PHYSICAL));
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_PROPERTY_COLORSPACE,
+ (gpointer) cd_colorspace_to_string (CD_COLORSPACE_RGB));
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_PROPERTY_VENDOR,
+ (gpointer) vendor);
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_PROPERTY_MODEL,
+ (gpointer) model);
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_PROPERTY_SERIAL,
+ (gpointer) serial);
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_METADATA_XRANDR_NAME,
+ (gpointer) gnome_rr_output_get_name (output));
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_METADATA_OUTPUT_PRIORITY,
+ gnome_rr_output_get_is_primary (output) ?
+ (gpointer) CD_DEVICE_METADATA_OUTPUT_PRIORITY_PRIMARY :
+ (gpointer) CD_DEVICE_METADATA_OUTPUT_PRIORITY_SECONDARY);
+ if (edid_checksum != NULL) {
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_METADATA_OUTPUT_EDID_MD5,
+ (gpointer) edid_checksum);
+ }
+ /* set this so we can call the device a 'Laptop Screen' in the
+ * control center main panel */
+ if (gnome_rr_output_is_builtin_display (output)) {
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_PROPERTY_EMBEDDED,
+ NULL);
+ }
+ cd_client_create_device (state->client,
+ device_id,
+ CD_OBJECT_SCOPE_TEMP,
+ device_props,
+ state->cancellable,
+ gcm_session_create_device_cb,
+ state);
+ g_free (device_id);
+ if (device_props != NULL)
+ g_hash_table_unref (device_props);
+ if (edid != NULL)
+ g_object_unref (edid);
+}
+
+
+static void
+gnome_rr_screen_output_added_cb (GnomeRRScreen *screen,
+ GnomeRROutput *output,
+ GsdColorState *state)
+{
+ gcm_session_add_state_output (state, output);
+}
+
+static void
+gcm_session_screen_removed_delete_device_cb (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+ gboolean ret;
+ GError *error = NULL;
+
+ /* deleted device */
+ ret = cd_client_delete_device_finish (CD_CLIENT (object),
+ res,
+ &error);
+ if (!ret) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("failed to delete device: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+gcm_session_screen_removed_find_device_cb (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+ GError *error = NULL;
+ CdDevice *device;
+ GsdColorState *state = GSD_COLOR_STATE (user_data);
+
+ device = cd_client_find_device_finish (state->client,
+ res,
+ &error);
+ if (device == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("failed to find device: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+ g_debug ("output %s found, and will be removed",
+ cd_device_get_object_path (device));
+ cd_client_delete_device (state->client,
+ device,
+ state->cancellable,
+ gcm_session_screen_removed_delete_device_cb,
+ state);
+ g_object_unref (device);
+}
+
+static void
+gnome_rr_screen_output_removed_cb (GnomeRRScreen *screen,
+ GnomeRROutput *output,
+ GsdColorState *state)
+{
+ g_debug ("output %s removed",
+ gnome_rr_output_get_name (output));
+ g_hash_table_remove (state->edid_cache,
+ gnome_rr_output_get_name (output));
+ cd_client_find_device_by_property (state->client,
+ CD_DEVICE_METADATA_XRANDR_NAME,
+ gnome_rr_output_get_name (output),
+ state->cancellable,
+ gcm_session_screen_removed_find_device_cb,
+ state);
+}
+
+static void
+gcm_session_get_devices_cb (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+ CdDevice *device;
+ GError *error = NULL;
+ GPtrArray *array;
+ guint i;
+ GsdColorState *state = GSD_COLOR_STATE (user_data);
+
+ array = cd_client_get_devices_finish (CD_CLIENT (object), res, &error);
+ if (array == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("failed to get devices: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+ for (i = 0; i < array->len; i++) {
+ device = g_ptr_array_index (array, i);
+ gcm_session_device_assign (state, device);
+ }
+
+ if (array != NULL)
+ g_ptr_array_unref (array);
+}
+
+static void
+gcm_session_profile_gamma_find_device_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CdClient *client = CD_CLIENT (object);
+ CdDevice *device = NULL;
+ GError *error = NULL;
+ GsdColorState *state = GSD_COLOR_STATE (user_data);
+
+ device = cd_client_find_device_by_property_finish (client,
+ res,
+ &error);
+ if (device == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("could not find device: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ /* get properties */
+ cd_device_connect (device,
+ state->cancellable,
+ gcm_session_device_assign_connect_cb,
+ state);
+
+ if (device != NULL)
+ g_object_unref (device);
+}
+
+static void
+gcm_session_set_gamma_for_all_devices (GsdColorState *state)
+{
+ GnomeRROutput **outputs;
+ guint i;
+
+ /* setting the temperature before we get the list of devices is fine,
+ * as we use the temperature in the calculation */
+ if (state->state_screen == NULL)
+ return;
+
+ /* get STATE outputs */
+ outputs = gnome_rr_screen_list_outputs (state->state_screen);
+ if (outputs == NULL) {
+ g_warning ("failed to get outputs");
+ return;
+ }
+ for (i = 0; outputs[i] != NULL; i++) {
+ /* get CdDevice for this output */
+ cd_client_find_device_by_property (state->client,
+ CD_DEVICE_METADATA_XRANDR_NAME,
+ gnome_rr_output_get_name (outputs[i]),
+ state->cancellable,
+ gcm_session_profile_gamma_find_device_cb,
+ state);
+ }
+}
+
+/* We have to reset the gamma tables each time as if the primary output
+ * has changed then different crtcs are going to be used.
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=660164 for an example */
+static void
+gnome_rr_screen_output_changed_cb (GnomeRRScreen *screen,
+ GsdColorState *state)
+{
+ gcm_session_set_gamma_for_all_devices (state);
+}
+
+static gboolean
+has_changed (char **strv,
+ const char *str)
+{
+ guint i;
+ for (i = 0; strv[i] != NULL; i++) {
+ if (g_str_equal (str, strv[i]))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+gcm_session_active_changed_cb (GDBusProxy *session,
+ GVariant *changed,
+ char **invalidated,
+ GsdColorState *state)
+{
+ GVariant *active_v = NULL;
+ gboolean is_active;
+
+ if (has_changed (invalidated, "SessionIsActive"))
+ return;
+
+ /* not yet connected to the daemon */
+ if (!cd_client_get_connected (state->client))
+ return;
+
+ active_v = g_dbus_proxy_get_cached_property (session, "SessionIsActive");
+ g_return_if_fail (active_v != NULL);
+ is_active = g_variant_get_boolean (active_v);
+ g_variant_unref (active_v);
+
+ /* When doing the fast-user-switch into a new account, load the
+ * new users chosen profiles.
+ *
+ * If this is the first time the GnomeSettingsSession has been
+ * loaded, then we'll get a change from unknown to active
+ * and we want to avoid reprobing the devices for that.
+ */
+ if (is_active && !state->session_is_active) {
+ g_debug ("Done switch to new account, reload devices");
+ cd_client_get_devices (state->client,
+ state->cancellable,
+ gcm_session_get_devices_cb,
+ state);
+ }
+ state->session_is_active = is_active;
+}
+
+static void
+gcm_session_client_connect_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ gboolean ret;
+ GError *error = NULL;
+ GnomeRROutput **outputs;
+ guint i;
+ GsdColorState *state = GSD_COLOR_STATE (user_data);
+
+ /* connected */
+ ret = cd_client_connect_finish (state->client, res, &error);
+ if (!ret) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("failed to connect to colord: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ /* is there an available colord instance? */
+ ret = cd_client_get_has_server (state->client);
+ if (!ret) {
+ g_warning ("There is no colord server available");
+ return;
+ }
+
+ /* watch if sessions change */
+ g_signal_connect_object (state->session, "g-properties-changed",
+ G_CALLBACK (gcm_session_active_changed_cb),
+ state, 0);
+
+ /* add screens */
+ gnome_rr_screen_refresh (state->state_screen, &error);
+ if (error != NULL) {
+ g_warning ("failed to refresh: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ /* get STATE outputs */
+ outputs = gnome_rr_screen_list_outputs (state->state_screen);
+ if (outputs == NULL) {
+ g_warning ("failed to get outputs");
+ return;
+ }
+ for (i = 0; outputs[i] != NULL; i++) {
+ gcm_session_add_state_output (state, outputs[i]);
+ }
+
+ /* only connect when colord is awake */
+ g_signal_connect (state->state_screen, "output-connected",
+ G_CALLBACK (gnome_rr_screen_output_added_cb),
+ state);
+ g_signal_connect (state->state_screen, "output-disconnected",
+ G_CALLBACK (gnome_rr_screen_output_removed_cb),
+ state);
+ g_signal_connect (state->state_screen, "changed",
+ G_CALLBACK (gnome_rr_screen_output_changed_cb),
+ state);
+
+ g_signal_connect (state->client, "device-added",
+ G_CALLBACK (gcm_session_device_added_assign_cb),
+ state);
+ g_signal_connect (state->client, "device-changed",
+ G_CALLBACK (gcm_session_device_changed_assign_cb),
+ state);
+
+ /* set for each device that already exist */
+ cd_client_get_devices (state->client,
+ state->cancellable,
+ gcm_session_get_devices_cb,
+ state);
+}
+
+static void
+on_rr_screen_acquired (GObject *object,
+ GAsyncResult *result,
+ gpointer data)
+{
+ GsdColorState *state = data;
+ GnomeRRScreen *screen;
+ GError *error = NULL;
+
+ /* gnome_rr_screen_new_async() does not take a GCancellable */
+ if (g_cancellable_is_cancelled (state->cancellable))
+ goto out;
+
+ screen = gnome_rr_screen_new_finish (result, &error);
+ if (screen == NULL) {
+ g_warning ("failed to get screens: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ state->state_screen = screen;
+
+ cd_client_connect (state->client,
+ state->cancellable,
+ gcm_session_client_connect_cb,
+ state);
+out:
+ /* manually added */
+ g_object_unref (state);
+}
+
+void
+gsd_color_state_start (GsdColorState *state)
+{
+ /* use a fresh cancellable for each start->stop operation */
+ g_cancellable_cancel (state->cancellable);
+ g_clear_object (&state->cancellable);
+ state->cancellable = g_cancellable_new ();
+
+ /* coldplug the list of screens */
+ gnome_rr_screen_new_async (gdk_screen_get_default (),
+ on_rr_screen_acquired,
+ g_object_ref (state));
+}
+
+void
+gsd_color_state_stop (GsdColorState *state)
+{
+ g_cancellable_cancel (state->cancellable);
+}
+
+static void
+gsd_color_state_class_init (GsdColorStateClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gsd_color_state_finalize;
+}
+
+static void
+gsd_color_state_init (GsdColorState *state)
+{
+ /* track the active session */
+ state->session = gnome_settings_bus_get_session_proxy ();
+
+#ifdef GDK_WINDOWING_X11
+ /* set the _ICC_PROFILE atoms on the root screen */
+ if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ state->gdk_window = gdk_screen_get_root_window (gdk_screen_get_default ());
+#endif
+
+ /* parsing the EDID is expensive */
+ state->edid_cache = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_object_unref);
+
+ /* we don't want to assign devices multiple times at startup */
+ state->device_assign_hash = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ NULL);
+
+ /* default color temperature */
+ state->color_temperature = GSD_COLOR_TEMPERATURE_DEFAULT;
+
+ state->client = cd_client_new ();
+}
+
+static void
+gsd_color_state_finalize (GObject *object)
+{
+ GsdColorState *state;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSD_IS_COLOR_STATE (object));
+
+ state = GSD_COLOR_STATE (object);
+
+ g_cancellable_cancel (state->cancellable);
+ g_clear_object (&state->cancellable);
+ g_clear_object (&state->client);
+ g_clear_object (&state->session);
+ g_clear_pointer (&state->edid_cache, g_hash_table_destroy);
+ g_clear_pointer (&state->device_assign_hash, g_hash_table_destroy);
+ g_clear_object (&state->state_screen);
+
+ G_OBJECT_CLASS (gsd_color_state_parent_class)->finalize (object);
+}
+
+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..f1c8827
--- /dev/null
+++ b/plugins/color/gsd-color-state.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ * Copyright (C) 2011-2013 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_COLOR_STATE_H
+#define __GSD_COLOR_STATE_H
+
+#include <glib-object.h>
+
+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 */
+
+GQuark gsd_color_state_error_quark (void);
+
+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 <richard@hughsie.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <math.h>
+
+#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 <richard@hughsie.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_NIGHT_LIGHT_COMMON_H
+#define __GSD_NIGHT_LIGHT_COMMON_H
+
+#include <glib-object.h>
+
+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..635f160
--- /dev/null
+++ b/plugins/color/gsd-night-light.c
@@ -0,0 +1,801 @@
+/*
+ * Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <geoclue.h>
+
+#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)
+{
+ if (ABS (self->cached_temperature - temperature) <= GSD_TEMPERATURE_MAX_DELTA)
+ 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);
+ 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);
+
+ 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);
+ 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);
+ 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");
+ } else {
+ g_debug ("night light still day-disabled, resetting");
+ gsd_night_light_set_temperature (self,
+ GSD_COLOR_TEMPERATURE_DEFAULT);
+ return;
+ }
+ }
+
+ /* 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;
+ }
+
+ /* 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_active (self, TRUE);
+ 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_sunrise);
+ break;
+ case PROP_TEMPERATURE:
+ g_value_set_double (value, self->cached_sunrise);
+ 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 <richard@hughsie.com>
+ *
+ * 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 <gio/gio.h>
+
+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..2ae1740
--- /dev/null
+++ b/plugins/color/meson.build
@@ -0,0 +1,55 @@
+sources = files(
+ 'gcm-edid.c',
+ 'gnome-datetime-source.c',
+ 'gsd-color-calibrate.c',
+ 'gsd-color-manager.c',
+ 'gsd-color-profiles.c',
+ 'gsd-color-state.c',
+ 'gsd-night-light.c',
+ 'gsd-night-light-common.c',
+ 'main.c'
+)
+
+deps = plugins_deps + [
+ colord_dep,
+ gnome_desktop_dep,
+ lcms_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-edid.c',
+ '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
--- /dev/null
+++ b/plugins/color/test-data/LG-L225W-External.bin
Binary files 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
--- /dev/null
+++ b/plugins/color/test-data/Lenovo-T61-Internal.bin
Binary files differ
diff --git a/plugins/common/daemon-skeleton-gtk.h b/plugins/common/daemon-skeleton-gtk.h
new file mode 100644
index 0000000..596281d
--- /dev/null
+++ b/plugins/common/daemon-skeleton-gtk.h
@@ -0,0 +1,292 @@
+/**
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <locale.h>
+
+#include <glib-unix.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#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);
+#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 <stdlib.h>
+#include <stdio.h>
+#include <locale.h>
+
+#include <glib-unix.h>
+#include <glib/gi18n.h>
+
+#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 <hadess@hadess.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+
+#include <sys/types.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/XInput2.h>
+
+#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 <hadess@hadess.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GSD_INPUT_HELPER_H
+#define __GSD_INPUT_HELPER_H
+
+G_BEGIN_DECLS
+
+#include <glib.h>
+
+#include <X11/extensions/XInput.h>
+#include <X11/extensions/XIproto.h>
+
+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 <carlosg@gnome.org>
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#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 <carlosg@gnome.org>
+ */
+
+#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 <carlosg@gnome.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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 <carlosg@gnome.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gtk/libgtk/theme/Disabled">
+ <file>gtk.css</file>
+ </gresource>
+</gresources>
diff --git a/plugins/common/gtk.css b/plugins/common/gtk.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/common/gtk.css
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 @@
+# <pre>
+# @(#)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 <kalevlember@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+#include <libnotify/notify.h>
+
+#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 <kalevlember@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_DATETIME_MANAGER_H
+#define __GSD_DATETIME_MANAGER_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+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 <kalevlember@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include "gsd-timezone-monitor.h"
+
+#include "timedated.h"
+#include "tz.h"
+#include "weather-tz.h"
+
+#include <geoclue.h>
+#include <geocode-glib/geocode-glib.h>
+#include <polkit/polkit.h>
+
+#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 <kalevlember@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_TIMEZONE_MONITOR_H
+#define __GSD_TIMEZONE_MONITOR_H
+
+#include <glib-object.h>
+
+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 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.freedesktop.timedate1">
+ <property name="Timezone" type="s" access="read"/>
+ <property name="LocalRTC" type="b" access="read"/>
+ <property name="CanNTP" type="b" access="read"/>
+ <property name="NTP" type="b" access="read"/>
+ <method name="SetTime">
+ <arg name="usec_utc" type="x" direction="in"/>
+ <arg name="relative" type="b" direction="in"/>
+ <arg name="user_interaction" type="b" direction="in"/>
+ </method>
+ <method name="SetTimezone">
+ <arg name="timezone" type="s" direction="in"/>
+ <arg name="user_interaction" type="b" direction="in"/>
+ </method>
+ <method name="SetLocalRTC">
+ <arg name="local_rtc" type="b" direction="in"/>
+ <arg name="fix_system" type="b" direction="in"/>
+ <arg name="user_interaction" type="b" direction="in"/>
+ </method>
+ <method name="SetNTP">
+ <arg name="use_ntp" type="b" direction="in"/>
+ <arg name="user_interaction" type="b" direction="in"/>
+ </method>
+ </interface>
+</node>
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 <hpj@ximian.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <math.h>
+#include <string.h>
+#include <ctype.h>
+#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<tab>" 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 <hpj@ximian.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _E_TZ_H
+#define _E_TZ_H
+
+#include <glib.h>
+
+#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..f1de2b9
--- /dev/null
+++ b/plugins/datetime/weather-tz.c
@@ -0,0 +1,122 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Kalev Lember <kalevlember@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include "weather-tz.h"
+#include "tz.h"
+
+#define GWEATHER_I_KNOW_THIS_IS_UNSTABLE
+#include <libgweather/gweather.h>
+
+static GList *
+location_get_cities (GWeatherLocation *parent_location)
+{
+ GList *cities = NULL;
+ GWeatherLocation **children;
+ gint i;
+
+ children = gweather_location_get_children (parent_location);
+ for (i = 0; children[i]; i++) {
+ if (gweather_location_get_level (children[i]) == GWEATHER_LOCATION_CITY) {
+ cities = g_list_prepend (cities,
+ children[i]);
+ } else {
+ cities = g_list_concat (cities,
+ location_get_cities (children[i]));
+ }
+ }
+
+ 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;
+ 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);
+ timezone_id = gweather_timezone_get_tzid (gweather_location_get_timezone (l->data));
+ 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)
+{
+ GList *cities;
+ GList *tz_locations;
+ GWeatherLocation *world;
+ GWeatherLocation *country;
+
+ 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);
+
+ g_list_free (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 <kalevlember@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __WEATHER_TZ_H
+#define __WEATHER_TZ_H
+
+#include <glib.h>
+
+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..79b5f55
--- /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=@plugin_restart@
+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 <vuntz@gnome.org>
+ * Bastien Nocera <hadess@hadess.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#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 <vuntz@gnome.org>
+ * Bastien Nocera <hadess@hadess.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_DISK_SPACE_HELPER_H
+#define __GSD_DISK_SPACE_HELPER_H
+
+#include <glib.h>
+#include <gio/gunixmounts.h>
+
+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 <vuntz@gnome.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+#include <gtk/gtk.h>
+#include <libnotify/notify.h>
+#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..9d57531
--- /dev/null
+++ b/plugins/housekeeping/gsd-disk-space.c
@@ -0,0 +1,1082 @@
+/* -*- 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 <vuntz@gnome.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <sys/statvfs.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <gio/gunixmounts.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <libnotify/notify.h>
+
+#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);
+ data->ref_count = 1;
+ 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)
+{
+ data->ref_count += 1;
+ return data;
+}
+
+void
+delete_data_unref (DeleteData *data)
+{
+ data->ref_count -= 1;
+ if (data->ref_count > 0)
+ return;
+
+ 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..38c2963
--- /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 <vuntz@gnome.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_DISK_SPACE_H
+#define __GSD_DISK_SPACE_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+ gint 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 <cosimoc@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+#include <gtk/gtk.h>
+#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..8ad985c
--- /dev/null
+++ b/plugins/housekeeping/gsd-housekeeping-manager.c
@@ -0,0 +1,512 @@
+/*
+ * Copyright (C) 2008 Michael J. Chudobiak <mjc@avtechpulse.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <glib/gstdio.h>
+#include <string.h>
+#include <libnotify/notify.h>
+
+#include "gnome-settings-profile.h"
+#include "gsd-housekeeping-manager.h"
+#include "gsd-disk-space.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[] =
+"<node>"
+" <interface name='org.gnome.SettingsDaemon.Housekeeping'>"
+" <method name='EmptyTrash'/>"
+" <method name='RemoveTempFiles'/>"
+" </interface>"
+"</node>";
+
+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;
+};
+
+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 (&current_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");
+
+ 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);
+
+ 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 <mjc@avtechpulse.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_HOUSEKEEPING_MANAGER_H
+#define __GSD_HOUSEKEEPING_MANAGER_H
+
+#include <glib-object.h>
+
+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 <vuntz@gnome.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+#include <gtk/gtk.h>
+#include <libnotify/notify.h>
+#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/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..a0b4ca5
--- /dev/null
+++ b/plugins/housekeeping/meson.build
@@ -0,0 +1,41 @@
+common_files = files(
+ 'gsd-disk-space.c',
+ 'gsd-disk-space-helper.c'
+)
+
+sources = common_files + files(
+ 'gsd-housekeeping-manager.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..2feb87e
--- /dev/null
+++ b/plugins/keyboard/gsd-keyboard-manager.c
@@ -0,0 +1,718 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright © 2001 Ximian, Inc.
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ * Written by Sergey V. Oudaltsov <svu@users.sourceforge.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <locale.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#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 KEY_GTK_IM_MODULE "gtk-im-module"
+#define GTK_IM_MODULE_SIMPLE "gtk-im-context-simple"
+#define GTK_IM_MODULE_IBUS "ibus"
+
+#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 GNOME_A11Y_APPLICATIONS_INTERFACE_DIR "org.gnome.desktop.a11y.applications"
+#define KEY_OSK_ENABLED "screen-keyboard-enabled"
+
+struct _GsdKeyboardManager
+{
+ GObject parent;
+
+ guint start_idle_id;
+ GSettings *settings;
+ GSettings *input_sources_settings;
+ GSettings *a11y_settings;
+ GDBusProxy *localed;
+ GCancellable *cancellable;
+
+ GdkDeviceManager *device_manager;
+ guint device_added_id;
+ guint device_removed_id;
+};
+
+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);
+
+static void update_gtk_im_module (GsdKeyboardManager *manager);
+
+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)
+{
+ 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;
+
+ 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_error_trap_push ();
+ XChangeKeyboardControl (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ KBKeyClickPercent | KBBellPercent | KBBellPitch | KBBellDuration,
+ &kbdcontrol);
+
+ XSync (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), FALSE);
+ gdk_error_trap_pop_ignored ();
+}
+
+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
+device_added_cb (GdkDeviceManager *device_manager,
+ GdkDevice *device,
+ GsdKeyboardManager *manager)
+{
+ GdkInputSource source;
+
+ source = gdk_device_get_source (device);
+ if (source == GDK_SOURCE_TOUCHSCREEN) {
+ update_gtk_im_module (manager);
+ }
+}
+
+static void
+device_removed_cb (GdkDeviceManager *device_manager,
+ GdkDevice *device,
+ GsdKeyboardManager *manager)
+{
+ GdkInputSource source;
+
+ source = gdk_device_get_source (device);
+ if (source == GDK_SOURCE_TOUCHSCREEN)
+ update_gtk_im_module (manager);
+}
+
+static void
+set_devicepresence_handler (GsdKeyboardManager *manager)
+{
+ GdkDeviceManager *device_manager;
+
+ if (gnome_settings_is_wayland ())
+ return;
+
+ device_manager = gdk_display_get_device_manager (gdk_display_get_default ());
+
+ manager->device_added_id = g_signal_connect (G_OBJECT (device_manager), "device-added",
+ G_CALLBACK (device_added_cb), manager);
+ manager->device_removed_id = g_signal_connect (G_OBJECT (device_manager), "device-removed",
+ G_CALLBACK (device_removed_cb), manager);
+ manager->device_manager = device_manager;
+}
+
+static gboolean
+need_ibus (GVariant *sources)
+{
+ GVariantIter iter;
+ const gchar *type;
+
+ 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))
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+need_osk (GsdKeyboardManager *manager)
+{
+ gboolean has_touchscreen = FALSE;
+ GList *devices;
+ GdkSeat *seat;
+
+ if (g_settings_get_boolean (manager->a11y_settings,
+ KEY_OSK_ENABLED))
+ 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
+set_gtk_im_module (GsdKeyboardManager *manager,
+ GSettings *settings,
+ GVariant *sources)
+{
+ const gchar *new_module;
+ gchar *current_module;
+
+ if (need_ibus (sources) || need_osk (manager))
+ new_module = GTK_IM_MODULE_IBUS;
+ else
+ new_module = GTK_IM_MODULE_SIMPLE;
+
+ current_module = g_settings_get_string (settings, KEY_GTK_IM_MODULE);
+ if (!g_str_equal (current_module, new_module))
+ g_settings_set_string (settings, KEY_GTK_IM_MODULE, new_module);
+ g_free (current_module);
+}
+
+static void
+update_gtk_im_module (GsdKeyboardManager *manager)
+{
+ GSettings *interface_settings;
+ GVariant *sources;
+
+ /* Gtk+ uses the IM module advertised in XSETTINGS so, if we
+ * have IBus input sources, we want it to load that
+ * module. Otherwise we can use the default "simple" module
+ * which is builtin gtk+
+ */
+ interface_settings = g_settings_new (GNOME_DESKTOP_INTERFACE_DIR);
+ sources = g_settings_get_value (manager->input_sources_settings,
+ KEY_INPUT_SOURCES);
+ set_gtk_im_module (manager, interface_settings, sources);
+ g_object_unref (interface_settings);
+ g_variant_unref (sources);
+}
+
+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);
+
+ set_devicepresence_handler (manager);
+
+ manager->input_sources_settings = g_settings_new (GNOME_DESKTOP_INPUT_SOURCES_DIR);
+ g_signal_connect_swapped (manager->input_sources_settings,
+ "changed::" KEY_INPUT_SOURCES,
+ G_CALLBACK (update_gtk_im_module), manager);
+
+ manager->a11y_settings = g_settings_new (GNOME_A11Y_APPLICATIONS_INTERFACE_DIR);
+ g_signal_connect_swapped (manager->a11y_settings,
+ "changed::" KEY_OSK_ENABLED,
+ G_CALLBACK (update_gtk_im_module), manager);
+ update_gtk_im_module (manager);
+
+ 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->a11y_settings);
+ g_clear_object (&manager->localed);
+
+ if (manager->device_manager != NULL) {
+ g_signal_handler_disconnect (manager->device_manager, manager->device_added_id);
+ g_signal_handler_disconnect (manager->device_manager, manager->device_removed_id);
+ manager->device_manager = NULL;
+ }
+}
+
+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 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 },
+ };
+
+ 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));
+}
+
+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 <mccann@jhu.edu>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_KEYBOARD_MANAGER_H
+#define __GSD_KEYBOARD_MANAGER_H
+
+#include <glib-object.h>
+
+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/README.media-keys-API b/plugins/media-keys/README.media-keys-API
new file mode 100644
index 0000000..55d1a59
--- /dev/null
+++ b/plugins/media-keys/README.media-keys-API
@@ -0,0 +1,51 @@
+This is very simple documentation to gnome-settings-daemon's
+D-Bus API for media players.
+
+gnome-settings-daemon will send key press events from multimedia
+keys to applications that register their interest in those events.
+This allows the play/pause button to control an audio player that's
+not focused for example.
+
+The D-Bus API is described in gsd-media-keys-manager.c (look for
+introspection_xml), but a small explanation follows here.
+
+1. Create yourself a proxy object for the remote interface:
+Object path: /org/gnome/SettingsDaemon/MediaKeys
+D-Bus name: org.gnome.SettingsDaemon.MediaKeys
+Interface name: org.gnome.SettingsDaemon.MediaKeys
+
+2. Register your application with gnome-settings-daemon
+GrabMediaPlayerKeys ("my-application", 0)
+with the second argument being the current time (usually 0,
+or the time passed to you from an event, such as a mouse click)
+
+3. Listen to the MediaPlayerKeyPressed() signal
+
+4. When receiving a MediaPlayerKeyPressed() signal,
+check whether the first argument (application) matches
+the value you passed to GrabMediaPlayerKeys() and apply the
+action depending on the key (2nd argument)
+
+Possible values of key are:
+- Play
+- Pause
+- Stop
+- Previous
+- Next
+- Rewind
+- FastForward
+- Repeat
+- Shuffle
+
+5. Every time your application is focused, you should call
+GrabMediaPlayerKeys() again, so that gnome-settings-daemon knows
+which one was last used. This allows switching between a movie player
+and a music player, for example, and have the buttons control the
+last used application.
+
+6. When your application wants to stop using the functionality
+it can call ReleaseMediaPlayerKeys(). If your application does
+not call ReleaseMediaPlayerKeys() and releases its D-Bus connection
+then the application will be automatically removed from the list of
+applications held by gnome-settings-daemon.
+
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 <hadess@hadess.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#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..1ffdff4
--- /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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#include <gio/gio.h>
+#include <string.h>
+#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_slice_free (GetNameOwnerData, 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_slice_new (GetNameOwnerData);
+ 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#ifndef __BUS_WATCH_NAMESPACE_H__
+#define __BUS_WATCH_NAMESPACE_H__
+
+#include <gio/gio.h>
+
+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..42fa14d
--- /dev/null
+++ b/plugins/media-keys/gsd-media-keys-manager.c
@@ -0,0 +1,3920 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2001-2003 Bastien Nocera <hadess@hadess.net>
+ * Copyright (C) 2006-2007 William Jon McCann <mccann@jhu.edu>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <math.h>
+
+#include <locale.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <gio/gdesktopappinfo.h>
+#include <gio/gunixfdlist.h>
+
+#include <libupower-glib/upower.h>
+#include <gdesktop-enums.h>
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-systemd.h>
+
+#if HAVE_GUDEV
+#include <gudev/gudev.h>
+#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-screenshot-utils.h"
+#include "gsd-input-helper.h"
+#include "gsd-enums.h"
+#include "gsd-shell-helper.h"
+
+#include <canberra.h>
+#include <pulse/pulseaudio.h>
+#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 */
+
+static const gchar introspection_xml[] =
+"<node name='/org/gnome/SettingsDaemon/MediaKeys'>"
+" <interface name='org.gnome.SettingsDaemon.MediaKeys'>"
+" <annotation name='org.freedesktop.DBus.GLib.CSymbol' value='gsd_media_keys_manager'/>"
+" <method name='GrabMediaPlayerKeys'>"
+" <arg name='application' direction='in' type='s'/>"
+" <arg name='time' direction='in' type='u'/>"
+" </method>"
+" <method name='ReleaseMediaPlayerKeys'>"
+" <arg name='application' direction='in' type='s'/>"
+" </method>"
+" <signal name='MediaPlayerKeyPressed'>"
+" <arg name='application' type='s'/>"
+" <arg name='key' type='s'/>"
+" </signal>"
+" </interface>"
+"</node>";
+
+#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 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;
+
+ /* Screencast stuff */
+ GDBusProxy *screencast_proxy;
+ guint screencast_timeout_id;
+ gboolean screencast_recording;
+ GCancellable *screencast_cancellable;
+
+ /* Rotation */
+ guint iio_sensor_watch_id;
+ gboolean has_accel;
+ GDBusProxy *iio_sensor_proxy;
+
+ /* RFKill stuff */
+ guint rfkill_watch_id;
+ GDBusProxy *rfkill_proxy;
+ GCancellable *rfkill_cancellable;
+
+ /* systemd stuff */
+ GDBusProxy *logind_proxy;
+ gint inhibit_keys_fd;
+ gint inhibit_suspend_fd;
+ gboolean inhibit_suspend_taken;
+
+ GList *media_players;
+
+ GDBusNodeInfo *introspection_data;
+ GDBusConnection *connection;
+ GCancellable *bus_cancellable;
+
+ guint start_idle_id;
+
+ /* Multimedia keys */
+ guint mmkeys_name_id;
+ 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;
+
+ /* not needed here */
+ if (g_str_equal (settings_key, "max-screencast-length"))
+ 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, "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 void
+free_media_player (MediaPlayer *player)
+{
+ if (player->watch_id > 0) {
+ g_bus_unwatch_name (player->watch_id);
+ player->watch_id = 0;
+ }
+ g_free (player->application);
+ g_free (player->dbus_name);
+ g_free (player);
+}
+
+static gint
+find_by_application (gconstpointer a,
+ gconstpointer b)
+{
+ return strcmp (((MediaPlayer *)a)->application, b);
+}
+
+static gint
+find_by_name (gconstpointer a,
+ gconstpointer b)
+{
+ return strcmp (((MediaPlayer *)a)->dbus_name, b);
+}
+
+static gint
+find_by_time (gconstpointer a,
+ gconstpointer b)
+{
+ return ((MediaPlayer *)a)->time < ((MediaPlayer *)b)->time;
+}
+
+static void
+name_vanished_handler (GDBusConnection *connection,
+ const gchar *name,
+ GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GList *iter;
+
+ iter = g_list_find_custom (priv->media_players,
+ name,
+ find_by_name);
+
+ if (iter != NULL) {
+ MediaPlayer *player;
+
+ player = iter->data;
+ g_debug ("Deregistering vanished %s (dbus_name: %s)", player->application, player->dbus_name);
+ free_media_player (player);
+ priv->media_players = g_list_delete_link (priv->media_players, iter);
+ }
+}
+
+/*
+ * Register a new media player. Most applications will want to call
+ * this with time = GDK_CURRENT_TIME. This way, the last registered
+ * player will receive media events. In some cases, applications
+ * may want to register with a lower priority (usually 1), to grab
+ * events only nobody else is interested in.
+ */
+static void
+gsd_media_keys_manager_grab_media_player_keys (GsdMediaKeysManager *manager,
+ const char *application,
+ const char *dbus_name,
+ guint32 time)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GList *iter;
+ MediaPlayer *media_player;
+ guint watch_id;
+
+ if (time == GDK_CURRENT_TIME) {
+ GTimeVal tv;
+
+ g_get_current_time (&tv);
+ time = tv.tv_sec * 1000 + tv.tv_usec / 1000;
+ }
+
+ iter = g_list_find_custom (priv->media_players,
+ application,
+ find_by_application);
+
+ if (iter != NULL) {
+ if (((MediaPlayer *)iter->data)->time < time) {
+ MediaPlayer *player = iter->data;
+ free_media_player (player);
+ priv->media_players = g_list_delete_link (priv->media_players, iter);
+ } else {
+ return;
+ }
+ }
+
+ watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ dbus_name,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ NULL,
+ (GBusNameVanishedCallback) name_vanished_handler,
+ manager,
+ NULL);
+
+ g_debug ("Registering %s at %u", application, time);
+ media_player = g_new0 (MediaPlayer, 1);
+ media_player->application = g_strdup (application);
+ media_player->dbus_name = g_strdup (dbus_name);
+ media_player->time = time;
+ media_player->watch_id = watch_id;
+
+ priv->media_players = g_list_insert_sorted (priv->media_players,
+ media_player,
+ find_by_time);
+}
+
+static void
+gsd_media_keys_manager_release_media_player_keys (GsdMediaKeysManager *manager,
+ const char *application,
+ const char *name)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GList *iter = NULL;
+
+ g_return_if_fail (application != NULL || name != NULL);
+
+ if (application != NULL) {
+ iter = g_list_find_custom (priv->media_players,
+ application,
+ find_by_application);
+ }
+
+ if (iter == NULL && name != NULL) {
+ iter = g_list_find_custom (priv->media_players,
+ name,
+ find_by_name);
+ }
+
+ if (iter != NULL) {
+ MediaPlayer *player;
+
+ player = iter->data;
+ g_debug ("Deregistering %s (dbus_name: %s)", application, player->dbus_name);
+ free_media_player (player);
+ priv->media_players = g_list_delete_link (priv->media_players, iter);
+ }
+}
+
+static gboolean
+gsd_media_player_key_pressed (GsdMediaKeysManager *manager,
+ const char *key)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ const char *application;
+ gboolean have_listeners;
+ GError *error = NULL;
+ MediaPlayer *player;
+
+ g_return_val_if_fail (key != NULL, FALSE);
+
+ g_debug ("Media key '%s' pressed", key);
+
+ /* Prefer MPRIS players to those using the native API */
+ if (mpris_controller_get_has_active_player (priv->mpris_controller)) {
+ if (mpris_controller_key (priv->mpris_controller, key))
+ return TRUE;
+ }
+
+ have_listeners = (priv->media_players != NULL);
+
+ if (!have_listeners) {
+ /* Popup a dialog with an (/) icon */
+ show_osd (manager, "action-unavailable-symbolic", NULL, -1, NULL);
+ return TRUE;
+ }
+
+ player = priv->media_players->data;
+ application = player->application;
+
+ if (g_dbus_connection_emit_signal (priv->connection,
+ player->dbus_name,
+ GSD_MEDIA_KEYS_DBUS_PATH,
+ GSD_MEDIA_KEYS_DBUS_NAME,
+ "MediaPlayerKeyPressed",
+ g_variant_new ("(ss)", application ? application : "", key),
+ &error) == FALSE) {
+ g_debug ("Error emitting signal: %s", error->message);
+ g_error_free (error);
+ }
+
+ return !have_listeners;
+}
+
+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)
+{
+ GsdMediaKeysManager *manager = (GsdMediaKeysManager *) user_data;
+
+ g_debug ("Calling method '%s' for media-keys", method_name);
+
+ if (g_strcmp0 (method_name, "ReleaseMediaPlayerKeys") == 0) {
+ const char *app_name;
+
+ g_variant_get (parameters, "(&s)", &app_name);
+ gsd_media_keys_manager_release_media_player_keys (manager, app_name, sender);
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ } else if (g_strcmp0 (method_name, "GrabMediaPlayerKeys") == 0) {
+ const char *app_name;
+ guint32 time;
+
+ g_variant_get (parameters, "(&su)", &app_name, &time);
+ gsd_media_keys_manager_grab_media_player_keys (manager, app_name, sender, time);
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ }
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+ handle_method_call,
+ NULL, /* Get Property */
+ NULL, /* Set Property */
+};
+
+static gboolean
+do_multimedia_player_action (GsdMediaKeysManager *manager,
+ const char *key)
+{
+ return gsd_media_player_key_pressed (manager, key);
+}
+
+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;
+ 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;
+ RfkillData *data;
+
+ has_mode = bluetooth ? "BluetoothHasAirplaneMode" : "HasAirplaneMode";
+ hw_mode = bluetooth ? "BluetoothHardwareAirplaneMode" : "HardwareAirplaneMode";
+ mode = bluetooth ? "BluetoothAirplaneMode" : "AirplaneMode";
+
+ if (priv->rfkill_proxy == NULL)
+ return;
+
+ 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
+screencast_stop (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ if (priv->screencast_timeout_id > 0) {
+ g_source_remove (priv->screencast_timeout_id);
+ priv->screencast_timeout_id = 0;
+ }
+
+ g_dbus_proxy_call (priv->screencast_proxy,
+ "StopScreencast", NULL,
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ priv->screencast_cancellable,
+ NULL, NULL);
+
+ priv->screencast_recording = FALSE;
+}
+
+static gboolean
+screencast_timeout (gpointer user_data)
+{
+ GsdMediaKeysManager *manager = user_data;
+ screencast_stop (manager);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+screencast_start (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ guint max_length;
+ g_dbus_proxy_call (priv->screencast_proxy,
+ "Screencast",
+ g_variant_new_parsed ("(%s, @a{sv} {})",
+ /* Translators: this is a filename used for screencast
+ * recording, where "%d" and "%t" date and time, e.g.
+ * "Screencast from 07-17-2013 10:00:46 PM.webm" */
+ /* xgettext:no-c-format */
+ _("Screencast from %d %t.webm")),
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ priv->screencast_cancellable,
+ NULL, NULL);
+
+ max_length = g_settings_get_uint (priv->settings, "max-screencast-length");
+
+ if (max_length > 0) {
+ priv->screencast_timeout_id = g_timeout_add_seconds (max_length,
+ screencast_timeout,
+ manager);
+ g_source_set_name_by_id (priv->screencast_timeout_id, "[gnome-settings-daemon] screencast_timeout");
+ }
+ priv->screencast_recording = TRUE;
+}
+
+static void
+do_screencast_action (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ if (priv->screencast_proxy == NULL)
+ return;
+
+ if (!priv->screencast_recording)
+ screencast_start (manager);
+ else
+ screencast_stop (manager);
+}
+
+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 SCREENSHOT_KEY:
+ case SCREENSHOT_CLIP_KEY:
+ case WINDOW_SCREENSHOT_KEY:
+ case WINDOW_SCREENSHOT_CLIP_KEY:
+ case AREA_SCREENSHOT_KEY:
+ case AREA_SCREENSHOT_CLIP_KEY:
+ gsd_screenshot_take (type);
+ break;
+ case SCREENCAST_KEY:
+ do_screencast_action (manager);
+ 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, "gnome-control-center.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", &timestamp))
+ 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_screencast_proxy_ready (GObject *source,
+ GAsyncResult *result,
+ gpointer data)
+{
+ GsdMediaKeysManager *manager = data;
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GError *error = NULL;
+
+ priv->screencast_proxy =
+ g_dbus_proxy_new_for_bus_finish (result, &error);
+
+ if (!priv->screencast_proxy) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to create proxy for screencast: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+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);
+ g_clear_object (&priv->screencast_proxy);
+
+ 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->screencast_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);
+
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+ 0, NULL,
+ SHELL_DBUS_NAME ".Screencast",
+ SHELL_DBUS_PATH "/Screencast",
+ SHELL_DBUS_NAME ".Screencast",
+ priv->screencast_cancellable,
+ on_screencast_proxy_ready, 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 },
+ { "screenshot", "screenshot", map_keybinding },
+ { "window-screenshot", "window-screenshot", map_keybinding },
+ { "area-screenshot", "area-screenshot", map_keybinding },
+ { "screenshot-clip", "screenshot-clip", map_keybinding },
+ { "window-screenshot-clip", "window-screenshot-clip", map_keybinding },
+ { "area-screenshot-clip", "area-screenshot-clip", map_keybinding },
+ { "screencast", "screencast", 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->mmkeys_name_id > 0) {
+ g_bus_unown_name (priv->mmkeys_name_id);
+ priv->mmkeys_name_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->screencast_proxy);
+ g_clear_object (&priv->iio_sensor_proxy);
+ g_clear_pointer (&priv->chassis_type, g_free);
+
+ g_clear_pointer (&priv->introspection_data, g_dbus_node_info_unref);
+ 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->screencast_cancellable != NULL) {
+ g_cancellable_cancel (priv->screencast_cancellable);
+ g_clear_object (&priv->screencast_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);
+
+ if (priv->media_players != NULL) {
+ g_list_free_full (priv->media_players, (GDestroyNotify) free_media_player);
+ priv->media_players = NULL;
+ }
+
+ 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_connection_register_object (connection,
+ GSD_MEDIA_KEYS_DBUS_PATH,
+ priv->introspection_data->interfaces[0],
+ &interface_vtable,
+ manager,
+ NULL,
+ NULL);
+
+ priv->mmkeys_name_id = g_bus_own_name_on_connection (priv->connection,
+ "org.gnome.SettingsDaemon.MediaKeys",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ NULL, NULL, NULL, NULL);
+
+ 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->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+ priv->bus_cancellable = g_cancellable_new ();
+ g_assert (priv->introspection_data != NULL);
+
+ 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 <mccann@jhu.edu>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_MEDIA_KEYS_MANAGER_H
+#define __GSD_MEDIA_KEYS_MANAGER_H
+
+#include <glib-object.h>
+
+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/gsd-screenshot-utils.c b/plugins/media-keys/gsd-screenshot-utils.c
new file mode 100644
index 0000000..17ea32d
--- /dev/null
+++ b/plugins/media-keys/gsd-screenshot-utils.c
@@ -0,0 +1,310 @@
+/* gsd-screenshot-utils.c - utilities to take screenshots
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * Adapted from gnome-screenshot code, which is
+ * Copyright (C) 2001-2006 Jonathan Blandford <jrb@alum.mit.edu>
+ * Copyright (C) 2006 Emmanuele Bassi <ebassi@gnome.org>
+ * Copyright (C) 2008-2012 Cosimo Cecchi <cosimoc@gnome.org>
+ *
+ * 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,
+ */
+
+#include <config.h>
+#include <canberra-gtk.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+#include <string.h>
+#include <glib/gstdio.h>
+
+#include "gsd-screenshot-utils.h"
+
+#define SHELL_SCREENSHOT_BUS_NAME "org.gnome.Shell"
+#define SHELL_SCREENSHOT_BUS_PATH "/org/gnome/Shell/Screenshot"
+#define SHELL_SCREENSHOT_BUS_IFACE "org.gnome.Shell.Screenshot"
+
+typedef enum {
+ SCREENSHOT_TYPE_SCREEN,
+ SCREENSHOT_TYPE_WINDOW,
+ SCREENSHOT_TYPE_AREA
+} ScreenshotType;
+
+typedef struct {
+ ScreenshotType type;
+ gboolean copy_to_clipboard;
+
+ GdkRectangle area_selection;
+ gchar *save_filename;
+ gchar *used_filename;
+
+ GDBusConnection *connection;
+} ScreenshotContext;
+
+static void
+screenshot_play_sound_effect (const gchar *event_id,
+ const gchar *event_desc)
+{
+ ca_context *c;
+
+ c = ca_gtk_context_get ();
+ ca_context_play (c, 0,
+ CA_PROP_EVENT_ID, event_id,
+ CA_PROP_EVENT_DESCRIPTION, event_desc,
+ CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
+ NULL);
+}
+
+static void
+screenshot_context_free (ScreenshotContext *ctx)
+{
+ g_free (ctx->save_filename);
+ g_free (ctx->used_filename);
+ g_clear_object (&ctx->connection);
+ g_slice_free (ScreenshotContext, ctx);
+}
+
+static void
+screenshot_play_error_sound_effect (void)
+{
+ screenshot_play_sound_effect ("dialog-error", _("Unable to capture a screenshot"));
+}
+
+static void
+screenshot_save_to_recent (ScreenshotContext *ctx)
+{
+ GFile *file = g_file_new_for_path (ctx->used_filename);
+ gchar *uri = g_file_get_uri (file);
+
+ gtk_recent_manager_add_item (gtk_recent_manager_get_default (), uri);
+
+ g_free (uri);
+ g_object_unref (file);
+}
+
+static void
+bus_call_ready_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ ScreenshotContext *ctx = user_data;
+ GVariant *variant;
+ gboolean success;
+
+ variant = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), res, &error);
+
+ if (error != NULL)
+ {
+ screenshot_play_error_sound_effect ();
+ g_warning ("Failed to save a screenshot: %s\n", error->message);
+ g_error_free (error);
+ screenshot_context_free (ctx);
+
+ return;
+ }
+
+ g_variant_get (variant, "(bs)", &success, &ctx->used_filename);
+
+ if (success)
+ {
+ if (!ctx->copy_to_clipboard)
+ {
+ screenshot_play_sound_effect ("screen-capture", _("Screenshot taken"));
+ screenshot_save_to_recent (ctx);
+ }
+ }
+
+ screenshot_context_free (ctx);
+ g_variant_unref (variant);
+}
+
+static void
+screenshot_call_shell (ScreenshotContext *ctx)
+{
+ const gchar *method_name;
+ GVariant *method_params;
+
+ if (ctx->type == SCREENSHOT_TYPE_SCREEN)
+ {
+ method_name = "Screenshot";
+ method_params = g_variant_new ("(bbs)",
+ FALSE, /* include pointer */
+ TRUE, /* flash */
+ ctx->save_filename);
+ }
+ else if (ctx->type == SCREENSHOT_TYPE_WINDOW)
+ {
+ method_name = "ScreenshotWindow";
+ method_params = g_variant_new ("(bbbs)",
+ TRUE, /* include border */
+ FALSE, /* include pointer */
+ TRUE, /* flash */
+ ctx->save_filename);
+ }
+ else
+ {
+ method_name = "ScreenshotArea";
+ method_params = g_variant_new ("(iiiibs)",
+ ctx->area_selection.x, ctx->area_selection.y,
+ ctx->area_selection.width, ctx->area_selection.height,
+ TRUE, /* flash */
+ ctx->save_filename);
+ }
+
+ g_dbus_connection_call (ctx->connection,
+ SHELL_SCREENSHOT_BUS_NAME,
+ SHELL_SCREENSHOT_BUS_PATH,
+ SHELL_SCREENSHOT_BUS_IFACE,
+ method_name,
+ method_params,
+ NULL,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ -1,
+ NULL,
+ bus_call_ready_cb,
+ ctx);
+}
+
+static void
+area_selection_ready_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GdkRectangle rectangle;
+ ScreenshotContext *ctx = user_data;
+ GVariant *geometry;
+
+ geometry = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source),
+ res, NULL);
+
+ /* cancelled by the user */
+ if (!geometry)
+ {
+ screenshot_context_free (ctx);
+ return;
+ }
+
+ g_variant_get (geometry, "(iiii)",
+ &rectangle.x, &rectangle.y,
+ &rectangle.width, &rectangle.height);
+
+ ctx->area_selection = rectangle;
+ screenshot_call_shell (ctx);
+ g_variant_unref (geometry);
+}
+
+static void
+bus_connection_ready_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ ScreenshotContext *ctx = user_data;
+
+ ctx->connection = g_bus_get_finish (res, &error);
+
+ if (error != NULL)
+ {
+ screenshot_play_error_sound_effect ();
+ g_warning ("Failed to save a screenshot: %s\n", error->message);
+ g_error_free (error);
+ screenshot_context_free (ctx);
+
+ return;
+ }
+
+ if (ctx->type == SCREENSHOT_TYPE_AREA)
+ g_dbus_connection_call (ctx->connection,
+ SHELL_SCREENSHOT_BUS_NAME,
+ SHELL_SCREENSHOT_BUS_PATH,
+ SHELL_SCREENSHOT_BUS_IFACE,
+ "SelectArea",
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ -1,
+ NULL,
+ area_selection_ready_cb,
+ ctx);
+ else
+ screenshot_call_shell (ctx);
+}
+
+static void
+screenshot_take (ScreenshotContext *ctx)
+{
+ g_bus_get (G_BUS_TYPE_SESSION, NULL, bus_connection_ready_cb, ctx);
+}
+
+static gchar *
+screenshot_build_filename (void)
+{
+ char *file_name, *origin;
+ GDateTime *d;
+
+ d = g_date_time_new_now_local ();
+ origin = g_date_time_format (d, "%Y-%m-%d %H-%M-%S");
+ g_date_time_unref (d);
+
+ /* translators: this is the name of the file that gets made up
+ * with the screenshot */
+ file_name = g_strdup_printf (_("Screenshot from %s"), origin);
+ g_free (origin);
+
+ return file_name;
+}
+
+static void
+screenshot_check_name_ready (ScreenshotContext *ctx)
+{
+ if (ctx->copy_to_clipboard)
+ ctx->save_filename = g_strdup ("");
+ else
+ ctx->save_filename = screenshot_build_filename ();
+
+ screenshot_take (ctx);
+}
+
+void
+gsd_screenshot_take (MediaKeyType key_type)
+{
+ ScreenshotContext *ctx = g_slice_new0 (ScreenshotContext);
+
+ ctx->copy_to_clipboard = (key_type == SCREENSHOT_CLIP_KEY ||
+ key_type == WINDOW_SCREENSHOT_CLIP_KEY ||
+ key_type == AREA_SCREENSHOT_CLIP_KEY);
+
+ switch (key_type)
+ {
+ case SCREENSHOT_KEY:
+ case SCREENSHOT_CLIP_KEY:
+ ctx->type = SCREENSHOT_TYPE_SCREEN;
+ break;
+ case WINDOW_SCREENSHOT_KEY:
+ case WINDOW_SCREENSHOT_CLIP_KEY:
+ ctx->type = SCREENSHOT_TYPE_WINDOW;
+ break;
+ case AREA_SCREENSHOT_KEY:
+ case AREA_SCREENSHOT_CLIP_KEY:
+ ctx->type = SCREENSHOT_TYPE_AREA;
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ screenshot_check_name_ready (ctx);
+}
diff --git a/plugins/media-keys/gsd-screenshot-utils.h b/plugins/media-keys/gsd-screenshot-utils.h
new file mode 100644
index 0000000..39ae136
--- /dev/null
+++ b/plugins/media-keys/gsd-screenshot-utils.h
@@ -0,0 +1,36 @@
+/* gsd-screenshot-utils.h - utilities to take screenshots
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * Adapted from gnome-screenshot code, which is
+ * Copyright (C) 2001-2006 Jonathan Blandford <jrb@alum.mit.edu>
+ * Copyright (C) 2006 Emmanuele Bassi <ebassi@gnome.org>
+ * Copyright (C) 2008-2012 Cosimo Cecchi <cosimoc@gnome.org>
+ *
+ * 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,
+ */
+
+#ifndef __GSD_SCREENSHOT_UTILS_H__
+#define __GSD_SCREENSHOT_UTILS_H__
+
+#include "media-keys.h"
+
+G_BEGIN_DECLS
+
+void gsd_screenshot_take (MediaKeyType key_type);
+
+G_END_DECLS
+
+#endif /* __GSD_SCREENSHOT_UTILS_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..cc4ea8e
--- /dev/null
+++ b/plugins/media-keys/media-keys.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2001 Bastien Nocera <hadess@hadess.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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,
+ SCREENSHOT_KEY,
+ WINDOW_SCREENSHOT_KEY,
+ AREA_SCREENSHOT_KEY,
+ SCREENSHOT_CLIP_KEY,
+ WINDOW_SCREENSHOT_CLIP_KEY,
+ AREA_SCREENSHOT_CLIP_KEY,
+ SCREENCAST_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..cbda8ee
--- /dev/null
+++ b/plugins/media-keys/meson.build
@@ -0,0 +1,59 @@
+sources = files(
+ 'bus-watch-namespace.c',
+ 'gsd-media-keys-manager.c',
+ 'gsd-screenshot-utils.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..c70f0bc
--- /dev/null
+++ b/plugins/media-keys/mpris-controller.c
@@ -0,0 +1,326 @@
+/*
+ * 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 <http://www.gnu.org/licenses>
+ *
+ * Author: Michael Wood <michael.g.wood@intel.com>
+ */
+
+#include "mpris-controller.h"
+#include "bus-watch-namespace.h"
+#include <gio/gio.h>
+
+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)
+{
+ 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;
+}
+
+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..a0044c4
--- /dev/null
+++ b/plugins/media-keys/mpris-controller.h
@@ -0,0 +1,36 @@
+/*
+ * 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 <http://www.gnu.org/licenses>
+ *
+ * Author: Michael Wood <michael.g.wood@intel.com>
+ */
+
+#ifndef __MPRIS_CONTROLLER_H__
+#define __MPRIS_CONTROLLER_H__
+
+#include <glib-object.h>
+
+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_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 @@
+<node>
+ <interface name="org.gnome.Shell">
+ <annotation name="org.gtk.GDBus.C.Name" value="KeyGrabber"/>
+ <method name="GrabAccelerator">
+ <arg type="s" direction="in" name="accelerator"/>
+ <arg type="u" direction="in" name="modeFlags"/>
+ <arg type="u" direction="in" name="grabFlags"/>
+ <arg type="u" direction="out" name="action"/>
+ </method>
+ <method name="GrabAccelerators">
+ <arg type="a(suu)" direction="in" name="accelerator"/>
+ <arg type="au" direction="out" name="action"/>
+ </method>
+ <method name="UngrabAccelerator">
+ <arg type="u" direction="in" name="action"/>
+ <arg type="b" direction="out" name="success"/>
+ </method>
+ <method name="UngrabAccelerators">
+ <arg type="au" direction="in" name="action"/>
+ <arg type="b" direction="out" name="success"/>
+ </method>
+ <signal name="AcceleratorActivated">
+ <arg type="u" name="action"/>
+ <arg type="a{sv}" name="parameters"/>
+ </signal>
+ </interface>
+</node>
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..8ced72d
--- /dev/null
+++ b/plugins/media-keys/shortcuts-list.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2001 Bastien Nocera <hadess@hadess.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 },
+ { SCREENSHOT_KEY, "screenshot", FALSE, NO_LOCK_MODE, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+ { WINDOW_SCREENSHOT_KEY, "window-screenshot", FALSE, NO_LOCK_MODE, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+ { AREA_SCREENSHOT_KEY, "area-screenshot", FALSE, NO_LOCK_MODE, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+ { SCREENSHOT_CLIP_KEY, "screenshot-clip", FALSE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+ { WINDOW_SCREENSHOT_CLIP_KEY, "window-screenshot-clip", FALSE, SHELL_ACTION_MODE_NORMAL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+ { AREA_SCREENSHOT_CLIP_KEY, "area-screenshot-clip", FALSE, SHELL_ACTION_MODE_ALL, META_KEY_BINDING_IGNORE_AUTOREPEAT },
+ { SCREENCAST_KEY, "screencast", FALSE, NO_LOCK_MODE, 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..83e0188
--- /dev/null
+++ b/plugins/meson.build
@@ -0,0 +1,194 @@
+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']],
+}
+
+# Restart=on-failure is the default
+plugin_restart_rule = {
+ 'xsettings' : 'on-abnormal',
+}
+
+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: true,
+ 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)
+ unit_conf.set('plugin_restart', plugin_restart_rule.get(plugin_name, 'on-failure'))
+
+ 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: true,
+ install_dir: systemd_userunitdir
+ )
+ configure_file(
+ input: 'gsd.target.in',
+ output: user_target,
+ configuration: unit_conf,
+ install: true,
+ 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..04249c8
--- /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=/bin/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 <richard@hughsie.com>
+ *
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <sys/wait.h>
+#include <math.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkx.h>
+#include <X11/extensions/dpms.h>
+#include <canberra-gtk.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-rr.h>
+
+#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 ()),
+ &current_server_timeout,
+ &current_server_interval,
+ &current_prefer_blank,
+ &current_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 <richard@hughsie.com>
+ *
+ * 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 <glib.h>
+#include <libupower-glib/upower.h>
+
+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..c370c79
--- /dev/null
+++ b/plugins/power/gsd-backlight-helper.c
@@ -0,0 +1,153 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2018 Benjamin Berg <bberg@redhat.com>
+ *
+ * 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 <stdlib.h>
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#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;
+ }
+
+ device = realpath (argv[1], NULL);
+ if (device == NULL) {
+ fprintf (stderr, "Error: Could not canonicalize given path (%d: %s)\n", errno, strerror (errno));
+ result = GSD_BACKLIGHT_HELPER_EXIT_CODE_FAILED;
+ 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;
+ }
+
+ 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 (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+#include <stdlib.h>
+
+#include "gsd-backlight.h"
+#include "gpm-common.h"
+#include "gsd-power-constants.h"
+#include "gsd-power-manager.h"
+
+#ifdef __linux__
+#include <gudev/gudev.h>
+#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 <bberg@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _GSD_BACKLIGHT_H
+#define _GSD_BACKLIGHT_H
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-rr.h>
+
+
+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 <simos at gnome dot org>.
+# Author : Bastien Nocera <hadess@hadess.net>
+# 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 <input> <output>\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<<EOF;
+
+# 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/.
+
+EOF
+
+while (<IN_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..91e2296
--- /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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* 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 4.0/5.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 <glib-object.h>
+#include <gsd-power-enums.h>
+#include <stdio.h>
+
+/* 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 <glib-object.h>
+
+/*** 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 <glib-object.h>
+
+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..c93c620
--- /dev/null
+++ b/plugins/power/gsd-power-manager.c
@@ -0,0 +1,3270 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ * Copyright (C) 2011-2012, 2015 Richard Hughes <richard@hughsie.com>
+ * Copyright (C) 2011 Ritesh Khadgaray <khadgaray@gmail.com>
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <libupower-glib/upower.h>
+#include <libnotify/notify.h>
+#include <canberra-gtk.h>
+#include <glib-unix.h>
+#include <gio/gunixfdlist.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-rr.h>
+#include <libgnome-desktop/gnome-idle-monitor.h>
+
+#include <gsd-input-helper.h>
+
+#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 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[] =
+"<node>"
+" <interface name='org.gnome.SettingsDaemon.Power.Screen'>"
+" <property name='Brightness' type='i' access='readwrite'/>"
+" <method name='StepUp'>"
+" <arg type='i' name='new_percentage' direction='out'/>"
+" <arg type='s' name='connector' direction='out'/>"
+" </method>"
+" <method name='StepDown'>"
+" <arg type='i' name='new_percentage' direction='out'/>"
+" <arg type='s' name='connector' direction='out'/>"
+" </method>"
+" <method name='Cycle'>"
+" <arg type='i' name='new_percentage' direction='out'/>"
+" <arg type='i' name='output_id' direction='out'/>"
+" </method>"
+" </interface>"
+" <interface name='org.gnome.SettingsDaemon.Power.Keyboard'>"
+" <property name='Brightness' type='i' access='readwrite'/>"
+" <method name='StepUp'>"
+" <arg type='i' name='new_percentage' direction='out'/>"
+" </method>"
+" <method name='StepDown'>"
+" <arg type='i' name='new_percentage' direction='out'/>"
+" </method>"
+" <method name='Toggle'>"
+" <arg type='i' name='new_percentage' direction='out'/>"
+" </method>"
+" <signal name='BrightnessChanged'>"
+" <arg name='brightness' type='i'/>"
+" <arg name='source' type='s'/>"
+" </signal>"
+" </interface>"
+"</node>";
+
+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; /* laptop 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;
+
+ /* 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)
+ g_hash_table_insert (manager->devices_notified_ht,
+ g_strdup (serial),
+ GINT_TO_POINTER (warning));
+
+ return ret;
+}
+
+static void
+engine_charge_low (GsdPowerManager *manager, UpDevice *device)
+{
+ const gchar *title = NULL;
+ gboolean ret;
+ 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) {
+
+ /* if the user has no other batteries, drop the "Laptop" wording */
+ ret = (manager->devices_array->len > 0);
+ if (ret) {
+ /* TRANSLATORS: laptop battery low, and we only have one battery */
+ title = _("Battery low");
+ } else {
+ /* TRANSLATORS: laptop battery low, and we have more than one kind of battery */
+ title = _("Laptop battery low");
+ }
+ tmp = gpm_get_timestring (time_to_empty);
+ remaining_text = g_strconcat ("<b>", tmp, "</b>", NULL);
+ g_free (tmp);
+
+ /* TRANSLATORS: tell the user how much time they have got */
+ message = g_strdup_printf (_("Approximately %s remaining (%.0f%%)"), remaining_text, percentage);
+ g_free (remaining_text);
+
+ } else if (kind == UP_DEVICE_KIND_UPS) {
+ /* TRANSLATORS: UPS is starting to get a little low */
+ title = _("UPS low");
+ tmp = gpm_get_timestring (time_to_empty);
+ remaining_text = g_strconcat ("<b>", tmp, "</b>", NULL);
+ g_free (tmp);
+
+ /* TRANSLATORS: tell the user how much time they have got */
+ message = g_strdup_printf (_("Approximately %s of remaining UPS backup power (%.0f%%)"),
+ remaining_text, percentage);
+ g_free (remaining_text);
+ } else if (kind == UP_DEVICE_KIND_MOUSE) {
+ /* TRANSLATORS: mouse is getting a little low */
+ title = _("Mouse battery low");
+
+ /* TRANSLATORS: tell user more details */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Wireless mouse is low in power (%.0f%%)"), percentage);
+ else
+ message = g_strdup_printf (_("Wireless mouse is low in power"));
+
+ } else if (kind == UP_DEVICE_KIND_KEYBOARD) {
+ /* TRANSLATORS: keyboard is getting a little low */
+ title = _("Keyboard battery low");
+
+ /* TRANSLATORS: tell user more details */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Wireless keyboard is low in power (%.0f%%)"), percentage);
+ else
+ message = g_strdup_printf (_("Wireless keyboard is low in power"));
+
+ } else if (kind == UP_DEVICE_KIND_PDA) {
+ /* TRANSLATORS: PDA is getting a little low */
+ title = _("PDA battery low");
+
+ /* TRANSLATORS: tell user more details */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("PDA is low in power (%.0f%%)"), percentage);
+ else
+ message = g_strdup_printf (_("PDA is low in power"));
+
+ } else if (kind == UP_DEVICE_KIND_PHONE) {
+ /* TRANSLATORS: cell phone (mobile) is getting a little low */
+ title = _("Cell phone battery low");
+
+ /* TRANSLATORS: tell user more details */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Cell phone is low in power (%.0f%%)"), percentage);
+ else
+ message = g_strdup_printf (_("Cell phone is low in power"));
+
+ } else if (kind == UP_DEVICE_KIND_MEDIA_PLAYER) {
+ /* TRANSLATORS: media player, e.g. mp3 is getting a little low */
+ title = _("Media player battery low");
+
+ /* TRANSLATORS: tell user more details */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Media player is low in power (%.0f%%)"), percentage);
+ else
+ message = g_strdup_printf (_("Media player is low in power"));
+
+ } else if (kind == UP_DEVICE_KIND_TABLET) {
+ /* TRANSLATORS: graphics tablet, e.g. wacom is getting a little low */
+ title = _("Tablet battery low");
+
+ /* TRANSLATORS: tell user more details */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Tablet is low in power (%.0f%%)"), percentage);
+ else
+ message = g_strdup_printf (_("Tablet is low in power"));
+
+ } else if (kind == UP_DEVICE_KIND_COMPUTER) {
+ /* TRANSLATORS: computer, e.g. ipad is getting a little low */
+ title = _("Attached computer battery low");
+
+ /* TRANSLATORS: tell user more details */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Attached computer is low in power (%.0f%%)"), percentage);
+ else
+ message = g_strdup_printf (_("Attached computer is low in power"));
+ }
+
+ /* 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;
+ gboolean ret;
+ 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) {
+
+ /* if the user has no other batteries, drop the "Laptop" wording */
+ ret = (manager->devices_array->len > 0);
+ if (ret) {
+ /* TRANSLATORS: laptop battery critically low, and only have one kind of battery */
+ title = _("Battery critically low");
+ } else {
+ /* TRANSLATORS: laptop battery critically low, and we have more than one type of battery */
+ title = _("Laptop battery critically low");
+ }
+
+ /* we have to do different warnings depending on the policy */
+ policy = manager_critical_action_get (manager);
+
+ /* use different text for different actions */
+ if (policy == GSD_POWER_ACTION_HIBERNATE) {
+ /* TRANSLATORS: give the user a ultimatum */
+ message = g_strdup_printf (_("Computer will hibernate very soon unless it is plugged in."));
+
+ } else if (policy == GSD_POWER_ACTION_SHUTDOWN) {
+ /* TRANSLATORS: give the user a ultimatum */
+ message = g_strdup_printf (_("Computer will shutdown very soon unless it is plugged in."));
+ }
+
+ } else if (kind == UP_DEVICE_KIND_UPS) {
+ gchar *remaining_text;
+ gchar *tmp;
+
+ /* TRANSLATORS: the UPS is very low */
+ title = _("UPS critically low");
+ tmp = gpm_get_timestring (time_to_empty);
+ remaining_text = g_strconcat ("<b>", tmp, "</b>", NULL);
+ g_free (tmp);
+
+ /* TRANSLATORS: give the user a ultimatum */
+ 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 if (kind == UP_DEVICE_KIND_MOUSE) {
+ /* TRANSLATORS: the mouse battery is very low */
+ title = _("Mouse battery low");
+
+ /* TRANSLATORS: the device is just going to stop working */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Wireless mouse is very low in power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ percentage);
+ else
+ message = g_strdup_printf (_("Wireless mouse is very low in power. "
+ "This device will soon stop functioning if not charged."));
+
+ } else if (kind == UP_DEVICE_KIND_KEYBOARD) {
+ /* TRANSLATORS: the keyboard battery is very low */
+ title = _("Keyboard battery low");
+
+ /* TRANSLATORS: the device is just going to stop working */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Wireless keyboard is very low in power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ percentage);
+ else
+ message = g_strdup_printf (_("Wireless keyboard is very low in power. "
+ "This device will soon stop functioning if not charged."));
+
+ } else if (kind == UP_DEVICE_KIND_PDA) {
+
+ /* TRANSLATORS: the PDA battery is very low */
+ title = _("PDA battery low");
+
+ /* TRANSLATORS: the device is just going to stop working */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("PDA is very low in power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ percentage);
+ else
+ message = g_strdup_printf (_("PDA is very low in power. "
+ "This device will soon stop functioning if not charged."));
+
+ } else if (kind == UP_DEVICE_KIND_PHONE) {
+
+ /* TRANSLATORS: the cell battery is very low */
+ title = _("Cell phone battery low");
+
+ /* TRANSLATORS: the device is just going to stop working */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Cell phone is very low in power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ percentage);
+ else
+ message = g_strdup_printf (_("Cell phone is very low in power. "
+ "This device will soon stop functioning if not charged."));
+
+ } else if (kind == UP_DEVICE_KIND_MEDIA_PLAYER) {
+
+ /* TRANSLATORS: the cell battery is very low */
+ title = _("Cell phone battery low");
+
+ /* TRANSLATORS: the device is just going to stop working */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Media player is very low in power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ percentage);
+ else
+ message = g_strdup_printf (_("Media player is very low in power. "
+ "This device will soon stop functioning if not charged."));
+
+ } else if (kind == UP_DEVICE_KIND_TABLET) {
+
+ /* TRANSLATORS: the cell battery is very low */
+ title = _("Tablet battery low");
+
+ /* TRANSLATORS: the device is just going to stop working */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Tablet is very low in power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ percentage);
+ else
+ message = g_strdup_printf (_("Tablet is very low in power. "
+ "This device will soon stop functioning if not charged."));
+
+ } else if (kind == UP_DEVICE_KIND_COMPUTER) {
+
+ /* TRANSLATORS: the cell battery is very low */
+ title = _("Attached computer battery low");
+
+ /* TRANSLATORS: the device is just going to stop working */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Attached computer is very low in power (%.0f%%). "
+ "The device will soon shutdown if not charged."),
+ percentage);
+ else
+ message = g_strdup_printf (_("Attached computer is very low in power. "
+ "The device will soon shutdown if not charged."));
+
+ }
+
+ /* 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: laptop battery is really, really, low */
+ title = _("Laptop battery critically low");
+
+ /* we have to do different warnings depending on the policy */
+ policy = manager_critical_action_get (manager);
+
+ /* use different text for different actions */
+ if (policy == GSD_POWER_ACTION_HIBERNATE) {
+ /* TRANSLATORS: computer will hibernate */
+ message = g_strdup (_("The battery is below the critical level and "
+ "this computer is about to hibernate."));
+
+ } else if (policy == GSD_POWER_ACTION_SHUTDOWN) {
+ /* TRANSLATORS: computer will just 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: UPS is really, really, low */
+ title = _("UPS critically low");
+
+ /* we have to do different warnings depending on the policy */
+ policy = manager_critical_action_get (manager);
+
+ /* use different text for different actions */
+ if (policy == GSD_POWER_ACTION_HIBERNATE) {
+ /* TRANSLATORS: computer will hibernate */
+ message = g_strdup (_("UPS is below the critical level and "
+ "this computer is about to hibernate."));
+
+ } else if (policy == GSD_POWER_ACTION_SHUTDOWN) {
+ /* TRANSLATORS: computer will just 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 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 (!on_battery) {
+ /* Don't dim when charging */
+ } else if (manager->battery_is_low) {
+ /* Aggressively blank when battery is low */
+ 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
+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);
+}
+
+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_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"), _("Computer will suspend very soon because of inactivity."),
+ NULL, NOTIFICATION_PRIVACY_SYSTEM,
+ &manager->notification_sleep_warning);
+ break;
+ case GSD_POWER_ACTION_HIBERNATE:
+ create_notification (_("Automatic hibernation"), _("Computer will suspend very 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;
+ }
+}
+
+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)
+{
+ 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 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);
+
+ 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 <mccann@jhu.edu>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_POWER_MANAGER_H
+#define __GSD_POWER_MANAGER_H
+
+#include <glib-object.h>
+
+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..a07798e
--- /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 = 4.0/5.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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GSM_INHIBITOR_FLAG_H__
+#define __GSM_INHIBITOR_FLAG_H__
+
+#include <glib-object.h>
+
+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 <jmccann@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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..69e619f
--- /dev/null
+++ b/plugins/power/meson.build
@@ -0,0 +1,150 @@
+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(
+ policy,
+ 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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE policyconfig PUBLIC
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
+<policyconfig>
+
+ <!--
+ Policy definitions for gnome-settings-daemon system-wide actions.
+ Copyright (c) 2010-2011 Richard Hughes <richard@hughsie.com>
+ -->
+
+ <vendor>GNOME Settings Daemon</vendor>
+ <vendor_url>http://git.gnome.org/browse/gnome-settings-daemon</vendor_url>
+ <icon_name>battery</icon_name>
+
+ <action id="org.gnome.settings-daemon.plugins.power.backlight-helper">
+ <!-- SECURITY:
+ - A normal active user on the local machine does not need permission
+ to change the backlight brightness.
+ -->
+ <description>Modify the laptop brightness</description>
+ <message>Authentication is required to modify the laptop brightness</message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>no</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.exec.path">@libexecdir@/gsd-backlight-helper</annotate>
+ </action>
+
+</policyconfig>
+
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..7bb190a
--- /dev/null
+++ b/plugins/power/test.py
@@ -0,0 +1,1397 @@
+#!/usr/bin/python3
+'''GNOME settings daemon tests for power plugin.'''
+
+__author__ = 'Martin Pitt <martin.pitt@ubuntu.com>'
+__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
+
+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
+
+class PowerPluginBase(gsdtestcase.GSDTestCase):
+ '''Test the power plugin'''
+
+ 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.check_logind_gnome_session()
+ self.start_logind()
+ self.daemon_death_expected = False
+
+
+ # Setup umockdev testbed
+ self.testbed = UMockdev.Testbed.new()
+ 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}, stdout=subprocess.PIPE)
+ gsdtestcase.set_nonblock(self.upowerd.stdout)
+
+ # start mock gnome-shell screensaver
+ (self.screensaver, self.obj_screensaver) = self.spawn_server_template(
+ 'gnome_screensaver', stdout=subprocess.PIPE)
+ gsdtestcase.set_nonblock(self.screensaver.stdout)
+
+ self.session_log_write = open(os.path.join(self.workdir, 'gnome-session.log'), 'wb', buffering=0)
+ self.session = subprocess.Popen(['gnome-session', '-f',
+ '-a', os.path.join(self.workdir, 'autostart'),
+ '--session=dummy', '--debug'],
+ stdout=self.session_log_write,
+ stderr=subprocess.STDOUT)
+
+ # wait until the daemon is on the bus
+ try:
+ self.wait_for_bus_object('org.gnome.SessionManager',
+ '/org/gnome/SessionManager')
+ except:
+ # on failure, print log
+ with open(self.session_log_write.name) as f:
+ print('----- session log -----\n%s\n------' % f.read())
+ raise
+
+ self.session_log = open(self.session_log_write.name, 'rb', buffering=0)
+
+ self.obj_session_mgr = self.session_bus_con.get_object(
+ 'org.gnome.SessionManager', '/org/gnome/SessionManager')
+
+ self.start_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.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')
+
+ Gio.Settings.sync()
+ self.plugin_log_write = open(os.path.join(self.workdir, 'plugin_power.log'), 'wb', buffering=0)
+ # 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']
+
+ # We need to redirect stdout to grab the debug messages.
+ # stderr is not needed by the testing infrastructure but is useful to
+ # see warnings and errors.
+ self.daemon = subprocess.Popen(
+ [os.path.join(builddir, 'gsd-power'), '--verbose'],
+ stdout=self.plugin_log_write,
+ env=env)
+
+ # you can use this for reading the current daemon log in tests
+ self.plugin_log = open(self.plugin_log_write.name, 'rb', buffering=0)
+
+ # wait until plugin is ready
+ timeout = 100
+ while timeout > 0:
+ time.sleep(0.1)
+ timeout -= 1
+ log = self.plugin_log.read()
+ if b'System inhibitor fd is' in log:
+ break
+
+ # always start with zero idle time
+ self.reset_idle_timer()
+
+ # flush notification log
+ self.p_notify.stdout.read()
+
+ def tearDown(self):
+
+ daemon_running = self.daemon.poll() == None
+ if daemon_running:
+ self.daemon.terminate()
+ self.daemon.wait()
+ self.plugin_log.close()
+ self.plugin_log_write.flush()
+ self.plugin_log_write.close()
+
+ self.upowerd.terminate()
+ self.upowerd.wait()
+ self.screensaver.terminate()
+ self.screensaver.wait()
+ self.stop_session()
+ self.stop_mutter()
+ self.stop_logind()
+
+ # reset all changed gsettings, so that tests are independent from each
+ # other
+ for schema in [self.settings_gsd_power, self.settings_session, self.settings_screensaver]:
+ for k in schema.list_keys():
+ schema.reset(k)
+ Gio.Settings.sync()
+
+ try:
+ os.unlink(self.mock_external_monitor_file)
+ except OSError:
+ pass
+
+ del self.testbed
+
+ # we check this at the end so that the other cleanup always happens
+ self.assertTrue(daemon_running or self.daemon_death_expected, 'daemon died during the test')
+
+ def stop_session(self):
+ '''Stop GNOME session'''
+
+ assert self.session
+ self.session.terminate()
+ self.session.wait()
+
+ self.session_log_write.flush()
+ self.session_log_write.close()
+ self.session_log.close()
+
+ 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.
+ '''
+ # check that it request logout
+ while timeout > 0:
+ time.sleep(1)
+ timeout -= 1
+ # check that it requested logout
+ log = self.session_log.read()
+ if log is None:
+ continue
+
+ if log and (b'GsmManager: requesting logout' in log):
+ break
+ else:
+ self.fail('timed out waiting for gnome-session logout call')
+
+ 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
+ time.sleep(seconds)
+ # check that it did not logout
+ log = self.session_log.read()
+ if log:
+ self.assertFalse(b'GsmManager: requesting logout' in log, 'unexpected logout request')
+
+ 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.
+ '''
+
+ # Create a list of byte string needles to search for
+ needles = [' {} '.format(m).encode('ascii') for m in methods]
+
+ suspended = False
+
+ # check that it request suspend
+ while timeout > 0:
+ time.sleep(1)
+ timeout -= 1
+ # check that it requested suspend
+ log = self.logind.stdout.read()
+ if log is None:
+ continue
+
+ for n in needles:
+ if n in log:
+ suspended = True
+ break
+
+ if suspended:
+ break
+
+ if not suspended:
+ self.fail('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.
+ '''
+ time.sleep(timeout)
+ # check that it requested uninhibition
+ log = self.plugin_log.read()
+
+ if b'uninhibiting lid close' in log:
+ self.fail('lid uninhibit should not have happened')
+
+ def check_no_suspend(self, seconds, methods=COMMON_SUSPEND_METHODS):
+ '''Check that no Suspend or Hibernate is requested in the given time'''
+
+ # wait for specified time to ensure it didn't do anything
+ time.sleep(seconds)
+ # check that it did not suspend or hibernate
+ log = self.logind.stdout.read()
+ if log is None:
+ return
+
+ for m in methods:
+ needle = ' {} '.format(m).encode('ascii')
+
+ self.assertFalse(needle in log, 'unexpected %s request' % m)
+
+ def check_suspend_no_hibernate(self, seconds):
+ '''Check that Suspend was requested and not Hibernate, in the given time'''
+
+ # wait for specified time to ensure it didn't do anything
+ time.sleep(seconds)
+ # check that it did suspend and didn't hibernate
+ log = self.logind.stdout.read()
+ if log:
+ self.assertTrue(b' Suspend' in log, 'missing Suspend request')
+ self.assertFalse(b' Hibernate' in log, 'unexpected Hibernate 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.
+ '''
+ if type(needle) == str:
+ needle = needle.encode('ascii')
+ # Fast path if the message was already logged
+ log = self.plugin_log.read()
+ if needle in log:
+ return
+
+ while timeout > 0:
+ time.sleep(0.5)
+ timeout -= 0.5
+
+ # read new data (lines) from the log
+ log = self.plugin_log.read()
+ if needle in log:
+ break
+ else:
+ if failmsg is not None:
+ self.fail(failmsg)
+ else:
+ self.fail('timed out waiting for needle "%s"' % needle)
+
+ 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
+ time.sleep(seconds)
+ # check that we don't dim
+ log = self.plugin_log.read()
+ if log:
+ self.assertFalse(b'Doing a state transition: dim' in log, 'unexpected dim request')
+
+ 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'''
+
+ # wait for specified time to ensure it didn't blank
+ time.sleep(seconds)
+ # check that it did not blank
+ log = self.plugin_log.read()
+ self.assertFalse(b'TESTSUITE: Blanked screen' in log, 'unexpected blank request')
+
+ def check_no_unblank(self, seconds):
+ '''Check that no unblank is requested in the given time'''
+
+ # wait for specified time to ensure it didn't unblank
+ time.sleep(seconds)
+ # check that it did not unblank
+ log = self.plugin_log.read()
+ self.assertFalse(b'TESTSUITE: Unblanked screen' in log, 'unexpected unblank request')
+
+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
+
+ # 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
+ 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
+ # 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
+ # 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
+ 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'
+
+ # 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'
+
+ # 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'
+
+ # 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
+
+ # 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')
+
+ time.sleep (gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT)
+
+ # 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
+
+ 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')
+
+ time.sleep (gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT)
+
+ # 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
+
+ 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')
+
+ time.sleep (gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT)
+
+ # 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 and flush log
+ time.sleep (gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 1)
+ self.plugin_log.read()
+
+ 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'
+ # 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 and flush log
+ time.sleep (gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 1)
+ self.plugin_log.read()
+
+ # 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)
+
+ # Wait a bit longer to ensure event has been fired
+ time.sleep(0.5)
+ # we should have gotten a notification now
+ notify_log = self.p_notify.stdout.read()
+
+ # verify notification
+ self.assertRegex(notify_log, b'[0-9.]+ Notify "Power" .* "battery-caution-symbolic" ".*battery critical.*"')
+
+ 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)
+
+ time.sleep(0.5)
+
+ # we should have gotten a notification by now
+ notify_log = self.p_notify.stdout.read()
+
+ # verify notification
+ self.assertRegex(notify_log, b'[0-9.]+ Notify "Power" .* "battery-caution-symbolic" ".*battery critical.*"')
+
+ 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)
+ time.sleep(0.5)
+
+ # we should have gotten a notification by now
+ notify_log = self.p_notify.stdout.read()
+
+ # verify notification
+ self.assertRegex(notify_log, b'[0-9.]+ Notify "Power" .* ".*" ".*Wireless mouse .*low.* power.*\([0-9.]+%\).*"')
+
+ 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)
+ time.sleep(0.5)
+
+ # we should have gotten a notification by now
+ notify_log = self.p_notify.stdout.read()
+
+ # verify notification
+ self.assertRegex(notify_log, b'[0-9.]+ Notify "Power" .* ".*" ".*Wireless mouse .*low.* power.*\([0-9.]+%\).*"')
+
+ # 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')
+ time.sleep(1)
+
+ # we shouldn't have gotten a notification by now
+ notify_log = self.p_notify.stdout.read()
+ self.assertIsNone(notify_log)
+
+ # 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)
+ time.sleep(0.5)
+
+ # we should have gotten a notification by now
+ notify_log = self.p_notify.stdout.read()
+
+ # verify notification
+ self.assertRegex(notify_log, b'[0-9.]+ Notify "Power" .* ".*" ".*Wireless mouse .*very low.* power.*\([0-9.]+%\).*"')
+
+ 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)
+
+ # we should have gotten a notification by now
+ notify_log = self.p_notify.stdout.read()
+
+ # verify notification
+ self.assertRegex(notify_log, b'[0-9.]+ Notify "Power" .* ".*" ".*Wireless mouse .*low.* power.*"')
+ self.assertNotRegex(notify_log, b'[0-9.]+ Notify "Power" .* ".*" ".*\([0-9.]+%\).*"')
+
+ def test_forced_logout(self):
+ '''Test forced logout'''
+
+ self.daemon_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'
+
+ self.check_for_logout(idle_delay + 2)
+
+ # The notification should have been received before the logout, but it's saved anyway
+ notify_log = self.p_notify.stdout.read()
+ self.assertTrue(b'You will soon log out because of inactivity.' in notify_log)
+
+ 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'
+
+ # 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'
+
+ # 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
+
+ # 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")
+
+ # We cannot use check_plugin_log here because the startup check already
+ # read the relevant message.
+ log = open(self.plugin_log_write.name, 'rb').read()
+ self.assertIn(b'Step size for backlight is 5.', log)
+
+ def test_legacy_brightness_step(self):
+ if self.skip_sysfs_backlight:
+ self.skipTest("sysfs backlight support required for test")
+
+ # We cannot use check_plugin_log here because the startup check already
+ # read the relevant message.
+ log = open(self.plugin_log_write.name, 'rb').read()
+ self.assertIn(b'Step size for backlight is 1.', log)
+
+ 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.4)
+ self.assertEqual(self.get_brightness(), 0)
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 10)
+ time.sleep(0.4)
+ self.assertEqual(self.get_brightness(), 2)
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 20)
+ time.sleep(0.4)
+ self.assertEqual(self.get_brightness(), 3)
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 25)
+ time.sleep(0.4)
+ self.assertEqual(self.get_brightness(), 4)
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 49)
+ time.sleep(0.4)
+ self.assertEqual(self.get_brightness(), 7)
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 50)
+ time.sleep(0.4)
+ self.assertEqual(self.get_brightness(), 8)
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 56)
+ time.sleep(0.4)
+ self.assertEqual(self.get_brightness(), 8)
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 57)
+ time.sleep(0.4)
+ self.assertEqual(self.get_brightness(), 9)
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 98)
+ time.sleep(0.4)
+ 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!')
+
+# 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <locale.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include <cups/cups.h>
+#include <cups/ppd.h>
+#include <libnotify/notify.h>
+
+#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 <mccann@jhu.edu>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_PRINT_NOTIFICATIONS_MANAGER_H
+#define __GSD_PRINT_NOTIFICATIONS_MANAGER_H
+
+#include <glib-object.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+#include <gio/gio.h>
+#include <stdlib.h>
+#include <libnotify/notify.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+#include <cups/cups.h>
+#include <cups/ppd.h>
+
+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[] =
+ "<node name='/com/redhat/NewPrinterNotification'>"
+ " <interface name='com.redhat.NewPrinterNotification'>"
+ " <method name='GetReady'>"
+ " </method>"
+ " <method name='NewPrinter'>"
+ " <arg type='i' name='status' direction='in'/>"
+ " <arg type='s' name='name' direction='in'/>"
+ " <arg type='s' name='mfg' direction='in'/>"
+ " <arg type='s' name='mdl' direction='in'/>"
+ " <arg type='s' name='des' direction='in'/>"
+ " <arg type='s' name='cmd' direction='in'/>"
+ " </method>"
+ " </interface>"
+ "</node>";
+
+static const gchar pdi_introspection_xml[] =
+ "<node name='/com/redhat/PrinterDriversInstaller'>"
+ " <interface name='com.redhat.PrinterDriversInstaller'>"
+ " <method name='InstallDrivers'>"
+ " <arg type='s' name='mfg' direction='in'/>"
+ " <arg type='s' name='mdl' direction='in'/>"
+ " <arg type='s' name='cmd' direction='in'/>"
+ " </method>"
+ " </interface>"
+ "</node>";
+
+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 <mccann@jhu.edu>
+ * Copyright (C) 2010,2011 Red Hat, Inc.
+ *
+ * Author: Bastien Nocera <hadess@hadess.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* 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" \
+ * "<true|false>"
+ * 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" \
+ * "<true|false>"
+ *
+ * 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" \
+ * "<true|false>"
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <string.h>
+
+#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[] =
+"<node>"
+" <interface name='org.gnome.SettingsDaemon.Rfkill'>"
+" <annotation name='org.freedesktop.DBus.GLib.CSymbol' value='gsd_rfkill_manager'/>"
+" <property name='AirplaneMode' type='b' access='readwrite'/>"
+" <property name='HardwareAirplaneMode' type='b' access='read'/>"
+" <property name='HasAirplaneMode' type='b' access='read'/>"
+" <property name='ShouldShowAirplaneMode' type='b' access='read'/>"
+" <property name='BluetoothAirplaneMode' type='b' access='readwrite'/>"
+" <property name='BluetoothHardwareAirplaneMode' type='b' access='read'/>"
+" <property name='BluetoothHasAirplaneMode' type='b' access='read'/>"
+" <property name='WwanAirplaneMode' type='b' access='readwrite'/>"
+" <property name='WwanHardwareAirplaneMode' type='b' access='read'/>"
+" <property name='WwanHasAirplaneMode' type='b' access='read'/>"
+" </interface>"
+"</node>";
+
+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 <mccann@jhu.edu>
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_RFKILL_MANAGER_H
+#define __GSD_RFKILL_MANAGER_H
+
+#include <glib-object.h>
+
+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..22309dc
--- /dev/null
+++ b/plugins/rfkill/rfkill-glib.c
@@ -0,0 +1,680 @@
+/*
+ *
+ * gnome-bluetooth - Bluetooth integration for GNOME
+ *
+ * Copyright (C) 2012 Bastien Nocera <hadess@hadess.net>
+ * 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 <config.h>
+#endif
+
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/ioctl.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <gio/gunixoutputstream.h>
+
+#include "rfkill-glib.h"
+#include <gudev/gudev.h>
+
+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;
+ gsize read;
+
+ status = g_io_channel_read_chars (source,
+ (char *) &event,
+ sizeof(event),
+ &read,
+ NULL);
+
+ while (status == G_IO_STATUS_NORMAL && read == sizeof(event)) {
+ 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;
+ GList *events;
+
+ 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;
+ }
+
+ events = NULL;
+
+ while (1) {
+ struct rfkill_event event;
+ struct rfkill_event *event_ptr;
+ ssize_t len;
+
+ len = read(fd, &event, sizeof(event));
+ if (len < 0) {
+ if (errno == EAGAIN)
+ break;
+ g_debug ("Reading of RFKILL events failed");
+ break;
+ }
+
+ if (len != RFKILL_EVENT_SIZE_V1) {
+ g_warning ("Wrong size of RFKILL event\n");
+ continue;
+ }
+
+ if (event.op != RFKILL_OP_ADD)
+ continue;
+
+ g_debug ("Read killswitch of type '%s' (idx=%d): soft %d hard %d",
+ type_to_string (event.type),
+ event.idx, event.soft, event.hard);
+
+ event_ptr = g_memdup (&event, sizeof(event));
+ events = g_list_prepend (events, event_ptr);
+ }
+
+ /* 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);
+
+ if (events) {
+ events = g_list_reverse (events);
+ emit_changed_signal_and_free (rfkill, events);
+ } else {
+ g_debug ("No rfkill device available on startup");
+ }
+
+ /* 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 <hadess@hadess.net>
+ * 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 <glib-object.h>
+#include <gio/gio.h>
+#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 <johannes@sipsolutions.net>
+ *
+ * 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 <linux/types.h>
+
+/* 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 <hadess@hadess.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <locale.h>
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#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[] =
+"<node name='/org/freedesktop/ScreenSaver'>"
+ "<interface name='org.freedesktop.ScreenSaver'>"
+ "<method name='Lock'/>"
+ "<method name='SimulateUserActivity'/>"
+ "<method name='GetActive'>"
+ "<arg type='b' direction='out'/>"
+ "</method>"
+ "<method name='GetActiveTime'>"
+ "<arg name='seconds' type='u' direction='out'/>"
+ "</method>"
+ "<method name='GetSessionIdleTime'>"
+ "<arg name='seconds' type='u' direction='out'/>"
+ "</method>"
+ "<method name='SetActive'>"
+ "<arg type='b' direction='out'/>"
+ "<arg name='e' type='b' direction='in'/>"
+ "</method>"
+ "<method name='Inhibit'>"
+ "<arg name='application_name' type='s' direction='in'/>"
+ "<arg name='reason_for_inhibit' type='s' direction='in'/>"
+ "<arg name='cookie' type='u' direction='out'/>"
+ "</method>"
+ "<method name='UnInhibit'>"
+ "<arg name='cookie' type='u' direction='in'/>"
+ "</method>"
+ "<method name='Throttle'>"
+ "<arg name='application_name' type='s' direction='in'/>"
+ "<arg name='reason_for_inhibit' type='s' direction='in'/>"
+ "<arg name='cookie' type='u' direction='out'/>"
+ "</method>"
+ "<method name='UnThrottle'>"
+ "<arg name='cookie' type='u' direction='in'/>"
+ "</method>"
+
+ "<signal name='ActiveChanged'>"
+ "<arg type='b'/>"
+ "</signal>"
+ "</interface>"
+"</node>";
+static const gchar introspection_xml2[] =
+"<node name='/ScreenSaver'>"
+ "<interface name='org.freedesktop.ScreenSaver'>"
+ "<method name='Lock'/>"
+ "<method name='SimulateUserActivity'/>"
+ "<method name='GetActive'>"
+ "<arg type='b' direction='out'/>"
+ "</method>"
+ "<method name='GetActiveTime'>"
+ "<arg name='seconds' type='u' direction='out'/>"
+ "</method>"
+ "<method name='GetSessionIdleTime'>"
+ "<arg name='seconds' type='u' direction='out'/>"
+ "</method>"
+ "<method name='SetActive'>"
+ "<arg type='b' direction='out'/>"
+ "<arg name='e' type='b' direction='in'/>"
+ "</method>"
+ "<method name='Inhibit'>"
+ "<arg name='application_name' type='s' direction='in'/>"
+ "<arg name='reason_for_inhibit' type='s' direction='in'/>"
+ "<arg name='cookie' type='u' direction='out'/>"
+ "</method>"
+ "<method name='UnInhibit'>"
+ "<arg name='cookie' type='u' direction='in'/>"
+ "</method>"
+ "<method name='Throttle'>"
+ "<arg name='application_name' type='s' direction='in'/>"
+ "<arg name='reason_for_inhibit' type='s' direction='in'/>"
+ "<arg name='cookie' type='u' direction='out'/>"
+ "</method>"
+ "<method name='UnThrottle'>"
+ "<arg name='cookie' type='u' direction='in'/>"
+ "</method>"
+
+ "<signal name='ActiveChanged'>"
+ "<arg type='b'/>"
+ "</signal>"
+ "</interface>"
+"</node>";
+
+#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 <hadess@hadess.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_SCREENSAVER_PROXY_MANAGER_H
+#define __GSD_SCREENSAVER_PROXY_MANAGER_H
+
+#include <glib-object.h>
+
+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 <hadess@hadess.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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..43067cc
--- /dev/null
+++ b/plugins/sharing/gsd-sharing-manager.c
@@ -0,0 +1,829 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2014 Bastien Nocera <hadess@hadess.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <locale.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <gio/gdesktopappinfo.h>
+#include <glib/gstdio.h>
+
+#if HAVE_NETWORK_MANAGER
+#include <NetworkManager.h>
+#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[] =
+"<node>"
+" <interface name='org.gnome.SettingsDaemon.Sharing'>"
+" <annotation name='org.freedesktop.DBus.GLib.CSymbol' value='gsd_sharing_manager'/>"
+" <property name='CurrentNetwork' type='s' access='read'/>"
+" <property name='CurrentNetworkName' type='s' access='read'/>"
+" <property name='CarrierType' type='s' access='read'/>"
+" <property name='SharingStatus' type='u' access='read'/>"
+" <method name='EnableService'>"
+" <arg name='service-name' direction='in' type='s'/>"
+" </method>"
+" <method name='DisableService'>"
+" <arg name='service-name' direction='in' type='s'/>"
+" <arg name='network' direction='in' type='s'/>"
+" </method>"
+" <method name='ListNetworks'>"
+" <arg name='service-name' direction='in' type='s'/>"
+" <arg name='networks' direction='out' type='a(sss)'/>"
+" </method>"
+" </interface>"
+"</node>";
+
+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-remote-desktop",
+ "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 <mccann@jhu.edu>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_SHARING_MANAGER_H
+#define __GSD_SHARING_MANAGER_H
+
+#include <glib-object.h>
+
+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 <glib-object.h>
+
+/*** 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 <glib-object.h>
+
+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 <mccann@jhu.edu>
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#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 <prerror.h>
+#include <prinit.h>
+#include <nss.h>
+#include <pk11func.h>
+#include <secmod.h>
+#include <secerr.h>
+
+#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, &parameters, 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 <mccann@jhu.edu>
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_SMARTCARD_MANAGER_H
+#define __GSD_SMARTCARD_MANAGER_H
+
+#include <glib-object.h>
+
+#include <prerror.h>
+#include <prinit.h>
+#include <nss.h>
+#include <pk11func.h>
+#include <secmod.h>
+#include <secerr.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ray Strode
+ */
+
+#ifndef __GSD_SMARTCARD_SERVICE_H__
+#define __GSD_SMARTCARD_SERVICE_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+#include "gsd-smartcard-manager.h"
+
+#include "org.gnome.SettingsDaemon.Smartcard.h"
+
+#include <prerror.h>
+#include <prinit.h>
+#include <nss.h>
+#include <pk11func.h>
+#include <secmod.h>
+#include <secerr.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+#include "gsd-smartcard-utils.h"
+
+#include <string.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_SMARTCARD_UTILS_H
+#define __GSD_SMARTCARD_UTILS_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+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 @@
+<!DOCTYPE node PUBLIC
+ "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<!--
+ Copyright (C) 2013 Red Hat, Inc.
+
+ This library 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 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+ Author: Ray Strode <rstrode@redhat.com>
+-->
+
+<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+ <!--
+ org.gnome.SettingsDaemon.Smartcard.Manager:
+
+ An interface used for managing smartcard functionality.
+ -->
+ <interface name="org.gnome.SettingsDaemon.Smartcard.Manager">
+ <method name="GetLoginToken">
+ <arg name="token" type="o" direction="out"/>
+ </method>
+
+ <method name="GetInsertedTokens">
+ <arg name="tokens" type="ao" direction="out"/>
+ </method>
+ </interface>
+
+ <!--
+ org.gnome.SettingsDaemon.Smartcard.Driver:
+
+ The smartcard driver interface.
+ -->
+ <interface name="org.gnome.SettingsDaemon.Smartcard.Driver">
+ <!--
+ Library:
+ Path to PKCS11 module
+ -->
+ <property name="Library" type="s" access="read"/>
+
+ <!--
+ Description:
+ String describing the PKCS11 module
+ -->
+ <property name="Description" type="s" access="read"/>
+ </interface>
+
+ <!--
+ org.gnome.SettingsDaemon.Smartcard.Token:
+
+ The smartcard interface.
+ -->
+ <interface name="org.gnome.SettingsDaemon.Smartcard.Token">
+ <!--
+ Name:
+ Name of the token
+ -->
+ <property name="Name" type="s" access="read"/>
+
+ <!--
+ Driver:
+ Driver handling token
+ -->
+ <property name="Driver" type="o" access="read"/>
+
+ <!--
+ IsInserted:
+ Whether or not the card is inserted
+ -->
+ <property name="IsInserted" type="b" access="read"/>
+
+ <!--
+ UsedToLogin:
+ Whether or not the card was used to log in
+ -->
+ <property name="UsedToLogin" type="b" access="read"/>
+ </interface>
+</node>
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 <lennart@poettering.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <locale.h>
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <pulse/pulseaudio.h>
+
+#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 <lennart@poettering.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_SOUND_MANAGER_H
+#define __GSD_SOUND_MANAGER_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+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..63a4e0d
--- /dev/null
+++ b/plugins/usb-protection/gsd-usb-protection-manager.c
@@ -0,0 +1,1179 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2018 Ludovico de Nittis <denittis@gnome.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include <libnotify/notify.h>
+#include <locale.h>
+#include <string.h>
+
+#include <gdesktop-enums.h>
+
+#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;
+ 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[] =
+"<node>"
+" <interface name='org.gnome.SettingsDaemon.UsbProtection'>"
+" <property name='Available' type='b' access='read'/>"
+" </interface>"
+"</node>";
+
+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; */
+ /* We can't use Rule::LastID, due to a bug in USBGuard.
+ * We cannot pick an arbitrary number, so we pick
+ * "0" which means we prepend our rule.
+ * https://github.com/USBGuard/usbguard/pull/355
+ */
+ const guint32 last_rule_id = 0;
+ 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);
+ while (g_variant_iter_loop (iter, "(us)", &number, &value)) {
+ if (g_strcmp0 (value, ALLOW_ALL) == 0) {
+ g_debug ("Detected rule!");
+ return TRUE;
+ }
+ }
+ 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);
+ 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_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)
+ 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);
+ if (!notify_notification_show (manager->notification, NULL)) {
+ g_warning ("Failed to send USB protection notification");
+ g_clear_object (&manager->notification);
+ }
+}
+
+static void authorize_device (GDBusProxy *proxy,
+ GsdUsbProtectionManager *manager,
+ guint device_id,
+ guint target,
+ gboolean permanent)
+{
+ if (manager->usb_protection_devices == NULL) {
+ g_warning("Could not authorize device, because DBus is missing");
+ } else {
+ 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);
+ 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);
+ 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
+auth_device (GsdUsbProtectionManager *manager,
+ GVariant *device)
+{
+ guint device_id;
+
+ if (manager->usb_protection_devices == NULL)
+ return;
+
+ g_variant_get_child (device, POLICY_DEVICE_ID, "u", &device_id);
+ authorize_device(manager->usb_protection_devices,
+ manager,
+ device_id,
+ TARGET_ALLOW,
+ FALSE);
+}
+
+static void
+on_screen_locked (GsdScreenSaver *screen_saver,
+ GAsyncResult *result,
+ GsdUsbProtectionManager *manager)
+{
+ 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);
+ }
+
+ 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_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);
+ 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)) {
+ g_debug ("Device is hardwired, allowing it to be connected");
+ auth_device (manager, parameters);
+ 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) {
+ 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."));
+ auth_device (manager, parameters);
+ } 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) {
+ gsd_screen_saver_call_lock (manager->screensaver_proxy,
+ manager->cancellable,
+ (GAsyncReadyCallback) on_screen_locked,
+ manager);
+ auth_device (manager, parameters);
+ } 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);
+ 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 ();
+
+ 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)
+{
+ 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 <denittis@gnome.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_USB_PROTECTION_MANAGER_H
+#define __GSD_USB_PROTECTION_MANAGER_H
+
+#include <glib-object.h>
+
+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 <mccann@jhu.edu>
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <locale.h>
+
+#include <gdk/gdk.h>
+
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/gdkwayland.h>
+#endif
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#if HAVE_WACOM
+#include <libwacom/libwacom.h>
+#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[] =
+"<node name='/org/gnome/SettingsDaemon/Wacom'>"
+" <interface name='org.gnome.SettingsDaemon.Wacom'>"
+" <method name='SetOLEDLabels'>"
+" <arg name='device_path' direction='in' type='s'/>"
+" <arg name='labels' direction='in' type='as'/>"
+" </method>"
+" </interface>"
+"</node>";
+
+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 <mccann@jhu.edu>
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_WACOM_MANAGER_H
+#define __GSD_WACOM_MANAGER_H
+
+#include <glib-object.h>
+
+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 <przemo@firszt.eu>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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 <przemo@firszt.eu>
+ *
+ * The code is derived from gsd-wacom-led-helper.c
+ * written by:
+ * Copyright (C) 2010-2011 Richard Hughes <richard@hughsie.com>
+ * Copyright (C) 2012 Bastien Nocera <hadess@hadess.net>
+ *
+ * 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 <unistd.h>
+
+#include <stdlib.h>
+#include "config.h"
+
+#include <glib.h>
+#include <locale.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <gudev/gudev.h>
+
+#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 <przemo@firszt.eu>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#include <math.h>
+#include <pango/pango.h>
+#include <pango/pangocairo.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <przemo@firszt.eu>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "gsd-wacom-oled-constants.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#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..d8dd0ec
--- /dev/null
+++ b/plugins/wacom/meson.build
@@ -0,0 +1,63 @@
+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(
+ policy,
+ 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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE policyconfig PUBLIC
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
+<policyconfig>
+
+ <!--
+ Policy definitions for gnome-settings-daemon system-wide actions.
+ -->
+
+ <vendor>GNOME Settings Daemon</vendor>
+ <vendor_url>http://git.gnome.org/browse/gnome-settings-daemon</vendor_url>
+ <icon_name>input-tablet</icon_name>
+
+ <action id="org.gnome.settings-daemon.plugins.wacom.wacom-led-helper">
+ <!-- SECURITY:
+ - A normal active user on the local machine does not need permission
+ to change the LED setting for a Wacom tablet
+ -->
+ <description>Modify the lit LED for a Wacom tablet</description>
+ <message>Authentication is required to modify the lit LED for a Wacom tablet</message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>no</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.exec.path">@libexecdir@/gsd-wacom-led-helper</annotate>
+ </action>
+
+ <action id="org.gnome.settings-daemon.plugins.wacom.wacom-oled-helper">
+ <!-- SECURITY:
+ - A normal active user on the local machine does not need permission
+ to change the OLED images for a Wacom tablet
+ -->
+ <description>Modify the OLED image for a Wacom tablet</description>
+ <message>Authentication is required to modify the OLED image for a Wacom tablet</message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>no</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.exec.path">@libexecdir@/gsd-wacom-oled-helper</annotate>
+ </action>
+
+</policyconfig>
+
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 <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * 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 <glib/gi18n.h>
+#include <polkit/polkit.h>
+#if defined(HAVE_NETWORK_MANAGER) && defined(BUILD_NETWORK)
+# include <NetworkManager.h>
+# include <nma-mobile-providers.h>
+#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 <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <libmm-glib.h>
+
+#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 <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Mohammed Sadiq <sadiq@sadiqpk.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <libmm-glib.h>
+
+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..42109ef
--- /dev/null
+++ b/plugins/wwan/gsd-wwan-manager.c
@@ -0,0 +1,826 @@
+/* -*- 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Guido Günther <agx@sigxcpu.org>
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <locale.h>
+
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <libmm-glib.h>
+
+#define GCR_API_SUBJECT_TO_CHANGE
+#include <gcr/gcr-base.h>
+
+#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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Guido Günther <agx@sigxcpu.org>
+ *
+ */
+
+# pragma once
+
+#include <glib-object.h>
+
+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..3f117fb
--- /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_base_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..63e8712
--- /dev/null
+++ b/plugins/xsettings/fc-monitor.c
@@ -0,0 +1,317 @@
+/* -*- 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) <jan.steffens@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Behdad Esfahbod, Red Hat, Inc.
+ */
+
+#include "fc-monitor.h"
+
+#include <gio/gio.h>
+#include <fontconfig/fontconfig.h>
+
+#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 const gchar *
+get_name (GType enum_type,
+ gint enum_value)
+{
+ GEnumClass *klass = g_type_class_ref (enum_type);
+ GEnumValue *value = g_enum_get_value (klass, enum_value);
+ const gchar *name = value ? value->value_name : "(unknown)";
+ g_type_class_unref (klass);
+ return name;
+}
+
+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 = get_name (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) <jan.steffens@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+#ifndef FC_MONITOR_H
+#define FC_MONITOR_H
+
+#include <glib-object.h>
+
+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 @@
+<?xml version="1.0"?>
+<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+<!-- /etc/fonts/fonts.conf file to configure system font access -->
+<fontconfig>
+
+<!-- Font directory list -->
+ <dir>/usr/share/fonts</dir>
+</fontconfig>
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 <hadess@hadess.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#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 <mccann@jhu.edu>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+
+#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 <hadess@hadess.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_XSETTINGS_GTK_H__
+#define __GSD_XSETTINGS_GTK_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gmodule.h>
+
+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..7b6fe43
--- /dev/null
+++ b/plugins/xsettings/gsd-xsettings-manager.c
@@ -0,0 +1,1588 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 Rodrigo Moya
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+
+#include <X11/Xatom.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#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 "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.settings-daemon.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 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 "antialiasing"
+#define FONT_HINTING_KEY "hinting"
+#define FONT_RGBA_ORDER_KEY "rgba-order"
+
+#define GTK_SETTINGS_DBUS_PATH "/org/gtk/Settings"
+#define GTK_SETTINGS_DBUS_NAME "org.gtk.Settings"
+
+static const gchar introspection_xml[] =
+"<node name='/org/gtk/Settings'>"
+" <interface name='org.gtk.Settings'>"
+" <property name='FontconfigTimestamp' type='x' access='read'/>"
+" <property name='Modules' type='s' access='read'/>"
+" <property name='EnableAnimations' type='b' access='read'/>"
+" </interface>"
+"</node>";
+
+/* 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;
+
+ 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 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
+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.settings-daemon.peripherals.mouse", "double-click", "Net/DoubleClickTime", translate_int_int },
+ { "org.gnome.settings-daemon.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_string_string },
+ { "org.gnome.desktop.interface", "gtk-im-module", "Gtk/IMModule", translate_string_string },
+ { "org.gnome.desktop.interface", "icon-theme", "Net/IconThemeName", translate_string_string },
+ { "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;
+ GsdFontAntialiasingMode antialiasing;
+ GsdFontHinting hinting;
+ GsdFontRgbaOrder 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 (manager->plugin_settings, FONT_ANTIALIASING_KEY);
+ hinting = g_settings_get_enum (manager->plugin_settings, FONT_HINTING_KEY);
+ order = g_settings_get_enum (manager->plugin_settings, FONT_RGBA_ORDER_KEY);
+
+ settings->antialias = (antialiasing != GSD_FONT_ANTIALIASING_MODE_NONE);
+ settings->hinting = (hinting != GSD_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 GSD_FONT_HINTING_NONE:
+ settings->hintstyle = "hintnone";
+ break;
+ case GSD_FONT_HINTING_SLIGHT:
+ settings->hintstyle = "hintslight";
+ break;
+ case GSD_FONT_HINTING_MEDIUM:
+ settings->hintstyle = "hintmedium";
+ break;
+ case GSD_FONT_HINTING_FULL:
+ settings->hintstyle = "hintfull";
+ break;
+ }
+
+ switch (order) {
+ case GSD_FONT_RGBA_ORDER_RGBA:
+ settings->rgba = "rgba";
+ break;
+ case GSD_FONT_RGBA_ORDER_RGB:
+ settings->rgba = "rgb";
+ break;
+ case GSD_FONT_RGBA_ORDER_BGR:
+ settings->rgba = "bgr";
+ break;
+ case GSD_FONT_RGBA_ORDER_VRGB:
+ settings->rgba = "vrgb";
+ break;
+ case GSD_FONT_RGBA_ORDER_VBGR:
+ settings->rgba = "vbgr";
+ break;
+ }
+
+ switch (antialiasing) {
+ case GSD_FONT_ANTIALIASING_MODE_NONE:
+ settings->antialias = 0;
+ break;
+ case GSD_FONT_ANTIALIASING_MODE_GRAYSCALE:
+ settings->antialias = 1;
+ break;
+ case GSD_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);
+ } else {
+ xft_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, CURSOR_SIZE_KEY) ||
+ g_str_equal (key, CURSOR_THEME_KEY)) {
+ xft_callback (NULL, key, 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);
+ }
+}
+
+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);
+
+ if (!setup_xsettings_managers (manager)) {
+ g_set_error (error, GSD_XSETTINGS_ERROR,
+ GSD_XSETTINGS_ERROR_INIT,
+ "Could not initialize xsettings manager.");
+ return FALSE;
+ }
+
+ 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));
+
+ 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;
+ }
+}
+
+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 <mccann@jhu.edu>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_XSETTINGS_MANAGER_H
+#define __GSD_XSETTINGS_MANAGER_H
+
+#include <glib-object.h>
+
+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..f91b963
--- /dev/null
+++ b/plugins/xsettings/meson.build
@@ -0,0 +1,66 @@
+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,
+ dependency('fontconfig')
+]
+
+cflags += ['-DGTK_MODULES_DIRECTORY="@0@"'.format(join_paths(gsd_pkglibdir, 'gtk-modules'))]
+
+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 <glib.h>
+
+#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..0b3a44d
--- /dev/null
+++ b/plugins/xsettings/test.py
@@ -0,0 +1,209 @@
+#!/usr/bin/python3
+'''GNOME settings daemon tests for xsettings plugin.'''
+
+__author__ = 'Bastien Nocera <hadess@hadess.net>'
+__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 gi.repository import Gio
+from gi.repository import GLib
+
+class XsettingsPluginTest(gsdtestcase.GSDTestCase):
+ '''Test the xsettings plugin'''
+
+ def setUp(self):
+ self.start_logind()
+
+ self.daemon_death_expected = False
+ self.session_log_write = open(os.path.join(self.workdir, 'gnome-session.log'), 'wb')
+ self.session = subprocess.Popen(['gnome-session', '-f',
+ '-a', os.path.join(self.workdir, 'autostart'),
+ '--session=dummy', '--debug'],
+ stdout=self.session_log_write,
+ stderr=subprocess.STDOUT)
+
+ # wait until the daemon is on the bus
+ try:
+ self.wait_for_bus_object('org.gnome.SessionManager',
+ '/org/gnome/SessionManager')
+ except:
+ # on failure, print log
+ with open(self.session_log_write.name) as f:
+ print('----- session log -----\n%s\n------' % f.read())
+ raise
+
+ self.session_log = open(self.session_log_write.name)
+
+ self.obj_session_mgr = self.session_bus_con.get_object(
+ 'org.gnome.SessionManager', '/org/gnome/SessionManager')
+
+ self.start_mutter()
+
+ Gio.Settings.sync()
+ self.plugin_log_write = open(os.path.join(self.workdir, 'plugin_xsettings.log'), 'wb', buffering=0)
+ 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')
+
+ env = os.environ.copy()
+ self.daemon = subprocess.Popen(
+ [os.path.join(builddir, 'gsd-xsettings'), '--verbose'],
+ # comment out this line if you want to see the logs in real time
+ stdout=self.plugin_log_write,
+ stderr=subprocess.STDOUT,
+ env=env)
+
+ # you can use this for reading the current daemon log in tests
+ self.plugin_log = open(self.plugin_log_write.name, 'rb', buffering=0)
+
+ # flush notification log
+ try:
+ self.p_notify.stdout.read()
+ except IOError:
+ pass
+
+ time.sleep(3)
+ 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 tearDown(self):
+
+ daemon_running = self.daemon.poll() == None
+ if daemon_running:
+ self.daemon.terminate()
+ self.daemon.wait()
+ self.plugin_log.close()
+ self.plugin_log_write.flush()
+ self.plugin_log_write.close()
+
+ self.stop_session()
+ self.stop_mutter()
+ self.stop_logind()
+
+ # reset all changed gsettings, so that tests are independent from each
+ # other
+ for schema in [self.settings_sound]:
+ for k in schema.list_keys():
+ schema.reset(k)
+ Gio.Settings.sync()
+
+ # we check this at the end so that the other cleanup always happens
+ self.assertTrue(daemon_running or self.daemon_death_expected, 'daemon died during the test')
+
+ def stop_session(self):
+ '''Stop GNOME session'''
+
+ assert self.session
+ self.session.terminate()
+ self.session.wait()
+
+ self.session_log_write.flush()
+ self.session_log_write.close()
+ self.session_log.close()
+
+ 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.
+ '''
+ if type(needle) == str:
+ needle = needle.encode('ascii')
+ # Fast path if the message was already logged
+ log = self.plugin_log.read()
+ if needle in log:
+ return
+
+ while timeout > 0:
+ time.sleep(0.5)
+ timeout -= 0.5
+
+ # read new data (lines) from the log
+ log = self.plugin_log.read()
+ if needle in log:
+ break
+ else:
+ if failmsg is not None:
+ self.fail(failmsg)
+ else:
+ self.fail('timed out waiting for needle "%s"' % needle)
+
+ def test_gtk_modules(self):
+ # Turn off event sounds
+ self.settings_sound['event-sounds'] = False
+ 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
+ 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)
+
+ # 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Florian Müllner <fmuellner@gnome.org>
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <glib.h>
+
+#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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Florian Müllner <fmuellner@gnome.org>
+ */
+
+#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..e5f9fcc
--- /dev/null
+++ b/plugins/xsettings/xsettings-common.c
@@ -0,0 +1,112 @@
+/*
+ * 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 <glib.h>
+
+#include "string.h"
+#include "stdlib.h"
+
+#include <X11/Xlib.h>
+#include <X11/Xmd.h> /* For CARD32 */
+
+#include "xsettings-common.h"
+
+XSettingsSetting *
+xsettings_setting_new (const gchar *name)
+{
+ XSettingsSetting *result;
+
+ result = g_slice_new0 (XSettingsSetting);
+ 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_slice_free (XSettingsSetting, 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 <glib.h>
+
+#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..947cc9e
--- /dev/null
+++ b/plugins/xsettings/xsettings-manager.c
@@ -0,0 +1,393 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+#include <X11/Xmd.h> /* 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];
+
+ manager = g_slice_new (XSettingsManager);
+
+ 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_slice_free (XSettingsManager, 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..a8cf151
--- /dev/null
+++ b/plugins/xsettings/xsettings-manager.h
@@ -0,0 +1,58 @@
+/*
+ * 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 <X11/Xlib.h>
+#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 */