-/* -*- tab-width: 8; c-basic-offset: 4 -*- */\r
-\r
-/*\r
- * MMSYTEM functions\r
- *\r
- * Copyright 1993 Martin Ayotte\r
- * 1998-2002 Eric Pouech\r
- *\r
- * This library is free software; you can redistribute it and/or\r
- * modify it under the terms of the GNU Lesser General Public\r
- * License as published by the Free Software Foundation; either\r
- * version 2.1 of the License, or (at your option) any later version.\r
- *\r
- * This library is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
- * Lesser General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU Lesser General Public\r
- * License along with this library; if not, write to the Free Software\r
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
- */\r
-\r
-#include <stdarg.h>\r
-#include <string.h>\r
-\r
-#include "windef.h"\r
-#include "winbase.h"\r
-#include "mmsystem.h"\r
-#include "wingdi.h"\r
-#include "winuser.h"\r
-#include "winreg.h"\r
-#include "winemm.h"\r
-#include "winternl.h"\r
-\r
-#include "wine/debug.h"\r
-\r
-WINE_DEFAULT_DEBUG_CHANNEL(winmm);\r
-\r
-static HMMIO get_mmioFromFile(LPCWSTR lpszName)\r
-{\r
- HMMIO ret;\r
- WCHAR buf[256];\r
- LPWSTR dummy;\r
-\r
- ret = mmioOpenW((LPWSTR)lpszName, NULL,\r
- MMIO_ALLOCBUF | MMIO_READ | MMIO_DENYWRITE);\r
- if (ret != 0) return ret;\r
- if (SearchPathW(NULL, lpszName, NULL, sizeof(buf)/sizeof(buf[0]), buf, &dummy))\r
- {\r
- return mmioOpenW(buf, NULL,\r
- MMIO_ALLOCBUF | MMIO_READ | MMIO_DENYWRITE);\r
- }\r
- return 0;\r
-}\r
-\r
-static HMMIO get_mmioFromProfile(UINT uFlags, LPCWSTR lpszName)\r
-{\r
- WCHAR str[128];\r
- LPWSTR ptr;\r
- HMMIO hmmio;\r
- HKEY hRegSnd, hRegApp, hScheme, hSnd;\r
- DWORD err, type, count;\r
-\r
- static const WCHAR wszSounds[] = {'S','o','u','n','d','s',0};\r
- static const WCHAR wszDefault[] = {'D','e','f','a','u','l','t',0};\r
- static const WCHAR wszKey[] = {'A','p','p','E','v','e','n','t','s','\\',\r
- 'S','c','h','e','m','e','s','\\',\r
- 'A','p','p','s',0};\r
- static const WCHAR wszDotDefault[] = {'.','D','e','f','a','u','l','t',0};\r
- static const WCHAR wszDotCurrent[] = {'.','C','u','r','r','e','n','t',0};\r
- static const WCHAR wszNull[] = {0};\r
-\r
- TRACE("searching in SystemSound list for %s\n", debugstr_w(lpszName));\r
- GetProfileStringW(wszSounds, lpszName, wszNull, str, sizeof(str)/sizeof(str[0]));\r
- if (lstrlenW(str) == 0)\r
- {\r
- if (uFlags & SND_NODEFAULT) goto next;\r
- GetProfileStringW(wszSounds, wszDefault, wszNull, str, sizeof(str)/sizeof(str[0]));\r
- if (lstrlenW(str) == 0) goto next;\r
- }\r
- for (ptr = str; *ptr && *ptr != ','; ptr++);\r
- if (*ptr) *ptr = 0;\r
- hmmio = mmioOpenW(str, NULL, MMIO_ALLOCBUF | MMIO_READ | MMIO_DENYWRITE);\r
- if (hmmio != 0) return hmmio;\r
- next:\r
- /* we look up the registry under\r
- * HKCU\AppEvents\Schemes\Apps\.Default\r
- * HKCU\AppEvents\Schemes\Apps\<AppName>\r
- */\r
- if (RegOpenKeyW(HKEY_CURRENT_USER, wszKey, &hRegSnd) != 0) goto none;\r
- if (uFlags & SND_APPLICATION)\r
- {\r
- DWORD len;\r
-\r
- err = 1; /* error */\r
- len = GetModuleFileNameW(0, str, sizeof(str)/sizeof(str[0]));\r
- if (len > 0 && len < sizeof(str)/sizeof(str[0]))\r
- {\r
- for (ptr = str + lstrlenW(str) - 1; ptr >= str; ptr--)\r
- {\r
- if (*ptr == '.') *ptr = 0;\r
- if (*ptr == '\\')\r
- {\r
- err = RegOpenKeyW(hRegSnd, ptr+1, &hRegApp);\r
- break;\r
- }\r
- }\r
- }\r
- }\r
- else\r
- {\r
- err = RegOpenKeyW(hRegSnd, wszDotDefault, &hRegApp);\r
- }\r
- RegCloseKey(hRegSnd);\r
- if (err != 0) goto none;\r
- err = RegOpenKeyW(hRegApp, lpszName, &hScheme);\r
- RegCloseKey(hRegApp);\r
- if (err != 0) goto none;\r
- /* what's the difference between .Current and .Default ? */\r
- err = RegOpenKeyW(hScheme, wszDotDefault, &hSnd);\r
- if (err != 0)\r
- {\r
- err = RegOpenKeyW(hScheme, wszDotCurrent, &hSnd);\r
- RegCloseKey(hScheme);\r
- if (err != 0)\r
- goto none;\r
- }\r
- count = sizeof(str)/sizeof(str[0]);\r
- err = RegQueryValueExW(hSnd, NULL, 0, &type, (LPBYTE)str, &count);\r
- RegCloseKey(hSnd);\r
- if (err != 0 || !*str) goto none;\r
- hmmio = mmioOpenW(str, NULL, MMIO_ALLOCBUF | MMIO_READ | MMIO_DENYWRITE);\r
- if (hmmio) return hmmio;\r
- none:\r
- WARN("can't find SystemSound='%s' !\n", debugstr_w(lpszName));\r
- return 0;\r
-}\r
-\r
-struct playsound_data\r
-{\r
- HANDLE hEvent;\r
- DWORD dwEventCount;\r
-};\r
-\r
-static void CALLBACK PlaySound_Callback(HWAVEOUT hwo, UINT uMsg,\r
- DWORD dwInstance,\r
- DWORD dwParam1, DWORD dwParam2)\r
-{\r
- struct playsound_data* s = (struct playsound_data*)dwInstance;\r
-\r
- switch (uMsg) {\r
- case WOM_OPEN:\r
- case WOM_CLOSE:\r
- break;\r
- case WOM_DONE:\r
- InterlockedIncrement(&s->dwEventCount);\r
- TRACE("Returning waveHdr=%lx\n", dwParam1);\r
- SetEvent(s->hEvent);\r
- break;\r
- default:\r
- ERR("Unknown uMsg=%d\n", uMsg);\r
- }\r
-}\r
-\r
-static void PlaySound_WaitDone(struct playsound_data* s)\r
-{\r
- for (;;) {\r
- ResetEvent(s->hEvent);\r
- if (InterlockedDecrement(&s->dwEventCount) >= 0) break;\r
- InterlockedIncrement(&s->dwEventCount);\r
-\r
- WaitForSingleObject(s->hEvent, INFINITE);\r
- }\r
-}\r
-\r
-static BOOL PlaySound_IsString(DWORD fdwSound, const void* psz)\r
-{\r
- /* SND_RESOURCE is 0x40004 while\r
- * SND_MEMORY is 0x00004\r
- */\r
- switch (fdwSound & (SND_RESOURCE|SND_ALIAS|SND_FILENAME))\r
- {\r
- case SND_RESOURCE: return HIWORD(psz) != 0; /* by name or by ID ? */\r
- case SND_MEMORY: return FALSE;\r
- case SND_ALIAS: /* what about ALIAS_ID ??? */\r
- case SND_FILENAME:\r
- case 0: return TRUE;\r
- default: FIXME("WTF\n"); return FALSE;\r
- }\r
-}\r
-\r
-static void PlaySound_Free(WINE_PLAYSOUND* wps)\r
-{\r
- WINE_PLAYSOUND** p;\r
-\r
- EnterCriticalSection(&WINMM_IData.cs);\r
- for (p = &WINMM_IData.lpPlaySound; *p && *p != wps; p = &((*p)->lpNext));\r
- if (*p) *p = (*p)->lpNext;\r
- if (WINMM_IData.lpPlaySound == NULL) SetEvent(WINMM_IData.psLastEvent);\r
- LeaveCriticalSection(&WINMM_IData.cs);\r
- if (wps->bAlloc) HeapFree(GetProcessHeap(), 0, (void*)wps->pszSound);\r
- if (wps->hThread) CloseHandle(wps->hThread);\r
- HeapFree(GetProcessHeap(), 0, wps);\r
-}\r
-\r
-static WINE_PLAYSOUND* PlaySound_Alloc(const void* pszSound, HMODULE hmod,\r
- DWORD fdwSound, BOOL bUnicode)\r
-{\r
- WINE_PLAYSOUND* wps;\r
-\r
- wps = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*wps));\r
- if (!wps) return NULL;\r
-\r
- wps->hMod = hmod;\r
- wps->fdwSound = fdwSound;\r
- if (PlaySound_IsString(fdwSound, pszSound))\r
- {\r
- if (bUnicode)\r
- {\r
- if (fdwSound & SND_ASYNC)\r
- {\r
- wps->pszSound = HeapAlloc(GetProcessHeap(), 0,\r
- (lstrlenW(pszSound)+1) * sizeof(WCHAR));\r
- if (!wps->pszSound) goto oom_error;\r
- lstrcpyW((LPWSTR)wps->pszSound, pszSound);\r
- wps->bAlloc = TRUE;\r
- }\r
- else\r
- wps->pszSound = pszSound;\r
- }\r
- else\r
- {\r
- UNICODE_STRING usBuffer;\r
- RtlCreateUnicodeStringFromAsciiz(&usBuffer, pszSound);\r
- wps->pszSound = usBuffer.Buffer;\r
- if (!wps->pszSound) goto oom_error;\r
- wps->bAlloc = TRUE;\r
- }\r
- }\r
- else\r
- wps->pszSound = pszSound;\r
-\r
- return wps;\r
- oom_error:\r
- PlaySound_Free(wps);\r
- return NULL;\r
-}\r
-\r
-static DWORD WINAPI proc_PlaySound(LPVOID arg)\r
-{\r
- WINE_PLAYSOUND* wps = (WINE_PLAYSOUND*)arg;\r
- BOOL bRet = FALSE;\r
- HMMIO hmmio = 0;\r
- MMCKINFO ckMainRIFF;\r
- MMCKINFO mmckInfo;\r
- LPWAVEFORMATEX lpWaveFormat = NULL;\r
- HWAVEOUT hWave = 0;\r
- LPWAVEHDR waveHdr = NULL;\r
- INT count, bufsize, left, index;\r
- struct playsound_data s;\r
- void* data;\r
-\r
- s.hEvent = 0;\r
-\r
- TRACE("SoundName='%s' !\n", debugstr_w(wps->pszSound));\r
-\r
- /* if resource, grab it */\r
- if ((wps->fdwSound & SND_RESOURCE) == SND_RESOURCE) {\r
- static const WCHAR wszWave[] = {'W','A','V','E',0};\r
- HRSRC hRes;\r
- HGLOBAL hGlob;\r
-\r
- if ((hRes = FindResourceW(wps->hMod, wps->pszSound, wszWave)) == 0 ||\r
- (hGlob = LoadResource(wps->hMod, hRes)) == 0)\r
- goto errCleanUp;\r
- if ((data = LockResource(hGlob)) == NULL) {\r
- FreeResource(hGlob);\r
- goto errCleanUp;\r
- }\r
- FreeResource(hGlob);\r
- } else\r
- data = (void*)wps->pszSound;\r
-\r
- /* construct an MMIO stream (either in memory, or from a file */\r
- if (wps->fdwSound & SND_MEMORY)\r
- { /* NOTE: SND_RESOURCE has the SND_MEMORY bit set */\r
- MMIOINFO mminfo;\r
-\r
- memset(&mminfo, 0, sizeof(mminfo));\r
- mminfo.fccIOProc = FOURCC_MEM;\r
- mminfo.pchBuffer = (LPSTR)data;\r
- mminfo.cchBuffer = -1; /* FIXME: when a resource, could grab real size */\r
- TRACE("Memory sound %p\n", data);\r
- hmmio = mmioOpenW(NULL, &mminfo, MMIO_READ);\r
- }\r
- else if (wps->fdwSound & SND_ALIAS)\r
- {\r
- hmmio = get_mmioFromProfile(wps->fdwSound, wps->pszSound);\r
- }\r
- else if (wps->fdwSound & SND_FILENAME)\r
- {\r
- hmmio = get_mmioFromFile(wps->pszSound);\r
- }\r
- else\r
- {\r
- if ((hmmio = get_mmioFromProfile(wps->fdwSound | SND_NODEFAULT, wps->pszSound)) == 0)\r
- {\r
- if ((hmmio = get_mmioFromFile(wps->pszSound)) == 0)\r
- {\r
- hmmio = get_mmioFromProfile(wps->fdwSound, wps->pszSound);\r
- }\r
- }\r
- }\r
- if (hmmio == 0) goto errCleanUp;\r
-\r
- if (mmioDescend(hmmio, &ckMainRIFF, NULL, 0))\r
- goto errCleanUp;\r
-\r
- TRACE("ParentChunk ckid=%.4s fccType=%.4s cksize=%08lX \n",\r
- (LPSTR)&ckMainRIFF.ckid, (LPSTR)&ckMainRIFF.fccType, ckMainRIFF.cksize);\r
-\r
- if ((ckMainRIFF.ckid != FOURCC_RIFF) ||\r
- (ckMainRIFF.fccType != mmioFOURCC('W', 'A', 'V', 'E')))\r
- goto errCleanUp;\r
-\r
- mmckInfo.ckid = mmioFOURCC('f', 'm', 't', ' ');\r
- if (mmioDescend(hmmio, &mmckInfo, &ckMainRIFF, MMIO_FINDCHUNK))\r
- goto errCleanUp;\r
-\r
- TRACE("Chunk Found ckid=%.4s fccType=%.4s cksize=%08lX \n",\r
- (LPSTR)&mmckInfo.ckid, (LPSTR)&mmckInfo.fccType, mmckInfo.cksize);\r
-\r
- lpWaveFormat = HeapAlloc(GetProcessHeap(), 0, mmckInfo.cksize);\r
- if (mmioRead(hmmio, (HPSTR)lpWaveFormat, mmckInfo.cksize) < sizeof(WAVEFORMAT))\r
- goto errCleanUp;\r
-\r
- TRACE("wFormatTag=%04X !\n", lpWaveFormat->wFormatTag);\r
- TRACE("nChannels=%d \n", lpWaveFormat->nChannels);\r
- TRACE("nSamplesPerSec=%ld\n", lpWaveFormat->nSamplesPerSec);\r
- TRACE("nAvgBytesPerSec=%ld\n", lpWaveFormat->nAvgBytesPerSec);\r
- TRACE("nBlockAlign=%d \n", lpWaveFormat->nBlockAlign);\r
- TRACE("wBitsPerSample=%u !\n", lpWaveFormat->wBitsPerSample);\r
-\r
- /* move to end of 'fmt ' chunk */\r
- mmioAscend(hmmio, &mmckInfo, 0);\r
-\r
- mmckInfo.ckid = mmioFOURCC('d', 'a', 't', 'a');\r
- if (mmioDescend(hmmio, &mmckInfo, &ckMainRIFF, MMIO_FINDCHUNK))\r
- goto errCleanUp;\r
-\r
- TRACE("Chunk Found ckid=%.4s fccType=%.4s cksize=%08lX\n",\r
- (LPSTR)&mmckInfo.ckid, (LPSTR)&mmckInfo.fccType, mmckInfo.cksize);\r
-\r
- s.hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);\r
-\r
- if (waveOutOpen(&hWave, WAVE_MAPPER, lpWaveFormat, (DWORD)PlaySound_Callback,\r
- (DWORD)&s, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)\r
- goto errCleanUp;\r
-\r
- /* make it so that 3 buffers per second are needed */\r
- bufsize = (((lpWaveFormat->nAvgBytesPerSec / 3) - 1) / lpWaveFormat->nBlockAlign + 1) *\r
- lpWaveFormat->nBlockAlign;\r
- waveHdr = HeapAlloc(GetProcessHeap(), 0, 2 * sizeof(WAVEHDR) + 2 * bufsize);\r
- waveHdr[0].lpData = (char*)waveHdr + 2 * sizeof(WAVEHDR);\r
- waveHdr[1].lpData = (char*)waveHdr + 2 * sizeof(WAVEHDR) + bufsize;\r
- waveHdr[0].dwUser = waveHdr[1].dwUser = 0L;\r
- waveHdr[0].dwLoops = waveHdr[1].dwLoops = 0L;\r
- waveHdr[0].dwFlags = waveHdr[1].dwFlags = 0L;\r
- waveHdr[0].dwBufferLength = waveHdr[1].dwBufferLength = bufsize;\r
- if (waveOutPrepareHeader(hWave, &waveHdr[0], sizeof(WAVEHDR)) ||\r
- waveOutPrepareHeader(hWave, &waveHdr[1], sizeof(WAVEHDR))) {\r
- goto errCleanUp;\r
- }\r
-\r
- s.dwEventCount = 1L; /* for first buffer */\r
- index = 0;\r
-\r
- do {\r
- left = mmckInfo.cksize;\r
-\r
- mmioSeek(hmmio, mmckInfo.dwDataOffset, SEEK_SET);\r
- while (left)\r
- {\r
- if (WaitForSingleObject(WINMM_IData.psStopEvent, 0) == WAIT_OBJECT_0)\r
- {\r
- wps->bLoop = FALSE;\r
- break;\r
- }\r
- count = mmioRead(hmmio, waveHdr[index].lpData, min(bufsize, left));\r
- if (count < 1) break;\r
- left -= count;\r
- waveHdr[index].dwBufferLength = count;\r
- waveHdr[index].dwFlags &= ~WHDR_DONE;\r
- if (waveOutWrite(hWave, &waveHdr[index], sizeof(WAVEHDR)) == MMSYSERR_NOERROR) {\r
- index ^= 1;\r
- PlaySound_WaitDone(&s);\r
- }\r
- else FIXME("Couldn't play header\n");\r
- }\r
- bRet = TRUE;\r
- } while (wps->bLoop);\r
-\r
- PlaySound_WaitDone(&s); /* for last buffer */\r
- waveOutReset(hWave);\r
-\r
- waveOutUnprepareHeader(hWave, &waveHdr[0], sizeof(WAVEHDR));\r
- waveOutUnprepareHeader(hWave, &waveHdr[1], sizeof(WAVEHDR));\r
-\r
-errCleanUp:\r
- TRACE("Done playing='%s' => %s!\n", debugstr_w(wps->pszSound), bRet ? "ok" : "ko");\r
- CloseHandle(s.hEvent);\r
- HeapFree(GetProcessHeap(), 0, waveHdr);\r
- HeapFree(GetProcessHeap(), 0, lpWaveFormat);\r
- if (hWave) while (waveOutClose(hWave) == WAVERR_STILLPLAYING) Sleep(100);\r
- if (hmmio) mmioClose(hmmio, 0);\r
-\r
- PlaySound_Free(wps);\r
-\r
- return bRet;\r
-}\r
-\r
-static BOOL MULTIMEDIA_PlaySound(const void* pszSound, HMODULE hmod, DWORD fdwSound, BOOL bUnicode)\r
-{\r
- WINE_PLAYSOUND* wps = NULL;\r
-\r
- TRACE("pszSound='%p' hmod=%p fdwSound=%08lX\n",\r
- pszSound, hmod, fdwSound);\r
-\r
- /* FIXME? I see no difference between SND_NOWAIT and SND_NOSTOP !\r
- * there could be one if several sounds can be played at once...\r
- */\r
- if ((fdwSound & (SND_NOWAIT | SND_NOSTOP)) && WINMM_IData.lpPlaySound != NULL)\r
- return FALSE;\r
-\r
- /* alloc internal structure, if we need to play something */\r
- if (pszSound && !(fdwSound & SND_PURGE))\r
- {\r
- if (!(wps = PlaySound_Alloc(pszSound, hmod, fdwSound, bUnicode)))\r
- return FALSE;\r
- }\r
-\r
- EnterCriticalSection(&WINMM_IData.cs);\r
- /* since several threads can enter PlaySound in parallel, we're not\r
- * sure, at this point, that another thread didn't start a new playsound\r
- */\r
- while (WINMM_IData.lpPlaySound != NULL)\r
- {\r
- ResetEvent(WINMM_IData.psLastEvent);\r
- /* FIXME: doc says we have to stop all instances of pszSound if it's non\r
- * NULL... as of today, we stop all playing instances */\r
- SetEvent(WINMM_IData.psStopEvent);\r
-\r
- LeaveCriticalSection(&WINMM_IData.cs);\r
- WaitForSingleObject(WINMM_IData.psLastEvent, INFINITE);\r
- EnterCriticalSection(&WINMM_IData.cs);\r
-\r
- ResetEvent(WINMM_IData.psStopEvent);\r
- }\r
-\r
- if (wps) wps->lpNext = WINMM_IData.lpPlaySound;\r
- WINMM_IData.lpPlaySound = wps;\r
- LeaveCriticalSection(&WINMM_IData.cs);\r
-\r
- if (!pszSound || (fdwSound & SND_PURGE)) return TRUE;\r
-\r
- if (fdwSound & SND_ASYNC)\r
- {\r
- DWORD id;\r
- HANDLE handle;\r
- wps->bLoop = (fdwSound & SND_LOOP) ? TRUE : FALSE;\r
- if ((handle = CreateThread(NULL, 0, proc_PlaySound, wps, 0, &id)) != 0) {\r
- wps->hThread = handle;\r
- SetThreadPriority(handle, THREAD_PRIORITY_TIME_CRITICAL);\r
- return TRUE;\r
- }\r
- }\r
- else return proc_PlaySound(wps);\r
-\r
- /* error cases */\r
- PlaySound_Free(wps);\r
- return FALSE;\r
-}\r
-\r
-/**************************************************************************\r
- * PlaySoundA [WINMM.@]\r
- */\r
-BOOL WINAPI PlaySoundA(LPCSTR pszSoundA, HMODULE hmod, DWORD fdwSound)\r
-{\r
- return MULTIMEDIA_PlaySound(pszSoundA, hmod, fdwSound, FALSE);\r
-}\r
-\r
-/**************************************************************************\r
- * PlaySoundW [WINMM.@]\r
- */\r
-BOOL WINAPI PlaySoundW(LPCWSTR pszSoundW, HMODULE hmod, DWORD fdwSound)\r
-{\r
- return MULTIMEDIA_PlaySound(pszSoundW, hmod, fdwSound, TRUE);\r
-}\r
-\r
-/**************************************************************************\r
- * sndPlaySoundA [WINMM.@]\r
- */\r
-BOOL WINAPI sndPlaySoundA(LPCSTR pszSoundA, UINT uFlags)\r
-{\r
- uFlags &= SND_ASYNC|SND_LOOP|SND_MEMORY|SND_NODEFAULT|SND_NOSTOP|SND_SYNC;\r
- return MULTIMEDIA_PlaySound(pszSoundA, 0, uFlags, FALSE);\r
-}\r
-\r
-/**************************************************************************\r
- * sndPlaySoundW [WINMM.@]\r
- */\r
-BOOL WINAPI sndPlaySoundW(LPCWSTR pszSound, UINT uFlags)\r
-{\r
- uFlags &= SND_ASYNC|SND_LOOP|SND_MEMORY|SND_NODEFAULT|SND_NOSTOP|SND_SYNC;\r
- return MULTIMEDIA_PlaySound(pszSound, 0, uFlags, TRUE);\r
-}\r
-\r
-/**************************************************************************\r
- * mmsystemGetVersion [WINMM.@]\r
- */\r
-UINT WINAPI mmsystemGetVersion(void)\r
-{\r
- TRACE("3.10 (Win95?)\n");\r
- return 0x030a;\r
-}\r
+/* -*- tab-width: 8; c-basic-offset: 4 -*- */
+
+/*
+ * MMSYTEM functions
+ *
+ * Copyright 1993 Martin Ayotte
+ * 1998-2002 Eric Pouech
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <stdarg.h>
+#include <string.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "mmsystem.h"
+#include "wingdi.h"
+#include "winuser.h"
+#include "winreg.h"
+#include "winemm.h"
+#include "winternl.h"
+
+#include "wine/debug.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(winmm);
+
+static HMMIO get_mmioFromFile(LPCWSTR lpszName)
+{
+ HMMIO ret;
+ WCHAR buf[256];
+ LPWSTR dummy;
+
+ ret = mmioOpenW((LPWSTR)lpszName, NULL,
+ MMIO_ALLOCBUF | MMIO_READ | MMIO_DENYWRITE);
+ if (ret != 0) return ret;
+ if (SearchPathW(NULL, lpszName, NULL, sizeof(buf)/sizeof(buf[0]), buf, &dummy))
+ {
+ return mmioOpenW(buf, NULL,
+ MMIO_ALLOCBUF | MMIO_READ | MMIO_DENYWRITE);
+ }
+ return 0;
+}
+
+static HMMIO get_mmioFromProfile(UINT uFlags, LPCWSTR lpszName)
+{
+ WCHAR str[128];
+ LPWSTR ptr;
+ HMMIO hmmio;
+ HKEY hRegSnd, hRegApp, hScheme, hSnd;
+ DWORD err, type, count;
+
+ static const WCHAR wszSounds[] = {'S','o','u','n','d','s',0};
+ static const WCHAR wszDefault[] = {'D','e','f','a','u','l','t',0};
+ static const WCHAR wszKey[] = {'A','p','p','E','v','e','n','t','s','\\',
+ '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));
+ GetProfileStringW(wszSounds, lpszName, wszNull, str, sizeof(str)/sizeof(str[0]));
+ if (lstrlenW(str) == 0)
+ {
+ if (uFlags & SND_NODEFAULT) goto next;
+ GetProfileStringW(wszSounds, wszDefault, wszNull, str, sizeof(str)/sizeof(str[0]));
+ if (lstrlenW(str) == 0) goto next;
+ }
+ for (ptr = str; *ptr && *ptr != ','; ptr++);
+ if (*ptr) *ptr = 0;
+ hmmio = mmioOpenW(str, NULL, MMIO_ALLOCBUF | MMIO_READ | MMIO_DENYWRITE);
+ if (hmmio != 0) return hmmio;
+ next:
+ /* we look up the registry under
+ * HKCU\AppEvents\Schemes\Apps\.Default
+ * HKCU\AppEvents\Schemes\Apps\<AppName>
+ */
+ if (RegOpenKeyW(HKEY_CURRENT_USER, wszKey, &hRegSnd) != 0) goto none;
+ if (uFlags & SND_APPLICATION)
+ {
+ DWORD len;
+
+ err = 1; /* error */
+ 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, ptr+1, &hRegApp);
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ err = RegOpenKeyW(hRegSnd, wszDotDefault, &hRegApp);
+ }
+ RegCloseKey(hRegSnd);
+ if (err != 0) goto none;
+ 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);
+ 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);
+ if (err != 0 || !*str) goto none;
+ hmmio = mmioOpenW(str, NULL, MMIO_ALLOCBUF | MMIO_READ | MMIO_DENYWRITE);
+ if (hmmio) return hmmio;
+ none:
+ WARN("can't find SystemSound='%s' !\n", debugstr_w(lpszName));
+ return 0;
+}
+
+struct playsound_data
+{
+ HANDLE hEvent;
+ DWORD dwEventCount;
+};
+
+static void CALLBACK PlaySound_Callback(HWAVEOUT hwo, UINT uMsg,
+ DWORD dwInstance,
+ DWORD dwParam1, DWORD dwParam2)
+{
+ struct playsound_data* s = (struct playsound_data*)dwInstance;
+
+ switch (uMsg) {
+ case WOM_OPEN:
+ case WOM_CLOSE:
+ break;
+ case WOM_DONE:
+ InterlockedIncrement(&s->dwEventCount);
+ TRACE("Returning waveHdr=%lx\n", dwParam1);
+ SetEvent(s->hEvent);
+ break;
+ default:
+ ERR("Unknown uMsg=%d\n", uMsg);
+ }
+}
+
+static void PlaySound_WaitDone(struct playsound_data* s)
+{
+ for (;;) {
+ ResetEvent(s->hEvent);
+ if (InterlockedDecrement(&s->dwEventCount) >= 0) break;
+ InterlockedIncrement(&s->dwEventCount);
+
+ WaitForSingleObject(s->hEvent, INFINITE);
+ }
+}
+
+static BOOL PlaySound_IsString(DWORD fdwSound, const void* psz)
+{
+ /* SND_RESOURCE is 0x40004 while
+ * SND_MEMORY is 0x00004
+ */
+ switch (fdwSound & (SND_RESOURCE|SND_ALIAS|SND_FILENAME))
+ {
+ case SND_RESOURCE: return HIWORD(psz) != 0; /* by name or by ID ? */
+ case SND_MEMORY: return FALSE;
+ case SND_ALIAS: /* what about ALIAS_ID ??? */
+ case SND_FILENAME:
+ case 0: return TRUE;
+ default: FIXME("WTF\n"); return FALSE;
+ }
+}
+
+static void PlaySound_Free(WINE_PLAYSOUND* wps)
+{
+ WINE_PLAYSOUND** p;
+
+ EnterCriticalSection(&WINMM_IData.cs);
+ for (p = &WINMM_IData.lpPlaySound; *p && *p != wps; p = &((*p)->lpNext));
+ if (*p) *p = (*p)->lpNext;
+ if (WINMM_IData.lpPlaySound == NULL) SetEvent(WINMM_IData.psLastEvent);
+ LeaveCriticalSection(&WINMM_IData.cs);
+ if (wps->bAlloc) HeapFree(GetProcessHeap(), 0, (void*)wps->pszSound);
+ if (wps->hThread) CloseHandle(wps->hThread);
+ HeapFree(GetProcessHeap(), 0, wps);
+}
+
+static WINE_PLAYSOUND* PlaySound_Alloc(const void* pszSound, HMODULE hmod,
+ DWORD fdwSound, BOOL bUnicode)
+{
+ WINE_PLAYSOUND* wps;
+
+ wps = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*wps));
+ if (!wps) return NULL;
+
+ wps->hMod = hmod;
+ wps->fdwSound = fdwSound;
+ if (PlaySound_IsString(fdwSound, pszSound))
+ {
+ if (bUnicode)
+ {
+ if (fdwSound & SND_ASYNC)
+ {
+ wps->pszSound = HeapAlloc(GetProcessHeap(), 0,
+ (lstrlenW(pszSound)+1) * sizeof(WCHAR));
+ if (!wps->pszSound) goto oom_error;
+ lstrcpyW((LPWSTR)wps->pszSound, pszSound);
+ wps->bAlloc = TRUE;
+ }
+ else
+ wps->pszSound = pszSound;
+ }
+ else
+ {
+ UNICODE_STRING usBuffer;
+ RtlCreateUnicodeStringFromAsciiz(&usBuffer, pszSound);
+ wps->pszSound = usBuffer.Buffer;
+ if (!wps->pszSound) goto oom_error;
+ wps->bAlloc = TRUE;
+ }
+ }
+ else
+ wps->pszSound = pszSound;
+
+ return wps;
+ oom_error:
+ PlaySound_Free(wps);
+ return NULL;
+}
+
+static DWORD WINAPI proc_PlaySound(LPVOID arg)
+{
+ WINE_PLAYSOUND* wps = (WINE_PLAYSOUND*)arg;
+ BOOL bRet = FALSE;
+ HMMIO hmmio = 0;
+ MMCKINFO ckMainRIFF;
+ MMCKINFO mmckInfo;
+ LPWAVEFORMATEX lpWaveFormat = NULL;
+ HWAVEOUT hWave = 0;
+ LPWAVEHDR waveHdr = NULL;
+ INT count, bufsize, left, index;
+ struct playsound_data s;
+ void* data;
+
+ s.hEvent = 0;
+
+ TRACE("SoundName='%s' !\n", debugstr_w(wps->pszSound));
+
+ /* if resource, grab it */
+ if ((wps->fdwSound & SND_RESOURCE) == SND_RESOURCE) {
+ static const WCHAR wszWave[] = {'W','A','V','E',0};
+ HRSRC hRes;
+ HGLOBAL hGlob;
+
+ if ((hRes = FindResourceW(wps->hMod, wps->pszSound, wszWave)) == 0 ||
+ (hGlob = LoadResource(wps->hMod, hRes)) == 0)
+ goto errCleanUp;
+ if ((data = LockResource(hGlob)) == NULL) {
+ FreeResource(hGlob);
+ goto errCleanUp;
+ }
+ FreeResource(hGlob);
+ } else
+ data = (void*)wps->pszSound;
+
+ /* construct an MMIO stream (either in memory, or from a file */
+ if (wps->fdwSound & SND_MEMORY)
+ { /* NOTE: SND_RESOURCE has the SND_MEMORY bit set */
+ MMIOINFO mminfo;
+
+ memset(&mminfo, 0, sizeof(mminfo));
+ mminfo.fccIOProc = FOURCC_MEM;
+ mminfo.pchBuffer = (LPSTR)data;
+ mminfo.cchBuffer = -1; /* FIXME: when a resource, could grab real size */
+ TRACE("Memory sound %p\n", data);
+ hmmio = mmioOpenW(NULL, &mminfo, MMIO_READ);
+ }
+ else if (wps->fdwSound & SND_ALIAS)
+ {
+ hmmio = get_mmioFromProfile(wps->fdwSound, wps->pszSound);
+ }
+ else if (wps->fdwSound & SND_FILENAME)
+ {
+ hmmio = get_mmioFromFile(wps->pszSound);
+ }
+ else
+ {
+ if ((hmmio = get_mmioFromProfile(wps->fdwSound | SND_NODEFAULT, wps->pszSound)) == 0)
+ {
+ if ((hmmio = get_mmioFromFile(wps->pszSound)) == 0)
+ {
+ hmmio = get_mmioFromProfile(wps->fdwSound, wps->pszSound);
+ }
+ }
+ }
+ if (hmmio == 0) goto errCleanUp;
+
+ if (mmioDescend(hmmio, &ckMainRIFF, NULL, 0))
+ goto errCleanUp;
+
+ TRACE("ParentChunk ckid=%.4s fccType=%.4s cksize=%08lX \n",
+ (LPSTR)&ckMainRIFF.ckid, (LPSTR)&ckMainRIFF.fccType, ckMainRIFF.cksize);
+
+ if ((ckMainRIFF.ckid != FOURCC_RIFF) ||
+ (ckMainRIFF.fccType != mmioFOURCC('W', 'A', 'V', 'E')))
+ goto errCleanUp;
+
+ mmckInfo.ckid = mmioFOURCC('f', 'm', 't', ' ');
+ if (mmioDescend(hmmio, &mmckInfo, &ckMainRIFF, MMIO_FINDCHUNK))
+ goto errCleanUp;
+
+ TRACE("Chunk Found ckid=%.4s fccType=%.4s cksize=%08lX \n",
+ (LPSTR)&mmckInfo.ckid, (LPSTR)&mmckInfo.fccType, mmckInfo.cksize);
+
+ lpWaveFormat = HeapAlloc(GetProcessHeap(), 0, mmckInfo.cksize);
+ if (mmioRead(hmmio, (HPSTR)lpWaveFormat, mmckInfo.cksize) < sizeof(WAVEFORMAT))
+ goto errCleanUp;
+
+ TRACE("wFormatTag=%04X !\n", lpWaveFormat->wFormatTag);
+ TRACE("nChannels=%d \n", lpWaveFormat->nChannels);
+ TRACE("nSamplesPerSec=%ld\n", lpWaveFormat->nSamplesPerSec);
+ TRACE("nAvgBytesPerSec=%ld\n", lpWaveFormat->nAvgBytesPerSec);
+ TRACE("nBlockAlign=%d \n", lpWaveFormat->nBlockAlign);
+ TRACE("wBitsPerSample=%u !\n", lpWaveFormat->wBitsPerSample);
+
+ /* move to end of 'fmt ' chunk */
+ mmioAscend(hmmio, &mmckInfo, 0);
+
+ mmckInfo.ckid = mmioFOURCC('d', 'a', 't', 'a');
+ if (mmioDescend(hmmio, &mmckInfo, &ckMainRIFF, MMIO_FINDCHUNK))
+ goto errCleanUp;
+
+ TRACE("Chunk Found ckid=%.4s fccType=%.4s cksize=%08lX\n",
+ (LPSTR)&mmckInfo.ckid, (LPSTR)&mmckInfo.fccType, mmckInfo.cksize);
+
+ s.hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
+
+ if (waveOutOpen(&hWave, WAVE_MAPPER, lpWaveFormat, (DWORD)PlaySound_Callback,
+ (DWORD)&s, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
+ goto errCleanUp;
+
+ /* make it so that 3 buffers per second are needed */
+ bufsize = (((lpWaveFormat->nAvgBytesPerSec / 3) - 1) / lpWaveFormat->nBlockAlign + 1) *
+ lpWaveFormat->nBlockAlign;
+ waveHdr = HeapAlloc(GetProcessHeap(), 0, 2 * sizeof(WAVEHDR) + 2 * bufsize);
+ waveHdr[0].lpData = (char*)waveHdr + 2 * sizeof(WAVEHDR);
+ waveHdr[1].lpData = (char*)waveHdr + 2 * sizeof(WAVEHDR) + bufsize;
+ waveHdr[0].dwUser = waveHdr[1].dwUser = 0L;
+ waveHdr[0].dwLoops = waveHdr[1].dwLoops = 0L;
+ waveHdr[0].dwFlags = waveHdr[1].dwFlags = 0L;
+ waveHdr[0].dwBufferLength = waveHdr[1].dwBufferLength = bufsize;
+ if (waveOutPrepareHeader(hWave, &waveHdr[0], sizeof(WAVEHDR)) ||
+ waveOutPrepareHeader(hWave, &waveHdr[1], sizeof(WAVEHDR))) {
+ goto errCleanUp;
+ }
+
+ s.dwEventCount = 1L; /* for first buffer */
+ index = 0;
+
+ do {
+ left = mmckInfo.cksize;
+
+ mmioSeek(hmmio, mmckInfo.dwDataOffset, SEEK_SET);
+ while (left)
+ {
+ if (WaitForSingleObject(WINMM_IData.psStopEvent, 0) == WAIT_OBJECT_0)
+ {
+ wps->bLoop = FALSE;
+ break;
+ }
+ count = mmioRead(hmmio, waveHdr[index].lpData, min(bufsize, left));
+ if (count < 1) break;
+ left -= count;
+ waveHdr[index].dwBufferLength = count;
+ waveHdr[index].dwFlags &= ~WHDR_DONE;
+ if (waveOutWrite(hWave, &waveHdr[index], sizeof(WAVEHDR)) == MMSYSERR_NOERROR) {
+ index ^= 1;
+ PlaySound_WaitDone(&s);
+ }
+ else FIXME("Couldn't play header\n");
+ }
+ bRet = TRUE;
+ } while (wps->bLoop);
+
+ PlaySound_WaitDone(&s); /* for last buffer */
+ waveOutReset(hWave);
+
+ waveOutUnprepareHeader(hWave, &waveHdr[0], sizeof(WAVEHDR));
+ waveOutUnprepareHeader(hWave, &waveHdr[1], sizeof(WAVEHDR));
+
+errCleanUp:
+ TRACE("Done playing='%s' => %s!\n", debugstr_w(wps->pszSound), bRet ? "ok" : "ko");
+ CloseHandle(s.hEvent);
+ HeapFree(GetProcessHeap(), 0, waveHdr);
+ HeapFree(GetProcessHeap(), 0, lpWaveFormat);
+ if (hWave) while (waveOutClose(hWave) == WAVERR_STILLPLAYING) Sleep(100);
+ if (hmmio) mmioClose(hmmio, 0);
+
+ PlaySound_Free(wps);
+
+ return bRet;
+}
+
+static BOOL MULTIMEDIA_PlaySound(const void* pszSound, HMODULE hmod, DWORD fdwSound, BOOL bUnicode)
+{
+ WINE_PLAYSOUND* wps = NULL;
+
+ TRACE("pszSound='%p' hmod=%p fdwSound=%08lX\n",
+ pszSound, hmod, fdwSound);
+
+ /* FIXME? I see no difference between SND_NOWAIT and SND_NOSTOP !
+ * there could be one if several sounds can be played at once...
+ */
+ if ((fdwSound & (SND_NOWAIT | SND_NOSTOP)) && WINMM_IData.lpPlaySound != NULL)
+ return FALSE;
+
+ /* alloc internal structure, if we need to play something */
+ if (pszSound && !(fdwSound & SND_PURGE))
+ {
+ if (!(wps = PlaySound_Alloc(pszSound, hmod, fdwSound, bUnicode)))
+ return FALSE;
+ }
+
+ EnterCriticalSection(&WINMM_IData.cs);
+ /* since several threads can enter PlaySound in parallel, we're not
+ * sure, at this point, that another thread didn't start a new playsound
+ */
+ while (WINMM_IData.lpPlaySound != NULL)
+ {
+ ResetEvent(WINMM_IData.psLastEvent);
+ /* FIXME: doc says we have to stop all instances of pszSound if it's non
+ * NULL... as of today, we stop all playing instances */
+ SetEvent(WINMM_IData.psStopEvent);
+
+ LeaveCriticalSection(&WINMM_IData.cs);
+ WaitForSingleObject(WINMM_IData.psLastEvent, INFINITE);
+ EnterCriticalSection(&WINMM_IData.cs);
+
+ ResetEvent(WINMM_IData.psStopEvent);
+ }
+
+ if (wps) wps->lpNext = WINMM_IData.lpPlaySound;
+ WINMM_IData.lpPlaySound = wps;
+ LeaveCriticalSection(&WINMM_IData.cs);
+
+ if (!pszSound || (fdwSound & SND_PURGE)) return TRUE;
+
+ if (fdwSound & SND_ASYNC)
+ {
+ DWORD id;
+ HANDLE handle;
+ wps->bLoop = (fdwSound & SND_LOOP) ? TRUE : FALSE;
+ if ((handle = CreateThread(NULL, 0, proc_PlaySound, wps, 0, &id)) != 0) {
+ wps->hThread = handle;
+ SetThreadPriority(handle, THREAD_PRIORITY_TIME_CRITICAL);
+ return TRUE;
+ }
+ }
+ else return proc_PlaySound(wps);
+
+ /* error cases */
+ PlaySound_Free(wps);
+ return FALSE;
+}
+
+/**************************************************************************
+ * PlaySoundA [WINMM.@]
+ */
+BOOL WINAPI PlaySoundA(LPCSTR pszSoundA, HMODULE hmod, DWORD fdwSound)
+{
+ return MULTIMEDIA_PlaySound(pszSoundA, hmod, fdwSound, FALSE);
+}
+
+/**************************************************************************
+ * PlaySoundW [WINMM.@]
+ */
+BOOL WINAPI PlaySoundW(LPCWSTR pszSoundW, HMODULE hmod, DWORD fdwSound)
+{
+ return MULTIMEDIA_PlaySound(pszSoundW, hmod, fdwSound, TRUE);
+}
+
+/**************************************************************************
+ * sndPlaySoundA [WINMM.@]
+ */
+BOOL WINAPI sndPlaySoundA(LPCSTR pszSoundA, UINT uFlags)
+{
+ uFlags &= SND_ASYNC|SND_LOOP|SND_MEMORY|SND_NODEFAULT|SND_NOSTOP|SND_SYNC;
+ return MULTIMEDIA_PlaySound(pszSoundA, 0, uFlags, FALSE);
+}
+
+/**************************************************************************
+ * sndPlaySoundW [WINMM.@]
+ */
+BOOL WINAPI sndPlaySoundW(LPCWSTR pszSound, UINT uFlags)
+{
+ uFlags &= SND_ASYNC|SND_LOOP|SND_MEMORY|SND_NODEFAULT|SND_NOSTOP|SND_SYNC;
+ return MULTIMEDIA_PlaySound(pszSound, 0, uFlags, TRUE);
+}
+
+/**************************************************************************
+ * mmsystemGetVersion [WINMM.@]
+ */
+UINT WINAPI mmsystemGetVersion(void)
+{
+ TRACE("3.10 (Win95?)\n");
+ return 0x030a;
+}