diff --git a/default.ini b/default.ini index 3ba94f4..938ffaf 100644 --- a/default.ini +++ b/default.ini @@ -33,3 +33,10 @@ name=Network Export # hostname=192.168.1.210 # # default port is 5004 # name=midid + +# enable automatic export of devices to rtpmidi, by name regex and type. Comment full section to disable. +[alsa_hw_auto_export] +name_positive_regex=.* +name_negative_regex=(System|Timer|Announce) +# hardware | software | all | none. Default none, so set to hardware normally. +type=hardware diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cb51e38..51e6912 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,8 +4,10 @@ add_executable( aseq.cpp argv.cpp ini.cpp + settings.cpp control_socket.cpp stringpp.cpp + hwautoannounce.cpp factory.cpp midirouter.cpp diff --git a/src/aseq.cpp b/src/aseq.cpp index 286f352..e9b47d6 100644 --- a/src/aseq.cpp +++ b/src/aseq.cpp @@ -182,6 +182,18 @@ void aseq_t::read_ready() { if (me != midi_event.end()) me->second(ev); } break; + case SND_SEQ_EVENT_PORT_START: { + auto name = get_client_name(&ev->data.addr); + auto type = get_client_type(&ev->data.addr); + auto port = port_t(ev->data.addr.client, ev->data.addr.port); + DEBUG("Client start {} {} {}", name, type, port); + added_port_announcement(name, type, port); + } break; + case SND_SEQ_EVENT_PORT_EXIT: { + auto port = port_t(ev->data.addr.client, ev->data.addr.port); + DEBUG("Client exit {}", port); + removed_port_announcement(port); + } break; default: static bool warning_raised[SND_SEQ_EVENT_NONE + 1]; if (!warning_raised[ev->type]) { @@ -193,11 +205,15 @@ void aseq_t::read_ready() { } } -uint8_t aseq_t::create_port(const std::string &name) { +uint8_t aseq_t::create_port(const std::string &name, bool do_export) { auto caps = SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ; auto type = SND_SEQ_TYPE_INET; + if (!do_export) { + caps |= SND_SEQ_PORT_CAP_NO_EXPORT; + } + auto port = snd_seq_create_simple_port(seq, name.c_str(), caps, type); return port; @@ -261,6 +277,41 @@ std::string aseq_t::get_client_name(snd_seq_addr_t *addr) { return fmt::format("{}-{}", client_name, port_name); } +aseq_t::client_type_e get_type_by_seq_type(int type) { + // Known types so far.. may be increased later? Dont know how to make it more + // future proof. If change here, quite probably will be incompatible changes + DEBUG("Type: {:b}", type); + if (type & 0b01'00000000'00000000) { + return aseq_t::client_type_e::TYPE_HARDWARE; + } else if (type == 0x02) { + return aseq_t::client_type_e::TYPE_HARDWARE; + } else { + return aseq_t::client_type_e::TYPE_SOFTWARE; + } +} + +aseq_t::client_type_e aseq_t::get_client_type(snd_seq_addr_t *addr) { + // get client type using snd_seq_port_info_get_type(const snd_seq_port_info_t + // *info); + snd_seq_client_info_t *client_info = nullptr; + snd_seq_client_info_malloc(&client_info); + snd_seq_get_any_client_info(seq, addr->client, client_info); + std::string client_name = snd_seq_client_info_get_name(client_info); + + snd_seq_port_info_t *port_info = nullptr; + snd_seq_port_info_malloc(&port_info); + snd_seq_get_any_port_info(seq, addr->client, addr->port, port_info); + std::string port_name = snd_seq_port_info_get_name(port_info); + + auto type = snd_seq_client_info_get_type(client_info); + // snd_seq_port_info_get_type(port_info); + + snd_seq_client_info_free(client_info); + snd_seq_port_info_free(port_info); + + return get_type_by_seq_type(type); +} + static void disconnect_port_at_subs(snd_seq_t *seq, snd_seq_query_subscribe_t *subs, uint8_t port) { @@ -353,16 +404,20 @@ void aseq_t::disconnect(const port_t &from, const port_t &to) { if (from.client == client_id) { int res = snd_seq_disconnect_to(seq, from.port, to.client, to.port); if (res < 0) { - throw rtpmidid::exception("Failed disconnection: {} -> {}: {} ({})", - from.to_string(), to.to_string(), - snd_strerror(res), res); + ERROR("Failed disconnection: {} -> {}: {} ({})", from.to_string(), + to.to_string(), snd_strerror(res), res); + // throw rtpmidid::exception("Failed disconnection: {} -> {}: {} ({})", + // from.to_string(), to.to_string(), + // snd_strerror(res), res); } } else if (to.client == client_id) { int res = snd_seq_disconnect_from(seq, to.port, from.client, from.port); if (res < 0) { - throw rtpmidid::exception("Failed disconnection: {} -> {}: {} ({})", - from.to_string(), to.to_string(), - snd_strerror(res), res); + ERROR("Failed disconnection: {} -> {}: {} ({})", from.to_string(), + to.to_string(), snd_strerror(res), res); + // throw rtpmidid::exception("Failed disconnection: {} -> {}: {} ({})", + // from.to_string(), to.to_string(), + // snd_strerror(res), res); } } else { ERROR("Can not disconnect ports I'm not part of."); @@ -432,6 +487,55 @@ uint8_t aseq_t::find_port(uint8_t device_id, const std::string &name) { return retv; } +void aseq_t::for_devices( + std::function + func) { + snd_seq_client_info_t *cinfo; + + int ret = snd_seq_client_info_malloc(&cinfo); + if (ret != 0) + throw rtpmidid::exception("Error allocating memory for alsa clients"); + + snd_seq_client_info_set_client(cinfo, -1); + while (snd_seq_query_next_client(seq, cinfo) >= 0) { + int cid = snd_seq_client_info_get_client(cinfo); + if (cid < 0) { + throw rtpmidid::exception("Error getting client info"); + } + auto cname = snd_seq_client_info_get_name(cinfo); + auto type = get_type_by_seq_type(snd_seq_client_info_get_type(cinfo)); + // DEBUG("Client {} {} {:b}", cid, cname, + // snd_seq_client_info_get_type(cinfo)); + + func(cid, cname, type); + } + snd_seq_client_info_free(cinfo); +} + +void aseq_t::for_ports(uint8_t device_id, + std::function func) { + snd_seq_port_info_t *pinfo; + + int ret = snd_seq_port_info_malloc(&pinfo); + if (ret != 0) + throw rtpmidid::exception("Error allocating memory for alsa ports"); + + snd_seq_port_info_set_client(pinfo, device_id); + + snd_seq_port_info_set_port(pinfo, -1); + while (snd_seq_query_next_port(seq, pinfo) >= 0) { + int pid = snd_seq_port_info_get_port(pinfo); + if (pid < 0) { + throw rtpmidid::exception("Error getting client info"); + } + const char *pname = snd_seq_port_info_get_name(pinfo); + + func(pid, pname); + } + + snd_seq_port_info_free(pinfo); +} + mididata_to_alsaevents_t::mididata_to_alsaevents_t() { snd_midi_event_new(1024, &buffer); } @@ -476,3 +580,25 @@ void mididata_to_alsaevents_t::ev_to_mididata(snd_seq_event_t *ev, } } // namespace rtpmididns + +fmt::v9::appender +fmt::formatter::format(rtpmididns::aseq_t::port_t c, + format_context &ctx) { + auto name = fmt::format("port_t[{}, {}]", c.client, c.port); + return formatter::format(name, ctx); +} + +fmt::v9::appender fmt::formatter::format( + rtpmididns::aseq_t::client_type_e c, format_context &ctx) { + auto name = c == rtpmididns::aseq_t::client_type_e::TYPE_HARDWARE + ? "TYPE_HARDWARE" + : "TYPE_SOFTWARE"; + return formatter::format(name, ctx); +} + +fmt::v9::appender fmt::formatter::format( + const rtpmididns::aseq_t::connection_t &c, format_context &ctx) { + auto name = + fmt::format("connection_t[{}, {} -> {}]", c.connected, c.from, c.to); + return formatter::format(name, ctx); +} diff --git a/src/aseq.hpp b/src/aseq.hpp index f40f755..1b07841 100644 --- a/src/aseq.hpp +++ b/src/aseq.hpp @@ -55,34 +55,49 @@ class aseq_t : public std::enable_shared_from_this { std::shared_ptr aseq; port_t from; port_t to; + bool connected = false; connection_t(const std::shared_ptr &aseq_, const port_t &from_, const port_t &to_) { aseq = aseq_; from = from_; to = to_; + connected = true; }; connection_t(const connection_t &other) = delete; connection_t(connection_t &&other) { aseq = other.aseq; from = other.from; to = other.to; + connected = other.connected; other.from = {0, 0}; other.to = {0, 0}; + other.connected = false; }; connection_t &operator=(const connection_t &other) = delete; connection_t &operator=(connection_t &&other) { aseq = other.aseq; from = other.from; to = other.to; + connected = other.connected; other.from = {0, 0}; other.to = {0, 0}; + other.connected = false; return *this; } - ~connection_t() { - if (from.client && from.port && to.client && to.port) + void disconnect() { + if (connected) aseq->disconnect(from, to); + connected = false; } + ~connection_t() { disconnect(); } + }; + + enum client_type_e { + TYPE_HARDWARE, + TYPE_SOFTWARE, + TYPE_SYSTEM, // generated by ALSA, announcements and such. Not really for + // exports. }; std::string name; @@ -94,14 +109,18 @@ class aseq_t : public std::enable_shared_from_this { std::map> midi_event; uint8_t client_id; std::vector aseq_listener; + rtpmidid::signal_t + added_port_announcement; + rtpmidid::signal_t removed_port_announcement; aseq_t(std::string name); ~aseq_t(); void read_ready(); std::string get_client_name(snd_seq_addr_t *addr); + aseq_t::client_type_e get_client_type(snd_seq_addr_t *addr); - uint8_t create_port(const std::string &name); + uint8_t create_port(const std::string &name, bool do_export = true); void remove_port(uint8_t port); /// Connect two ports @@ -112,6 +131,10 @@ class aseq_t : public std::enable_shared_from_this { uint8_t find_device(const std::string &name); uint8_t find_port(uint8_t device_id, const std::string &name); + void for_devices( + std::function); + void for_ports(uint8_t device_id, + std::function); }; std::vector get_ports(aseq_t *); @@ -152,8 +175,19 @@ template <> struct hash { template <> struct fmt::formatter : formatter { - auto format(rtpmididns::aseq_t::port_t c, format_context &ctx) { - auto name = fmt::format("port_t[{}, {}]", c.client, c.port); - return formatter::format(name, ctx); - } + fmt::v9::appender format(rtpmididns::aseq_t::port_t c, format_context &ctx); +}; + +template <> +struct fmt::formatter + : formatter { + fmt::v9::appender format(rtpmididns::aseq_t::client_type_e c, + format_context &ctx); +}; + +template <> +struct fmt::formatter + : formatter { + fmt::v9::appender format(const rtpmididns::aseq_t::connection_t &c, + format_context &ctx); }; diff --git a/src/hwautoannounce.cpp b/src/hwautoannounce.cpp new file mode 100644 index 0000000..6291bc1 --- /dev/null +++ b/src/hwautoannounce.cpp @@ -0,0 +1,175 @@ +/** + * Real Time Protocol Music Instrument Digital Interface Daemon + * Copyright (C) 2019-2024 David Moreno Montero + * + * 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 3 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, see . + */ + +#include "hwautoannounce.hpp" +#include "aseq.hpp" +#include "local_alsa_multi_listener.hpp" +#include "midirouter.hpp" +#include "settings.hpp" +#include +#include + +namespace rtpmididns { + +HwAutoAnnounce::HwAutoAnnounce(std::shared_ptr aseq, + std::shared_ptr router) + : router(router), aseq(aseq) { + + auto ann_port = aseq->create_port("Announcements", false); + auto conn = aseq->connect(aseq_t::port_t{0, 1}, + aseq_t::port_t{aseq->client_id, ann_port}); + connections.push_back(std::move(conn)); + + aseq->for_devices([&](uint8_t device_id, const std::string &device_name, + aseq_t::client_type_e type) { + if (type != aseq_t::client_type_e::TYPE_HARDWARE) + return; + aseq->for_ports( + device_id, [&](uint8_t port_id, const std::string &port_name) { + // DEBUG("HwAutoAnnounce::HwAutoAnnounce::for_ports {}:{} + // {}:{} + // ", + // device_name, port_name, device_id, port_id); + added_port_announcement(device_name, type, + aseq_t::port_t{device_id, port_id}); + }); + }); + + added_client_announcement_connection = aseq->added_port_announcement.connect( + [this](const std::string &name, aseq_t::client_type_e type, + const aseq_t::port_t &port) { + // DEBUG("HwAutoAnnounce::HwAutoAnnounce::added_port_announcement {} {} + // ", + // name, type); + added_port_announcement(name, type, port); + }); + + removed_port_announcement_connection = + aseq->removed_port_announcement.connect( + [this](const aseq_t::port_t &port) { + // DEBUG("HwAutoAnnounce::HwAutoAnnounce::removed_port_announcement + // {} {} + // ", + // name, type); + removed_port_announcement(port); + }); +} + +HwAutoAnnounce::~HwAutoAnnounce() { + for (auto &conn : connections) { + try { + DEBUG("Disconnecting {} -> {}", conn.from, conn.to); + conn.disconnect(); + } catch (...) { + ERROR("Error disconnecting {} -> {}", conn.from, conn.to); + // Ignore + } + } +} + +void HwAutoAnnounce::added_port_announcement(const std::string &name, + aseq_t::client_type_e type, + const aseq_t::port_t &port) { + // DEBUG("HwAutoAnnounce::added_port_announcement {} {} {}", name, type, + // port); + auto ahwaa = &settings.alsa_hw_auto_export; + + if (ahwaa->type == settings_t::alsa_hw_auto_export_type_e::NONE) + return; + + // Check positive regex + if (!ahwaa->name_negative_regex.has_value()) + return; + + auto pos_regex = ahwaa->name_positive_regex.value(); + + if (!std::regex_match(name, pos_regex)) { + return; + } + + // Check negative regex + if (ahwaa->name_positive_regex.has_value()) { + auto neg_regex = ahwaa->name_negative_regex.value(); + + if (std::regex_match(name, neg_regex)) { + return; + } + } + + // Check the type is appropiate + if (ahwaa->type == settings_t::alsa_hw_auto_export_type_e::HARDWARE) { + if (type != aseq_t::client_type_e::TYPE_HARDWARE) + return; + } else if (ahwaa->type == settings_t::alsa_hw_auto_export_type_e::SOFTWARE) { + if (type != aseq_t::client_type_e::TYPE_SOFTWARE) + return; + } else if (ahwaa->type == settings_t::alsa_hw_auto_export_type_e::SYSTEM) { + if (type != aseq_t::client_type_e::TYPE_SYSTEM) + return; + } + + DEBUG("HwAutoAnnounce::added_port_announcement {} {} {}", name, type, port); + + // Find the local_alsa_listener_t + bool connected = false; + router->for_each_peer( + [&](local_alsa_multi_listener_t *peer) { + INFO("Auto announcing {} {} {}", name, type, port); + auto annport = aseq_t::port_t{aseq->client_id, peer->port}; + auto con1 = aseq->connect(port, annport); + // auto con2 = aseq->connect(annport, port); + // FIXME should be removed from here, or it may grow for each new + // client. There is no real problem as will never disconnect, but it is + // not clean. + connections.push_back(std::move(con1)); + // connections.push_back(std::move(con2)); + + connected = true; + }); + if (!connected) { + ERROR("No local_alsa_multi_listener_t found to connect {} {} {}", name, + type, port); + } +} + +void HwAutoAnnounce::removed_port_announcement(const aseq_t::port_t &port) { + DEBUG("HwAutoAnnounce::removed_client_announcement {}", port); + connections.erase( + std::remove_if(connections.begin(), connections.end(), + [&](const aseq_t::connection_t &conn) { + if (conn.from == port || conn.to == port) { + DEBUG("Disconnecting {} -> {}. May show error as we " + "really try to disconnect, but normally it does " + "not even exist anymore.", + conn.from, conn.to); + return true; + } + return false; + }), + connections.end()); + + // DEBUG("Still got this connections:"); + // for (auto &conn : connections) { + // DEBUG(" {} -> {}", conn.from, conn.to); + // if (conn.from == port || conn.to == port) { + // ERROR("Still got a connection to the removed port"); + // } + // } +} + +} // namespace rtpmididns diff --git a/src/hwautoannounce.hpp b/src/hwautoannounce.hpp new file mode 100644 index 0000000..9a8bfc9 --- /dev/null +++ b/src/hwautoannounce.hpp @@ -0,0 +1,48 @@ +/** + * Real Time Protocol Music Instrument Digital Interface Daemon + * Copyright (C) 2019-2024 David Moreno Montero + * + * 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 3 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, see . + */ + +#include "aseq.hpp" +#include "midirouter.hpp" +#include +#include +#include + +namespace rtpmididns { +class HwAutoAnnounce { +public: + std::shared_ptr router; + std::shared_ptr aseq; + std::vector connections; + + rtpmidid::connection_t + added_client_announcement_connection; + rtpmidid::connection_t + removed_port_announcement_connection; + + HwAutoAnnounce(std::shared_ptr aseq, + std::shared_ptr router); + ~HwAutoAnnounce(); + + void added_port_announcement(const std::string &name, + aseq_t::client_type_e type, + const aseq_t::port_t &port); + void removed_port_announcement(const aseq_t::port_t &port); +}; + +} // namespace rtpmididns \ No newline at end of file diff --git a/src/ini.cpp b/src/ini.cpp index f26bdcc..5d422f8 100644 --- a/src/ini.cpp +++ b/src/ini.cpp @@ -63,22 +63,28 @@ void load_ini(const std::string &filename) { } section = line.substr(1, line.length() - 2); + // sections that are unique, can not be repeated if (section == "general") { continue; - } else if (section == "rtpmidi_announce") { - settings.rtpmidi_announces.emplace_back(); - rtpmidi_announce = settings.rtpmidi_announces.data() + - settings.rtpmidi_announces.size() - 1; - } else if (section == "alsa_announce") { - settings.alsa_announces.emplace_back(); - alsa_announce = - settings.alsa_announces.data() + settings.alsa_announces.size() - 1; - } else if (section == "connect_to") { - settings.connect_to.emplace_back(); - connect_to = - settings.connect_to.data() + settings.connect_to.size() - 1; + } else if (section == "alsa_hw_auto_export") { + continue; } else { - throw rtpmidid::exception("Invalid section: {}", section); + // Sections that can be repeated + if (section == "rtpmidi_announce") { + settings.rtpmidi_announces.emplace_back(); + rtpmidi_announce = settings.rtpmidi_announces.data() + + settings.rtpmidi_announces.size() - 1; + } else if (section == "alsa_announce") { + settings.alsa_announces.emplace_back(); + alsa_announce = settings.alsa_announces.data() + + settings.alsa_announces.size() - 1; + } else if (section == "connect_to") { + settings.connect_to.emplace_back(); + connect_to = + settings.connect_to.data() + settings.connect_to.size() - 1; + } else { + throw rtpmidid::exception("Invalid section: {}", section); + } } continue; @@ -140,6 +146,35 @@ void load_ini(const std::string &filename) { } else { throw rtpmidid::exception("Invalid key: {}", key); } + } else if (section == "alsa_hw_auto_export") { + if (key == "type") { + if (value == "none") { + settings.alsa_hw_auto_export.type = + settings_t::alsa_hw_auto_export_type_e::NONE; + } else if (value == "hardware") { + settings.alsa_hw_auto_export.type = + settings_t::alsa_hw_auto_export_type_e::HARDWARE; + } else if (value == "software") { + settings.alsa_hw_auto_export.type = + settings_t::alsa_hw_auto_export_type_e::SOFTWARE; + } else if (value == "system") { + settings.alsa_hw_auto_export.type = + settings_t::alsa_hw_auto_export_type_e::SYSTEM; + } else if (value == "all") { + settings.alsa_hw_auto_export.type = + settings_t::alsa_hw_auto_export_type_e::ALL; + } else { + throw rtpmidid::exception("Invalid value: {}", value); + } + } else if (key == "name_positive_regex") { + settings.alsa_hw_auto_export.name_positive = value; + settings.alsa_hw_auto_export.name_positive_regex.emplace(value); + } else if (key == "name_negative_regex") { + settings.alsa_hw_auto_export.name_negative = value; + settings.alsa_hw_auto_export.name_negative_regex.emplace(value); + } else { + throw rtpmidid::exception("Invalid key: {}", key); + } } else { throw rtpmidid::exception("Invalid section: {}", section); } diff --git a/src/main.cpp b/src/main.cpp index 07860ff..572f3e5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #include "aseq.hpp" #include "control_socket.hpp" #include "factory.hpp" +#include "hwautoannounce.hpp" #include "rtpmidid/exceptions.hpp" #include "rtpmidid/mdns_rtpmidi.hpp" #include "rtpmidid/poller.hpp" @@ -55,6 +56,7 @@ void sigint_f(int) { int main(int argc, char **argv) { rtpmididns::parse_argv(argc, argv); + std::optional hwautoannounce; signal(SIGINT, sigint_f); signal(SIGTERM, sigterm_f); @@ -71,12 +73,13 @@ int main(int argc, char **argv) { // Create all the alsa network midipeers for (const auto &announce : rtpmididns::settings.alsa_announces) { - router->add_peer(rtpmididns::make_local_alsa_multi_listener(announce.name, aseq)); + router->add_peer( + rtpmididns::make_local_alsa_multi_listener(announce.name, aseq)); } // Create all the rtpmidi network midipeers for (const auto &announce : rtpmididns::settings.rtpmidi_announces) { - router->add_peer( - rtpmididns::make_network_rtpmidi_multi_listener(announce.name, announce.port, aseq)); + router->add_peer(rtpmididns::make_network_rtpmidi_multi_listener( + announce.name, announce.port, aseq)); } // Connect to all static endpoints for (const auto &connect_to : rtpmididns::settings.connect_to) { @@ -84,6 +87,8 @@ int main(int argc, char **argv) { router, connect_to.name, connect_to.hostname, connect_to.port, aseq)); } + hwautoannounce.emplace(aseq, router); + INFO("Waiting for connections."); while (rtpmidid::poller.is_open()) { rtpmidid::poller.wait(); diff --git a/src/midipeer.cpp b/src/midipeer.cpp index 62ee0d2..edfeb75 100644 --- a/src/midipeer.cpp +++ b/src/midipeer.cpp @@ -18,6 +18,7 @@ #include "midipeer.hpp" #include "midirouter.hpp" +#include #include "json.hpp" diff --git a/src/midirouter.hpp b/src/midirouter.hpp index 4983754..abc6069 100644 --- a/src/midirouter.hpp +++ b/src/midirouter.hpp @@ -18,7 +18,6 @@ #pragma once #include "json_fwd.hpp" -#include "rtpmidid/iobytes.hpp" #include #include #include diff --git a/src/settings.cpp b/src/settings.cpp new file mode 100644 index 0000000..640692b --- /dev/null +++ b/src/settings.cpp @@ -0,0 +1,132 @@ +/** + * Real Time Protocol Music Instrument Digital Interface Daemon + * Copyright (C) 2019-2024 David Moreno Montero + * + * 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 3 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, see . + */ + +#include "settings.hpp" + +fmt::v9::appender +fmt::formatter::format( + const rtpmididns::settings_t::alsa_announce_t &data, format_context &ctx) { + + return fmt::format_to(ctx.out(), "[alsa_announce_t {}]", data.name); +} + +fmt::v9::appender +fmt::formatter>::format( + const std::vector &data, + format_context &ctx) { + std::string result = "["; + for (auto &item : data) { + result += fmt::format("[{}] ", item.name); + } + result += "]"; + return fmt::format_to(ctx.out(), "{}", result); +} + +fmt::v9::appender +fmt::formatter::format( + const rtpmididns::settings_t::rtpmidi_announce_t &data, + format_context &ctx) { + + return fmt::format_to(ctx.out(), "[rtpmidi_announce_t {} {}]", data.name, + data.port); +} + +fmt::v9::appender +fmt::formatter>::format( + const std::vector &data, + format_context &ctx) { + std::string result = "["; + for (auto &item : data) { + result += fmt::format("[rtpmidi_announce_t {} {}] ", item.name, item.port); + } + result += "]"; + return fmt::format_to(ctx.out(), "{}", result); +} + +fmt::v9::appender fmt::formatter::format( + const rtpmididns::settings_t::connect_to_t &data, format_context &ctx) { + + return fmt::format_to(ctx.out(), "[connect_to_t {} {} {}]", data.hostname, + data.port, data.name); +} + +fmt::v9::appender +fmt::formatter>::format( + const std::vector &data, + format_context &ctx) { + std::string result = "["; + for (auto &item : data) { + result += fmt::format("[connect_to_t {} {} {}] ", item.hostname, item.port, + item.name); + } + result += "]"; + return fmt::format_to(ctx.out(), "{}", result); +} + +fmt::v9::appender fmt::formatter::format( + const rtpmididns::settings_t &data, format_context &ctx) { + return fmt::format_to(ctx.out(), + "[settings_t: alsa_name: {}, alsa_network: {}, " + "control_filename: {}, rtpmidi_announces: {}, " + "alsa_announces: {}, connect_to: {}, " + "alsa_hw_auto_export: {}]", + data.alsa_name, data.alsa_network, + data.control_filename, data.rtpmidi_announces, + data.alsa_announces, data.connect_to, + data.alsa_hw_auto_export); +} + +fmt::v9::appender +fmt::formatter::format( + const rtpmididns::settings_t::alsa_hw_auto_export_t &data, + format_context &ctx) { + std::string result = "["; + if (data.name_positive_regex.has_value()) { + result += fmt::format("name_positive_regex: {} ", data.name_positive); + } + if (data.name_negative_regex.has_value()) { + result += fmt::format("name_negative_regex: {} ", data.name_negative); + } + result += fmt::format("type: {} ", data.type); + result += "]"; + return fmt::format_to(ctx.out(), "{}", result); +} + +fmt::v9::appender +fmt::formatter::format( + const rtpmididns::settings_t::alsa_hw_auto_export_type_e &data, + format_context &ctx) { + std::string result = "["; + if (data == rtpmididns::settings_t::alsa_hw_auto_export_type_e::NONE) { + result += "NONE"; + } else if (data == rtpmididns::settings_t::alsa_hw_auto_export_type_e::ALL) { + result += "ALL"; + } else { + if (data & rtpmididns::settings_t::alsa_hw_auto_export_type_e::HARDWARE) { + result += "HARDWARE "; + } + if (data & rtpmididns::settings_t::alsa_hw_auto_export_type_e::SOFTWARE) { + result += "SOFTWARE "; + } + if (data & rtpmididns::settings_t::alsa_hw_auto_export_type_e::SYSTEM) { + result += "SYSTEM "; + } + } + result += "]"; + return fmt::format_to(ctx.out(), "{}", result); +} \ No newline at end of file diff --git a/src/settings.hpp b/src/settings.hpp index 37b6218..e64d5fa 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -18,7 +18,9 @@ #pragma once #include -#include +#include +#include +#include #include #include #include @@ -49,6 +51,24 @@ struct settings_t { std::vector rtpmidi_announces; std::vector alsa_announces; std::vector connect_to; + + enum alsa_hw_auto_export_type_e { + NONE = 0, + ALL = 7, + HARDWARE = 1, + SOFTWARE = 2, + SYSTEM = 4, + }; + + struct alsa_hw_auto_export_t { + std::string name_positive; + std::optional name_positive_regex; + std::string name_negative; + std::optional name_negative_regex; + alsa_hw_auto_export_type_e type = alsa_hw_auto_export_type_e::NONE; + }; + + alsa_hw_auto_export_t alsa_hw_auto_export; }; extern settings_t settings; @@ -57,90 +77,67 @@ extern settings_t settings; template <> struct fmt::formatter : formatter { - auto format(const rtpmididns::settings_t::alsa_announce_t &data, - format_context &ctx) { - - return fmt::format_to(ctx.out(), "[alsa_announce_t {}]", data.name); - } + fmt::v9::appender format(const rtpmididns::settings_t::alsa_announce_t &data, + format_context &ctx); }; template <> struct fmt::formatter> : formatter { - auto format(const std::vector &data, - format_context &ctx) { - std::string result = "["; - for (auto &item : data) { - result += fmt::format("[{}] ", item.name); - } - result += "]"; - return fmt::format_to(ctx.out(), "{}", result); - } + fmt::v9::appender + format(const std::vector &data, + format_context &ctx); }; template <> struct fmt::formatter : formatter { - auto format(const rtpmididns::settings_t::rtpmidi_announce_t &data, - format_context &ctx) { - - return fmt::format_to(ctx.out(), "[rtpmidi_announce_t {} {}]", data.name, - data.port); - } + fmt::v9::appender + format(const rtpmididns::settings_t::rtpmidi_announce_t &data, + format_context &ctx); }; template <> struct fmt::formatter> : formatter { - auto + fmt::v9::appender format(const std::vector &data, - format_context &ctx) { - std::string result = "["; - for (auto &item : data) { - result += - fmt::format("[rtpmidi_announce_t {} {}] ", item.name, item.port); - } - result += "]"; - return fmt::format_to(ctx.out(), "{}", result); - } + format_context &ctx); }; template <> struct fmt::formatter : formatter { - auto format(const rtpmididns::settings_t::connect_to_t &data, - format_context &ctx) { - - return fmt::format_to(ctx.out(), "[connect_to_t {} {} {}]", data.hostname, - data.port, data.name); - } + fmt::v9::appender format(const rtpmididns::settings_t::connect_to_t &data, + format_context &ctx); }; template <> struct fmt::formatter> : formatter { - auto format(const std::vector &data, - format_context &ctx) { - std::string result = "["; - for (auto &item : data) { - result += fmt::format("[connect_to_t {} {} {}] ", item.hostname, - item.port, item.name); - } - result += "]"; - return fmt::format_to(ctx.out(), "{}", result); - } + fmt::v9::appender + format(const std::vector &data, + format_context &ctx); }; template <> struct fmt::formatter : formatter { - auto format(const rtpmididns::settings_t &data, format_context &ctx) { - - return fmt::format_to(ctx.out(), - "[settings_t alsa_name: {} alsa_network: {} " - "control_filename: {} rtpmidi_announces: {} " - "alsa_announces: {} connect_to: {}]", - data.alsa_name, data.alsa_network, - data.control_filename, data.rtpmidi_announces, - data.alsa_announces, data.connect_to); - } + fmt::v9::appender format(const rtpmididns::settings_t &data, + format_context &ctx); }; + +template <> +struct fmt::formatter + : formatter { + fmt::v9::appender + format(const rtpmididns::settings_t::alsa_hw_auto_export_t &data, + format_context &ctx); +}; + +template <> +struct fmt::formatter + : formatter { + fmt::v9::appender + format(const rtpmididns::settings_t::alsa_hw_auto_export_type_e &data, + format_context &ctx); +}; \ No newline at end of file