1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2008 Bastien Nocera <hadess@hadess.net>
4 * Copyright (C) 2008 William Jon McCann
5 * Copyright (C) 2014-2021 MATE Developers
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "config.h"
24
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <unistd.h>
28 #include <utime.h>
29 #include <errno.h>
30
31 #include <glib.h>
32 #include <glib/gi18n.h>
33 #include <glib-object.h>
34 #include <gio/gio.h>
35 #include <gtk/gtk.h>
36 #include <canberra-gtk.h>
37 #include <libxml/tree.h>
38
39 #include "gvc-sound-theme-chooser.h"
40 #include "sound-theme-file-utils.h"
41
42 struct GvcSoundThemeChooserPrivate
43 {
44 GtkWidget *combo_box;
45 GtkWidget *treeview;
46 GtkWidget *theme_box;
47 GtkWidget *selection_box;
48 GtkWidget *click_feedback_button;
49 GSettings *sound_settings;
50 };
51
52 static void gvc_sound_theme_chooser_dispose (GObject *object);
53
54 G_DEFINE_TYPE_WITH_PRIVATE (GvcSoundThemeChooser, gvc_sound_theme_chooser, GTK_TYPE_BOX)
55
56 #define KEY_SOUNDS_SCHEMA "org.mate.sound"
57 #define EVENT_SOUNDS_KEY "event-sounds"
58 #define INPUT_SOUNDS_KEY "input-feedback-sounds"
59 #define SOUND_THEME_KEY "theme-name"
60
61 #define DEFAULT_ALERT_ID "__default"
62 #define CUSTOM_THEME_NAME "__custom"
63 #define NO_SOUNDS_THEME_NAME "__no_sounds"
64
65 enum {
66 THEME_DISPLAY_COL,
67 THEME_IDENTIFIER_COL,
68 THEME_PARENT_ID_COL,
69 THEME_NUM_COLS
70 };
71
72 enum {
73 ALERT_DISPLAY_COL,
74 ALERT_IDENTIFIER_COL,
75 ALERT_SOUND_TYPE_COL,
76 ALERT_ACTIVE_COL,
77 ALERT_NUM_COLS
78 };
79
80 enum {
81 SOUND_TYPE_UNSET,
82 SOUND_TYPE_OFF,
83 SOUND_TYPE_DEFAULT_FROM_THEME,
84 SOUND_TYPE_BUILTIN,
85 SOUND_TYPE_CUSTOM
86 };
87
88 static void
on_combobox_changed(GtkComboBox * widget,GvcSoundThemeChooser * chooser)89 on_combobox_changed (GtkComboBox *widget,
90 GvcSoundThemeChooser *chooser)
91 {
92 GtkTreeIter iter;
93 GtkTreeModel *model;
94 char *theme_name;
95
96 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser->priv->combo_box), &iter) == FALSE) {
97 return;
98 }
99
100 model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser->priv->combo_box));
101 gtk_tree_model_get (model, &iter, THEME_IDENTIFIER_COL, &theme_name, -1);
102
103 g_assert (theme_name != NULL);
104
105 /* It is necessary to update the theme name before any other setting as
106 * the "changed" notification will reload the contents of the widget */
107 g_settings_set_string (chooser->priv->sound_settings, SOUND_THEME_KEY, theme_name);
108
109 /* special case for no sounds */
110 if (strcmp (theme_name, NO_SOUNDS_THEME_NAME) == 0) {
111 g_settings_set_boolean (chooser->priv->sound_settings, EVENT_SOUNDS_KEY, FALSE);
112 return;
113 } else {
114 g_settings_set_boolean (chooser->priv->sound_settings, EVENT_SOUNDS_KEY, TRUE);
115 }
116
117 g_free (theme_name);
118
119 /* FIXME: reset alert model */
120 }
121
122 static char *
load_index_theme_name(const char * index,char ** parent)123 load_index_theme_name (const char *index,
124 char **parent)
125 {
126 GKeyFile *file;
127 char *indexname = NULL;
128 gboolean hidden;
129
130 file = g_key_file_new ();
131 if (g_key_file_load_from_file (file, index, G_KEY_FILE_KEEP_TRANSLATIONS, NULL) == FALSE) {
132 g_key_file_free (file);
133 return NULL;
134 }
135 /* Don't add hidden themes to the list */
136 hidden = g_key_file_get_boolean (file, "Sound Theme", "Hidden", NULL);
137 if (!hidden) {
138 indexname = g_key_file_get_locale_string (file,
139 "Sound Theme",
140 "Name",
141 NULL,
142 NULL);
143
144 /* Save the parent theme, if there's one */
145 if (parent != NULL) {
146 *parent = g_key_file_get_string (file,
147 "Sound Theme",
148 "Inherits",
149 NULL);
150 }
151 }
152
153 g_key_file_free (file);
154 return indexname;
155 }
156
157 static void
sound_theme_in_dir(GHashTable * hash,const char * dir)158 sound_theme_in_dir (GHashTable *hash,
159 const char *dir)
160 {
161 GDir *d;
162 const char *name;
163
164 d = g_dir_open (dir, 0, NULL);
165 if (d == NULL) {
166 return;
167 }
168
169 while ((name = g_dir_read_name (d)) != NULL) {
170 char *dirname, *index, *indexname;
171
172 /* Look for directories */
173 dirname = g_build_filename (dir, name, NULL);
174 if (g_file_test (dirname, G_FILE_TEST_IS_DIR) == FALSE) {
175 g_free (dirname);
176 continue;
177 }
178
179 /* Look for index files */
180 index = g_build_filename (dirname, "index.theme", NULL);
181 g_free (dirname);
182
183 /* Check the name of the theme in the index.theme file */
184 indexname = load_index_theme_name (index, NULL);
185 g_free (index);
186 if (indexname == NULL) {
187 continue;
188 }
189
190 g_hash_table_insert (hash, g_strdup (name), indexname);
191 }
192
193 g_dir_close (d);
194 }
195
196 static void
add_theme_to_store(const char * key,const char * value,GtkListStore * store)197 add_theme_to_store (const char *key,
198 const char *value,
199 GtkListStore *store)
200 {
201 char *parent;
202
203 parent = NULL;
204
205 /* Get the parent, if we're checking the custom theme */
206 if (strcmp (key, CUSTOM_THEME_NAME) == 0) {
207 char *name, *path;
208
209 path = custom_theme_dir_path ("index.theme");
210 name = load_index_theme_name (path, &parent);
211 g_free (name);
212 g_free (path);
213 }
214 gtk_list_store_insert_with_values (store, NULL, G_MAXINT,
215 THEME_DISPLAY_COL, value,
216 THEME_IDENTIFIER_COL, key,
217 THEME_PARENT_ID_COL, parent,
218 -1);
219 g_free (parent);
220 }
221
222 static void
set_combox_for_theme_name(GvcSoundThemeChooser * chooser,const char * name)223 set_combox_for_theme_name (GvcSoundThemeChooser *chooser,
224 const char *name)
225 {
226 GtkTreeIter iter;
227 GtkTreeModel *model;
228 gboolean found;
229
230 /* If the name is empty, use "freedesktop" */
231 if (name == NULL || *name == '\0') {
232 name = "freedesktop";
233 }
234
235 model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser->priv->combo_box));
236
237 if (gtk_tree_model_get_iter_first (model, &iter) == FALSE) {
238 return;
239 }
240
241 do {
242 char *value;
243
244 gtk_tree_model_get (model, &iter, THEME_IDENTIFIER_COL, &value, -1);
245 found = (value != NULL && strcmp (value, name) == 0);
246 g_free (value);
247
248 } while (!found && gtk_tree_model_iter_next (model, &iter));
249
250 /* When we can't find the theme we need to set, try to set the default
251 * one "freedesktop" */
252 if (found) {
253 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (chooser->priv->combo_box), &iter);
254 } else if (strcmp (name, "freedesktop") != 0) {
255 g_debug ("not found, falling back to fdo");
256 set_combox_for_theme_name (chooser, "freedesktop");
257 }
258 }
259
260 static void
set_input_feedback_enabled(GvcSoundThemeChooser * chooser,gboolean enabled)261 set_input_feedback_enabled (GvcSoundThemeChooser *chooser,
262 gboolean enabled)
263 {
264 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chooser->priv->click_feedback_button),
265 enabled);
266 }
267
268 static void
setup_theme_selector(GvcSoundThemeChooser * chooser)269 setup_theme_selector (GvcSoundThemeChooser *chooser)
270 {
271 GHashTable *hash;
272 GtkListStore *store;
273 GtkCellRenderer *renderer;
274 const char * const *data_dirs;
275 const char *data_dir;
276 char *dir;
277 guint i;
278
279 /* Add the theme names and their display name to a hash table,
280 * makes it easy to avoid duplicate themes */
281 hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
282
283 data_dirs = g_get_system_data_dirs ();
284 for (i = 0; data_dirs[i] != NULL; i++) {
285 dir = g_build_filename (data_dirs[i], "sounds", NULL);
286 sound_theme_in_dir (hash, dir);
287 g_free (dir);
288 }
289
290 data_dir = g_get_user_data_dir ();
291 dir = g_build_filename (data_dir, "sounds", NULL);
292 sound_theme_in_dir (hash, dir);
293 g_free (dir);
294
295 /* If there isn't at least one theme, make everything
296 * insensitive, LAME! */
297 if (g_hash_table_size (hash) == 0) {
298 gtk_widget_set_sensitive (GTK_WIDGET (chooser), FALSE);
299 g_warning ("Bad setup, install the freedesktop sound theme");
300 g_hash_table_destroy (hash);
301 return;
302 }
303
304 /* Setup the tree model, 3 columns:
305 * - internal theme name/directory
306 * - display theme name
307 * - the internal id for the parent theme, used for the custom theme */
308 store = gtk_list_store_new (THEME_NUM_COLS,
309 G_TYPE_STRING,
310 G_TYPE_STRING,
311 G_TYPE_STRING);
312
313 /* Add the themes to a combobox */
314 gtk_list_store_insert_with_values (store,
315 NULL,
316 G_MAXINT,
317 THEME_DISPLAY_COL, _("No sounds"),
318 THEME_IDENTIFIER_COL, "__no_sounds",
319 THEME_PARENT_ID_COL, NULL,
320 -1);
321 g_hash_table_foreach (hash, (GHFunc) add_theme_to_store, store);
322 g_hash_table_destroy (hash);
323
324 /* Set the display */
325 gtk_combo_box_set_model (GTK_COMBO_BOX (chooser->priv->combo_box),
326 GTK_TREE_MODEL (store));
327
328 renderer = gtk_cell_renderer_text_new ();
329 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser->priv->combo_box),
330 renderer,
331 TRUE);
332 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (chooser->priv->combo_box),
333 renderer,
334 "text", THEME_DISPLAY_COL,
335 NULL);
336
337 g_signal_connect (G_OBJECT (chooser->priv->combo_box),
338 "changed",
339 G_CALLBACK (on_combobox_changed),
340 chooser);
341 }
342
343 #define GVC_SOUND_SOUND (xmlChar *) "sound"
344 #define GVC_SOUND_NAME (xmlChar *) "name"
345 #define GVC_SOUND_FILENAME (xmlChar *) "filename"
346
347 /* Adapted from yelp-toc-pager.c */
348 static xmlChar *
xml_get_and_trim_names(xmlNodePtr node)349 xml_get_and_trim_names (xmlNodePtr node)
350 {
351 xmlNodePtr cur;
352 xmlChar *keep_lang = NULL;
353 xmlChar *value;
354 int j, keep_pri = INT_MAX;
355
356 const gchar * const * langs = g_get_language_names ();
357
358 value = NULL;
359
360 for (cur = node->children; cur; cur = cur->next) {
361 if (! xmlStrcmp (cur->name, GVC_SOUND_NAME)) {
362 xmlChar *cur_lang = NULL;
363 int cur_pri = INT_MAX;
364
365 cur_lang = xmlNodeGetLang (cur);
366
367 if (cur_lang) {
368 for (j = 0; langs[j]; j++) {
369 if (g_str_equal (cur_lang, langs[j])) {
370 cur_pri = j;
371 break;
372 }
373 }
374 } else {
375 cur_pri = INT_MAX - 1;
376 }
377
378 if (cur_pri <= keep_pri) {
379 if (keep_lang)
380 xmlFree (keep_lang);
381 if (value)
382 xmlFree (value);
383
384 value = xmlNodeGetContent (cur);
385
386 keep_lang = cur_lang;
387 keep_pri = cur_pri;
388 } else {
389 if (cur_lang)
390 xmlFree (cur_lang);
391 }
392 }
393 }
394
395 /* Delete all GVC_SOUND_NAME nodes */
396 cur = node->children;
397 while (cur) {
398 xmlNodePtr this = cur;
399 cur = cur->next;
400 if (! xmlStrcmp (this->name, GVC_SOUND_NAME)) {
401 xmlUnlinkNode (this);
402 xmlFreeNode (this);
403 }
404 }
405
406 return value;
407 }
408
409 static void
populate_model_from_node(GvcSoundThemeChooser * chooser,GtkTreeModel * model,xmlNodePtr node)410 populate_model_from_node (GvcSoundThemeChooser *chooser,
411 GtkTreeModel *model,
412 xmlNodePtr node)
413 {
414 xmlNodePtr child;
415 xmlChar *filename;
416 xmlChar *name;
417
418 filename = NULL;
419 name = xml_get_and_trim_names (node);
420 for (child = node->children; child; child = child->next) {
421 if (xmlNodeIsText (child)) {
422 continue;
423 }
424
425 if (xmlStrcmp (child->name, GVC_SOUND_FILENAME) == 0) {
426 filename = xmlNodeGetContent (child);
427 } else if (xmlStrcmp (child->name, GVC_SOUND_NAME) == 0) {
428 /* EH? should have been trimmed */
429 }
430 }
431
432 if (filename != NULL && name != NULL) {
433 gtk_list_store_insert_with_values (GTK_LIST_STORE (model),
434 NULL,
435 G_MAXINT,
436 ALERT_IDENTIFIER_COL, filename,
437 ALERT_DISPLAY_COL, name,
438 ALERT_SOUND_TYPE_COL, _("Built-in"),
439 ALERT_ACTIVE_COL, FALSE,
440 -1);
441 }
442
443 xmlFree (filename);
444 xmlFree (name);
445 }
446
447 static void
populate_model_from_file(GvcSoundThemeChooser * chooser,GtkTreeModel * model,const char * filename)448 populate_model_from_file (GvcSoundThemeChooser *chooser,
449 GtkTreeModel *model,
450 const char *filename)
451 {
452 xmlDocPtr doc;
453 xmlNodePtr root;
454 xmlNodePtr child;
455 gboolean exists;
456
457 exists = g_file_test (filename, G_FILE_TEST_EXISTS);
458 if (! exists) {
459 return;
460 }
461
462 doc = xmlParseFile (filename);
463 if (doc == NULL) {
464 return;
465 }
466
467 root = xmlDocGetRootElement (doc);
468
469 for (child = root->children; child; child = child->next) {
470 if (xmlNodeIsText (child)) {
471 continue;
472 }
473 if (xmlStrcmp (child->name, GVC_SOUND_SOUND) != 0) {
474 continue;
475 }
476
477 populate_model_from_node (chooser, model, child);
478 }
479
480 xmlFreeDoc (doc);
481 }
482
483 static void
populate_model_from_dir(GvcSoundThemeChooser * chooser,GtkTreeModel * model,const char * dirname)484 populate_model_from_dir (GvcSoundThemeChooser *chooser,
485 GtkTreeModel *model,
486 const char *dirname)
487 {
488 GDir *d;
489 const char *name;
490
491 d = g_dir_open (dirname, 0, NULL);
492 if (d == NULL) {
493 return;
494 }
495
496 while ((name = g_dir_read_name (d)) != NULL) {
497 char *path;
498
499 if (! g_str_has_suffix (name, ".xml")) {
500 continue;
501 }
502
503 path = g_build_filename (dirname, name, NULL);
504 populate_model_from_file (chooser, model, path);
505 g_free (path);
506 }
507
508 g_dir_close (d);
509 }
510
511 static gboolean
save_alert_sounds(GvcSoundThemeChooser * chooser,const char * id)512 save_alert_sounds (GvcSoundThemeChooser *chooser,
513 const char *id)
514 {
515 const char *sounds[3] = { "bell-terminal", "bell-window-system", NULL };
516 char *path;
517
518 if (strcmp (id, DEFAULT_ALERT_ID) == 0) {
519 delete_old_files (sounds);
520 delete_disabled_files (sounds);
521 } else {
522 delete_old_files (sounds);
523 delete_disabled_files (sounds);
524 add_custom_file (sounds, id);
525 }
526
527 /* And poke the directory so the theme gets updated */
528 path = custom_theme_dir_path (NULL);
529 if (utime (path, NULL) != 0) {
530 g_warning ("Failed to update mtime for directory '%s': %s",
531 path, g_strerror (errno));
532 }
533 g_free (path);
534
535 return FALSE;
536 }
537
538
539 static void
update_alert_model(GvcSoundThemeChooser * chooser,const char * id)540 update_alert_model (GvcSoundThemeChooser *chooser,
541 const char *id)
542 {
543 GtkTreeModel *model;
544 GtkTreeIter iter;
545
546 model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser->priv->treeview));
547 gtk_tree_model_get_iter_first (model, &iter);
548 do {
549 gboolean toggled;
550 char *this_id;
551
552 gtk_tree_model_get (model, &iter,
553 ALERT_IDENTIFIER_COL, &this_id,
554 -1);
555
556 if (strcmp (this_id, id) == 0) {
557 toggled = TRUE;
558 } else {
559 toggled = FALSE;
560 }
561 g_free (this_id);
562
563 gtk_list_store_set (GTK_LIST_STORE (model),
564 &iter,
565 ALERT_ACTIVE_COL, toggled,
566 -1);
567 } while (gtk_tree_model_iter_next (model, &iter));
568 }
569
570 static void
update_alert(GvcSoundThemeChooser * chooser,const char * alert_id)571 update_alert (GvcSoundThemeChooser *chooser,
572 const char *alert_id)
573 {
574 GtkTreeModel *theme_model;
575 GtkTreeIter iter;
576 char *theme;
577 char *parent;
578 gboolean is_custom;
579 gboolean is_default;
580 gboolean add_custom;
581 gboolean remove_custom;
582
583 theme_model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser->priv->combo_box));
584 /* Get the current theme's name, and set the parent */
585 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser->priv->combo_box), &iter) == FALSE) {
586 return;
587 }
588
589 gtk_tree_model_get (theme_model, &iter,
590 THEME_IDENTIFIER_COL, &theme,
591 THEME_IDENTIFIER_COL, &parent,
592 -1);
593 is_custom = strcmp (theme, CUSTOM_THEME_NAME) == 0;
594 is_default = strcmp (alert_id, DEFAULT_ALERT_ID) == 0;
595
596 /* So a few possibilities:
597 * 1. Named theme, default alert selected: noop
598 * 2. Named theme, alternate alert selected: create new custom with sound
599 * 3. Custom theme, default alert selected: remove sound and possibly custom
600 * 4. Custom theme, alternate alert selected: update custom sound
601 */
602 add_custom = FALSE;
603 remove_custom = FALSE;
604 if (! is_custom && is_default) {
605 /* remove custom just in case */
606 remove_custom = TRUE;
607 } else if (! is_custom && ! is_default) {
608 create_custom_theme (parent);
609 save_alert_sounds (chooser, alert_id);
610 add_custom = TRUE;
611 } else if (is_custom && is_default) {
612 save_alert_sounds (chooser, alert_id);
613 /* after removing files check if it is empty */
614 if (custom_theme_dir_is_empty ()) {
615 remove_custom = TRUE;
616 }
617 } else if (is_custom && ! is_default) {
618 save_alert_sounds (chooser, alert_id);
619 }
620
621 if (add_custom) {
622 gtk_list_store_insert_with_values (GTK_LIST_STORE (theme_model),
623 NULL,
624 G_MAXINT,
625 THEME_DISPLAY_COL, _("Custom"),
626 THEME_IDENTIFIER_COL, CUSTOM_THEME_NAME,
627 THEME_PARENT_ID_COL, theme,
628 -1);
629 set_combox_for_theme_name (chooser, CUSTOM_THEME_NAME);
630 } else if (remove_custom) {
631 gtk_tree_model_get_iter_first (theme_model, &iter);
632 do {
633 char *this_parent;
634
635 gtk_tree_model_get (theme_model, &iter,
636 THEME_PARENT_ID_COL, &this_parent,
637 -1);
638 if (this_parent != NULL && strcmp (this_parent, CUSTOM_THEME_NAME) != 0) {
639 g_free (this_parent);
640 gtk_list_store_remove (GTK_LIST_STORE (theme_model), &iter);
641 break;
642 }
643 g_free (this_parent);
644 } while (gtk_tree_model_iter_next (theme_model, &iter));
645
646 delete_custom_theme_dir ();
647
648 set_combox_for_theme_name (chooser, parent);
649 }
650
651 update_alert_model (chooser, alert_id);
652
653 g_free (theme);
654 g_free (parent);
655 }
656
657 static void
on_alert_toggled(GtkCellRendererToggle * renderer,char * path_str,GvcSoundThemeChooser * chooser)658 on_alert_toggled (GtkCellRendererToggle *renderer,
659 char *path_str,
660 GvcSoundThemeChooser *chooser)
661 {
662 GtkTreeModel *model;
663 GtkTreeIter iter;
664 GtkTreePath *path;
665 gboolean toggled;
666 char *id;
667
668 model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser->priv->treeview));
669
670 path = gtk_tree_path_new_from_string (path_str);
671 gtk_tree_model_get_iter (model, &iter, path);
672 gtk_tree_path_free (path);
673
674 id = NULL;
675 gtk_tree_model_get (model, &iter,
676 ALERT_IDENTIFIER_COL, &id,
677 ALERT_ACTIVE_COL, &toggled,
678 -1);
679
680 toggled ^= 1;
681 if (toggled) {
682 update_alert (chooser, id);
683 }
684
685 g_free (id);
686 }
687
688 static void
play_preview_for_path(GvcSoundThemeChooser * chooser,GtkTreePath * path)689 play_preview_for_path (GvcSoundThemeChooser *chooser, GtkTreePath *path)
690 {
691 GtkTreeModel *model;
692 GtkTreeIter iter;
693 GtkTreeIter theme_iter;
694 gchar *id = NULL;
695 gchar *parent_theme = NULL;
696
697 model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser->priv->treeview));
698 if (gtk_tree_model_get_iter (model, &iter, path) == FALSE)
699 return;
700
701 gtk_tree_model_get (model, &iter,
702 ALERT_IDENTIFIER_COL, &id,
703 -1);
704 if (id == NULL)
705 return;
706
707 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser->priv->combo_box), &theme_iter)) {
708 GtkTreeModel *theme_model;
709 gchar *theme_id = NULL;
710 gchar *parent_id = NULL;
711
712 theme_model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser->priv->combo_box));
713
714 gtk_tree_model_get (theme_model, &theme_iter,
715 THEME_IDENTIFIER_COL, &theme_id,
716 THEME_PARENT_ID_COL, &parent_id, -1);
717 if (theme_id && strcmp (theme_id, CUSTOM_THEME_NAME) == 0)
718 parent_theme = g_strdup (parent_id);
719
720 g_free (theme_id);
721 g_free (parent_id);
722 }
723
724 /* special case: for the default item on custom themes
725 * play the alert for the parent theme */
726 if (strcmp (id, DEFAULT_ALERT_ID) == 0) {
727 if (parent_theme != NULL) {
728 ca_gtk_play_for_widget (GTK_WIDGET (chooser), 0,
729 CA_PROP_APPLICATION_NAME, _("Sound Preferences"),
730 CA_PROP_EVENT_ID, "bell-window-system",
731 CA_PROP_CANBERRA_XDG_THEME_NAME, parent_theme,
732 CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"),
733 CA_PROP_CANBERRA_CACHE_CONTROL, "never",
734 CA_PROP_APPLICATION_ID, "org.mate.VolumeControl",
735 #ifdef CA_PROP_CANBERRA_ENABLE
736 CA_PROP_CANBERRA_ENABLE, "1",
737 #endif
738 NULL);
739 } else {
740 ca_gtk_play_for_widget (GTK_WIDGET (chooser), 0,
741 CA_PROP_APPLICATION_NAME, _("Sound Preferences"),
742 CA_PROP_EVENT_ID, "bell-window-system",
743 CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"),
744 CA_PROP_CANBERRA_CACHE_CONTROL, "never",
745 CA_PROP_APPLICATION_ID, "org.mate.VolumeControl",
746 #ifdef CA_PROP_CANBERRA_ENABLE
747 CA_PROP_CANBERRA_ENABLE, "1",
748 #endif
749 NULL);
750 }
751 } else {
752 ca_gtk_play_for_widget (GTK_WIDGET (chooser), 0,
753 CA_PROP_APPLICATION_NAME, _("Sound Preferences"),
754 CA_PROP_MEDIA_FILENAME, id,
755 CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"),
756 CA_PROP_CANBERRA_CACHE_CONTROL, "never",
757 CA_PROP_APPLICATION_ID, "org.mate.VolumeControl",
758 #ifdef CA_PROP_CANBERRA_ENABLE
759 CA_PROP_CANBERRA_ENABLE, "1",
760 #endif
761 NULL);
762
763 }
764 g_free (parent_theme);
765 g_free (id);
766 }
767
768 static void
on_treeview_row_activated(GtkTreeView * treeview,GtkTreePath * path,GtkTreeViewColumn * column,GvcSoundThemeChooser * chooser)769 on_treeview_row_activated (GtkTreeView *treeview,
770 GtkTreePath *path,
771 GtkTreeViewColumn *column,
772 GvcSoundThemeChooser *chooser)
773 {
774 play_preview_for_path (chooser, path);
775 }
776
777 static void
on_treeview_selection_changed(GtkTreeSelection * selection,GvcSoundThemeChooser * chooser)778 on_treeview_selection_changed (GtkTreeSelection *selection,
779 GvcSoundThemeChooser *chooser)
780 {
781 GList *paths;
782 GtkTreeModel *model;
783 GtkTreePath *path;
784
785 if (chooser->priv->treeview == NULL)
786 return;
787
788 model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser->priv->treeview));
789
790 paths = gtk_tree_selection_get_selected_rows (selection, &model);
791 if (paths == NULL)
792 return;
793
794 path = paths->data;
795 play_preview_for_path (chooser, path);
796
797 g_list_foreach (paths, (GFunc)gtk_tree_path_free, NULL);
798 g_list_free (paths);
799 }
800
801 static GtkWidget *
create_alert_treeview(GvcSoundThemeChooser * chooser)802 create_alert_treeview (GvcSoundThemeChooser *chooser)
803 {
804 GtkListStore *store;
805 GtkWidget *treeview;
806 GtkCellRenderer *renderer;
807 GtkTreeViewColumn *column;
808 GtkTreeSelection *selection;
809
810 treeview = gtk_tree_view_new ();
811
812 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
813 g_signal_connect (G_OBJECT (treeview),
814 "row-activated",
815 G_CALLBACK (on_treeview_row_activated),
816 chooser);
817
818 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
819
820 gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
821 g_signal_connect (G_OBJECT (selection),
822 "changed",
823 G_CALLBACK (on_treeview_selection_changed),
824 chooser);
825
826 /* Setup the tree model, 3 columns:
827 * - display name
828 * - sound id
829 * - sound type
830 */
831 store = gtk_list_store_new (ALERT_NUM_COLS,
832 G_TYPE_STRING,
833 G_TYPE_STRING,
834 G_TYPE_STRING,
835 G_TYPE_BOOLEAN);
836
837 gtk_list_store_insert_with_values (store,
838 NULL,
839 G_MAXINT,
840 ALERT_IDENTIFIER_COL, DEFAULT_ALERT_ID,
841 ALERT_DISPLAY_COL, _("Default"),
842 ALERT_SOUND_TYPE_COL, _("From theme"),
843 ALERT_ACTIVE_COL, TRUE,
844 -1);
845
846 populate_model_from_dir (chooser, GTK_TREE_MODEL (store), SOUND_SET_DIR);
847
848 gtk_tree_view_set_model (GTK_TREE_VIEW (treeview),
849 GTK_TREE_MODEL (store));
850
851 renderer = gtk_cell_renderer_toggle_new ();
852 gtk_cell_renderer_toggle_set_radio (GTK_CELL_RENDERER_TOGGLE (renderer), TRUE);
853
854 column = gtk_tree_view_column_new_with_attributes (NULL,
855 renderer,
856 "active", ALERT_ACTIVE_COL,
857 NULL);
858 gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
859 g_signal_connect (renderer,
860 "toggled",
861 G_CALLBACK (on_alert_toggled),
862 chooser);
863
864 renderer = gtk_cell_renderer_text_new ();
865 column = gtk_tree_view_column_new_with_attributes (_("Name"),
866 renderer,
867 "text", ALERT_DISPLAY_COL,
868 NULL);
869 gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
870
871 renderer = gtk_cell_renderer_text_new ();
872 column = gtk_tree_view_column_new_with_attributes (_("Type"),
873 renderer,
874 "text", ALERT_SOUND_TYPE_COL,
875 NULL);
876
877 gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
878
879 return treeview;
880 }
881
882 static int
get_file_type(const char * sound_name,char ** linked_name)883 get_file_type (const char *sound_name,
884 char **linked_name)
885 {
886 char *name, *filename;
887
888 *linked_name = NULL;
889
890 name = g_strdup_printf ("%s.disabled", sound_name);
891 filename = custom_theme_dir_path (name);
892 g_free (name);
893
894 if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) != FALSE) {
895 g_free (filename);
896 return SOUND_TYPE_OFF;
897 }
898 g_free (filename);
899
900 /* We only check for .ogg files because those are the
901 * only ones we create */
902 name = g_strdup_printf ("%s.ogg", sound_name);
903 filename = custom_theme_dir_path (name);
904 g_free (name);
905
906 if (g_file_test (filename, G_FILE_TEST_IS_SYMLINK) != FALSE) {
907 *linked_name = g_file_read_link (filename, NULL);
908 g_free (filename);
909 return SOUND_TYPE_CUSTOM;
910 }
911 g_free (filename);
912
913 return SOUND_TYPE_BUILTIN;
914 }
915
916 static void
update_alerts_from_theme_name(GvcSoundThemeChooser * chooser,const gchar * name)917 update_alerts_from_theme_name (GvcSoundThemeChooser *chooser,
918 const gchar *name)
919 {
920 if (strcmp (name, CUSTOM_THEME_NAME) != 0) {
921 /* reset alert to default */
922 update_alert (chooser, DEFAULT_ALERT_ID);
923 } else {
924 int sound_type;
925 char *linkname;
926
927 linkname = NULL;
928 sound_type = get_file_type ("bell-terminal", &linkname);
929 g_debug ("Found link: %s", linkname);
930 if (sound_type == SOUND_TYPE_CUSTOM) {
931 update_alert (chooser, linkname);
932 }
933 }
934 }
935
936 static void
update_theme(GvcSoundThemeChooser * chooser)937 update_theme (GvcSoundThemeChooser *chooser)
938 {
939 char *theme_name;
940 gboolean events_enabled;
941 gboolean feedback_enabled;
942
943 feedback_enabled = g_settings_get_boolean (chooser->priv->sound_settings, INPUT_SOUNDS_KEY);
944 set_input_feedback_enabled (chooser, feedback_enabled);
945
946 events_enabled = g_settings_get_boolean (chooser->priv->sound_settings, EVENT_SOUNDS_KEY);
947 if (events_enabled) {
948 theme_name = g_settings_get_string (chooser->priv->sound_settings, SOUND_THEME_KEY);
949 } else {
950 theme_name = g_strdup (NO_SOUNDS_THEME_NAME);
951 }
952
953 gtk_widget_set_sensitive (chooser->priv->selection_box, events_enabled);
954 gtk_widget_set_sensitive (chooser->priv->click_feedback_button, events_enabled);
955
956 set_combox_for_theme_name (chooser, theme_name);
957
958 update_alerts_from_theme_name (chooser, theme_name);
959
960 g_free (theme_name);
961 }
962
963 static void
gvc_sound_theme_chooser_class_init(GvcSoundThemeChooserClass * klass)964 gvc_sound_theme_chooser_class_init (GvcSoundThemeChooserClass *klass)
965 {
966 GObjectClass *object_class = G_OBJECT_CLASS (klass);
967
968 object_class->dispose = gvc_sound_theme_chooser_dispose;
969 }
970
971 static void
on_click_feedback_toggled(GtkToggleButton * button,GvcSoundThemeChooser * chooser)972 on_click_feedback_toggled (GtkToggleButton *button,
973 GvcSoundThemeChooser *chooser)
974 {
975 gboolean enabled;
976
977 enabled = gtk_toggle_button_get_active (button);
978
979 g_settings_set_boolean (chooser->priv->sound_settings, INPUT_SOUNDS_KEY, enabled);
980 }
981
982 static void
on_key_changed(GSettings * settings,gchar * key,GvcSoundThemeChooser * chooser)983 on_key_changed (GSettings *settings,
984 gchar *key,
985 GvcSoundThemeChooser *chooser)
986 {
987 if (!strcmp (key, EVENT_SOUNDS_KEY) ||
988 !strcmp (key, SOUND_THEME_KEY) ||
989 !strcmp (key, INPUT_SOUNDS_KEY))
990 update_theme (chooser);
991 }
992
993 static void
setup_list_size_constraint(GtkWidget * widget,GtkWidget * to_size)994 setup_list_size_constraint (GtkWidget *widget,
995 GtkWidget *to_size)
996 {
997 GtkRequisition req;
998 gint sc_height;
999 int max_height;
1000
1001 /* Constrain height to be the tree height up to a max */
1002 gdk_window_get_geometry (gdk_screen_get_root_window (gtk_widget_get_screen (widget)),
1003 NULL, NULL, NULL, &sc_height);
1004
1005 max_height = sc_height / 4;
1006
1007 // XXX this doesn't work
1008 gtk_widget_get_preferred_size (to_size, NULL, &req);
1009
1010 gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (widget),
1011 MIN (req.height, max_height));
1012 }
1013
1014 static void
gvc_sound_theme_chooser_init(GvcSoundThemeChooser * chooser)1015 gvc_sound_theme_chooser_init (GvcSoundThemeChooser *chooser)
1016 {
1017 GtkWidget *box;
1018 GtkWidget *label;
1019 GtkWidget *scrolled_window;
1020 gchar *str;
1021
1022 chooser->priv = gvc_sound_theme_chooser_get_instance_private (chooser);
1023
1024 chooser->priv->theme_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1025
1026 gtk_box_pack_start (GTK_BOX (chooser),
1027 chooser->priv->theme_box, FALSE, FALSE, 0);
1028
1029 label = gtk_label_new_with_mnemonic (_("Sound _theme:"));
1030 gtk_box_pack_start (GTK_BOX (chooser->priv->theme_box), label, FALSE, FALSE, 0);
1031 chooser->priv->combo_box = gtk_combo_box_new ();
1032 gtk_box_pack_start (GTK_BOX (chooser->priv->theme_box), chooser->priv->combo_box, FALSE, FALSE, 6);
1033 gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser->priv->combo_box);
1034
1035 chooser->priv->sound_settings = g_settings_new (KEY_SOUNDS_SCHEMA);
1036
1037 g_signal_connect (G_OBJECT (chooser->priv->sound_settings),
1038 "changed",
1039 G_CALLBACK (on_key_changed),
1040 chooser);
1041
1042 str = g_strdup_printf ("<b>%s</b>", _("C_hoose an alert sound:"));
1043 chooser->priv->selection_box = box = gtk_frame_new (str);
1044 g_free (str);
1045
1046 label = gtk_frame_get_label_widget (GTK_FRAME (box));
1047 gtk_label_set_use_underline (GTK_LABEL (label), TRUE);
1048 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1049 gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE);
1050
1051 gtk_box_pack_start (GTK_BOX (chooser), box, TRUE, TRUE, 6);
1052
1053 chooser->priv->treeview = create_alert_treeview (chooser);
1054 gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser->priv->treeview);
1055
1056 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1057 gtk_widget_set_hexpand (scrolled_window, TRUE);
1058 gtk_widget_set_vexpand (scrolled_window, TRUE);
1059 gtk_widget_set_margin_top (scrolled_window, 6);
1060
1061 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
1062 GTK_POLICY_NEVER,
1063 GTK_POLICY_AUTOMATIC);
1064 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
1065 GTK_SHADOW_IN);
1066
1067 gtk_container_add (GTK_CONTAINER (scrolled_window), chooser->priv->treeview);
1068 gtk_container_add (GTK_CONTAINER (box), scrolled_window);
1069
1070 chooser->priv->click_feedback_button = gtk_check_button_new_with_mnemonic (_("Enable _window and button sounds"));
1071 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chooser->priv->click_feedback_button),
1072 g_settings_get_boolean (chooser->priv->sound_settings, INPUT_SOUNDS_KEY));
1073
1074 gtk_box_pack_start (GTK_BOX (chooser),
1075 chooser->priv->click_feedback_button,
1076 FALSE, FALSE, 0);
1077
1078 g_signal_connect (G_OBJECT (chooser->priv->click_feedback_button),
1079 "toggled",
1080 G_CALLBACK (on_click_feedback_toggled),
1081 chooser);
1082
1083 setup_theme_selector (chooser);
1084 update_theme (chooser);
1085
1086 setup_list_size_constraint (scrolled_window, chooser->priv->treeview);
1087 }
1088
1089 static void
gvc_sound_theme_chooser_dispose(GObject * object)1090 gvc_sound_theme_chooser_dispose (GObject *object)
1091 {
1092 GvcSoundThemeChooser *chooser;
1093
1094 chooser = GVC_SOUND_THEME_CHOOSER (object);
1095
1096 g_clear_object (&chooser->priv->sound_settings);
1097
1098 G_OBJECT_CLASS (gvc_sound_theme_chooser_parent_class)->dispose (object);
1099 }
1100
1101 GtkWidget *
gvc_sound_theme_chooser_new(void)1102 gvc_sound_theme_chooser_new (void)
1103 {
1104 return g_object_new (GVC_TYPE_SOUND_THEME_CHOOSER,
1105 "spacing", 6,
1106 "orientation", GTK_ORIENTATION_VERTICAL,
1107 NULL);
1108 }
1109