widget/gtk/nsSound.cpp

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

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

mercurial