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