From 86a06a7acc1ada8cea245bfcb7277b3e50c488f0 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 19 Dec 2017 08:45:34 +0100 Subject: [PATCH] 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. --- Makefile.am | 2 +- NEWS | 1 + doc/protocol.xml | 19 +++++++++++++ src/command/AllCommands.cxx | 1 + src/command/OutputCommands.cxx | 50 ++++++++++++++++++++++++++++++++++ src/command/OutputCommands.hxx | 3 ++ src/output/Control.cxx | 12 ++++++++ src/output/Control.hxx | 5 ++++ src/output/Filtered.cxx | 12 ++++++++ src/output/Filtered.hxx | 4 +++ src/output/Interface.cxx | 30 ++++++++++++++++++++ src/output/Interface.hxx | 19 +++++++++++++ src/output/Print.cxx | 4 +++ 13 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 src/output/Interface.cxx diff --git a/Makefile.am b/Makefile.am index 3c56e2539..3f99b925a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/NEWS b/NEWS index e30950bd2..d2d414a7e 100644 --- a/NEWS +++ b/NEWS @@ -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" diff --git a/doc/protocol.xml b/doc/protocol.xml index 517e89caa..bea985e9e 100644 --- a/doc/protocol.xml +++ b/doc/protocol.xml @@ -2523,6 +2523,7 @@ outputid: 0 outputname: My ALSA Device plugin: alsa outputenabled: 0 +attribute: dop=0 OK @@ -2547,6 +2548,24 @@ OK + + + + + outputset + ID + NAME + VALUE + + + + + Set a runtime attribute. These are specific to the + output plugin, and supported values are usually printed + in the outputs response. + + + diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx index 3a4934dd8..47afd411a 100644 --- a/src/command/AllCommands.cxx +++ b/src/command/AllCommands.cxx @@ -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 }, diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx index 36296f94f..1fb86a1a4 100644 --- a/src/command/OutputCommands.cxx +++ b/src/command/OutputCommands.cxx @@ -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) { diff --git a/src/command/OutputCommands.hxx b/src/command/OutputCommands.hxx index 47053393e..5473a0aa6 100644 --- a/src/command/OutputCommands.hxx +++ b/src/command/OutputCommands.hxx @@ -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); diff --git a/src/output/Control.cxx b/src/output/Control.cxx index f75bb2134..8b56cf142 100644 --- a/src/output/Control.cxx +++ b/src/output/Control.cxx @@ -76,6 +76,18 @@ AudioOutputControl::GetMixer() const noexcept return output->mixer; } +const std::map +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 { diff --git a/src/output/Control.hxx b/src/output/Control.hxx index 2c230f5bf..343d46010 100644 --- a/src/output/Control.hxx +++ b/src/output/Control.hxx @@ -30,6 +30,8 @@ #include #include +#include +#include #ifndef NDEBUG #include @@ -324,6 +326,9 @@ public: void BeginDestroy() noexcept; void FinishDestroy() noexcept; + const std::map GetAttributes() const noexcept; + void SetAttribute(std::string &&name, std::string &&value); + /** * Enables the device, but don't wait for completion. * diff --git a/src/output/Filtered.cxx b/src/output/Filtered.cxx index 2b434feab..e65945034 100644 --- a/src/output/Filtered.cxx +++ b/src/output/Filtered.cxx @@ -40,6 +40,18 @@ FilteredAudioOutput::SupportsPause() const noexcept return output->SupportsPause(); } +const std::map +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() { diff --git a/src/output/Filtered.hxx b/src/output/Filtered.hxx index f2195b83d..34ac4de38 100644 --- a/src/output/Filtered.hxx +++ b/src/output/Filtered.hxx @@ -25,6 +25,7 @@ #include #include +#include #include class PreparedFilter; @@ -167,6 +168,9 @@ public: gcc_pure bool SupportsPause() const noexcept; + const std::map GetAttributes() const noexcept; + void SetAttribute(std::string &&name, std::string &&value); + /** * Throws #std::runtime_error on error. */ diff --git a/src/output/Interface.cxx b/src/output/Interface.cxx new file mode 100644 index 000000000..5480642df --- /dev/null +++ b/src/output/Interface.cxx @@ -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 + +void +AudioOutput::SetAttribute(gcc_unused std::string &&name, + gcc_unused std::string &&value) +{ + throw std::invalid_argument("Unsupported attribute"); +} diff --git a/src/output/Interface.hxx b/src/output/Interface.hxx index f1059ad75..86123dc7c 100644 --- a/src/output/Interface.hxx +++ b/src/output/Interface.hxx @@ -20,7 +20,10 @@ #ifndef MPD_AUDIO_OUTPUT_INTERFACE_HXX #define MPD_AUDIO_OUTPUT_INTERFACE_HXX +#include +#include #include +#include 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 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. diff --git a/src/output/Print.cxx b/src/output/Print.cxx index 1fb7eb0ed..e534ae85c 100644 --- a/src/output/Print.cxx +++ b/src/output/Print.cxx @@ -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()); } }