1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/gtk/nsSound.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,438 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +/* vim:expandtab:shiftwidth=4:tabstop=4: 1.6 + */ 1.7 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.8 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.9 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.10 + 1.11 +#include <string.h> 1.12 + 1.13 +#include "nscore.h" 1.14 +#include "plstr.h" 1.15 +#include "prlink.h" 1.16 + 1.17 +#include "nsSound.h" 1.18 + 1.19 +#include "nsIURL.h" 1.20 +#include "nsIFileURL.h" 1.21 +#include "nsNetUtil.h" 1.22 +#include "nsCOMPtr.h" 1.23 +#include "nsAutoPtr.h" 1.24 +#include "nsString.h" 1.25 +#include "nsDirectoryService.h" 1.26 +#include "nsDirectoryServiceDefs.h" 1.27 +#include "mozilla/FileUtils.h" 1.28 +#include "mozilla/Services.h" 1.29 +#include "nsIStringBundle.h" 1.30 +#include "nsIXULAppInfo.h" 1.31 + 1.32 +#include <stdio.h> 1.33 +#include <unistd.h> 1.34 + 1.35 +#include <gtk/gtk.h> 1.36 +static PRLibrary *libcanberra = nullptr; 1.37 + 1.38 +/* used to play sounds with libcanberra. */ 1.39 +typedef struct _ca_context ca_context; 1.40 +typedef struct _ca_proplist ca_proplist; 1.41 + 1.42 +typedef void (*ca_finish_callback_t) (ca_context *c, 1.43 + uint32_t id, 1.44 + int error_code, 1.45 + void *userdata); 1.46 + 1.47 +typedef int (*ca_context_create_fn) (ca_context **); 1.48 +typedef int (*ca_context_destroy_fn) (ca_context *); 1.49 +typedef int (*ca_context_play_fn) (ca_context *c, 1.50 + uint32_t id, 1.51 + ...); 1.52 +typedef int (*ca_context_change_props_fn) (ca_context *c, 1.53 + ...); 1.54 +typedef int (*ca_proplist_create_fn) (ca_proplist **); 1.55 +typedef int (*ca_proplist_destroy_fn) (ca_proplist *); 1.56 +typedef int (*ca_proplist_sets_fn) (ca_proplist *c, 1.57 + const char *key, 1.58 + const char *value); 1.59 +typedef int (*ca_context_play_full_fn) (ca_context *c, 1.60 + uint32_t id, 1.61 + ca_proplist *p, 1.62 + ca_finish_callback_t cb, 1.63 + void *userdata); 1.64 + 1.65 +static ca_context_create_fn ca_context_create; 1.66 +static ca_context_destroy_fn ca_context_destroy; 1.67 +static ca_context_play_fn ca_context_play; 1.68 +static ca_context_change_props_fn ca_context_change_props; 1.69 +static ca_proplist_create_fn ca_proplist_create; 1.70 +static ca_proplist_destroy_fn ca_proplist_destroy; 1.71 +static ca_proplist_sets_fn ca_proplist_sets; 1.72 +static ca_context_play_full_fn ca_context_play_full; 1.73 + 1.74 +struct ScopedCanberraFile { 1.75 + ScopedCanberraFile(nsIFile *file): mFile(file) {}; 1.76 + 1.77 + ~ScopedCanberraFile() { 1.78 + if (mFile) { 1.79 + mFile->Remove(false); 1.80 + } 1.81 + } 1.82 + 1.83 + void forget() { 1.84 + mFile.forget(); 1.85 + } 1.86 + nsIFile* operator->() { return mFile; } 1.87 + operator nsIFile*() { return mFile; } 1.88 + 1.89 + nsCOMPtr<nsIFile> mFile; 1.90 +}; 1.91 + 1.92 +static ca_context* 1.93 +ca_context_get_default() 1.94 +{ 1.95 + // This allows us to avoid race conditions with freeing the context by handing that 1.96 + // responsibility to Glib, and still use one context at a time 1.97 + static GStaticPrivate ctx_static_private = G_STATIC_PRIVATE_INIT; 1.98 + 1.99 + ca_context* ctx = (ca_context*) g_static_private_get(&ctx_static_private); 1.100 + 1.101 + if (ctx) { 1.102 + return ctx; 1.103 + } 1.104 + 1.105 + ca_context_create(&ctx); 1.106 + if (!ctx) { 1.107 + return nullptr; 1.108 + } 1.109 + 1.110 + g_static_private_set(&ctx_static_private, ctx, (GDestroyNotify) ca_context_destroy); 1.111 + 1.112 + GtkSettings* settings = gtk_settings_get_default(); 1.113 + if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings), 1.114 + "gtk-sound-theme-name")) { 1.115 + gchar* sound_theme_name = nullptr; 1.116 + g_object_get(settings, "gtk-sound-theme-name", &sound_theme_name, 1.117 + nullptr); 1.118 + 1.119 + if (sound_theme_name) { 1.120 + ca_context_change_props(ctx, "canberra.xdg-theme.name", 1.121 + sound_theme_name, nullptr); 1.122 + g_free(sound_theme_name); 1.123 + } 1.124 + } 1.125 + 1.126 + nsCOMPtr<nsIStringBundleService> bundleService = 1.127 + mozilla::services::GetStringBundleService(); 1.128 + if (bundleService) { 1.129 + nsCOMPtr<nsIStringBundle> brandingBundle; 1.130 + bundleService->CreateBundle("chrome://branding/locale/brand.properties", 1.131 + getter_AddRefs(brandingBundle)); 1.132 + if (brandingBundle) { 1.133 + nsAutoString wbrand; 1.134 + brandingBundle->GetStringFromName(MOZ_UTF16("brandShortName"), 1.135 + getter_Copies(wbrand)); 1.136 + NS_ConvertUTF16toUTF8 brand(wbrand); 1.137 + 1.138 + ca_context_change_props(ctx, "application.name", brand.get(), 1.139 + nullptr); 1.140 + } 1.141 + } 1.142 + 1.143 + nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1"); 1.144 + if (appInfo) { 1.145 + nsAutoCString version; 1.146 + appInfo->GetVersion(version); 1.147 + 1.148 + ca_context_change_props(ctx, "application.version", version.get(), 1.149 + nullptr); 1.150 + } 1.151 + 1.152 + ca_context_change_props(ctx, "application.icon_name", MOZ_APP_NAME, 1.153 + nullptr); 1.154 + 1.155 + return ctx; 1.156 +} 1.157 + 1.158 +static void 1.159 +ca_finish_cb(ca_context *c, 1.160 + uint32_t id, 1.161 + int error_code, 1.162 + void *userdata) 1.163 +{ 1.164 + nsIFile *file = reinterpret_cast<nsIFile *>(userdata); 1.165 + if (file) { 1.166 + file->Remove(false); 1.167 + NS_RELEASE(file); 1.168 + } 1.169 +} 1.170 + 1.171 +NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver) 1.172 + 1.173 +//////////////////////////////////////////////////////////////////////// 1.174 +nsSound::nsSound() 1.175 +{ 1.176 + mInited = false; 1.177 +} 1.178 + 1.179 +nsSound::~nsSound() 1.180 +{ 1.181 +} 1.182 + 1.183 +NS_IMETHODIMP 1.184 +nsSound::Init() 1.185 +{ 1.186 + // This function is designed so that no library is compulsory, and 1.187 + // one library missing doesn't cause the other(s) to not be used. 1.188 + if (mInited) 1.189 + return NS_OK; 1.190 + 1.191 + mInited = true; 1.192 + 1.193 + if (!libcanberra) { 1.194 + libcanberra = PR_LoadLibrary("libcanberra.so.0"); 1.195 + if (libcanberra) { 1.196 + ca_context_create = (ca_context_create_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_create"); 1.197 + if (!ca_context_create) { 1.198 + PR_UnloadLibrary(libcanberra); 1.199 + libcanberra = nullptr; 1.200 + } else { 1.201 + // at this point we know we have a good libcanberra library 1.202 + ca_context_destroy = (ca_context_destroy_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_destroy"); 1.203 + ca_context_play = (ca_context_play_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_play"); 1.204 + ca_context_change_props = (ca_context_change_props_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_change_props"); 1.205 + ca_proplist_create = (ca_proplist_create_fn) PR_FindFunctionSymbol(libcanberra, "ca_proplist_create"); 1.206 + ca_proplist_destroy = (ca_proplist_destroy_fn) PR_FindFunctionSymbol(libcanberra, "ca_proplist_destroy"); 1.207 + ca_proplist_sets = (ca_proplist_sets_fn) PR_FindFunctionSymbol(libcanberra, "ca_proplist_sets"); 1.208 + ca_context_play_full = (ca_context_play_full_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_play_full"); 1.209 + } 1.210 + } 1.211 + } 1.212 + 1.213 + return NS_OK; 1.214 +} 1.215 + 1.216 +/* static */ void 1.217 +nsSound::Shutdown() 1.218 +{ 1.219 + if (libcanberra) { 1.220 + PR_UnloadLibrary(libcanberra); 1.221 + libcanberra = nullptr; 1.222 + } 1.223 +} 1.224 + 1.225 +NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader, 1.226 + nsISupports *context, 1.227 + nsresult aStatus, 1.228 + uint32_t dataLen, 1.229 + const uint8_t *data) 1.230 +{ 1.231 + // print a load error on bad status, and return 1.232 + if (NS_FAILED(aStatus)) { 1.233 +#ifdef DEBUG 1.234 + if (aLoader) { 1.235 + nsCOMPtr<nsIRequest> request; 1.236 + aLoader->GetRequest(getter_AddRefs(request)); 1.237 + if (request) { 1.238 + nsCOMPtr<nsIURI> uri; 1.239 + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); 1.240 + if (channel) { 1.241 + channel->GetURI(getter_AddRefs(uri)); 1.242 + if (uri) { 1.243 + nsAutoCString uriSpec; 1.244 + uri->GetSpec(uriSpec); 1.245 + printf("Failed to load %s\n", uriSpec.get()); 1.246 + } 1.247 + } 1.248 + } 1.249 + } 1.250 +#endif 1.251 + return aStatus; 1.252 + } 1.253 + 1.254 + nsCOMPtr<nsIFile> tmpFile; 1.255 + nsDirectoryService::gService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsIFile), 1.256 + getter_AddRefs(tmpFile)); 1.257 + 1.258 + nsresult rv = tmpFile->AppendNative(nsDependentCString("mozilla_audio_sample")); 1.259 + if (NS_FAILED(rv)) { 1.260 + return rv; 1.261 + } 1.262 + 1.263 + rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, PR_IRUSR | PR_IWUSR); 1.264 + if (NS_FAILED(rv)) { 1.265 + return rv; 1.266 + } 1.267 + 1.268 + ScopedCanberraFile canberraFile(tmpFile); 1.269 + 1.270 + mozilla::AutoFDClose fd; 1.271 + rv = canberraFile->OpenNSPRFileDesc(PR_WRONLY, PR_IRUSR | PR_IWUSR, 1.272 + &fd.rwget()); 1.273 + if (NS_FAILED(rv)) { 1.274 + return rv; 1.275 + } 1.276 + 1.277 + // XXX: Should we do this on another thread? 1.278 + uint32_t length = dataLen; 1.279 + while (length > 0) { 1.280 + int32_t amount = PR_Write(fd, data, length); 1.281 + if (amount < 0) { 1.282 + return NS_ERROR_FAILURE; 1.283 + } 1.284 + length -= amount; 1.285 + data += amount; 1.286 + } 1.287 + 1.288 + ca_context* ctx = ca_context_get_default(); 1.289 + if (!ctx) { 1.290 + return NS_ERROR_OUT_OF_MEMORY; 1.291 + } 1.292 + 1.293 + ca_proplist *p; 1.294 + ca_proplist_create(&p); 1.295 + if (!p) { 1.296 + return NS_ERROR_OUT_OF_MEMORY; 1.297 + } 1.298 + 1.299 + nsAutoCString path; 1.300 + rv = canberraFile->GetNativePath(path); 1.301 + if (NS_FAILED(rv)) { 1.302 + return rv; 1.303 + } 1.304 + 1.305 + ca_proplist_sets(p, "media.filename", path.get()); 1.306 + if (ca_context_play_full(ctx, 0, p, ca_finish_cb, canberraFile) >= 0) { 1.307 + // Don't delete the temporary file here if ca_context_play_full succeeds 1.308 + canberraFile.forget(); 1.309 + } 1.310 + ca_proplist_destroy(p); 1.311 + 1.312 + return NS_OK; 1.313 +} 1.314 + 1.315 +NS_METHOD nsSound::Beep() 1.316 +{ 1.317 + ::gdk_beep(); 1.318 + return NS_OK; 1.319 +} 1.320 + 1.321 +NS_METHOD nsSound::Play(nsIURL *aURL) 1.322 +{ 1.323 + if (!mInited) 1.324 + Init(); 1.325 + 1.326 + if (!libcanberra) 1.327 + return NS_ERROR_NOT_AVAILABLE; 1.328 + 1.329 + bool isFile; 1.330 + nsresult rv = aURL->SchemeIs("file", &isFile); 1.331 + if (NS_SUCCEEDED(rv) && isFile) { 1.332 + ca_context* ctx = ca_context_get_default(); 1.333 + if (!ctx) { 1.334 + return NS_ERROR_OUT_OF_MEMORY; 1.335 + } 1.336 + 1.337 + nsAutoCString spec; 1.338 + rv = aURL->GetSpec(spec); 1.339 + if (NS_FAILED(rv)) { 1.340 + return rv; 1.341 + } 1.342 + gchar *path = g_filename_from_uri(spec.get(), nullptr, nullptr); 1.343 + if (!path) { 1.344 + return NS_ERROR_FILE_UNRECOGNIZED_PATH; 1.345 + } 1.346 + 1.347 + ca_context_play(ctx, 0, "media.filename", path, nullptr); 1.348 + g_free(path); 1.349 + } else { 1.350 + nsCOMPtr<nsIStreamLoader> loader; 1.351 + rv = NS_NewStreamLoader(getter_AddRefs(loader), aURL, this); 1.352 + } 1.353 + 1.354 + return rv; 1.355 +} 1.356 + 1.357 +NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId) 1.358 +{ 1.359 + if (!mInited) 1.360 + Init(); 1.361 + 1.362 + if (!libcanberra) 1.363 + return NS_OK; 1.364 + 1.365 + // Do we even want alert sounds? 1.366 + GtkSettings* settings = gtk_settings_get_default(); 1.367 + 1.368 + if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings), 1.369 + "gtk-enable-event-sounds")) { 1.370 + gboolean enable_sounds = TRUE; 1.371 + g_object_get(settings, "gtk-enable-event-sounds", &enable_sounds, nullptr); 1.372 + 1.373 + if (!enable_sounds) { 1.374 + return NS_OK; 1.375 + } 1.376 + } 1.377 + 1.378 + ca_context* ctx = ca_context_get_default(); 1.379 + if (!ctx) { 1.380 + return NS_ERROR_OUT_OF_MEMORY; 1.381 + } 1.382 + 1.383 + switch (aEventId) { 1.384 + case EVENT_ALERT_DIALOG_OPEN: 1.385 + ca_context_play(ctx, 0, "event.id", "dialog-warning", nullptr); 1.386 + break; 1.387 + case EVENT_CONFIRM_DIALOG_OPEN: 1.388 + ca_context_play(ctx, 0, "event.id", "dialog-question", nullptr); 1.389 + break; 1.390 + case EVENT_NEW_MAIL_RECEIVED: 1.391 + ca_context_play(ctx, 0, "event.id", "message-new-email", nullptr); 1.392 + break; 1.393 + case EVENT_MENU_EXECUTE: 1.394 + ca_context_play(ctx, 0, "event.id", "menu-click", nullptr); 1.395 + break; 1.396 + case EVENT_MENU_POPUP: 1.397 + ca_context_play(ctx, 0, "event.id", "menu-popup", nullptr); 1.398 + break; 1.399 + } 1.400 + return NS_OK; 1.401 +} 1.402 + 1.403 +NS_IMETHODIMP nsSound::PlaySystemSound(const nsAString &aSoundAlias) 1.404 +{ 1.405 + if (NS_IsMozAliasSound(aSoundAlias)) { 1.406 + NS_WARNING("nsISound::playSystemSound is called with \"_moz_\" events, they are obsolete, use nsISound::playEventSound instead"); 1.407 + uint32_t eventId; 1.408 + if (aSoundAlias.Equals(NS_SYSSOUND_ALERT_DIALOG)) 1.409 + eventId = EVENT_ALERT_DIALOG_OPEN; 1.410 + else if (aSoundAlias.Equals(NS_SYSSOUND_CONFIRM_DIALOG)) 1.411 + eventId = EVENT_CONFIRM_DIALOG_OPEN; 1.412 + else if (aSoundAlias.Equals(NS_SYSSOUND_MAIL_BEEP)) 1.413 + eventId = EVENT_NEW_MAIL_RECEIVED; 1.414 + else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_EXECUTE)) 1.415 + eventId = EVENT_MENU_EXECUTE; 1.416 + else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_POPUP)) 1.417 + eventId = EVENT_MENU_POPUP; 1.418 + else 1.419 + return NS_OK; 1.420 + return PlayEventSound(eventId); 1.421 + } 1.422 + 1.423 + nsresult rv; 1.424 + nsCOMPtr <nsIURI> fileURI; 1.425 + 1.426 + // create a nsIFile and then a nsIFileURL from that 1.427 + nsCOMPtr <nsIFile> soundFile; 1.428 + rv = NS_NewLocalFile(aSoundAlias, true, 1.429 + getter_AddRefs(soundFile)); 1.430 + NS_ENSURE_SUCCESS(rv,rv); 1.431 + 1.432 + rv = NS_NewFileURI(getter_AddRefs(fileURI), soundFile); 1.433 + NS_ENSURE_SUCCESS(rv,rv); 1.434 + 1.435 + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI,&rv); 1.436 + NS_ENSURE_SUCCESS(rv,rv); 1.437 + 1.438 + rv = Play(fileURL); 1.439 + 1.440 + return rv; 1.441 +}