[gnome-flashback] media-keys: add unmodified files from gnome-settings-daemon



commit 76b7c4981ec77e3dab6ad5d0cbf780c16d1ebc3d
Author: Alberts Muktupāvels <alberts muktupavels gmail com>
Date:   Sat Mar 5 10:18:10 2022 +0200

    media-keys: add unmodified files from gnome-settings-daemon
    
    Files are copied from a96bf904468c. Next commits will make needed
    changes to restore screenshot and screencast keyboard shortcuts.

 daemons/media-keys/gsd-media-keys-manager.c | 3686 +++++++++++++++++++++++++++
 daemons/media-keys/gsd-media-keys-manager.h |   47 +
 daemons/media-keys/gsd-screenshot-utils.c   |  286 +++
 daemons/media-keys/gsd-screenshot-utils.h   |   36 +
 daemons/media-keys/media-keys.h             |   86 +
 daemons/media-keys/shell-action-modes.h     |   53 +
 daemons/media-keys/shortcuts-list.h         |  110 +
 7 files changed, 4304 insertions(+)
---
diff --git a/daemons/media-keys/gsd-media-keys-manager.c b/daemons/media-keys/gsd-media-keys-manager.c
new file mode 100644
index 0000000..3e2ec3f
--- /dev/null
+++ b/daemons/media-keys/gsd-media-keys-manager.c
@@ -0,0 +1,3686 @@
+/* -*- 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 */
+
+#define SETTINGS_INTERFACE_DIR "org.gnome.desktop.interface"
+#define SETTINGS_POWER_DIR "org.gnome.settings-daemon.plugins.power"
+#define SETTINGS_XSETTINGS_DIR "org.gnome.settings-daemon.plugins.xsettings"
+#define SETTINGS_TOUCHPAD_DIR "org.gnome.desktop.peripherals.touchpad"
+#define TOUCHPAD_ENABLED_KEY "send-events"
+#define HIGH_CONTRAST "HighContrast"
+
+#define REWIND_USEC (-10 * G_USEC_PER_SEC)
+#define FASTFORWARD_USEC (45 * G_USEC_PER_SEC)
+
+#define VOLUME_STEP "volume-step"
+#define VOLUME_STEP_PRECISE 2
+#define MAX_VOLUME 65536.0
+
+#define SYSTEMD_DBUS_NAME                       "org.freedesktop.login1"
+#define SYSTEMD_DBUS_PATH                       "/org/freedesktop/login1"
+#define SYSTEMD_DBUS_INTERFACE                  "org.freedesktop.login1.Manager"
+
+#define AUDIO_SELECTION_DBUS_NAME               "org.gnome.Shell.AudioDeviceSelection"
+#define AUDIO_SELECTION_DBUS_PATH               "/org/gnome/Shell/AudioDeviceSelection"
+#define AUDIO_SELECTION_DBUS_INTERFACE          "org.gnome.Shell.AudioDeviceSelection"
+
+#define GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE(o) (gsd_media_keys_manager_get_instance_private (o))
+
+typedef struct {
+        char   *application;
+        char   *dbus_name;
+        guint32 time;
+        guint   watch_id;
+} MediaPlayer;
+
+typedef struct {
+        gint ref_count;
+
+        MediaKeyType key_type;
+        ShellActionMode modes;
+        MetaKeyBindingFlags grab_flags;
+        const char *settings_key;
+        gboolean static_setting;
+        char *custom_path;
+        char *custom_command;
+        GArray *accel_ids;
+} MediaKey;
+
+typedef struct {
+        GsdMediaKeysManager *manager;
+        GPtrArray *keys;
+
+        /* NOTE: This is to implement a custom cancellation handling where
+         *       we immediately emit an ungrab call if grabbing was cancelled.
+         */
+        gboolean cancelled;
+} GrabUngrabData;
+
+typedef struct
+{
+        /* Volume bits */
+        GvcMixerControl *volume;
+        GvcMixerStream  *sink;
+        GvcMixerStream  *source;
+        ca_context      *ca;
+        GSettings       *sound_settings;
+        pa_volume_t      max_volume;
+        GtkSettings     *gtksettings;
+#if HAVE_GUDEV
+        GHashTable      *streams; /* key = X device ID, value = stream id */
+        GUdevClient     *udev_client;
+#endif /* HAVE_GUDEV */
+        guint            audio_selection_watch_id;
+        guint            audio_selection_signal_id;
+        GDBusConnection *audio_selection_conn;
+        gboolean         audio_selection_requested;
+        guint            audio_selection_device_id;
+
+        GSettings       *settings;
+        GHashTable      *custom_settings;
+
+        GPtrArray       *keys;
+
+        /* HighContrast theme settings */
+        GSettings       *interface_settings;
+        char            *icon_theme;
+        char            *gtk_theme;
+
+        /* Power stuff */
+        GSettings       *power_settings;
+        GDBusProxy      *power_proxy;
+        GDBusProxy      *power_screen_proxy;
+        GDBusProxy      *power_keyboard_proxy;
+        UpDevice        *composite_device;
+        char            *chassis_type;
+        gboolean         power_button_disabled;
+        guint            reenable_power_button_timer_id;
+
+        /* Shell stuff */
+        GsdShell        *shell_proxy;
+        ShellKeyGrabber *key_grabber;
+        GCancellable    *grab_cancellable;
+        GHashTable      *keys_to_sync;
+        guint            keys_sync_source_id;
+        GrabUngrabData  *keys_sync_data;
+
+        /* ScreenSaver stuff */
+        GsdScreenSaver  *screen_saver_proxy;
+
+        /* 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;
+        guint64          rfkill_last_time;
+        GDBusProxy      *rfkill_proxy;
+        GCancellable    *rfkill_cancellable;
+
+        /* systemd stuff */
+        GDBusProxy      *logind_proxy;
+        gint             inhibit_keys_fd;
+        gint             inhibit_suspend_fd;
+        gboolean         inhibit_suspend_taken;
+
+        GDBusConnection *connection;
+        GCancellable    *bus_cancellable;
+
+        guint            start_idle_id;
+
+        /* Multimedia keys */
+        MprisController *mpris_controller;
+} GsdMediaKeysManagerPrivate;
+
+static void     gsd_media_keys_manager_class_init  (GsdMediaKeysManagerClass *klass);
+static void     gsd_media_keys_manager_init        (GsdMediaKeysManager      *media_keys_manager);
+static void     gsd_media_keys_manager_finalize    (GObject                  *object);
+static void     register_manager                   (GsdMediaKeysManager      *manager);
+static void     custom_binding_changed             (GSettings           *settings,
+                                                    const char          *settings_key,
+                                                    GsdMediaKeysManager *manager);
+static void     keys_sync_queue                    (GsdMediaKeysManager *manager,
+                                                    gboolean             immediate,
+                                                    gboolean             retry);
+static void     keys_sync_continue                 (GsdMediaKeysManager *manager);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GsdMediaKeysManager, gsd_media_keys_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+static void
+media_key_unref (MediaKey *key)
+{
+        if (key == NULL)
+                return;
+        if (!g_atomic_int_dec_and_test (&key->ref_count))
+                return;
+        g_clear_pointer (&key->accel_ids, g_array_unref);
+        g_free (key->custom_path);
+        g_free (key->custom_command);
+        g_free (key);
+}
+
+static MediaKey *
+media_key_ref (MediaKey *key)
+{
+        g_atomic_int_inc (&key->ref_count);
+        return key;
+}
+
+static MediaKey *
+media_key_new (void)
+{
+        MediaKey *key = g_new0 (MediaKey, 1);
+
+        key->accel_ids = g_array_new (FALSE, TRUE, sizeof(guint));
+
+        return media_key_ref (key);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (MediaKey, media_key_unref)
+
+static void
+grab_ungrab_data_free (GrabUngrabData *data)
+{
+        /* NOTE: The manager pointer is not owned and is invalid if the
+         *       operation was cancelled.
+         */
+
+        if (!data->cancelled) {
+                GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (data->manager);
+
+                if (priv->keys_sync_data == data)
+                        priv->keys_sync_data = NULL;
+        }
+
+        data->manager = NULL;
+        g_clear_pointer (&data->keys, g_ptr_array_unref);
+        g_free (data);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GrabUngrabData, grab_ungrab_data_free)
+
+static void
+set_launch_context_env (GsdMediaKeysManager *manager,
+                       GAppLaunchContext   *launch_context)
+{
+       GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+       GError *error = NULL;
+       GVariant *variant, *item;
+       GVariantIter *iter;
+
+       variant = g_dbus_connection_call_sync (priv->connection,
+                                              GNOME_KEYRING_DBUS_NAME,
+                                              GNOME_KEYRING_DBUS_PATH,
+                                              GNOME_KEYRING_DBUS_INTERFACE,
+                                              "GetEnvironment",
+                                              NULL,
+                                              NULL,
+                                              G_DBUS_CALL_FLAGS_NONE,
+                                              -1,
+                                              NULL,
+                                              &error);
+       if (variant == NULL) {
+               g_warning ("Failed to call GetEnvironment on keyring daemon: %s", error->message);
+               g_error_free (error);
+               return;
+       }
+
+       g_variant_get (variant, "(a{ss})", &iter);
+
+       while ((item = g_variant_iter_next_value (iter))) {
+               char *key;
+               char *value;
+
+               g_variant_get (item,
+                              "{ss}",
+                              &key,
+                              &value);
+
+               g_app_launch_context_setenv (launch_context, key, value);
+
+               g_variant_unref (item);
+               g_free (key);
+               g_free (value);
+       }
+
+       g_variant_iter_free (iter);
+       g_variant_unref (variant);
+}
+
+static char *
+get_key_string (MediaKey *key)
+{
+       if (key->settings_key != NULL)
+               return g_strdup_printf ("settings:%s", key->settings_key);
+       else if (key->custom_path != NULL)
+               return g_strdup_printf ("custom:%s", key->custom_path);
+       else
+               g_assert_not_reached ();
+}
+
+static GStrv
+get_bindings (GsdMediaKeysManager *manager,
+             MediaKey            *key)
+{
+       GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+       GPtrArray *array;
+       gchar *binding;
+
+       if (key->settings_key != NULL) {
+               g_autofree gchar *static_settings_key = NULL;
+               g_autofree GStrv keys = NULL;
+               g_autofree GStrv static_keys = NULL;
+               gchar **item;
+
+               if (!key->static_setting)
+                       return g_settings_get_strv (priv->settings, key->settings_key);
+
+               static_settings_key = g_strconcat (key->settings_key, "-static", NULL);
+               keys = g_settings_get_strv (priv->settings, key->settings_key);
+               static_keys = g_settings_get_strv (priv->settings, static_settings_key);
+
+               array = g_ptr_array_new ();
+               /* Steals all strings from the settings */
+               for (item = keys; *item; item++)
+                       g_ptr_array_add (array, *item);
+               for (item = static_keys; *item; item++)
+                       g_ptr_array_add (array, *item);
+               g_ptr_array_add (array, NULL);
+
+               return (GStrv) g_ptr_array_free (array, FALSE);
+       }
+
+       else if (key->custom_path != NULL) {
+                GSettings *settings;
+
+                settings = g_hash_table_lookup (priv->custom_settings,
+                                                key->custom_path);
+               binding = g_settings_get_string (settings, "binding");
+       } else
+               g_assert_not_reached ();
+
+        array = g_ptr_array_new ();
+        g_ptr_array_add (array, binding);
+        g_ptr_array_add (array, NULL);
+
+        return (GStrv) g_ptr_array_free (array, FALSE);
+}
+
+static void
+show_osd_with_max_level (GsdMediaKeysManager *manager,
+                         const char          *icon,
+                         const char          *label,
+                         double               level,
+                         double               max_level,
+                         const gchar         *connector)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        if (priv->shell_proxy == NULL)
+                return;
+
+        shell_show_osd_with_max_level (priv->shell_proxy,
+                                       icon, label, level, max_level, connector);
+}
+
+static void
+show_osd (GsdMediaKeysManager *manager,
+          const char          *icon,
+          const char          *label,
+          double               level,
+          const char          *connector)
+{
+        show_osd_with_max_level(manager,
+                                icon, label, level, -1, connector);
+}
+
+static const char *
+get_icon_name_for_volume (gboolean is_mic,
+                          gboolean muted,
+                          double volume)
+{
+        static const char *icon_names[] = {
+                "audio-volume-muted-symbolic",
+                "audio-volume-low-symbolic",
+                "audio-volume-medium-symbolic",
+                "audio-volume-high-symbolic",
+                "audio-volume-overamplified-symbolic",
+                NULL
+        };
+        static const char *mic_icon_names[] = {
+                "microphone-sensitivity-muted-symbolic",
+                "microphone-sensitivity-low-symbolic",
+                "microphone-sensitivity-medium-symbolic",
+                "microphone-sensitivity-high-symbolic",
+                NULL
+        };
+        int n;
+
+        if (muted) {
+                n = 0;
+        } else {
+                /* select image */
+                n = ceill (3.0 * volume);
+                if (n < 1)
+                        n = 1;
+                /* output volume above 100% */
+                else if (n > 3 && !is_mic)
+                        n = 4;
+                else if (n > 3)
+                        n = 3;
+        }
+
+       if (is_mic)
+               return mic_icon_names[n];
+       else
+               return icon_names[n];
+}
+
+static void
+ungrab_accelerators_complete (GObject      *object,
+                              GAsyncResult *result,
+                              gpointer      user_data)
+{
+        g_autoptr(GrabUngrabData) data = user_data;
+        gboolean success = FALSE;
+        g_autoptr(GError) error = NULL;
+        gint i;
+
+        g_debug ("Ungrab call completed!");
+
+        if (!shell_key_grabber_call_ungrab_accelerators_finish (SHELL_KEY_GRABBER (object),
+                                                                &success, result, &error)) {
+                g_warning ("Failed to ungrab accelerators: %s", error->message);
+
+                if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
+                        keys_sync_queue (data->manager, FALSE, TRUE);
+                        return;
+                }
+
+                /* We are screwed at this point; we'll still keep going assuming that we don't
+                 * have the bindings registered anymore.
+                 * The only alternative would be to die and force cleanup of all registered
+                 * grabs that way.
+                 */
+        } else if (!success) {
+                g_warning ("Failed to ungrab some accelerators, they were probably not registered!");
+        }
+
+        /* Clear the accelerator IDs. */
+        for (i = 0; i < data->keys->len; i++) {
+                MediaKey *key;
+
+                key = g_ptr_array_index (data->keys, i);
+
+                /* Always clear, as it would just fail again the next time. */
+                g_array_set_size (key->accel_ids, 0);
+        }
+
+        /* Nothing left to do if the operation was cancelled */
+        if (data->cancelled)
+                return;
+
+        keys_sync_continue (data->manager);
+}
+
+static void
+grab_accelerators_complete (GObject      *object,
+                            GAsyncResult *result,
+                            gpointer      user_data)
+{
+        g_autoptr(GrabUngrabData) data = user_data;
+        g_autoptr(GVariant) actions = NULL;
+        g_autoptr(GError) error = NULL;
+        gint i;
+
+        g_debug ("Grab call completed!");
+
+        if (!shell_key_grabber_call_grab_accelerators_finish (SHELL_KEY_GRABBER (object),
+                                                              &actions, result, &error)) {
+                g_warning ("Failed to grab accelerators: %s", error->message);
+
+                if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
+                        keys_sync_queue (data->manager, FALSE, TRUE);
+                        return;
+                }
+
+                /* We are screwed at this point as we can't grab the keys. Most likely
+                 * this means we are not running on GNOME, or ran into some other weird
+                 * error.
+                 * Either way, finish the operation as there is no way we can recover
+                 * from this.
+                 */
+                keys_sync_continue (data->manager);
+                return;
+        }
+
+        /* Do an immediate ungrab if the operation was cancelled.
+         * This may happen on daemon shutdown for example. */
+        if (data->cancelled) {
+                g_debug ("Doing an immediate ungrab on the grabbed accelerators!");
+
+                shell_key_grabber_call_ungrab_accelerators (SHELL_KEY_GRABBER (object),
+                                                            actions,
+                                                            NULL,
+                                                            ungrab_accelerators_complete,
+                                                            g_steal_pointer (&data));
+
+                return;
+        }
+
+        /* We need to stow away the accel_ids that have been registered successfully. */
+        for (i = 0; i < data->keys->len; i++) {
+                MediaKey *key;
+
+                key = g_ptr_array_index (data->keys, i);
+                g_assert (key->accel_ids->len == 0);
+        }
+        for (i = 0; i < data->keys->len; i++) {
+                MediaKey *key;
+                guint accel_id;
+
+                key = g_ptr_array_index (data->keys, i);
+
+                g_variant_get_child (actions, i, "u", &accel_id);
+                if (accel_id == 0) {
+                        g_autofree gchar *tmp = NULL;
+                        tmp = get_key_string (key);
+                        g_warning ("Failed to grab accelerator for keybinding %s", tmp);
+                } else {
+                        g_array_append_val (key->accel_ids, accel_id);
+                }
+        }
+
+        keys_sync_continue (data->manager);
+}
+
+static void
+keys_sync_continue (GsdMediaKeysManager *manager)
+{
+       GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        g_auto(GVariantBuilder) ungrab_builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("au"));
+        g_auto(GVariantBuilder) grab_builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(suu)"));
+        g_autoptr(GPtrArray) keys_being_ungrabbed = NULL;
+        g_autoptr(GPtrArray) keys_being_grabbed = NULL;
+        g_autoptr(GrabUngrabData) data = NULL;
+        GHashTableIter iter;
+        MediaKey *key;
+        gboolean need_ungrab = FALSE;
+
+        /* Syncing keys is a two step process in principle, i.e. we first ungrab all keys
+         * and then grab the new ones.
+         * To make this work, this function will be called multiple times and it will
+         * either emit an ungrab or grab call or do nothing when done.
+         */
+
+        /* If the keys_to_sync hash table is empty at this point, then we are done.
+         * priv->keys_sync_data will be cleared automatically when it is unref'ed.
+         */
+        if (g_hash_table_size (priv->keys_to_sync) == 0)
+                return;
+
+        keys_being_ungrabbed = g_ptr_array_new_with_free_func ((GDestroyNotify) media_key_unref);
+        keys_being_grabbed = g_ptr_array_new_with_free_func ((GDestroyNotify) media_key_unref);
+
+        g_hash_table_iter_init (&iter, priv->keys_to_sync);
+        while (g_hash_table_iter_next (&iter, (gpointer*) &key, NULL)) {
+                g_auto(GStrv) bindings = NULL;
+                gchar **pos = NULL;
+                gint i;
+
+                for (i = 0; i < key->accel_ids->len; i++) {
+                        g_variant_builder_add (&ungrab_builder, "u", g_array_index (key->accel_ids, guint, 
i));
+                        g_ptr_array_add (keys_being_ungrabbed, media_key_ref (key));
+
+                        need_ungrab = TRUE;
+                }
+
+                /* Keys that are synced but aren't in the internal list are being removed. */
+                if (!g_ptr_array_find (priv->keys, key, NULL))
+                        continue;
+
+                bindings = get_bindings (manager, key);
+                pos = bindings;
+                while (*pos) {
+                        /* Do not try to register empty keybindings. */
+                        if (strlen (*pos) > 0) {
+                                g_variant_builder_add (&grab_builder, "(suu)", *pos, key->modes, 
key->grab_flags);
+                                g_ptr_array_add (keys_being_grabbed, media_key_ref (key));
+                        }
+                        pos++;
+                }
+        }
+
+        data = g_new0 (GrabUngrabData, 1);
+        data->manager = manager;
+
+        /* These calls intentionally do not get a cancellable. See comment in
+         * GrabUngrabData.
+         */
+        priv->keys_sync_data = data;
+
+        if (need_ungrab) {
+                data->keys = g_steal_pointer (&keys_being_ungrabbed);
+
+                shell_key_grabber_call_ungrab_accelerators (priv->key_grabber,
+                                                            g_variant_builder_end (&ungrab_builder),
+                                                            NULL,
+                                                            ungrab_accelerators_complete,
+                                                            g_steal_pointer (&data));
+        } else {
+                data->keys = g_steal_pointer (&keys_being_grabbed);
+
+                g_hash_table_remove_all (priv->keys_to_sync);
+
+                shell_key_grabber_call_grab_accelerators (priv->key_grabber,
+                                                          g_variant_builder_end (&grab_builder),
+                                                          NULL,
+                                                          grab_accelerators_complete,
+                                                          g_steal_pointer (&data));
+        }
+}
+
+static gboolean
+keys_sync_start (gpointer user_data)
+{
+        GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        priv->keys_sync_source_id = 0;
+        g_assert (priv->keys_sync_data == NULL);
+        keys_sync_continue (manager);
+
+        return G_SOURCE_REMOVE;
+}
+
+void
+keys_sync_queue (GsdMediaKeysManager *manager, gboolean immediate, gboolean retry)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        guint i;
+
+        if (priv->keys_sync_source_id)
+                g_source_remove (priv->keys_sync_source_id);
+
+        if (retry) {
+                /* Abort the currently running operation, and don't retry
+                 * immediately to avoid race condition if an operation was
+                 * already active. */
+                if (priv->keys_sync_data) {
+                        priv->keys_sync_data->cancelled = TRUE;
+                        priv->keys_sync_data = NULL;
+
+                        immediate = FALSE;
+                }
+
+                /* Mark all existing keys for sync. */
+                for (i = 0; i < priv->keys->len; i++) {
+                        MediaKey *key = g_ptr_array_index (priv->keys, i);
+                        g_hash_table_add (priv->keys_to_sync, media_key_ref (key));
+                }
+        } else if (priv->keys_sync_data) {
+                /* We are already actively syncing, no need to do anything. */
+                return;
+        }
+
+        priv->keys_sync_source_id =
+                g_timeout_add (immediate ? 0 : (retry ? SHELL_GRABBER_RETRY_INTERVAL_MS : 50),
+                               keys_sync_start,
+                               manager);
+}
+
+static void
+gsettings_changed_cb (GSettings           *settings,
+                      const gchar         *settings_key,
+                      GsdMediaKeysManager *manager)
+{
+       GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        int      i;
+
+        /* Give up if we don't have proxy to the shell */
+        if (!priv->key_grabber)
+                return;
+
+       /* handled in gsettings_custom_changed_cb() */
+        if (g_str_equal (settings_key, "custom-keybindings"))
+               return;
+
+       /* 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, "[OUT] Speaker") != 0 &&
+             g_strcmp0 (port->port, "[OUT] Handset") != 0 &&
+             g_strcmp0 (port->port, "analog-output-speaker") != 0 &&
+             g_strcmp0 (port->port, "analog-output") != 0)) {
+                device = gvc_mixer_control_lookup_device_from_stream (priv->volume, stream);
+                show_osd_with_max_level (manager, icon,
+                                         gvc_mixer_ui_device_get_description (device),
+                                         new_vol, max_volume, NULL);
+        } else {
+                show_osd_with_max_level (manager, icon, NULL, new_vol, max_volume, NULL);
+        }
+
+        if (priv->ca)
+                ca_context_playing (priv->ca, 1, &playing);
+        playing = !playing && gvc_mixer_stream_get_state (stream) == GVC_STREAM_STATE_RUNNING;
+
+        if (quiet == FALSE && sound_changed != FALSE && muted == FALSE && playing == FALSE)
+                play_volume_changed_audio (manager, stream);
+}
+
+#if HAVE_GUDEV
+/* PulseAudio gives us /devices/... paths, when udev
+ * expects /sys/devices/... paths. */
+static GUdevDevice *
+get_udev_device_for_sysfs_path (GsdMediaKeysManager *manager,
+                               const char *sysfs_path)
+{
+       GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+       char *path;
+       GUdevDevice *dev;
+
+       path = g_strdup_printf ("/sys%s", sysfs_path);
+       dev = g_udev_client_query_by_sysfs_path (priv->udev_client, path);
+       g_free (path);
+
+       return dev;
+}
+
+static GvcMixerStream *
+get_stream_for_device_node (GsdMediaKeysManager *manager,
+                            gboolean             is_output,
+                            const gchar         *devnode)
+{
+       GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+       gpointer id_ptr;
+       GvcMixerStream *res;
+       GUdevDevice *dev, *parent;
+       GSList *streams, *l;
+
+       id_ptr = g_hash_table_lookup (priv->streams, devnode);
+       if (id_ptr != NULL) {
+               if (GPOINTER_TO_UINT (id_ptr) == (guint) -1)
+                       return NULL;
+               else
+                       return gvc_mixer_control_lookup_stream_id (priv->volume, GPOINTER_TO_UINT (id_ptr));
+       }
+
+       dev = g_udev_client_query_by_device_file (priv->udev_client, devnode);
+       if (dev == NULL) {
+               g_debug ("Could not find udev device for device path '%s'", devnode);
+               return NULL;
+       }
+
+       if (g_strcmp0 (g_udev_device_get_property (dev, "ID_BUS"), "usb") != 0) {
+               g_debug ("Not handling XInput device %s, not USB", devnode);
+               g_hash_table_insert (priv->streams,
+                                    g_strdup (devnode),
+                                    GUINT_TO_POINTER ((guint) -1));
+               g_object_unref (dev);
+               return NULL;
+       }
+
+       parent = g_udev_device_get_parent_with_subsystem (dev, "usb", "usb_device");
+       if (parent == NULL) {
+               g_warning ("No USB device parent for XInput device %s even though it's USB", devnode);
+               g_object_unref (dev);
+               return NULL;
+       }
+
+       res = NULL;
+       if (is_output)
+               streams = gvc_mixer_control_get_sinks (priv->volume);
+       else
+               streams = gvc_mixer_control_get_sources (priv->volume);
+       for (l = streams; l; l = l->next) {
+               GvcMixerStream *stream = l->data;
+               const char *sysfs_path;
+               GUdevDevice *stream_dev, *stream_parent;
+
+               sysfs_path = gvc_mixer_stream_get_sysfs_path (stream);
+               stream_dev = get_udev_device_for_sysfs_path (manager, sysfs_path);
+               if (stream_dev == NULL)
+                       continue;
+               stream_parent = g_udev_device_get_parent_with_subsystem (stream_dev, "usb", "usb_device");
+               g_object_unref (stream_dev);
+               if (stream_parent == NULL)
+                       continue;
+
+               if (g_strcmp0 (g_udev_device_get_sysfs_path (stream_parent),
+                              g_udev_device_get_sysfs_path (parent)) == 0) {
+                       res = stream;
+               }
+               g_object_unref (stream_parent);
+               if (res != NULL)
+                       break;
+       }
+
+       g_slist_free (streams);
+
+       if (res)
+               g_hash_table_insert (priv->streams,
+                                    g_strdup (devnode),
+                                    GUINT_TO_POINTER (gvc_mixer_stream_get_id (res)));
+       else
+               g_hash_table_insert (priv->streams,
+                                    g_strdup (devnode),
+                                    GUINT_TO_POINTER ((guint) -1));
+
+       return res;
+}
+#endif /* HAVE_GUDEV */
+
+typedef enum {
+       SOUND_ACTION_FLAG_IS_OUTPUT  = 1 << 0,
+       SOUND_ACTION_FLAG_IS_QUIET   = 1 << 1,
+       SOUND_ACTION_FLAG_IS_PRECISE = 1 << 2,
+} SoundActionFlags;
+
+static void
+do_sound_action (GsdMediaKeysManager *manager,
+                 const gchar         *device_node,
+                 int                  type,
+                 SoundActionFlags     flags)
+{
+       GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GvcMixerStream *stream = NULL;
+        gboolean old_muted, new_muted;
+        guint old_vol, new_vol, norm_vol_step, vol_step;
+        gboolean sound_changed;
+
+        /* Find the stream that corresponds to the device, if any */
+        stream = NULL;
+#if HAVE_GUDEV
+        if (device_node) {
+                stream = get_stream_for_device_node (manager,
+                                                     flags & SOUND_ACTION_FLAG_IS_OUTPUT,
+                                                     device_node);
+        }
+#endif /* HAVE_GUDEV */
+
+        if (stream == NULL) {
+                if (flags & SOUND_ACTION_FLAG_IS_OUTPUT)
+                        stream = priv->sink;
+                else
+                        stream = priv->source;
+        }
+
+        if (stream == NULL)
+                return;
+
+        if (flags & SOUND_ACTION_FLAG_IS_PRECISE) {
+                norm_vol_step = PA_VOLUME_NORM * VOLUME_STEP_PRECISE / 100;
+        }
+        else {
+                vol_step = g_settings_get_int (priv->settings, VOLUME_STEP);
+                norm_vol_step = PA_VOLUME_NORM * vol_step / 100;
+        }
+        /* FIXME: this is racy */
+        new_vol = old_vol = gvc_mixer_stream_get_volume (stream);
+        new_muted = old_muted = gvc_mixer_stream_get_is_muted (stream);
+        sound_changed = FALSE;
+
+        switch (type) {
+        case MUTE_KEY:
+                new_muted = !old_muted;
+                break;
+        case VOLUME_DOWN_KEY:
+                if (old_vol <= norm_vol_step) {
+                        new_vol = 0;
+                        new_muted = TRUE;
+                } else {
+                        new_vol = old_vol - norm_vol_step;
+                }
+                break;
+        case VOLUME_UP_KEY:
+                new_muted = FALSE;
+                /* When coming out of mute only increase the volume if it was 0 */
+                if (!old_muted || old_vol == 0)
+                        new_vol = MIN (old_vol + norm_vol_step, priv->max_volume);
+                break;
+        }
+
+        if (old_muted != new_muted) {
+                gvc_mixer_stream_change_is_muted (stream, new_muted);
+                sound_changed = TRUE;
+        }
+
+        if (old_vol != new_vol) {
+                if (gvc_mixer_stream_set_volume (stream, new_vol) != FALSE) {
+                        gvc_mixer_stream_push_volume (stream);
+                        sound_changed = TRUE;
+                }
+        }
+
+        show_volume_osd (manager, stream, new_vol, new_muted, sound_changed,
+                         flags & SOUND_ACTION_FLAG_IS_QUIET);
+}
+
+static void
+update_default_sink (GsdMediaKeysManager *manager,
+                     gboolean             warn)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GvcMixerStream *stream;
+
+        stream = gvc_mixer_control_get_default_sink (priv->volume);
+        if (stream == priv->sink)
+                return;
+
+        g_clear_object (&priv->sink);
+
+        if (stream != NULL) {
+                priv->sink = g_object_ref (stream);
+        } else {
+                if (warn)
+                        g_warning ("Unable to get default sink");
+                else
+                        g_debug ("Unable to get default sink");
+        }
+}
+
+static void
+update_default_source (GsdMediaKeysManager *manager,
+                       gboolean             warn)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GvcMixerStream *stream;
+
+        stream = gvc_mixer_control_get_default_source (priv->volume);
+        if (stream == priv->source)
+                return;
+
+        g_clear_object (&priv->source);
+
+        if (stream != NULL) {
+                priv->source = g_object_ref (stream);
+        } else {
+                if (warn)
+                        g_warning ("Unable to get default source");
+                else
+                        g_debug ("Unable to get default source");
+        }
+}
+
+static void
+on_control_state_changed (GvcMixerControl     *control,
+                          GvcMixerControlState new_state,
+                          GsdMediaKeysManager *manager)
+{
+        update_default_sink (manager, new_state == GVC_STATE_READY);
+        update_default_source (manager, new_state == GVC_STATE_READY);
+}
+
+static void
+on_control_default_sink_changed (GvcMixerControl     *control,
+                                 guint                id,
+                                 GsdMediaKeysManager *manager)
+{
+        update_default_sink (manager, TRUE);
+}
+
+static void
+on_control_default_source_changed (GvcMixerControl     *control,
+                                   guint                id,
+                                   GsdMediaKeysManager *manager)
+{
+        update_default_source (manager, TRUE);
+}
+
+#if HAVE_GUDEV
+static gboolean
+remove_stream (gpointer key,
+              gpointer value,
+              gpointer id)
+{
+       if (GPOINTER_TO_UINT (value) == GPOINTER_TO_UINT (id))
+               return TRUE;
+       return FALSE;
+}
+#endif /* HAVE_GUDEV */
+
+static void
+on_control_stream_removed (GvcMixerControl     *control,
+                           guint                id,
+                           GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        if (priv->sink != NULL) {
+               if (gvc_mixer_stream_get_id (priv->sink) == id)
+                       g_clear_object (&priv->sink);
+        }
+        if (priv->source != NULL) {
+               if (gvc_mixer_stream_get_id (priv->source) == id)
+                       g_clear_object (&priv->source);
+        }
+
+#if HAVE_GUDEV
+       g_hash_table_foreach_remove (priv->streams, (GHRFunc) remove_stream, GUINT_TO_POINTER (id));
+#endif
+}
+
+static gboolean
+do_multimedia_player_action (GsdMediaKeysManager *manager,
+                             const char          *key)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        g_return_val_if_fail (key != NULL, FALSE);
+
+        g_debug ("Media key '%s' pressed", key);
+
+        if (mpris_controller_get_has_active_player (priv->mpris_controller)) {
+                if (g_str_equal (key, "Rewind")) {
+                        if (mpris_controller_seek (priv->mpris_controller, REWIND_USEC))
+                                return TRUE;
+                } else if (g_str_equal (key, "FastForward")) {
+                        if (mpris_controller_seek (priv->mpris_controller, FASTFORWARD_USEC))
+                                return TRUE;
+                } else if (g_str_equal (key, "Repeat")) {
+                        if (mpris_controller_toggle (priv->mpris_controller, "LoopStatus"))
+                                return TRUE;
+                } else if (g_str_equal (key, "Shuffle")) {
+                        if (mpris_controller_toggle (priv->mpris_controller, "Shuffle"))
+                                return TRUE;
+                } else if (mpris_controller_key (priv->mpris_controller, key)) {
+                        return TRUE;
+                }
+        }
+
+       /* Popup a dialog with an (/) icon */
+       show_osd (manager, "action-unavailable-symbolic", NULL, -1, NULL);
+       return TRUE;
+}
+
+static void
+sensor_properties_changed (GDBusProxy *proxy,
+                           GVariant   *changed_properties,
+                           GStrv       invalidated_properties,
+                           gpointer    user_data)
+{
+        GsdMediaKeysManager *manager = user_data;
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GVariant *v;
+        GVariantDict dict;
+
+        if (priv->iio_sensor_proxy == NULL)
+                return;
+
+        if (changed_properties)
+                g_variant_dict_init (&dict, changed_properties);
+
+        if (changed_properties == NULL ||
+            g_variant_dict_contains (&dict, "HasAccelerometer")) {
+                v = g_dbus_proxy_get_cached_property (priv->iio_sensor_proxy,
+                                                      "HasAccelerometer");
+                if (v == NULL) {
+                        g_debug ("Couldn't fetch HasAccelerometer property");
+                        return;
+                }
+                priv->has_accel = g_variant_get_boolean (v);
+                g_variant_unref (v);
+        }
+}
+
+static void
+iio_sensor_appeared_cb (GDBusConnection *connection,
+                        const gchar     *name,
+                        const gchar     *name_owner,
+                        gpointer         user_data)
+{
+        GsdMediaKeysManager *manager = user_data;
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GError *error = NULL;
+
+        priv->iio_sensor_proxy = g_dbus_proxy_new_sync (connection,
+                                                        G_DBUS_PROXY_FLAGS_NONE,
+                                                        NULL,
+                                                        "net.hadess.SensorProxy",
+                                                        "/net/hadess/SensorProxy",
+                                                        "net.hadess.SensorProxy",
+                                                        NULL,
+                                                        &error);
+
+        if (priv->iio_sensor_proxy == NULL) {
+                g_warning ("Failed to access net.hadess.SensorProxy after it appeared");
+                return;
+        }
+        g_signal_connect (G_OBJECT (priv->iio_sensor_proxy),
+                          "g-properties-changed",
+                          G_CALLBACK (sensor_properties_changed), manager);
+
+        sensor_properties_changed (priv->iio_sensor_proxy,
+                                   NULL, NULL, manager);
+}
+
+static void
+iio_sensor_disappeared_cb (GDBusConnection *connection,
+                           const gchar     *name,
+                           gpointer         user_data)
+{
+        GsdMediaKeysManager *manager = user_data;
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        g_clear_object (&priv->iio_sensor_proxy);
+        priv->has_accel = FALSE;
+}
+
+static void
+do_video_rotate_lock_action (GsdMediaKeysManager *manager,
+                             gint64               timestamp)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GSettings *settings;
+        gboolean locked;
+
+        if (!priv->has_accel) {
+                g_debug ("Ignoring attempt to set orientation lock: no accelerometer");
+                return;
+        }
+
+        settings = g_settings_new ("org.gnome.settings-daemon.peripherals.touchscreen");
+        locked = !g_settings_get_boolean (settings, "orientation-lock");
+        g_settings_set_boolean (settings, "orientation-lock", locked);
+        g_object_unref (settings);
+
+        show_osd (manager, locked ? "rotation-locked-symbolic"
+                                  : "rotation-allowed-symbolic", NULL, -1, NULL);
+}
+
+static void
+do_toggle_accessibility_key (const char *key)
+{
+        GSettings *settings;
+        gboolean state;
+
+        settings = g_settings_new ("org.gnome.desktop.a11y.applications");
+        state = g_settings_get_boolean (settings, key);
+        g_settings_set_boolean (settings, key, !state);
+        g_object_unref (settings);
+}
+
+static void
+do_magnifier_action (GsdMediaKeysManager *manager)
+{
+        do_toggle_accessibility_key ("screen-magnifier-enabled");
+}
+
+static void
+do_screenreader_action (GsdMediaKeysManager *manager)
+{
+        do_toggle_accessibility_key ("screen-reader-enabled");
+}
+
+static void
+do_on_screen_keyboard_action (GsdMediaKeysManager *manager)
+{
+        do_toggle_accessibility_key ("screen-keyboard-enabled");
+}
+
+static void
+do_text_size_action (GsdMediaKeysManager *manager,
+                    MediaKeyType         type)
+{
+       GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+       gdouble factor, best, distance;
+       guint i;
+
+       /* Same values used in the Seeing tab of the Universal Access panel */
+       static gdouble factors[] = {
+               0.75,
+               1.0,
+               1.25,
+               1.5
+       };
+
+       /* Figure out the current DPI scaling factor */
+       factor = g_settings_get_double (priv->interface_settings, "text-scaling-factor");
+       factor += (type == INCREASE_TEXT_KEY ? 0.25 : -0.25);
+
+       /* Try to find a matching value */
+       distance = 1e6;
+       best = 1.0;
+       for (i = 0; i < G_N_ELEMENTS(factors); i++) {
+               gdouble d;
+               d = fabs (factor - factors[i]);
+               if (d < distance) {
+                       best = factors[i];
+                       distance = d;
+               }
+       }
+
+       if (best == 1.0)
+               g_settings_reset (priv->interface_settings, "text-scaling-factor");
+       else
+               g_settings_set_double (priv->interface_settings, "text-scaling-factor", best);
+}
+
+static void
+do_magnifier_zoom_action (GsdMediaKeysManager *manager,
+                         MediaKeyType         type)
+{
+       GSettings *settings;
+       gdouble offset, value;
+
+       if (type == MAGNIFIER_ZOOM_IN_KEY)
+               offset = 1.0;
+       else
+               offset = -1.0;
+
+       settings = g_settings_new ("org.gnome.desktop.a11y.magnifier");
+       value = g_settings_get_double (settings, "mag-factor");
+       value += offset;
+       value = roundl (value);
+       g_settings_set_double (settings, "mag-factor", value);
+       g_object_unref (settings);
+}
+
+static void
+do_toggle_contrast_action (GsdMediaKeysManager *manager)
+{
+       GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+       gboolean high_contrast;
+       char *theme;
+
+       /* Are we using HighContrast now? */
+       theme = g_settings_get_string (priv->interface_settings, "gtk-theme");
+       high_contrast = g_str_equal (theme, HIGH_CONTRAST);
+       g_free (theme);
+
+       if (high_contrast != FALSE) {
+               if (priv->gtk_theme == NULL)
+                       g_settings_reset (priv->interface_settings, "gtk-theme");
+               else
+                       g_settings_set (priv->interface_settings, "gtk-theme", priv->gtk_theme);
+               g_settings_set (priv->interface_settings, "icon-theme", priv->icon_theme);
+       } else {
+               g_settings_set (priv->interface_settings, "gtk-theme", HIGH_CONTRAST);
+               g_settings_set (priv->interface_settings, "icon-theme", HIGH_CONTRAST);
+       }
+}
+
+static void
+power_action (GsdMediaKeysManager *manager,
+              const char          *action,
+              gboolean             allow_interaction)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        g_dbus_proxy_call (priv->logind_proxy,
+                           action,
+                           g_variant_new ("(b)", allow_interaction),
+                           G_DBUS_CALL_FLAGS_NONE,
+                           G_MAXINT,
+                           priv->bus_cancellable,
+                           NULL, NULL);
+}
+
+static void
+do_config_power_action (GsdMediaKeysManager *manager,
+                        GsdPowerActionType   action_type,
+                        gboolean             in_lock_screen)
+{
+        switch (action_type) {
+        case GSD_POWER_ACTION_SUSPEND:
+                power_action (manager, "Suspend", !in_lock_screen);
+                break;
+        case GSD_POWER_ACTION_INTERACTIVE:
+                if (!in_lock_screen)
+                        gnome_session_shutdown (manager);
+                break;
+        case GSD_POWER_ACTION_SHUTDOWN:
+                power_action (manager, "PowerOff", !in_lock_screen);
+                break;
+        case GSD_POWER_ACTION_HIBERNATE:
+                power_action (manager, "Hibernate", !in_lock_screen);
+                break;
+        case GSD_POWER_ACTION_BLANK:
+        case GSD_POWER_ACTION_LOGOUT:
+        case GSD_POWER_ACTION_NOTHING:
+                /* these actions cannot be handled by media-keys and
+                 * are not used in this context */
+                break;
+        }
+}
+
+static gboolean
+supports_power_action (GsdMediaKeysManager *manager,
+                       GsdPowerActionType   action_type)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        const char *method_name = NULL;
+        g_autoptr(GVariant) variant = NULL;
+        const char *reply;
+        gboolean result = FALSE;
+
+        switch (action_type) {
+        case GSD_POWER_ACTION_SUSPEND:
+                method_name = "CanSuspend";
+                break;
+        case GSD_POWER_ACTION_SHUTDOWN:
+                method_name = "CanPowerOff";
+                break;
+        case GSD_POWER_ACTION_HIBERNATE:
+                method_name = "CanHibernate";
+                break;
+        case GSD_POWER_ACTION_INTERACTIVE:
+        case GSD_POWER_ACTION_BLANK:
+        case GSD_POWER_ACTION_LOGOUT:
+        case GSD_POWER_ACTION_NOTHING:
+                break;
+        }
+
+        if (method_name == NULL)
+                return FALSE;
+
+        variant = g_dbus_proxy_call_sync (priv->logind_proxy,
+                                          method_name,
+                                          NULL,
+                                          G_DBUS_CALL_FLAGS_NONE,
+                                          -1,
+                                          priv->bus_cancellable,
+                                          NULL);
+
+        if (variant == NULL)
+                return FALSE;
+
+        g_variant_get (variant, "(&s)", &reply);
+        if (g_strcmp0 (reply, "yes") == 0)
+                result = TRUE;
+
+        return result;
+}
+
+static void
+do_config_power_button_action (GsdMediaKeysManager *manager,
+                               gboolean             in_lock_screen)
+{
+       GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GsdPowerButtonActionType action_type;
+        GsdPowerActionType action;
+
+        if (priv->power_button_disabled)
+                return;
+
+        /* Always power off VMs when power off is pressed in the menus */
+        if (g_strcmp0 (priv->chassis_type, "vm") == 0) {
+                power_action (manager, "PowerOff", !in_lock_screen);
+                return;
+        }
+
+        action_type = g_settings_get_enum (priv->power_settings, "power-button-action");
+        switch (action_type) {
+        case GSD_POWER_BUTTON_ACTION_SUSPEND:
+                action = GSD_POWER_ACTION_SUSPEND;
+                break;
+        case GSD_POWER_BUTTON_ACTION_HIBERNATE:
+                action = GSD_POWER_ACTION_HIBERNATE;
+                break;
+        case GSD_POWER_BUTTON_ACTION_INTERACTIVE:
+                action = GSD_POWER_ACTION_INTERACTIVE;
+                break;
+        default:
+                g_warn_if_reached ();
+                G_GNUC_FALLTHROUGH;
+        case GSD_POWER_BUTTON_ACTION_NOTHING:
+                /* do nothing */
+                return;
+        }
+
+        if (action != GSD_POWER_ACTION_INTERACTIVE && !supports_power_action (manager, action))
+                action = GSD_POWER_ACTION_INTERACTIVE;
+
+        do_config_power_action (manager, action, in_lock_screen);
+}
+
+static void
+update_brightness_cb (GObject             *source_object,
+                      GAsyncResult        *res,
+                      gpointer             user_data)
+{
+        GError *error = NULL;
+        int percentage;
+        GVariant *variant;
+        GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        const char *icon, *debug;
+        char *connector = NULL;
+
+        /* update the dialog with the new value */
+        if (G_DBUS_PROXY (source_object) == priv->power_keyboard_proxy) {
+                debug = "keyboard";
+        } else {
+                debug = "screen";
+        }
+
+        variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+                                        res, &error);
+        if (variant == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to set new %s percentage: %s",
+                                   debug, error->message);
+                g_error_free (error);
+                return;
+        }
+
+        /* update the dialog with the new value */
+        if (G_DBUS_PROXY (source_object) == priv->power_keyboard_proxy) {
+                icon = "keyboard-brightness-symbolic";
+                g_variant_get (variant, "(i)", &percentage);
+        } else {
+                icon = "display-brightness-symbolic";
+                g_variant_get (variant, "(i&s)", &percentage, &connector);
+        }
+
+        show_osd (manager, icon, NULL, (double) percentage / 100.0, connector);
+        g_variant_unref (variant);
+}
+
+static void
+do_brightness_action (GsdMediaKeysManager *manager,
+                      MediaKeyType type)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        const char *cmd;
+        GDBusProxy *proxy;
+
+        switch (type) {
+        case KEYBOARD_BRIGHTNESS_UP_KEY:
+        case KEYBOARD_BRIGHTNESS_DOWN_KEY:
+        case KEYBOARD_BRIGHTNESS_TOGGLE_KEY:
+                proxy = priv->power_keyboard_proxy;
+                break;
+        case SCREEN_BRIGHTNESS_UP_KEY:
+        case SCREEN_BRIGHTNESS_DOWN_KEY:
+        case SCREEN_BRIGHTNESS_CYCLE_KEY:
+                proxy = priv->power_screen_proxy;
+                break;
+        default:
+                g_assert_not_reached ();
+        }
+
+        if (priv->connection == NULL ||
+            proxy == NULL) {
+                g_warning ("No existing D-Bus connection trying to handle power keys");
+                return;
+        }
+
+        switch (type) {
+        case KEYBOARD_BRIGHTNESS_UP_KEY:
+        case SCREEN_BRIGHTNESS_UP_KEY:
+                cmd = "StepUp";
+                break;
+        case KEYBOARD_BRIGHTNESS_DOWN_KEY:
+        case SCREEN_BRIGHTNESS_DOWN_KEY:
+                cmd = "StepDown";
+                break;
+        case KEYBOARD_BRIGHTNESS_TOGGLE_KEY:
+                cmd = "Toggle";
+                break;
+        case SCREEN_BRIGHTNESS_CYCLE_KEY:
+                cmd = "Cycle";
+                break;
+        default:
+                g_assert_not_reached ();
+        }
+
+        /* call into the power plugin */
+        g_dbus_proxy_call (proxy,
+                           cmd,
+                           NULL,
+                           G_DBUS_CALL_FLAGS_NONE,
+                           -1,
+                           NULL,
+                           update_brightness_cb,
+                           manager);
+}
+
+static void
+do_battery_action (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        gdouble percentage;
+        UpDeviceKind kind;
+        gchar *icon_name;
+
+        g_return_if_fail (priv->composite_device != NULL);
+
+        g_object_get (priv->composite_device,
+                      "kind", &kind,
+                      "icon-name", &icon_name,
+                      "percentage", &percentage,
+                      NULL);
+
+        if (kind == UP_DEVICE_KIND_UPS || kind == UP_DEVICE_KIND_BATTERY) {
+                g_debug ("showing battery level OSD");
+                show_osd (manager, icon_name, NULL, (double) percentage / 100.0, NULL);
+        }
+
+        g_free (icon_name);
+}
+
+static gboolean
+get_rfkill_property (GsdMediaKeysManager *manager,
+                     const char          *property)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GVariant *v;
+        gboolean ret;
+
+        v = g_dbus_proxy_get_cached_property (priv->rfkill_proxy, property);
+        if (!v)
+                return FALSE;
+        ret = g_variant_get_boolean (v);
+        g_variant_unref (v);
+
+        return ret;
+}
+
+typedef struct {
+        GsdMediaKeysManager *manager;
+        char *property;
+        gboolean bluetooth;
+        gboolean target_state;
+} RfkillData;
+
+static void
+set_rfkill_complete (GObject      *object,
+                     GAsyncResult *result,
+                     gpointer      user_data)
+{
+        GError *error = NULL;
+        GVariant *variant;
+        RfkillData *data = user_data;
+
+        variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), result, &error);
+
+        if (variant == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to set '%s' property: %s", data->property, error->message);
+                g_error_free (error);
+                goto out;
+        }
+        g_variant_unref (variant);
+
+        g_debug ("Finished changing rfkill, property %s is now %s",
+                 data->property, data->target_state ? "true" : "false");
+
+        if (data->bluetooth) {
+                if (data->target_state)
+                        show_osd (data->manager, "bluetooth-disabled-symbolic",
+                                  _("Bluetooth disabled"), -1, NULL);
+                else
+                        show_osd (data->manager, "bluetooth-active-symbolic",
+                                  _("Bluetooth enabled"), -1, NULL);
+        } else {
+                if (data->target_state)
+                        show_osd (data->manager, "airplane-mode-symbolic",
+                                  _("Airplane mode enabled"), -1, NULL);
+                else
+                        show_osd (data->manager, "network-wireless-signal-excellent-symbolic",
+                                  _("Airplane mode disabled"), -1, NULL);
+        }
+
+out:
+        g_free (data->property);
+        g_free (data);
+}
+
+static void
+do_rfkill_action (GsdMediaKeysManager *manager,
+                  gboolean             bluetooth)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        const char *has_mode, *hw_mode, *mode;
+        gboolean new_state;
+        guint64 current_time;
+        RfkillData *data;
+
+        has_mode = bluetooth ? "BluetoothHasAirplaneMode" : "HasAirplaneMode";
+        hw_mode = bluetooth ? "BluetoothHardwareAirplaneMode" : "HardwareAirplaneMode";
+        mode = bluetooth ? "BluetoothAirplaneMode" : "AirplaneMode";
+
+        if (priv->rfkill_proxy == NULL)
+                return;
+
+        /* Some hardwares can generate multiple rfkill events from different
+         * drivers, on a single hotkey press. Only process the first event and
+         * debounce the others */
+        current_time = g_get_monotonic_time ();
+        if (current_time - priv->rfkill_last_time < G_USEC_PER_SEC)
+                return;
+
+        priv->rfkill_last_time = current_time;
+
+        if (get_rfkill_property (manager, has_mode) == FALSE)
+                return;
+
+        if (get_rfkill_property (manager, hw_mode)) {
+                show_osd (manager, "airplane-mode-symbolic",
+                          _("Hardware Airplane Mode"), -1, NULL);
+                return;
+        }
+
+        new_state = !get_rfkill_property (manager, mode);
+        data = g_new0 (RfkillData, 1);
+        data->manager = manager;
+        data->property = g_strdup (mode);
+        data->bluetooth = bluetooth;
+        data->target_state = new_state;
+        g_dbus_proxy_call (priv->rfkill_proxy,
+                           "org.freedesktop.DBus.Properties.Set",
+                           g_variant_new ("(ssv)",
+                                          "org.gnome.SettingsDaemon.Rfkill",
+                                          data->property,
+                                          g_variant_new_boolean (new_state)),
+                           G_DBUS_CALL_FLAGS_NONE, -1,
+                           priv->rfkill_cancellable,
+                           set_rfkill_complete, data);
+
+        g_debug ("Setting rfkill property %s to %s",
+                 data->property, new_state ? "true" : "false");
+}
+
+static void
+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, "org.gnome.Settings.desktop", NULL, timestamp);
+                break;
+        case PLAY_KEY:
+                return do_multimedia_player_action (manager, "Play");
+        case PAUSE_KEY:
+                return do_multimedia_player_action (manager, "Pause");
+        case STOP_KEY:
+                return do_multimedia_player_action (manager, "Stop");
+        case PREVIOUS_KEY:
+                return do_multimedia_player_action (manager, "Previous");
+        case NEXT_KEY:
+                return do_multimedia_player_action (manager, "Next");
+        case REWIND_KEY:
+                return do_multimedia_player_action (manager, "Rewind");
+        case FORWARD_KEY:
+                return do_multimedia_player_action (manager, "FastForward");
+        case REPEAT_KEY:
+                return do_multimedia_player_action (manager, "Repeat");
+        case RANDOM_KEY:
+                return do_multimedia_player_action (manager, "Shuffle");
+        case ROTATE_VIDEO_LOCK_KEY:
+                do_video_rotate_lock_action (manager, timestamp);
+                break;
+        case MAGNIFIER_KEY:
+                do_magnifier_action (manager);
+                break;
+        case SCREENREADER_KEY:
+                do_screenreader_action (manager);
+                break;
+        case ON_SCREEN_KEYBOARD_KEY:
+                do_on_screen_keyboard_action (manager);
+                break;
+       case INCREASE_TEXT_KEY:
+       case DECREASE_TEXT_KEY:
+               do_text_size_action (manager, type);
+               break;
+       case MAGNIFIER_ZOOM_IN_KEY:
+       case MAGNIFIER_ZOOM_OUT_KEY:
+               do_magnifier_zoom_action (manager, type);
+               break;
+       case TOGGLE_CONTRAST_KEY:
+               do_toggle_contrast_action (manager);
+               break;
+        case POWER_KEY:
+                do_config_power_button_action (manager, power_action_noninteractive);
+                break;
+        case SUSPEND_KEY:
+                do_config_power_action (manager, GSD_POWER_ACTION_SUSPEND, power_action_noninteractive);
+                break;
+        case HIBERNATE_KEY:
+                do_config_power_action (manager, GSD_POWER_ACTION_HIBERNATE, power_action_noninteractive);
+                break;
+        case SCREEN_BRIGHTNESS_UP_KEY:
+        case SCREEN_BRIGHTNESS_DOWN_KEY:
+        case SCREEN_BRIGHTNESS_CYCLE_KEY:
+        case KEYBOARD_BRIGHTNESS_UP_KEY:
+        case KEYBOARD_BRIGHTNESS_DOWN_KEY:
+        case KEYBOARD_BRIGHTNESS_TOGGLE_KEY:
+                do_brightness_action (manager, type);
+                break;
+        case BATTERY_KEY:
+                do_battery_action (manager);
+                break;
+        case RFKILL_KEY:
+                do_rfkill_action (manager, FALSE);
+                break;
+        case BLUETOOTH_RFKILL_KEY:
+                do_rfkill_action (manager, TRUE);
+                break;
+        /* Note, no default so compiler catches missing keys */
+        case CUSTOM_KEY:
+                g_assert_not_reached ();
+        }
+
+        return FALSE;
+}
+
+static void
+on_accelerator_activated (ShellKeyGrabber     *grabber,
+                          guint                accel_id,
+                          GVariant            *parameters,
+                          GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GVariantDict dict;
+        guint i;
+        guint deviceid;
+        gchar *device_node;
+        guint timestamp;
+        guint mode;
+
+        g_variant_dict_init (&dict, parameters);
+
+        if (!g_variant_dict_lookup (&dict, "device-id", "u", &deviceid))
+              deviceid = 0;
+        if (!g_variant_dict_lookup (&dict, "device-node", "s", &device_node))
+              device_node = NULL;
+        if (!g_variant_dict_lookup (&dict, "timestamp", "u", &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->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_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);
+        g_clear_object (&priv->shell_proxy);
+
+        if (priv->audio_selection_watch_id)
+                g_bus_unwatch_name (priv->audio_selection_watch_id);
+        priv->audio_selection_watch_id = 0;
+        clear_audio_selection (manager);
+}
+
+static void
+inhibit_suspend_done (GObject      *source,
+                      GAsyncResult *result,
+                      gpointer      user_data)
+{
+        GDBusProxy *proxy = G_DBUS_PROXY (source);
+        GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GError *error = NULL;
+        GVariant *res;
+        GUnixFDList *fd_list = NULL;
+        gint idx;
+
+        res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error);
+        if (res == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Unable to inhibit suspend: %s", error->message);
+                g_error_free (error);
+        } else {
+                g_variant_get (res, "(h)", &idx);
+                priv->inhibit_suspend_fd = g_unix_fd_list_get (fd_list, idx, &error);
+                if (priv->inhibit_suspend_fd == -1) {
+                        g_warning ("Failed to receive system suspend inhibitor fd: %s", error->message);
+                        g_error_free (error);
+                }
+                g_debug ("System suspend inhibitor fd is %d", priv->inhibit_suspend_fd);
+                g_object_unref (fd_list);
+                g_variant_unref (res);
+        }
+}
+
+/* We take a delay inhibitor here, which causes logind to send a PrepareForSleep
+ * signal, so that we can set power_button_disabled on suspend.
+ */
+static void
+inhibit_suspend (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        if (priv->inhibit_suspend_taken) {
+                g_debug ("already inhibited suspend");
+                return;
+        }
+        g_debug ("Adding suspend delay inhibitor");
+        priv->inhibit_suspend_taken = TRUE;
+        g_dbus_proxy_call_with_unix_fd_list (priv->logind_proxy,
+                                             "Inhibit",
+                                             g_variant_new ("(ssss)",
+                                                            "sleep",
+                                                            g_get_user_name (),
+                                                            "GNOME handling keypresses",
+                                                            "delay"),
+                                             0,
+                                             G_MAXINT,
+                                             NULL,
+                                             NULL,
+                                             inhibit_suspend_done,
+                                             manager);
+}
+
+static void
+uninhibit_suspend (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        if (priv->inhibit_suspend_fd == -1) {
+                g_debug ("no suspend delay inhibitor");
+                return;
+        }
+        g_debug ("Removing suspend delay inhibitor");
+        close (priv->inhibit_suspend_fd);
+        priv->inhibit_suspend_fd = -1;
+        priv->inhibit_suspend_taken = FALSE;
+}
+
+static gboolean
+reenable_power_button_timer_cb (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        priv->power_button_disabled = FALSE;
+        /* This is a one shot timer. */
+        priv->reenable_power_button_timer_id = 0;
+        return G_SOURCE_REMOVE;
+}
+
+static void
+setup_reenable_power_button_timer (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        if (priv->reenable_power_button_timer_id != 0)
+                return;
+
+        priv->reenable_power_button_timer_id =
+                g_timeout_add (GSD_REENABLE_POWER_BUTTON_DELAY,
+                               (GSourceFunc) reenable_power_button_timer_cb,
+                               manager);
+        g_source_set_name_by_id (priv->reenable_power_button_timer_id,
+                                 "[GsdMediaKeysManager] Reenable power button timer");
+}
+
+static void
+stop_reenable_power_button_timer (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        if (priv->reenable_power_button_timer_id == 0)
+                return;
+
+        g_source_remove (priv->reenable_power_button_timer_id);
+        priv->reenable_power_button_timer_id = 0;
+}
+
+static void
+logind_proxy_signal_cb (GDBusProxy  *proxy,
+                        const gchar *sender_name,
+                        const gchar *signal_name,
+                        GVariant    *parameters,
+                        gpointer     user_data)
+{
+        GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        gboolean is_about_to_suspend;
+
+        if (g_strcmp0 (signal_name, "PrepareForSleep") != 0)
+                return;
+        g_variant_get (parameters, "(b)", &is_about_to_suspend);
+        if (is_about_to_suspend) {
+                /* Some devices send a power-button press on resume when woken
+                 * up with the power-button, suppress this, to avoid immediate
+                 * re-suspend. */
+                stop_reenable_power_button_timer (manager);
+                priv->power_button_disabled = TRUE;
+                uninhibit_suspend (manager);
+        } else {
+                inhibit_suspend (manager);
+                /* Re-enable power-button handling (after a small delay) */
+                setup_reenable_power_button_timer (manager);
+        }
+}
+
+static void
+gsd_media_keys_manager_class_init (GsdMediaKeysManagerClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_media_keys_manager_finalize;
+}
+
+static void
+inhibit_done (GObject      *source,
+              GAsyncResult *result,
+              gpointer      user_data)
+{
+        GDBusProxy *proxy = G_DBUS_PROXY (source);
+        GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GError *error = NULL;
+        GVariant *res;
+        GUnixFDList *fd_list = NULL;
+        gint idx;
+
+        res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error);
+        if (res == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Unable to inhibit keypresses: %s", error->message);
+                g_error_free (error);
+        } else {
+                g_variant_get (res, "(h)", &idx);
+                priv->inhibit_keys_fd = g_unix_fd_list_get (fd_list, idx, &error);
+                if (priv->inhibit_keys_fd == -1) {
+                        g_warning ("Failed to receive system inhibitor fd: %s", error->message);
+                        g_error_free (error);
+                }
+                g_debug ("System inhibitor fd is %d", priv->inhibit_keys_fd);
+                g_object_unref (fd_list);
+                g_variant_unref (res);
+        }
+}
+
+static void
+gsd_media_keys_manager_init (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GError *error;
+        GDBusConnection *bus;
+
+        error = NULL;
+        priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+        if (bus == NULL) {
+                g_warning ("Failed to connect to system bus: %s",
+                           error->message);
+                g_error_free (error);
+                return;
+        }
+
+        priv->logind_proxy =
+                g_dbus_proxy_new_sync (bus,
+                                       0,
+                                       NULL,
+                                       SYSTEMD_DBUS_NAME,
+                                       SYSTEMD_DBUS_PATH,
+                                       SYSTEMD_DBUS_INTERFACE,
+                                       NULL,
+                                       &error);
+
+        if (priv->logind_proxy == NULL) {
+                g_warning ("Failed to connect to systemd: %s",
+                           error->message);
+                g_error_free (error);
+        }
+
+        g_object_unref (bus);
+
+        g_debug ("Adding system inhibitors for power keys");
+        priv->inhibit_keys_fd = -1;
+        g_dbus_proxy_call_with_unix_fd_list (priv->logind_proxy,
+                                             "Inhibit",
+                                             g_variant_new ("(ssss)",
+                                                            
"handle-power-key:handle-suspend-key:handle-hibernate-key",
+                                                            g_get_user_name (),
+                                                            "GNOME handling keypresses",
+                                                            "block"),
+                                             0,
+                                             G_MAXINT,
+                                             NULL,
+                                             NULL,
+                                             inhibit_done,
+                                             manager);
+
+        g_debug ("Adding delay inhibitor for suspend");
+        priv->inhibit_suspend_fd = -1;
+        g_signal_connect (priv->logind_proxy, "g-signal",
+                          G_CALLBACK (logind_proxy_signal_cb),
+                          manager);
+        inhibit_suspend (manager);
+}
+
+static void
+gsd_media_keys_manager_finalize (GObject *object)
+{
+        GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (object);
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        gsd_media_keys_manager_stop (manager);
+
+        if (priv->inhibit_keys_fd != -1)
+                close (priv->inhibit_keys_fd);
+
+        g_clear_object (&priv->logind_proxy);
+        g_clear_object (&priv->screen_saver_proxy);
+
+        G_OBJECT_CLASS (gsd_media_keys_manager_parent_class)->finalize (object);
+}
+
+static void
+power_keyboard_proxy_signal_cb (GDBusProxy  *proxy,
+                       const gchar *sender_name,
+                       const gchar *signal_name,
+                       GVariant    *parameters,
+                       gpointer     user_data)
+{
+        GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+        gint brightness;
+        const gchar *source;
+
+        if (g_strcmp0 (signal_name, "BrightnessChanged") != 0)
+                return;
+
+        g_variant_get (parameters, "(i&s)", &brightness, &source);
+
+        /* For non "internal" changes we already show the osd when handling
+         * the hotkey causing the change. */
+        if (g_strcmp0 (source, "internal") != 0)
+                return;
+
+        show_osd (manager, "keyboard-brightness-symbolic", NULL, (double) brightness / 100.0, NULL);
+}
+
+static void
+power_ready_cb (GObject             *source_object,
+                GAsyncResult        *res,
+                GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GError *error = NULL;
+
+        priv->power_proxy = g_dbus_proxy_new_finish (res, &error);
+        if (priv->power_proxy == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to get proxy for power: %s",
+                                   error->message);
+                g_error_free (error);
+        }
+}
+
+static void
+power_screen_ready_cb (GObject             *source_object,
+                       GAsyncResult        *res,
+                       GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GError *error = NULL;
+
+        priv->power_screen_proxy = g_dbus_proxy_new_finish (res, &error);
+        if (priv->power_screen_proxy == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to get proxy for power (screen): %s",
+                                   error->message);
+                g_error_free (error);
+        }
+}
+
+static void
+power_keyboard_ready_cb (GObject             *source_object,
+                         GAsyncResult        *res,
+                         GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GError *error = NULL;
+
+        priv->power_keyboard_proxy = g_dbus_proxy_new_finish (res, &error);
+        if (priv->power_keyboard_proxy == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Failed to get proxy for power (keyboard): %s",
+                                   error->message);
+                g_error_free (error);
+        }
+
+        g_signal_connect (priv->power_keyboard_proxy, "g-signal",
+                          G_CALLBACK (power_keyboard_proxy_signal_cb),
+                          manager);
+}
+
+static void
+on_bus_gotten (GObject             *source_object,
+               GAsyncResult        *res,
+               GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+        GDBusConnection *connection;
+        GError *error = NULL;
+        UpClient *up_client;
+
+        connection = g_bus_get_finish (res, &error);
+        if (connection == NULL) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                        g_warning ("Could not get session bus: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+        priv->connection = connection;
+
+        g_dbus_proxy_new (priv->connection,
+                          G_DBUS_PROXY_FLAGS_NONE,
+                          NULL,
+                          GSD_DBUS_NAME ".Power",
+                          GSD_DBUS_PATH "/Power",
+                          GSD_DBUS_BASE_INTERFACE ".Power",
+                          NULL,
+                          (GAsyncReadyCallback) power_ready_cb,
+                          manager);
+
+        g_dbus_proxy_new (priv->connection,
+                          G_DBUS_PROXY_FLAGS_NONE,
+                          NULL,
+                          GSD_DBUS_NAME ".Power",
+                          GSD_DBUS_PATH "/Power",
+                          GSD_DBUS_BASE_INTERFACE ".Power.Screen",
+                          NULL,
+                          (GAsyncReadyCallback) power_screen_ready_cb,
+                          manager);
+
+        g_dbus_proxy_new (priv->connection,
+                          G_DBUS_PROXY_FLAGS_NONE,
+                          NULL,
+                          GSD_DBUS_NAME ".Power",
+                          GSD_DBUS_PATH "/Power",
+                          GSD_DBUS_BASE_INTERFACE ".Power.Keyboard",
+                          NULL,
+                          (GAsyncReadyCallback) power_keyboard_ready_cb,
+                          manager);
+
+        up_client = up_client_new ();
+        priv->composite_device = up_client_get_display_device (up_client);
+        g_object_unref (up_client);
+}
+
+static void
+register_manager (GsdMediaKeysManager *manager)
+{
+        GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+        priv->bus_cancellable = g_cancellable_new ();
+        g_bus_get (G_BUS_TYPE_SESSION,
+                   priv->bus_cancellable,
+                   (GAsyncReadyCallback) on_bus_gotten,
+                   manager);
+}
+
+GsdMediaKeysManager *
+gsd_media_keys_manager_new (void)
+{
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_MEDIA_KEYS_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+        }
+
+        return GSD_MEDIA_KEYS_MANAGER (manager_object);
+}
diff --git a/daemons/media-keys/gsd-media-keys-manager.h b/daemons/media-keys/gsd-media-keys-manager.h
new file mode 100644
index 0000000..d7042b8
--- /dev/null
+++ b/daemons/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/daemons/media-keys/gsd-screenshot-utils.c b/daemons/media-keys/gsd-screenshot-utils.c
new file mode 100644
index 0000000..bf792d9
--- /dev/null
+++ b/daemons/media-keys/gsd-screenshot-utils.c
@@ -0,0 +1,286 @@
+/* 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 <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_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_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)
+    {
+      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_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)
+    {
+      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/daemons/media-keys/gsd-screenshot-utils.h b/daemons/media-keys/gsd-screenshot-utils.h
new file mode 100644
index 0000000..39ae136
--- /dev/null
+++ b/daemons/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/daemons/media-keys/media-keys.h b/daemons/media-keys/media-keys.h
new file mode 100644
index 0000000..cc4ea8e
--- /dev/null
+++ b/daemons/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/daemons/media-keys/shell-action-modes.h b/daemons/media-keys/shell-action-modes.h
new file mode 100644
index 0000000..f17a7a0
--- /dev/null
+++ b/daemons/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/daemons/media-keys/shortcuts-list.h b/daemons/media-keys/shortcuts-list.h
new file mode 100644
index 0000000..8ced72d
--- /dev/null
+++ b/daemons/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__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]