[PATCH 6/8] winealsa: Move is_format_supported to the unixlib.

Andrew Eikum aeikum at codeweavers.com
Thu Feb 17 09:26:03 CST 2022


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

On Wed, Feb 16, 2022 at 09:34:52AM +0000, Huw Davies wrote:
> Signed-off-by: Huw Davies <huw at codeweavers.com>
> ---
>  dlls/winealsa.drv/alsa.c     | 291 +++++++++++++++++++++++++++++++++++
>  dlls/winealsa.drv/mmdevdrv.c | 150 ++----------------
>  dlls/winealsa.drv/unixlib.h  |  11 ++
>  3 files changed, 317 insertions(+), 135 deletions(-)
> 
> diff --git a/dlls/winealsa.drv/alsa.c b/dlls/winealsa.drv/alsa.c
> index 320d676effb..0680383f59b 100644
> --- a/dlls/winealsa.drv/alsa.c
> +++ b/dlls/winealsa.drv/alsa.c
> @@ -453,6 +453,27 @@ static NTSTATUS get_endpoint_ids(void *args)
>      return STATUS_SUCCESS;
>  }
>  
> +static WAVEFORMATEXTENSIBLE *clone_format(const WAVEFORMATEX *fmt)
> +{
> +    WAVEFORMATEXTENSIBLE *ret;
> +    size_t size;
> +
> +    if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
> +        size = sizeof(WAVEFORMATEXTENSIBLE);
> +    else
> +        size = sizeof(WAVEFORMATEX);
> +
> +    ret = malloc(size);
> +    if(!ret)
> +        return NULL;
> +
> +    memcpy(ret, fmt, size);
> +
> +    ret->Format.cbSize = size - sizeof(WAVEFORMATEX);
> +
> +    return ret;
> +}
> +
>  static HRESULT alsa_open_device(const char *alsa_name, EDataFlow flow, snd_pcm_t **pcm_handle,
>                                  snd_pcm_hw_params_t **hw_params)
>  {
> @@ -486,6 +507,78 @@ static HRESULT alsa_open_device(const char *alsa_name, EDataFlow flow, snd_pcm_t
>      return S_OK;
>  }
>  
> +static snd_pcm_format_t alsa_format(const WAVEFORMATEX *fmt)
> +{
> +    snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN;
> +    const WAVEFORMATEXTENSIBLE *fmtex = (const WAVEFORMATEXTENSIBLE *)fmt;
> +
> +    if(fmt->wFormatTag == WAVE_FORMAT_PCM ||
> +      (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
> +       IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))){
> +        if(fmt->wBitsPerSample == 8)
> +            format = SND_PCM_FORMAT_U8;
> +        else if(fmt->wBitsPerSample == 16)
> +            format = SND_PCM_FORMAT_S16_LE;
> +        else if(fmt->wBitsPerSample == 24)
> +            format = SND_PCM_FORMAT_S24_3LE;
> +        else if(fmt->wBitsPerSample == 32)
> +            format = SND_PCM_FORMAT_S32_LE;
> +        else
> +            WARN("Unsupported bit depth: %u\n", fmt->wBitsPerSample);
> +        if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
> +           fmt->wBitsPerSample != fmtex->Samples.wValidBitsPerSample){
> +            if(fmtex->Samples.wValidBitsPerSample == 20 && fmt->wBitsPerSample == 24)
> +                format = SND_PCM_FORMAT_S20_3LE;
> +            else
> +                WARN("Unsupported ValidBits: %u\n", fmtex->Samples.wValidBitsPerSample);
> +        }
> +    }else if(fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT ||
> +            (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
> +             IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))){
> +        if(fmt->wBitsPerSample == 32)
> +            format = SND_PCM_FORMAT_FLOAT_LE;
> +        else if(fmt->wBitsPerSample == 64)
> +            format = SND_PCM_FORMAT_FLOAT64_LE;
> +        else
> +            WARN("Unsupported float size: %u\n", fmt->wBitsPerSample);
> +    }else
> +        WARN("Unknown wave format: %04x\n", fmt->wFormatTag);
> +    return format;
> +}
> +
> +static int alsa_channel_index(DWORD flag)
> +{
> +    switch(flag){
> +    case SPEAKER_FRONT_LEFT:
> +        return 0;
> +    case SPEAKER_FRONT_RIGHT:
> +        return 1;
> +    case SPEAKER_BACK_LEFT:
> +        return 2;
> +    case SPEAKER_BACK_RIGHT:
> +        return 3;
> +    case SPEAKER_FRONT_CENTER:
> +        return 4;
> +    case SPEAKER_LOW_FREQUENCY:
> +        return 5;
> +    case SPEAKER_SIDE_LEFT:
> +        return 6;
> +    case SPEAKER_SIDE_RIGHT:
> +        return 7;
> +    }
> +    return -1;
> +}
> +
> +static BOOL need_remapping(const WAVEFORMATEX *fmt, int *map)
> +{
> +    unsigned int i;
> +    for(i = 0; i < fmt->nChannels; ++i){
> +        if(map[i] != i)
> +            return TRUE;
> +    }
> +    return FALSE;
> +}
> +
>  static DWORD get_channel_mask(unsigned int channels)
>  {
>      switch(channels){
> @@ -512,6 +605,203 @@ static DWORD get_channel_mask(unsigned int channels)
>      return 0;
>  }
>  
> +static HRESULT map_channels(EDataFlow flow, const WAVEFORMATEX *fmt, int *alsa_channels, int *map)
> +{
> +    BOOL need_remap;
> +
> +    if(flow != eCapture && (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE || fmt->nChannels > 2) ){
> +        WAVEFORMATEXTENSIBLE *fmtex = (void*)fmt;
> +        DWORD mask, flag = SPEAKER_FRONT_LEFT;
> +        UINT i = 0;
> +
> +        if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
> +                fmtex->dwChannelMask != 0)
> +            mask = fmtex->dwChannelMask;
> +        else
> +            mask = get_channel_mask(fmt->nChannels);
> +
> +        *alsa_channels = 0;
> +
> +        while(i < fmt->nChannels && !(flag & SPEAKER_RESERVED)){
> +            if(mask & flag){
> +                map[i] = alsa_channel_index(flag);
> +                TRACE("Mapping mmdevapi channel %u (0x%x) to ALSA channel %d\n",
> +                        i, flag, map[i]);
> +                if(map[i] >= *alsa_channels)
> +                    *alsa_channels = map[i] + 1;
> +                ++i;
> +            }
> +            flag <<= 1;
> +        }
> +
> +        while(i < fmt->nChannels){
> +            map[i] = *alsa_channels;
> +            TRACE("Mapping mmdevapi channel %u to ALSA channel %d\n",
> +                    i, map[i]);
> +            ++*alsa_channels;
> +            ++i;
> +        }
> +
> +        for(i = 0; i < fmt->nChannels; ++i){
> +            if(map[i] == -1){
> +                map[i] = *alsa_channels;
> +                ++*alsa_channels;
> +                TRACE("Remapping mmdevapi channel %u to ALSA channel %d\n",
> +                        i, map[i]);
> +            }
> +        }
> +
> +        need_remap = need_remapping(fmt, map);
> +    }else{
> +        *alsa_channels = fmt->nChannels;
> +
> +        need_remap = FALSE;
> +    }
> +
> +    TRACE("need_remapping: %u, alsa_channels: %d\n", need_remap, *alsa_channels);
> +
> +    return need_remap ? S_OK : S_FALSE;
> +}
> +
> +static NTSTATUS is_format_supported(void *args)
> +{
> +    struct is_format_supported_params *params = args;
> +    const WAVEFORMATEXTENSIBLE *fmtex = (const WAVEFORMATEXTENSIBLE *)params->fmt_in;
> +    snd_pcm_t *pcm_handle;
> +    snd_pcm_hw_params_t *hw_params;
> +    snd_pcm_format_mask_t *formats = NULL;
> +    snd_pcm_format_t format;
> +    WAVEFORMATEXTENSIBLE *closest = NULL;
> +    unsigned int max = 0, min = 0;
> +    int err;
> +    int alsa_channels, alsa_channel_map[32];
> +
> +    params->result = S_OK;
> +
> +    if(!params->fmt_in || (params->share == AUDCLNT_SHAREMODE_SHARED && !params->fmt_out))
> +        params->result = E_POINTER;
> +    else if(params->share != AUDCLNT_SHAREMODE_SHARED && params->share != AUDCLNT_SHAREMODE_EXCLUSIVE)
> +        params->result = E_INVALIDARG;
> +    else if(params->fmt_in->wFormatTag == WAVE_FORMAT_EXTENSIBLE){
> +        if(params->fmt_in->cbSize < sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX))
> +            params->result = E_INVALIDARG;
> +        else if(params->fmt_in->nAvgBytesPerSec == 0 || params->fmt_in->nBlockAlign == 0 ||
> +                (fmtex->Samples.wValidBitsPerSample > params->fmt_in->wBitsPerSample))
> +            params->result = E_INVALIDARG;
> +    }
> +    if(FAILED(params->result))
> +        return STATUS_SUCCESS;
> +
> +    if(params->fmt_in->nChannels == 0){
> +        params->result = AUDCLNT_E_UNSUPPORTED_FORMAT;
> +        return STATUS_SUCCESS;
> +    }
> +
> +    params->result = alsa_open_device(params->alsa_name, params->flow, &pcm_handle, &hw_params);
> +    if(FAILED(params->result))
> +        return STATUS_SUCCESS;
> +
> +    if((err = snd_pcm_hw_params_any(pcm_handle, hw_params)) < 0){
> +        params->result = AUDCLNT_E_DEVICE_INVALIDATED;
> +        goto exit;
> +    }
> +
> +    formats = calloc(1, snd_pcm_format_mask_sizeof());
> +    if(!formats){
> +        params->result = E_OUTOFMEMORY;
> +        goto exit;
> +    }
> +
> +    snd_pcm_hw_params_get_format_mask(hw_params, formats);
> +    format = alsa_format(params->fmt_in);
> +    if (format == SND_PCM_FORMAT_UNKNOWN ||
> +        !snd_pcm_format_mask_test(formats, format)){
> +        params->result = AUDCLNT_E_UNSUPPORTED_FORMAT;
> +        goto exit;
> +    }
> +
> +    closest = clone_format(params->fmt_in);
> +    if(!closest){
> +        params->result = E_OUTOFMEMORY;
> +        goto exit;
> +    }
> +
> +    if((err = snd_pcm_hw_params_get_rate_min(hw_params, &min, NULL)) < 0){
> +        params->result = AUDCLNT_E_DEVICE_INVALIDATED;
> +        WARN("Unable to get min rate: %d (%s)\n", err, snd_strerror(err));
> +        goto exit;
> +    }
> +
> +    if((err = snd_pcm_hw_params_get_rate_max(hw_params, &max, NULL)) < 0){
> +        params->result = AUDCLNT_E_DEVICE_INVALIDATED;
> +        WARN("Unable to get max rate: %d (%s)\n", err, snd_strerror(err));
> +        goto exit;
> +    }
> +
> +    if(params->fmt_in->nSamplesPerSec < min || params->fmt_in->nSamplesPerSec > max){
> +        params->result = AUDCLNT_E_UNSUPPORTED_FORMAT;
> +        goto exit;
> +    }
> +
> +    if((err = snd_pcm_hw_params_get_channels_min(hw_params, &min)) < 0){
> +        params->result = AUDCLNT_E_DEVICE_INVALIDATED;
> +        WARN("Unable to get min channels: %d (%s)\n", err, snd_strerror(err));
> +        goto exit;
> +    }
> +
> +    if((err = snd_pcm_hw_params_get_channels_max(hw_params, &max)) < 0){
> +        params->result = AUDCLNT_E_DEVICE_INVALIDATED;
> +        WARN("Unable to get max channels: %d (%s)\n", err, snd_strerror(err));
> +        goto exit;
> +    }
> +    if(params->fmt_in->nChannels > max){
> +        params->result = S_FALSE;
> +        closest->Format.nChannels = max;
> +    }else if(params->fmt_in->nChannels < min){
> +        params->result = S_FALSE;
> +        closest->Format.nChannels = min;
> +    }
> +
> +    map_channels(params->flow, params->fmt_in, &alsa_channels, alsa_channel_map);
> +
> +    if(alsa_channels > max){
> +        params->result = S_FALSE;
> +        closest->Format.nChannels = max;
> +    }
> +
> +    if(closest->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE)
> +        closest->dwChannelMask = get_channel_mask(closest->Format.nChannels);
> +
> +    if(params->fmt_in->nBlockAlign != params->fmt_in->nChannels * params->fmt_in->wBitsPerSample / 8 ||
> +       params->fmt_in->nAvgBytesPerSec != params->fmt_in->nBlockAlign * params->fmt_in->nSamplesPerSec ||
> +       (params->fmt_in->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
> +        fmtex->Samples.wValidBitsPerSample < params->fmt_in->wBitsPerSample))
> +        params->result = S_FALSE;
> +
> +    if(params->share == AUDCLNT_SHAREMODE_EXCLUSIVE && params->fmt_in->wFormatTag == WAVE_FORMAT_EXTENSIBLE){
> +        if(fmtex->dwChannelMask == 0 || fmtex->dwChannelMask & SPEAKER_RESERVED)
> +            params->result = S_FALSE;
> +    }
> +
> +exit:
> +    if(params->result == S_FALSE && !params->fmt_out)
> +        params->result = AUDCLNT_E_UNSUPPORTED_FORMAT;
> +
> +    if(params->result == S_FALSE && params->fmt_out) {
> +        closest->Format.nBlockAlign = closest->Format.nChannels * closest->Format.wBitsPerSample / 8;
> +        closest->Format.nAvgBytesPerSec = closest->Format.nBlockAlign * closest->Format.nSamplesPerSec;
> +        if(closest->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE)
> +            closest->Samples.wValidBitsPerSample = closest->Format.wBitsPerSample;
> +        memcpy(params->fmt_out, closest, closest->Format.cbSize);
> +    }
> +    free(closest);
> +    free(formats);
> +    free(hw_params);
> +    snd_pcm_close(pcm_handle);
> +
> +    return STATUS_SUCCESS;
> +}
> +
>  static NTSTATUS get_mix_format(void *args)
>  {
>      struct get_mix_format_params *params = args;
> @@ -632,5 +922,6 @@ exit:
>  unixlib_entry_t __wine_unix_call_funcs[] =
>  {
>      get_endpoint_ids,
> +    is_format_supported,
>      get_mix_format,
>  };
> diff --git a/dlls/winealsa.drv/mmdevdrv.c b/dlls/winealsa.drv/mmdevdrv.c
> index 24c6755d697..8a80acd871a 100644
> --- a/dlls/winealsa.drv/mmdevdrv.c
> +++ b/dlls/winealsa.drv/mmdevdrv.c
> @@ -1296,150 +1296,30 @@ static HRESULT WINAPI AudioClient_IsFormatSupported(IAudioClient3 *iface,
>          WAVEFORMATEX **out)
>  {
>      ACImpl *This = impl_from_IAudioClient3(iface);
> -    snd_pcm_format_mask_t *formats = NULL;
> -    snd_pcm_format_t format;
> -    HRESULT hr = S_OK;
> -    WAVEFORMATEX *closest = NULL;
> -    unsigned int max = 0, min = 0;
> -    int err;
> -    int alsa_channels, alsa_channel_map[32];
> +    struct is_format_supported_params params;
>  
>      TRACE("(%p)->(%x, %p, %p)\n", This, mode, fmt, out);
> +    if(fmt) dump_fmt(fmt);
>  
> -    if(!fmt || (mode == AUDCLNT_SHAREMODE_SHARED && !out))
> -        return E_POINTER;
> -
> -    if(mode != AUDCLNT_SHAREMODE_SHARED && mode != AUDCLNT_SHAREMODE_EXCLUSIVE)
> -        return E_INVALIDARG;
> -
> -    if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
> -            fmt->cbSize < sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX))
> -        return E_INVALIDARG;
> -
> -    dump_fmt(fmt);
> +    params.alsa_name = This->alsa_name;
> +    params.flow = This->dataflow;
> +    params.share = mode;
> +    params.fmt_in = fmt;
> +    params.fmt_out = NULL;
>  
>      if(out){
>          *out = NULL;
> -        if(mode != AUDCLNT_SHAREMODE_SHARED)
> -            out = NULL;
> +        if(mode == AUDCLNT_SHAREMODE_SHARED)
> +            params.fmt_out = CoTaskMemAlloc(sizeof(*params.fmt_out));
>      }
> +    ALSA_CALL(is_format_supported, &params);
>  
> -    if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
> -            (fmt->nAvgBytesPerSec == 0 ||
> -             fmt->nBlockAlign == 0 ||
> -             ((WAVEFORMATEXTENSIBLE*)fmt)->Samples.wValidBitsPerSample > fmt->wBitsPerSample))
> -        return E_INVALIDARG;
> -
> -    if(fmt->nChannels == 0)
> -        return AUDCLNT_E_UNSUPPORTED_FORMAT;
> -
> -    EnterCriticalSection(&This->lock);
> -
> -    if((err = snd_pcm_hw_params_any(This->stream->pcm_handle, This->stream->hw_params)) < 0){
> -        hr = AUDCLNT_E_DEVICE_INVALIDATED;
> -        goto exit;
> -    }
> -
> -    formats = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
> -            snd_pcm_format_mask_sizeof());
> -    if(!formats){
> -        hr = E_OUTOFMEMORY;
> -        goto exit;
> -    }
> -
> -    snd_pcm_hw_params_get_format_mask(This->stream->hw_params, formats);
> -    format = alsa_format(fmt);
> -    if (format == SND_PCM_FORMAT_UNKNOWN ||
> -        !snd_pcm_format_mask_test(formats, format)){
> -        hr = AUDCLNT_E_UNSUPPORTED_FORMAT;
> -        goto exit;
> -    }
> -
> -    closest = clone_format(fmt);
> -    if(!closest){
> -        hr = E_OUTOFMEMORY;
> -        goto exit;
> -    }
> -
> -    if((err = snd_pcm_hw_params_get_rate_min(This->stream->hw_params, &min, NULL)) < 0){
> -        hr = AUDCLNT_E_DEVICE_INVALIDATED;
> -        WARN("Unable to get min rate: %d (%s)\n", err, snd_strerror(err));
> -        goto exit;
> -    }
> -
> -    if((err = snd_pcm_hw_params_get_rate_max(This->stream->hw_params, &max, NULL)) < 0){
> -        hr = AUDCLNT_E_DEVICE_INVALIDATED;
> -        WARN("Unable to get max rate: %d (%s)\n", err, snd_strerror(err));
> -        goto exit;
> -    }
> -
> -    if(fmt->nSamplesPerSec < min || fmt->nSamplesPerSec > max){
> -        hr = AUDCLNT_E_UNSUPPORTED_FORMAT;
> -        goto exit;
> -    }
> -
> -    if((err = snd_pcm_hw_params_get_channels_min(This->stream->hw_params, &min)) < 0){
> -        hr = AUDCLNT_E_DEVICE_INVALIDATED;
> -        WARN("Unable to get min channels: %d (%s)\n", err, snd_strerror(err));
> -        goto exit;
> -    }
> -
> -    if((err = snd_pcm_hw_params_get_channels_max(This->stream->hw_params, &max)) < 0){
> -        hr = AUDCLNT_E_DEVICE_INVALIDATED;
> -        WARN("Unable to get max channels: %d (%s)\n", err, snd_strerror(err));
> -        goto exit;
> -    }
> -    if(fmt->nChannels > max){
> -        hr = S_FALSE;
> -        closest->nChannels = max;
> -    }else if(fmt->nChannels < min){
> -        hr = S_FALSE;
> -        closest->nChannels = min;
> -    }
> -
> -    map_channels(This, fmt, &alsa_channels, alsa_channel_map);
> -
> -    if(alsa_channels > max){
> -        hr = S_FALSE;
> -        closest->nChannels = max;
> -    }
> -
> -    if(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
> -        ((WAVEFORMATEXTENSIBLE*)closest)->dwChannelMask = get_channel_mask(closest->nChannels);
> -
> -    if(fmt->nBlockAlign != fmt->nChannels * fmt->wBitsPerSample / 8 ||
> -            fmt->nAvgBytesPerSec != fmt->nBlockAlign * fmt->nSamplesPerSec ||
> -            (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
> -             ((WAVEFORMATEXTENSIBLE*)fmt)->Samples.wValidBitsPerSample < fmt->wBitsPerSample))
> -        hr = S_FALSE;
> -
> -    if(mode == AUDCLNT_SHAREMODE_EXCLUSIVE &&
> -            fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE){
> -        if(((WAVEFORMATEXTENSIBLE*)fmt)->dwChannelMask == 0 ||
> -                ((WAVEFORMATEXTENSIBLE*)fmt)->dwChannelMask & SPEAKER_RESERVED)
> -            hr = S_FALSE;
> -    }
> -
> -exit:
> -    LeaveCriticalSection(&This->lock);
> -    HeapFree(GetProcessHeap(), 0, formats);
> -
> -    if(hr == S_FALSE && !out)
> -        hr = AUDCLNT_E_UNSUPPORTED_FORMAT;
> -
> -    if(hr == S_FALSE && out) {
> -        closest->nBlockAlign =
> -            closest->nChannels * closest->wBitsPerSample / 8;
> -        closest->nAvgBytesPerSec =
> -            closest->nBlockAlign * closest->nSamplesPerSec;
> -        if(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
> -            ((WAVEFORMATEXTENSIBLE*)closest)->Samples.wValidBitsPerSample = closest->wBitsPerSample;
> -        *out = closest;
> -    } else
> -        CoTaskMemFree(closest);
> +    if(params.result == S_FALSE)
> +        *out = &params.fmt_out->Format;
> +    else
> +        CoTaskMemFree(params.fmt_out);
>  
> -    TRACE("returning: %08x\n", hr);
> -    return hr;
> +    return params.result;
>  }
>  
>  static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient3 *iface,
> diff --git a/dlls/winealsa.drv/unixlib.h b/dlls/winealsa.drv/unixlib.h
> index ef5b470e1ed..cd8d303b5d3 100644
> --- a/dlls/winealsa.drv/unixlib.h
> +++ b/dlls/winealsa.drv/unixlib.h
> @@ -67,6 +67,16 @@ struct get_endpoint_ids_params
>      unsigned int default_idx;
>  };
>  
> +struct is_format_supported_params
> +{
> +    const char *alsa_name;
> +    EDataFlow flow;
> +    AUDCLNT_SHAREMODE share;
> +    const WAVEFORMATEX *fmt_in;
> +    WAVEFORMATEXTENSIBLE *fmt_out;
> +    HRESULT result;
> +};
> +
>  struct get_mix_format_params
>  {
>      const char *alsa_name;
> @@ -78,6 +88,7 @@ struct get_mix_format_params
>  enum alsa_funcs
>  {
>      alsa_get_endpoint_ids,
> +    alsa_is_format_supported,
>      alsa_get_mix_format,
>  };
>  
> -- 
> 2.25.1
> 
> 



More information about the wine-devel mailing list