How to read ProcessBuilder's output stream in controlP5's console in the sketch?

Intent: Run a shell command from sketch on a keyPress event and show the command’s output in Sketch window.

Idea: Use controlp5 GUI library to draw a console in sketch and use println() to print output.

Sketch:

import java.io.InputStreamReader;

import controlP5.*;
ControlP5 cp5;

Textarea myTextarea;
Println console;

String cmd[] = {"ping", "-c", "3", "www.bing.com"};

void setup() {
  size(640, 480);
  background(25);

  cp5 = new ControlP5(this);
  cp5.enableShortcuts();

  myTextarea = cp5.addTextarea("txt")
    .setPosition(10, 10)
    .setSize(width - 10*2, height-10*2)
    .setColor(color(80, 90, 90))
    .setColorBackground(color(#1D1F21))
    .setColorForeground(color(#F0C674))
    ;
  console = cp5.addConsole(myTextarea);
}

void draw() {
  background(25);

  if (run) {
    println("Running command ...\n");
    // Print the command to show before running
    StringBuffer cmd_buffer = new StringBuffer();
    for (int i = 0; i < cmd.length; i++) {
      cmd_buffer.append(cmd[i]+" ");
    }
    println(cmd_buffer.toString());


    //--------
    ProcessBuilder pb = new ProcessBuilder(cmd);
    pb.inheritIO();
    try {
      Process process = pb.start();
      InputStream inputStream = process.getInputStream();
      BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
      String res = "";
      while ((res = reader.readLine()) != null) {
        // This part get's printed in the Procesisng IDE's console, why?
        println(res);
      }
      int exitVal = process.waitFor();
      // While this part get's printed in the Procesisng Sketch's console (as made to run)
      println(exitVal);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
    //--------

    run = false;
  }
}


boolean run;
void keyPressed() {
  if (key == 'r') {
    run = true;
  }
}

The questions are in then comments.
But basically some outputs are rendered in the sketch console (the end result, after the command finishes running), while the rest of the result output (while the command is running) is rendered in the Processing’s IDE console.

Not a java ninja clearly, so some pointers here would really help. :sweat_smile:

I was unable to redirect Process Builder output to the controlP5 textArea. My conclusion is that the IDE console is the default output unless there is code to redirect the output. It will send the output there even if println(res) is not used (REMMed out). Unfortunately I could only redirect the output to a file and not a widget. The work around below will send the output to a file in the sketch folder, then load the string lines back into a Processing window. Someone else may know a more correct way to get output redirected to a widget. This was done on a Mac; you will have to change the sketch path to fit your system. You should see a current calendar in the Processing window. It’s possible that a Windows system would use a different command string to get a current calendar.

import java.io.*;

String[] lines;
PFont font;

void setup() {
  size(400, 400);
  String[] commands = {"/bin/sh", "-c", "cal"};
  try {
    ProcessBuilder pb = new ProcessBuilder(commands);
    pb.redirectErrorStream(true);
    pb.redirectOutput(new File("/Users/yourName/Documents/Processing/ProcessBuilder_example/cal.txt"));
    Process p = pb.start();
    BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
    String line = null;
    StringBuilder output = new StringBuilder();
    while ((line = reader.readLine()) != null) {
      output.append(line);
    }
    int exitVal = p.waitFor();
    println("exit value = ",exitVal);
  }
  catch(Exception e) {
    e.printStackTrace();
  }
  lines = loadStrings("cal.txt");
}

void draw() {
  fill(0);
  font = createFont("andale mono", 18);
  textFont(font);
  for(int x = 0; x < lines.length; x++){
    text(lines[x], 30, 30 + 25*x);   
  }
}

cal

@svan I think there can be better ways than hardcoding a “log file’s path” (sort of) as you have shown here. But anyways, it’s one way, if you are looking at grabbing response just once and the response is simple in terms of output structure.

Also I’d be careful of importing all the io libs:
import java.io.*;
As to my experience if say, you are using some other libs or functions from processing environment, it sometimes creates conflicts (especially if you also have serial library).

So I better import just the ones that I would need, in this case just the InputStreamReader class as we need to get re-direction outputs.

I basically gave up on ProcessBuilder and tried using just Process .

Note:
There are some solutions that need to be there to be more generalised. Mine is tailored towards using controlP5’s console class that re-directs println() statements to the TextArea. (That was my goal from the beginning)

There are some gotchas that bothered me a bit and are still not clear (Although I found a bandaid solution). You can see them in my comments :thinking:. If anyone has any ideas, please let me know.

Here’s the sketch I got working so far.

import java.io.InputStreamReader;

import controlP5.*;
ControlP5 cp5;

Textarea myTextarea;
Println console;

String cmd[] = {"ping", "-c", "2", "www.bing.com"};
//String cmd[] = {"python3", "-V"};

void setup() {
  size(640, 480);
  background(25);

  cp5 = new ControlP5(this);

  myTextarea = cp5.addTextarea("txt")
    .setPosition(10, 10)
    .setSize(width - 10*2, height-10*2)
    .setColor(color(80, 90, 90))
    .setColorBackground(color(#1D1F21))
    .setColorForeground(color(#F0C674))
    ;
  console = cp5.addConsole(myTextarea);
}


// boolean run = false;
// String newOutputLine = null;

void draw() {
  background(25);

  //if (newOutputLine != null) {
  //  println(newOutputLine);
  //  newOutputLine = null;
  //}
}


void keyPressed() {
  if (key == 'r') {
    thread("run_cmd");
  }
}

void run_cmd() {
  /*
   Don't know why I had to instantiate console here again. But this came out of trail and error.
   Or else if we use println(), in this threaded fucntion, then we can only call the thread once,
   or basicaly I don't know if the thread is called again, but I do not see any text output in the
   sketch console.
   */
  console = cp5.addConsole(myTextarea);

  // 1. Print the command string
  StringBuffer cmd_buffer = new StringBuffer();
  for (int i = 0; i < cmd.length; i++) {
    cmd_buffer.append(cmd[i]+" ");
  }
  println("\nCMD:\t" + cmd_buffer.toString() + "\n");
  //newOutputLine = "\nCMD:\t" + cmd_buffer.toString() + "\n";

  // 2. Run the command
  try {
    Process p = Runtime.getRuntime().exec(cmd);
    // 3. Create a buffer reader to capture input stream. (Note: we are not capturing error stream
    // but it can be done)
    BufferedReader buff = new BufferedReader(new InputStreamReader(p.getInputStream()));
    String stdIn = null;
    // 4. Read a line and if it's not null, print it.  
    while ((stdIn = buff.readLine()) != null) {
      println(stdIn.toString());
      //newOutputLine = stdIn.toString();
    }
    // 5. Check the exit code to be 100% sure, the command ran successfully (exitCode 0) 
    int exitVal = p.waitFor();
    println("EXIT CODE:\t", str(exitVal));
    //newOutputLine = "EXIT CODE:\t" + str(exitVal);
    buff.close();
  }
  catch (Exception e) {
    println("Some exception happened!\n");
    //newOutputLine = "some exception happened!";
    //e.printStackTrace();
  }
}

Big improvement; looks like using just exec() is the way to go. The readability on my Mac was pretty poor. For whatever it’s worth, this is what I did to improve it. Thanks for posting.

void setup() {
  size(640, 480);
  background(209);
  cp5 = new ControlP5(this);
  myTextarea = cp5.addTextarea("txt")
    .setPosition(10, 10)
    .setSize(width - 10*2, height-10*2)
    .setFont(createFont("menlo-bold",12))
    .setLineHeight(14)
    .setColor(color(0))
    .setColorBackground(color(209))
    .setColorForeground(color(0))
    ;
  console = cp5.addConsole(myTextarea);
}

void draw() {
}

If you use an empty draw(), ie don’t draw the background again you only need one console = cp5.addConsole();

Addendum: If I try this command String cmd[] = {"/bin/sh", “-c”, “cal”};, I get this error:StringIndexOutOfBoundsException: begin 0, end -1, length 0

Due to the StringIndexOutOfBoundsException with String cmd[] = {"/bin/sh", “-c”, “cal”}; I looked for another solution and found this reference: https://www.codejava.net/java-se/swing/redirect-standard-output-streams-to-jtextarea which uses a JTextArea added directly to the window. This is also possible in Processing and the following is a modification of the published demo posted on that site. A CustomOutputStream class is used to divert the PrintStream to the JTextArea, obviating the StringIndexOutOfBoundsException.

/*
https://www.codejava.net/java-se/swing/redirect-standard-output-streams-to-jtextarea
*/

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import javax.swing.text.BadLocationException;

javax.swing.JFrame frame;
java.awt.Canvas canvas;

JTextArea txtArea;
PrintStream standardOut;

//String cmd[] = {"ping", "-c", "3", "www.bing.com"};
String cmd[] = {"/bin/sh", "-c", "cal"};

int _wndW = 500;
int _wndH = 320;

class CustomOutputStream extends OutputStream {
  private JTextArea textArea;

  public CustomOutputStream(JTextArea textArea) {
    this.textArea = textArea;
  }

  @Override
    public void write(int b) throws IOException {
    // redirects data to the text area
    txtArea.append(String.valueOf((char) b));
    // scrolls the text area to the end of data
    txtArea.setCaretPosition(txtArea.getDocument().getLength());
  }
}

void runAction() {

  try {
    Process proc = Runtime.getRuntime().exec(cmd);
    BufferedReader out = new BufferedReader(new InputStreamReader(proc.getInputStream()));
    BufferedReader err = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
    PrintStream printStream = new PrintStream(new CustomOutputStream(txtArea));
    String s = "";
    while ((s = out.readLine()) != null) {
      // keeps reference of standard output stream
      standardOut = System.out;
      // re-assigns standard output stream and error output stream
      System.setOut(printStream);
      System.setErr(printStream);
      println(s);
    }
    while ((s = err.readLine()) != null) {
      println(s);
    }
    int exitVal = proc.waitFor();
    println("EXIT CODE:\t", str(exitVal));
  }
  catch (Exception e) {
    println("Some exception happened!\n");
  }
}

void clearAction() {
  try {
    txtArea.getDocument().remove(0, txtArea.getDocument().getLength());
  }
  catch (BadLocationException ex) {
    println("Bad location exception\n");
  }
}

void textArea() {
  txtArea = new JTextArea();
  JScrollPane scrlPane = new JScrollPane(txtArea);
  scrlPane.setBounds(10, 10, _wndW - 20, 220);
  frame.add(scrlPane);
  txtArea.setEditable(true);
  txtArea.setToolTipText("JTextArea");
  //txtArea.setFont(new Font("Menlo", Font.BOLD, 16));
  txtArea.setLineWrap(false);
  txtArea.setWrapStyleWord(true);
  txtArea.repaint();
}

void runBtn() {
  JButton btn = new JButton("Run");
  btn.setBounds(50, 240, 60, 24);
  frame.add(btn);
  // **** Action **** //
  btn.addActionListener( new ActionListener() {
    void actionPerformed(ActionEvent actionEvent) {
      runAction();
    }
  }
  );
  btn.repaint();
}

void clearBtn() {
  JButton btn = new JButton("Clear");
  btn.setBounds(150, 240, 70, 24);
  frame.add(btn);
  // **** Action **** //
  btn.addActionListener( new ActionListener() {
    void actionPerformed(ActionEvent actionEvent) {
      clearAction();
    }
  }
  );
  btn.repaint();
}

void buildWnd() {
  textArea();
  runBtn();
  clearBtn();
}

void setup() {
  frame = (javax.swing.JFrame) ((processing.awt.PSurfaceAWT.SmoothCanvas) surface.getNative()).getFrame();
  canvas = (processing.awt.PSurfaceAWT.SmoothCanvas) ((processing.awt.PSurfaceAWT)surface).getNative();
  frame.setBounds(500, 300, _wndW, _wndH);
  frame.remove(canvas);

  javax.swing.SwingUtilities.invokeLater(new Runnable() {
    public void run() {
      buildWnd(); // Builds components on EventDispatchThread
    }
  }
  );
}

1 Like