diff --git a/src/main/cpp/generic.cpp b/src/main/cpp/generic.cpp index 25fe59d..1e79d96 100755 --- a/src/main/cpp/generic.cpp +++ b/src/main/cpp/generic.cpp @@ -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; } diff --git a/src/main/cpp/posix.cpp b/src/main/cpp/posix.cpp index 1b3eee0..5c0a574 100755 --- a/src/main/cpp/posix.cpp +++ b/src/main/cpp/posix.cpp @@ -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 diff --git a/src/main/java/net/rubygrapefruit/platform/Main.java b/src/main/java/net/rubygrapefruit/platform/Main.java index deb7c7c..dec880c 100755 --- a/src/main/java/net/rubygrapefruit/platform/Main.java +++ b/src/main/java/net/rubygrapefruit/platform/Main.java @@ -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(); diff --git a/src/main/java/net/rubygrapefruit/platform/Terminal.java b/src/main/java/net/rubygrapefruit/platform/Terminal.java index b8172dc..bc87614 100644 --- a/src/main/java/net/rubygrapefruit/platform/Terminal.java +++ b/src/main/java/net/rubygrapefruit/platform/Terminal.java @@ -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; } diff --git a/src/main/java/net/rubygrapefruit/platform/internal/TerminalCapabilities.java b/src/main/java/net/rubygrapefruit/platform/internal/TerminalCapabilities.java new file mode 100644 index 0000000..e04a0e1 --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/internal/TerminalCapabilities.java @@ -0,0 +1,8 @@ +package net.rubygrapefruit.platform.internal; + +public class TerminalCapabilities { + String terminalName; + boolean textAttributes; + boolean colors; + boolean cursorMotion; +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/TerminfoTerminal.java b/src/main/java/net/rubygrapefruit/platform/internal/TerminfoTerminal.java index b6f10d4..4fc3b07 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/TerminfoTerminal.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/TerminfoTerminal.java @@ -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; } diff --git a/src/main/java/net/rubygrapefruit/platform/internal/WindowsTerminal.java b/src/main/java/net/rubygrapefruit/platform/internal/WindowsTerminal.java index e237e19..de91b71 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/WindowsTerminal.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/WindowsTerminal.java @@ -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; + } +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java b/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java index bc8d209..1f59094 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java @@ -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(); } diff --git a/src/main/java/net/rubygrapefruit/platform/internal/jni/TerminfoFunctions.java b/src/main/java/net/rubygrapefruit/platform/internal/jni/TerminfoFunctions.java index 099cc1b..9107978 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/jni/TerminfoFunctions.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/jni/TerminfoFunctions.java @@ -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);