Cache glyphs, fix vttest test
[nikiroo-utils.git] / src / jexer / tterminal / ECMA48.java
index 7b41594be4eff9ab073437f9fb64f2302b18f259..f657eaffa1255bdf0ee5a391ca0cfcdaaec0aa0f 100644 (file)
@@ -1,29 +1,27 @@
-/**
+/*
  * Jexer - Java Text User Interface
  *
- * License: LGPLv3 or later
- *
- * This module is licensed under the GNU Lesser General Public License
- * Version 3.  Please see the file "COPYING" in this directory for more
- * information about the GNU Lesser General Public License Version 3.
+ * The MIT License (MIT)
  *
- *     Copyright (C) 2015  Kevin Lamonte
+ * Copyright (C) 2016 Kevin Lamonte
  *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation; either version 3 of
- * the License, or (at your option) any later version.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
  *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program; if not, see
- * http://www.gnu.org/licenses/, or write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  *
  * @author Kevin Lamonte [kevin.lamonte@gmail.com]
  * @version 1
@@ -131,15 +129,13 @@ public class ECMA48 implements Runnable {
             return "\033[?6c";
 
         case VT220:
+        case XTERM:
             // "I am a VT220" - 7 bit version
             if (!s8c1t) {
                 return "\033[?62;1;6c";
             }
             // "I am a VT220" - 8 bit version
             return "\u009b?62;1;6c";
-        case XTERM:
-            // "I am a VT100 with advanced video option" (often VT102)
-            return "\033[?1;2c";
         default:
             throw new IllegalArgumentException("Invalid device type: " + type);
         }
@@ -522,7 +518,8 @@ public class ECMA48 implements Runnable {
      */
     private enum MouseEncoding {
         X10,
-        UTF8
+        UTF8,
+        SGR
     }
 
     /**
@@ -682,6 +679,7 @@ public class ECMA48 implements Runnable {
      * Whether number pad keys send VT100 or VT52, application or numeric
      * sequences.
      */
+    @SuppressWarnings("unused")
     private KeypadMode keypadMode;
 
     /**
@@ -698,7 +696,7 @@ public class ECMA48 implements Runnable {
                 return columns132;
         }
 
-        /**
+    /**
      * true = reverse video.  Set by DECSCNM.
      */
     private boolean reverseVideo = false;
@@ -1110,7 +1108,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 +1161,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());
@@ -1199,6 +1253,46 @@ public class ECMA48 implements Runnable {
         writeRemote(keypressToString(keypress));
     }
 
+    /**
+     * Build one of the complex xterm keystroke sequences, storing the result in
+     * xterm_keystroke_buffer.
+     *
+     * @param ss3 the prefix to use based on VT100 state.
+     * @param first the first character, usually a number.
+     * @param first the last character, one of the following: ~ A B C D F H
+     * @param ctrl whether or not ctrl is down
+     * @param alt whether or not alt is down
+     * @param shift whether or not shift is down
+     * @return the buffer with the full key sequence
+     */
+    private String xtermBuildKeySequence(final String ss3, final char first,
+        final char last, boolean ctrl, boolean alt, boolean shift) {
+
+        StringBuilder sb = new StringBuilder(ss3);
+        if ((last == '~') || (ctrl == true) || (alt == true)
+            || (shift == true)
+        ) {
+            sb.append(first);
+            if (       (ctrl == false) && (alt == false) && (shift == true)) {
+                sb.append(";2");
+            } else if ((ctrl == false) && (alt == true) && (shift == false)) {
+                sb.append(";3");
+            } else if ((ctrl == false) && (alt == true) && (shift == true)) {
+                sb.append(";4");
+            } else if ((ctrl == true) && (alt == false) && (shift == false)) {
+                sb.append(";5");
+            } else if ((ctrl == true) && (alt == false) && (shift == true)) {
+                sb.append(";6");
+            } else if ((ctrl == true) && (alt == true) && (shift == false)) {
+                sb.append(";7");
+            } else if ((ctrl == true) && (alt == true) && (shift == true)) {
+                sb.append(";8");
+            }
+        }
+        sb.append(last);
+        return sb.toString();
+    }
+
     /**
      * Translate the keyboard press to a VT100, VT220, or XTERM sequence.
      *
@@ -1255,69 +1349,177 @@ public class ECMA48 implements Runnable {
             }
         }
 
-        if (keypress.equals(kbLeft)) {
-            switch (arrowKeyMode) {
-            case ANSI:
-                return "\033[D";
-            case VT52:
-                return "\033D";
-            case VT100:
-                return "\033OD";
+        if (keypress.equalsWithoutModifiers(kbLeft)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '1', 'D',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '1', 'D',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '1', 'D',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return "\033[D";
+                case VT52:
+                    return "\033D";
+                case VT100:
+                    return "\033OD";
+                }
             }
         }
 
-        if (keypress.equals(kbRight)) {
-            switch (arrowKeyMode) {
-            case ANSI:
-                return "\033[C";
-            case VT52:
-                return "\033C";
-            case VT100:
-                return "\033OC";
+        if (keypress.equalsWithoutModifiers(kbRight)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '1', 'C',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '1', 'C',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '1', 'C',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return "\033[C";
+                case VT52:
+                    return "\033C";
+                case VT100:
+                    return "\033OC";
+                }
             }
         }
 
-        if (keypress.equals(kbUp)) {
-            switch (arrowKeyMode) {
-            case ANSI:
-                return "\033[A";
-            case VT52:
-                return "\033A";
-            case VT100:
-                return "\033OA";
+        if (keypress.equalsWithoutModifiers(kbUp)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '1', 'A',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '1', 'A',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '1', 'A',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return "\033[A";
+                case VT52:
+                    return "\033A";
+                case VT100:
+                    return "\033OA";
+                }
             }
         }
 
-        if (keypress.equals(kbDown)) {
-            switch (arrowKeyMode) {
-            case ANSI:
-                return "\033[B";
-            case VT52:
-                return "\033B";
-            case VT100:
-                return "\033OB";
+        if (keypress.equalsWithoutModifiers(kbDown)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '1', 'B',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '1', 'B',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '1', 'B',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return "\033[B";
+                case VT52:
+                    return "\033B";
+                case VT100:
+                    return "\033OB";
+                }
             }
         }
 
-        if (keypress.equals(kbHome)) {
-            switch (arrowKeyMode) {
-            case ANSI:
-                return "\033[H";
-            case VT52:
-                return "\033H";
-            case VT100:
-                return "\033OH";
+        if (keypress.equalsWithoutModifiers(kbHome)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '1', 'H',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '1', 'H',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '1', 'H',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return "\033[H";
+                case VT52:
+                    return "\033H";
+                case VT100:
+                    return "\033OH";
+                }
             }
         }
 
-        if (keypress.equals(kbEnd)) {
-            switch (arrowKeyMode) {
-            case ANSI:
-                return "\033[F";
-            case VT52:
-                return "\033F";
-            case VT100:
-                return "\033OF";
+        if (keypress.equalsWithoutModifiers(kbEnd)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '1', 'F',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '1', 'F',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '1', 'F',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return "\033[F";
+                case VT52:
+                    return "\033F";
+                case VT100:
+                    return "\033OF";
+                }
             }
         }
 
@@ -1444,6 +1646,9 @@ public class ECMA48 implements Runnable {
             if (vt52Mode) {
                 return "\0332P";
             }
+            if (type == DeviceType.XTERM) {
+                return "\0331;2P";
+            }
             return "\033O2P";
         }
 
@@ -1452,6 +1657,9 @@ public class ECMA48 implements Runnable {
             if (vt52Mode) {
                 return "\0332Q";
             }
+            if (type == DeviceType.XTERM) {
+                return "\0331;2Q";
+            }
             return "\033O2Q";
         }
 
@@ -1460,6 +1668,9 @@ public class ECMA48 implements Runnable {
             if (vt52Mode) {
                 return "\0332R";
             }
+            if (type == DeviceType.XTERM) {
+                return "\0331;2R";
+            }
             return "\033O2R";
         }
 
@@ -1468,6 +1679,9 @@ public class ECMA48 implements Runnable {
             if (vt52Mode) {
                 return "\0332S";
             }
+            if (type == DeviceType.XTERM) {
+                return "\0331;2S";
+            }
             return "\033O2S";
         }
 
@@ -1516,6 +1730,9 @@ public class ECMA48 implements Runnable {
             if (vt52Mode) {
                 return "\0335P";
             }
+            if (type == DeviceType.XTERM) {
+                return "\0331;5P";
+            }
             return "\033O5P";
         }
 
@@ -1524,6 +1741,9 @@ public class ECMA48 implements Runnable {
             if (vt52Mode) {
                 return "\0335Q";
             }
+            if (type == DeviceType.XTERM) {
+                return "\0331;5Q";
+            }
             return "\033O5Q";
         }
 
@@ -1532,6 +1752,9 @@ public class ECMA48 implements Runnable {
             if (vt52Mode) {
                 return "\0335R";
             }
+            if (type == DeviceType.XTERM) {
+                return "\0331;5R";
+            }
             return "\033O5R";
         }
 
@@ -1540,6 +1763,9 @@ public class ECMA48 implements Runnable {
             if (vt52Mode) {
                 return "\0335S";
             }
+            if (type == DeviceType.XTERM) {
+                return "\0331;5S";
+            }
             return "\033O5S";
         }
 
@@ -1583,39 +1809,93 @@ public class ECMA48 implements Runnable {
             return "\033[24;5~";
         }
 
-        if (keypress.equals(kbPgUp)) {
-            // Page Up
-            return "\033[5~";
-        }
-
-        if (keypress.equals(kbPgDn)) {
-            // Page Down
-            return "\033[6~";
-        }
-
-        if (keypress.equals(kbIns)) {
-            // Ins
-            return "\033[2~";
+        if (keypress.equalsWithoutModifiers(kbPgUp)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '5', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '5', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '5', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                return "\033[5~";
+            }
         }
 
-        if (keypress.equals(kbShiftIns)) {
-            // This is what xterm sends for SHIFT-INS
-            return "\033[2;2~";
-            // This is what xterm sends for CTRL-INS
-            // return "\033[2;5~";
+        if (keypress.equalsWithoutModifiers(kbPgDn)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '6', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '6', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '6', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                return "\033[6~";
+            }
         }
 
-        if (keypress.equals(kbShiftDel)) {
-            // This is what xterm sends for SHIFT-DEL
-            return "\033[3;2~";
-            // This is what xterm sends for CTRL-DEL
-            // return "\033[3;5~";
+        if (keypress.equalsWithoutModifiers(kbIns)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '2', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '2', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '2', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                return "\033[2~";
+            }
         }
 
-        if (keypress.equals(kbDel)) {
-            // Delete sends real delete for VTxxx
-            return "\177";
-            // return "\033[3~";
+        if (keypress.equalsWithoutModifiers(kbDel)) {
+            switch (type) {
+            case XTERM:
+                switch (arrowKeyMode) {
+                case ANSI:
+                    return xtermBuildKeySequence("\033[", '3', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT52:
+                    return xtermBuildKeySequence("\033", '3', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                case VT100:
+                    return xtermBuildKeySequence("\033O", '3', '~',
+                        keypress.isCtrl(), keypress.isAlt(),
+                        keypress.isShift());
+                }
+            default:
+                // Delete sends real delete for VTxxx
+                return "\177";
+            }
         }
 
         if (keypress.equals(kbEnter)) {
@@ -1634,6 +1914,17 @@ public class ECMA48 implements Runnable {
             return "\011";
         }
 
+        if ((keypress.equalsWithoutModifiers(kbBackTab)) ||
+            (keypress.equals(kbShiftTab))
+        ) {
+            switch (type) {
+            case XTERM:
+                return "\033[Z";
+            default:
+                return "\011";
+            }
+        }
+
         // Non-alt, non-ctrl characters
         if (!keypress.isFnKey()) {
             StringBuilder sb = new StringBuilder();
@@ -2127,8 +2418,9 @@ public class ECMA48 implements Runnable {
     }
 
     /**
-     * Set or unset a toggle.  value is 'true' for set ('h'), false for reset
-     * ('l').
+     * Set or unset a toggle.
+     *
+     * @param value true for set ('h'), false for reset ('l')
      */
     private void setToggle(final boolean value) {
         boolean decPrivateModeFlag = false;
@@ -2441,6 +2733,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;
 
@@ -3187,9 +3492,41 @@ public class ECMA48 implements Runnable {
                 currentState.attr.setForeColor(Color.WHITE);
                 break;
             case 38:
-                // Underscore on, default foreground color
-                currentState.attr.setUnderline(true);
-                currentState.attr.setForeColor(Color.WHITE);
+                if (type == DeviceType.XTERM) {
+                    /*
+                     * Xterm supports T.416 / ISO-8613-3 codes to select
+                     * either an indexed color or an RGB value.  (It also
+                     * permits these ISO-8613-3 SGR sequences to be separated
+                     * by colons rather than semicolons.)
+                     *
+                     * We will not support any of these additional color
+                     * codes at this time:
+                     *
+                     * 1. http://invisible-island.net/ncurses/ncurses.faq.html#xterm_16MegaColors
+                     *    has a detailed discussion of the current state of
+                     *    RGB in various terminals, the point of which is
+                     *    that none of them really do the same thing despite
+                     *    all appearing to be "xterm".
+                     *
+                     * 2. As seen in
+                     *    https://bugs.kde.org/show_bug.cgi?id=107487#c3,
+                     *    even supporting just the "indexed mode" of these
+                     *    sequences (which could align easily with existing
+                     *    SGR colors) is assumed to mean full support of
+                     *    24-bit RGB.  So it is all or nothing.
+                     *
+                     * Finally, these sequences break the assumptions of
+                     * standard ECMA-48 style parsers as pointed out at
+                     * https://bugs.kde.org/show_bug.cgi?id=107487#c11 .
+                     * Therefore in order to keep a clean display, we cannot
+                     * parse anything else in this sequence.
+                     */
+                    return;
+                } else {
+                    // Underscore on, default foreground color
+                    currentState.attr.setUnderline(true);
+                    currentState.attr.setForeColor(Color.WHITE);
+                }
                 break;
             case 39:
                 // Underscore off, default foreground color
@@ -3228,6 +3565,21 @@ public class ECMA48 implements Runnable {
                 // Set white background
                 currentState.attr.setBackColor(Color.WHITE);
                 break;
+            case 48:
+                if (type == DeviceType.XTERM) {
+                    /*
+                     * Xterm supports T.416 / ISO-8613-3 codes to select
+                     * either an indexed color or an RGB value.  (It also
+                     * permits these ISO-8613-3 SGR sequences to be separated
+                     * by colons rather than semicolons.)
+                     *
+                     * We will not support this at this time.  Also, in order
+                     * to keep a clean display, we cannot parse anything else
+                     * in this sequence.
+                     */
+                    return;
+                }
+                break;
             case 49:
                 // Default background
                 currentState.attr.setBackColor(Color.BLACK);
@@ -3406,6 +3758,7 @@ public class ECMA48 implements Runnable {
      */
     private void dsr() {
         boolean decPrivateModeFlag = false;
+        int row = currentState.cursorY;
 
         for (int i = 0; i < collectBuffer.length(); i++) {
             if (collectBuffer.charAt(i) == '?') {
@@ -3433,15 +3786,18 @@ public class ECMA48 implements Runnable {
 
         case 6:
             // Request cursor position.  Respond with current position.
+            if (currentState.originMode == true) {
+                row -= scrollRegionTop;
+            }
             String str = "";
             if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
                 && (s8c1t == true)
             ) {
-                str = String.format("\u009b%d;%dR",
-                    currentState.cursorY + 1, currentState.cursorX + 1);
+                str = String.format("\u009b%d;%dR", row + 1,
+                    currentState.cursorX + 1);
             } else {
-                str = String.format("\033[%d;%dR",
-                    currentState.cursorY + 1, currentState.cursorX + 1);
+                str = String.format("\033[%d;%dR", row + 1,
+                    currentState.cursorX + 1);
             }
 
             // Send string directly to remote side
@@ -5463,8 +5819,8 @@ public class ECMA48 implements Runnable {
                 collect(ch);
             }
             if (ch == 0x5C) {
-                if ((collectBuffer.length() > 0) &&
-                    (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
+                if ((collectBuffer.length() > 0)
+                    && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
                 ) {
                     toGround();
                 }
@@ -5495,8 +5851,8 @@ public class ECMA48 implements Runnable {
                 collect(ch);
             }
             if (ch == 0x5C) {
-                if ((collectBuffer.length() > 0) &&
-                    (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
+                if ((collectBuffer.length() > 0)
+                    && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
                 ) {
                     toGround();
                 }
@@ -5648,7 +6004,7 @@ public class ECMA48 implements Runnable {
     /**
      * Read function runs on a separate thread.
      */
-    public void run() {
+    public final void run() {
         boolean utf8 = false;
         boolean done = false;