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/. */
15 #include "HeadlessSound.h"
17 #include "nsNetUtil.h"
18 #include "nsIChannel.h"
21 #include "nsDirectoryService.h"
22 #include "nsDirectoryServiceDefs.h"
23 #include "mozilla/FileUtils.h"
24 #include "mozilla/Unused.h"
25 #include "mozilla/WidgetUtils.h"
26 #include "nsIXULAppInfo.h"
27 #include "nsContentUtils.h"
28 #include "gfxPlatform.h"
29 #include "mozilla/ClearOnShutdown.h"
35 static PRLibrary
* libcanberra
= nullptr;
37 /* used to play sounds with libcanberra. */
38 typedef struct _ca_context ca_context
;
39 typedef struct _ca_proplist ca_proplist
;
41 typedef void (*ca_finish_callback_t
)(ca_context
* c
, uint32_t id
, int error_code
,
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
, uint32_t id
, ...);
47 typedef int (*ca_context_change_props_fn
)(ca_context
* c
, ...);
48 typedef int (*ca_proplist_create_fn
)(ca_proplist
**);
49 typedef int (*ca_proplist_destroy_fn
)(ca_proplist
*);
50 typedef int (*ca_proplist_sets_fn
)(ca_proplist
* c
, const char* key
,
52 typedef int (*ca_context_play_full_fn
)(ca_context
* c
, uint32_t id
,
53 ca_proplist
* p
, ca_finish_callback_t cb
,
56 static ca_context_create_fn ca_context_create
;
57 static ca_context_destroy_fn ca_context_destroy
;
58 static ca_context_play_fn ca_context_play
;
59 static ca_context_change_props_fn ca_context_change_props
;
60 static ca_proplist_create_fn ca_proplist_create
;
61 static ca_proplist_destroy_fn ca_proplist_destroy
;
62 static ca_proplist_sets_fn ca_proplist_sets
;
63 static ca_context_play_full_fn ca_context_play_full
;
65 struct ScopedCanberraFile
{
66 explicit ScopedCanberraFile(nsIFile
* file
) : mFile(file
){};
68 ~ScopedCanberraFile() {
74 void forget() { mozilla::Unused
<< mFile
.forget(); }
75 nsIFile
* operator->() { return mFile
; }
76 operator nsIFile
*() { return mFile
; }
78 nsCOMPtr
<nsIFile
> mFile
;
81 static ca_context
* ca_context_get_default() {
82 // This allows us to avoid race conditions with freeing the context by handing
83 // that responsibility to Glib, and still use one context at a time
84 static GPrivate ctx_private
=
85 G_PRIVATE_INIT((GDestroyNotify
)ca_context_destroy
);
87 ca_context
* ctx
= (ca_context
*)g_private_get(&ctx_private
);
93 ca_context_create(&ctx
);
98 g_private_set(&ctx_private
, ctx
);
100 GtkSettings
* settings
= gtk_settings_get_default();
101 if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings
),
102 "gtk-sound-theme-name")) {
103 gchar
* sound_theme_name
= nullptr;
104 g_object_get(settings
, "gtk-sound-theme-name", &sound_theme_name
, nullptr);
106 if (sound_theme_name
) {
107 ca_context_change_props(ctx
, "canberra.xdg-theme.name", sound_theme_name
,
109 g_free(sound_theme_name
);
114 mozilla::widget::WidgetUtils::GetBrandShortName(wbrand
);
115 ca_context_change_props(ctx
, "application.name",
116 NS_ConvertUTF16toUTF8(wbrand
).get(), nullptr);
118 nsCOMPtr
<nsIXULAppInfo
> appInfo
=
119 do_GetService("@mozilla.org/xre/app-info;1");
121 nsAutoCString version
;
122 appInfo
->GetVersion(version
);
124 ca_context_change_props(ctx
, "application.version", version
.get(), nullptr);
127 ca_context_change_props(ctx
, "application.icon_name", MOZ_APP_NAME
, nullptr);
132 static void ca_finish_cb(ca_context
* c
, uint32_t id
, int error_code
,
134 nsIFile
* file
= reinterpret_cast<nsIFile
*>(userdata
);
141 NS_IMPL_ISUPPORTS(nsSound
, nsISound
, nsIStreamLoaderObserver
)
143 ////////////////////////////////////////////////////////////////////////
144 nsSound::nsSound() { mInited
= false; }
146 nsSound::~nsSound() = default;
150 // This function is designed so that no library is compulsory, and
151 // one library missing doesn't cause the other(s) to not be used.
152 if (mInited
) return NS_OK
;
157 libcanberra
= PR_LoadLibrary("libcanberra.so.0");
159 ca_context_create
= (ca_context_create_fn
)PR_FindFunctionSymbol(
160 libcanberra
, "ca_context_create");
161 if (!ca_context_create
) {
163 // With TSan, we cannot unload libcanberra once we have loaded it
164 // because TSan does not support unloading libraries that are matched
165 // from its suppression list. Hence we just keep the library loaded in
167 libcanberra
= nullptr;
170 PR_UnloadLibrary(libcanberra
);
171 libcanberra
= nullptr;
173 // at this point we know we have a good libcanberra library
174 ca_context_destroy
= (ca_context_destroy_fn
)PR_FindFunctionSymbol(
175 libcanberra
, "ca_context_destroy");
176 ca_context_play
= (ca_context_play_fn
)PR_FindFunctionSymbol(
177 libcanberra
, "ca_context_play");
178 ca_context_change_props
=
179 (ca_context_change_props_fn
)PR_FindFunctionSymbol(
180 libcanberra
, "ca_context_change_props");
181 ca_proplist_create
= (ca_proplist_create_fn
)PR_FindFunctionSymbol(
182 libcanberra
, "ca_proplist_create");
183 ca_proplist_destroy
= (ca_proplist_destroy_fn
)PR_FindFunctionSymbol(
184 libcanberra
, "ca_proplist_destroy");
185 ca_proplist_sets
= (ca_proplist_sets_fn
)PR_FindFunctionSymbol(
186 libcanberra
, "ca_proplist_sets");
187 ca_context_play_full
= (ca_context_play_full_fn
)PR_FindFunctionSymbol(
188 libcanberra
, "ca_context_play_full");
197 void nsSound::Shutdown() {
200 PR_UnloadLibrary(libcanberra
);
201 libcanberra
= nullptr;
208 StaticRefPtr
<nsISound
> sInstance
;
210 } // namespace mozilla
212 already_AddRefed
<nsISound
> nsSound::GetInstance() {
213 using namespace mozilla::sound
;
216 if (gfxPlatform::IsHeadless()) {
217 sInstance
= new mozilla::widget::HeadlessSound();
219 sInstance
= new nsSound();
221 ClearOnShutdown(&sInstance
);
224 RefPtr
<nsISound
> service
= sInstance
.get();
225 return service
.forget();
228 NS_IMETHODIMP
nsSound::OnStreamComplete(nsIStreamLoader
* aLoader
,
229 nsISupports
* context
, nsresult aStatus
,
230 uint32_t dataLen
, const uint8_t* data
) {
231 // print a load error on bad status, and return
232 if (NS_FAILED(aStatus
)) {
235 nsCOMPtr
<nsIRequest
> request
;
236 aLoader
->GetRequest(getter_AddRefs(request
));
238 nsCOMPtr
<nsIURI
> uri
;
239 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(request
);
241 channel
->GetURI(getter_AddRefs(uri
));
243 printf("Failed to load %s\n", uri
->GetSpecOrDefault().get());
252 nsCOMPtr
<nsIFile
> tmpFile
;
253 nsDirectoryService::gService
->Get(NS_OS_TEMP_DIR
, NS_GET_IID(nsIFile
),
254 getter_AddRefs(tmpFile
));
257 tmpFile
->AppendNative(nsDependentCString("mozilla_audio_sample"));
262 rv
= tmpFile
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, PR_IRUSR
| PR_IWUSR
);
267 ScopedCanberraFile
canberraFile(tmpFile
);
269 mozilla::AutoFDClose fd
;
270 rv
= canberraFile
->OpenNSPRFileDesc(PR_WRONLY
, PR_IRUSR
| PR_IWUSR
,
271 getter_Transfers(fd
));
276 // XXX: Should we do this on another thread?
277 uint32_t length
= dataLen
;
279 int32_t amount
= PR_Write(fd
.get(), data
, length
);
281 return NS_ERROR_FAILURE
;
287 ca_context
* ctx
= ca_context_get_default();
289 return NS_ERROR_OUT_OF_MEMORY
;
293 ca_proplist_create(&p
);
295 return NS_ERROR_OUT_OF_MEMORY
;
299 rv
= canberraFile
->GetNativePath(path
);
304 ca_proplist_sets(p
, "media.filename", path
.get());
305 if (ca_context_play_full(ctx
, 0, p
, ca_finish_cb
, canberraFile
) >= 0) {
306 // Don't delete the temporary file here if ca_context_play_full succeeds
307 canberraFile
.forget();
309 ca_proplist_destroy(p
);
314 NS_IMETHODIMP
nsSound::Beep() {
319 NS_IMETHODIMP
nsSound::Play(nsIURL
* aURL
) {
320 if (!mInited
) Init();
322 if (!libcanberra
) return NS_ERROR_NOT_AVAILABLE
;
325 if (aURL
->SchemeIs("file")) {
326 ca_context
* ctx
= ca_context_get_default();
328 return NS_ERROR_OUT_OF_MEMORY
;
332 rv
= aURL
->GetSpec(spec
);
336 gchar
* path
= g_filename_from_uri(spec
.get(), nullptr, nullptr);
338 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
341 ca_context_play(ctx
, 0, "media.filename", path
, nullptr);
344 nsCOMPtr
<nsIStreamLoader
> loader
;
345 rv
= NS_NewStreamLoader(
346 getter_AddRefs(loader
), aURL
,
348 nsContentUtils::GetSystemPrincipal(),
349 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
350 nsIContentPolicy::TYPE_OTHER
);
356 NS_IMETHODIMP
nsSound::PlayEventSound(uint32_t aEventId
) {
357 if (!mInited
) Init();
359 if (!libcanberra
) return NS_OK
;
361 // Do we even want alert sounds?
362 GtkSettings
* settings
= gtk_settings_get_default();
364 if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings
),
365 "gtk-enable-event-sounds")) {
366 gboolean enable_sounds
= TRUE
;
367 g_object_get(settings
, "gtk-enable-event-sounds", &enable_sounds
, nullptr);
369 if (!enable_sounds
) {
374 ca_context
* ctx
= ca_context_get_default();
376 return NS_ERROR_OUT_OF_MEMORY
;
380 case EVENT_ALERT_DIALOG_OPEN
:
381 ca_context_play(ctx
, 0, "event.id", "dialog-warning", nullptr);
383 case EVENT_CONFIRM_DIALOG_OPEN
:
384 ca_context_play(ctx
, 0, "event.id", "dialog-question", nullptr);
386 case EVENT_NEW_MAIL_RECEIVED
:
387 ca_context_play(ctx
, 0, "event.id", "message-new-email", nullptr);
389 case EVENT_MENU_EXECUTE
:
390 ca_context_play(ctx
, 0, "event.id", "menu-click", nullptr);
392 case EVENT_MENU_POPUP
:
393 ca_context_play(ctx
, 0, "event.id", "menu-popup", nullptr);