From 9461a30a97d83e21e253b152417b554bfbfc1989 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Thu, 2 Apr 2020 14:26:08 -0600 Subject: refactor shared PortEngine implementation from AlsaAudioBackend --- libs/ardour/ardour/port_engine_shared.h | 188 +++++++++ libs/ardour/port_engine_shared.cc | 652 ++++++++++++++++++++++++++++++++ 2 files changed, 840 insertions(+) create mode 100644 libs/ardour/ardour/port_engine_shared.h create mode 100644 libs/ardour/port_engine_shared.cc (limited to 'libs/ardour') diff --git a/libs/ardour/ardour/port_engine_shared.h b/libs/ardour/ardour/port_engine_shared.h new file mode 100644 index 0000000000..5afa66b594 --- /dev/null +++ b/libs/ardour/ardour/port_engine_shared.h @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2014-2019 Robin Gareus + * Copyright (C) 2015-2020 Paul Davis + * + * 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 __libardour_port_engine_shared_h__ +#define __libardour_port_engine_shared_h__ + +#include +#include +#include +#include + +#include + +#include "pbd/natsort.h" +#include "pbd/rcu.h" + +#include "ardour/types.h" +#include "ardour/port_engine.h" + +namespace ARDOUR { + +class PortEngineSharedImpl; +class PortManager; + + +class BackendPort +{ + protected: + BackendPort (PortEngineSharedImpl &b, const std::string&, PortFlags); + + public: + virtual ~BackendPort (); + + const std::string& name () const { return _name; } + const std::string& pretty_name () const { return _pretty_name; } + PortFlags flags () const { return _flags; } + + int set_name (const std::string &name) { _name = name; return 0; } + int set_pretty_name (const std::string &name) { _pretty_name = name; return 0; } + + virtual DataType type () const = 0; + + bool is_input () const { return flags () & IsInput; } + bool is_output () const { return flags () & IsOutput; } + bool is_physical () const { return flags () & IsPhysical; } + bool is_terminal () const { return flags () & IsTerminal; } + bool is_connected () const { return _connections.size () != 0; } + bool is_connected (const BackendPort *port) const; + bool is_physically_connected () const; + + const std::set& get_connections () const { return _connections; } + + int connect (BackendPort *port); + int disconnect (BackendPort *port); + void disconnect_all (); + + virtual void* get_buffer (pframes_t nframes) = 0; + + const LatencyRange latency_range (bool for_playback) const + { + return for_playback ? _playback_latency_range : _capture_latency_range; + } + + void set_latency_range (const LatencyRange &latency_range, bool for_playback); + + void update_connected_latency (bool for_playback); + + private: + PortEngineSharedImpl &_backend; + std::string _name; + std::string _pretty_name; + const PortFlags _flags; + LatencyRange _capture_latency_range; + LatencyRange _playback_latency_range; + std::set _connections; + + void _connect (BackendPort* , bool); + void _disconnect (BackendPort* , bool); + +}; // class BackendPort + +class PortEngineSharedImpl +{ + public: + PortEngineSharedImpl (PortManager& mgr, std::string const & instance_name); + virtual ~PortEngineSharedImpl(); + + /* Discovering physical ports */ + + bool port_is_physical (PortEngine::PortHandle) const; + void get_physical_outputs (DataType type, std::vector&); + void get_physical_inputs (DataType type, std::vector&); + ChanCount n_physical_outputs () const; + ChanCount n_physical_inputs () const; + + uint32_t port_name_size () const; + + int set_port_name (PortEngine::PortHandle, const std::string&); + std::string get_port_name (PortEngine::PortHandle) const; + PortFlags get_port_flags (PortEngine::PortHandle) const; + PortEngine::PortHandle get_port_by_name (const std::string&) const; + + int get_port_property (PortEngine::PortHandle, const std::string& key, std::string& value, std::string& type) const; + int set_port_property (PortEngine::PortHandle, const std::string& key, const std::string& value, const std::string& type); + + int get_ports (const std::string& port_name_pattern, DataType type, PortFlags flags, std::vector&) const; + + DataType port_data_type (PortEngine::PortHandle) const; + + PortEngine::PortHandle register_port (const std::string& shortname, ARDOUR::DataType, ARDOUR::PortFlags); + virtual void unregister_port (PortEngine::PortHandle); + + int connect (const std::string& src, const std::string& dst); + int disconnect (const std::string& src, const std::string& dst); + int connect (PortEngine::PortHandle, const std::string&); + int disconnect (PortEngine::PortHandle, const std::string&); + int disconnect_all (PortEngine::PortHandle); + + bool connected (PortEngine::PortHandle, bool process_callback_safe); + bool connected_to (PortEngine::PortHandle, const std::string&, bool process_callback_safe); + bool physically_connected (PortEngine::PortHandle, bool process_callback_safe); + int get_connections (PortEngine::PortHandle, std::vector&, bool process_callback_safe); + + virtual void port_connect_callback (const std::string& a, const std::string& b, bool conn) = 0; + virtual void port_connect_add_remove_callback () = 0; + + protected: + std::string _instance_name; + + std::vector _system_inputs; + std::vector _system_outputs; + std::vector _system_midi_in; + std::vector _system_midi_out; + + void clear_ports (); + + PortEngine::PortHandle add_port (const std::string& shortname, ARDOUR::DataType, ARDOUR::PortFlags); + void unregister_ports (bool system_only = false); + + struct SortByPortName + { + bool operator ()(const BackendPort* lhs, const BackendPort* rhs) const + { + return PBD::naturally_less (lhs->name ().c_str (), rhs->name ().c_str ()); + } + }; + + typedef std::map PortMap; // fast lookup in _ports + typedef std::set PortIndex; // fast lookup in _ports + SerializedRCUManager _portmap; + SerializedRCUManager _ports; + + bool valid_port (PortEngine::PortHandle port) const { + boost::shared_ptr p = _ports.reader(); + return std::find (p->begin(), p->end(), static_cast(port)) != p->end (); + } + + BackendPort* find_port (const std::string& port_name) const { + boost::shared_ptr p = _portmap.reader(); + PortMap::const_iterator it = p->find (port_name); + if (it == p->end()) { + return NULL; + } + return (*it).second; + } + + virtual BackendPort* port_factory (std::string const & name, ARDOUR::DataType dt, ARDOUR::PortFlags flags) = 0; +}; + +} /* namespace ARDOUR */ + +#endif /* __libardour_port_engine_shared_h__ */ diff --git a/libs/ardour/port_engine_shared.cc b/libs/ardour/port_engine_shared.cc new file mode 100644 index 0000000000..2e236c769e --- /dev/null +++ b/libs/ardour/port_engine_shared.cc @@ -0,0 +1,652 @@ +/* + * Copyright (C) 2014-2015 Tim Mayberry + * Copyright (C) 2014-2020 Paul Davis + * Copyright (C) 2014-2019 Robin Gareus + * + * 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 + +#include "pbd/error.h" +#include "pbd/i18n.h" + +#include "ardour/port_engine_shared.h" + +using namespace ARDOUR; + +BackendPort::BackendPort (PortEngineSharedImpl &b, const std::string& name, PortFlags flags) + : _backend (b) + , _name (name) + , _flags (flags) +{ + _capture_latency_range.min = 0; + _capture_latency_range.max = 0; + _playback_latency_range.min = 0; + _playback_latency_range.max = 0; +} + +BackendPort::~BackendPort () { + disconnect_all (); +} + +int BackendPort::connect (BackendPort *port) +{ + if (!port) { + PBD::error << _("BackendPort::connect (): invalid (null) port") << endmsg; + return -1; + } + + if (type () != port->type ()) { + PBD::error << _("BackendPort::connect (): wrong port-type") << endmsg; + return -1; + } + + if (is_output () && port->is_output ()) { + PBD::error << _("BackendPort::connect (): cannot inter-connect output ports.") << endmsg; + return -1; + } + + if (is_input () && port->is_input ()) { + PBD::error << _("BackendPort::connect (): cannot inter-connect input ports.") << endmsg; + return -1; + } + + if (this == port) { + PBD::error << _("BackendPort::connect (): cannot self-connect ports.") << endmsg; + return -1; + } + + if (is_connected (port)) { +#if 0 // don't bother to warn about this for now. just ignore it + PBD::error << _("BackendPort::connect (): ports are already connected:") + << " (" << name () << ") -> (" << port->name () << ")" + << endmsg; +#endif + return -1; + } + + _connect (port, true); + return 0; +} + +void BackendPort::_connect (BackendPort *port, bool callback) +{ + _connections.insert (port); + if (callback) { + port->_connect (this, false); + _backend.port_connect_callback (name(), port->name(), true); + } +} + +int BackendPort::disconnect (BackendPort *port) +{ + if (!port) { + PBD::error << _("BackendPort::disconnect (): invalid (null) port") << endmsg; + return -1; + } + + if (!is_connected (port)) { + PBD::error << _("BackendPort::disconnect (): ports are not connected:") + << " (" << name () << ") -> (" << port->name () << ")" + << endmsg; + return -1; + } + _disconnect (port, true); + return 0; +} + +void BackendPort::_disconnect (BackendPort *port, bool callback) +{ + std::set::iterator it = _connections.find (port); + assert (it != _connections.end ()); + _connections.erase (it); + if (callback) { + port->_disconnect (this, false); + _backend.port_connect_callback (name(), port->name(), false); + } +} + + +void BackendPort::disconnect_all () +{ + while (!_connections.empty ()) { + std::set::iterator it = _connections.begin (); + (*it)->_disconnect (this, false); + _backend.port_connect_callback (name(), (*it)->name(), false); + _connections.erase (it); + } +} + +bool +BackendPort::is_connected (const BackendPort *port) const +{ + return _connections.find (const_cast(port)) != _connections.end (); +} + +bool BackendPort::is_physically_connected () const +{ + for (std::set::const_iterator it = _connections.begin (); it != _connections.end (); ++it) { + if ((*it)->is_physical ()) { + return true; + } + } + return false; +} + +void +BackendPort::set_latency_range (const LatencyRange &latency_range, bool for_playback) +{ + if (for_playback) { + _playback_latency_range = latency_range; + } else { + _capture_latency_range = latency_range; + } + + for (std::set::const_iterator it = _connections.begin (); it != _connections.end (); ++it) { + if ((*it)->is_physical ()) { + (*it)->update_connected_latency (is_input ()); + } + } +} + +void +BackendPort::update_connected_latency (bool for_playback) +{ + LatencyRange lr; + lr.min = lr.max = 0; + for (std::set::const_iterator it = _connections.begin (); it != _connections.end (); ++it) { + LatencyRange l; + l = (*it)->latency_range (for_playback); + lr.min = std::max (lr.min, l.min); + lr.max = std::max (lr.max, l.max); + } + set_latency_range (lr, for_playback); +} + + + +PortEngineSharedImpl::PortEngineSharedImpl (PortManager& mgr, std::string const & str) + : _instance_name (str) + , _portmap (new PortMap) + , _ports (new PortIndex) +{ +} + +PortEngineSharedImpl::~PortEngineSharedImpl () +{ +} + +int +PortEngineSharedImpl::get_ports ( + const std::string& port_name_pattern, + DataType type, PortFlags flags, + std::vector& port_names) const +{ + int rv = 0; + regex_t port_regex; + bool use_regexp = false; + if (port_name_pattern.size () > 0) { + if (!regcomp (&port_regex, port_name_pattern.c_str (), REG_EXTENDED|REG_NOSUB)) { + use_regexp = true; + } + } + + boost::shared_ptr p = _ports.reader (); + + for (PortIndex::const_iterator i = p->begin (); i != p->end (); ++i) { + BackendPort* port = *i; + if ((port->type () == type) && flags == (port->flags () & flags)) { + if (!use_regexp || !regexec (&port_regex, port->name ().c_str (), 0, NULL, 0)) { + port_names.push_back (port->name ()); + ++rv; + } + } + } + if (use_regexp) { + regfree (&port_regex); + } + return rv; +} + +/* Discovering physical ports */ + +bool +PortEngineSharedImpl::port_is_physical (PortEngine::PortHandle port) const +{ + if (!valid_port (port)) { + PBD::error << _("BackendPort::port_is_physical (): invalid port.") << endmsg; + return false; + } + return static_cast(port)->is_physical (); +} + +void +PortEngineSharedImpl::get_physical_outputs (DataType type, std::vector& port_names) +{ + boost::shared_ptr p = _ports.reader(); + + for (PortIndex::iterator i = p->begin (); i != p->end (); ++i) { + BackendPort* port = *i; + if ((port->type () == type) && port->is_input () && port->is_physical ()) { + port_names.push_back (port->name ()); + } + } +} + +void +PortEngineSharedImpl::get_physical_inputs (DataType type, std::vector& port_names) +{ + boost::shared_ptr p = _ports.reader(); + + for (PortIndex::iterator i = p->begin (); i != p->end (); ++i) { + BackendPort* port = *i; + if ((port->type () == type) && port->is_output () && port->is_physical ()) { + port_names.push_back (port->name ()); + } + } +} + +ChanCount +PortEngineSharedImpl::n_physical_outputs () const +{ + int n_midi = 0; + int n_audio = 0; + + boost::shared_ptr p = _ports.reader(); + + for (PortIndex::const_iterator i = p->begin (); i != p->end (); ++i) { + BackendPort* port = *i; + if (port->is_output () && port->is_physical ()) { + switch (port->type ()) { + case DataType::AUDIO: ++n_audio; break; + case DataType::MIDI: ++n_midi; break; + default: break; + } + } + } + ChanCount cc; + cc.set (DataType::AUDIO, n_audio); + cc.set (DataType::MIDI, n_midi); + return cc; +} + +ChanCount +PortEngineSharedImpl::n_physical_inputs () const +{ + int n_midi = 0; + int n_audio = 0; + + boost::shared_ptr p = _ports.reader(); + + for (PortIndex::const_iterator i = p->begin (); i != p->end (); ++i) { + BackendPort* port = *i; + if (port->is_input () && port->is_physical ()) { + switch (port->type ()) { + case DataType::AUDIO: ++n_audio; break; + case DataType::MIDI: ++n_midi; break; + default: break; + } + } + } + ChanCount cc; + cc.set (DataType::AUDIO, n_audio); + cc.set (DataType::MIDI, n_midi); + return cc; +} + +PortEngine::PortHandle +PortEngineSharedImpl::add_port ( + const std::string& name, + ARDOUR::DataType type, + ARDOUR::PortFlags flags) +{ + assert(name.size ()); + if (find_port (name)) { + PBD::error << _("AlsaBackend::register_port: Port already exists:") + << " (" << name << ")" << endmsg; + return 0; + } + + BackendPort* port = port_factory (name, type, flags); + + if (!port) { + return 0; + } + + { + RCUWriter index_writer (_ports); + RCUWriter map_writer (_portmap); + + boost::shared_ptr ps = index_writer.get_copy (); + boost::shared_ptr pm = map_writer.get_copy (); + + ps->insert (port); + pm->insert (make_pair (name, port)); + } + + return port; +} + +void +PortEngineSharedImpl::unregister_port (PortEngine::PortHandle port_handle) +{ + BackendPort* port = static_cast(port_handle); + + { + RCUWriter index_writer (_ports); + RCUWriter map_writer (_portmap); + + boost::shared_ptr ps = index_writer.get_copy (); + boost::shared_ptr pm = map_writer.get_copy (); + + PortIndex::iterator i = std::find (ps->begin(), ps->end(), static_cast(port_handle)); + + if (i == ps->end ()) { + PBD::error << _("AlsaBackend::unregister_port: Failed to find port") << endmsg; + return; + } + + disconnect_all(port_handle); + + pm->erase (port->name()); + ps->erase (i); + } + + delete port; +} + + +void +PortEngineSharedImpl::unregister_ports (bool system_only) +{ + _system_inputs.clear(); + _system_outputs.clear(); + _system_midi_in.clear(); + _system_midi_out.clear(); + + RCUWriter index_writer (_ports); + RCUWriter map_writer (_portmap); + + boost::shared_ptr ps = index_writer.get_copy (); + boost::shared_ptr pm = map_writer.get_copy (); + + + for (PortIndex::iterator i = ps->begin (); i != ps->end ();) { + PortIndex::iterator cur = i++; + BackendPort* port = *cur; + if (! system_only || (port->is_physical () && port->is_terminal ())) { + port->disconnect_all (); + pm->erase (port->name()); + delete port; + ps->erase (cur); + } + } +} + +void +PortEngineSharedImpl::clear_ports () +{ + RCUWriter index_writer (_ports); + RCUWriter map_writer (_portmap); + + boost::shared_ptr ps = index_writer.get_copy(); + boost::shared_ptr pm = map_writer.get_copy (); + + if (ps->size () || pm->size ()) { + PBD::warning << _("PortEngineSharedImpl: recovering from unclean shutdown, port registry is not empty.") << endmsg; + _system_inputs.clear(); + _system_outputs.clear(); + _system_midi_in.clear(); + _system_midi_out.clear(); + ps->clear(); + pm->clear(); + } +} + +uint32_t +PortEngineSharedImpl::port_name_size () const +{ + return 256; +} + +int +PortEngineSharedImpl::set_port_name (PortEngine::PortHandle port, const std::string& name) +{ + std::string newname (_instance_name + ":" + name); + + if (!valid_port (port)) { + PBD::error << _("AlsaBackend::set_port_name: Invalid Port") << endmsg; + return -1; + } + + if (find_port (newname)) { + PBD::error << _("AlsaBackend::set_port_name: Port with given name already exists") << endmsg; + return -1; + } + + BackendPort* p = static_cast(port); + + { + RCUWriter map_writer (_portmap); + boost::shared_ptr pm = map_writer.get_copy (); + + pm->erase (p->name()); + pm->insert (make_pair (newname, p)); + } + + return p->set_name (newname); +} + +std::string +PortEngineSharedImpl::get_port_name (PortEngine::PortHandle port) const +{ + if (!valid_port (port)) { + PBD::warning << _("AlsaBackend::get_port_name: Invalid Port(s)") << endmsg; + return std::string (); + } + return static_cast(port)->name (); +} + +PortFlags +PortEngineSharedImpl::get_port_flags (PortEngine::PortHandle port) const +{ + if (!valid_port (port)) { + PBD::warning << _("AlsaBackend::get_port_flags: Invalid Port(s)") << endmsg; + return PortFlags (0); + } + return static_cast(port)->flags (); +} + +int +PortEngineSharedImpl::get_port_property (PortEngine::PortHandle port, const std::string& key, std::string& value, std::string& type) const +{ + if (!valid_port (port)) { + PBD::warning << _("AlsaBackend::get_port_property: Invalid Port(s)") << endmsg; + return -1; + } + if (key == "http://jackaudio.org/metadata/pretty-name") { + type = ""; + value = static_cast(port)->pretty_name (); + if (!value.empty()) { + return 0; + } + } + return -1; +} + +int +PortEngineSharedImpl::set_port_property (PortEngine::PortHandle port, const std::string& key, const std::string& value, const std::string& type) +{ + if (!valid_port (port)) { + PBD::warning << _("AlsaBackend::set_port_property: Invalid Port(s)") << endmsg; + return -1; + } + if (key == "http://jackaudio.org/metadata/pretty-name" && type.empty ()) { + static_cast(port)->set_pretty_name (value); + return 0; + } + return -1; +} + +PortEngine::PortHandle +PortEngineSharedImpl::get_port_by_name (const std::string& name) const +{ + PortEngine::PortHandle port = (PortEngine::PortHandle) find_port (name); + return port; +} + +DataType +PortEngineSharedImpl::port_data_type (PortEngine::PortHandle port) const +{ + if (!valid_port (port)) { + return DataType::NIL; + } + return static_cast(port)->type (); +} + +PortEngine::PortHandle +PortEngineSharedImpl::register_port ( + const std::string& name, + ARDOUR::DataType type, + ARDOUR::PortFlags flags) +{ + if (name.size () == 0) { return 0; } + if (flags & IsPhysical) { return 0; } + return add_port (_instance_name + ":" + name, type, flags); +} + + +int +PortEngineSharedImpl::connect (const std::string& src, const std::string& dst) +{ + BackendPort* src_port = find_port (src); + BackendPort* dst_port = find_port (dst); + + if (!src_port) { + PBD::error << _("AlsaBackend::connect: Invalid Source port:") + << " (" << src <<")" << endmsg; + return -1; + } + if (!dst_port) { + PBD::error << _("AlsaBackend::connect: Invalid Destination port:") + << " (" << dst <<")" << endmsg; + return -1; + } + return src_port->connect (dst_port); +} + +int +PortEngineSharedImpl::disconnect (const std::string& src, const std::string& dst) +{ + BackendPort* src_port = find_port (src); + BackendPort* dst_port = find_port (dst); + + if (!src_port || !dst_port) { + PBD::error << _("AlsaBackend::disconnect: Invalid Port(s)") << endmsg; + return -1; + } + return src_port->disconnect (dst_port); +} + +int +PortEngineSharedImpl::connect (PortEngine::PortHandle src, const std::string& dst) +{ + BackendPort* dst_port = find_port (dst); + if (!valid_port (src)) { + PBD::error << _("AlsaBackend::connect: Invalid Source Port Handle") << endmsg; + return -1; + } + if (!dst_port) { + PBD::error << _("AlsaBackend::connect: Invalid Destination Port") + << " (" << dst << ")" << endmsg; + return -1; + } + return static_cast(src)->connect (dst_port); +} + +int +PortEngineSharedImpl::disconnect (PortEngine::PortHandle src, const std::string& dst) +{ + BackendPort* dst_port = find_port (dst); + if (!valid_port (src) || !dst_port) { + PBD::error << _("AlsaBackend::disconnect: Invalid Port(s)") << endmsg; + return -1; + } + return static_cast(src)->disconnect (dst_port); +} + +int +PortEngineSharedImpl::disconnect_all (PortEngine::PortHandle port) +{ + if (!valid_port (port)) { + PBD::error << _("AlsaBackend::disconnect_all: Invalid Port") << endmsg; + return -1; + } + static_cast(port)->disconnect_all (); + return 0; +} + +bool +PortEngineSharedImpl::connected (PortEngine::PortHandle port, bool /* process_callback_safe*/) +{ + if (!valid_port (port)) { + PBD::error << _("AlsaBackend::disconnect_all: Invalid Port") << endmsg; + return false; + } + return static_cast(port)->is_connected (); +} + +bool +PortEngineSharedImpl::connected_to (PortEngine::PortHandle src, const std::string& dst, bool /*process_callback_safe*/) +{ + BackendPort* dst_port = find_port (dst); +#ifndef NDEBUG + if (!valid_port (src) || !dst_port) { + PBD::error << _("AlsaBackend::connected_to: Invalid Port") << endmsg; + return false; + } +#endif + return static_cast(src)->is_connected (dst_port); +} + +bool +PortEngineSharedImpl::physically_connected (PortEngine::PortHandle port, bool /*process_callback_safe*/) +{ + if (!valid_port (port)) { + PBD::error << _("AlsaBackend::physically_connected: Invalid Port") << endmsg; + return false; + } + return static_cast(port)->is_physically_connected (); +} + +int +PortEngineSharedImpl::get_connections (PortEngine::PortHandle port, std::vector& names, bool /*process_callback_safe*/) +{ + if (!valid_port (port)) { + PBD::error << _("AlsaBackend::get_connections: Invalid Port") << endmsg; + return -1; + } + + assert (0 == names.size ()); + + const std::set& connected_ports = static_cast(port)->get_connections (); + + for (std::set::const_iterator i = connected_ports.begin (); i != connected_ports.end (); ++i) { + names.push_back ((*i)->name ()); + } + + return (int)names.size (); +} -- cgit v1.2.3