diff --git a/reactos/lib/winmm/lolvldrv.c b/reactos/lib/winmm/lolvldrv.c index 07a9cb6b22e..a9ad64472d8 100644 --- a/reactos/lib/winmm/lolvldrv.c +++ b/reactos/lib/winmm/lolvldrv.c @@ -95,6 +95,7 @@ void MMDRV_InstallMap(unsigned int drv, */ BOOL MMDRV_Is32(unsigned int idx) { + TRACE("(%d)\n", idx); return MMDrvs[idx].bIs32; } @@ -114,6 +115,7 @@ static BOOL MMDRV_GetDescription32(const char* fname, char* buf, int buflen) FARPROC pGetFileVersionInfoA; FARPROC pVerQueryValueA; HMODULE hmodule = 0; + TRACE("(%p, %p, %d)\n", fname, buf, buflen); #define E(_x) do {TRACE _x;goto theEnd;} while(0) @@ -164,6 +166,7 @@ theEnd: */ UINT MMDRV_GetNum(UINT type) { + TRACE("(%04x)\n", type); assert(type < MMDRV_MAX); return llTypes[type].wMaxId; } @@ -218,7 +221,7 @@ DWORD MMDRV_Message(LPWINE_MLD mld, UINT wMsg, DWORD_PTR dwParam1, TRACE("Calling message(dev=%u msg=%u usr=0x%08lx p1=0x%08lx p2=0x%08lx)\n", mld->uDeviceID, wMsg, mld->dwDriverInstance, dwParam1, dwParam2); ret = part->u.fnMessage32(mld->uDeviceID, wMsg, mld->dwDriverInstance, dwParam1, dwParam2); - TRACE("=> %lu\n", ret); + TRACE("=> %s\n", WINMM_ErrorToString(ret)); } else { map = llType->Map16To32A(wMsg, &mld->dwDriverInstance, &dwParam1, &dwParam2); switch (map) { @@ -235,7 +238,7 @@ DWORD MMDRV_Message(LPWINE_MLD mld, UINT wMsg, DWORD_PTR dwParam1, mld->uDeviceID, wMsg, mld->dwDriverInstance, dwParam1, dwParam2); ret = part->u.fnMessage32(mld->uDeviceID, wMsg, mld->dwDriverInstance, dwParam1, dwParam2); - TRACE("=> %lu\n", ret); + TRACE("=> %s\n", WINMM_ErrorToString(ret)); if (map == WINMM_MAP_OKMEM) llType->UnMap16To32A(wMsg, &mld->dwDriverInstance, &dwParam1, &dwParam2, ret); break; @@ -265,7 +268,7 @@ DWORD MMDRV_Message(LPWINE_MLD mld, UINT wMsg, DWORD_PTR dwParam1, ret = pFnCallMMDrvFunc16((DWORD)part->u.fnMessage16, mld->uDeviceID, wMsg, mld->dwDriverInstance, dwParam1, dwParam2); - TRACE("=> %lu\n", ret); + TRACE("=> %s\n", WINMM_ErrorToString(ret)); if (map == WINMM_MAP_OKMEM) llType->UnMap32ATo16(wMsg, &mld->dwDriverInstance, &dwParam1, &dwParam2, ret); break; @@ -280,7 +283,7 @@ DWORD MMDRV_Message(LPWINE_MLD mld, UINT wMsg, DWORD_PTR dwParam1, ret = pFnCallMMDrvFunc16((DWORD)part->u.fnMessage16, mld->uDeviceID, wMsg, mld->dwDriverInstance, dwParam1, dwParam2); - TRACE("=> %lu\n", ret); + TRACE("=> %s\n", WINMM_ErrorToString(ret)); } } return ret; @@ -294,6 +297,8 @@ LPWINE_MLD MMDRV_Alloc(UINT size, UINT type, LPHANDLE hndl, DWORD* dwFlags, { LPWINE_MLD mld; UINT i; + TRACE("(%d, %04x, %p, %p, %p, %p, %c)\n", + size, type, hndl, dwFlags, dwCallback, dwInstance, bFrom32?'Y':'N'); mld = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size); if (!mld) return NULL; @@ -338,6 +343,8 @@ LPWINE_MLD MMDRV_Alloc(UINT size, UINT type, LPHANDLE hndl, DWORD* dwFlags, */ void MMDRV_Free(HANDLE hndl, LPWINE_MLD mld) { + TRACE("(%p, %p)\n", hndl, mld); + if ((UINT)hndl & 0x8000) { unsigned idx = (UINT)hndl & ~0x8000; if (idx < sizeof(MM_MLDrvs) / sizeof(MM_MLDrvs[0])) { @@ -357,6 +364,7 @@ DWORD MMDRV_Open(LPWINE_MLD mld, UINT wMsg, DWORD dwParam1, DWORD dwFlags) DWORD dwRet = MMSYSERR_BADDEVICEID; DWORD dwInstance; WINE_LLTYPE* llType = &llTypes[mld->type]; + TRACE("(%p, %04x, 0x%08lx, 0x%08lx)\n", mld, wMsg, dwParam1, dwFlags); mld->dwDriverInstance = (DWORD)&dwInstance; @@ -398,6 +406,7 @@ DWORD MMDRV_Open(LPWINE_MLD mld, UINT wMsg, DWORD dwParam1, DWORD dwFlags) */ DWORD MMDRV_Close(LPWINE_MLD mld, UINT wMsg) { + TRACE("(%p, %04x)\n", mld, wMsg); return MMDRV_Message(mld, wMsg, 0L, 0L, TRUE); } @@ -406,6 +415,7 @@ DWORD MMDRV_Close(LPWINE_MLD mld, UINT wMsg) */ LPWINE_MLD MMDRV_GetByID(UINT uDevID, UINT type) { + TRACE("(%04x, %04x)\n", uDevID, type); if (uDevID < llTypes[type].wMaxId) return &llTypes[type].lpMlds[uDevID]; if ((uDevID == (UINT16)-1 || uDevID == (UINT)-1) && llTypes[type].nMapper != -1) @@ -420,6 +430,7 @@ LPWINE_MLD MMDRV_Get(HANDLE _hndl, UINT type, BOOL bCanBeID) { LPWINE_MLD mld = NULL; UINT hndl = (UINT)_hndl; + TRACE("(%p, %04x, %c)\n", _hndl, type, bCanBeID ? 'Y' : 'N'); assert(type < MMDRV_MAX); @@ -448,6 +459,8 @@ LPWINE_MLD MMDRV_GetRelated(HANDLE hndl, UINT srcType, BOOL bSrcCanBeID, UINT dstType) { LPWINE_MLD mld; + TRACE("(%p, %04x, %c, %04x)\n", + hndl, srcType, bSrcCanBeID ? 'Y' : 'N', dstType); if ((mld = MMDRV_Get(hndl, srcType, bSrcCanBeID)) != NULL) { WINE_MM_DRIVER_PART* part = &MMDrvs[mld->mmdIndex].parts[dstType]; @@ -520,6 +533,7 @@ static BOOL MMDRV_InitPerType(LPWINE_MM_DRIVER lpDrv, UINT type, UINT wMsg) DWORD ret; UINT count = 0; int i, k; + TRACE("(%p, %04x, %04x)\n", lpDrv, type, wMsg); part->nIDMin = part->nIDMax = 0; @@ -528,7 +542,7 @@ static BOOL MMDRV_InitPerType(LPWINE_MM_DRIVER lpDrv, UINT type, UINT wMsg) if (lpDrv->bIs32 && part->u.fnMessage32) { ret = part->u.fnMessage32(0, DRVM_INIT, 0L, 0L, 0L); - TRACE("DRVM_INIT => %08lx\n", ret); + TRACE("DRVM_INIT => %s\n", WINMM_ErrorToString(ret)); #if 0 ret = part->u.fnMessage32(0, DRVM_ENABLE, 0L, 0L, 0L); TRACE("DRVM_ENABLE => %08lx\n", ret); @@ -537,7 +551,7 @@ static BOOL MMDRV_InitPerType(LPWINE_MM_DRIVER lpDrv, UINT type, UINT wMsg) } else if (!lpDrv->bIs32 && part->u.fnMessage16 && pFnCallMMDrvFunc16) { ret = pFnCallMMDrvFunc16((DWORD)part->u.fnMessage16, 0, DRVM_INIT, 0L, 0L, 0L); - TRACE("DRVM_INIT => %08lx\n", ret); + TRACE("DRVM_INIT => %s\n", WINMM_ErrorToString(ret)); #if 0 ret = pFnCallMMDrvFunc16((DWORD)part->u.fnMessage16, 0, DRVM_ENABLE, 0L, 0L, 0L); @@ -709,6 +723,7 @@ static BOOL MMDRV_InitFromRegistry(void) char* p2; DWORD type, size; BOOL ret = FALSE; + TRACE("()\n"); if (RegCreateKeyA(HKEY_LOCAL_MACHINE, "Software\\Wine\\Wine\\Config\\WinMM", &hKey)) { TRACE("Cannot open WinMM config key\n"); @@ -744,6 +759,7 @@ static BOOL MMDRV_InitFromRegistry(void) */ static BOOL MMDRV_InitHardcoded(void) { + TRACE("()\n"); /* first load hardware drivers */ #ifndef __REACTOS__ MMDRV_Install("wineoss.drv", "wineoss.drv", FALSE); @@ -766,6 +782,7 @@ static BOOL MMDRV_InitHardcoded(void) */ BOOL MMDRV_Init(void) { + TRACE("()\n"); /* FIXME: MMDRV_InitFromRegistry shall be MMDRV_Init in a near future */ return MMDRV_InitFromRegistry() || MMDRV_InitHardcoded(); } @@ -779,6 +796,7 @@ static BOOL MMDRV_ExitPerType(LPWINE_MM_DRIVER lpDrv, UINT type) { WINE_MM_DRIVER_PART* part = &lpDrv->parts[type]; DWORD ret; + TRACE("(%p, %04x)\n", lpDrv, type); if (lpDrv->bIs32 && part->u.fnMessage32) { #if 0 @@ -786,7 +804,7 @@ static BOOL MMDRV_ExitPerType(LPWINE_MM_DRIVER lpDrv, UINT type) TRACE("DRVM_DISABLE => %08lx\n", ret); #endif ret = part->u.fnMessage32(0, DRVM_EXIT, 0L, 0L, 0L); - TRACE("DRVM_EXIT => %08lx\n", ret); + TRACE("DRVM_EXIT => %s\n", WINMM_ErrorToString(ret)); } else if (!lpDrv->bIs32 && part->u.fnMessage16 && pFnCallMMDrvFunc16) { #if 0 ret = pFnCallMMDrvFunc16((DWORD)part->u.fnMessage16, @@ -795,7 +813,7 @@ static BOOL MMDRV_ExitPerType(LPWINE_MM_DRIVER lpDrv, UINT type) #endif ret = pFnCallMMDrvFunc16((DWORD)part->u.fnMessage16, 0, DRVM_EXIT, 0L, 0L, 0L); - TRACE("DRVM_EXIT => %08lx\n", ret); + TRACE("DRVM_EXIT => %s\n", WINMM_ErrorToString(ret)); } else { return FALSE; } @@ -811,6 +829,7 @@ static BOOL MMDRV_ExitPerType(LPWINE_MM_DRIVER lpDrv, UINT type) void MMDRV_Exit(void) { int i; + TRACE("()\n"); for (i = 0; i < sizeof(MM_MLDrvs) / sizeof(MM_MLDrvs[0]); i++) { diff --git a/reactos/lib/winmm/playsound.c b/reactos/lib/winmm/playsound.c index 39f5d4e006e..09e332aa7a4 100644 --- a/reactos/lib/winmm/playsound.c +++ b/reactos/lib/winmm/playsound.c @@ -68,6 +68,7 @@ static HMMIO get_mmioFromProfile(UINT uFlags, LPCWSTR lpszName) 'S','c','h','e','m','e','s','\\', 'A','p','p','s',0}; static const WCHAR wszDotDefault[] = {'.','D','e','f','a','u','l','t',0}; + static const WCHAR wszDotCurrent[] = {'.','C','u','r','r','e','n','t',0}; static const WCHAR wszNull[] = {0}; TRACE("searching in SystemSound list for %s\n", debugstr_w(lpszName)); @@ -90,15 +91,18 @@ static HMMIO get_mmioFromProfile(UINT uFlags, LPCWSTR lpszName) if (RegOpenKeyW(HKEY_CURRENT_USER, wszKey, &hRegSnd) != 0) goto none; if (uFlags & SND_APPLICATION) { + DWORD len; + err = 1; /* error */ - if (GetModuleFileNameW(0, str, sizeof(str)/sizeof(str[0]))) + len = GetModuleFileNameW(0, str, sizeof(str)/sizeof(str[0])); + if (len > 0 && len < sizeof(str)/sizeof(str[0])) { for (ptr = str + lstrlenW(str) - 1; ptr >= str; ptr--) { if (*ptr == '.') *ptr = 0; if (*ptr == '\\') { - err = RegOpenKeyW(hRegSnd, str, &hRegApp); + err = RegOpenKeyW(hRegSnd, ptr+1, &hRegApp); break; } } @@ -113,9 +117,15 @@ static HMMIO get_mmioFromProfile(UINT uFlags, LPCWSTR lpszName) err = RegOpenKeyW(hRegApp, lpszName, &hScheme); RegCloseKey(hRegApp); if (err != 0) goto none; + /* what's the difference between .Current and .Default ? */ err = RegOpenKeyW(hScheme, wszDotDefault, &hSnd); - RegCloseKey(hScheme); - if (err != 0) goto none; + if (err != 0) + { + err = RegOpenKeyW(hScheme, wszDotCurrent, &hSnd); + RegCloseKey(hScheme); + if (err != 0) + goto none; + } count = sizeof(str)/sizeof(str[0]); err = RegQueryValueExW(hSnd, NULL, 0, &type, (LPBYTE)str, &count); RegCloseKey(hSnd); diff --git a/reactos/lib/winmm/winehq2ros.patch b/reactos/lib/winmm/winehq2ros.patch index b957070dde4..dc0589e3d0e 100644 --- a/reactos/lib/winmm/winehq2ros.patch +++ b/reactos/lib/winmm/winehq2ros.patch @@ -1,11 +1,11 @@ Index: lolvldrv.c =================================================================== RCS file: /home/wine/wine/dlls/winmm/lolvldrv.c,v -retrieving revision 1.56 -diff -u -r1.56 lolvldrv.c ---- lolvldrv.c 5 May 2004 01:00:54 -0000 1.56 -+++ lolvldrv.c 8 May 2004 14:17:20 -0000 -@@ -497,10 +497,12 @@ +retrieving revision 1.58 +diff -u -r1.58 lolvldrv.c +--- lolvldrv.c 1 Jun 2004 19:40:48 -0000 1.58 ++++ lolvldrv.c 16 Jun 2004 18:56:14 -0000 +@@ -510,10 +510,12 @@ case DRV_QUERYDEVICEINTERFACESIZE: return MMDRV_Message(mld, uMsg, dwParam1, dwParam2, TRUE); @@ -18,9 +18,9 @@ diff -u -r1.56 lolvldrv.c default: WARN("Unknown call %04x\n", uMsg); -@@ -743,11 +745,18 @@ - static BOOL MMDRV_InitHardcoded(void) +@@ -759,11 +761,18 @@ { + TRACE("()\n"); /* first load hardware drivers */ +#ifndef __REACTOS__ MMDRV_Install("wineoss.drv", "wineoss.drv", FALSE); @@ -45,7 +45,7 @@ RCS file: /home/wine/wine/dlls/winmm/time.c,v retrieving revision 1.26 diff -u -r1.26 time.c --- time.c 12 Jan 2004 21:03:10 -0000 1.26 -+++ time.c 8 May 2004 14:17:20 -0000 ++++ time.c 16 Jun 2004 18:56:14 -0000 @@ -175,14 +175,19 @@ volatile HANDLE *pActive = (volatile HANDLE *)&TIME_hMMTimer; DWORD last_time, cur_time; @@ -69,11 +69,11 @@ diff -u -r1.26 time.c Index: winmm.c =================================================================== RCS file: /home/wine/wine/dlls/winmm/winmm.c,v -retrieving revision 1.33 -diff -u -r1.33 winmm.c ---- winmm.c 27 Feb 2004 21:29:32 -0000 1.33 -+++ winmm.c 8 May 2004 14:17:21 -0000 -@@ -125,10 +125,12 @@ +retrieving revision 1.37 +diff -u -r1.37 winmm.c +--- winmm.c 14 Jun 2004 16:53:59 -0000 1.37 ++++ winmm.c 16 Jun 2004 18:56:15 -0000 +@@ -126,10 +126,12 @@ loaded = -1; if (h) { diff --git a/reactos/lib/winmm/winemm.h b/reactos/lib/winmm/winemm.h index 1447cd1ba4d..4f42b0ec562 100644 --- a/reactos/lib/winmm/winemm.h +++ b/reactos/lib/winmm/winemm.h @@ -256,6 +256,7 @@ DWORD MCI_SendCommandFrom16(UINT wDevID, UINT16 wMsg, DWORD dwParam1, DWORD dwP UINT MCI_SetCommandTable(void *table, UINT uDevType); BOOL WINMM_CheckForMMSystem(void); +const char* WINMM_ErrorToString(MMRESULT error); UINT MIXER_Open(LPHMIXER lphMix, UINT uDeviceID, DWORD dwCallback, DWORD dwInstance, DWORD fdwOpen, BOOL bFrom32); diff --git a/reactos/lib/winmm/winmm.c b/reactos/lib/winmm/winmm.c index 773eb0955b9..575f5105493 100644 --- a/reactos/lib/winmm/winmm.c +++ b/reactos/lib/winmm/winmm.c @@ -28,6 +28,7 @@ * 99/9 added support for loadable low level drivers */ +#include #include #include @@ -137,6 +138,36 @@ BOOL WINMM_CheckForMMSystem(void) return loaded > 0; } +/****************************************************************** + * WINMM_ErrorToString + */ +const char* WINMM_ErrorToString(MMRESULT error) +{ +#define ERR_TO_STR(dev) case dev: return #dev + static char unknown[32]; + switch (error) { + ERR_TO_STR(MMSYSERR_NOERROR); + ERR_TO_STR(MMSYSERR_ERROR); + ERR_TO_STR(MMSYSERR_BADDEVICEID); + ERR_TO_STR(MMSYSERR_NOTENABLED); + ERR_TO_STR(MMSYSERR_ALLOCATED); + ERR_TO_STR(MMSYSERR_INVALHANDLE); + ERR_TO_STR(MMSYSERR_NODRIVER); + ERR_TO_STR(MMSYSERR_NOMEM); + ERR_TO_STR(MMSYSERR_NOTSUPPORTED); + ERR_TO_STR(MMSYSERR_BADERRNUM); + ERR_TO_STR(MMSYSERR_INVALFLAG); + ERR_TO_STR(MMSYSERR_INVALPARAM); + ERR_TO_STR(WAVERR_BADFORMAT); + ERR_TO_STR(WAVERR_STILLPLAYING); + ERR_TO_STR(WAVERR_UNPREPARED); + ERR_TO_STR(WAVERR_SYNC); + } + sprintf(unknown, "Unknown(0x%08x)", error); + return unknown; +#undef ERR_TO_STR +} + /************************************************************************** * DllMain (WINMM.init) * @@ -1985,7 +2016,7 @@ static BOOL MMSYSTEM_MidiStream_MessageHandler(WINE_MIDIStream* lpMidiStrm, LPWI lpMidiHdr = (LPMIDIHDR)msg->lParam; lpMidiHdr->lpNext = 0; lpMidiHdr->dwFlags |= MHDR_INQUEUE; - lpMidiHdr->dwFlags &= MHDR_DONE; + lpMidiHdr->dwFlags &= ~MHDR_DONE; lpMidiHdr->dwOffset = 0; break; @@ -2427,19 +2458,29 @@ UINT WAVE_Open(HANDLE* lphndl, UINT uDeviceID, UINT uType, lphndl, (int)uDeviceID, (uType==MMDRV_WAVEOUT)?"Out":"In", lpFormat, dwCallback, dwInstance, dwFlags, bFrom32?32:16); - if (dwFlags & WAVE_FORMAT_QUERY) TRACE("WAVE_FORMAT_QUERY requested !\n"); + if (dwFlags & WAVE_FORMAT_QUERY) + TRACE("WAVE_FORMAT_QUERY requested !\n"); - if (lpFormat == NULL) return WAVERR_BADFORMAT; - if ((dwFlags & WAVE_MAPPED) && (uDeviceID == (UINT)-1)) + if (lpFormat == NULL) { + WARN("bad format\n"); + return WAVERR_BADFORMAT; + } + + if ((dwFlags & WAVE_MAPPED) && (uDeviceID == (UINT)-1)) { + WARN("invalid parameter\n"); return MMSYSERR_INVALPARAM; + } - TRACE("wFormatTag=%u, nChannels=%u, nSamplesPerSec=%lu, nAvgBytesPerSec=%lu, nBlockAlign=%u, wBitsPerSample=%u, cbSize=%u\n", + /* may have a PCMWAVEFORMAT rather than a WAVEFORMATEX so don't read cbSize */ + TRACE("wFormatTag=%u, nChannels=%u, nSamplesPerSec=%lu, nAvgBytesPerSec=%lu, nBlockAlign=%u, wBitsPerSample=%u\n", lpFormat->wFormatTag, lpFormat->nChannels, lpFormat->nSamplesPerSec, - lpFormat->nAvgBytesPerSec, lpFormat->nBlockAlign, lpFormat->wBitsPerSample, lpFormat->cbSize); + lpFormat->nAvgBytesPerSec, lpFormat->nBlockAlign, lpFormat->wBitsPerSample); if ((wmld = MMDRV_Alloc(sizeof(WINE_WAVE), uType, &handle, - &dwFlags, &dwCallback, &dwInstance, bFrom32)) == NULL) + &dwFlags, &dwCallback, &dwInstance, bFrom32)) == NULL) { + WARN("no memory\n"); return MMSYSERR_NOMEM; + } wod.hWave = handle; wod.lpFormat = lpFormat; /* should the struct be copied iso pointer? */ @@ -2461,11 +2502,12 @@ UINT WAVE_Open(HANDLE* lphndl, UINT uDeviceID, UINT uType, dwRet = MMDRV_Open(wmld, (uType == MMDRV_WAVEOUT) ? WODM_OPEN : WIDM_OPEN, (DWORD)&wod, dwFlags); + TRACE("dwRet = %s\n", WINMM_ErrorToString(dwRet)); if (dwRet != WAVERR_BADFORMAT || - (dwFlags & (WAVE_MAPPED|WAVE_FORMAT_DIRECT)) != 0) break; + ((dwFlags & (WAVE_MAPPED|WAVE_FORMAT_DIRECT)) != 0) || (uDeviceID == WAVE_MAPPER)) break; /* if we ask for a format which isn't supported by the physical driver, * let's try to map it through the wave mapper (except, if we already tried - * or user didn't allow us to use acm codecs) + * or user didn't allow us to use acm codecs or the device is already the mapper) */ dwFlags |= WAVE_MAPPED; /* we shall loop only one */ @@ -2477,7 +2519,7 @@ UINT WAVE_Open(HANDLE* lphndl, UINT uDeviceID, UINT uType, } if (lphndl != NULL) *lphndl = handle; - TRACE("=> %ld hWave=%p\n", dwRet, handle); + TRACE("=> %s hWave=%p\n", WINMM_ErrorToString(dwRet), handle); return dwRet; } @@ -2859,12 +2901,15 @@ UINT WINAPI waveOutMessage(HWAVEOUT hWaveOut, UINT uMessage, if ((wmld = MMDRV_Get(hWaveOut, MMDRV_WAVEOUT, TRUE)) != NULL) { return MMDRV_PhysicalFeatures(wmld, uMessage, dwParam1, dwParam2); } + WARN("invalid handle\n"); return MMSYSERR_INVALHANDLE; } /* from M$ KB */ - if (uMessage < DRVM_IOCTL || (uMessage >= DRVM_IOCTL_LAST && uMessage < DRVM_MAPPER)) + if (uMessage < DRVM_IOCTL || (uMessage >= DRVM_IOCTL_LAST && uMessage < DRVM_MAPPER)) { + WARN("invalid parameter\n"); return MMSYSERR_INVALPARAM; + } return MMDRV_Message(wmld, uMessage, dwParam1, dwParam2, TRUE); } diff --git a/reactos/lib/winmm/winmm_It.rc b/reactos/lib/winmm/winmm_It.rc index 1ff5d7a5c48..821a839b304 100644 --- a/reactos/lib/winmm/winmm_It.rc +++ b/reactos/lib/winmm/winmm_It.rc @@ -1,6 +1,6 @@ /* * Copyright 1999 Eric Pouech - * Copyright 2003 Ivan Leo Murray-Smith + * Copyright 2003 Ivan Leo Puoti * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public