Java Linux Terminal in Jpanel

Java Linux Terminal in JPanel

This is a modification of my previous answer to a question about executing terminal commands from within a JTextArea, but preventing the user from modifying the previously outputted text...

This version adds the ability to send text to the running process

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringJoiner;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;

public class QuickTerminal {

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

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

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

public interface CommandListener {

public void commandOutput(String text);

public void commandCompleted(String cmd, int result);

public void commandFailed(Exception exp);
}

public class ConsolePane extends JPanel implements CommandListener, Terminal {

private JTextArea textArea;
private int userInputStart = 0;
private Command cmd;

public ConsolePane() {

cmd = new Command(this);

setLayout(new BorderLayout());
textArea = new JTextArea(20, 30);
((AbstractDocument) textArea.getDocument()).setDocumentFilter(new ProtectedDocumentFilter(this));
add(new JScrollPane(textArea));

InputMap im = textArea.getInputMap(WHEN_FOCUSED);
ActionMap am = textArea.getActionMap();

Action oldAction = am.get("insert-break");
am.put("insert-break", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
int range = textArea.getCaretPosition() - userInputStart;
try {
String text = textArea.getText(userInputStart, range).trim();
System.out.println("[" + text + "]");
userInputStart += range;
if (!cmd.isRunning()) {
cmd.execute(text);
} else {
try {
cmd.send(text + "\n");
} catch (IOException ex) {
appendText("!! Failed to send command to process: " + ex.getMessage() + "\n");
}
}
} catch (BadLocationException ex) {
Logger.getLogger(QuickTerminal.class.getName()).log(Level.SEVERE, null, ex);
}
oldAction.actionPerformed(e);
}
});

}

@Override
public void commandOutput(String text) {
SwingUtilities.invokeLater(new AppendTask(this, text));
}

@Override
public void commandFailed(Exception exp) {
SwingUtilities.invokeLater(new AppendTask(this, "Command failed - " + exp.getMessage()));
}

@Override
public void commandCompleted(String cmd, int result) {
appendText("\n> " + cmd + " exited with " + result + "\n");
appendText("\n");
}

protected void updateUserInputPos() {
int pos = textArea.getCaretPosition();
textArea.setCaretPosition(textArea.getText().length());
userInputStart = pos;

}

@Override
public int getUserInputStart() {
return userInputStart;
}

@Override
public void appendText(String text) {
textArea.append(text);
updateUserInputPos();
}
}

public interface UserInput {

public int getUserInputStart();
}

public interface Terminal extends UserInput {
public void appendText(String text);
}

public class AppendTask implements Runnable {

private Terminal terminal;
private String text;

public AppendTask(Terminal textArea, String text) {
this.terminal = textArea;
this.text = text;
}

@Override
public void run() {
terminal.appendText(text);
}
}

public class Command {

private CommandListener listener;
private ProcessRunner runner;

public Command(CommandListener listener) {
this.listener = listener;
}

public boolean isRunning() {

return runner != null && runner.isAlive();

}

public void execute(String cmd) {

if (!cmd.trim().isEmpty()) {

List<String> values = new ArrayList<>(25);
if (cmd.contains("\"")) {

while (cmd.contains("\"")) {

String start = cmd.substring(0, cmd.indexOf("\""));
cmd = cmd.substring(start.length());
String quote = cmd.substring(cmd.indexOf("\"") + 1);
cmd = cmd.substring(cmd.indexOf("\"") + 1);
quote = quote.substring(0, cmd.indexOf("\""));
cmd = cmd.substring(cmd.indexOf("\"") + 1);

if (!start.trim().isEmpty()) {
String parts[] = start.trim().split(" ");
values.addAll(Arrays.asList(parts));
}
values.add(quote.trim());

}

if (!cmd.trim().isEmpty()) {
String parts[] = cmd.trim().split(" ");
values.addAll(Arrays.asList(parts));
}

for (String value : values) {
System.out.println("[" + value + "]");
}

} else {

if (!cmd.trim().isEmpty()) {
String parts[] = cmd.trim().split(" ");
values.addAll(Arrays.asList(parts));
}

}

runner = new ProcessRunner(listener, values);

}

}

public void send(String cmd) throws IOException {
runner.write(cmd);
}
}

public class ProcessRunner extends Thread {

private List<String> cmds;
private CommandListener listener;

private Process process;

public ProcessRunner(CommandListener listener, List<String> cmds) {
this.cmds = cmds;
this.listener = listener;
start();
}

@Override
public void run() {
try {
System.out.println("cmds = " + cmds);
ProcessBuilder pb = new ProcessBuilder(cmds);
pb.redirectErrorStream();
process = pb.start();
StreamReader reader = new StreamReader(listener, process.getInputStream());
// Need a stream writer...

int result = process.waitFor();

// Terminate the stream writer
reader.join();

StringJoiner sj = new StringJoiner(" ");
cmds.stream().forEach((cmd) -> {
sj.add(cmd);
});

listener.commandCompleted(sj.toString(), result);
} catch (Exception exp) {
exp.printStackTrace();
listener.commandFailed(exp);
}
}

public void write(String text) throws IOException {
if (process != null && process.isAlive()) {
process.getOutputStream().write(text.getBytes());
process.getOutputStream().flush();
}
}
}

public class StreamReader extends Thread {

private InputStream is;
private CommandListener listener;

public StreamReader(CommandListener listener, InputStream is) {
this.is = is;
this.listener = listener;
start();
}

@Override
public void run() {
try {
int value = -1;
while ((value = is.read()) != -1) {
listener.commandOutput(Character.toString((char) value));
}
} catch (IOException exp) {
exp.printStackTrace();
}
}
}

public class ProtectedDocumentFilter extends DocumentFilter {

private UserInput userInput;

public ProtectedDocumentFilter(UserInput userInput) {
this.userInput = userInput;
}

public UserInput getUserInput() {
return userInput;
}

@Override
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException {
if (offset >= getUserInput().getUserInputStart()) {
super.insertString(fb, offset, string, attr);
}
}

@Override
public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
if (offset >= getUserInput().getUserInputStart()) {
super.remove(fb, offset, length); //To change body of generated methods, choose Tools | Templates.
}
}

@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
if (offset >= getUserInput().getUserInputStart()) {
super.replace(fb, offset, length, text, attrs); //To change body of generated methods, choose Tools | Templates.
}
}
}
}

So, I wrote myself a very simple Windows batch file...

@echo off
@echo Hello World!
set /p pathName=Enter The Value:%=%
@echo %pathName%

It doesn't do much, it outputs "Hello World!" and prompts the user to enter a value, which is further echoed to the screen and then terminates...

And used it to test the above code...

Terminal

Java Program that runs commands with Linux Terminal

You "could" read the process input and when you "detect" the password prompt, display a JOptionPane and request that the user enter the password.

You "could" prompt the user for the password before you start the process, knowing that you are going to need to send it to the process.

You would probably still need to monitor the output of the process to determine when you need to send the password though.

Let's start with...

Runtime.getRuntime().exec("sudo python ./flashimage.py");

You are ignoring the Process completely. Neither are you processing the output, but you have no means to provide input to the process...

Generally, Runtime#exec is problematic at best. You are far better of using ProcessBuilder....

// Build the command to be executed.  Note that each parameter becomes
// it's own argument, this deals with parameters that contain spaces
// much better then Runtime#exec alone...
ProcessBuilder pb = new ProcessBuilder("sudo", "python", "./flashimage.py");
pb.redirectError();

InputStream is = null;
try {
Process p = pb.start();
is = p.getInputStream();
StringBuilder output = new StringBuilder(80);
int in = -1;
while ((in = is.read()) != -1) {
if (in != '\n') {
output.append((char)in);
// You will need to define PASSWORD_PROMPT
if (PASSWORD_PROMPT.equals(output.toString())) {
String text = JOptionPane.showInputDialog("Password");
OutputStream os = p.getOutputStream();
os.write(text.getBytes());
}
} else {
System.out.println(output.toString());
output.delete(0, output.length());
}
}
} catch (IOException exp) {
exp.printStackTrace();
} finally {
try {
is.close();
} catch (Exception e) {
}
}

Now, undoubtedly, someone will point out (at least) two problems with this approach...

  1. JOptionPane.showInputDialog("Password"); will present a normal JTextField, which won't hide the password characters and
  2. String is not the safest way to to store a password...

Instead, we should use a JPasswordField and convert the resulting char array to a byte array...

JPasswordField password = new JPasswordField(10);
JLabel label = new JLabel("Password: ");
JPanel panel = new JPanel();
panel.add(label);
panel.add(password);

int option = JOptionPane.showConfirmDialog(null, panel, "Password", JOptionPane.OK_CANCEL_OPTION);
if (option == JOptionPane.OK_OPTION) {
char[] userPassword = password.getPassword();
byte[] bytes = new byte[userPassword.length * 2];
for (int i = 0; i < userPassword.length; i++) {
bytes[i * 2] = (byte) (userPassword[i] >> 8);
bytes[i * 2 + 1] = (byte) userPassword[i];
}
os.write(bytes);
}

jTextArea as IO console

Okay, so this is my idea...

The basic idea is we want to keep track of "user" input and "process" output.

Basically what I've done is set it up so that when the process terminates, we calculate the current position of the caret in the document and mark that as the start position of the user input.

This example doesn't include writing output to the process, you can slide that in ;)

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.text.BadLocationException;
import org.w3c.dom.ls.LSException;

public class QuickTerminal {

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

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

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

});
}

public interface CommandListener {

public void commandOutput(String text);

public void commandCompleted(String cmd, int result);

public void commandFailed(Exception exp);

}

public class ConsolePane extends JPanel implements CommandListener {

private JTextArea textArea;
private int userInputStart = 0;
private Command cmd;

public ConsolePane() {

cmd = new Command(this);

setLayout(new BorderLayout());
textArea = new JTextArea(20, 30);
add(new JScrollPane(textArea));

textArea.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
int range = textArea.getCaretPosition() - userInputStart;
try {
String text = textArea.getText(userInputStart, range).trim();
System.out.println("[" + text + "]");
userInputStart += range;
if (!cmd.isRunning()) {
cmd.execute(text);
} else {
}
} catch (BadLocationException ex) {
Logger.getLogger(QuickTerminal.class.getName()).log(Level.SEVERE, null, ex);
}
} else {
// if (!cmd.isRunning()) {
// cmd.send(...);
// }
}
}

});
}

@Override
public void commandOutput(String text) {
SwingUtilities.invokeLater(new AppendTask(textArea, text));
}

@Override
public void commandFailed(Exception exp) {
SwingUtilities.invokeLater(new AppendTask(textArea, "Command failed - " + exp.getMessage()));
}

@Override
public void commandCompleted(String cmd, int result) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
int pos = textArea.getCaretPosition();

System.out.println("pos = " + pos + "; length = " + textArea.getText().length());

textArea.setCaretPosition(textArea.getText().length());
userInputStart = pos;
}
});
}

}

public class AppendTask implements Runnable {

private JTextArea textArea;
private String text;

public AppendTask(JTextArea textArea, String text) {
this.textArea = textArea;
this.text = text;
}

@Override
public void run() {
textArea.append(text);
}

}

public class Command {

private CommandListener listener;

private ProcessRunner runner;

public Command(CommandListener listener) {
this.listener = listener;
}

public boolean isRunning() {

return runner != null && runner.isAlive();

}

public void execute(String cmd) {

if (!cmd.trim().isEmpty()) {


List<String> values = new ArrayList<>(25);
if (cmd.contains("\"")) {

while (cmd.contains("\"")) {

String start = cmd.substring(0, cmd.indexOf("\""));
cmd = cmd.substring(start.length());
String quote = cmd.substring(cmd.indexOf("\"") + 1);
cmd = cmd.substring(cmd.indexOf("\"") + 1);
quote = quote.substring(0, cmd.indexOf("\""));
cmd = cmd.substring(cmd.indexOf("\"") + 1);

if (!start.trim().isEmpty()) {
String parts[] = start.trim().split(" ");
values.addAll(Arrays.asList(parts));
}
values.add(quote.trim());

}

if (!cmd.trim().isEmpty()) {
String parts[] = cmd.trim().split(" ");
values.addAll(Arrays.asList(parts));
}

for (String value : values) {
System.out.println("[" + value + "]");
}

} else {

if (!cmd.trim().isEmpty()) {
String parts[] = cmd.trim().split(" ");
values.addAll(Arrays.asList(parts));
}

}

runner = new ProcessRunner(listener, values);

}

}

public void send(String cmd) {
// Send user input to the running process...
}

}

public class ProcessRunner extends Thread {

private List<String> cmds;
private CommandListener listener;

public ProcessRunner(CommandListener listener, List<String> cmds) {
this.cmds = cmds;
this.listener = listener;
start();
}

@Override
public void run() {
try {
System.out.println("cmds = " + cmds);
ProcessBuilder pb = new ProcessBuilder(cmds);
pb.redirectErrorStream();
Process p = pb.start();
StreamReader reader = new StreamReader(listener, p.getInputStream());
// Need a stream writer...

int result = p.waitFor();

// Terminate the stream writer
reader.join();

listener.commandCompleted(null, result);
} catch (Exception exp) {
exp.printStackTrace();
}
}

}

public class StreamReader extends Thread {

private InputStream is;
private CommandListener listener;

public StreamReader(CommandListener listener, InputStream is) {
this.is = is;
this.listener = listener;
start();
}

@Override
public void run() {
try {
int value = -1;
while ((value = is.read()) != -1) {
listener.commandOutput(Character.toString((char) value));
}
} catch (IOException exp) {
exp.printStackTrace();
}
}

}

}

PS- I was running this on my Mac, so you might need to put in a call to "cmd" for Windows... ;)

PPS- This is an incomplete example, if it wasn't 1am I might fill it in, but, this doesn't stop the user from pressing back space beyond the last known user input position. If I was going to fix this, I would use a DocumentFilter and simply "protect" all the text before the user position, disallowing the user from removing it

Updated with "Protected DocumentFilter" example

Added a "protected" DocumentFilter to protected areas of the Document that the user should no longer be allowed to edit.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;

public class QuickTerminal {

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

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

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

public interface CommandListener {

public void commandOutput(String text);

public void commandCompleted(String cmd, int result);

public void commandFailed(Exception exp);
}

public class ConsolePane extends JPanel implements CommandListener, UserInput {

private JTextArea textArea;
private int userInputStart = 0;
private Command cmd;

public ConsolePane() {

cmd = new Command(this);

setLayout(new BorderLayout());
textArea = new JTextArea(20, 30);
((AbstractDocument)textArea.getDocument()).setDocumentFilter(new ProtectedDocumentFilter(this));
add(new JScrollPane(textArea));

textArea.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
int range = textArea.getCaretPosition() - userInputStart;
try {
String text = textArea.getText(userInputStart, range).trim();
System.out.println("[" + text + "]");
userInputStart += range;
if (!cmd.isRunning()) {
cmd.execute(text);
} else {
}
} catch (BadLocationException ex) {
Logger.getLogger(QuickTerminal.class.getName()).log(Level.SEVERE, null, ex);
}
} else {
// if (!cmd.isRunning()) {
// cmd.send(...);
// }
}
}
});
}

@Override
public void commandOutput(String text) {
SwingUtilities.invokeLater(new AppendTask(textArea, text));
}

@Override
public void commandFailed(Exception exp) {
SwingUtilities.invokeLater(new AppendTask(textArea, "Command failed - " + exp.getMessage()));
}

@Override
public void commandCompleted(String cmd, int result) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
int pos = textArea.getCaretPosition();

System.out.println("pos = " + pos + "; length = " + textArea.getText().length());

textArea.setCaretPosition(textArea.getText().length());
userInputStart = pos;
}
});
}

@Override
public int getUserInputStart() {
return userInputStart;
}
}

public interface UserInput {

public int getUserInputStart();
}

public class AppendTask implements Runnable {

private JTextArea textArea;
private String text;

public AppendTask(JTextArea textArea, String text) {
this.textArea = textArea;
this.text = text;
}

@Override
public void run() {
textArea.append(text);
}
}

public class Command {

private CommandListener listener;
private ProcessRunner runner;

public Command(CommandListener listener) {
this.listener = listener;
}

public boolean isRunning() {

return runner != null && runner.isAlive();

}

public void execute(String cmd) {

if (!cmd.trim().isEmpty()) {


List<String> values = new ArrayList<>(25);
if (cmd.contains("\"")) {

while (cmd.contains("\"")) {

String start = cmd.substring(0, cmd.indexOf("\""));
cmd = cmd.substring(start.length());
String quote = cmd.substring(cmd.indexOf("\"") + 1);
cmd = cmd.substring(cmd.indexOf("\"") + 1);
quote = quote.substring(0, cmd.indexOf("\""));
cmd = cmd.substring(cmd.indexOf("\"") + 1);

if (!start.trim().isEmpty()) {
String parts[] = start.trim().split(" ");

Related Topics



Leave a reply



Submit