© 2021 Dr Sebastien Sikora.
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.
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.
Using satellite_terminal in a C++ project is very easy. Let's demonstrate this via a simple example.
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.
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.
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.
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...
satellite_terminal is distributed under the terms of the MIT license. Learn about the MIT license here