/src/mozilla-central/widget/gtk/nsSound.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* vim:expandtab:shiftwidth=4:tabstop=4: |
3 | | */ |
4 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
5 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
6 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
7 | | |
8 | | #include <string.h> |
9 | | |
10 | | #include "nscore.h" |
11 | | #include "plstr.h" |
12 | | #include "prlink.h" |
13 | | |
14 | | #include "nsSound.h" |
15 | | |
16 | | #include "HeadlessSound.h" |
17 | | #include "nsIURL.h" |
18 | | #include "nsIFileURL.h" |
19 | | #include "nsNetUtil.h" |
20 | | #include "nsIChannel.h" |
21 | | #include "nsCOMPtr.h" |
22 | | #include "nsString.h" |
23 | | #include "nsDirectoryService.h" |
24 | | #include "nsDirectoryServiceDefs.h" |
25 | | #include "mozilla/FileUtils.h" |
26 | | #include "mozilla/Services.h" |
27 | | #include "mozilla/Unused.h" |
28 | | #include "mozilla/WidgetUtils.h" |
29 | | #include "nsIXULAppInfo.h" |
30 | | #include "nsContentUtils.h" |
31 | | #include "gfxPlatform.h" |
32 | | #include "mozilla/ClearOnShutdown.h" |
33 | | |
34 | | #include <stdio.h> |
35 | | #include <unistd.h> |
36 | | |
37 | | #include <gtk/gtk.h> |
38 | | static PRLibrary *libcanberra = nullptr; |
39 | | |
40 | | /* used to play sounds with libcanberra. */ |
41 | | typedef struct _ca_context ca_context; |
42 | | typedef struct _ca_proplist ca_proplist; |
43 | | |
44 | | typedef void (*ca_finish_callback_t) (ca_context *c, |
45 | | uint32_t id, |
46 | | int error_code, |
47 | | void *userdata); |
48 | | |
49 | | typedef int (*ca_context_create_fn) (ca_context **); |
50 | | typedef int (*ca_context_destroy_fn) (ca_context *); |
51 | | typedef int (*ca_context_play_fn) (ca_context *c, |
52 | | uint32_t id, |
53 | | ...); |
54 | | typedef int (*ca_context_change_props_fn) (ca_context *c, |
55 | | ...); |
56 | | typedef int (*ca_proplist_create_fn) (ca_proplist **); |
57 | | typedef int (*ca_proplist_destroy_fn) (ca_proplist *); |
58 | | typedef int (*ca_proplist_sets_fn) (ca_proplist *c, |
59 | | const char *key, |
60 | | const char *value); |
61 | | typedef int (*ca_context_play_full_fn) (ca_context *c, |
62 | | uint32_t id, |
63 | | ca_proplist *p, |
64 | | ca_finish_callback_t cb, |
65 | | void *userdata); |
66 | | |
67 | | static ca_context_create_fn ca_context_create; |
68 | | static ca_context_destroy_fn ca_context_destroy; |
69 | | static ca_context_play_fn ca_context_play; |
70 | | static ca_context_change_props_fn ca_context_change_props; |
71 | | static ca_proplist_create_fn ca_proplist_create; |
72 | | static ca_proplist_destroy_fn ca_proplist_destroy; |
73 | | static ca_proplist_sets_fn ca_proplist_sets; |
74 | | static ca_context_play_full_fn ca_context_play_full; |
75 | | |
76 | | struct ScopedCanberraFile { |
77 | 0 | explicit ScopedCanberraFile(nsIFile *file): mFile(file) {}; |
78 | | |
79 | 0 | ~ScopedCanberraFile() { |
80 | 0 | if (mFile) { |
81 | 0 | mFile->Remove(false); |
82 | 0 | } |
83 | 0 | } |
84 | | |
85 | 0 | void forget() { |
86 | 0 | mozilla::Unused << mFile.forget(); |
87 | 0 | } |
88 | 0 | nsIFile* operator->() { return mFile; } |
89 | 0 | operator nsIFile*() { return mFile; } |
90 | | |
91 | | nsCOMPtr<nsIFile> mFile; |
92 | | }; |
93 | | |
94 | | static ca_context* |
95 | | ca_context_get_default() |
96 | 0 | { |
97 | 0 | // This allows us to avoid race conditions with freeing the context by handing that |
98 | 0 | // responsibility to Glib, and still use one context at a time |
99 | 0 | static GStaticPrivate ctx_static_private = G_STATIC_PRIVATE_INIT; |
100 | 0 |
|
101 | 0 | ca_context* ctx = (ca_context*) g_static_private_get(&ctx_static_private); |
102 | 0 |
|
103 | 0 | if (ctx) { |
104 | 0 | return ctx; |
105 | 0 | } |
106 | 0 | |
107 | 0 | ca_context_create(&ctx); |
108 | 0 | if (!ctx) { |
109 | 0 | return nullptr; |
110 | 0 | } |
111 | 0 | |
112 | 0 | g_static_private_set(&ctx_static_private, ctx, (GDestroyNotify) ca_context_destroy); |
113 | 0 |
|
114 | 0 | GtkSettings* settings = gtk_settings_get_default(); |
115 | 0 | if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings), |
116 | 0 | "gtk-sound-theme-name")) { |
117 | 0 | gchar* sound_theme_name = nullptr; |
118 | 0 | g_object_get(settings, "gtk-sound-theme-name", &sound_theme_name, |
119 | 0 | nullptr); |
120 | 0 |
|
121 | 0 | if (sound_theme_name) { |
122 | 0 | ca_context_change_props(ctx, "canberra.xdg-theme.name", |
123 | 0 | sound_theme_name, nullptr); |
124 | 0 | g_free(sound_theme_name); |
125 | 0 | } |
126 | 0 | } |
127 | 0 |
|
128 | 0 | nsAutoString wbrand; |
129 | 0 | mozilla::widget::WidgetUtils::GetBrandShortName(wbrand); |
130 | 0 | ca_context_change_props(ctx, "application.name", |
131 | 0 | NS_ConvertUTF16toUTF8(wbrand).get(), |
132 | 0 | nullptr); |
133 | 0 |
|
134 | 0 | nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1"); |
135 | 0 | if (appInfo) { |
136 | 0 | nsAutoCString version; |
137 | 0 | appInfo->GetVersion(version); |
138 | 0 |
|
139 | 0 | ca_context_change_props(ctx, "application.version", version.get(), |
140 | 0 | nullptr); |
141 | 0 | } |
142 | 0 |
|
143 | 0 | ca_context_change_props(ctx, "application.icon_name", MOZ_APP_NAME, |
144 | 0 | nullptr); |
145 | 0 |
|
146 | 0 | return ctx; |
147 | 0 | } |
148 | | |
149 | | static void |
150 | | ca_finish_cb(ca_context *c, |
151 | | uint32_t id, |
152 | | int error_code, |
153 | | void *userdata) |
154 | 0 | { |
155 | 0 | nsIFile *file = reinterpret_cast<nsIFile *>(userdata); |
156 | 0 | if (file) { |
157 | 0 | file->Remove(false); |
158 | 0 | NS_RELEASE(file); |
159 | 0 | } |
160 | 0 | } |
161 | | |
162 | | NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver) |
163 | | |
164 | | //////////////////////////////////////////////////////////////////////// |
165 | | nsSound::nsSound() |
166 | 0 | { |
167 | 0 | mInited = false; |
168 | 0 | } |
169 | | |
170 | | nsSound::~nsSound() |
171 | 0 | { |
172 | 0 | } |
173 | | |
174 | | NS_IMETHODIMP |
175 | | nsSound::Init() |
176 | 0 | { |
177 | 0 | // This function is designed so that no library is compulsory, and |
178 | 0 | // one library missing doesn't cause the other(s) to not be used. |
179 | 0 | if (mInited) |
180 | 0 | return NS_OK; |
181 | 0 | |
182 | 0 | mInited = true; |
183 | 0 |
|
184 | 0 | if (!libcanberra) { |
185 | 0 | libcanberra = PR_LoadLibrary("libcanberra.so.0"); |
186 | 0 | if (libcanberra) { |
187 | 0 | ca_context_create = (ca_context_create_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_create"); |
188 | 0 | if (!ca_context_create) { |
189 | 0 | PR_UnloadLibrary(libcanberra); |
190 | 0 | libcanberra = nullptr; |
191 | 0 | } else { |
192 | 0 | // at this point we know we have a good libcanberra library |
193 | 0 | ca_context_destroy = (ca_context_destroy_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_destroy"); |
194 | 0 | ca_context_play = (ca_context_play_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_play"); |
195 | 0 | ca_context_change_props = (ca_context_change_props_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_change_props"); |
196 | 0 | ca_proplist_create = (ca_proplist_create_fn) PR_FindFunctionSymbol(libcanberra, "ca_proplist_create"); |
197 | 0 | ca_proplist_destroy = (ca_proplist_destroy_fn) PR_FindFunctionSymbol(libcanberra, "ca_proplist_destroy"); |
198 | 0 | ca_proplist_sets = (ca_proplist_sets_fn) PR_FindFunctionSymbol(libcanberra, "ca_proplist_sets"); |
199 | 0 | ca_context_play_full = (ca_context_play_full_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_play_full"); |
200 | 0 | } |
201 | 0 | } |
202 | 0 | } |
203 | 0 |
|
204 | 0 | return NS_OK; |
205 | 0 | } |
206 | | |
207 | | /* static */ void |
208 | | nsSound::Shutdown() |
209 | 0 | { |
210 | 0 | if (libcanberra) { |
211 | 0 | PR_UnloadLibrary(libcanberra); |
212 | 0 | libcanberra = nullptr; |
213 | 0 | } |
214 | 0 | } |
215 | | |
216 | | namespace mozilla { |
217 | | namespace sound { |
218 | | StaticRefPtr<nsISound> sInstance; |
219 | | } |
220 | | } |
221 | | /* static */ already_AddRefed<nsISound> |
222 | | nsSound::GetInstance() |
223 | 0 | { |
224 | 0 | using namespace mozilla::sound; |
225 | 0 |
|
226 | 0 | if (!sInstance) { |
227 | 0 | if (gfxPlatform::IsHeadless()) { |
228 | 0 | sInstance = new mozilla::widget::HeadlessSound(); |
229 | 0 | } else { |
230 | 0 | sInstance = new nsSound(); |
231 | 0 | } |
232 | 0 | ClearOnShutdown(&sInstance); |
233 | 0 | } |
234 | 0 |
|
235 | 0 | RefPtr<nsISound> service = sInstance.get(); |
236 | 0 | return service.forget(); |
237 | 0 | } |
238 | | |
239 | | NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader, |
240 | | nsISupports *context, |
241 | | nsresult aStatus, |
242 | | uint32_t dataLen, |
243 | | const uint8_t *data) |
244 | 0 | { |
245 | 0 | // print a load error on bad status, and return |
246 | 0 | if (NS_FAILED(aStatus)) { |
247 | | #ifdef DEBUG |
248 | | if (aLoader) { |
249 | | nsCOMPtr<nsIRequest> request; |
250 | | aLoader->GetRequest(getter_AddRefs(request)); |
251 | | if (request) { |
252 | | nsCOMPtr<nsIURI> uri; |
253 | | nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); |
254 | | if (channel) { |
255 | | channel->GetURI(getter_AddRefs(uri)); |
256 | | if (uri) { |
257 | | printf("Failed to load %s\n", |
258 | | uri->GetSpecOrDefault().get()); |
259 | | } |
260 | | } |
261 | | } |
262 | | } |
263 | | #endif |
264 | | return aStatus; |
265 | 0 | } |
266 | 0 |
|
267 | 0 | nsCOMPtr<nsIFile> tmpFile; |
268 | 0 | nsDirectoryService::gService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsIFile), |
269 | 0 | getter_AddRefs(tmpFile)); |
270 | 0 |
|
271 | 0 | nsresult rv = tmpFile->AppendNative(nsDependentCString("mozilla_audio_sample")); |
272 | 0 | if (NS_FAILED(rv)) { |
273 | 0 | return rv; |
274 | 0 | } |
275 | 0 | |
276 | 0 | rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, PR_IRUSR | PR_IWUSR); |
277 | 0 | if (NS_FAILED(rv)) { |
278 | 0 | return rv; |
279 | 0 | } |
280 | 0 | |
281 | 0 | ScopedCanberraFile canberraFile(tmpFile); |
282 | 0 |
|
283 | 0 | mozilla::AutoFDClose fd; |
284 | 0 | rv = canberraFile->OpenNSPRFileDesc(PR_WRONLY, PR_IRUSR | PR_IWUSR, |
285 | 0 | &fd.rwget()); |
286 | 0 | if (NS_FAILED(rv)) { |
287 | 0 | return rv; |
288 | 0 | } |
289 | 0 | |
290 | 0 | // XXX: Should we do this on another thread? |
291 | 0 | uint32_t length = dataLen; |
292 | 0 | while (length > 0) { |
293 | 0 | int32_t amount = PR_Write(fd, data, length); |
294 | 0 | if (amount < 0) { |
295 | 0 | return NS_ERROR_FAILURE; |
296 | 0 | } |
297 | 0 | length -= amount; |
298 | 0 | data += amount; |
299 | 0 | } |
300 | 0 |
|
301 | 0 | ca_context* ctx = ca_context_get_default(); |
302 | 0 | if (!ctx) { |
303 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
304 | 0 | } |
305 | 0 | |
306 | 0 | ca_proplist *p; |
307 | 0 | ca_proplist_create(&p); |
308 | 0 | if (!p) { |
309 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
310 | 0 | } |
311 | 0 | |
312 | 0 | nsAutoCString path; |
313 | 0 | rv = canberraFile->GetNativePath(path); |
314 | 0 | if (NS_FAILED(rv)) { |
315 | 0 | return rv; |
316 | 0 | } |
317 | 0 | |
318 | 0 | ca_proplist_sets(p, "media.filename", path.get()); |
319 | 0 | if (ca_context_play_full(ctx, 0, p, ca_finish_cb, canberraFile) >= 0) { |
320 | 0 | // Don't delete the temporary file here if ca_context_play_full succeeds |
321 | 0 | canberraFile.forget(); |
322 | 0 | } |
323 | 0 | ca_proplist_destroy(p); |
324 | 0 |
|
325 | 0 | return NS_OK; |
326 | 0 | } |
327 | | |
328 | | NS_IMETHODIMP nsSound::Beep() |
329 | 0 | { |
330 | 0 | ::gdk_beep(); |
331 | 0 | return NS_OK; |
332 | 0 | } |
333 | | |
334 | | NS_IMETHODIMP nsSound::Play(nsIURL *aURL) |
335 | 0 | { |
336 | 0 | if (!mInited) |
337 | 0 | Init(); |
338 | 0 |
|
339 | 0 | if (!libcanberra) |
340 | 0 | return NS_ERROR_NOT_AVAILABLE; |
341 | 0 | |
342 | 0 | bool isFile; |
343 | 0 | nsresult rv = aURL->SchemeIs("file", &isFile); |
344 | 0 | if (NS_SUCCEEDED(rv) && isFile) { |
345 | 0 | ca_context* ctx = ca_context_get_default(); |
346 | 0 | if (!ctx) { |
347 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
348 | 0 | } |
349 | 0 | |
350 | 0 | nsAutoCString spec; |
351 | 0 | rv = aURL->GetSpec(spec); |
352 | 0 | if (NS_FAILED(rv)) { |
353 | 0 | return rv; |
354 | 0 | } |
355 | 0 | gchar *path = g_filename_from_uri(spec.get(), nullptr, nullptr); |
356 | 0 | if (!path) { |
357 | 0 | return NS_ERROR_FILE_UNRECOGNIZED_PATH; |
358 | 0 | } |
359 | 0 | |
360 | 0 | ca_context_play(ctx, 0, "media.filename", path, nullptr); |
361 | 0 | g_free(path); |
362 | 0 | } else { |
363 | 0 | nsCOMPtr<nsIStreamLoader> loader; |
364 | 0 | rv = NS_NewStreamLoader(getter_AddRefs(loader), |
365 | 0 | aURL, |
366 | 0 | this, // aObserver |
367 | 0 | nsContentUtils::GetSystemPrincipal(), |
368 | 0 | nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, |
369 | 0 | nsIContentPolicy::TYPE_OTHER); |
370 | 0 | } |
371 | 0 |
|
372 | 0 | return rv; |
373 | 0 | } |
374 | | |
375 | | NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId) |
376 | 0 | { |
377 | 0 | if (!mInited) |
378 | 0 | Init(); |
379 | 0 |
|
380 | 0 | if (!libcanberra) |
381 | 0 | return NS_OK; |
382 | 0 | |
383 | 0 | // Do we even want alert sounds? |
384 | 0 | GtkSettings* settings = gtk_settings_get_default(); |
385 | 0 |
|
386 | 0 | if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings), |
387 | 0 | "gtk-enable-event-sounds")) { |
388 | 0 | gboolean enable_sounds = TRUE; |
389 | 0 | g_object_get(settings, "gtk-enable-event-sounds", &enable_sounds, nullptr); |
390 | 0 |
|
391 | 0 | if (!enable_sounds) { |
392 | 0 | return NS_OK; |
393 | 0 | } |
394 | 0 | } |
395 | 0 | |
396 | 0 | ca_context* ctx = ca_context_get_default(); |
397 | 0 | if (!ctx) { |
398 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
399 | 0 | } |
400 | 0 | |
401 | 0 | switch (aEventId) { |
402 | 0 | case EVENT_ALERT_DIALOG_OPEN: |
403 | 0 | ca_context_play(ctx, 0, "event.id", "dialog-warning", nullptr); |
404 | 0 | break; |
405 | 0 | case EVENT_CONFIRM_DIALOG_OPEN: |
406 | 0 | ca_context_play(ctx, 0, "event.id", "dialog-question", nullptr); |
407 | 0 | break; |
408 | 0 | case EVENT_NEW_MAIL_RECEIVED: |
409 | 0 | ca_context_play(ctx, 0, "event.id", "message-new-email", nullptr); |
410 | 0 | break; |
411 | 0 | case EVENT_MENU_EXECUTE: |
412 | 0 | ca_context_play(ctx, 0, "event.id", "menu-click", nullptr); |
413 | 0 | break; |
414 | 0 | case EVENT_MENU_POPUP: |
415 | 0 | ca_context_play(ctx, 0, "event.id", "menu-popup", nullptr); |
416 | 0 | break; |
417 | 0 | } |
418 | 0 | return NS_OK; |
419 | 0 | } |