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 /*****************************************************************************/
15 #define INCL_WINSHELLDATA
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
38 #define DBG_MSG(x) fprintf(stderr, x "\n")
43 // the number of defined Mozilla events (see nsISound.idl)
46 /*****************************************************************************/
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
);
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).
76 /*****************************************************************************/
82 /*****************************************************************************/
84 NS_IMETHODIMP
nsSound::Init()
89 /*****************************************************************************/
91 NS_IMETHODIMP
nsSound::Beep()
93 WinAlarm(HWND_DESKTOP
, WA_WARNING
);
97 /*****************************************************************************/
99 // All attempts to play a sound file should be routed through this method.
101 NS_IMETHODIMP
nsSound::Play(nsIURL
*aURL
)
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
,
122 if (NS_FAILED(aStatus
)) {
125 nsCOMPtr
<nsIRequest
> request
;
126 aLoader
->GetRequest(getter_AddRefs(request
));
128 nsCOMPtr
<nsIURI
> uri
;
129 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(request
);
131 channel
->GetURI(getter_AddRefs(uri
));
133 nsAutoCString uriSpec
;
134 uri
->GetSpec(uriSpec
);
135 fprintf(stderr
, "nsSound::OnStreamComplete: failed to load %s\n",
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
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");
170 /*****************************************************************************/
172 // This is obsolete and shouldn't get called (in theory).
174 NS_IMETHODIMP
nsSound::PlaySystemSound(const nsAString
&aSoundAlias
)
176 if (aSoundAlias
.IsEmpty())
179 if (NS_IsMozAliasSound(aSoundAlias
)) {
180 DBG_MSG("nsISound::playSystemSound was called with \"_moz_\" events, "
181 "they are obsolete, use nsISound::playEventSound instead");
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
;
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
)
217 aEventId
< EVENT_CNT
&&
218 sSoundFiles
[aEventId
] &&
219 NS_SUCCEEDED(PlaySoundFile(
220 nsDependentCString(sSoundFiles
[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
230 case EVENT_CONFIRM_DIALOG_OPEN
:
231 WinAlarm(HWND_DESKTOP
, WA_NOTE
); // play "Information" sound
233 case EVENT_PROMPT_DIALOG_OPEN
:
234 case EVENT_SELECT_DIALOG_OPEN
:
235 case EVENT_MENU_EXECUTE
:
236 case EVENT_MENU_POPUP
:
243 /*****************************************************************************/
245 // Convert a UCS file path to native charset, then play it.
247 nsresult
nsSound::PlaySoundFile(const nsAString
&aSoundFile
)
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
)
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
;
307 // Confirm mmpm.ini exists where it's expected to prevent
308 // PrfOpenProfile() from creating a new empty file there.
310 char buffer
[CCHMAXPATH
];
312 ULONG rc
= DosScanEnv("MMBASE", const_cast<const char **>(&ptr
));
315 ptr
= strchr(buffer
, ';');
317 ptr
= strchr(buffer
, 0);
319 strcpy(ptr
, "\\MMPM.INI");
320 rc
= DosQueryPathInfo(buffer
, FIL_STANDARD
, &fs3
, sizeof(fs3
));
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
));
331 DBG_MSG("initSounds: unable to locate mmpm.ini");
335 HINI hini
= PrfOpenProfile(0, buffer
);
337 DBG_MSG("initSounds: unable to open mmpm.ini");
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",
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
++) {
354 ultoa(i
+ baseNdx
, key
, 10);
355 if (!PrfQueryProfileString(hini
, "MMPM2_AlarmSounds", key
,
356 0, buffer
, sizeof(buffer
))) {
359 ptr
= strchr(buffer
, '#');
360 if (!ptr
|| ptr
== buffer
) {
364 if (DosQueryPathInfo(buffer
, FIL_STANDARD
, &fs3
, sizeof(fs3
)) ||
365 (fs3
.attrFile
& FILE_DIRECTORY
) || !fs3
.cbFile
) {
368 sSoundFiles
[i
] = strdup(buffer
);
371 PrfCloseProfile(hini
);
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
;
389 HMODULE hmodMMIO
= 0;
392 if (DosLoadModule(szError
, sizeof(szError
), "MMIO", &hmodMMIO
) ||
393 DosLoadModule(szError
, sizeof(szError
), "MDM", &hmodMDM
)) {
394 DBG_MSG("initDlls: DosLoadModule failed");
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");
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
)
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
);
433 DBG_MSG("playSound: unknown sound format");
437 hmmio
= _mmioOpen(NULL
, &mi
, MMIO_READ
| MMIO_DENYWRITE
);
439 DBG_MSG("playSound: _mmioOpen failed");
443 // open the sound device
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
,
452 DBG_MSG("playSound: MCI_OPEN failed");
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");
473 WinAlarm(HWND_DESKTOP
, WA_WARNING
);
476 _mmioClose(hmmio
, 0);
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
)
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');
498 if (memcmp(aData
, "ID3", 3) == 0) // likely MP3 with ID3 header
499 fcc
= mmioFOURCC('M','P','3',' ');
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',' ');
507 if (memcmp(aData
, "OggS", 4) == 0) // OGG
508 fcc
= mmioFOURCC('O','G','G','S');
510 if (memcmp(aData
, "fLaC", 4) == 0) // FLAC
511 fcc
= mmioFOURCC('f','L','a','C');
516 /*****************************************************************************/