Bug 867104 - Add a crashtest. r=ehsan
[gecko.git] / widget / os2 / nsSound.cpp
blob6ff68a4546008ada82e8a1016fe48a979e6871ce
1 /* vim: set sw=2 sts=2 et cin: */
2 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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 /*****************************************************************************/
10 #include <stdio.h>
11 #include <string.h>
12 #include <stdlib.h>
14 #define INCL_DOS
15 #define INCL_WINSHELLDATA
16 #define INCL_MMIOOS2
17 #include <os2.h>
18 #include <mmioos2.h>
19 #include <mcios2.h>
21 #include "nsSound.h"
22 #include "nsIURL.h"
23 #include "nsNetUtil.h"
24 #include "nsNativeCharsetUtils.h"
26 NS_IMPL_ISUPPORTS2(nsSound, nsISound, nsIStreamLoaderObserver)
28 /*****************************************************************************/
30 // argument structure to pass to the background thread
31 typedef struct _ARGBUFFER
33 uint32_t bufLen;
34 char buffer[1];
35 } ARGBUFFER;
37 #ifdef DEBUG
38 #define DBG_MSG(x) fprintf(stderr, x "\n")
39 #else
40 #define DBG_MSG(x)
41 #endif
43 // the number of defined Mozilla events (see nsISound.idl)
44 #define EVENT_CNT 7
46 /*****************************************************************************/
48 // static variables
49 static bool sDllError = FALSE; // set if the MMOS2 dlls fail to load
50 static char * sSoundFiles[EVENT_CNT] = {0}; // an array of sound file names
52 // function pointer definitions (underscore works around redef. warning)
53 static HMMIO (*APIENTRY _mmioOpen)(PSZ, PMMIOINFO, ULONG);
54 static USHORT (*APIENTRY _mmioClose)(HMMIO, USHORT);
55 static ULONG (*APIENTRY _mciSendCommand)(USHORT, USHORT, ULONG, PVOID, USHORT);
57 // helper functions
58 static void initSounds(void);
59 static bool initDlls(void);
60 static void playSound(void *aArgs);
61 static FOURCC determineFourCC(uint32_t aDataLen, const char *aData);
63 /*****************************************************************************/
64 /* nsSound implementation */
65 /*****************************************************************************/
67 // Get the list of sound files associated with mozilla events the first time
68 // this class is instantiated. However, defer initialization of the MMOS2
69 // dlls until they're actually needed (which may be never).
71 nsSound::nsSound()
73 initSounds();
76 /*****************************************************************************/
78 nsSound::~nsSound()
82 /*****************************************************************************/
84 NS_IMETHODIMP nsSound::Init()
86 return NS_OK;
89 /*****************************************************************************/
91 NS_IMETHODIMP nsSound::Beep()
93 WinAlarm(HWND_DESKTOP, WA_WARNING);
94 return NS_OK;
97 /*****************************************************************************/
99 // All attempts to play a sound file should be routed through this method.
101 NS_IMETHODIMP nsSound::Play(nsIURL *aURL)
103 if (sDllError) {
104 DBG_MSG("nsSound::Play: MMOS2 initialization failed");
105 return NS_ERROR_FAILURE;
108 nsCOMPtr<nsIStreamLoader> loader;
109 return NS_NewStreamLoader(getter_AddRefs(loader), aURL, this);
112 /*****************************************************************************/
114 // After a sound has been loaded, start a new thread to play it.
116 NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader,
117 nsISupports *context,
118 nsresult aStatus,
119 uint32_t dataLen,
120 const uint8_t *data)
122 if (NS_FAILED(aStatus)) {
123 #ifdef DEBUG
124 if (aLoader) {
125 nsCOMPtr<nsIRequest> request;
126 aLoader->GetRequest(getter_AddRefs(request));
127 if (request) {
128 nsCOMPtr<nsIURI> uri;
129 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
130 if (channel) {
131 channel->GetURI(getter_AddRefs(uri));
132 if (uri) {
133 nsAutoCString uriSpec;
134 uri->GetSpec(uriSpec);
135 fprintf(stderr, "nsSound::OnStreamComplete: failed to load %s\n",
136 uriSpec.get());
141 #endif
142 return NS_ERROR_FAILURE;
145 // allocate a buffer to hold the ARGBUFFER struct and sound data;
146 // try using high-memory - if that fails, try low-memory
147 ARGBUFFER * arg;
148 if (DosAllocMem((PPVOID)&arg, sizeof(ARGBUFFER) + dataLen,
149 OBJ_ANY | PAG_READ | PAG_WRITE | PAG_COMMIT)) {
150 if (DosAllocMem((PPVOID)&arg, sizeof(ARGBUFFER) + dataLen,
151 PAG_READ | PAG_WRITE | PAG_COMMIT)) {
152 DBG_MSG("nsSound::OnStreamComplete: DosAllocMem failed");
153 return NS_ERROR_FAILURE;
157 // copy the sound data
158 arg->bufLen = dataLen;
159 memcpy(arg->buffer, data, dataLen);
161 // Play the sound on a new thread using MMOS2
162 if (_beginthread(playSound, NULL, 32768, (void*)arg) < 0) {
163 DosFreeMem((void*)arg);
164 DBG_MSG("nsSound::OnStreamComplete: _beginthread failed");
167 return NS_OK;
170 /*****************************************************************************/
172 // This is obsolete and shouldn't get called (in theory).
174 NS_IMETHODIMP nsSound::PlaySystemSound(const nsAString &aSoundAlias)
176 if (aSoundAlias.IsEmpty())
177 return NS_OK;
179 if (NS_IsMozAliasSound(aSoundAlias)) {
180 DBG_MSG("nsISound::playSystemSound was called with \"_moz_\" events, "
181 "they are obsolete, use nsISound::playEventSound instead");
183 uint32_t eventId;
184 if (aSoundAlias.Equals(NS_SYSSOUND_MAIL_BEEP)) {
185 eventId = EVENT_NEW_MAIL_RECEIVED;
186 } else if (aSoundAlias.Equals(NS_SYSSOUND_ALERT_DIALOG)) {
187 eventId = EVENT_ALERT_DIALOG_OPEN;
188 } else if (aSoundAlias.Equals(NS_SYSSOUND_CONFIRM_DIALOG)) {
189 eventId = EVENT_CONFIRM_DIALOG_OPEN;
190 } else if (aSoundAlias.Equals(NS_SYSSOUND_PROMPT_DIALOG)) {
191 eventId = EVENT_PROMPT_DIALOG_OPEN;
192 } else if (aSoundAlias.Equals(NS_SYSSOUND_SELECT_DIALOG)) {
193 eventId = EVENT_SELECT_DIALOG_OPEN;
194 } else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_EXECUTE)) {
195 eventId = EVENT_MENU_EXECUTE;
196 } else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_POPUP)) {
197 eventId = EVENT_MENU_POPUP;
198 } else {
199 return NS_OK;
201 return PlayEventSound(eventId);
204 // assume aSoundAlias is a file name
205 return PlaySoundFile(aSoundAlias);
208 /*****************************************************************************/
210 // Attempt to play whatever event sounds the user has enabled.
211 // If the attempt fails or a sound isn't set, fall back to a
212 // beep for selected events.
214 NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId)
216 if (!sDllError &&
217 aEventId < EVENT_CNT &&
218 sSoundFiles[aEventId] &&
219 NS_SUCCEEDED(PlaySoundFile(
220 nsDependentCString(sSoundFiles[aEventId])))) {
221 return NS_OK;
224 switch(aEventId) {
225 case EVENT_NEW_MAIL_RECEIVED:
226 return Beep(); // play "Warning" sound
227 case EVENT_ALERT_DIALOG_OPEN:
228 WinAlarm(HWND_DESKTOP, WA_ERROR); // play "Error" sound
229 break;
230 case EVENT_CONFIRM_DIALOG_OPEN:
231 WinAlarm(HWND_DESKTOP, WA_NOTE); // play "Information" sound
232 break;
233 case EVENT_PROMPT_DIALOG_OPEN:
234 case EVENT_SELECT_DIALOG_OPEN:
235 case EVENT_MENU_EXECUTE:
236 case EVENT_MENU_POPUP:
237 break;
240 return NS_OK;
243 /*****************************************************************************/
245 // Convert a UCS file path to native charset, then play it.
247 nsresult nsSound::PlaySoundFile(const nsAString &aSoundFile)
249 nsAutoCString buf;
250 nsresult rv = NS_CopyUnicodeToNative(aSoundFile, buf);
251 NS_ENSURE_SUCCESS(rv,rv);
253 return PlaySoundFile(buf);
256 /*****************************************************************************/
258 // Take a native charset path, convert it to a file URL, then play the file.
260 nsresult nsSound::PlaySoundFile(const nsACString &aSoundFile)
262 nsresult rv;
263 nsCOMPtr <nsIFile> soundFile;
264 rv = NS_NewNativeLocalFile(aSoundFile, false,
265 getter_AddRefs(soundFile));
266 NS_ENSURE_SUCCESS(rv,rv);
268 nsCOMPtr <nsIURI> fileURI;
269 rv = NS_NewFileURI(getter_AddRefs(fileURI), soundFile);
270 NS_ENSURE_SUCCESS(rv,rv);
272 nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI,&rv);
273 NS_ENSURE_SUCCESS(rv,rv);
275 return Play(fileURL);
278 /*****************************************************************************/
279 /* static helper functions */
280 /*****************************************************************************/
282 // This function loads the names of sound files associated with Mozilla
283 // events from mmpm.ini. Because of the overhead, it only makes one
284 // attempt per session and caches the results.
286 // Mozilla event sounds can be added to mmpm.ini via a REXX script,
287 // and can be edited using the 'Sound' object in the System Setup folder.
288 // mmpm.ini entries have this format: [fq_path#event_name#volume]
289 // 'fq_path' identifies the file; 'event_name' is the description that
290 // appears in the Sound object's listbox; 'volume' is a numeric string
291 // used by the WPS. Here's the REXX command to add a "New Mail" sound:
292 // result = SysIni('C:\MMOS2\MMPM.INI', 'MMPM2_AlarmSounds', '800',
293 // 'C:\MMOS2\SOUNDS\NOTES.WAV#New Mail#80');
294 // Note that the key value is a numeric string. The base index for
295 // Mozilla event keys is an arbitrary number that defaults to 800 if
296 // an entry for "MOZILLA_Events\BaseIndex" can't be found in mmpm.ini.
298 static void initSounds(void)
300 static bool sSoundInit = FALSE;
302 if (sSoundInit) {
303 return;
305 sSoundInit = TRUE;
307 // Confirm mmpm.ini exists where it's expected to prevent
308 // PrfOpenProfile() from creating a new empty file there.
309 FILESTATUS3 fs3;
310 char buffer[CCHMAXPATH];
311 char * ptr;
312 ULONG rc = DosScanEnv("MMBASE", const_cast<const char **>(&ptr));
313 if (!rc) {
314 strcpy(buffer, ptr);
315 ptr = strchr(buffer, ';');
316 if (!ptr) {
317 ptr = strchr(buffer, 0);
319 strcpy(ptr, "\\MMPM.INI");
320 rc = DosQueryPathInfo(buffer, FIL_STANDARD, &fs3, sizeof(fs3));
322 if (rc) {
323 ULONG ulBootDrive = 0;
324 strcpy(buffer, "x:\\MMOS2\\MMPM.INI");
325 DosQuerySysInfo( QSV_BOOT_DRIVE, QSV_BOOT_DRIVE,
326 &ulBootDrive, sizeof ulBootDrive);
327 buffer[0] = 0x40 + ulBootDrive;
328 rc = DosQueryPathInfo(buffer, FIL_STANDARD, &fs3, sizeof(fs3));
330 if (rc) {
331 DBG_MSG("initSounds: unable to locate mmpm.ini");
332 return;
335 HINI hini = PrfOpenProfile(0, buffer);
336 if (!hini) {
337 DBG_MSG("initSounds: unable to open mmpm.ini");
338 return;
341 // If a base index has been set, use it, provided it doesn't
342 // collide with the system-defined indices (0 - 12)
343 LONG baseNdx = PrfQueryProfileInt(hini, "MOZILLA_Events",
344 "BaseIndex", 800);
345 if (baseNdx <= 12) {
346 baseNdx = 800;
349 // For each event, see if there's an entry in mmpm.ini.
350 // If so, extract the file path, confirm it's valid,
351 // then duplicate the string & save it for later use.
352 for (LONG i = 0; i < EVENT_CNT; i++) {
353 char key[16];
354 ultoa(i + baseNdx, key, 10);
355 if (!PrfQueryProfileString(hini, "MMPM2_AlarmSounds", key,
356 0, buffer, sizeof(buffer))) {
357 continue;
359 ptr = strchr(buffer, '#');
360 if (!ptr || ptr == buffer) {
361 continue;
363 *ptr = 0;
364 if (DosQueryPathInfo(buffer, FIL_STANDARD, &fs3, sizeof(fs3)) ||
365 (fs3.attrFile & FILE_DIRECTORY) || !fs3.cbFile) {
366 continue;
368 sSoundFiles[i] = strdup(buffer);
371 PrfCloseProfile(hini);
372 return;
375 /*****************************************************************************/
377 // Only one attempt is made per session to initialize MMOS2. Once
378 // the dlls are loaded, they remain loaded to avoid stability issues.
380 static bool initDlls(void)
382 static bool sDllInit = FALSE;
384 if (sDllInit) {
385 return TRUE;
387 sDllInit = TRUE;
389 HMODULE hmodMMIO = 0;
390 HMODULE hmodMDM = 0;
391 char szError[32];
392 if (DosLoadModule(szError, sizeof(szError), "MMIO", &hmodMMIO) ||
393 DosLoadModule(szError, sizeof(szError), "MDM", &hmodMDM)) {
394 DBG_MSG("initDlls: DosLoadModule failed");
395 sDllError = TRUE;
396 return FALSE;
399 if (DosQueryProcAddr(hmodMMIO, 0L, "mmioOpen", (PFN *)&_mmioOpen) ||
400 DosQueryProcAddr(hmodMMIO, 0L, "mmioClose", (PFN *)&_mmioClose) ||
401 DosQueryProcAddr(hmodMDM, 0L, "mciSendCommand", (PFN *)&_mciSendCommand)) {
402 DBG_MSG("initDlls: DosQueryProcAddr failed");
403 sDllError = TRUE;
404 return FALSE;
407 return TRUE;
410 /*****************************************************************************/
412 // Background thread proc to play the sound that was set up in
413 // the argument structure. If an error occurs, beep at least.
414 // aArgs is allocated from system memory & must be freed.
416 static void playSound(void * aArgs)
418 BOOL fOK = FALSE;
419 HMMIO hmmio = 0;
420 MMIOINFO mi;
422 do {
423 if (!initDlls())
424 break;
426 memset(&mi, 0, sizeof(MMIOINFO));
427 mi.cchBuffer = ((ARGBUFFER*)aArgs)->bufLen;
428 mi.pchBuffer = ((ARGBUFFER*)aArgs)->buffer;
429 mi.ulTranslate = MMIO_TRANSLATEDATA | MMIO_TRANSLATEHEADER;
430 mi.fccChildIOProc = FOURCC_MEM;
431 mi.fccIOProc = determineFourCC(mi.cchBuffer, mi.pchBuffer);
432 if (!mi.fccIOProc) {
433 DBG_MSG("playSound: unknown sound format");
434 break;
437 hmmio = _mmioOpen(NULL, &mi, MMIO_READ | MMIO_DENYWRITE);
438 if (!hmmio) {
439 DBG_MSG("playSound: _mmioOpen failed");
440 break;
443 // open the sound device
444 MCI_OPEN_PARMS mop;
445 memset(&mop, 0, sizeof(mop));
446 mop.pszElementName = (PSZ)hmmio;
447 mop.pszDeviceType = (PSZ)MAKEULONG(MCI_DEVTYPE_WAVEFORM_AUDIO, 0);
448 if (_mciSendCommand(0, MCI_OPEN,
449 MCI_OPEN_MMIO | MCI_OPEN_TYPE_ID |
450 MCI_OPEN_SHAREABLE | MCI_WAIT,
451 (PVOID)&mop, 0)) {
452 DBG_MSG("playSound: MCI_OPEN failed");
453 break;
455 fOK = TRUE;
457 // play the sound
458 MCI_PLAY_PARMS mpp;
459 memset(&mpp, 0, sizeof(mpp));
460 if (_mciSendCommand(mop.usDeviceID, MCI_PLAY, MCI_WAIT, &mpp, 0)) {
461 DBG_MSG("playSound: MCI_PLAY failed");
464 // stop & close the device
465 _mciSendCommand(mop.usDeviceID, MCI_STOP, MCI_WAIT, &mpp, 0);
466 if (_mciSendCommand(mop.usDeviceID, MCI_CLOSE, MCI_WAIT, &mpp, 0)) {
467 DBG_MSG("playSound: MCI_CLOSE failed");
470 } while (0);
472 if (!fOK)
473 WinAlarm(HWND_DESKTOP, WA_WARNING);
475 if (hmmio)
476 _mmioClose(hmmio, 0);
477 DosFreeMem(aArgs);
479 _endthread();
482 /*****************************************************************************/
484 // Try to determine the data format in the buffer using file "magic".
485 // Returns the FourCC handle for the format, or 0 when failing to
486 // find format and codec.
488 static FOURCC determineFourCC(uint32_t aDataLen, const char *aData)
490 FOURCC fcc = 0;
492 // Compare the first bytes of the data with magic to determine
493 // the most likely format (other possible formats are ignored
494 // because the mmio procs for them are too unreliable)
495 if (memcmp(aData, "RIFF", 4) == 0) // WAV
496 fcc = mmioFOURCC('W', 'A', 'V', 'E');
497 else
498 if (memcmp(aData, "ID3", 3) == 0) // likely MP3 with ID3 header
499 fcc = mmioFOURCC('M','P','3',' ');
500 else
501 if ((aData[0] & 0xFF) == 0xFF && // various versions of MPEG layer 3
502 ((aData[1] & 0xFE) == 0xFA || // v1
503 (aData[1] & 0xFE) == 0xF2 || // v2
504 (aData[1] & 0xFE) == 0xE2)) // v2.5
505 fcc = mmioFOURCC('M','P','3',' ');
506 else
507 if (memcmp(aData, "OggS", 4) == 0) // OGG
508 fcc = mmioFOURCC('O','G','G','S');
509 else
510 if (memcmp(aData, "fLaC", 4) == 0) // FLAC
511 fcc = mmioFOURCC('f','L','a','C');
513 return fcc;
516 /*****************************************************************************/