1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
3
* Copyright (C) 2008 Bastien Nocera <hadess@hadess.net>
4
* Copyright (C) 2008 William Jon McCann
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
31
#include <glib/gi18n-lib.h>
33
#include <canberra-gtk.h>
34
#include <libxml/tree.h>
36
#include <gconf/gconf-client.h>
38
#include "gvc-sound-theme-chooser.h"
39
#include "sound-theme-file-utils.h"
41
#define GVC_SOUND_THEME_CHOOSER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_SOUND_THEME_CHOOSER, GvcSoundThemeChooserPrivate))
43
struct GvcSoundThemeChooserPrivate
46
GtkWidget *selection_box;
48
GSettings *sound_settings;
49
guint metacity_dir_id;
54
static void gvc_sound_theme_chooser_class_init (GvcSoundThemeChooserClass *klass);
55
static void gvc_sound_theme_chooser_init (GvcSoundThemeChooser *sound_theme_chooser);
56
static void gvc_sound_theme_chooser_finalize (GObject *object);
58
G_DEFINE_TYPE (GvcSoundThemeChooser, gvc_sound_theme_chooser, GTK_TYPE_VBOX)
60
#define KEY_SOUNDS_SCHEMA "org.gnome.desktop.sound"
61
#define EVENT_SOUNDS_KEY "event-sounds"
62
#define INPUT_SOUNDS_KEY "input-feedback-sounds"
63
#define SOUND_THEME_KEY "theme-name"
65
#define KEY_METACITY_DIR "/apps/metacity/general"
66
#define AUDIO_BELL_KEY KEY_METACITY_DIR "/audible_bell"
68
#define DEFAULT_ALERT_ID "__default"
69
#define CUSTOM_THEME_NAME "__custom"
70
#define NO_SOUNDS_THEME_NAME "__no_sounds"
71
#define DEFAULT_THEME "freedesktop"
90
SOUND_TYPE_DEFAULT_FROM_THEME,
95
#define GVC_SOUND_SOUND (xmlChar *) "sound"
96
#define GVC_SOUND_NAME (xmlChar *) "name"
97
#define GVC_SOUND_FILENAME (xmlChar *) "filename"
99
/* Adapted from yelp-toc-pager.c */
101
xml_get_and_trim_names (xmlNodePtr node)
104
xmlChar *keep_lang = NULL;
106
int j, keep_pri = INT_MAX;
108
const gchar * const * langs = g_get_language_names ();
112
for (cur = node->children; cur; cur = cur->next) {
113
if (! xmlStrcmp (cur->name, GVC_SOUND_NAME)) {
114
xmlChar *cur_lang = NULL;
115
int cur_pri = INT_MAX;
117
cur_lang = xmlNodeGetLang (cur);
120
for (j = 0; langs[j]; j++) {
121
if (g_str_equal (cur_lang, langs[j])) {
127
cur_pri = INT_MAX - 1;
130
if (cur_pri <= keep_pri) {
136
value = xmlNodeGetContent (cur);
138
keep_lang = cur_lang;
147
/* Delete all GVC_SOUND_NAME nodes */
148
cur = node->children;
150
xmlNodePtr this = cur;
152
if (! xmlStrcmp (this->name, GVC_SOUND_NAME)) {
153
xmlUnlinkNode (this);
162
populate_model_from_node (GvcSoundThemeChooser *chooser,
171
name = xml_get_and_trim_names (node);
172
for (child = node->children; child; child = child->next) {
173
if (xmlNodeIsText (child)) {
177
if (xmlStrcmp (child->name, GVC_SOUND_FILENAME) == 0) {
178
filename = xmlNodeGetContent (child);
179
} else if (xmlStrcmp (child->name, GVC_SOUND_NAME) == 0) {
180
/* EH? should have been trimmed */
184
if (filename != NULL && name != NULL) {
185
gtk_list_store_insert_with_values (GTK_LIST_STORE (model),
188
ALERT_IDENTIFIER_COL, filename,
189
ALERT_DISPLAY_COL, name,
190
ALERT_SOUND_TYPE_COL, _("Built-in"),
199
populate_model_from_file (GvcSoundThemeChooser *chooser,
201
const char *filename)
208
exists = g_file_test (filename, G_FILE_TEST_EXISTS);
213
doc = xmlParseFile (filename);
218
root = xmlDocGetRootElement (doc);
220
for (child = root->children; child; child = child->next) {
221
if (xmlNodeIsText (child)) {
224
if (xmlStrcmp (child->name, GVC_SOUND_SOUND) != 0) {
228
populate_model_from_node (chooser, model, child);
235
populate_model_from_dir (GvcSoundThemeChooser *chooser,
242
d = g_dir_open (dirname, 0, NULL);
247
while ((name = g_dir_read_name (d)) != NULL) {
250
if (! g_str_has_suffix (name, ".xml")) {
254
path = g_build_filename (dirname, name, NULL);
255
populate_model_from_file (chooser, model, path);
261
save_alert_sounds (GvcSoundThemeChooser *chooser,
264
const char *sounds[3] = { "bell-terminal", "bell-window-system", NULL };
267
if (strcmp (id, DEFAULT_ALERT_ID) == 0) {
268
delete_old_files (sounds);
269
delete_disabled_files (sounds);
271
delete_old_files (sounds);
272
delete_disabled_files (sounds);
273
add_custom_file (sounds, id);
276
/* And poke the directory so the theme gets updated */
277
path = custom_theme_dir_path (NULL);
278
if (utime (path, NULL) != 0) {
279
g_warning ("Failed to update mtime for directory '%s': %s",
280
path, g_strerror (errno));
289
update_alert_model (GvcSoundThemeChooser *chooser,
295
model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser->priv->treeview));
296
gtk_tree_model_get_iter_first (model, &iter);
300
gtk_tree_model_get (model, &iter,
301
ALERT_IDENTIFIER_COL, &this_id,
304
if (strcmp (this_id, id) == 0) {
305
GtkTreeSelection *selection;
307
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (chooser->priv->treeview));
308
gtk_tree_selection_select_iter (selection, &iter);
312
} while (gtk_tree_model_iter_next (model, &iter));
316
save_theme_name (GvcSoundThemeChooser *chooser,
317
const char *theme_name)
319
/* If the name is empty, use "freedesktop" */
320
if (theme_name == NULL || *theme_name == '\0') {
321
theme_name = DEFAULT_THEME;
324
/* special case for no sounds */
325
if (strcmp (theme_name, NO_SOUNDS_THEME_NAME) == 0) {
326
g_settings_set_boolean (chooser->priv->sound_settings, EVENT_SOUNDS_KEY, FALSE);
329
g_settings_set_boolean (chooser->priv->sound_settings, EVENT_SOUNDS_KEY, TRUE);
332
g_settings_set_string (chooser->priv->sound_settings, SOUND_THEME_KEY, theme_name);
336
load_theme_file (const char *path,
342
file = g_key_file_new ();
343
if (g_key_file_load_from_file (file, path, G_KEY_FILE_KEEP_TRANSLATIONS, NULL) == FALSE) {
344
g_key_file_free (file);
347
/* Don't add hidden themes to the list */
348
hidden = g_key_file_get_boolean (file, "Sound Theme", "Hidden", NULL);
350
/* Save the parent theme, if there's one */
351
if (parent != NULL) {
352
*parent = g_key_file_get_string (file,
359
g_key_file_free (file);
365
load_theme_name (const char *name,
368
const char * const *data_dirs;
369
const char *data_dir;
374
data_dir = g_get_user_data_dir ();
375
path = g_build_filename (data_dir, "sounds", name, "index.theme", NULL);
376
res = load_theme_file (path, parent);
381
data_dirs = g_get_system_data_dirs ();
382
for (i = 0; data_dirs[i] != NULL; i++) {
383
path = g_build_filename (data_dirs[i], "sounds", name, "index.theme", NULL);
384
res = load_theme_file (path, parent);
394
update_alert (GvcSoundThemeChooser *chooser,
395
const char *alert_id)
400
gboolean remove_custom;
402
is_custom = strcmp (chooser->priv->current_theme, CUSTOM_THEME_NAME) == 0;
403
is_default = strcmp (alert_id, DEFAULT_ALERT_ID) == 0;
405
/* So a few possibilities:
406
* 1. Named theme, default alert selected: noop
407
* 2. Named theme, alternate alert selected: create new custom with sound
408
* 3. Custom theme, default alert selected: remove sound and possibly custom
409
* 4. Custom theme, alternate alert selected: update custom sound
412
remove_custom = FALSE;
413
if (! is_custom && is_default) {
414
/* remove custom just in case */
415
remove_custom = TRUE;
416
} else if (! is_custom && ! is_default) {
417
if (chooser->priv->current_parent)
418
create_custom_theme (chooser->priv->current_parent);
420
create_custom_theme (DEFAULT_THEME);
421
save_alert_sounds (chooser, alert_id);
423
} else if (is_custom && is_default) {
424
save_alert_sounds (chooser, alert_id);
425
/* after removing files check if it is empty */
426
if (custom_theme_dir_is_empty ()) {
427
remove_custom = TRUE;
429
} else if (is_custom && ! is_default) {
430
save_alert_sounds (chooser, alert_id);
434
save_theme_name (chooser, CUSTOM_THEME_NAME);
435
} else if (remove_custom) {
436
delete_custom_theme_dir ();
438
save_theme_name (chooser, chooser->priv->current_parent);
442
update_alert_model (chooser, alert_id);
446
play_preview_for_id (GvcSoundThemeChooser *chooser,
449
g_return_if_fail (id != NULL);
451
/* special case: for the default item on custom themes
452
* play the alert for the parent theme */
453
if (strcmp (id, DEFAULT_ALERT_ID) == 0) {
454
if (chooser->priv->current_parent != NULL) {
455
ca_gtk_play_for_widget (GTK_WIDGET (chooser), 0,
456
CA_PROP_APPLICATION_NAME, _("Sound Preferences"),
457
CA_PROP_EVENT_ID, "bell-window-system",
458
CA_PROP_CANBERRA_XDG_THEME_NAME, chooser->priv->current_parent,
459
CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"),
460
CA_PROP_CANBERRA_CACHE_CONTROL, "never",
461
CA_PROP_APPLICATION_ID, "org.gnome.VolumeControl",
462
#ifdef CA_PROP_CANBERRA_ENABLE
463
CA_PROP_CANBERRA_ENABLE, "1",
467
ca_gtk_play_for_widget (GTK_WIDGET (chooser), 0,
468
CA_PROP_APPLICATION_NAME, _("Sound Preferences"),
469
CA_PROP_EVENT_ID, "bell-window-system",
470
CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"),
471
CA_PROP_CANBERRA_CACHE_CONTROL, "never",
472
CA_PROP_APPLICATION_ID, "org.gnome.VolumeControl",
473
#ifdef CA_PROP_CANBERRA_ENABLE
474
CA_PROP_CANBERRA_ENABLE, "1",
479
ca_gtk_play_for_widget (GTK_WIDGET (chooser), 0,
480
CA_PROP_APPLICATION_NAME, _("Sound Preferences"),
481
CA_PROP_MEDIA_FILENAME, id,
482
CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"),
483
CA_PROP_CANBERRA_CACHE_CONTROL, "never",
484
CA_PROP_APPLICATION_ID, "org.gnome.VolumeControl",
485
#ifdef CA_PROP_CANBERRA_ENABLE
486
CA_PROP_CANBERRA_ENABLE, "1",
494
on_treeview_selection_changed (GtkTreeSelection *selection,
495
GvcSoundThemeChooser *chooser)
501
if (chooser->priv->treeview == NULL) {
505
model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser->priv->treeview));
507
if (gtk_tree_selection_get_selected (selection, &model, &iter) == FALSE) {
512
gtk_tree_model_get (model, &iter,
513
ALERT_IDENTIFIER_COL, &id,
519
play_preview_for_id (chooser, id);
520
update_alert (chooser, id);
525
on_treeview_button_pressed (GtkTreeView *treeview,
526
GdkEventButton *event,
527
GvcSoundThemeChooser *chooser)
529
GtkTreeSelection *selection;
532
selection = gtk_tree_view_get_selection (treeview);
533
if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview),
534
event->x, event->y, &path, NULL, NULL, NULL) == FALSE) {
538
if (gtk_tree_selection_path_is_selected (selection, path) == FALSE) {
539
gtk_tree_path_free (path);
542
gtk_tree_path_free (path);
544
on_treeview_selection_changed (selection, chooser);
550
create_alert_treeview (GvcSoundThemeChooser *chooser)
554
GtkCellRenderer *renderer;
555
GtkTreeViewColumn *column;
556
GtkTreeSelection *selection;
558
treeview = gtk_tree_view_new ();
559
gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
560
g_signal_connect (treeview,
561
"button-press-event",
562
G_CALLBACK (on_treeview_button_pressed),
565
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
566
gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
567
g_signal_connect (selection,
569
G_CALLBACK (on_treeview_selection_changed),
572
/* Setup the tree model, 3 columns:
577
store = gtk_list_store_new (ALERT_NUM_COLS,
582
gtk_list_store_insert_with_values (store,
585
ALERT_IDENTIFIER_COL, DEFAULT_ALERT_ID,
586
ALERT_DISPLAY_COL, _("Default"),
587
ALERT_SOUND_TYPE_COL, _("From theme"),
590
populate_model_from_dir (chooser, GTK_TREE_MODEL (store), SOUND_SET_DIR);
592
gtk_tree_view_set_model (GTK_TREE_VIEW (treeview),
593
GTK_TREE_MODEL (store));
595
renderer = gtk_cell_renderer_text_new ();
596
column = gtk_tree_view_column_new_with_attributes (_("Name"),
598
"text", ALERT_DISPLAY_COL,
600
gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
606
get_file_type (const char *sound_name,
609
char *name, *filename;
613
name = g_strdup_printf ("%s.disabled", sound_name);
614
filename = custom_theme_dir_path (name);
617
if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) != FALSE) {
619
return SOUND_TYPE_OFF;
623
/* We only check for .ogg files because those are the
624
* only ones we create */
625
name = g_strdup_printf ("%s.ogg", sound_name);
626
filename = custom_theme_dir_path (name);
629
if (g_file_test (filename, G_FILE_TEST_IS_SYMLINK) != FALSE) {
630
*linked_name = g_file_read_link (filename, NULL);
632
return SOUND_TYPE_CUSTOM;
636
return SOUND_TYPE_BUILTIN;
640
update_alerts_from_theme_name (GvcSoundThemeChooser *chooser,
643
if (strcmp (name, CUSTOM_THEME_NAME) != 0) {
644
/* reset alert to default */
645
update_alert (chooser, DEFAULT_ALERT_ID);
651
sound_type = get_file_type ("bell-terminal", &linkname);
652
g_debug ("Found link: %s", linkname);
653
if (sound_type == SOUND_TYPE_CUSTOM) {
654
update_alert (chooser, linkname);
660
update_theme (GvcSoundThemeChooser *chooser)
662
gboolean events_enabled;
665
events_enabled = g_settings_get_boolean (chooser->priv->sound_settings, EVENT_SOUNDS_KEY);
667
last_theme = chooser->priv->current_theme;
668
if (events_enabled) {
669
chooser->priv->current_theme = g_settings_get_string (chooser->priv->sound_settings, SOUND_THEME_KEY);
671
chooser->priv->current_theme = g_strdup (NO_SOUNDS_THEME_NAME);
674
if (g_strcmp0 (last_theme, chooser->priv->current_theme) != 0) {
675
g_free (chooser->priv->current_parent);
676
if (load_theme_name (chooser->priv->current_theme,
677
&chooser->priv->current_parent) == FALSE) {
678
g_free (chooser->priv->current_theme);
679
chooser->priv->current_theme = g_strdup (DEFAULT_THEME);
680
load_theme_name (DEFAULT_THEME,
681
&chooser->priv->current_parent);
686
gtk_widget_set_sensitive (chooser->priv->selection_box, events_enabled);
688
update_alerts_from_theme_name (chooser, chooser->priv->current_theme);
692
gvc_sound_theme_chooser_constructor (GType type,
693
guint n_construct_properties,
694
GObjectConstructParam *construct_params)
697
GvcSoundThemeChooser *self;
699
object = G_OBJECT_CLASS (gvc_sound_theme_chooser_parent_class)->constructor (type, n_construct_properties, construct_params);
701
self = GVC_SOUND_THEME_CHOOSER (object);
709
gvc_sound_theme_chooser_class_init (GvcSoundThemeChooserClass *klass)
711
GObjectClass *object_class = G_OBJECT_CLASS (klass);
713
object_class->constructor = gvc_sound_theme_chooser_constructor;
714
object_class->finalize = gvc_sound_theme_chooser_finalize;
716
g_type_class_add_private (klass, sizeof (GvcSoundThemeChooserPrivate));
720
on_sound_settings_changed (GSettings *settings,
722
GvcSoundThemeChooser *chooser)
724
if (strcmp (key, EVENT_SOUNDS_KEY) == 0) {
725
update_theme (chooser);
726
} else if (strcmp (key, SOUND_THEME_KEY) == 0) {
727
update_theme (chooser);
728
} else if (strcmp (key, INPUT_SOUNDS_KEY) == 0) {
729
update_theme (chooser);
734
on_key_changed (GConfClient *client,
737
GvcSoundThemeChooser *chooser)
741
key = gconf_entry_get_key (entry);
743
if (! g_str_has_prefix (key, KEY_METACITY_DIR)) {
747
if (strcmp (key, AUDIO_BELL_KEY) == 0) {
748
update_theme (chooser);
753
setup_list_size_constraint (GtkWidget *widget,
759
/* constrain height to be the tree height up to a max */
760
max_height = (gdk_screen_get_height (gtk_widget_get_screen (widget))) / 4;
762
gtk_widget_get_preferred_size (to_size, NULL, &req);
764
gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (widget),
765
MIN (req.height, max_height));
769
gvc_sound_theme_chooser_init (GvcSoundThemeChooser *chooser)
773
GtkWidget *scrolled_window;
774
GtkWidget *alignment;
777
chooser->priv = GVC_SOUND_THEME_CHOOSER_GET_PRIVATE (chooser);
779
chooser->priv->client = gconf_client_get_default ();
780
chooser->priv->sound_settings = g_settings_new (KEY_SOUNDS_SCHEMA);
782
str = g_strdup_printf ("<b>%s</b>", _("C_hoose an alert sound:"));
783
chooser->priv->selection_box = box = gtk_frame_new (str);
785
label = gtk_frame_get_label_widget (GTK_FRAME (box));
786
gtk_label_set_use_underline (GTK_LABEL (label), TRUE);
787
gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
788
gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE);
790
alignment = gtk_alignment_new (0, 0, 1, 1);
791
gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0);
792
gtk_container_add (GTK_CONTAINER (alignment), box);
793
gtk_box_pack_start (GTK_BOX (chooser), alignment, TRUE, TRUE, 6);
795
alignment = gtk_alignment_new (0, 0, 1, 1);
796
gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0);
797
gtk_container_add (GTK_CONTAINER (box), alignment);
799
chooser->priv->treeview = create_alert_treeview (chooser);
800
gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser->priv->treeview);
802
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
803
setup_list_size_constraint (scrolled_window, chooser->priv->treeview);
805
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
807
GTK_POLICY_AUTOMATIC);
808
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
810
gtk_container_add (GTK_CONTAINER (scrolled_window), chooser->priv->treeview);
811
gtk_container_add (GTK_CONTAINER (alignment), scrolled_window);
813
g_signal_connect (G_OBJECT (chooser->priv->sound_settings), "changed",
814
G_CALLBACK (on_sound_settings_changed), chooser);
815
gconf_client_add_dir (chooser->priv->client, KEY_METACITY_DIR,
816
GCONF_CLIENT_PRELOAD_ONELEVEL,
818
chooser->priv->metacity_dir_id = gconf_client_notify_add (chooser->priv->client,
820
(GConfClientNotifyFunc)on_key_changed,
821
chooser, NULL, NULL);
825
gvc_sound_theme_chooser_finalize (GObject *object)
827
GvcSoundThemeChooser *sound_theme_chooser;
829
g_return_if_fail (object != NULL);
830
g_return_if_fail (GVC_IS_SOUND_THEME_CHOOSER (object));
832
sound_theme_chooser = GVC_SOUND_THEME_CHOOSER (object);
834
if (sound_theme_chooser->priv != NULL) {
835
if (sound_theme_chooser->priv->metacity_dir_id > 0) {
836
gconf_client_notify_remove (sound_theme_chooser->priv->client,
837
sound_theme_chooser->priv->metacity_dir_id);
838
sound_theme_chooser->priv->metacity_dir_id = 0;
840
g_object_unref (sound_theme_chooser->priv->client);
841
g_object_unref (sound_theme_chooser->priv->sound_settings);
842
sound_theme_chooser->priv->client = NULL;
845
G_OBJECT_CLASS (gvc_sound_theme_chooser_parent_class)->finalize (object);
849
gvc_sound_theme_chooser_new (void)
852
chooser = g_object_new (GVC_TYPE_SOUND_THEME_CHOOSER,
855
return GTK_WIDGET (chooser);