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

Andrew Eikum aeikum at codeweavers.com
Mon Feb 21 14:23:00 CST 2022


Signed-off-by: Andrew Eikum <aeikum at codeweavers.com>

On Fri, Feb 18, 2022 at 08:37:59PM +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>
> ---
> 
> Minor issue when allocating, it multiplied by sizeof(WCHAR) again...
> 
>  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..8ef3575 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, &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 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, &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;
> +    }
> +
> +    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))) {
> +            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 *)&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, 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,
> -- 
> 2.34.1
> 
> 



More information about the wine-devel mailing list