blinking cursor and SGR 1006 mode
authorKevin Lamonte <kevin.lamonte@gmail.com>
Sun, 22 Mar 2015 12:56:11 +0000 (08:56 -0400)
committerKevin Lamonte <kevin.lamonte@gmail.com>
Sun, 22 Mar 2015 12:56:11 +0000 (08:56 -0400)
README.md
src/jexer/io/ECMA48Terminal.java
src/jexer/io/SwingScreen.java
src/jexer/tterminal/ECMA48.java

index 24b437a65a4dee26b1392ddda1033bc40860f3d8..1e1bb544bbaa83f05eda45babc25eef10c57b1dc 100644 (file)
--- a/README.md
+++ b/README.md
@@ -16,9 +16,9 @@ Two backends are available:
 * System.in/out to a command-line ECMA-48 / ANSI X3.64 type terminal
   (tested on Linux + xterm).  I/O is handled through terminal escape
   sequences generated by the library itself: ncurses is not required
-  or linked to.  xterm mouse tracking using UTF8 coordinates is
-  supported.  For the demo application, this is the default backend on
-  non-Windows platforms.
+  or linked to.  xterm mouse tracking using UTF8 and SGR coordinates
+  are supported.  For the demo application, this is the default
+  backend on non-Windows platforms.
 
 * Java Swing UI.  This backend can be selected by setting
   jexer.Swing=true.  The default window size for Swing is 132x40,
@@ -110,15 +110,52 @@ ambiguous.  This section describes such issues.
     input (see the ENABLE_LINE_INPUT flag for GetConsoleMode() and
     SetConsoleMode()).
 
+  - TTerminalWindow launches 'script -fqe /dev/null' on non-Windows
+    platforms.  This is a workaround for the C library behavior of
+    checking for a tty: script launches $SHELL in a pseudo-tty.  This
+    works on Linux but might not on other Posix-y platforms.
+
   ECMA48 Backend
   --------------
-  
-  - Mouse support for BackendType.ECMA48/XTERM currently requires UTF-8 
-    coordinates (1005 mode).  Terminals that support UTF-8 mouse coordinates
-    include xterm, rxvt-unicode, gnome-terminal, and konsole.  Due to Java's
-    InputStreamReader requirement of a valid UTF-8 stream, one must assume
-    the terminal will always generate correct UTF-8 bytes.  Mode 1006 (SGR)
-    will be supported in a future release.
+
+  - Java's InputStreamReader requires a valid UTF-8 stream.  The
+    default X10 encoding for mouse coordinates outside (160,94) can
+    corrupt that stream, at best putting garbage keyboard events in
+    the input queue but at worst causing the backend reader thread to
+    throw an Exception and exit and make the entire UI unusable.
+    Mouse support therefore requires a terminal that can deliver
+    either UTF-8 coordinates (1005 mode) or SGR coordinates (1006
+    mode).  Most modern terminals can do this.
+
+  Use of 'stty'
+  -------------
+
+  - jexer.session.TTYSession calls 'stty size' once every second to
+    check the current window size, performing the same function as
+    ioctl(TIOCGWINSZ) but without requiring a native library.
+
+  - jexer.io.ECMA48Terminal calls 'stty' to perform the equivalent of
+    cfmakeraw().  The terminal is (blindly!) put back in 'stty sane
+    cooked' mode when exiting.
+
+
+System Properties
+-----------------
+
+The following properties control features of Jexer:
+
+  jexer.Swing
+  -----------
+
+  Used only by jexer.demos.Demo1.  If true, use the Swing interface
+  for the demo application.  Default: true on Windows platforms
+  (os.name starts with "Windows"), false on non-Windows platforms.
+
+  jexer.Swing.cursorStyle
+  -----------------------
+
+  Used by jexer.io.SwingScreen.  Selects the cursor style to draw.
+  Valid values are: underline, block, outline.  Default: underline.
 
 
 
@@ -129,15 +166,7 @@ Many tasks remain before calling this version 1.0:
 
 0.0.2: STABILIZE EXISTING
 
-- TTerminalWindow
-  - Expose shell commands as properties
-- Swing:
-  - Blinking cursor
-  - Block cursor
 - ECMA48Backend running on socket
-- TFileOpen
-- Document any properties used
-  - Expose use of 'stty'
 
 0.0.3: FINISH PORTING
 
@@ -145,6 +174,7 @@ Many tasks remain before calling this version 1.0:
   - Also add keyboard navigation
 - TDirectoryList
   - Also add keyboard navigation
+- TFileOpen
 
 0.0.4: NEW STUFF
 
@@ -157,8 +187,6 @@ Many tasks remain before calling this version 1.0:
 0.0.5: BUG HUNT
 
 - TSubMenu keyboard mnemonic not working
-- ECMA48Terminal
-  - Mode 1006 mouse coordinates
 
 0.1.0: BETA RELEASE
 
@@ -188,4 +216,3 @@ Screenshots
 ![Several Windows Open Including A Terminal](/screenshots/screenshot1.png?raw=true "Several Windows Open Including A Terminal")
 
 ![Yo Dawg...](/screenshots/yodawg.png?raw=true "Yo Dawg, I heard you like text windowing systems, so I ran a text windowing system inside your text windowing system so you can have a terminal in your terminal.")
-
index b496d8c2557806c0752b8730e16f658d3fd069df..a4c2861ba81f988d715881e076accfa85d36a713 100644 (file)
@@ -107,7 +107,8 @@ public final class ECMA48Terminal implements Runnable {
         CSI_ENTRY,
         CSI_PARAM,
         // CSI_INTERMEDIATE,
-        MOUSE
+        MOUSE,
+        MOUSE_SGR,
     }
 
     /**
@@ -625,6 +626,105 @@ public final class ECMA48Terminal implements Runnable {
             eventMouseWheelUp, eventMouseWheelDown);
     }
 
+    /**
+     * Produce mouse events based on "Any event tracking" and SGR
+     * coordinates.  See
+     * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
+     *
+     * @param release if true, this was a release ('m')
+     * @return a MOUSE_MOTION, MOUSE_UP, or MOUSE_DOWN event
+     */
+    private TInputEvent parseMouseSGR(final boolean release) {
+        // SGR extended coordinates - mode 1006
+        if (params.size() < 3) {
+            // Invalid position, bail out.
+            return null;
+        }
+        int buttons = Integer.parseInt(params.get(0));
+        int x = Integer.parseInt(params.get(1)) - 1;
+        int y = Integer.parseInt(params.get(2)) - 1;
+
+        // Clamp X and Y to the physical screen coordinates.
+        if (x >= windowResize.getWidth()) {
+            x = windowResize.getWidth() - 1;
+        }
+        if (y >= windowResize.getHeight()) {
+            y = windowResize.getHeight() - 1;
+        }
+
+        TMouseEvent.Type eventType = TMouseEvent.Type.MOUSE_DOWN;
+        boolean eventMouse1 = false;
+        boolean eventMouse2 = false;
+        boolean eventMouse3 = false;
+        boolean eventMouseWheelUp = false;
+        boolean eventMouseWheelDown = false;
+
+        if (release) {
+            eventType = TMouseEvent.Type.MOUSE_UP;
+        }
+
+        switch (buttons) {
+        case 0:
+            eventMouse1 = true;
+            break;
+        case 1:
+            eventMouse2 = true;
+            break;
+        case 2:
+            eventMouse3 = true;
+            break;
+        case 35:
+            // Motion only, no buttons down
+            eventType = TMouseEvent.Type.MOUSE_MOTION;
+            break;
+
+        case 32:
+            // Dragging with mouse1 down
+            eventMouse1 = true;
+            eventType = TMouseEvent.Type.MOUSE_MOTION;
+            break;
+
+        case 33:
+            // Dragging with mouse2 down
+            eventMouse2 = true;
+            eventType = TMouseEvent.Type.MOUSE_MOTION;
+            break;
+
+        case 34:
+            // Dragging with mouse3 down
+            eventMouse3 = true;
+            eventType = TMouseEvent.Type.MOUSE_MOTION;
+            break;
+
+        case 96:
+            // Dragging with mouse2 down after wheelUp
+            eventMouse2 = true;
+            eventType = TMouseEvent.Type.MOUSE_MOTION;
+            break;
+
+        case 97:
+            // Dragging with mouse2 down after wheelDown
+            eventMouse2 = true;
+            eventType = TMouseEvent.Type.MOUSE_MOTION;
+            break;
+
+        case 64:
+            eventMouseWheelUp = true;
+            break;
+
+        case 65:
+            eventMouseWheelDown = true;
+            break;
+
+        default:
+            // Unknown, bail out
+            return null;
+        }
+        return new TMouseEvent(eventType, x, y, x, y,
+            eventMouse1, eventMouse2, eventMouse3,
+            eventMouseWheelUp, eventMouseWheelDown);
+    }
+
     /**
      * Return any events in the IO queue.
      *
@@ -886,6 +986,10 @@ public final class ECMA48Terminal implements Runnable {
                     // Mouse position
                     state = ParseState.MOUSE;
                     return;
+                case '<':
+                    // Mouse position, SGR (1006) coordinates
+                    state = ParseState.MOUSE_SGR;
+                    return;
                 default:
                     break;
                 }
@@ -895,6 +999,44 @@ public final class ECMA48Terminal implements Runnable {
             reset();
             return;
 
+        case MOUSE_SGR:
+            // Numbers - parameter values
+            if ((ch >= '0') && (ch <= '9')) {
+                params.set(params.size() - 1,
+                    params.get(params.size() - 1) + ch);
+                return;
+            }
+            // Parameter separator
+            if (ch == ';') {
+                params.add("");
+                return;
+            }
+
+            switch (ch) {
+            case 'M':
+                // Generate a mouse press event
+                TInputEvent event = parseMouseSGR(false);
+                if (event != null) {
+                    events.add(event);
+                }
+                reset();
+                return;
+            case 'm':
+                // Generate a mouse release event
+                event = parseMouseSGR(true);
+                if (event != null) {
+                    events.add(event);
+                }
+                reset();
+                return;
+            default:
+                break;
+            }
+
+            // Unknown keystroke, ignore
+            reset();
+            return;
+
         case CSI_PARAM:
             // Numbers - parameter values
             if ((ch >= '0') && (ch <= '9')) {
@@ -1015,7 +1157,7 @@ public final class ECMA48Terminal implements Runnable {
      * @param on if true, enable metaSendsEscape
      * @return the string to emit to xterm
      */
-    public String xtermMetaSendsEscape(final boolean on) {
+    private String xtermMetaSendsEscape(final boolean on) {
         if (on) {
             return "\033[?1036h\033[?1034l";
         }
@@ -1031,7 +1173,7 @@ public final class ECMA48Terminal implements Runnable {
      * @return the string to emit to an ANSI / ECMA-style terminal,
      * e.g. "\033[31;1m"
      */
-    public String addHeaderSGR(String str) {
+    private String addHeaderSGR(String str) {
         if (str.length() > 0) {
             // Nix any trailing ';' because that resets all attributes
             while (str.endsWith(":")) {
@@ -1042,14 +1184,15 @@ public final class ECMA48Terminal implements Runnable {
     }
 
     /**
-     * Create a SGR parameter sequence for a single color change.
+     * Create a SGR parameter sequence for a single color change.  Note
+     * package private access.
      *
      * @param color one of the Color.WHITE, Color.BLUE, etc. constants
      * @param foreground if true, this is a foreground color
      * @return the string to emit to an ANSI / ECMA-style terminal,
      * e.g. "\033[42m"
      */
-    public String color(final Color color, final boolean foreground) {
+    String color(final Color color, final boolean foreground) {
         return color(color, foreground, true);
     }
 
@@ -1063,7 +1206,7 @@ public final class ECMA48Terminal implements Runnable {
      * @return the string to emit to an ANSI / ECMA-style terminal,
      * e.g. "\033[42m"
      */
-    public String color(final Color color, final boolean foreground,
+    private String color(final Color color, final boolean foreground,
         final boolean header) {
 
         int ecmaColor = color.getValue();
@@ -1083,15 +1226,15 @@ public final class ECMA48Terminal implements Runnable {
     }
 
     /**
-     * Create a SGR parameter sequence for both foreground and
-     * background color change.
+     * Create a SGR parameter sequence for both foreground and background
+     * color change.  Note package private access.
      *
      * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
      * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
      * @return the string to emit to an ANSI / ECMA-style terminal,
      * e.g. "\033[31;42m"
      */
-    public String color(final Color foreColor, final Color backColor) {
+    String color(final Color foreColor, final Color backColor) {
         return color(foreColor, backColor, true);
     }
 
@@ -1106,7 +1249,7 @@ public final class ECMA48Terminal implements Runnable {
      * @return the string to emit to an ANSI / ECMA-style terminal,
      * e.g. "\033[31;42m"
      */
-    public String color(final Color foreColor, final Color backColor,
+    private String color(final Color foreColor, final Color backColor,
         final boolean header) {
 
         int ecmaForeColor = foreColor.getValue();
@@ -1126,7 +1269,8 @@ public final class ECMA48Terminal implements Runnable {
     /**
      * Create a SGR parameter sequence for foreground, background, and
      * several attributes.  This sequence first resets all attributes to
-     * default, then sets attributes as per the parameters.
+     * default, then sets attributes as per the parameters.  Note package
+     * private access.
      *
      * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
      * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
@@ -1137,7 +1281,7 @@ public final class ECMA48Terminal implements Runnable {
      * @return the string to emit to an ANSI / ECMA-style terminal,
      * e.g. "\033[0;1;31;42m"
      */
-    public String color(final Color foreColor, final Color backColor,
+    String color(final Color foreColor, final Color backColor,
         final boolean bold, final boolean reverse, final boolean blink,
         final boolean underline) {
 
@@ -1194,7 +1338,7 @@ public final class ECMA48Terminal implements Runnable {
      * @return the string to emit to an ANSI / ECMA-style terminal,
      * e.g. "\033[7m"
      */
-    public String reverse(final boolean on) {
+    private String reverse(final boolean on) {
         if (on) {
             return "\033[7m";
         }
@@ -1202,12 +1346,13 @@ public final class ECMA48Terminal implements Runnable {
     }
 
     /**
-     * Create a SGR parameter sequence to reset to defaults.
+     * Create a SGR parameter sequence to reset to defaults.  Note package
+     * private access.
      *
      * @return the string to emit to an ANSI / ECMA-style terminal,
      * e.g. "\033[0m"
      */
-    public String normal() {
+    String normal() {
         return normal(true);
     }
 
@@ -1219,7 +1364,7 @@ public final class ECMA48Terminal implements Runnable {
      * @return the string to emit to an ANSI / ECMA-style terminal,
      * e.g. "\033[0m"
      */
-    public String normal(final boolean header) {
+    private String normal(final boolean header) {
         if (header) {
             return "\033[0;37;40m";
         }
@@ -1233,7 +1378,7 @@ public final class ECMA48Terminal implements Runnable {
      * @return the string to emit to an ANSI / ECMA-style terminal,
      * e.g. "\033[1m"
      */
-    public String bold(final boolean on) {
+    private String bold(final boolean on) {
         return bold(on, true);
     }
 
@@ -1246,7 +1391,7 @@ public final class ECMA48Terminal implements Runnable {
      * @return the string to emit to an ANSI / ECMA-style terminal,
      * e.g. "\033[1m"
      */
-    public String bold(final boolean on, final boolean header) {
+    private String bold(final boolean on, final boolean header) {
         if (header) {
             if (on) {
                 return "\033[1m";
@@ -1266,7 +1411,7 @@ public final class ECMA48Terminal implements Runnable {
      * @return the string to emit to an ANSI / ECMA-style terminal,
      * e.g. "\033[5m"
      */
-    public String blink(final boolean on) {
+    private String blink(final boolean on) {
         return blink(on, true);
     }
 
@@ -1279,7 +1424,7 @@ public final class ECMA48Terminal implements Runnable {
      * @return the string to emit to an ANSI / ECMA-style terminal,
      * e.g. "\033[5m"
      */
-    public String blink(final boolean on, final boolean header) {
+    private String blink(final boolean on, final boolean header) {
         if (header) {
             if (on) {
                 return "\033[5m";
@@ -1300,7 +1445,7 @@ public final class ECMA48Terminal implements Runnable {
      * @return the string to emit to an ANSI / ECMA-style terminal,
      * e.g. "\033[4m"
      */
-    public String underline(final boolean on) {
+    private String underline(final boolean on) {
         if (on) {
             return "\033[4m";
         }
@@ -1308,12 +1453,13 @@ public final class ECMA48Terminal implements Runnable {
     }
 
     /**
-     * Create a SGR parameter sequence for enabling the visible cursor.
+     * Create a SGR parameter sequence for enabling the visible cursor.  Note
+     * package private access.
      *
      * @param on if true, turn on cursor
      * @return the string to emit to an ANSI / ECMA-style terminal
      */
-    public String cursor(final boolean on) {
+    String cursor(final boolean on) {
         if (on && !cursorOn) {
             cursorOn = true;
             return "\033[?25h";
@@ -1338,11 +1484,11 @@ public final class ECMA48Terminal implements Runnable {
     /**
      * Clear the line from the cursor (inclusive) to the end of the screen.
      * Because some terminals use back-color-erase, set the color to
-     * white-on-black beforehand.
+     * white-on-black beforehand.  Note package private access.
      *
      * @return the string to emit to an ANSI / ECMA-style terminal
      */
-    public String clearRemainingLine() {
+    String clearRemainingLine() {
         return "\033[0;37;40m\033[K";
     }
 
@@ -1352,7 +1498,7 @@ public final class ECMA48Terminal implements Runnable {
      *
      * @return the string to emit to an ANSI / ECMA-style terminal
      */
-    public String clearPreceedingLine() {
+    private String clearPreceedingLine() {
         return "\033[0;37;40m\033[1K";
     }
 
@@ -1362,7 +1508,7 @@ public final class ECMA48Terminal implements Runnable {
      *
      * @return the string to emit to an ANSI / ECMA-style terminal
      */
-    public String clearLine() {
+    private String clearLine() {
         return "\033[0;37;40m\033[2K";
     }
 
@@ -1371,24 +1517,26 @@ public final class ECMA48Terminal implements Runnable {
      *
      * @return the string to emit to an ANSI / ECMA-style terminal
      */
-    public String home() {
+    private String home() {
         return "\033[H";
     }
 
     /**
-     * Move the cursor to (x, y).
+     * Move the cursor to (x, y).  Note package private access.
      *
      * @param x column coordinate.  0 is the left-most column.
      * @param y row coordinate.  0 is the top-most row.
      * @return the string to emit to an ANSI / ECMA-style terminal
      */
-    public String gotoXY(final int x, final int y) {
+    String gotoXY(final int x, final int y) {
         return String.format("\033[%d;%dH", y + 1, x + 1);
     }
 
     /**
      * Tell (u)xterm that we want to receive mouse events based on "Any event
-     * tracking" and UTF-8 coordinates.  See
+     * tracking", UTF-8 coordinates, and then SGR coordinates.  Ideally we
+     * will end up with SGR coordinates with UTF-8 coordinates as a fallback.
+     * See
      * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
      *
      * Note that this also sets the alternate/primary screen buffer.
@@ -1398,11 +1546,11 @@ public final class ECMA48Terminal implements Runnable {
      * buffer.
      * @return the string to emit to xterm
      */
-    public String mouse(final boolean on) {
+    private String mouse(final boolean on) {
         if (on) {
-            return "\033[?1003;1005h\033[?1049h";
+            return "\033[?1003;1005;1006h\033[?1049h";
         }
-        return "\033[?1003;1005l\033[?1049l";
+        return "\033[?1003;1006;1005l\033[?1049l";
     }
 
     /**
index bdd74a7ca7e47728905f88f9b7bb58947fe83639..2a96122b00bfd6035d2652fbd367931bfc2554be 100644 (file)
  */
 package jexer.io;
 
-import jexer.bits.Cell;
-import jexer.bits.CellAttributes;
-import jexer.session.SwingSessionInfo;
-
 import java.awt.Color;
 import java.awt.Cursor;
 import java.awt.Font;
@@ -46,14 +42,39 @@ import java.awt.Toolkit;
 import java.awt.geom.Rectangle2D;
 import java.awt.image.BufferedImage;
 import java.io.InputStream;
+import java.util.Date;
 import javax.swing.JFrame;
 import javax.swing.SwingUtilities;
 
+import jexer.bits.Cell;
+import jexer.bits.CellAttributes;
+import jexer.session.SwingSessionInfo;
+
 /**
  * This Screen implementation draws to a Java Swing JFrame.
  */
 public final class SwingScreen extends Screen {
 
+    /**
+     * Cursor style to draw.
+     */
+    public enum CursorStyle {
+        /**
+         * Use an underscore for the cursor.
+         */
+        UNDERLINE,
+
+        /**
+         * Use a solid block for the cursor.
+         */
+        BLOCK,
+
+        /**
+         * Use an outlined block for the cursor.
+         */
+        OUTLINE
+    }
+
     private static Color MYBLACK;
     private static Color MYRED;
     private static Color MYGREEN;
@@ -137,15 +158,38 @@ public final class SwingScreen extends Screen {
         private int maxDescent = 0;
 
         /**
-         * Top pixel value.
+         * Top pixel absolute location.
          */
         private int top = 30;
 
         /**
-         * Left pixel value.
+         * Left pixel absolute location.
          */
         private int left = 30;
 
+        /**
+         * The cursor style to draw.
+         */
+        private CursorStyle cursorStyle = CursorStyle.UNDERLINE;
+
+        /**
+         * The number of millis to wait before switching the blink from
+         * visible to invisible.
+         */
+        private long blinkMillis = 500;
+
+        /**
+         * If true, the cursor should be visible right now based on the blink
+         * time.
+         */
+        private boolean cursorBlinkVisible = true;
+
+        /**
+         * The time that the blink last flipped from visible to invisible or
+         * from invisible to visible.
+         */
+        private long lastBlinkTime = 0;
+
         /**
          * Convert a CellAttributes foreground color to an Swing Color.
          *
@@ -241,11 +285,20 @@ public final class SwingScreen extends Screen {
             this.screen = screen;
             setDOSColors();
 
+            // Figure out my cursor style
+            String cursorStyleString = System.getProperty("jexer.Swing.cursorStyle",
+                "underline").toLowerCase();
+
+            if (cursorStyleString.equals("underline")) {
+                cursorStyle = CursorStyle.UNDERLINE;
+            } else if (cursorStyleString.equals("outline")) {
+                cursorStyle = CursorStyle.OUTLINE;
+            } else if (cursorStyleString.equals("block")) {
+                cursorStyle = CursorStyle.BLOCK;
+            }
+
             setTitle("Jexer Application");
             setBackground(Color.black);
-            // setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
-            // setFont(new Font("Liberation Mono", Font.BOLD, 16));
-            // setFont(new Font(Font.MONOSPACED, Font.PLAIN, 16));
 
             try {
                 // Always try to use Terminus, the one decent font.
@@ -334,6 +387,13 @@ public final class SwingScreen extends Screen {
                 return;
             }
 
+            // See if it is time to flip the blink time.
+            long nowTime = (new Date()).getTime();
+            if (nowTime > blinkMillis + lastBlinkTime) {
+                lastBlinkTime = nowTime;
+                cursorBlinkVisible = !cursorBlinkVisible;
+            }
+
             int xCellMin = 0;
             int xCellMax = screen.width;
             int yCellMin = 0;
@@ -404,15 +464,28 @@ public final class SwingScreen extends Screen {
                 }
 
                 // Draw the cursor if it is visible
-                if ((cursorVisible)
+                if (cursorVisible
                     && (cursorY <= screen.height - 1)
                     && (cursorX <= screen.width - 1)
+                    && cursorBlinkVisible
                 ) {
                     int xPixel = cursorX * textWidth + left;
                     int yPixel = cursorY * textHeight + top;
                     Cell lCell = screen.logical[cursorX][cursorY];
                     gr.setColor(attrToForegroundColor(lCell));
-                    gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
+                    switch (cursorStyle) {
+                    case UNDERLINE:
+                        gr.fillRect(xPixel, yPixel + textHeight - 2,
+                            textWidth, 2);
+                        break;
+                    case BLOCK:
+                        gr.fillRect(xPixel, yPixel, textWidth, textHeight);
+                        break;
+                    case OUTLINE:
+                        gr.drawRect(xPixel, yPixel, textWidth - 1,
+                            textHeight - 1);
+                        break;
+                    }
                 }
 
                 dirty = false;
@@ -543,7 +616,17 @@ public final class SwingScreen extends Screen {
      */
     @Override
     public void putCursor(final boolean visible, final int x, final int y) {
-        if ((cursorVisible)
+
+        if ((visible == cursorVisible) && ((x == cursorX) && (y == cursorY))) {
+            // See if it is time to flip the blink time.
+            long nowTime = (new Date()).getTime();
+            if (nowTime < frame.blinkMillis + frame.lastBlinkTime) {
+                // Nothing has changed, so don't do anything.
+                return;
+            }
+        }
+
+        if (cursorVisible
             && (cursorY <= height - 1)
             && (cursorX <= width - 1)
         ) {
index 7b41594be4eff9ab073437f9fb64f2302b18f259..0918e9b6630f8636ef8a21bfb7270e2411c3ffb5 100644 (file)
@@ -522,7 +522,8 @@ public class ECMA48 implements Runnable {
      */
     private enum MouseEncoding {
         X10,
-        UTF8
+        UTF8,
+        SGR
     }
 
     /**
@@ -1110,7 +1111,7 @@ public class ECMA48 implements Runnable {
             mouseProtocol, mouseEncoding, mouse);
          */
 
-        if (mouseEncoding != MouseEncoding.UTF8) {
+        if (mouseEncoding == MouseEncoding.X10) {
             // We will support X10 but only for (160,94) and smaller.
             if ((mouse.getX() >= 160) || (mouse.getY() >= 94)) {
                 return;
@@ -1163,27 +1164,83 @@ public class ECMA48 implements Runnable {
 
         // Now encode the event
         StringBuilder sb = new StringBuilder(6);
-        sb.append((char) 0x1B);
-        sb.append('[');
-        sb.append('M');
-        if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
-            sb.append((char) (0x03 + 32));
-        } else if (mouse.isMouse1()) {
-            sb.append((char) (0x00 + 32));
-        } else if (mouse.isMouse2()) {
-            sb.append((char) (0x01 + 32));
-        } else if (mouse.isMouse3()) {
-            sb.append((char) (0x02 + 32));
-        } else if (mouse.isMouseWheelUp()) {
-            sb.append((char) (0x04 + 64));
-        } else if (mouse.isMouseWheelDown()) {
-            sb.append((char) (0x05 + 64));
+        if (mouseEncoding == MouseEncoding.SGR) {
+            sb.append((char) 0x1B);
+            sb.append("[<");
+
+            if (mouse.isMouse1()) {
+                if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+                    sb.append("32;");
+                } else {
+                    sb.append("0;");
+                }
+            } else if (mouse.isMouse2()) {
+                if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+                    sb.append("33;");
+                } else {
+                    sb.append("1;");
+                }
+            } else if (mouse.isMouse3()) {
+                if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+                    sb.append("34;");
+                } else {
+                    sb.append("2;");
+                }
+            } else if (mouse.isMouseWheelUp()) {
+                sb.append("64;");
+            } else if (mouse.isMouseWheelDown()) {
+                sb.append("65;");
+            } else {
+                // This is motion with no buttons down.
+                sb.append("35;");
+            }
+
+            sb.append(String.format("%d;%d", mouse.getX() + 1,
+                    mouse.getY() + 1));
+
+            if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
+                sb.append("m");
+            } else {
+                sb.append("M");
+            }
+
         } else {
-            sb.append((char) (0x03 + 32));
-        }
+            // X10 and UTF8 encodings
+            sb.append((char) 0x1B);
+            sb.append('[');
+            sb.append('M');
+            if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
+                sb.append((char) (0x03 + 32));
+            } else if (mouse.isMouse1()) {
+                if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+                    sb.append((char) (0x00 + 32 + 32));
+                } else {
+                    sb.append((char) (0x00 + 32));
+                }
+            } else if (mouse.isMouse2()) {
+                if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+                    sb.append((char) (0x01 + 32 + 32));
+                } else {
+                    sb.append((char) (0x01 + 32));
+                }
+            } else if (mouse.isMouse3()) {
+                if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+                    sb.append((char) (0x02 + 32 + 32));
+                } else {
+                    sb.append((char) (0x02 + 32));
+                }
+            } else if (mouse.isMouseWheelUp()) {
+                sb.append((char) (0x04 + 64));
+            } else if (mouse.isMouseWheelDown()) {
+                sb.append((char) (0x05 + 64));
+            } else {
+                // This is motion with no buttons down.
+                sb.append((char) (0x03 + 32));
+            }
 
-        sb.append((char) (mouse.getX() + 33));
-        sb.append((char) (mouse.getY() + 33));
+            sb.append((char) (mouse.getX() + 33));
+            sb.append((char) (mouse.getY() + 33));
+        }
 
         // System.err.printf("Would write: \'%s\'\n", sb.toString());
         writeRemote(sb.toString());
@@ -2441,6 +2498,19 @@ public class ECMA48 implements Runnable {
                 }
                 break;
 
+            case 1006:
+                if ((type == DeviceType.XTERM)
+                    && (decPrivateModeFlag == true)
+                ) {
+                    // Mouse: SGR coordinates
+                    if (value == true) {
+                        mouseEncoding = MouseEncoding.SGR;
+                    } else {
+                        mouseEncoding = MouseEncoding.X10;
+                    }
+                }
+                break;
+
             default:
                 break;