FELIX-1431: Add a web console plugin to have access to the gogo shell
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@801921 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/assembly/src/main/filtered-resources/features.xml b/karaf/assembly/src/main/filtered-resources/features.xml
index aad78e6..2cbb230 100644
--- a/karaf/assembly/src/main/filtered-resources/features.xml
+++ b/karaf/assembly/src/main/filtered-resources/features.xml
@@ -57,10 +57,11 @@
</config>
<bundle>mvn:org.apache.felix/org.apache.felix.metatype/${felix.metatype.version}</bundle>
<bundle>mvn:org.apache.felix/org.apache.felix.webconsole/${felix.webconsole.version}</bundle>
- <bundle>mvn:org.apache.felix.karaf.webconsole/org.apache.felix.karaf.webconsole.plugins/${version}</bundle>
<!-- TODO: uncomment when FELIX-1133 is resolved
<bundle>mvn:org.apache.felix.karaf.webconsole/org.apache.felix.karaf.webconsole.branding/${version}</bundle>
-->
+ <bundle>mvn:org.apache.felix.karaf.webconsole/org.apache.felix.karaf.webconsole.features/${version}</bundle>
+ <bundle>mvn:org.apache.felix.karaf.webconsole/org.apache.felix.karaf.webconsole.gogo/${version}</bundle>
</feature>
<feature name="ssh" version="${version}">
<config name="org.apache.felix.karaf.gshell.ssh">
diff --git a/karaf/gshell/gshell-console/src/main/java/org/apache/felix/karaf/gshell/console/jline/Console.java b/karaf/gshell/gshell-console/src/main/java/org/apache/felix/karaf/gshell/console/jline/Console.java
index dd46afc..c1e726b 100644
--- a/karaf/gshell/gshell-console/src/main/java/org/apache/felix/karaf/gshell/console/jline/Console.java
+++ b/karaf/gshell/gshell-console/src/main/java/org/apache/felix/karaf/gshell/console/jline/Console.java
@@ -18,21 +18,22 @@
*/
package org.apache.felix.karaf.gshell.console.jline;
-import jline.*;
-import org.osgi.service.command.CommandSession;
-import org.osgi.service.command.Converter;
-import org.osgi.service.command.CommandProcessor;
-import org.apache.felix.karaf.gshell.console.Completer;
-import org.fusesource.jansi.Ansi;
-
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
-import java.io.PrintWriter;
import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.Properties;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
-import java.util.Properties;
+
+import jline.ConsoleReader;
+import jline.Terminal;
+import jline.UnsupportedTerminal;
+import org.apache.felix.karaf.gshell.console.Completer;
+import org.osgi.service.command.CommandProcessor;
+import org.osgi.service.command.CommandSession;
+import org.osgi.service.command.Converter;
public class Console implements Runnable
{
diff --git a/karaf/webconsole/plugins/pom.xml b/karaf/webconsole/features/pom.xml
similarity index 88%
rename from karaf/webconsole/plugins/pom.xml
rename to karaf/webconsole/features/pom.xml
index 7025e4d..5910ace 100644
--- a/karaf/webconsole/plugins/pom.xml
+++ b/karaf/webconsole/features/pom.xml
@@ -23,16 +23,16 @@
<modelVersion>4.0.0</modelVersion>
<parent>
- <groupId>org.apache.felix.karaf.webconsole</groupId>
- <artifactId>webconsole</artifactId>
- <version>1.2.0-SNAPSHOT</version>
+ <groupId>org.apache.felix.karaf.webconsole</groupId>
+ <artifactId>webconsole</artifactId>
+ <version>1.2.0-SNAPSHOT</version>
</parent>
<groupId>org.apache.felix.karaf.webconsole</groupId>
- <artifactId>org.apache.felix.karaf.webconsole.plugins</artifactId>
+ <artifactId>org.apache.felix.karaf.webconsole.features</artifactId>
<packaging>bundle</packaging>
<version>1.2.0-SNAPSHOT</version>
- <name>Apache Felix Karaf :: Web Console :: Plugins</name>
+ <name>Apache Felix Karaf :: Web Console :: Features Plugin</name>
<dependencies>
<dependency>
@@ -85,7 +85,7 @@
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
- <Export-Package>org.apache.felix.karaf.webconsole;version=${pom.version}</Export-Package>
+ <Export-Package>org.apache.felix.karaf.webconsole.features;version=${pom.version}</Export-Package>
<Embed-Dependency>
<!-- Required for JSON data transfer -->
<!-- TODO: this needs to be put in a common place for reuse. -->
diff --git a/karaf/webconsole/plugins/src/main/java/org/apache/felix/karaf/webconsole/Feature.java b/karaf/webconsole/features/src/main/java/org/apache/felix/karaf/webconsole/features/Feature.java
similarity index 95%
rename from karaf/webconsole/plugins/src/main/java/org/apache/felix/karaf/webconsole/Feature.java
rename to karaf/webconsole/features/src/main/java/org/apache/felix/karaf/webconsole/features/Feature.java
index cc2a627..67aba5e 100644
--- a/karaf/webconsole/plugins/src/main/java/org/apache/felix/karaf/webconsole/Feature.java
+++ b/karaf/webconsole/features/src/main/java/org/apache/felix/karaf/webconsole/features/Feature.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.felix.karaf.webconsole;
+package org.apache.felix.karaf.webconsole.features;
/**
* Represents a feature with a name, version and state
diff --git a/karaf/webconsole/plugins/src/main/java/org/apache/felix/karaf/webconsole/FeaturesPlugin.java b/karaf/webconsole/features/src/main/java/org/apache/felix/karaf/webconsole/features/FeaturesPlugin.java
similarity index 99%
rename from karaf/webconsole/plugins/src/main/java/org/apache/felix/karaf/webconsole/FeaturesPlugin.java
rename to karaf/webconsole/features/src/main/java/org/apache/felix/karaf/webconsole/features/FeaturesPlugin.java
index ad22929..b4c9946 100644
--- a/karaf/webconsole/plugins/src/main/java/org/apache/felix/karaf/webconsole/FeaturesPlugin.java
+++ b/karaf/webconsole/features/src/main/java/org/apache/felix/karaf/webconsole/features/FeaturesPlugin.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.felix.karaf.webconsole;
+package org.apache.felix.karaf.webconsole.features;
import java.io.IOException;
diff --git a/karaf/webconsole/plugins/src/main/resources/OSGI-INF/blueprint/webconsole.xml b/karaf/webconsole/features/src/main/resources/OSGI-INF/blueprint/webconsole-features.xml
similarity index 94%
rename from karaf/webconsole/plugins/src/main/resources/OSGI-INF/blueprint/webconsole.xml
rename to karaf/webconsole/features/src/main/resources/OSGI-INF/blueprint/webconsole-features.xml
index b564a35..0cd5325 100644
--- a/karaf/webconsole/plugins/src/main/resources/OSGI-INF/blueprint/webconsole.xml
+++ b/karaf/webconsole/features/src/main/resources/OSGI-INF/blueprint/webconsole-features.xml
@@ -22,7 +22,7 @@
<reference id="featuresService" interface="org.apache.felix.karaf.gshell.features.FeaturesService" />
- <bean id="featuresPlugin" class="org.apache.felix.karaf.webconsole.FeaturesPlugin" init-method="start" destroy-method="stop">
+ <bean id="featuresPlugin" class="org.apache.felix.karaf.webconsole.features.FeaturesPlugin" init-method="start" destroy-method="stop">
<property name="featuresService" ref="featuresService" />
<property name="bundleContext" ref="blueprintBundleContext" />
</bean>
diff --git a/karaf/webconsole/plugins/src/main/resources/res/ui/features.js b/karaf/webconsole/features/src/main/resources/res/ui/features.js
similarity index 100%
rename from karaf/webconsole/plugins/src/main/resources/res/ui/features.js
rename to karaf/webconsole/features/src/main/resources/res/ui/features.js
diff --git a/karaf/webconsole/plugins/pom.xml b/karaf/webconsole/gogo/pom.xml
similarity index 87%
copy from karaf/webconsole/plugins/pom.xml
copy to karaf/webconsole/gogo/pom.xml
index 7025e4d..f7edf26 100644
--- a/karaf/webconsole/plugins/pom.xml
+++ b/karaf/webconsole/gogo/pom.xml
@@ -23,16 +23,16 @@
<modelVersion>4.0.0</modelVersion>
<parent>
- <groupId>org.apache.felix.karaf.webconsole</groupId>
- <artifactId>webconsole</artifactId>
- <version>1.2.0-SNAPSHOT</version>
+ <groupId>org.apache.felix.karaf.webconsole</groupId>
+ <artifactId>webconsole</artifactId>
+ <version>1.2.0-SNAPSHOT</version>
</parent>
<groupId>org.apache.felix.karaf.webconsole</groupId>
- <artifactId>org.apache.felix.karaf.webconsole.plugins</artifactId>
+ <artifactId>org.apache.felix.karaf.webconsole.gogo</artifactId>
<packaging>bundle</packaging>
<version>1.2.0-SNAPSHOT</version>
- <name>Apache Felix Karaf :: Web Console :: Plugins</name>
+ <name>Apache Felix Karaf :: Web Console :: Gogo Plugin</name>
<dependencies>
<dependency>
@@ -62,7 +62,7 @@
</dependency>
<dependency>
<groupId>org.apache.felix.karaf.gshell</groupId>
- <artifactId>org.apache.felix.karaf.gshell.features</artifactId>
+ <artifactId>org.apache.felix.karaf.gshell.console</artifactId>
</dependency>
<dependency>
<groupId>org.apache.servicemix.bundles</groupId>
@@ -85,7 +85,7 @@
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
- <Export-Package>org.apache.felix.karaf.webconsole;version=${pom.version}</Export-Package>
+ <Export-Package>org.apache.felix.karaf.webconsole.gogo;version=${pom.version}</Export-Package>
<Embed-Dependency>
<!-- Required for JSON data transfer -->
<!-- TODO: this needs to be put in a common place for reuse. -->
diff --git a/karaf/webconsole/gogo/src/main/java/org/apache/felix/karaf/webconsole/gogo/GogoPlugin.java b/karaf/webconsole/gogo/src/main/java/org/apache/felix/karaf/webconsole/gogo/GogoPlugin.java
new file mode 100644
index 0000000..6b08431
--- /dev/null
+++ b/karaf/webconsole/gogo/src/main/java/org/apache/felix/karaf/webconsole/gogo/GogoPlugin.java
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Based on http://antony.lesuisse.org/software/ajaxterm/
+ * Public Domain License
+ */
+
+package org.apache.felix.karaf.webconsole.gogo;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.PipedOutputStream;
+import java.io.PipedInputStream;
+import java.io.InterruptedIOException;
+import java.io.InputStreamReader;
+import java.io.ByteArrayInputStream;
+import java.io.PrintStream;
+import java.io.InputStream;
+import java.util.zip.GZIPOutputStream;
+import java.util.List;
+import java.net.URL;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.ServletException;
+
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.apache.felix.karaf.gshell.console.jline.Console;
+import org.apache.felix.karaf.gshell.console.Completer;
+import org.apache.felix.karaf.gshell.console.completer.AggregateCompleter;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.command.CommandProcessor;
+import org.osgi.service.command.CommandSession;
+
+/**
+ * The <code>GogoPlugin</code>
+ */
+public class GogoPlugin extends AbstractWebConsolePlugin {
+
+ /** Pseudo class version ID to keep the IDE quite. */
+ private static final long serialVersionUID = 1L;
+
+ public static final String NAME = "gogo";
+
+ public static final String LABEL = "Gogo";
+
+ public static final int TERM_WIDTH = 120;
+ public static final int TERM_HEIGHT = 39;
+
+ private Log log = LogFactory.getLog(GogoPlugin.class);
+
+ private BundleContext bundleContext;
+
+ private CommandProcessor commandProcessor;
+
+ private List<Completer> completers;
+
+ public void setBundleContext(BundleContext bundleContext)
+ {
+ this.bundleContext = bundleContext;
+ }
+
+ public void setCommandProcessor(CommandProcessor commandProcessor)
+ {
+ this.commandProcessor = commandProcessor;
+ }
+
+ public void setCompleters(List<Completer> completers)
+ {
+ this.completers = completers;
+ }
+
+ /*
+ * Blueprint lifecycle callback methods
+ */
+
+ public void start()
+ {
+ super.activate( bundleContext );
+ this.log.info( LABEL + " plugin activated" );
+ }
+
+ public void stop()
+ {
+ this.log.info( LABEL + " plugin deactivated" );
+ super.deactivate();
+ }
+
+ //
+ // AbstractWebConsolePlugin interface
+ //
+ public String getLabel()
+ {
+ return NAME;
+ }
+
+
+ public String getTitle()
+ {
+ return LABEL;
+ }
+
+
+ protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
+ {
+ PrintWriter pw = response.getWriter();
+
+ String appRoot = request.getContextPath() + request.getServletPath();
+ pw.println( "<link href=\"" + appRoot + "/gogo/res/ui/gogo.css\" rel=\"stylesheet\" type=\"text/css\" />" );
+ pw.println( "<script src=\"" + appRoot + "/gogo/res/ui/gogo.js\" type=\"text/javascript\"></script>" );
+ pw.println( "<div id='console'><div id='term'></div></div>" );
+ pw.println( "<script type=\"text/javascript\"><!--" );
+ pw.println( "window.onload = function() { gogo.Terminal(document.getElementById(\"term\"), " + TERM_WIDTH + ", " + TERM_HEIGHT + "); }" );
+ pw.println( "--></script>" );
+ }
+
+ protected URL getResource( String path )
+ {
+ path = path.substring( NAME.length() + 1 );
+ URL url = this.getClass().getClassLoader().getResource( path );
+ try
+ {
+ InputStream ins = url.openStream();
+ if ( ins == null )
+ {
+ this.log.error( "failed to open " + url );
+ }
+ }
+ catch ( IOException e )
+ {
+ this.log.error( e.getMessage(), e );
+ }
+ return url;
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ String encoding = request.getHeader("Accept-Encoding");
+ boolean supportsGzip = (encoding != null && encoding.toLowerCase().indexOf("gzip") > -1);
+ SessionTerminal st = (SessionTerminal) request.getSession(true).getAttribute("terminal");
+ if (st == null || st.isClosed()) {
+ st = new SessionTerminal();
+ request.getSession().setAttribute("terminal", st);
+ }
+ String str = request.getParameter("k");
+ String f = request.getParameter("f");
+ String dump = st.handle(str, f != null && f.length() > 0);
+ if (dump != null) {
+ if (supportsGzip) {
+ response.setHeader("Content-Encoding", "gzip");
+ response.setHeader("Content-Type", "text/html");
+ try {
+ GZIPOutputStream gzos = new GZIPOutputStream(response.getOutputStream());
+ gzos.write(dump.getBytes());
+ gzos.close();
+ } catch (IOException ie) {
+ // handle the error here
+ ie.printStackTrace();
+ }
+ } else {
+ response.getOutputStream().write(dump.getBytes());
+ }
+ }
+ }
+
+
+ public class SessionTerminal implements Runnable {
+
+ private Terminal terminal;
+ private Console console;
+ private PipedOutputStream in;
+ private PipedInputStream out;
+ private boolean closed;
+
+ public SessionTerminal() throws IOException {
+ try {
+ this.terminal = new Terminal(TERM_WIDTH, TERM_HEIGHT);
+ terminal.write("\u001b\u005B20\u0068"); // set newline mode on
+
+ in = new PipedOutputStream();
+ out = new PipedInputStream();
+ PrintStream pipedOut = new PrintStream(new PipedOutputStream(out), true);
+
+ console = new Console(commandProcessor,
+ new PipedInputStream(in),
+ pipedOut,
+ pipedOut,
+ new WebTerminal(TERM_WIDTH, TERM_HEIGHT),
+ new AggregateCompleter(completers),
+ null);
+ CommandSession session = console.getSession();
+ session.put("APPLICATION", System.getProperty("karaf.name", "root"));
+ session.put("USER", "karaf");
+ session.put("COLUMNS", Integer.toString(TERM_WIDTH));
+ session.put("LINES", Integer.toString(TERM_HEIGHT));
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw e;
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new IOException(e);
+ }
+ new Thread(console).start();
+ new Thread(this).start();
+ }
+
+ public boolean isClosed() {
+ return closed;
+ }
+
+ public String handle(String str, boolean forceDump) throws IOException {
+ try {
+ if (str != null && str.length() > 0) {
+ String d = terminal.pipe(str);
+ for (byte b : d.getBytes()) {
+ in.write(b);
+ }
+ in.flush();
+ }
+ } catch (IOException e) {
+ closed = true;
+ throw e;
+ }
+ try {
+ return terminal.dump(10, forceDump);
+ } catch (InterruptedException e) {
+ throw new InterruptedIOException(e.toString());
+ }
+ }
+
+ public void run() {
+ try {
+ for (;;) {
+ byte[] buf = new byte[8192];
+ int l = out.read(buf);
+ InputStreamReader r = new InputStreamReader(new ByteArrayInputStream(buf, 0, l));
+ StringBuilder sb = new StringBuilder();
+ for (;;) {
+ int c = r.read();
+ if (c == -1) {
+ break;
+ }
+ sb.append((char) c);
+ }
+ if (sb.length() > 0) {
+ terminal.write(sb.toString());
+ }
+ String s = terminal.read();
+ if (s != null && s.length() > 0) {
+ for (byte b : s.getBytes()) {
+ in.write(b);
+ }
+ }
+ }
+ } catch (IOException e) {
+ closed = true;
+ e.printStackTrace();
+ }
+ }
+
+ }
+}
diff --git a/karaf/webconsole/gogo/src/main/java/org/apache/felix/karaf/webconsole/gogo/Terminal.java b/karaf/webconsole/gogo/src/main/java/org/apache/felix/karaf/webconsole/gogo/Terminal.java
new file mode 100644
index 0000000..ecb9a9e
--- /dev/null
+++ b/karaf/webconsole/gogo/src/main/java/org/apache/felix/karaf/webconsole/gogo/Terminal.java
@@ -0,0 +1,1494 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Based on http://antony.lesuisse.org/software/ajaxterm/
+ * Public Domain License
+ */
+
+/**
+ * See http://www.ecma-international.org/publications/standards/Ecma-048.htm
+ * and http://vt100.net/docs/vt510-rm/
+ */
+
+package org.apache.felix.karaf.webconsole.gogo;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class Terminal {
+
+ enum State {
+ None,
+ Esc,
+ Str,
+ Csi,
+ }
+
+ private int width;
+ private int height;
+ private int attr;
+ private boolean eol;
+ private int cx;
+ private int cy;
+ private int[] screen;
+ private int[] screen2;
+ private State vt100_parse_state = State.None;
+ private int vt100_parse_len;
+ private int vt100_lastchar;
+ private int vt100_parse_func;
+ private String vt100_parse_param;
+ private boolean vt100_mode_autowrap;
+ private boolean vt100_mode_insert;
+ private boolean vt100_charset_is_single_shift;
+ private boolean vt100_charset_is_graphical;
+ private boolean vt100_mode_lfnewline;
+ private boolean vt100_mode_origin;
+ private boolean vt100_mode_inverse;
+ private boolean vt100_mode_cursorkey;
+ private boolean vt100_mode_cursor;
+ private boolean vt100_mode_alt_screen;
+ private boolean vt100_mode_backspace;
+ private boolean vt100_mode_column_switch;
+ private boolean vt100_keyfilter_escape;
+ private int[] vt100_charset_graph = new int[] {
+ 0x25ca, 0x2026, 0x2022, 0x3f,
+ 0xb6, 0x3f, 0xb0, 0xb1,
+ 0x3f, 0x3f, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0xaf,
+ 0x2014, 0x2014, 0x2014, 0x5f,
+ 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x7c, 0x2264, 0x2265, 0xb6,
+ 0x2260, 0xa3, 0xb7, 0x7f
+ };
+ private int vt100_charset_g_sel;
+ private int[] vt100_charset_g = { 0, 0 };
+ private Map<String, Object> vt100_saved;
+ private Map<String, Object> vt100_saved2;
+ private int vt100_saved_cx;
+ private int vt100_saved_cy;
+ private String vt100_out;
+
+ private int scroll_area_y0;
+ private int scroll_area_y1;
+
+ private List<Integer> tab_stops;
+
+ private int utf8_char;
+ private int utf8_units_count;
+ private int utf8_units_received;
+
+ private AtomicBoolean dirty = new AtomicBoolean(true);
+
+ public Terminal() {
+ this(80, 24);
+ }
+
+ public Terminal(int width, int height) {
+ this.width = width;
+ this.height = height;
+ reset_hard();
+ }
+
+ private void reset_hard() {
+ // Attribute mask: 0x0XFB0000
+ // X: Bit 0 - Underlined
+ // Bit 1 - Negative
+ // Bit 2 - Concealed
+ // F: Foreground
+ // B: Background
+ attr = 0x00fe0000;
+ // UTF-8 decoder
+ utf8_units_count = 0;
+ utf8_units_received = 0;
+ utf8_char = 0;
+ // Key filter
+ vt100_keyfilter_escape = false;
+ // Last char
+ vt100_lastchar = 0;
+ // Control sequences
+ vt100_parse_len = 0;
+ vt100_parse_state = State.None;
+ vt100_parse_func = 0;
+ vt100_parse_param = "";
+ // Buffers
+ vt100_out = "";
+ // Invoke other resets
+ reset_screen();
+ reset_soft();
+ }
+
+ private void reset_soft() {
+ // Attribute mask: 0x0XFB0000
+ // X: Bit 0 - Underlined
+ // Bit 1 - Negative
+ // Bit 2 - Concealed
+ // F: Foreground
+ // B: Background
+ attr = 0x00fe0000;
+ // Scroll parameters
+ scroll_area_y0 = 0;
+ scroll_area_y1 = height;
+ // Character sets
+ vt100_charset_is_single_shift = false;
+ vt100_charset_is_graphical = false;
+ vt100_charset_g_sel = 0;
+ vt100_charset_g = new int[] { 0, 0 };
+ // Modes
+ vt100_mode_insert = false;
+ vt100_mode_lfnewline = false;
+ vt100_mode_cursorkey = false;
+ vt100_mode_column_switch = false;
+ vt100_mode_inverse = false;
+ vt100_mode_origin = false;
+ vt100_mode_autowrap = true;
+ vt100_mode_cursor = true;
+ vt100_mode_alt_screen = false;
+ vt100_mode_backspace = false;
+ // Init DECSC state
+ esc_DECSC();
+ vt100_saved2 = vt100_saved;
+ esc_DECSC();
+ }
+
+ private void reset_screen() {
+ // Screen
+ screen = new int[width * height];
+ Arrays.fill(screen, attr | 0x0020);
+ screen2 = new int[width * height];
+ Arrays.fill(screen2, attr | 0x0020);
+ // Scroll parameters
+ scroll_area_y0 = 0;
+ scroll_area_y1 = height;
+ // Cursor position
+ cx = 0;
+ cy = 0;
+ // Tab stops
+ tab_stops = new ArrayList<Integer>();
+ for (int i = 7; i < width; i += 8) {
+ tab_stops.add(i);
+ }
+ }
+
+ //
+ // UTF-8 functions
+ //
+
+ private String utf8_decode(String d) {
+ StringBuilder o = new StringBuilder();
+ for (char c : d.toCharArray()) {
+ if (utf8_units_count != utf8_units_received) {
+ utf8_units_received++;
+ if ((c & 0xc0) == 0x80) {
+ utf8_char = (utf8_char << 6) | (c & 0x3f);
+ if (utf8_units_count == utf8_units_received) {
+ if (utf8_char < 0x10000) {
+ o.append((char) utf8_char);
+ }
+ utf8_units_count = utf8_units_received = 0;
+ }
+ } else {
+ o.append('?');
+ while (utf8_units_received-- > 0) {
+ o.append('?');
+ }
+ utf8_units_count = 0;
+ }
+ } else {
+ if ((c & 0x80) == 0x00) {
+ o.append(c);
+ } else if ((c & 0xe0) == 0xc0) {
+ utf8_units_count = 1;
+ utf8_char = c & 0x1f;
+ } else if ((c & 0xf0) == 0xe0) {
+ utf8_units_count = 2;
+ utf8_char = c & 0x0f;
+ } else if ((c & 0xf8) == 0xf0) {
+ utf8_units_count = 3;
+ utf8_char = c & 0x07;
+ } else {
+ o.append('?');
+ }
+
+ }
+ }
+ return o.toString();
+ }
+
+ private int utf8_charwidth(int c) {
+ if (c >= 0x2e80) {
+ return 2;
+ } else {
+ return 1;
+ }
+ }
+
+ //
+ // Low-level terminal functions
+ //
+
+ private int[] peek(int y0, int x0, int y1, int x1) {
+ return Arrays.copyOfRange(screen, width * y0 + x0, width * (y1 - 1) + x1);
+ }
+
+ private void poke(int y, int x, int[] s) {
+ System.arraycopy(s, 0, screen, width * y + x, s.length);
+ setDirty();
+ }
+
+ private void fill(int y0, int x0, int y1, int x1, int c) {
+ int d0 = width * y0 + x0;
+ int d1 = width * (y1 - 1) + x1;
+ if (d0 <= d1) {
+ Arrays.fill(screen, width * y0 + x0, width * (y1 - 1) + x1, c);
+ setDirty();
+ }
+ }
+
+ private void clear(int y0, int x0, int y1, int x1) {
+ fill(y0, x0, y1, x1, attr | 0x20);
+ }
+
+ //
+ // Scrolling functions
+ //
+
+ private void scroll_area_up(int y0, int y1) {
+ scroll_area_up(y0, y1, 1);
+ }
+
+ private void scroll_area_up(int y0, int y1, int n) {
+ n = Math.min(y1 - y0, n);
+ poke(y0, 0, peek(y0 + n, 0, y1, width));
+ clear(y1-n, 0, y1, width);
+ }
+
+ private void scroll_area_down(int y0, int y1) {
+ scroll_area_down(y0, y1, 1);
+ }
+
+ private void scroll_area_down(int y0, int y1, int n) {
+ n = Math.min(y1 - y0, n);
+ poke(y0 + n, 0, peek(y0, 0, y1-n, width));
+ clear(y0, 0, y0 + n, width);
+ }
+
+ private void scroll_area_set(int y0, int y1) {
+ y0 = Math.max(0, Math.min(height - 1, y0));
+ y1 = Math.max(1, Math.min(height, y1));
+ if (y1 > y0) {
+ scroll_area_y0 = y0;
+ scroll_area_y1 = y1;
+ }
+ }
+
+ private void scroll_line_right(int y, int x) {
+ scroll_line_right(y, x, 1);
+ }
+
+ private void scroll_line_right(int y, int x, int n) {
+ if (x < width) {
+ n = Math.min(width - cx, n);
+ poke(y, x + n, peek(y, x, y + 1, width - n));
+ clear(y, x, y + 1, x + n);
+ }
+ }
+
+ private void scroll_line_left(int y, int x) {
+ scroll_line_left(y, x, 1);
+ }
+
+ private void scroll_line_left(int y, int x, int n) {
+ if (x < width) {
+ n = Math.min(width - cx, n);
+ poke(y, x, peek(y, x + n, y + 1, width));
+ clear(y, width - n, y + 1, width);
+ }
+ }
+
+ //
+ // Cursor functions
+ //
+
+ private int[] cursor_line_width(int next_char) {
+ int wx = utf8_charwidth(next_char);
+ int lx = 0;
+ for (int x = 0; x < Math.min(cx, width); x++) {
+ int c = peek(cy, x, cy + 1, x + 1)[0] & 0xffff;
+ wx += utf8_charwidth(c);
+ lx += 1;
+ }
+ return new int[] { wx, lx };
+ }
+
+ private void cursor_up() {
+ cursor_up(1);
+ }
+
+ private void cursor_up(int n) {
+ cy = Math.max(scroll_area_y0, cy - n);
+ setDirty();
+ }
+
+ private void cursor_down() {
+ cursor_down(1);
+ }
+
+ private void cursor_down(int n) {
+ cy = Math.min(scroll_area_y1 - 1, cy + n);
+ setDirty();
+ }
+
+ private void cursor_left() {
+ cursor_left(1);
+ }
+
+ private void cursor_left(int n) {
+ eol = false;
+ cx = Math.max(0, cx - n);
+ setDirty();
+ }
+
+ private void cursor_right() {
+ cursor_right(1);
+ }
+
+ private void cursor_right(int n) {
+ eol = cx + n >= width;
+ cx = Math.min(width - 1, cx + n);
+ setDirty();
+ }
+
+ private void cursor_set_x(int x) {
+ eol = false;
+ cx = Math.max(0, x);
+ setDirty();
+ }
+
+ private void cursor_set_y(int y) {
+ cy = Math.max(0, Math.min(height - 1, y));
+ setDirty();
+ }
+
+ private void cursor_set(int y, int x) {
+ cursor_set_x(x);
+ cursor_set_y(y);
+ }
+
+ //
+ // Dumb terminal
+ //
+
+ private void ctrl_BS() {
+ int dy = (cx - 1) / width;
+ cursor_set(Math.max(scroll_area_y0, cy + dy), (cx - 1) % width);
+ }
+
+ private void ctrl_HT() {
+ ctrl_HT(1);
+ }
+
+ private void ctrl_HT(int n) {
+ if (n > 0 && cx >= width) {
+ return;
+ }
+ if (n <= 0 && cx == 0) {
+ return;
+ }
+ int ts = -1;
+ for (int i = 0; i < tab_stops.size(); i++) {
+ if (cx >= tab_stops.get(i)) {
+ ts = i;
+ }
+ }
+ ts += n;
+ if (ts < tab_stops.size() && ts >= 0) {
+ cursor_set_x(tab_stops.get(ts));
+ } else {
+ cursor_set_x(width - 1);
+ }
+ }
+
+ private void ctrl_LF() {
+ if (vt100_mode_lfnewline) {
+ ctrl_CR();
+ }
+ if (cy == scroll_area_y1 - 1) {
+ scroll_area_up(scroll_area_y0, scroll_area_y1);
+ } else {
+ cursor_down();
+ }
+ }
+
+ private void ctrl_CR() {
+ cursor_set_x(0);
+ }
+
+ private boolean dumb_write(int c) {
+ if (c < 32) {
+ if (c == 8) {
+ ctrl_BS();
+ } else if (c == 9) {
+ ctrl_HT();
+ } else if (c >= 10 && c <= 12) {
+ ctrl_LF();
+ } else if (c == 13) {
+ ctrl_CR();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void dumb_echo(int c) {
+ if (eol) {
+ if (vt100_mode_autowrap) {
+ ctrl_CR();
+ ctrl_LF();
+ } else {
+ cx = cursor_line_width(c)[1] - 1;
+ }
+ }
+ if (vt100_mode_insert) {
+ scroll_line_right(cy, cx);
+ }
+ if (vt100_charset_is_single_shift) {
+ vt100_charset_is_single_shift = false;
+ } else if (vt100_charset_is_graphical && ((c & 0xffe0) == 0x0060)) {
+ c = vt100_charset_graph[c - 0x60];
+ }
+ poke(cy, cx, new int[] { attr | c });
+ cursor_right();
+ }
+
+ //
+ // VT100
+ //
+
+ private void vt100_charset_update() {
+ vt100_charset_is_graphical = (vt100_charset_g[vt100_charset_g_sel] == 2);
+ }
+
+ private void vt100_charset_set(int g) {
+ // Invoke active character set
+ vt100_charset_g_sel = g;
+ vt100_charset_update();
+ }
+
+ private void vt100_charset_select(int g, int charset) {
+ // Select charset
+ vt100_charset_g[g] = charset;
+ vt100_charset_update();
+ }
+
+ private void vt100_setmode(String p, boolean state) {
+ // Set VT100 mode
+ String[] ps = vt100_parse_params(p, new String[0]);
+ for (String m : ps) {
+ // 1 : GATM: Guarded area transfer
+ // 2 : KAM: Keyboard action
+ // 3 : CRM: Control representation
+ if ("4".equals(m)) {
+ // Insertion replacement mode
+ vt100_mode_insert = state;
+ // 5 : SRTM: Status reporting transfer
+ // 7 : VEM: Vertical editing
+ // 10 : HEM: Horizontal editing
+ // 11 : PUM: Positioning nit
+ // 12 : SRM: Send/receive
+ // 13 : FEAM: Format effector action
+ // 14 : FETM: Format effector transfer
+ // 15 : MATM: Multiple area transfer
+ // 16 : TTM: Transfer termination
+ // 17 : SATM: Selected area transfer
+ // 18 : TSM: Tabulation stop
+ // 19 : EBM: Editing boundary
+ } else if ("20".equals(m)) {
+ // LNM: Line feed/new line
+ vt100_mode_lfnewline = state;
+ } else if ("?1".equals(m)) {
+ // DECCKM: Cursor keys
+ vt100_mode_cursorkey = state;
+ // ?2 : DECANM: ANSI
+ } else if ("?3".equals(m)) {
+ // DECCOLM: Column
+ if (vt100_mode_column_switch) {
+ if (state) {
+ width = 132;
+ } else {
+ width = 80;
+ }
+ reset_screen();
+ }
+ // ?4 : DECSCLM: Scrolling
+ } else if ("?5".equals(m)) {
+ // DECSCNM: Screen
+ vt100_mode_inverse = state;
+ } else if ("?6".equals(m)) {
+ // DECOM: Origin
+ vt100_mode_origin = state;
+ if (state) {
+ cursor_set(scroll_area_y0, 0);
+ } else {
+ cursor_set(0, 0);
+ }
+ } else if ("?7".equals(m)) {
+ // DECAWM: Autowrap
+ vt100_mode_autowrap = state;
+ // ?8 : DECARM: Autorepeat
+ // ?9 : Interlacing
+ // ?18 : DECPFF: Print form feed
+ // ?19 : DECPEX: Printer extent
+ } else if ("?25".equals(m)) {
+ // DECTCEM: Text cursor enable
+ vt100_mode_cursor = state;
+ // ?34 : DECRLM: Cursor direction, right to left
+ // ?35 : DECHEBM: Hebrew keyboard mapping
+ // ?36 : DECHEM: Hebrew encoding mode
+ } else if ("?40".equals(m)) {
+ // Column switch control
+ vt100_mode_column_switch = state;
+ // ?42 : DECNRCM: National replacement character set
+ } else if ("?47".equals(m)) {
+ // Alternate screen mode
+ if ((state && !vt100_mode_alt_screen) || (!state && vt100_mode_alt_screen)) {
+ int[] s = screen; screen = screen2; screen2 = s;
+ Map<String, Object> map = vt100_saved; vt100_saved = vt100_saved2; vt100_saved = map;
+ }
+ vt100_mode_alt_screen = state;
+ // ?57 : DECNAKB: Greek keyboard mapping
+ } else if ("?67".equals(m)) {
+ // DECBKM: Backarrow key
+ vt100_mode_backspace = state;
+ }
+ // ?98 : DECARSM: auto-resize
+ // ?101 : DECCANSM: Conceal answerback message
+ // ?109 : DECCAPSLK: caps lock
+ }
+ }
+
+ private void ctrl_SO() {
+ vt100_charset_set(1);
+ }
+
+ private void ctrl_SI() {
+ vt100_charset_set(0);
+ }
+
+ private void esc_CSI() {
+ vt100_parse_reset(State.Csi);
+ }
+
+ private void esc_DECALN() {
+ fill(0, 0, height, width, 0x00fe0045);
+ }
+
+ private void esc_G0_0() {
+ vt100_charset_select(0, 0);
+ }
+ private void esc_G0_1() {
+ vt100_charset_select(0, 1);
+ }
+ private void esc_G0_2() {
+ vt100_charset_select(0, 2);
+ }
+ private void esc_G0_3() {
+ vt100_charset_select(0, 3);
+ }
+ private void esc_G0_4() {
+ vt100_charset_select(0, 4);
+ }
+
+ private void esc_G1_0() {
+ vt100_charset_select(1, 0);
+ }
+ private void esc_G1_1() {
+ vt100_charset_select(1, 1);
+ }
+ private void esc_G1_2() {
+ vt100_charset_select(1, 2);
+ }
+ private void esc_G1_3() {
+ vt100_charset_select(1, 3);
+ }
+ private void esc_G1_4() {
+ vt100_charset_select(1, 4);
+ }
+
+ private void esc_DECSC() {
+ vt100_saved = new HashMap<String, Object>();
+ vt100_saved.put("cx", cx);
+ vt100_saved.put("cy", cy);
+ vt100_saved.put("attr", attr);
+ vt100_saved.put("vt100_charset_g_sel", vt100_charset_g_sel);
+ vt100_saved.put("vt100_charset_g", vt100_charset_g);
+ vt100_saved.put("vt100_mode_autowrap", vt100_mode_autowrap);
+ vt100_saved.put("vt100_mode_origin", vt100_mode_origin);
+ }
+
+ private void esc_DECRC() {
+ cx = (Integer) vt100_saved.get("cx");
+ cy = (Integer) vt100_saved.get("cy");
+ attr = (Integer) vt100_saved.get("attr");
+ vt100_charset_g_sel = (Integer) vt100_saved.get("vt100_charset_g_sel");
+ vt100_charset_g = (int[]) vt100_saved.get("vt100_charset_g");
+ vt100_charset_update();
+ vt100_mode_autowrap = (Boolean) vt100_saved.get("vt100_mode_autowrap");
+ vt100_mode_origin = (Boolean) vt100_saved.get("vt100_mode_origin");
+ }
+
+ private void esc_IND() {
+ ctrl_LF();
+ }
+
+ private void esc_NEL() {
+ ctrl_CR();
+ ctrl_LF();
+ }
+
+ private void esc_HTS() {
+ csi_CTC("0");
+ }
+
+ private void esc_RI() {
+ if (cy == scroll_area_y0) {
+ scroll_area_down(scroll_area_y0, scroll_area_y1);
+ } else {
+ cursor_up();
+ }
+ }
+
+ private void esc_SS2() {
+ vt100_charset_is_single_shift = true;
+ }
+
+ private void esc_SS3() {
+ vt100_charset_is_single_shift = true;
+ }
+
+ private void esc_DCS() {
+ vt100_parse_reset(State.Str);
+ }
+
+ private void esc_SOS() {
+ vt100_parse_reset(State.Str);
+ }
+
+ private void esc_DECID() {
+ csi_DA("0");
+ }
+
+ private void esc_ST() {
+ }
+
+ private void esc_OSC() {
+ vt100_parse_reset(State.Str);
+ }
+
+ private void esc_PM() {
+ vt100_parse_reset(State.Str);
+ }
+
+ private void esc_APC() {
+ vt100_parse_reset(State.Str);
+ }
+
+ private void esc_RIS() {
+ reset_hard();
+ }
+
+ private void csi_ICH(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ scroll_line_right(cy, cx, ps[0]);
+ }
+
+ private void csi_CUU(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ cursor_up(Math.max(1, ps[0]));
+ }
+
+ private void csi_CUD(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ cursor_down(Math.max(1, ps[0]));
+ }
+
+ private void csi_CUF(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ cursor_right(Math.max(1, ps[0]));
+ }
+
+ private void csi_CUB(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ cursor_left(Math.max(1, ps[0]));
+ }
+
+ private void csi_CNL(String p) {
+ csi_CUD(p);
+ ctrl_CR();
+ }
+
+ private void csi_CPL(String p) {
+ csi_CUU(p);
+ ctrl_CR();
+ }
+
+ private void csi_CHA(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ cursor_set_x(ps[0] - 1);
+ }
+
+ private void csi_CUP(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1, 1 });
+ if (vt100_mode_origin) {
+ cursor_set(scroll_area_y0 + ps[0] - 1, ps[1] - 1);
+ } else {
+ cursor_set(ps[0] - 1, ps[1] - 1);
+ }
+ }
+
+ private void csi_CHT(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ ctrl_HT(Math.max(1, ps[0]));
+ }
+
+ private void csi_ED(String p) {
+ String[] ps = vt100_parse_params(p, new String[] { "0" });
+ if ("0".equals(ps[0])) {
+ clear(cy, cx, height, width);
+ } else if ("1".equals(ps[0])) {
+ clear(0, 0, cy + 1, cx + 1);
+ } else if ("2".equals(ps[0])) {
+ clear(0, 0, height, width);
+ }
+ }
+
+ private void csi_EL(String p) {
+ String[] ps = vt100_parse_params(p, new String[] { "0" });
+ if ("0".equals(ps[0])) {
+ clear(cy, cx, cy + 1, width);
+ } else if ("1".equals(ps[0])) {
+ clear(cy, 0, cy + 1, cx + 1);
+ } else if ("2".equals(ps[0])) {
+ clear(cy, 0, cy + 1, width);
+ }
+ }
+
+ private void csi_IL(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ if (cy >= scroll_area_y0 && cy < scroll_area_y1) {
+ scroll_area_down(cy, scroll_area_y1, Math.max(1, ps[0]));
+ }
+ }
+
+ private void csi_DL(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ if (cy >= scroll_area_y0 && cy < scroll_area_y1) {
+ scroll_area_up(cy, scroll_area_y1, Math.max(1, ps[0]));
+ }
+ }
+
+ private void csi_DCH(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ scroll_line_left(cy, cx, Math.max(1, ps[0]));
+ }
+
+ private void csi_SU(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ scroll_area_up(scroll_area_y0, scroll_area_y1, Math.max(1, ps[0]));
+ }
+
+ private void csi_SD(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ scroll_area_down(scroll_area_y0, scroll_area_y1, Math.max(1, ps[0]));
+ }
+
+ private void csi_CTC(String p) {
+ String[] ps = vt100_parse_params(p, new String[] { "0" });
+ for (String m : ps) {
+ if ("0".equals(m)) {
+ if (tab_stops.indexOf(cx) < 0) {
+ tab_stops.add(cx);
+ Collections.sort(tab_stops);
+ }
+ } else if ("2".equals(m)) {
+ tab_stops.remove(Integer.valueOf(cx));
+ } else if ("5".equals(m)) {
+ tab_stops = new ArrayList<Integer>();
+ }
+ }
+ }
+
+ private void csi_ECH(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ int n = Math.min(width - cx, Math.max(1, ps[0]));
+ clear(cy, cx, cy + 1, cx + n);
+ }
+
+ private void csi_CBT(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ ctrl_HT(1 - Math.max(1, ps[0]));
+ }
+
+ private void csi_HPA(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ cursor_set_x(ps[0] - 1);
+ }
+
+ private void csi_HPR(String p) {
+ csi_CUF(p);
+ }
+
+ private void csi_REP(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ if (vt100_lastchar < 32) {
+ return;
+ }
+ int n = Math.min(2000, Math.max(1, ps[0]));
+ while (n-- > 0) {
+ dumb_echo(vt100_lastchar);
+ }
+ vt100_lastchar = 0;
+ }
+
+ private void csi_DA(String p) {
+ String[] ps = vt100_parse_params(p, new String[] { "0" });
+ if ("0".equals(ps[0])) {
+ vt100_out = "\u001b[?1;2c";
+ } else if (">0".equals(ps[0]) || ">".equals(ps[0])) {
+ vt100_out = "\u001b[>0;184;0c";
+ }
+ }
+
+ private void csi_VPA(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ cursor_set_y(ps[0] - 1);
+ }
+
+ private void csi_VPR(String p) {
+ csi_CUD(p);
+ }
+
+ private void csi_HVP(String p) {
+ csi_CUP(p);
+ }
+
+ private void csi_TBC(String p) {
+ String[] ps = vt100_parse_params(p, new String[] { "0" });
+ if ("0".equals(ps[0])) {
+ csi_CTC("2");
+ } else if ("3".equals(ps[0])) {
+ csi_CTC("5");
+ }
+ }
+
+ private void csi_SM(String p) {
+ vt100_setmode(p, true);
+ }
+
+ private void csi_RM(String p) {
+ vt100_setmode(p, false);
+ }
+
+ private void csi_SGR(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 0 });
+ for (int m : ps) {
+ if (m == 0) {
+ attr = 0x00fe0000;
+ } else if (m == 1) {
+ attr |= 0x08000000;
+ } else if (m == 4) {
+ attr |= 0x01000000;
+ } else if (m == 7) {
+ attr |= 0x02000000;
+ } else if (m == 8) {
+ attr |= 0x04000000;
+ } else if (m == 24) {
+ attr &= 0x7eff0000;
+ } else if (m == 27) {
+ attr &= 0x7dff0000;
+ } else if (m == 28) {
+ attr &= 0x7bff0000;
+ } else if (m >= 30 && m <= 37) {
+ attr = (attr & 0x7f0f0000) | ((m - 30) << 20);
+ } else if (m == 39) {
+ attr = (attr & 0x7f0f0000) | 0x00f00000;
+ } else if (m >= 40 && m <= 47) {
+ attr = (attr & 0x7ff00000) | ((m - 40) << 16);
+ } else if (m == 49) {
+ attr = (attr & 0x7ff00000) | 0x000e0000;
+ }
+ }
+ }
+
+ private void csi_DSR(String p) {
+ String[] ps = vt100_parse_params(p, new String[] { "0" });
+ if ("5".equals(ps[0])) {
+ vt100_out = "\u001b[0n";
+ } else if ("6".equals(ps[0])) {
+ vt100_out = "\u001b[" + (cy + 1) + ";" + (cx + 1) + "R";
+ } else if ("7".equals(ps[0])) {
+ vt100_out = "gogo-term";
+ } else if ("8".equals(ps[0])) {
+ vt100_out = "1.0-SNAPSHOT";
+ } else if ("?6".equals(ps[0])) {
+ vt100_out = "\u001b[" + (cy + 1) + ";" + (cx + 1) + ";0R";
+ } else if ("?15".equals(ps[0])) {
+ vt100_out = "\u001b[?13n";
+ } else if ("?25".equals(ps[0])) {
+ vt100_out = "\u001b[?20n";
+ } else if ("?26".equals(ps[0])) {
+ vt100_out = "\u001b[?27;1n";
+ } else if ("?53".equals(ps[0])) {
+ vt100_out = "\u001b[?53n";
+ }
+ // ?75 : Data Integrity report
+ // ?62 : Macro Space report
+ // ?63 : Memory Checksum report
+ }
+
+ private void csi_DECSTBM(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1, height });
+ scroll_area_set(ps[0] - 1, ps[1]);
+ if (vt100_mode_origin) {
+ cursor_set(scroll_area_y0, 0);
+ } else {
+ cursor_set(0, 0);
+ }
+ }
+
+ private void csi_SCP(String p) {
+ vt100_saved_cx = cx;
+ vt100_saved_cy = cy;
+ }
+
+ private void csi_RCP(String p) {
+ cx = vt100_saved_cx;
+ cy = vt100_saved_cy;
+ }
+
+ private void csi_DECREQTPARM(String p) {
+ String[] ps = vt100_parse_params(p, new String[0]);
+ if ("0".equals(ps[0])) {
+ vt100_out = "\u001b[2;1;1;112;112;1;0x";
+ } else if ("1".equals(ps[0])) {
+ vt100_out = "\u001b[3;1;1;112;112;1;0x";
+ }
+ }
+
+ private void csi_DECSTR(String p) {
+ reset_soft();
+ }
+
+ //
+ // VT100 parser
+ //
+
+ private String[] vt100_parse_params(String p, String[] defaults) {
+ String prefix = "";
+ if (p.length() > 0) {
+ if (p.charAt(0) >= '<' && p.charAt(0) <= '?') {
+ prefix = "" + p.charAt(0);
+ p = p.substring(1);
+ }
+ }
+ String[] ps = p.split(";");
+ int n = Math.max(ps.length, defaults.length);
+ String[] values = new String[n];
+ for (int i = 0; i < n; i++) {
+ String value = null;
+ if (i < ps.length) {
+ value = prefix + ps[i];
+ }
+ if (value == null && i < defaults.length) {
+ value = defaults[i];
+ }
+ if (value == null) {
+ value = "";
+ }
+ values[i] = value;
+ }
+ return values;
+ }
+
+ private int[] vt100_parse_params(String p, int[] defaults) {
+ String prefix = "";
+ p = p == null ? "" : p;
+ if (p.length() > 0) {
+ if (p.charAt(0) >= '<' && p.charAt(0) <= '?') {
+ prefix = p.substring(0, 1);
+ p = p.substring(1);
+ }
+ }
+ String[] ps = p.split(";");
+ int n = Math.max(ps.length, defaults.length);
+ int[] values = new int[n];
+ for (int i = 0; i < n; i++) {
+ Integer value = null;
+ if (i < ps.length) {
+ String v = prefix + ps[i];
+ try {
+ value = Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ }
+ }
+ if (value == null && i < defaults.length) {
+ value = defaults[i];
+ }
+ if (value == null) {
+ value = 0;
+ }
+ values[i] = value;
+ }
+ return values;
+ }
+
+ private void vt100_parse_reset() {
+ vt100_parse_reset(State.None);
+ }
+
+ private void vt100_parse_reset(State state) {
+ vt100_parse_state = state;
+ vt100_parse_len = 0;
+ vt100_parse_func = 0;
+ vt100_parse_param = "";
+ }
+
+ private void vt100_parse_process() {
+ if (vt100_parse_state == State.Esc) {
+ switch (vt100_parse_func) {
+ case 0x0036: /* DECBI */ break;
+ case 0x0037: esc_DECSC(); break;
+ case 0x0038: esc_DECRC(); break;
+ case 0x0042: /* BPH */ break;
+ case 0x0043: /* NBH */ break;
+ case 0x0044: esc_IND(); break;
+ case 0x0045: esc_NEL(); break;
+ case 0x0046: /* SSA */ esc_NEL(); break;
+ case 0x0048: esc_HTS(); break;
+ case 0x0049: /* HTJ */ break;
+ case 0x004A: /* VTS */ break;
+ case 0x004B: /* PLD */ break;
+ case 0x004C: /* PLU */ break;
+ case 0x004D: esc_RI(); break;
+ case 0x004E: esc_SS2(); break;
+ case 0x004F: esc_SS3(); break;
+ case 0x0050: esc_DCS(); break;
+ case 0x0051: /* PU1 */ break;
+ case 0x0052: /* PU2 */ break;
+ case 0x0053: /* STS */ break;
+ case 0x0054: /* CCH */ break;
+ case 0x0055: /* MW */ break;
+ case 0x0056: /* SPA */ break;
+ case 0x0057: /* ESA */ break;
+ case 0x0058: esc_SOS(); break;
+ case 0x005A: /* SCI */ break;
+ case 0x005B: esc_CSI(); break;
+ case 0x005C: esc_ST(); break;
+ case 0x005D: esc_OSC(); break;
+ case 0x005E: esc_PM(); break;
+ case 0x005F: esc_APC(); break;
+ case 0x0060: /* DMI */ break;
+ case 0x0061: /* INT */ break;
+ case 0x0062: /* EMI */ break;
+ case 0x0063: esc_RIS(); break;
+ case 0x0064: /* CMD */ break;
+ case 0x006C: /* RM */ break;
+ case 0x006E: /* LS2 */ break;
+ case 0x006F: /* LS3 */ break;
+ case 0x007C: /* LS3R */ break;
+ case 0x007D: /* LS2R */ break;
+ case 0x007E: /* LS1R */ break;
+ case 0x2338: esc_DECALN(); break;
+ case 0x2841: esc_G0_0(); break;
+ case 0x2842: esc_G0_1(); break;
+ case 0x2830: esc_G0_2(); break;
+ case 0x2831: esc_G0_3(); break;
+ case 0x2832: esc_G0_4(); break;
+ case 0x2930: esc_G1_2(); break;
+ case 0x2931: esc_G1_3(); break;
+ case 0x2932: esc_G1_4(); break;
+ case 0x2941: esc_G1_0(); break;
+ case 0x2942: esc_G1_1(); break;
+ }
+ if (vt100_parse_state == State.Esc) {
+ vt100_parse_reset();
+ }
+ } else {
+ switch (vt100_parse_func) {
+ case 0x0040: csi_ICH(vt100_parse_param); break;
+ case 0x0041: csi_CUU(vt100_parse_param); break;
+ case 0x0042: csi_CUD(vt100_parse_param); break;
+ case 0x0043: csi_CUF(vt100_parse_param); break;
+ case 0x0044: csi_CUB(vt100_parse_param); break;
+ case 0x0045: csi_CNL(vt100_parse_param); break;
+ case 0x0046: csi_CPL(vt100_parse_param); break;
+ case 0x0047: csi_CHA(vt100_parse_param); break;
+ case 0x0048: csi_CUP(vt100_parse_param); break;
+ case 0x0049: csi_CHT(vt100_parse_param); break;
+ case 0x004A: csi_ED(vt100_parse_param); break;
+ case 0x004B: csi_EL(vt100_parse_param); break;
+ case 0x004C: csi_IL(vt100_parse_param); break;
+ case 0x004D: csi_DL(vt100_parse_param); break;
+ case 0x004E: /* EF */ break;
+ case 0x004F: /* EA */ break;
+ case 0x0050: csi_DCH(vt100_parse_param); break;
+ case 0x0051: /* SEE */ break;
+ case 0x0052: /* CPR */ break;
+ case 0x0053: csi_SU(vt100_parse_param); break;
+ case 0x0054: csi_SD(vt100_parse_param); break;
+ case 0x0055: /* NP */ break;
+ case 0x0056: /* PP */ break;
+ case 0x0057: csi_CTC(vt100_parse_param); break;
+ case 0x0058: csi_ECH(vt100_parse_param); break;
+ case 0x0059: /* CVT */ break;
+ case 0x005A: csi_CBT(vt100_parse_param); break;
+ case 0x005B: /* SRS */ break;
+ case 0x005C: /* PTX */ break;
+ case 0x005D: /* SDS */ break;
+ case 0x005E: /* SIMD */ break;
+ case 0x0060: csi_HPA(vt100_parse_param); break;
+ case 0x0061: csi_HPR(vt100_parse_param); break;
+ case 0x0062: csi_REP(vt100_parse_param); break;
+ case 0x0063: csi_DA(vt100_parse_param); break;
+ case 0x0064: csi_VPA(vt100_parse_param); break;
+ case 0x0065: csi_VPR(vt100_parse_param); break;
+ case 0x0066: csi_HVP(vt100_parse_param); break;
+ case 0x0067: csi_TBC(vt100_parse_param); break;
+ case 0x0068: csi_SM(vt100_parse_param); break;
+ case 0x0069: /* MC */ break;
+ case 0x006A: /* HPB */ break;
+ case 0x006B: /* VPB */ break;
+ case 0x006C: csi_RM(vt100_parse_param); break;
+ case 0x006D: csi_SGR(vt100_parse_param); break;
+ case 0x006E: csi_DSR(vt100_parse_param); break;
+ case 0x006F: /* DAQ */ break;
+ case 0x0072: csi_DECSTBM(vt100_parse_param); break;
+ case 0x0073: csi_SCP(vt100_parse_param); break;
+ case 0x0075: csi_RCP(vt100_parse_param); break;
+ case 0x0078: csi_DECREQTPARM(vt100_parse_param); break;
+ case 0x2040: /* SL */ break;
+ case 0x2041: /* SR */ break;
+ case 0x2042: /* GSM */ break;
+ case 0x2043: /* GSS */ break;
+ case 0x2044: /* FNT */ break;
+ case 0x2045: /* TSS */ break;
+ case 0x2046: /* JFY */ break;
+ case 0x2047: /* SPI */ break;
+ case 0x2048: /* QUAD */ break;
+ case 0x2049: /* SSU */ break;
+ case 0x204A: /* PFS */ break;
+ case 0x204B: /* SHS */ break;
+ case 0x204C: /* SVS */ break;
+ case 0x204D: /* IGS */ break;
+ case 0x204E: /* deprecated: HTSA */ break;
+ case 0x204F: /* IDCS */ break;
+ case 0x2050: /* PPA */ break;
+ case 0x2051: /* PPR */ break;
+ case 0x2052: /* PPB */ break;
+ case 0x2053: /* SPD */ break;
+ case 0x2054: /* DTA */ break;
+ case 0x2055: /* SLH */ break;
+ case 0x2056: /* SLL */ break;
+ case 0x2057: /* FNK */ break;
+ case 0x2058: /* SPQR */ break;
+ case 0x2059: /* SEF */ break;
+ case 0x205A: /* PEC */ break;
+ case 0x205B: /* SSW */ break;
+ case 0x205C: /* SACS */ break;
+ case 0x205D: /* SAPV */ break;
+ case 0x205E: /* STAB */ break;
+ case 0x205F: /* GCC */ break;
+ case 0x2060: /* TAPE */ break;
+ case 0x2061: /* TALE */ break;
+ case 0x2062: /* TAC */ break;
+ case 0x2063: /* TCC */ break;
+ case 0x2064: /* TSR */ break;
+ case 0x2065: /* SCO */ break;
+ case 0x2066: /* SRCS */ break;
+ case 0x2067: /* SCS */ break;
+ case 0x2068: /* SLS */ break;
+ case 0x2069: /* SPH */ break;
+ case 0x206A: /* SPL */ break;
+ case 0x206B: /* SCP */ break;
+ case 0x2170: csi_DECSTR(vt100_parse_param); break;
+ case 0x2472: /* DECCARA */ break;
+ case 0x2477: /* DECRQPSR */ break;
+ }
+ if (vt100_parse_state == State.Csi) {
+ vt100_parse_reset();
+ }
+ }
+ }
+
+ private boolean vt100_write(int c) {
+ if (c < 32) {
+ if (c == 27) {
+ vt100_parse_reset(State.Esc);
+ return true;
+ } else if (c == 14) {
+ ctrl_SO();
+ } else if (c == 15) {
+ ctrl_SI();
+ }
+ } else if ((c & 0xffe0) == 0x0080) {
+ vt100_parse_reset(State.Esc);
+ vt100_parse_func = (char)(c - 0x0040);
+ vt100_parse_process();
+ return true;
+ }
+ if (vt100_parse_state != State.None) {
+ if (vt100_parse_state == State.Str) {
+ if (c >= 32) {
+ return true;
+ }
+ vt100_parse_reset();
+ } else {
+ if (c < 32) {
+ if (c == 24 || c == 26) {
+ vt100_parse_reset();
+ return true;
+ }
+ } else {
+ vt100_parse_len += 1;
+ if (vt100_parse_len > 32) {
+ vt100_parse_reset();
+ } else {
+ int msb = c & 0xf0;
+ if (msb == 0x20) {
+ vt100_parse_func <<= 8;
+ vt100_parse_func += (char) c;
+ } else if (msb == 0x30 && vt100_parse_state == State.Csi) {
+ vt100_parse_param += new String(new char[] { (char) c } );
+ } else {
+ vt100_parse_func <<= 8;
+ vt100_parse_func += (char) c;
+ vt100_parse_process();
+ }
+ return true;
+ }
+ }
+ }
+ }
+ vt100_lastchar = c;
+ return false;
+ }
+
+ //
+ // Dirty
+ //
+
+ private synchronized void setDirty() {
+ dirty.set(true);
+ notifyAll();
+ }
+
+ //
+ // External interface
+ //
+
+ public synchronized boolean setSize(int w, int h) {
+ if (w < 2 || w > 256 || h < 2 || h > 256) {
+ return false;
+ }
+ this.width = w;
+ this.height = h;
+ reset_screen();
+ return true;
+ }
+
+ public synchronized String read() {
+ String d = vt100_out;
+ vt100_out = "";
+ return d;
+ }
+
+ public synchronized String pipe(String d) {
+ String o = "";
+ for (char c : d.toCharArray()) {
+ if (vt100_keyfilter_escape) {
+ vt100_keyfilter_escape = false;
+ if (vt100_mode_cursorkey) {
+ switch (c) {
+ case '~': o += "~"; break;
+ case 'A': o += "\u001bOA"; break;
+ case 'B': o += "\u001bOB"; break;
+ case 'C': o += "\u001bOC"; break;
+ case 'D': o += "\u001bOD"; break;
+ case 'F': o += "\u001bOF"; break;
+ case 'H': o += "\u001bOH"; break;
+ case '1': o += "\u001b[5~"; break;
+ case '2': o += "\u001b[6~"; break;
+ case '3': o += "\u001b[2~"; break;
+ case '4': o += "\u001b[3~"; break;
+ case 'a': o += "\u001bOP"; break;
+ case 'b': o += "\u001bOQ"; break;
+ case 'c': o += "\u001bOR"; break;
+ case 'd': o += "\u001bOS"; break;
+ case 'e': o += "\u001b[15~"; break;
+ case 'f': o += "\u001b[17~"; break;
+ case 'g': o += "\u001b[18~"; break;
+ case 'h': o += "\u001b[19~"; break;
+ case 'i': o += "\u001b[20~"; break;
+ case 'j': o += "\u001b[21~"; break;
+ case 'k': o += "\u001b[23~"; break;
+ case 'l': o += "\u001b[24~"; break;
+ }
+ } else {
+ switch (c) {
+ case '~': o += "~"; break;
+ case 'A': o += "\u001b[A"; break;
+ case 'B': o += "\u001b[B"; break;
+ case 'C': o += "\u001b[C"; break;
+ case 'D': o += "\u001b[D"; break;
+ case 'F': o += "\u001b[F"; break;
+ case 'H': o += "\u001b[H"; break;
+ case '1': o += "\u001b[5~"; break;
+ case '2': o += "\u001b[6~"; break;
+ case '3': o += "\u001b[2~"; break;
+ case '4': o += "\u001b[3~"; break;
+ case 'a': o += "\u001bOP"; break;
+ case 'b': o += "\u001bOQ"; break;
+ case 'c': o += "\u001bOR"; break;
+ case 'd': o += "\u001bOS"; break;
+ case 'e': o += "\u001b[15~"; break;
+ case 'f': o += "\u001b[17~"; break;
+ case 'g': o += "\u001b[18~"; break;
+ case 'h': o += "\u001b[19~"; break;
+ case 'i': o += "\u001b[20~"; break;
+ case 'j': o += "\u001b[21~"; break;
+ case 'k': o += "\u001b[23~"; break;
+ case 'l': o += "\u001b[24~"; break;
+ }
+ }
+ } else if (c == '~') {
+ vt100_keyfilter_escape = true;
+ } else if (c == 127) {
+ if (vt100_mode_backspace) {
+ o += (char) 8;
+ } else {
+ o += (char) 127;
+ }
+ } else {
+ o += c;
+ if (vt100_mode_lfnewline && c == 13) {
+ o += (char) 10;
+ }
+ }
+ }
+ return o;
+ }
+
+ public synchronized boolean write(String d) {
+ d = utf8_decode(d);
+ for (int c : d.toCharArray()) {
+ if (vt100_write(c)) {
+ continue;
+ }
+ if (dumb_write(c)) {
+ continue;
+ }
+ if (c <= 0xffff) {
+ dumb_echo(c);
+ }
+ }
+ return true;
+ }
+
+ public synchronized String dump(long timeout, boolean forceDump) throws InterruptedException {
+ if (!dirty.get() && timeout > 0) {
+ wait(timeout);
+ }
+ if (dirty.compareAndSet(true, false) || forceDump) {
+ StringBuilder sb = new StringBuilder();
+ int prev_attr = -1;
+ int cx = Math.min(this.cx, width - 1);
+ int cy = this.cy;
+ sb.append("<div><pre class='term'>");
+ for (int y = 0; y < height; y++) {
+ int wx = 0;
+ for (int x = 0; x < width; x++) {
+ int d = screen[y * width + x];
+ int c = d & 0xffff;
+ int a = d >> 16;
+ if (cy == y && cx == x && vt100_mode_cursor) {
+ a = a & 0xfff0 | 0x000c;
+ }
+ if (a != prev_attr) {
+ if (prev_attr != -1) {
+ sb.append("</span>");
+ }
+ int bg = a & 0x000f;
+ int fg = (a & 0x00f0) >> 4;
+ boolean inv = (a & 0x0200) != 0;
+ boolean inv2 = vt100_mode_inverse;
+ if (inv && !inv2 || inv2 && !inv) {
+ int i = fg; fg = bg; bg = i;
+ }
+ if ((a & 0x0400) != 0) {
+ fg = 0x0c;
+ }
+ String ul;
+ if ((a & 0x0100) != 0) {
+ ul = " ul";
+ } else {
+ ul = "";
+ }
+ String b;
+ if ((a & 0x0800) != 0) {
+ b = " b";
+ } else {
+ b = "";
+ }
+ sb.append("<span class='f").append(fg).append(" b").append(bg).append(ul).append(b).append("'>");
+ prev_attr = a;
+ }
+ switch (c) {
+ case '&': sb.append("&"); break;
+ case '<': sb.append("<"); break;
+ case '>': sb.append(">"); break;
+ default:
+ wx += utf8_charwidth(c);
+ if (wx <= width) {
+ sb.append((char) c);
+ }
+ break;
+ }
+ }
+ sb.append("\n");
+ }
+ sb.append("</span></pre></div>");
+ return sb.toString();
+ }
+ return null;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ sb.append((char) (screen[y * width + x] & 0xffff));
+ }
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+}
diff --git a/karaf/webconsole/gogo/src/main/java/org/apache/felix/karaf/webconsole/gogo/WebTerminal.java b/karaf/webconsole/gogo/src/main/java/org/apache/felix/karaf/webconsole/gogo/WebTerminal.java
new file mode 100644
index 0000000..550cb03
--- /dev/null
+++ b/karaf/webconsole/gogo/src/main/java/org/apache/felix/karaf/webconsole/gogo/WebTerminal.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.webconsole.gogo;
+
+import java.io.InputStreamReader;
+import java.io.InputStream;
+import java.io.IOException;
+
+public class WebTerminal extends jline.Terminal {
+
+ public static final short ARROW_START = 27;
+ public static final short ARROW_PREFIX = 91;
+ public static final short ARROW_LEFT = 68;
+ public static final short ARROW_RIGHT = 67;
+ public static final short ARROW_UP = 65;
+ public static final short ARROW_DOWN = 66;
+ public static final short O_PREFIX = 79;
+ public static final short HOME_CODE = 72;
+ public static final short END_CODE = 70;
+
+ public static final short DEL_THIRD = 51;
+ public static final short DEL_SECOND = 126;
+
+ private int width;
+ private int height;
+ private boolean backspaceDeleteSwitched = false;
+ private String encoding = System.getProperty("input.encoding", "UTF-8");
+ private ReplayPrefixOneCharInputStream replayStream = new ReplayPrefixOneCharInputStream(encoding);
+ private InputStreamReader replayReader;
+
+ public WebTerminal(int width, int height) {
+ this.width = width;
+ this.height = height;
+ try {
+ replayReader = new InputStreamReader(replayStream, encoding);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void initializeTerminal() throws Exception {
+ }
+
+ public void restoreTerminal() throws Exception {
+ }
+
+ public int getTerminalWidth() {
+ return width;
+ }
+
+ public int getTerminalHeight() {
+ return height;
+ }
+
+ public boolean isSupported() {
+ return true;
+ }
+
+ public boolean getEcho() {
+ return false;
+ }
+
+ public boolean isEchoEnabled() {
+ return false;
+ }
+
+ public void enableEcho() {
+ }
+
+ public void disableEcho() {
+ }
+
+ public int readVirtualKey(InputStream in) throws IOException {
+ int c = readCharacter(in);
+
+ if (backspaceDeleteSwitched) {
+ if (c == DELETE) {
+ c = '\b';
+ } else if (c == '\b') {
+ c = DELETE;
+ }
+ }
+
+ // in Unix terminals, arrow keys are represented by
+ // a sequence of 3 characters. E.g., the up arrow
+ // key yields 27, 91, 68
+ if (c == ARROW_START) {
+ //also the escape key is 27
+ //thats why we read until we
+ //have something different than 27
+ //this is a bugfix, because otherwise
+ //pressing escape and than an arrow key
+ //was an undefined state
+ while (c == ARROW_START) {
+ c = readCharacter(in);
+ }
+ if (c == ARROW_PREFIX || c == O_PREFIX) {
+ c = readCharacter(in);
+ if (c == ARROW_UP) {
+ return CTRL_P;
+ } else if (c == ARROW_DOWN) {
+ return CTRL_N;
+ } else if (c == ARROW_LEFT) {
+ return CTRL_B;
+ } else if (c == ARROW_RIGHT) {
+ return CTRL_F;
+ } else if (c == HOME_CODE) {
+ return CTRL_A;
+ } else if (c == END_CODE) {
+ return CTRL_E;
+ } else if (c == DEL_THIRD) {
+ c = readCharacter(in); // read 4th
+ return DELETE;
+ }
+ }
+ }
+ // handle unicode characters, thanks for a patch from amyi@inf.ed.ac.uk
+ if (c > 128) {
+ // handle unicode characters longer than 2 bytes,
+ // thanks to Marc.Herbert@continuent.com
+ replayStream.setInput(c, in);
+ c = replayReader.read();
+ }
+
+ return c;
+ }
+
+ /**
+ * This is awkward and inefficient, but probably the minimal way to add
+ * UTF-8 support to JLine
+ *
+ * @author <a href="mailto:Marc.Herbert@continuent.com">Marc Herbert</a>
+ */
+ static class ReplayPrefixOneCharInputStream extends InputStream {
+
+ byte firstByte;
+ int byteLength;
+ InputStream wrappedStream;
+ int byteRead;
+
+ final String encoding;
+
+ public ReplayPrefixOneCharInputStream(String encoding) {
+ this.encoding = encoding;
+ }
+
+ public void setInput(int recorded, InputStream wrapped) throws IOException {
+ this.byteRead = 0;
+ this.firstByte = (byte) recorded;
+ this.wrappedStream = wrapped;
+
+ byteLength = 1;
+ if (encoding.equalsIgnoreCase("UTF-8")) {
+ setInputUTF8(recorded, wrapped);
+ } else if (encoding.equalsIgnoreCase("UTF-16")) {
+ byteLength = 2;
+ } else if (encoding.equalsIgnoreCase("UTF-32")) {
+ byteLength = 4;
+ }
+ }
+
+
+ public void setInputUTF8(int recorded, InputStream wrapped) throws IOException {
+ // 110yyyyy 10zzzzzz
+ if ((firstByte & (byte) 0xE0) == (byte) 0xC0) {
+ this.byteLength = 2;
+ // 1110xxxx 10yyyyyy 10zzzzzz
+ } else if ((firstByte & (byte) 0xF0) == (byte) 0xE0) {
+ this.byteLength = 3;
+ // 11110www 10xxxxxx 10yyyyyy 10zzzzzz
+ } else if ((firstByte & (byte) 0xF8) == (byte) 0xF0) {
+ this.byteLength = 4;
+ } else {
+ throw new IOException("invalid UTF-8 first byte: " + firstByte);
+ }
+ }
+
+ public int read() throws IOException {
+ if (available() == 0) {
+ return -1;
+ }
+
+ byteRead++;
+
+ if (byteRead == 1) {
+ return firstByte;
+ }
+
+ return wrappedStream.read();
+ }
+
+ /**
+ * InputStreamReader is greedy and will try to read bytes in advance. We
+ * do NOT want this to happen since we use a temporary/"losing bytes"
+ * InputStreamReader above, that's why we hide the real
+ * wrappedStream.available() here.
+ */
+ public int available() {
+ return byteLength - byteRead;
+ }
+ }
+
+}
diff --git a/karaf/webconsole/gogo/src/main/resources/OSGI-INF/blueprint/webconsole-gogo.xml b/karaf/webconsole/gogo/src/main/resources/OSGI-INF/blueprint/webconsole-gogo.xml
new file mode 100644
index 0000000..c792c0e
--- /dev/null
+++ b/karaf/webconsole/gogo/src/main/resources/OSGI-INF/blueprint/webconsole-gogo.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+ xmlns:cm="http://www.osgi.org/xmlns/blueprint-cm/v1.0.0">
+
+ <reference id="commandProcessor" interface="org.osgi.service.command.CommandProcessor" />
+
+ <bean id="gogoPlugin" class="org.apache.felix.karaf.webconsole.gogo.GogoPlugin" init-method="start" destroy-method="stop">
+ <property name="completers">
+ <list>
+ <ref component-id="commandCompleter"/>
+ </list>
+ </property>
+ <property name="commandProcessor" ref="commandProcessor" />
+ <property name="bundleContext" ref="blueprintBundleContext" />
+ </bean>
+
+ <reference-list id="functions" filter="(&(osgi.command.scope=*)(osgi.command.function=*))" availability="optional">
+ <reference-listener ref="commandCompleter"
+ bind-method="register"
+ unbind-method="unregister"/>
+ </reference-list>
+
+ <bean id="commandCompleter" class="org.apache.felix.karaf.gshell.console.completer.CommandsCompleter">
+ <property name="bundleContext" ref="blueprintBundleContext" />
+ </bean>
+
+ <service ref="gogoPlugin" interface="javax.servlet.Servlet" >
+ <service-properties>
+ <entry key="felix.webconsole.label" value="gogo"/>
+ </service-properties>
+ </service>
+
+</blueprint>
diff --git a/karaf/webconsole/gogo/src/main/resources/res/ui/gogo.css b/karaf/webconsole/gogo/src/main/resources/res/ui/gogo.css
new file mode 100644
index 0000000..0e806a1
--- /dev/null
+++ b/karaf/webconsole/gogo/src/main/resources/res/ui/gogo.css
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Based on http://antony.lesuisse.org/software/ajaxterm/
+ * Public Domain License
+ */
+
+body {
+ background-color: #888;
+}
+
+div#console {
+ font-size: 12px;
+ margin: 12px;
+}
+
+div#term {
+ display: inline-block;
+}
+
+pre.stat {
+ margin: 0px;
+ padding: 4px;
+ display: block;
+ font-family: monospace;
+ white-space: pre;
+ background-color: black;
+ border-top: 1px solid black;
+ color: white;
+}
+pre.stat span {
+ padding: 0px;
+}
+pre.stat .on {
+ background-color: #080;
+ font-weight: bold;
+ color: white;
+ cursor: pointer;
+}
+pre.stat .off {
+ background-color: #888;
+ font-weight: bold;
+ color: white;
+ cursor: pointer;
+}
+pre.term {
+ margin: 0px;
+ padding: 4px;
+ display: block;
+ font-family: monospace;
+ white-space: pre;
+ background:#000;
+ border-top: 1px solid white;
+ color: #eee;
+}
+pre.term span.f0 { color: #000000; }
+pre.term span.f1 { color: #c00006; }
+pre.term span.f2 { color: #1bc806; }
+pre.term span.f3 { color: #c3c609; }
+pre.term span.f4 { color: #0000c2; }
+pre.term span.f5 { color: #bf00c2; }
+pre.term span.f6 { color: #19c4c2; }
+pre.term span.f7 { color: #f2f2f2; }
+pre.term span.f12 { color: transparent; }
+pre.term span.f14 { color: #000000; }
+pre.term span.f15 { color: #bbbbbb; }
+pre.term span.b0 { background-color: #000000; }
+pre.term span.b1 { background-color: #cc2300; }
+pre.term span.b2 { background-color: #00cc00; }
+pre.term span.b3 { background-color: #cccc00; }
+pre.term span.b4 { background-color: #0e2acc; }
+pre.term span.b5 { background-color: #cc34cc; }
+pre.term span.b6 { background-color: #00cccc; }
+pre.term span.b7 { background-color: #f5f5f5; }
+pre.term span.b12 { background-color: #555555; }
+pre.term span.b14 { background-color: transparent; }
+pre.term span.b15 { background-color: #ffffff; }
+pre.term span.ul { text-decoration: underline; }
+pre.term span.b { font-weight:900; }
+
diff --git a/karaf/webconsole/gogo/src/main/resources/res/ui/gogo.js b/karaf/webconsole/gogo/src/main/resources/res/ui/gogo.js
new file mode 100644
index 0000000..771efe0
--- /dev/null
+++ b/karaf/webconsole/gogo/src/main/resources/res/ui/gogo.js
@@ -0,0 +1,246 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+//
+// Based on http://antony.lesuisse.org/software/ajaxterm/
+// Public Domain License
+//
+
+gogo = { };
+
+gogo.Terminal_ctor = function(div, width, height) {
+
+ var ie = (window.ActiveXObject) ? 0 : 1;
+ var query0 = "w=" + width + "&h=" + height;
+ var query1 = query0 + "&k=";
+ var buf = "";
+ var timeout;
+ var error_timeout;
+ var keybuf = [];
+ var sending = 0;
+ var rmax = 1;
+ var force = 1;
+
+ var dstat = document.createElement('pre');
+ var sled = document.createElement('span');
+ var sdebug = document.createElement('span');
+ var dterm = document.createElement('div');
+
+ function debug(s) {
+ sdebug.innerHTML = s;
+ }
+
+ function error() {
+ sled.className = 'off';
+ debug("Connection lost timeout ts:" + ((new Date).getTime()));
+ }
+
+ function update() {
+ if (sending == 0) {
+ sending = 1;
+ sled.className = 'on';
+ var r = new XMLHttpRequest();
+ var send = "";
+ while (keybuf.length > 0) {
+ send += keybuf.pop();
+ }
+ var query = query1 + send;
+ if (force) {
+ query = query + "&f=1";
+ force = 0;
+ }
+ r.open("POST", "gogo", true);
+ r.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ r.onreadystatechange = function () {
+ if (r.readyState == 4) {
+ if (r.status == 200) {
+ window.clearTimeout(error_timeout);
+ if (r.responseText.length > 0) {
+ dterm.innerHTML = r.responseText;
+ rmax = 100;
+ } else {
+ rmax *= 2;
+ if (rmax > 2000)
+ rmax = 2000;
+ }
+ sending=0;
+ sled.className = 'off';
+ timeout = window.setTimeout(update, rmax);
+ } else {
+ debug("Connection error status:" + r.status);
+ }
+ }
+ }
+ error_timeout = window.setTimeout(error, 5000);
+ r.send(query);
+ }
+ }
+
+ function queue(s) {
+ keybuf.unshift(s);
+ if (sending == 0) {
+ window.clearTimeout(timeout);
+ timeout = window.setTimeout(update, 1);
+ }
+ }
+
+ function keypress(ev) {
+ // Translate to standard keycodes
+ if (!ev)
+ ev = window.event;
+ var kc;
+ if (ev.keyCode)
+ kc = ev.keyCode;
+ if (ev.which)
+ kc = ev.which;
+ if (ev.ctrlKey) {
+ if (kc >= 0 && kc <= 32)
+ kc = kc;
+ else if (kc >= 65 && kc <= 90)
+ kc -= 64;
+ else if (kc >= 97 && kc <= 122)
+ kc -= 96;
+ else {
+ switch (kc) {
+ case 54: kc=30; break; // Ctrl-^
+ case 109: kc=31; break; // Ctrl-_
+ case 219: kc=27; break; // Ctrl-[
+ case 220: kc=28; break; // Ctrl-\
+ case 221: kc=29; break; // Ctrl-]
+ default: return true;
+ }
+ }
+ } else if (ev.which == 0) {
+ switch(kc) {
+ case 8: break; // Backspace
+ case 9: break; // Tab
+ case 27: break; // ESC
+ case 33: kc = 63276; break; // PgUp
+ case 34: kc = 63277; break; // PgDn
+ case 35: kc = 63275; break; // End
+ case 36: kc = 63273; break; // Home
+ case 37: kc = 63234; break; // Left
+ case 38: kc = 63232; break; // Up
+ case 39: kc = 63235; break; // Right
+ case 40: kc = 63233; break; // Down
+ case 45: kc = 63302; break; // Ins
+ case 46: kc = 63272; break; // Del
+ case 112: kc = 63236; break; // F1
+ case 113: kc = 63237; break; // F2
+ case 114: kc = 63238; break; // F3
+ case 115: kc = 63239; break; // F4
+ case 116: kc = 63240; break; // F5
+ case 117: kc = 63241; break; // F6
+ case 118: kc = 63242; break; // F7
+ case 119: kc = 63243; break; // F8
+ case 120: kc = 63244; break; // F9
+ case 121: kc = 63245; break; // F10
+ case 122: kc = 63246; break; // F11
+ case 123: kc = 63247; break; // F12
+ default: return true;
+ }
+ }
+ if (kc == 8)
+ kc = 127;
+
+ var k = "";
+ // Build character
+ switch (kc) {
+ case 126: k = "~~"; break;
+ case 63232: k = "~A"; break; // Up
+ case 63233: k = "~B"; break; // Down
+ case 63234: k = "~D"; break; // Left
+ case 63235: k = "~C"; break; // Right
+ case 63276: k = "~1"; break; // PgUp
+ case 63277: k = "~2"; break; // PgDn
+ case 63273: k = "~H"; break; // Home
+ case 63275: k = "~F"; break; // End
+ case 63302: k = "~3"; break; // Ins
+ case 63272: k = "~4"; break; // Del
+ case 63236: k = "~a"; break; // F1
+ case 63237: k = "~b"; break; // F2
+ case 63238: k = "~c"; break; // F3
+ case 63239: k = "~d"; break; // F4
+ case 63240: k = "~e"; break; // F5
+ case 63241: k = "~f"; break; // F6
+ case 63242: k = "~g"; break; // F7
+ case 63243: k = "~h"; break; // F8
+ case 63244: k = "~i"; break; // F9
+ case 63245: k = "~j"; break; // F10
+ case 63246: k = "~k"; break; // F11
+ case 63247: k = "~l"; break; // F12
+ default: k = String.fromCharCode(kc); break;
+ }
+ queue(encodeURIComponent(k));
+
+ ev.cancelBubble = true;
+ if (ev.stopPropagation) ev.stopPropagation();
+ if (ev.preventDefault) ev.preventDefault();
+
+ return true;
+ }
+
+ function keydown(ev) {
+ if (!ev)
+ ev = window.event;
+ if (ie) {
+ o = { 9:1, 8:1, 27:1, 33:1, 34:1, 35:1, 36:1, 37:1, 38:1, 39:1, 40:1, 45:1, 46:1, 112:1,
+ 113:1, 114:1, 115:1, 116:1, 117:1, 118:1, 119:1, 120:1, 121:1, 122:1, 123:1 };
+ if (o[ev.keyCode] || ev.ctrlKey || ev.altKey) {
+ ev.which = 0;
+ return keypress(ev);
+ }
+ }
+ }
+
+ function init() {
+ if (typeof(XMLHttpRequest) == "undefined") {
+ XMLHttpRequest = function() {
+ try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); }
+ catch(e) {}
+ try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }
+ catch(e) {}
+ try { return new ActiveXObject("Msxml2.XMLHTTP"); }
+ catch(e) {}
+ try { return new ActiveXObject("Microsoft.XMLHTTP"); }
+ catch(e) {}
+ throw new Error("This browser does not support XMLHttpRequest.");
+ };
+ }
+ sled.appendChild(document.createTextNode('\xb7'));
+ sled.className = 'off';
+ dstat.appendChild(sled);
+ dstat.appendChild(document.createTextNode(' '));
+ dstat.appendChild(sdebug);
+ dstat.className = 'stat';
+ div.appendChild(dstat);
+ var d = document.createElement('div');
+ d.appendChild(dterm);
+ div.appendChild(d);
+ document.onkeypress = keypress;
+ document.onkeydown = keydown;
+ timeout = window.setTimeout(update, 100);
+ }
+
+ init();
+
+}
+
+gogo.Terminal = function(div, width, height) {
+ return new this.Terminal_ctor(div, width, height);
+}
+
diff --git a/karaf/webconsole/pom.xml b/karaf/webconsole/pom.xml
index 225c074..24c116f 100644
--- a/karaf/webconsole/pom.xml
+++ b/karaf/webconsole/pom.xml
@@ -35,7 +35,8 @@
<name>Apache Felix Karaf :: Web Console</name>
<modules>
- <module>plugins</module>
+ <module>features</module>
+ <module>gogo</module>
<module>branding</module>
</modules>