From c4b94b1ba4ee1664c0a14515d6fbc6d648e9175b Mon Sep 17 00:00:00 2001
From: Andrew Eikum <aeikum@codeweavers.com>
Date: Wed, 31 Aug 2011 15:04:37 -0500
Subject: [PATCH] mmdevapi: Automatically select the correct driver.

---
 dlls/mmdevapi/main.c                          | 97 ++++++++++++-------
 dlls/mmdevapi/mmdevapi.h                      | 15 +++
 dlls/winealsa.drv/mmdevdrv.c                  | 13 +++
 dlls/winealsa.drv/winealsa.drv.spec           |  1 +
 dlls/winecoreaudio.drv/mmdevdrv.c             | 13 +++
 dlls/winecoreaudio.drv/winecoreaudio.drv.spec |  1 +
 dlls/wineoss.drv/mmdevdrv.c                   | 48 +++++++++
 dlls/wineoss.drv/wineoss.drv.spec             |  1 +
 8 files changed, 155 insertions(+), 34 deletions(-)

diff --git a/dlls/mmdevapi/main.c b/dlls/mmdevapi/main.c
index 57541aba10c..c2c3562e712 100644
--- a/dlls/mmdevapi/main.c
+++ b/dlls/mmdevapi/main.c
@@ -53,7 +53,22 @@ static HINSTANCE instance;
 
 DriverFuncs drvs;
 
-static BOOL load_driver(const WCHAR *name)
+static const char *get_priority_string(int prio)
+{
+    switch(prio){
+    case Priority_Unavailable:
+        return "Unavailable";
+    case Priority_Low:
+        return "Low";
+    case Priority_Neutral:
+        return "Neutral";
+    case Priority_Preferred:
+        return "Preferred";
+    }
+    return "Invalid";
+}
+
+static BOOL load_driver(const WCHAR *name, DriverFuncs *driver)
 {
     WCHAR driver_module[264];
     static const WCHAR wineW[] = {'w','i','n','e',0};
@@ -65,75 +80,89 @@ static BOOL load_driver(const WCHAR *name)
 
     TRACE("Attempting to load %s\n", wine_dbgstr_w(driver_module));
 
-    drvs.module = LoadLibraryW(driver_module);
-    if(!drvs.module){
+    driver->module = LoadLibraryW(driver_module);
+    if(!driver->module){
         TRACE("Unable to load %s: %u\n", wine_dbgstr_w(driver_module),
                 GetLastError());
         return FALSE;
     }
 
-#define LDFC(n) do { drvs.p##n = (void*)GetProcAddress(drvs.module, #n);\
-        if(!drvs.p##n) { FreeLibrary(drvs.module); return FALSE; } } while(0)
+#define LDFC(n) do { driver->p##n = (void*)GetProcAddress(driver->module, #n);\
+        if(!driver->p##n) { FreeLibrary(driver->module); return FALSE; } } while(0)
+    LDFC(GetPriority);
     LDFC(GetEndpointIDs);
     LDFC(GetAudioEndpoint);
     LDFC(GetAudioSessionManager);
 #undef LDFC
 
-    lstrcpyW(drvs.module_name, driver_module);
-    TRACE("Successfully loaded %s\n", wine_dbgstr_w(driver_module));
+    driver->priority = driver->pGetPriority();
+    lstrcpyW(driver->module_name, driver_module);
+
+    TRACE("Successfully loaded %s with priority %s\n",
+            wine_dbgstr_w(driver_module), get_priority_string(driver->priority));
 
     return TRUE;
 }
 
 static BOOL init_driver(void)
 {
-    static const WCHAR alsaW[] = {'a','l','s','a',0};
-    static const WCHAR ossW[] = {'o','s','s',0};
-    static const WCHAR coreaudioW[] = {'c','o','r','e','a','u','d','i','o',0};
-    static const WCHAR *default_drivers[] = { alsaW, coreaudioW, ossW };
     static const WCHAR drv_key[] = {'S','o','f','t','w','a','r','e','\\',
         'W','i','n','e','\\','D','r','i','v','e','r','s',0};
     static const WCHAR drv_value[] = {'A','u','d','i','o',0};
+
+    static WCHAR default_list[] = {'a','l','s','a',',','o','s','s',',',
+        'c','o','r','e','a','u','d','i','o',0};
+
+    DriverFuncs driver;
     HKEY key;
-    UINT i;
+    WCHAR reg_list[256], *p, *next, *driver_list = default_list;
 
     if(drvs.module)
         return TRUE;
 
     if(RegOpenKeyW(HKEY_CURRENT_USER, drv_key, &key) == ERROR_SUCCESS){
-        WCHAR driver_name[256], *p, *next;
-        DWORD size = sizeof(driver_name);
+        DWORD size = sizeof(reg_list);
 
-        if(RegQueryValueExW(key, drv_value, 0, NULL, (BYTE*)driver_name,
+        if(RegQueryValueExW(key, drv_value, 0, NULL, (BYTE*)reg_list,
                     &size) == ERROR_SUCCESS){
-            RegCloseKey(key);
-
-            if(driver_name[0] == '\0')
+            if(reg_list[0] == '\0'){
+                TRACE("User explicitly chose no driver\n");
+                RegCloseKey(key);
                 return TRUE;
-
-            for(next = p = driver_name; next; p = next + 1){
-                next = strchrW(p, ',');
-                if(next)
-                    *next = '\0';
-
-                if(load_driver(p))
-                    return TRUE;
-
-                TRACE("Failed to load driver: %s\n", wine_dbgstr_w(driver_name));
             }
 
-            ERR("No drivers in the registry loaded successfully!\n");
-            return FALSE;
+            driver_list = reg_list;
         }
 
         RegCloseKey(key);
     }
 
-    for(i = 0; i < sizeof(default_drivers)/sizeof(*default_drivers); ++i)
-        if(load_driver(default_drivers[i]))
-            return TRUE;
+    TRACE("Loading driver list %s\n", wine_dbgstr_w(driver_list));
+    for(next = p = driver_list; next; p = next + 1){
+        next = strchrW(p, ',');
+        if(next)
+            *next = '\0';
+
+        driver.priority = Priority_Unavailable;
+        if(load_driver(p, &driver)){
+            if(driver.priority == Priority_Unavailable)
+                FreeLibrary(driver.module);
+            else if(!drvs.module || driver.priority > drvs.priority){
+                TRACE("Selecting driver %s with priority %s\n",
+                        wine_dbgstr_w(p), get_priority_string(driver.priority));
+                if(drvs.module)
+                    FreeLibrary(drvs.module);
+                drvs = driver;
+            }else
+                FreeLibrary(driver.module);
+        }else
+            TRACE("Failed to load driver %s\n", wine_dbgstr_w(p));
+
+        if(next)
+            *next = ',';
+    }
 
-    return FALSE;
+    return drvs.module ? TRUE : FALSE;
 }
 
 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
diff --git a/dlls/mmdevapi/mmdevapi.h b/dlls/mmdevapi/mmdevapi.h
index e61c539d4a2..6ad1ab371c8 100644
--- a/dlls/mmdevapi/mmdevapi.h
+++ b/dlls/mmdevapi/mmdevapi.h
@@ -25,9 +25,24 @@ extern void MMDevEnum_Free(void) DECLSPEC_HIDDEN;
 
 extern HRESULT MMDevice_GetPropValue(const GUID *devguid, DWORD flow, REFPROPERTYKEY key, PROPVARIANT *pv) DECLSPEC_HIDDEN;
 
+/* Changes to this enum must be synced in drivers. */
+enum _DriverPriority {
+    Priority_Unavailable = 0, /* driver won't work */
+    Priority_Low, /* driver may work, but unlikely */
+    Priority_Neutral, /* driver makes no judgment */
+    Priority_Preferred /* driver thinks it's correct */
+};
+
 typedef struct _DriverFuncs {
     HMODULE module;
     WCHAR module_name[64];
+    int priority;
+
+    /* Returns a "priority" value for the driver. Highest priority wins.
+     * If multiple drivers think they are valid, they will return a
+     * priority value reflecting the likelihood that they are actually
+     * valid. See enum _DriverPriority. */
+    int WINAPI (*pGetPriority)(void);
 
     /* ids gets an array of human-friendly endpoint names
      * keys gets an array of driver-specific stuff that is used
diff --git a/dlls/winealsa.drv/mmdevdrv.c b/dlls/winealsa.drv/mmdevdrv.c
index 24dbeef1ea9..180dbc74870 100644
--- a/dlls/winealsa.drv/mmdevdrv.c
+++ b/dlls/winealsa.drv/mmdevdrv.c
@@ -223,6 +223,19 @@ BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, void *reserved)
     return TRUE;
 }
 
+/* From <dlls/mmdevapi/mmdevapi.h> */
+enum DriverPriority {
+    Priority_Unavailable = 0,
+    Priority_Low,
+    Priority_Neutral,
+    Priority_Preferred
+};
+
+int WINAPI AUDDRV_GetPriority(void)
+{
+    return Priority_Neutral;
+}
+
 static HRESULT alsa_get_card_devices(EDataFlow flow, WCHAR **ids, char **keys,
         UINT *num, snd_ctl_t *ctl, int card, const WCHAR *cardnameW)
 {
diff --git a/dlls/winealsa.drv/winealsa.drv.spec b/dlls/winealsa.drv/winealsa.drv.spec
index 5840f46e236..c87ec17c8e3 100644
--- a/dlls/winealsa.drv/winealsa.drv.spec
+++ b/dlls/winealsa.drv/winealsa.drv.spec
@@ -7,6 +7,7 @@
 @ stdcall -private wodMessage(long long long long long) ALSA_wodMessage
 
 # MMDevAPI driver functions
+@ stdcall -private GetPriority() AUDDRV_GetPriority
 @ stdcall -private GetEndpointIDs(long ptr ptr ptr ptr) AUDDRV_GetEndpointIDs
 @ stdcall -private GetAudioEndpoint(ptr ptr long ptr) AUDDRV_GetAudioEndpoint
 @ stdcall -private GetAudioSessionManager(ptr ptr) AUDDRV_GetAudioSessionManager
diff --git a/dlls/winecoreaudio.drv/mmdevdrv.c b/dlls/winecoreaudio.drv/mmdevdrv.c
index 8311a29d1a0..a951673ef6a 100644
--- a/dlls/winecoreaudio.drv/mmdevdrv.c
+++ b/dlls/winecoreaudio.drv/mmdevdrv.c
@@ -242,6 +242,19 @@ BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, void *reserved)
     return TRUE;
 }
 
+/* From <dlls/mmdevapi/mmdevapi.h> */
+enum DriverPriority {
+    Priority_Unavailable = 0,
+    Priority_Low,
+    Priority_Neutral,
+    Priority_Preferred
+};
+
+int WINAPI AUDDRV_GetPriority(void)
+{
+    return Priority_Neutral;
+}
+
 HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, WCHAR ***ids,
         AudioDeviceID ***keys, UINT *num, UINT *def_index)
 {
diff --git a/dlls/winecoreaudio.drv/winecoreaudio.drv.spec b/dlls/winecoreaudio.drv/winecoreaudio.drv.spec
index 14d95488f90..f1126c1a359 100644
--- a/dlls/winecoreaudio.drv/winecoreaudio.drv.spec
+++ b/dlls/winecoreaudio.drv/winecoreaudio.drv.spec
@@ -7,6 +7,7 @@
 @ stdcall -private mxdMessage(long long long long long) CoreAudio_mxdMessage
 
 # MMDevAPI driver functions
+@ stdcall -private GetPriority() AUDDRV_GetPriority
 @ stdcall -private GetEndpointIDs(long ptr ptr ptr) AUDDRV_GetEndpointIDs
 @ stdcall -private GetAudioEndpoint(str long ptr) AUDDRV_GetAudioEndpoint
 @ stdcall -private GetAudioSessionManager(ptr ptr) AUDDRV_GetAudioSessionManager
diff --git a/dlls/wineoss.drv/mmdevdrv.c b/dlls/wineoss.drv/mmdevdrv.c
index 64d60fec983..fe0c1d84489 100644
--- a/dlls/wineoss.drv/mmdevdrv.c
+++ b/dlls/wineoss.drv/mmdevdrv.c
@@ -233,6 +233,54 @@ BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, void *reserved)
     return TRUE;
 }
 
+/* From <dlls/mmdevapi/mmdevapi.h> */
+enum DriverPriority {
+    Priority_Unavailable = 0,
+    Priority_Low,
+    Priority_Neutral,
+    Priority_Preferred
+};
+
+int WINAPI AUDDRV_GetPriority(void)
+{
+    int mixer_fd;
+    oss_sysinfo sysinfo;
+
+    /* Attempt to determine if we are running on OSS or ALSA's OSS
+     * compatibility layer. There is no official way to do that, so just check
+     * for validity as best as possible, without rejecting valid OSS
+     * implementations. */
+
+    mixer_fd = open("/dev/mixer", O_RDONLY, 0);
+    if(mixer_fd < 0){
+        TRACE("Priority_Unavailable: open failed\n");
+        return Priority_Unavailable;
+    }
+
+    sysinfo.version[0] = 0xFF;
+    sysinfo.versionnum = ~0;
+    if(ioctl(mixer_fd, SNDCTL_SYSINFO, &sysinfo) < 0){
+        TRACE("Priority_Unavailable: ioctl failed\n");
+        close(mixer_fd);
+        return Priority_Unavailable;
+    }
+
+    close(mixer_fd);
+
+    if(sysinfo.version[0] < '4' || sysinfo.version[0] > '9'){
+        TRACE("Priority_Low: sysinfo.version[0]: %x\n", sysinfo.version[0]);
+        return Priority_Low;
+    }
+    if(sysinfo.versionnum & 0x80000000){
+        TRACE("Priority_Low: sysinfo.versionnum: %x\n", sysinfo.versionnum);
+        return Priority_Low;
+    }
+
+    TRACE("Priority_Preferred: Seems like valid OSS!\n");
+
+    return Priority_Preferred;
+}
+
 static UINT get_default_index(EDataFlow flow, char **keys, UINT num)
 {
     int fd = -1, err, i;
diff --git a/dlls/wineoss.drv/wineoss.drv.spec b/dlls/wineoss.drv/wineoss.drv.spec
index 5e5c4c1e975..4b1219655b6 100644
--- a/dlls/wineoss.drv/wineoss.drv.spec
+++ b/dlls/wineoss.drv/wineoss.drv.spec
@@ -8,6 +8,7 @@
 @ stdcall -private wodMessage(long long long long long) OSS_wodMessage
 
 # MMDevAPI driver functions
+@ stdcall -private GetPriority() AUDDRV_GetPriority
 @ stdcall -private GetEndpointIDs(long ptr ptr ptr ptr) AUDDRV_GetEndpointIDs
 @ stdcall -private GetAudioEndpoint(ptr ptr long ptr) AUDDRV_GetAudioEndpoint
 @ stdcall -private GetAudioSessionManager(ptr ptr) AUDDRV_GetAudioSessionManager
-- 
2.24.1