Can I find the console width with Java? - java

Is there a way to find the width of the console in which my Java program is running?
I would like this to be cross platform if possible...
I have no desire to change the width of the buffer or the window, I just want to know its width so I can properly format text that is being printed to screen.

There are no reliable cross-platform solutions to this problem. Indeed, there are situations where it is not possible to know what the real console width is.
(See other answers for approaches that work some of the time and/or on some platforms. But beware of the limitations ...)
For example, on a Linux system you can typically find out the notional terminal dimensions from the LINES and COLUMNS environment variables. While these variables are automatically updated when you resize some "terminal emulator" windows, this is not always the case. Indeed, in the case of a remote console connected via telnet protocol, there is no way to get the actual terminal dimensions to the user's shell.
EDIT: Just to add that if the user changes the dimensions of his/her xterm on Linux after launching a Java app, the Java app won't be notified, and it won't see the new dimensions reflected in its copy of the LINES and COLUMNS environment variables!
EDIT 2: My mistake: LINES and COLUMNS are bash shell variables, and they are not exported to the environment by default. You can "fix" this by running export COLUMNS LINES before you run your Java application.

Actually, a Java library already exists to do this in Java: JLine 2. (There's an old version on SourceForce, but that link to GitHub seems to be the latest.)
This worked for me under Linux (Mint 14) and Windows (I don't know what version), using JLine 2.11:
terminalWidth = jline.TerminalFactory.get().getWidth();
JLine promises to work on Mac, too.
I've found that it returns bad widths (like 1!) under the Eclipse console (but even Java's native Console doesn't work under Eclipse!), and, unfortunately, under Cygwin. But I've worked around this with code that checks for unreasonable values (< 10) and just uses 80 in those cases.
Update for JLine 3 (per Mark—thanks, mate!):
terminalWidth = org.jline.terminal.TerminalBuilder.terminal().getWidth()

There's a trick that you can use based on ANSI Escape Codes. They don't provide a direct way to query the console size, but they do have a command for requesting the current cursor position. By moving the cursor to a really high row and column and then requesting the cursor position you can get an accurate measurement.
Combine this with commands to store/restore the cursor position, as in the following example:
Send the following sequences to the terminal (stdout)
"\u001b[s" // save cursor position
"\u001b[5000;5000H" // move to col 5000 row 5000
"\u001b[6n" // request cursor position
"\u001b[u" // restore cursor position
Now watch stdin, you should receive a sequece that looks like \u001b[25;80R", where 25 is the row count, and 80 the columns.
I first saw this used in the Lanterna library.
Update:
There are really four different ways that I know of to achieve this, but they all make certain assumptions about the environment the program is running in, or the terminal device/emulator it is talking to.
Using the VT100 protocol. This is what this solution does, it assumes you are talking over stdin/stdout to a terminal emulator that honors these escape codes. This seems like a relatively safe assumption for a CLI program, but e.g. if someone is using cmd.exe this likely won't work.
terminfo/termcap. These are databases with terminal information, which you can query for instance with tput. Operating system dependent, and assumes you are connected to a TTY device. Won't work over ssh for instance.
Using the telnet protocol. Telnet has its own affordances for querying the screen size, but of course this only works if people connect to your application via the telnet client, not really an option in most cases.
Rely on the shell (e.g. bash), this is what solutions that use COLUMNS/ROWS variables do. Far from universal, but could work quite well if you provide a wrapper script for your app that makes sure the necessary env vars are exported.

Edit: See #dave_thompson_085's comment about ProcessBuilder, as that's almost certainly a better approach.
Another answer mentioned running tput cols in a script before you start your command. But if you want to run that after Java has already started, using Runtime.getRuntime().exec(), you'll find that tput can't talk to your terminal, because Java has redirected stdout and stderr. As a not-at-all-portable workaround, you can use the magical /dev/tty device, which refers to the terminal of the current process. That lets you run something like this:
Process p = Runtime.getRuntime().exec(new String[] {
"bash", "-c", "tput cols 2> /dev/tty" });
// Read the output of this process to get your terminal width
This works for me on Linux, but I wouldn't expect it to work everywhere. It will hopefully work on Mac. It definitely won't work on Windows, though it might with Cygwin.

Java 6 has a class java.io.Console, but it unfortunately lacks the functionality you're asking for. Getting the console window width is not possible with the standard Java library and pure, cross-platform Java.
Here is an alternative Java console library which allows you to get the screen size, but it includes a Windows-specific DLL. You might be able to take the source code and compile the C part into a Linux or Mac OS X shared library, so that it will work on those platforms as well.

I have been working on this problem before. I use a couple of different techniques. However it is difficult to have a truly cross platform solution.
I tried doing try something like this:
String os = System.getProperty("os.name").toLowerCase();
//Windows
if(os.contains("win")){
System.out.append("Windows Detected");
//set Windows Dos Terminal width 80, height 25
Process p = Runtime.getRuntime().exec("mode 80, 25");
}
//Mac
if(os.contains("mac")){
System.out.println("Macintosh Detected");
//... I dont know...try Google
}
//Linux
if(os.contains("linux")){
System.out.println("Linux Detected");
You can read/test and append "export COLUMNS" to the .bashrc file in every Linux users home directory with the String.contains("export COLUMNS") method and the user.dir property.
That would allow you to get the columns to load up every time the java app starts up.
Then I would pass it to a temp file. Like this:
try {
ProcessBuilder pb = new ProcessBuilder("bash","-c","echo $COLUMNS >/home/$USER/bin/temp.txt" );
pb.start();
}catch (Exception e) {
System.out.println("exception happened - here's what I know: ");
e.printStackTrace();
System.exit(-1);
}
}
Another option you have is to execute yor Java.jar with a bash script at startup. Inside the script you can use "tput cols" to get the width. Then pass that value to your Java app as a String[] arg.
Like so:
//#!/bin/bash
//#clear the screen
clear
//#get the console width and height
c=$[$(tput cols)]
l=$[$(tput lines)]
//#pass the columns, lines and an optional third value as String[] args.
java -jar ~/bin/Plus.jar $c $l $1
why is this such a difficult task with Java? Obviously a good place to write a good API. I guess we could try Apache.commons.exec as well?

For me, the only way to get an idea of the terminal window (still not correct when the window resizes) is to use a command like
ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", "mode con");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
When run without the cmd.exe part, it shows that the command could not be found. Also note the redirectError part. If not used then the Java output size will be used, not the actual one. Only with this combination it was possible to grab the actual size.

Python seems to have a good solution: 11.9.3. Querying the size of the output terminal. I wouldn't hold my breath waiting for this to be available in core Java, but you might be able to use Jython to make the Python functionality available.

Related

Embedding a terminal Window inside of a simple scala application

I'm trying to build a simple text editor using Scala's swing library and I have to support two side by side windows. I was wondering if it was possible to have the second window be the terminal (bash, Unix). I haven't been able to find any information on the subject. Thank you for any information.
The question is: What is "the terminal"? bash is a shell but you need an implementation of a terminal which runs a certain shell. In general, I would say there are two possibilities:
Find a terminal implementation, which can be used directly in Swing. Maybe you this or that may help.
Implement your own terminal. You may want to start to wrap the shell with a ProcessBuilder. Now you can redirect standard input and output of this process so that you can control it programmatically (more information: here and there). Then you have to create the UI part which (1) reads input from the user and (2) displays the shell output in your window.

Trying to set terminal width/height for a program running on a tomcat server

So I'm trying to get data on a program through linux, and the only way the makers have given is to run a script that runs some internal commands which run prints, and then it saves them to file. The only issue is that the way it prints out the data is determinate of the terminal width/height, making it potentially unparsable if run at weird size.
I'm trying to run java code from tomcat a tomcat server that will call this script and then create the file so that I can parse the data. However, when I run the script this way, my "stty columns 10000" commands fail to change the terminal size, such that the print is stunted.
The end goal is to run this as an automated task, so there won't even be a terminal and I'm wondering how this will effect the prints. I'm working on getting that up and running, but I figured I'd post this sooner than later to see if anyone has any solutions.
I've done the crossed out stuff above, and the program uses the linux terminal's default sizes (80 columns, 24 rows). I managed to use stty before starting the java, and then the program reported 80 columns and 134555573 rows... lol. This stuff is all being run on root and set at boot.
Thank you

I want to run bash directly interactively but not getting ANSI codes

I'm creating my own Java-based term program and it works great with SSH and Telnet connections.
But now I want to open a local direct connection like xterm or gnome-terminal do.
I run "bash -i -l" with ProcessBuilder and use getInputStream() / getOutputStream() and it seems to work. (I need to call redirectErrorStream(true))
My only problem is that bash seems to be ignoring the TERM environment variable and is not generating any ANSI codes. I get a prompt but running 'ls -la' is missing CR's and color. Plus it's not echoing back the input (like it does over the SSH connection).
I use JCraft's JSch library and call setPtyType("vt100") for the SSH connections.
How would I do that when running bash directly?
I'm not using a pty, is that needed? Allocating a pty would require native code I think.
If so, how do pty's work.
I've tried to look at xterm source code but that's a mess. Now I remember why I'll never go back to C code.
Is there a simple solution.
Thanks.
As far as ls goes, it may be that your ls is alias for ls --color=auto (you can check this by running alias ls in a terminal). In that case, ls will make an educated guess whether to output the color codes. From man ls:
Using color to distinguish file types is disabled both by default
and with --color=never. With --color=auto, ls emits color codes
only when standard output is connected to a terminal.
Many other programs will probably do this kind of thing.
As far as your general studies of terminal emulation goes, "Terminator" seems to do some pty emulation (using a JNI library though) that maybe you can get some ideas from:
http://software.jessies.org/terminator/

How do I accept left/right/up/down arrow inputs in a Linux shell?

I wrote a Java application which has a while(input.readLine()) which continues reading input until the user types a quit command. I have a .bat script to launch it in Windows and a .sh to launch it in Linux. The application works fine in both.
While in Windows, if you type a command like "check email" and hit enter, it will perform the command. Then, at the next prompt (that is: the next time the Java application reads a line of input) you can hit the up arrow and it recalls your "check email" command. You could press the left arrow key to move your cursor left, etc. This is all exactly how I want it.
On Linux, however, pressing the up key causes ^[[D to appear. The left and right arrow keys produce similar ASCII output, like ^[[C.
I've heard of stty and viewed the man pages, but cannot figure out what I need to change in the .sh script with launches my Java application. Can anyone help me?
The simplest solution is to use an external wrapper that reads a line with edition capabilities, then sends it to your program. A good wrapper is rlwrap.
Using a separate tool is in keeping with the unix philosophy of using separate tools for separate purposes (line edition and whatever your program does). It's also simple: just make your shell wrapper run rlwrap myprogram instead of myprogram.
The underlying readline library (no relation to any readLine method in Java; you'll want a Java wrapper such as Java-Readline) is more work to integrate, and constrains your program to be GPL. Go for that only if you want more than what a wrapper can provide — primarily application-specific completions.
The readline function is used for this stuff on Unices, but it's a C function.
Here you could find a Java wrapper for it. (I never used it)

Starting an external process fails: cannot connect to X server

as part of my eclipse plugin I try to start an external program by using process.exec. This works with some tools (I tested it with gedit, for example), but with the one I need it does not work: isimgui: cannot connect to X server.
This is part of the XILINX webpack, none of the included graphic tools can be started like this.
Any ideas how I met get it to work?
You probably need to pass the -display argument to the executable you are running, or better (more widely supported) set the environment variable DISPLAY to the right value (try ':0')
use for example: process.exec(String[] cmdarray, String[] envp)
envp should contain at least one string "DISPLAY=:0"
You must inherit the DISPLAY variable from your shell (and possibly also the X11 authentication file information).

Categories

Resources