Skip to content

Latest commit

 

History

History
296 lines (220 loc) · 11.3 KB

README.md

File metadata and controls

296 lines (220 loc) · 11.3 KB

satellite_terminal


Parent and Child process terminal emulator instances

© 2021 Dr Sebastien Sikora.

seb.nf.sikora@protonmail.com

A simple C++ library for POSIX compatible operating systems that enables your project to easily launch and communicate bidirectionally with client processes connected to separate terminal emulator instances.

Updated 06/12/2021.

What is it?

satellite_terminal automates the process of launching a client process connected to it's own terminal emulator instance from within a parent process, linked with an arbitrary number of named pipes(otherwise known as FIFOs) to allow bidirectional interprocess communication.

satellite_terminal leverages the functionality defined in the unistd.h, sys/stat.h & fcntl.h headers, and as-such will work only with POSIX compatible operating systems and environments. satellite_terminal has to-date been compiled and tested on GNU/Linux.

satellite_terminal is easy to incorporate and use within your own projects, as the basic examples below will demonstrate.

How to use it?

Using satellite_terminal in a C++ project is very easy. Let's demonstrate this via a simple example.

Parent process

In the parent process, we spawn the child process by instantiating a SatTerm_Server. At a minimum, we pass the server constructor an identifier string and a string comprising the path to the child process binary as arguments. We can set the third (optional) boolean argument to false to hide console information and error messages for both parent and child process.

The fourth (optional) argument is a vector of Port identifiers. By default a single send-receive pair of FIFOs will be created, accessed via a single corresponding Port ("comms") instantiated by each of the SatTerm_Server and SatTerm_Client. If desired we can specify an arbitrary number of additional Port identifiers here.

// parent.cpp
#include "satellite_terminal.h"
...

// Server constructor prototype in satellite_terminal.h:
//
// SatTerm_Server(std::string const& identifier, std::string const& path_to_client_binary, bool display_messages = true,
//                std::vector<std::string> port_identifiers = {"comms"}, std::string const& stop_message = "q",
//                std::string const& path_to_terminal_emulator_paths = "./terminal_emulator_paths.txt",
//                char end_char = 3, std::string const& stop_port_identifier = "", unsigned long timeout_seconds = 5);

SatTerm_Server sts("test_server", "./child_binary");

// Path to child binary above can incorporate desired command-line arguments
// eg: "./child_binary --full-screen=true"

if (sts.IsConnected()) {
	// We are good to go!
	...
}
...

The server constructor will spawn a terminal emulator (from the list in terminal_emulator_paths.txt) via-which it will directly execute the child binary using the '-e' option, passing the required configuration options for the child process via automatically generated command-line options.

The server constructor will return once the communication channel is established with the child process, an error occurs or a timeout is reached. When it returns, if the server's IsConnected() member function returns true, the child process started correctly and the required FIFOs were created and opened for reading/writing without error.

Child process

In the child process, we establish communication with the parent process by instantiating a SatTerm_Client. The client parameters are passed to the child process via command-line arguments, therefore argc and argv must be passed to the constructor.

The parameters are appended directly onto the child binary path string passed to the server constructor, following an automatically applied delimiter ("client_args"), so you can use any command-line arguments required by the child process as normal and the client constructor will automatically parse the remaining arguments.

// child.cpp
#include "satellite_terminal.h"

int main(int argc, char *argv[]) {
	
	// -- Your argument parser goes here ---
	//      -- Don't modify argc/v! --
	
	SatTerm_Client stc("test_client", argc, argv);
	
	if (stc.IsConnected()) {
		// We are good to go!
		...
	}
...

The client constructor will return once the communication channel is established with the parent process, an error occurs or a timeout is reached. When it returns, if the client's IsConnected() member function returns true, the bi-directional communication channel was established without error.

Sending and receiving

Once the SatTerm_Server constructor in our parent process and SatTerm_Client constructor in our spawned child process have returned our two processes can exchange messages.


// SendMessage() function prototype in satellite_terminal.h:
//
// std::string SendMessage(std::string const& message, size_t tx_fifo_index = 0, unsigned long timeout_seconds = 10);

std::string message_unsent = stc.SendMessage("Outbound message");


// GetMessage() function prototype in satellite_terminal.h:
//
// std::string GetMessage(size_t rx_fifo_index = 0, bool capture_end_char = false, unsigned long timeout_seconds = 0);

std::string inbound_message = stc.GetMessage();

Blah blah blah.cpp.

Blah.

Complete basic example

Blah...

// server_demo.cpp

#include <unistd.h>                 // sleep(), usleep().
#include <ctime>                    // time().
#include <iostream>                 // std::cout, std::cerr, std::endl.
#include <string>                   // std::string.

#include "satellite_terminal.h"

int main (void) {
	
	SatTerm_Server sts("test_server", "./client_demo");
	
	if (sts.IsConnected()) {
		size_t message_count = 10;
		for (size_t i = 0; i < message_count; i ++) {
			std::string outbound_message = "Message number " + std::to_string(i) + " from server.";
			sts.SendMessage(outbound_message);
		}
		
		unsigned long timeout_seconds = 5;
		unsigned long start_time = time(0);
		
		while ((sts.GetErrorCode().err_no == 0) && ((time(0) - start_time) < timeout_seconds)) {
			std::string inbound_message = sts.GetMessage();
			if (inbound_message != "") {
				std::cout << "Message \"" << inbound_message << "\" returned by client." << std::endl;
			}
			usleep(1000);
		}
		
		std::cerr << "On termination error code = " << sts.GetErrorCode().err_no << "    Error detail = " << sts.GetErrorCode().detail << std::endl;
		
	} else {
		
		std::cerr << "On termination error code = " << sts.GetErrorCode().err_no << "    Error detail = " << sts.GetErrorCode().detail << std::endl;
		
	}
	return 0;
}

Blah...

// client_demo.cpp

#include <unistd.h>                 // sleep(), usleep().
#include <ctime>                    // time().
#include <iostream>                 // std::cout, std::cerr, std::endl.
#include <string>                   // std::string.

#include "satellite_terminal.h"

int main(int argc, char *argv[]) {
	
	SatTerm_Client stc("test_client", argc, argv);

	if (stc.IsConnected()) {
		
		while (stc.GetErrorCode().err_no == 0) {
			
			std::string inbound_message = stc.GetMessage();
			
			if (inbound_message != "") {
				std::cout << inbound_message << std::endl;
				if (inbound_message != stc.GetStopMessage()) {
					stc.SendMessage(inbound_message);
				} else {
					break;
				}
				
			}
			usleep(1000);
		}
		std::cerr << "On termination error code = " << stc.GetErrorCode().err_no << "    Error detail = " << stc.GetErrorCode().detail << std::endl;
		sleep(5);        // Delay to read the message before terminal emulator window closes.
	} else {
		std::cerr << "On termination error code = " << stc.GetErrorCode().err_no << "    Error detail = " << stc.GetErrorCode().detail << std::endl;
		sleep(5);        // Delay to read the message before terminal emulator window closes.
	}
	return 0;
}

Compile server_demo.cpp and client_demo.cpp, then execute server_demo from within the project directory:

user@home:~/Documents/cpp_projects/satellite_terminal$ g++  -Wall -g -O3 -I src/ src/satterm_agent.cpp src/satterm_client.cpp src/satterm_server.cpp src/satterm_port.cpp demos/server_demo.cpp -o server_demo
user@home:~/Documents/cpp_projects/satellite_terminal$ g++  -Wall -g -O3 -I src/ src/satterm_agent.cpp src/satterm_client.cpp src/satterm_server.cpp src/satterm_port.cpp demos/client_demo.cpp -o client_demo
user@home:~/Documents/cpp_projects/satellite_terminal$ ./server_demo 
Server working path is /home/user/Documents/cpp_projects/satellite_terminal/
Client process started.
Client process attempting to execute via terminal emulator '-e':
./client_demo client_args /home/user/Documents/cpp_projects/satellite_terminal/ 3 q 1 1 server_rx server_tx
Trying /usr/bin/x-terminal-emulator
In Port server_rx opened fifo /home/user/Documents/cpp_projects/satellite_terminal/server_rx for reading on descriptor 3
Out Port server_tx opened fifo /home/user/Documents/cpp_projects/satellite_terminal/server_tx for writing on descriptor 4
Server test_server successfully initialised connection.
Message "Message number 0 from server." returned by client.
Message "Message number 1 from server." returned by client.
Message "Message number 2 from server." returned by client.
Message "Message number 3 from server." returned by client.
Message "Message number 4 from server." returned by client.
Message "Message number 5 from server." returned by client.
Message "Message number 6 from server." returned by client.
Message "Message number 7 from server." returned by client.
Message "Message number 8 from server." returned by client.
Message "Message number 9 from server." returned by client.
On termination error code = 0    Error detail = 
Waiting for client process to terminate...
EOF error on GetMessage() for In Port server_rx suggests counterpart terminated.
user@home:~/Documents/cpp_projects/satellite_terminal$

Output in child terminal emulator instance:

Client working path is /home/user/Documents/cpp_projects/satellite_terminal/
Out Port server_rx opened fifo /home/user/Documents/cpp_projects/satellite_terminal/server_rx for writing on descriptor 3
In Port server_tx opened fifo /home/user/Documents/cpp_projects/satellite_terminal/server_tx for reading on descriptor 4
Client test_client successfully initialised connection.
Message number 0 from server.
Message number 1 from server.
Message number 2 from server.
Message number 3 from server.
Message number 4 from server.
Message number 5 from server.
Message number 6 from server.
Message number 7 from server.
Message number 8 from server.
Message number 9 from server.
q
On termination error code = 0    Error detail = 

Blah...

License:


Mit License Logo

satellite_terminal is distributed under the terms of the MIT license. Learn about the MIT license here