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.
It keeps a list of the devices on the unixlib side, which then stores the device type, pulse name and display (friendly) name into the registry, for persistent settings of an app to identify the device by guid. The keys are stored under HKLM\Software\Wine\Drivers\winepulse.drv\devices, with the following format:
<type>,<pulse device name>
Where <type> is a single character, which is either 0 (for output/sinks) or 1 (for input/sources), and <pulse device name> is the device name that PulseAudio uses to identify the device. The "name" value is stored under this key which is the display name of the device. When enumerating the devices, the "name" is taken and a GUID is generated if it's missing or invalid under the "guid" value of the same key; this preserves the GUID for the same device unless the registry key is cleaned.
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 ---
This patchset has lived in wine-staging for a long time, but has been recently rebased to deal with unixlib separation.
dlls/winepulse.drv/mmdevdrv.c | 184 ++++++++++++++++++++++++++++------ dlls/winepulse.drv/pulse.c | 176 ++++++++++++++++++++++++++++++-- dlls/winepulse.drv/unixlib.h | 1 + 3 files changed, 320 insertions(+), 41 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 35a66e1..bf14324 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -134,6 +134,7 @@ struct ACImpl { IUnknown *marshal; IMMDevice *parent; struct list entry; + char device[256]; float *vol;
LONG ref; @@ -147,8 +148,6 @@ struct ACImpl { AudioSessionWrapper *session_wrapper; };
-static const WCHAR defaultW[] = L"PulseAudio"; - static const IAudioClient3Vtbl AudioClient3_Vtbl; static const IAudioRenderClientVtbl AudioRenderClient_Vtbl; static const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl; @@ -267,39 +266,96 @@ static void set_stream_volumes(ACImpl *This) pulse_call(set_volumes, ¶ms); }
-HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, const WCHAR ***ids, GUID **keys, +HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, WCHAR ***ids, GUID **keys, UINT *num, UINT *def_index) { - WCHAR *id; + WCHAR flow_char = (flow == eRender) ? '0' : '1'; + DWORD i, k = 0, count = 0, maxlen, size, type; + WCHAR *id, *key_name = NULL; + HKEY dev_key, key = NULL; + LSTATUS status; + GUID guid;
TRACE("%d %p %p %p\n", flow, ids, num, def_index);
- *num = 1; + *num = 0; *def_index = 0; - - *ids = HeapAlloc(GetProcessHeap(), 0, sizeof(**ids)); + *ids = NULL; *keys = NULL; - if (!*ids) - return E_OUTOFMEMORY;
- (*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; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\Wine\Drivers\winepulse.drv\devices", + 0, KEY_READ | KEY_WOW64_64KEY, &key) == ERROR_SUCCESS) { + status = RegQueryInfoKeyW(key, NULL, NULL, NULL, &count, &maxlen, NULL, NULL, NULL, NULL, NULL, NULL); + if (status != ERROR_SUCCESS || maxlen < 3) + count = 0; } - memcpy(id, defaultW, sizeof(defaultW));
- if (flow == eRender) - (*keys)[0] = pulse_render_guid; - else - (*keys)[0] = pulse_capture_guid; + if (count && !(key_name = malloc((maxlen + 1) * sizeof(WCHAR)))) + goto err; + + *ids = HeapAlloc(GetProcessHeap(), 0, (count + 1) * sizeof(**ids)); + *keys = HeapAlloc(GetProcessHeap(), 0, (count + 1) * sizeof(**keys)); + if (!*ids || !*keys) + goto err; + + if (!(id = HeapAlloc(GetProcessHeap(), 0, sizeof(L"PulseAudio")))) + goto err; + wcscpy(id, L"PulseAudio"); + (*ids)[k] = id; + (*keys)[k++] = (flow == eRender) ? pulse_render_guid : pulse_capture_guid; + + for (i = 0; i < count; i++) { + DWORD key_name_size = maxlen + 1; + + if (RegEnumKeyExW(key, i, key_name, &key_name_size, NULL, NULL, NULL, NULL) != ERROR_SUCCESS || + key_name_size < 3 || key_name[0] != flow_char || + RegOpenKeyExW(key, key_name, 0, KEY_READ | KEY_WRITE | KEY_WOW64_64KEY, &dev_key) != ERROR_SUCCESS) + continue; + + status = RegQueryValueExW(dev_key, L"name", 0, &type, NULL, &size); + if (status == ERROR_SUCCESS && type == REG_SZ && size >= sizeof(WCHAR)) { + if (!(id = HeapAlloc(GetProcessHeap(), 0, size))) { + RegCloseKey(dev_key); + goto err; + } + status = RegQueryValueExW(dev_key, L"name", 0, &type, (BYTE*)id, &size); + if (status == ERROR_SUCCESS && type == REG_SZ && size >= sizeof(WCHAR)) { + id[size / sizeof(WCHAR) - 1] = 0; + + size = sizeof(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\n", debugstr_w(key_name + 2)); + } + (*ids)[k] = id; + (*keys)[k++] = guid; + } else { + HeapFree(GetProcessHeap(), 0, id); + } + } + RegCloseKey(dev_key); + } + *num = k; + *ids = HeapReAlloc(GetProcessHeap(), 0, *ids, k * sizeof(**ids)); + *keys = HeapReAlloc(GetProcessHeap(), 0, *keys, k * sizeof(**keys));
+ if (key) RegCloseKey(key); + free(key_name); return S_OK; + +err: + if (key) RegCloseKey(key); + while (k--) HeapFree(GetProcessHeap(), 0, (*ids)[k]); + HeapFree(GetProcessHeap(), 0, *keys); + HeapFree(GetProcessHeap(), 0, *ids); + free(key_name); + *ids = NULL; + *keys = NULL; + return E_OUTOFMEMORY; }
int WINAPI AUDDRV_GetPriority(void) @@ -314,26 +370,89 @@ int WINAPI AUDDRV_GetPriority(void) return SUCCEEDED(params.result) ? Priority_Preferred : Priority_Unavailable; }
+static BOOL get_pulse_name_by_guid(const GUID *guid, char name[256], EDataFlow *flow) +{ + DWORD key_name_size; + WCHAR key_name[258]; + DWORD index = 0; + HKEY key; + + /* Return empty string for default PulseAudio device */ + 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_LOCAL_MACHINE, L"Software\Wine\Drivers\winepulse.drv\devices", + 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, name, 256, 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; + ACImpl *This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This)); EDataFlow dataflow; 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;
*out = NULL; - - This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This)); if (!This) return E_OUTOFMEMORY;
+ if (!get_pulse_name_by_guid(guid, This->device, &dataflow)) { + HeapFree(GetProcessHeap(), 0, This); + return AUDCLNT_E_DEVICE_INVALIDATED; + } + This->IAudioClient3_iface.lpVtbl = &AudioClient3_Vtbl; This->IAudioRenderClient_iface.lpVtbl = &AudioRenderClient_Vtbl; This->IAudioCaptureClient_iface.lpVtbl = &AudioCaptureClient_Vtbl; @@ -609,6 +728,7 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface, }
params.name = name = get_application_name(); + params.device = This->device; 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..8934387 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -81,6 +81,11 @@ typedef struct _ACPacket UINT32 discont; } ACPacket;
+typedef struct _PhysDevice { + struct list entry; + char device[0]; +} PhysDevice; + static pa_context *pulse_ctx; static pa_mainloop *pulse_ml;
@@ -89,6 +94,9 @@ 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 HKEY devices_key;
static const REFERENCE_TIME MinimumPeriod = 30000; static const REFERENCE_TIME DefaultPeriod = 100000; @@ -128,6 +136,16 @@ static void dump_attr(const pa_buffer_attr *attr) TRACE("prebuf: %u\n", attr->prebuf); }
+static void free_phys_device_lists(void) +{ + PhysDevice *dev, *dev_next; + + LIST_FOR_EACH_ENTRY_SAFE(dev, dev_next, &g_phys_speakers, PhysDevice, entry) + free(dev); + LIST_FOR_EACH_ENTRY_SAFE(dev, dev_next, &g_phys_sources, PhysDevice, entry) + free(dev); +} + /* copied from kernelbase */ static int muldiv(int a, int b, int c) { @@ -152,6 +170,61 @@ static int muldiv(int a, int b, int c) return ret; }
+/* wrapper for NtCreateKey that creates the key recursively if necessary */ +static HKEY reg_create_key(HKEY root, const WCHAR *name, ULONG name_len) +{ + UNICODE_STRING nameW = { name_len, name_len, (WCHAR *)name }; + OBJECT_ATTRIBUTES attr; + NTSTATUS status; + HANDLE ret = 0; + + attr.Length = sizeof(attr); + attr.RootDirectory = root; + attr.ObjectName = &nameW; + attr.Attributes = 0; + attr.SecurityDescriptor = NULL; + attr.SecurityQualityOfService = NULL; + + status = NtCreateKey(&ret, KEY_QUERY_VALUE | KEY_WRITE | KEY_WOW64_64KEY, &attr, 0, NULL, 0, NULL); + if (status == STATUS_OBJECT_NAME_NOT_FOUND) { + DWORD pos = 0, i = 0, len = name_len / sizeof(WCHAR); + + /* don't try to create registry root */ + if (!root) i += 10; + + while (i < len && name[i] != '\') i++; + if (i == len) return 0; + for (;;) { + nameW.Buffer = (WCHAR *)name + pos; + nameW.Length = (i - pos) * sizeof(WCHAR); + status = NtCreateKey(&ret, KEY_QUERY_VALUE | KEY_WRITE | KEY_WOW64_64KEY, &attr, 0, NULL, 0, NULL); + + if (attr.RootDirectory != root) NtClose(attr.RootDirectory); + if (!NT_SUCCESS(status)) return 0; + if (i == len) break; + attr.RootDirectory = ret; + while (i < len && name[i] == '\') i++; + pos = i; + while (i < len && name[i] != '\') i++; + } + } + return ret; +} + +static HKEY open_devices_key(void) +{ + static const WCHAR drv_key_devicesW[] = { + '\','R','e','g','i','s','t','r','y','\','M','a','c','h','i','n','e','\', + 'S','o','f','t','w','a','r','e','\','W','i','n','e','\','D','r','i','v','e','r','s','\', + 'w','i','n','e','p','u','l','s','e','.','d','r','v','\','d','e','v','i','c','e','s' + }; + HANDLE ret; + + if (!(ret = reg_create_key(NULL, drv_key_devicesW, sizeof(drv_key_devicesW)))) + ERR("Failed to open devices registry key\n"); + return ret; +} + /* Following pulseaudio design here, mainloop has the lock taken whenever * it is handling something for pulse, and the lock is required whenever * doing any pa_* call that can affect the state in any way @@ -190,6 +263,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); @@ -357,12 +431,73 @@ 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 store_device_info(EDataFlow flow, const char *device, const char *name) +{ + static const WCHAR nameW[] = { 'n','a','m','e' }; + UNICODE_STRING name_str = { sizeof(nameW), sizeof(nameW), (WCHAR*)nameW }; + UINT name_len = strlen(name); + WCHAR key_name[258], *wname; + DWORD len, key_len; + HKEY key; + + if (!devices_key || !(wname = malloc((name_len + 1) * sizeof(WCHAR)))) + return; + + key_name[0] = (flow == eCapture) ? '1' : '0'; + key_name[1] = ','; + + key_len = ntdll_umbstowcs(device, strlen(device), key_name + 2, ARRAY_SIZE(key_name) - 2); + if (!key_len || key_len >= ARRAY_SIZE(key_name) - 2) + goto done; + key_len += 2; + + if (!(len = ntdll_umbstowcs(name, name_len, wname, name_len))) + goto done; + wname[len] = 0; + + if (!(key = reg_create_key(devices_key, key_name, key_len * sizeof(WCHAR)))) { + ERR("Failed to open registry key for device %s\n", device); + goto done; + } + + if (NtSetValueKey(key, &name_str, 0, REG_SZ, wname, (len + 1) * sizeof(WCHAR))) + ERR("Failed to store name for %s to registry\n", device); + NtClose(key); + +done: + free(wname); +} + +static void pulse_add_device(struct list *list, const char *device) +{ + DWORD len = strlen(device); + PhysDevice *dev = malloc(FIELD_OFFSET(PhysDevice, device[len + 1])); + + if (!dev) + return; + memcpy(dev->device, device, 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) + if (i && i->name && i->name[0]) { + /* 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); + + store_device_info(eRender, i->name, i->description); + pulse_add_device(&g_phys_speakers, i->name); + } +} + +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]) { + store_device_info(eCapture, i->name, i->description); + pulse_add_device(&g_phys_sources, i->name); + } }
/* For most hardware on Windows, users must choose a configuration with an even @@ -579,7 +714,15 @@ 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; + + devices_key = open_devices_key(); + pulse_add_device(&g_phys_speakers, ""); + pulse_add_device(&g_phys_sources, ""); + 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 +731,15 @@ 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); + } + NtClose(devices_key); + pa_context_unref(pulse_ctx); pulse_ctx = NULL; pa_mainloop_free(pulse_ml); @@ -771,8 +923,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 *device, 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 +950,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 device name is given, use exactly the specified device */ + if (device[0]) + flags |= PA_STREAM_DONT_MOVE; + else + device = 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, device, &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, device, &attr, flags); if (ret < 0) { WARN("Returns %i\n", ret); return AUDCLNT_E_ENDPOINT_CREATE_FAILED; @@ -864,7 +1022,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->device, stream->period_bytes); if (SUCCEEDED(hr)) { UINT32 unalign; const pa_buffer_attr *attr = pa_stream_get_buffer_attr(stream->stream); diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index d28a73c..5445a0f 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -40,6 +40,7 @@ struct main_loop_params struct create_stream_params { const char *name; + const char *device; EDataFlow dataflow; AUDCLNT_SHAREMODE mode; DWORD flags;
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
Will be necessary 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 bf14324..0e4fe9e 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 device[0]; +};
static GUID pulse_render_guid = { 0xfd47d9cc, 0x4218, 0x4135, { 0x9c, 0xe2, 0x0c, 0x19, 0x5c, 0x87, 0x40, 0x5b } }; @@ -88,6 +96,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); @@ -372,6 +384,7 @@ int WINAPI AUDDRV_GetPriority(void)
static BOOL get_pulse_name_by_guid(const GUID *guid, char name[256], EDataFlow *flow) { + struct device_cache *device; DWORD key_name_size; WCHAR key_name[258]; DWORD index = 0; @@ -387,6 +400,15 @@ static BOOL get_pulse_name_by_guid(const GUID *guid, char name[256], EDataFlow * 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(name, device->device); + return TRUE; + } + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\Wine\Drivers\winepulse.drv\devices", 0, KEY_READ | KEY_WOW64_64KEY, &key) != ERROR_SUCCESS) { WARN("No devices found in registry\n"); @@ -398,6 +420,7 @@ static BOOL get_pulse_name_by_guid(const GUID *guid, char name[256], EDataFlow * 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, @@ -427,7 +450,16 @@ static BOOL get_pulse_name_by_guid(const GUID *guid, char name[256], EDataFlow * return FALSE; }
- return WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, name, 256, NULL, NULL); + if (!(len = WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, name, 256, NULL, NULL))) + return FALSE; + + if ((device = malloc(FIELD_OFFSET(struct device_cache, device[len])))) { + device->guid = reg_guid; + device->dataflow = *flow; + strcpy(device->device, 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 | 57 +++++++++++++++++++++++++++++ dlls/winepulse.drv/pulse.c | 69 ++++++++++++++++++++++++++++++++--- dlls/winepulse.drv/unixlib.h | 18 +++++++++ 3 files changed, 139 insertions(+), 5 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 0e4fe9e..f07b0ca 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -2524,8 +2524,49 @@ HRESULT WINAPI AUDDRV_GetAudioSessionManager(IMMDevice *device, return S_OK; }
+static HRESULT get_device_path(struct get_device_info_params *params, GUID *guid, PROPVARIANT *out) +{ + UINT serial_number; + const WCHAR *fmt; + WCHAR path[128]; + int len; + + switch (params->bus_type) { + case phys_device_bus_pci: + fmt = L"{1}.HDAUDIO\FUNC_01&VEN_%04X&DEV_%04X\%u&%08X"; + break; + case phys_device_bus_usb: + fmt = L"{1}.USB\VID_%04X&PID_%04X\%u&%08X"; + break; + default: + return E_FAIL; + } + + /* 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 = swprintf(path, ARRAY_SIZE(path), fmt, params->vendor_id, params->product_id, params->index, serial_number); + if (len < 0) + return E_FAIL; + + out->vt = VT_LPWSTR; + out->pwszVal = CoTaskMemAlloc((len + 1) * sizeof(WCHAR)); + if (!out->pwszVal) + return E_OUTOFMEMORY; + + wcscpy(out->pwszVal, path); + return S_OK; +} + HRESULT WINAPI AUDDRV_GetPropValue(GUID *guid, const PROPERTYKEY *prop, PROPVARIANT *out) { + 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_device_info_params params; + 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)) { @@ -2535,5 +2576,21 @@ HRESULT WINAPI AUDDRV_GetPropValue(GUID *guid, const PROPERTYKEY *prop, PROPVARI return out->ulVal ? S_OK : E_FAIL; }
+ if (!get_pulse_name_by_guid(guid, params.device, ¶ms.dataflow)) + return E_FAIL; + + pulse_call(get_device_info, ¶ms); + if (params.result != S_OK) + return params.result; + + if (IsEqualPropertyKey(*prop, devicepath_key)) + return get_device_path(¶ms, guid, out); + + if (IsEqualPropertyKey(*prop, PKEY_AudioEndpoint_FormFactor)) { + out->vt = VT_UI4; + out->ulVal = params.form; + return S_OK; + } + return E_NOTIMPL; } diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 8934387..3204c08 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -83,6 +83,10 @@ typedef struct _ACPacket
typedef struct _PhysDevice { struct list entry; + enum phys_device_bus_type bus_type; + USHORT vendor_id, product_id; + EndpointFormFactor form; + UINT index; char device[0]; } PhysDevice;
@@ -468,7 +472,33 @@ done: free(wname); }
-static void pulse_add_device(struct list *list, const char *device) +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 *device) { DWORD len = strlen(device); PhysDevice *dev = malloc(FIELD_OFFSET(PhysDevice, device[len + 1])); @@ -476,6 +506,9 @@ static void pulse_add_device(struct list *list, const char *device) if (!dev) return; memcpy(dev->device, device, len + 1); + dev->form = form; + dev->index = index; + fill_device_info(dev, proplist);
list_add_tail(list, &dev->entry); } @@ -488,7 +521,7 @@ static void pulse_phys_speakers_cb(pa_context *c, const pa_sink_info *i, int eol g_phys_speakers_mask |= pulse_channel_map_to_channel_mask(&i->channel_map);
store_device_info(eRender, i->name, i->description); - pulse_add_device(&g_phys_speakers, i->name); + pulse_add_device(&g_phys_speakers, i->proplist, i->index, Speakers, i->name); } }
@@ -496,7 +529,8 @@ static void pulse_phys_sources_cb(pa_context *c, const pa_source_info *i, int eo { if (i && i->name && i->name[0]) { store_device_info(eCapture, i->name, i->description); - pulse_add_device(&g_phys_sources, i->name); + pulse_add_device(&g_phys_sources, i->proplist, i->index, + (i->monitor_of_sink == PA_INVALID_INDEX) ? Microphone : LineLevel, i->name); } }
@@ -720,8 +754,8 @@ static NTSTATUS pulse_test_connect(void *args) g_phys_speakers_mask = 0;
devices_key = open_devices_key(); - pulse_add_device(&g_phys_speakers, ""); - pulse_add_device(&g_phys_sources, ""); + pulse_add_device(&g_phys_speakers, NULL, 0, Speakers, ""); + pulse_add_device(&g_phys_sources, NULL, 0, Microphone, "");
o = pa_context_get_sink_info_list(pulse_ctx, &pulse_phys_speakers_cb, NULL); if (o) { @@ -768,6 +802,30 @@ fail: return STATUS_SUCCESS; }
+static NTSTATUS pulse_get_device_info(void *args) +{ + struct get_device_info_params *params = args; + const struct list *const list = (params->dataflow == eRender) ? &g_phys_speakers : &g_phys_sources; + const char *device = params->device; + PhysDevice *dev; + + LIST_FOR_EACH_ENTRY(dev, list, PhysDevice, entry) { + if (!strcmp(device, dev->device)) { + params->bus_type = dev->bus_type; + params->vendor_id = dev->vendor_id; + params->product_id = dev->product_id; + params->index = dev->index; + params->form = dev->form; + params->result = S_OK; + return STATUS_SUCCESS; + } + } + + WARN("Unknown device %s\n", device); + params->result = E_FAIL; + return STATUS_SUCCESS; +} + static DWORD get_channel_mask(unsigned int channels) { switch(channels) { @@ -2144,5 +2202,6 @@ const unixlib_entry_t __wine_unix_call_funcs[] = pulse_set_volumes, pulse_set_event_handle, pulse_test_connect, + pulse_get_device_info, pulse_is_started, }; diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index 5445a0f..7383110 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -181,6 +181,23 @@ struct test_connect_params struct pulse_config *config; };
+enum phys_device_bus_type { + phys_device_bus_invalid = -1, + phys_device_bus_pci, + phys_device_bus_usb +}; + +struct get_device_info_params +{ + char device[256]; + EDataFlow dataflow; + enum phys_device_bus_type bus_type; + USHORT vendor_id, product_id; + EndpointFormFactor form; + UINT index; + HRESULT result; +}; + struct is_started_params { struct pulse_stream *stream; @@ -211,5 +228,6 @@ enum unix_funcs set_volumes, set_event_handle, test_connect, + get_device_info, is_started, };
On 2/9/22 17:10, Gabriel Ivăncescu wrote:
+static HRESULT get_device_path(struct get_device_info_params *params, GUID *guid, PROPVARIANT *out) +{
- UINT serial_number;
- const WCHAR *fmt;
- WCHAR path[128];
- int len;
- switch (params->bus_type) {
- case phys_device_bus_pci:
fmt = L"{1}.HDAUDIO\\FUNC_01&VEN_%04X&DEV_%04X\\%u&%08X";
break;
- case phys_device_bus_usb:
fmt = L"{1}.USB\\VID_%04X&PID_%04X\\%u&%08X";
break;
- default:
return E_FAIL;
- }
- /* 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 = swprintf(path, ARRAY_SIZE(path), fmt, params->vendor_id, params->product_id, params->index, serial_number);
- if (len < 0)
return E_FAIL;
Does this belong to winepulse or mmdevapi? Could it be shared, and not depend on the backend? Also, could this use some setupapi API instead of constructing device paths?
On 09/02/2022 16:19, Nikolay Sivov wrote:
On 2/9/22 17:10, Gabriel Ivăncescu wrote:
+static HRESULT get_device_path(struct get_device_info_params *params, GUID *guid, PROPVARIANT *out) +{ + UINT serial_number; + const WCHAR *fmt; + WCHAR path[128]; + int len;
+ switch (params->bus_type) { + case phys_device_bus_pci: + fmt = L"{1}.HDAUDIO\FUNC_01&VEN_%04X&DEV_%04X\%u&%08X"; + break; + case phys_device_bus_usb: + fmt = L"{1}.USB\VID_%04X&PID_%04X\%u&%08X"; + break; + default: + return E_FAIL; + }
+ /* 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 = swprintf(path, ARRAY_SIZE(path), fmt, params->vendor_id, params->product_id, params->index, serial_number); + if (len < 0) + return E_FAIL;
Does this belong to winepulse or mmdevapi? Could it be shared, and not depend on the backend? Also, could this use some setupapi API instead of constructing device paths?
Unfortunately I don't know almost anything about mmdevapi, I did work on rebasing the patchset in staging though so at least I'm familiar with it on the winepulse side. winealsa.drv seems to have similar logic though, so you might be right, this could be made more generic for sure.
I'm not sure what's the best way to do that. This function returns a VARIANT, but for this specific prop key, it needs to return a structure of sorts. It needs info about:
1) Bus type (PCI, USB) 2) Vendor ID 3) Product ID 4) Device ID
What do you suggest to return? Something custom?
BTW am I correct to assume that this function (GetPropValue) is only called by mmdevapi and thus is internal/its signature can be changed? Or is it possible for an app to call it? Because if the latter I'm afraid I can't see a way out.
On Wed, Feb 09, 2022 at 05:54:56PM +0200, Gabriel Ivăncescu wrote:
On 09/02/2022 16:19, Nikolay Sivov wrote:
On 2/9/22 17:10, Gabriel Ivăncescu wrote:
+static HRESULT get_device_path(struct get_device_info_params
Does this belong to winepulse or mmdevapi? Could it be shared, and not depend on the backend? Also, could this use some setupapi API instead of constructing device paths?
Unfortunately I don't know almost anything about mmdevapi, I did work on rebasing the patchset in staging though so at least I'm familiar with it on the winepulse side. winealsa.drv seems to have similar logic though, so you might be right, this could be made more generic for sure.
I'm not sure what's the best way to do that. This function returns a VARIANT, but for this specific prop key, it needs to return a structure of sorts. It needs info about:
- Bus type (PCI, USB)
- Vendor ID
- Product ID
- Device ID
What do you suggest to return? Something custom?
BTW am I correct to assume that this function (GetPropValue) is only called by mmdevapi and thus is internal/its signature can be changed? Or is it possible for an app to call it? Because if the latter I'm afraid I can't see a way out.
For the alsa driver I've added a get_prop_value syscall which handles everything in the unixlib - see the attached patch. Obviously if mmdevapi wants to short-circuit some of these props, it can do so.
I'm trying to keep the syscall interface as similar as possible between the drivers (I'm hoping at some point we could have a single PE-side, not four).
Huw.
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 | 107 +++++++++++++++++++++++++++++++-- 2 files changed, 104 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 f07b0ca..a14638f 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" @@ -240,13 +241,111 @@ 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_program_name) { WCHAR path[MAX_PATH], *name; + char *str = NULL; size_t len; - char *str;
GetModuleFileNameW(NULL, path, ARRAY_SIZE(path)); + + if (query_program_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; @@ -375,7 +474,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); @@ -759,7 +858,7 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface, CloseHandle(event); }
- params.name = name = get_application_name(); + params.name = name = get_application_name(TRUE); params.device = This->device; params.dataflow = This->dataflow; params.mode = mode;
And get rid of config->speakers_mask and g_phys_speakers_mask since they are no longer needed.
From: Mark Harmstone mark@harmstone.com Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/winepulse.drv/mmdevdrv.c | 13 ++++++------- dlls/winepulse.drv/pulse.c | 24 +++++++++++++++--------- dlls/winepulse.drv/unixlib.h | 2 +- 3 files changed, 22 insertions(+), 17 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index a14638f..7f743b5 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -2668,13 +2668,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, params.device, ¶ms.dataflow)) return E_FAIL;
@@ -2691,5 +2684,11 @@ HRESULT WINAPI AUDDRV_GetPropValue(GUID *guid, const PROPERTYKEY *prop, PROPVARI return S_OK; }
+ if (IsEqualPropertyKey(*prop, PKEY_AudioEndpoint_PhysicalSpeakers)) { + out->vt = VT_UI4; + out->ulVal = params.channel_mask; + return out->ulVal ? S_OK : E_FAIL; + } + return E_NOTIMPL; } diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 3204c08..570f055 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -86,6 +86,7 @@ typedef struct _PhysDevice { enum phys_device_bus_type bus_type; USHORT vendor_id, product_id; EndpointFormFactor form; + DWORD channel_mask; UINT index; char device[0]; } PhysDevice; @@ -97,7 +98,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); static HKEY devices_key; @@ -498,7 +498,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 *device) + DWORD channel_mask, const char *device) { DWORD len = strlen(device); PhysDevice *dev = malloc(FIELD_OFFSET(PhysDevice, device[len + 1])); @@ -508,6 +508,7 @@ static void pulse_add_device(struct list *list, pa_proplist *proplist, int index memcpy(dev->device, device, len + 1); dev->form = form; dev->index = index; + dev->channel_mask = channel_mask; fill_device_info(dev, proplist);
list_add_tail(list, &dev->entry); @@ -515,13 +516,19 @@ 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; + if (i && i->name && i->name[0]) { + DWORD 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;
store_device_info(eRender, i->name, i->description); - pulse_add_device(&g_phys_speakers, i->proplist, i->index, Speakers, i->name); + pulse_add_device(&g_phys_speakers, i->proplist, i->index, Speakers, channel_mask, i->name); } }
@@ -530,7 +537,7 @@ static void pulse_phys_sources_cb(pa_context *c, const pa_source_info *i, int eo if (i && i->name && i->name[0]) { store_device_info(eCapture, 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->monitor_of_sink == PA_INVALID_INDEX) ? Microphone : LineLevel, 0, i->name); } }
@@ -751,11 +758,10 @@ 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;
devices_key = open_devices_key(); - pulse_add_device(&g_phys_speakers, NULL, 0, Speakers, ""); - pulse_add_device(&g_phys_sources, NULL, 0, Microphone, ""); + pulse_add_device(&g_phys_speakers, NULL, 0, Speakers, 0, ""); + pulse_add_device(&g_phys_sources, NULL, 0, Microphone, 0, "");
o = pa_context_get_sink_info_list(pulse_ctx, &pulse_phys_speakers_cb, NULL); if (o) { @@ -779,7 +785,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]; @@ -816,6 +821,7 @@ static NTSTATUS pulse_get_device_info(void *args) params->product_id = dev->product_id; params->index = dev->index; params->form = dev->form; + params->channel_mask = dev->channel_mask; params->result = S_OK; return STATUS_SUCCESS; } diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index 7383110..9cd73a8 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -29,7 +29,6 @@ struct pulse_config REFERENCE_TIME def_period; REFERENCE_TIME min_period; } modes[2]; - unsigned int speakers_mask; };
struct main_loop_params @@ -194,6 +193,7 @@ struct get_device_info_params enum phys_device_bus_type bus_type; USHORT vendor_id, product_id; EndpointFormFactor form; + DWORD channel_mask; UINT index; HRESULT result; };
On Wed, Feb 09, 2022 at 04:10:54PM +0200, Gabriel Ivăncescu wrote:
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.
It keeps a list of the devices on the unixlib side, which then stores the device type, pulse name and display (friendly) name into the registry, for persistent settings of an app to identify the device by guid. The keys are stored under HKLM\Software\Wine\Drivers\winepulse.drv\devices, with the following format:
<type>,<pulse device name>
Where <type> is a single character, which is either 0 (for output/sinks) or 1 (for input/sources), and <pulse device name> is the device name that PulseAudio uses to identify the device. The "name" value is stored under this key which is the display name of the device. When enumerating the devices, the "name" is taken and a GUID is generated if it's missing or invalid under the "guid" value of the same key; this preserves the GUID for the same device unless the registry key is cleaned.
Based on a patch by Mark Harmstone mark@harmstone.com, with changes by Sebastian Lackner sebastian@fds-team.de.
Why are you storing this in the registry? It would likely be simpler to add a get_endpoint_ids() syscall. See the core audio driver for an example.
Huw.
On 09/02/2022 17:26, Huw Davies wrote:
On Wed, Feb 09, 2022 at 04:10:54PM +0200, Gabriel Ivăncescu wrote:
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.
It keeps a list of the devices on the unixlib side, which then stores the device type, pulse name and display (friendly) name into the registry, for persistent settings of an app to identify the device by guid. The keys are stored under HKLM\Software\Wine\Drivers\winepulse.drv\devices, with the following format:
<type>,<pulse device name>
Where <type> is a single character, which is either 0 (for output/sinks) or 1 (for input/sources), and <pulse device name> is the device name that PulseAudio uses to identify the device. The "name" value is stored under this key which is the display name of the device. When enumerating the devices, the "name" is taken and a GUID is generated if it's missing or invalid under the "guid" value of the same key; this preserves the GUID for the same device unless the registry key is cleaned.
Based on a patch by Mark Harmstone mark@harmstone.com, with changes by Sebastian Lackner sebastian@fds-team.de.
Why are you storing this in the registry? It would likely be simpler to add a get_endpoint_ids() syscall. See the core audio driver for an example.
Huw.
I've been rebasing it for wine-staging, and I add the name to the registry because AFAIK Zeb didn't want to add another syscall if it could be avoided. Note that we'll still have to store the "guid" since it has to be persistent, so it would only avoid the "name" value.
I see coreaudio does it similar to how I had it originally with a unix syscall, so probably I should go with that.
On 2/9/22 09:56, Gabriel Ivăncescu wrote:
On 09/02/2022 17:26, Huw Davies wrote:
On Wed, Feb 09, 2022 at 04:10:54PM +0200, Gabriel Ivăncescu wrote:
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.
It keeps a list of the devices on the unixlib side, which then stores the device type, pulse name and display (friendly) name into the registry, for persistent settings of an app to identify the device by guid. The keys are stored under HKLM\Software\Wine\Drivers\winepulse.drv\devices, with the following format:
<type>,<pulse device name>
Where <type> is a single character, which is either 0 (for output/sinks) or 1 (for input/sources), and <pulse device name> is the device name that PulseAudio uses to identify the device. The "name" value is stored under this key which is the display name of the device. When enumerating the devices, the "name" is taken and a GUID is generated if it's missing or invalid under the "guid" value of the same key; this preserves the GUID for the same device unless the registry key is cleaned.
Based on a patch by Mark Harmstone mark@harmstone.com, with changes by Sebastian Lackner sebastian@fds-team.de.
Why are you storing this in the registry? It would likely be simpler to add a get_endpoint_ids() syscall. See the core audio driver for an example.
Huw.
I've been rebasing it for wine-staging, and I add the name to the registry because AFAIK Zeb didn't want to add another syscall if it could be avoided. Note that we'll still have to store the "guid" since it has to be persistent, so it would only avoid the "name" value.
I see coreaudio does it similar to how I had it originally with a unix syscall, so probably I should go with that.
Well, not exactly. I mentioned that because the patches were using the registry anyway, and I figured it made more sense not to do the same thing in two different ways.
My assumption is that the point of storing it in the registry is so that it would persist across reboots. E.g. if a Windows program has been configured to use a specific audio endpoint GUID, and stores that information in its configuration file somewhere, that it would continue to do so after being restarted.
I didn't actually verify that these GUIDs are persistent on Windows, though, and I don't know that we have any programs that do this. It was definitely just a guess on my part.
On Wed, Feb 09, 2022 at 10:23:16AM -0600, Zebediah Figura wrote:
On 2/9/22 09:56, Gabriel Ivăncescu wrote:
On 09/02/2022 17:26, Huw Davies wrote:
On Wed, Feb 09, 2022 at 04:10:54PM +0200, Gabriel Ivăncescu wrote:
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.
It keeps a list of the devices on the unixlib side, which then stores the device type, pulse name and display (friendly) name into the registry, for persistent settings of an app to identify the device by guid. The keys are stored under HKLM\Software\Wine\Drivers\winepulse.drv\devices, with the following format:
<type>,<pulse device name>
Where <type> is a single character, which is either 0 (for output/sinks) or 1 (for input/sources), and <pulse device name> is the device name that PulseAudio uses to identify the device. The "name" value is stored under this key which is the display name of the device. When enumerating the devices, the "name" is taken and a GUID is generated if it's missing or invalid under the "guid" value of the same key; this preserves the GUID for the same device unless the registry key is cleaned.
Based on a patch by Mark Harmstone mark@harmstone.com, with changes by Sebastian Lackner sebastian@fds-team.de.
Why are you storing this in the registry? It would likely be simpler to add a get_endpoint_ids() syscall. See the core audio driver for an example.
Huw.
I've been rebasing it for wine-staging, and I add the name to the registry because AFAIK Zeb didn't want to add another syscall if it could be avoided. Note that we'll still have to store the "guid" since it has to be persistent, so it would only avoid the "name" value.
I see coreaudio does it similar to how I had it originally with a unix syscall, so probably I should go with that.
Well, not exactly. I mentioned that because the patches were using the registry anyway, and I figured it made more sense not to do the same thing in two different ways.
My assumption is that the point of storing it in the registry is so that it would persist across reboots. E.g. if a Windows program has been configured to use a specific audio endpoint GUID, and stores that information in its configuration file somewhere, that it would continue to do so after being restarted.
I didn't actually verify that these GUIDs are persistent on Windows, though, and I don't know that we have any programs that do this. It was definitely just a guess on my part.
In the CoreAudio (and in the upcoming ALSA) driver, I'm generating and persisting the GUIDs on the PE-side. I guess these could be done in the unixlib, but I didn't see much advantage in doing so. Either way, I think the clean way to get the list of endpoints is via a syscall.
Huw.