From 32437017f5e13c3668fe12328364ed3cd8eac8eb Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Tue, 31 Jan 2017 12:46:23 -0500 Subject: [PATCH] Support for Xterm ctrl/alt/shift function keys --- src/jexer/TKeypress.java | 17 ++ src/jexer/TWindow.java | 4 +- src/jexer/io/ECMA48Terminal.java | 190 +++++++--------- src/jexer/tterminal/ECMA48.java | 376 +++++++++++++++++++++++++------ 4 files changed, 403 insertions(+), 184 deletions(-) diff --git a/src/jexer/TKeypress.java b/src/jexer/TKeypress.java index 5d1eabc..5e551d8 100644 --- a/src/jexer/TKeypress.java +++ b/src/jexer/TKeypress.java @@ -296,6 +296,23 @@ public final class TKeypress { && (shift == that.shift)); } + /** + * Comparison check, omitting the ctrl/alt/shift flags. + * + * @param rhs another TKeypress instance + * @return true if all fields (except for ctrl/alt/shift) are equal + */ + public boolean equalsWithoutModifiers(final Object rhs) { + if (!(rhs instanceof TKeypress)) { + return false; + } + + TKeypress that = (TKeypress) rhs; + return ((isFunctionKey == that.isFunctionKey) + && (keyCode == that.keyCode) + && (ch == that.ch)); + } + /** * Hashcode uses all fields in equals(). * diff --git a/src/jexer/TWindow.java b/src/jexer/TWindow.java index 10f4e59..26bd8c4 100644 --- a/src/jexer/TWindow.java +++ b/src/jexer/TWindow.java @@ -736,8 +736,8 @@ public class TWindow extends TWidget { if (inKeyboardResize) { - // ESC - Exit size/move - if (keypress.equals(kbEsc)) { + // ESC or ENTER - Exit size/move + if (keypress.equals(kbEsc) || keypress.equals(kbEnter)) { inKeyboardResize = false; } diff --git a/src/jexer/io/ECMA48Terminal.java b/src/jexer/io/ECMA48Terminal.java index 2af3a3e..069c143 100644 --- a/src/jexer/io/ECMA48Terminal.java +++ b/src/jexer/io/ECMA48Terminal.java @@ -104,7 +104,6 @@ public final class ECMA48Terminal implements Runnable { ESCAPE_INTERMEDIATE, CSI_ENTRY, CSI_PARAM, - // CSI_INTERMEDIATE, MOUSE, MOUSE_SGR, } @@ -442,36 +441,16 @@ public final class ECMA48Terminal implements Runnable { */ private TInputEvent csiFnKey() { int key = 0; - int modifier = 0; if (params.size() > 0) { key = Integer.parseInt(params.get(0)); } - if (params.size() > 1) { - modifier = Integer.parseInt(params.get(1)); - } boolean alt = false; boolean ctrl = false; boolean shift = false; - - switch (modifier) { - case 0: - // No modifier - break; - case 2: - // Shift - shift = true; - break; - case 3: - // Alt - alt = true; - break; - case 5: - // Ctrl - ctrl = true; - break; - default: - // Unknown modifier, bail out - return null; + if (params.size() > 1) { + shift = csiIsShift(params.get(1)); + alt = csiIsAlt(params.get(1)); + ctrl = csiIsCtrl(params.get(1)); } switch (key) { @@ -777,6 +756,51 @@ public final class ECMA48Terminal implements Runnable { } } + /** + * Returns true if the CSI parameter for a keyboard command means that + * shift was down. + */ + private boolean csiIsShift(final String x) { + if ((x.equals("2")) + || (x.equals("4")) + || (x.equals("6")) + || (x.equals("8")) + ) { + return true; + } + return false; + } + + /** + * Returns true if the CSI parameter for a keyboard command means that + * alt was down. + */ + private boolean csiIsAlt(final String x) { + if ((x.equals("3")) + || (x.equals("4")) + || (x.equals("7")) + || (x.equals("8")) + ) { + return true; + } + return false; + } + + /** + * Returns true if the CSI parameter for a keyboard command means that + * ctrl was down. + */ + private boolean csiIsCtrl(final String x) { + if ((x.equals("5")) + || (x.equals("6")) + || (x.equals("7")) + || (x.equals("8")) + ) { + return true; + } + return false; + } + /** * Parses the next character of input to see if an InputEvent is * fully here. @@ -904,65 +928,21 @@ public final class ECMA48Terminal implements Runnable { switch (ch) { case 'A': // Up - if (params.size() > 1) { - if (params.get(1).equals("2")) { - shift = true; - } - if (params.get(1).equals("5")) { - ctrl = true; - } - if (params.get(1).equals("3")) { - alt = true; - } - } events.add(new TKeypressEvent(kbUp, alt, ctrl, shift)); reset(); return; case 'B': // Down - if (params.size() > 1) { - if (params.get(1).equals("2")) { - shift = true; - } - if (params.get(1).equals("5")) { - ctrl = true; - } - if (params.get(1).equals("3")) { - alt = true; - } - } events.add(new TKeypressEvent(kbDown, alt, ctrl, shift)); reset(); return; case 'C': // Right - if (params.size() > 1) { - if (params.get(1).equals("2")) { - shift = true; - } - if (params.get(1).equals("5")) { - ctrl = true; - } - if (params.get(1).equals("3")) { - alt = true; - } - } events.add(new TKeypressEvent(kbRight, alt, ctrl, shift)); reset(); return; case 'D': // Left - if (params.size() > 1) { - if (params.get(1).equals("2")) { - shift = true; - } - if (params.get(1).equals("5")) { - ctrl = true; - } - if (params.get(1).equals("3")) { - alt = true; - } - } events.add(new TKeypressEvent(kbLeft, alt, ctrl, shift)); reset(); return; @@ -1061,15 +1041,9 @@ public final class ECMA48Terminal implements Runnable { case 'A': // Up if (params.size() > 1) { - if (params.get(1).equals("2")) { - shift = true; - } - if (params.get(1).equals("5")) { - ctrl = true; - } - if (params.get(1).equals("3")) { - alt = true; - } + shift = csiIsShift(params.get(1)); + alt = csiIsAlt(params.get(1)); + ctrl = csiIsCtrl(params.get(1)); } events.add(new TKeypressEvent(kbUp, alt, ctrl, shift)); reset(); @@ -1077,15 +1051,9 @@ public final class ECMA48Terminal implements Runnable { case 'B': // Down if (params.size() > 1) { - if (params.get(1).equals("2")) { - shift = true; - } - if (params.get(1).equals("5")) { - ctrl = true; - } - if (params.get(1).equals("3")) { - alt = true; - } + shift = csiIsShift(params.get(1)); + alt = csiIsAlt(params.get(1)); + ctrl = csiIsCtrl(params.get(1)); } events.add(new TKeypressEvent(kbDown, alt, ctrl, shift)); reset(); @@ -1093,15 +1061,9 @@ public final class ECMA48Terminal implements Runnable { case 'C': // Right if (params.size() > 1) { - if (params.get(1).equals("2")) { - shift = true; - } - if (params.get(1).equals("5")) { - ctrl = true; - } - if (params.get(1).equals("3")) { - alt = true; - } + shift = csiIsShift(params.get(1)); + alt = csiIsAlt(params.get(1)); + ctrl = csiIsCtrl(params.get(1)); } events.add(new TKeypressEvent(kbRight, alt, ctrl, shift)); reset(); @@ -1109,19 +1071,33 @@ public final class ECMA48Terminal implements Runnable { case 'D': // Left if (params.size() > 1) { - if (params.get(1).equals("2")) { - shift = true; - } - if (params.get(1).equals("5")) { - ctrl = true; - } - if (params.get(1).equals("3")) { - alt = true; - } + shift = csiIsShift(params.get(1)); + alt = csiIsAlt(params.get(1)); + ctrl = csiIsCtrl(params.get(1)); } events.add(new TKeypressEvent(kbLeft, alt, ctrl, shift)); reset(); return; + case 'H': + // Home + if (params.size() > 1) { + shift = csiIsShift(params.get(1)); + alt = csiIsAlt(params.get(1)); + ctrl = csiIsCtrl(params.get(1)); + } + events.add(new TKeypressEvent(kbHome, alt, ctrl, shift)); + reset(); + return; + case 'F': + // End + if (params.size() > 1) { + shift = csiIsShift(params.get(1)); + alt = csiIsAlt(params.get(1)); + ctrl = csiIsCtrl(params.get(1)); + } + events.add(new TKeypressEvent(kbEnd, alt, ctrl, shift)); + reset(); + return; default: break; } @@ -1404,9 +1380,9 @@ public final class ECMA48Terminal implements Runnable { */ private String mouse(final boolean on) { if (on) { - return "\033[?1003;1005;1006h\033[?1049h"; + return "\033[?1002;1003;1005;1006h\033[?1049h"; } - return "\033[?1003;1006;1005l\033[?1049l"; + return "\033[?1002;1003;1006;1005l\033[?1049l"; } /** diff --git a/src/jexer/tterminal/ECMA48.java b/src/jexer/tterminal/ECMA48.java index f088f6a..97c9b41 100644 --- a/src/jexer/tterminal/ECMA48.java +++ b/src/jexer/tterminal/ECMA48.java @@ -1255,6 +1255,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. * @@ -1311,69 +1351,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"; + } } } @@ -1500,6 +1648,9 @@ public class ECMA48 implements Runnable { if (vt52Mode) { return "\0332P"; } + if (type == DeviceType.XTERM) { + return "\0331;2P"; + } return "\033O2P"; } @@ -1508,6 +1659,9 @@ public class ECMA48 implements Runnable { if (vt52Mode) { return "\0332Q"; } + if (type == DeviceType.XTERM) { + return "\0331;2Q"; + } return "\033O2Q"; } @@ -1516,6 +1670,9 @@ public class ECMA48 implements Runnable { if (vt52Mode) { return "\0332R"; } + if (type == DeviceType.XTERM) { + return "\0331;2R"; + } return "\033O2R"; } @@ -1524,6 +1681,9 @@ public class ECMA48 implements Runnable { if (vt52Mode) { return "\0332S"; } + if (type == DeviceType.XTERM) { + return "\0331;2S"; + } return "\033O2S"; } @@ -1572,6 +1732,9 @@ public class ECMA48 implements Runnable { if (vt52Mode) { return "\0335P"; } + if (type == DeviceType.XTERM) { + return "\0331;5P"; + } return "\033O5P"; } @@ -1580,6 +1743,9 @@ public class ECMA48 implements Runnable { if (vt52Mode) { return "\0335Q"; } + if (type == DeviceType.XTERM) { + return "\0331;5Q"; + } return "\033O5Q"; } @@ -1588,6 +1754,9 @@ public class ECMA48 implements Runnable { if (vt52Mode) { return "\0335R"; } + if (type == DeviceType.XTERM) { + return "\0331;5R"; + } return "\033O5R"; } @@ -1596,6 +1765,9 @@ public class ECMA48 implements Runnable { if (vt52Mode) { return "\0335S"; } + if (type == DeviceType.XTERM) { + return "\0331;5S"; + } return "\033O5S"; } @@ -1639,39 +1811,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)) { -- 2.27.0