add options --list-jack-ports and --list-midi-messages useful for bash-completion v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Wed, 07 Oct 2020 01:45:30 +0200
branchv_0
changeset 11 07247893054e
parent 10 ded44e94147c
child 12 e8aae4d42c01
add options --list-jack-ports and --list-midi-messages useful for bash-completion
bash-completion.sh
src/CLIParser.h
src/Configuration.h
src/JackCommand.h
src/relpipe-in-jack.cpp
--- a/bash-completion.sh	Tue Oct 06 16:55:22 2020 +0200
+++ b/bash-completion.sh	Wed Oct 07 01:45:30 2020 +0200
@@ -13,22 +13,15 @@
 # You should have received a copy of the GNU General Public License
 # along with this program. If not, see <http://www.gnu.org/licenses/>.
 
+_relpipe_in_jack_completion_read_nullbyte() { local IFS=; for v in "$@"; do export "$v"; read -r -d '' "$v"; done }
+
 _relpipe_in_jack_completion_ports() {
-	# TODO: simpler and faster implementation in relpipe-out-jack or relpipe-in-jack C++ code
-	jack_lsp -tp 2>/dev/null \
-		| tr -d \\t \
-		| tr \\n \\0 \
-		| relpipe-in-cli \
-			--relation "jack_midi_port" \
-			--attribute "name" string \
-			--attribute "properties" string \
-			--attribute "type" string \
-			--records-on-stdin true \
-		| relpipe-tr-grep '.*' 'properties' 'output' \
-		| relpipe-tr-grep '.*' 'type' 'midi' \
-		| relpipe-tr-cut '.*' 'name' \
-		| relpipe-out-nullbyte \
-		| tr \\0 \\n
+	if type relpipe-in-jack &> /dev/null && type relpipe-out-nullbyte &> /dev/null; then
+		relpipe-in-jack --list-jack-ports true --list-midi-messages false 2>/dev/null \
+			| relpipe-out-nullbyte \
+			| while _relpipe_in_jack_completion_read_nullbyte "name" "input" "output" "physical" "terminal" "mine" "midi" "type"; do
+				 if [[ "$midi" = "true" && "$output" = "true" && "$mine" = "false" ]]; then echo "$name"; fi; done
+	fi
 }
 
 _relpipe_in_jack_completion() {
@@ -40,14 +33,23 @@
 	w2=${COMP_WORDS[COMP_CWORD-2]}
 	w3=${COMP_WORDS[COMP_CWORD-3]}
 
+	BOOLEAN_VALUES=(
+		"true"
+		"false"
+	)
+
 	  if [[ "$w1" == "--jack-client-name"             && "x$w0" == "x" ]];    then COMPREPLY=("'relpipe-in-jack'")
 	elif [[ "$w1" == "--jack-connect-to-port"                          ]];    then COMPREPLY=($(compgen -W "$(_relpipe_in_jack_completion_ports)" -- "$w0"))
 	elif [[ "$w1" == "--required-jack-connections"    && "x$w0" == "x" ]];    then COMPREPLY=("0")
+	elif [[ "$w1" == "--list-jack-ports"                               ]];    then COMPREPLY=($(compgen -W "${BOOLEAN_VALUES[*]}" -- "$w0"))
+	elif [[ "$w1" == "--list-midi-messages"                            ]];    then COMPREPLY=($(compgen -W "${BOOLEAN_VALUES[*]}" -- "$w0"))
 	else
 		OPTIONS=(
 			"--jack-client-name"
 			"--jack-connect-to-port"
 			"--required-jack-connections"
+			"--list-jack-ports"
+			"--list-midi-messages"
 		)
 		COMPREPLY=($(compgen -W "${OPTIONS[*]}" -- "$w0"))
 	fi
--- a/src/CLIParser.h	Tue Oct 06 16:55:22 2020 +0200
+++ b/src/CLIParser.h	Wed Oct 07 01:45:30 2020 +0200
@@ -51,6 +51,8 @@
 	static const relpipe::common::type::StringX OPTION_JACK_CLIENT_NAME;
 	static const relpipe::common::type::StringX OPTION_JACK_CONNECT_TO_PORT;
 	static const relpipe::common::type::StringX OPTION_REQUIRED_JACK_CONNECTIONS;
+	static const relpipe::common::type::StringX OPTION_LIST_JACK_PORTS;
+	static const relpipe::common::type::StringX OPTION_LIST_MIDI_MESSAGES;
 
 	Configuration parse(const std::vector<relpipe::common::type::StringX>& arguments) {
 		Configuration c;
@@ -64,6 +66,10 @@
 				c.portsToConnect.push_back(readNext(arguments, i));
 			} else if (option == OPTION_REQUIRED_JACK_CONNECTIONS) {
 				c.requiredJackConnections = std::stoi(readNext(arguments, i));
+			} else if (option == OPTION_LIST_JACK_PORTS) {
+				c.listJackPorts = parseBoolean(readNext(arguments, i));
+			} else if (option == OPTION_LIST_MIDI_MESSAGES) {
+				c.listMidiMessages = parseBoolean(readNext(arguments, i));
 			} else throw relpipe::cli::RelpipeCLIException(L"Unsupported CLI option: " + option, relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS);
 		}
 
@@ -77,6 +83,8 @@
 const relpipe::common::type::StringX CLIParser::OPTION_JACK_CLIENT_NAME = L"--jack-client-name";
 const relpipe::common::type::StringX CLIParser::OPTION_JACK_CONNECT_TO_PORT = L"--jack-connect-to-port";
 const relpipe::common::type::StringX CLIParser::OPTION_REQUIRED_JACK_CONNECTIONS = L"--required-jack-connections";
+const relpipe::common::type::StringX CLIParser::OPTION_LIST_JACK_PORTS = L"--list-jack-ports";
+const relpipe::common::type::StringX CLIParser::OPTION_LIST_MIDI_MESSAGES = L"--list-midi-messages";
 
 }
 }
--- a/src/Configuration.h	Tue Oct 06 16:55:22 2020 +0200
+++ b/src/Configuration.h	Wed Oct 07 01:45:30 2020 +0200
@@ -28,9 +28,17 @@
 
 class Configuration {
 public:
+	
+	enum class PortType {
+		MIDI_INPUT,
+		MIDI_OUTPUT
+	};
+	
 	relpipe::common::type::StringX jackClientName = L"relpipe-in-jack";
 	std::vector<relpipe::common::type::StringX> portsToConnect;
 	int requiredJackConnections = 0;
+	relpipe::common::type::Boolean listJackPorts = false;
+	relpipe::common::type::Boolean listMidiMessages = true;
 
 	virtual ~Configuration() {
 	}
--- a/src/JackCommand.h	Tue Oct 06 16:55:22 2020 +0200
+++ b/src/JackCommand.h	Wed Oct 07 01:45:30 2020 +0200
@@ -26,6 +26,7 @@
 #include <pthread.h>
 #include <functional>
 #include <iomanip>
+#include <regex>
 
 #include <jack/jack.h>
 #include <jack/midiport.h>
@@ -171,6 +172,50 @@
 		return result.str();
 	}
 
+	void listPorts(std::shared_ptr<relpipe::writer::RelationalWriter> writer) {
+		using namespace relpipe::writer;
+		vector<AttributeMetadata> metadata;
+
+		metadata.push_back({L"name", TypeId::STRING});
+		metadata.push_back({L"input", TypeId::BOOLEAN});
+		metadata.push_back({L"output", TypeId::BOOLEAN});
+		metadata.push_back({L"physical", TypeId::BOOLEAN});
+		metadata.push_back({L"terminal", TypeId::BOOLEAN});
+		metadata.push_back({L"mine", TypeId::BOOLEAN});
+		metadata.push_back({L"midi", TypeId::BOOLEAN});
+		metadata.push_back({L"type", TypeId::STRING});
+		writer->startRelation(L"port", metadata, true);
+
+		const char** portNames = jack_get_ports(realTimeContext.jackClient, nullptr, nullptr, 0);
+		
+		std::regex midiTypePattern(".*midi$");
+
+		for (const char** portName = portNames; *portName; portName++) {
+			jack_port_t* port = jack_port_by_name(realTimeContext.jackClient, *portName);
+
+			const char* portType = jack_port_type(port);
+			int portFlags = jack_port_flags(port);
+
+			bool isInput = portFlags & JackPortFlags::JackPortIsInput;
+			bool isOuputput = portFlags & JackPortFlags::JackPortIsOutput;
+			bool isPhysical = portFlags & JackPortFlags::JackPortIsPhysical;
+			bool isTerminal = portFlags & JackPortFlags::JackPortIsTerminal;
+			bool isMine = jack_port_is_mine(realTimeContext.jackClient, port);
+			bool isMidi = std::regex_search(portType, midiTypePattern);
+
+			writer->writeAttribute(convertor.from_bytes(*portName));
+			writer->writeAttribute(&isInput, typeid (isInput));
+			writer->writeAttribute(&isOuputput, typeid (isOuputput));
+			writer->writeAttribute(&isPhysical, typeid (isPhysical));
+			writer->writeAttribute(&isTerminal, typeid (isTerminal));
+			writer->writeAttribute(&isMine, typeid (isMine));
+			writer->writeAttribute(&isMidi, typeid (isMidi));
+			writer->writeAttribute(convertor.from_bytes(portType));
+		}
+
+		jack_free(portNames);
+	}
+
 	static void jackErrorCallback(const char * message) {
 		std::wstring_convert < std::codecvt_utf8<wchar_t>> convertor; // TODO: local system encoding
 		std::wcerr << L"JACK: " << convertor.from_bytes(message) << std::endl;
@@ -235,6 +280,10 @@
 		// Relation headers:
 		using namespace relpipe::writer;
 		vector<AttributeMetadata> metadata;
+
+		if (configuration.listJackPorts) listPorts(writer);
+		if (!configuration.listMidiMessages) return;
+
 		metadata.push_back({L"event", TypeId::STRING});
 		metadata.push_back({L"channel", TypeId::INTEGER});
 		metadata.push_back({L"note_on", TypeId::BOOLEAN});
--- a/src/relpipe-in-jack.cpp	Tue Oct 06 16:55:22 2020 +0200
+++ b/src/relpipe-in-jack.cpp	Wed Oct 07 01:45:30 2020 +0200
@@ -40,7 +40,6 @@
 	setlocale(LC_ALL, "");
 	CLI::untieStdIO();
 	CLI cli(argc, argv);
-	// TODO: options, CLI parsing, configurable attributes
 	int resultCode = CLI::EXIT_CODE_UNEXPECTED_ERROR;
 
 	try {
@@ -52,6 +51,10 @@
 		std::shared_ptr<RelationalWriter> writer(Factory::create(std::cout));
 		jackCommand->processJackStream(writer, std::bind(fflush, stdout)); // std::bind(fflush, XXX) Factory::create(XXX) must be the same stream XXX
 		resultCode = CLI::EXIT_CODE_SUCCESS;
+	} catch (RelpipeCLIException e) {
+		fwprintf(stderr, L"Caught CLI exception: %ls\n", e.getMessge().c_str());
+		fwprintf(stderr, L"Debug: Input stream: eof=%ls, lastRead=%d\n", (cin.eof() ? L"true" : L"false"), cin.gcount());
+		resultCode = e.getExitCode();
 	} catch (JackException e) {
 		fwprintf(stderr, L"Caught JACK exception: %ls\n", e.getMessge().c_str());
 		fwprintf(stderr, L"Debug: Input stream: eof=%ls, lastRead=%d\n", (cin.eof() ? L"true" : L"false"), cin.gcount());