From 12f8325cbc42354918d061ac03ba42c190fc8968 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Sun, 9 Dec 2018 12:44:20 +0100 Subject: [PATCH] core: Add MetaSoundPlayer abstraction This is a simple libcanberra abstraction object, so we are able to play file/theme sounds without poking into GTK+/X11. Play requests are delegated to a separate thread, so we don't block UI on cards that are slow to wake up from power saving. --- src/Makefile.am | 2 + src/core/display-private.h | 2 + src/core/display.c | 16 ++ src/core/meta-sound-player.c | 290 +++++++++++++++++++++++++++++++++++ src/meson.build | 1 + src/meta/display.h | 3 + src/meta/meson.build | 1 + src/meta/meta-sound-player.h | 39 +++++ 8 files changed, 354 insertions(+) create mode 100644 src/core/meta-sound-player.c create mode 100644 src/meta/meta-sound-player.h diff --git a/src/Makefile.am b/src/Makefile.am index 8e5d03bce..3c5540bf9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -295,6 +295,7 @@ libmutter_@LIBMUTTER_API_VERSION@_la_SOURCES = \ meta/meta-inhibit-shortcuts-dialog.h \ core/meta-inhibit-shortcuts-dialog-default.c \ core/meta-inhibit-shortcuts-dialog-default-private.h \ + core/meta-sound-player.c \ core/delete.c \ core/display.c \ core/display-private.h \ @@ -585,6 +586,7 @@ libmutterinclude_headers = \ meta/meta-settings.h \ meta/meta-shaped-texture.h \ meta/meta-shadow-factory.h \ + meta/meta-sound-player.h \ meta/meta-stage.h \ meta/meta-startup-notification.h \ meta/meta-window-actor.h \ diff --git a/src/core/display-private.h b/src/core/display-private.h index 576a4254e..4156c2342 100644 --- a/src/core/display-private.h +++ b/src/core/display-private.h @@ -241,6 +241,8 @@ struct _MetaDisplay MetaBell *bell; MetaWorkspaceManager *workspace_manager; + + MetaSoundPlayer *sound_player; }; struct _MetaDisplayClass diff --git a/src/core/display.c b/src/core/display.c index f96f36b38..02b6f92e0 100644 --- a/src/core/display.c +++ b/src/core/display.c @@ -66,6 +66,7 @@ #include "meta/main.h" #include "meta/meta-backend.h" #include "meta/meta-enum-types.h" +#include "meta/meta-sound-player.h" #include "meta/meta-x11-errors.h" #include "meta/prefs.h" #include "x11/meta-startup-notification-x11.h" @@ -777,6 +778,8 @@ meta_display_open (void) meta_idle_monitor_init_dbus (); + display->sound_player = g_object_new (META_TYPE_SOUND_PLAYER, NULL); + /* Done opening new display */ display->display_opening = FALSE; @@ -954,6 +957,7 @@ meta_display_close (MetaDisplay *display, g_clear_object (&display->bell); g_clear_object (&display->startup_notification); g_clear_object (&display->workspace_manager); + g_clear_object (&display->sound_player); g_object_unref (display); the_display = NULL; @@ -3629,3 +3633,15 @@ meta_display_generate_window_id (MetaDisplay *display) /* We can overflow here, that's fine */ return (base_window_id + last_window_id++); } + +/** + * meta_display_get_sound_player: + * @display: a #MetaDisplay + * + * Returns: (transfer none): The sound player of the display + */ +MetaSoundPlayer * +meta_display_get_sound_player (MetaDisplay *display) +{ + return display->sound_player; +} diff --git a/src/core/meta-sound-player.c b/src/core/meta-sound-player.c new file mode 100644 index 000000000..319510a5b --- /dev/null +++ b/src/core/meta-sound-player.c @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2018 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Author: Carlos Garnacho + */ + +#include "config.h" + +#include + +#include "meta/meta-sound-player.h" + +#define EVENT_SOUNDS_KEY "event-sounds" +#define THEME_NAME_KEY "theme-name" + +typedef struct _MetaPlayRequest MetaPlayRequest; + +struct _MetaSoundPlayer +{ + GObject parent; + GThreadPool *queue; + GSettings *settings; + ca_context *context; + uint32_t id_pool; +}; + +struct _MetaPlayRequest +{ + ca_proplist *props; + uint32_t id; + guint cancel_id; + GCancellable *cancellable; + MetaSoundPlayer *player; +}; + +const char * const cache_whitelist[] = { + "bell-window-system", + "desktop-switch-left", + "desktop-switch-right", + "desktop-switch-up", + "desktop-switch-down", + NULL +}; + +G_DEFINE_TYPE (MetaSoundPlayer, meta_sound_player, G_TYPE_OBJECT) + +static MetaPlayRequest * +meta_play_request_new (MetaSoundPlayer *player, + ca_proplist *props, + GCancellable *cancellable) +{ + MetaPlayRequest *req; + + req = g_new0 (MetaPlayRequest, 1); + req->props = props; + req->player = player; + g_set_object (&req->cancellable, cancellable); + + return req; +} + +static void +meta_play_request_free (MetaPlayRequest *req) +{ + g_clear_object (&req->cancellable); + ca_proplist_destroy (req->props); + g_free (req); +} + +static void +meta_sound_player_finalize (GObject *object) +{ + MetaSoundPlayer *player = META_SOUND_PLAYER (object); + + g_object_unref (player->settings); + g_thread_pool_free (player->queue, FALSE, TRUE); + ca_context_destroy (player->context); + + G_OBJECT_CLASS (meta_sound_player_parent_class)->finalize (object); +} + +static void +meta_sound_player_class_init (MetaSoundPlayerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = meta_sound_player_finalize; +} + +static void +cancelled_cb (GCancellable *cancellable, + MetaPlayRequest *req) +{ + ca_context_cancel (req->player->context, req->id); +} + +static void +finish_cb (ca_context *context, + uint32_t id, + int error_code, + gpointer user_data) +{ + MetaPlayRequest *req = user_data; + + g_cancellable_disconnect (req->cancellable, req->cancel_id); + meta_play_request_free (req); +} + +static void +play_sound (MetaPlayRequest *req, + MetaSoundPlayer *player) +{ + req->id = player->id_pool++; + + if (ca_context_play_full (player->context, req->id, req->props, + finish_cb, req) != CA_SUCCESS) + { + meta_play_request_free (req); + return; + } + + if (req->cancellable) + { + req->cancel_id = + g_cancellable_connect (req->cancellable, + G_CALLBACK (cancelled_cb), req, NULL); + } +} + +static void +settings_changed_cb (GSettings *settings, + const char *key, + MetaSoundPlayer *player) +{ + if (strcmp (key, EVENT_SOUNDS_KEY) == 0) + { + gboolean enabled; + + enabled = g_settings_get_boolean (settings, EVENT_SOUNDS_KEY); + ca_context_change_props (player->context, CA_PROP_CANBERRA_ENABLE, + enabled ? "1" : "0", NULL); + } + else if (strcmp (key, THEME_NAME_KEY) == 0) + { + char *theme_name; + + theme_name = g_settings_get_string (settings, THEME_NAME_KEY); + ca_context_change_props (player->context, CA_PROP_CANBERRA_XDG_THEME_NAME, + theme_name, NULL); + g_free (theme_name); + } +} + +static ca_context * +create_context (GSettings *settings) +{ + ca_context *context; + ca_proplist *props; + gboolean enabled; + char *theme_name; + + if (ca_context_create (&context) != CA_SUCCESS) + return NULL; + + if (ca_proplist_create (&props) != CA_SUCCESS) + { + ca_context_destroy (context); + return NULL; + } + + ca_proplist_sets (props, CA_PROP_APPLICATION_NAME, "Mutter"); + + enabled = g_settings_get_boolean (settings, EVENT_SOUNDS_KEY); + ca_proplist_sets (props, CA_PROP_CANBERRA_ENABLE, enabled ? "1" : "0"); + + theme_name = g_settings_get_string (settings, THEME_NAME_KEY); + ca_proplist_sets (props, CA_PROP_CANBERRA_XDG_THEME_NAME, theme_name); + g_free (theme_name); + + ca_context_change_props_full (context, props); + ca_proplist_destroy (props); + + return context; +} + +static void +meta_sound_player_init (MetaSoundPlayer *player) +{ + player->queue = g_thread_pool_new ((GFunc) play_sound, + player, 1, FALSE, NULL); + player->settings = g_settings_new ("org.gnome.desktop.sound"); + player->context = create_context (player->settings); + + g_signal_connect (player->settings, "changed", + G_CALLBACK (settings_changed_cb), player); +} + +static void +build_ca_proplist (ca_proplist *props, + const char *event_property, + const char *event_id, + const char *event_description) +{ + ca_proplist_sets (props, event_property, event_id); + ca_proplist_sets (props, CA_PROP_EVENT_DESCRIPTION, event_description); +} + +/** + * meta_sound_player_play_from_theme: + * @sound: a #MetaSoundPlayer + * @name: sound theme name of the event + * @description: description of the event + * @cancellable: cancellable for the request + * + * Plays a sound from the sound theme. + **/ +void +meta_sound_player_play_from_theme (MetaSoundPlayer *player, + const char *name, + const char *description, + GCancellable *cancellable) +{ + MetaPlayRequest *req; + ca_proplist *props; + + g_return_if_fail (META_IS_SOUND_PLAYER (player)); + g_return_if_fail (name != NULL); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + ca_proplist_create (&props); + build_ca_proplist (props, CA_PROP_EVENT_ID, name, description); + + if (g_strv_contains (cache_whitelist, name)) + ca_proplist_sets (props, CA_PROP_CANBERRA_CACHE_CONTROL, "permanent"); + else + ca_proplist_sets (props, CA_PROP_CANBERRA_CACHE_CONTROL, "volatile"); + + req = meta_play_request_new (player, props, cancellable); + g_thread_pool_push (player->queue, req, NULL); +} + +/** + * meta_sound_player_play_from_file: + * @sound: a #MetaSoundPlayer + * @file: file to play + * @description: description of the played sound + * @cancellable: cancellable for the request + * + * Plays a sound from a file. + **/ +void +meta_sound_player_play_from_file (MetaSoundPlayer *player, + GFile *file, + const char *description, + GCancellable *cancellable) +{ + MetaPlayRequest *req; + ca_proplist *props; + char *path; + + g_return_if_fail (META_IS_SOUND_PLAYER (player)); + g_return_if_fail (G_IS_FILE (file)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + path = g_file_get_path (file); + g_return_if_fail (path != NULL); + + ca_proplist_create (&props); + build_ca_proplist (props, CA_PROP_MEDIA_FILENAME, path, description); + ca_proplist_sets (props, CA_PROP_CANBERRA_CACHE_CONTROL, "volatile"); + g_free (path); + + req = meta_play_request_new (player, props, cancellable); + g_thread_pool_push (player->queue, req, NULL); +} diff --git a/src/meson.build b/src/meson.build index e258d8d39..c2f43b3ac 100644 --- a/src/meson.build +++ b/src/meson.build @@ -334,6 +334,7 @@ mutter_sources = [ 'core/meta-inhibit-shortcuts-dialog.c', 'core/meta-inhibit-shortcuts-dialog-default.c', 'core/meta-inhibit-shortcuts-dialog-default-private.h', + 'core/meta-sound-player.c', 'core/meta-workspace-manager.c', 'core/meta-workspace-manager-private.h', 'core/place.c', diff --git a/src/meta/display.h b/src/meta/display.h index 68ddee7db..918817809 100644 --- a/src/meta/display.h +++ b/src/meta/display.h @@ -27,6 +27,7 @@ #include #include #include +#include #include /** @@ -230,4 +231,6 @@ MetaWorkspaceManager *meta_display_get_workspace_manager (MetaDisplay *display); */ MetaStartupNotification * meta_display_get_startup_notification (MetaDisplay *display); +MetaSoundPlayer * meta_display_get_sound_player (MetaDisplay *display); + #endif diff --git a/src/meta/meson.build b/src/meta/meson.build index bdd77a08e..a5a6ad2be 100644 --- a/src/meta/meson.build +++ b/src/meta/meson.build @@ -24,6 +24,7 @@ mutter_public_headers = [ 'meta-settings.h', 'meta-shadow-factory.h', 'meta-shaped-texture.h', + 'meta-sound-player.h', 'meta-stage.h', 'meta-startup-notification.h', 'meta-window-actor.h', diff --git a/src/meta/meta-sound-player.h b/src/meta/meta-sound-player.h new file mode 100644 index 000000000..e6ddabd34 --- /dev/null +++ b/src/meta/meta-sound-player.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Author: Carlos Garnacho + */ +#ifndef META_SOUND_PLAYER_H +#define META_SOUND_PLAYER_H + +#include + +#define META_TYPE_SOUND_PLAYER (meta_sound_player_get_type ()) +G_DECLARE_FINAL_TYPE (MetaSoundPlayer, meta_sound_player, + META, SOUND_PLAYER, GObject) + +void meta_sound_player_play_from_theme (MetaSoundPlayer *player, + const char *name, + const char *description, + GCancellable *cancellable); +void meta_sound_player_play_from_file (MetaSoundPlayer *player, + GFile *file, + const char *description, + GCancellable *cancellable); + +#endif /* META_SOUND_PLAYER_H */