[OS/2] Bug 509330 - rewrite nsSound, r=pweilbacher

This commit is contained in:
Rich Walsh 2009-10-23 13:30:38 +02:00
Родитель a7cca2c611
Коммит 4872835c4b
3 изменённых файлов: 795 добавлений и 406 удалений

Просмотреть файл

@ -0,0 +1,369 @@
/* mozillasounds.cmd */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is a supplemental OS/2 script.
*
* The Initial Developer of the Original Code is
* Rich Walsh <dragtext@e-vertise.com>
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/*****************************************************************************/
/* */
/* MozSounds.cmd enables the user to associate sounds with Mozilla events */
/* using the WPS Sound object in the System Setup folder. It does not set */
/* the sounds itself - it simply adds entries to the Sound object's list of */
/* events. This script only needs to be run once or twice: the first time */
/* to enable selected sounds, and the second time to disable most of them */
/* because they're so annoying. */
/* */
/* This script's design is coordinated with code in widget\os2\nsSound.cpp. */
/* Please don't make significant changes to it (e.g. changing the names of */
/* ini-file entries) without first examining nsSound.cpp. */
/* */
/* Note to Translators: everything that needs to be translated has been */
/* grouped together and placed toward the end of the file (a heading */
/* identifies where to start). Please preserve the formatting and don't */
/* change any of the numeric values. Thanks... */
/* */
/*****************************************************************************/
call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
call SysLoadFuncs
call InitVariables
call Main
exit
/*****************************************************************************/
Main:
/* Get the location of the MMOS2 directory. */
path = value('MMBASE',,'OS2ENVIRONMENT')
if path = '' then do
call SysCls
call EnvError
return
end
path = strip(path, B, ';')
/* Confirm that mmpm.ini can be found where we expect it to be. */
iniFile = path'\MMPM.INI'
call SysFileTree iniFile, 'stem', 'FO'
if stem.0 <> 1 then do
call SysCls
call IniError
return
end
/* Make a backup of mmpm.ini if one doesn't already exist. */
call SysFileTree iniFile'.BAK', 'stem', 'FO'
if stem.0 = 0 then
'@xcopy' iniFile path'\*.*.BAK /T > NUL'
/* Events are identified by number - MMOS2 uses 0-12. Mozilla events */
/* start at 800. If this conflicts with another app, the base index */
/* can be changed. The new value is stored in mmpm.ini where it can */
/* be accessed by the Mozilla apps and this script. */
baseIndex = SysIni(iniFile, 'MOZILLA_Events', 'BaseIndex')
baseIndex = strip(baseIndex, T, X2C('0'))
if baseIndex = 'ERROR:' | baseIndex <= '12' then
baseIndex = defaultIndex
/* The main loop: display current status & respond to commands */
do FOREVER
call SysCls
call GetStatus
call Display
pull cmd parms
select
when cmd = soundCmd then do
rc = SysOpenObject('<WP_SOUND>', 2, 'TRUE')
leave
end
when cmd = exitCmd then do
leave
end
when cmd = disableCmd then do
call Disable
end
when cmd = enableCmd then do
call Enable
end
/* this command is "undocumented" & should NOT be translated */
when cmd = 'BASEINDEX' then do
call ChangeBaseIndex
end
otherwise
end
end
return
/*****************************************************************************/
/* Generate a listing of Mozilla events and their status: */
/* 'enabled' if there's an entry, 'disabled' if not. */
GetStatus:
ctr = 1
show = 0
do nbr = 1 to events.0
rc = SysIni(iniFile, 'MMPM2_AlarmSounds', baseIndex + events.nbr.ndx)
if rc = 'ERROR:' then
status = disabledWord
else
status = enabledWord
if show = 0 then do
out = " "nbr". " left(events.nbr.name, 15) status
show = 1
end
else do
line.ctr = left(out, 35) " " nbr". " left(events.nbr.name, 15) status
ctr = ctr + 1
show = 0
end
end
if show = 1 then
line.ctr = left(out, 35)
return
/*****************************************************************************/
/* Disable an event sound by deleting its entry. */
Disable:
parse var parms nbr parms
if nbr = allWord then do
nbr = '1'
parms = '2 3 4 5 6 7'
end
do while nbr <> ''
if nbr >= '1' & nbr <= '7' then do
key = baseIndex + events.nbr.ndx
rc = SysIni(iniFile, 'MMPM2_AlarmSounds', key, 'DELETE:')
end
parse var parms nbr parms
end
return
/*****************************************************************************/
/* Enable an event sound by adding an entry whose format is: */
/* 'fq_soundfile#event_name#volume'. */
/* Since this script isn't intended to set the actual soundfile, */
/* it uses the same dummy value as MMOS2: 'x:\MMOS2\SOUNDS\' */
Enable:
parse var parms nbr parms
if nbr = allWord then do
nbr = '1'
parms = '2 3 4 5 6 7'
end
do while nbr <> ''
if nbr >= '1' & nbr <= '7' then do
key = baseIndex + events.nbr.ndx
/* if there's an existing entry, preserve the filename */
sndFile = SysIni(iniFile, 'MMPM2_AlarmSounds', key)
if sndFile = 'ERROR:' | left(sndFile, 1) = '#' then
sndFile = path'\'soundsDir'\'
else
parse var sndFile sndFile '#' .
value = sndFile'#'events.nbr.name' (Mozilla)#80'X2C('0')
rc = SysIni(iniFile, 'MMPM2_AlarmSounds', key, value)
end
parse var parms nbr parms
end
return
/*****************************************************************************/
/* An "undocumented" function to change & restore the base index. */
/* It renumbers existing entries using the new base & adds or deletes */
/* a 'MOZILLA_Events\BaseIndex' entry depending on the new value. */
ChangeBaseIndex:
parse var parms newIndex parms
/* Ignore invalid values. */
if newIndex = '' | newIndex <= '12' then
return
/* Do NOT translate this. */
if newIndex = 'DEFAULT' then
newIndex = defaultIndex
/* If there's no change, exit after deleting the entry if it's the default */
if newIndex = baseIndex then do
if baseIndex = defaultIndex then
rc SysIni(iniFile, 'MOZILLA_Events', 'BaseIndex', 'DELETE:')
return
end
/* Move existing entries from the old index to the new index. */
do nbr = 1 to events.0
value = SysIni(iniFile, 'MMPM2_AlarmSounds', baseIndex + events.nbr.ndx)
if value = 'ERROR:' then
iterate
rc = SysIni(iniFile, 'MMPM2_AlarmSounds', baseIndex + events.nbr.ndx, 'DELETE:')
rc = SysIni(iniFile, 'MMPM2_AlarmSounds', newIndex + events.nbr.ndx, value)
end
/* If the new index is the default, delete the ini entry; */
/* otherwise, add or update the entry with the new value. */
if newIndex = defaultIndex then
rc = SysIni(iniFile, 'MOZILLA_Events', 'BaseIndex', 'DELETE:')
else
rc = SysIni(iniFile, 'MOZILLA_Events', 'BaseIndex', newIndex||X2C('0'))
baseIndex = newIndex
return
/*****************************************************************************/
/* All strings that need to be translated appear below */
/*****************************************************************************/
/* Just like it says, init variables. */
InitVariables:
events.0 = 7
events.1.ndx = 0
events.1.name = 'New Mail'
events.2.ndx = 1
events.2.name = 'Alert Dialog'
events.3.ndx = 2
events.3.name = 'Confirm Dialog'
events.4.ndx = 3
events.4.name = 'Prompt Dialog'
events.5.ndx = 4
events.5.name = 'Select Dialog'
events.6.ndx = 5
events.6.name = 'Menu Execute'
events.7.ndx = 6
events.7.name = 'Menu Popup'
enabledWord = 'enabled'
disabledWord = 'disabled'
allWord = 'ALL'
soundsDir = 'SOUNDS'
enableCmd = 'E'
disableCmd = 'D'
soundCmd = 'S'
exitCmd = 'X'
defaultIndex = '800'
return
/*****************************************************************************/
/* Display event status & command info. */
Display:
say
say " MozSounds.cmd lets you use the WPS Sound object in your System Setup"
say " folder to assign sounds to the Mozilla events listed below. It does"
say " NOT set the sound itself - it just adds and deletes entries in Sound."
say " New or changed sounds take effect after the Mozilla app is restarted."
say
say " Event Status Event Status"
say " ----------- -------- ----------- --------"
say line.1
say line.2
say line.3
say line.4
say
say " Commands:"
say " E - enable event sound(s) example: 'E 1' or 'E 3 5 7' or 'E All'"
say " D - disable event sound(s) example: 'D 2' or 'D 1 4 6' or 'D All'"
say " S - open the WPS Sound object"
say " X - exit this script"
say
call charout ," Enter a command > "
return
/*****************************************************************************/
/* Display an error message */
EnvError:
say ""
say "ERROR: the 'MMBASE' environment variable is missing or invalid!"
say " Your Mozilla app won't be able to play system sounds without it."
say " Please add a line to config.sys like the following, then reboot."
say " SET MMBASE=x:\MMOS2 (where 'x' is the correct drive)"
return
/*****************************************************************************/
/* Display an error message */
IniError:
say ""
say "ERROR: file '"iniFile"' is missing or invalid!"
say " Your Mozilla app won't be able to play system sounds without it."
say " Please confirm that the 'SET MMBASE=' line in config.sys points"
say " at your MMOS2 directory and that it contains 'MMPM.INI'."
return
/*****************************************************************************/

Просмотреть файл

@ -27,6 +27,7 @@
* IBM Corp.
* Peter Weilbacher <mozilla@Weilbacher.org>
* Lars Erdmann
* Rich Walsh <dragtext@e-vertise.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -42,369 +43,114 @@
*
* ***** END LICENSE BLOCK ***** */
#include "nscore.h"
#include "plstr.h"
/*****************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define INCL_DOS
#define INCL_DOSERRORS
#define INCL_WINSHELLDATA
#define INCL_MMIOOS2
#include <os2.h>
#include <mmioos2.h>
#include <mcios2.h>
#define MCI_ERROR_LENGTH 128
#include "nsSound.h"
#include "nsIURL.h"
#include "nsNetUtil.h"
#include "nsString.h"
#include "nsDirectoryServiceDefs.h"
#include "nsNativeCharsetUtils.h"
NS_IMPL_ISUPPORTS2(nsSound, nsISound, nsIStreamLoaderObserver)
static int sInitialized = 0;
static PRBool sMMPMInstalled = PR_FALSE;
static HMODULE sHModMMIO = NULLHANDLE;
// function pointer definitions, include underscore (work around redef. warning)
HMMIO (*APIENTRY _mmioOpen)(PSZ, PMMIOINFO, ULONG);
USHORT (*APIENTRY _mmioClose)(HMMIO, USHORT);
ULONG (*APIENTRY _mmioGetFormats)(PMMFORMATINFO, LONG, PVOID, PLONG, ULONG, ULONG);
ULONG (*APIENTRY _mciSendCommand)(USHORT, USHORT, ULONG, PVOID, USHORT);
#ifdef DEBUG
ULONG (*APIENTRY _mmioGetLastError)(HMMIO);
ULONG (*APIENTRY _mmioQueryFormatCount)(PMMFORMATINFO, PLONG, ULONG, ULONG);
ULONG (*APIENTRY _mmioGetFormatName)(PMMFORMATINFO, PSZ, PLONG, ULONG, ULONG);
ULONG (*APIENTRY _mciGetErrorString)(ULONG, PSZ, USHORT);
#endif
ULONG (*APIENTRY _mmioIniFileHandler)(PMMINIFILEINFO, ULONG);
/*****************************************************************************/
// argument structure to pass to the background thread
typedef struct _ARGBUFFER
{
HEV hev;
PRUint32 bufLen;
const char *buffer;
PSZ pszFilename;
PRUint32 bufLen;
char buffer[1];
} ARGBUFFER;
////////////////////////////////////////////////////////////////////////
static void InitGlobals(void)
{
ULONG ulrc = 0;
char LoadError[CCHMAXPATH];
HMODULE hModMDM = NULLHANDLE;
ulrc = DosLoadModule(LoadError, CCHMAXPATH, "MMIO", &sHModMMIO);
ulrc += DosLoadModule(LoadError, CCHMAXPATH, "MDM", &hModMDM);
if (ulrc == NO_ERROR) {
#ifdef DEBUG
printf("InitGlobals: MMOS2 is installed, both DLLs loaded\n");
#endif
sMMPMInstalled = PR_TRUE;
// MMOS2 is installed, so we can query the necessary functions
// mmio functions are in MMIO.DLL
ulrc = DosQueryProcAddr(sHModMMIO, 0L, "mmioOpen", (PFN *)&_mmioOpen);
ulrc += DosQueryProcAddr(sHModMMIO, 0L, "mmioClose", (PFN *)&_mmioClose);
ulrc += DosQueryProcAddr(sHModMMIO, 0L, "mmioGetFormats", (PFN *)&_mmioGetFormats);
// mci functions are in MDM.DLL
ulrc += DosQueryProcAddr(hModMDM, 0L, "mciSendCommand", (PFN *)&_mciSendCommand);
#ifdef DEBUG
ulrc += DosQueryProcAddr(sHModMMIO, 0L, "mmioGetLastError", (PFN *)&_mmioGetLastError);
ulrc += DosQueryProcAddr(sHModMMIO, 0L, "mmioQueryFormatCount", (PFN *)&_mmioQueryFormatCount);
ulrc += DosQueryProcAddr(sHModMMIO, 0L, "mmioGetFormatName", (PFN *)&_mmioGetFormatName);
ulrc += DosQueryProcAddr(hModMDM, 0L, "mciGetErrorString", (PFN *)&_mciGetErrorString);
#define DBG_MSG(x) fprintf(stderr, x "\n")
#else
#define DBG_MSG(x)
#endif
ulrc += DosQueryProcAddr(sHModMMIO, 0L, "mmioIniFileHandler", (PFN *)&_mmioIniFileHandler);
// the number of defined Mozilla events (see nsISound.idl)
#define EVENT_CNT 7
// if one of these failed, we have some kind of non-functional MMOS2 installation
if (ulrc != NO_ERROR) {
NS_WARNING("MMOS2 is installed, but seems to have corrupt DLLs");
sMMPMInstalled = PR_FALSE;
}
}
}
/*****************************************************************************/
////////////////////////////////////////////////////////////////////////
// static variables
static PRBool sDllError = FALSE; // set if the MMOS2 dlls fail to load
static char * sSoundFiles[EVENT_CNT] = {0}; // an array of sound file names
// Tries to determine the data format in the buffer using file "magic"
// and a loop through MMOS2 audio codecs.
// Returns the FourCC handle for the format, or 0 when failing to find format
// and codec.
FOURCC determineFourCC(PRUint32 aDataLen, const char *aData)
{
FOURCC fcc = 0;
// 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);
// Start to compare the first bytes of the data with magic to determine the
// most likely format upfront.
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
((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');
}
// helper functions
static void initSounds(void);
static PRBool initDlls(void);
static void playSound(void *aArgs);
static FOURCC determineFourCC(PRUint32 aDataLen, const char *aData);
// The following is too flakey because several OS/2 IOProc don't behave as
// they should and would cause us to crash. So just skip this for now...
#if 0
if (fcc) // already found one
return fcc;
/*****************************************************************************/
/* nsSound implementation */
/*****************************************************************************/
// None of the popular formats found, so use the list of MMOS2 audio codecs to
// find one that can open the file.
MMFORMATINFO mmfi;
LONG lNum;
memset(&mmfi, '\0', sizeof(mmfi));
mmfi.ulStructLen = sizeof(mmfi);
mmfi.ulMediaType |= MMIO_MEDIATYPE_AUDIO;
ULONG ulrc = _mmioQueryFormatCount(&mmfi, &lNum, 0L, 0L);
PMMFORMATINFO mmflist = (PMMFORMATINFO)calloc(lNum, sizeof(MMFORMATINFO));
LONG lFormats;
ulrc = _mmioGetFormats(&mmfi, lNum, mmflist, &lFormats, 0L, 0L);
MMIOINFO mi;
memset(&mi, '\0', sizeof(mi));
mi.fccChildIOProc = FOURCC_MEM;
unsigned char szBuffer[sizeof(FOURCC) + CCHMAXPATH + 4];
for (int i = lFormats-1; i >= 0; i--) {
// Loop through formats. Do it backwards to find at least WAV before the
// faulty VORBIS/FLAC/MP3 IOProcs that will open any format.
MMFORMATINFO mmfi = mmflist[i];
#ifdef DEBUG
LONG lBytesRead;
_mmioGetFormatName(&mmfi, (char *)szBuffer, &lBytesRead, 0L, 0L);
printf("determineFour Codec %d: name=%s media=0x%lx ext=%s fcc=%c%c%c%c/%ld/%p\n",
i, szBuffer, mmfi.ulMediaType, mmfi.szDefaultFormatExt,
(char)(mmfi.fccIOProc), (char)(mmfi.fccIOProc >> 8),
(char)(mmfi.fccIOProc >> 16), (char)(mmfi.fccIOProc >> 24),
mmfi.fccIOProc, (void *)mmfi.fccIOProc);
#endif
// this codec likely crashes the program when the buffer is not of the
// expected format
if (mmfi.fccIOProc == mmioFOURCC('A','V','C','A')) {
continue;
}
mi.fccIOProc = mmfi.fccIOProc;
HMMIO hmmio= _mmioOpen(NULL, &mi, MMIO_READ);
if (hmmio) {
fcc = mmfi.fccIOProc;
_mmioClose(hmmio, 0);
break;
}
}
free(mmflist);
#endif
#ifdef DEBUG
printf("determineFourCC: Codec fcc is 0x%lx or --%c%c%c%c--\n", fcc,
(char)(fcc), (char)(fcc >> 8), (char)(fcc >> 16), (char)(fcc >> 24));
#endif
return fcc;
}
// Play the sound that was set up in the argument structure. If an error occurs,
// beep at least. To be used as function for a new background thread.
static void playSound(void *aArgs)
{
ULONG ulrc = NO_ERROR;
ARGBUFFER args;
memcpy(&args, aArgs, sizeof(args));
MMIOINFO mi;
memset(&mi, '\0', sizeof(mi));
HMMIO hmmio = NULLHANDLE;
do { // inner block (break in case of error)
if (args.pszFilename) {
// determine size of file that we want to read
FILESTATUS3 fs3;
memset(&fs3, '\0', sizeof(fs3));
ulrc = DosQueryPathInfo(args.pszFilename, FIL_STANDARD, &fs3, sizeof(fs3));
mi.cchBuffer = fs3.cbFile;
} else {
// use size of the existing buffer
mi.cchBuffer = args.bufLen;
}
// Read or copy the sound into a local memory buffer for easy playback.
// (If we got the sound in a buffer originally, that buffer could
// "disappear" while we are still playing it.)
ulrc = DosAllocMem((PPVOID)&mi.pchBuffer, mi.cchBuffer,
#ifdef OS2_HIGH_MEMORY /* only if compiled with high-memory */
OBJ_ANY | /* support, we can allocate anywhere! */
#endif
PAG_READ | PAG_WRITE | PAG_COMMIT);
if (ulrc != NO_ERROR) {
#ifdef DEBUG
printf("playSound: Could not allocate the sound buffer, ulrc=%ld\n", ulrc);
#endif
break;
}
if (args.pszFilename) {
// read the sound from file into memory
HFILE hf = NULLHANDLE;
ULONG ulAction = 0;
ulrc = DosOpen(args.pszFilename, &hf, &ulAction, 0, FILE_NORMAL,
OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_FAIL_IF_NEW,
OPEN_ACCESS_READONLY | OPEN_SHARE_DENYNONE,
NULL);
if (ulrc != NO_ERROR) {
#ifdef DEBUG
printf("playSound: could not open the sound file \"%s\" (%ld)\n",
args.pszFilename, ulrc);
#endif
break;
}
ULONG ulRead = 0;
ulrc = DosRead(hf, mi.pchBuffer, mi.cchBuffer, &ulRead);
DosClose(hf);
if (ulrc != NO_ERROR) {
#ifdef DEBUG
printf("playSound: read %ld of %ld bytes from the sound file \"%s\" (%ld)\n",
ulRead, mi.cchBuffer, args.pszFilename, ulrc);
#endif
break;
}
} else {
// copy the passed sound buffer into local memory
memcpy(mi.pchBuffer, args.buffer, args.bufLen);
}
DosPostEventSem(args.hev); // calling thread can continue
// Now the sound is loaded into memory in any case, play it from there.
mi.fccChildIOProc = FOURCC_MEM;
mi.fccIOProc = determineFourCC(mi.cchBuffer, mi.pchBuffer);
if (!mi.fccIOProc) {
NS_WARNING("playSound: unknown sound format in memory buffer");
break;
}
mi.ulTranslate = MMIO_TRANSLATEDATA | MMIO_TRANSLATEHEADER;
hmmio = _mmioOpen(NULL, &mi, MMIO_READ | MMIO_DENYWRITE);
if (!hmmio) {
#ifdef DEBUG
ULONG ulrc = _mmioGetLastError(hmmio);
if (args.pszFilename) {
printf("playSound: mmioOpen failed, cannot play sound from \"%s\" (%ld)\n",
args.pszFilename, ulrc);
} else {
printf("playSound: mmioOpen failed, cannot play sound buffer (%ld)\n",
ulrc);
}
#endif
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);
ulrc = _mciSendCommand(0, MCI_OPEN,
MCI_OPEN_MMIO | MCI_OPEN_TYPE_ID | MCI_OPEN_SHAREABLE | MCI_WAIT,
(PVOID)&mop, 0);
if (ulrc != MCIERR_SUCCESS) {
#ifdef DEBUG
CHAR errorBuffer[MCI_ERROR_LENGTH];
_mciGetErrorString(ulrc, errorBuffer, MCI_ERROR_LENGTH);
printf("playSound: mciSendCommand with MCI_OPEN_MMIO returned %ld: %s\n",
ulrc, errorBuffer);
#endif
break;
}
// play the sound
MCI_PLAY_PARMS mpp;
memset(&mpp, '\0', sizeof(mpp));
ulrc = _mciSendCommand(mop.usDeviceID, MCI_PLAY, MCI_WAIT, &mpp, 0);
#ifdef DEBUG
// just ignore further failures in non-debug mode
if (ulrc != MCIERR_SUCCESS) {
CHAR errorBuffer[MCI_ERROR_LENGTH];
_mciGetErrorString(ulrc, errorBuffer, MCI_ERROR_LENGTH);
printf("playSound: mciSendCommand with MCI_PLAY returned %ld: %s\n",
ulrc, errorBuffer);
}
#endif
// end playing
ulrc = _mciSendCommand(mop.usDeviceID, MCI_STOP, MCI_WAIT, &mpp, 0); // be nice
ulrc = _mciSendCommand(mop.usDeviceID, MCI_CLOSE, MCI_WAIT, &mpp, 0);
#ifdef DEBUG
if (ulrc != MCIERR_SUCCESS) {
CHAR errorBuffer[MCI_ERROR_LENGTH];
_mciGetErrorString(ulrc, errorBuffer, MCI_ERROR_LENGTH);
printf("playSound: mciSendCommand with MCI_CLOSE returned %ld: %s\n",
ulrc, errorBuffer);
}
#endif
_mmioClose(hmmio, 0);
DosFreeMem(mi.pchBuffer);
_endthread();
} while(0); // end of inner block
// cleanup after an error
WinAlarm(HWND_DESKTOP, WA_WARNING); // Beep()
if (hmmio)
_mmioClose(hmmio, 0);
if (mi.pchBuffer)
DosFreeMem(mi.pchBuffer);
}
////////////////////////////////////////////////////////////////////////
// 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()
{
if (!sInitialized) {
InitGlobals();
}
sInitialized++;
#ifdef DEBUG
printf("nsSound::nsSound: sInitialized=%d\n", sInitialized);
#endif
initSounds();
}
/*****************************************************************************/
nsSound::~nsSound()
{
sInitialized--;
#ifdef DEBUG
printf("nsSound::~nsSound: sInitialized=%d\n", sInitialized);
#endif
// (try to) unload modules after last user ended
if (!sInitialized) {
#ifdef DEBUG
printf("nsSound::~nsSound: Trying to free modules...\n");
#endif
ULONG ulrc;
ulrc = DosFreeModule(sHModMMIO);
// do not free MDM.DLL because it doesn't like to be unloaded repeatedly
if (ulrc != NO_ERROR) {
NS_WARNING("DosFreeModule did not work");
}
}
}
/*****************************************************************************/
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,
@ -420,12 +166,13 @@ NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader,
nsCOMPtr<nsIURI> uri;
nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
if (channel) {
channel->GetURI(getter_AddRefs(uri));
if (uri) {
nsCAutoString uriSpec;
uri->GetSpec(uriSpec);
printf("Failed to load %s\n", uriSpec.get());
}
channel->GetURI(getter_AddRefs(uri));
if (uri) {
nsCAutoString uriSpec;
uri->GetSpec(uriSpec);
fprintf(stderr, "nsSound::OnStreamComplete: failed to load %s\n",
uriSpec.get());
}
}
}
}
@ -433,107 +180,376 @@ NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader,
return NS_ERROR_FAILURE;
}
if (!sMMPMInstalled) {
NS_WARNING("Sound output only works with MMOS2 installed");
Beep();
return NS_OK;
// 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;
}
}
ARGBUFFER arg;
memset(&arg, '\0', sizeof(arg));
APIRET rc = DosCreateEventSem(NULL, &(arg.hev), 0UL, 0UL);
// copy the sound data
arg->bufLen = dataLen;
memcpy(arg->buffer, data, dataLen);
// Play the sound on a new thread using MMOS2, in this case pass
// the memory buffer in the argument structure.
arg.bufLen = dataLen;
arg.buffer = (char *)data;
_beginthread(playSound, NULL, 32768, (void *)&arg);
// Wait until the buffer was copied, but not indefinitely to not block the
// UI in case a really large sound file is copied.
rc = DosWaitEventSem(arg.hev, 100);
rc = DosCloseEventSem(arg.hev);
// 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;
}
NS_IMETHODIMP nsSound::Beep()
{
WinAlarm(HWND_DESKTOP, WA_WARNING);
/*****************************************************************************/
return NS_OK;
}
NS_IMETHODIMP nsSound::Play(nsIURL *aURL)
{
nsresult rv;
nsCOMPtr<nsIStreamLoader> loader;
rv = NS_NewStreamLoader(getter_AddRefs(loader), aURL, this);
return rv;
}
NS_IMETHODIMP nsSound::Init()
{
return NS_OK;
}
// This is obsolete and shouldn't get called (in theory).
NS_IMETHODIMP nsSound::PlaySystemSound(const nsAString &aSoundAlias)
{
if (!sMMPMInstalled) {
return Beep();
}
if (aSoundAlias.IsEmpty())
return NS_OK;
if (NS_IsMozAliasSound(aSoundAlias)) {
NS_WARNING("nsISound::playSystemSound is called with \"_moz_\" events, they are obsolete, use nsISound::playEventSound instead");
DBG_MSG("nsISound::playSystemSound was called with \"_moz_\" events, "
"they are obsolete, use nsISound::playEventSound instead");
PRUint32 eventId;
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_MAIL_BEEP))
eventId = EVENT_NEW_MAIL_RECEIVED;
else
return NS_OK;
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);
}
nsCAutoString nativeSoundAlias;
NS_CopyUnicodeToNative(aSoundAlias, nativeSoundAlias);
ARGBUFFER arg;
memset(&arg, '\0', sizeof(arg));
APIRET rc = DosCreateEventSem(NULL, &(arg.hev), 0UL, 0UL);
// Play the sound on a new thread using MMOS2, in this case pass
// the filename in the argument structure.
arg.pszFilename = (PSZ)nativeSoundAlias.get();
_beginthread(playSound, NULL, 32768, (void *)&arg);
// Try to wait a while until the file is loaded, but not too long...
rc = DosWaitEventSem(arg.hev, 100);
rc = DosCloseEventSem(arg.hev);
return NS_OK;
// 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(PRUint32 aEventId)
{
// Prompt dialog and select dialog sounds do not correspond to OS/2
// system sounds, ignore them. Ignore the menu sounds, too. Try to handle
// the rest. Skip the beeps on systems without MMPM, too many of them are
// confusing and annoying.
if (!sDllError &&
aEventId < EVENT_CNT &&
sSoundFiles[aEventId] &&
NS_SUCCEEDED(PlaySoundFile(
nsDependentCString(sSoundFiles[aEventId])))) {
return NS_OK;
}
switch(aEventId) {
case EVENT_NEW_MAIL_RECEIVED:
// We don't have a default mail sound on OS/2, so just "beep"
return Beep(); // this corresponds to the "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_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)
{
nsCAutoString 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 <nsILocalFile> soundFile;
rv = NS_NewNativeLocalFile(aSoundFile, PR_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 PRBool 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 PRBool initDlls(void)
{
static PRBool 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(PRUint32 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;
}
/*****************************************************************************/

Просмотреть файл

@ -54,6 +54,10 @@ public:
NS_DECL_ISUPPORTS
NS_DECL_NSISOUND
NS_DECL_NSISTREAMLOADEROBSERVER
protected:
nsresult PlaySoundFile(const nsAString &aSoundFile);
nsresult PlaySoundFile(const nsACString &aSoundFile);
};
#endif /* __nsSound_h__ */