[PATCH v2 1/5] winepulse.drv: Expose audio devices to the application.

Andrew Eikum aeikum at codeweavers.com
Tue Feb 15 09:16:41 CST 2022


On Thu, Feb 10, 2022 at 06:07:44PM +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,
> with the same GUID to preserve compatibility with existing setups.
> 
> Based on a patch by Mark Harmstone <mark at harmstone.com>, with changes by
> Sebastian Lackner <sebastian at fds-team.de>.
> 
> Signed-off-by: Gabriel Ivăncescu <gabrielopcode at 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 | 210 ++++++++++++++++++++++++++++------
>  dlls/winepulse.drv/pulse.c    | 135 ++++++++++++++++++++--
>  dlls/winepulse.drv/unixlib.h  |  11 ++
>  3 files changed, 310 insertions(+), 46 deletions(-)
> 
> diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c
> index 35a66e1..844c14c 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,
> @@ -134,6 +136,7 @@ struct ACImpl {
>      IUnknown *marshal;
>      IMMDevice *parent;
>      struct list entry;
> +    char device[256];

These magic numbers could be replaced by a symbol. I noticed a bunch
in this patch (256 and 256+2), and one more in the GetPropValue patch.

>      float *vol;
>  
>      LONG ref;
> @@ -147,8 +150,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 +268,114 @@ static void set_stream_volumes(ACImpl *This)
>      pulse_call(set_volumes, &params);
>  }
>  
> -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 WCHAR *device, GUID *guid)
>  {
> -    WCHAR *id;
> +    DWORD type, size = sizeof(*guid);
> +    WCHAR key_name[258];
> +    LSTATUS status;
> +    HKEY dev_key;
>  
> -    TRACE("%d %p %p %p\n", flow, ids, num, def_index);
> +    if (!device[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] = ',';
> +    wcscpy(key_name + 2, device);
>  
> -    (*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", debugstr_w(device), 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", debugstr_w(device), 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;
> +    DWORD name_len;
> +    HKEY drv_key;
> +    WCHAR *p;
> +
> +    TRACE("%d %p %p %p\n", flow, ids_out, num, def_index);
> +
> +    params.flow = flow;
> +    params.size = 1024;

Another magic number here. Maybe instead, max_name_len * 4?

> +    params.devices = NULL;
> +    do {
> +        HeapFree(GetProcessHeap(), 0, params.devices);
> +        params.devices = HeapAlloc(GetProcessHeap(), 0, params.size);
> +        pulse_call(get_endpoint_ids, &params);
> +    } 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;
> +    }
> +
> +    p = params.devices;
> +    for (i = 0; i < params.num; i++) {
> +        get_device_guid(drv_key, flow, p, &guids[i]);
> +        p += wcslen(p) + 1;
> +
> +        name_len = wcslen(p) + 1;
> +        if (!(ids[i] = HeapAlloc(GetProcessHeap(), 0, name_len * sizeof(WCHAR)))) {
> +            params.result = E_OUTOFMEMORY;
> +            break;
> +        }
> +        memcpy(ids[i], p, name_len * sizeof(WCHAR));
> +        p += name_len;
> +    }
> +    if (drv_key)
> +        RegCloseKey(drv_key);
> +
> +end:
> +    HeapFree(GetProcessHeap(), 0, params.devices);
> +    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 = 0;
> +    }
> +    return params.result;
>  }
>  
>  int WINAPI AUDDRV_GetPriority(void)
> @@ -314,26 +390,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 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_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 *)&reg_guid, &size);
> +        RegCloseKey(dev_key);
> +
> +        if (status == ERROR_SUCCESS && type == REG_BINARY && size == sizeof(reg_guid) && IsEqualGUID(&reg_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 +746,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..d5f8edf 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 device[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,39 @@ 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;
> +    DWORD len, name_len, needed = 0;
> +    WCHAR *p = params->devices;
> +    WCHAR device[256];
> +    PhysDevice *dev;
> +
> +    params->num = 0;
> +    LIST_FOR_EACH_ENTRY(dev, list, PhysDevice, entry) {
> +        if (!(len = ntdll_umbstowcs(dev->device, strlen(dev->device) + 1, device, ARRAY_SIZE(device))))
> +            continue;
> +        name_len = lstrlenW(dev->name) + 1;
> +
> +        needed += (len + name_len) * sizeof(WCHAR);
> +        if (needed <= params->size) {
> +            memcpy(p, device, len * sizeof(WCHAR));
> +            p += len;
> +            memcpy(p, dev->name, name_len * sizeof(WCHAR));
> +            p += name_len;
> +        }
> +        params->num++;
> +    }

I find this API difficult to understand. At least a comment describing
what 'num' and 'devices' will contain on output would be nice.

If I understand it right, 'devices' will contain 2x'num'
null-terminated strings, right? Another way to represent that is with
a single array of null-terminated strings, with the end-of-array
sentinel being a zero-length string. That eliminates one of the param
fields, and simplifies the array creation and also the iteration side.

You could also split the 'device' and 'name' strings each into their
own arrays, to make the code easier to read.

> +
> +    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 +413,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 *device, const char *name)
> ...
> +    pulse_add_device(&g_phys_speakers, i->name, i->description);

The usage of "device" and "name" is very confusing in this patch. E.g.
note the 'device' parameter is called with a 'name' argument here, and
the 'name' param is called with 'description'.

Later, in AUDDRV_GetEndpointIDs, we're handling ids and devices and
names. It's pretty mixed up.

Could you use more descriptive terms, more consistently across the
entire codebase? E.g. you could use 'id' for an internal
representation, and 'friendly' for something user-visible?

Andrew

> +{
> +    DWORD len = strlen(device), name_len = strlen(name);
> +    PhysDevice *dev = malloc(FIELD_OFFSET(PhysDevice, device[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->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)
> -        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);
> +
> +}
> +
> +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 +672,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 +688,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 +879,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 +906,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 +978,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);
> @@ -1967,6 +2081,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..c4d274c 100644
> --- a/dlls/winepulse.drv/unixlib.h
> +++ b/dlls/winepulse.drv/unixlib.h
> @@ -37,9 +37,19 @@ struct main_loop_params
>      HANDLE event;
>  };
>  
> +struct get_endpoint_ids_params
> +{
> +    EDataFlow flow;
> +    DWORD size;
> +    WCHAR *devices;
> +    HRESULT result;
> +    unsigned int num;
> +};
> +
>  struct create_stream_params
>  {
>      const char *name;
> +    const char *device;
>      EDataFlow dataflow;
>      AUDCLNT_SHAREMODE mode;
>      DWORD flags;
> @@ -191,6 +201,7 @@ enum unix_funcs
>      process_attach,
>      process_detach,
>      main_loop,
> +    get_endpoint_ids,
>      create_stream,
>      release_stream,
>      start,
> -- 
> 2.34.1
> 
> 



More information about the wine-devel mailing list