summaryrefslogtreecommitdiffstats
path: root/plugins/media-keys
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--plugins/media-keys/audio-selection-test.c263
-rw-r--r--plugins/media-keys/bus-watch-namespace.c347
-rw-r--r--plugins/media-keys/bus-watch-namespace.h34
-rw-r--r--plugins/media-keys/gsd-marshal.list1
-rw-r--r--plugins/media-keys/gsd-media-keys-manager.c3553
-rw-r--r--plugins/media-keys/gsd-media-keys-manager.h47
-rw-r--r--plugins/media-keys/main.c7
-rw-r--r--plugins/media-keys/media-keys.h79
-rw-r--r--plugins/media-keys/meson.build58
-rw-r--r--plugins/media-keys/mpris-controller.c440
-rw-r--r--plugins/media-keys/mpris-controller.h38
-rw-r--r--plugins/media-keys/org.gnome.ShellKeyGrabber.xml27
-rw-r--r--plugins/media-keys/shell-action-modes.h53
-rw-r--r--plugins/media-keys/shortcuts-list.h103
14 files changed, 5050 insertions, 0 deletions
diff --git a/plugins/media-keys/audio-selection-test.c b/plugins/media-keys/audio-selection-test.c
new file mode 100644
index 0000000..d06759f
--- /dev/null
+++ b/plugins/media-keys/audio-selection-test.c
@@ -0,0 +1,263 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2017 Bastien Nocera <hadess@hadess.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#define AUDIO_SELECTION_DBUS_NAME "org.gnome.Shell.AudioDeviceSelection"
+#define AUDIO_SELECTION_DBUS_PATH "/org/gnome/Shell/AudioDeviceSelection"
+#define AUDIO_SELECTION_DBUS_INTERFACE "org.gnome.Shell.AudioDeviceSelection"
+
+static guint audio_selection_watch_id;
+static guint audio_selection_signal_id;
+static GDBusConnection *audio_selection_conn;
+static gboolean audio_selection_requested;
+static GtkWidget *check_headphones, *check_headset, *check_micro;
+static GtkWidget *button, *label;
+
+/* Copy-paste from gvc-mixer-control.h */
+typedef enum
+{
+ GVC_HEADSET_PORT_CHOICE_NONE = 0,
+ GVC_HEADSET_PORT_CHOICE_HEADPHONES = 1 << 0,
+ GVC_HEADSET_PORT_CHOICE_HEADSET = 1 << 1,
+ GVC_HEADSET_PORT_CHOICE_MIC = 1 << 2
+} GvcHeadsetPortChoice;
+
+typedef struct {
+ GvcHeadsetPortChoice choice;
+ gchar *name;
+} AudioSelectionChoice;
+
+static AudioSelectionChoice audio_selection_choices[] = {
+ { GVC_HEADSET_PORT_CHOICE_HEADPHONES, "headphones" },
+ { GVC_HEADSET_PORT_CHOICE_HEADSET, "headset" },
+ { GVC_HEADSET_PORT_CHOICE_MIC, "microphone" },
+};
+
+static void
+audio_selection_done (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer data)
+{
+ const gchar *choice;
+
+ if (!audio_selection_requested)
+ return;
+
+ choice = NULL;
+ g_variant_get_child (parameters, 0, "&s", &choice);
+ if (!choice)
+ return;
+
+ gtk_label_set_text (GTK_LABEL (label), choice);
+
+ audio_selection_requested = FALSE;
+}
+
+static void
+audio_selection_needed (GvcHeadsetPortChoice choices)
+{
+ gchar *args[G_N_ELEMENTS (audio_selection_choices) + 1];
+ guint i, n;
+
+ if (!audio_selection_conn)
+ return;
+
+ n = 0;
+ for (i = 0; i < G_N_ELEMENTS (audio_selection_choices); ++i) {
+ if (choices & audio_selection_choices[i].choice)
+ args[n++] = audio_selection_choices[i].name;
+ }
+ args[n] = NULL;
+
+ audio_selection_requested = TRUE;
+ g_dbus_connection_call (audio_selection_conn,
+ AUDIO_SELECTION_DBUS_NAME,
+ AUDIO_SELECTION_DBUS_PATH,
+ AUDIO_SELECTION_DBUS_INTERFACE,
+ "Open",
+ g_variant_new ("(^as)", args),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, NULL, NULL);
+}
+
+static void
+update_ask_button (void)
+{
+ guint num_buttons = 0;
+ gboolean active = FALSE;
+
+ /* Need gnome-shell running */
+ if (audio_selection_conn == NULL)
+ goto end;
+
+ /* Need at least 2 choices */
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_headphones)))
+ num_buttons++;
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_headset)))
+ num_buttons++;
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_micro)))
+ num_buttons++;
+
+ if (num_buttons < 2)
+ goto end;
+
+ /* And no questions in flight */
+ if (audio_selection_requested)
+ goto end;
+
+ active = TRUE;
+
+end:
+ gtk_widget_set_sensitive (GTK_WIDGET (button), active);
+}
+
+static void
+audio_selection_appeared (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer data)
+{
+ audio_selection_conn = connection;
+ audio_selection_signal_id =
+ g_dbus_connection_signal_subscribe (connection,
+ AUDIO_SELECTION_DBUS_NAME,
+ AUDIO_SELECTION_DBUS_INTERFACE,
+ "DeviceSelected",
+ AUDIO_SELECTION_DBUS_PATH,
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ audio_selection_done,
+ NULL,
+ NULL);
+ update_ask_button ();
+}
+
+static void
+audio_selection_vanished (GDBusConnection *connection,
+ const gchar *name,
+ gpointer data)
+{
+ if (audio_selection_signal_id)
+ g_dbus_connection_signal_unsubscribe (audio_selection_conn,
+ audio_selection_signal_id);
+ audio_selection_signal_id = 0;
+ audio_selection_conn = NULL;
+ update_ask_button ();
+}
+
+static void
+watch_gnome_shell (void)
+{
+ audio_selection_watch_id =
+ g_bus_watch_name (G_BUS_TYPE_SESSION,
+ AUDIO_SELECTION_DBUS_NAME,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ audio_selection_appeared,
+ audio_selection_vanished,
+ NULL,
+ NULL);
+}
+
+static void
+check_buttons_changed (GtkToggleButton *button,
+ gpointer user_data)
+{
+ update_ask_button ();
+}
+
+static void
+button_clicked (GtkButton *button,
+ gpointer user_data)
+{
+ guint choices = 0;
+
+ gtk_label_set_text (GTK_LABEL (label), "");
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_headphones)))
+ choices |= GVC_HEADSET_PORT_CHOICE_HEADPHONES;
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_headset)))
+ choices |= GVC_HEADSET_PORT_CHOICE_HEADSET;
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_micro)))
+ choices |= GVC_HEADSET_PORT_CHOICE_MIC;
+
+ audio_selection_needed (choices);
+}
+
+static void
+setup_ui (void)
+{
+ GtkWidget *window;
+ GtkWidget *box;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ g_signal_connect (GTK_WINDOW (window), "delete-event",
+ G_CALLBACK (gtk_main_quit), NULL);
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
+ gtk_container_add (GTK_CONTAINER (window), box);
+
+ check_headphones = gtk_check_button_new_with_label ("Headphones");
+ g_signal_connect (check_headphones, "toggled",
+ G_CALLBACK (check_buttons_changed), NULL);
+ gtk_container_add (GTK_CONTAINER (box), check_headphones);
+
+ check_headset = gtk_check_button_new_with_label ("Headset");
+ g_signal_connect (check_headset, "toggled",
+ G_CALLBACK (check_buttons_changed), NULL);
+ gtk_container_add (GTK_CONTAINER (box), check_headset);
+
+ check_micro = gtk_check_button_new_with_label ("Microphone");
+ g_signal_connect (check_micro, "toggled",
+ G_CALLBACK (check_buttons_changed), NULL);
+ gtk_container_add (GTK_CONTAINER (box), check_micro);
+
+ button = gtk_button_new_with_label ("Ask!");
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (button_clicked), NULL);
+ gtk_container_add (GTK_CONTAINER (box), button);
+ gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE);
+
+ label = gtk_label_new ("");
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_headphones), TRUE);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_headset), TRUE);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_micro), TRUE);
+
+ gtk_widget_show_all (window);
+}
+
+int main (int argc, char **argv)
+{
+ gtk_init (&argc, &argv);
+
+ setup_ui ();
+ watch_gnome_shell ();
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/plugins/media-keys/bus-watch-namespace.c b/plugins/media-keys/bus-watch-namespace.c
new file mode 100644
index 0000000..a9349b4
--- /dev/null
+++ b/plugins/media-keys/bus-watch-namespace.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#include <gio/gio.h>
+#include <string.h>
+#include "bus-watch-namespace.h"
+
+typedef struct
+{
+ guint id;
+ gchar *name_space;
+ GBusNameAppearedCallback appeared_handler;
+ GBusNameVanishedCallback vanished_handler;
+ gpointer user_data;
+ GDestroyNotify user_data_destroy;
+
+ GDBusConnection *connection;
+ GCancellable *cancellable;
+ GHashTable *names;
+ guint subscription_id;
+} NamespaceWatcher;
+
+typedef struct
+{
+ NamespaceWatcher *watcher;
+ gchar *name;
+} GetNameOwnerData;
+
+static guint namespace_watcher_next_id;
+static GHashTable *namespace_watcher_watchers;
+
+static void
+namespace_watcher_stop (gpointer data)
+{
+ NamespaceWatcher *watcher = data;
+
+ g_cancellable_cancel (watcher->cancellable);
+ g_object_unref (watcher->cancellable);
+
+ if (watcher->subscription_id)
+ g_dbus_connection_signal_unsubscribe (watcher->connection, watcher->subscription_id);
+
+ if (watcher->vanished_handler)
+ {
+ GHashTableIter it;
+ const gchar *name;
+
+ g_hash_table_iter_init (&it, watcher->names);
+ while (g_hash_table_iter_next (&it, (gpointer *) &name, NULL))
+ watcher->vanished_handler (watcher->connection, name, watcher->user_data);
+ }
+
+ if (watcher->user_data_destroy)
+ watcher->user_data_destroy (watcher->user_data);
+
+ if (watcher->connection)
+ {
+ g_signal_handlers_disconnect_by_func (watcher->connection, namespace_watcher_stop, watcher);
+ g_object_unref (watcher->connection);
+ }
+
+ g_hash_table_unref (watcher->names);
+
+ g_hash_table_remove (namespace_watcher_watchers, GUINT_TO_POINTER (watcher->id));
+ if (g_hash_table_size (namespace_watcher_watchers) == 0)
+ g_clear_pointer (&namespace_watcher_watchers, g_hash_table_destroy);
+
+ g_free (watcher);
+}
+
+static void
+namespace_watcher_name_appeared (NamespaceWatcher *watcher,
+ const gchar *name,
+ const gchar *owner)
+{
+ /* There's a race between NameOwnerChanged signals arriving and the
+ * ListNames/GetNameOwner sequence returning, so this function might
+ * be called more than once for the same name. To ensure that
+ * appeared_handler is only called once for each name, it is only
+ * called when inserting the name into watcher->names (each name is
+ * only inserted once there).
+ */
+ if (g_hash_table_contains (watcher->names, name))
+ return;
+
+ g_hash_table_add (watcher->names, g_strdup (name));
+
+ if (watcher->appeared_handler)
+ watcher->appeared_handler (watcher->connection, name, owner, watcher->user_data);
+}
+
+static void
+namespace_watcher_name_vanished (NamespaceWatcher *watcher,
+ const gchar *name)
+{
+ if (g_hash_table_remove (watcher->names, name) && watcher->vanished_handler)
+ watcher->vanished_handler (watcher->connection, name, watcher->user_data);
+}
+
+static gboolean
+dbus_name_has_namespace (const gchar *name,
+ const gchar *name_space)
+{
+ gint len_name;
+ gint len_namespace;
+
+ len_name = strlen (name);
+ len_namespace = strlen (name_space);
+
+ if (len_name < len_namespace)
+ return FALSE;
+
+ if (memcmp (name_space, name, len_namespace) != 0)
+ return FALSE;
+
+ return len_namespace == len_name || name[len_namespace] == '.';
+}
+
+static void
+name_owner_changed (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ NamespaceWatcher *watcher = user_data;
+ const gchar *name;
+ const gchar *old_owner;
+ const gchar *new_owner;
+
+ g_variant_get (parameters, "(&s&s&s)", &name, &old_owner, &new_owner);
+
+ if (old_owner[0] != '\0')
+ namespace_watcher_name_vanished (watcher, name);
+
+ if (new_owner[0] != '\0')
+ namespace_watcher_name_appeared (watcher, name, new_owner);
+}
+
+static void
+got_name_owner (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GetNameOwnerData *data = user_data;
+ GError *error = NULL;
+ GVariant *reply;
+ const gchar *owner;
+
+ reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ goto out;
+ }
+
+ if (reply == NULL)
+ {
+ if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER))
+ g_warning ("bus_watch_namespace: error calling org.freedesktop.DBus.GetNameOwner: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ g_variant_get (reply, "(&s)", &owner);
+ namespace_watcher_name_appeared (data->watcher, data->name, owner);
+
+ g_variant_unref (reply);
+
+out:
+ g_free (data->name);
+ g_free (data);
+}
+
+static void
+names_listed (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ NamespaceWatcher *watcher;
+ GError *error = NULL;
+ GVariant *reply;
+ GVariantIter *iter;
+ const gchar *name;
+
+ reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ return;
+ }
+
+ watcher = user_data;
+
+ if (reply == NULL)
+ {
+ g_warning ("bus_watch_namespace: error calling org.freedesktop.DBus.ListNames: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_variant_get (reply, "(as)", &iter);
+ while (g_variant_iter_next (iter, "&s", &name))
+ {
+ if (dbus_name_has_namespace (name, watcher->name_space))
+ {
+ GetNameOwnerData *data = g_new (GetNameOwnerData, 1);
+ data->watcher = watcher;
+ data->name = g_strdup (name);
+ g_dbus_connection_call (watcher->connection, "org.freedesktop.DBus", "/",
+ "org.freedesktop.DBus", "GetNameOwner",
+ g_variant_new ("(s)", name), G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE, -1, watcher->cancellable,
+ got_name_owner, data);
+ }
+ }
+
+ g_variant_iter_free (iter);
+ g_variant_unref (reply);
+}
+
+static void
+connection_closed (GDBusConnection *connection,
+ gboolean remote_peer_vanished,
+ GError *error,
+ gpointer user_data)
+{
+ NamespaceWatcher *watcher = user_data;
+
+ namespace_watcher_stop (watcher);
+}
+
+static void
+got_bus (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusConnection *connection;
+ NamespaceWatcher *watcher;
+ GError *error = NULL;
+
+ connection = g_bus_get_finish (result, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ return;
+ }
+
+ watcher = user_data;
+
+ if (connection == NULL)
+ {
+ namespace_watcher_stop (watcher);
+ return;
+ }
+
+ watcher->connection = connection;
+ g_signal_connect (watcher->connection, "closed", G_CALLBACK (connection_closed), watcher);
+
+ watcher->subscription_id =
+ g_dbus_connection_signal_subscribe (watcher->connection, "org.freedesktop.DBus",
+ "org.freedesktop.DBus", "NameOwnerChanged", "/org/freedesktop/DBus",
+ watcher->name_space, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE,
+ name_owner_changed, watcher, NULL);
+
+ g_dbus_connection_call (watcher->connection, "org.freedesktop.DBus", "/",
+ "org.freedesktop.DBus", "ListNames", NULL, G_VARIANT_TYPE ("(as)"),
+ G_DBUS_CALL_FLAGS_NONE, -1, watcher->cancellable,
+ names_listed, watcher);
+}
+
+guint
+bus_watch_namespace (GBusType bus_type,
+ const gchar *name_space,
+ GBusNameAppearedCallback appeared_handler,
+ GBusNameVanishedCallback vanished_handler,
+ gpointer user_data,
+ GDestroyNotify user_data_destroy)
+{
+ NamespaceWatcher *watcher;
+
+ /* same rules for interfaces and well-known names */
+ g_return_val_if_fail (name_space != NULL && g_dbus_is_interface_name (name_space), 0);
+ g_return_val_if_fail (appeared_handler || vanished_handler, 0);
+
+ watcher = g_new0 (NamespaceWatcher, 1);
+ watcher->id = namespace_watcher_next_id++;
+ watcher->name_space = g_strdup (name_space);
+ watcher->appeared_handler = appeared_handler;
+ watcher->vanished_handler = vanished_handler;
+ watcher->user_data = user_data;
+ watcher->user_data_destroy = user_data_destroy;
+ watcher->cancellable = g_cancellable_new ();;
+ watcher->names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ if (namespace_watcher_watchers == NULL)
+ namespace_watcher_watchers = g_hash_table_new (g_direct_hash, g_direct_equal);
+ g_hash_table_insert (namespace_watcher_watchers, GUINT_TO_POINTER (watcher->id), watcher);
+
+ g_bus_get (bus_type, watcher->cancellable, got_bus, watcher);
+
+ return watcher->id;
+}
+
+void
+bus_unwatch_namespace (guint id)
+{
+ /* namespace_watcher_stop() might have already removed the watcher
+ * with @id in the case of a connection error. Thus, this function
+ * doesn't warn when @id is absent from the hash table.
+ */
+
+ if (namespace_watcher_watchers)
+ {
+ NamespaceWatcher *watcher;
+
+ watcher = g_hash_table_lookup (namespace_watcher_watchers, GUINT_TO_POINTER (id));
+ if (watcher)
+ {
+ /* make sure vanished() is not called as a result of this function */
+ g_hash_table_remove_all (watcher->names);
+
+ namespace_watcher_stop (watcher);
+ }
+ }
+}
diff --git a/plugins/media-keys/bus-watch-namespace.h b/plugins/media-keys/bus-watch-namespace.h
new file mode 100644
index 0000000..215f6be
--- /dev/null
+++ b/plugins/media-keys/bus-watch-namespace.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#ifndef __BUS_WATCH_NAMESPACE_H__
+#define __BUS_WATCH_NAMESPACE_H__
+
+#include <gio/gio.h>
+
+guint bus_watch_namespace (GBusType bus_type,
+ const gchar *name_space,
+ GBusNameAppearedCallback appeared_handler,
+ GBusNameVanishedCallback vanished_handler,
+ gpointer user_data,
+ GDestroyNotify user_data_destroy);
+
+void bus_unwatch_namespace (guint id);
+
+#endif
diff --git a/plugins/media-keys/gsd-marshal.list b/plugins/media-keys/gsd-marshal.list
new file mode 100644
index 0000000..72f9937
--- /dev/null
+++ b/plugins/media-keys/gsd-marshal.list
@@ -0,0 +1 @@
+VOID:STRING,STRING
diff --git a/plugins/media-keys/gsd-media-keys-manager.c b/plugins/media-keys/gsd-media-keys-manager.c
new file mode 100644
index 0000000..15e96e0
--- /dev/null
+++ b/plugins/media-keys/gsd-media-keys-manager.c
@@ -0,0 +1,3553 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2001-2003 Bastien Nocera <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-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;
+
+ /* Rotation */
+ guint iio_sensor_watch_id;
+ gboolean has_accel;
+ GDBusProxy *iio_sensor_proxy;
+
+ /* RFKill stuff */
+ guint rfkill_watch_id;
+ guint64 rfkill_last_time;
+ GDBusProxy *rfkill_proxy;
+ GCancellable *rfkill_cancellable;
+
+ /* systemd stuff */
+ GDBusProxy *logind_proxy;
+ gint inhibit_keys_fd;
+ gint inhibit_suspend_fd;
+ gboolean inhibit_suspend_taken;
+
+ GDBusConnection *connection;
+ GCancellable *bus_cancellable;
+
+ guint start_idle_id;
+
+ /* Multimedia keys */
+ MprisController *mpris_controller;
+} GsdMediaKeysManagerPrivate;
+
+static void gsd_media_keys_manager_class_init (GsdMediaKeysManagerClass *klass);
+static void gsd_media_keys_manager_init (GsdMediaKeysManager *media_keys_manager);
+static void gsd_media_keys_manager_finalize (GObject *object);
+static void register_manager (GsdMediaKeysManager *manager);
+static void custom_binding_changed (GSettings *settings,
+ const char *settings_key,
+ GsdMediaKeysManager *manager);
+static void keys_sync_queue (GsdMediaKeysManager *manager,
+ gboolean immediate,
+ gboolean retry);
+static void keys_sync_continue (GsdMediaKeysManager *manager);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GsdMediaKeysManager, gsd_media_keys_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+static void
+media_key_unref (MediaKey *key)
+{
+ if (key == NULL)
+ return;
+ if (!g_atomic_int_dec_and_test (&key->ref_count))
+ return;
+ g_clear_pointer (&key->accel_ids, g_array_unref);
+ g_free (key->custom_path);
+ g_free (key->custom_command);
+ g_free (key);
+}
+
+static MediaKey *
+media_key_ref (MediaKey *key)
+{
+ g_atomic_int_inc (&key->ref_count);
+ return key;
+}
+
+static MediaKey *
+media_key_new (void)
+{
+ MediaKey *key = g_new0 (MediaKey, 1);
+
+ key->accel_ids = g_array_new (FALSE, TRUE, sizeof(guint));
+
+ return media_key_ref (key);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (MediaKey, media_key_unref)
+
+static void
+grab_ungrab_data_free (GrabUngrabData *data)
+{
+ /* NOTE: The manager pointer is not owned and is invalid if the
+ * operation was cancelled.
+ */
+
+ if (!data->cancelled) {
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (data->manager);
+
+ if (priv->keys_sync_data == data)
+ priv->keys_sync_data = NULL;
+ }
+
+ data->manager = NULL;
+ g_clear_pointer (&data->keys, g_ptr_array_unref);
+ g_free (data);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GrabUngrabData, grab_ungrab_data_free)
+
+static void
+set_launch_context_env (GsdMediaKeysManager *manager,
+ GAppLaunchContext *launch_context)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GError *error = NULL;
+ GVariant *variant, *item;
+ GVariantIter *iter;
+
+ variant = g_dbus_connection_call_sync (priv->connection,
+ GNOME_KEYRING_DBUS_NAME,
+ GNOME_KEYRING_DBUS_PATH,
+ GNOME_KEYRING_DBUS_INTERFACE,
+ "GetEnvironment",
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (variant == NULL) {
+ g_warning ("Failed to call GetEnvironment on keyring daemon: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_variant_get (variant, "(a{ss})", &iter);
+
+ while ((item = g_variant_iter_next_value (iter))) {
+ char *key;
+ char *value;
+
+ g_variant_get (item,
+ "{ss}",
+ &key,
+ &value);
+
+ g_app_launch_context_setenv (launch_context, key, value);
+
+ g_variant_unref (item);
+ g_free (key);
+ g_free (value);
+ }
+
+ g_variant_iter_free (iter);
+ g_variant_unref (variant);
+}
+
+static char *
+get_key_string (MediaKey *key)
+{
+ if (key->settings_key != NULL)
+ return g_strdup_printf ("settings:%s", key->settings_key);
+ else if (key->custom_path != NULL)
+ return g_strdup_printf ("custom:%s", key->custom_path);
+ else
+ g_assert_not_reached ();
+}
+
+static GStrv
+get_bindings (GsdMediaKeysManager *manager,
+ MediaKey *key)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GPtrArray *array;
+ gchar *binding;
+
+ if (key->settings_key != NULL) {
+ g_autofree gchar *static_settings_key = NULL;
+ g_autofree GStrv keys = NULL;
+ g_autofree GStrv static_keys = NULL;
+ gchar **item;
+
+ if (!key->static_setting)
+ return g_settings_get_strv (priv->settings, key->settings_key);
+
+ static_settings_key = g_strconcat (key->settings_key, "-static", NULL);
+ keys = g_settings_get_strv (priv->settings, key->settings_key);
+ static_keys = g_settings_get_strv (priv->settings, static_settings_key);
+
+ array = g_ptr_array_new ();
+ /* Steals all strings from the settings */
+ for (item = keys; *item; item++)
+ g_ptr_array_add (array, *item);
+ for (item = static_keys; *item; item++)
+ g_ptr_array_add (array, *item);
+ g_ptr_array_add (array, NULL);
+
+ return (GStrv) g_ptr_array_free (array, FALSE);
+ }
+
+ else if (key->custom_path != NULL) {
+ GSettings *settings;
+
+ settings = g_hash_table_lookup (priv->custom_settings,
+ key->custom_path);
+ binding = g_settings_get_string (settings, "binding");
+ } else
+ g_assert_not_reached ();
+
+ array = g_ptr_array_new ();
+ g_ptr_array_add (array, binding);
+ g_ptr_array_add (array, NULL);
+
+ return (GStrv) g_ptr_array_free (array, FALSE);
+}
+
+static void
+show_osd_with_max_level (GsdMediaKeysManager *manager,
+ const char *icon,
+ const char *label,
+ double level,
+ double max_level,
+ const gchar *connector)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ if (priv->shell_proxy == NULL)
+ return;
+
+ shell_show_osd_with_max_level (priv->shell_proxy,
+ icon, label, level, max_level, connector);
+}
+
+static void
+show_osd (GsdMediaKeysManager *manager,
+ const char *icon,
+ const char *label,
+ double level,
+ const char *connector)
+{
+ show_osd_with_max_level(manager,
+ icon, label, level, -1, connector);
+}
+
+static const char *
+get_icon_name_for_volume (gboolean is_mic,
+ gboolean muted,
+ double volume)
+{
+ static const char *icon_names[] = {
+ "audio-volume-muted-symbolic",
+ "audio-volume-low-symbolic",
+ "audio-volume-medium-symbolic",
+ "audio-volume-high-symbolic",
+ "audio-volume-overamplified-symbolic",
+ NULL
+ };
+ static const char *mic_icon_names[] = {
+ "microphone-sensitivity-muted-symbolic",
+ "microphone-sensitivity-low-symbolic",
+ "microphone-sensitivity-medium-symbolic",
+ "microphone-sensitivity-high-symbolic",
+ NULL
+ };
+ int n;
+
+ if (muted) {
+ n = 0;
+ } else {
+ /* select image */
+ n = ceill (3.0 * volume);
+ if (n < 1)
+ n = 1;
+ /* output volume above 100% */
+ else if (n > 3 && !is_mic)
+ n = 4;
+ else if (n > 3)
+ n = 3;
+ }
+
+ if (is_mic)
+ return mic_icon_names[n];
+ else
+ return icon_names[n];
+}
+
+static void
+ungrab_accelerators_complete (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(GrabUngrabData) data = user_data;
+ gboolean success = FALSE;
+ g_autoptr(GError) error = NULL;
+ gint i;
+
+ g_debug ("Ungrab call completed!");
+
+ if (!shell_key_grabber_call_ungrab_accelerators_finish (SHELL_KEY_GRABBER (object),
+ &success, result, &error)) {
+ g_warning ("Failed to ungrab accelerators: %s", error->message);
+
+ if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
+ keys_sync_queue (data->manager, FALSE, TRUE);
+ return;
+ }
+
+ /* We are screwed at this point; we'll still keep going assuming that we don't
+ * have the bindings registered anymore.
+ * The only alternative would be to die and force cleanup of all registered
+ * grabs that way.
+ */
+ } else if (!success) {
+ g_warning ("Failed to ungrab some accelerators, they were probably not registered!");
+ }
+
+ /* Clear the accelerator IDs. */
+ for (i = 0; i < data->keys->len; i++) {
+ MediaKey *key;
+
+ key = g_ptr_array_index (data->keys, i);
+
+ /* Always clear, as it would just fail again the next time. */
+ g_array_set_size (key->accel_ids, 0);
+ }
+
+ /* Nothing left to do if the operation was cancelled */
+ if (data->cancelled)
+ return;
+
+ keys_sync_continue (data->manager);
+}
+
+static void
+grab_accelerators_complete (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(GrabUngrabData) data = user_data;
+ g_autoptr(GVariant) actions = NULL;
+ g_autoptr(GError) error = NULL;
+ gint i;
+
+ g_debug ("Grab call completed!");
+
+ if (!shell_key_grabber_call_grab_accelerators_finish (SHELL_KEY_GRABBER (object),
+ &actions, result, &error)) {
+ g_warning ("Failed to grab accelerators: %s", error->message);
+
+ if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
+ keys_sync_queue (data->manager, FALSE, TRUE);
+ return;
+ }
+
+ /* We are screwed at this point as we can't grab the keys. Most likely
+ * this means we are not running on GNOME, or ran into some other weird
+ * error.
+ * Either way, finish the operation as there is no way we can recover
+ * from this.
+ */
+ keys_sync_continue (data->manager);
+ return;
+ }
+
+ /* Do an immediate ungrab if the operation was cancelled.
+ * This may happen on daemon shutdown for example. */
+ if (data->cancelled) {
+ g_debug ("Doing an immediate ungrab on the grabbed accelerators!");
+
+ shell_key_grabber_call_ungrab_accelerators (SHELL_KEY_GRABBER (object),
+ actions,
+ NULL,
+ ungrab_accelerators_complete,
+ g_steal_pointer (&data));
+
+ return;
+ }
+
+ /* We need to stow away the accel_ids that have been registered successfully. */
+ for (i = 0; i < data->keys->len; i++) {
+ MediaKey *key;
+
+ key = g_ptr_array_index (data->keys, i);
+ g_assert (key->accel_ids->len == 0);
+ }
+ for (i = 0; i < data->keys->len; i++) {
+ MediaKey *key;
+ guint accel_id;
+
+ key = g_ptr_array_index (data->keys, i);
+
+ g_variant_get_child (actions, i, "u", &accel_id);
+ if (accel_id == 0) {
+ g_autofree gchar *tmp = NULL;
+ tmp = get_key_string (key);
+ g_warning ("Failed to grab accelerator for keybinding %s", tmp);
+ } else {
+ g_array_append_val (key->accel_ids, accel_id);
+ }
+ }
+
+ keys_sync_continue (data->manager);
+}
+
+static void
+keys_sync_continue (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ g_auto(GVariantBuilder) ungrab_builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("au"));
+ g_auto(GVariantBuilder) grab_builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(suu)"));
+ g_autoptr(GPtrArray) keys_being_ungrabbed = NULL;
+ g_autoptr(GPtrArray) keys_being_grabbed = NULL;
+ g_autoptr(GrabUngrabData) data = NULL;
+ GHashTableIter iter;
+ MediaKey *key;
+ gboolean need_ungrab = FALSE;
+
+ /* Syncing keys is a two step process in principle, i.e. we first ungrab all keys
+ * and then grab the new ones.
+ * To make this work, this function will be called multiple times and it will
+ * either emit an ungrab or grab call or do nothing when done.
+ */
+
+ /* If the keys_to_sync hash table is empty at this point, then we are done.
+ * priv->keys_sync_data will be cleared automatically when it is unref'ed.
+ */
+ if (g_hash_table_size (priv->keys_to_sync) == 0)
+ return;
+
+ keys_being_ungrabbed = g_ptr_array_new_with_free_func ((GDestroyNotify) media_key_unref);
+ keys_being_grabbed = g_ptr_array_new_with_free_func ((GDestroyNotify) media_key_unref);
+
+ g_hash_table_iter_init (&iter, priv->keys_to_sync);
+ while (g_hash_table_iter_next (&iter, (gpointer*) &key, NULL)) {
+ g_auto(GStrv) bindings = NULL;
+ gchar **pos = NULL;
+ gint i;
+
+ for (i = 0; i < key->accel_ids->len; i++) {
+ g_variant_builder_add (&ungrab_builder, "u", g_array_index (key->accel_ids, guint, i));
+ g_ptr_array_add (keys_being_ungrabbed, media_key_ref (key));
+
+ need_ungrab = TRUE;
+ }
+
+ /* Keys that are synced but aren't in the internal list are being removed. */
+ if (!g_ptr_array_find (priv->keys, key, NULL))
+ continue;
+
+ bindings = get_bindings (manager, key);
+ pos = bindings;
+ while (*pos) {
+ /* Do not try to register empty keybindings. */
+ if (strlen (*pos) > 0) {
+ g_variant_builder_add (&grab_builder, "(suu)", *pos, key->modes, key->grab_flags);
+ g_ptr_array_add (keys_being_grabbed, media_key_ref (key));
+ }
+ pos++;
+ }
+ }
+
+ data = g_new0 (GrabUngrabData, 1);
+ data->manager = manager;
+
+ /* These calls intentionally do not get a cancellable. See comment in
+ * GrabUngrabData.
+ */
+ priv->keys_sync_data = data;
+
+ if (need_ungrab) {
+ data->keys = g_steal_pointer (&keys_being_ungrabbed);
+
+ shell_key_grabber_call_ungrab_accelerators (priv->key_grabber,
+ g_variant_builder_end (&ungrab_builder),
+ NULL,
+ ungrab_accelerators_complete,
+ g_steal_pointer (&data));
+ } else {
+ data->keys = g_steal_pointer (&keys_being_grabbed);
+
+ g_hash_table_remove_all (priv->keys_to_sync);
+
+ shell_key_grabber_call_grab_accelerators (priv->key_grabber,
+ g_variant_builder_end (&grab_builder),
+ NULL,
+ grab_accelerators_complete,
+ g_steal_pointer (&data));
+ }
+}
+
+static gboolean
+keys_sync_start (gpointer user_data)
+{
+ GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ priv->keys_sync_source_id = 0;
+ g_assert (priv->keys_sync_data == NULL);
+ keys_sync_continue (manager);
+
+ return G_SOURCE_REMOVE;
+}
+
+void
+keys_sync_queue (GsdMediaKeysManager *manager, gboolean immediate, gboolean retry)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ guint i;
+
+ if (priv->keys_sync_source_id)
+ g_source_remove (priv->keys_sync_source_id);
+
+ if (retry) {
+ /* Abort the currently running operation, and don't retry
+ * immediately to avoid race condition if an operation was
+ * already active. */
+ if (priv->keys_sync_data) {
+ priv->keys_sync_data->cancelled = TRUE;
+ priv->keys_sync_data = NULL;
+
+ immediate = FALSE;
+ }
+
+ /* Mark all existing keys for sync. */
+ for (i = 0; i < priv->keys->len; i++) {
+ MediaKey *key = g_ptr_array_index (priv->keys, i);
+ g_hash_table_add (priv->keys_to_sync, media_key_ref (key));
+ }
+ } else if (priv->keys_sync_data) {
+ /* We are already actively syncing, no need to do anything. */
+ return;
+ }
+
+ priv->keys_sync_source_id =
+ g_timeout_add (immediate ? 0 : (retry ? SHELL_GRABBER_RETRY_INTERVAL_MS : 50),
+ keys_sync_start,
+ manager);
+}
+
+static void
+gsettings_changed_cb (GSettings *settings,
+ const gchar *settings_key,
+ GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ int i;
+
+ /* Give up if we don't have proxy to the shell */
+ if (!priv->key_grabber)
+ return;
+
+ /* handled in gsettings_custom_changed_cb() */
+ if (g_str_equal (settings_key, "custom-keybindings"))
+ return;
+
+ /* Find the key that was modified */
+ if (priv->keys == NULL)
+ return;
+
+ for (i = 0; i < priv->keys->len; i++) {
+ MediaKey *key;
+
+ key = g_ptr_array_index (priv->keys, i);
+
+ /* Skip over hard-coded and GConf keys */
+ if (key->settings_key == NULL)
+ continue;
+ if (strcmp (settings_key, key->settings_key) == 0) {
+ g_hash_table_add (priv->keys_to_sync, media_key_ref (key));
+ keys_sync_queue (manager, FALSE, FALSE);
+ break;
+ }
+ }
+}
+
+static MediaKey *
+media_key_new_for_path (GsdMediaKeysManager *manager,
+ char *path)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GSettings *settings;
+ char *command, *binding;
+ MediaKey *key;
+
+ g_debug ("media_key_new_for_path: %s", path);
+
+ settings = g_hash_table_lookup (priv->custom_settings, path);
+ if (settings == NULL) {
+ settings = g_settings_new_with_path (CUSTOM_BINDING_SCHEMA, path);
+
+ g_signal_connect (settings, "changed",
+ G_CALLBACK (custom_binding_changed), manager);
+ g_hash_table_insert (priv->custom_settings,
+ g_strdup (path), settings);
+ }
+
+ command = g_settings_get_string (settings, "command");
+ binding = g_settings_get_string (settings, "binding");
+
+ if (*command == '\0' && *binding == '\0') {
+ g_debug ("Key binding (%s) is incomplete", path);
+ g_free (command);
+ g_free (binding);
+ return NULL;
+ }
+ g_free (binding);
+
+ key = media_key_new ();
+ key->key_type = CUSTOM_KEY;
+ key->modes = GSD_ACTION_MODE_LAUNCHER;
+ key->custom_path = g_strdup (path);
+ key->custom_command = command;
+ key->grab_flags = META_KEY_BINDING_NONE;
+
+ return key;
+}
+
+static void
+update_custom_binding (GsdMediaKeysManager *manager,
+ char *path)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ MediaKey *key;
+ int i;
+
+ /* Remove the existing key */
+ for (i = 0; i < priv->keys->len; i++) {
+ key = g_ptr_array_index (priv->keys, i);
+
+ if (key->custom_path == NULL)
+ continue;
+ if (strcmp (key->custom_path, path) == 0) {
+ g_debug ("Removing custom key binding %s", path);
+ g_hash_table_add (priv->keys_to_sync, media_key_ref (key));
+ g_ptr_array_remove_index_fast (priv->keys, i);
+ break;
+ }
+ }
+
+ /* And create a new one! */
+ key = media_key_new_for_path (manager, path);
+ if (key) {
+ g_debug ("Adding new custom key binding %s", path);
+ g_ptr_array_add (priv->keys, key);
+
+ g_hash_table_add (priv->keys_to_sync, media_key_ref (key));
+ }
+
+ keys_sync_queue (manager, FALSE, FALSE);
+}
+
+static void
+update_custom_binding_command (GsdMediaKeysManager *manager,
+ GSettings *settings,
+ char *path)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ MediaKey *key;
+ int i;
+
+ for (i = 0; i < priv->keys->len; i++) {
+ key = g_ptr_array_index (priv->keys, i);
+
+ if (key->custom_path == NULL)
+ continue;
+ if (strcmp (key->custom_path, path) == 0) {
+ g_free (key->custom_command);
+ key->custom_command = g_settings_get_string (settings, "command");
+ break;
+ }
+ }
+}
+
+static void
+custom_binding_changed (GSettings *settings,
+ const char *settings_key,
+ GsdMediaKeysManager *manager)
+{
+ char *path;
+
+ g_object_get (settings, "path", &path, NULL);
+
+ if (strcmp (settings_key, "binding") == 0)
+ update_custom_binding (manager, path);
+ else if (strcmp (settings_key, "command") == 0)
+ update_custom_binding_command (manager, settings, path);
+
+ g_free (path);
+}
+
+static void
+gsettings_custom_changed_cb (GSettings *settings,
+ const char *settings_key,
+ GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ char **bindings;
+ int i, j, n_bindings;
+
+ bindings = g_settings_get_strv (settings, settings_key);
+ n_bindings = g_strv_length (bindings);
+
+ /* Handle additions */
+ for (i = 0; i < n_bindings; i++) {
+ if (g_hash_table_lookup (priv->custom_settings,
+ bindings[i]))
+ continue;
+ update_custom_binding (manager, bindings[i]);
+ }
+
+ /* Handle removals */
+ for (i = 0; i < priv->keys->len; i++) {
+ gboolean found = FALSE;
+ MediaKey *key = g_ptr_array_index (priv->keys, i);
+ if (key->key_type != CUSTOM_KEY)
+ continue;
+
+ for (j = 0; j < n_bindings && !found; j++)
+ found = strcmp (bindings[j], key->custom_path) == 0;
+
+ if (found)
+ continue;
+
+ g_hash_table_add (priv->keys_to_sync, media_key_ref (key));
+ g_hash_table_remove (priv->custom_settings,
+ key->custom_path);
+ g_ptr_array_remove_index_fast (priv->keys, i);
+ --i; /* make up for the removed key */
+ }
+ keys_sync_queue (manager, FALSE, FALSE);
+ g_strfreev (bindings);
+}
+
+static void
+add_key (GsdMediaKeysManager *manager, guint i)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ MediaKey *key;
+
+ key = media_key_new ();
+ key->key_type = media_keys[i].key_type;
+ key->settings_key = media_keys[i].settings_key;
+ key->static_setting = media_keys[i].static_setting;
+ key->modes = media_keys[i].modes;
+ key->grab_flags = media_keys[i].grab_flags;
+
+ g_ptr_array_add (priv->keys, key);
+}
+
+static void
+init_kbd (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ char **custom_paths;
+ int i;
+
+ gnome_settings_profile_start (NULL);
+
+ for (i = 0; i < G_N_ELEMENTS (media_keys); i++)
+ add_key (manager, i);
+
+ /* Custom shortcuts */
+ custom_paths = g_settings_get_strv (priv->settings,
+ "custom-keybindings");
+
+ for (i = 0; i < g_strv_length (custom_paths); i++) {
+ MediaKey *key;
+
+ g_debug ("Setting up custom keybinding %s", custom_paths[i]);
+
+ key = media_key_new_for_path (manager, custom_paths[i]);
+ if (!key) {
+ continue;
+ }
+ g_ptr_array_add (priv->keys, key);
+ }
+ g_strfreev (custom_paths);
+
+ keys_sync_queue (manager, TRUE, TRUE);
+
+ gnome_settings_profile_end (NULL);
+}
+
+static void
+app_launched_cb (GAppLaunchContext *context,
+ GAppInfo *info,
+ GVariant *platform_data,
+ gpointer user_data)
+{
+ GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ gint32 pid;
+ const gchar *app_name;
+
+ if (!g_variant_lookup (platform_data, "pid", "i", &pid))
+ return;
+
+ app_name = g_app_info_get_id (info);
+ if (app_name == NULL)
+ app_name = g_app_info_get_executable (info);
+
+ /* Start async request; we don't care about the result */
+ gnome_start_systemd_scope (app_name,
+ pid,
+ NULL,
+ priv->connection,
+ NULL, NULL, NULL);
+}
+
+static void
+launch_app (GsdMediaKeysManager *manager,
+ GAppInfo *app_info,
+ gint64 timestamp)
+{
+ GError *error = NULL;
+ GdkAppLaunchContext *launch_context;
+
+ /* setup the launch context so the startup notification is correct */
+ launch_context = gdk_display_get_app_launch_context (gdk_display_get_default ());
+ gdk_app_launch_context_set_timestamp (launch_context, timestamp);
+ set_launch_context_env (manager, G_APP_LAUNCH_CONTEXT (launch_context));
+
+ g_signal_connect_object (launch_context,
+ "launched",
+ G_CALLBACK (app_launched_cb),
+ manager,
+ 0);
+
+ if (!g_app_info_launch (app_info, NULL, G_APP_LAUNCH_CONTEXT (launch_context), &error)) {
+ g_warning ("Could not launch '%s': %s",
+ g_app_info_get_commandline (app_info),
+ error->message);
+ g_error_free (error);
+ }
+ g_object_unref (launch_context);
+}
+
+static void
+execute (GsdMediaKeysManager *manager,
+ char *cmd,
+ gint64 timestamp)
+{
+ GAppInfo *app_info;
+ g_autofree gchar *escaped = NULL;
+ gchar *p;
+
+ /* Escape all % characters as g_app_info_create_from_commandline will
+ * try to interpret them otherwise. */
+ escaped = g_malloc (strlen (cmd) * 2 + 1);
+ p = escaped;
+ while (*cmd) {
+ *p = *cmd;
+ p++;
+ if (*cmd == '%') {
+ *p = '%';
+ p++;
+ }
+ cmd++;
+ }
+ *p = '\0';
+
+ app_info = g_app_info_create_from_commandline (escaped, NULL, G_APP_INFO_CREATE_NONE, NULL);
+ launch_app (manager, app_info, timestamp);
+ g_object_unref (app_info);
+}
+
+static void
+do_url_action (GsdMediaKeysManager *manager,
+ const char *scheme,
+ gint64 timestamp)
+{
+ GAppInfo *app_info;
+
+ app_info = g_app_info_get_default_for_uri_scheme (scheme);
+ if (app_info != NULL) {
+ launch_app (manager, app_info, timestamp);
+ g_object_unref (app_info);
+ } else {
+ g_warning ("Could not find default application for '%s' scheme", scheme);
+ }
+}
+
+static void
+do_media_action (GsdMediaKeysManager *manager,
+ gint64 timestamp)
+{
+ GAppInfo *app_info;
+
+ app_info = g_app_info_get_default_for_type ("audio/x-vorbis+ogg", FALSE);
+ if (app_info != NULL) {
+ launch_app (manager, app_info, timestamp);
+ g_object_unref (app_info);
+ } else {
+ g_warning ("Could not find default application for '%s' mime-type", "audio/x-vorbis+ogg");
+ }
+}
+
+static void
+gnome_session_shutdown_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GVariant *result;
+ GError *error = NULL;
+
+ result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+ res,
+ &error);
+ if (result == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to call Shutdown on session manager: %s",
+ error->message);
+ g_error_free (error);
+ } else {
+ g_variant_unref (result);
+ }
+}
+
+static void
+gnome_session_shutdown (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GDBusProxy *proxy;
+
+ proxy = G_DBUS_PROXY (gnome_settings_bus_get_session_proxy ());
+
+ g_dbus_proxy_call (proxy,
+ "Shutdown",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ priv->bus_cancellable,
+ gnome_session_shutdown_cb,
+ NULL);
+
+ g_object_unref (proxy);
+}
+
+static void
+do_eject_action_cb (GDrive *drive,
+ GAsyncResult *res,
+ GsdMediaKeysManager *manager)
+{
+ g_drive_eject_with_operation_finish (drive, res, NULL);
+}
+
+#define NO_SCORE 0
+#define SCORE_CAN_EJECT 50
+#define SCORE_HAS_MEDIA 100
+static void
+do_eject_action (GsdMediaKeysManager *manager)
+{
+ GList *drives, *l;
+ GDrive *fav_drive;
+ guint score;
+ GVolumeMonitor *volume_monitor;
+
+ volume_monitor = g_volume_monitor_get ();
+
+
+ /* Find the best drive to eject */
+ fav_drive = NULL;
+ score = NO_SCORE;
+ drives = g_volume_monitor_get_connected_drives (volume_monitor);
+ for (l = drives; l != NULL; l = l->next) {
+ GDrive *drive = l->data;
+
+ if (g_drive_can_eject (drive) == FALSE)
+ continue;
+ if (g_drive_is_media_removable (drive) == FALSE)
+ continue;
+ if (score < SCORE_CAN_EJECT) {
+ fav_drive = drive;
+ score = SCORE_CAN_EJECT;
+ }
+ if (g_drive_has_media (drive) == FALSE)
+ continue;
+ if (score < SCORE_HAS_MEDIA) {
+ fav_drive = drive;
+ score = SCORE_HAS_MEDIA;
+ break;
+ }
+ }
+
+ /* Show OSD */
+ show_osd (manager, "media-eject-symbolic", NULL, -1, NULL);
+
+ /* Clean up the drive selection and exit if no suitable
+ * drives are found */
+ if (fav_drive != NULL)
+ fav_drive = g_object_ref (fav_drive);
+
+ g_list_foreach (drives, (GFunc) g_object_unref, NULL);
+ if (fav_drive == NULL)
+ return;
+
+ /* Eject! */
+ g_drive_eject_with_operation (fav_drive, G_MOUNT_UNMOUNT_FORCE,
+ NULL, NULL,
+ (GAsyncReadyCallback) do_eject_action_cb,
+ manager);
+ g_object_unref (fav_drive);
+ g_object_unref (volume_monitor);
+}
+
+static void
+do_home_key_action (GsdMediaKeysManager *manager,
+ gint64 timestamp)
+{
+ GFile *file;
+ GError *error = NULL;
+ char *uri;
+
+ file = g_file_new_for_path (g_get_home_dir ());
+ uri = g_file_get_uri (file);
+ g_object_unref (file);
+
+ if (gtk_show_uri_on_window (NULL, uri, timestamp, &error) == FALSE) {
+ g_warning ("Failed to launch '%s': %s", uri, error->message);
+ g_error_free (error);
+ }
+ g_free (uri);
+}
+
+static void
+do_search_action (GsdMediaKeysManager *manager,
+ gint64 timestamp)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ if (priv->shell_proxy == NULL)
+ return;
+
+ gsd_shell_call_focus_search (priv->shell_proxy,
+ NULL, NULL, NULL);
+}
+
+static void
+do_execute_desktop_or_desktop (GsdMediaKeysManager *manager,
+ const char *desktop,
+ const char *alt_desktop,
+ gint64 timestamp)
+{
+ GDesktopAppInfo *app_info;
+
+ app_info = g_desktop_app_info_new (desktop);
+ if (app_info == NULL && alt_desktop != NULL)
+ app_info = g_desktop_app_info_new (alt_desktop);
+
+ if (app_info != NULL) {
+ launch_app (manager, G_APP_INFO (app_info), timestamp);
+ g_object_unref (app_info);
+ return;
+ }
+
+ g_warning ("Could not find application '%s' or '%s'", desktop, alt_desktop);
+}
+
+static void
+do_touchpad_osd_action (GsdMediaKeysManager *manager, gboolean state)
+{
+ show_osd (manager, state ? "input-touchpad-symbolic"
+ : "touchpad-disabled-symbolic", NULL, -1, NULL);
+}
+
+static void
+do_touchpad_action (GsdMediaKeysManager *manager)
+{
+ GSettings *settings;
+ gboolean state;
+
+ settings = g_settings_new (SETTINGS_TOUCHPAD_DIR);
+ state = (g_settings_get_enum (settings, TOUCHPAD_ENABLED_KEY) ==
+ G_DESKTOP_DEVICE_SEND_EVENTS_ENABLED);
+
+ do_touchpad_osd_action (manager, !state);
+
+ g_settings_set_enum (settings, TOUCHPAD_ENABLED_KEY,
+ !state ?
+ G_DESKTOP_DEVICE_SEND_EVENTS_ENABLED :
+ G_DESKTOP_DEVICE_SEND_EVENTS_DISABLED);
+ g_object_unref (settings);
+}
+
+static void
+on_screen_locked (GsdScreenSaver *screen_saver,
+ GAsyncResult *result,
+ GsdMediaKeysManager *manager)
+{
+ gboolean is_locked;
+ GError *error = NULL;
+
+ is_locked = gsd_screen_saver_call_lock_finish (screen_saver, result, &error);
+
+ if (!is_locked) {
+ g_warning ("Couldn't lock screen: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+}
+
+static void
+do_lock_screensaver (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ if (priv->screen_saver_proxy == NULL)
+ priv->screen_saver_proxy = gnome_settings_bus_get_screen_saver_proxy ();
+
+ gsd_screen_saver_call_lock (priv->screen_saver_proxy,
+ priv->bus_cancellable,
+ (GAsyncReadyCallback) on_screen_locked,
+ manager);
+}
+
+static void
+sound_theme_changed (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ char *theme_name;
+
+ g_object_get (G_OBJECT (priv->gtksettings), "gtk-sound-theme-name", &theme_name, NULL);
+ if (theme_name)
+ ca_context_change_props (priv->ca, CA_PROP_CANBERRA_XDG_THEME_NAME, theme_name, NULL);
+ g_free (theme_name);
+}
+
+static void
+allow_volume_above_100_percent_changed_cb (GSettings *settings,
+ const char *settings_key,
+ GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ gboolean allow_volume_above_100_percent;
+
+ g_assert (g_str_equal (settings_key, ALLOW_VOLUME_ABOVE_100_PERCENT_KEY));
+
+ allow_volume_above_100_percent = g_settings_get_boolean (settings, settings_key);
+ priv->max_volume = allow_volume_above_100_percent ? PA_VOLUME_UI_MAX : PA_VOLUME_NORM;
+}
+
+static void
+play_volume_changed_audio (GsdMediaKeysManager *manager,
+ GvcMixerStream *stream)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ if (priv->ca == NULL) {
+ ca_context_create (&priv->ca);
+ ca_context_set_driver (priv->ca, "pulse");
+ ca_context_change_props (priv->ca, 0,
+ CA_PROP_APPLICATION_ID,
+ "org.gnome.VolumeControl",
+ NULL);
+
+ priv->gtksettings =
+ gtk_settings_get_for_screen (gdk_screen_get_default ());
+
+ g_signal_connect_swapped (priv->gtksettings,
+ "notify::gtk-sound-theme-name",
+ G_CALLBACK (sound_theme_changed),
+ manager);
+ sound_theme_changed (manager);
+ }
+
+ ca_context_change_device (priv->ca,
+ gvc_mixer_stream_get_name (stream));
+ ca_context_play (priv->ca, 1,
+ CA_PROP_EVENT_ID, "audio-volume-change",
+ CA_PROP_EVENT_DESCRIPTION, "volume changed through key press",
+ CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
+ NULL);
+}
+
+static void
+show_volume_osd (GsdMediaKeysManager *manager,
+ GvcMixerStream *stream,
+ guint vol,
+ gboolean muted,
+ gboolean sound_changed,
+ gboolean quiet)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GvcMixerUIDevice *device;
+ const GvcMixerStreamPort *port;
+ const char *icon;
+ gboolean playing = FALSE;
+ double new_vol;
+ double max_volume;
+
+ max_volume = (double) priv->max_volume / PA_VOLUME_NORM;
+ if (!muted) {
+ new_vol = (double) vol / PA_VOLUME_NORM;
+ new_vol = CLAMP (new_vol, 0, max_volume);
+ } else {
+ new_vol = 0.0;
+ }
+ icon = get_icon_name_for_volume (!GVC_IS_MIXER_SINK (stream), muted, new_vol);
+ port = gvc_mixer_stream_get_port (stream);
+ if (g_strcmp0 (gvc_mixer_stream_get_form_factor (stream), "internal") != 0 ||
+ (port != NULL &&
+ g_strcmp0 (port->port, "[OUT] Speaker") != 0 &&
+ g_strcmp0 (port->port, "[OUT] Handset") != 0 &&
+ g_strcmp0 (port->port, "analog-output-speaker") != 0 &&
+ g_strcmp0 (port->port, "analog-output") != 0)) {
+ device = gvc_mixer_control_lookup_device_from_stream (priv->volume, stream);
+ show_osd_with_max_level (manager, icon,
+ gvc_mixer_ui_device_get_description (device),
+ new_vol, max_volume, NULL);
+ } else {
+ show_osd_with_max_level (manager, icon, NULL, new_vol, max_volume, NULL);
+ }
+
+ if (priv->ca)
+ ca_context_playing (priv->ca, 1, &playing);
+ playing = !playing && gvc_mixer_stream_get_state (stream) == GVC_STREAM_STATE_RUNNING;
+
+ if (quiet == FALSE && sound_changed != FALSE && muted == FALSE && playing == FALSE)
+ play_volume_changed_audio (manager, stream);
+}
+
+#if HAVE_GUDEV
+/* PulseAudio gives us /devices/... paths, when udev
+ * expects /sys/devices/... paths. */
+static GUdevDevice *
+get_udev_device_for_sysfs_path (GsdMediaKeysManager *manager,
+ const char *sysfs_path)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ char *path;
+ GUdevDevice *dev;
+
+ path = g_strdup_printf ("/sys%s", sysfs_path);
+ dev = g_udev_client_query_by_sysfs_path (priv->udev_client, path);
+ g_free (path);
+
+ return dev;
+}
+
+static GvcMixerStream *
+get_stream_for_device_node (GsdMediaKeysManager *manager,
+ gboolean is_output,
+ const gchar *devnode)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ gpointer id_ptr;
+ GvcMixerStream *res;
+ GUdevDevice *dev, *parent;
+ GSList *streams, *l;
+
+ id_ptr = g_hash_table_lookup (priv->streams, devnode);
+ if (id_ptr != NULL) {
+ if (GPOINTER_TO_UINT (id_ptr) == (guint) -1)
+ return NULL;
+ else
+ return gvc_mixer_control_lookup_stream_id (priv->volume, GPOINTER_TO_UINT (id_ptr));
+ }
+
+ dev = g_udev_client_query_by_device_file (priv->udev_client, devnode);
+ if (dev == NULL) {
+ g_debug ("Could not find udev device for device path '%s'", devnode);
+ return NULL;
+ }
+
+ if (g_strcmp0 (g_udev_device_get_property (dev, "ID_BUS"), "usb") != 0) {
+ g_debug ("Not handling XInput device %s, not USB", devnode);
+ g_hash_table_insert (priv->streams,
+ g_strdup (devnode),
+ GUINT_TO_POINTER ((guint) -1));
+ g_object_unref (dev);
+ return NULL;
+ }
+
+ parent = g_udev_device_get_parent_with_subsystem (dev, "usb", "usb_device");
+ if (parent == NULL) {
+ g_warning ("No USB device parent for XInput device %s even though it's USB", devnode);
+ g_object_unref (dev);
+ return NULL;
+ }
+
+ res = NULL;
+ if (is_output)
+ streams = gvc_mixer_control_get_sinks (priv->volume);
+ else
+ streams = gvc_mixer_control_get_sources (priv->volume);
+ for (l = streams; l; l = l->next) {
+ GvcMixerStream *stream = l->data;
+ const char *sysfs_path;
+ GUdevDevice *stream_dev, *stream_parent;
+
+ sysfs_path = gvc_mixer_stream_get_sysfs_path (stream);
+ stream_dev = get_udev_device_for_sysfs_path (manager, sysfs_path);
+ if (stream_dev == NULL)
+ continue;
+ stream_parent = g_udev_device_get_parent_with_subsystem (stream_dev, "usb", "usb_device");
+ g_object_unref (stream_dev);
+ if (stream_parent == NULL)
+ continue;
+
+ if (g_strcmp0 (g_udev_device_get_sysfs_path (stream_parent),
+ g_udev_device_get_sysfs_path (parent)) == 0) {
+ res = stream;
+ }
+ g_object_unref (stream_parent);
+ if (res != NULL)
+ break;
+ }
+
+ g_slist_free (streams);
+
+ if (res)
+ g_hash_table_insert (priv->streams,
+ g_strdup (devnode),
+ GUINT_TO_POINTER (gvc_mixer_stream_get_id (res)));
+ else
+ g_hash_table_insert (priv->streams,
+ g_strdup (devnode),
+ GUINT_TO_POINTER ((guint) -1));
+
+ return res;
+}
+#endif /* HAVE_GUDEV */
+
+typedef enum {
+ SOUND_ACTION_FLAG_IS_OUTPUT = 1 << 0,
+ SOUND_ACTION_FLAG_IS_QUIET = 1 << 1,
+ SOUND_ACTION_FLAG_IS_PRECISE = 1 << 2,
+} SoundActionFlags;
+
+static void
+do_sound_action (GsdMediaKeysManager *manager,
+ const gchar *device_node,
+ int type,
+ SoundActionFlags flags)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GvcMixerStream *stream = NULL;
+ gboolean old_muted, new_muted;
+ guint old_vol, new_vol, norm_vol_step, vol_step;
+ gboolean sound_changed;
+
+ /* Find the stream that corresponds to the device, if any */
+ stream = NULL;
+#if HAVE_GUDEV
+ if (device_node) {
+ stream = get_stream_for_device_node (manager,
+ flags & SOUND_ACTION_FLAG_IS_OUTPUT,
+ device_node);
+ }
+#endif /* HAVE_GUDEV */
+
+ if (stream == NULL) {
+ if (flags & SOUND_ACTION_FLAG_IS_OUTPUT)
+ stream = priv->sink;
+ else
+ stream = priv->source;
+ }
+
+ if (stream == NULL)
+ return;
+
+ if (flags & SOUND_ACTION_FLAG_IS_PRECISE) {
+ norm_vol_step = PA_VOLUME_NORM * VOLUME_STEP_PRECISE / 100;
+ }
+ else {
+ vol_step = g_settings_get_int (priv->settings, VOLUME_STEP);
+ norm_vol_step = PA_VOLUME_NORM * vol_step / 100;
+ }
+ /* FIXME: this is racy */
+ new_vol = old_vol = gvc_mixer_stream_get_volume (stream);
+ new_muted = old_muted = gvc_mixer_stream_get_is_muted (stream);
+ sound_changed = FALSE;
+
+ switch (type) {
+ case MUTE_KEY:
+ new_muted = !old_muted;
+ break;
+ case VOLUME_DOWN_KEY:
+ if (old_vol <= norm_vol_step) {
+ new_vol = 0;
+ new_muted = TRUE;
+ } else {
+ new_vol = old_vol - norm_vol_step;
+ }
+ break;
+ case VOLUME_UP_KEY:
+ new_muted = FALSE;
+ /* When coming out of mute only increase the volume if it was 0 */
+ if (!old_muted || old_vol == 0)
+ new_vol = MIN (old_vol + norm_vol_step, priv->max_volume);
+ break;
+ }
+
+ if (old_muted != new_muted) {
+ gvc_mixer_stream_change_is_muted (stream, new_muted);
+ sound_changed = TRUE;
+ }
+
+ if (old_vol != new_vol) {
+ if (gvc_mixer_stream_set_volume (stream, new_vol) != FALSE) {
+ gvc_mixer_stream_push_volume (stream);
+ sound_changed = TRUE;
+ }
+ }
+
+ show_volume_osd (manager, stream, new_vol, new_muted, sound_changed,
+ flags & SOUND_ACTION_FLAG_IS_QUIET);
+}
+
+static void
+update_default_sink (GsdMediaKeysManager *manager,
+ gboolean warn)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GvcMixerStream *stream;
+
+ stream = gvc_mixer_control_get_default_sink (priv->volume);
+ if (stream == priv->sink)
+ return;
+
+ g_clear_object (&priv->sink);
+
+ if (stream != NULL) {
+ priv->sink = g_object_ref (stream);
+ } else {
+ if (warn)
+ g_warning ("Unable to get default sink");
+ else
+ g_debug ("Unable to get default sink");
+ }
+}
+
+static void
+update_default_source (GsdMediaKeysManager *manager,
+ gboolean warn)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GvcMixerStream *stream;
+
+ stream = gvc_mixer_control_get_default_source (priv->volume);
+ if (stream == priv->source)
+ return;
+
+ g_clear_object (&priv->source);
+
+ if (stream != NULL) {
+ priv->source = g_object_ref (stream);
+ } else {
+ if (warn)
+ g_warning ("Unable to get default source");
+ else
+ g_debug ("Unable to get default source");
+ }
+}
+
+static void
+on_control_state_changed (GvcMixerControl *control,
+ GvcMixerControlState new_state,
+ GsdMediaKeysManager *manager)
+{
+ update_default_sink (manager, new_state == GVC_STATE_READY);
+ update_default_source (manager, new_state == GVC_STATE_READY);
+}
+
+static void
+on_control_default_sink_changed (GvcMixerControl *control,
+ guint id,
+ GsdMediaKeysManager *manager)
+{
+ update_default_sink (manager, TRUE);
+}
+
+static void
+on_control_default_source_changed (GvcMixerControl *control,
+ guint id,
+ GsdMediaKeysManager *manager)
+{
+ update_default_source (manager, TRUE);
+}
+
+#if HAVE_GUDEV
+static gboolean
+remove_stream (gpointer key,
+ gpointer value,
+ gpointer id)
+{
+ if (GPOINTER_TO_UINT (value) == GPOINTER_TO_UINT (id))
+ return TRUE;
+ return FALSE;
+}
+#endif /* HAVE_GUDEV */
+
+static void
+on_control_stream_removed (GvcMixerControl *control,
+ guint id,
+ GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ if (priv->sink != NULL) {
+ if (gvc_mixer_stream_get_id (priv->sink) == id)
+ g_clear_object (&priv->sink);
+ }
+ if (priv->source != NULL) {
+ if (gvc_mixer_stream_get_id (priv->source) == id)
+ g_clear_object (&priv->source);
+ }
+
+#if HAVE_GUDEV
+ g_hash_table_foreach_remove (priv->streams, (GHRFunc) remove_stream, GUINT_TO_POINTER (id));
+#endif
+}
+
+static gboolean
+do_multimedia_player_action (GsdMediaKeysManager *manager,
+ const char *key)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ g_return_val_if_fail (key != NULL, FALSE);
+
+ g_debug ("Media key '%s' pressed", key);
+
+ if (mpris_controller_get_has_active_player (priv->mpris_controller)) {
+ if (g_str_equal (key, "Rewind")) {
+ if (mpris_controller_seek (priv->mpris_controller, REWIND_USEC))
+ return TRUE;
+ } else if (g_str_equal (key, "FastForward")) {
+ if (mpris_controller_seek (priv->mpris_controller, FASTFORWARD_USEC))
+ return TRUE;
+ } else if (g_str_equal (key, "Repeat")) {
+ if (mpris_controller_toggle (priv->mpris_controller, "LoopStatus"))
+ return TRUE;
+ } else if (g_str_equal (key, "Shuffle")) {
+ if (mpris_controller_toggle (priv->mpris_controller, "Shuffle"))
+ return TRUE;
+ } else if (mpris_controller_key (priv->mpris_controller, key)) {
+ return TRUE;
+ }
+ }
+
+ /* Popup a dialog with an (/) icon */
+ show_osd (manager, "action-unavailable-symbolic", NULL, -1, NULL);
+ return TRUE;
+}
+
+static void
+sensor_properties_changed (GDBusProxy *proxy,
+ GVariant *changed_properties,
+ GStrv invalidated_properties,
+ gpointer user_data)
+{
+ GsdMediaKeysManager *manager = user_data;
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GVariant *v;
+ GVariantDict dict;
+
+ if (priv->iio_sensor_proxy == NULL)
+ return;
+
+ if (changed_properties)
+ g_variant_dict_init (&dict, changed_properties);
+
+ if (changed_properties == NULL ||
+ g_variant_dict_contains (&dict, "HasAccelerometer")) {
+ v = g_dbus_proxy_get_cached_property (priv->iio_sensor_proxy,
+ "HasAccelerometer");
+ if (v == NULL) {
+ g_debug ("Couldn't fetch HasAccelerometer property");
+ return;
+ }
+ priv->has_accel = g_variant_get_boolean (v);
+ g_variant_unref (v);
+ }
+}
+
+static void
+iio_sensor_appeared_cb (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ GsdMediaKeysManager *manager = user_data;
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GError *error = NULL;
+
+ priv->iio_sensor_proxy = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "net.hadess.SensorProxy",
+ "/net/hadess/SensorProxy",
+ "net.hadess.SensorProxy",
+ NULL,
+ &error);
+
+ if (priv->iio_sensor_proxy == NULL) {
+ g_warning ("Failed to access net.hadess.SensorProxy after it appeared");
+ return;
+ }
+ g_signal_connect (G_OBJECT (priv->iio_sensor_proxy),
+ "g-properties-changed",
+ G_CALLBACK (sensor_properties_changed), manager);
+
+ sensor_properties_changed (priv->iio_sensor_proxy,
+ NULL, NULL, manager);
+}
+
+static void
+iio_sensor_disappeared_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ GsdMediaKeysManager *manager = user_data;
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ g_clear_object (&priv->iio_sensor_proxy);
+ priv->has_accel = FALSE;
+}
+
+static void
+do_video_rotate_lock_action (GsdMediaKeysManager *manager,
+ gint64 timestamp)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GSettings *settings;
+ gboolean locked;
+
+ if (!priv->has_accel) {
+ g_debug ("Ignoring attempt to set orientation lock: no accelerometer");
+ return;
+ }
+
+ settings = g_settings_new ("org.gnome.settings-daemon.peripherals.touchscreen");
+ locked = !g_settings_get_boolean (settings, "orientation-lock");
+ g_settings_set_boolean (settings, "orientation-lock", locked);
+ g_object_unref (settings);
+
+ show_osd (manager, locked ? "rotation-locked-symbolic"
+ : "rotation-allowed-symbolic", NULL, -1, NULL);
+}
+
+static void
+do_toggle_accessibility_key (const char *key)
+{
+ GSettings *settings;
+ gboolean state;
+
+ settings = g_settings_new ("org.gnome.desktop.a11y.applications");
+ state = g_settings_get_boolean (settings, key);
+ g_settings_set_boolean (settings, key, !state);
+ g_object_unref (settings);
+}
+
+static void
+do_magnifier_action (GsdMediaKeysManager *manager)
+{
+ do_toggle_accessibility_key ("screen-magnifier-enabled");
+}
+
+static void
+do_screenreader_action (GsdMediaKeysManager *manager)
+{
+ do_toggle_accessibility_key ("screen-reader-enabled");
+}
+
+static void
+do_on_screen_keyboard_action (GsdMediaKeysManager *manager)
+{
+ do_toggle_accessibility_key ("screen-keyboard-enabled");
+}
+
+static void
+do_text_size_action (GsdMediaKeysManager *manager,
+ MediaKeyType type)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ gdouble factor, best, distance;
+ guint i;
+
+ /* Same values used in the Seeing tab of the Universal Access panel */
+ static gdouble factors[] = {
+ 0.75,
+ 1.0,
+ 1.25,
+ 1.5
+ };
+
+ /* Figure out the current DPI scaling factor */
+ factor = g_settings_get_double (priv->interface_settings, "text-scaling-factor");
+ factor += (type == INCREASE_TEXT_KEY ? 0.25 : -0.25);
+
+ /* Try to find a matching value */
+ distance = 1e6;
+ best = 1.0;
+ for (i = 0; i < G_N_ELEMENTS(factors); i++) {
+ gdouble d;
+ d = fabs (factor - factors[i]);
+ if (d < distance) {
+ best = factors[i];
+ distance = d;
+ }
+ }
+
+ if (best == 1.0)
+ g_settings_reset (priv->interface_settings, "text-scaling-factor");
+ else
+ g_settings_set_double (priv->interface_settings, "text-scaling-factor", best);
+}
+
+static void
+do_magnifier_zoom_action (GsdMediaKeysManager *manager,
+ MediaKeyType type)
+{
+ GSettings *settings;
+ gdouble offset, value;
+
+ if (type == MAGNIFIER_ZOOM_IN_KEY)
+ offset = 1.0;
+ else
+ offset = -1.0;
+
+ settings = g_settings_new ("org.gnome.desktop.a11y.magnifier");
+ value = g_settings_get_double (settings, "mag-factor");
+ value += offset;
+ value = roundl (value);
+ g_settings_set_double (settings, "mag-factor", value);
+ g_object_unref (settings);
+}
+
+static void
+do_toggle_contrast_action (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ gboolean high_contrast;
+ char *theme;
+
+ /* Are we using HighContrast now? */
+ theme = g_settings_get_string (priv->interface_settings, "gtk-theme");
+ high_contrast = g_str_equal (theme, HIGH_CONTRAST);
+ g_free (theme);
+
+ if (high_contrast != FALSE) {
+ if (priv->gtk_theme == NULL)
+ g_settings_reset (priv->interface_settings, "gtk-theme");
+ else
+ g_settings_set (priv->interface_settings, "gtk-theme", priv->gtk_theme);
+ g_settings_set (priv->interface_settings, "icon-theme", priv->icon_theme);
+ } else {
+ g_settings_set (priv->interface_settings, "gtk-theme", HIGH_CONTRAST);
+ g_settings_set (priv->interface_settings, "icon-theme", HIGH_CONTRAST);
+ }
+}
+
+static void
+power_action (GsdMediaKeysManager *manager,
+ const char *action,
+ gboolean allow_interaction)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ g_dbus_proxy_call (priv->logind_proxy,
+ action,
+ g_variant_new ("(b)", allow_interaction),
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT,
+ priv->bus_cancellable,
+ NULL, NULL);
+}
+
+static void
+do_config_power_action (GsdMediaKeysManager *manager,
+ GsdPowerActionType action_type,
+ gboolean in_lock_screen)
+{
+ switch (action_type) {
+ case GSD_POWER_ACTION_SUSPEND:
+ power_action (manager, "Suspend", !in_lock_screen);
+ break;
+ case GSD_POWER_ACTION_INTERACTIVE:
+ if (!in_lock_screen)
+ gnome_session_shutdown (manager);
+ break;
+ case GSD_POWER_ACTION_SHUTDOWN:
+ power_action (manager, "PowerOff", !in_lock_screen);
+ break;
+ case GSD_POWER_ACTION_HIBERNATE:
+ power_action (manager, "Hibernate", !in_lock_screen);
+ break;
+ case GSD_POWER_ACTION_BLANK:
+ case GSD_POWER_ACTION_LOGOUT:
+ case GSD_POWER_ACTION_NOTHING:
+ /* these actions cannot be handled by media-keys and
+ * are not used in this context */
+ break;
+ }
+}
+
+static gboolean
+supports_power_action (GsdMediaKeysManager *manager,
+ GsdPowerActionType action_type)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ const char *method_name = NULL;
+ g_autoptr(GVariant) variant = NULL;
+ const char *reply;
+ gboolean result = FALSE;
+
+ switch (action_type) {
+ case GSD_POWER_ACTION_SUSPEND:
+ method_name = "CanSuspend";
+ break;
+ case GSD_POWER_ACTION_SHUTDOWN:
+ method_name = "CanPowerOff";
+ break;
+ case GSD_POWER_ACTION_HIBERNATE:
+ method_name = "CanHibernate";
+ break;
+ case GSD_POWER_ACTION_INTERACTIVE:
+ case GSD_POWER_ACTION_BLANK:
+ case GSD_POWER_ACTION_LOGOUT:
+ case GSD_POWER_ACTION_NOTHING:
+ break;
+ }
+
+ if (method_name == NULL)
+ return FALSE;
+
+ variant = g_dbus_proxy_call_sync (priv->logind_proxy,
+ method_name,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ priv->bus_cancellable,
+ NULL);
+
+ if (variant == NULL)
+ return FALSE;
+
+ g_variant_get (variant, "(&s)", &reply);
+ if (g_strcmp0 (reply, "yes") == 0)
+ result = TRUE;
+
+ return result;
+}
+
+static void
+do_config_power_button_action (GsdMediaKeysManager *manager,
+ gboolean in_lock_screen)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GsdPowerButtonActionType action_type;
+ GsdPowerActionType action;
+
+ if (priv->power_button_disabled)
+ return;
+
+ /* Always power off VMs when power off is pressed in the menus */
+ if (g_strcmp0 (priv->chassis_type, "vm") == 0) {
+ power_action (manager, "PowerOff", !in_lock_screen);
+ return;
+ }
+
+ action_type = g_settings_get_enum (priv->power_settings, "power-button-action");
+ switch (action_type) {
+ case GSD_POWER_BUTTON_ACTION_SUSPEND:
+ action = GSD_POWER_ACTION_SUSPEND;
+ break;
+ case GSD_POWER_BUTTON_ACTION_HIBERNATE:
+ action = GSD_POWER_ACTION_HIBERNATE;
+ break;
+ case GSD_POWER_BUTTON_ACTION_INTERACTIVE:
+ action = GSD_POWER_ACTION_INTERACTIVE;
+ break;
+ default:
+ g_warn_if_reached ();
+ G_GNUC_FALLTHROUGH;
+ case GSD_POWER_BUTTON_ACTION_NOTHING:
+ /* do nothing */
+ return;
+ }
+
+ if (action != GSD_POWER_ACTION_INTERACTIVE && !supports_power_action (manager, action))
+ action = GSD_POWER_ACTION_INTERACTIVE;
+
+ do_config_power_action (manager, action, in_lock_screen);
+}
+
+static void
+update_brightness_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ int percentage;
+ GVariant *variant;
+ GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ const char *icon, *debug;
+ char *connector = NULL;
+
+ /* update the dialog with the new value */
+ if (G_DBUS_PROXY (source_object) == priv->power_keyboard_proxy) {
+ debug = "keyboard";
+ } else {
+ debug = "screen";
+ }
+
+ variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+ res, &error);
+ if (variant == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to set new %s percentage: %s",
+ debug, error->message);
+ g_error_free (error);
+ return;
+ }
+
+ /* update the dialog with the new value */
+ if (G_DBUS_PROXY (source_object) == priv->power_keyboard_proxy) {
+ icon = "keyboard-brightness-symbolic";
+ g_variant_get (variant, "(i)", &percentage);
+ } else {
+ icon = "display-brightness-symbolic";
+ g_variant_get (variant, "(i&s)", &percentage, &connector);
+ }
+
+ show_osd (manager, icon, NULL, (double) percentage / 100.0, connector);
+ g_variant_unref (variant);
+}
+
+static void
+do_brightness_action (GsdMediaKeysManager *manager,
+ MediaKeyType type)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ const char *cmd;
+ GDBusProxy *proxy;
+
+ switch (type) {
+ case KEYBOARD_BRIGHTNESS_UP_KEY:
+ case KEYBOARD_BRIGHTNESS_DOWN_KEY:
+ case KEYBOARD_BRIGHTNESS_TOGGLE_KEY:
+ proxy = priv->power_keyboard_proxy;
+ break;
+ case SCREEN_BRIGHTNESS_UP_KEY:
+ case SCREEN_BRIGHTNESS_DOWN_KEY:
+ case SCREEN_BRIGHTNESS_CYCLE_KEY:
+ proxy = priv->power_screen_proxy;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (priv->connection == NULL ||
+ proxy == NULL) {
+ g_warning ("No existing D-Bus connection trying to handle power keys");
+ return;
+ }
+
+ switch (type) {
+ case KEYBOARD_BRIGHTNESS_UP_KEY:
+ case SCREEN_BRIGHTNESS_UP_KEY:
+ cmd = "StepUp";
+ break;
+ case KEYBOARD_BRIGHTNESS_DOWN_KEY:
+ case SCREEN_BRIGHTNESS_DOWN_KEY:
+ cmd = "StepDown";
+ break;
+ case KEYBOARD_BRIGHTNESS_TOGGLE_KEY:
+ cmd = "Toggle";
+ break;
+ case SCREEN_BRIGHTNESS_CYCLE_KEY:
+ cmd = "Cycle";
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* call into the power plugin */
+ g_dbus_proxy_call (proxy,
+ cmd,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ update_brightness_cb,
+ manager);
+}
+
+static void
+do_battery_action (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ gdouble percentage;
+ UpDeviceKind kind;
+ gchar *icon_name;
+
+ g_return_if_fail (priv->composite_device != NULL);
+
+ g_object_get (priv->composite_device,
+ "kind", &kind,
+ "icon-name", &icon_name,
+ "percentage", &percentage,
+ NULL);
+
+ if (kind == UP_DEVICE_KIND_UPS || kind == UP_DEVICE_KIND_BATTERY) {
+ g_debug ("showing battery level OSD");
+ show_osd (manager, icon_name, NULL, (double) percentage / 100.0, NULL);
+ }
+
+ g_free (icon_name);
+}
+
+static gboolean
+get_rfkill_property (GsdMediaKeysManager *manager,
+ const char *property)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GVariant *v;
+ gboolean ret;
+
+ v = g_dbus_proxy_get_cached_property (priv->rfkill_proxy, property);
+ if (!v)
+ return FALSE;
+ ret = g_variant_get_boolean (v);
+ g_variant_unref (v);
+
+ return ret;
+}
+
+typedef struct {
+ GsdMediaKeysManager *manager;
+ char *property;
+ gboolean bluetooth;
+ gboolean target_state;
+} RfkillData;
+
+static void
+set_rfkill_complete (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ GVariant *variant;
+ RfkillData *data = user_data;
+
+ variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), result, &error);
+
+ if (variant == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to set '%s' property: %s", data->property, error->message);
+ g_error_free (error);
+ goto out;
+ }
+ g_variant_unref (variant);
+
+ g_debug ("Finished changing rfkill, property %s is now %s",
+ data->property, data->target_state ? "true" : "false");
+
+ if (data->bluetooth) {
+ if (data->target_state)
+ show_osd (data->manager, "bluetooth-disabled-symbolic",
+ _("Bluetooth disabled"), -1, NULL);
+ else
+ show_osd (data->manager, "bluetooth-active-symbolic",
+ _("Bluetooth enabled"), -1, NULL);
+ } else {
+ if (data->target_state)
+ show_osd (data->manager, "airplane-mode-symbolic",
+ _("Airplane mode enabled"), -1, NULL);
+ else
+ show_osd (data->manager, "network-wireless-signal-excellent-symbolic",
+ _("Airplane mode disabled"), -1, NULL);
+ }
+
+out:
+ g_free (data->property);
+ g_free (data);
+}
+
+static void
+do_rfkill_action (GsdMediaKeysManager *manager,
+ gboolean bluetooth)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ const char *has_mode, *hw_mode, *mode;
+ gboolean new_state;
+ guint64 current_time;
+ RfkillData *data;
+
+ has_mode = bluetooth ? "BluetoothHasAirplaneMode" : "HasAirplaneMode";
+ hw_mode = bluetooth ? "BluetoothHardwareAirplaneMode" : "HardwareAirplaneMode";
+ mode = bluetooth ? "BluetoothAirplaneMode" : "AirplaneMode";
+
+ if (priv->rfkill_proxy == NULL)
+ return;
+
+ /* Some hardwares can generate multiple rfkill events from different
+ * drivers, on a single hotkey press. Only process the first event and
+ * debounce the others */
+ current_time = g_get_monotonic_time ();
+ if (current_time - priv->rfkill_last_time < G_USEC_PER_SEC)
+ return;
+
+ priv->rfkill_last_time = current_time;
+
+ if (get_rfkill_property (manager, has_mode) == FALSE)
+ return;
+
+ if (get_rfkill_property (manager, hw_mode)) {
+ show_osd (manager, "airplane-mode-symbolic",
+ _("Hardware Airplane Mode"), -1, NULL);
+ return;
+ }
+
+ new_state = !get_rfkill_property (manager, mode);
+ data = g_new0 (RfkillData, 1);
+ data->manager = manager;
+ data->property = g_strdup (mode);
+ data->bluetooth = bluetooth;
+ data->target_state = new_state;
+ g_dbus_proxy_call (priv->rfkill_proxy,
+ "org.freedesktop.DBus.Properties.Set",
+ g_variant_new ("(ssv)",
+ "org.gnome.SettingsDaemon.Rfkill",
+ data->property,
+ g_variant_new_boolean (new_state)),
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ priv->rfkill_cancellable,
+ set_rfkill_complete, data);
+
+ g_debug ("Setting rfkill property %s to %s",
+ data->property, new_state ? "true" : "false");
+}
+
+static void
+do_custom_action (GsdMediaKeysManager *manager,
+ const gchar *device_node,
+ MediaKey *key,
+ gint64 timestamp)
+{
+ g_debug ("Launching custom action for key (on device node %s)", device_node);
+
+ execute (manager, key->custom_command, timestamp);
+}
+
+static gboolean
+do_action (GsdMediaKeysManager *manager,
+ const gchar *device_node,
+ guint mode,
+ MediaKeyType type,
+ gint64 timestamp)
+{
+ g_debug ("Launching action for key type '%d' (on device node %s)", type, device_node);
+
+ gboolean power_action_noninteractive = (POWER_KEYS_MODE_NO_DIALOG & mode);
+
+ switch (type) {
+ case TOUCHPAD_KEY:
+ do_touchpad_action (manager);
+ break;
+ case TOUCHPAD_ON_KEY:
+ do_touchpad_osd_action (manager, TRUE);
+ break;
+ case TOUCHPAD_OFF_KEY:
+ do_touchpad_osd_action (manager, FALSE);
+ break;
+ case MUTE_KEY:
+ case VOLUME_DOWN_KEY:
+ case VOLUME_UP_KEY:
+ do_sound_action (manager, device_node, type, SOUND_ACTION_FLAG_IS_OUTPUT);
+ break;
+ case MIC_MUTE_KEY:
+ do_sound_action (manager, device_node, MUTE_KEY, SOUND_ACTION_FLAG_IS_QUIET);
+ break;
+ case MUTE_QUIET_KEY:
+ do_sound_action (manager, device_node, MUTE_KEY,
+ SOUND_ACTION_FLAG_IS_OUTPUT | SOUND_ACTION_FLAG_IS_QUIET);
+ break;
+ case VOLUME_DOWN_QUIET_KEY:
+ do_sound_action (manager, device_node, VOLUME_DOWN_KEY,
+ SOUND_ACTION_FLAG_IS_OUTPUT | SOUND_ACTION_FLAG_IS_QUIET);
+ break;
+ case VOLUME_UP_QUIET_KEY:
+ do_sound_action (manager, device_node, VOLUME_UP_KEY,
+ SOUND_ACTION_FLAG_IS_OUTPUT | SOUND_ACTION_FLAG_IS_QUIET);
+ break;
+ case VOLUME_DOWN_PRECISE_KEY:
+ do_sound_action (manager, device_node, VOLUME_DOWN_KEY,
+ SOUND_ACTION_FLAG_IS_OUTPUT | SOUND_ACTION_FLAG_IS_PRECISE);
+ break;
+ case VOLUME_UP_PRECISE_KEY:
+ do_sound_action (manager, device_node, VOLUME_UP_KEY,
+ SOUND_ACTION_FLAG_IS_OUTPUT | SOUND_ACTION_FLAG_IS_PRECISE);
+ break;
+ case LOGOUT_KEY:
+ gnome_session_shutdown (manager);
+ break;
+ case EJECT_KEY:
+ do_eject_action (manager);
+ break;
+ case HOME_KEY:
+ do_home_key_action (manager, timestamp);
+ break;
+ case SEARCH_KEY:
+ do_search_action (manager, timestamp);
+ break;
+ case EMAIL_KEY:
+ do_url_action (manager, "mailto", timestamp);
+ break;
+ case SCREENSAVER_KEY:
+ do_lock_screensaver (manager);
+ break;
+ case HELP_KEY:
+ do_url_action (manager, "ghelp", timestamp);
+ break;
+ case WWW_KEY:
+ do_url_action (manager, "http", timestamp);
+ break;
+ case MEDIA_KEY:
+ do_media_action (manager, timestamp);
+ break;
+ case CALCULATOR_KEY:
+ do_execute_desktop_or_desktop (manager, "org.gnome.Calculator.desktop", "gnome-calculator.desktop", timestamp);
+ break;
+ case CONTROL_CENTER_KEY:
+ do_execute_desktop_or_desktop (manager, "org.gnome.Settings.desktop", NULL, timestamp);
+ break;
+ case PLAY_KEY:
+ return do_multimedia_player_action (manager, "Play");
+ case PAUSE_KEY:
+ return do_multimedia_player_action (manager, "Pause");
+ case STOP_KEY:
+ return do_multimedia_player_action (manager, "Stop");
+ case PREVIOUS_KEY:
+ return do_multimedia_player_action (manager, "Previous");
+ case NEXT_KEY:
+ return do_multimedia_player_action (manager, "Next");
+ case REWIND_KEY:
+ return do_multimedia_player_action (manager, "Rewind");
+ case FORWARD_KEY:
+ return do_multimedia_player_action (manager, "FastForward");
+ case REPEAT_KEY:
+ return do_multimedia_player_action (manager, "Repeat");
+ case RANDOM_KEY:
+ return do_multimedia_player_action (manager, "Shuffle");
+ case ROTATE_VIDEO_LOCK_KEY:
+ do_video_rotate_lock_action (manager, timestamp);
+ break;
+ case MAGNIFIER_KEY:
+ do_magnifier_action (manager);
+ break;
+ case SCREENREADER_KEY:
+ do_screenreader_action (manager);
+ break;
+ case ON_SCREEN_KEYBOARD_KEY:
+ do_on_screen_keyboard_action (manager);
+ break;
+ case INCREASE_TEXT_KEY:
+ case DECREASE_TEXT_KEY:
+ do_text_size_action (manager, type);
+ break;
+ case MAGNIFIER_ZOOM_IN_KEY:
+ case MAGNIFIER_ZOOM_OUT_KEY:
+ do_magnifier_zoom_action (manager, type);
+ break;
+ case TOGGLE_CONTRAST_KEY:
+ do_toggle_contrast_action (manager);
+ break;
+ case POWER_KEY:
+ do_config_power_button_action (manager, power_action_noninteractive);
+ break;
+ case SUSPEND_KEY:
+ do_config_power_action (manager, GSD_POWER_ACTION_SUSPEND, power_action_noninteractive);
+ break;
+ case HIBERNATE_KEY:
+ do_config_power_action (manager, GSD_POWER_ACTION_HIBERNATE, power_action_noninteractive);
+ break;
+ case SCREEN_BRIGHTNESS_UP_KEY:
+ case SCREEN_BRIGHTNESS_DOWN_KEY:
+ case SCREEN_BRIGHTNESS_CYCLE_KEY:
+ case KEYBOARD_BRIGHTNESS_UP_KEY:
+ case KEYBOARD_BRIGHTNESS_DOWN_KEY:
+ case KEYBOARD_BRIGHTNESS_TOGGLE_KEY:
+ do_brightness_action (manager, type);
+ break;
+ case BATTERY_KEY:
+ do_battery_action (manager);
+ break;
+ case RFKILL_KEY:
+ do_rfkill_action (manager, FALSE);
+ break;
+ case BLUETOOTH_RFKILL_KEY:
+ do_rfkill_action (manager, TRUE);
+ break;
+ /* Note, no default so compiler catches missing keys */
+ case CUSTOM_KEY:
+ g_assert_not_reached ();
+ }
+
+ return FALSE;
+}
+
+static void
+on_accelerator_activated (ShellKeyGrabber *grabber,
+ guint accel_id,
+ GVariant *parameters,
+ GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GVariantDict dict;
+ guint i;
+ guint deviceid;
+ gchar *device_node;
+ guint timestamp;
+ guint mode;
+
+ g_variant_dict_init (&dict, parameters);
+
+ if (!g_variant_dict_lookup (&dict, "device-id", "u", &deviceid))
+ deviceid = 0;
+ if (!g_variant_dict_lookup (&dict, "device-node", "s", &device_node))
+ device_node = NULL;
+ if (!g_variant_dict_lookup (&dict, "timestamp", "u", &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_key_grabber_ready (GObject *source,
+ GAsyncResult *result,
+ gpointer data)
+{
+ GsdMediaKeysManager *manager = data;
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GError *error = NULL;
+
+ priv->key_grabber = shell_key_grabber_proxy_new_for_bus_finish (result, &error);
+
+ if (!priv->key_grabber) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to create proxy for key grabber: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (priv->key_grabber),
+ SHELL_GRABBER_CALL_TIMEOUT);
+
+ g_signal_connect (priv->key_grabber, "accelerator-activated",
+ G_CALLBACK (on_accelerator_activated), manager);
+
+ init_kbd (manager);
+}
+
+static void
+shell_presence_changed (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ gchar *name_owner;
+
+ name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (priv->shell_proxy));
+
+ g_ptr_array_set_size (priv->keys, 0);
+ g_clear_object (&priv->key_grabber);
+
+ if (name_owner) {
+ shell_key_grabber_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+ 0,
+ name_owner,
+ SHELL_DBUS_PATH,
+ priv->grab_cancellable,
+ on_key_grabber_ready, manager);
+ g_free (name_owner);
+ }
+}
+
+static void
+on_rfkill_proxy_ready (GObject *source,
+ GAsyncResult *result,
+ gpointer data)
+{
+ GsdMediaKeysManager *manager = data;
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ priv->rfkill_proxy =
+ g_dbus_proxy_new_for_bus_finish (result, NULL);
+}
+
+static void
+rfkill_appeared_cb (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ GsdMediaKeysManager *manager = user_data;
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+ 0, NULL,
+ "org.gnome.SettingsDaemon.Rfkill",
+ "/org/gnome/SettingsDaemon/Rfkill",
+ "org.gnome.SettingsDaemon.Rfkill",
+ priv->rfkill_cancellable,
+ on_rfkill_proxy_ready, manager);
+}
+
+static gboolean
+start_media_keys_idle_cb (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ g_debug ("Starting media_keys manager");
+ gnome_settings_profile_start (NULL);
+
+ priv->keys = g_ptr_array_new_with_free_func ((GDestroyNotify) media_key_unref);
+ priv->keys_to_sync = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) media_key_unref, NULL);
+
+ initialize_volume_handler (manager);
+
+ priv->settings = g_settings_new (SETTINGS_BINDING_DIR);
+ g_signal_connect (G_OBJECT (priv->settings), "changed",
+ G_CALLBACK (gsettings_changed_cb), manager);
+ g_signal_connect (G_OBJECT (priv->settings), "changed::custom-keybindings",
+ G_CALLBACK (gsettings_custom_changed_cb), manager);
+
+ priv->custom_settings =
+ g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+
+ priv->sound_settings = g_settings_new (SETTINGS_SOUND_DIR);
+ g_signal_connect (G_OBJECT (priv->sound_settings),
+ "changed::" ALLOW_VOLUME_ABOVE_100_PERCENT_KEY,
+ G_CALLBACK (allow_volume_above_100_percent_changed_cb), manager);
+ allow_volume_above_100_percent_changed_cb (priv->sound_settings,
+ ALLOW_VOLUME_ABOVE_100_PERCENT_KEY, manager);
+
+ /* for the power plugin interface code */
+ priv->power_settings = g_settings_new (SETTINGS_POWER_DIR);
+ priv->chassis_type = gnome_settings_get_chassis_type ();
+
+ /* Logic from http://git.gnome.org/browse/gnome-shell/tree/js/ui/status/accessibility.js#n163 */
+ priv->interface_settings = g_settings_new (SETTINGS_INTERFACE_DIR);
+ g_signal_connect (G_OBJECT (priv->interface_settings), "changed::gtk-theme",
+ G_CALLBACK (update_theme_settings), manager);
+ g_signal_connect (G_OBJECT (priv->interface_settings), "changed::icon-theme",
+ G_CALLBACK (update_theme_settings), manager);
+ priv->gtk_theme = g_settings_get_string (priv->interface_settings, "gtk-theme");
+ if (g_str_equal (priv->gtk_theme, HIGH_CONTRAST)) {
+ g_free (priv->gtk_theme);
+ priv->gtk_theme = NULL;
+ }
+ priv->icon_theme = g_settings_get_string (priv->interface_settings, "icon-theme");
+
+ priv->grab_cancellable = g_cancellable_new ();
+ priv->rfkill_cancellable = g_cancellable_new ();
+
+ priv->shell_proxy = gnome_settings_bus_get_shell_proxy ();
+ g_signal_connect_swapped (priv->shell_proxy, "notify::g-name-owner",
+ G_CALLBACK (shell_presence_changed), manager);
+ shell_presence_changed (manager);
+
+ priv->rfkill_watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ "org.gnome.SettingsDaemon.Rfkill",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ rfkill_appeared_cb,
+ NULL,
+ manager, NULL);
+
+ g_debug ("Starting mpris controller");
+ priv->mpris_controller = mpris_controller_new ();
+
+ /* Rotation */
+ priv->iio_sensor_watch_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM,
+ "net.hadess.SensorProxy",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ iio_sensor_appeared_cb,
+ iio_sensor_disappeared_cb,
+ manager, NULL);
+
+ gnome_settings_profile_end (NULL);
+
+ priv->start_idle_id = 0;
+
+ return FALSE;
+}
+
+static GVariant *
+map_keybinding (GVariant *variant, GVariant *old_default, GVariant *new_default)
+{
+ g_autoptr(GPtrArray) array = g_ptr_array_new ();
+ g_autofree const gchar **defaults = NULL;
+ const gchar *old_default_value;
+ const gchar **pos;
+ const gchar *value;
+
+ defaults = g_variant_get_strv (new_default, NULL);
+ g_return_val_if_fail (defaults != NULL, NULL);
+ pos = defaults;
+
+ value = g_variant_get_string (variant, NULL);
+ old_default_value = g_variant_get_string (old_default, NULL);
+
+ /* Reset the keybinding configuration even if the user has the default
+ * configured explicitly (as the key will be bound by the corresponding
+ * static binding now). */
+ if (g_strcmp0 (value, old_default_value) == 0)
+ return NULL;
+
+ /* If the user has a custom value that is not in the list, then
+ * insert it instead of the first default entry. */
+ if (!g_strv_contains (defaults, value)) {
+ g_ptr_array_add (array, (gpointer) value);
+ if (*pos)
+ pos++;
+ }
+
+ /* Add all remaining default values */
+ for (; *pos; pos++)
+ g_ptr_array_add (array, (gpointer) *pos);
+
+ g_ptr_array_add (array, NULL);
+
+ return g_variant_new_strv ((const gchar * const *) array->pdata, -1);
+}
+
+static void
+migrate_keybinding_settings (void)
+{
+ GsdSettingsMigrateEntry binding_entries[] = {
+ { "calculator", "calculator", map_keybinding },
+ { "control-center", "control-center", map_keybinding },
+ { "email", "email", map_keybinding },
+ { "eject", "eject", map_keybinding },
+ { "help", "help", map_keybinding },
+ { "home", "home", map_keybinding },
+ { "media", "media", map_keybinding },
+ { "next", "next", map_keybinding },
+ { "pause", "pause", map_keybinding },
+ { "play", "play", map_keybinding },
+ { "logout", "logout", map_keybinding },
+ { "previous", "previous", map_keybinding },
+ { "screensaver", "screensaver", map_keybinding },
+ { "search", "search", map_keybinding },
+ { "stop", "stop", map_keybinding },
+ { "volume-down", "volume-down", map_keybinding },
+ { "volume-mute", "volume-mute", map_keybinding },
+ { "volume-up", "volume-up", map_keybinding },
+ { "mic-mute", "mic-mute", map_keybinding },
+ { "www", "www", map_keybinding },
+ { "magnifier", "magnifier", map_keybinding },
+ { "screenreader", "screenreader", map_keybinding },
+ { "on-screen-keyboard", "on-screen-keyboard", map_keybinding },
+ { "increase-text-size", "increase-text-size", map_keybinding },
+ { "decrease-text-size", "decrease-text-size", map_keybinding },
+ { "toggle-contrast", "toggle-contrast", map_keybinding },
+ { "magnifier-zoom-in", "magnifier-zoom-in", map_keybinding },
+ { "magnifier-zoom-out", "magnifier-zoom-out", map_keybinding },
+ };
+
+ gsd_settings_migrate_check ("org.gnome.settings-daemon.plugins.media-keys.deprecated",
+ "/org/gnome/settings-daemon/plugins/media-keys/",
+ "org.gnome.settings-daemon.plugins.media-keys",
+ "/org/gnome/settings-daemon/plugins/media-keys/",
+ binding_entries, G_N_ELEMENTS (binding_entries));
+}
+
+gboolean
+gsd_media_keys_manager_start (GsdMediaKeysManager *manager,
+ GError **error)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ const char * const subsystems[] = { "input", "usb", "sound", NULL };
+
+ gnome_settings_profile_start (NULL);
+
+ migrate_keybinding_settings ();
+
+#if HAVE_GUDEV
+ priv->streams = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ priv->udev_client = g_udev_client_new (subsystems);
+#endif
+
+ priv->start_idle_id = g_idle_add ((GSourceFunc) start_media_keys_idle_cb, manager);
+ g_source_set_name_by_id (priv->start_idle_id, "[gnome-settings-daemon] start_media_keys_idle_cb");
+
+ register_manager (manager_object);
+
+ gnome_settings_profile_end (NULL);
+
+ return TRUE;
+}
+
+void
+gsd_media_keys_manager_stop (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ g_debug ("Stopping media_keys manager");
+
+ if (priv->start_idle_id != 0) {
+ g_source_remove (priv->start_idle_id);
+ priv->start_idle_id = 0;
+ }
+
+ if (priv->bus_cancellable != NULL) {
+ g_cancellable_cancel (priv->bus_cancellable);
+ g_object_unref (priv->bus_cancellable);
+ priv->bus_cancellable = NULL;
+ }
+
+ if (priv->gtksettings != NULL) {
+ g_signal_handlers_disconnect_by_func (priv->gtksettings, sound_theme_changed, manager);
+ priv->gtksettings = NULL;
+ }
+
+ if (priv->rfkill_watch_id > 0) {
+ g_bus_unwatch_name (priv->rfkill_watch_id);
+ priv->rfkill_watch_id = 0;
+ }
+
+ if (priv->iio_sensor_watch_id > 0) {
+ g_bus_unwatch_name (priv->iio_sensor_watch_id);
+ priv->iio_sensor_watch_id = 0;
+ }
+
+ if (priv->inhibit_suspend_fd != -1) {
+ close (priv->inhibit_suspend_fd);
+ priv->inhibit_suspend_fd = -1;
+ priv->inhibit_suspend_taken = FALSE;
+ }
+
+ if (priv->reenable_power_button_timer_id) {
+ g_source_remove (priv->reenable_power_button_timer_id);
+ priv->reenable_power_button_timer_id = 0;
+ }
+
+ g_clear_pointer (&priv->ca, ca_context_destroy);
+
+#if HAVE_GUDEV
+ g_clear_pointer (&priv->streams, g_hash_table_destroy);
+ g_clear_object (&priv->udev_client);
+#endif /* HAVE_GUDEV */
+
+ g_clear_object (&priv->logind_proxy);
+ g_clear_object (&priv->settings);
+ g_clear_object (&priv->sound_settings);
+ g_clear_object (&priv->power_settings);
+ g_clear_object (&priv->power_proxy);
+ g_clear_object (&priv->power_screen_proxy);
+ g_clear_object (&priv->power_keyboard_proxy);
+ g_clear_object (&priv->composite_device);
+ g_clear_object (&priv->mpris_controller);
+ g_clear_object (&priv->iio_sensor_proxy);
+ g_clear_pointer (&priv->chassis_type, g_free);
+ g_clear_object (&priv->connection);
+
+ if (priv->keys_sync_data) {
+ /* Cancel ongoing sync. */
+ priv->keys_sync_data->cancelled = TRUE;
+ priv->keys_sync_data = NULL;
+ }
+ if (priv->keys_sync_source_id)
+ g_source_remove (priv->keys_sync_source_id);
+ priv->keys_sync_source_id = 0;
+
+ /* Remove all grabs; i.e.:
+ * - add all keys to the sync queue
+ * - remove all keys from the internal keys list
+ * - call the function to start a sync
+ * - "cancel" the sync operation as the manager will be gone
+ */
+ if (priv->keys != NULL) {
+ while (priv->keys->len) {
+ MediaKey *key = g_ptr_array_index (priv->keys, 0);
+ g_hash_table_add (priv->keys_to_sync, media_key_ref (key));
+ g_ptr_array_remove_index_fast (priv->keys, 0);
+ }
+
+ keys_sync_start (manager);
+
+ g_clear_pointer (&priv->keys, g_ptr_array_unref);
+ }
+
+ g_clear_pointer (&priv->keys_to_sync, g_hash_table_destroy);
+
+ g_clear_object (&priv->key_grabber);
+
+ if (priv->grab_cancellable != NULL) {
+ g_cancellable_cancel (priv->grab_cancellable);
+ g_clear_object (&priv->grab_cancellable);
+ }
+
+ if (priv->rfkill_cancellable != NULL) {
+ g_cancellable_cancel (priv->rfkill_cancellable);
+ g_clear_object (&priv->rfkill_cancellable);
+ }
+
+ g_clear_object (&priv->sink);
+ g_clear_object (&priv->source);
+ g_clear_object (&priv->volume);
+ g_clear_object (&priv->shell_proxy);
+
+ if (priv->audio_selection_watch_id)
+ g_bus_unwatch_name (priv->audio_selection_watch_id);
+ priv->audio_selection_watch_id = 0;
+ clear_audio_selection (manager);
+}
+
+static void
+inhibit_suspend_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GError *error = NULL;
+ GVariant *res;
+ GUnixFDList *fd_list = NULL;
+ gint idx;
+
+ res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error);
+ if (res == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Unable to inhibit suspend: %s", error->message);
+ g_error_free (error);
+ } else {
+ g_variant_get (res, "(h)", &idx);
+ priv->inhibit_suspend_fd = g_unix_fd_list_get (fd_list, idx, &error);
+ if (priv->inhibit_suspend_fd == -1) {
+ g_warning ("Failed to receive system suspend inhibitor fd: %s", error->message);
+ g_error_free (error);
+ }
+ g_debug ("System suspend inhibitor fd is %d", priv->inhibit_suspend_fd);
+ g_object_unref (fd_list);
+ g_variant_unref (res);
+ }
+}
+
+/* We take a delay inhibitor here, which causes logind to send a PrepareForSleep
+ * signal, so that we can set power_button_disabled on suspend.
+ */
+static void
+inhibit_suspend (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ if (priv->inhibit_suspend_taken) {
+ g_debug ("already inhibited suspend");
+ return;
+ }
+ g_debug ("Adding suspend delay inhibitor");
+ priv->inhibit_suspend_taken = TRUE;
+ g_dbus_proxy_call_with_unix_fd_list (priv->logind_proxy,
+ "Inhibit",
+ g_variant_new ("(ssss)",
+ "sleep",
+ g_get_user_name (),
+ "GNOME handling keypresses",
+ "delay"),
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL,
+ inhibit_suspend_done,
+ manager);
+}
+
+static void
+uninhibit_suspend (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ if (priv->inhibit_suspend_fd == -1) {
+ g_debug ("no suspend delay inhibitor");
+ return;
+ }
+ g_debug ("Removing suspend delay inhibitor");
+ close (priv->inhibit_suspend_fd);
+ priv->inhibit_suspend_fd = -1;
+ priv->inhibit_suspend_taken = FALSE;
+}
+
+static gboolean
+reenable_power_button_timer_cb (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ priv->power_button_disabled = FALSE;
+ /* This is a one shot timer. */
+ priv->reenable_power_button_timer_id = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static void
+setup_reenable_power_button_timer (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ if (priv->reenable_power_button_timer_id != 0)
+ return;
+
+ priv->reenable_power_button_timer_id =
+ g_timeout_add (GSD_REENABLE_POWER_BUTTON_DELAY,
+ (GSourceFunc) reenable_power_button_timer_cb,
+ manager);
+ g_source_set_name_by_id (priv->reenable_power_button_timer_id,
+ "[GsdMediaKeysManager] Reenable power button timer");
+}
+
+static void
+stop_reenable_power_button_timer (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ if (priv->reenable_power_button_timer_id == 0)
+ return;
+
+ g_source_remove (priv->reenable_power_button_timer_id);
+ priv->reenable_power_button_timer_id = 0;
+}
+
+static void
+logind_proxy_signal_cb (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ gboolean is_about_to_suspend;
+
+ if (g_strcmp0 (signal_name, "PrepareForSleep") != 0)
+ return;
+ g_variant_get (parameters, "(b)", &is_about_to_suspend);
+ if (is_about_to_suspend) {
+ /* Some devices send a power-button press on resume when woken
+ * up with the power-button, suppress this, to avoid immediate
+ * re-suspend. */
+ stop_reenable_power_button_timer (manager);
+ priv->power_button_disabled = TRUE;
+ uninhibit_suspend (manager);
+ } else {
+ inhibit_suspend (manager);
+ /* Re-enable power-button handling (after a small delay) */
+ setup_reenable_power_button_timer (manager);
+ }
+}
+
+static void
+gsd_media_keys_manager_class_init (GsdMediaKeysManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gsd_media_keys_manager_finalize;
+}
+
+static void
+inhibit_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GError *error = NULL;
+ GVariant *res;
+ GUnixFDList *fd_list = NULL;
+ gint idx;
+
+ res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error);
+ if (res == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Unable to inhibit keypresses: %s", error->message);
+ g_error_free (error);
+ } else {
+ g_variant_get (res, "(h)", &idx);
+ priv->inhibit_keys_fd = g_unix_fd_list_get (fd_list, idx, &error);
+ if (priv->inhibit_keys_fd == -1) {
+ g_warning ("Failed to receive system inhibitor fd: %s", error->message);
+ g_error_free (error);
+ }
+ g_debug ("System inhibitor fd is %d", priv->inhibit_keys_fd);
+ g_object_unref (fd_list);
+ g_variant_unref (res);
+ }
+}
+
+static void
+gsd_media_keys_manager_init (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GError *error;
+ GDBusConnection *bus;
+
+ error = NULL;
+ priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (bus == NULL) {
+ g_warning ("Failed to connect to system bus: %s",
+ error->message);
+ g_error_free (error);
+ return;
+ }
+
+ priv->logind_proxy =
+ g_dbus_proxy_new_sync (bus,
+ 0,
+ NULL,
+ SYSTEMD_DBUS_NAME,
+ SYSTEMD_DBUS_PATH,
+ SYSTEMD_DBUS_INTERFACE,
+ NULL,
+ &error);
+
+ if (priv->logind_proxy == NULL) {
+ g_warning ("Failed to connect to systemd: %s",
+ error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (bus);
+
+ g_debug ("Adding system inhibitors for power keys");
+ priv->inhibit_keys_fd = -1;
+ g_dbus_proxy_call_with_unix_fd_list (priv->logind_proxy,
+ "Inhibit",
+ g_variant_new ("(ssss)",
+ "handle-power-key:handle-suspend-key:handle-hibernate-key",
+ g_get_user_name (),
+ "GNOME handling keypresses",
+ "block"),
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL,
+ inhibit_done,
+ manager);
+
+ g_debug ("Adding delay inhibitor for suspend");
+ priv->inhibit_suspend_fd = -1;
+ g_signal_connect (priv->logind_proxy, "g-signal",
+ G_CALLBACK (logind_proxy_signal_cb),
+ manager);
+ inhibit_suspend (manager);
+}
+
+static void
+gsd_media_keys_manager_finalize (GObject *object)
+{
+ GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (object);
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ gsd_media_keys_manager_stop (manager);
+
+ if (priv->inhibit_keys_fd != -1)
+ close (priv->inhibit_keys_fd);
+
+ g_clear_object (&priv->logind_proxy);
+ g_clear_object (&priv->screen_saver_proxy);
+
+ G_OBJECT_CLASS (gsd_media_keys_manager_parent_class)->finalize (object);
+}
+
+static void
+power_keyboard_proxy_signal_cb (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data);
+ gint brightness;
+ const gchar *source;
+
+ if (g_strcmp0 (signal_name, "BrightnessChanged") != 0)
+ return;
+
+ g_variant_get (parameters, "(i&s)", &brightness, &source);
+
+ /* For non "internal" changes we already show the osd when handling
+ * the hotkey causing the change. */
+ if (g_strcmp0 (source, "internal") != 0)
+ return;
+
+ show_osd (manager, "keyboard-brightness-symbolic", NULL, (double) brightness / 100.0, NULL);
+}
+
+static void
+power_ready_cb (GObject *source_object,
+ GAsyncResult *res,
+ GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GError *error = NULL;
+
+ priv->power_proxy = g_dbus_proxy_new_finish (res, &error);
+ if (priv->power_proxy == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to get proxy for power: %s",
+ error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+power_screen_ready_cb (GObject *source_object,
+ GAsyncResult *res,
+ GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GError *error = NULL;
+
+ priv->power_screen_proxy = g_dbus_proxy_new_finish (res, &error);
+ if (priv->power_screen_proxy == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to get proxy for power (screen): %s",
+ error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+power_keyboard_ready_cb (GObject *source_object,
+ GAsyncResult *res,
+ GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GError *error = NULL;
+
+ priv->power_keyboard_proxy = g_dbus_proxy_new_finish (res, &error);
+ if (priv->power_keyboard_proxy == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to get proxy for power (keyboard): %s",
+ error->message);
+ g_error_free (error);
+ }
+
+ g_signal_connect (priv->power_keyboard_proxy, "g-signal",
+ G_CALLBACK (power_keyboard_proxy_signal_cb),
+ manager);
+}
+
+static void
+on_bus_gotten (GObject *source_object,
+ GAsyncResult *res,
+ GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+ GDBusConnection *connection;
+ GError *error = NULL;
+ UpClient *up_client;
+
+ connection = g_bus_get_finish (res, &error);
+ if (connection == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Could not get session bus: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+ priv->connection = connection;
+
+ g_dbus_proxy_new (priv->connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ GSD_DBUS_NAME ".Power",
+ GSD_DBUS_PATH "/Power",
+ GSD_DBUS_BASE_INTERFACE ".Power",
+ NULL,
+ (GAsyncReadyCallback) power_ready_cb,
+ manager);
+
+ g_dbus_proxy_new (priv->connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ GSD_DBUS_NAME ".Power",
+ GSD_DBUS_PATH "/Power",
+ GSD_DBUS_BASE_INTERFACE ".Power.Screen",
+ NULL,
+ (GAsyncReadyCallback) power_screen_ready_cb,
+ manager);
+
+ g_dbus_proxy_new (priv->connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ GSD_DBUS_NAME ".Power",
+ GSD_DBUS_PATH "/Power",
+ GSD_DBUS_BASE_INTERFACE ".Power.Keyboard",
+ NULL,
+ (GAsyncReadyCallback) power_keyboard_ready_cb,
+ manager);
+
+ up_client = up_client_new ();
+ priv->composite_device = up_client_get_display_device (up_client);
+ g_object_unref (up_client);
+}
+
+static void
+register_manager (GsdMediaKeysManager *manager)
+{
+ GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager);
+
+ priv->bus_cancellable = g_cancellable_new ();
+ g_bus_get (G_BUS_TYPE_SESSION,
+ priv->bus_cancellable,
+ (GAsyncReadyCallback) on_bus_gotten,
+ manager);
+}
+
+GsdMediaKeysManager *
+gsd_media_keys_manager_new (void)
+{
+ if (manager_object != NULL) {
+ g_object_ref (manager_object);
+ } else {
+ manager_object = g_object_new (GSD_TYPE_MEDIA_KEYS_MANAGER, NULL);
+ g_object_add_weak_pointer (manager_object,
+ (gpointer *) &manager_object);
+ }
+
+ return GSD_MEDIA_KEYS_MANAGER (manager_object);
+}
diff --git a/plugins/media-keys/gsd-media-keys-manager.h b/plugins/media-keys/gsd-media-keys-manager.h
new file mode 100644
index 0000000..d7042b8
--- /dev/null
+++ b/plugins/media-keys/gsd-media-keys-manager.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_MEDIA_KEYS_MANAGER_H
+#define __GSD_MEDIA_KEYS_MANAGER_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_MEDIA_KEYS_MANAGER (gsd_media_keys_manager_get_type ())
+
+G_DECLARE_DERIVABLE_TYPE (GsdMediaKeysManager, gsd_media_keys_manager, GSD, MEDIA_KEYS_MANAGER, GObject)
+
+struct _GsdMediaKeysManagerClass
+{
+ GObjectClass parent_class;
+ void (* media_player_key_pressed) (GsdMediaKeysManager *manager,
+ const char *application,
+ const char *key);
+};
+
+GsdMediaKeysManager * gsd_media_keys_manager_new (void);
+gboolean gsd_media_keys_manager_start (GsdMediaKeysManager *manager,
+ GError **error);
+void gsd_media_keys_manager_stop (GsdMediaKeysManager *manager);
+
+
+G_END_DECLS
+
+#endif /* __GSD_MEDIA_KEYS_MANAGER_H */
diff --git a/plugins/media-keys/main.c b/plugins/media-keys/main.c
new file mode 100644
index 0000000..fab28f6
--- /dev/null
+++ b/plugins/media-keys/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_media_keys_manager_new
+#define START gsd_media_keys_manager_start
+#define STOP gsd_media_keys_manager_stop
+#define MANAGER GsdMediaKeysManager
+#include "gsd-media-keys-manager.h"
+
+#include "daemon-skeleton-gtk.h"
diff --git a/plugins/media-keys/media-keys.h b/plugins/media-keys/media-keys.h
new file mode 100644
index 0000000..801a13b
--- /dev/null
+++ b/plugins/media-keys/media-keys.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2001 Bastien Nocera <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,
+ WWW_KEY,
+ PLAY_KEY,
+ PAUSE_KEY,
+ STOP_KEY,
+ PREVIOUS_KEY,
+ NEXT_KEY,
+ REWIND_KEY,
+ FORWARD_KEY,
+ REPEAT_KEY,
+ RANDOM_KEY,
+ ROTATE_VIDEO_LOCK_KEY,
+ MAGNIFIER_KEY,
+ SCREENREADER_KEY,
+ ON_SCREEN_KEYBOARD_KEY,
+ INCREASE_TEXT_KEY,
+ DECREASE_TEXT_KEY,
+ TOGGLE_CONTRAST_KEY,
+ MAGNIFIER_ZOOM_IN_KEY,
+ MAGNIFIER_ZOOM_OUT_KEY,
+ POWER_KEY,
+ SUSPEND_KEY,
+ HIBERNATE_KEY,
+ SCREEN_BRIGHTNESS_UP_KEY,
+ SCREEN_BRIGHTNESS_DOWN_KEY,
+ SCREEN_BRIGHTNESS_CYCLE_KEY,
+ KEYBOARD_BRIGHTNESS_UP_KEY,
+ KEYBOARD_BRIGHTNESS_DOWN_KEY,
+ KEYBOARD_BRIGHTNESS_TOGGLE_KEY,
+ BATTERY_KEY,
+ RFKILL_KEY,
+ BLUETOOTH_RFKILL_KEY,
+ CUSTOM_KEY
+} MediaKeyType;
+
+#endif /* __MEDIA_KEYS_H__ */
diff --git a/plugins/media-keys/meson.build b/plugins/media-keys/meson.build
new file mode 100644
index 0000000..92d471a
--- /dev/null
+++ b/plugins/media-keys/meson.build
@@ -0,0 +1,58 @@
+sources = files(
+ 'bus-watch-namespace.c',
+ 'gsd-media-keys-manager.c',
+ 'main.c',
+ 'mpris-controller.c'
+)
+
+marshal = 'gsd-marshal'
+
+sources += gnome.genmarshal(
+ marshal,
+ sources: marshal + '.list',
+ prefix: marshal.underscorify(),
+ internal: true
+)
+
+sources += gnome.gdbus_codegen(
+ 'shell-key-grabber',
+ 'org.gnome.ShellKeyGrabber.xml',
+ interface_prefix: 'org.gnome.',
+ namespace: 'Shell'
+)
+
+deps = plugins_deps + [
+ gio_unix_dep,
+ gsettings_desktop_dep,
+ libcanberra_gtk_dep,
+ libcommon_dep,
+ gnome_desktop_dep,
+ libgvc_dep,
+ libpulse_mainloop_glib_dep,
+ m_dep,
+ upower_glib_dep
+]
+
+if enable_gudev
+ deps += gudev_dep
+endif
+
+executable(
+ 'gsd-' + plugin_name,
+ sources,
+ include_directories: [top_inc, data_inc],
+ dependencies: deps,
+ c_args: cflags,
+ install: true,
+ install_rpath: gsd_pkglibdir,
+ install_dir: gsd_libexecdir
+)
+
+program = 'audio-selection-test'
+
+executable(
+ program,
+ program + '.c',
+ include_directories: top_inc,
+ dependencies: deps
+)
diff --git a/plugins/media-keys/mpris-controller.c b/plugins/media-keys/mpris-controller.c
new file mode 100644
index 0000000..d60d362
--- /dev/null
+++ b/plugins/media-keys/mpris-controller.c
@@ -0,0 +1,440 @@
+/*
+ * Copyright © 2013 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses>
+ *
+ * Author: Michael Wood <michael.g.wood@intel.com>
+ */
+
+#include "mpris-controller.h"
+#include "bus-watch-namespace.h"
+#include <gio/gio.h>
+
+enum {
+ PROP_0,
+ PROP_HAS_ACTIVE_PLAYER
+};
+
+struct _MprisController
+{
+ GObject parent;
+
+ GCancellable *cancellable;
+ GDBusProxy *mpris_client_proxy;
+ guint namespace_watcher_id;
+ GSList *other_proxies;
+};
+
+G_DEFINE_TYPE (MprisController, mpris_controller, G_TYPE_OBJECT)
+
+static void
+mpris_controller_dispose (GObject *object)
+{
+ MprisController *self = MPRIS_CONTROLLER (object);
+
+ g_clear_object (&self->cancellable);
+ g_clear_object (&self->mpris_client_proxy);
+
+ if (self->namespace_watcher_id)
+ {
+ bus_unwatch_namespace (self->namespace_watcher_id);
+ self->namespace_watcher_id = 0;
+ }
+
+ g_slist_free_full (g_steal_pointer (&self->other_proxies), g_object_unref);
+
+ G_OBJECT_CLASS (mpris_controller_parent_class)->dispose (object);
+}
+
+static void
+mpris_proxy_call_done (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ GVariant *ret;
+
+ if (!(ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), res, &error)))
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Error calling method %s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+ g_variant_unref (ret);
+}
+
+gboolean
+mpris_controller_key (MprisController *self, const gchar *key)
+{
+ g_return_val_if_fail (MPRIS_IS_CONTROLLER (self), FALSE);
+ g_return_val_if_fail (key != NULL, FALSE);
+
+ if (!self->mpris_client_proxy)
+ return FALSE;
+
+ if (g_strcmp0 (key, "Play") == 0)
+ key = "PlayPause";
+
+ g_debug ("calling %s over dbus to mpris client %s",
+ key, g_dbus_proxy_get_name (self->mpris_client_proxy));
+ g_dbus_proxy_call (self->mpris_client_proxy,
+ key, NULL, 0, -1, self->cancellable,
+ mpris_proxy_call_done,
+ NULL);
+ return TRUE;
+}
+
+gboolean
+mpris_controller_seek (MprisController *self, gint64 offset)
+{
+ g_return_val_if_fail (MPRIS_IS_CONTROLLER (self), FALSE);
+
+ if (!self->mpris_client_proxy)
+ return FALSE;
+
+ g_debug ("calling Seek over dbus to mpris client %s",
+ g_dbus_proxy_get_name (self->mpris_client_proxy));
+ g_dbus_proxy_call (self->mpris_client_proxy,
+ "Seek", g_variant_new ("(x)", offset, NULL),
+ G_DBUS_CALL_FLAGS_NONE, -1, self->cancellable,
+ mpris_proxy_call_done,
+ NULL);
+ return TRUE;
+}
+
+static GDBusProxy *
+get_props_proxy (GDBusProxy *proxy,
+ GCancellable *cancellable)
+{
+ GDBusProxy *props = NULL;
+ g_autoptr(GError) error = NULL;
+
+ g_return_val_if_fail (proxy != NULL, NULL);
+
+ props = g_dbus_proxy_new_sync (g_dbus_proxy_get_connection (proxy),
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+ NULL,
+ g_dbus_proxy_get_name (proxy),
+ g_dbus_proxy_get_object_path (proxy),
+ "org.freedesktop.DBus.Properties",
+ cancellable,
+ &error);
+ if (!props) {
+ g_debug ("Could not get properties proxy for %s: %s",
+ g_dbus_proxy_get_interface_name (proxy),
+ error->message);
+ return NULL;
+ }
+
+ return props;
+}
+
+gboolean
+mpris_controller_toggle (MprisController *self, const gchar *property)
+{
+ g_return_val_if_fail (MPRIS_IS_CONTROLLER (self), FALSE);
+ g_return_val_if_fail (property != NULL, FALSE);
+
+ if (!self->mpris_client_proxy)
+ return FALSE;
+
+ if (g_str_equal (property, "LoopStatus")) {
+ g_autoptr(GDBusProxy) props = NULL;
+ g_autoptr(GVariant) loop_status;
+ const gchar *status_str, *new_status;
+
+ loop_status = g_dbus_proxy_get_cached_property (self->mpris_client_proxy, "LoopStatus");
+ if (!loop_status)
+ return FALSE;
+ if (!g_variant_is_of_type (loop_status, G_VARIANT_TYPE_STRING))
+ return FALSE;
+ status_str = g_variant_get_string (loop_status, NULL);
+ if (g_str_equal (status_str, "Playlist"))
+ new_status = "None";
+ else
+ new_status = "Playlist";
+
+ props = get_props_proxy (self->mpris_client_proxy, self->cancellable);
+ if (!props)
+ return FALSE;
+ g_dbus_proxy_call (props,
+ "Set",
+ g_variant_new_parsed ("('org.mpris.MediaPlayer2.Player', 'LoopStatus', %v)",
+ g_variant_new_string (new_status)),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ self->cancellable,
+ mpris_proxy_call_done, NULL);
+ } else if (g_str_equal (property, "Shuffle")) {
+ g_autoptr(GDBusProxy) props = NULL;
+ g_autoptr(GVariant) shuffle_status;
+ gboolean status;
+
+ shuffle_status = g_dbus_proxy_get_cached_property (self->mpris_client_proxy, "Shuffle");
+ if (!shuffle_status)
+ return FALSE;
+ if (!g_variant_is_of_type (shuffle_status, G_VARIANT_TYPE_BOOLEAN))
+ return FALSE;
+ status = g_variant_get_boolean (shuffle_status);
+
+ props = get_props_proxy (self->mpris_client_proxy, self->cancellable);
+ if (!props)
+ return FALSE;
+ g_dbus_proxy_call (props,
+ "Set",
+ g_variant_new_parsed ("('org.mpris.MediaPlayer2.Player', 'Shuffle', %v)",
+ g_variant_new_boolean (!status)),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ self->cancellable,
+ mpris_proxy_call_done, NULL);
+ }
+
+ g_debug ("Unhandled toggle property '%s'", property);
+
+ return TRUE;
+}
+
+static gboolean
+mpris_client_is_playing (GDBusProxy *proxy)
+{
+ g_autoptr(GVariant) playback_status;
+ const gchar *status_str;
+
+ playback_status = g_dbus_proxy_get_cached_property (proxy, "PlaybackStatus");
+ if (!playback_status)
+ return FALSE;
+
+ if (!g_variant_is_of_type (playback_status, G_VARIANT_TYPE_STRING))
+ return FALSE;
+
+ status_str = g_variant_get_string (playback_status, NULL);
+ return g_strcmp0 (status_str, "Playing") == 0;
+}
+
+static void
+mpris_client_notify_name_owner_cb (GDBusProxy *proxy,
+ GParamSpec *pspec,
+ MprisController *self)
+{
+ g_autofree gchar *name_owner = NULL;
+ GSList *first;
+
+ /* Owner changed, but the proxy is still valid. */
+ name_owner = g_dbus_proxy_get_name_owner (proxy);
+ if (name_owner)
+ return;
+
+ if (proxy == self->mpris_client_proxy)
+ {
+ g_debug ("Clearing the current MPRIS client proxy");
+ g_clear_object (&self->mpris_client_proxy);
+
+ if ((first = self->other_proxies))
+ {
+ self->mpris_client_proxy = first->data;
+ self->other_proxies = first->next;
+ g_slist_free_1 (first);
+
+ g_debug ("Falling back to MPRIS client %s",
+ g_dbus_proxy_get_name (self->mpris_client_proxy));
+ }
+ else
+ {
+ g_object_notify (G_OBJECT (self), "has-active-player");
+ }
+ }
+ else
+ {
+ g_debug ("Forgetting MPRIS client %s", g_dbus_proxy_get_name (proxy));
+ self->other_proxies = g_slist_remove (self->other_proxies, proxy);
+ g_object_unref (proxy);
+ }
+}
+
+static void
+mpris_client_properties_changed_cb (GDBusProxy *proxy,
+ GVariant *changed_properties,
+ GStrv invalidated_properties,
+ gpointer user_data)
+{
+ MprisController *self = MPRIS_CONTROLLER (user_data);
+ GDBusProxy *current_proxy;
+
+ current_proxy = self->mpris_client_proxy;
+ if (current_proxy == proxy)
+ return;
+
+ if (current_proxy && mpris_client_is_playing (current_proxy))
+ return;
+
+ if (mpris_client_is_playing (proxy))
+ {
+ g_debug ("Switching to MPRIS client %s because it is playing",
+ g_dbus_proxy_get_name (proxy));
+
+ self->other_proxies = g_slist_remove (self->other_proxies, proxy);
+
+ if (current_proxy)
+ self->other_proxies = g_slist_prepend (self->other_proxies, current_proxy);
+
+ self->mpris_client_proxy = proxy;
+
+ if (!current_proxy)
+ g_object_notify (user_data, "has-active-player");
+ }
+}
+
+static void
+mpris_proxy_ready_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ MprisController *self = MPRIS_CONTROLLER (user_data);
+ GError *error = NULL;
+ GDBusProxy *proxy;
+ const gchar *name;
+
+ proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+
+ if (!proxy)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Error connecting to MPRIS interface: %s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ g_signal_connect (proxy, "notify::g-name-owner",
+ G_CALLBACK (mpris_client_notify_name_owner_cb), user_data);
+
+ g_signal_connect (proxy, "g-properties-changed",
+ G_CALLBACK (mpris_client_properties_changed_cb), user_data);
+
+ name = g_dbus_proxy_get_name (proxy);
+
+ if (self->mpris_client_proxy)
+ {
+ if (mpris_client_is_playing (self->mpris_client_proxy))
+ {
+ g_debug ("Remembering %s for later because the current MPRIS client is playing",
+ name);
+ self->other_proxies = g_slist_prepend (self->other_proxies, proxy);
+ return;
+ }
+
+ g_debug ("Remembering the current MPRIS client for later");
+ self->other_proxies =
+ g_slist_prepend (self->other_proxies, self->mpris_client_proxy);
+ }
+
+ g_debug ("Switching to MPRIS client %s because it just appeared", name);
+
+ self->mpris_client_proxy = proxy;
+
+ g_object_notify (user_data, "has-active-player");
+}
+
+static void
+start_mpris_proxy (MprisController *self, const gchar *name)
+{
+ g_debug ("Creating proxy for %s", name);
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+ 0,
+ NULL,
+ name,
+ "/org/mpris/MediaPlayer2",
+ "org.mpris.MediaPlayer2.Player",
+ self->cancellable,
+ mpris_proxy_ready_cb,
+ self);
+}
+
+static void
+mpris_player_appeared (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ start_mpris_proxy (MPRIS_CONTROLLER (user_data), name);
+}
+
+static void
+mpris_controller_constructed (GObject *object)
+{
+ MprisController *self = MPRIS_CONTROLLER (object);
+
+ self->namespace_watcher_id = bus_watch_namespace (G_BUS_TYPE_SESSION,
+ "org.mpris.MediaPlayer2",
+ mpris_player_appeared,
+ NULL,
+ MPRIS_CONTROLLER (object),
+ NULL);
+}
+
+static void
+mpris_controller_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MprisController *self = MPRIS_CONTROLLER (object);
+
+ switch (prop_id) {
+ case PROP_HAS_ACTIVE_PLAYER:
+ g_value_set_boolean (value,
+ mpris_controller_get_has_active_player (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+mpris_controller_class_init (MprisControllerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = mpris_controller_constructed;
+ object_class->dispose = mpris_controller_dispose;
+ object_class->get_property = mpris_controller_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_HAS_ACTIVE_PLAYER,
+ g_param_spec_boolean ("has-active-player",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READABLE));
+}
+
+static void
+mpris_controller_init (MprisController *self)
+{
+}
+
+gboolean
+mpris_controller_get_has_active_player (MprisController *controller)
+{
+ g_return_val_if_fail (MPRIS_IS_CONTROLLER (controller), FALSE);
+
+ return (controller->mpris_client_proxy != NULL);
+}
+
+MprisController *
+mpris_controller_new (void)
+{
+ return g_object_new (MPRIS_TYPE_CONTROLLER, NULL);
+}
diff --git a/plugins/media-keys/mpris-controller.h b/plugins/media-keys/mpris-controller.h
new file mode 100644
index 0000000..e44bc90
--- /dev/null
+++ b/plugins/media-keys/mpris-controller.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2013 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses>
+ *
+ * Author: Michael Wood <michael.g.wood@intel.com>
+ */
+
+#ifndef __MPRIS_CONTROLLER_H__
+#define __MPRIS_CONTROLLER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define MPRIS_TYPE_CONTROLLER mpris_controller_get_type()
+
+G_DECLARE_FINAL_TYPE (MprisController, mpris_controller, MPRIS, CONTROLLER, GObject)
+
+MprisController *mpris_controller_new (void);
+gboolean mpris_controller_key (MprisController *self, const gchar *key);
+gboolean mpris_controller_seek (MprisController *self, gint64 offset);
+gboolean mpris_controller_toggle (MprisController *self, const gchar *property);
+gboolean mpris_controller_get_has_active_player (MprisController *controller);
+
+G_END_DECLS
+
+#endif /* __MPRIS_CONTROLLER_H__ */
diff --git a/plugins/media-keys/org.gnome.ShellKeyGrabber.xml b/plugins/media-keys/org.gnome.ShellKeyGrabber.xml
new file mode 100644
index 0000000..df7b8d5
--- /dev/null
+++ b/plugins/media-keys/org.gnome.ShellKeyGrabber.xml
@@ -0,0 +1,27 @@
+<node>
+ <interface name="org.gnome.Shell">
+ <annotation name="org.gtk.GDBus.C.Name" value="KeyGrabber"/>
+ <method name="GrabAccelerator">
+ <arg type="s" direction="in" name="accelerator"/>
+ <arg type="u" direction="in" name="modeFlags"/>
+ <arg type="u" direction="in" name="grabFlags"/>
+ <arg type="u" direction="out" name="action"/>
+ </method>
+ <method name="GrabAccelerators">
+ <arg type="a(suu)" direction="in" name="accelerator"/>
+ <arg type="au" direction="out" name="action"/>
+ </method>
+ <method name="UngrabAccelerator">
+ <arg type="u" direction="in" name="action"/>
+ <arg type="b" direction="out" name="success"/>
+ </method>
+ <method name="UngrabAccelerators">
+ <arg type="au" direction="in" name="action"/>
+ <arg type="b" direction="out" name="success"/>
+ </method>
+ <signal name="AcceleratorActivated">
+ <arg type="u" name="action"/>
+ <arg type="a{sv}" name="parameters"/>
+ </signal>
+ </interface>
+</node>
diff --git a/plugins/media-keys/shell-action-modes.h b/plugins/media-keys/shell-action-modes.h
new file mode 100644
index 0000000..f17a7a0
--- /dev/null
+++ b/plugins/media-keys/shell-action-modes.h
@@ -0,0 +1,53 @@
+/**
+ * ShellActionMode:
+ * @SHELL_ACTION_MODE_NONE: block action
+ * @SHELL_ACTION_MODE_NORMAL: allow action when in window mode,
+ * e.g. when the focus is in an application window
+ * @SHELL_ACTION_MODE_OVERVIEW: allow action while the overview
+ * is active
+ * @SHELL_ACTION_MODE_LOCK_SCREEN: allow action when the screen
+ * is locked, e.g. when the screen shield is shown
+ * @SHELL_ACTION_MODE_UNLOCK_SCREEN: allow action in the unlock
+ * dialog
+ * @SHELL_ACTION_MODE_LOGIN_SCREEN: allow action in the login screen
+ * @SHELL_ACTION_MODE_SYSTEM_MODAL: allow action when a system modal
+ * dialog (e.g. authentification or session dialogs) is open
+ * @SHELL_ACTION_MODE_LOOKING_GLASS: allow action in looking glass
+ * @SHELL_ACTION_MODE_POPUP: allow action while a shell menu is open
+ * @SHELL_ACTION_MODE_ALL: always allow action
+ *
+ * Controls in which GNOME Shell states an action (like keybindings and gestures)
+ * should be handled.
+*/
+typedef enum {
+ SHELL_ACTION_MODE_NONE = 0,
+ SHELL_ACTION_MODE_NORMAL = 1 << 0,
+ SHELL_ACTION_MODE_OVERVIEW = 1 << 1,
+ SHELL_ACTION_MODE_LOCK_SCREEN = 1 << 2,
+ SHELL_ACTION_MODE_UNLOCK_SCREEN = 1 << 3,
+ SHELL_ACTION_MODE_LOGIN_SCREEN = 1 << 4,
+ SHELL_ACTION_MODE_SYSTEM_MODAL = 1 << 5,
+ SHELL_ACTION_MODE_LOOKING_GLASS = 1 << 6,
+ SHELL_ACTION_MODE_POPUP = 1 << 7,
+
+ SHELL_ACTION_MODE_ALL = ~0,
+} ShellActionMode;
+
+/**
+ * MetaKeyBindingFlags:
+ * @META_KEY_BINDING_NONE: none
+ * @META_KEY_BINDING_PER_WINDOW: per-window
+ * @META_KEY_BINDING_BUILTIN: built-in
+ * @META_KEY_BINDING_IS_REVERSED: is reversed
+ * @META_KEY_BINDING_NON_MASKABLE: always active
+ * @META_KEY_BINDING_IGNORE_AUTOREPEAT: ignore key autorepeat
+ */
+typedef enum
+{
+ META_KEY_BINDING_NONE,
+ META_KEY_BINDING_PER_WINDOW = 1 << 0,
+ META_KEY_BINDING_BUILTIN = 1 << 1,
+ META_KEY_BINDING_IS_REVERSED = 1 << 2,
+ META_KEY_BINDING_NON_MASKABLE = 1 << 3,
+ META_KEY_BINDING_IGNORE_AUTOREPEAT = 1 << 4,
+} MetaKeyBindingFlags; \ No newline at end of file
diff --git a/plugins/media-keys/shortcuts-list.h b/plugins/media-keys/shortcuts-list.h
new file mode 100644
index 0000000..c8f084c
--- /dev/null
+++ b/plugins/media-keys/shortcuts-list.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2001 Bastien Nocera <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 },
+ { 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__ */