Merge mozilla-central to autoland. CLOSED TREE
[gecko.git] / widget / gtk / nsSound.cpp
blob0530c7322410918c980e499d0844bf6ca22b8759
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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/. */
8 #include <string.h>
10 #include "nscore.h"
11 #include "prlink.h"
13 #include "nsSound.h"
15 #include "HeadlessSound.h"
16 #include "nsIURL.h"
17 #include "nsNetUtil.h"
18 #include "nsIChannel.h"
19 #include "nsCOMPtr.h"
20 #include "nsString.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"
31 #include <stdio.h>
32 #include <unistd.h>
34 #include <gtk/gtk.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,
42 void* userdata);
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,
51 const char* value);
52 typedef int (*ca_context_play_full_fn)(ca_context* c, uint32_t id,
53 ca_proplist* p, ca_finish_callback_t cb,
54 void* userdata);
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() {
69 if (mFile) {
70 mFile->Remove(false);
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);
89 if (ctx) {
90 return ctx;
93 ca_context_create(&ctx);
94 if (!ctx) {
95 return nullptr;
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,
108 nullptr);
109 g_free(sound_theme_name);
113 nsAutoString wbrand;
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");
120 if (appInfo) {
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);
129 return ctx;
132 static void ca_finish_cb(ca_context* c, uint32_t id, int error_code,
133 void* userdata) {
134 nsIFile* file = reinterpret_cast<nsIFile*>(userdata);
135 if (file) {
136 file->Remove(false);
137 NS_RELEASE(file);
141 NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver)
143 ////////////////////////////////////////////////////////////////////////
144 nsSound::nsSound() { mInited = false; }
146 nsSound::~nsSound() = default;
148 NS_IMETHODIMP
149 nsSound::Init() {
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;
154 mInited = true;
156 if (!libcanberra) {
157 libcanberra = PR_LoadLibrary("libcanberra.so.0");
158 if (libcanberra) {
159 ca_context_create = (ca_context_create_fn)PR_FindFunctionSymbol(
160 libcanberra, "ca_context_create");
161 if (!ca_context_create) {
162 #ifdef MOZ_TSAN
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
166 // TSan builds.
167 libcanberra = nullptr;
168 return NS_OK;
169 #endif
170 PR_UnloadLibrary(libcanberra);
171 libcanberra = nullptr;
172 } else {
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");
193 return NS_OK;
196 /* static */
197 void nsSound::Shutdown() {
198 #ifndef MOZ_TSAN
199 if (libcanberra) {
200 PR_UnloadLibrary(libcanberra);
201 libcanberra = nullptr;
203 #endif
206 namespace mozilla {
207 namespace sound {
208 StaticRefPtr<nsISound> sInstance;
210 } // namespace mozilla
211 /* static */
212 already_AddRefed<nsISound> nsSound::GetInstance() {
213 using namespace mozilla::sound;
215 if (!sInstance) {
216 if (gfxPlatform::IsHeadless()) {
217 sInstance = new mozilla::widget::HeadlessSound();
218 } else {
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)) {
233 #ifdef DEBUG
234 if (aLoader) {
235 nsCOMPtr<nsIRequest> request;
236 aLoader->GetRequest(getter_AddRefs(request));
237 if (request) {
238 nsCOMPtr<nsIURI> uri;
239 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
240 if (channel) {
241 channel->GetURI(getter_AddRefs(uri));
242 if (uri) {
243 printf("Failed to load %s\n", uri->GetSpecOrDefault().get());
248 #endif
249 return aStatus;
252 nsCOMPtr<nsIFile> tmpFile;
253 nsDirectoryService::gService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsIFile),
254 getter_AddRefs(tmpFile));
256 nsresult rv =
257 tmpFile->AppendNative(nsDependentCString("mozilla_audio_sample"));
258 if (NS_FAILED(rv)) {
259 return rv;
262 rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, PR_IRUSR | PR_IWUSR);
263 if (NS_FAILED(rv)) {
264 return rv;
267 ScopedCanberraFile canberraFile(tmpFile);
269 mozilla::AutoFDClose fd;
270 rv = canberraFile->OpenNSPRFileDesc(PR_WRONLY, PR_IRUSR | PR_IWUSR,
271 getter_Transfers(fd));
272 if (NS_FAILED(rv)) {
273 return rv;
276 // XXX: Should we do this on another thread?
277 uint32_t length = dataLen;
278 while (length > 0) {
279 int32_t amount = PR_Write(fd.get(), data, length);
280 if (amount < 0) {
281 return NS_ERROR_FAILURE;
283 length -= amount;
284 data += amount;
287 ca_context* ctx = ca_context_get_default();
288 if (!ctx) {
289 return NS_ERROR_OUT_OF_MEMORY;
292 ca_proplist* p;
293 ca_proplist_create(&p);
294 if (!p) {
295 return NS_ERROR_OUT_OF_MEMORY;
298 nsAutoCString path;
299 rv = canberraFile->GetNativePath(path);
300 if (NS_FAILED(rv)) {
301 return rv;
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);
311 return NS_OK;
314 NS_IMETHODIMP nsSound::Beep() {
315 ::gdk_beep();
316 return NS_OK;
319 NS_IMETHODIMP nsSound::Play(nsIURL* aURL) {
320 if (!mInited) Init();
322 if (!libcanberra) return NS_ERROR_NOT_AVAILABLE;
324 nsresult rv;
325 if (aURL->SchemeIs("file")) {
326 ca_context* ctx = ca_context_get_default();
327 if (!ctx) {
328 return NS_ERROR_OUT_OF_MEMORY;
331 nsAutoCString spec;
332 rv = aURL->GetSpec(spec);
333 if (NS_FAILED(rv)) {
334 return rv;
336 gchar* path = g_filename_from_uri(spec.get(), nullptr, nullptr);
337 if (!path) {
338 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
341 ca_context_play(ctx, 0, "media.filename", path, nullptr);
342 g_free(path);
343 } else {
344 nsCOMPtr<nsIStreamLoader> loader;
345 rv = NS_NewStreamLoader(
346 getter_AddRefs(loader), aURL,
347 this, // aObserver
348 nsContentUtils::GetSystemPrincipal(),
349 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
350 nsIContentPolicy::TYPE_OTHER);
353 return rv;
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) {
370 return NS_OK;
374 ca_context* ctx = ca_context_get_default();
375 if (!ctx) {
376 return NS_ERROR_OUT_OF_MEMORY;
379 switch (aEventId) {
380 case EVENT_ALERT_DIALOG_OPEN:
381 ca_context_play(ctx, 0, "event.id", "dialog-warning", nullptr);
382 break;
383 case EVENT_CONFIRM_DIALOG_OPEN:
384 ca_context_play(ctx, 0, "event.id", "dialog-question", nullptr);
385 break;
386 case EVENT_NEW_MAIL_RECEIVED:
387 ca_context_play(ctx, 0, "event.id", "message-new-email", nullptr);
388 break;
389 case EVENT_MENU_EXECUTE:
390 ca_context_play(ctx, 0, "event.id", "menu-click", nullptr);
391 break;
392 case EVENT_MENU_POPUP:
393 ca_context_play(ctx, 0, "event.id", "menu-popup", nullptr);
394 break;
396 return NS_OK;