This series adds a couple of features to WinRT's media.speech: - IVoiceInformation, and synthetizer's options are present (mainly use to store information, not yet to make fully use of it) - add a dummy implementation (1 single voice); that should be extended in future series by using ISpVoice instead - add a couple of more tests to go with implementation
(the serie should prevent MS Flight simulator to crash on first connection. Crash is due to exception handling of C# generated exceptions from E_NOTIMPL with some X11 related critical resources).
A+
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/tests/speech.c | 38 ++++++++++++++++++++++++ 1 file changed, 38 insertions(+)
diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index fb0fa9e3b4d..5a41e8c257b 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -1033,16 +1033,54 @@ static void test_SpeechSynthesizer(void)
if (hr == S_OK) { + ISpeechSynthesizerOptions2 *options2; ISpeechSynthesizerOptions3 *options3; + boolean bool_value; + DOUBLE double_value; + enum SpeechAppendedSilence silence_value; + enum SpeechPunctuationSilence punctuation_value;
check_interface(options, &IID_IAgileObject, TRUE); + bool_value = 0xff; + hr = ISpeechSynthesizerOptions_get_IncludeSentenceBoundaryMetadata(options, &bool_value); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(! bool_value, "Got unepected option %u\n", bool_value); + bool_value = 0xff; + hr = ISpeechSynthesizerOptions_get_IncludeWordBoundaryMetadata(options, &bool_value); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(!bool_value, "Got unepected option %u\n", bool_value); + check_optional_interface(options, &IID_ISpeechSynthesizerOptions2, TRUE); /* Requires Win10 >= 1709 */ + hr = ISpeechSynthesizerOptions_QueryInterface(options, &IID_ISpeechSynthesizerOptions2, (void **)&options2); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + hr = ISpeechSynthesizerOptions2_get_AudioPitch(options2, &double_value); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(double_value == 1.0f, "Got unepected option %f\n", double_value); + + hr = ISpeechSynthesizerOptions2_get_AudioVolume(options2, &double_value); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(double_value == 1.0f, "Got unepected option %f\n", double_value); + + hr = ISpeechSynthesizerOptions2_get_SpeakingRate(options2, &double_value); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(double_value == 1.0f, "Got unepected option %f\n", double_value); + + ISpeechSynthesizerOptions2_Release(options2);
hr = ISpeechSynthesizerOptions_QueryInterface(options, &IID_ISpeechSynthesizerOptions3, (void **)&options3); ok(hr == S_OK || broken(hr == E_NOINTERFACE), "Got unexpected hr %#lx.\n", hr); /* Requires Win10 >= 1803 */
if (hr == S_OK) { + hr = ISpeechSynthesizerOptions3_get_AppendedSilence(options3, &silence_value); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(silence_value == SpeechAppendedSilence_Default, "Got unepected option %u\n", silence_value); + + hr = ISpeechSynthesizerOptions3_get_PunctuationSilence(options3, &punctuation_value); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(punctuation_value == SpeechPunctuationSilence_Default, "Got unepected option %u\n", punctuation_value); + ref = ISpeechSynthesizerOptions3_Release(options3); ok(ref == 2, "Got unexpected ref %lu.\n", ref); }
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/synthesizer.c | 326 ++++++++++++++++++++++- dlls/windows.media.speech/tests/speech.c | 2 +- 2 files changed, 325 insertions(+), 3 deletions(-)
diff --git a/dlls/windows.media.speech/synthesizer.c b/dlls/windows.media.speech/synthesizer.c index 39d14b84ab7..95dfe3150bb 100644 --- a/dlls/windows.media.speech/synthesizer.c +++ b/dlls/windows.media.speech/synthesizer.c @@ -280,6 +280,309 @@ error: return hr; }
+/* + * + * SpeechSynthesizerOptions runtimeclass + * + */ +struct synthesizer_options +{ + ISpeechSynthesizerOptions ISpeechSynthesizerOptions_iface; + ISpeechSynthesizerOptions2 ISpeechSynthesizerOptions2_iface; + ISpeechSynthesizerOptions3 ISpeechSynthesizerOptions3_iface; + LONG ref; + + /* options */ + boolean include_word_boundary; + boolean include_sentence_boundary; + + /* options 2 */ + double audio_volume; + double speaking_rate; + double audio_pitch; + + /* options 3 */ + enum SpeechAppendedSilence appended_silence; + enum SpeechPunctuationSilence punctuation_silence; +}; + +static inline struct synthesizer_options *impl_from_ISpeechSynthesizerOptions( ISpeechSynthesizerOptions *iface ) +{ + return CONTAINING_RECORD(iface, struct synthesizer_options, ISpeechSynthesizerOptions_iface); +} + +static HRESULT WINAPI synthesizer_options_QueryInterface( ISpeechSynthesizerOptions *iface, REFIID iid, void **out) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions(iface); + + TRACE("iface %p, iid %s, out %p stub!\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || + IsEqualGUID(iid, &IID_IInspectable) || + IsEqualGUID(iid, &IID_IAgileObject) || + IsEqualGUID(iid, &IID_ISpeechSynthesizerOptions)) + { + IInspectable_AddRef((*out = &impl->ISpeechSynthesizerOptions_iface)); + return S_OK; + } + + if (IsEqualGUID(iid, &IID_ISpeechSynthesizerOptions2)) + { + IInspectable_AddRef((*out = &impl->ISpeechSynthesizerOptions2_iface)); + return S_OK; + } + + if (IsEqualGUID(iid, &IID_ISpeechSynthesizerOptions3)) + { + IInspectable_AddRef((*out = &impl->ISpeechSynthesizerOptions3_iface)); + return S_OK; + } + + FIXME("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + *out = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI synthesizer_options_AddRef( ISpeechSynthesizerOptions *iface ) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions(iface); + ULONG ref = InterlockedIncrement(&impl->ref); + + TRACE("iface %p, ref %lu.\n", iface, ref); + return ref; +} + +static ULONG WINAPI synthesizer_options_Release( ISpeechSynthesizerOptions *iface ) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions(iface); + ULONG ref = InterlockedDecrement(&impl->ref); + + TRACE("iface %p, ref %lu.\n", iface, ref); + if (ref == 0) + free(impl); + return ref; +} + +static HRESULT WINAPI synthesizer_options_GetIids( ISpeechSynthesizerOptions *iface, ULONG *iid_count, IID **iids ) +{ + FIXME("iface %p, iid_count %p, iids %p stub.\n", iface, iid_count, iids); + return E_NOTIMPL; +} + +static HRESULT WINAPI synthesizer_options_GetRuntimeClassName( ISpeechSynthesizerOptions *iface, HSTRING *class_name ) +{ + FIXME("iface %p, class_name %p stub.\n", iface, class_name); + return E_NOTIMPL; +} + +static HRESULT WINAPI synthesizer_options_GetTrustLevel( ISpeechSynthesizerOptions *iface, TrustLevel *trust_level ) +{ + FIXME("iface %p, trust_level %p stub.\n", iface, trust_level); + return E_NOTIMPL; +} + +static HRESULT WINAPI synthesizer_options_get_IncludeWordBoundaryMetadata( ISpeechSynthesizerOptions *iface, boolean *value ) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions(iface); + TRACE("iface %p, value %p semi-stub.\n", iface, value); + + *value = impl->include_word_boundary; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options_put_IncludeWordBoundaryMetadata( ISpeechSynthesizerOptions *iface, boolean value ) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions(iface); + TRACE("iface %p, value %s semi-stub.\n", iface, value ? "true" : "false"); + + impl->include_word_boundary = value; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options_get_IncludeSentenceBoundaryMetadata( ISpeechSynthesizerOptions *iface, boolean *value ) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions(iface); + TRACE("iface %p, value %p stub.\n", iface, value); + + *value = impl->include_sentence_boundary; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options_put_IncludeSentenceBoundaryMetadata( ISpeechSynthesizerOptions *iface, boolean value ) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions(iface); + TRACE("iface %p, value %s stub.\n", iface, value ? "true" : "false"); + + impl->include_sentence_boundary = value; + return S_OK; +} + +static const struct ISpeechSynthesizerOptionsVtbl synthesizer_options_vtbl = +{ + /*** IUnknown methods ***/ + synthesizer_options_QueryInterface, + synthesizer_options_AddRef, + synthesizer_options_Release, + /*** IInspectable methods ***/ + synthesizer_options_GetIids, + synthesizer_options_GetRuntimeClassName, + synthesizer_options_GetTrustLevel, + /*** ISpeechSynthesizerOptions methods ***/ + synthesizer_options_get_IncludeWordBoundaryMetadata, + synthesizer_options_put_IncludeWordBoundaryMetadata, + synthesizer_options_get_IncludeSentenceBoundaryMetadata, + synthesizer_options_put_IncludeSentenceBoundaryMetadata, +}; + +DEFINE_IINSPECTABLE(synthesizer_options2, ISpeechSynthesizerOptions2, struct synthesizer_options, ISpeechSynthesizerOptions_iface) + +static HRESULT WINAPI synthesizer_options2_get_AudioVolume( ISpeechSynthesizerOptions2 *iface, DOUBLE *value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions2(iface); + + TRACE("iface %p value %p semi-stub!\n", iface, value); + *value = impl->audio_volume; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options2_put_AudioVolume( ISpeechSynthesizerOptions2 *iface, DOUBLE value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions2(iface); + + TRACE("iface %p value %g semi-stub!\n", iface, value); + impl->audio_volume = value; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options2_get_SpeakingRate( ISpeechSynthesizerOptions2 *iface, DOUBLE *value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions2(iface); + + TRACE("iface %p value %p semi-stub!\n", iface, value); + *value = impl->speaking_rate; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options2_put_SpeakingRate( ISpeechSynthesizerOptions2 *iface, DOUBLE value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions2(iface); + + TRACE("iface %p value %g semi-stub!\n", iface, value); + impl->speaking_rate = value; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options2_get_AudioPitch( ISpeechSynthesizerOptions2 *iface, DOUBLE *value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions2(iface); + + TRACE("iface %p value %p semi-stub!\n", iface, value); + *value = impl->audio_pitch; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options2_put_AudioPitch( ISpeechSynthesizerOptions2 *iface, DOUBLE value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions2(iface); + + TRACE("iface %p value %g semi-stub!\n", iface, value); + impl->audio_pitch = value; + return S_OK; +} + +static const struct ISpeechSynthesizerOptions2Vtbl synthesizer_options2_vtbl = +{ + /*** IUnknown methods ***/ + synthesizer_options2_QueryInterface, + synthesizer_options2_AddRef, + synthesizer_options2_Release, + /*** IInspectable methods ***/ + synthesizer_options2_GetIids, + synthesizer_options2_GetRuntimeClassName, + synthesizer_options2_GetTrustLevel, + /*** ISpeechSynthesizerOptions methods ***/ + synthesizer_options2_get_AudioVolume, + synthesizer_options2_put_AudioVolume, + synthesizer_options2_get_SpeakingRate, + synthesizer_options2_put_SpeakingRate, + synthesizer_options2_get_AudioPitch, + synthesizer_options2_put_AudioPitch, +}; + +DEFINE_IINSPECTABLE(synthesizer_options3, ISpeechSynthesizerOptions3, struct synthesizer_options, ISpeechSynthesizerOptions_iface) + +static HRESULT WINAPI synthesizer_options3_get_AppendedSilence( ISpeechSynthesizerOptions3 *iface, enum SpeechAppendedSilence *value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions3(iface); + + TRACE("iface %p value %p semi-stub!\n", iface, value); + *value = impl->appended_silence; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options3_put_AppendedSilence( ISpeechSynthesizerOptions3 *iface, enum SpeechAppendedSilence value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions3(iface); + + TRACE("iface %p value %u semi-stub!\n", iface, value); + impl->appended_silence = value; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options3_get_PunctuationSilence( ISpeechSynthesizerOptions3 *iface, enum SpeechPunctuationSilence *value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions3(iface); + + TRACE("iface %p value %p semi-stub!\n", iface, value); + *value = impl->punctuation_silence; + return S_OK; +} + +static HRESULT WINAPI synthesizer_options3_put_PunctuationSilence( ISpeechSynthesizerOptions3 *iface, enum SpeechPunctuationSilence value) +{ + struct synthesizer_options *impl = impl_from_ISpeechSynthesizerOptions3(iface); + + TRACE("iface %p value %u semi-stub!\n", iface, value); + impl->punctuation_silence = value; + return S_OK; +} + +static const struct ISpeechSynthesizerOptions3Vtbl synthesizer_options3_vtbl = +{ + /*** IUnknown methods ***/ + synthesizer_options3_QueryInterface, + synthesizer_options3_AddRef, + synthesizer_options3_Release, + /*** IInspectable methods ***/ + synthesizer_options3_GetIids, + synthesizer_options3_GetRuntimeClassName, + synthesizer_options3_GetTrustLevel, + /*** ISpeechSynthesizerOptions methods ***/ + synthesizer_options3_get_AppendedSilence, + synthesizer_options3_put_AppendedSilence, + synthesizer_options3_get_PunctuationSilence, + synthesizer_options3_put_PunctuationSilence, +}; + +static HRESULT synthesizer_options_allocate( struct synthesizer_options **out ) +{ + struct synthesizer_options *options; + + if (!(options = calloc(1, sizeof(*options)))) return E_OUTOFMEMORY; + + options->ISpeechSynthesizerOptions_iface.lpVtbl = &synthesizer_options_vtbl; + options->ISpeechSynthesizerOptions2_iface.lpVtbl = &synthesizer_options2_vtbl; + options->ISpeechSynthesizerOptions3_iface.lpVtbl = &synthesizer_options3_vtbl; + /* all other values default to 0 or false */ + options->audio_pitch = 1.0; + options->audio_volume = 1.0; + options->speaking_rate = 1.0; + options->ref = 1; + *out = options; + + return S_OK; +} + /* * * SpeechSynthesizer runtimeclass @@ -292,6 +595,8 @@ struct synthesizer ISpeechSynthesizer2 ISpeechSynthesizer2_iface; IClosable IClosable_iface; LONG ref; + + struct synthesizer_options *options; };
/* @@ -352,7 +657,11 @@ static ULONG WINAPI synthesizer_Release( ISpeechSynthesizer *iface ) TRACE("iface %p, ref %lu.\n", iface, ref);
if (!ref) + { + if (impl->options) + ISpeechSynthesizerOptions_Release(&impl->options->ISpeechSynthesizerOptions_iface); free(impl); + }
return ref; } @@ -440,8 +749,21 @@ DEFINE_IINSPECTABLE(synthesizer2, ISpeechSynthesizer2, struct synthesizer, ISpee
static HRESULT WINAPI synthesizer2_get_Options( ISpeechSynthesizer2 *iface, ISpeechSynthesizerOptions **value ) { - FIXME("iface %p, value %p stub.\n", iface, value); - return E_NOTIMPL; + struct synthesizer *impl = impl_from_ISpeechSynthesizer2(iface); + + WARN("iface %p, value %p semi-stub.\n", iface, value); + if (!impl->options) + { + struct synthesizer_options *options; + HRESULT hr = synthesizer_options_allocate(&options); + if (FAILED(hr)) return hr; + + if (InterlockedCompareExchangePointer((void **)&impl->options, options, NULL) != NULL) + /* another thread beat us */ + ISpeechSynthesizerOptions_AddRef(&options->ISpeechSynthesizerOptions_iface); + } + ISpeechSynthesizerOptions_AddRef(*value = &impl->options->ISpeechSynthesizerOptions_iface); + return S_OK; }
static const struct ISpeechSynthesizer2Vtbl synthesizer2_vtbl = diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index 5a41e8c257b..4bb3d9dda13 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -1029,7 +1029,7 @@ static void test_SpeechSynthesizer(void) ISpeechSynthesizerOptions *options;
hr = ISpeechSynthesizer2_get_Options(synthesizer2, &options); - todo_wine ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
if (hr == S_OK) {
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/tests/speech.c | 73 +++++++++++++++++++++++- 1 file changed, 70 insertions(+), 3 deletions(-)
diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index 4bb3d9dda13..5f528084cc2 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -683,6 +683,66 @@ static HRESULT WINAPI iterable_hstring_create_static( struct iterable_hstring *i return S_OK; }
+#define check_comparable_presence(a, b) _check_comparable_presence( __LINE__, (a), (b)) +static void _check_comparable_presence( unsigned line, IVectorView_VoiceInformation *voices, IVoiceInformation *voice) +{ + HSTRING in_display, in_id, in_language; + HSTRING vc_display, vc_id, vc_language; + IVoiceInformation *vc_voice; + enum VoiceGender in_gender, vc_gender; + UINT32 size, idx, found_count = 0; + HRESULT hr; + INT32 cmp; + + hr = IVoiceInformation_get_DisplayName(voice, &in_display); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVoiceInformation_get_Id(voice, &in_id); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVoiceInformation_get_Language(voice, &in_language); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVoiceInformation_get_Gender(voice, &in_gender); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVectorView_VoiceInformation_get_Size(voices, &size); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + for (idx = 0; SUCCEEDED(hr = IVectorView_VoiceInformation_GetAt(voices, idx, &vc_voice)); idx++) + { + hr = IVoiceInformation_get_DisplayName(vc_voice, &vc_display); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVoiceInformation_get_Id(vc_voice, &vc_id); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVoiceInformation_get_Language(vc_voice, &vc_language); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVoiceInformation_get_Gender(vc_voice, &vc_gender); + ok_(__FILE__, line)(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + trace("%u] %s/%s/%s/%u\n", + idx - 1, debugstr_hstring(vc_display), debugstr_hstring(vc_id), debugstr_hstring(vc_language), vc_gender); + + if (SUCCEEDED(WindowsCompareStringOrdinal(in_display, vc_display, &cmp)) && !cmp && + SUCCEEDED(WindowsCompareStringOrdinal(in_id, vc_id, &cmp)) && !cmp && + SUCCEEDED(WindowsCompareStringOrdinal(in_language, vc_language, &cmp)) && !cmp && + in_gender == vc_gender) + { + found_count++; + } + WindowsDeleteString(vc_display); + WindowsDeleteString(vc_id); + WindowsDeleteString(vc_language); + IVoiceInformation_Release(vc_voice); + } + ok(hr == E_BOUNDS, "Got unexpected hr %#lx.\n", hr); + ok(idx != 0, "Vector view shouldn't be empty!\n"); + ok(idx == size, "Incoherent index/size %u/%u!\n", idx, size); + + ok_(__FILE__, line)(found_count == 1, "Found several (%u) instances of %s/%s/%s/%u\n", + found_count, + debugstr_hstring(in_display), debugstr_hstring(in_id), debugstr_hstring(in_language), in_gender); + + WindowsDeleteString(in_display); + WindowsDeleteString(in_id); + WindowsDeleteString(in_language); +} + static void test_ActivationFactory(void) { static const WCHAR *synthesizer_name = L"Windows.Media.SpeechSynthesis.SpeechSynthesizer"; @@ -807,7 +867,8 @@ static void test_SpeechSynthesizer(void) HMODULE hdll; HSTRING str, str2; HRESULT hr; - UINT32 size; + UINT32 size, idx; + BOOLEAN found; ULONG ref;
hr = RoInitialize(RO_INIT_MULTITHREADED); @@ -901,13 +962,19 @@ static void test_SpeechSynthesizer(void) ok(hr == S_OK, "IVectorView_VoiceInformation_GetMany failed, hr %#lx\n", hr); ok(size == 0, "IVectorView_VoiceInformation_GetMany returned count %u\n", size);
- IVectorView_VoiceInformation_Release(voices); - hr = IInstalledVoicesStatic_get_DefaultVoice(voices_static, &voice); todo_wine ok(hr == S_OK, "IInstalledVoicesStatic_get_DefaultVoice failed, hr %#lx\n", hr);
if (hr == S_OK) { + /* check that VoiceInformation in static vector voice are not shared when exposed to user */ + idx = size; + hr = IVectorView_VoiceInformation_IndexOf(voices, voice, &idx, &found); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(!found, "Shouldn't find default element\n"); + + check_comparable_presence(voices, voice); + IVoiceInformation_get_Description(voice, &str2); trace("SpeechSynthesizer default voice %s.\n", debugstr_hstring(str2));
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/synthesizer.c | 181 ++++++++++++++++++++++++ 1 file changed, 181 insertions(+)
diff --git a/dlls/windows.media.speech/synthesizer.c b/dlls/windows.media.speech/synthesizer.c index 95dfe3150bb..f49e06dbd4c 100644 --- a/dlls/windows.media.speech/synthesizer.c +++ b/dlls/windows.media.speech/synthesizer.c @@ -23,6 +23,187 @@
WINE_DEFAULT_DEBUG_CHANNEL(speech);
+struct voice_information +{ + IVoiceInformation IVoiceInformation_iface; + LONG ref; + + HSTRING id; + HSTRING display_name; + HSTRING language; + HSTRING description; + VoiceGender gender; +}; + +static inline struct voice_information *impl_from_IVoiceInformation( IVoiceInformation *iface ) +{ + return CONTAINING_RECORD(iface, struct voice_information, IVoiceInformation_iface); +} + +static void voice_information_delete( struct voice_information *voice_info ) +{ + WindowsDeleteString(voice_info->id); + WindowsDeleteString(voice_info->display_name); + WindowsDeleteString(voice_info->language); + WindowsDeleteString(voice_info->description); + free(voice_info); +} + +static HRESULT WINAPI voice_information_QueryInterface( IVoiceInformation *iface, REFIID iid, void **ppvObject) +{ + struct voice_information *impl = impl_from_IVoiceInformation( iface ); + + TRACE("iface %p, riid %s, ppv %p\n", iface, wine_dbgstr_guid(iid), ppvObject); + + if (IsEqualGUID(iid, &IID_IUnknown) || + IsEqualGUID(iid, &IID_IInspectable) || + IsEqualGUID(iid, &IID_IVoiceInformation)) + { + IInspectable_AddRef((*ppvObject = &impl->IVoiceInformation_iface)); + return S_OK; + } + + WARN("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + *ppvObject = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI voice_information_AddRef( IVoiceInformation *iface ) +{ + struct voice_information *impl = impl_from_IVoiceInformation(iface); + ULONG ref = InterlockedIncrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + return ref; +} + +static ULONG WINAPI voice_information_Release( IVoiceInformation *iface ) +{ + struct voice_information *impl = impl_from_IVoiceInformation(iface); + ULONG ref = InterlockedDecrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + /* all voices are (for now) statically allocated in all_voices vector. so don't free them */ + return ref; +} + +static HRESULT WINAPI voice_information_GetIids( IVoiceInformation *iface, ULONG *iid_count, IID **iids ) +{ + FIXME("iface %p, iid_count %p, iids %p stub!\n", iface, iid_count, iids); + return E_NOTIMPL; +} + +static HRESULT WINAPI voice_information_GetRuntimeClassName( IVoiceInformation *iface, HSTRING *class_name ) +{ + FIXME("iface %p, class_name %p stub!\n", iface, class_name); + return E_NOTIMPL; +} + +static HRESULT WINAPI voice_information_GetTrustLevel( IVoiceInformation *iface, TrustLevel *trust_level ) +{ + FIXME("iface %p, trust_level %p stub!\n", iface, trust_level); + return E_NOTIMPL; +} + +static HRESULT WINAPI voice_information_get_DisplayName( IVoiceInformation *iface, HSTRING *value ) +{ + struct voice_information *impl = impl_from_IVoiceInformation(iface); + + TRACE("iface %p, value %p!n", iface, value); + return WindowsDuplicateString(impl->display_name, value); +} + +static HRESULT WINAPI voice_information_get_Id( IVoiceInformation *iface, HSTRING *value ) +{ + struct voice_information *impl = impl_from_IVoiceInformation(iface); + + TRACE("iface %p, value %p\n", iface, value); + return WindowsDuplicateString(impl->id, value); +} + +static HRESULT WINAPI voice_information_get_Language( IVoiceInformation *iface, HSTRING *value ) +{ + struct voice_information *impl = impl_from_IVoiceInformation(iface); + + TRACE("iface %p, value %p\n", iface, value); + return WindowsDuplicateString(impl->language, value); +} + +static HRESULT WINAPI voice_information_get_Description( IVoiceInformation *iface, HSTRING *value ) +{ + struct voice_information *impl = impl_from_IVoiceInformation(iface); + + TRACE("iface %p, value %p\n", iface, value); + return WindowsDuplicateString(impl->description, value); +} + +static HRESULT WINAPI voice_information_get_Gender( IVoiceInformation *iface, VoiceGender *value ) +{ + struct voice_information *impl = impl_from_IVoiceInformation(iface); + + TRACE("iface %p, value %p\n", iface, value); + *value = impl->gender; + return S_OK; +} + +static const struct IVoiceInformationVtbl voice_information_vtbl = +{ + /*** IUnknown methods ***/ + voice_information_QueryInterface, + voice_information_AddRef, + voice_information_Release, + + /*** IInspectable methods ***/ + voice_information_GetIids, + voice_information_GetRuntimeClassName, + voice_information_GetTrustLevel, + + /*** IVoiceInformation methods ***/ + voice_information_get_DisplayName, + voice_information_get_Id, + voice_information_get_Language, + voice_information_get_Description, + voice_information_get_Gender, +}; + +HRESULT voice_information_allocate(const WCHAR *display_name, const WCHAR *id, const WCHAR *locale, + VoiceGender gender, IVoiceInformation **pvoice) +{ + struct voice_information *voice_info; + WCHAR *description; + HRESULT hr; + size_t len, langlen; + + voice_info = calloc(1, sizeof(*voice_info)); + if (!voice_info) return E_OUTOFMEMORY; + + len = wcslen(display_name) + 3; + langlen = GetLocaleInfoEx(locale, LOCALE_SLOCALIZEDDISPLAYNAME, NULL, 0); + description = malloc((len + langlen) * sizeof(WCHAR)); + wcscpy(description, display_name); + wcscat(description, L" - "); + GetLocaleInfoEx(locale, LOCALE_SLOCALIZEDDISPLAYNAME, description + len, langlen); + + hr = WindowsCreateString(display_name, wcslen(display_name), &voice_info->display_name); + if (SUCCEEDED(hr)) + hr = WindowsCreateString(id, wcslen(id), &voice_info->id); + if (SUCCEEDED(hr)) + hr = WindowsCreateString(locale, wcslen(locale), &voice_info->language); + if (SUCCEEDED(hr)) + hr = WindowsCreateString(description, len + langlen - 1, &voice_info->description); + if (SUCCEEDED(hr)) + { + voice_info->gender = gender; + voice_info->IVoiceInformation_iface.lpVtbl = &voice_information_vtbl; + + *pvoice = &voice_info->IVoiceInformation_iface; + } + else + { + voice_information_delete(voice_info); + } + free(description); + return hr; +} + /* * * IVectorView_VoiceInformation
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/private.h | 10 +++ dlls/windows.media.speech/synthesizer.c | 95 ++++++++++++++++++++---- dlls/windows.media.speech/tests/speech.c | 2 +- 3 files changed, 92 insertions(+), 15 deletions(-)
diff --git a/dlls/windows.media.speech/private.h b/dlls/windows.media.speech/private.h index e80d73ec1fb..d03fe0e773e 100644 --- a/dlls/windows.media.speech/private.h +++ b/dlls/windows.media.speech/private.h @@ -126,4 +126,14 @@ HRESULT vector_inspectable_create( const struct vector_iids *iids, IVector_IInsp #define DEFINE_IINSPECTABLE_OUTER( pfx, iface_type, impl_type, outer_iface ) \ DEFINE_IINSPECTABLE_( pfx, iface_type, impl_type, impl_from_##iface_type, iface_type##_iface, impl->outer_iface )
+struct synth_provider +{ + struct IVoiceInformation **voices; + unsigned num_voices; + void (*dispose)(struct synth_provider *provider); +}; + +HRESULT voice_information_allocate(const WCHAR *display_name, const WCHAR *id, const WCHAR *locale, + VoiceGender gender, IVoiceInformation **pvoice); + #endif diff --git a/dlls/windows.media.speech/synthesizer.c b/dlls/windows.media.speech/synthesizer.c index f49e06dbd4c..02e3e76e183 100644 --- a/dlls/windows.media.speech/synthesizer.c +++ b/dlls/windows.media.speech/synthesizer.c @@ -214,6 +214,8 @@ struct voice_information_vector { IVectorView_VoiceInformation IVectorView_VoiceInformation_iface; LONG ref; + + struct synth_provider provider; };
static inline struct voice_information_vector *impl_from_IVectorView_VoiceInformation( IVectorView_VoiceInformation *iface ) @@ -225,7 +227,7 @@ static HRESULT WINAPI vector_view_voice_information_QueryInterface( IVectorView_ { struct voice_information_vector *impl = impl_from_IVectorView_VoiceInformation(iface);
- TRACE("iface %p, iid %s, out %p stub!\n", iface, debugstr_guid(iid), out); + TRACE("iface %p, iid %s, out %p\n", iface, debugstr_guid(iid), out);
if (IsEqualGUID(iid, &IID_IUnknown) || IsEqualGUID(iid, &IID_IInspectable) || @@ -276,23 +278,39 @@ static HRESULT WINAPI vector_view_voice_information_GetTrustLevel( IVectorView_V
static HRESULT WINAPI vector_view_voice_information_GetAt( IVectorView_VoiceInformation *iface, UINT32 index, IVoiceInformation **value ) { - FIXME("iface %p, index %#x, value %p stub!\n", iface, index, value); - *value = NULL; - return E_BOUNDS; + struct voice_information_vector *impl = impl_from_IVectorView_VoiceInformation(iface); + TRACE("iface %p, index %#x, value %p\n", iface, index, value); + if (index >= impl->provider.num_voices) + { + *value = NULL; + return E_BOUNDS; + } + IVoiceInformation_AddRef( *value = impl->provider.voices[index] ); + return S_OK; }
static HRESULT WINAPI vector_view_voice_information_get_Size( IVectorView_VoiceInformation *iface, UINT32 *value ) { - FIXME("iface %p, value %p stub!\n", iface, value); - *value = 0; + struct voice_information_vector *impl = impl_from_IVectorView_VoiceInformation(iface); + TRACE("iface %p, value %p\n", iface, value); + *value = impl->provider.num_voices; return S_OK; }
static HRESULT WINAPI vector_view_voice_information_IndexOf( IVectorView_VoiceInformation *iface, IVoiceInformation *element, UINT32 *index, BOOLEAN *found ) { - FIXME("iface %p, element %p, index %p, found %p stub!\n", iface, element, index, found); - *index = 0; + struct voice_information_vector *impl = impl_from_IVectorView_VoiceInformation(iface); + int i; + + TRACE("iface %p, element %p, index %p, found %p\n", iface, element, index, found); + for (i = 0; i < impl->provider.num_voices; i++) + if (element == impl->provider.voices[i]) + { + *index = i; + *found = TRUE; + return S_OK; + } *found = FALSE; return S_OK; } @@ -300,8 +318,18 @@ static HRESULT WINAPI vector_view_voice_information_IndexOf( IVectorView_VoiceIn static HRESULT WINAPI vector_view_voice_information_GetMany( IVectorView_VoiceInformation *iface, UINT32 start_index, UINT32 items_size, IVoiceInformation **items, UINT *value ) { - FIXME("iface %p, start_index %#x, items %p, value %p stub!\n", iface, start_index, items, value); - *value = 0; + struct voice_information_vector *impl = impl_from_IVectorView_VoiceInformation(iface); + int i; + + TRACE("iface %p, start_index %#x, items %p, value %p\n", iface, start_index, items, value); + if (start_index >= impl->provider.num_voices) + { + *value = 0; + return S_OK; + } + *value = min(impl->provider.num_voices - start_index, items_size); + for (i = 0; i < *value; i++) + IVoiceInformation_AddRef(items[i] = impl->provider.voices[start_index + i]); return S_OK; }
@@ -324,7 +352,8 @@ static const struct IVectorView_VoiceInformationVtbl vector_view_voice_informati static struct voice_information_vector all_voices = { {&vector_view_voice_information_vtbl}, - 0 + 0, + {}, };
/* @@ -1107,6 +1136,26 @@ static const struct IActivationFactoryVtbl factory_vtbl = factory_ActivateInstance, };
+static HRESULT dummy_provider_init(struct synth_provider *provider) +{ + HRESULT hr; + WCHAR locale[LOCALE_NAME_MAX_LENGTH]; + + if (GetUserDefaultLocaleName(locale, ARRAY_SIZE(locale)) > ARRAY_SIZE(locale)) + return E_OUTOFMEMORY; + + provider->voices = calloc(1, sizeof(all_voices.provider.voices[0])); + if (!provider->voices) return E_OUTOFMEMORY; + hr = voice_information_allocate(L"Dummy voice", L"--noid--", locale, VoiceGender_Male, &provider->voices[0]); + if (FAILED(hr)) + { + free(provider->voices); + } + else + provider->num_voices = 1; + return hr; +} + /* * * IInstalledVoicesStatic for SpeechSynthesizer runtimeclass @@ -1115,12 +1164,30 @@ static const struct IActivationFactoryVtbl factory_vtbl =
DEFINE_IINSPECTABLE(installed_voices_static, IInstalledVoicesStatic, struct synthesizer_statics, IActivationFactory_iface)
+static CRITICAL_SECTION allvoices_cs; +static CRITICAL_SECTION_DEBUG allvoices_critsect_debug = +{ + 0, 0, &allvoices_cs, + { &allvoices_critsect_debug.ProcessLocksList, &allvoices_critsect_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": allvoices_cs") } +}; +static CRITICAL_SECTION allvoices_cs = { &allvoices_critsect_debug, -1, 0, 0, 0, 0 }; + static HRESULT WINAPI installed_voices_static_get_AllVoices( IInstalledVoicesStatic *iface, IVectorView_VoiceInformation **value ) { + HRESULT hr; + TRACE("iface %p, value %p.\n", iface, value); - *value = &all_voices.IVectorView_VoiceInformation_iface; - IVectorView_VoiceInformation_AddRef(*value); - return S_OK; + + EnterCriticalSection(&allvoices_cs); + if (all_voices.provider.num_voices == 0) + hr = dummy_provider_init(&all_voices.provider); + else + hr = S_OK; + if (SUCCEEDED(hr)) + IVectorView_VoiceInformation_AddRef(*value = &all_voices.IVectorView_VoiceInformation_iface); + LeaveCriticalSection(&allvoices_cs); + return hr; }
static HRESULT WINAPI installed_voices_static_get_DefaultVoice( IInstalledVoicesStatic *iface, IVoiceInformation **value ) diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index 5f528084cc2..dcd478e62d6 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -951,7 +951,7 @@ static void test_SpeechSynthesizer(void) size = 0xdeadbeef; hr = IVectorView_VoiceInformation_get_Size(voices, &size); ok(hr == S_OK, "IVectorView_VoiceInformation_get_Size voices failed, hr %#lx\n", hr); - todo_wine ok(size != 0 && size != 0xdeadbeef, "IVectorView_VoiceInformation_get_Size returned %u\n", size); + ok(size != 0 && size != 0xdeadbeef, "IVectorView_VoiceInformation_get_Size returned %u\n", size);
voice = (IVoiceInformation *)0xdeadbeef; hr = IVectorView_VoiceInformation_GetAt(voices, size, &voice);
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/tests/speech.c | 34 +++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-)
diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index dcd478e62d6..e7852f84356 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -865,7 +865,7 @@ static void test_SpeechSynthesizer(void) IClosable *closable; struct async_inspectable_handler async_inspectable_handler; HMODULE hdll; - HSTRING str, str2; + HSTRING str, str2, default_voice_id; HRESULT hr; UINT32 size, idx; BOOLEAN found; @@ -975,13 +975,18 @@ static void test_SpeechSynthesizer(void)
check_comparable_presence(voices, voice);
- IVoiceInformation_get_Description(voice, &str2); + hr = IVoiceInformation_get_Description(voice, &str2); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); trace("SpeechSynthesizer default voice %s.\n", debugstr_hstring(str2)); - WindowsDeleteString(str2); + + hr = IVoiceInformation_get_Id(voice, &default_voice_id); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ref = IVoiceInformation_Release(voice); ok(ref == 0, "Got unexpected ref %lu.\n", ref); } + else default_voice_id = NULL;
IInstalledVoicesStatic_Release(voices_static); IAgileObject_Release(agile_object); @@ -998,6 +1003,29 @@ static void test_SpeechSynthesizer(void) hr = IInspectable_QueryInterface(inspectable, &IID_ISpeechSynthesizer, (void **)&synthesizer); ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
+ hr = ISpeechSynthesizer_get_Voice(synthesizer, &voice); + todo_wine ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + if (default_voice_id) + { + if (hr == S_OK) + { + INT32 cmp; + IVoiceInformation_get_Id(voice, &str); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = WindowsCompareStringOrdinal(str, default_voice_id, &cmp); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + hr = WindowsDeleteString(str); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + IVoiceInformation_Release(voice); + } + + hr = WindowsDeleteString(default_voice_id); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + } + /* Test SynthesizeTextToStreamAsync */ hr = WindowsCreateString(simple_synth_text, wcslen(simple_synth_text), &str); ok(hr == S_OK, "WindowsCreateString failed, hr %#lx\n", hr);
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/synthesizer.c | 50 +++++++++++++++++-- dlls/windows.media.speech/tests/speech.c | 61 +++++++++++------------- 2 files changed, 73 insertions(+), 38 deletions(-)
diff --git a/dlls/windows.media.speech/synthesizer.c b/dlls/windows.media.speech/synthesizer.c index 02e3e76e183..0d352ae8c26 100644 --- a/dlls/windows.media.speech/synthesizer.c +++ b/dlls/windows.media.speech/synthesizer.c @@ -33,6 +33,7 @@ struct voice_information HSTRING language; HSTRING description; VoiceGender gender; + BOOL is_static; };
static inline struct voice_information *impl_from_IVoiceInformation( IVoiceInformation *iface ) @@ -81,7 +82,9 @@ static ULONG WINAPI voice_information_Release( IVoiceInformation *iface ) struct voice_information *impl = impl_from_IVoiceInformation(iface); ULONG ref = InterlockedDecrement(&impl->ref); TRACE("iface %p, ref %lu.\n", iface, ref); - /* all voices are (for now) statically allocated in all_voices vector. so don't free them */ + /* only deallocate non static instances */ + if (!ref && !impl->is_static) + voice_information_delete(impl); return ref; }
@@ -192,8 +195,8 @@ HRESULT voice_information_allocate(const WCHAR *display_name, const WCHAR *id, c if (SUCCEEDED(hr)) { voice_info->gender = gender; + voice_info->is_static = TRUE; voice_info->IVoiceInformation_iface.lpVtbl = &voice_information_vtbl; - *pvoice = &voice_info->IVoiceInformation_iface; } else @@ -204,6 +207,35 @@ HRESULT voice_information_allocate(const WCHAR *display_name, const WCHAR *id, c return hr; }
+HRESULT voice_information_clone(IVoiceInformation *voice, IVoiceInformation **out) +{ + struct voice_information *voice_info; + HRESULT hr; + + voice_info = calloc(1, sizeof(*voice_info)); + if (!voice_info) return E_OUTOFMEMORY; + + hr = IVoiceInformation_get_DisplayName(voice, &voice_info->display_name); + if (SUCCEEDED(hr)) + hr = IVoiceInformation_get_Id(voice, &voice_info->id); + if (SUCCEEDED(hr)) + hr = IVoiceInformation_get_Language(voice, &voice_info->language); + if (SUCCEEDED(hr)) + hr = IVoiceInformation_get_Description(voice, &voice_info->description); + if (SUCCEEDED(hr)) + hr = IVoiceInformation_get_Gender(voice, &voice_info->gender); + if (SUCCEEDED(hr)) + { + voice_info->IVoiceInformation_iface.lpVtbl = &voice_information_vtbl; + voice_info->ref = 1; + *out = &voice_info->IVoiceInformation_iface; + } + else + voice_information_delete(voice_info); + + return hr; +} + /* * * IVectorView_VoiceInformation @@ -1192,8 +1224,18 @@ static HRESULT WINAPI installed_voices_static_get_AllVoices( IInstalledVoicesSta
static HRESULT WINAPI installed_voices_static_get_DefaultVoice( IInstalledVoicesStatic *iface, IVoiceInformation **value ) { - FIXME("iface %p, value %p stub!\n", iface, value); - return E_NOTIMPL; + struct IVoiceInformation *static_voice; + HRESULT hr; + + TRACE("iface %p, value %p\n", iface, value); + + EnterCriticalSection(&allvoices_cs); + hr = IVectorView_VoiceInformation_GetAt(&all_voices.IVectorView_VoiceInformation_iface, 0, &static_voice); + if (SUCCEEDED(hr)) + hr = voice_information_clone(static_voice, value); + LeaveCriticalSection(&allvoices_cs); + + return hr; }
static const struct IInstalledVoicesStaticVtbl installed_voices_static_vtbl = diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index e7852f84356..7db1f510ccc 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -963,30 +963,26 @@ static void test_SpeechSynthesizer(void) ok(size == 0, "IVectorView_VoiceInformation_GetMany returned count %u\n", size);
hr = IInstalledVoicesStatic_get_DefaultVoice(voices_static, &voice); - todo_wine ok(hr == S_OK, "IInstalledVoicesStatic_get_DefaultVoice failed, hr %#lx\n", hr); + ok(hr == S_OK, "IInstalledVoicesStatic_get_DefaultVoice failed, hr %#lx\n", hr);
- if (hr == S_OK) - { - /* check that VoiceInformation in static vector voice are not shared when exposed to user */ - idx = size; - hr = IVectorView_VoiceInformation_IndexOf(voices, voice, &idx, &found); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - ok(!found, "Shouldn't find default element\n"); + /* check that VoiceInformation in static vector voice are not shared when exposed to user */ + idx = size; + hr = IVectorView_VoiceInformation_IndexOf(voices, voice, &idx, &found); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(!found, "Shouldn't find default element\n");
- check_comparable_presence(voices, voice); + check_comparable_presence(voices, voice);
- hr = IVoiceInformation_get_Description(voice, &str2); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - trace("SpeechSynthesizer default voice %s.\n", debugstr_hstring(str2)); - WindowsDeleteString(str2); + hr = IVoiceInformation_get_Description(voice, &str2); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + trace("SpeechSynthesizer default voice %s.\n", debugstr_hstring(str2)); + WindowsDeleteString(str2);
- hr = IVoiceInformation_get_Id(voice, &default_voice_id); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVoiceInformation_get_Id(voice, &default_voice_id); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
- ref = IVoiceInformation_Release(voice); - ok(ref == 0, "Got unexpected ref %lu.\n", ref); - } - else default_voice_id = NULL; + ref = IVoiceInformation_Release(voice); + ok(ref == 0, "Got unexpected ref %lu.\n", ref);
IInstalledVoicesStatic_Release(voices_static); IAgileObject_Release(agile_object); @@ -1006,26 +1002,23 @@ static void test_SpeechSynthesizer(void) hr = ISpeechSynthesizer_get_Voice(synthesizer, &voice); todo_wine ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
- if (default_voice_id) + if (hr == S_OK) { - if (hr == S_OK) - { - INT32 cmp; - IVoiceInformation_get_Id(voice, &str); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - hr = WindowsCompareStringOrdinal(str, default_voice_id, &cmp); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - - hr = WindowsDeleteString(str); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - - IVoiceInformation_Release(voice); - } + INT32 cmp; + IVoiceInformation_get_Id(voice, &str); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = WindowsCompareStringOrdinal(str, default_voice_id, &cmp); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
- hr = WindowsDeleteString(default_voice_id); + hr = WindowsDeleteString(str); ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + IVoiceInformation_Release(voice); }
+ hr = WindowsDeleteString(default_voice_id); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + /* Test SynthesizeTextToStreamAsync */ hr = WindowsCreateString(simple_synth_text, wcslen(simple_synth_text), &str); ok(hr == S_OK, "WindowsCreateString failed, hr %#lx\n", hr);
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/windows.media.speech/synthesizer.c | 57 ++++++++++++++++++++++-- dlls/windows.media.speech/tests/speech.c | 22 +++++---- 2 files changed, 63 insertions(+), 16 deletions(-)
diff --git a/dlls/windows.media.speech/synthesizer.c b/dlls/windows.media.speech/synthesizer.c index 0d352ae8c26..bdf7325f669 100644 --- a/dlls/windows.media.speech/synthesizer.c +++ b/dlls/windows.media.speech/synthesizer.c @@ -839,6 +839,7 @@ struct synthesizer LONG ref;
struct synthesizer_options *options; + IVoiceInformation *current_voice; };
/* @@ -902,6 +903,8 @@ static ULONG WINAPI synthesizer_Release( ISpeechSynthesizer *iface ) { if (impl->options) ISpeechSynthesizerOptions_Release(&impl->options->ISpeechSynthesizerOptions_iface); + if (impl->current_voice) + IVoiceInformation_Release(impl->current_voice); free(impl); }
@@ -954,14 +957,49 @@ static HRESULT WINAPI synthesizer_SynthesizeSsmlToStreamAsync( ISpeechSynthesize
static HRESULT WINAPI synthesizer_put_Voice( ISpeechSynthesizer *iface, IVoiceInformation *value ) { - FIXME("iface %p, value %p stub.\n", iface, value); - return E_NOTIMPL; + struct synthesizer *impl = impl_from_ISpeechSynthesizer(iface); + IVoiceInformation *voice; + HSTRING id, id2; + HRESULT hr; + INT32 cmp, idx; + + TRACE("iface %p, value %p semi-stub.\n", iface, value); + + hr = IVoiceInformation_get_Id(value, &id); + if (FAILED(hr)) return hr; + + for (idx = 0; ; idx++) + { + if (SUCCEEDED(hr = IVectorView_VoiceInformation_GetAt(&all_voices.IVectorView_VoiceInformation_iface, idx, &voice))) + { + if (SUCCEEDED(hr = IVoiceInformation_get_Id(voice, &id2))) + { + hr = WindowsCompareStringOrdinal(id, id2, &cmp); + WindowsDeleteString(id2); + } + IVoiceInformation_Release(voice); + } + if (FAILED(hr) || cmp == 0) break; + } + WindowsDeleteString(id); + + if (SUCCEEDED(hr)) + { + if (impl->current_voice) + IVoiceInformation_Release(impl->current_voice); + IVoiceInformation_AddRef(impl->current_voice = value); + } + return hr; }
static HRESULT WINAPI synthesizer_get_Voice( ISpeechSynthesizer *iface, IVoiceInformation **value ) { - FIXME("iface %p, value %p stub.\n", iface, value); - return E_NOTIMPL; + struct synthesizer *impl = impl_from_ISpeechSynthesizer(iface); + + TRACE("iface %p, value %p.\n", iface, value); + if (!impl->current_voice) return E_NOTIMPL; + IVoiceInformation_AddRef(*value = impl->current_voice); + return S_OK; }
static const struct ISpeechSynthesizerVtbl synthesizer_vtbl = @@ -1136,7 +1174,9 @@ static HRESULT WINAPI factory_GetTrustLevel( IActivationFactory *iface, TrustLev
static HRESULT WINAPI factory_ActivateInstance( IActivationFactory *iface, IInspectable **instance ) { + struct IVoiceInformation *static_voice; struct synthesizer *impl; + HRESULT hr;
TRACE("iface %p, instance %p.\n", iface, instance);
@@ -1149,6 +1189,15 @@ static HRESULT WINAPI factory_ActivateInstance( IActivationFactory *iface, IInsp impl->ISpeechSynthesizer_iface.lpVtbl = &synthesizer_vtbl; impl->ISpeechSynthesizer2_iface.lpVtbl = &synthesizer2_vtbl; impl->IClosable_iface.lpVtbl = &closable_vtbl; + /* assuming default is the first one... */ + hr = IVectorView_VoiceInformation_GetAt(&all_voices.IVectorView_VoiceInformation_iface, 0, &static_voice); + if (SUCCEEDED(hr)) + hr = voice_information_clone(static_voice, &impl->current_voice); + if (FAILED(hr)) + { + free(impl); + return hr; + } impl->ref = 1;
*instance = (IInspectable *)&impl->ISpeechSynthesizer_iface; diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index 7db1f510ccc..019ca6fc260 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -870,6 +870,7 @@ static void test_SpeechSynthesizer(void) UINT32 size, idx; BOOLEAN found; ULONG ref; + INT32 cmp;
hr = RoInitialize(RO_INIT_MULTITHREADED); ok(hr == S_OK, "RoInitialize failed, hr %#lx\n", hr); @@ -1000,21 +1001,18 @@ static void test_SpeechSynthesizer(void) ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
hr = ISpeechSynthesizer_get_Voice(synthesizer, &voice); - todo_wine ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
- if (hr == S_OK) - { - INT32 cmp; - IVoiceInformation_get_Id(voice, &str); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - hr = WindowsCompareStringOrdinal(str, default_voice_id, &cmp); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IVoiceInformation_get_Id(voice, &str); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
- hr = WindowsDeleteString(str); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = WindowsCompareStringOrdinal(str, default_voice_id, &cmp); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
- IVoiceInformation_Release(voice); - } + hr = WindowsDeleteString(str); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + IVoiceInformation_Release(voice);
hr = WindowsDeleteString(default_voice_id); ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=133696
Your paranoid android.
=== debian11 (32 bit report) ===
user32: msg.c:6897: Test failed: SetFocus(hwnd) on a button: 4: the msg 0x0007 was expected, but got msg 0x0005 instead msg.c:6897: Test failed: SetFocus(hwnd) on a button: 5: the msg 0x0135 was expected, but got msg 0x030f instead msg.c:6897: Test failed: SetFocus(hwnd) on a button: 6: the msg 0x0111 was expected, but got msg 0x001c instead msg.c:6897: Test failed: SetFocus(hwnd) on a button: 7: the msg 0x8000 was expected, but got msg 0x0086 instead msg.c:6897: Test failed: SetFocus(hwnd) on a button: 8: the msg sequence is not complete: expected 0000 - actual 0006
On Mon Jun 12 15:27:39 2023 +0000, **** wrote:
Marvin replied on the mailing list:
Hi, It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated. The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details: The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=133696 Your paranoid android. === debian11 (32 bit report) === user32: msg.c:6897: Test failed: SetFocus(hwnd) on a button: 4: the msg 0x0007 was expected, but got msg 0x0005 instead msg.c:6897: Test failed: SetFocus(hwnd) on a button: 5: the msg 0x0135 was expected, but got msg 0x030f instead msg.c:6897: Test failed: SetFocus(hwnd) on a button: 6: the msg 0x0111 was expected, but got msg 0x001c instead msg.c:6897: Test failed: SetFocus(hwnd) on a button: 7: the msg 0x8000 was expected, but got msg 0x0086 instead msg.c:6897: Test failed: SetFocus(hwnd) on a button: 8: the msg sequence is not complete: expected 0000 - actual 0006
not related to series (and https://testbot.winehq.org/JobDetails.pl?Key=133694 passes)
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/synthesizer.c:
static HRESULT WINAPI synthesizer2_get_Options( ISpeechSynthesizer2 *iface, ISpeechSynthesizerOptions **value ) {
- FIXME("iface %p, value %p stub.\n", iface, value);
- return E_NOTIMPL;
- struct synthesizer *impl = impl_from_ISpeechSynthesizer2(iface);
- WARN("iface %p, value %p semi-stub.\n", iface, value);
- if (!impl->options)
- {
struct synthesizer_options *options;
HRESULT hr = synthesizer_options_allocate(&options);
if (FAILED(hr)) return hr;
if (InterlockedCompareExchangePointer((void **)&impl->options, options, NULL) != NULL)
`!= NULL` seems redundant.
On Mon Jun 12 21:31:02 2023 +0000, Bernhard Kölbl wrote:
`!= NULL` seems redundant.
- actually, it's the comparison against `InterlockedCompareExchangePointer` third argument, so I usually find it more readable this way - the DLL has other cases of explicit comparisons of pointers against NULL, so I can't really see a pattern applied uniformely in the DLL either - this can be changed of course
On Tue Jun 13 06:58:55 2023 +0000, eric pouech wrote:
- actually, it's the comparison against
`InterlockedCompareExchangePointer` third argument, so I usually find it more readable this way
- the DLL has other cases of explicit comparisons of pointers against
NULL, so I can't really see a pattern applied uniformely in the DLL either
- this can be changed of course
Alright, I'd say we keep it.
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/synthesizer.c:
- /*** IUnknown methods ***/
- synthesizer_options3_QueryInterface,
- synthesizer_options3_AddRef,
- synthesizer_options3_Release,
- /*** IInspectable methods ***/
- synthesizer_options3_GetIids,
- synthesizer_options3_GetRuntimeClassName,
- synthesizer_options3_GetTrustLevel,
- /*** ISpeechSynthesizerOptions methods ***/
- synthesizer_options3_get_AppendedSilence,
- synthesizer_options3_put_AppendedSilence,
- synthesizer_options3_get_PunctuationSilence,
- synthesizer_options3_put_PunctuationSilence,
+};
+static HRESULT synthesizer_options_allocate( struct synthesizer_options **out )
Any reason, you called these functions with `_allocate` instead of `_create` ?
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/synthesizer.c:
WINE_DEFAULT_DEBUG_CHANNEL(speech);
+struct voice_information
Please add a description, like for the other interfaces/classes.
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/synthesizer.c:
DEFINE_IINSPECTABLE(installed_voices_static, IInstalledVoicesStatic, struct synthesizer_statics, IActivationFactory_iface)
+static CRITICAL_SECTION allvoices_cs; +static CRITICAL_SECTION_DEBUG allvoices_critsect_debug = +{
- 0, 0, &allvoices_cs,
- { &allvoices_critsect_debug.ProcessLocksList, &allvoices_critsect_debug.ProcessLocksList },
0, 0, { (DWORD_PTR)(__FILE__ ": allvoices_cs") }
+}; +static CRITICAL_SECTION allvoices_cs = { &allvoices_critsect_debug, -1, 0, 0, 0, 0 };
I honestly don't like the idea of adding a whole cs for just a single function. Maybe make this part of the statics iface?
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/tests/speech.c:
hr = IInspectable_QueryInterface(inspectable, &IID_ISpeechSynthesizer, (void **)&synthesizer); ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
- hr = ISpeechSynthesizer_get_Voice(synthesizer, &voice);
- todo_wine ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr);
- if (default_voice_id)
- {
WindowsDeleteString is null safe iirc, so you can squash these ifs.
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/synthesizer.c:
+static HRESULT synthesizer_options_allocate( struct synthesizer_options **out ) +{
- struct synthesizer_options *options;
- if (!(options = calloc(1, sizeof(*options)))) return E_OUTOFMEMORY;
- options->ISpeechSynthesizerOptions_iface.lpVtbl = &synthesizer_options_vtbl;
- options->ISpeechSynthesizerOptions2_iface.lpVtbl = &synthesizer_options2_vtbl;
- options->ISpeechSynthesizerOptions3_iface.lpVtbl = &synthesizer_options3_vtbl;
- /* all other values default to 0 or false */
- options->audio_pitch = 1.0;
- options->audio_volume = 1.0;
- options->speaking_rate = 1.0;
- options->ref = 1;
- *out = options;
Please add a trace showing the address of the created object.
Comme ça: `TRACE("Created I... %p.\n", *out)`
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/synthesizer.c:
- FIXME("iface %p, value %p stub.\n", iface, value);
- return E_NOTIMPL;
- struct synthesizer *impl = impl_from_ISpeechSynthesizer2(iface);
- WARN("iface %p, value %p semi-stub.\n", iface, value);
- if (!impl->options)
- {
struct synthesizer_options *options;
HRESULT hr = synthesizer_options_allocate(&options);
if (FAILED(hr)) return hr;
if (InterlockedCompareExchangePointer((void **)&impl->options, options, NULL) != NULL)
/* another thread beat us */
ISpeechSynthesizerOptions_AddRef(&options->ISpeechSynthesizerOptions_iface);
- }
- ISpeechSynthesizerOptions_AddRef(*value = &impl->options->ISpeechSynthesizerOptions_iface);
I honestly think this function is just too complicated, can we just put the Options creation im the Synthesizer creation?
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/synthesizer.c:
- voice_information_get_Id,
- voice_information_get_Language,
- voice_information_get_Description,
- voice_information_get_Gender,
+};
+HRESULT voice_information_allocate(const WCHAR *display_name, const WCHAR *id, const WCHAR *locale,
VoiceGender gender, IVoiceInformation **pvoice)
+{
- struct voice_information *voice_info;
- WCHAR *description;
- HRESULT hr;
- size_t len, langlen;
- voice_info = calloc(1, sizeof(*voice_info));
- if (!voice_info) return E_OUTOFMEMORY;
Make this a one liner, please. :))
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/synthesizer.c:
- wcscat(description, L" - ");
- GetLocaleInfoEx(locale, LOCALE_SLOCALIZEDDISPLAYNAME, description + len, langlen);
- hr = WindowsCreateString(display_name, wcslen(display_name), &voice_info->display_name);
- if (SUCCEEDED(hr))
hr = WindowsCreateString(id, wcslen(id), &voice_info->id);
- if (SUCCEEDED(hr))
hr = WindowsCreateString(locale, wcslen(locale), &voice_info->language);
- if (SUCCEEDED(hr))
hr = WindowsCreateString(description, len + langlen - 1, &voice_info->description);
- if (SUCCEEDED(hr))
- {
voice_info->gender = gender;
voice_info->IVoiceInformation_iface.lpVtbl = &voice_information_vtbl;
*pvoice = &voice_info->IVoiceInformation_iface;
Here same thing, please trace the created objects address.
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/private.h:
#define DEFINE_IINSPECTABLE_OUTER( pfx, iface_type, impl_type, outer_iface ) \ DEFINE_IINSPECTABLE_( pfx, iface_type, impl_type, impl_from_##iface_type, iface_type##_iface, impl->outer_iface )
+struct synth_provider
I find this name to be really confusing. Hbu static_voice_information_provider ?
Bernhard Kölbl (@besentv) commented about dlls/windows.media.speech/private.h:
#define DEFINE_IINSPECTABLE_OUTER( pfx, iface_type, impl_type, outer_iface ) \ DEFINE_IINSPECTABLE_( pfx, iface_type, impl_type, impl_from_##iface_type, iface_type##_iface, impl->outer_iface )
+struct synth_provider +{
- struct IVoiceInformation **voices;
- unsigned num_voices;
- void (*dispose)(struct synth_provider *provider);
This function pointer seems unused.
For the vector-view it would generally be interesting, if you receive the same object everytime you query for the default voices list, or not. If you receive a different object, then you could just use the built-in Vector implementation, delete the static interface and just return a VectorView via IVector_GetView() everytime it's queried. If not, it would still be worth checking if you receive the same `IVoiceInformation` objects, if not you could make a list and just return copies without having to not delete these instances in the Release function.
On Wed Jun 14 20:32:59 2023 +0000, Bernhard Kölbl wrote:
For the vector-view it would generally be interesting, if you receive the same object everytime you query for the default voices list, or not. If you receive a different object, then you could just use the built-in Vector implementation, delete the static interface and just return a VectorView via IVector_GetView() everytime it's queried. If not, it would still be worth checking if you receive the same `IVoiceInformation` objects, if not you could make a list and just return copies without having to not delete these instances in the Release function.
Well, I just realized my first idea can't possibly work out, but the check for same `IVoiceInformation` objects would still be very interesting.
On Tue Jun 13 08:37:15 2023 +0000, Bernhard Kölbl wrote:
Any reason, you called these functions with `_allocate` instead of `_create` ?
will change
On Tue Jun 13 09:37:31 2023 +0000, Bernhard Kölbl wrote:
Please add a description, like for the other interfaces/classes.
will do
On Tue Jun 13 10:23:17 2023 +0000, Bernhard Kölbl wrote:
I honestly don't like the idea of adding a whole cs for just a single function. Maybe make this part of the statics iface?
I don't see a way of doing it since you need a forward decl of the cs to init the debug info part. or either: - we drop debug info - or more initialisation into a function called at DLL load time (or factory creation) So I wouldn't change it
On Wed Jun 14 16:10:27 2023 +0000, Bernhard Kölbl wrote:
WindowsDeleteString is null safe iirc, so you can squash these ifs.
I'll change
On Thu Jun 15 06:53:55 2023 +0000, eric pouech wrote:
I'll change
that's removed in next patches anyway
On Wed Jun 14 16:23:25 2023 +0000, Bernhard Kölbl wrote:
Please add a trace showing the address of the created object. Comme ça: `TRACE("Created I... %p.\n", *out)`
will do
On Wed Jun 14 16:26:34 2023 +0000, Bernhard Kölbl wrote:
I honestly think this function is just too complicated, can we just put the Options creation im the Synthesizer creation?
will do
On Wed Jun 14 19:16:36 2023 +0000, Bernhard Kölbl wrote:
Make this a one liner, please. :))
will do
On Wed Jun 14 19:17:15 2023 +0000, Bernhard Kölbl wrote:
Here same thing, please trace the created objects address.
will do
On Wed Jun 14 19:37:08 2023 +0000, Bernhard Kölbl wrote:
I find this name to be really confusing. Hbu static_voice_information_provider ?
the actual idea is to be able to support several voice providers: - the current code as the "dummy" one, which provides an empty and unique voice, so that most applications expecting TSS available won't fail (that should be the fallback when all the other providers fail to be instanciated) - using sapi and expose sapi results - some other platforms (macos, android) may also require their own provider so your proposal doesn't really fit the above (perhaps synth_voices_provider would match better)
On Wed Jun 14 19:38:16 2023 +0000, Bernhard Kölbl wrote:
This function pointer seems unused.
(it's used in next unreleased patches, I'll move it forward)
On Thu Jun 15 07:47:04 2023 +0000, eric pouech wrote:
the actual idea is to be able to support several voice providers:
- the current code as the "dummy" one, which provides an empty and
unique voice, so that most applications expecting TSS available won't fail (that should be the fallback when all the other providers fail to be instanciated)
- using sapi and expose sapi results
- some other platforms (macos, android) may also require their own provider
so your proposal doesn't really fit the above (perhaps synth_voices_provider would match better)
Sure synth_voices_provider sounds good.
On Wed Jun 14 20:33:16 2023 +0000, Bernhard Kölbl wrote:
Well, I just realized my first idea can't possibly work out, but the check for same `IVoiceInformation` objects would still be very interesting.
ok will do (also adding cross checks between GetAt and GetMany)