mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-02 01:48:05 +00:00
518 lines
16 KiB
C++
518 lines
16 KiB
C++
/* vim: set sw=2 sts=2 et cin: */
|
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/*****************************************************************************/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#define INCL_DOS
|
|
#define INCL_WINSHELLDATA
|
|
#define INCL_MMIOOS2
|
|
#include <os2.h>
|
|
#include <mmioos2.h>
|
|
#include <mcios2.h>
|
|
|
|
#include "nsSound.h"
|
|
#include "nsIURL.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsNativeCharsetUtils.h"
|
|
|
|
NS_IMPL_ISUPPORTS2(nsSound, nsISound, nsIStreamLoaderObserver)
|
|
|
|
/*****************************************************************************/
|
|
|
|
// argument structure to pass to the background thread
|
|
typedef struct _ARGBUFFER
|
|
{
|
|
uint32_t bufLen;
|
|
char buffer[1];
|
|
} ARGBUFFER;
|
|
|
|
#ifdef DEBUG
|
|
#define DBG_MSG(x) fprintf(stderr, x "\n")
|
|
#else
|
|
#define DBG_MSG(x)
|
|
#endif
|
|
|
|
// the number of defined Mozilla events (see nsISound.idl)
|
|
#define EVENT_CNT 7
|
|
|
|
/*****************************************************************************/
|
|
|
|
// static variables
|
|
static bool sDllError = FALSE; // set if the MMOS2 dlls fail to load
|
|
static char * sSoundFiles[EVENT_CNT] = {0}; // an array of sound file names
|
|
|
|
// function pointer definitions (underscore works around redef. warning)
|
|
static HMMIO (*APIENTRY _mmioOpen)(PSZ, PMMIOINFO, ULONG);
|
|
static USHORT (*APIENTRY _mmioClose)(HMMIO, USHORT);
|
|
static ULONG (*APIENTRY _mciSendCommand)(USHORT, USHORT, ULONG, PVOID, USHORT);
|
|
|
|
// helper functions
|
|
static void initSounds(void);
|
|
static bool initDlls(void);
|
|
static void playSound(void *aArgs);
|
|
static FOURCC determineFourCC(uint32_t aDataLen, const char *aData);
|
|
|
|
/*****************************************************************************/
|
|
/* nsSound implementation */
|
|
/*****************************************************************************/
|
|
|
|
// Get the list of sound files associated with mozilla events the first time
|
|
// this class is instantiated. However, defer initialization of the MMOS2
|
|
// dlls until they're actually needed (which may be never).
|
|
|
|
nsSound::nsSound()
|
|
{
|
|
initSounds();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
nsSound::~nsSound()
|
|
{
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
NS_IMETHODIMP nsSound::Init()
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
NS_IMETHODIMP nsSound::Beep()
|
|
{
|
|
WinAlarm(HWND_DESKTOP, WA_WARNING);
|
|
return NS_OK;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// All attempts to play a sound file should be routed through this method.
|
|
|
|
NS_IMETHODIMP nsSound::Play(nsIURL *aURL)
|
|
{
|
|
if (sDllError) {
|
|
DBG_MSG("nsSound::Play: MMOS2 initialization failed");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsIStreamLoader> loader;
|
|
return NS_NewStreamLoader(getter_AddRefs(loader), aURL, this);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// After a sound has been loaded, start a new thread to play it.
|
|
|
|
NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader,
|
|
nsISupports *context,
|
|
nsresult aStatus,
|
|
uint32_t dataLen,
|
|
const uint8_t *data)
|
|
{
|
|
if (NS_FAILED(aStatus)) {
|
|
#ifdef DEBUG
|
|
if (aLoader) {
|
|
nsCOMPtr<nsIRequest> request;
|
|
aLoader->GetRequest(getter_AddRefs(request));
|
|
if (request) {
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
|
|
if (channel) {
|
|
channel->GetURI(getter_AddRefs(uri));
|
|
if (uri) {
|
|
nsAutoCString uriSpec;
|
|
uri->GetSpec(uriSpec);
|
|
fprintf(stderr, "nsSound::OnStreamComplete: failed to load %s\n",
|
|
uriSpec.get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// allocate a buffer to hold the ARGBUFFER struct and sound data;
|
|
// try using high-memory - if that fails, try low-memory
|
|
ARGBUFFER * arg;
|
|
if (DosAllocMem((PPVOID)&arg, sizeof(ARGBUFFER) + dataLen,
|
|
OBJ_ANY | PAG_READ | PAG_WRITE | PAG_COMMIT)) {
|
|
if (DosAllocMem((PPVOID)&arg, sizeof(ARGBUFFER) + dataLen,
|
|
PAG_READ | PAG_WRITE | PAG_COMMIT)) {
|
|
DBG_MSG("nsSound::OnStreamComplete: DosAllocMem failed");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
// copy the sound data
|
|
arg->bufLen = dataLen;
|
|
memcpy(arg->buffer, data, dataLen);
|
|
|
|
// Play the sound on a new thread using MMOS2
|
|
if (_beginthread(playSound, NULL, 32768, (void*)arg) < 0) {
|
|
DosFreeMem((void*)arg);
|
|
DBG_MSG("nsSound::OnStreamComplete: _beginthread failed");
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// This is obsolete and shouldn't get called (in theory).
|
|
|
|
NS_IMETHODIMP nsSound::PlaySystemSound(const nsAString &aSoundAlias)
|
|
{
|
|
if (aSoundAlias.IsEmpty())
|
|
return NS_OK;
|
|
|
|
if (NS_IsMozAliasSound(aSoundAlias)) {
|
|
DBG_MSG("nsISound::playSystemSound was called with \"_moz_\" events, "
|
|
"they are obsolete, use nsISound::playEventSound instead");
|
|
|
|
uint32_t eventId;
|
|
if (aSoundAlias.Equals(NS_SYSSOUND_MAIL_BEEP)) {
|
|
eventId = EVENT_NEW_MAIL_RECEIVED;
|
|
} else if (aSoundAlias.Equals(NS_SYSSOUND_ALERT_DIALOG)) {
|
|
eventId = EVENT_ALERT_DIALOG_OPEN;
|
|
} else if (aSoundAlias.Equals(NS_SYSSOUND_CONFIRM_DIALOG)) {
|
|
eventId = EVENT_CONFIRM_DIALOG_OPEN;
|
|
} else if (aSoundAlias.Equals(NS_SYSSOUND_PROMPT_DIALOG)) {
|
|
eventId = EVENT_PROMPT_DIALOG_OPEN;
|
|
} else if (aSoundAlias.Equals(NS_SYSSOUND_SELECT_DIALOG)) {
|
|
eventId = EVENT_SELECT_DIALOG_OPEN;
|
|
} else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_EXECUTE)) {
|
|
eventId = EVENT_MENU_EXECUTE;
|
|
} else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_POPUP)) {
|
|
eventId = EVENT_MENU_POPUP;
|
|
} else {
|
|
return NS_OK;
|
|
}
|
|
return PlayEventSound(eventId);
|
|
}
|
|
|
|
// assume aSoundAlias is a file name
|
|
return PlaySoundFile(aSoundAlias);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// Attempt to play whatever event sounds the user has enabled.
|
|
// If the attempt fails or a sound isn't set, fall back to a
|
|
// beep for selected events.
|
|
|
|
NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId)
|
|
{
|
|
if (!sDllError &&
|
|
aEventId < EVENT_CNT &&
|
|
sSoundFiles[aEventId] &&
|
|
NS_SUCCEEDED(PlaySoundFile(
|
|
nsDependentCString(sSoundFiles[aEventId])))) {
|
|
return NS_OK;
|
|
}
|
|
|
|
switch(aEventId) {
|
|
case EVENT_NEW_MAIL_RECEIVED:
|
|
return Beep(); // play "Warning" sound
|
|
case EVENT_ALERT_DIALOG_OPEN:
|
|
WinAlarm(HWND_DESKTOP, WA_ERROR); // play "Error" sound
|
|
break;
|
|
case EVENT_CONFIRM_DIALOG_OPEN:
|
|
WinAlarm(HWND_DESKTOP, WA_NOTE); // play "Information" sound
|
|
break;
|
|
case EVENT_PROMPT_DIALOG_OPEN:
|
|
case EVENT_SELECT_DIALOG_OPEN:
|
|
case EVENT_MENU_EXECUTE:
|
|
case EVENT_MENU_POPUP:
|
|
break;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// Convert a UCS file path to native charset, then play it.
|
|
|
|
nsresult nsSound::PlaySoundFile(const nsAString &aSoundFile)
|
|
{
|
|
nsAutoCString buf;
|
|
nsresult rv = NS_CopyUnicodeToNative(aSoundFile, buf);
|
|
NS_ENSURE_SUCCESS(rv,rv);
|
|
|
|
return PlaySoundFile(buf);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// Take a native charset path, convert it to a file URL, then play the file.
|
|
|
|
nsresult nsSound::PlaySoundFile(const nsACString &aSoundFile)
|
|
{
|
|
nsresult rv;
|
|
nsCOMPtr <nsIFile> soundFile;
|
|
rv = NS_NewNativeLocalFile(aSoundFile, false,
|
|
getter_AddRefs(soundFile));
|
|
NS_ENSURE_SUCCESS(rv,rv);
|
|
|
|
nsCOMPtr <nsIURI> fileURI;
|
|
rv = NS_NewFileURI(getter_AddRefs(fileURI), soundFile);
|
|
NS_ENSURE_SUCCESS(rv,rv);
|
|
|
|
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI,&rv);
|
|
NS_ENSURE_SUCCESS(rv,rv);
|
|
|
|
return Play(fileURL);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* static helper functions */
|
|
/*****************************************************************************/
|
|
|
|
// This function loads the names of sound files associated with Mozilla
|
|
// events from mmpm.ini. Because of the overhead, it only makes one
|
|
// attempt per session and caches the results.
|
|
//
|
|
// Mozilla event sounds can be added to mmpm.ini via a REXX script,
|
|
// and can be edited using the 'Sound' object in the System Setup folder.
|
|
// mmpm.ini entries have this format: [fq_path#event_name#volume]
|
|
// 'fq_path' identifies the file; 'event_name' is the description that
|
|
// appears in the Sound object's listbox; 'volume' is a numeric string
|
|
// used by the WPS. Here's the REXX command to add a "New Mail" sound:
|
|
// result = SysIni('C:\MMOS2\MMPM.INI', 'MMPM2_AlarmSounds', '800',
|
|
// 'C:\MMOS2\SOUNDS\NOTES.WAV#New Mail#80');
|
|
// Note that the key value is a numeric string. The base index for
|
|
// Mozilla event keys is an arbitrary number that defaults to 800 if
|
|
// an entry for "MOZILLA_Events\BaseIndex" can't be found in mmpm.ini.
|
|
|
|
static void initSounds(void)
|
|
{
|
|
static bool sSoundInit = FALSE;
|
|
|
|
if (sSoundInit) {
|
|
return;
|
|
}
|
|
sSoundInit = TRUE;
|
|
|
|
// Confirm mmpm.ini exists where it's expected to prevent
|
|
// PrfOpenProfile() from creating a new empty file there.
|
|
FILESTATUS3 fs3;
|
|
char buffer[CCHMAXPATH];
|
|
char * ptr;
|
|
ULONG rc = DosScanEnv("MMBASE", const_cast<const char **>(&ptr));
|
|
if (!rc) {
|
|
strcpy(buffer, ptr);
|
|
ptr = strchr(buffer, ';');
|
|
if (!ptr) {
|
|
ptr = strchr(buffer, 0);
|
|
}
|
|
strcpy(ptr, "\\MMPM.INI");
|
|
rc = DosQueryPathInfo(buffer, FIL_STANDARD, &fs3, sizeof(fs3));
|
|
}
|
|
if (rc) {
|
|
ULONG ulBootDrive = 0;
|
|
strcpy(buffer, "x:\\MMOS2\\MMPM.INI");
|
|
DosQuerySysInfo( QSV_BOOT_DRIVE, QSV_BOOT_DRIVE,
|
|
&ulBootDrive, sizeof ulBootDrive);
|
|
buffer[0] = 0x40 + ulBootDrive;
|
|
rc = DosQueryPathInfo(buffer, FIL_STANDARD, &fs3, sizeof(fs3));
|
|
}
|
|
if (rc) {
|
|
DBG_MSG("initSounds: unable to locate mmpm.ini");
|
|
return;
|
|
}
|
|
|
|
HINI hini = PrfOpenProfile(0, buffer);
|
|
if (!hini) {
|
|
DBG_MSG("initSounds: unable to open mmpm.ini");
|
|
return;
|
|
}
|
|
|
|
// If a base index has been set, use it, provided it doesn't
|
|
// collide with the system-defined indices (0 - 12)
|
|
LONG baseNdx = PrfQueryProfileInt(hini, "MOZILLA_Events",
|
|
"BaseIndex", 800);
|
|
if (baseNdx <= 12) {
|
|
baseNdx = 800;
|
|
}
|
|
|
|
// For each event, see if there's an entry in mmpm.ini.
|
|
// If so, extract the file path, confirm it's valid,
|
|
// then duplicate the string & save it for later use.
|
|
for (LONG i = 0; i < EVENT_CNT; i++) {
|
|
char key[16];
|
|
ultoa(i + baseNdx, key, 10);
|
|
if (!PrfQueryProfileString(hini, "MMPM2_AlarmSounds", key,
|
|
0, buffer, sizeof(buffer))) {
|
|
continue;
|
|
}
|
|
ptr = strchr(buffer, '#');
|
|
if (!ptr || ptr == buffer) {
|
|
continue;
|
|
}
|
|
*ptr = 0;
|
|
if (DosQueryPathInfo(buffer, FIL_STANDARD, &fs3, sizeof(fs3)) ||
|
|
(fs3.attrFile & FILE_DIRECTORY) || !fs3.cbFile) {
|
|
continue;
|
|
}
|
|
sSoundFiles[i] = strdup(buffer);
|
|
}
|
|
|
|
PrfCloseProfile(hini);
|
|
return;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// Only one attempt is made per session to initialize MMOS2. Once
|
|
// the dlls are loaded, they remain loaded to avoid stability issues.
|
|
|
|
static bool initDlls(void)
|
|
{
|
|
static bool sDllInit = FALSE;
|
|
|
|
if (sDllInit) {
|
|
return TRUE;
|
|
}
|
|
sDllInit = TRUE;
|
|
|
|
HMODULE hmodMMIO = 0;
|
|
HMODULE hmodMDM = 0;
|
|
char szError[32];
|
|
if (DosLoadModule(szError, sizeof(szError), "MMIO", &hmodMMIO) ||
|
|
DosLoadModule(szError, sizeof(szError), "MDM", &hmodMDM)) {
|
|
DBG_MSG("initDlls: DosLoadModule failed");
|
|
sDllError = TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
if (DosQueryProcAddr(hmodMMIO, 0L, "mmioOpen", (PFN *)&_mmioOpen) ||
|
|
DosQueryProcAddr(hmodMMIO, 0L, "mmioClose", (PFN *)&_mmioClose) ||
|
|
DosQueryProcAddr(hmodMDM, 0L, "mciSendCommand", (PFN *)&_mciSendCommand)) {
|
|
DBG_MSG("initDlls: DosQueryProcAddr failed");
|
|
sDllError = TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// Background thread proc to play the sound that was set up in
|
|
// the argument structure. If an error occurs, beep at least.
|
|
// aArgs is allocated from system memory & must be freed.
|
|
|
|
static void playSound(void * aArgs)
|
|
{
|
|
BOOL fOK = FALSE;
|
|
HMMIO hmmio = 0;
|
|
MMIOINFO mi;
|
|
|
|
do {
|
|
if (!initDlls())
|
|
break;
|
|
|
|
memset(&mi, 0, sizeof(MMIOINFO));
|
|
mi.cchBuffer = ((ARGBUFFER*)aArgs)->bufLen;
|
|
mi.pchBuffer = ((ARGBUFFER*)aArgs)->buffer;
|
|
mi.ulTranslate = MMIO_TRANSLATEDATA | MMIO_TRANSLATEHEADER;
|
|
mi.fccChildIOProc = FOURCC_MEM;
|
|
mi.fccIOProc = determineFourCC(mi.cchBuffer, mi.pchBuffer);
|
|
if (!mi.fccIOProc) {
|
|
DBG_MSG("playSound: unknown sound format");
|
|
break;
|
|
}
|
|
|
|
hmmio = _mmioOpen(NULL, &mi, MMIO_READ | MMIO_DENYWRITE);
|
|
if (!hmmio) {
|
|
DBG_MSG("playSound: _mmioOpen failed");
|
|
break;
|
|
}
|
|
|
|
// open the sound device
|
|
MCI_OPEN_PARMS mop;
|
|
memset(&mop, 0, sizeof(mop));
|
|
mop.pszElementName = (PSZ)hmmio;
|
|
mop.pszDeviceType = (PSZ)MAKEULONG(MCI_DEVTYPE_WAVEFORM_AUDIO, 0);
|
|
if (_mciSendCommand(0, MCI_OPEN,
|
|
MCI_OPEN_MMIO | MCI_OPEN_TYPE_ID |
|
|
MCI_OPEN_SHAREABLE | MCI_WAIT,
|
|
(PVOID)&mop, 0)) {
|
|
DBG_MSG("playSound: MCI_OPEN failed");
|
|
break;
|
|
}
|
|
fOK = TRUE;
|
|
|
|
// play the sound
|
|
MCI_PLAY_PARMS mpp;
|
|
memset(&mpp, 0, sizeof(mpp));
|
|
if (_mciSendCommand(mop.usDeviceID, MCI_PLAY, MCI_WAIT, &mpp, 0)) {
|
|
DBG_MSG("playSound: MCI_PLAY failed");
|
|
}
|
|
|
|
// stop & close the device
|
|
_mciSendCommand(mop.usDeviceID, MCI_STOP, MCI_WAIT, &mpp, 0);
|
|
if (_mciSendCommand(mop.usDeviceID, MCI_CLOSE, MCI_WAIT, &mpp, 0)) {
|
|
DBG_MSG("playSound: MCI_CLOSE failed");
|
|
}
|
|
|
|
} while (0);
|
|
|
|
if (!fOK)
|
|
WinAlarm(HWND_DESKTOP, WA_WARNING);
|
|
|
|
if (hmmio)
|
|
_mmioClose(hmmio, 0);
|
|
DosFreeMem(aArgs);
|
|
|
|
_endthread();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// Try to determine the data format in the buffer using file "magic".
|
|
// Returns the FourCC handle for the format, or 0 when failing to
|
|
// find format and codec.
|
|
|
|
static FOURCC determineFourCC(uint32_t aDataLen, const char *aData)
|
|
{
|
|
FOURCC fcc = 0;
|
|
|
|
// Compare the first bytes of the data with magic to determine
|
|
// the most likely format (other possible formats are ignored
|
|
// because the mmio procs for them are too unreliable)
|
|
if (memcmp(aData, "RIFF", 4) == 0) // WAV
|
|
fcc = mmioFOURCC('W', 'A', 'V', 'E');
|
|
else
|
|
if (memcmp(aData, "ID3", 3) == 0) // likely MP3 with ID3 header
|
|
fcc = mmioFOURCC('M','P','3',' ');
|
|
else
|
|
if ((aData[0] & 0xFF) == 0xFF && // various versions of MPEG layer 3
|
|
((aData[1] & 0xFE) == 0xFA || // v1
|
|
(aData[1] & 0xFE) == 0xF2 || // v2
|
|
(aData[1] & 0xFE) == 0xE2)) // v2.5
|
|
fcc = mmioFOURCC('M','P','3',' ');
|
|
else
|
|
if (memcmp(aData, "OggS", 4) == 0) // OGG
|
|
fcc = mmioFOURCC('O','G','G','S');
|
|
else
|
|
if (memcmp(aData, "fLaC", 4) == 0) // FLAC
|
|
fcc = mmioFOURCC('f','L','a','C');
|
|
|
|
return fcc;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|