Merge branch 'v0.22.x'
This commit is contained in:
commit
ef1acb4e2f
6
NEWS
6
NEWS
@ -7,8 +7,14 @@ ver 0.23 (not yet released)
|
|||||||
- snapcast: new plugin
|
- snapcast: new plugin
|
||||||
|
|
||||||
ver 0.22.7 (not yet released)
|
ver 0.22.7 (not yet released)
|
||||||
|
* protocol
|
||||||
|
- don't use glibc extension to parse time stamps
|
||||||
* decoder
|
* decoder
|
||||||
- ffmpeg: fix build problem with FFmpeg 3.4
|
- ffmpeg: fix build problem with FFmpeg 3.4
|
||||||
|
* storage
|
||||||
|
- curl: don't use glibc extension
|
||||||
|
* output
|
||||||
|
- wasapi: add algorithm for finding usable audio format
|
||||||
|
|
||||||
ver 0.22.6 (2021/02/16)
|
ver 0.22.6 (2021/02/16)
|
||||||
* fix missing tags on songs in queue
|
* fix missing tags on songs in queue
|
||||||
|
14
doc/user.rst
14
doc/user.rst
@ -141,6 +141,15 @@ Basically, there are two ways to compile :program:`MPD` for Windows:
|
|||||||
|
|
||||||
This section is about the latter.
|
This section is about the latter.
|
||||||
|
|
||||||
|
You need:
|
||||||
|
|
||||||
|
* `mingw-w64 <http://mingw-w64.org/doku.php>`__
|
||||||
|
* `Meson 0.49.0 <http://mesonbuild.com/>`__ and `Ninja
|
||||||
|
<https://ninja-build.org/>`__
|
||||||
|
* cmake
|
||||||
|
* pkg-config
|
||||||
|
* quilt
|
||||||
|
|
||||||
Just like with the native build, unpack the :program:`MPD` source
|
Just like with the native build, unpack the :program:`MPD` source
|
||||||
tarball and change into the directory. Then, instead of
|
tarball and change into the directory. Then, instead of
|
||||||
:program:`meson`, type:
|
:program:`meson`, type:
|
||||||
@ -168,6 +177,11 @@ You need:
|
|||||||
|
|
||||||
* Android SDK
|
* Android SDK
|
||||||
* `Android NDK r22 <https://developer.android.com/ndk/downloads>`_
|
* `Android NDK r22 <https://developer.android.com/ndk/downloads>`_
|
||||||
|
* `Meson 0.49.0 <http://mesonbuild.com/>`__ and `Ninja
|
||||||
|
<https://ninja-build.org/>`__
|
||||||
|
* cmake
|
||||||
|
* pkg-config
|
||||||
|
* quilt
|
||||||
|
|
||||||
Just like with the native build, unpack the :program:`MPD` source
|
Just like with the native build, unpack the :program:`MPD` source
|
||||||
tarball and change into the directory. Then, instead of
|
tarball and change into the directory. Then, instead of
|
||||||
|
@ -261,7 +261,6 @@ sources = [
|
|||||||
'src/LogInit.cxx',
|
'src/LogInit.cxx',
|
||||||
'src/ls.cxx',
|
'src/ls.cxx',
|
||||||
'src/Instance.cxx',
|
'src/Instance.cxx',
|
||||||
'src/win32/Win32Main.cxx',
|
|
||||||
'src/MusicBuffer.cxx',
|
'src/MusicBuffer.cxx',
|
||||||
'src/MusicPipe.cxx',
|
'src/MusicPipe.cxx',
|
||||||
'src/MusicChunk.cxx',
|
'src/MusicChunk.cxx',
|
||||||
@ -309,6 +308,13 @@ sources = [
|
|||||||
'src/PlaylistFile.cxx',
|
'src/PlaylistFile.cxx',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if is_windows
|
||||||
|
sources += [
|
||||||
|
'src/win32/Win32Main.cxx',
|
||||||
|
'src/win32/ComWorker.cxx',
|
||||||
|
]
|
||||||
|
endif
|
||||||
|
|
||||||
if not is_android
|
if not is_android
|
||||||
sources += [
|
sources += [
|
||||||
'src/CommandLine.cxx',
|
'src/CommandLine.cxx',
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
#include "mixer/MixerInternal.hxx"
|
#include "mixer/MixerInternal.hxx"
|
||||||
#include "output/plugins/WasapiOutputPlugin.hxx"
|
#include "output/plugins/WasapiOutputPlugin.hxx"
|
||||||
#include "win32/Com.hxx"
|
#include "win32/ComWorker.hxx"
|
||||||
#include "win32/HResult.hxx"
|
#include "win32/HResult.hxx"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@ -28,92 +28,103 @@
|
|||||||
|
|
||||||
class WasapiMixer final : public Mixer {
|
class WasapiMixer final : public Mixer {
|
||||||
WasapiOutput &output;
|
WasapiOutput &output;
|
||||||
std::optional<COM> com;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WasapiMixer(WasapiOutput &_output, MixerListener &_listener)
|
WasapiMixer(WasapiOutput &_output, MixerListener &_listener)
|
||||||
: Mixer(wasapi_mixer_plugin, _listener), output(_output) {}
|
: Mixer(wasapi_mixer_plugin, _listener), output(_output) {}
|
||||||
|
|
||||||
void Open() override { com.emplace(); }
|
void Open() override {}
|
||||||
|
|
||||||
void Close() noexcept override { com.reset(); }
|
void Close() noexcept override {}
|
||||||
|
|
||||||
int GetVolume() override {
|
int GetVolume() override {
|
||||||
HRESULT result;
|
auto future = COMWorker::Async([&]() -> int {
|
||||||
float volume_level;
|
HRESULT result;
|
||||||
|
float volume_level;
|
||||||
|
|
||||||
if (wasapi_is_exclusive(output)) {
|
if (wasapi_is_exclusive(output)) {
|
||||||
ComPtr<IAudioEndpointVolume> endpoint_volume;
|
ComPtr<IAudioEndpointVolume> endpoint_volume;
|
||||||
result = wasapi_output_get_device(output)->Activate(
|
result = wasapi_output_get_device(output)->Activate(
|
||||||
__uuidof(IAudioEndpointVolume), CLSCTX_ALL, nullptr,
|
__uuidof(IAudioEndpointVolume), CLSCTX_ALL,
|
||||||
endpoint_volume.AddressCast());
|
nullptr, endpoint_volume.AddressCast());
|
||||||
if (FAILED(result)) {
|
if (FAILED(result)) {
|
||||||
throw FormatHResultError(
|
throw FormatHResultError(result,
|
||||||
result, "Unable to get device endpoint volume");
|
"Unable to get device "
|
||||||
|
"endpoint volume");
|
||||||
|
}
|
||||||
|
|
||||||
|
result = endpoint_volume->GetMasterVolumeLevelScalar(
|
||||||
|
&volume_level);
|
||||||
|
if (FAILED(result)) {
|
||||||
|
throw FormatHResultError(result,
|
||||||
|
"Unable to get master "
|
||||||
|
"volume level");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ComPtr<ISimpleAudioVolume> session_volume;
|
||||||
|
result = wasapi_output_get_client(output)->GetService(
|
||||||
|
__uuidof(ISimpleAudioVolume),
|
||||||
|
session_volume.AddressCast<void>());
|
||||||
|
if (FAILED(result)) {
|
||||||
|
throw FormatHResultError(result,
|
||||||
|
"Unable to get client "
|
||||||
|
"session volume");
|
||||||
|
}
|
||||||
|
|
||||||
|
result = session_volume->GetMasterVolume(&volume_level);
|
||||||
|
if (FAILED(result)) {
|
||||||
|
throw FormatHResultError(
|
||||||
|
result, "Unable to get master volume");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result = endpoint_volume->GetMasterVolumeLevelScalar(
|
return std::lround(volume_level * 100.0f);
|
||||||
&volume_level);
|
});
|
||||||
if (FAILED(result)) {
|
return future.get();
|
||||||
throw FormatHResultError(
|
|
||||||
result, "Unable to get master volume level");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ComPtr<ISimpleAudioVolume> session_volume;
|
|
||||||
result = wasapi_output_get_client(output)->GetService(
|
|
||||||
__uuidof(ISimpleAudioVolume),
|
|
||||||
session_volume.AddressCast<void>());
|
|
||||||
if (FAILED(result)) {
|
|
||||||
throw FormatHResultError(
|
|
||||||
result, "Unable to get client session volume");
|
|
||||||
}
|
|
||||||
|
|
||||||
result = session_volume->GetMasterVolume(&volume_level);
|
|
||||||
if (FAILED(result)) {
|
|
||||||
throw FormatHResultError(result,
|
|
||||||
"Unable to get master volume");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::lround(volume_level * 100.0f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetVolume(unsigned volume) override {
|
void SetVolume(unsigned volume) override {
|
||||||
HRESULT result;
|
COMWorker::Async([&]() {
|
||||||
const float volume_level = volume / 100.0f;
|
HRESULT result;
|
||||||
|
const float volume_level = volume / 100.0f;
|
||||||
|
|
||||||
if (wasapi_is_exclusive(output)) {
|
if (wasapi_is_exclusive(output)) {
|
||||||
ComPtr<IAudioEndpointVolume> endpoint_volume;
|
ComPtr<IAudioEndpointVolume> endpoint_volume;
|
||||||
result = wasapi_output_get_device(output)->Activate(
|
result = wasapi_output_get_device(output)->Activate(
|
||||||
__uuidof(IAudioEndpointVolume), CLSCTX_ALL, nullptr,
|
__uuidof(IAudioEndpointVolume), CLSCTX_ALL,
|
||||||
endpoint_volume.AddressCast());
|
nullptr, endpoint_volume.AddressCast());
|
||||||
if (FAILED(result)) {
|
if (FAILED(result)) {
|
||||||
throw FormatHResultError(
|
throw FormatHResultError(
|
||||||
result, "Unable to get device endpoint volume");
|
result,
|
||||||
}
|
"Unable to get device endpoint volume");
|
||||||
|
}
|
||||||
|
|
||||||
result = endpoint_volume->SetMasterVolumeLevelScalar(volume_level,
|
result = endpoint_volume->SetMasterVolumeLevelScalar(
|
||||||
nullptr);
|
volume_level, nullptr);
|
||||||
if (FAILED(result)) {
|
if (FAILED(result)) {
|
||||||
throw FormatHResultError(
|
throw FormatHResultError(
|
||||||
result, "Unable to set master volume level");
|
result,
|
||||||
}
|
"Unable to set master volume level");
|
||||||
} else {
|
}
|
||||||
ComPtr<ISimpleAudioVolume> session_volume;
|
} else {
|
||||||
result = wasapi_output_get_client(output)->GetService(
|
ComPtr<ISimpleAudioVolume> session_volume;
|
||||||
__uuidof(ISimpleAudioVolume),
|
result = wasapi_output_get_client(output)->GetService(
|
||||||
session_volume.AddressCast<void>());
|
__uuidof(ISimpleAudioVolume),
|
||||||
if (FAILED(result)) {
|
session_volume.AddressCast<void>());
|
||||||
throw FormatHResultError(
|
if (FAILED(result)) {
|
||||||
result, "Unable to get client session volume");
|
throw FormatHResultError(
|
||||||
}
|
result,
|
||||||
|
"Unable to get client session volume");
|
||||||
|
}
|
||||||
|
|
||||||
result = session_volume->SetMasterVolume(volume_level, nullptr);
|
result = session_volume->SetMasterVolume(volume_level,
|
||||||
if (FAILED(result)) {
|
nullptr);
|
||||||
throw FormatHResultError(result,
|
if (FAILED(result)) {
|
||||||
"Unable to set master volume");
|
throw FormatHResultError(
|
||||||
|
result, "Unable to set master volume");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}).get();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -193,7 +193,7 @@ ParseTimeStamp(const char *s)
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// TODO: make this more robust
|
// TODO: make this more robust
|
||||||
return ParseTimePoint(s, "%a, %d %b %Y %T %Z");
|
return ParseTimePoint(s, "%a, %d %b %Y %T");
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
return std::chrono::system_clock::time_point::min();
|
return std::chrono::system_clock::time_point::min();
|
||||||
}
|
}
|
||||||
|
43
src/thread/Future.hxx
Normal file
43
src/thread/Future.hxx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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 THREAD_FUTURE_HXX
|
||||||
|
#define THREAD_FUTURE_HXX
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
#include "WindowsFuture.hxx"
|
||||||
|
|
||||||
|
template <typename R>
|
||||||
|
using Future = WinFuture<R>;
|
||||||
|
template <typename R>
|
||||||
|
using Promise = WinPromise<R>;
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include <future>
|
||||||
|
|
||||||
|
template <typename R>
|
||||||
|
using Future = std::future<R>;
|
||||||
|
template <typename R>
|
||||||
|
using Promise = std::promise<R>;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
394
src/thread/WindowsFuture.hxx
Normal file
394
src/thread/WindowsFuture.hxx
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
/*
|
||||||
|
* 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 THREAD_WINDOWS_FUTURE_HXX
|
||||||
|
#define THREAD_WINDOWS_FUTURE_HXX
|
||||||
|
|
||||||
|
#include "CriticalSection.hxx"
|
||||||
|
#include "WindowsCond.hxx"
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
enum class WinFutureErrc : int {
|
||||||
|
future_already_retrieved = 1,
|
||||||
|
promise_already_satisfied,
|
||||||
|
no_state,
|
||||||
|
broken_promise,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class WinFutureStatus { ready, timeout, deferred };
|
||||||
|
|
||||||
|
static inline const std::error_category &win_future_category() noexcept;
|
||||||
|
class WinFutureCategory : public std::error_category {
|
||||||
|
public:
|
||||||
|
const char *name() const noexcept override { return "win_future"; }
|
||||||
|
std::string message(int Errcode) const override {
|
||||||
|
using namespace std::literals;
|
||||||
|
switch (static_cast<WinFutureErrc>(Errcode)) {
|
||||||
|
case WinFutureErrc::broken_promise:
|
||||||
|
return "Broken promise"s;
|
||||||
|
case WinFutureErrc::future_already_retrieved:
|
||||||
|
return "Future already retrieved"s;
|
||||||
|
case WinFutureErrc::promise_already_satisfied:
|
||||||
|
return "Promise already satisfied"s;
|
||||||
|
case WinFutureErrc::no_state:
|
||||||
|
return "No associated state"s;
|
||||||
|
default:
|
||||||
|
return "Unknown error"s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::error_condition default_error_condition(int code) const noexcept override {
|
||||||
|
return std::error_condition(code, win_future_category());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
static inline const std::error_category &win_future_category() noexcept {
|
||||||
|
static const WinFutureCategory win_future_category_instance{};
|
||||||
|
return win_future_category_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WinFutureError : public std::logic_error {
|
||||||
|
public:
|
||||||
|
WinFutureError(WinFutureErrc errcode)
|
||||||
|
: WinFutureError(
|
||||||
|
std::error_code(static_cast<int>(errcode), win_future_category())) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit WinFutureError(std::error_code errcode)
|
||||||
|
: std::logic_error("WinFutureError: " + errcode.message()), code(errcode) {}
|
||||||
|
std::error_code code;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class WinFutureState {
|
||||||
|
private:
|
||||||
|
mutable CriticalSection mutex;
|
||||||
|
WindowsCond condition;
|
||||||
|
std::variant<std::monostate, T, std::exception_ptr> result;
|
||||||
|
bool retrieved = false;
|
||||||
|
bool ready = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool is_ready() const noexcept {
|
||||||
|
std::unique_lock<CriticalSection> lock(mutex);
|
||||||
|
return ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool already_retrieved() const noexcept {
|
||||||
|
std::unique_lock<CriticalSection> lock(mutex);
|
||||||
|
return retrieved;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wait() {
|
||||||
|
std::unique_lock<CriticalSection> lock(mutex);
|
||||||
|
condition.wait(lock, [this]() { return ready; });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Rep, class Period>
|
||||||
|
WinFutureStatus
|
||||||
|
wait_for(const std::chrono::duration<Rep, Period> &timeout_duration) const {
|
||||||
|
std::unique_lock<CriticalSection> lock(mutex);
|
||||||
|
// deferred function not support yet
|
||||||
|
if (condition.wait_for(lock, timeout_duration,
|
||||||
|
[this]() { return ready; })) {
|
||||||
|
return WinFutureStatus::ready;
|
||||||
|
}
|
||||||
|
return WinFutureStatus::timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual T &get_value() {
|
||||||
|
std::unique_lock<CriticalSection> lock(mutex);
|
||||||
|
if (retrieved) {
|
||||||
|
throw WinFutureError(WinFutureErrc::future_already_retrieved);
|
||||||
|
}
|
||||||
|
if (auto eptr = std::get_if<std::exception_ptr>(&result)) {
|
||||||
|
std::rethrow_exception(*eptr);
|
||||||
|
}
|
||||||
|
retrieved = true;
|
||||||
|
condition.wait(lock, [this]() { return ready; });
|
||||||
|
if (auto eptr = std::get_if<std::exception_ptr>(&result)) {
|
||||||
|
std::rethrow_exception(*eptr);
|
||||||
|
}
|
||||||
|
return *std::get_if<T>(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_value(const T &value) {
|
||||||
|
std::unique_lock<CriticalSection> lock(mutex);
|
||||||
|
if (!std::holds_alternative<std::monostate>(&result)) {
|
||||||
|
throw WinFutureError(WinFutureErrc::promise_already_satisfied);
|
||||||
|
}
|
||||||
|
result.template emplace<T>(value);
|
||||||
|
ready = true;
|
||||||
|
condition.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_value(T &&value) {
|
||||||
|
std::unique_lock<CriticalSection> lock(mutex);
|
||||||
|
if (!std::holds_alternative<std::monostate>(result)) {
|
||||||
|
throw WinFutureError(WinFutureErrc::promise_already_satisfied);
|
||||||
|
}
|
||||||
|
result.template emplace<T>(std::move(value));
|
||||||
|
ready = true;
|
||||||
|
condition.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_exception(std::exception_ptr eptr) {
|
||||||
|
std::unique_lock<CriticalSection> lock(mutex);
|
||||||
|
if (!std::holds_alternative<std::monostate>(result)) {
|
||||||
|
throw WinFutureError(WinFutureErrc::promise_already_satisfied);
|
||||||
|
}
|
||||||
|
result.template emplace<std::exception_ptr>(eptr);
|
||||||
|
ready = true;
|
||||||
|
condition.notify_all();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class WinFutureStateManager {
|
||||||
|
public:
|
||||||
|
WinFutureStateManager() = default;
|
||||||
|
WinFutureStateManager(std::shared_ptr<WinFutureState<T>> new_state)
|
||||||
|
: state(std::move(new_state)) {}
|
||||||
|
WinFutureStateManager(const WinFutureStateManager &) = default;
|
||||||
|
WinFutureStateManager &operator=(const WinFutureStateManager &) = default;
|
||||||
|
WinFutureStateManager(WinFutureStateManager &&) = default;
|
||||||
|
WinFutureStateManager &operator=(WinFutureStateManager &&) = default;
|
||||||
|
|
||||||
|
[[nodiscard]] bool valid() const noexcept { return static_cast<bool>(state); }
|
||||||
|
|
||||||
|
void wait() const {
|
||||||
|
if (!valid()) {
|
||||||
|
throw WinFutureError(WinFutureErrc::no_state);
|
||||||
|
}
|
||||||
|
state->wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Rep, class Period>
|
||||||
|
WinFutureStatus
|
||||||
|
wait_for(const std::chrono::duration<Rep, Period> &timeout_duration) const {
|
||||||
|
if (!valid()) {
|
||||||
|
throw WinFutureError(WinFutureErrc::no_state);
|
||||||
|
}
|
||||||
|
return state->wait_for(timeout_duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
T &get_value() const {
|
||||||
|
if (!valid()) {
|
||||||
|
throw WinFutureError(WinFutureErrc::no_state);
|
||||||
|
}
|
||||||
|
return state->get_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_value(const T &value) {
|
||||||
|
if (!valid()) {
|
||||||
|
throw WinFutureError(WinFutureErrc::no_state);
|
||||||
|
}
|
||||||
|
state->set_value(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_value(T &&value) {
|
||||||
|
if (!valid()) {
|
||||||
|
throw WinFutureError(WinFutureErrc::no_state);
|
||||||
|
}
|
||||||
|
state->set_value(std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_exception(std::exception_ptr eptr) {
|
||||||
|
if (!valid()) {
|
||||||
|
throw WinFutureError(WinFutureErrc::no_state);
|
||||||
|
}
|
||||||
|
state->set_exception(eptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<WinFutureState<T>> state;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class WinFuture : public WinFutureStateManager<T> {
|
||||||
|
using Base = WinFutureStateManager<T>;
|
||||||
|
static_assert(!std::is_array_v<T> && std::is_object_v<T> &&
|
||||||
|
std::is_destructible_v<T>,
|
||||||
|
"T in future<T> must meet the Cpp17Destructible requirements "
|
||||||
|
"(N4878 [futures.unique.future]/4).");
|
||||||
|
|
||||||
|
public:
|
||||||
|
WinFuture() noexcept = default;
|
||||||
|
WinFuture(WinFuture &&) noexcept = default;
|
||||||
|
WinFuture &operator=(WinFuture &&) noexcept = default;
|
||||||
|
WinFuture(const WinFuture &) noexcept = delete;
|
||||||
|
WinFuture &operator=(const WinFuture &) noexcept = delete;
|
||||||
|
|
||||||
|
WinFuture(const Base &base, std::monostate) : Base(base) {}
|
||||||
|
~WinFuture() noexcept = default;
|
||||||
|
T get() {
|
||||||
|
WinFuture local(std::move(*this));
|
||||||
|
return std::move(local.get_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
using Base::get_value;
|
||||||
|
using Base::set_exception;
|
||||||
|
using Base::set_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class WinFuture<T &> : public WinFutureStateManager<T *> {
|
||||||
|
using Base = WinFutureStateManager<T *>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
WinFuture() noexcept = default;
|
||||||
|
WinFuture(WinFuture &&) noexcept = default;
|
||||||
|
WinFuture &operator=(WinFuture &&) noexcept = default;
|
||||||
|
WinFuture(const WinFuture &) noexcept = delete;
|
||||||
|
WinFuture &operator=(const WinFuture &) noexcept = delete;
|
||||||
|
|
||||||
|
WinFuture(const Base &base, std::monostate) : Base(base) {}
|
||||||
|
~WinFuture() noexcept = default;
|
||||||
|
T &get() {
|
||||||
|
WinFuture local(std::move(*this));
|
||||||
|
return *local.get_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
using Base::get_value;
|
||||||
|
using Base::set_exception;
|
||||||
|
using Base::set_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
class WinFuture<void> : public WinFutureStateManager<int> {
|
||||||
|
using Base = WinFutureStateManager<int>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
WinFuture() noexcept = default;
|
||||||
|
WinFuture(WinFuture &&) noexcept = default;
|
||||||
|
WinFuture &operator=(WinFuture &&) noexcept = default;
|
||||||
|
WinFuture(const WinFuture &) noexcept = delete;
|
||||||
|
WinFuture &operator=(const WinFuture &) noexcept = delete;
|
||||||
|
|
||||||
|
WinFuture(const Base &base, std::monostate) : Base(base) {}
|
||||||
|
~WinFuture() noexcept = default;
|
||||||
|
void get() {
|
||||||
|
WinFuture local(std::move(*this));
|
||||||
|
local.get_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
using Base::get_value;
|
||||||
|
using Base::set_exception;
|
||||||
|
using Base::set_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class WinPromiseBase {
|
||||||
|
public:
|
||||||
|
WinPromiseBase(std::shared_ptr<WinFutureState<T>> new_state)
|
||||||
|
: state(std::move(new_state)) {}
|
||||||
|
WinPromiseBase(WinPromiseBase &&) = default;
|
||||||
|
WinPromiseBase &operator=(WinPromiseBase &&) = default;
|
||||||
|
WinPromiseBase(const WinPromiseBase &) = delete;
|
||||||
|
WinPromiseBase &operator=(const WinPromiseBase &) = delete;
|
||||||
|
|
||||||
|
WinFutureStateManager<T> &get_state_for_set() {
|
||||||
|
if (!state.valid()) {
|
||||||
|
throw WinFutureError(WinFutureErrc::no_state);
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
WinFutureStateManager<T> &get_state_for_future() {
|
||||||
|
if (!state.valid()) {
|
||||||
|
throw WinFutureError(WinFutureErrc::no_state);
|
||||||
|
}
|
||||||
|
if (future_retrieved) {
|
||||||
|
throw WinFutureError(WinFutureErrc::future_already_retrieved);
|
||||||
|
}
|
||||||
|
future_retrieved = true;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
WinFutureStateManager<T> state;
|
||||||
|
bool future_retrieved = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class WinPromise {
|
||||||
|
public:
|
||||||
|
WinPromise() : base(std::make_shared<WinFutureState<T>>()) {}
|
||||||
|
WinPromise(WinPromise &&) = default;
|
||||||
|
WinPromise(const WinPromise &) = delete;
|
||||||
|
~WinPromise() noexcept {}
|
||||||
|
[[nodiscard]] WinFuture<T> get_future() {
|
||||||
|
return WinFuture<T>(base.get_state_for_future(), std::monostate());
|
||||||
|
}
|
||||||
|
void set_value(const T &value) { base.get_state_for_set().set_value(value); }
|
||||||
|
void set_value(T &&value) {
|
||||||
|
base.get_state_for_set().set_value(std::forward<T>(value));
|
||||||
|
}
|
||||||
|
void set_exception(std::exception_ptr eptr) {
|
||||||
|
base.get_state_for_set().set_exception(eptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
WinPromiseBase<T> base;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class WinPromise<T &> {
|
||||||
|
public:
|
||||||
|
WinPromise() : base(std::make_shared<WinFutureState<T *>>()) {}
|
||||||
|
WinPromise(WinPromise &&) = default;
|
||||||
|
WinPromise(const WinPromise &) = delete;
|
||||||
|
~WinPromise() noexcept {}
|
||||||
|
[[nodiscard]] WinFuture<T &> get_future() {
|
||||||
|
return WinFuture<T>(base.get_state_for_future(), std::monostate());
|
||||||
|
}
|
||||||
|
void set_value(T &value) {
|
||||||
|
base.get_state_for_set().set_value(std::addressof(value));
|
||||||
|
}
|
||||||
|
void set_exception(std::exception_ptr eptr) {
|
||||||
|
base.get_state_for_set().set_exception(eptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
WinPromiseBase<T *> base;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
class WinPromise<void> {
|
||||||
|
public:
|
||||||
|
WinPromise() : base(std::make_shared<WinFutureState<int>>()) {}
|
||||||
|
WinPromise(WinPromise &&) = default;
|
||||||
|
WinPromise(const WinPromise &) = delete;
|
||||||
|
~WinPromise() noexcept {}
|
||||||
|
[[nodiscard]] WinFuture<void> get_future() {
|
||||||
|
return WinFuture<void>(base.get_state_for_future(), std::monostate());
|
||||||
|
}
|
||||||
|
void set_value() { base.get_state_for_set().set_value(0); }
|
||||||
|
void set_exception(std::exception_ptr eptr) {
|
||||||
|
base.get_state_for_set().set_exception(eptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
WinPromiseBase<int> base;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -202,7 +202,7 @@ ParseISO8601(const char *s)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* parse the date */
|
/* parse the date */
|
||||||
const char *end = strptime(s, "%F", &tm);
|
const char *end = strptime(s, "%Y-%m-%d", &tm);
|
||||||
if (end == nullptr) {
|
if (end == nullptr) {
|
||||||
/* try without field separators */
|
/* try without field separators */
|
||||||
end = strptime(s, "%Y%m%d", &tm);
|
end = strptime(s, "%Y%m%d", &tm);
|
||||||
|
@ -29,9 +29,19 @@
|
|||||||
class COM {
|
class COM {
|
||||||
public:
|
public:
|
||||||
COM() {
|
COM() {
|
||||||
HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
if (HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||||
if (FAILED(result)) {
|
FAILED(result)) {
|
||||||
throw FormatHResultError(result, "Unable to initialize COM");
|
throw FormatHResultError(
|
||||||
|
result,
|
||||||
|
"Unable to initialize COM with COINIT_MULTITHREADED");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
COM(bool) {
|
||||||
|
if (HRESULT result = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||||
|
FAILED(result)) {
|
||||||
|
throw FormatHResultError(
|
||||||
|
result,
|
||||||
|
"Unable to initialize COM with COINIT_APARTMENTTHREADED");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
~COM() noexcept { CoUninitialize(); }
|
~COM() noexcept { CoUninitialize(); }
|
||||||
|
49
src/win32/ComWorker.cxx
Normal file
49
src/win32/ComWorker.cxx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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 "ComWorker.hxx"
|
||||||
|
#include "Log.hxx"
|
||||||
|
#include "thread/Name.hxx"
|
||||||
|
#include "util/Domain.hxx"
|
||||||
|
#include "win32/Com.hxx"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
static constexpr Domain com_worker_domain("com_worker");
|
||||||
|
}
|
||||||
|
|
||||||
|
Mutex COMWorker::mutex;
|
||||||
|
unsigned int COMWorker::reference_count = 0;
|
||||||
|
std::optional<COMWorker::COMWorkerThread> COMWorker::thread;
|
||||||
|
|
||||||
|
void COMWorker::COMWorkerThread::Work() noexcept {
|
||||||
|
FormatDebug(com_worker_domain, "Working thread started");
|
||||||
|
SetThreadName("COM Worker");
|
||||||
|
COM com{true};
|
||||||
|
while (true) {
|
||||||
|
if (!running_flag.test_and_set()) {
|
||||||
|
FormatDebug(com_worker_domain, "Working thread ended");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (!spsc_buffer.empty()) {
|
||||||
|
std::function<void()> function;
|
||||||
|
spsc_buffer.pop(function);
|
||||||
|
function();
|
||||||
|
}
|
||||||
|
event.Wait(200);
|
||||||
|
}
|
||||||
|
}
|
110
src/win32/ComWorker.hxx
Normal file
110
src/win32/ComWorker.hxx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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_COM_WORKER_HXX
|
||||||
|
#define MPD_WIN32_COM_WORKER_HXX
|
||||||
|
|
||||||
|
#include <boost/lockfree/spsc_queue.hpp>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "thread/Future.hxx"
|
||||||
|
#include "thread/Mutex.hxx"
|
||||||
|
#include "thread/Thread.hxx"
|
||||||
|
#include "win32/WinEvent.hxx"
|
||||||
|
#include <objbase.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
// Worker thread for all COM operation
|
||||||
|
class COMWorker {
|
||||||
|
private:
|
||||||
|
class COMWorkerThread : public Thread {
|
||||||
|
public:
|
||||||
|
COMWorkerThread() : Thread{BIND_THIS_METHOD(Work)} {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class COMWorker;
|
||||||
|
void Work() noexcept;
|
||||||
|
void Finish() noexcept {
|
||||||
|
running_flag.clear();
|
||||||
|
event.Set();
|
||||||
|
}
|
||||||
|
void Push(const std::function<void()> &function) {
|
||||||
|
spsc_buffer.push(function);
|
||||||
|
event.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::lockfree::spsc_queue<std::function<void()>> spsc_buffer{32};
|
||||||
|
std::atomic_flag running_flag = true;
|
||||||
|
WinEvent event{};
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void Aquire() {
|
||||||
|
std::unique_lock locker(mutex);
|
||||||
|
if (reference_count == 0) {
|
||||||
|
thread.emplace();
|
||||||
|
thread->Start();
|
||||||
|
}
|
||||||
|
++reference_count;
|
||||||
|
}
|
||||||
|
static void Release() noexcept {
|
||||||
|
std::unique_lock locker(mutex);
|
||||||
|
--reference_count;
|
||||||
|
if (reference_count == 0) {
|
||||||
|
thread->Finish();
|
||||||
|
thread->Join();
|
||||||
|
thread.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Function, typename... Args>
|
||||||
|
static auto Async(Function &&function, Args &&...args) {
|
||||||
|
using R = std::invoke_result_t<std::decay_t<Function>,
|
||||||
|
std::decay_t<Args>...>;
|
||||||
|
auto promise = std::make_shared<Promise<R>>();
|
||||||
|
auto future = promise->get_future();
|
||||||
|
thread->Push([function = std::forward<Function>(function),
|
||||||
|
args = std::make_tuple(std::forward<Args>(args)...),
|
||||||
|
promise = std::move(promise)]() mutable {
|
||||||
|
try {
|
||||||
|
if constexpr (std::is_void_v<R>) {
|
||||||
|
std::apply(std::forward<Function>(function),
|
||||||
|
std::move(args));
|
||||||
|
promise->set_value();
|
||||||
|
} else {
|
||||||
|
promise->set_value(std::apply(
|
||||||
|
std::forward<Function>(function),
|
||||||
|
std::move(args)));
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
promise->set_exception(std::current_exception());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static Mutex mutex;
|
||||||
|
static unsigned int reference_count;
|
||||||
|
static std::optional<COMWorkerThread> thread;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -18,9 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Main.hxx"
|
#include "Main.hxx"
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
|
|
||||||
#include "util/Compiler.h"
|
#include "util/Compiler.h"
|
||||||
#include "Instance.hxx"
|
#include "Instance.hxx"
|
||||||
#include "system/FatalError.hxx"
|
#include "system/FatalError.hxx"
|
||||||
@ -155,5 +152,3 @@ void win32_app_stopping()
|
|||||||
else
|
else
|
||||||
running.store(false);
|
running.store(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
Loading…
Reference in New Issue
Block a user