2

EDIT: The code now works! Here's how I did it:

package me.nrubin29.jterminal;

import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.*;
import java.util.ArrayList;

public class JTerminal extends JFrame {

    private JTextPane area = new JTextPane();
    private JTextField input = new JTextField("Input");

    private SimpleAttributeSet inputSAS = new SimpleAttributeSet(), output = new SimpleAttributeSet(), error = new SimpleAttributeSet();

    private File workingFolder = FileSystemView.getFileSystemView().getDefaultDirectory();

    public JTerminal() throws IOException {
        super("JTerminal");

        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        StyleConstants.setForeground(inputSAS, Color.GREEN);
        StyleConstants.setBackground(inputSAS, Color.BLACK);

        StyleConstants.setForeground(output, Color.WHITE);
        StyleConstants.setBackground(output, Color.BLACK);

        StyleConstants.setForeground(error, Color.RED);
        StyleConstants.setBackground(error, Color.BLACK);

        input.addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    try {
                        String command = input.getText();
                        if (command.equals("")) return;

                        setTitle("JTerminal (" + command.split(" ")[0] + ")");

                        input.setText("");
                        input.setEditable(false);

                        write(inputSAS, command);

                        Process bash = new ProcessBuilder("bash").directory(workingFolder).start();

                        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(bash.getOutputStream());
                        outputStreamWriter.write(command);
                        outputStreamWriter.close();

                        int code = bash.waitFor();

                        writeStream(bash.getErrorStream(), error);
                        writeStream(bash.getInputStream(), output);

                        input.setEditable(true);
                        setTitle("JTerminal");

                        if (code == 0 && command.split(" ").length > 1) workingFolder = new File(command.split(" ")[1]);

                    } catch (Exception ex) { error(ex); }
                }
            }

            public void keyTyped(KeyEvent e) {}
            public void keyReleased(KeyEvent e) {}
        });

        area.setBackground(Color.black);
        area.setCaretColor(Color.green);
        area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        area.setEditable(false);

        JScrollPane pane = new JScrollPane(area);
        pane.setBorder(BorderFactory.createLineBorder(Color.GREEN));
        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        pane.setPreferredSize(new Dimension(640, 460));

        input.setBackground(Color.black);
        input.setForeground(Color.green);
        input.setCaretColor(Color.green);
        input.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        input.setBorder(BorderFactory.createLineBorder(Color.GREEN));

        add(pane);
        add(input);

        Dimension DIM = new Dimension(640, 480);
        setPreferredSize(DIM);
        setSize(DIM);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(true);
        pack();
        setVisible(true);

        input.requestFocus();
    }

    public static void main(String[] args) throws IOException {
        new JTerminal();
    }

    private void write(SimpleAttributeSet attributeSet, String... lines) {
        try {
            if (lines.length == 0) return;
            for (String line : lines) {
                area.getStyledDocument().insertString(area.getStyledDocument().getLength(), line + "\n", attributeSet);
            }
            area.getStyledDocument().insertString(area.getStyledDocument().getLength(), "\n", attributeSet);
        }
        catch (Exception e) { error(e); }
    }

    private void error(Exception e) {
        write(error, "An error has occured: " + e.getLocalizedMessage());
        e.printStackTrace(); //TODO: temp.
    }

    private void writeStream(InputStream s, SimpleAttributeSet color) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(s));

            ArrayList<String> strs = new ArrayList<String>();

            while(reader.ready()) strs.add(reader.readLine());

            if (strs.size() > 0) write(color, strs.toArray(new String[strs.size()]));
        }
        catch (Exception e) { error(e); }
    }
}

I have been working on a Java terminal application. It works except that it only prints the output of the first command. Here's a picture of the GUI when I try to run ls more than once.

JTerminal GUI

package me.nrubin29.jterminal;

import javax.swing.*;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.*;
import java.util.ArrayList;

public class JTerminal extends JFrame {

    private JTextPane area = new JTextPane();
    private JTextField input = new JTextField("Input");

    private SimpleAttributeSet inputSAS = new SimpleAttributeSet(), output = new SimpleAttributeSet(), error = new SimpleAttributeSet();

    public JTerminal() throws IOException {
        super("JTerminal");

        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        StyleConstants.setForeground(inputSAS, Color.GREEN);
        StyleConstants.setBackground(inputSAS, Color.BLACK);

        StyleConstants.setForeground(output, Color.WHITE);
        StyleConstants.setBackground(output, Color.BLACK);

        StyleConstants.setForeground(error, Color.RED);
        StyleConstants.setBackground(error, Color.BLACK);

        final Process bash = new ProcessBuilder("/bin/bash").start();

        input.addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    try {
                        String command = input.getText();
                        if (command.equals("")) return;

                        setTitle("JTerminal (" + command.split(" ")[0] + ")");

                        input.setText("");
                        input.setEditable(false);

                        write(inputSAS, command);

                        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(bash.getOutputStream());
                        outputStreamWriter.write(command);
                        outputStreamWriter.close();

                        bash.waitFor();

                        writeStream(bash.getErrorStream(), error);
                        writeStream(bash.getInputStream(), output);

                        input.setEditable(true);
                        setTitle("JTerminal");

                    } catch (Exception ex) { error(ex); }
                }
            }

            public void keyTyped(KeyEvent e) {}
            public void keyReleased(KeyEvent e) {}
        });

        area.setBackground(Color.black);
        area.setCaretColor(Color.green);
        area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        area.setEditable(false);

        JScrollPane pane = new JScrollPane(area);
        pane.setBorder(BorderFactory.createLineBorder(Color.GREEN));
        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        pane.setPreferredSize(new Dimension(640, 460));

        input.setBackground(Color.black);
        input.setForeground(Color.green);
        input.setCaretColor(Color.green);
        input.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        input.setBorder(BorderFactory.createLineBorder(Color.GREEN));

        add(pane);
        add(input);

        Dimension DIM = new Dimension(640, 480);
        setPreferredSize(DIM);
        setSize(DIM);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(true);
        pack();
        setVisible(true);

        input.requestFocus();
    }

    public static void main(String[] args) throws IOException {
        new JTerminal();
    }

    private void write(SimpleAttributeSet attributeSet, String... lines) {
        try {
            area.getStyledDocument().insertString(area.getStyledDocument().getLength(), "\n", attributeSet);
            for (String line : lines) {
                area.getStyledDocument().insertString(area.getStyledDocument().getLength(), line + "\n", attributeSet);
            }
        }
        catch (Exception e) { error(e); }
    }

    private void error(Exception e) {
        write(error, "An error has occured: " + e.getLocalizedMessage());
        e.printStackTrace(); //TODO: temp.
    }

    private void writeStream(InputStream s, SimpleAttributeSet color) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(s));

            ArrayList<String> strs = new ArrayList<String>();

            while(reader.ready()) strs.add(reader.readLine());

            if (strs.size() > 0) write(color, strs.toArray(new String[strs.size()]));
        }
        catch (Exception e) { error(e); }
    }
}
3
  • Wanted to test this, but apparently it doesn't work on Mac. Have you tried debugging it to see where data is lost? Commented Aug 27, 2013 at 22:00
  • Oh well, I may have missing JARs then. Go ahead and debug it. Commented Aug 27, 2013 at 22:04
  • @GGrec Should work on a Mac, tested my solution using one... Commented Aug 27, 2013 at 22:19

2 Answers 2

1

A Process object can be used only once, so subsequent calls to Process.waitFor() just immediately return (as the process is already terminated). Instead you have to request a new Process each time from your ProcessBuilder.

Here is the correct code:

package me.nrubin29.jterminal;
import javax.swing.*;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.*;
import java.util.ArrayList;

public class JTerminal extends JFrame {

    private JTextPane area = new JTextPane();
    private JTextField input = new JTextField("Input");

    private SimpleAttributeSet inputSAS = new SimpleAttributeSet(), output = new SimpleAttributeSet(), error = new SimpleAttributeSet();

    public JTerminal() throws IOException {
        super("JTerminal");

        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        StyleConstants.setForeground(inputSAS, Color.GREEN);
        StyleConstants.setBackground(inputSAS, Color.BLACK);

        StyleConstants.setForeground(output, Color.WHITE);
        StyleConstants.setBackground(output, Color.BLACK);

        StyleConstants.setForeground(error, Color.RED);
        StyleConstants.setBackground(error, Color.BLACK);

        final ProcessBuilder builder = new ProcessBuilder("/bin/bash");

        input.addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    try {
                        String command = input.getText();
                        if (command.equals("")) return;

                        setTitle("JTerminal (" + command.split(" ")[0] + ")");

                        input.setText("");
                        input.setEditable(false);

                        write(inputSAS, command);

                                Process bash = builder.start();
                        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(bash.getOutputStream());
                        outputStreamWriter.write(command);
                        outputStreamWriter.close();

                        bash.waitFor();

                        writeStream(bash.getErrorStream(), error);
                        writeStream(bash.getInputStream(), output);

                        input.setEditable(true);
                        setTitle("JTerminal");

                    } catch (Exception ex) { error(ex); }
                }
            }

            public void keyTyped(KeyEvent e) {}
            public void keyReleased(KeyEvent e) {}
        });

        area.setBackground(Color.black);
        area.setCaretColor(Color.green);
        area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        area.setEditable(false);

        JScrollPane pane = new JScrollPane(area);
        pane.setBorder(BorderFactory.createLineBorder(Color.GREEN));
        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        pane.setPreferredSize(new Dimension(640, 460));

        input.setBackground(Color.black);
        input.setForeground(Color.green);
        input.setCaretColor(Color.green);
        input.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        input.setBorder(BorderFactory.createLineBorder(Color.GREEN));

        add(pane);
        add(input);

        Dimension DIM = new Dimension(640, 480);
        setPreferredSize(DIM);
        setSize(DIM);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(true);
        pack();
        setVisible(true);

        input.requestFocus();
    }

    public static void main(String[] args) throws IOException {
        new JTerminal();
    }

    private void write(SimpleAttributeSet attributeSet, String... lines) {
        try {
            area.getStyledDocument().insertString(area.getStyledDocument().getLength(), "\n", attributeSet);
            for (String line : lines) {
                area.getStyledDocument().insertString(area.getStyledDocument().getLength(), line + "\n", attributeSet);
            }
        }
        catch (Exception e) { error(e); }
    }

    private void error(Exception e) {
        write(error, "An error has occured: " + e.getLocalizedMessage());
        e.printStackTrace(); //TODO: temp.
    }

    private void writeStream(InputStream s, SimpleAttributeSet color) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(s));

            ArrayList<String> strs = new ArrayList<String>();

            while(reader.ready()) strs.add(reader.readLine());

            if (strs.size() > 0) write(color, strs.toArray(new String[strs.size()]));
        }
        catch (Exception e) { error(e); }
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

Or you can set the ProcessBuilder's working directory after each command: docs.oracle.com/javase/6/docs/api/java/lang/…
On the right track, but I'd be concerned about the bash.waitFor, once it returns, it would indicate that the process has exited. Also, waitingFor should be done AFTER reading the process input...
@Menthos "Or you can set the ProcessBuilder's working directory after each command:" - Would still require you to create a new Process for each new command, defeating the purpose...
@MadProgrammer "Also, waitingFor should be done AFTER reading the process input..." Yes that's true, and especially because you can't rely on the output stream buffers that are system dependent. On the other hand correctly handling this is way out of the questions scope :)
@Menthos Possibly, but making a comment about in your answer may alleviate the OP from coming back to you and asking "why" something else doesn't work ;)
1

Once a process has exited, it can not be "read" from or "written" to.

You're code will also block on the "waitFor" method, meaning that your UI won't be updated until the process has completed running. This is really helpful...

Instead, you need to start the process and allow it to continue running, monitoring the state in the background.

Because Swing is a single threaded environment, you need to be careful about how you handle long running/blocking processes and updates to the UI.

To this end, I've used a SwingWorker to read the output of the Process in a background thread and re-sync the updates back to the Event Dispatching Thread. This allows the UI to continue running and remain responsive while the output from the running process is read in...

Also, instead of "running" a new command each time, creating a new process, you will need to write to the Process's input put stream.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestTerminal {

    public static void main(String[] args) {
        new TestTerminal();
    }

    public TestTerminal() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private JTextArea output;
        private JTextField input;

        private Process process;

        public TestPane() {
            setLayout(new BorderLayout());

            output = new JTextArea(20, 20);
            input = new JTextField(10);

            output.setLineWrap(false);
            output.setWrapStyleWord(false);
            output.setEditable(false);
            output.setFocusable(false);

            add(new JScrollPane(output));
            add(input, BorderLayout.SOUTH);

            input.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    String cmd = input.getText() + "\n";
                    input.setText(null);
                    output.append("\n" + cmd + "\n\n");
                    if (process == null) {
                        ProcessBuilder pb = new ProcessBuilder("bash");
                        pb.directory(new File("."));
                        try {
                            process = pb.start();
                            InputStreamWorker isw = new InputStreamWorker(output, process.getInputStream());
                            isw.execute();
                        } catch (IOException ex) {
                            ex.printStackTrace();
                            input.setEnabled(false);
                        }

                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                int exit = -1;
                                try {
                                    exit = process.waitFor();
                                } catch (InterruptedException ex) {
                                }
                                System.out.println("Exited with " + exit);
                                input.setEnabled(false);
                            }
                        }).start();

                    }
                    OutputStream os = process.getOutputStream();
                    try {
                        os.write(cmd.getBytes());
                        os.flush();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                        input.setEnabled(false);
                    }
                }
            });
        }

    }

    public class InputStreamWorker extends SwingWorker<Void, Character> {

        private InputStream is;
        private JTextArea output;

        public InputStreamWorker(JTextArea output, InputStream is) {
            this.is = is;
            this.output = output;
        }

        @Override
        protected void process(List<Character> chunks) {
            StringBuilder sb = new StringBuilder(chunks.size());
            for (Character c : chunks) {
                sb.append(c);
            }
            output.append(sb.toString());
        }

        @Override
        protected Void doInBackground() throws Exception {
            int in = -1;
            while ((in = is.read()) != -1) {
                publish((char)in);
            }
            return null;
        }

    }

}

I would also recommend that you avoid KeyListener where you can. JTextField utilises a ActionListener, which will be called when ever the user presses the "action" key, what ever that might be for the given platform...

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.