This exposes the actual devices (and virtual sinks/sources) as reported by PulseAudio to an application, allowing it to select the devices itself and, for example, record from (or render to) two devices at the same time. The "PulseAudio" device (which is movable) is still the default, as before, with the same GUID to preserve compatibility with existing setups.
Based on a patch by Mark Harmstone mark@harmstone.com, with changes by Sebastian Lackner sebastian@fds-team.de.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/winepulse.drv/mmdevdrv.c | 205 ++++++++++++++++++++++++++++------ dlls/winepulse.drv/pulse.c | 140 +++++++++++++++++++++-- dlls/winepulse.drv/unixlib.h | 20 ++++ 3 files changed, 321 insertions(+), 44 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index e3d5ea9..3e6ac7c 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -69,6 +69,8 @@ static GUID pulse_render_guid = static GUID pulse_capture_guid = { 0x25da76d0, 0x033c, 0x4235, { 0x90, 0x02, 0x19, 0xf4, 0x88, 0x94, 0xac, 0x6f } };
+static const WCHAR *drv_key_devicesW = L"Software\Wine\Drivers\winepulse.drv\devices"; + static CRITICAL_SECTION session_cs; static CRITICAL_SECTION_DEBUG session_cs_debug = { 0, 0, &session_cs, @@ -145,9 +147,9 @@ struct ACImpl {
AudioSession *session; AudioSessionWrapper *session_wrapper; -};
-static const WCHAR defaultW[] = L"PulseAudio"; + char pulse_name[0]; +};
static const IAudioClient3Vtbl AudioClient3_Vtbl; static const IAudioRenderClientVtbl AudioRenderClient_Vtbl; @@ -267,39 +269,108 @@ static void set_stream_volumes(ACImpl *This) pulse_call(set_volumes, ¶ms); }
-HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, const WCHAR ***ids, GUID **keys, - UINT *num, UINT *def_index) +static void get_device_guid(HKEY drv_key, EDataFlow flow, const char *pulse_name, GUID *guid) { - WCHAR *id; + WCHAR key_name[MAX_PULSE_NAME_LEN + 2]; + DWORD type, size = sizeof(*guid); + LSTATUS status; + HKEY dev_key;
- TRACE("%d %p %p %p\n", flow, ids, num, def_index); + if (!pulse_name[0]) { + *guid = (flow == eRender) ? pulse_render_guid : pulse_capture_guid; + return; + }
- *num = 1; - *def_index = 0; + if (!drv_key) { + CoCreateGuid(guid); + return; + }
- *ids = HeapAlloc(GetProcessHeap(), 0, sizeof(**ids)); - *keys = NULL; - if (!*ids) - return E_OUTOFMEMORY; + key_name[0] = (flow == eRender) ? '0' : '1'; + key_name[1] = ','; + MultiByteToWideChar(CP_UNIXCP, 0, pulse_name, -1, key_name + 2, ARRAY_SIZE(key_name) - 2);
- (*ids)[0] = id = HeapAlloc(GetProcessHeap(), 0, sizeof(defaultW)); - *keys = HeapAlloc(GetProcessHeap(), 0, sizeof(**keys)); - if (!*keys || !id) { - HeapFree(GetProcessHeap(), 0, id); - HeapFree(GetProcessHeap(), 0, *keys); - HeapFree(GetProcessHeap(), 0, *ids); - *ids = NULL; - *keys = NULL; - return E_OUTOFMEMORY; + status = RegCreateKeyExW(drv_key, key_name, 0, NULL, 0, KEY_READ | KEY_WRITE | KEY_WOW64_64KEY, + NULL, &dev_key, NULL); + if (status != ERROR_SUCCESS) { + ERR("Failed to open registry key for device %s: %u\n", pulse_name, status); + CoCreateGuid(guid); + return; } - memcpy(id, defaultW, sizeof(defaultW));
- if (flow == eRender) - (*keys)[0] = pulse_render_guid; - else - (*keys)[0] = pulse_capture_guid; + status = RegQueryValueExW(dev_key, L"guid", 0, &type, (BYTE*)guid, &size); + if (status != ERROR_SUCCESS || type != REG_BINARY || size != sizeof(*guid)) { + CoCreateGuid(guid); + status = RegSetValueExW(dev_key, L"guid", 0, REG_BINARY, (BYTE*)guid, sizeof(*guid)); + if (status != ERROR_SUCCESS) + ERR("Failed to store device GUID for %s to registry: %u\n", pulse_name, status); + } + RegCloseKey(dev_key); +}
- return S_OK; +HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, WCHAR ***ids_out, GUID **keys, + UINT *num, UINT *def_index) +{ + struct get_endpoint_ids_params params; + GUID *guids = NULL; + WCHAR **ids = NULL; + unsigned int i = 0; + LSTATUS status; + HKEY drv_key; + + TRACE("%d %p %p %p\n", flow, ids_out, num, def_index); + + params.flow = flow; + params.size = MAX_PULSE_NAME_LEN * 4; + params.endpoints = NULL; + do { + HeapFree(GetProcessHeap(), 0, params.endpoints); + params.endpoints = HeapAlloc(GetProcessHeap(), 0, params.size); + pulse_call(get_endpoint_ids, ¶ms); + } while(params.result == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)); + + if (FAILED(params.result)) + goto end; + + ids = HeapAlloc(GetProcessHeap(), 0, params.num * sizeof(*ids)); + guids = HeapAlloc(GetProcessHeap(), 0, params.num * sizeof(*guids)); + if (!ids || !guids) { + params.result = E_OUTOFMEMORY; + goto end; + } + + status = RegCreateKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, NULL, 0, + KEY_WRITE | KEY_WOW64_64KEY, NULL, &drv_key, NULL); + if (status != ERROR_SUCCESS) { + ERR("Failed to open devices registry key: %u\n", status); + drv_key = NULL; + } + + for (i = 0; i < params.num; i++) { + unsigned int size = (wcslen(params.endpoints[i].name) + 1) * sizeof(WCHAR); + if (!(ids[i] = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR)))) { + params.result = E_OUTOFMEMORY; + break; + } + memcpy(ids[i], params.endpoints[i].name, size); + get_device_guid(drv_key, flow, params.endpoints[i].pulse_name, &guids[i]); + } + if (drv_key) + RegCloseKey(drv_key); + +end: + HeapFree(GetProcessHeap(), 0, params.endpoints); + if (FAILED(params.result)) { + HeapFree(GetProcessHeap(), 0, guids); + while (i--) HeapFree(GetProcessHeap(), 0, ids[i]); + HeapFree(GetProcessHeap(), 0, ids); + } else { + *ids_out = ids; + *keys = guids; + *num = params.num; + *def_index = params.default_idx; + } + return params.result; }
int WINAPI AUDDRV_GetPriority(void) @@ -314,23 +385,87 @@ int WINAPI AUDDRV_GetPriority(void) return SUCCEEDED(params.result) ? Priority_Preferred : Priority_Unavailable; }
+static BOOL get_pulse_name_by_guid(const GUID *guid, char pulse_name[MAX_PULSE_NAME_LEN], EDataFlow *flow) +{ + WCHAR key_name[MAX_PULSE_NAME_LEN + 2]; + DWORD key_name_size; + DWORD index = 0; + HKEY key; + + /* Return empty string for default PulseAudio device */ + pulse_name[0] = 0; + if (IsEqualGUID(guid, &pulse_render_guid)) { + *flow = eRender; + return TRUE; + } else if (IsEqualGUID(guid, &pulse_capture_guid)) { + *flow = eCapture; + return TRUE; + } + + if (RegOpenKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, KEY_READ | KEY_WOW64_64KEY, &key) != ERROR_SUCCESS) { + WARN("No devices found in registry\n"); + return FALSE; + } + + for (;;) { + DWORD size, type; + LSTATUS status; + GUID reg_guid; + HKEY dev_key; + + key_name_size = ARRAY_SIZE(key_name); + if (RegEnumKeyExW(key, index++, key_name, &key_name_size, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) + break; + + if (RegOpenKeyExW(key, key_name, 0, KEY_READ | KEY_WOW64_64KEY, &dev_key) != ERROR_SUCCESS) { + ERR("Couldn't open key: %s\n", wine_dbgstr_w(key_name)); + continue; + } + + size = sizeof(reg_guid); + status = RegQueryValueExW(dev_key, L"guid", 0, &type, (BYTE *)®_guid, &size); + RegCloseKey(dev_key); + + if (status == ERROR_SUCCESS && type == REG_BINARY && size == sizeof(reg_guid) && IsEqualGUID(®_guid, guid)) { + RegCloseKey(key); + + TRACE("Found matching device key: %s\n", wine_dbgstr_w(key_name)); + + if (key_name[0] == '0') + *flow = eRender; + else if (key_name[0] == '1') + *flow = eCapture; + else { + WARN("Unknown device type: %c\n", key_name[0]); + return FALSE; + } + + return WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, pulse_name, MAX_PULSE_NAME_LEN, NULL, NULL); + } + } + + RegCloseKey(key); + WARN("No matching device in registry for GUID %s\n", debugstr_guid(guid)); + return FALSE; +} + HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient **out) { ACImpl *This; + char pulse_name[MAX_PULSE_NAME_LEN]; EDataFlow dataflow; + unsigned len; HRESULT hr;
TRACE("%s %p %p\n", debugstr_guid(guid), dev, out); - if (IsEqualGUID(guid, &pulse_render_guid)) - dataflow = eRender; - else if (IsEqualGUID(guid, &pulse_capture_guid)) - dataflow = eCapture; - else - return E_UNEXPECTED; + + if (!get_pulse_name_by_guid(guid, pulse_name, &dataflow)) + return AUDCLNT_E_DEVICE_INVALIDATED;
*out = NULL;
- This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This)); + len = strlen(pulse_name) + 1; + This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, FIELD_OFFSET(ACImpl, pulse_name[len])); if (!This) return E_OUTOFMEMORY;
@@ -342,6 +477,7 @@ HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient This->IAudioStreamVolume_iface.lpVtbl = &AudioStreamVolume_Vtbl; This->dataflow = dataflow; This->parent = dev; + memcpy(This->pulse_name, pulse_name, len);
hr = CoCreateFreeThreadedMarshaler((IUnknown*)&This->IAudioClient3_iface, &This->marshal); if (FAILED(hr)) { @@ -609,6 +745,7 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface, }
params.name = name = get_application_name(); + params.pulse_name = This->pulse_name; params.dataflow = This->dataflow; params.mode = mode; params.flags = flags; diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 3e65936..311c9bb 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -81,6 +81,12 @@ typedef struct _ACPacket UINT32 discont; } ACPacket;
+typedef struct _PhysDevice { + struct list entry; + WCHAR *name; + char pulse_name[0]; +} PhysDevice; + static pa_context *pulse_ctx; static pa_mainloop *pulse_ml;
@@ -89,6 +95,8 @@ static WAVEFORMATEXTENSIBLE pulse_fmt[2]; static REFERENCE_TIME pulse_min_period[2], pulse_def_period[2];
static UINT g_phys_speakers_mask = 0; +static struct list g_phys_speakers = LIST_INIT(g_phys_speakers); +static struct list g_phys_sources = LIST_INIT(g_phys_sources);
static const REFERENCE_TIME MinimumPeriod = 30000; static const REFERENCE_TIME DefaultPeriod = 100000; @@ -128,6 +136,20 @@ static void dump_attr(const pa_buffer_attr *attr) TRACE("prebuf: %u\n", attr->prebuf); }
+static void free_phys_device_lists(void) +{ + static struct list *const lists[] = { &g_phys_speakers, &g_phys_sources, NULL }; + struct list *const *list = lists; + PhysDevice *dev, *dev_next; + + do { + LIST_FOR_EACH_ENTRY_SAFE(dev, dev_next, *list, PhysDevice, entry) { + free(dev->name); + free(dev); + } + } while (*(++list)); +} + /* copied from kernelbase */ static int muldiv(int a, int b, int c) { @@ -190,6 +212,7 @@ static NTSTATUS pulse_process_attach(void *args)
static NTSTATUS pulse_process_detach(void *args) { + free_phys_device_lists(); if (pulse_ctx) { pa_context_disconnect(pulse_ctx); @@ -215,6 +238,44 @@ static NTSTATUS pulse_main_loop(void *args) return STATUS_SUCCESS; }
+static NTSTATUS pulse_get_endpoint_ids(void *args) +{ + struct get_endpoint_ids_params *params = args; + struct list *list = (params->flow == eRender) ? &g_phys_speakers : &g_phys_sources; + struct endpoint *endpoint = params->endpoints; + DWORD len, name_len, needed; + PhysDevice *dev; + char *ptr; + + params->num = list_count(list); + needed = params->num * sizeof(*params->endpoints); + ptr = (char*)(endpoint + params->num); + + LIST_FOR_EACH_ENTRY(dev, list, PhysDevice, entry) { + name_len = lstrlenW(dev->name) + 1; + len = strlen(dev->pulse_name) + 1; + needed += name_len * sizeof(WCHAR) + ((len + 1) & ~1); + + if (needed <= params->size) { + endpoint->name = (WCHAR*)ptr; + memcpy(endpoint->name, dev->name, name_len * sizeof(WCHAR)); + ptr += name_len * sizeof(WCHAR); + endpoint->pulse_name = ptr; + memcpy(endpoint->pulse_name, dev->pulse_name, len); + ptr += (len + 1) & ~1; + endpoint++; + } + } + params->default_idx = 0; + + if (needed > params->size) { + params->size = needed; + params->result = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } else + params->result = S_OK; + return STATUS_SUCCESS; +} + static void pulse_contextcallback(pa_context *c, void *userdata) { switch (pa_context_get_state(c)) { @@ -357,12 +418,49 @@ static DWORD pulse_channel_map_to_channel_mask(const pa_channel_map *map) return mask; }
-/* For default PulseAudio render device, OR together all of the - * PKEY_AudioEndpoint_PhysicalSpeakers values of the sinks. */ +static void pulse_add_device(struct list *list, const char *pulse_name, const char *name) +{ + DWORD len = strlen(pulse_name), name_len = strlen(name); + PhysDevice *dev = malloc(FIELD_OFFSET(PhysDevice, pulse_name[len + 1])); + WCHAR *wname; + + if (!dev) + return; + + if (!(wname = malloc((name_len + 1) * sizeof(WCHAR)))) { + free(dev); + return; + } + + if (!(name_len = ntdll_umbstowcs(name, name_len, wname, name_len)) || + !(dev->name = realloc(wname, (name_len + 1) * sizeof(WCHAR)))) { + free(wname); + free(dev); + return; + } + dev->name[name_len] = 0; + memcpy(dev->pulse_name, pulse_name, len + 1); + + list_add_tail(list, &dev->entry); +} + static void pulse_phys_speakers_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { - if (i) - g_phys_speakers_mask |= pulse_channel_map_to_channel_mask(&i->channel_map); + if (!i || !i->name || !i->name[0]) + return; + + /* For default PulseAudio render device, OR together all of the + * PKEY_AudioEndpoint_PhysicalSpeakers values of the sinks. */ + g_phys_speakers_mask |= pulse_channel_map_to_channel_mask(&i->channel_map); + + pulse_add_device(&g_phys_speakers, i->name, i->description); +} + +static void pulse_phys_sources_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) +{ + if (!i || !i->name || !i->name[0]) + return; + pulse_add_device(&g_phys_sources, i->name, i->description); }
/* For most hardware on Windows, users must choose a configuration with an even @@ -579,7 +677,14 @@ static NTSTATUS pulse_test_connect(void *args) pulse_probe_settings(1, &pulse_fmt[0]); pulse_probe_settings(0, &pulse_fmt[1]);
+ free_phys_device_lists(); + list_init(&g_phys_speakers); + list_init(&g_phys_sources); g_phys_speakers_mask = 0; + + pulse_add_device(&g_phys_speakers, "", "PulseAudio"); + pulse_add_device(&g_phys_sources, "", "PulseAudio"); + o = pa_context_get_sink_info_list(pulse_ctx, &pulse_phys_speakers_cb, NULL); if (o) { while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 && @@ -588,6 +693,14 @@ static NTSTATUS pulse_test_connect(void *args) pa_operation_unref(o); }
+ o = pa_context_get_source_info_list(pulse_ctx, &pulse_phys_sources_cb, NULL); + if (o) { + while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 && + pa_operation_get_state(o) == PA_OPERATION_RUNNING) + {} + pa_operation_unref(o); + } + pa_context_unref(pulse_ctx); pulse_ctx = NULL; pa_mainloop_free(pulse_ml); @@ -771,8 +884,9 @@ static HRESULT pulse_spec_from_waveformat(struct pulse_stream *stream, const WAV return S_OK; }
-static HRESULT pulse_stream_connect(struct pulse_stream *stream, UINT32 period_bytes) +static HRESULT pulse_stream_connect(struct pulse_stream *stream, const char *pulse_name, UINT32 period_bytes) { + pa_stream_flags_t flags = PA_STREAM_START_CORKED | PA_STREAM_START_UNMUTED | PA_STREAM_ADJUST_LATENCY; int ret; char buffer[64]; static LONG number; @@ -797,12 +911,17 @@ static HRESULT pulse_stream_connect(struct pulse_stream *stream, UINT32 period_b attr.maxlength = stream->bufsize_frames * pa_frame_size(&stream->ss); attr.prebuf = pa_frame_size(&stream->ss); dump_attr(&attr); + + /* If specific device was requested, use it exactly */ + if (pulse_name[0]) + flags |= PA_STREAM_DONT_MOVE; + else + pulse_name = NULL; /* use default */ + if (stream->dataflow == eRender) - ret = pa_stream_connect_playback(stream->stream, NULL, &attr, - PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_ADJUST_LATENCY, NULL, NULL); + ret = pa_stream_connect_playback(stream->stream, pulse_name, &attr, flags, NULL, NULL); else - ret = pa_stream_connect_record(stream->stream, NULL, &attr, - PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_ADJUST_LATENCY); + ret = pa_stream_connect_record(stream->stream, pulse_name, &attr, flags); if (ret < 0) { WARN("Returns %i\n", ret); return AUDCLNT_E_ENDPOINT_CREATE_FAILED; @@ -864,7 +983,7 @@ static NTSTATUS pulse_create_stream(void *args)
stream->share = params->mode; stream->flags = params->flags; - hr = pulse_stream_connect(stream, stream->period_bytes); + hr = pulse_stream_connect(stream, params->pulse_name, stream->period_bytes); if (SUCCEEDED(hr)) { UINT32 unalign; const pa_buffer_attr *attr = pa_stream_get_buffer_attr(stream->stream); @@ -1967,6 +2086,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = pulse_process_attach, pulse_process_detach, pulse_main_loop, + pulse_get_endpoint_ids, pulse_create_stream, pulse_release_stream, pulse_start, diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index d28a73c..982704a 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -19,6 +19,8 @@ #include "wine/list.h" #include "wine/unixlib.h"
+#define MAX_PULSE_NAME_LEN 256 + struct pulse_stream;
struct pulse_config @@ -32,14 +34,31 @@ struct pulse_config unsigned int speakers_mask; };
+struct endpoint +{ + WCHAR *name; + char *pulse_name; +}; + struct main_loop_params { HANDLE event; };
+struct get_endpoint_ids_params +{ + EDataFlow flow; + struct endpoint *endpoints; + unsigned int size; + HRESULT result; + unsigned int num; + unsigned int default_idx; +}; + struct create_stream_params { const char *name; + const char *pulse_name; EDataFlow dataflow; AUDCLNT_SHAREMODE mode; DWORD flags; @@ -191,6 +210,7 @@ enum unix_funcs process_attach, process_detach, main_loop, + get_endpoint_ids, create_stream, release_stream, start,
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
Will be useful for next patch since we'll have to look it up on each prop call.
dlls/winepulse.drv/mmdevdrv.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 3e6ac7c..f6e754d 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -63,6 +63,14 @@ static struct pulse_config pulse_config;
static HANDLE pulse_thread; static struct list g_sessions = LIST_INIT(g_sessions); +static struct list g_devices_cache = LIST_INIT(g_devices_cache); + +struct device_cache { + struct list entry; + GUID guid; + EDataFlow dataflow; + char pulse_name[0]; +};
static GUID pulse_render_guid = { 0xfd47d9cc, 0x4218, 0x4135, { 0x9c, 0xe2, 0x0c, 0x19, 0x5c, 0x87, 0x40, 0x5b } }; @@ -90,6 +98,10 @@ BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, void *reserved) if (__wine_unix_call(pulse_handle, process_attach, NULL)) return FALSE; } else if (reason == DLL_PROCESS_DETACH) { + struct device_cache *device, *device_next; + + LIST_FOR_EACH_ENTRY_SAFE(device, device_next, &g_devices_cache, struct device_cache, entry) + free(device); __wine_unix_call(pulse_handle, process_detach, NULL); if (pulse_thread) { WaitForSingleObject(pulse_thread, INFINITE); @@ -387,6 +399,7 @@ int WINAPI AUDDRV_GetPriority(void)
static BOOL get_pulse_name_by_guid(const GUID *guid, char pulse_name[MAX_PULSE_NAME_LEN], EDataFlow *flow) { + struct device_cache *device; WCHAR key_name[MAX_PULSE_NAME_LEN + 2]; DWORD key_name_size; DWORD index = 0; @@ -402,6 +415,15 @@ static BOOL get_pulse_name_by_guid(const GUID *guid, char pulse_name[MAX_PULSE_N return TRUE; }
+ /* Check the cache first */ + LIST_FOR_EACH_ENTRY(device, &g_devices_cache, struct device_cache, entry) { + if (!IsEqualGUID(guid, &device->guid)) + continue; + *flow = device->dataflow; + strcpy(pulse_name, device->pulse_name); + return TRUE; + } + if (RegOpenKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, KEY_READ | KEY_WOW64_64KEY, &key) != ERROR_SUCCESS) { WARN("No devices found in registry\n"); return FALSE; @@ -412,6 +434,7 @@ static BOOL get_pulse_name_by_guid(const GUID *guid, char pulse_name[MAX_PULSE_N LSTATUS status; GUID reg_guid; HKEY dev_key; + int len;
key_name_size = ARRAY_SIZE(key_name); if (RegEnumKeyExW(key, index++, key_name, &key_name_size, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) @@ -440,7 +463,16 @@ static BOOL get_pulse_name_by_guid(const GUID *guid, char pulse_name[MAX_PULSE_N return FALSE; }
- return WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, pulse_name, MAX_PULSE_NAME_LEN, NULL, NULL); + if (!(len = WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, pulse_name, MAX_PULSE_NAME_LEN, NULL, NULL))) + return FALSE; + + if ((device = malloc(FIELD_OFFSET(struct device_cache, pulse_name[len])))) { + device->guid = reg_guid; + device->dataflow = *flow; + strcpy(device->pulse_name, pulse_name); + list_add_tail(&g_devices_cache, &device->entry); + } + return TRUE; } }
Based on a patch by Mark Harmstone mark@harmstone.com.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/winepulse.drv/mmdevdrv.c | 32 +++++++++- dlls/winepulse.drv/pulse.c | 114 ++++++++++++++++++++++++++++++++-- dlls/winepulse.drv/unixlib.h | 22 +++++++ 3 files changed, 162 insertions(+), 6 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index f6e754d..ca26976 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -2543,6 +2543,10 @@ HRESULT WINAPI AUDDRV_GetAudioSessionManager(IMMDevice *device,
HRESULT WINAPI AUDDRV_GetPropValue(GUID *guid, const PROPERTYKEY *prop, PROPVARIANT *out) { + struct get_prop_value_params params; + char pulse_name[MAX_PULSE_NAME_LEN]; + DWORD size; + TRACE("%s, (%s,%u), %p\n", wine_dbgstr_guid(guid), wine_dbgstr_guid(&prop->fmtid), prop->pid, out);
if (IsEqualGUID(guid, &pulse_render_guid) && IsEqualPropertyKey(*prop, PKEY_AudioEndpoint_PhysicalSpeakers)) { @@ -2552,5 +2556,31 @@ HRESULT WINAPI AUDDRV_GetPropValue(GUID *guid, const PROPERTYKEY *prop, PROPVARI return out->ulVal ? S_OK : E_FAIL; }
- return E_NOTIMPL; + if (!get_pulse_name_by_guid(guid, pulse_name, ¶ms.flow)) + return E_FAIL; + + params.pulse_name = pulse_name; + params.guid = guid; + params.prop = prop; + pulse_call(get_prop_value, ¶ms); + + if (params.result != S_OK) + return params.result; + + switch (params.vt) { + case VT_LPWSTR: + size = (wcslen(params.wstr) + 1) * sizeof(WCHAR); + if (!(out->pwszVal = CoTaskMemAlloc(size))) + return E_OUTOFMEMORY; + memcpy(out->pwszVal, params.wstr, size); + break; + case VT_UI4: + out->ulVal = params.ulVal; + break; + default: + assert(0); + } + out->vt = params.vt; + + return S_OK; } diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 311c9bb..d41160b 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -84,6 +84,10 @@ typedef struct _ACPacket typedef struct _PhysDevice { struct list entry; WCHAR *name; + enum phys_device_bus_type bus_type; + USHORT vendor_id, product_id; + EndpointFormFactor form; + UINT index; char pulse_name[0]; } PhysDevice;
@@ -418,7 +422,33 @@ static DWORD pulse_channel_map_to_channel_mask(const pa_channel_map *map) return mask; }
-static void pulse_add_device(struct list *list, const char *pulse_name, const char *name) +static void fill_device_info(PhysDevice *dev, pa_proplist *p) +{ + const char *buffer; + + dev->bus_type = phys_device_bus_invalid; + dev->vendor_id = 0; + dev->product_id = 0; + + if (!p) + return; + + if ((buffer = pa_proplist_gets(p, PA_PROP_DEVICE_BUS))) { + if (!strcmp(buffer, "usb")) + dev->bus_type = phys_device_bus_usb; + else if (!strcmp(buffer, "pci")) + dev->bus_type = phys_device_bus_pci; + } + + if ((buffer = pa_proplist_gets(p, PA_PROP_DEVICE_VENDOR_ID))) + dev->vendor_id = strtol(buffer, NULL, 16); + + if ((buffer = pa_proplist_gets(p, PA_PROP_DEVICE_PRODUCT_ID))) + dev->product_id = strtol(buffer, NULL, 16); +} + +static void pulse_add_device(struct list *list, pa_proplist *proplist, int index, EndpointFormFactor form, + const char *pulse_name, const char *name) { DWORD len = strlen(pulse_name), name_len = strlen(name); PhysDevice *dev = malloc(FIELD_OFFSET(PhysDevice, pulse_name[len + 1])); @@ -439,6 +469,9 @@ static void pulse_add_device(struct list *list, const char *pulse_name, const ch return; } dev->name[name_len] = 0; + dev->form = form; + dev->index = index; + fill_device_info(dev, proplist); memcpy(dev->pulse_name, pulse_name, len + 1);
list_add_tail(list, &dev->entry); @@ -453,14 +486,15 @@ static void pulse_phys_speakers_cb(pa_context *c, const pa_sink_info *i, int eol * PKEY_AudioEndpoint_PhysicalSpeakers values of the sinks. */ g_phys_speakers_mask |= pulse_channel_map_to_channel_mask(&i->channel_map);
- pulse_add_device(&g_phys_speakers, i->name, i->description); + pulse_add_device(&g_phys_speakers, i->proplist, i->index, Speakers, i->name, i->description); }
static void pulse_phys_sources_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { if (!i || !i->name || !i->name[0]) return; - pulse_add_device(&g_phys_sources, i->name, i->description); + pulse_add_device(&g_phys_sources, i->proplist, i->index, + (i->monitor_of_sink == PA_INVALID_INDEX) ? Microphone : LineLevel, i->name, i->description); }
/* For most hardware on Windows, users must choose a configuration with an even @@ -682,8 +716,8 @@ static NTSTATUS pulse_test_connect(void *args) list_init(&g_phys_sources); g_phys_speakers_mask = 0;
- pulse_add_device(&g_phys_speakers, "", "PulseAudio"); - pulse_add_device(&g_phys_sources, "", "PulseAudio"); + pulse_add_device(&g_phys_speakers, NULL, 0, Speakers, "", "PulseAudio"); + pulse_add_device(&g_phys_sources, NULL, 0, Microphone, "", "PulseAudio");
o = pa_context_get_sink_info_list(pulse_ctx, &pulse_phys_speakers_cb, NULL); if (o) { @@ -2081,6 +2115,75 @@ static NTSTATUS pulse_is_started(void *args) return STATUS_SUCCESS; }
+static BOOL get_device_path(PhysDevice *dev, struct get_prop_value_params *params) +{ + const GUID *guid = params->guid; + UINT serial_number; + const char *fmt; + char path[128]; + int len; + + switch (dev->bus_type) { + case phys_device_bus_pci: + fmt = "{1}.HDAUDIO\FUNC_01&VEN_%04X&DEV_%04X\%u&%08X"; + break; + case phys_device_bus_usb: + fmt = "{1}.USB\VID_%04X&PID_%04X\%u&%08X"; + break; + default: + return FALSE; + } + + /* As hardly any audio devices have serial numbers, Windows instead + appears to use a persistent random number. We emulate this here + by instead using the last 8 hex digits of the GUID. */ + serial_number = (guid->Data4[4] << 24) | (guid->Data4[5] << 16) | (guid->Data4[6] << 8) | guid->Data4[7]; + + len = sprintf(path, fmt, dev->vendor_id, dev->product_id, dev->index, serial_number); + ntdll_umbstowcs(path, len + 1, params->wstr, ARRAY_SIZE(params->wstr)); + + params->vt = VT_LPWSTR; + return TRUE; +} + +static NTSTATUS pulse_get_prop_value(void *args) +{ + static const GUID PKEY_AudioEndpoint_GUID = { + 0x1da5d803, 0xd492, 0x4edd, {0x8c, 0x23, 0xe0, 0xc0, 0xff, 0xee, 0x7f, 0x0e} + }; + static const PROPERTYKEY devicepath_key = { /* undocumented? - {b3f8fa53-0004-438e-9003-51a46e139bfc},2 */ + {0xb3f8fa53, 0x0004, 0x438e, {0x90, 0x03, 0x51, 0xa4, 0x6e, 0x13, 0x9b, 0xfc}}, 2 + }; + struct get_prop_value_params *params = args; + struct list *list = (params->flow == eRender) ? &g_phys_speakers : &g_phys_sources; + PhysDevice *dev; + + params->result = S_OK; + LIST_FOR_EACH_ENTRY(dev, list, PhysDevice, entry) { + if (strcmp(params->pulse_name, dev->pulse_name)) + continue; + if (IsEqualPropertyKey(*params->prop, devicepath_key)) { + if (!get_device_path(dev, params)) + break; + return STATUS_SUCCESS; + } else if (IsEqualGUID(¶ms->prop->fmtid, &PKEY_AudioEndpoint_GUID)) { + switch (params->prop->pid) { + case 0: /* FormFactor */ + params->vt = VT_UI4; + params->ulVal = dev->form; + return STATUS_SUCCESS; + default: + break; + } + } + params->result = E_NOTIMPL; + return STATUS_SUCCESS; + } + + params->result = E_FAIL; + return STATUS_SUCCESS; +} + const unixlib_entry_t __wine_unix_call_funcs[] = { pulse_process_attach, @@ -2107,4 +2210,5 @@ const unixlib_entry_t __wine_unix_call_funcs[] = pulse_set_event_handle, pulse_test_connect, pulse_is_started, + pulse_get_prop_value, }; diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index 982704a..4acebb1 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -23,6 +23,12 @@
struct pulse_stream;
+enum phys_device_bus_type { + phys_device_bus_invalid = -1, + phys_device_bus_pci, + phys_device_bus_usb +}; + struct pulse_config { struct @@ -205,6 +211,21 @@ struct is_started_params BOOL started; };
+struct get_prop_value_params +{ + const char *pulse_name; + const GUID *guid; + const PROPERTYKEY *prop; + EDataFlow flow; + HRESULT result; + VARTYPE vt; + union + { + WCHAR wstr[128]; + ULONG ulVal; + }; +}; + enum unix_funcs { process_attach, @@ -231,4 +252,5 @@ enum unix_funcs set_event_handle, test_connect, is_started, + get_prop_value, };
From: Mark Harmstone mark@harmstone.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/winepulse.drv/Makefile.in | 2 +- dlls/winepulse.drv/mmdevdrv.c | 106 +++++++++++++++++++++++++++++++-- 2 files changed, 103 insertions(+), 5 deletions(-)
diff --git a/dlls/winepulse.drv/Makefile.in b/dlls/winepulse.drv/Makefile.in index c71b283..d4b40e6 100644 --- a/dlls/winepulse.drv/Makefile.in +++ b/dlls/winepulse.drv/Makefile.in @@ -1,7 +1,7 @@ EXTRADEFS = -DWINE_NO_LONG_TYPES MODULE = winepulse.drv UNIXLIB = winepulse.so -IMPORTS = dxguid uuid winmm user32 advapi32 ole32 +IMPORTS = dxguid uuid winmm user32 advapi32 ole32 version EXTRALIBS = $(PULSE_LIBS) $(PTHREAD_LIBS) -lm EXTRAINCL = $(PULSE_CFLAGS)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index ca26976..ef8f1ea 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -30,6 +30,7 @@ #include "wine/list.h"
#include "ole2.h" +#include "mimeole.h" #include "dshow.h" #include "dsound.h" #include "propsys.h" @@ -243,13 +244,110 @@ static DWORD CALLBACK pulse_mainloop_thread(void *event) return 0; }
-static char *get_application_name(void) +typedef struct tagLANGANDCODEPAGE +{ + WORD wLanguage; + WORD wCodePage; +} LANGANDCODEPAGE; + +static BOOL query_productname(void *data, LANGANDCODEPAGE *lang, LPVOID *buffer, DWORD *len) +{ + WCHAR pn[37]; + swprintf(pn, ARRAY_SIZE(pn), L"\StringFileInfo\%04x%04x\ProductName", lang->wLanguage, lang->wCodePage); + return VerQueryValueW(data, pn, buffer, len) && *len; +} + +static char *get_application_name(BOOL query_app_name) { WCHAR path[MAX_PATH], *name; + char *str = NULL; size_t len; - char *str;
GetModuleFileNameW(NULL, path, ARRAY_SIZE(path)); + + if (query_app_name) + { + UINT translate_size, productname_size; + LANGANDCODEPAGE *translate; + LPVOID productname; + BOOL found = FALSE; + void *data = NULL; + unsigned int i; + LCID locale; + DWORD size; + + size = GetFileVersionInfoSizeW(path, NULL); + if (!size) + goto skip; + + data = malloc(size); + if (!data) + goto skip; + + if (!GetFileVersionInfoW(path, 0, size, data)) + goto skip; + + if (!VerQueryValueW(data, L"\VarFileInfo\Translation", (LPVOID *)&translate, &translate_size)) + goto skip; + + /* no translations found */ + if (translate_size < sizeof(LANGANDCODEPAGE)) + goto skip; + + /* The following code will try to find the best translation. We first search for an + * exact match of the language, then a match of the language PRIMARYLANGID, then we + * search for a LANG_NEUTRAL match, and if that still doesn't work we pick the + * first entry which contains a proper productname. */ + locale = GetThreadLocale(); + + for (i = 0; i < translate_size / sizeof(LANGANDCODEPAGE); i++) { + if (translate[i].wLanguage == locale && + query_productname(data, &translate[i], &productname, &productname_size)) { + found = TRUE; + break; + } + } + + if (!found) { + for (i = 0; i < translate_size / sizeof(LANGANDCODEPAGE); i++) { + if (PRIMARYLANGID(translate[i].wLanguage) == PRIMARYLANGID(locale) && + query_productname(data, &translate[i], &productname, &productname_size)) { + found = TRUE; + break; + } + } + } + + if (!found) { + for (i = 0; i < translate_size / sizeof(LANGANDCODEPAGE); i++) { + if (PRIMARYLANGID(translate[i].wLanguage) == LANG_NEUTRAL && + query_productname(data, &translate[i], &productname, &productname_size)) { + found = TRUE; + break; + } + } + } + + if (!found) { + for (i = 0; i < translate_size / sizeof(LANGANDCODEPAGE); i++) { + if (query_productname(data, &translate[i], &productname, &productname_size)) { + found = TRUE; + break; + } + } + } + + if (found) { + len = WideCharToMultiByte(CP_UTF8, 0, productname, -1, NULL, 0, NULL, NULL); + str = malloc(len); + if (str) WideCharToMultiByte(CP_UTF8, 0, productname, -1, str, len, NULL, NULL); + } + + skip: + free(data); + if (str) return str; + } + name = wcsrchr(path, '\'); if (!name) name = path; @@ -390,7 +488,7 @@ int WINAPI AUDDRV_GetPriority(void) struct test_connect_params params; char *name;
- params.name = name = get_application_name(); + params.name = name = get_application_name(FALSE); params.config = &pulse_config; pulse_call(test_connect, ¶ms); free(name); @@ -776,7 +874,7 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface, CloseHandle(event); }
- params.name = name = get_application_name(); + params.name = name = get_application_name(TRUE); params.pulse_name = This->pulse_name; params.dataflow = This->dataflow; params.mode = mode;
From: Mark Harmstone mark@harmstone.com
And get rid of config->speakers_mask and g_phys_speakers_mask since they are no longer needed.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/winepulse.drv/mmdevdrv.c | 7 ------- dlls/winepulse.drv/pulse.c | 30 +++++++++++++++++++++--------- dlls/winepulse.drv/unixlib.h | 1 - 3 files changed, 21 insertions(+), 17 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index ef8f1ea..fa21043 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -2647,13 +2647,6 @@ HRESULT WINAPI AUDDRV_GetPropValue(GUID *guid, const PROPERTYKEY *prop, PROPVARI
TRACE("%s, (%s,%u), %p\n", wine_dbgstr_guid(guid), wine_dbgstr_guid(&prop->fmtid), prop->pid, out);
- if (IsEqualGUID(guid, &pulse_render_guid) && IsEqualPropertyKey(*prop, PKEY_AudioEndpoint_PhysicalSpeakers)) { - out->vt = VT_UI4; - out->ulVal = pulse_config.speakers_mask; - - return out->ulVal ? S_OK : E_FAIL; - } - if (!get_pulse_name_by_guid(guid, pulse_name, ¶ms.flow)) return E_FAIL;
diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index d41160b..6552e4c 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -87,6 +87,7 @@ typedef struct _PhysDevice { enum phys_device_bus_type bus_type; USHORT vendor_id, product_id; EndpointFormFactor form; + DWORD channel_mask; UINT index; char pulse_name[0]; } PhysDevice; @@ -98,7 +99,6 @@ static pa_mainloop *pulse_ml; static WAVEFORMATEXTENSIBLE pulse_fmt[2]; static REFERENCE_TIME pulse_min_period[2], pulse_def_period[2];
-static UINT g_phys_speakers_mask = 0; static struct list g_phys_speakers = LIST_INIT(g_phys_speakers); static struct list g_phys_sources = LIST_INIT(g_phys_sources);
@@ -448,7 +448,7 @@ static void fill_device_info(PhysDevice *dev, pa_proplist *p) }
static void pulse_add_device(struct list *list, pa_proplist *proplist, int index, EndpointFormFactor form, - const char *pulse_name, const char *name) + DWORD channel_mask, const char *pulse_name, const char *name) { DWORD len = strlen(pulse_name), name_len = strlen(name); PhysDevice *dev = malloc(FIELD_OFFSET(PhysDevice, pulse_name[len + 1])); @@ -471,6 +471,7 @@ static void pulse_add_device(struct list *list, pa_proplist *proplist, int index dev->name[name_len] = 0; dev->form = form; dev->index = index; + dev->channel_mask = channel_mask; fill_device_info(dev, proplist); memcpy(dev->pulse_name, pulse_name, len + 1);
@@ -479,14 +480,20 @@ static void pulse_add_device(struct list *list, pa_proplist *proplist, int index
static void pulse_phys_speakers_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + struct list *speaker; + DWORD channel_mask; + if (!i || !i->name || !i->name[0]) return; + channel_mask = pulse_channel_map_to_channel_mask(&i->channel_map);
/* For default PulseAudio render device, OR together all of the * PKEY_AudioEndpoint_PhysicalSpeakers values of the sinks. */ - g_phys_speakers_mask |= pulse_channel_map_to_channel_mask(&i->channel_map); + speaker = list_head(&g_phys_speakers); + if (speaker) + LIST_ENTRY(speaker, PhysDevice, entry)->channel_mask |= channel_mask;
- pulse_add_device(&g_phys_speakers, i->proplist, i->index, Speakers, i->name, i->description); + pulse_add_device(&g_phys_speakers, i->proplist, i->index, Speakers, channel_mask, i->name, i->description); }
static void pulse_phys_sources_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) @@ -494,7 +501,7 @@ static void pulse_phys_sources_cb(pa_context *c, const pa_source_info *i, int eo if (!i || !i->name || !i->name[0]) return; pulse_add_device(&g_phys_sources, i->proplist, i->index, - (i->monitor_of_sink == PA_INVALID_INDEX) ? Microphone : LineLevel, i->name, i->description); + (i->monitor_of_sink == PA_INVALID_INDEX) ? Microphone : LineLevel, 0, i->name, i->description); }
/* For most hardware on Windows, users must choose a configuration with an even @@ -714,10 +721,9 @@ static NTSTATUS pulse_test_connect(void *args) free_phys_device_lists(); list_init(&g_phys_speakers); list_init(&g_phys_sources); - g_phys_speakers_mask = 0;
- pulse_add_device(&g_phys_speakers, NULL, 0, Speakers, "", "PulseAudio"); - pulse_add_device(&g_phys_sources, NULL, 0, Microphone, "", "PulseAudio"); + pulse_add_device(&g_phys_speakers, NULL, 0, Speakers, 0, "", "PulseAudio"); + pulse_add_device(&g_phys_sources, NULL, 0, Microphone, 0, "", "PulseAudio");
o = pa_context_get_sink_info_list(pulse_ctx, &pulse_phys_speakers_cb, NULL); if (o) { @@ -740,7 +746,6 @@ static NTSTATUS pulse_test_connect(void *args) pa_mainloop_free(pulse_ml); pulse_ml = NULL;
- config->speakers_mask = g_phys_speakers_mask; config->modes[0].format = pulse_fmt[0]; config->modes[0].def_period = pulse_def_period[0]; config->modes[0].min_period = pulse_min_period[0]; @@ -2172,6 +2177,12 @@ static NTSTATUS pulse_get_prop_value(void *args) params->vt = VT_UI4; params->ulVal = dev->form; return STATUS_SUCCESS; + case 3: /* PhysicalSpeakers */ + if (!dev->channel_mask) + goto fail; + params->vt = VT_UI4; + params->ulVal = dev->channel_mask; + return STATUS_SUCCESS; default: break; } @@ -2180,6 +2191,7 @@ static NTSTATUS pulse_get_prop_value(void *args) return STATUS_SUCCESS; }
+fail: params->result = E_FAIL; return STATUS_SUCCESS; } diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index 4acebb1..9089e28 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -37,7 +37,6 @@ struct pulse_config REFERENCE_TIME def_period; REFERENCE_TIME min_period; } modes[2]; - unsigned int speakers_mask; };
struct endpoint