Commit 9ff790b7 authored by Max Kellermann's avatar Max Kellermann

output/wasapi: move COM utilities to separate headers

parent ebc1fe28
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
*/ */
#include "output/plugins/wasapi/ForMixer.hxx" #include "output/plugins/wasapi/ForMixer.hxx"
#include "output/plugins/wasapi/AudioClient.hxx"
#include "output/plugins/wasapi/Device.hxx"
#include "mixer/MixerInternal.hxx" #include "mixer/MixerInternal.hxx"
#include "win32/ComPtr.hxx" #include "win32/ComPtr.hxx"
#include "win32/ComWorker.hxx" #include "win32/ComWorker.hxx"
...@@ -47,15 +49,8 @@ public: ...@@ -47,15 +49,8 @@ public:
float volume_level; float volume_level;
if (wasapi_is_exclusive(output)) { if (wasapi_is_exclusive(output)) {
ComPtr<IAudioEndpointVolume> endpoint_volume; auto endpoint_volume =
result = wasapi_output_get_device(output)->Activate( Activate<IAudioEndpointVolume>(*wasapi_output_get_device(output));
__uuidof(IAudioEndpointVolume), CLSCTX_ALL,
nullptr, endpoint_volume.AddressCast());
if (FAILED(result)) {
throw FormatHResultError(result,
"Unable to get device "
"endpoint volume");
}
result = endpoint_volume->GetMasterVolumeLevelScalar( result = endpoint_volume->GetMasterVolumeLevelScalar(
&volume_level); &volume_level);
...@@ -65,15 +60,8 @@ public: ...@@ -65,15 +60,8 @@ public:
"volume level"); "volume level");
} }
} else { } else {
ComPtr<ISimpleAudioVolume> session_volume; auto session_volume =
result = wasapi_output_get_client(output)->GetService( GetService<ISimpleAudioVolume>(*wasapi_output_get_client(output));
__uuidof(ISimpleAudioVolume),
session_volume.AddressCast<void>());
if (FAILED(result)) {
throw FormatHResultError(result,
"Unable to get client "
"session volume");
}
result = session_volume->GetMasterVolume(&volume_level); result = session_volume->GetMasterVolume(&volume_level);
if (FAILED(result)) { if (FAILED(result)) {
...@@ -93,15 +81,8 @@ public: ...@@ -93,15 +81,8 @@ public:
const float volume_level = volume / 100.0f; const float volume_level = volume / 100.0f;
if (wasapi_is_exclusive(output)) { if (wasapi_is_exclusive(output)) {
ComPtr<IAudioEndpointVolume> endpoint_volume; auto endpoint_volume =
result = wasapi_output_get_device(output)->Activate( Activate<IAudioEndpointVolume>(*wasapi_output_get_device(output));
__uuidof(IAudioEndpointVolume), CLSCTX_ALL,
nullptr, endpoint_volume.AddressCast());
if (FAILED(result)) {
throw FormatHResultError(
result,
"Unable to get device endpoint volume");
}
result = endpoint_volume->SetMasterVolumeLevelScalar( result = endpoint_volume->SetMasterVolumeLevelScalar(
volume_level, nullptr); volume_level, nullptr);
...@@ -111,15 +92,8 @@ public: ...@@ -111,15 +92,8 @@ public:
"Unable to set master volume level"); "Unable to set master volume level");
} }
} else { } else {
ComPtr<ISimpleAudioVolume> session_volume; auto session_volume =
result = wasapi_output_get_client(output)->GetService( GetService<ISimpleAudioVolume>(*wasapi_output_get_client(output));
__uuidof(ISimpleAudioVolume),
session_volume.AddressCast<void>());
if (FAILED(result)) {
throw FormatHResultError(
result,
"Unable to get client session volume");
}
result = session_volume->SetMasterVolume(volume_level, result = session_volume->SetMasterVolume(volume_level,
nullptr); nullptr);
......
/*
* Copyright 2020-2021 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_WASAPI_AUDIO_CLIENT_HXX
#define MPD_WASAPI_AUDIO_CLIENT_HXX
#include "win32/ComHeapPtr.hxx"
#include "win32/ComPtr.hxx"
#include "win32/HResult.hxx"
#include <audioclient.h>
inline UINT32
GetBufferSizeInFrames(IAudioClient &client)
{
UINT32 buffer_size_in_frames;
HRESULT result = client.GetBufferSize(&buffer_size_in_frames);
if (FAILED(result))
throw FormatHResultError(result,
"Unable to get audio client buffer size");
return buffer_size_in_frames;
}
inline UINT32
GetCurrentPaddingFrames(IAudioClient &client)
{
UINT32 padding_frames;
HRESULT result = client.GetCurrentPadding(&padding_frames);
if (FAILED(result))
throw FormatHResultError(result,
"Failed to get current padding");
return padding_frames;
}
inline ComHeapPtr<WAVEFORMATEX>
GetMixFormat(IAudioClient &client)
{
WAVEFORMATEX *f;
HRESULT result = client.GetMixFormat(&f);
if (FAILED(result))
throw FormatHResultError(result, "GetMixFormat failed");
return ComHeapPtr{f};
}
inline void
Start(IAudioClient &client)
{
HRESULT result = client.Start();
if (FAILED(result))
throw FormatHResultError(result, "Failed to start client");
}
inline void
Stop(IAudioClient &client)
{
HRESULT result = client.Stop();
if (FAILED(result))
throw FormatHResultError(result, "Failed to stop client");
}
inline void
SetEventHandle(IAudioClient &client, HANDLE h)
{
HRESULT result = client.SetEventHandle(h);
if (FAILED(result))
throw FormatHResultError(result, "Unable to set event handle");
}
template<typename T>
inline ComPtr<T>
GetService(IAudioClient &client)
{
T *p = nullptr;
HRESULT result = client.GetService(IID_PPV_ARGS(&p));
if (FAILED(result))
throw FormatHResultError(result, "Unable to get service");
return ComPtr{p};
}
#endif
/*
* Copyright 2020-2021 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_WASAPI_DEVICE_COLLECTION_HXX
#define MPD_WASAPI_DEVICE_COLLECTION_HXX
#include "win32/ComPtr.hxx"
#include "win32/HResult.hxx"
#include <mmdeviceapi.h>
inline ComPtr<IMMDevice>
GetDefaultAudioEndpoint(IMMDeviceEnumerator &e)
{
IMMDevice *device = nullptr;
HRESULT result = e.GetDefaultAudioEndpoint(eRender, eMultimedia,
&device);
if (FAILED(result))
throw FormatHResultError(result,
"Unable to get default device for multimedia");
return ComPtr{device};
}
inline ComPtr<IMMDeviceCollection>
EnumAudioEndpoints(IMMDeviceEnumerator &e)
{
IMMDeviceCollection *dc = nullptr;
HRESULT result = e.EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE,
&dc);
if (FAILED(result))
throw FormatHResultError(result, "Unable to enumerate devices");
return ComPtr{dc};
}
inline UINT
GetCount(IMMDeviceCollection &dc)
{
UINT count;
HRESULT result = dc.GetCount(&count);
if (FAILED(result))
throw FormatHResultError(result, "Collection->GetCount failed");
return count;
}
inline ComPtr<IMMDevice>
Item(IMMDeviceCollection &dc, UINT i)
{
IMMDevice *device = nullptr;
auto result = dc.Item(i, &device);
if (FAILED(result))
throw FormatHResultError(result, "Collection->Item failed");
return ComPtr{device};
}
inline DWORD
GetState(IMMDevice &device)
{
DWORD state;
HRESULT result = device.GetState(&state);;
if (FAILED(result))
throw FormatHResultError(result, "Unable to get device status");
return state;
}
template<typename T>
inline ComPtr<T>
Activate(IMMDevice &device)
{
T *p = nullptr;
HRESULT result = device.Activate(__uuidof(T), CLSCTX_ALL,
nullptr, (void **)&p);
if (FAILED(result))
throw FormatHResultError(result, "Unable to activate device");
return ComPtr{p};
}
inline ComPtr<IPropertyStore>
OpenPropertyStore(IMMDevice &device)
{
IPropertyStore *property_store = nullptr;
HRESULT result = device.OpenPropertyStore(STGM_READ, &property_store);
if (FAILED(result))
throw FormatHResultError(result,
"Device->OpenPropertyStore failed");
return ComPtr{property_store};
}
#endif
/*
* Copyright 2020-2021 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_WASAPI_PROPERTY_STORE_HXX
#define MPD_WASAPI_PROPERTY_STORE_HXX
#include "win32/PropVariant.hxx"
#include "util/AllocatedString.hxx"
#include "util/ScopeExit.hxx"
#include <propsys.h>
[[gnu::pure]]
inline AllocatedString
GetString(IPropertyStore &ps, REFPROPERTYKEY key) noexcept
{
PROPVARIANT pv;
PropVariantInit(&pv);
HRESULT result = ps.GetValue(key, &pv);
if (FAILED(result))
return nullptr;
AtScopeExit(&) { PropVariantClear(&pv); };
return ToString(pv);
}
#endif
...@@ -19,6 +19,9 @@ ...@@ -19,6 +19,9 @@
#include "WasapiOutputPlugin.hxx" #include "WasapiOutputPlugin.hxx"
#include "ForMixer.hxx" #include "ForMixer.hxx"
#include "AudioClient.hxx"
#include "Device.hxx"
#include "PropertyStore.hxx"
#include "output/OutputAPI.hxx" #include "output/OutputAPI.hxx"
#include "lib/icu/Win32.hxx" #include "lib/icu/Win32.hxx"
#include "mixer/MixerList.hxx" #include "mixer/MixerList.hxx"
...@@ -35,7 +38,6 @@ ...@@ -35,7 +38,6 @@
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"
#include "util/StringBuffer.hxx" #include "util/StringBuffer.hxx"
#include "win32/Com.hxx" #include "win32/Com.hxx"
#include "win32/ComHeapPtr.hxx"
#include "win32/ComPtr.hxx" #include "win32/ComPtr.hxx"
#include "win32/ComWorker.hxx" #include "win32/ComWorker.hxx"
#include "win32/HResult.hxx" #include "win32/HResult.hxx"
...@@ -254,7 +256,6 @@ private: ...@@ -254,7 +256,6 @@ private:
void EnumerateDevices(); void EnumerateDevices();
void GetDevice(unsigned int index); void GetDevice(unsigned int index);
unsigned int SearchDevice(std::string_view name); unsigned int SearchDevice(std::string_view name);
void GetDefaultDevice();
}; };
WasapiOutput &wasapi_output_downcast(AudioOutput &output) noexcept { WasapiOutput &wasapi_output_downcast(AudioOutput &output) noexcept {
...@@ -288,13 +289,8 @@ void WasapiOutputThread::Work() noexcept { ...@@ -288,13 +289,8 @@ void WasapiOutputThread::Work() noexcept {
UINT32 write_in_frames = buffer_size_in_frames; UINT32 write_in_frames = buffer_size_in_frames;
if (!is_exclusive) { if (!is_exclusive) {
UINT32 data_in_frames; UINT32 data_in_frames =
if (HRESULT result = GetCurrentPaddingFrames(*client);
client->GetCurrentPadding(&data_in_frames);
FAILED(result)) {
throw FormatHResultError(
result, "Failed to get current padding");
}
if (data_in_frames >= buffer_size_in_frames) { if (data_in_frames >= buffer_size_in_frames) {
continue; continue;
...@@ -366,20 +362,12 @@ void WasapiOutput::DoDisable() noexcept { ...@@ -366,20 +362,12 @@ void WasapiOutput::DoDisable() noexcept {
void WasapiOutput::DoOpen(AudioFormat &audio_format) { void WasapiOutput::DoOpen(AudioFormat &audio_format) {
client.reset(); client.reset();
DWORD state; if (GetState(*device) != DEVICE_STATE_ACTIVE) {
if (HRESULT result = device->GetState(&state); FAILED(result)) {
throw FormatHResultError(result, "Unable to get device status");
}
if (state != DEVICE_STATE_ACTIVE) {
device.reset(); device.reset();
OpenDevice(); OpenDevice();
} }
if (HRESULT result = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, client = Activate<IAudioClient>(*device);
client.AddressCast());
FAILED(result)) {
throw FormatHResultError(result, "Unable to activate audio client");
}
if (audio_format.channels > 8) { if (audio_format.channels > 8) {
audio_format.channels = 8; audio_format.channels = 8;
...@@ -453,13 +441,8 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) { ...@@ -453,13 +441,8 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) {
FAILED(result)) { FAILED(result)) {
if (result == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { if (result == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
// https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-initialize // https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-initialize
UINT32 buffer_size_in_frames = 0; UINT32 buffer_size_in_frames =
result = client->GetBufferSize(&buffer_size_in_frames); GetBufferSizeInFrames(*client);
if (FAILED(result)) {
throw FormatHResultError(
result,
"Unable to get audio client buffer size");
}
buffer_duration = buffer_duration =
std::ceil(double(buffer_size_in_frames * std::ceil(double(buffer_size_in_frames *
hundred_ns(s(1)).count()) / hundred_ns(s(1)).count()) /
...@@ -469,14 +452,7 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) { ...@@ -469,14 +452,7 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) {
"Aligned buffer duration: %I64u ns", "Aligned buffer duration: %I64u ns",
size_t(ns(hundred_ns(buffer_duration)).count())); size_t(ns(hundred_ns(buffer_duration)).count()));
client.reset(); client.reset();
result = device->Activate(__uuidof(IAudioClient), client = Activate<IAudioClient>(*device);
CLSCTX_ALL, nullptr,
client.AddressCast());
if (FAILED(result)) {
throw FormatHResultError(
result,
"Unable to activate audio client");
}
result = client->Initialize( result = client->Initialize(
AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
...@@ -501,27 +477,15 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) { ...@@ -501,27 +477,15 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) {
} }
} }
ComPtr<IAudioRenderClient> render_client; auto render_client = GetService<IAudioRenderClient>(*client);
if (HRESULT result = client->GetService(IID_PPV_ARGS(render_client.Address()));
FAILED(result)) {
throw FormatHResultError(result, "Unable to get new render client");
}
UINT32 buffer_size_in_frames; const UINT32 buffer_size_in_frames = GetBufferSizeInFrames(*client);
if (HRESULT result = client->GetBufferSize(&buffer_size_in_frames);
FAILED(result)) {
throw FormatHResultError(result,
"Unable to get audio client buffer size");
}
watermark = buffer_size_in_frames * 3 * FrameSize(); watermark = buffer_size_in_frames * 3 * FrameSize();
thread.emplace(client.get(), std::move(render_client), FrameSize(), thread.emplace(client.get(), std::move(render_client), FrameSize(),
buffer_size_in_frames, is_exclusive); buffer_size_in_frames, is_exclusive);
if (HRESULT result = client->SetEventHandle(thread->event.handle()); SetEventHandle(*client, thread->event.handle());
FAILED(result)) {
throw FormatHResultError(result, "Unable to set event handler");
}
thread->Start(); thread->Start();
} }
...@@ -531,9 +495,7 @@ void WasapiOutput::Close() noexcept { ...@@ -531,9 +495,7 @@ void WasapiOutput::Close() noexcept {
try { try {
COMWorker::Async([&]() { COMWorker::Async([&]() {
if (HRESULT result = client->Stop(); FAILED(result)) { Stop(*client);
throw FormatHResultError(result, "Failed to stop client");
}
}).get(); }).get();
thread->CheckException(); thread->CheckException();
} catch (std::exception &err) { } catch (std::exception &err) {
...@@ -595,10 +557,7 @@ size_t WasapiOutput::Play(const void *chunk, size_t size) { ...@@ -595,10 +557,7 @@ size_t WasapiOutput::Play(const void *chunk, size_t size) {
is_started = true; is_started = true;
thread->Play(); thread->Play();
COMWorker::Async([&]() { COMWorker::Async([&]() {
if (HRESULT result = client->Start(); FAILED(result)) { Start(*client);
throw FormatHResultError(
result, "Failed to start client");
}
}).wait(); }).wait();
} }
...@@ -660,7 +619,7 @@ void WasapiOutput::OpenDevice() { ...@@ -660,7 +619,7 @@ void WasapiOutput::OpenDevice() {
} }
if (!device) { if (!device) {
GetDefaultDevice(); device = GetDefaultAudioEndpoint(*enumerator);
} }
device_desc.clear(); device_desc.clear();
...@@ -735,13 +694,10 @@ void WasapiOutput::FindExclusiveFormatSupported(AudioFormat &audio_format) { ...@@ -735,13 +694,10 @@ void WasapiOutput::FindExclusiveFormatSupported(AudioFormat &audio_format) {
/// run inside COMWorkerThread /// run inside COMWorkerThread
void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) { void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) {
HRESULT result; HRESULT result;
ComHeapPtr<WAVEFORMATEX> mixer_format;
// In shared mode, different sample rate is always unsupported. // In shared mode, different sample rate is always unsupported.
result = client->GetMixFormat(mixer_format.Address()); auto mixer_format = GetMixFormat(*client);
if (FAILED(result)) {
throw FormatHResultError(result, "GetMixFormat failed");
}
audio_format.sample_rate = mixer_format->nSamplesPerSec; audio_format.sample_rate = mixer_format->nSamplesPerSec;
device_format = GetFormats(audio_format).front(); device_format = GetFormats(audio_format).front();
...@@ -846,66 +802,30 @@ void WasapiOutput::EnumerateDevices() { ...@@ -846,66 +802,30 @@ void WasapiOutput::EnumerateDevices() {
HRESULT result; HRESULT result;
ComPtr<IMMDeviceCollection> device_collection; const auto device_collection = EnumAudioEndpoints(*enumerator);
result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE,
device_collection.Address());
if (FAILED(result)) {
throw FormatHResultError(result, "Unable to enumerate devices");
}
UINT count; const UINT count = GetCount(*device_collection);
result = device_collection->GetCount(&count);
if (FAILED(result)) {
throw FormatHResultError(result, "Collection->GetCount failed");
}
device_desc.reserve(count); device_desc.reserve(count);
for (UINT i = 0; i < count; ++i) { for (UINT i = 0; i < count; ++i) {
ComPtr<IMMDevice> enumerated_device; const auto enumerated_device = Item(*device_collection, i);
result = device_collection->Item(i, enumerated_device.Address());
if (FAILED(result)) {
throw FormatHResultError(result, "Collection->Item failed");
}
ComPtr<IPropertyStore> property_store;
result = enumerated_device->OpenPropertyStore(STGM_READ,
property_store.Address());
if (FAILED(result)) {
throw FormatHResultError(result,
"Device->OpenPropertyStore failed");
}
PROPVARIANT var_name; const auto property_store =
PropVariantInit(&var_name); OpenPropertyStore(*enumerated_device);
AtScopeExit(&) { PropVariantClear(&var_name); };
result = property_store->GetValue(PKEY_Device_FriendlyName, &var_name); auto name = GetString(*property_store,
if (FAILED(result)) { PKEY_Device_FriendlyName);
throw FormatHResultError(result, if (name == nullptr)
"PropertyStore->GetValue failed"); continue;
}
device_desc.emplace_back( device_desc.emplace_back(i, std::move(name));
i, WideCharToMultiByte(CP_UTF8,
std::wstring_view(var_name.pwszVal)));
} }
} }
/// run inside COMWorkerThread /// run inside COMWorkerThread
void WasapiOutput::GetDevice(unsigned int index) { void WasapiOutput::GetDevice(unsigned int index) {
HRESULT result; const auto device_collection = EnumAudioEndpoints(*enumerator);
device = Item(*device_collection, index);
ComPtr<IMMDeviceCollection> device_collection;
result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE,
device_collection.Address());
if (FAILED(result)) {
throw FormatHResultError(result, "Unable to enumerate devices");
}
result = device_collection->Item(index, device.Address());
if (FAILED(result)) {
throw FormatHResultError(result, "Collection->Item failed");
}
} }
/// run inside COMWorkerThread /// run inside COMWorkerThread
...@@ -926,17 +846,6 @@ unsigned int WasapiOutput::SearchDevice(std::string_view name) { ...@@ -926,17 +846,6 @@ unsigned int WasapiOutput::SearchDevice(std::string_view name) {
return iter->first; return iter->first;
} }
/// run inside COMWorkerThread
void WasapiOutput::GetDefaultDevice() {
HRESULT result;
result = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia,
device.Address());
if (FAILED(result)) {
throw FormatHResultError(result,
"Unable to get default device for multimedia");
}
}
static bool wasapi_output_test_default_device() { return true; } static bool wasapi_output_test_default_device() { return true; }
const struct AudioOutputPlugin wasapi_output_plugin = { const struct AudioOutputPlugin wasapi_output_plugin = {
......
/*
* Copyright 2020-2021 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "PropVariant.hxx"
#include "lib/icu/Win32.hxx"
#include "util/AllocatedString.hxx"
#include "util/ScopeExit.hxx"
AllocatedString
ToString(const PROPVARIANT &pv) noexcept
{
// TODO: VT_BSTR
switch (pv.vt) {
case VT_LPSTR:
return AllocatedString{static_cast<const char *>(pv.pszVal)};
case VT_LPWSTR:
return WideCharToMultiByte(CP_UTF8, pv.pwszVal);
default:
return nullptr;
}
}
/*
* Copyright 2020-2021 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_WIN32_PROPVARIANT_HXX
#define MPD_WIN32_PROPVARIANT_HXX
#include <propidl.h>
class AllocatedString;
[[gnu::pure]]
AllocatedString
ToString(const PROPVARIANT &pv) noexcept;
#endif
...@@ -7,6 +7,7 @@ win32 = static_library( ...@@ -7,6 +7,7 @@ win32 = static_library(
'win32', 'win32',
'ComWorker.cxx', 'ComWorker.cxx',
'HResult.cxx', 'HResult.cxx',
'PropVariant.cxx',
'WinEvent.cxx', 'WinEvent.cxx',
include_directories: inc, include_directories: inc,
) )
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment