How can I redirect System.in to JavaFX TextField?

2k views Asked by At

I am working on new project. I am trying to send commands to external computer (Linux) through ethernet from Java. I am using Jsch to create shell connection. I set shell input and output to System.out and System.in.

((ChannelShell)channel).setInputStream(System.in);
((ChannelShell)channel).setOutputStream(System.out);

It works in console! But I need to remote it from javafx GUI app. I have already solved redirecting of System.out to TextArea:

    public void redirectOutputStream() {
        OutputStream out = new OutputStream() {

            private final StringBuilder sb = new StringBuilder();

            @Override
            public void write(int b) throws IOException {
                if (b == '\r') {
                    return;
                }

                if (b == '\n') {
                    final String tmp = sb.toString() + "\n";
                    text.add(tmp);
                    updateText(text);
                    sb.setLength(0);
                } else {
                    sb.append((char) b);
                }
            }
        };

        System.setOut(new PrintStream(out, true));
        // System.setErr(new PrintStream(out, true));
    }

But now I need also to redirect System.in to TextField, so that I can write something into TextField, press enter and send it through shell to the external computer.

Any help would be appreciate. Thanks!

EDIT: Sorry, still doesn't work for me :( ... Now I have this piece of code (I am using javafx):

/** Tmp queue for standard input redirecting */
BlockingQueue<Integer> stdInQueue = new LinkedBlockingQueue<>();



 @Override
    public void initialize(URL arg0, ResourceBundle arg1) {
        redirectOutputStream();
        redirectInputStream();
}


/** redirects standard System.out to GUI command_window */
public void redirectOutputStream() {
    OutputStream out = new OutputStream() {

        private final StringBuilder sb = new StringBuilder();

        @Override
        public void write(int b) throws IOException {
            if (b == '\r') {
                return;
            }
            if (b == '\n') {
                final String tmp = sb.toString() + "\n";
                text.add(tmp);
                updateText(text);
                sb.setLength(0);
            } else {
                sb.append((char) b);
            }
        }
    };
    System.setOut(new PrintStream(out, true));
    System.setErr(new PrintStream(out, true));
}

/** redirects standard System.in to GUI command_line */
public void redirectInputStream() {
    InputStream in = new InputStream() {

        @Override
        public int read() throws IOException {
            try {
                int c = stdInQueue.take().intValue();
                return c;
            } catch (InterruptedException exc) {
                Thread.currentThread().interrupt();
                return -1;
            }
        }
    };
    System.setIn(in);
}


@FXML
    void sendButtonPressed(ActionEvent event) {
        if (!command_line.getText().isEmpty()) {
            for (char c : command_line.getText().toCharArray()) {
                System.out.write(new Integer(c)); //display in ListView (output)
                stdInQueue.add(new Integer(c)); 
            }
            System.out.write(new Integer('\n')); //display in ListView (output)     
            stdInQueue.add(new Integer('\n'));
            command_line.clear();
        }
    }

The redirecting of System.out and System.err works perfectly. It is being displayed in javafx ListView.

The problem is still that under the ListView I have a "command line" javafx TextField and I need to write some ssh command into this TextField and redirect it to System.in when pressing "enter" or clicking on "send" button.

The reason why I need to do this is that I am using SSH communication, which is set for System.in and System.out. It fully works in console (tested), but not in my GUI app.

Thanks for any further advice!

2

There are 2 answers

1
James_D On

You can set up a BlockingQueue<Integer> to send the individual characters to, and then have the input stream take characters from it:

BlockingQueue<Integer> stdInQueue = new LinkedBlockingQueue<>();

System.setIn(new InputStream() {

    @Override
    public int read() throws IOException {
        try {
            int c = stdInQueue.take().intValue();
            return c;
        } catch (InterruptedException exc) {
            Thread.currentThread().interrupt();
            return -1 ;
        }
    }
});

textField.setOnAction(e -> {
    for (char c : textField.getText().toCharArray()) {
        stdInQueue.add(new Integer(c));
    }
    stdInQueue.add(new Integer('\n'));
    textField.clear();
});

Here's a quick demo: for testing I just set up a background thread that reads from System.in:

import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class StdInFromTextField extends Application {

    @Override
    public void start(Stage primaryStage) {

        TextField textField = new TextField();

        BlockingQueue<Integer> stdInQueue = new LinkedBlockingQueue<>();

        System.setIn(new InputStream() {

            @Override
            public int read() throws IOException {
                try {
                    int c = stdInQueue.take().intValue();
                    return c;
                } catch (InterruptedException exc) {
                    Thread.currentThread().interrupt();
                    return -1 ;
                }
            }
        });

        textField.setOnAction(e -> {
            for (char c : textField.getText().toCharArray()) {
                stdInQueue.add(new Integer(c));
            }
            stdInQueue.add(new Integer('\n'));
            textField.clear();
        });

        // for testing:
        Thread readThread = new Thread(() -> {
            try {
                int i ;
                while ((i = System.in.read()) != -1) {
                    System.out.print((char)i);
                }
            } catch (IOException exc) {
                exc.printStackTrace();
            }
        });
        readThread.setDaemon(true);
        readThread.start();

        primaryStage.setScene(new Scene(new StackPane(textField), 300, 120));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
2
Zuzana On

Ok, I have working code now. The problem is, that I did not figure out, how to redirect System.in (it is probably impossible to do). So I had to redirect streams directly from SSH connector.

Here is how I set input and output in jsch stream:

public class ToradexSSHCommunicator {

String user;
String password;
String ip;
int port;

InputStream fromChannel;
OutputStream toChannel;

/** logger for error output feed */
private static final Logger LOGGER = Logger.getLogger(ToradexSSHCommunicator.class);

public ToradexSSHCommunicator(String user, String password, String ip, int port) {
    this.user = user;
    this.password = password;
    this.ip = ip;
    this.port = port;
}

public void startup() {
    Session session = null;
    ChannelShell channel = null;
    boolean isConnected = false;

    JSch jsch = new JSch();
    while (!isConnected) {
        try {
            session = jsch.getSession(user, ip, port);

            session.setPassword(password);
            session.setConfig("StrictHostKeyChecking", "no");

            LOGGER.info("Establishing Toradex Connection...");
            session.setTimeout(1000);
            session.connect();
            LOGGER.info("Toradex connection established.");

            LOGGER.info("Creating Toradex channel...");
            channel = (ChannelShell) session.openChannel("shell");
            LOGGER.info("Toradex channel created.");

            fromChannel = channel.getInputStream();
            toChannel = channel.getOutputStream();

            channel.connect();
            isConnected = true;

        }
        catch (JSchException e) {
            LOGGER.error("Toradex connection error.....RECONNECTING", e);
            session.disconnect();
        }
        catch (IOException e) {
            LOGGER.error("Toradex connection error.....RECONNECTING", e);
            session.disconnect();
        }
    }

}

public InputStream getFromChannel() {
    return fromChannel;
}

public OutputStream getToChannel() {
    return toChannel;
    }
}

And this part of GUI that use this streams:

@FXML
private ListView<String> command_window_toradex;
@FXML
private TextField command_line;

private ToradexSSHCommunicator comm;

private BufferedReader br;
private BufferedWriter bw;

@FXML
    void sendButtonPressed(ActionEvent event) {
        executor.submit(new Task<Void>() {

            @Override
            protected Void call() throws Exception {
                writeCommand();
                return null;
            }
        });
    }

/** initialize SSH connection to Toradex and redirect input and output streams to GUI */
private void initToradex() {
    comm = new ToradexSSHCommunicator(GuiConstants.TORADEX_USER, GuiConstants.TORADEX_PASSWORD,
            GuiConstants.TORADEX_IP_ADDRESS, GuiConstants.TORADEX_PORT);
    comm.startup();
    br = new BufferedReader(new InputStreamReader(comm.getFromChannel()));
    bw = new BufferedWriter(new OutputStreamWriter(comm.getToChannel()));
}

private void writeCommand() throws IOException {
    if (!command_line.getText().isEmpty()) {
        String command = command_line.getText();
        bw.write(command + '\n');
        bw.flush();
        command_line.clear();
    }
}

private void readCommand() throws IOException {
    String commandLine = null;
    while (true) {
        commandLine = br.readLine();
        textToradex.add(commandLine);
    }
}

It works! However this is now the whole code, if you would need any further help, just ask.