diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 43a1ce596..38a057e9e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -838,6 +838,7 @@ optional_source(WIN32 HEADERS core/windows7thumbbar.h ) +optional_source(MSVC SOURCES engine/uwpdevicefinder.cpp) optional_source(HAVE_SUBSONIC SOURCES @@ -1198,6 +1199,9 @@ endif() if(WIN32) target_link_libraries(strawberry_lib PRIVATE dsound dwmapi) + if(MSVC) + target_link_libraries(strawberry_lib PRIVATE WindowsApp) + endif() if(GETOPT_INCLUDE_DIRS) target_include_directories(strawberry_lib SYSTEM PRIVATE ${GETOPT_INCLUDE_DIRS}) endif() diff --git a/src/engine/AsyncOperations.h b/src/engine/AsyncOperations.h new file mode 100644 index 000000000..68d4442e5 --- /dev/null +++ b/src/engine/AsyncOperations.h @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#pragma once + +#include +#include +#include +#include + +// eg. TOperation = IAsyncOperationWithProgress +// eg. THandler = IAsyncOperationWithProgressCompletedHandler +template +class AsyncEventDelegate + : public Microsoft::WRL::RuntimeClass, THandler, Microsoft::WRL::FtmBase> { + public: + AsyncEventDelegate() + : _completedEvent(CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS)) { + Microsoft::WRL::ComPtr spThis(this); + auto lambda = ([this, spThis](_In_ HRESULT hr, _In_ TOperation *pOperation) { + SetEvent(_completedEvent.Get()); + }); + _func = std::move(lambda); + } + + STDMETHOD(Invoke) + ( + _In_ TOperation *pOperation, + _In_ AsyncStatus status) { + HRESULT hr = S_OK; + + // if we completed successfully, then there is no need for getting hresult + if (status != AsyncStatus::Completed) { + Microsoft::WRL::ComPtr spOperation(pOperation); + Microsoft::WRL::ComPtr spAsyncInfo; + if (SUCCEEDED(spOperation.As(&spAsyncInfo))) { + spAsyncInfo->get_ErrorCode(&hr); + } + } + + _func(hr, pOperation); + + return S_OK; + } + + STDMETHOD(SyncWait) + (_In_ TOperation *pOperation, _In_ DWORD dwMilliseconds) { + HRESULT hr = pOperation->put_Completed(this); + if (FAILED(hr)) { + return hr; + } + + DWORD dwWait = WaitForSingleObjectEx(_completedEvent.Get(), dwMilliseconds, TRUE); + if (WAIT_IO_COMPLETION == dwWait || WAIT_OBJECT_0 == dwWait) + return S_OK; + + return HRESULT_FROM_WIN32(GetLastError()); + } + + private: + std::function _func; + Microsoft::WRL::Wrappers::Event _completedEvent; +}; + +template +HRESULT SyncWait(_In_ TOperation *pOperation, _In_ DWORD dwMilliseconds) { + auto spCallback = Microsoft::WRL::Make>(); + + return spCallback->SyncWait(pOperation, dwMilliseconds); +} + +template +HRESULT SyncWait(_In_ ABI::Windows::Foundation::IAsyncAction *pOperation, _In_ DWORD dwMilliseconds = INFINITE) { + return SyncWait(pOperation, dwMilliseconds); +} + +template +HRESULT SyncWait(_In_ ABI::Windows::Foundation::IAsyncOperation *pOperation, _In_ DWORD dwMilliseconds = INFINITE) { + return SyncWait, ABI::Windows::Foundation::IAsyncOperationCompletedHandler>(pOperation, dwMilliseconds); +} + +template +HRESULT SyncWait(_In_ ABI::Windows::Foundation::IAsyncOperationWithProgress *pOperation, _In_ DWORD dwMilliseconds = INFINITE) { + return SyncWait, ABI::Windows::Foundation::IAsyncOperationWithProgressCompletedHandler>(pOperation, dwMilliseconds); +} diff --git a/src/engine/devicefinders.cpp b/src/engine/devicefinders.cpp index 2ee37a981..4f86c425b 100644 --- a/src/engine/devicefinders.cpp +++ b/src/engine/devicefinders.cpp @@ -43,7 +43,10 @@ #ifdef Q_OS_WIN32 # include "directsounddevicefinder.h" # include "mmdevicefinder.h" -#endif +# ifdef _MSC_VER +# include "uwpdevicefinder.h" +# endif // _MSC_VER +#endif // Q_OS_WIN32 DeviceFinders::DeviceFinders(QObject *parent) : QObject(parent) {} @@ -68,7 +71,10 @@ void DeviceFinders::Init() { #ifdef Q_OS_WIN32 device_finders.append(new DirectSoundDeviceFinder); device_finders.append(new MMDeviceFinder); -#endif +# ifdef _MSC_VER + device_finders.append(new UWPDeviceFinder); +# endif // _MSC_VER +#endif // Q_OS_WIN32 for (DeviceFinder *finder : device_finders) { if (!finder->Initialize()) { diff --git a/src/engine/uwpdevicefinder.cpp b/src/engine/uwpdevicefinder.cpp new file mode 100644 index 000000000..766455945 --- /dev/null +++ b/src/engine/uwpdevicefinder.cpp @@ -0,0 +1,150 @@ +/* + * Strawberry Music Player + * Copyright 2023, Jonas Kvinge + * + * Strawberry 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 3 of the License, or + * (at your option) any later version. + * + * Strawberry 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 Strawberry. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "AsyncOperations.h" + +#include "uwpdevicefinder.h" +#include "core/logging.h" + +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::Devices::Enumeration; + +UWPDeviceFinder::UWPDeviceFinder() : DeviceFinder("uwpdevice", { "wasapi2sink" }) {} + +namespace { + +static std::string wstring_to_stdstring(const std::wstring &wstr) { + + std::wstring_convert, wchar_t> converter; + + return converter.to_bytes(wstr.c_str()); + +} + +static std::string hstring_to_stdstring(HString *hstr) { + + if (!hstr) { + return std::string(); + } + + const wchar_t *raw_hstr = hstr->GetRawBuffer(nullptr); + if (!raw_hstr) { + return std::string(); + } + + return wstring_to_stdstring(std::wstring(raw_hstr)); + +} + +} // namespace + +QList UWPDeviceFinder::ListDevices() { + + ComPtr device_info_statics; + HRESULT hr = ABI::Windows::Foundation::GetActivationFactory(HStringReference(RuntimeClass_Windows_Devices_Enumeration_DeviceInformation).Get(), &device_info_statics); + if (FAILED(hr)) { + return QList(); + } + + ComPtr> async_op; + hr = device_info_statics->FindAllAsyncDeviceClass(DeviceClass::DeviceClass_AudioRender, &async_op); + device_info_statics.Reset(); + if (FAILED(hr)) { + return QList(); + } + + hr = SyncWait(async_op.Get()); + if (FAILED(hr)) { + return QList(); + } + + ComPtr> device_list; + hr = async_op->GetResults(&device_list); + async_op.Reset(); + if (FAILED(hr)) { + return QList(); + } + + unsigned int count = 0; + hr = device_list->get_Size(&count); + if (FAILED(hr)) { + return QList(); + } + + QList devices; + + { + Device default_device; + default_device.description = "Default device"; + default_device.iconname = GuessIconName(default_device.description); + devices.append(default_device); + } + + for (unsigned int i = 0; i < count; i++) { + + ComPtr device_info; + hr = device_list->GetAt(i, &device_info); + if (FAILED(hr)) { + continue; + } + + boolean enabled; + hr = device_info->get_IsEnabled(&enabled); + if (FAILED(hr) || !enabled) { + continue; + } + + HString id; + hr = device_info->get_Id(id.GetAddressOf()); + if (FAILED(hr) || !id.IsValid()) { + continue; + } + + HString name; + hr = device_info->get_Name(name.GetAddressOf()); + if (FAILED(hr) || !name.IsValid()) { + continue; + } + + Device device; + device.value = QString::fromStdString(hstring_to_stdstring(&id)); + device.description = QString::fromStdString(hstring_to_stdstring(&name)); + device.iconname = GuessIconName(device.description); + devices.append(device); + } + + return devices; + +} diff --git a/src/engine/uwpdevicefinder.h b/src/engine/uwpdevicefinder.h new file mode 100644 index 000000000..1732d74b3 --- /dev/null +++ b/src/engine/uwpdevicefinder.h @@ -0,0 +1,35 @@ +/* + * Strawberry Music Player + * Copyright 2023, Jonas Kvinge + * + * Strawberry 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 3 of the License, or + * (at your option) any later version. + * + * Strawberry 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 Strawberry. If not, see . + * + */ + +#ifndef UWPDEVICEFINDER_H +#define UWPDEVICEFINDER_H + +#include "config.h" + +#include "devicefinder.h" + +class UWPDeviceFinder : public DeviceFinder { + public: + explicit UWPDeviceFinder(); + + virtual bool Initialize() { return true; } + virtual QList ListDevices(); +}; + +#endif // UWPDEVICEFINDER_H