GCC Code Coverage Report


Directory: ./
File: panels/sound/cc-alert-chooser-window.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 187 0.0%
Functions: 0 18 0.0%
Branches: 0 93 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (C) 2018 Canonical Ltd.
3 * Copyright (C) 2023 Marco Melorio
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <glib/gi18n.h>
20 #include <gsound.h>
21
22 #include "config.h"
23 #include "cc-alert-chooser-window.h"
24
25 #define KEY_SOUNDS_SCHEMA "org.gnome.desktop.sound"
26
27 struct _CcAlertChooserWindow
28 {
29 AdwDialog parent_instance;
30
31 GtkCheckButton *none_button;
32 GtkCheckButton *click_button;
33 GtkCheckButton *string_button;
34 GtkCheckButton *swing_button;
35 GtkCheckButton *hum_button;
36
37 GSoundContext *context;
38 GSettings *sound_settings;
39 };
40
41 G_DEFINE_TYPE (CcAlertChooserWindow, cc_alert_chooser_window, ADW_TYPE_DIALOG)
42
43 #define CUSTOM_THEME_NAME "__custom"
44
45 static gchar *
46 get_theme_dir (void)
47 {
48 return g_build_filename (g_get_user_data_dir (), "sounds", CUSTOM_THEME_NAME, NULL);
49 }
50
51 static gchar *
52 get_sound_path (const gchar *name)
53 {
54 g_autofree gchar *filename = NULL;
55
56 filename = g_strdup_printf ("%s.ogg", name);
57 return g_build_filename (SOUND_DATA_DIR, "gnome", "default", "alerts", filename, NULL);
58 }
59
60 static gchar *
61 get_alert_name (void)
62 {
63 g_autofree gchar *dir = NULL;
64 g_autofree gchar *path = NULL;
65 g_autoptr(GFile) file = NULL;
66 g_autoptr(GFileInfo) info = NULL;
67 const gchar *target;
68 g_autofree gchar *basename = NULL;
69 g_autoptr(GError) error = NULL;
70
71 dir = get_theme_dir ();
72 path = g_build_filename (dir, "bell-terminal.ogg", NULL);
73 file = g_file_new_for_path (path);
74
75 info = g_file_query_info (file,
76 G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
77 G_FILE_QUERY_INFO_NONE,
78 NULL,
79 &error);
80 if (info == NULL)
81 {
82 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
83 g_warning ("Failed to get sound theme symlink %s: %s", path, error->message);
84 return NULL;
85 }
86 target = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET);
87 if (target == NULL)
88 return NULL;
89
90 basename = g_path_get_basename (target);
91 if (g_str_has_suffix (basename, ".ogg"))
92 basename[strlen (basename) - 4] = '\0';
93
94 return g_steal_pointer (&basename);
95 }
96
97 static void
98 set_sound_symlink (const gchar *alert_name,
99 const gchar *name)
100 {
101 g_autofree gchar *dir = NULL;
102 g_autofree gchar *source_filename = NULL;
103 g_autofree gchar *source_path = NULL;
104 g_autofree gchar *target_path = NULL;
105 g_autoptr(GFile) file = NULL;
106 g_autoptr(GError) error = NULL;
107
108 dir = get_theme_dir ();
109 source_filename = g_strdup_printf ("%s.ogg", alert_name);
110 source_path = g_build_filename (dir, source_filename, NULL);
111 target_path = get_sound_path (name);
112
113 file = g_file_new_for_path (source_path);
114 if (!g_file_delete (file, NULL, &error))
115 {
116 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
117 g_warning ("Failed to remove existing sound symbolic link %s: %s", source_path, error->message);
118 }
119 if (!g_file_make_symbolic_link (file, target_path, NULL, &error))
120 g_warning ("Failed to make sound theme symbolic link %s->%s: %s", source_path, target_path, error->message);
121 }
122
123 static void
124 update_dir_mtime (const char *dir_path)
125 {
126 g_autoptr(GFile) dir = NULL;
127 g_autoptr(GDateTime) now = NULL;
128 g_autoptr(GError) mtime_error = NULL;
129
130 now = g_date_time_new_now_utc ();
131 dir = g_file_new_for_path (dir_path);
132 if (!g_file_set_attribute_uint64 (dir,
133 G_FILE_ATTRIBUTE_TIME_MODIFIED,
134 g_date_time_to_unix (now),
135 G_FILE_QUERY_INFO_NONE,
136 NULL,
137 &mtime_error))
138 {
139 g_warning ("Failed to update directory modification time for %s: %s",
140 dir_path, mtime_error->message);
141 }
142 }
143
144 static void
145 set_custom_theme (CcAlertChooserWindow *self,
146 const gchar *name)
147 {
148 g_autofree gchar *dir_path = NULL;
149 g_autofree gchar *theme_path = NULL;
150 g_autofree gchar *sounds_path = NULL;
151 g_autoptr(GKeyFile) theme_file = NULL;
152 g_autoptr(GVariant) default_theme = NULL;
153 g_autoptr(GError) load_error = NULL;
154 g_autoptr(GError) save_error = NULL;
155
156 dir_path = get_theme_dir ();
157 g_mkdir_with_parents (dir_path, USER_DIR_MODE);
158
159 theme_path = g_build_filename (dir_path, "index.theme", NULL);
160
161 default_theme = g_settings_get_default_value (self->sound_settings, "theme-name");
162
163 theme_file = g_key_file_new ();
164 if (!g_key_file_load_from_file (theme_file, theme_path, G_KEY_FILE_KEEP_COMMENTS, &load_error))
165 {
166 if (!g_error_matches (load_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
167 g_printerr ("Failed to load theme file %s: %s", theme_path, load_error->message);
168 }
169 g_key_file_set_string (theme_file, "Sound Theme", "Name", _("Custom"));
170 if (default_theme != NULL)
171 g_key_file_set_string (theme_file, "Sound Theme", "Inherits", g_variant_get_string (default_theme, NULL));
172 g_key_file_set_string (theme_file, "Sound Theme", "Directories", ".");
173
174 if (!g_key_file_save_to_file (theme_file, theme_path, &save_error))
175 {
176 g_warning ("Failed to save theme file %s: %s", theme_path, save_error->message);
177 }
178
179 set_sound_symlink ("bell-terminal", name);
180 set_sound_symlink ("bell-window-system", name);
181
182 /* Ensure canberra's event-sound-cache will get updated when g-s-d
183 * clears the cached samples.
184 */
185 sounds_path = g_build_filename (g_get_user_data_dir (), "sounds", NULL);
186 update_dir_mtime (sounds_path);
187
188 /* Ensure the g-s-d sound plugin which does non-recursive monitoring
189 * notices the change even if the theme directory already existed.
190 */
191 update_dir_mtime (dir_path);
192
193 g_settings_set_boolean (self->sound_settings, "event-sounds", TRUE);
194 g_settings_set_string (self->sound_settings, "theme-name", CUSTOM_THEME_NAME);
195 }
196
197 static void
198 play_sound (CcAlertChooserWindow *self,
199 const gchar *name)
200 {
201 g_autofree gchar *path = NULL;
202 g_autoptr(GError) error = NULL;
203
204 path = get_sound_path (name);
205 if (!gsound_context_play_simple (self->context, NULL, &error,
206 GSOUND_ATTR_MEDIA_FILENAME, path,
207 NULL))
208 {
209 g_warning ("Failed to play alert sound %s: %s", path, error->message);
210 }
211 }
212
213 static void
214 activate_cb (CcAlertChooserWindow *self)
215 {
216 if (gtk_check_button_get_active (self->click_button))
217 play_sound (self, "click");
218 else if (gtk_check_button_get_active (self->string_button))
219 play_sound (self, "string");
220 else if (gtk_check_button_get_active (self->swing_button))
221 play_sound (self, "swing");
222 else if (gtk_check_button_get_active (self->hum_button))
223 play_sound (self, "hum");
224 }
225
226 static void
227 toggled_cb (CcAlertChooserWindow *self)
228 {
229 if (gtk_check_button_get_active (self->none_button))
230 g_settings_set_boolean (self->sound_settings, "event-sounds", FALSE);
231 else if (gtk_check_button_get_active (self->click_button))
232 set_custom_theme (self, "click");
233 else if (gtk_check_button_get_active (self->string_button))
234 set_custom_theme (self, "string");
235 else if (gtk_check_button_get_active (self->swing_button))
236 set_custom_theme (self, "swing");
237 else if (gtk_check_button_get_active (self->hum_button))
238 set_custom_theme (self, "hum");
239 }
240
241 static void
242 set_button_active (CcAlertChooserWindow *self,
243 GtkCheckButton *button,
244 gboolean active)
245 {
246 g_signal_handlers_block_by_func (button, toggled_cb, self);
247 gtk_check_button_set_active (button, active);
248 g_signal_handlers_unblock_by_func (button, toggled_cb, self);
249 }
250
251 static void
252 cc_alert_chooser_window_dispose (GObject *object)
253 {
254 CcAlertChooserWindow *self = CC_ALERT_CHOOSER_WINDOW (object);
255
256 g_clear_object (&self->context);
257 g_clear_object (&self->sound_settings);
258
259 G_OBJECT_CLASS (cc_alert_chooser_window_parent_class)->dispose (object);
260 }
261
262 void
263 cc_alert_chooser_window_class_init (CcAlertChooserWindowClass *klass)
264 {
265 GObjectClass *object_class = G_OBJECT_CLASS (klass);
266 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
267
268 object_class->dispose = cc_alert_chooser_window_dispose;
269
270 gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/sound/cc-alert-chooser-window.ui");
271
272 gtk_widget_class_bind_template_child (widget_class, CcAlertChooserWindow, none_button);
273 gtk_widget_class_bind_template_child (widget_class, CcAlertChooserWindow, click_button);
274 gtk_widget_class_bind_template_child (widget_class, CcAlertChooserWindow, string_button);
275 gtk_widget_class_bind_template_child (widget_class, CcAlertChooserWindow, swing_button);
276 gtk_widget_class_bind_template_child (widget_class, CcAlertChooserWindow, hum_button);
277
278 gtk_widget_class_bind_template_callback (widget_class, activate_cb);
279 gtk_widget_class_bind_template_callback (widget_class, toggled_cb);
280 }
281
282 void
283 cc_alert_chooser_window_init (CcAlertChooserWindow *self)
284 {
285 g_autofree gchar *alert_name = NULL;
286 g_autoptr(GError) error = NULL;
287
288 gtk_widget_init_template (GTK_WIDGET (self));
289
290 self->context = gsound_context_new (NULL, &error);
291 if (self->context == NULL)
292 g_error ("Failed to make sound context: %s", error->message);
293
294 self->sound_settings = g_settings_new (KEY_SOUNDS_SCHEMA);
295
296 alert_name = get_alert_name ();
297
298 /* If user has selected an old sound alert, migrate them to click. */
299 if (g_strcmp0 (alert_name, "click") != 0 &&
300 g_strcmp0 (alert_name, "hum") != 0 &&
301 g_strcmp0 (alert_name, "string") != 0 &&
302 g_strcmp0 (alert_name, "swing") != 0)
303 {
304 set_custom_theme (self, "click");
305 g_free (alert_name);
306 alert_name = g_strdup ("click");
307 }
308
309 if (!g_settings_get_boolean (self->sound_settings, "event-sounds"))
310 set_button_active (self, self->none_button, TRUE);
311 else if (g_strcmp0 (alert_name, "click") == 0)
312 set_button_active (self, self->click_button, TRUE);
313 else if (g_strcmp0 (alert_name, "hum") == 0)
314 set_button_active (self, self->hum_button, TRUE);
315 else if (g_strcmp0 (alert_name, "string") == 0)
316 set_button_active (self, self->string_button, TRUE);
317 else if (g_strcmp0 (alert_name, "swing") == 0)
318 set_button_active (self, self->swing_button, TRUE);
319 else if (alert_name != NULL)
320 g_warning ("Current alert sound has unknown name %s", alert_name);
321 }
322
323 CcAlertChooserWindow *
324 cc_alert_chooser_window_new (void)
325 {
326 return g_object_new (CC_TYPE_ALERT_CHOOSER_WINDOW, NULL);
327 }
328
329 const gchar *
330 get_selected_alert_display_name (void)
331 {
332 g_autofree gchar *alert_name = NULL;
333 g_autoptr(GSettings) sound_settings = NULL;
334
335 sound_settings = g_settings_new (KEY_SOUNDS_SCHEMA);
336
337 alert_name = get_alert_name ();
338
339 if (!g_settings_get_boolean (sound_settings, "event-sounds"))
340 return _("None");
341 else if (g_strcmp0 (alert_name, "click") == 0)
342 return _("Click");
343 else if (g_strcmp0 (alert_name, "hum") == 0)
344 return _("Hum");
345 else if (g_strcmp0 (alert_name, "string") == 0)
346 return _("String");
347 else if (g_strcmp0 (alert_name, "swing") == 0)
348 return _("Swing");
349 else
350 return _("Unknown");
351 }
352