diff --git a/terminal-emulator/src/main/java/com/termux/terminal/TerminalEmulator.java b/terminal-emulator/src/main/java/com/termux/terminal/TerminalEmulator.java index cbbccbb4f2..14fb84865a 100644 --- a/terminal-emulator/src/main/java/com/termux/terminal/TerminalEmulator.java +++ b/terminal-emulator/src/main/java/com/termux/terminal/TerminalEmulator.java @@ -79,6 +79,10 @@ public final class TerminalEmulator { private static final int ESC_CSI_SINGLE_QUOTE = 18; /** Escape processing: CSI ! */ private static final int ESC_CSI_EXCLAMATION = 19; + /** Escape processing: "ESC _" or Application Program Command (APC). */ + private static final int ESC_APC = 20; + /** Escape processing: "ESC _" or Application Program Command (APC), followed by Escape. */ + private static final int ESC_APC_ESCAPE = 21; /** The number of parameter arguments. This name comes from the ANSI standard for terminal escape codes. */ private static final int MAX_ESCAPE_PARAMETERS = 16; @@ -548,6 +552,15 @@ private void processByte(byte byteToProcess) { } public void processCodePoint(int b) { + // The Application Program-Control (APC) string might be arbitrary non-printable characters, so handle that early. + if (mEscapeState == ESC_APC) { + doApc(b); + return; + } else if (mEscapeState == ESC_APC_ESCAPE) { + doApcEscape(b); + return; + } + switch (b) { case 0: // Null character (NUL, ^@). Do nothing. break; @@ -1004,6 +1017,30 @@ private void doDeviceControl(int b) { } } + /** + * When in {@link #ESC_APC} (APC, Application Program Command) sequence. + */ + private void doApc(int b) { + if (b == 27) { + continueSequence(ESC_APC_ESCAPE); + } + // Eat APC sequences silently for now. + } + + /** + * When in {@link #ESC_APC} (APC, Application Program Command) sequence. + */ + private void doApcEscape(int b) { + if (b == '\\') { + // A String Terminator (ST), ending the APC escape sequence. + finishSequence(); + } else { + // The Escape character was not the start of a String Terminator (ST), + // but instead just data inside of the APC escape sequence. + continueSequence(ESC_APC); + } + } + private int nextTabStop(int numTabs) { for (int i = mCursorCol + 1; i < mColumns; i++) if (mTabStop[i] && --numTabs == 0) return Math.min(i, mRightMargin); @@ -1399,6 +1436,9 @@ private void doEsc(int b) { case '>': // DECKPNM setDecsetinternalBit(DECSET_BIT_APPLICATION_KEYPAD, false); break; + case '_': // APC - Application Program Command. + continueSequence(ESC_APC); + break; default: unknownSequence(b); break; diff --git a/terminal-emulator/src/test/java/com/termux/terminal/ApcTest.java b/terminal-emulator/src/test/java/com/termux/terminal/ApcTest.java new file mode 100644 index 0000000000..4f6292aaa9 --- /dev/null +++ b/terminal-emulator/src/test/java/com/termux/terminal/ApcTest.java @@ -0,0 +1,21 @@ +package com.termux.terminal; + +public class ApcTest extends TerminalTestCase { + + public void testApcConsumed() { + // At time of writing this is part of what yazi sends for probing for kitty graphics protocol support: + // https://github.com/sxyazi/yazi/blob/0cdaff98d0b3723caff63eebf1974e7907a43a2c/yazi-adapter/src/emulator.rs#L129 + // This should not result in anything being written to the screen: If kitty graphics protocol support + // is implemented it should instead result in an error code on stdin, and if not it should be consumed + // silently just as xterm does. See https://sw.kovidgoyal.net/kitty/graphics-protocol/. + withTerminalSized(2, 2) + .enterString("\033_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\033\\") + .assertLinesAre(" ", " "); + + // It is ok for the APC content to be non printable characters: + withTerminalSized(12, 2) + .enterString("hello \033_some\023\033_\\apc#end\033\\ world") + .assertLinesAre("hello world", " "); + } + +}