I have a parent GUI app built with GTKmm, and I need to spawn a child process (another GUI app) and communicate with it. I use boost::process to do that. I know that I should do it asynchronously, so that the parent UI wouldn't be blocked.
So the questions:
- How can I asynchronously listen to any output from the child app and process it?
- How can I know when the child app/process has been closed/terminated?
here is how I currently do it (which is blocking the UI):
#include <iostream>
#include <boost/process.hpp>
#include <gtkmm.h>
using namespace std;
using namespace boost::process;
class MyWindow : public Gtk::Window
{
public:
MyWindow();
private:
Gtk::Button *start_btn;
void Start();
};
void MyWindow::Start() {
// The target app is built from .NET 5.0 to run on RPi (linux-arm)
ipstream pipe_stream;
// change to your own target process
child c("/usr/bin/dotnet", "/home/pi/updater/Updater.dll", std_out > pipe_stream);
std::string line;
bool upToDate;
while (pipe_stream && std::getline(pipe_stream, line) && !line.empty()) {
std::cout << line << std::endl;
try {
upToDate = line == "True" || line == "true" || line == "1";
if (upToDate) {
std::cout << "up-to-date" << std::endl;
break;
}
else {
std::cout << "update available!" << std::endl;
break;
}
}
catch(exception& e) {
std::cerr << e.what() << std::endl;
}
}
c.wait();
}
MyWindow::MyWindow()
{
set_title("Basic application");
set_default_size(200, 200);
start_btn = Gtk::make_managed<Gtk::Button>("Start process");
start_btn->signal_clicked().connect(sigc::mem_fun(*this, &MyWindow::Start));
this->add(*start_btn);
this->show_all();
}
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.examples.base");
MyWindow win;
return app->run(win);
}
This code use GTKmm 3.0 lib
As you've guessed, the
Start()
method blocks, so no other Gtk code gets a chance to run. This means nothing gets done, not even drawing the UI.Instead, make the
child
a member of the class. Next, use anasync_pipe
instead of the blocking pipe stream, so you don't have to block to read either. Now, set-up an async read loop to respond to incoming data from the child process'es standard output.I've created a simple dotnet core console application to test this with:
Now we replace the default Program.cs with:
Building and running again prints, over a total timespan of 5 seconds:
Doing The GTK Side
I've simplified many things.
The trickiest part is to make the
io_context
be polled from the Gtk event loop. I opted to useg_add_timeout
for the purpose. It is very important to correctly de-register the tick handler, so no undefined behavior results afterMyWindow
is destructed.I added a
Stop
button for good measure, and made sure thatStart
/Stop
buttons are enabled/disabled as appropriate. Let's do some live demo:Full Demo