/* * 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, see . * * Author: Carlos Garnacho */ #include "config.h" #ifdef HAVE_SOUND_PLAYER #include #endif #include "meta/meta-sound-player.h" #define EVENT_SOUNDS_KEY "event-sounds" #define THEME_NAME_KEY "theme-name" struct _MetaSoundPlayer { GObject parent; #ifdef HAVE_SOUND_PLAYER GThreadPool *queue; GSettings *settings; ca_context *context; uint32_t id_pool; #endif }; #ifdef HAVE_SOUND_PLAYER typedef struct _MetaPlayRequest MetaPlayRequest; struct _MetaPlayRequest { ca_proplist *props; uint32_t id; gulong cancel_id; GCancellable *cancellable; MetaSoundPlayer *player; }; #endif const char * const cache_allow_list[] = { "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 void meta_sound_player_finalize (GObject *object) { #ifdef HAVE_SOUND_PLAYER MetaSoundPlayer *player = META_SOUND_PLAYER (object); g_clear_object (&player->settings); g_thread_pool_free (player->queue, FALSE, TRUE); g_clear_pointer (&player->context, ca_context_destroy); #endif 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; } #ifdef HAVE_SOUND_PLAYER 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 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; if (error_code != CA_ERROR_CANCELED) g_cancellable_disconnect (req->cancellable, req->cancel_id); else if (req->cancellable != NULL) g_clear_signal_handler (&req->cancel_id, req->cancellable); 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) { gulong cancel_id = g_cancellable_connect (req->cancellable, G_CALLBACK (cancelled_cb), req, NULL); if (cancel_id) req->cancel_id = cancel_id; } } 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 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); } #endif /* HAVE_SOUND_PLAYER */ static void meta_sound_player_init (MetaSoundPlayer *player) { #ifdef HAVE_SOUND_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); #endif } /** * meta_sound_player_play_from_theme: * @player: 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) { #ifdef HAVE_SOUND_PLAYER 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_allow_list, 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); #endif } /** * meta_sound_player_play_from_file: * @player: 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) { #ifdef HAVE_SOUND_PLAYER 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); #endif }