Coverage Report

Created: 2018-09-25 14:53

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