output/Interface: add "attributes" map

These attributes are printed in the "outputs" response, and the new
command "outputset" allows setting new values.

No attributes are currently implemented.
This commit is contained in:
Max Kellermann 2017-12-19 08:45:34 +01:00
parent d05c3f4e4d
commit 86a06a7acc
13 changed files with 161 additions and 1 deletions

View File

@ -1390,7 +1390,6 @@ OUTPUT_LIBS = \
OUTPUT_API_SRC = \
src/output/Client.hxx \
src/output/OutputAPI.hxx \
src/output/Interface.hxx \
src/output/Filtered.cxx src/output/Filtered.hxx \
src/output/Registry.cxx src/output/Registry.hxx \
src/output/MultipleOutputs.cxx src/output/MultipleOutputs.hxx \
@ -1407,6 +1406,7 @@ OUTPUT_API_SRC = \
src/output/Init.cxx
liboutput_plugins_a_SOURCES = \
src/output/Interface.cxx src/output/Interface.hxx \
src/output/Timer.cxx src/output/Timer.hxx \
src/output/plugins/NullOutputPlugin.cxx \
src/output/plugins/NullOutputPlugin.hxx

1
NEWS
View File

@ -3,6 +3,7 @@ ver 0.21 (not yet released)
- "tagtypes" can be used to hide tags
- "find" and "search" can sort
- "outputs" prints the plugin name
- "outputset" sets runtime attributes
- close connection when client sends HTTP request
* tags
- new tag "OriginalDate"

View File

@ -2523,6 +2523,7 @@ outputid: 0
outputname: My ALSA Device
plugin: alsa
outputenabled: 0
attribute: dop=0
OK
</screen>
<para>
@ -2547,6 +2548,24 @@ OK
</itemizedlist>
</listitem>
</varlistentry>
<varlistentry id="command_outputset">
<term>
<cmdsynopsis>
<command>outputset</command>
<arg choice="req"><replaceable>ID</replaceable></arg>
<arg choice="req"><replaceable>NAME</replaceable></arg>
<arg choice="req"><replaceable>VALUE</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Set a runtime attribute. These are specific to the
output plugin, and supported values are usually printed
in the <command>outputs</command> response.
</para>
</listitem>
</varlistentry>
</variablelist>
</section>

View File

@ -138,6 +138,7 @@ static constexpr struct command commands[] = {
{ "next", PERMISSION_CONTROL, 0, 0, handle_next },
{ "notcommands", PERMISSION_NONE, 0, 0, handle_not_commands },
{ "outputs", PERMISSION_READ, 0, 0, handle_devices },
{ "outputset", PERMISSION_ADMIN, 3, 3, handle_outputset },
{ "partition", PERMISSION_READ, 1, 1, handle_partition },
{ "password", PERMISSION_NONE, 1, 1, handle_password },
{ "pause", PERMISSION_CONTROL, 0, 1, handle_pause },

View File

@ -25,6 +25,8 @@
#include "client/Client.hxx"
#include "client/Response.hxx"
#include "Partition.hxx"
#include "IdleFlags.hxx"
#include "util/CharUtil.hxx"
CommandResult
handle_enableoutput(Client &client, Request args, Response &r)
@ -68,6 +70,54 @@ handle_toggleoutput(Client &client, Request args, Response &r)
return CommandResult::OK;
}
static bool
IsValidAttributeNameChar(char ch) noexcept
{
return IsAlphaNumericASCII(ch) || ch == '_';
}
gcc_pure
static bool
IsValidAttributeName(const char *s) noexcept
{
do {
if (!IsValidAttributeNameChar(*s))
return false;
} while (*++s);
return true;
}
CommandResult
handle_outputset(Client &client, Request request, Response &response)
{
assert(request.size == 3);
const unsigned i = request.ParseUnsigned(0);
auto &partition = client.GetPartition();
auto &outputs = partition.outputs;
if (i >= outputs.Size()) {
response.Error(ACK_ERROR_NO_EXIST, "No such audio output");
return CommandResult::ERROR;
}
auto &ao = outputs.Get(i);
const char *const name = request[1];
if (!IsValidAttributeName(name)) {
response.Error(ACK_ERROR_ARG, "Illegal attribute name");
return CommandResult::ERROR;
}
const char *const value = request[2];
ao.SetAttribute(name, value);
partition.EmitIdle(IDLE_OUTPUT);
return CommandResult::OK;
}
CommandResult
handle_devices(Client &client, gcc_unused Request args, Response &r)
{

View File

@ -35,6 +35,9 @@ handle_disableoutput(Client &client, Request request, Response &response);
CommandResult
handle_toggleoutput(Client &client, Request request, Response &response);
CommandResult
handle_outputset(Client &client, Request request, Response &response);
CommandResult
handle_devices(Client &client, Request request, Response &response);

View File

@ -76,6 +76,18 @@ AudioOutputControl::GetMixer() const noexcept
return output->mixer;
}
const std::map<std::string, std::string>
AudioOutputControl::GetAttributes() const noexcept
{
return output->GetAttributes();
}
void
AudioOutputControl::SetAttribute(std::string &&name, std::string &&value)
{
output->SetAttribute(std::move(name), std::move(value));
}
bool
AudioOutputControl::LockSetEnabled(bool new_value) noexcept
{

View File

@ -30,6 +30,8 @@
#include <utility>
#include <exception>
#include <string>
#include <map>
#ifndef NDEBUG
#include <assert.h>
@ -324,6 +326,9 @@ public:
void BeginDestroy() noexcept;
void FinishDestroy() noexcept;
const std::map<std::string, std::string> GetAttributes() const noexcept;
void SetAttribute(std::string &&name, std::string &&value);
/**
* Enables the device, but don't wait for completion.
*

View File

@ -40,6 +40,18 @@ FilteredAudioOutput::SupportsPause() const noexcept
return output->SupportsPause();
}
const std::map<std::string, std::string>
FilteredAudioOutput::GetAttributes() const noexcept
{
return output->GetAttributes();
}
void
FilteredAudioOutput::SetAttribute(std::string &&_name, std::string &&_value)
{
output->SetAttribute(std::move(_name), std::move(_value));
}
void
FilteredAudioOutput::Enable()
{

View File

@ -25,6 +25,7 @@
#include <memory>
#include <string>
#include <map>
#include <chrono>
class PreparedFilter;
@ -167,6 +168,9 @@ public:
gcc_pure
bool SupportsPause() const noexcept;
const std::map<std::string, std::string> GetAttributes() const noexcept;
void SetAttribute(std::string &&name, std::string &&value);
/**
* Throws #std::runtime_error on error.
*/

30
src/output/Interface.cxx Normal file
View File

@ -0,0 +1,30 @@
/*
* Copyright 2003-2017 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 "Interface.hxx"
#include "Compiler.h"
#include <stdexcept>
void
AudioOutput::SetAttribute(gcc_unused std::string &&name,
gcc_unused std::string &&value)
{
throw std::invalid_argument("Unsupported attribute");
}

View File

@ -20,7 +20,10 @@
#ifndef MPD_AUDIO_OUTPUT_INTERFACE_HXX
#define MPD_AUDIO_OUTPUT_INTERFACE_HXX
#include <map>
#include <string>
#include <chrono>
#include <stdexcept>
struct AudioFormat;
struct Tag;
@ -57,6 +60,22 @@ public:
return flags & FLAG_NEED_FULLY_DEFINED_AUDIO_FORMAT;
}
/**
* Returns a map of runtime attributes.
*
* This method must be thread-safe.
*/
virtual const std::map<std::string, std::string> GetAttributes() const noexcept {
return {};
}
/**
* Manipulate a runtime attribute on client request.
*
* This method must be thread-safe.
*/
virtual void SetAttribute(std::string &&name, std::string &&value);
/**
* Enable the device. This may allocate resources, preparing
* for the device to be opened.

View File

@ -41,5 +41,9 @@ printAudioDevices(Response &r, const MultipleOutputs &outputs)
i,
ao.GetName(), ao.GetPluginName(),
ao.IsEnabled());
for (const auto &a : ao.GetAttributes())
r.Format("attribute: %s=%s\n",
a.first.c_str(), a.second.c_str());
}
}