mciwave: Rework MCI notification system.
diff --git a/dlls/mciwave/mciwave.c b/dlls/mciwave/mciwave.c
index 05d16ef..deb7b40 100644
--- a/dlls/mciwave/mciwave.c
+++ b/dlls/mciwave/mciwave.c
@@ -4,6 +4,7 @@
  * Copyright 	1994 Martin Ayotte
  *		1999,2000,2005 Eric Pouech
  *              2000 Francois Jacques
+ *		2009 Jörg Höhle
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -40,6 +41,7 @@
     int				nUseCount;	/* Incremented for each shared open */
     HMMIO			hFile;  	/* mmio file handle open as Element */
     MCI_WAVE_OPEN_PARMSW 	openParms;
+    HANDLE			hCallback;	/* Callback handle for pending notification */
     WAVEFORMATEX		wfxRef;
     LPWAVEFORMATEX		lpWaveFormat;	/* Points to wfxRef until set by OPEN or RECORD */
     BOOL			fInput;		/* FALSE = Output, TRUE = Input */
@@ -199,6 +201,21 @@
 }
 
 /**************************************************************************
+ *				WAVE_mciNotify			[internal]
+ *
+ * Notifications in MCI work like a 1-element queue.
+ * Each new notification request supersedes the previous one.
+ * This affects Play and Record; other commands are immediate.
+ */
+static void WAVE_mciNotify(DWORD_PTR hWndCallBack, WINE_MCIWAVE* wmw, UINT wStatus)
+{
+    MCIDEVICEID wDevID = wmw->openParms.wDeviceID;
+    HANDLE old = InterlockedExchangePointer(&wmw->hCallback, NULL);
+    if (old) mciDriverNotify(old, wDevID, MCI_NOTIFY_SUPERSEDED);
+    mciDriverNotify(HWND_32(LOWORD(hWndCallBack)), wDevID, wStatus);
+}
+
+/**************************************************************************
  * 				WAVE_ConvertByteToTimeFormat	[internal]
  */
 static	DWORD 	WAVE_ConvertByteToTimeFormat(WINE_MCIWAVE* wmw, DWORD val, LPDWORD lpRet)
@@ -509,6 +526,7 @@
     memcpy(&wmw->openParms, lpOpenParms, sizeof(MCI_WAVE_OPEN_PARMSA));
     /* will be set by WAVE_mciOpenFile */
     wmw->openParms.lpstrElementName = NULL;
+    wmw->hCallback = NULL;
     WAVE_mciDefaultFmt(wmw);
 
     TRACE("wDevID=%04X (lpParams->wDeviceID=%08X)\n", wDevID, lpOpenParms->wDeviceID);
@@ -529,6 +547,9 @@
 	wmw->dwPosition = 0;
 
 	wmw->dwStatus = MCI_MODE_STOP;
+
+	if (dwFlags & MCI_NOTIFY)
+	    WAVE_mciNotify(lpOpenParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
     } else {
 	wmw->nUseCount--;
 	if (wmw->hFile != 0)
@@ -595,6 +616,11 @@
 
     if (wmw == NULL)		return MCIERR_INVALID_DEVICE_ID;
 
+    if (wmw->dwStatus != MCI_MODE_STOP) {
+	HANDLE old = InterlockedExchangePointer(&wmw->hCallback, NULL);
+	if (old) mciDriverNotify(old, wDevID, MCI_NOTIFY_ABORTED);
+    }
+
     /* wait for playback thread (if any) to exit before processing further */
     switch (wmw->dwStatus) {
     case MCI_MODE_PAUSE:
@@ -614,10 +640,8 @@
     /* sanity resets */
     wmw->dwStatus = MCI_MODE_STOP;
 
-    if ((dwFlags & MCI_NOTIFY) && lpParms) {
-	mciDriverNotify(HWND_32(LOWORD(lpParms->dwCallback)),
-			wmw->openParms.wDeviceID, MCI_NOTIFY_SUCCESSFUL);
-    }
+    if ((dwFlags & MCI_NOTIFY) && lpParms && MMSYSERR_NOERROR==dwRet)
+	WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
 
     return dwRet;
 }
@@ -635,6 +659,7 @@
     if (wmw == NULL)		return MCIERR_INVALID_DEVICE_ID;
 
     if (wmw->dwStatus != MCI_MODE_STOP) {
+        /* mciStop handles MCI_NOTIFY_ABORTED */
 	dwRet = WAVE_mciStop(wDevID, MCI_WAIT, lpParms);
     }
 
@@ -654,9 +679,8 @@
     wmw->openParms.lpstrElementName = NULL;
 
     if ((dwFlags & MCI_NOTIFY) && lpParms) {
-	mciDriverNotify(HWND_32(LOWORD(lpParms->dwCallback)),
-			wmw->openParms.wDeviceID,
-			(dwRet == 0) ? MCI_NOTIFY_SUCCESSFUL : MCI_NOTIFY_FAILURE);
+	WAVE_mciNotify(lpParms->dwCallback, wmw,
+	    (dwRet == 0) ? MCI_NOTIFY_SUCCESSFUL : MCI_NOTIFY_FAILURE);
     }
 
     return 0;
@@ -712,6 +736,7 @@
     DWORD		dwRet;
     LPWAVEHDR		waveHdr = NULL;
     WINE_MCIWAVE*	wmw = WAVE_mciGetOpenDev(wDevID);
+    HANDLE		oldcb;
     int			whidx;
 
     TRACE("(%u, %08lX, %p);\n", wDevID, dwFlags, lpParms);
@@ -779,6 +804,11 @@
 
     TRACE("Playing from byte=%u to byte=%u\n", wmw->dwPosition, end);
 
+    oldcb = InterlockedExchangePointer(&wmw->hCallback,
+	(dwFlags & MCI_NOTIFY) ? HWND_32(LOWORD(lpParms->dwCallback)) : NULL);
+    if (oldcb) mciDriverNotify(oldcb, wDevID, MCI_NOTIFY_ABORTED);
+    oldcb = NULL;
+
     if (end <= wmw->dwPosition)
 	return MMSYSERR_NOERROR;
 
@@ -870,6 +900,9 @@
     dwRet = 0;
 
 cleanUp:
+    if (dwFlags & MCI_NOTIFY)
+	oldcb = InterlockedExchangePointer(&wmw->hCallback, NULL);
+
     HeapFree(GetProcessHeap(), 0, waveHdr);
 
     if (wmw->hWave) {
@@ -880,11 +913,9 @@
 
     wmw->dwStatus = MCI_MODE_STOP;
 
-    if (lpParms && (dwFlags & MCI_NOTIFY)) {
-	mciDriverNotify(HWND_32(LOWORD(lpParms->dwCallback)),
-			wmw->openParms.wDeviceID,
-			dwRet ? MCI_NOTIFY_FAILURE : MCI_NOTIFY_SUCCESSFUL);
-    }
+    /* Let the potentically asynchronous commands support FAILURE notification. */
+    if (oldcb) mciDriverNotify(oldcb, wDevID,
+	dwRet ? MCI_NOTIFY_FAILURE : MCI_NOTIFY_SUCCESSFUL);
 
     return dwRet;
 }
@@ -959,6 +990,7 @@
     LONG		bufsize;
     LPWAVEHDR		waveHdr = NULL;
     WINE_MCIWAVE*	wmw = WAVE_mciGetOpenDev(wDevID);
+    HANDLE		oldcb;
 
     TRACE("(%u, %08lX, %p);\n", wDevID, dwFlags, lpParms);
 
@@ -1013,6 +1045,11 @@
 
     TRACE("Recording from byte=%u to byte=%u\n", wmw->dwPosition, end);
 
+    oldcb = InterlockedExchangePointer(&wmw->hCallback,
+	(dwFlags & MCI_NOTIFY) ? HWND_32(LOWORD(lpParms->dwCallback)) : NULL);
+    if (oldcb) mciDriverNotify(oldcb, wDevID, MCI_NOTIFY_ABORTED);
+    oldcb = NULL;
+
     if (end <= wmw->dwPosition)
     {
 	return MMSYSERR_NOERROR;
@@ -1081,7 +1118,11 @@
     while (wmw->dwPosition < end && wmw->dwStatus != MCI_MODE_STOP && wmw->dwStatus != MCI_MODE_NOT_READY) {
 	WAVE_mciRecordWaitDone(wmw);
     }
-
+    /* Grab callback before another thread kicks in after we change dwStatus. */
+    if (dwFlags & MCI_NOTIFY) {
+	oldcb = InterlockedExchangePointer(&wmw->hCallback, NULL);
+	dwFlags &= ~MCI_NOTIFY;
+    }
     /* needed so that the callback above won't add again the buffers returned by the reset */
     wmw->dwStatus = MCI_MODE_STOP;
 
@@ -1093,6 +1134,9 @@
     dwRet = 0;
 
 cleanUp:
+    if (dwFlags & MCI_NOTIFY)
+	oldcb = InterlockedExchangePointer(&wmw->hCallback, NULL);
+
     HeapFree(GetProcessHeap(), 0, waveHdr);
 
     if (wmw->hWave) {
@@ -1103,11 +1147,8 @@
 
     wmw->dwStatus = MCI_MODE_STOP;
 
-    if (lpParms && (dwFlags & MCI_NOTIFY)) {
-	mciDriverNotify(HWND_32(LOWORD(lpParms->dwCallback)),
-			wmw->openParms.wDeviceID,
-			dwRet ? MCI_NOTIFY_FAILURE : MCI_NOTIFY_SUCCESSFUL);
-    }
+    if (oldcb) mciDriverNotify(oldcb, wDevID,
+	dwRet ? MCI_NOTIFY_FAILURE : MCI_NOTIFY_SUCCESSFUL);
 
     return dwRet;
 
@@ -1148,6 +1189,8 @@
     default:
 	dwRet = MCIERR_NONAPPLICABLE_FUNCTION;
     }
+    if (MMSYSERR_NOERROR==dwRet && (dwFlags & MCI_NOTIFY) && lpParms)
+	WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
     return dwRet;
 }
 
@@ -1189,6 +1232,8 @@
     default:
 	dwRet = MCIERR_NONAPPLICABLE_FUNCTION;
     }
+    if (MMSYSERR_NOERROR==dwRet && (dwFlags & MCI_NOTIFY) && lpParms)
+	WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
     return dwRet;
 }
 
@@ -1219,10 +1264,9 @@
 
     TRACE("Seeking to position=%u bytes\n", wmw->dwPosition);
 
-    if (dwFlags & MCI_NOTIFY) {
-	mciDriverNotify(HWND_32(LOWORD(lpParms->dwCallback)),
-			wmw->openParms.wDeviceID, MCI_NOTIFY_SUCCESSFUL);
-    }
+    if (dwFlags & MCI_NOTIFY)
+	WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
+
     return MMSYSERR_NOERROR;
 }
 
@@ -1327,6 +1371,8 @@
 	wmw->wfxRef.nSamplesPerSec = ((LPMCI_WAVE_SET_PARMS)lpParms)->nSamplesPerSec;
 	TRACE("MCI_WAVE_SET_SAMPLESPERSEC = %d\n", wmw->wfxRef.nSamplesPerSec);
     }
+    if (dwFlags & MCI_NOTIFY)
+	WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
     return 0;
 }
 
@@ -1337,7 +1383,6 @@
 {
     WINE_MCIWAVE*	wmw = WAVE_mciGetOpenDev(wDevID);
     DWORD		ret = MCIERR_FILE_NOT_SAVED, tmpRet;
-    WPARAM           	wparam = MCI_NOTIFY_FAILURE;
 
     TRACE("%d, %08X, %p);\n", wDevID, dwFlags, lpParms);
     if (lpParms == NULL)	return MCIERR_NULL_PARAMETER_BLOCK;
@@ -1372,12 +1417,8 @@
 	ret = MMSYSERR_NOERROR;
     }
 
-    if (dwFlags & MCI_NOTIFY) {
-	if (ret == MMSYSERR_NOERROR) wparam = MCI_NOTIFY_SUCCESSFUL;
-
-    	mciDriverNotify(HWND_32(LOWORD(lpParms->dwCallback)),
-			 wmw->openParms.wDeviceID, wparam);
-    }
+    if (MMSYSERR_NOERROR==ret && (dwFlags & MCI_NOTIFY))
+	WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
 
     if (ret == MMSYSERR_NOERROR)
         ret = WAVE_mciOpenFile(wmw, lpParms->lpfilename);
@@ -1502,10 +1543,8 @@
 	    return MCIERR_UNRECOGNIZED_COMMAND;
 	}
     }
-    if (dwFlags & MCI_NOTIFY) {
-	mciDriverNotify(HWND_32(LOWORD(lpParms->dwCallback)),
-			wmw->openParms.wDeviceID, MCI_NOTIFY_SUCCESSFUL);
-    }
+    if ((dwFlags & MCI_NOTIFY) && HRESULT_CODE(ret)==0)
+	WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
     return ret;
 }
 
@@ -1575,6 +1614,8 @@
 	WARN("No GetDevCaps-Item !\n");
 	return MCIERR_UNRECOGNIZED_COMMAND;
     }
+    if ((dwFlags & MCI_NOTIFY) && HRESULT_CODE(ret)==0)
+	WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
     return ret;
 }
 
@@ -1620,7 +1661,8 @@
     } else {
 	lpParms->lpstrReturn[0] = 0;
     }
-
+    if (MMSYSERR_NOERROR==ret && (dwFlags & MCI_NOTIFY))
+	WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
     return ret;
 }
 
diff --git a/dlls/winmm/tests/mci.c b/dlls/winmm/tests/mci.c
index 5ce8cd4..e6d83e2 100644
--- a/dlls/winmm/tests/mci.c
+++ b/dlls/winmm/tests/mci.c
@@ -416,8 +416,8 @@
     err = mciSendString("status mysound mode notify", buf, sizeof(buf), hwnd);
     ok(!err,"mci status mode returned error: %d\n", err);
     if(!err) ok(!strcmp(buf,"paused"), "mci status mode: %s\n", buf);
-    todo_wine test_notification1(hwnd,"play",MCI_NOTIFY_SUPERSEDED);
-    todo_wine test_notification1(hwnd,"status",MCI_NOTIFY_SUCCESSFUL);
+    test_notification(hwnd,"play",MCI_NOTIFY_SUPERSEDED);
+    test_notification(hwnd,"status",MCI_NOTIFY_SUCCESSFUL);
 
     buf[0]=0;
     err = mciSendString("status mysound position", buf, sizeof(buf), hwnd);
@@ -430,13 +430,14 @@
     ok(!err,"mci stop returned error: %s\n", dbg_mcierr(err));
 
     buf[0]=0;
-    err = mciSendString("info mysound file", buf, sizeof(buf), NULL);
+    err = mciSendString("info mysound file notify", buf, sizeof(buf), hwnd);
     ok(!err,"mci info file returned error: %d\n", err);
     if(!err) { /* fully qualified name */
         int len = strlen(buf);
         todo_wine ok(len>2 && buf[1]==':',"Expected full pathname from info file: %s\n", buf);
         ok(len>=12 && !strcmp(&buf[len-12],"tempfile.wav"), "info file returned: %s\n", buf);
     }
+    test_notification(hwnd,"info file",MCI_NOTIFY_SUCCESSFUL);
 
     buf[0]=0;
     err = mciSendString("status mysound mode", buf, sizeof(buf), hwnd);
@@ -473,8 +474,8 @@
 
     err = mciSendString("pause mysound notify", NULL, 0, NULL); /* notify no callback */
     ok(!err,"mci pause notify returned error: %s\n", dbg_mcierr(err));
-    /* Supersede even though pause cannot not notify given no callback */
-    todo_wine test_notification1(hwnd,"pause aborted play #1 notification",MCI_NOTIFY_SUPERSEDED);
+    /* Supersede even though pause cannot notify given no callback */
+    test_notification(hwnd,"pause aborted play #1 notification",MCI_NOTIFY_SUPERSEDED);
     test_notification(hwnd,"impossible pause notification",0);
 
     /* Seek or even Stop used to hang Wine on MacOS. */
@@ -490,7 +491,7 @@
     err = mciSendString("pause mysound wait", NULL, 0, NULL);
     ok(!err,"mci pause wait returned error: %d\n", err);
     /* Unlike sequencer and cdaudio, waveaudio's pause does not abort. */
-    todo_wine test_notification1(hwnd,"pause aborted play #2 notification",0);
+    test_notification(hwnd,"pause aborted play #2 notification",0);
 
     err = mciSendString("resume mysound wait", NULL, 0, NULL);
     ok(!err,"mci resume wait returned error: %d\n", err);
@@ -518,7 +519,7 @@
 
     err = mciSendString("close mysound wait", NULL, 0, NULL);
     ok(!err,"mci close wait returned error: %d\n", err);
-    todo_wine test_notification1(hwnd,"play (aborted by close)",MCI_NOTIFY_ABORTED);
+    test_notification(hwnd,"play (aborted by close)",MCI_NOTIFY_ABORTED);
 }
 
 static void test_AutoOpenWAVE(HWND hwnd)