From 4872835c4bb10c760349940998dd38dd2765aeb0 Mon Sep 17 00:00:00 2001 From: Rich Walsh Date: Fri, 23 Oct 2009 13:30:38 +0200 Subject: [PATCH] [OS/2] Bug 509330 - rewrite nsSound, r=pweilbacher --- widget/src/os2/MozSounds.cmd | 369 ++++++++++++++++ widget/src/os2/nsSound.cpp | 828 ++++++++++++++++++----------------- widget/src/os2/nsSound.h | 4 + 3 files changed, 795 insertions(+), 406 deletions(-) create mode 100644 widget/src/os2/MozSounds.cmd diff --git a/widget/src/os2/MozSounds.cmd b/widget/src/os2/MozSounds.cmd new file mode 100644 index 000000000000..d7836d4c5d69 --- /dev/null +++ b/widget/src/os2/MozSounds.cmd @@ -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 + * 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('', 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 + +/*****************************************************************************/ + diff --git a/widget/src/os2/nsSound.cpp b/widget/src/os2/nsSound.cpp index ee48d3904624..4e1569ddd001 100644 --- a/widget/src/os2/nsSound.cpp +++ b/widget/src/os2/nsSound.cpp @@ -27,6 +27,7 @@ * IBM Corp. * Peter Weilbacher * Lars Erdmann + * Rich Walsh * * 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 #include #include #define INCL_DOS -#define INCL_DOSERRORS +#define INCL_WINSHELLDATA #define INCL_MMIOOS2 #include #include #include -#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 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 uri; nsCOMPtr 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 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 soundFile; + rv = NS_NewNativeLocalFile(aSoundFile, PR_FALSE, + getter_AddRefs(soundFile)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr fileURI; + rv = NS_NewFileURI(getter_AddRefs(fileURI), soundFile); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr 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(&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; +} + +/*****************************************************************************/ + diff --git a/widget/src/os2/nsSound.h b/widget/src/os2/nsSound.h index eb40ae08ba31..bb3beb5debf8 100644 --- a/widget/src/os2/nsSound.h +++ b/widget/src/os2/nsSound.h @@ -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__ */