/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2001-2003 Bastien Nocera * Copyright (C) 2006-2007 William Jon McCann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define GNOME_DESKTOP_USE_UNSTABLE_API #include #if HAVE_GUDEV #include #endif #include "gsd-settings-migrate.h" #include "mpris-controller.h" #include "gnome-settings-bus.h" #include "gnome-settings-profile.h" #include "gsd-marshal.h" #include "gsd-media-keys-manager.h" #include "shortcuts-list.h" #include "shell-key-grabber.h" #include "gsd-input-helper.h" #include "gsd-enums.h" #include "gsd-shell-helper.h" #include #include #include "gvc-mixer-control.h" #include "gvc-mixer-sink.h" #define GSD_DBUS_PATH "/org/gnome/SettingsDaemon" #define GSD_DBUS_NAME "org.gnome.SettingsDaemon" #define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon" #define GSD_MEDIA_KEYS_DBUS_PATH GSD_DBUS_PATH "/MediaKeys" #define GSD_MEDIA_KEYS_DBUS_NAME GSD_DBUS_NAME ".MediaKeys" #define GNOME_KEYRING_DBUS_NAME "org.gnome.keyring" #define GNOME_KEYRING_DBUS_PATH "/org/gnome/keyring/daemon" #define GNOME_KEYRING_DBUS_INTERFACE "org.gnome.keyring.Daemon" #define SHELL_DBUS_NAME "org.gnome.Shell" #define SHELL_DBUS_PATH "/org/gnome/Shell" #define CUSTOM_BINDING_SCHEMA SETTINGS_BINDING_DIR ".custom-keybinding" #define SETTINGS_SOUND_DIR "org.gnome.desktop.sound" #define ALLOW_VOLUME_ABOVE_100_PERCENT_KEY "allow-volume-above-100-percent" #define SHELL_GRABBER_CALL_TIMEOUT G_MAXINT #define SHELL_GRABBER_RETRY_INTERVAL_MS 1000 /* How long to suppress power-button presses after resume, * 3 seconds is the minimum necessary to make resume reliable */ #define GSD_REENABLE_POWER_BUTTON_DELAY 3000 /* ms */ #define SETTINGS_INTERFACE_DIR "org.gnome.desktop.interface" #define SETTINGS_POWER_DIR "org.gnome.settings-daemon.plugins.power" #define SETTINGS_XSETTINGS_DIR "org.gnome.settings-daemon.plugins.xsettings" #define SETTINGS_TOUCHPAD_DIR "org.gnome.desktop.peripherals.touchpad" #define TOUCHPAD_ENABLED_KEY "send-events" #define HIGH_CONTRAST "HighContrast" #define REWIND_USEC (-10 * G_USEC_PER_SEC) #define FASTFORWARD_USEC (45 * G_USEC_PER_SEC) #define VOLUME_STEP "volume-step" #define VOLUME_STEP_PRECISE 2 #define MAX_VOLUME 65536.0 #define SYSTEMD_DBUS_NAME "org.freedesktop.login1" #define SYSTEMD_DBUS_PATH "/org/freedesktop/login1" #define SYSTEMD_DBUS_INTERFACE "org.freedesktop.login1.Manager" #define AUDIO_SELECTION_DBUS_NAME "org.gnome.Shell.AudioDeviceSelection" #define AUDIO_SELECTION_DBUS_PATH "/org/gnome/Shell/AudioDeviceSelection" #define AUDIO_SELECTION_DBUS_INTERFACE "org.gnome.Shell.AudioDeviceSelection" #define GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE(o) (gsd_media_keys_manager_get_instance_private (o)) typedef struct { char *application; char *dbus_name; guint32 time; guint watch_id; } MediaPlayer; typedef struct { gint ref_count; MediaKeyType key_type; ShellActionMode modes; MetaKeyBindingFlags grab_flags; const char *settings_key; gboolean static_setting; char *custom_path; char *custom_command; GArray *accel_ids; } MediaKey; typedef struct { GsdMediaKeysManager *manager; GPtrArray *keys; /* NOTE: This is to implement a custom cancellation handling where * we immediately emit an ungrab call if grabbing was cancelled. */ gboolean cancelled; } GrabUngrabData; typedef struct { /* Volume bits */ GvcMixerControl *volume; GvcMixerStream *sink; GvcMixerStream *source; ca_context *ca; GSettings *sound_settings; pa_volume_t max_volume; GtkSettings *gtksettings; #if HAVE_GUDEV GHashTable *streams; /* key = X device ID, value = stream id */ GUdevClient *udev_client; #endif /* HAVE_GUDEV */ guint audio_selection_watch_id; guint audio_selection_signal_id; GDBusConnection *audio_selection_conn; gboolean audio_selection_requested; guint audio_selection_device_id; GSettings *settings; GHashTable *custom_settings; GPtrArray *keys; /* HighContrast theme settings */ GSettings *interface_settings; char *icon_theme; char *gtk_theme; /* Power stuff */ GSettings *power_settings; GDBusProxy *power_proxy; GDBusProxy *power_screen_proxy; GDBusProxy *power_keyboard_proxy; UpDevice *composite_device; char *chassis_type; gboolean power_button_disabled; guint reenable_power_button_timer_id; /* Shell stuff */ GsdShell *shell_proxy; ShellKeyGrabber *key_grabber; GCancellable *grab_cancellable; GHashTable *keys_to_sync; guint keys_sync_source_id; GrabUngrabData *keys_sync_data; /* ScreenSaver stuff */ GsdScreenSaver *screen_saver_proxy; /* Rotation */ guint iio_sensor_watch_id; gboolean has_accel; GDBusProxy *iio_sensor_proxy; /* RFKill stuff */ guint rfkill_watch_id; guint64 rfkill_last_time; GDBusProxy *rfkill_proxy; GCancellable *rfkill_cancellable; /* systemd stuff */ GDBusProxy *logind_proxy; gint inhibit_keys_fd; gint inhibit_suspend_fd; gboolean inhibit_suspend_taken; GDBusConnection *connection; GCancellable *bus_cancellable; guint start_idle_id; /* Multimedia keys */ MprisController *mpris_controller; } GsdMediaKeysManagerPrivate; static void gsd_media_keys_manager_class_init (GsdMediaKeysManagerClass *klass); static void gsd_media_keys_manager_init (GsdMediaKeysManager *media_keys_manager); static void gsd_media_keys_manager_finalize (GObject *object); static void register_manager (GsdMediaKeysManager *manager); static void custom_binding_changed (GSettings *settings, const char *settings_key, GsdMediaKeysManager *manager); static void keys_sync_queue (GsdMediaKeysManager *manager, gboolean immediate, gboolean retry); static void keys_sync_continue (GsdMediaKeysManager *manager); G_DEFINE_TYPE_WITH_PRIVATE (GsdMediaKeysManager, gsd_media_keys_manager, G_TYPE_OBJECT) static gpointer manager_object = NULL; static void media_key_unref (MediaKey *key) { if (key == NULL) return; if (!g_atomic_int_dec_and_test (&key->ref_count)) return; g_clear_pointer (&key->accel_ids, g_array_unref); g_free (key->custom_path); g_free (key->custom_command); g_free (key); } static MediaKey * media_key_ref (MediaKey *key) { g_atomic_int_inc (&key->ref_count); return key; } static MediaKey * media_key_new (void) { MediaKey *key = g_new0 (MediaKey, 1); key->accel_ids = g_array_new (FALSE, TRUE, sizeof(guint)); return media_key_ref (key); } G_DEFINE_AUTOPTR_CLEANUP_FUNC (MediaKey, media_key_unref) static void grab_ungrab_data_free (GrabUngrabData *data) { /* NOTE: The manager pointer is not owned and is invalid if the * operation was cancelled. */ if (!data->cancelled) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (data->manager); if (priv->keys_sync_data == data) priv->keys_sync_data = NULL; } data->manager = NULL; g_clear_pointer (&data->keys, g_ptr_array_unref); g_free (data); } G_DEFINE_AUTOPTR_CLEANUP_FUNC (GrabUngrabData, grab_ungrab_data_free) static void set_launch_context_env (GsdMediaKeysManager *manager, GAppLaunchContext *launch_context) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GError *error = NULL; GVariant *variant, *item; GVariantIter *iter; variant = g_dbus_connection_call_sync (priv->connection, GNOME_KEYRING_DBUS_NAME, GNOME_KEYRING_DBUS_PATH, GNOME_KEYRING_DBUS_INTERFACE, "GetEnvironment", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (variant == NULL) { g_warning ("Failed to call GetEnvironment on keyring daemon: %s", error->message); g_error_free (error); return; } g_variant_get (variant, "(a{ss})", &iter); while ((item = g_variant_iter_next_value (iter))) { char *key; char *value; g_variant_get (item, "{ss}", &key, &value); g_app_launch_context_setenv (launch_context, key, value); g_variant_unref (item); g_free (key); g_free (value); } g_variant_iter_free (iter); g_variant_unref (variant); } static char * get_key_string (MediaKey *key) { if (key->settings_key != NULL) return g_strdup_printf ("settings:%s", key->settings_key); else if (key->custom_path != NULL) return g_strdup_printf ("custom:%s", key->custom_path); else g_assert_not_reached (); } static GStrv get_bindings (GsdMediaKeysManager *manager, MediaKey *key) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GPtrArray *array; gchar *binding; if (key->settings_key != NULL) { g_autofree gchar *static_settings_key = NULL; g_autofree GStrv keys = NULL; g_autofree GStrv static_keys = NULL; gchar **item; if (!key->static_setting) return g_settings_get_strv (priv->settings, key->settings_key); static_settings_key = g_strconcat (key->settings_key, "-static", NULL); keys = g_settings_get_strv (priv->settings, key->settings_key); static_keys = g_settings_get_strv (priv->settings, static_settings_key); array = g_ptr_array_new (); /* Steals all strings from the settings */ for (item = keys; *item; item++) g_ptr_array_add (array, *item); for (item = static_keys; *item; item++) g_ptr_array_add (array, *item); g_ptr_array_add (array, NULL); return (GStrv) g_ptr_array_free (array, FALSE); } else if (key->custom_path != NULL) { GSettings *settings; settings = g_hash_table_lookup (priv->custom_settings, key->custom_path); binding = g_settings_get_string (settings, "binding"); } else g_assert_not_reached (); array = g_ptr_array_new (); g_ptr_array_add (array, binding); g_ptr_array_add (array, NULL); return (GStrv) g_ptr_array_free (array, FALSE); } static void show_osd_with_max_level (GsdMediaKeysManager *manager, const char *icon, const char *label, double level, double max_level, const gchar *connector) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); if (priv->shell_proxy == NULL) return; shell_show_osd_with_max_level (priv->shell_proxy, icon, label, level, max_level, connector); } static void show_osd (GsdMediaKeysManager *manager, const char *icon, const char *label, double level, const char *connector) { show_osd_with_max_level(manager, icon, label, level, -1, connector); } static const char * get_icon_name_for_volume (gboolean is_mic, gboolean muted, double volume) { static const char *icon_names[] = { "audio-volume-muted-symbolic", "audio-volume-low-symbolic", "audio-volume-medium-symbolic", "audio-volume-high-symbolic", "audio-volume-overamplified-symbolic", NULL }; static const char *mic_icon_names[] = { "microphone-sensitivity-muted-symbolic", "microphone-sensitivity-low-symbolic", "microphone-sensitivity-medium-symbolic", "microphone-sensitivity-high-symbolic", NULL }; int n; if (muted) { n = 0; } else { /* select image */ n = ceill (3.0 * volume); if (n < 1) n = 1; /* output volume above 100% */ else if (n > 3 && !is_mic) n = 4; else if (n > 3) n = 3; } if (is_mic) return mic_icon_names[n]; else return icon_names[n]; } static void ungrab_accelerators_complete (GObject *object, GAsyncResult *result, gpointer user_data) { g_autoptr(GrabUngrabData) data = user_data; gboolean success = FALSE; g_autoptr(GError) error = NULL; gint i; g_debug ("Ungrab call completed!"); if (!shell_key_grabber_call_ungrab_accelerators_finish (SHELL_KEY_GRABBER (object), &success, result, &error)) { g_warning ("Failed to ungrab accelerators: %s", error->message); if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) { keys_sync_queue (data->manager, FALSE, TRUE); return; } /* We are screwed at this point; we'll still keep going assuming that we don't * have the bindings registered anymore. * The only alternative would be to die and force cleanup of all registered * grabs that way. */ } else if (!success) { g_warning ("Failed to ungrab some accelerators, they were probably not registered!"); } /* Clear the accelerator IDs. */ for (i = 0; i < data->keys->len; i++) { MediaKey *key; key = g_ptr_array_index (data->keys, i); /* Always clear, as it would just fail again the next time. */ g_array_set_size (key->accel_ids, 0); } /* Nothing left to do if the operation was cancelled */ if (data->cancelled) return; keys_sync_continue (data->manager); } static void grab_accelerators_complete (GObject *object, GAsyncResult *result, gpointer user_data) { g_autoptr(GrabUngrabData) data = user_data; g_autoptr(GVariant) actions = NULL; g_autoptr(GError) error = NULL; gint i; g_debug ("Grab call completed!"); if (!shell_key_grabber_call_grab_accelerators_finish (SHELL_KEY_GRABBER (object), &actions, result, &error)) { g_warning ("Failed to grab accelerators: %s", error->message); if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) { keys_sync_queue (data->manager, FALSE, TRUE); return; } /* We are screwed at this point as we can't grab the keys. Most likely * this means we are not running on GNOME, or ran into some other weird * error. * Either way, finish the operation as there is no way we can recover * from this. */ keys_sync_continue (data->manager); return; } /* Do an immediate ungrab if the operation was cancelled. * This may happen on daemon shutdown for example. */ if (data->cancelled) { g_debug ("Doing an immediate ungrab on the grabbed accelerators!"); shell_key_grabber_call_ungrab_accelerators (SHELL_KEY_GRABBER (object), actions, NULL, ungrab_accelerators_complete, g_steal_pointer (&data)); return; } /* We need to stow away the accel_ids that have been registered successfully. */ for (i = 0; i < data->keys->len; i++) { MediaKey *key; key = g_ptr_array_index (data->keys, i); g_assert (key->accel_ids->len == 0); } for (i = 0; i < data->keys->len; i++) { MediaKey *key; guint accel_id; key = g_ptr_array_index (data->keys, i); g_variant_get_child (actions, i, "u", &accel_id); if (accel_id == 0) { g_autofree gchar *tmp = NULL; tmp = get_key_string (key); g_warning ("Failed to grab accelerator for keybinding %s", tmp); } else { g_array_append_val (key->accel_ids, accel_id); } } keys_sync_continue (data->manager); } static void keys_sync_continue (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); g_auto(GVariantBuilder) ungrab_builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("au")); g_auto(GVariantBuilder) grab_builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(suu)")); g_autoptr(GPtrArray) keys_being_ungrabbed = NULL; g_autoptr(GPtrArray) keys_being_grabbed = NULL; g_autoptr(GrabUngrabData) data = NULL; GHashTableIter iter; MediaKey *key; gboolean need_ungrab = FALSE; /* Syncing keys is a two step process in principle, i.e. we first ungrab all keys * and then grab the new ones. * To make this work, this function will be called multiple times and it will * either emit an ungrab or grab call or do nothing when done. */ /* If the keys_to_sync hash table is empty at this point, then we are done. * priv->keys_sync_data will be cleared automatically when it is unref'ed. */ if (g_hash_table_size (priv->keys_to_sync) == 0) return; keys_being_ungrabbed = g_ptr_array_new_with_free_func ((GDestroyNotify) media_key_unref); keys_being_grabbed = g_ptr_array_new_with_free_func ((GDestroyNotify) media_key_unref); g_hash_table_iter_init (&iter, priv->keys_to_sync); while (g_hash_table_iter_next (&iter, (gpointer*) &key, NULL)) { g_auto(GStrv) bindings = NULL; gchar **pos = NULL; gint i; for (i = 0; i < key->accel_ids->len; i++) { g_variant_builder_add (&ungrab_builder, "u", g_array_index (key->accel_ids, guint, i)); g_ptr_array_add (keys_being_ungrabbed, media_key_ref (key)); need_ungrab = TRUE; } /* Keys that are synced but aren't in the internal list are being removed. */ if (!g_ptr_array_find (priv->keys, key, NULL)) continue; bindings = get_bindings (manager, key); pos = bindings; while (*pos) { /* Do not try to register empty keybindings. */ if (strlen (*pos) > 0) { g_variant_builder_add (&grab_builder, "(suu)", *pos, key->modes, key->grab_flags); g_ptr_array_add (keys_being_grabbed, media_key_ref (key)); } pos++; } } data = g_new0 (GrabUngrabData, 1); data->manager = manager; /* These calls intentionally do not get a cancellable. See comment in * GrabUngrabData. */ priv->keys_sync_data = data; if (need_ungrab) { data->keys = g_steal_pointer (&keys_being_ungrabbed); shell_key_grabber_call_ungrab_accelerators (priv->key_grabber, g_variant_builder_end (&ungrab_builder), NULL, ungrab_accelerators_complete, g_steal_pointer (&data)); } else { data->keys = g_steal_pointer (&keys_being_grabbed); g_hash_table_remove_all (priv->keys_to_sync); shell_key_grabber_call_grab_accelerators (priv->key_grabber, g_variant_builder_end (&grab_builder), NULL, grab_accelerators_complete, g_steal_pointer (&data)); } } static gboolean keys_sync_start (gpointer user_data) { GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data); GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); priv->keys_sync_source_id = 0; g_assert (priv->keys_sync_data == NULL); keys_sync_continue (manager); return G_SOURCE_REMOVE; } void keys_sync_queue (GsdMediaKeysManager *manager, gboolean immediate, gboolean retry) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); guint i; if (priv->keys_sync_source_id) g_source_remove (priv->keys_sync_source_id); if (retry) { /* Abort the currently running operation, and don't retry * immediately to avoid race condition if an operation was * already active. */ if (priv->keys_sync_data) { priv->keys_sync_data->cancelled = TRUE; priv->keys_sync_data = NULL; immediate = FALSE; } /* Mark all existing keys for sync. */ for (i = 0; i < priv->keys->len; i++) { MediaKey *key = g_ptr_array_index (priv->keys, i); g_hash_table_add (priv->keys_to_sync, media_key_ref (key)); } } else if (priv->keys_sync_data) { /* We are already actively syncing, no need to do anything. */ return; } priv->keys_sync_source_id = g_timeout_add (immediate ? 0 : (retry ? SHELL_GRABBER_RETRY_INTERVAL_MS : 50), keys_sync_start, manager); } static void gsettings_changed_cb (GSettings *settings, const gchar *settings_key, GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); int i; /* Give up if we don't have proxy to the shell */ if (!priv->key_grabber) return; /* handled in gsettings_custom_changed_cb() */ if (g_str_equal (settings_key, "custom-keybindings")) return; /* Find the key that was modified */ if (priv->keys == NULL) return; for (i = 0; i < priv->keys->len; i++) { MediaKey *key; key = g_ptr_array_index (priv->keys, i); /* Skip over hard-coded and GConf keys */ if (key->settings_key == NULL) continue; if (strcmp (settings_key, key->settings_key) == 0) { g_hash_table_add (priv->keys_to_sync, media_key_ref (key)); keys_sync_queue (manager, FALSE, FALSE); break; } } } static MediaKey * media_key_new_for_path (GsdMediaKeysManager *manager, char *path) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GSettings *settings; char *command, *binding; MediaKey *key; g_debug ("media_key_new_for_path: %s", path); settings = g_hash_table_lookup (priv->custom_settings, path); if (settings == NULL) { settings = g_settings_new_with_path (CUSTOM_BINDING_SCHEMA, path); g_signal_connect (settings, "changed", G_CALLBACK (custom_binding_changed), manager); g_hash_table_insert (priv->custom_settings, g_strdup (path), settings); } command = g_settings_get_string (settings, "command"); binding = g_settings_get_string (settings, "binding"); if (*command == '\0' && *binding == '\0') { g_debug ("Key binding (%s) is incomplete", path); g_free (command); g_free (binding); return NULL; } g_free (binding); key = media_key_new (); key->key_type = CUSTOM_KEY; key->modes = GSD_ACTION_MODE_LAUNCHER; key->custom_path = g_strdup (path); key->custom_command = command; key->grab_flags = META_KEY_BINDING_NONE; return key; } static void update_custom_binding (GsdMediaKeysManager *manager, char *path) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); MediaKey *key; int i; /* Remove the existing key */ for (i = 0; i < priv->keys->len; i++) { key = g_ptr_array_index (priv->keys, i); if (key->custom_path == NULL) continue; if (strcmp (key->custom_path, path) == 0) { g_debug ("Removing custom key binding %s", path); g_hash_table_add (priv->keys_to_sync, media_key_ref (key)); g_ptr_array_remove_index_fast (priv->keys, i); break; } } /* And create a new one! */ key = media_key_new_for_path (manager, path); if (key) { g_debug ("Adding new custom key binding %s", path); g_ptr_array_add (priv->keys, key); g_hash_table_add (priv->keys_to_sync, media_key_ref (key)); } keys_sync_queue (manager, FALSE, FALSE); } static void update_custom_binding_command (GsdMediaKeysManager *manager, GSettings *settings, char *path) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); MediaKey *key; int i; for (i = 0; i < priv->keys->len; i++) { key = g_ptr_array_index (priv->keys, i); if (key->custom_path == NULL) continue; if (strcmp (key->custom_path, path) == 0) { g_free (key->custom_command); key->custom_command = g_settings_get_string (settings, "command"); break; } } } static void custom_binding_changed (GSettings *settings, const char *settings_key, GsdMediaKeysManager *manager) { char *path; g_object_get (settings, "path", &path, NULL); if (strcmp (settings_key, "binding") == 0) update_custom_binding (manager, path); else if (strcmp (settings_key, "command") == 0) update_custom_binding_command (manager, settings, path); g_free (path); } static void gsettings_custom_changed_cb (GSettings *settings, const char *settings_key, GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); char **bindings; int i, j, n_bindings; bindings = g_settings_get_strv (settings, settings_key); n_bindings = g_strv_length (bindings); /* Handle additions */ for (i = 0; i < n_bindings; i++) { if (g_hash_table_lookup (priv->custom_settings, bindings[i])) continue; update_custom_binding (manager, bindings[i]); } /* Handle removals */ for (i = 0; i < priv->keys->len; i++) { gboolean found = FALSE; MediaKey *key = g_ptr_array_index (priv->keys, i); if (key->key_type != CUSTOM_KEY) continue; for (j = 0; j < n_bindings && !found; j++) found = strcmp (bindings[j], key->custom_path) == 0; if (found) continue; g_hash_table_add (priv->keys_to_sync, media_key_ref (key)); g_hash_table_remove (priv->custom_settings, key->custom_path); g_ptr_array_remove_index_fast (priv->keys, i); --i; /* make up for the removed key */ } keys_sync_queue (manager, FALSE, FALSE); g_strfreev (bindings); } static void add_key (GsdMediaKeysManager *manager, guint i) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); MediaKey *key; key = media_key_new (); key->key_type = media_keys[i].key_type; key->settings_key = media_keys[i].settings_key; key->static_setting = media_keys[i].static_setting; key->modes = media_keys[i].modes; key->grab_flags = media_keys[i].grab_flags; g_ptr_array_add (priv->keys, key); } static void init_kbd (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); char **custom_paths; int i; gnome_settings_profile_start (NULL); for (i = 0; i < G_N_ELEMENTS (media_keys); i++) add_key (manager, i); /* Custom shortcuts */ custom_paths = g_settings_get_strv (priv->settings, "custom-keybindings"); for (i = 0; i < g_strv_length (custom_paths); i++) { MediaKey *key; g_debug ("Setting up custom keybinding %s", custom_paths[i]); key = media_key_new_for_path (manager, custom_paths[i]); if (!key) { continue; } g_ptr_array_add (priv->keys, key); } g_strfreev (custom_paths); keys_sync_queue (manager, TRUE, TRUE); gnome_settings_profile_end (NULL); } static void app_launched_cb (GAppLaunchContext *context, GAppInfo *info, GVariant *platform_data, gpointer user_data) { GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data); GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); gint32 pid; const gchar *app_name; if (!g_variant_lookup (platform_data, "pid", "i", &pid)) return; app_name = g_app_info_get_id (info); if (app_name == NULL) app_name = g_app_info_get_executable (info); /* Start async request; we don't care about the result */ gnome_start_systemd_scope (app_name, pid, NULL, priv->connection, NULL, NULL, NULL); } static void launch_app (GsdMediaKeysManager *manager, GAppInfo *app_info, gint64 timestamp) { GError *error = NULL; GdkAppLaunchContext *launch_context; /* setup the launch context so the startup notification is correct */ launch_context = gdk_display_get_app_launch_context (gdk_display_get_default ()); gdk_app_launch_context_set_timestamp (launch_context, timestamp); set_launch_context_env (manager, G_APP_LAUNCH_CONTEXT (launch_context)); g_signal_connect_object (launch_context, "launched", G_CALLBACK (app_launched_cb), manager, 0); if (!g_app_info_launch (app_info, NULL, G_APP_LAUNCH_CONTEXT (launch_context), &error)) { g_warning ("Could not launch '%s': %s", g_app_info_get_commandline (app_info), error->message); g_error_free (error); } g_object_unref (launch_context); } static void execute (GsdMediaKeysManager *manager, char *cmd, gint64 timestamp) { GAppInfo *app_info; g_autofree gchar *escaped = NULL; gchar *p; /* Escape all % characters as g_app_info_create_from_commandline will * try to interpret them otherwise. */ escaped = g_malloc (strlen (cmd) * 2 + 1); p = escaped; while (*cmd) { *p = *cmd; p++; if (*cmd == '%') { *p = '%'; p++; } cmd++; } *p = '\0'; app_info = g_app_info_create_from_commandline (escaped, NULL, G_APP_INFO_CREATE_NONE, NULL); launch_app (manager, app_info, timestamp); g_object_unref (app_info); } static void do_url_action (GsdMediaKeysManager *manager, const char *scheme, gint64 timestamp) { GAppInfo *app_info; app_info = g_app_info_get_default_for_uri_scheme (scheme); if (app_info != NULL) { launch_app (manager, app_info, timestamp); g_object_unref (app_info); } else { g_warning ("Could not find default application for '%s' scheme", scheme); } } static void do_media_action (GsdMediaKeysManager *manager, gint64 timestamp) { GAppInfo *app_info; app_info = g_app_info_get_default_for_type ("audio/x-vorbis+ogg", FALSE); if (app_info != NULL) { launch_app (manager, app_info, timestamp); g_object_unref (app_info); } else { g_warning ("Could not find default application for '%s' mime-type", "audio/x-vorbis+ogg"); } } static void gnome_session_shutdown_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GVariant *result; GError *error = NULL; result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error); if (result == NULL) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Failed to call Shutdown on session manager: %s", error->message); g_error_free (error); } else { g_variant_unref (result); } } static void gnome_session_shutdown (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GDBusProxy *proxy; proxy = G_DBUS_PROXY (gnome_settings_bus_get_session_proxy ()); g_dbus_proxy_call (proxy, "Shutdown", NULL, G_DBUS_CALL_FLAGS_NONE, -1, priv->bus_cancellable, gnome_session_shutdown_cb, NULL); g_object_unref (proxy); } static void do_eject_action_cb (GDrive *drive, GAsyncResult *res, GsdMediaKeysManager *manager) { g_drive_eject_with_operation_finish (drive, res, NULL); } #define NO_SCORE 0 #define SCORE_CAN_EJECT 50 #define SCORE_HAS_MEDIA 100 static void do_eject_action (GsdMediaKeysManager *manager) { GList *drives, *l; GDrive *fav_drive; guint score; GVolumeMonitor *volume_monitor; volume_monitor = g_volume_monitor_get (); /* Find the best drive to eject */ fav_drive = NULL; score = NO_SCORE; drives = g_volume_monitor_get_connected_drives (volume_monitor); for (l = drives; l != NULL; l = l->next) { GDrive *drive = l->data; if (g_drive_can_eject (drive) == FALSE) continue; if (g_drive_is_media_removable (drive) == FALSE) continue; if (score < SCORE_CAN_EJECT) { fav_drive = drive; score = SCORE_CAN_EJECT; } if (g_drive_has_media (drive) == FALSE) continue; if (score < SCORE_HAS_MEDIA) { fav_drive = drive; score = SCORE_HAS_MEDIA; break; } } /* Show OSD */ show_osd (manager, "media-eject-symbolic", NULL, -1, NULL); /* Clean up the drive selection and exit if no suitable * drives are found */ if (fav_drive != NULL) fav_drive = g_object_ref (fav_drive); g_list_foreach (drives, (GFunc) g_object_unref, NULL); if (fav_drive == NULL) return; /* Eject! */ g_drive_eject_with_operation (fav_drive, G_MOUNT_UNMOUNT_FORCE, NULL, NULL, (GAsyncReadyCallback) do_eject_action_cb, manager); g_object_unref (fav_drive); g_object_unref (volume_monitor); } static void do_home_key_action (GsdMediaKeysManager *manager, gint64 timestamp) { GFile *file; GError *error = NULL; char *uri; file = g_file_new_for_path (g_get_home_dir ()); uri = g_file_get_uri (file); g_object_unref (file); if (gtk_show_uri_on_window (NULL, uri, timestamp, &error) == FALSE) { g_warning ("Failed to launch '%s': %s", uri, error->message); g_error_free (error); } g_free (uri); } static void do_search_action (GsdMediaKeysManager *manager, gint64 timestamp) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); if (priv->shell_proxy == NULL) return; gsd_shell_call_focus_search (priv->shell_proxy, NULL, NULL, NULL); } static void do_execute_desktop_or_desktop (GsdMediaKeysManager *manager, const char *desktop, const char *alt_desktop, gint64 timestamp) { GDesktopAppInfo *app_info; app_info = g_desktop_app_info_new (desktop); if (app_info == NULL && alt_desktop != NULL) app_info = g_desktop_app_info_new (alt_desktop); if (app_info != NULL) { launch_app (manager, G_APP_INFO (app_info), timestamp); g_object_unref (app_info); return; } g_warning ("Could not find application '%s' or '%s'", desktop, alt_desktop); } static void do_touchpad_osd_action (GsdMediaKeysManager *manager, gboolean state) { show_osd (manager, state ? "input-touchpad-symbolic" : "touchpad-disabled-symbolic", NULL, -1, NULL); } static void do_touchpad_action (GsdMediaKeysManager *manager) { GSettings *settings; gboolean state; settings = g_settings_new (SETTINGS_TOUCHPAD_DIR); state = (g_settings_get_enum (settings, TOUCHPAD_ENABLED_KEY) == G_DESKTOP_DEVICE_SEND_EVENTS_ENABLED); do_touchpad_osd_action (manager, !state); g_settings_set_enum (settings, TOUCHPAD_ENABLED_KEY, !state ? G_DESKTOP_DEVICE_SEND_EVENTS_ENABLED : G_DESKTOP_DEVICE_SEND_EVENTS_DISABLED); g_object_unref (settings); } static void on_screen_locked (GsdScreenSaver *screen_saver, GAsyncResult *result, GsdMediaKeysManager *manager) { gboolean is_locked; GError *error = NULL; is_locked = gsd_screen_saver_call_lock_finish (screen_saver, result, &error); if (!is_locked) { g_warning ("Couldn't lock screen: %s", error->message); g_error_free (error); return; } } static void do_lock_screensaver (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); if (priv->screen_saver_proxy == NULL) priv->screen_saver_proxy = gnome_settings_bus_get_screen_saver_proxy (); gsd_screen_saver_call_lock (priv->screen_saver_proxy, priv->bus_cancellable, (GAsyncReadyCallback) on_screen_locked, manager); } static void sound_theme_changed (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); char *theme_name; g_object_get (G_OBJECT (priv->gtksettings), "gtk-sound-theme-name", &theme_name, NULL); if (theme_name) ca_context_change_props (priv->ca, CA_PROP_CANBERRA_XDG_THEME_NAME, theme_name, NULL); g_free (theme_name); } static void allow_volume_above_100_percent_changed_cb (GSettings *settings, const char *settings_key, GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); gboolean allow_volume_above_100_percent; g_assert (g_str_equal (settings_key, ALLOW_VOLUME_ABOVE_100_PERCENT_KEY)); allow_volume_above_100_percent = g_settings_get_boolean (settings, settings_key); priv->max_volume = allow_volume_above_100_percent ? PA_VOLUME_UI_MAX : PA_VOLUME_NORM; } static void play_volume_changed_audio (GsdMediaKeysManager *manager, GvcMixerStream *stream) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); if (priv->ca == NULL) { ca_context_create (&priv->ca); ca_context_set_driver (priv->ca, "pulse"); ca_context_change_props (priv->ca, 0, CA_PROP_APPLICATION_ID, "org.gnome.VolumeControl", NULL); priv->gtksettings = gtk_settings_get_for_screen (gdk_screen_get_default ()); g_signal_connect_swapped (priv->gtksettings, "notify::gtk-sound-theme-name", G_CALLBACK (sound_theme_changed), manager); sound_theme_changed (manager); } ca_context_change_device (priv->ca, gvc_mixer_stream_get_name (stream)); ca_context_play (priv->ca, 1, CA_PROP_EVENT_ID, "audio-volume-change", CA_PROP_EVENT_DESCRIPTION, "volume changed through key press", CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", NULL); } static void show_volume_osd (GsdMediaKeysManager *manager, GvcMixerStream *stream, guint vol, gboolean muted, gboolean sound_changed, gboolean quiet) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GvcMixerUIDevice *device; const GvcMixerStreamPort *port; const char *icon; gboolean playing = FALSE; double new_vol; double max_volume; max_volume = (double) priv->max_volume / PA_VOLUME_NORM; if (!muted) { new_vol = (double) vol / PA_VOLUME_NORM; new_vol = CLAMP (new_vol, 0, max_volume); } else { new_vol = 0.0; } icon = get_icon_name_for_volume (!GVC_IS_MIXER_SINK (stream), muted, new_vol); port = gvc_mixer_stream_get_port (stream); if (g_strcmp0 (gvc_mixer_stream_get_form_factor (stream), "internal") != 0 || (port != NULL && g_strcmp0 (port->port, "[OUT] Speaker") != 0 && g_strcmp0 (port->port, "[OUT] Handset") != 0 && g_strcmp0 (port->port, "analog-output-speaker") != 0 && g_strcmp0 (port->port, "analog-output") != 0)) { device = gvc_mixer_control_lookup_device_from_stream (priv->volume, stream); show_osd_with_max_level (manager, icon, gvc_mixer_ui_device_get_description (device), new_vol, max_volume, NULL); } else { show_osd_with_max_level (manager, icon, NULL, new_vol, max_volume, NULL); } if (priv->ca) ca_context_playing (priv->ca, 1, &playing); playing = !playing && gvc_mixer_stream_get_state (stream) == GVC_STREAM_STATE_RUNNING; if (quiet == FALSE && sound_changed != FALSE && muted == FALSE && playing == FALSE) play_volume_changed_audio (manager, stream); } #if HAVE_GUDEV /* PulseAudio gives us /devices/... paths, when udev * expects /sys/devices/... paths. */ static GUdevDevice * get_udev_device_for_sysfs_path (GsdMediaKeysManager *manager, const char *sysfs_path) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); char *path; GUdevDevice *dev; path = g_strdup_printf ("/sys%s", sysfs_path); dev = g_udev_client_query_by_sysfs_path (priv->udev_client, path); g_free (path); return dev; } static GvcMixerStream * get_stream_for_device_node (GsdMediaKeysManager *manager, gboolean is_output, const gchar *devnode) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); gpointer id_ptr; GvcMixerStream *res; GUdevDevice *dev, *parent; GSList *streams, *l; id_ptr = g_hash_table_lookup (priv->streams, devnode); if (id_ptr != NULL) { if (GPOINTER_TO_UINT (id_ptr) == (guint) -1) return NULL; else return gvc_mixer_control_lookup_stream_id (priv->volume, GPOINTER_TO_UINT (id_ptr)); } dev = g_udev_client_query_by_device_file (priv->udev_client, devnode); if (dev == NULL) { g_debug ("Could not find udev device for device path '%s'", devnode); return NULL; } if (g_strcmp0 (g_udev_device_get_property (dev, "ID_BUS"), "usb") != 0) { g_debug ("Not handling XInput device %s, not USB", devnode); g_hash_table_insert (priv->streams, g_strdup (devnode), GUINT_TO_POINTER ((guint) -1)); g_object_unref (dev); return NULL; } parent = g_udev_device_get_parent_with_subsystem (dev, "usb", "usb_device"); if (parent == NULL) { g_warning ("No USB device parent for XInput device %s even though it's USB", devnode); g_object_unref (dev); return NULL; } res = NULL; if (is_output) streams = gvc_mixer_control_get_sinks (priv->volume); else streams = gvc_mixer_control_get_sources (priv->volume); for (l = streams; l; l = l->next) { GvcMixerStream *stream = l->data; const char *sysfs_path; GUdevDevice *stream_dev, *stream_parent; sysfs_path = gvc_mixer_stream_get_sysfs_path (stream); stream_dev = get_udev_device_for_sysfs_path (manager, sysfs_path); if (stream_dev == NULL) continue; stream_parent = g_udev_device_get_parent_with_subsystem (stream_dev, "usb", "usb_device"); g_object_unref (stream_dev); if (stream_parent == NULL) continue; if (g_strcmp0 (g_udev_device_get_sysfs_path (stream_parent), g_udev_device_get_sysfs_path (parent)) == 0) { res = stream; } g_object_unref (stream_parent); if (res != NULL) break; } g_slist_free (streams); if (res) g_hash_table_insert (priv->streams, g_strdup (devnode), GUINT_TO_POINTER (gvc_mixer_stream_get_id (res))); else g_hash_table_insert (priv->streams, g_strdup (devnode), GUINT_TO_POINTER ((guint) -1)); return res; } #endif /* HAVE_GUDEV */ typedef enum { SOUND_ACTION_FLAG_IS_OUTPUT = 1 << 0, SOUND_ACTION_FLAG_IS_QUIET = 1 << 1, SOUND_ACTION_FLAG_IS_PRECISE = 1 << 2, } SoundActionFlags; static void do_sound_action (GsdMediaKeysManager *manager, const gchar *device_node, int type, SoundActionFlags flags) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GvcMixerStream *stream = NULL; gboolean old_muted, new_muted; guint old_vol, new_vol, norm_vol_step, vol_step; gboolean sound_changed; /* Find the stream that corresponds to the device, if any */ stream = NULL; #if HAVE_GUDEV if (device_node) { stream = get_stream_for_device_node (manager, flags & SOUND_ACTION_FLAG_IS_OUTPUT, device_node); } #endif /* HAVE_GUDEV */ if (stream == NULL) { if (flags & SOUND_ACTION_FLAG_IS_OUTPUT) stream = priv->sink; else stream = priv->source; } if (stream == NULL) return; if (flags & SOUND_ACTION_FLAG_IS_PRECISE) { norm_vol_step = PA_VOLUME_NORM * VOLUME_STEP_PRECISE / 100; } else { vol_step = g_settings_get_int (priv->settings, VOLUME_STEP); norm_vol_step = PA_VOLUME_NORM * vol_step / 100; } /* FIXME: this is racy */ new_vol = old_vol = gvc_mixer_stream_get_volume (stream); new_muted = old_muted = gvc_mixer_stream_get_is_muted (stream); sound_changed = FALSE; switch (type) { case MUTE_KEY: new_muted = !old_muted; break; case VOLUME_DOWN_KEY: if (old_vol <= norm_vol_step) { new_vol = 0; new_muted = TRUE; } else { new_vol = old_vol - norm_vol_step; } break; case VOLUME_UP_KEY: new_muted = FALSE; /* When coming out of mute only increase the volume if it was 0 */ if (!old_muted || old_vol == 0) new_vol = MIN (old_vol + norm_vol_step, priv->max_volume); break; } if (old_muted != new_muted) { gvc_mixer_stream_change_is_muted (stream, new_muted); sound_changed = TRUE; } if (old_vol != new_vol) { if (gvc_mixer_stream_set_volume (stream, new_vol) != FALSE) { gvc_mixer_stream_push_volume (stream); sound_changed = TRUE; } } show_volume_osd (manager, stream, new_vol, new_muted, sound_changed, flags & SOUND_ACTION_FLAG_IS_QUIET); } static void update_default_sink (GsdMediaKeysManager *manager, gboolean warn) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GvcMixerStream *stream; stream = gvc_mixer_control_get_default_sink (priv->volume); if (stream == priv->sink) return; g_clear_object (&priv->sink); if (stream != NULL) { priv->sink = g_object_ref (stream); } else { if (warn) g_warning ("Unable to get default sink"); else g_debug ("Unable to get default sink"); } } static void update_default_source (GsdMediaKeysManager *manager, gboolean warn) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GvcMixerStream *stream; stream = gvc_mixer_control_get_default_source (priv->volume); if (stream == priv->source) return; g_clear_object (&priv->source); if (stream != NULL) { priv->source = g_object_ref (stream); } else { if (warn) g_warning ("Unable to get default source"); else g_debug ("Unable to get default source"); } } static void on_control_state_changed (GvcMixerControl *control, GvcMixerControlState new_state, GsdMediaKeysManager *manager) { update_default_sink (manager, new_state == GVC_STATE_READY); update_default_source (manager, new_state == GVC_STATE_READY); } static void on_control_default_sink_changed (GvcMixerControl *control, guint id, GsdMediaKeysManager *manager) { update_default_sink (manager, TRUE); } static void on_control_default_source_changed (GvcMixerControl *control, guint id, GsdMediaKeysManager *manager) { update_default_source (manager, TRUE); } #if HAVE_GUDEV static gboolean remove_stream (gpointer key, gpointer value, gpointer id) { if (GPOINTER_TO_UINT (value) == GPOINTER_TO_UINT (id)) return TRUE; return FALSE; } #endif /* HAVE_GUDEV */ static void on_control_stream_removed (GvcMixerControl *control, guint id, GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); if (priv->sink != NULL) { if (gvc_mixer_stream_get_id (priv->sink) == id) g_clear_object (&priv->sink); } if (priv->source != NULL) { if (gvc_mixer_stream_get_id (priv->source) == id) g_clear_object (&priv->source); } #if HAVE_GUDEV g_hash_table_foreach_remove (priv->streams, (GHRFunc) remove_stream, GUINT_TO_POINTER (id)); #endif } static gboolean do_multimedia_player_action (GsdMediaKeysManager *manager, const char *key) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); g_return_val_if_fail (key != NULL, FALSE); g_debug ("Media key '%s' pressed", key); if (mpris_controller_get_has_active_player (priv->mpris_controller)) { if (g_str_equal (key, "Rewind")) { if (mpris_controller_seek (priv->mpris_controller, REWIND_USEC)) return TRUE; } else if (g_str_equal (key, "FastForward")) { if (mpris_controller_seek (priv->mpris_controller, FASTFORWARD_USEC)) return TRUE; } else if (g_str_equal (key, "Repeat")) { if (mpris_controller_toggle (priv->mpris_controller, "LoopStatus")) return TRUE; } else if (g_str_equal (key, "Shuffle")) { if (mpris_controller_toggle (priv->mpris_controller, "Shuffle")) return TRUE; } else if (mpris_controller_key (priv->mpris_controller, key)) { return TRUE; } } /* Popup a dialog with an (/) icon */ show_osd (manager, "action-unavailable-symbolic", NULL, -1, NULL); return TRUE; } static void sensor_properties_changed (GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidated_properties, gpointer user_data) { GsdMediaKeysManager *manager = user_data; GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GVariant *v; GVariantDict dict; if (priv->iio_sensor_proxy == NULL) return; if (changed_properties) g_variant_dict_init (&dict, changed_properties); if (changed_properties == NULL || g_variant_dict_contains (&dict, "HasAccelerometer")) { v = g_dbus_proxy_get_cached_property (priv->iio_sensor_proxy, "HasAccelerometer"); if (v == NULL) { g_debug ("Couldn't fetch HasAccelerometer property"); return; } priv->has_accel = g_variant_get_boolean (v); g_variant_unref (v); } } static void iio_sensor_appeared_cb (GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data) { GsdMediaKeysManager *manager = user_data; GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GError *error = NULL; priv->iio_sensor_proxy = g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_NONE, NULL, "net.hadess.SensorProxy", "/net/hadess/SensorProxy", "net.hadess.SensorProxy", NULL, &error); if (priv->iio_sensor_proxy == NULL) { g_warning ("Failed to access net.hadess.SensorProxy after it appeared"); return; } g_signal_connect (G_OBJECT (priv->iio_sensor_proxy), "g-properties-changed", G_CALLBACK (sensor_properties_changed), manager); sensor_properties_changed (priv->iio_sensor_proxy, NULL, NULL, manager); } static void iio_sensor_disappeared_cb (GDBusConnection *connection, const gchar *name, gpointer user_data) { GsdMediaKeysManager *manager = user_data; GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); g_clear_object (&priv->iio_sensor_proxy); priv->has_accel = FALSE; } static void do_video_rotate_lock_action (GsdMediaKeysManager *manager, gint64 timestamp) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GSettings *settings; gboolean locked; if (!priv->has_accel) { g_debug ("Ignoring attempt to set orientation lock: no accelerometer"); return; } settings = g_settings_new ("org.gnome.settings-daemon.peripherals.touchscreen"); locked = !g_settings_get_boolean (settings, "orientation-lock"); g_settings_set_boolean (settings, "orientation-lock", locked); g_object_unref (settings); show_osd (manager, locked ? "rotation-locked-symbolic" : "rotation-allowed-symbolic", NULL, -1, NULL); } static void do_toggle_accessibility_key (const char *key) { GSettings *settings; gboolean state; settings = g_settings_new ("org.gnome.desktop.a11y.applications"); state = g_settings_get_boolean (settings, key); g_settings_set_boolean (settings, key, !state); g_object_unref (settings); } static void do_magnifier_action (GsdMediaKeysManager *manager) { do_toggle_accessibility_key ("screen-magnifier-enabled"); } static void do_screenreader_action (GsdMediaKeysManager *manager) { do_toggle_accessibility_key ("screen-reader-enabled"); } static void do_on_screen_keyboard_action (GsdMediaKeysManager *manager) { do_toggle_accessibility_key ("screen-keyboard-enabled"); } static void do_text_size_action (GsdMediaKeysManager *manager, MediaKeyType type) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); gdouble factor, best, distance; guint i; /* Same values used in the Seeing tab of the Universal Access panel */ static gdouble factors[] = { 0.75, 1.0, 1.25, 1.5 }; /* Figure out the current DPI scaling factor */ factor = g_settings_get_double (priv->interface_settings, "text-scaling-factor"); factor += (type == INCREASE_TEXT_KEY ? 0.25 : -0.25); /* Try to find a matching value */ distance = 1e6; best = 1.0; for (i = 0; i < G_N_ELEMENTS(factors); i++) { gdouble d; d = fabs (factor - factors[i]); if (d < distance) { best = factors[i]; distance = d; } } if (best == 1.0) g_settings_reset (priv->interface_settings, "text-scaling-factor"); else g_settings_set_double (priv->interface_settings, "text-scaling-factor", best); } static void do_magnifier_zoom_action (GsdMediaKeysManager *manager, MediaKeyType type) { GSettings *settings; gdouble offset, value; if (type == MAGNIFIER_ZOOM_IN_KEY) offset = 1.0; else offset = -1.0; settings = g_settings_new ("org.gnome.desktop.a11y.magnifier"); value = g_settings_get_double (settings, "mag-factor"); value += offset; value = roundl (value); g_settings_set_double (settings, "mag-factor", value); g_object_unref (settings); } static void do_toggle_contrast_action (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); gboolean high_contrast; char *theme; /* Are we using HighContrast now? */ theme = g_settings_get_string (priv->interface_settings, "gtk-theme"); high_contrast = g_str_equal (theme, HIGH_CONTRAST); g_free (theme); if (high_contrast != FALSE) { if (priv->gtk_theme == NULL) g_settings_reset (priv->interface_settings, "gtk-theme"); else g_settings_set (priv->interface_settings, "gtk-theme", priv->gtk_theme); g_settings_set (priv->interface_settings, "icon-theme", priv->icon_theme); } else { g_settings_set (priv->interface_settings, "gtk-theme", HIGH_CONTRAST); g_settings_set (priv->interface_settings, "icon-theme", HIGH_CONTRAST); } } static void power_action (GsdMediaKeysManager *manager, const char *action, gboolean allow_interaction) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); g_dbus_proxy_call (priv->logind_proxy, action, g_variant_new ("(b)", allow_interaction), G_DBUS_CALL_FLAGS_NONE, G_MAXINT, priv->bus_cancellable, NULL, NULL); } static void do_config_power_action (GsdMediaKeysManager *manager, GsdPowerActionType action_type, gboolean in_lock_screen) { switch (action_type) { case GSD_POWER_ACTION_SUSPEND: power_action (manager, "Suspend", !in_lock_screen); break; case GSD_POWER_ACTION_INTERACTIVE: if (!in_lock_screen) gnome_session_shutdown (manager); break; case GSD_POWER_ACTION_SHUTDOWN: power_action (manager, "PowerOff", !in_lock_screen); break; case GSD_POWER_ACTION_HIBERNATE: power_action (manager, "Hibernate", !in_lock_screen); break; case GSD_POWER_ACTION_BLANK: case GSD_POWER_ACTION_LOGOUT: case GSD_POWER_ACTION_NOTHING: /* these actions cannot be handled by media-keys and * are not used in this context */ break; } } static gboolean supports_power_action (GsdMediaKeysManager *manager, GsdPowerActionType action_type) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); const char *method_name = NULL; g_autoptr(GVariant) variant = NULL; const char *reply; gboolean result = FALSE; switch (action_type) { case GSD_POWER_ACTION_SUSPEND: method_name = "CanSuspend"; break; case GSD_POWER_ACTION_SHUTDOWN: method_name = "CanPowerOff"; break; case GSD_POWER_ACTION_HIBERNATE: method_name = "CanHibernate"; break; case GSD_POWER_ACTION_INTERACTIVE: case GSD_POWER_ACTION_BLANK: case GSD_POWER_ACTION_LOGOUT: case GSD_POWER_ACTION_NOTHING: break; } if (method_name == NULL) return FALSE; variant = g_dbus_proxy_call_sync (priv->logind_proxy, method_name, NULL, G_DBUS_CALL_FLAGS_NONE, -1, priv->bus_cancellable, NULL); if (variant == NULL) return FALSE; g_variant_get (variant, "(&s)", &reply); if (g_strcmp0 (reply, "yes") == 0) result = TRUE; return result; } static void do_config_power_button_action (GsdMediaKeysManager *manager, gboolean in_lock_screen) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GsdPowerButtonActionType action_type; GsdPowerActionType action; if (priv->power_button_disabled) return; /* Always power off VMs when power off is pressed in the menus */ if (g_strcmp0 (priv->chassis_type, "vm") == 0) { power_action (manager, "PowerOff", !in_lock_screen); return; } action_type = g_settings_get_enum (priv->power_settings, "power-button-action"); switch (action_type) { case GSD_POWER_BUTTON_ACTION_SUSPEND: action = GSD_POWER_ACTION_SUSPEND; break; case GSD_POWER_BUTTON_ACTION_HIBERNATE: action = GSD_POWER_ACTION_HIBERNATE; break; case GSD_POWER_BUTTON_ACTION_INTERACTIVE: action = GSD_POWER_ACTION_INTERACTIVE; break; default: g_warn_if_reached (); G_GNUC_FALLTHROUGH; case GSD_POWER_BUTTON_ACTION_NOTHING: /* do nothing */ return; } if (action != GSD_POWER_ACTION_INTERACTIVE && !supports_power_action (manager, action)) action = GSD_POWER_ACTION_INTERACTIVE; do_config_power_action (manager, action, in_lock_screen); } static void update_brightness_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GError *error = NULL; int percentage; GVariant *variant; GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data); GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); const char *icon, *debug; char *connector = NULL; /* update the dialog with the new value */ if (G_DBUS_PROXY (source_object) == priv->power_keyboard_proxy) { debug = "keyboard"; } else { debug = "screen"; } variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error); if (variant == NULL) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Failed to set new %s percentage: %s", debug, error->message); g_error_free (error); return; } /* update the dialog with the new value */ if (G_DBUS_PROXY (source_object) == priv->power_keyboard_proxy) { icon = "keyboard-brightness-symbolic"; g_variant_get (variant, "(i)", &percentage); } else { icon = "display-brightness-symbolic"; g_variant_get (variant, "(i&s)", &percentage, &connector); } show_osd (manager, icon, NULL, (double) percentage / 100.0, connector); g_variant_unref (variant); } static void do_brightness_action (GsdMediaKeysManager *manager, MediaKeyType type) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); const char *cmd; GDBusProxy *proxy; switch (type) { case KEYBOARD_BRIGHTNESS_UP_KEY: case KEYBOARD_BRIGHTNESS_DOWN_KEY: case KEYBOARD_BRIGHTNESS_TOGGLE_KEY: proxy = priv->power_keyboard_proxy; break; case SCREEN_BRIGHTNESS_UP_KEY: case SCREEN_BRIGHTNESS_DOWN_KEY: case SCREEN_BRIGHTNESS_CYCLE_KEY: proxy = priv->power_screen_proxy; break; default: g_assert_not_reached (); } if (priv->connection == NULL || proxy == NULL) { g_warning ("No existing D-Bus connection trying to handle power keys"); return; } switch (type) { case KEYBOARD_BRIGHTNESS_UP_KEY: case SCREEN_BRIGHTNESS_UP_KEY: cmd = "StepUp"; break; case KEYBOARD_BRIGHTNESS_DOWN_KEY: case SCREEN_BRIGHTNESS_DOWN_KEY: cmd = "StepDown"; break; case KEYBOARD_BRIGHTNESS_TOGGLE_KEY: cmd = "Toggle"; break; case SCREEN_BRIGHTNESS_CYCLE_KEY: cmd = "Cycle"; break; default: g_assert_not_reached (); } /* call into the power plugin */ g_dbus_proxy_call (proxy, cmd, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, update_brightness_cb, manager); } static void do_battery_action (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); gdouble percentage; UpDeviceKind kind; gchar *icon_name; g_return_if_fail (priv->composite_device != NULL); g_object_get (priv->composite_device, "kind", &kind, "icon-name", &icon_name, "percentage", &percentage, NULL); if (kind == UP_DEVICE_KIND_UPS || kind == UP_DEVICE_KIND_BATTERY) { g_debug ("showing battery level OSD"); show_osd (manager, icon_name, NULL, (double) percentage / 100.0, NULL); } g_free (icon_name); } static gboolean get_rfkill_property (GsdMediaKeysManager *manager, const char *property) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GVariant *v; gboolean ret; v = g_dbus_proxy_get_cached_property (priv->rfkill_proxy, property); if (!v) return FALSE; ret = g_variant_get_boolean (v); g_variant_unref (v); return ret; } typedef struct { GsdMediaKeysManager *manager; char *property; gboolean bluetooth; gboolean target_state; } RfkillData; static void set_rfkill_complete (GObject *object, GAsyncResult *result, gpointer user_data) { GError *error = NULL; GVariant *variant; RfkillData *data = user_data; variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), result, &error); if (variant == NULL) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Failed to set '%s' property: %s", data->property, error->message); g_error_free (error); goto out; } g_variant_unref (variant); g_debug ("Finished changing rfkill, property %s is now %s", data->property, data->target_state ? "true" : "false"); if (data->bluetooth) { if (data->target_state) show_osd (data->manager, "bluetooth-disabled-symbolic", _("Bluetooth disabled"), -1, NULL); else show_osd (data->manager, "bluetooth-active-symbolic", _("Bluetooth enabled"), -1, NULL); } else { if (data->target_state) show_osd (data->manager, "airplane-mode-symbolic", _("Airplane mode enabled"), -1, NULL); else show_osd (data->manager, "network-wireless-signal-excellent-symbolic", _("Airplane mode disabled"), -1, NULL); } out: g_free (data->property); g_free (data); } static void do_rfkill_action (GsdMediaKeysManager *manager, gboolean bluetooth) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); const char *has_mode, *hw_mode, *mode; gboolean new_state; guint64 current_time; RfkillData *data; has_mode = bluetooth ? "BluetoothHasAirplaneMode" : "HasAirplaneMode"; hw_mode = bluetooth ? "BluetoothHardwareAirplaneMode" : "HardwareAirplaneMode"; mode = bluetooth ? "BluetoothAirplaneMode" : "AirplaneMode"; if (priv->rfkill_proxy == NULL) return; /* Some hardwares can generate multiple rfkill events from different * drivers, on a single hotkey press. Only process the first event and * debounce the others */ current_time = g_get_monotonic_time (); if (current_time - priv->rfkill_last_time < G_USEC_PER_SEC) return; priv->rfkill_last_time = current_time; if (get_rfkill_property (manager, has_mode) == FALSE) return; if (get_rfkill_property (manager, hw_mode)) { show_osd (manager, "airplane-mode-symbolic", _("Hardware Airplane Mode"), -1, NULL); return; } new_state = !get_rfkill_property (manager, mode); data = g_new0 (RfkillData, 1); data->manager = manager; data->property = g_strdup (mode); data->bluetooth = bluetooth; data->target_state = new_state; g_dbus_proxy_call (priv->rfkill_proxy, "org.freedesktop.DBus.Properties.Set", g_variant_new ("(ssv)", "org.gnome.SettingsDaemon.Rfkill", data->property, g_variant_new_boolean (new_state)), G_DBUS_CALL_FLAGS_NONE, -1, priv->rfkill_cancellable, set_rfkill_complete, data); g_debug ("Setting rfkill property %s to %s", data->property, new_state ? "true" : "false"); } static void do_custom_action (GsdMediaKeysManager *manager, const gchar *device_node, MediaKey *key, gint64 timestamp) { g_debug ("Launching custom action for key (on device node %s)", device_node); execute (manager, key->custom_command, timestamp); } static gboolean do_action (GsdMediaKeysManager *manager, const gchar *device_node, guint mode, MediaKeyType type, gint64 timestamp) { g_debug ("Launching action for key type '%d' (on device node %s)", type, device_node); gboolean power_action_noninteractive = (POWER_KEYS_MODE_NO_DIALOG & mode); switch (type) { case TOUCHPAD_KEY: do_touchpad_action (manager); break; case TOUCHPAD_ON_KEY: do_touchpad_osd_action (manager, TRUE); break; case TOUCHPAD_OFF_KEY: do_touchpad_osd_action (manager, FALSE); break; case MUTE_KEY: case VOLUME_DOWN_KEY: case VOLUME_UP_KEY: do_sound_action (manager, device_node, type, SOUND_ACTION_FLAG_IS_OUTPUT); break; case MIC_MUTE_KEY: do_sound_action (manager, device_node, MUTE_KEY, SOUND_ACTION_FLAG_IS_QUIET); break; case MUTE_QUIET_KEY: do_sound_action (manager, device_node, MUTE_KEY, SOUND_ACTION_FLAG_IS_OUTPUT | SOUND_ACTION_FLAG_IS_QUIET); break; case VOLUME_DOWN_QUIET_KEY: do_sound_action (manager, device_node, VOLUME_DOWN_KEY, SOUND_ACTION_FLAG_IS_OUTPUT | SOUND_ACTION_FLAG_IS_QUIET); break; case VOLUME_UP_QUIET_KEY: do_sound_action (manager, device_node, VOLUME_UP_KEY, SOUND_ACTION_FLAG_IS_OUTPUT | SOUND_ACTION_FLAG_IS_QUIET); break; case VOLUME_DOWN_PRECISE_KEY: do_sound_action (manager, device_node, VOLUME_DOWN_KEY, SOUND_ACTION_FLAG_IS_OUTPUT | SOUND_ACTION_FLAG_IS_PRECISE); break; case VOLUME_UP_PRECISE_KEY: do_sound_action (manager, device_node, VOLUME_UP_KEY, SOUND_ACTION_FLAG_IS_OUTPUT | SOUND_ACTION_FLAG_IS_PRECISE); break; case LOGOUT_KEY: gnome_session_shutdown (manager); break; case EJECT_KEY: do_eject_action (manager); break; case HOME_KEY: do_home_key_action (manager, timestamp); break; case SEARCH_KEY: do_search_action (manager, timestamp); break; case EMAIL_KEY: do_url_action (manager, "mailto", timestamp); break; case SCREENSAVER_KEY: do_lock_screensaver (manager); break; case HELP_KEY: do_url_action (manager, "ghelp", timestamp); break; case WWW_KEY: do_url_action (manager, "http", timestamp); break; case MEDIA_KEY: do_media_action (manager, timestamp); break; case CALCULATOR_KEY: do_execute_desktop_or_desktop (manager, "org.gnome.Calculator.desktop", "gnome-calculator.desktop", timestamp); break; case CONTROL_CENTER_KEY: do_execute_desktop_or_desktop (manager, "org.gnome.Settings.desktop", NULL, timestamp); break; case PLAY_KEY: return do_multimedia_player_action (manager, "Play"); case PAUSE_KEY: return do_multimedia_player_action (manager, "Pause"); case STOP_KEY: return do_multimedia_player_action (manager, "Stop"); case PREVIOUS_KEY: return do_multimedia_player_action (manager, "Previous"); case NEXT_KEY: return do_multimedia_player_action (manager, "Next"); case REWIND_KEY: return do_multimedia_player_action (manager, "Rewind"); case FORWARD_KEY: return do_multimedia_player_action (manager, "FastForward"); case REPEAT_KEY: return do_multimedia_player_action (manager, "Repeat"); case RANDOM_KEY: return do_multimedia_player_action (manager, "Shuffle"); case ROTATE_VIDEO_LOCK_KEY: do_video_rotate_lock_action (manager, timestamp); break; case MAGNIFIER_KEY: do_magnifier_action (manager); break; case SCREENREADER_KEY: do_screenreader_action (manager); break; case ON_SCREEN_KEYBOARD_KEY: do_on_screen_keyboard_action (manager); break; case INCREASE_TEXT_KEY: case DECREASE_TEXT_KEY: do_text_size_action (manager, type); break; case MAGNIFIER_ZOOM_IN_KEY: case MAGNIFIER_ZOOM_OUT_KEY: do_magnifier_zoom_action (manager, type); break; case TOGGLE_CONTRAST_KEY: do_toggle_contrast_action (manager); break; case POWER_KEY: do_config_power_button_action (manager, power_action_noninteractive); break; case SUSPEND_KEY: do_config_power_action (manager, GSD_POWER_ACTION_SUSPEND, power_action_noninteractive); break; case HIBERNATE_KEY: do_config_power_action (manager, GSD_POWER_ACTION_HIBERNATE, power_action_noninteractive); break; case SCREEN_BRIGHTNESS_UP_KEY: case SCREEN_BRIGHTNESS_DOWN_KEY: case SCREEN_BRIGHTNESS_CYCLE_KEY: case KEYBOARD_BRIGHTNESS_UP_KEY: case KEYBOARD_BRIGHTNESS_DOWN_KEY: case KEYBOARD_BRIGHTNESS_TOGGLE_KEY: do_brightness_action (manager, type); break; case BATTERY_KEY: do_battery_action (manager); break; case RFKILL_KEY: do_rfkill_action (manager, FALSE); break; case BLUETOOTH_RFKILL_KEY: do_rfkill_action (manager, TRUE); break; /* Note, no default so compiler catches missing keys */ case CUSTOM_KEY: g_assert_not_reached (); } return FALSE; } static void on_accelerator_activated (ShellKeyGrabber *grabber, guint accel_id, GVariant *parameters, GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GVariantDict dict; guint i; guint deviceid; gchar *device_node; guint timestamp; guint mode; g_variant_dict_init (&dict, parameters); if (!g_variant_dict_lookup (&dict, "device-id", "u", &deviceid)) deviceid = 0; if (!g_variant_dict_lookup (&dict, "device-node", "s", &device_node)) device_node = NULL; if (!g_variant_dict_lookup (&dict, "timestamp", "u", ×tamp)) timestamp = GDK_CURRENT_TIME; if (!g_variant_dict_lookup (&dict, "action-mode", "u", &mode)) mode = 0; if (!device_node && !gnome_settings_is_wayland ()) device_node = xdevice_get_device_node (deviceid); g_debug ("Received accel id %u (device-id: %u, timestamp: %u, mode: 0x%X)", accel_id, deviceid, timestamp, mode); for (i = 0; i < priv->keys->len; i++) { MediaKey *key; guint j; key = g_ptr_array_index (priv->keys, i); for (j = 0; j < key->accel_ids->len; j++) { if (g_array_index (key->accel_ids, guint, j) == accel_id) break; } if (j >= key->accel_ids->len) continue; if (key->key_type == CUSTOM_KEY) do_custom_action (manager, device_node, key, timestamp); else do_action (manager, device_node, mode, key->key_type, timestamp); g_free (device_node); return; } g_warning ("Could not find accelerator for accel id %u", accel_id); g_free (device_node); } static void update_theme_settings (GSettings *settings, const char *key, GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); char *theme; theme = g_settings_get_string (priv->interface_settings, key); if (g_str_equal (theme, HIGH_CONTRAST)) { g_free (theme); } else { if (g_str_equal (key, "gtk-theme")) { g_free (priv->gtk_theme); priv->gtk_theme = theme; } else { g_free (priv->icon_theme); priv->icon_theme = theme; } } } typedef struct { GvcHeadsetPortChoice choice; gchar *name; } AudioSelectionChoice; static AudioSelectionChoice audio_selection_choices[] = { { GVC_HEADSET_PORT_CHOICE_HEADPHONES, "headphones" }, { GVC_HEADSET_PORT_CHOICE_HEADSET, "headset" }, { GVC_HEADSET_PORT_CHOICE_MIC, "microphone" }, }; static void audio_selection_done (GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer data) { GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (data); GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); const gchar *choice; guint i; if (!priv->audio_selection_requested) return; choice = NULL; g_variant_get_child (parameters, 0, "&s", &choice); if (!choice) return; for (i = 0; i < G_N_ELEMENTS (audio_selection_choices); ++i) { if (g_str_equal (choice, audio_selection_choices[i].name)) { gvc_mixer_control_set_headset_port (priv->volume, priv->audio_selection_device_id, audio_selection_choices[i].choice); break; } } priv->audio_selection_requested = FALSE; } static void audio_selection_needed (GvcMixerControl *control, guint id, gboolean show_dialog, GvcHeadsetPortChoice choices, GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); gchar *args[G_N_ELEMENTS (audio_selection_choices) + 1]; guint i, n; if (!priv->audio_selection_conn) return; if (priv->audio_selection_requested) { g_dbus_connection_call (priv->audio_selection_conn, AUDIO_SELECTION_DBUS_NAME, AUDIO_SELECTION_DBUS_PATH, AUDIO_SELECTION_DBUS_INTERFACE, "Close", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); priv->audio_selection_requested = FALSE; } if (!show_dialog) return; n = 0; for (i = 0; i < G_N_ELEMENTS (audio_selection_choices); ++i) { if (choices & audio_selection_choices[i].choice) args[n++] = audio_selection_choices[i].name; } args[n] = NULL; priv->audio_selection_requested = TRUE; priv->audio_selection_device_id = id; g_dbus_connection_call (priv->audio_selection_conn, AUDIO_SELECTION_DBUS_NAME, AUDIO_SELECTION_DBUS_PATH, AUDIO_SELECTION_DBUS_INTERFACE, "Open", g_variant_new ("(^as)", args), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); } static void audio_selection_appeared (GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer data) { GsdMediaKeysManager *manager = data; GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); priv->audio_selection_conn = connection; priv->audio_selection_signal_id = g_dbus_connection_signal_subscribe (connection, AUDIO_SELECTION_DBUS_NAME, AUDIO_SELECTION_DBUS_INTERFACE, "DeviceSelected", AUDIO_SELECTION_DBUS_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, audio_selection_done, manager, NULL); } static void clear_audio_selection (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); if (priv->audio_selection_signal_id) g_dbus_connection_signal_unsubscribe (priv->audio_selection_conn, priv->audio_selection_signal_id); priv->audio_selection_signal_id = 0; priv->audio_selection_conn = NULL; } static void audio_selection_vanished (GDBusConnection *connection, const gchar *name, gpointer data) { if (connection) clear_audio_selection (data); } static void initialize_volume_handler (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); /* initialise Volume handler * * We do this one here to force checking gstreamer cache, etc. * The rest (grabbing and setting the keys) can happen in an * idle. */ gnome_settings_profile_start ("gvc_mixer_control_new"); priv->volume = gvc_mixer_control_new ("GNOME Volume Control Media Keys"); g_signal_connect (priv->volume, "state-changed", G_CALLBACK (on_control_state_changed), manager); g_signal_connect (priv->volume, "default-sink-changed", G_CALLBACK (on_control_default_sink_changed), manager); g_signal_connect (priv->volume, "default-source-changed", G_CALLBACK (on_control_default_source_changed), manager); g_signal_connect (priv->volume, "stream-removed", G_CALLBACK (on_control_stream_removed), manager); g_signal_connect (priv->volume, "audio-device-selection-needed", G_CALLBACK (audio_selection_needed), manager); gvc_mixer_control_open (priv->volume); priv->audio_selection_watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, AUDIO_SELECTION_DBUS_NAME, G_BUS_NAME_WATCHER_FLAGS_NONE, audio_selection_appeared, audio_selection_vanished, manager, NULL); gnome_settings_profile_end ("gvc_mixer_control_new"); } static void on_key_grabber_ready (GObject *source, GAsyncResult *result, gpointer data) { GsdMediaKeysManager *manager = data; GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GError *error = NULL; priv->key_grabber = shell_key_grabber_proxy_new_for_bus_finish (result, &error); if (!priv->key_grabber) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Failed to create proxy for key grabber: %s", error->message); g_error_free (error); return; } g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (priv->key_grabber), SHELL_GRABBER_CALL_TIMEOUT); g_signal_connect (priv->key_grabber, "accelerator-activated", G_CALLBACK (on_accelerator_activated), manager); init_kbd (manager); } static void shell_presence_changed (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); gchar *name_owner; name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (priv->shell_proxy)); g_ptr_array_set_size (priv->keys, 0); g_clear_object (&priv->key_grabber); if (name_owner) { shell_key_grabber_proxy_new_for_bus (G_BUS_TYPE_SESSION, 0, name_owner, SHELL_DBUS_PATH, priv->grab_cancellable, on_key_grabber_ready, manager); g_free (name_owner); } } static void on_rfkill_proxy_ready (GObject *source, GAsyncResult *result, gpointer data) { GsdMediaKeysManager *manager = data; GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); priv->rfkill_proxy = g_dbus_proxy_new_for_bus_finish (result, NULL); } static void rfkill_appeared_cb (GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data) { GsdMediaKeysManager *manager = user_data; GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, 0, NULL, "org.gnome.SettingsDaemon.Rfkill", "/org/gnome/SettingsDaemon/Rfkill", "org.gnome.SettingsDaemon.Rfkill", priv->rfkill_cancellable, on_rfkill_proxy_ready, manager); } static gboolean start_media_keys_idle_cb (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); g_debug ("Starting media_keys manager"); gnome_settings_profile_start (NULL); priv->keys = g_ptr_array_new_with_free_func ((GDestroyNotify) media_key_unref); priv->keys_to_sync = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) media_key_unref, NULL); initialize_volume_handler (manager); priv->settings = g_settings_new (SETTINGS_BINDING_DIR); g_signal_connect (G_OBJECT (priv->settings), "changed", G_CALLBACK (gsettings_changed_cb), manager); g_signal_connect (G_OBJECT (priv->settings), "changed::custom-keybindings", G_CALLBACK (gsettings_custom_changed_cb), manager); priv->custom_settings = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); priv->sound_settings = g_settings_new (SETTINGS_SOUND_DIR); g_signal_connect (G_OBJECT (priv->sound_settings), "changed::" ALLOW_VOLUME_ABOVE_100_PERCENT_KEY, G_CALLBACK (allow_volume_above_100_percent_changed_cb), manager); allow_volume_above_100_percent_changed_cb (priv->sound_settings, ALLOW_VOLUME_ABOVE_100_PERCENT_KEY, manager); /* for the power plugin interface code */ priv->power_settings = g_settings_new (SETTINGS_POWER_DIR); priv->chassis_type = gnome_settings_get_chassis_type (); /* Logic from http://git.gnome.org/browse/gnome-shell/tree/js/ui/status/accessibility.js#n163 */ priv->interface_settings = g_settings_new (SETTINGS_INTERFACE_DIR); g_signal_connect (G_OBJECT (priv->interface_settings), "changed::gtk-theme", G_CALLBACK (update_theme_settings), manager); g_signal_connect (G_OBJECT (priv->interface_settings), "changed::icon-theme", G_CALLBACK (update_theme_settings), manager); priv->gtk_theme = g_settings_get_string (priv->interface_settings, "gtk-theme"); if (g_str_equal (priv->gtk_theme, HIGH_CONTRAST)) { g_free (priv->gtk_theme); priv->gtk_theme = NULL; } priv->icon_theme = g_settings_get_string (priv->interface_settings, "icon-theme"); priv->grab_cancellable = g_cancellable_new (); priv->rfkill_cancellable = g_cancellable_new (); priv->shell_proxy = gnome_settings_bus_get_shell_proxy (); g_signal_connect_swapped (priv->shell_proxy, "notify::g-name-owner", G_CALLBACK (shell_presence_changed), manager); shell_presence_changed (manager); priv->rfkill_watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, "org.gnome.SettingsDaemon.Rfkill", G_BUS_NAME_WATCHER_FLAGS_NONE, rfkill_appeared_cb, NULL, manager, NULL); g_debug ("Starting mpris controller"); priv->mpris_controller = mpris_controller_new (); /* Rotation */ priv->iio_sensor_watch_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM, "net.hadess.SensorProxy", G_BUS_NAME_WATCHER_FLAGS_NONE, iio_sensor_appeared_cb, iio_sensor_disappeared_cb, manager, NULL); gnome_settings_profile_end (NULL); priv->start_idle_id = 0; return FALSE; } static GVariant * map_keybinding (GVariant *variant, GVariant *old_default, GVariant *new_default) { g_autoptr(GPtrArray) array = g_ptr_array_new (); g_autofree const gchar **defaults = NULL; const gchar *old_default_value; const gchar **pos; const gchar *value; defaults = g_variant_get_strv (new_default, NULL); g_return_val_if_fail (defaults != NULL, NULL); pos = defaults; value = g_variant_get_string (variant, NULL); old_default_value = g_variant_get_string (old_default, NULL); /* Reset the keybinding configuration even if the user has the default * configured explicitly (as the key will be bound by the corresponding * static binding now). */ if (g_strcmp0 (value, old_default_value) == 0) return NULL; /* If the user has a custom value that is not in the list, then * insert it instead of the first default entry. */ if (!g_strv_contains (defaults, value)) { g_ptr_array_add (array, (gpointer) value); if (*pos) pos++; } /* Add all remaining default values */ for (; *pos; pos++) g_ptr_array_add (array, (gpointer) *pos); g_ptr_array_add (array, NULL); return g_variant_new_strv ((const gchar * const *) array->pdata, -1); } static void migrate_keybinding_settings (void) { GsdSettingsMigrateEntry binding_entries[] = { { "calculator", "calculator", map_keybinding }, { "control-center", "control-center", map_keybinding }, { "email", "email", map_keybinding }, { "eject", "eject", map_keybinding }, { "help", "help", map_keybinding }, { "home", "home", map_keybinding }, { "media", "media", map_keybinding }, { "next", "next", map_keybinding }, { "pause", "pause", map_keybinding }, { "play", "play", map_keybinding }, { "logout", "logout", map_keybinding }, { "previous", "previous", map_keybinding }, { "screensaver", "screensaver", map_keybinding }, { "search", "search", map_keybinding }, { "stop", "stop", map_keybinding }, { "volume-down", "volume-down", map_keybinding }, { "volume-mute", "volume-mute", map_keybinding }, { "volume-up", "volume-up", map_keybinding }, { "mic-mute", "mic-mute", map_keybinding }, { "www", "www", map_keybinding }, { "magnifier", "magnifier", map_keybinding }, { "screenreader", "screenreader", map_keybinding }, { "on-screen-keyboard", "on-screen-keyboard", map_keybinding }, { "increase-text-size", "increase-text-size", map_keybinding }, { "decrease-text-size", "decrease-text-size", map_keybinding }, { "toggle-contrast", "toggle-contrast", map_keybinding }, { "magnifier-zoom-in", "magnifier-zoom-in", map_keybinding }, { "magnifier-zoom-out", "magnifier-zoom-out", map_keybinding }, }; gsd_settings_migrate_check ("org.gnome.settings-daemon.plugins.media-keys.deprecated", "/org/gnome/settings-daemon/plugins/media-keys/", "org.gnome.settings-daemon.plugins.media-keys", "/org/gnome/settings-daemon/plugins/media-keys/", binding_entries, G_N_ELEMENTS (binding_entries)); } gboolean gsd_media_keys_manager_start (GsdMediaKeysManager *manager, GError **error) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); const char * const subsystems[] = { "input", "usb", "sound", NULL }; gnome_settings_profile_start (NULL); migrate_keybinding_settings (); #if HAVE_GUDEV priv->streams = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); priv->udev_client = g_udev_client_new (subsystems); #endif priv->start_idle_id = g_idle_add ((GSourceFunc) start_media_keys_idle_cb, manager); g_source_set_name_by_id (priv->start_idle_id, "[gnome-settings-daemon] start_media_keys_idle_cb"); register_manager (manager_object); gnome_settings_profile_end (NULL); return TRUE; } void gsd_media_keys_manager_stop (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); g_debug ("Stopping media_keys manager"); if (priv->start_idle_id != 0) { g_source_remove (priv->start_idle_id); priv->start_idle_id = 0; } if (priv->bus_cancellable != NULL) { g_cancellable_cancel (priv->bus_cancellable); g_object_unref (priv->bus_cancellable); priv->bus_cancellable = NULL; } if (priv->gtksettings != NULL) { g_signal_handlers_disconnect_by_func (priv->gtksettings, sound_theme_changed, manager); priv->gtksettings = NULL; } if (priv->rfkill_watch_id > 0) { g_bus_unwatch_name (priv->rfkill_watch_id); priv->rfkill_watch_id = 0; } if (priv->iio_sensor_watch_id > 0) { g_bus_unwatch_name (priv->iio_sensor_watch_id); priv->iio_sensor_watch_id = 0; } if (priv->inhibit_suspend_fd != -1) { close (priv->inhibit_suspend_fd); priv->inhibit_suspend_fd = -1; priv->inhibit_suspend_taken = FALSE; } if (priv->reenable_power_button_timer_id) { g_source_remove (priv->reenable_power_button_timer_id); priv->reenable_power_button_timer_id = 0; } g_clear_pointer (&priv->ca, ca_context_destroy); #if HAVE_GUDEV g_clear_pointer (&priv->streams, g_hash_table_destroy); g_clear_object (&priv->udev_client); #endif /* HAVE_GUDEV */ g_clear_object (&priv->logind_proxy); g_clear_object (&priv->settings); g_clear_object (&priv->sound_settings); g_clear_object (&priv->power_settings); g_clear_object (&priv->power_proxy); g_clear_object (&priv->power_screen_proxy); g_clear_object (&priv->power_keyboard_proxy); g_clear_object (&priv->composite_device); g_clear_object (&priv->mpris_controller); g_clear_object (&priv->iio_sensor_proxy); g_clear_pointer (&priv->chassis_type, g_free); g_clear_object (&priv->connection); if (priv->keys_sync_data) { /* Cancel ongoing sync. */ priv->keys_sync_data->cancelled = TRUE; priv->keys_sync_data = NULL; } if (priv->keys_sync_source_id) g_source_remove (priv->keys_sync_source_id); priv->keys_sync_source_id = 0; /* Remove all grabs; i.e.: * - add all keys to the sync queue * - remove all keys from the internal keys list * - call the function to start a sync * - "cancel" the sync operation as the manager will be gone */ if (priv->keys != NULL) { while (priv->keys->len) { MediaKey *key = g_ptr_array_index (priv->keys, 0); g_hash_table_add (priv->keys_to_sync, media_key_ref (key)); g_ptr_array_remove_index_fast (priv->keys, 0); } keys_sync_start (manager); g_clear_pointer (&priv->keys, g_ptr_array_unref); } g_clear_pointer (&priv->keys_to_sync, g_hash_table_destroy); g_clear_object (&priv->key_grabber); if (priv->grab_cancellable != NULL) { g_cancellable_cancel (priv->grab_cancellable); g_clear_object (&priv->grab_cancellable); } if (priv->rfkill_cancellable != NULL) { g_cancellable_cancel (priv->rfkill_cancellable); g_clear_object (&priv->rfkill_cancellable); } g_clear_object (&priv->sink); g_clear_object (&priv->source); g_clear_object (&priv->volume); g_clear_object (&priv->shell_proxy); if (priv->audio_selection_watch_id) g_bus_unwatch_name (priv->audio_selection_watch_id); priv->audio_selection_watch_id = 0; clear_audio_selection (manager); } static void inhibit_suspend_done (GObject *source, GAsyncResult *result, gpointer user_data) { GDBusProxy *proxy = G_DBUS_PROXY (source); GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data); GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GError *error = NULL; GVariant *res; GUnixFDList *fd_list = NULL; gint idx; res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error); if (res == NULL) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Unable to inhibit suspend: %s", error->message); g_error_free (error); } else { g_variant_get (res, "(h)", &idx); priv->inhibit_suspend_fd = g_unix_fd_list_get (fd_list, idx, &error); if (priv->inhibit_suspend_fd == -1) { g_warning ("Failed to receive system suspend inhibitor fd: %s", error->message); g_error_free (error); } g_debug ("System suspend inhibitor fd is %d", priv->inhibit_suspend_fd); g_object_unref (fd_list); g_variant_unref (res); } } /* We take a delay inhibitor here, which causes logind to send a PrepareForSleep * signal, so that we can set power_button_disabled on suspend. */ static void inhibit_suspend (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); if (priv->inhibit_suspend_taken) { g_debug ("already inhibited suspend"); return; } g_debug ("Adding suspend delay inhibitor"); priv->inhibit_suspend_taken = TRUE; g_dbus_proxy_call_with_unix_fd_list (priv->logind_proxy, "Inhibit", g_variant_new ("(ssss)", "sleep", g_get_user_name (), "GNOME handling keypresses", "delay"), 0, G_MAXINT, NULL, NULL, inhibit_suspend_done, manager); } static void uninhibit_suspend (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); if (priv->inhibit_suspend_fd == -1) { g_debug ("no suspend delay inhibitor"); return; } g_debug ("Removing suspend delay inhibitor"); close (priv->inhibit_suspend_fd); priv->inhibit_suspend_fd = -1; priv->inhibit_suspend_taken = FALSE; } static gboolean reenable_power_button_timer_cb (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); priv->power_button_disabled = FALSE; /* This is a one shot timer. */ priv->reenable_power_button_timer_id = 0; return G_SOURCE_REMOVE; } static void setup_reenable_power_button_timer (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); if (priv->reenable_power_button_timer_id != 0) return; priv->reenable_power_button_timer_id = g_timeout_add (GSD_REENABLE_POWER_BUTTON_DELAY, (GSourceFunc) reenable_power_button_timer_cb, manager); g_source_set_name_by_id (priv->reenable_power_button_timer_id, "[GsdMediaKeysManager] Reenable power button timer"); } static void stop_reenable_power_button_timer (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); if (priv->reenable_power_button_timer_id == 0) return; g_source_remove (priv->reenable_power_button_timer_id); priv->reenable_power_button_timer_id = 0; } static void logind_proxy_signal_cb (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data); GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); gboolean is_about_to_suspend; if (g_strcmp0 (signal_name, "PrepareForSleep") != 0) return; g_variant_get (parameters, "(b)", &is_about_to_suspend); if (is_about_to_suspend) { /* Some devices send a power-button press on resume when woken * up with the power-button, suppress this, to avoid immediate * re-suspend. */ stop_reenable_power_button_timer (manager); priv->power_button_disabled = TRUE; uninhibit_suspend (manager); } else { inhibit_suspend (manager); /* Re-enable power-button handling (after a small delay) */ setup_reenable_power_button_timer (manager); } } static void gsd_media_keys_manager_class_init (GsdMediaKeysManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = gsd_media_keys_manager_finalize; } static void inhibit_done (GObject *source, GAsyncResult *result, gpointer user_data) { GDBusProxy *proxy = G_DBUS_PROXY (source); GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data); GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GError *error = NULL; GVariant *res; GUnixFDList *fd_list = NULL; gint idx; res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error); if (res == NULL) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Unable to inhibit keypresses: %s", error->message); g_error_free (error); } else { g_variant_get (res, "(h)", &idx); priv->inhibit_keys_fd = g_unix_fd_list_get (fd_list, idx, &error); if (priv->inhibit_keys_fd == -1) { g_warning ("Failed to receive system inhibitor fd: %s", error->message); g_error_free (error); } g_debug ("System inhibitor fd is %d", priv->inhibit_keys_fd); g_object_unref (fd_list); g_variant_unref (res); } } static void gsd_media_keys_manager_init (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GError *error; GDBusConnection *bus; error = NULL; priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); if (bus == NULL) { g_warning ("Failed to connect to system bus: %s", error->message); g_error_free (error); return; } priv->logind_proxy = g_dbus_proxy_new_sync (bus, 0, NULL, SYSTEMD_DBUS_NAME, SYSTEMD_DBUS_PATH, SYSTEMD_DBUS_INTERFACE, NULL, &error); if (priv->logind_proxy == NULL) { g_warning ("Failed to connect to systemd: %s", error->message); g_error_free (error); } g_object_unref (bus); g_debug ("Adding system inhibitors for power keys"); priv->inhibit_keys_fd = -1; g_dbus_proxy_call_with_unix_fd_list (priv->logind_proxy, "Inhibit", g_variant_new ("(ssss)", "handle-power-key:handle-suspend-key:handle-hibernate-key", g_get_user_name (), "GNOME handling keypresses", "block"), 0, G_MAXINT, NULL, NULL, inhibit_done, manager); g_debug ("Adding delay inhibitor for suspend"); priv->inhibit_suspend_fd = -1; g_signal_connect (priv->logind_proxy, "g-signal", G_CALLBACK (logind_proxy_signal_cb), manager); inhibit_suspend (manager); } static void gsd_media_keys_manager_finalize (GObject *object) { GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (object); GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); gsd_media_keys_manager_stop (manager); if (priv->inhibit_keys_fd != -1) close (priv->inhibit_keys_fd); g_clear_object (&priv->logind_proxy); g_clear_object (&priv->screen_saver_proxy); G_OBJECT_CLASS (gsd_media_keys_manager_parent_class)->finalize (object); } static void power_keyboard_proxy_signal_cb (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data); gint brightness; const gchar *source; if (g_strcmp0 (signal_name, "BrightnessChanged") != 0) return; g_variant_get (parameters, "(i&s)", &brightness, &source); /* For non "internal" changes we already show the osd when handling * the hotkey causing the change. */ if (g_strcmp0 (source, "internal") != 0) return; show_osd (manager, "keyboard-brightness-symbolic", NULL, (double) brightness / 100.0, NULL); } static void power_ready_cb (GObject *source_object, GAsyncResult *res, GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GError *error = NULL; priv->power_proxy = g_dbus_proxy_new_finish (res, &error); if (priv->power_proxy == NULL) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Failed to get proxy for power: %s", error->message); g_error_free (error); } } static void power_screen_ready_cb (GObject *source_object, GAsyncResult *res, GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GError *error = NULL; priv->power_screen_proxy = g_dbus_proxy_new_finish (res, &error); if (priv->power_screen_proxy == NULL) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Failed to get proxy for power (screen): %s", error->message); g_error_free (error); } } static void power_keyboard_ready_cb (GObject *source_object, GAsyncResult *res, GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GError *error = NULL; priv->power_keyboard_proxy = g_dbus_proxy_new_finish (res, &error); if (priv->power_keyboard_proxy == NULL) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Failed to get proxy for power (keyboard): %s", error->message); g_error_free (error); } g_signal_connect (priv->power_keyboard_proxy, "g-signal", G_CALLBACK (power_keyboard_proxy_signal_cb), manager); } static void on_bus_gotten (GObject *source_object, GAsyncResult *res, GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); GDBusConnection *connection; GError *error = NULL; UpClient *up_client; connection = g_bus_get_finish (res, &error); if (connection == NULL) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Could not get session bus: %s", error->message); g_error_free (error); return; } priv->connection = connection; g_dbus_proxy_new (priv->connection, G_DBUS_PROXY_FLAGS_NONE, NULL, GSD_DBUS_NAME ".Power", GSD_DBUS_PATH "/Power", GSD_DBUS_BASE_INTERFACE ".Power", NULL, (GAsyncReadyCallback) power_ready_cb, manager); g_dbus_proxy_new (priv->connection, G_DBUS_PROXY_FLAGS_NONE, NULL, GSD_DBUS_NAME ".Power", GSD_DBUS_PATH "/Power", GSD_DBUS_BASE_INTERFACE ".Power.Screen", NULL, (GAsyncReadyCallback) power_screen_ready_cb, manager); g_dbus_proxy_new (priv->connection, G_DBUS_PROXY_FLAGS_NONE, NULL, GSD_DBUS_NAME ".Power", GSD_DBUS_PATH "/Power", GSD_DBUS_BASE_INTERFACE ".Power.Keyboard", NULL, (GAsyncReadyCallback) power_keyboard_ready_cb, manager); up_client = up_client_new (); priv->composite_device = up_client_get_display_device (up_client); g_object_unref (up_client); } static void register_manager (GsdMediaKeysManager *manager) { GsdMediaKeysManagerPrivate *priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); priv->bus_cancellable = g_cancellable_new (); g_bus_get (G_BUS_TYPE_SESSION, priv->bus_cancellable, (GAsyncReadyCallback) on_bus_gotten, manager); } GsdMediaKeysManager * gsd_media_keys_manager_new (void) { if (manager_object != NULL) { g_object_ref (manager_object); } else { manager_object = g_object_new (GSD_TYPE_MEDIA_KEYS_MANAGER, NULL); g_object_add_weak_pointer (manager_object, (gpointer *) &manager_object); } return GSD_MEDIA_KEYS_MANAGER (manager_object); }