© 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 30/11/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 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 trivial example.
The parent process spawns the child process by instantiating a SatTerm_Server. The server constructor is passed an identifier string and the path to the child process binary as arguments.
By default two named pipes will be created to form a tx-rx pair, but an arbitrary of tx and rx named pipes can be created if desired.
// 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, size_t stop_fifo_index = 0,
// size_t sc_fifo_count = 1, size_t cs_fifo_count = 1, char end_char = 3, std::string const& stop_message = "q");
SatTerm_Server sts("test_server", "./child_binary");
// Path to child binary above can incorporate desired command-line arguments
// eg: "./client_demo --full-screen=true"
if (sts.IsConnected()) {
// We are good to go!
...
}
...
The server constructor will create the named pipe temporary files in the working directory and then spawn a terminal emulator (from the list in terminal_emulator_paths.txt
) via-which it will directly execute the child binary via the '-e' option. The paths to the named pipes and all other required parameters are automatically passed to the child process as 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 bi-directional communication channel was established without error.
Client parameters are passed to the child process via it's command-line arguments, therefore argc and argv must be passed to the SatTerm_Client 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.
Blah...
// 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);
}
if (sts.GetErrorCode().err_no != 0) {
std::cerr << sts.GetErrorCode().err_no << " " << sts.GetErrorCode().function << std::endl;
}
} else {
if (sts.GetErrorCode().err_no != 0) {
std::cerr << sts.GetErrorCode().err_no << " " << sts.GetErrorCode().function << std::endl;
}
sleep(5);
}
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);
}
if (stc.GetErrorCode().err_no != 0) {
std::cerr << stc.GetErrorCode().err_no << " " << stc.GetErrorCode().function << std::endl;
}
sleep(5);
} else {
if (stc.GetErrorCode().err_no != 0) {
std::cerr << stc.GetErrorCode().err_no << " " << stc.GetErrorCode().function << std::endl;
}
sleep(5);
}
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_client.cpp src/satterm_server.cpp src/satterm_component.cpp demos/server_demo.cpp -o server_demo
user@home:~/Documents/cpp_projects/satellite_terminal$ g++ -Wall -g -O3 -I src/ src/satterm_client.cpp src/satterm_server.cpp src/satterm_component.cpp demos/client_demo.cpp -o client_demo
user@home:~/Documents/cpp_projects/satellite_terminal$ ./server_demo
Fifo 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/ 0 3 q 1 1 test_server_fifo_cs_0 test_server_fifo_sc_0
Trying /usr/bin/x-terminal-emulator
Server test_server opened fifo /home/user/Documents/cpp_projects/satellite_terminal/test_server_fifo_cs_0 for reading on descriptor 3
Server test_server opened fifo /home/user/Documents/cpp_projects/satellite_terminal/test_server_fifo_sc_0 for writing on descriptor 4
Server test_server initialised successfully.
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.
Waiting for client process to terminate...
EOF on read() to fifo index 0 suggests counterpart terminated.
user@home:~/Documents/cpp_projects/satellite_terminal$
Output in child terminal emulator instance:
Fifo working path is /home/user/Documents/cpp_projects/satellite_terminal/
Client test_client opened fifo /home/user/Documents/cpp_projects/satellite_terminal/test_server_fifo_cs_0 for writing on descriptor 3
Client test_client opened fifo /home/user/Documents/cpp_projects/satellite_terminal/test_server_fifo_sc_0 for reading on descriptor 4
Client test_client initialised successfully.
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
Blah...
satellite_terminal is distributed under the terms of the MIT license. Learn about the MIT license here