First off, I only do this for fun and I'm not a pro by any means. So I wouldn't be surprised if my code is a bit sloppy!
I'm attempting to write a GUI wrapper in Java 11 for a console application. My plan was to use a BufferedReader to capture stdOut and stdErr from the process and display it in a JTextArea. I'm running this thread from my main GUI thread after populating an ArrayList with the command line parameters. It works perfectly on Ubuntu or Fedora but I just can't get it right on Windows. When I attempt to run a cross-compiled Windows version of the console application, my application only displays its output after the console application has closed. I also tried substituting in a very simple Hello World application in C (which normally displays Hello, waits 5 seconds and then displays World) and this does the same thing. But, if I change my ArrayList to run ping.exe -t 8.8.8.8, this works fine.
What I suspect is happening is that the while loop is blocking the thread but I don't understand how it works on Linux and if I use ping.exe on Windows. I also tried the code in Redirect stdin and stdout in Java and inheritIO mentioned in ProcessBuilder: Forwarding stdout and stderr of started processes without blocking the main thread but am having the same problem with those too. Any ideas?
public class RunThread extends Thread {
@Override
public void run(){
// Create process with the ArrayList we populated above
ProcessBuilder pb = new ProcessBuilder(allArgs);
pb.redirectErrorStream(true);
// Clear the console
txtConsoleOutput.setText("");
// Try to start the process
try {
Process p = pb.start();
// Get the PID of the process we just started
pid = p.pid();
// Capture the output
String cmdOutput;
BufferedReader inputStream = new BufferedReader(new InputStreamReader(p.getInputStream()));
// Get stdOut/stdErr of the process and display in the console
while ((cmdOutput = inputStream.readLine()) != null) {
txtConsoleOutput.append(cmdOutput + "\n");
}
inputStream.close();
}
catch (IOException ex) {
JOptionPane.showMessageDialog(null,
"An error (" + ex + ") occurred while attempting to run.", AppName, JOptionPane.ERROR_MESSAGE);
}
// Clear the ArrayList so we can run again with a fresh set
allArgs.clear();
}
}
Update Based on the code provided by @ControlAltDel and the advice by @Holger, I've rewritten this to be thread safe (hopefully!), but the end result is the same.
SwingWorker <Void, String> RunTV = new SwingWorker <Void, String> () {
@Override
protected Void doInBackground() {
// Create process with the ArrayList we populated above
ProcessBuilder pb = new ProcessBuilder(allArgs);
pb.directory(new File(hacktvDirectory));
pb.redirectErrorStream(true);
// Try to start the process
try {
Process p = pb.start();
// Get the PID of the process we just started
pid = p.pid();
// Capture the output
DataFetcher df = new DataFetcher(p.getInputStream(), new byte[1024], 0);
FetcherListener fl = new FetcherListener() {
@Override
public void fetchedAll(byte[] bytes) {}
@Override
public void fetchedMore(byte[] bytes, int start, int end) {
publish(new String (bytes, start, end-start));
}
};
df.addFetcherListener(fl);
new Thread(df).start();
} catch (IOException ex) {
ex.printStackTrace();
}
return null;
} // End doInBackground
// Update the GUI from this method.
@Override
protected void done() {
// Revert button to say Run instead of Stop
changeStopToRun();
// Clear the ArrayList so we can run again with a fresh set
allArgs.clear();
}
// Update the GUI from this method.
@Override
protected void process(List<String> chunks) {
// Here we receive the values from publish() and display
// them in the console
for (String o : chunks) {
txtConsoleOutput.append(o);
txtConsoleOutput.repaint();
}
}
};
RunTV.execute();
}
Update 10/11/2020 Following the posts by kriegaex I took another look at this. The sample code did the same thing unfortunately, but their comment "If for example your sample program uses System.out.print() instead of println(), you will never see anything on the console because the output will be buffered." rang a bell with me.
I have access to the source for the program I'm wrapping and it's written in C. It has the following code to print the video resolution to the console:
void vid_info(vid_t *s)
{
fprintf(stderr, "Video: %dx%d %.2f fps (full frame %dx%d)\n",
s->active_width, s->conf.active_lines,
(double) s->conf.frame_rate_num / s->conf.frame_rate_den,
s->width, s->conf.lines
);
fprintf(stderr, "Sample rate: %d\n", s->sample_rate);
}
If I add fflush(stderr); underneath the second fprintf statement, I see these lines on the console, without modifying a thing in my own code. I still don't understand why it works in Linux without this, but at least I know the answer.