Signed-off-by: Paul Gofman pgofman@codeweavers.com --- dlls/windows.media.speech/main.c | 143 +++++++++++++++++++++- dlls/windows.media.speech/tests/speech.c | 19 ++- include/Makefile.in | 1 + include/windows.media.idl | 48 ++++++++ include/windows.media.speechsynthesis.idl | 41 +++++++ 5 files changed, 249 insertions(+), 3 deletions(-) create mode 100644 include/windows.media.idl
diff --git a/dlls/windows.media.speech/main.c b/dlls/windows.media.speech/main.c index e34fa9d16b8..1802d19dff8 100644 --- a/dlls/windows.media.speech/main.c +++ b/dlls/windows.media.speech/main.c @@ -24,6 +24,7 @@ #include "winbase.h" #include "winstring.h" #include "wine/debug.h" +#include "wine/heap.h" #include "objbase.h"
#include "initguid.h" @@ -171,6 +172,132 @@ static struct voice_information_vector all_voices = 0 };
+struct speech_synthesizer +{ + ISpeechSynthesizer ISpeechSynthesizer_iface; + LONG ref; +}; + +static inline struct speech_synthesizer *impl_from_ISpeechSynthesizer(ISpeechSynthesizer *iface) +{ + return CONTAINING_RECORD(iface, struct speech_synthesizer, ISpeechSynthesizer_iface); +} + +static HRESULT STDMETHODCALLTYPE speech_synthesizer_QueryInterface( + ISpeechSynthesizer *iface, REFIID iid, void **out) +{ + 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_ISpeechSynthesizer)) + { + IUnknown_AddRef(iface); + *out = iface; + return S_OK; + } + + FIXME("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + *out = NULL; + return E_NOINTERFACE; +} + +static ULONG STDMETHODCALLTYPE speech_synthesizer_AddRef( + ISpeechSynthesizer *iface) +{ + struct speech_synthesizer *impl = impl_from_ISpeechSynthesizer(iface); + ULONG ref = InterlockedIncrement(&impl->ref); + + TRACE("iface %p, ref %u.\n", iface, ref); + + return ref; +} + +static ULONG STDMETHODCALLTYPE speech_synthesizer_Release( + ISpeechSynthesizer *iface) +{ + struct speech_synthesizer *impl = impl_from_ISpeechSynthesizer(iface); + ULONG ref = InterlockedDecrement(&impl->ref); + + TRACE("iface %p, ref %u.\n", iface, ref); + + if (!ref) + heap_free(impl); + + return ref; +} + +static HRESULT STDMETHODCALLTYPE speech_synthesizer_GetIids( + ISpeechSynthesizer *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 STDMETHODCALLTYPE speech_synthesizer_GetRuntimeClassName( + ISpeechSynthesizer *iface, HSTRING *class_name) +{ + FIXME("iface %p, class_name %p stub.\n", iface, class_name); + + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE speech_synthesizer_GetTrustLevel( + ISpeechSynthesizer *iface, TrustLevel *trust_level) +{ + FIXME("iface %p, trust_level %p stub.\n", iface, trust_level); + + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE speech_synthesizer_SynthesizeTextToStreamAsync(ISpeechSynthesizer *iface, + HSTRING text, IAsyncOperation_SpeechSynthesisStream **operation) +{ + FIXME("iface %p, text %p, operation %p stub.\n", iface, text, operation); + + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE speech_synthesizer_SynthesizeSsmlToStreamAsync(ISpeechSynthesizer *iface, + HSTRING ssml, IAsyncOperation_SpeechSynthesisStream **operation) +{ + FIXME("iface %p, text %p, operation %p stub.\n", iface, ssml, operation); + + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE speech_synthesizer_put_Voice(ISpeechSynthesizer *iface, IVoiceInformation *value) +{ + FIXME("iface %p, value %p stub.\n", iface, value); + + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE speech_synthesizer_get_Voice(ISpeechSynthesizer *iface, IVoiceInformation **value) +{ + FIXME("iface %p, value %p stub.\n", iface, value); + + return E_NOTIMPL; +} + +static const struct ISpeechSynthesizerVtbl speech_synthesizer_vtbl = +{ + /* IUnknown methods */ + speech_synthesizer_QueryInterface, + speech_synthesizer_AddRef, + speech_synthesizer_Release, + /* IInspectable methods */ + speech_synthesizer_GetIids, + speech_synthesizer_GetRuntimeClassName, + speech_synthesizer_GetTrustLevel, + /* ISpeechSynthesizer methods */ + speech_synthesizer_SynthesizeTextToStreamAsync, + speech_synthesizer_SynthesizeSsmlToStreamAsync, + speech_synthesizer_put_Voice, + speech_synthesizer_get_Voice, +}; + struct windows_media_speech { IActivationFactory IActivationFactory_iface; @@ -259,8 +386,20 @@ static HRESULT STDMETHODCALLTYPE windows_media_speech_GetTrustLevel( static HRESULT STDMETHODCALLTYPE windows_media_speech_ActivateInstance( IActivationFactory *iface, IInspectable **instance) { - FIXME("iface %p, instance %p stub!\n", iface, instance); - return E_NOTIMPL; + struct speech_synthesizer *obj; + + TRACE("iface %p, instance %p.\n", iface, instance); + + if (!(obj = heap_alloc_zero(sizeof(*obj)))) + { + *instance = NULL; + return E_OUTOFMEMORY; + } + + obj->ISpeechSynthesizer_iface.lpVtbl = &speech_synthesizer_vtbl; + obj->ref = 1; + *instance = (IInspectable *)&obj->ISpeechSynthesizer_iface; + return S_OK; }
static const struct IActivationFactoryVtbl activation_factory_vtbl = diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index c949b35900b..e27a2bfa4d4 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -44,9 +44,11 @@ static void test_SpeechSynthesizer(void) IVoiceInformation *voice; IInspectable *inspectable = NULL, *tmp_inspectable = NULL; IAgileObject *agile_object = NULL, *tmp_agile_object = NULL; + ISpeechSynthesizer *synthesizer; HSTRING str; HRESULT hr; UINT32 size; + ULONG ref;
hr = RoInitialize(RO_INIT_MULTITHREADED); ok(hr == S_OK, "RoInitialize failed, hr %#x\n", hr); @@ -107,8 +109,23 @@ static void test_SpeechSynthesizer(void)
IAgileObject_Release(agile_object); IInspectable_Release(inspectable); - IActivationFactory_Release(factory);
+ hr = IActivationFactory_QueryInterface(factory, &IID_ISpeechSynthesizer, (void **)&synthesizer); + ok(hr == E_NOINTERFACE, "Got unexpected hr %#x.\n", hr); + + hr = RoActivateInstance(str, &inspectable); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + + hr = IInspectable_QueryInterface(inspectable, &IID_ISpeechSynthesizer, (void **)&synthesizer); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + + ref = ISpeechSynthesizer_Release(synthesizer); + ok(ref == 1, "Got unexpected ref %u.\n", ref); + + ref = IInspectable_Release(inspectable); + ok(!ref, "Got unexpected ref %u.\n", ref); + + IActivationFactory_Release(factory); WindowsDeleteString(str);
RoUninitialize(); diff --git a/include/Makefile.in b/include/Makefile.in index 756e25dcc82..530c5b73f19 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -775,6 +775,7 @@ SOURCES = \ windows.gaming.input.idl \ windows.globalization.idl \ windows.h \ + windows.media.idl \ windows.media.devices.idl \ windows.media.speechsynthesis.idl \ windows.storage.streams.idl \ diff --git a/include/windows.media.idl b/include/windows.media.idl new file mode 100644 index 00000000000..94a9a516245 --- /dev/null +++ b/include/windows.media.idl @@ -0,0 +1,48 @@ +/* + * Copyright 2021 Paul Gofman for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifdef __WIDL__ +#pragma winrt ns_prefix +#endif + +import "inspectable.idl"; +import "windows.foundation.idl"; + +namespace Windows { + namespace Media { + apicontract MediaControlContract; + interface IMediaControl; + } +} + +namespace Windows { + namespace Media { + [contractversion(1.0)] + apicontract MediaControlContract { + } + + [contract(Windows.Foundation.UniversalApiContract, 1.0)] + [uuid(1803def8-dca5-4b6f-9c20-e3d3c0643625)] + interface IMediaMarker : IInspectable + { + [propget] HRESULT Time([out] [retval] Windows.Foundation.TimeSpan* value); + [propget] HRESULT MediaMarkerType([out] [retval] HSTRING* value); + [propget] HRESULT Text([out] [retval] HSTRING* value); + } + } +} diff --git a/include/windows.media.speechsynthesis.idl b/include/windows.media.speechsynthesis.idl index af4466681dc..169dfdf5a5e 100644 --- a/include/windows.media.speechsynthesis.idl +++ b/include/windows.media.speechsynthesis.idl @@ -22,6 +22,7 @@
import "inspectable.idl"; import "windows.foundation.idl"; +import "windows.media.idl";
namespace Windows { namespace Foundation { @@ -35,12 +36,20 @@ namespace Windows { interface ISpeechSynthesizer; interface ISpeechSynthesizer2; interface IVoiceInformation; + interface ISpeechSynthesisStream; runtimeclass SpeechSynthesizer; runtimeclass VoiceInformation; + runtimeclass SpeechSynthesisStream; } } }
+namespace Windows { + namespace Media { + interface IMediaMarker; + } +} + namespace Windows { namespace Media { namespace SpeechSynthesis { @@ -48,6 +57,9 @@ namespace Windows { interface Windows.Foundation.Collections.IIterator<Windows.Media.SpeechSynthesis.VoiceInformation*>; interface Windows.Foundation.Collections.IIterable<Windows.Media.SpeechSynthesis.VoiceInformation*>; interface Windows.Foundation.Collections.IVectorView<Windows.Media.SpeechSynthesis.VoiceInformation*>; + interface Windows.Foundation.Collections.IVectorView<Windows.Media.IMediaMarker*>; + interface Windows.Foundation.IAsyncOperation<Windows.Media.SpeechSynthesis.SpeechSynthesisStream*>; + interface Windows.Foundation.AsyncOperationCompletedHandler<Windows.Media.SpeechSynthesis.SpeechSynthesisStream*>; } } } @@ -63,6 +75,27 @@ namespace Windows { Female = 1 };
+ [contract(Windows.Foundation.UniversalApiContract, 1.0)] + [exclusiveto(Windows.Media.SpeechSynthesis.SpeechSynthesisStream)] + [uuid(83E46E93-244C-4622-BA0B-6229C4D0D65D)] + interface ISpeechSynthesisStream : IInspectable + { + [propget] HRESULT Markers([out] [retval] Windows.Foundation.Collections.IVectorView<Windows.Media.IMediaMarker*>** value); + } + + [ + contract(Windows.Foundation.UniversalApiContract, 1.0), + exclusiveto(Windows.Media.SpeechSynthesis.SpeechSynthesizer), + uuid(ce9f7c76-97f4-4ced-ad68-d51c458e45c6) + ] + interface ISpeechSynthesizer : IInspectable + { + HRESULT SynthesizeTextToStreamAsync([in] HSTRING text, [out] [retval] Windows.Foundation.IAsyncOperation<Windows.Media.SpeechSynthesis.SpeechSynthesisStream*> **operation); + HRESULT SynthesizeSsmlToStreamAsync([in] HSTRING Ssml, [out] [retval] Windows.Foundation.IAsyncOperation<Windows.Media.SpeechSynthesis.SpeechSynthesisStream*> **operation); + [propput] HRESULT Voice([in] VoiceInformation *value); + [propget] HRESULT Voice([out] [retval] VoiceInformation **value); + } + [ contract(Windows.Foundation.UniversalApiContract, 1.0), exclusiveto(Windows.Media.SpeechSynthesis.VoiceInformation), @@ -97,6 +130,14 @@ namespace Windows { [default] interface Windows.Media.SpeechSynthesis.IVoiceInformation; }
+ + [contract(Windows.Foundation.UniversalApiContract, 1.0)] + [marshaling_behavior(agile)] + runtimeclass SpeechSynthesisStream + { + [default] interface Windows.Media.SpeechSynthesis.ISpeechSynthesisStream; + } + [ activatable(Windows.Foundation.UniversalApiContract, 1.0), contract(Windows.Foundation.UniversalApiContract, 1.0),
Paul Gofman pgofman@codeweavers.com writes:
diff --git a/dlls/windows.media.speech/main.c b/dlls/windows.media.speech/main.c index e34fa9d16b8..1802d19dff8 100644 --- a/dlls/windows.media.speech/main.c +++ b/dlls/windows.media.speech/main.c @@ -24,6 +24,7 @@ #include "winbase.h" #include "winstring.h" #include "wine/debug.h" +#include "wine/heap.h"
Please use standard malloc functions in new code instead of adding new users of wine/heap.h, we'd like to phase it out at some point.
On 11/23/21 13:10, Paul Gofman wrote:
+static HRESULT STDMETHODCALLTYPE speech_synthesizer_QueryInterface(
ISpeechSynthesizer *iface, REFIID iid, void **out)
+{
- 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_ISpeechSynthesizer))
- {
IUnknown_AddRef(iface);
*out = iface;
return S_OK;
- }
- FIXME("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid));
- *out = NULL;
- return E_NOINTERFACE;
+}
I think it's also supposed to implement IClosable, and ISpeechSynthesizer2 which, if they don't pull too many things, should probably not be too hard to add, and be accepted here as we declare them in the IDL.
struct windows_media_speech { IActivationFactory IActivationFactory_iface; @@ -259,8 +386,20 @@ static HRESULT STDMETHODCALLTYPE windows_media_speech_GetTrustLevel( static HRESULT STDMETHODCALLTYPE windows_media_speech_ActivateInstance( IActivationFactory *iface, IInspectable **instance) {
- FIXME("iface %p, instance %p stub!\n", iface, instance);
- return E_NOTIMPL;
- struct speech_synthesizer *obj;
- TRACE("iface %p, instance %p.\n", iface, instance);
- if (!(obj = heap_alloc_zero(sizeof(*obj))))
- {
*instance = NULL;
return E_OUTOFMEMORY;
- }
- obj->ISpeechSynthesizer_iface.lpVtbl = &speech_synthesizer_vtbl;
- obj->ref = 1;
- *instance = (IInspectable *)&obj->ISpeechSynthesizer_iface;
- return S_OK; }
As I understand it, although maybe tests could validate or invalidate this hypothesis, we will need a different activation factory for each class, instead of using the same one for every class and allocating ISpeechSynthesizer every time ActivateInstance is called.
Right now it was just all stubs so it didn't really matter, but if a different class was initially requested I think we would here silently activate an instance of the wrong class and return the wrong interface.