1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=4:tabstop=4:
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/. */
16 #include "HeadlessSound.h"
18 #include "nsNetUtil.h"
19 #include "nsIChannel.h"
22 #include "nsDirectoryService.h"
23 #include "nsDirectoryServiceDefs.h"
24 #include "mozilla/FileUtils.h"
25 #include "mozilla/Unused.h"
26 #include "mozilla/WidgetUtils.h"
27 #include "nsIXULAppInfo.h"
28 #include "nsContentUtils.h"
29 #include "gfxPlatform.h"
30 #include "mozilla/ClearOnShutdown.h"
36 static PRLibrary
* libcanberra
= nullptr;
38 /* used to play sounds with libcanberra. */
39 typedef struct _ca_context ca_context
;
40 typedef struct _ca_proplist ca_proplist
;
42 typedef void (*ca_finish_callback_t
)(ca_context
* c
, uint32_t id
, int error_code
,
45 typedef int (*ca_context_create_fn
)(ca_context
**);
46 typedef int (*ca_context_destroy_fn
)(ca_context
*);
47 typedef int (*ca_context_play_fn
)(ca_context
* c
, uint32_t id
, ...);
48 typedef int (*ca_context_change_props_fn
)(ca_context
* c
, ...);
49 typedef int (*ca_proplist_create_fn
)(ca_proplist
**);
50 typedef int (*ca_proplist_destroy_fn
)(ca_proplist
*);
51 typedef int (*ca_proplist_sets_fn
)(ca_proplist
* c
, const char* key
,
53 typedef int (*ca_context_play_full_fn
)(ca_context
* c
, uint32_t id
,
54 ca_proplist
* p
, ca_finish_callback_t cb
,
57 static ca_context_create_fn ca_context_create
;
58 static ca_context_destroy_fn ca_context_destroy
;
59 static ca_context_play_fn ca_context_play
;
60 static ca_context_change_props_fn ca_context_change_props
;
61 static ca_proplist_create_fn ca_proplist_create
;
62 static ca_proplist_destroy_fn ca_proplist_destroy
;
63 static ca_proplist_sets_fn ca_proplist_sets
;
64 static ca_context_play_full_fn ca_context_play_full
;
66 struct ScopedCanberraFile
{
67 explicit ScopedCanberraFile(nsIFile
* file
) : mFile(file
){};
69 ~ScopedCanberraFile() {
75 void forget() { mozilla::Unused
<< mFile
.forget(); }
76 nsIFile
* operator->() { return mFile
; }
77 operator nsIFile
*() { return mFile
; }
79 nsCOMPtr
<nsIFile
> mFile
;
82 static ca_context
* ca_context_get_default() {
83 // This allows us to avoid race conditions with freeing the context by handing
84 // that responsibility to Glib, and still use one context at a time
85 static GPrivate ctx_private
=
86 G_PRIVATE_INIT((GDestroyNotify
)ca_context_destroy
);
88 ca_context
* ctx
= (ca_context
*)g_private_get(&ctx_private
);
94 ca_context_create(&ctx
);
99 g_private_set(&ctx_private
, ctx
);
101 GtkSettings
* settings
= gtk_settings_get_default();
102 if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings
),
103 "gtk-sound-theme-name")) {
104 gchar
* sound_theme_name
= nullptr;
105 g_object_get(settings
, "gtk-sound-theme-name", &sound_theme_name
, nullptr);
107 if (sound_theme_name
) {
108 ca_context_change_props(ctx
, "canberra.xdg-theme.name", sound_theme_name
,
110 g_free(sound_theme_name
);
115 mozilla::widget::WidgetUtils::GetBrandShortName(wbrand
);
116 ca_context_change_props(ctx
, "application.name",
117 NS_ConvertUTF16toUTF8(wbrand
).get(), nullptr);
119 nsCOMPtr
<nsIXULAppInfo
> appInfo
=
120 do_GetService("@mozilla.org/xre/app-info;1");
122 nsAutoCString version
;
123 appInfo
->GetVersion(version
);
125 ca_context_change_props(ctx
, "application.version", version
.get(), nullptr);
128 ca_context_change_props(ctx
, "application.icon_name", MOZ_APP_NAME
, nullptr);
133 static void ca_finish_cb(ca_context
* c
, uint32_t id
, int error_code
,
135 nsIFile
* file
= reinterpret_cast<nsIFile
*>(userdata
);
142 NS_IMPL_ISUPPORTS(nsSound
, nsISound
, nsIStreamLoaderObserver
)
144 ////////////////////////////////////////////////////////////////////////
145 nsSound::nsSound() { mInited
= false; }
147 nsSound::~nsSound() = default;
151 // This function is designed so that no library is compulsory, and
152 // one library missing doesn't cause the other(s) to not be used.
153 if (mInited
) return NS_OK
;
158 libcanberra
= PR_LoadLibrary("libcanberra.so.0");
160 ca_context_create
= (ca_context_create_fn
)PR_FindFunctionSymbol(
161 libcanberra
, "ca_context_create");
162 if (!ca_context_create
) {
164 // With TSan, we cannot unload libcanberra once we have loaded it
165 // because TSan does not support unloading libraries that are matched
166 // from its suppression list. Hence we just keep the library loaded in
168 libcanberra
= nullptr;
171 PR_UnloadLibrary(libcanberra
);
172 libcanberra
= nullptr;
174 // at this point we know we have a good libcanberra library
175 ca_context_destroy
= (ca_context_destroy_fn
)PR_FindFunctionSymbol(
176 libcanberra
, "ca_context_destroy");
177 ca_context_play
= (ca_context_play_fn
)PR_FindFunctionSymbol(
178 libcanberra
, "ca_context_play");
179 ca_context_change_props
=
180 (ca_context_change_props_fn
)PR_FindFunctionSymbol(
181 libcanberra
, "ca_context_change_props");
182 ca_proplist_create
= (ca_proplist_create_fn
)PR_FindFunctionSymbol(
183 libcanberra
, "ca_proplist_create");
184 ca_proplist_destroy
= (ca_proplist_destroy_fn
)PR_FindFunctionSymbol(
185 libcanberra
, "ca_proplist_destroy");
186 ca_proplist_sets
= (ca_proplist_sets_fn
)PR_FindFunctionSymbol(
187 libcanberra
, "ca_proplist_sets");
188 ca_context_play_full
= (ca_context_play_full_fn
)PR_FindFunctionSymbol(
189 libcanberra
, "ca_context_play_full");
198 void nsSound::Shutdown() {
201 PR_UnloadLibrary(libcanberra
);
202 libcanberra
= nullptr;
209 StaticRefPtr
<nsISound
> sInstance
;
211 } // namespace mozilla
213 already_AddRefed
<nsISound
> nsSound::GetInstance() {
214 using namespace mozilla::sound
;
217 if (gfxPlatform::IsHeadless()) {
218 sInstance
= new mozilla::widget::HeadlessSound();
220 sInstance
= new nsSound();
222 ClearOnShutdown(&sInstance
);
225 RefPtr
<nsISound
> service
= sInstance
.get();
226 return service
.forget();
229 NS_IMETHODIMP
nsSound::OnStreamComplete(nsIStreamLoader
* aLoader
,
230 nsISupports
* context
, nsresult aStatus
,
231 uint32_t dataLen
, const uint8_t* data
) {
232 // print a load error on bad status, and return
233 if (NS_FAILED(aStatus
)) {
236 nsCOMPtr
<nsIRequest
> request
;
237 aLoader
->GetRequest(getter_AddRefs(request
));
239 nsCOMPtr
<nsIURI
> uri
;
240 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(request
);
242 channel
->GetURI(getter_AddRefs(uri
));
244 printf("Failed to load %s\n", uri
->GetSpecOrDefault().get());
253 nsCOMPtr
<nsIFile
> tmpFile
;
254 nsDirectoryService::gService
->Get(NS_OS_TEMP_DIR
, NS_GET_IID(nsIFile
),
255 getter_AddRefs(tmpFile
));
258 tmpFile
->AppendNative(nsDependentCString("mozilla_audio_sample"));
263 rv
= tmpFile
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, PR_IRUSR
| PR_IWUSR
);
268 ScopedCanberraFile
canberraFile(tmpFile
);
270 mozilla::AutoFDClose fd
;
271 rv
= canberraFile
->OpenNSPRFileDesc(PR_WRONLY
, PR_IRUSR
| PR_IWUSR
,
277 // XXX: Should we do this on another thread?
278 uint32_t length
= dataLen
;
280 int32_t amount
= PR_Write(fd
, data
, length
);
282 return NS_ERROR_FAILURE
;
288 ca_context
* ctx
= ca_context_get_default();
290 return NS_ERROR_OUT_OF_MEMORY
;
294 ca_proplist_create(&p
);
296 return NS_ERROR_OUT_OF_MEMORY
;
300 rv
= canberraFile
->GetNativePath(path
);
305 ca_proplist_sets(p
, "media.filename", path
.get());
306 if (ca_context_play_full(ctx
, 0, p
, ca_finish_cb
, canberraFile
) >= 0) {
307 // Don't delete the temporary file here if ca_context_play_full succeeds
308 canberraFile
.forget();
310 ca_proplist_destroy(p
);
315 NS_IMETHODIMP
nsSound::Beep() {
320 NS_IMETHODIMP
nsSound::Play(nsIURL
* aURL
) {
321 if (!mInited
) Init();
323 if (!libcanberra
) return NS_ERROR_NOT_AVAILABLE
;
326 if (aURL
->SchemeIs("file")) {
327 ca_context
* ctx
= ca_context_get_default();
329 return NS_ERROR_OUT_OF_MEMORY
;
333 rv
= aURL
->GetSpec(spec
);
337 gchar
* path
= g_filename_from_uri(spec
.get(), nullptr, nullptr);
339 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
342 ca_context_play(ctx
, 0, "media.filename", path
, nullptr);
345 nsCOMPtr
<nsIStreamLoader
> loader
;
346 rv
= NS_NewStreamLoader(
347 getter_AddRefs(loader
), aURL
,
349 nsContentUtils::GetSystemPrincipal(),
350 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
351 nsIContentPolicy::TYPE_OTHER
);
357 NS_IMETHODIMP
nsSound::PlayEventSound(uint32_t aEventId
) {
358 if (!mInited
) Init();
360 if (!libcanberra
) return NS_OK
;
362 // Do we even want alert sounds?
363 GtkSettings
* settings
= gtk_settings_get_default();
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);
370 if (!enable_sounds
) {
375 ca_context
* ctx
= ca_context_get_default();
377 return NS_ERROR_OUT_OF_MEMORY
;
381 case EVENT_ALERT_DIALOG_OPEN
:
382 ca_context_play(ctx
, 0, "event.id", "dialog-warning", nullptr);
384 case EVENT_CONFIRM_DIALOG_OPEN
:
385 ca_context_play(ctx
, 0, "event.id", "dialog-question", nullptr);
387 case EVENT_NEW_MAIL_RECEIVED
:
388 ca_context_play(ctx
, 0, "event.id", "message-new-email", nullptr);
390 case EVENT_MENU_EXECUTE
:
391 ca_context_play(ctx
, 0, "event.id", "menu-click", nullptr);
393 case EVENT_MENU_POPUP
:
394 ca_context_play(ctx
, 0, "event.id", "menu-popup", nullptr);