msacm: Implement acmDriverPriority with driver priority/enabled saving.
Foundation for notification broadcasts with support for deferred
notification.
diff --git a/dlls/msacm/driver.c b/dlls/msacm/driver.c
index eef7875..302cfee 100644
--- a/dlls/msacm/driver.c
+++ b/dlls/msacm/driver.c
@@ -404,22 +404,9 @@
  */
 MMRESULT WINAPI acmDriverPriority(HACMDRIVERID hadid, DWORD dwPriority, DWORD fdwPriority)
 {
-    PWINE_ACMDRIVERID padid;
-    CHAR szSubKey[17];
-    CHAR szBuffer[256];
-    LONG lBufferLength = sizeof(szBuffer);
-    LONG lError;
-    HKEY hPriorityKey;
-    DWORD dwPriorityCounter;
 
     TRACE("(%p, %08lx, %08lx)\n", hadid, dwPriority, fdwPriority);
 
-    padid = MSACM_GetDriverID(hadid);
-    if (!padid) {
-        WARN("invalid handle\n");
-	return MMSYSERR_INVALHANDLE;
-    }
-
     /* Check for unknown flags */
     if (fdwPriority &
 	~(ACM_DRIVERPRIORITYF_ENABLE|ACM_DRIVERPRIORITYF_DISABLE|
@@ -441,33 +428,91 @@
         WARN("invalid flag\n");
 	return MMSYSERR_INVALFLAG;
     }
+    
+    /* According to MSDN, ACM_DRIVERPRIORITYF_BEGIN and ACM_DRIVERPRIORITYF_END 
+       may only appear by themselves, and in addition, hadid and dwPriority must
+       both be zero */
+    if ((fdwPriority & ACM_DRIVERPRIORITYF_BEGIN) ||
+	(fdwPriority & ACM_DRIVERPRIORITYF_END)) {
+	if (fdwPriority & ~(ACM_DRIVERPRIORITYF_BEGIN|ACM_DRIVERPRIORITYF_END)) {
+	    WARN("ACM_DRIVERPRIORITYF_[BEGIN|END] cannot be used with any other flags\n");
+	    return MMSYSERR_INVALPARAM;
+	}
+	if (dwPriority) {
+	    WARN("priority invalid with ACM_DRIVERPRIORITYF_[BEGIN|END]\n");
+	    return MMSYSERR_INVALPARAM;
+	}
+	if (hadid) {
+	    WARN("non-null hadid invalid with ACM_DRIVERPRIORITYF_[BEGIN|END]\n");
+	    return MMSYSERR_INVALPARAM;
+	}
+	/* FIXME: MSDN wording suggests that deferred notification should be 
+	   implemented as a system-wide lock held by a calling task, and that 
+	   re-enabling notifications should broadcast them across all processes.
+	   This implementation uses a simple DWORD counter. One consequence of the
+	   current implementation is that applications will never see 
+	   MMSYSERR_ALLOCATED as a return error.
+	 */
+	if (fdwPriority & ACM_DRIVERPRIORITYF_BEGIN) {
+	    MSACM_DisableNotifications();
+	} else if (fdwPriority & ACM_DRIVERPRIORITYF_END) {
+	    MSACM_EnableNotifications();
+	}
+	return MMSYSERR_NOERROR;
+    } else {
+        PWINE_ACMDRIVERID padid;
+        BOOL bPerformBroadcast = FALSE;
 
-    lError = RegOpenKeyA(HKEY_CURRENT_USER,
-			 "Software\\Microsoft\\Multimedia\\"
-			 "Audio Compression Manager\\Priority v4.00",
-			 &hPriorityKey
-			 );
-    /* FIXME: Create key */
-    if (lError != ERROR_SUCCESS) {
-        WARN("RegOpenKeyA failed\n");
-	return MMSYSERR_ERROR;
+        /* Fetch driver ID */
+        padid = MSACM_GetDriverID(hadid);
+        if (!padid) {
+            WARN("invalid handle\n");
+	    return MMSYSERR_INVALHANDLE;
+        }
+        
+        /* Check whether driver ID is appropriate for requested op */
+        if (dwPriority) {
+            if (padid->fdwSupport & ACMDRIVERDETAILS_SUPPORTF_LOCAL) {
+                return MMSYSERR_NOTSUPPORTED;
+            }
+            if (dwPriority != 1 && dwPriority != -1) {
+                FIXME("unexpected priority %ld, using sign only\n", dwPriority);
+                if (dwPriority < 0) dwPriority = -1;
+                if (dwPriority > 0) dwPriority = 1;
+            }
+            
+            if (dwPriority == 1 && (padid->pPrevACMDriverID == NULL || 
+                (padid->pPrevACMDriverID->fdwSupport & ACMDRIVERDETAILS_SUPPORTF_LOCAL))) {
+                /* do nothing - driver is first of list, or first after last
+                   local driver */
+            } else if (dwPriority == -1 && padid->pNextACMDriverID == NULL) {
+                /* do nothing - driver is last of list */
+            } else {
+                MSACM_RePositionDriver(padid, dwPriority);
+                bPerformBroadcast = TRUE;
+            }
+        }
+
+        /* Check whether driver ID should be enabled or disabled */
+        if (fdwPriority & ACM_DRIVERPRIORITYF_DISABLE) {
+            if (!(padid->fdwSupport & ACMDRIVERDETAILS_SUPPORTF_DISABLED)) {
+                padid->fdwSupport |= ACMDRIVERDETAILS_SUPPORTF_DISABLED;
+                bPerformBroadcast = TRUE;
+            }
+        } else if (fdwPriority & ACM_DRIVERPRIORITYF_ENABLE) {
+            if (padid->fdwSupport & ACMDRIVERDETAILS_SUPPORTF_DISABLED) {
+                padid->fdwSupport &= ~ACMDRIVERDETAILS_SUPPORTF_DISABLED;
+                bPerformBroadcast = TRUE;
+            }
+        }
+        
+        /* Perform broadcast of changes */
+        if (bPerformBroadcast) {
+            MSACM_WriteCurrentPriorities();
+            MSACM_BroadcastNotification();
+        }
+        return MMSYSERR_NOERROR;
     }
-
-    for (dwPriorityCounter = 1; ; dwPriorityCounter++)	{
-	snprintf(szSubKey, 17, "Priority%ld", dwPriorityCounter);
-	lError = RegQueryValueA(hPriorityKey, szSubKey, szBuffer, &lBufferLength);
-	if (lError != ERROR_SUCCESS)
-	    break;
-
-	FIXME("(%p, %ld, %ld): stub (partial)\n",
-	      hadid, dwPriority, fdwPriority);
-	break;
-    }
-
-    RegCloseKey(hPriorityKey);
-
-    WARN("RegQueryValueA failed\n");
-    return MMSYSERR_ERROR;
 }
 
 /***********************************************************************
@@ -491,6 +536,7 @@
     }
 
     MSACM_UnregisterDriver(padid);
+    MSACM_BroadcastNotification();
 
     return MMSYSERR_NOERROR;
 }
diff --git a/dlls/msacm/internal.c b/dlls/msacm/internal.c
index 19b64cb..a5eb350 100644
--- a/dlls/msacm/internal.c
+++ b/dlls/msacm/internal.c
@@ -46,6 +46,9 @@
 PWINE_ACMDRIVERID MSACM_pFirstACMDriverID = NULL;
 PWINE_ACMDRIVERID MSACM_pLastACMDriverID = NULL;
 
+static DWORD MSACM_suspendBroadcastCount = 0;
+static BOOL MSACM_pendingBroadcast = FALSE;
+
 static void MSACM_ReorderDriversByPriority(void);
 
 #if 0
@@ -334,6 +337,90 @@
 }
 
 /***********************************************************************
+ *           MSACM_BroadcastNotification()
+ */
+void MSACM_BroadcastNotification(void)
+{
+    if (MSACM_suspendBroadcastCount <= 0) {
+        FIXME("notification broadcast not (yet) implemented\n");
+    } else {
+        MSACM_pendingBroadcast = TRUE;
+    }
+}
+
+/***********************************************************************
+ *           MSACM_DisableNotifications()
+ */
+void MSACM_DisableNotifications(void)
+{
+    MSACM_suspendBroadcastCount++;
+}
+
+/***********************************************************************
+ *           MSACM_EnableNotifications()
+ */
+void MSACM_EnableNotifications(void)
+{
+    if (MSACM_suspendBroadcastCount > 0) {
+        MSACM_suspendBroadcastCount--;
+        if (MSACM_suspendBroadcastCount == 0 && MSACM_pendingBroadcast) {
+            MSACM_pendingBroadcast = FALSE;
+            MSACM_BroadcastNotification();
+        }
+    }
+}
+
+/***********************************************************************
+ *           MSACM_RePositionDriver()
+ */
+void MSACM_RePositionDriver(PWINE_ACMDRIVERID padid, DWORD dwPriority)
+{
+    PWINE_ACMDRIVERID pTargetPosition = NULL;
+                
+    /* Remove selected driver from linked list */
+    if (MSACM_pFirstACMDriverID == padid) {
+        MSACM_pFirstACMDriverID = padid->pNextACMDriverID;
+    }
+    if (MSACM_pLastACMDriverID == padid) {
+        MSACM_pLastACMDriverID = padid->pPrevACMDriverID;
+    }
+    if (padid->pPrevACMDriverID != NULL) {
+        padid->pPrevACMDriverID->pNextACMDriverID = padid->pNextACMDriverID;
+    }
+    if (padid->pNextACMDriverID != NULL) {
+        padid->pNextACMDriverID->pPrevACMDriverID = padid->pPrevACMDriverID;
+    }
+    
+    /* Look up position where selected driver should be */
+    if (dwPriority == 1) {
+        pTargetPosition = padid->pPrevACMDriverID;
+        while (pTargetPosition->pPrevACMDriverID != NULL &&
+            !(pTargetPosition->pPrevACMDriverID->fdwSupport & ACMDRIVERDETAILS_SUPPORTF_LOCAL)) {
+            pTargetPosition = pTargetPosition->pPrevACMDriverID;
+        }
+    } else if (dwPriority == -1) {
+        pTargetPosition = padid->pNextACMDriverID;
+        while (pTargetPosition->pNextACMDriverID != NULL) {
+            pTargetPosition = pTargetPosition->pNextACMDriverID;
+        }
+    }
+    
+    /* Place selected driver in selected position */
+    padid->pPrevACMDriverID = pTargetPosition->pPrevACMDriverID;
+    padid->pNextACMDriverID = pTargetPosition;
+    if (padid->pPrevACMDriverID != NULL) {
+        padid->pPrevACMDriverID->pNextACMDriverID = padid;
+    } else {
+        MSACM_pFirstACMDriverID = padid;
+    }
+    if (padid->pNextACMDriverID != NULL) {
+        padid->pNextACMDriverID->pPrevACMDriverID = padid;
+    } else {
+        MSACM_pLastACMDriverID = padid;
+    }
+}
+
+/***********************************************************************
  *           MSACM_ReorderDriversByPriority()
  * Reorders all drivers based on the priority list indicated by the registry key:
  * HKCU\\Software\\Microsoft\\Multimedia\\Audio Compression Manager\\Priority v4.00
@@ -446,6 +533,79 @@
 }
 
 /***********************************************************************
+ *           MSACM_WriteCurrentPriorities()
+ * Writes out current order of driver priorities to registry key:
+ * HKCU\\Software\\Microsoft\\Multimedia\\Audio Compression Manager\\Priority v4.00
+ */
+void MSACM_WriteCurrentPriorities(void)
+{
+    LONG lError;
+    HKEY hPriorityKey;
+    static const WCHAR basePriorityKey[] = {
+        'S','o','f','t','w','a','r','e','\\',
+        'M','i','c','r','o','s','o','f','t','\\',
+        'M','u','l','t','i','m','e','d','i','a','\\',
+        'A','u','d','i','o',' ','C','o','m','p','r','e','s','s','i','o','n',' ','M','a','n','a','g','e','r','\\',
+        'P','r','i','o','r','i','t','y',' ','v','4','.','0','0','\0'
+    };
+    PWINE_ACMDRIVERID padid;
+    DWORD dwPriorityCounter;
+    static const WCHAR priorityTmpl[] = {'P','r','i','o','r','i','t','y','%','l','d','\0'};
+    static const WCHAR valueTmpl[] = {'%','c',',',' ','%','s','\0'};
+    static const WCHAR converterAlias[] = {'I','n','t','e','r','n','a','l',' ','P','C','M',' ','C','o','n','v','e','r','t','e','r','\0'};
+    WCHAR szSubKey[17];
+    WCHAR szBuffer[256];
+
+    /* Delete ACM priority key and create it anew */
+    lError = RegDeleteKeyW(HKEY_CURRENT_USER, basePriorityKey);
+    if (lError != ERROR_SUCCESS && lError != ERROR_FILE_NOT_FOUND) {
+        ERR("unable to remove current key %s (0x%08lx) - priority changes won't persist past application end.\n",
+            debugstr_w(basePriorityKey), lError);
+        return;
+    }
+    lError = RegCreateKeyW(HKEY_CURRENT_USER, basePriorityKey, &hPriorityKey);
+    if (lError != ERROR_SUCCESS) {
+        ERR("unable to create key %s (0x%08lx) - priority changes won't persist past application end.\n",
+            debugstr_w(basePriorityKey), lError);
+        return;
+    }
+    
+    /* Write current list of priorities */
+    for (dwPriorityCounter = 0, padid = MSACM_pFirstACMDriverID; padid; padid = padid->pNextACMDriverID) {        
+        if (padid->fdwSupport & ACMDRIVERDETAILS_SUPPORTF_LOCAL) continue;
+        if (padid->pszDriverAlias == NULL) continue;    /* internal PCM converter is last */
+
+        /* Build required value name */
+        dwPriorityCounter++;
+        snprintfW(szSubKey, 17, priorityTmpl, dwPriorityCounter);
+        
+        /* Value has a 1 in front for enabled drivers and 0 for disabled drivers */
+        snprintfW(szBuffer, 256, valueTmpl, (padid->fdwSupport & ACMDRIVERDETAILS_SUPPORTF_DISABLED) ? '0' : '1', padid->pszDriverAlias);
+        strlwrW(szBuffer);
+        
+        lError = RegSetValueExW(hPriorityKey, szSubKey, 0, REG_SZ, (BYTE *)szBuffer, (strlenW(szBuffer) + 1) * sizeof(WCHAR));
+        if (lError != ERROR_SUCCESS) {
+            ERR("unable to write value for %s under key %s (0x%08lx)\n",
+                debugstr_w(padid->pszDriverAlias), debugstr_w(basePriorityKey), lError);
+        }
+    }
+    
+    /* Build required value name */
+    dwPriorityCounter++;
+    snprintfW(szSubKey, 17, priorityTmpl, dwPriorityCounter);
+        
+    /* Value has a 1 in front for enabled drivers and 0 for disabled drivers */
+    snprintfW(szBuffer, 256, valueTmpl, '1', converterAlias);
+        
+    lError = RegSetValueExW(hPriorityKey, szSubKey, 0, REG_SZ, (BYTE *)szBuffer, (strlenW(szBuffer) + 1) * sizeof(WCHAR));
+    if (lError != ERROR_SUCCESS) {
+        ERR("unable to write value for %s under key %s (0x%08lx)\n",
+            debugstr_w(converterAlias), debugstr_w(basePriorityKey), lError);
+    }
+    RegCloseKey(hPriorityKey);
+}
+
+/***********************************************************************
  *           MSACM_UnregisterDriver()
  */
 PWINE_ACMDRIVERID MSACM_UnregisterDriver(PWINE_ACMDRIVERID p)
diff --git a/dlls/msacm/wineacm.h b/dlls/msacm/wineacm.h
index c844ab5..0a3af41 100644
--- a/dlls/msacm/wineacm.h
+++ b/dlls/msacm/wineacm.h
@@ -348,6 +348,12 @@
 extern MMRESULT MSACM_Message(HACMDRIVER, UINT, LPARAM, LPARAM);
 extern BOOL MSACM_FindFormatTagInCache(WINE_ACMDRIVERID*, DWORD, LPDWORD);
 
+extern void MSACM_RePositionDriver(PWINE_ACMDRIVERID, DWORD);
+extern void MSACM_WriteCurrentPriorities(void);
+extern void MSACM_BroadcastNotification(void);
+extern void MSACM_DisableNotifications(void);
+extern void MSACM_EnableNotifications(void);
+
 /* From msacm32.c */
 extern HINSTANCE MSACM_hInstance32;