- Added Terminal.supportsTextAttributes(), supportsColor() and supportCursorMotion().

- Changed semantics for Terminal.normal(), bold(), foreground() and reset() so that they are no-ops when not supported.
- Fixed test app not to blow up on unsupported capability.
This commit is contained in:
Adam Murdoch
2012-08-10 06:00:05 +10:00
parent 0180d2f035
commit ec2eb737a6
9 changed files with 282 additions and 186 deletions

View File

@@ -17,5 +17,5 @@ void mark_failed_with_code(JNIEnv *env, const char* message, int error_code, job
JNIEXPORT jint JNICALL
Java_net_rubygrapefruit_platform_internal_jni_NativeLibraryFunctions_getVersion(JNIEnv *env, jclass target) {
return 3;
return 4;
}

View File

@@ -115,9 +115,9 @@ int write_to_terminal(TERMINAL_CHAR_TYPE ch) {
const char* getcap(const char* capability) {
char* cap = tgetstr((char*)capability, NULL);
if (cap == NULL) {
printf("unknown capability '%s'\n", capability);
}
// if (cap == NULL) {
// printf("unknown capability '%s'\n", capability);
// }
return cap;
}
@@ -151,7 +151,7 @@ void write_param_capability(JNIEnv *env, const char* capability, int count, jobj
}
JNIEXPORT void JNICALL
Java_net_rubygrapefruit_platform_internal_jni_TerminfoFunctions_initTerminal(JNIEnv *env, jclass target, jint output, jobject result) {
Java_net_rubygrapefruit_platform_internal_jni_TerminfoFunctions_initTerminal(JNIEnv *env, jclass target, jint output, jobject capabilities, jobject result) {
if (!isatty(output+1)) {
mark_failed_with_message(env, "not a terminal", result);
return;
@@ -167,18 +167,42 @@ Java_net_rubygrapefruit_platform_internal_jni_TerminfoFunctions_initTerminal(JNI
mark_failed_with_message(env, "could not get termcap entry", result);
return;
}
jclass destClass = env->GetObjectClass(capabilities);
jfieldID field = env->GetFieldID(destClass, "terminalName", "Ljava/lang/String;");
jstring jtermType = env->NewStringUTF(termType);
env->SetObjectField(capabilities, field, jtermType);
// Text attributes
terminal_capabilities[NORMAL_TEXT] = getcap("me");
terminal_capabilities[BRIGHT_TEXT] = getcap("md");
field = env->GetFieldID(destClass, "textAttributes", "Z");
env->SetBooleanField(capabilities, field, terminal_capabilities[NORMAL_TEXT] != NULL && terminal_capabilities[BRIGHT_TEXT] != NULL);
// Colors
terminal_capabilities[FOREGROUND_COLOR] = getcap("AF");
field = env->GetFieldID(destClass, "colors", "Z");
env->SetBooleanField(capabilities, field, terminal_capabilities[FOREGROUND_COLOR] != NULL);
// Cursor motion
terminal_capabilities[CURSOR_UP] = getcap("up");
terminal_capabilities[CURSOR_DOWN] = getcap("do");
terminal_capabilities[CURSOR_LEFT] = getcap("le");
terminal_capabilities[CURSOR_RIGHT] = getcap("nd");
terminal_capabilities[CURSOR_START_LINE] = getcap("cr");
terminal_capabilities[CLEAR_END_OF_LINE] = getcap("ce");
field = env->GetFieldID(destClass, "cursorMotion", "Z");
env->SetBooleanField(capabilities, field, terminal_capabilities[CURSOR_UP] != NULL
&& terminal_capabilities[CURSOR_DOWN] != NULL
&& terminal_capabilities[CURSOR_RIGHT] != NULL
&& terminal_capabilities[CURSOR_LEFT] != NULL
&& terminal_capabilities[CURSOR_START_LINE] != NULL
&& terminal_capabilities[CLEAR_END_OF_LINE] != NULL);
}
current_terminal = output + 1;
write_capability(env, terminal_capabilities[NORMAL_TEXT], result);
if (terminal_capabilities[NORMAL_TEXT] != NULL) {
write_capability(env, terminal_capabilities[NORMAL_TEXT], result);
}
}
JNIEXPORT void JNICALL
@@ -188,7 +212,9 @@ Java_net_rubygrapefruit_platform_internal_jni_TerminfoFunctions_bold(JNIEnv *env
JNIEXPORT void JNICALL
Java_net_rubygrapefruit_platform_internal_jni_TerminfoFunctions_reset(JNIEnv *env, jclass target, jobject result) {
write_capability(env, terminal_capabilities[NORMAL_TEXT], result);
if (terminal_capabilities[NORMAL_TEXT] != NULL) {
write_capability(env, terminal_capabilities[NORMAL_TEXT], result);
}
}
JNIEXPORT void JNICALL

View File

@@ -4,6 +4,7 @@ public class Main {
public static void main(String[] args) {
System.out.println();
System.out.println("* OS: " + System.getProperty("os.name") + ' ' + System.getProperty("os.version") + ' ' + System.getProperty("os.arch"));
System.out.println("* JVM: " + System.getProperty("java.vm.vendor") + ' ' + System.getProperty("java.version"));
Process process = Native.get(Process.class);
System.out.println("* PID: " + process.getProcessId());
@@ -17,8 +18,11 @@ public class Main {
Terminal terminal = terminalAccess.getTerminal(TerminalAccess.Output.Stdout);
TerminalSize terminalSize = terminal.getTerminalSize();
System.out.println("* terminal size: " + terminalSize.getCols() + " cols x " + terminalSize.getRows() + " rows");
System.out.println("* text attributes: " + (terminal.supportsTextAttributes() ? "yes" : "no"));
System.out.println("* color: " + (terminal.supportsColor() ? "yes" : "no"));
System.out.println("* cursor motion: " + (terminal.supportsCursorMotion() ? "yes" : "no"));
System.out.println();
System.out.println("TERMINAL OUTPUT");
System.out.println("TEXT ATTRIBUTES");
System.out.print("[normal] ");
terminal.bold();
System.out.print("[bold]");
@@ -39,28 +43,29 @@ public class Main {
System.out.println();
terminal.reset();
System.out.println("CURSOR MOVEMENT");
System.out.println(" ");
System.out.println(" ");
System.out.println(" ");
System.out.print("[delete me]");
if (terminal.supportsCursorMotion()) {
System.out.println("CURSOR MOVEMENT");
System.out.println(" ");
System.out.println(" ");
System.out.print("[delete me]");
terminal.cursorLeft(11);
terminal.cursorUp(1);
terminal.cursorRight(10);
System.out.print("[4]");
terminal.cursorUp(1);
terminal.cursorLeft(3);
System.out.print("[2]");
terminal.cursorLeft(13);
System.out.print("[1]");
terminal.cursorLeft(3);
terminal.cursorDown(1);
System.out.print("[3]");
terminal.cursorDown(1);
terminal.cursorStartOfLine();
terminal.clearToEndOfLine();
System.out.println("done!");
terminal.cursorLeft(11);
terminal.cursorUp(1);
terminal.cursorRight(10);
System.out.print("[4]");
terminal.cursorUp(1);
terminal.cursorLeft(3);
System.out.print("[2]");
terminal.cursorLeft(13);
System.out.print("[1]");
terminal.cursorLeft(3);
terminal.cursorDown(1);
System.out.print("[3]");
terminal.cursorDown(1);
terminal.cursorStartOfLine();
terminal.clearToEndOfLine();
System.out.println("done!");
}
}
System.out.println();

View File

@@ -11,35 +11,51 @@ public interface Terminal {
}
/**
* Returns the size of the terminal.
* Returns true if this terminal supports setting text attributes, such as bold.
*/
boolean supportsTextAttributes();
/**
* Returns true if this terminal supports setting output colors.
*/
boolean supportsColor();
/**
* Returns true if this terminal supports moving the cursor.
*/
boolean supportsCursorMotion();
/**
* Returns the size of the terminal. Supported by all terminals.
*
* @throws NativeException On failure.
*/
TerminalSize getTerminalSize() throws NativeException;
/**
* Sets the terminal foreground color.
* Sets the terminal foreground color, if supported. Does nothing if this terminal does not support setting the
* foreground color.
*
* @throws NativeException On failure.
*/
Terminal foreground(Color color) throws NativeException;
/**
* Switches the terminal to bold mode.
* Switches the terminal to bold mode, if supported. Does nothing if this terminal does not support bold mode.
*
* @throws NativeException On failure.
*/
Terminal bold() throws NativeException;
/**
* Switches the terminal to normal mode.
* Switches the terminal to normal mode. Supported by all terminals.
*
* @throws NativeException On failure.
*/
Terminal normal() throws NativeException;
/**
* Switches the terminal to normal mode and restores default colors.
* Switches the terminal to normal mode and restores default colors. Supported by all terminals.
*
* @throws NativeException On failure.
*/
@@ -48,42 +64,42 @@ public interface Terminal {
/**
* Moves the cursor the given number of characters to the left.
*
* @throws NativeException On failure.
* @throws NativeException On failure, or if this terminal does not support cursor motion.
*/
Terminal cursorLeft(int count) throws NativeException;
/**
* Moves the cursor the given number of characters to the right.
*
* @throws NativeException On failure.
* @throws NativeException On failure, or if this terminal does not support cursor motion.
*/
Terminal cursorRight(int count) throws NativeException;
/**
* Moves the cursor the given number of characters up.
*
* @throws NativeException On failure.
* @throws NativeException On failure, or if this terminal does not support cursor motion.
*/
Terminal cursorUp(int count) throws NativeException;
/**
* Moves the cursor the given number of characters down.
*
* @throws NativeException On failure.
* @throws NativeException On failure, or if this terminal does not support cursor motion.
*/
Terminal cursorDown(int count) throws NativeException;
/**
* Moves the cursor to the start of the current line.
*
* @throws NativeException On failure.
* @throws NativeException On failure, or if this terminal does not support cursor motion.
*/
Terminal cursorStartOfLine() throws NativeException;
/**
* Clears characters from the cursor position to the end of the current line.
*
* @throws NativeException On failure.
* @throws NativeException On failure, or if this terminal does not support clearing.
*/
Terminal clearToEndOfLine() throws NativeException;
}

View File

@@ -0,0 +1,8 @@
package net.rubygrapefruit.platform.internal;
public class TerminalCapabilities {
String terminalName;
boolean textAttributes;
boolean colors;
boolean cursorMotion;
}

View File

@@ -12,6 +12,7 @@ import java.io.PrintStream;
public class TerminfoTerminal extends AbstractTerminal {
private final TerminalAccess.Output output;
private final PrintStream stream;
private final TerminalCapabilities capabilities = new TerminalCapabilities();
private Color foreground;
public TerminfoTerminal(TerminalAccess.Output output) {
@@ -28,7 +29,7 @@ public class TerminfoTerminal extends AbstractTerminal {
protected void doInit() {
stream.flush();
FunctionResult result = new FunctionResult();
TerminfoFunctions.initTerminal(output.ordinal(), result);
TerminfoFunctions.initTerminal(output.ordinal(), capabilities, result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not open terminal for %s: %s", this, result.getMessage()));
}
@@ -45,13 +46,33 @@ public class TerminfoTerminal extends AbstractTerminal {
return terminalSize;
}
@Override
public boolean supportsColor() {
return capabilities.colors;
}
@Override
public boolean supportsCursorMotion() {
return capabilities.cursorMotion;
}
@Override
public boolean supportsTextAttributes() {
return capabilities.textAttributes;
}
@Override
public Terminal foreground(Color color) {
if (!capabilities.colors) {
return this;
}
stream.flush();
FunctionResult result = new FunctionResult();
TerminfoFunctions.foreground(color.ordinal(), result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not switch foreground color for %s: %s", this, result.getMessage()));
throw new NativeException(String.format("Could not switch foreground color for %s: %s", this,
result.getMessage()));
}
foreground = color;
return this;
@@ -59,11 +80,16 @@ public class TerminfoTerminal extends AbstractTerminal {
@Override
public Terminal bold() {
if (!capabilities.textAttributes) {
return this;
}
stream.flush();
FunctionResult result = new FunctionResult();
TerminfoFunctions.bold(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not switch to bold mode for %s: %s", this, result.getMessage()));
throw new NativeException(String.format("Could not switch to bold mode for %s: %s", this,
result.getMessage()));
}
return this;
}

View File

@@ -1,141 +1,155 @@
package net.rubygrapefruit.platform.internal;
import net.rubygrapefruit.platform.NativeException;
import net.rubygrapefruit.platform.Terminal;
import net.rubygrapefruit.platform.TerminalAccess;
import net.rubygrapefruit.platform.TerminalSize;
import net.rubygrapefruit.platform.internal.jni.TerminfoFunctions;
import net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions;
public class WindowsTerminal extends AbstractTerminal {
private final TerminalAccess.Output output;
public WindowsTerminal(TerminalAccess.Output output) {
this.output = output;
}
@Override
public String toString() {
return output.toString().toLowerCase();
}
@Override
protected void doInit() {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.initConsole(output.ordinal(), result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not open console for %s: %s", this, result.getMessage()));
}
}
@Override
public TerminalSize getTerminalSize() {
FunctionResult result = new FunctionResult();
MutableTerminalSize size = new MutableTerminalSize();
WindowsConsoleFunctions.getConsoleSize(output.ordinal(), size, result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not determine console size for %s: %s", this, result.getMessage()));
}
return size;
}
@Override
public Terminal bold() {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.bold(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not switch console to bold mode for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal foreground(Color color) {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.foreground(color.ordinal(), result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not change console foreground color for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal normal() {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.normal(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not switch console to normal mode for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal reset() {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.reset(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not reset console for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal cursorDown(int count) throws NativeException {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.down(count, result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not move cursor down for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal cursorUp(int count) throws NativeException {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.up(count, result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not move cursor up for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal cursorLeft(int count) throws NativeException {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.left(count, result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not move cursor left for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal cursorRight(int count) throws NativeException {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.right(count, result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not move cursor right for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal cursorStartOfLine() throws NativeException {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.startLine(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not move cursor to start of line for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal clearToEndOfLine() throws NativeException {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.clearToEndOfLine(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could clear to end of line for %s: %s", this, result.getMessage()));
}
return this;
}
}
package net.rubygrapefruit.platform.internal;
import net.rubygrapefruit.platform.NativeException;
import net.rubygrapefruit.platform.Terminal;
import net.rubygrapefruit.platform.TerminalAccess;
import net.rubygrapefruit.platform.TerminalSize;
import net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions;
public class WindowsTerminal extends AbstractTerminal {
private final TerminalAccess.Output output;
public WindowsTerminal(TerminalAccess.Output output) {
this.output = output;
}
@Override
public String toString() {
return output.toString().toLowerCase();
}
@Override
protected void doInit() {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.initConsole(output.ordinal(), result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not open console for %s: %s", this, result.getMessage()));
}
}
@Override
public boolean supportsColor() {
return true;
}
@Override
public boolean supportsTextAttributes() {
return true;
}
@Override
public boolean supportsCursorMotion() {
return true;
}
@Override
public TerminalSize getTerminalSize() {
FunctionResult result = new FunctionResult();
MutableTerminalSize size = new MutableTerminalSize();
WindowsConsoleFunctions.getConsoleSize(output.ordinal(), size, result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not determine console size for %s: %s", this, result.getMessage()));
}
return size;
}
@Override
public Terminal bold() {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.bold(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not switch console to bold mode for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal foreground(Color color) {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.foreground(color.ordinal(), result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not change console foreground color for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal normal() {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.normal(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not switch console to normal mode for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal reset() {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.reset(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not reset console for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal cursorDown(int count) throws NativeException {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.down(count, result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not move cursor down for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal cursorUp(int count) throws NativeException {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.up(count, result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not move cursor up for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal cursorLeft(int count) throws NativeException {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.left(count, result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not move cursor left for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal cursorRight(int count) throws NativeException {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.right(count, result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not move cursor right for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal cursorStartOfLine() throws NativeException {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.startLine(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not move cursor to start of line for %s: %s", this, result.getMessage()));
}
return this;
}
@Override
public Terminal clearToEndOfLine() throws NativeException {
FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.clearToEndOfLine(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could clear to end of line for %s: %s", this, result.getMessage()));
}
return this;
}
}

View File

@@ -1,7 +1,7 @@
package net.rubygrapefruit.platform.internal.jni;
public class NativeLibraryFunctions {
public static final int VERSION = 3;
public static final int VERSION = 4;
public static native int getVersion();
}

View File

@@ -1,12 +1,13 @@
package net.rubygrapefruit.platform.internal.jni;
import net.rubygrapefruit.platform.internal.FunctionResult;
import net.rubygrapefruit.platform.internal.TerminalCapabilities;
public class TerminfoFunctions {
/**
* Sets up terminal info and switches output to normal mode.
*/
public static native void initTerminal(int filedes, FunctionResult result);
public static native void initTerminal(int filedes, TerminalCapabilities terminalCapabilities, FunctionResult result);
public static native void bold(FunctionResult result);